[TOC]

# Zephyr Shimming How-To

## Objective

Allow a subset of the platform/ec code to be built as part of the Zephyr-based
EC without needing to land code into upstream zephyr, or our zephyr-chrome
repository.

## Background

Now that Google has joined [Zephyr OS](https://www.zephyrproject.org/), the EC
team is moving toward it instead of platform/ec code on embedded controllers for
future Chrome OS devices. See the
[originally proposed idea](https://goto.google.com/cros-ec-rtos) and a more
specific [Zephyr process doc](https://goto.google.com/zephyr-structure) of what
future development on Zephyr will look like.

Simply put, we want to move to Zephyr OS to use an open-source embedded OS that
has a vibrant community. The embedded OS scene for the industry is very
fragmented, with various parties using their own custom solution. We see the
strong open-source community at Zephyr as potentially helping to consolidate
efforts in the embedded controller space. It will also benefit our partners
(both chip vendors and OEMs) since they should only have to support one embedded
OS (i.e., Zephyr) for both their Chrome OS and Windows based devices.

Migrating to use Zephyr fully is going to take a few years. We do not want to
diverge from the active development happening on platform/ec code. We
potentially want to ship a product using Zephyr before the migration is
complete.

## Design ideas

In order to reuse `platform/ec` development , we shim "active" `platform/ec`
code as a
[Zephyr Module](https://docs.zephyrproject.org/latest/guides/modules.html). This
requires us to add some Zephyr specific code in a `zephyr` directory in the
`platform/ec` codebase. Once we release a Zephyr-based EC, then we can turn down
platform/ec for future development and work on migrating the platform/ec-module
code out of the module directory and into a first-class Zephyr code format -- in
the local
[Chrome Zephyr repo](https://chromium.googlesource.com/chromiumos/platform/zephyr-chrome/+/HEAD)
and ultimately [upstream](https://github.com/zephyrproject-rtos/zephyr).

For platform/ec code that is stable and not under active development, the Zephyr
team may port that code to Zephyr, thus skipping the shimming process.

### Subsystems of interest

#### With Shim

We shim the following subsystems (non-exhaustive).

*   USB-C: TCPC, PPC, MUX, TCPMv2
*   Charge Manager
*   SoC Power Sequencing
*   Sensors, if Intel’s HID-based solution is delayed in getting to Zephyr
    upstream

#### Little-to-No Shim

We adopt upstream Zephyr or skip the shimming process (non-exhaustive).

*   CBI and dependent EEPROM code
    *   The format is stable. We pull in the list of CBI tags from platform/ec
        though
*   EFS2, Vboot, RO/RW split
    *   Adjusting flash layout would be difficult to shim, and the concept is
        very stable.
    *   We may shim some core EFS2 logic
*   Host command framework
*   Sensors, if Intel’s HID-based solution getts to Zephyr upstream and passes
    CTS
*   Keyboard and keycode scanning support
    *   We may shim the newer Vivaldi feature.
*   Console support
    *   We allow individual console commands via DECLARE\_CONSOLE\_COMMAND to be
        shimmed to Zephyr. These convert commands to work with Zephyr's shell
        subsystem.
*   I2C

### New content in platform/ec

Add the `src/platform/ec/zephyr` folder with:

*   [Module integration files](https://docs.zephyrproject.org/latest/guides/modules.html#build-system-integration),
    e.g., module.yml, CMakeLists.txt, and KConfig.
    *   **module.yml** is the required entry point (must be located at
        _zephyr/module.yml_ in the repository) for Zephyr modules, and declares
        the location of Kconfig and CMakeLists.txt files used by the Zephyr
        build system.
    *   **CMakeLists.txt** replicates build logic for the files being shimmed,
        outside of the platform/ec Makefile.
    *   **Kconfig** will declare any CONFIG\_\* options which are important to
        expose from platform/ec code to the Zephyr build.
*   Shim code to translate platform/ec code into Zephyr code
    *   For example, redefine platform/ec’s
        [`DECLARE_HOST_COMMAND`](https://source.chromium.org/chromiumos/chromiumos/codesearch/+/HEAD:src/platform/ec/include/host_command.h;l=256;drc=514923bc59f5a3435dbb7cbf348735ed41889ffe)
        to map to Zephyr's upstream
        [`EC_HOST_CMD_HANDLER`](https://github.com/zephyrproject-rtos/zephyr/blob/d7468bf836b75c29980441f294a61eae6bf4bc75/include/ec_host_cmd.h#L73)
        macro. This allows us to compile select platform/ec files in the Zephyr
        build.

### Namespace Collisions

One significant issue of mixing Zephyr headers with our existing EC code is that
we currently have many names colliding with the Zephyr code. For example,
Zephyr's atomic functions also are named `atomic_add`, `atomic_or`, ...,
however, have a different API from our EC's atomic functions. This is critical,
since atomic operations are often used in `static inline` functions placed in
header files.

In some cases, we are able to hack around these collisions by creating macros
and functions which are compatible with both Zephyr and our EC's usages. For
example, we can create a modified `IS_ENABLED` which accepts both defined to
nothing usages (CrOS EC `config.h` style), and defined to `1` usages (Zephyr
Kconfig style).

However, long term, we may find this to be a continual cause of issues, and
creating hacks for every colliding macro or function may be unsustainable. We
propose _gradually_ introducing a namespace prefix to the `platform/ec`
codebase, for example `crec_`. We can begin at the critical areas of namespace
collision (e.g., atomics) and continue to improve the naming convention with
time.

### New CQ check

As long as code from platform/ec is part of the zephyr
[ebuild](http://cs/chromeos_public/src/third_party/chromiumos-overlay/chromeos-base/chromeos-zephyr-2_3/chromeos-zephyr-2_3-9999.ebuild),
then we need to run the Zephyr CQ checks on any platform/ec CLs in addition to
the normal platform/ec CQ checks. This ensures that platform/ec changes aren’t
breaking the Zephyr builds and requiring the Zephyr team to debug recent
changes.

For local builds, we can run `emerge-betty chromeos-zephyr-2_3` or `zmake`
utility to check that an EC CL has not broken anything on the Zephyr side.

We will work with the CI team to enable this.

## How to shim features

Before you get started shimming a feature, it's important to
understand the general philosophies behind Zephyr OS and shimming:

* Our current EC's OS isn't going away any time soon. Even after we
  ship our first device with a Zephyr-based EC, we may still be working on
  other projects using the old OS.  It's important to consider how
  your feature will apply to both the Zephyr world and CrOS EC OS
  world.

* We won't be converting old devices to use Zephyr-based firmware.
  This means that our existing OS and its code will need maintained
  for bug and security fixes for years to come.  **Do not allow the
  code you write for the CrOS EC OS to lack in quality or be "throw
  away code" as it will need to be maintained for a long time.**

* Shimming, by the very nature of the design, will lead to some ugly
  hacks.  We try and avoid this when we can, but some of them may be
  completely unavoidable.  This means we need to actively work against
  nature to keep the code clean.  If we do things right, there's even
  a possibility that we leave things cleaner than we found them.

* Shimming occasionally digs up landmines.  Be prepared to step on
  them. 💣

### What code can be shimmed?

Code in the `common/` directory (and other common code directories,
like `power/`) is the ideal target for shimming, with the exception of
core OS features which have Zephyr OS equivalents.

Code in the following directories should **never be shimmed**:

- `core/`: this directory contains architecture-specific code which
  should have a Zephyr OS equivalent feature.

- `chip/`: this directory contains chip-specific code, and similarly
  should have a Zephyr OS equivalent feature.

In both cases, you should instead determine (or, in rare cases,
implement upstream) the equivalent Zephyr OS feature, and *implement
an architecture and chip agnostic* "shim layer" in the `zephyr/shim/`
directory which translates the APIs as necessary.

As of the time of this document, the shim layer is nearing 100%
complete, and it should be rare that you encounter an API which needs
translation.

Finally, code in the following directories should **avoid being
shimmed, if possible**:

- `board/`: this directory contains variant-specific code.

- `baseboard/`: this directory contains baseboard-specific code.

In both cases, the only value in shimming in code from one of those
directories would be to enable a Zephyr OS build for a device which
already has CrOS EC OS support, as *Zephyr-only projects will not have
these directories*.  You should be thinking about how this would be
implemented for a Zephyr-only project, and filing bugs to create the
appropriate device-tree and Kconfig equivalents before shimming this
code.

See [Zephyr PoC device bringup](zephyr_poc_device_bringup.md) for more
information about bringing up proof-of-concept devices.

### Configuration

CrOS EC OS uses a special header `config.h`, which sets configuration
defaults, and includes board and chip specific configurations by
expecting the headers `board.h` and `config_chip.h` to be present.
Most of these configuration options start with `CONFIG_`, however the
rules were loosely defined over the years.

Zephyr OS, on the other hand, uses two different configuration
systems:

* Kconfig, the configuration system from the Linux Kernel, which
  fits well within the domain of preprocessor definitions in C.  The
  schema for our Kconfig files can be found under `zephyr/Kconfig`,
  and project-specific configurations are made in `prj.conf` files.

  Kconfig is generally used to select which EC software features are
  enabled, and should be avoided for hardware configurations, such as
  chip configuration registers and their default settings.

* Open Firmware Device Tree, which you may also be familiar with from
  the Linux kernel.  This configuration can be found in `*.dts` files.

  Device-tree is generally used for hardware configurations, and
  should be avoided for EC software feature configuration.

For code which is shimmed, we need to play nicely with both the CrOS
EC OS configuration system, and Zephyr's configuration systems.  Thus,
we follow the following pattern:

* EC software features are configured using `Kconfig` and
  `zephyr/shim/include/config_chip.h` translates them into the
  appropriate CrOS EC OS configurations using patterns such as below:

  ```c
  #undef CONFIG_CMD_GETTIME
  #ifdef CONFIG_PLATFORM_EC_TIMER_CMD_GETTIME
  #define CONFIG_CMD_GETTIME
  #endif
  ```

  The preprocessor code should follow that template exactly, and not
  use any nesting (Kconfig handles dependencies, there is no reason to
  do it again in the preprocessor code).

* **The domain of Kconfig options and CrOS EC configuration names
  should be completely distinct.**  This is because the Kconfig options
  are included automatically, and including `config.h` may undefine
  them.  To mitigate this, we follow a convention of using
  `CONFIG_PLATFORM_EC_` as the prefix for EC software features in
  Kconfig.

One special configuration option exists, `CONFIG_ZEPHYR`, which you
can use to detect whether the OS is Zephyr OS.  This is the
conventional way to add Zephyr-specific (or excluded) code in CrOS EC
code.

The typical EC macros for reducing `#ifdef` messes (e.g.,
`IS_ENABLED`, `STATIC_IF`, etc.) work with both CrOS EC OS and Kconfig
options, and should be used when possible.

### Header Files

Besides the include paths provided by Zephyr OS, the following paths
are additionally added for shimmed code:

* `include/`
* `zephyr/include/`
* `zephyr/shim/include/`

The names of headers in these directories should be completely
distinct.  C compilers have no mechanism for "include ordering", and
there is no way to "override a header".

If you feel the need to "override" a header, say `foo.h` in
`include/`, the best way to do this is to give it a different name
under `zephyr/shim/include` (e.g., `zephyr_foo_shim.h`), and include
that in the `foo.h` header with a `#ifdef CONFIG_ZEPHYR` guard.

The typical styling convention for includes (following existing
conventions in `platform/ec` and other C codebases we have) is:

* Zephyr OS headers in pointy brackets, in alphabetical order.

* One blank line

* CrOS EC OS headers (either from `include/`, `zephyr/shim/include/`,
  or the current directory), in quotes (not pointy brackets).

### Adding files to Cmake

Zephyr's build system (including shimmed code) uses CMake instead of
`Makefiles`, and your code will not be compiled for Zephyr unless you
list the files in `zephyr/CMakeLists.txt`.

### Step-by-step guide to adding a Kconfig

Follow these steps:

1. Make sure you have read the above Configuration section

2. Add your config to one of zephyr/Kconfig* files. Note the PLATFORM_EC_ prefix
   and try to put it near related things:

  ```kconfig
   config PLATFORM_EC_CHARGER_BQ25720
     bool "TI BQ25720 charger"
     help
       The TI BQ25720 is a blah blah (describe summary from datasheet,
       at least 3 lines so take 10 minutes to write something truly useful)
  ```

   Consider a `depends on PLATFORM_EC_...` line if it depends on an existing
   feature.

3. Add to zephyr/shim/include/config_chip.h (put it at the bottom):

  ```kconfig
   #undef CONFIG_CHARGER_BQ25720
   #ifdef CONFIG_PLATFORM_EC_CHARGER_BQ25720
   #define CONFIG_CHARGER_BQ25720
   #endif
  ```

4. Add the source file to zephyr/CMakeLists.txt if it is not already there. For
   ordering check the comments in that file:

   `zephyr_sources_ifdef(CONFIG_PLATFORM_EC_CHARGER_BQ25720
                                     "${PLATFORM_EC}/driver/charger/bq25720.c")`

5. Run a build on a board that enables the new CONFIG (in config.h) to make sure
   there are no problems.

6. If it doesn't work, please email zephyr-task-force@ or file a bug and assign
   it to sjg@, cc zephyr-task-force@ (please include CL link and the error
   output).

### Unit Tests

Unit tests, implemented using the Ztest framework, can be found in
`zephyr/test`.

To run all unit tests, you use `zmake testall`.

## Alternatives Considered

### Translate code and mirror into the zephyr-chrome repository

We could potentially write a script which, via a series of find/replace
operations, translates a platform/ec module to use Zephyr functions, macros, and
paradigms. On a frequent basis, we would translate all modules of interest in
the platform/ec repository and land an "uprev" change in the zephyr-chrome
repository.

The main disadvantage of this technique is that we can't get any CQ coverage
when platform/ec CLs land that the modules will continue to work in Zephyr.
Additionally, the translator script would be delicate and probably require
frequent maintenance.

However, this technique does have some benefits. With modules directly
translated to code in the Zephyr paradigm, the process of upstreaming a shimmed
module to ZephyrOS would be significantly easier. Additionally, it would require
no shim code in platform/ec.

### Don't do any code sharing

One option is to avoid shimming in any platform/ec code and allow the Zephyr
team to re-implement features in upstream zephyr, or our local zephyr-chrome
repository.

Disregarding the infeasible amount of work required to complete this option, the
platform/ec repository has a far faster development pace as there are many more
contributors, and the Zephyr features would quickly lose parity during the time
frame that we are launching both Zephyr-based and platform/ec-based devices.
