This directory contains the Docker and scripting configuration to set up an isolated, automated build and test environment for building ChromiumOS EC firmware and running tests on the Dagwood test fixture.
The docker build is set up to clone all repositories needed to build EC firmware images and run the twister based tests.
When entering the container, entrypoint.sh populates /workspace from the repositories cached in the image on first run and leaves existing checkouts untouched on subsequent entries. Pass --update to pull the latest upstream changes.
The workspace directory structure matches standard expectations of ChromiumOS zmake module lookups, using a src/ nested repository layout:
Dockerfile: Builds the optimized Ubuntu-based environment with compiler pre-requisites.entrypoint.sh: Runs on container startup to populate/update checkouts, initialize python virtual environments, auto-install .vpython3 dependencies, and export Coreboot SDK environment variables dynamically.workspace/: Bind mount source for the container. Tracked, but empty, so that a fresh checkout owns it; everything below it is ignored.workspace/src/platform/ec/: The Chromium OS EC firmware source.workspace/src/platform/dagwood/: The Dagwood test/verification tool.workspace/src/third_party/zephyrproject/: Zephyr RTOS project dependencies.workspace/src/third_party/pigweed/: Google's Pigweed libraries.workspace/src/third_party/u-boot/: U-Boot tools repository (contains binman python signing packages).workspace/src/third_party/chromiumos-overlay/: A sparse-checkout containing eclass files needed to resolve SDK dependencies.workspace/.cache/coreboot-sdk/: Persistent cache folder on the host storing the downloaded cross-compilation toolchains.workspace/.venv/: The Python virtual environment inside the container.Ensure you have Docker installed and running on your workstation.
Run the following command inside this directory to build the image:
docker build -t ec-builder .
To spin up the container, map your persistent local workspace, and drop into a bash shell with all dependencies ready:
docker run -it --rm \ -v $(pwd)/workspace:/workspace \ ec-builder
Or using the helper script:
./run_docker.sh
The container runs as the owner of
workspace/. That directory is part of the checkout, so it belongs to whoever cloned the tree, and build artifacts land on the host owned by them. Do not delete it: Docker recreates a missing bind mount source asroot:root, leaving the container no way to tell who started it, so it runs everything as root instead. To recover, runsudo chown -R "$(id -u):$(id -g)" workspace.
Updating Repositories: Container startup skips remote git checks by default so existing checkouts launch immediately and local state is left untouched. Pass
--updateto pull the latest upstream changes across all workspace repositories:./run_docker.sh --update
The plain docker run above is enough for building. Flashing and device testing additionally need the host's USB devices and serial ports, which run_docker.sh configures automatically. To do the same from a raw docker run, add:
docker run -it --rm \ --device-cgroup-rule="c *:* rmw" \ -v $(pwd)/workspace:/workspace \ -v /dev:/dev \ ec-builder
Bind mounting /dev keeps devices visible across hotplug and re-enumeration, so a board that resets into its bootloader and returns under a different ttyACM number stays usable. The cgroup rule is required because Docker denies open() on devices outside a small default allowlist; limiting it to character devices leaves block devices (disks) inaccessible. --privileged is not needed.
Once you have entered the container, the virtual environment is automatically activated and environment variables are exported globally.
zmake is workingzmake --help
zmake BuildsTo build a specific board configuration (e.g., skyrim):
zmake --checkout /workspace build skyrim
Because zmake is not running inside a full cros_sdk checkout, you must always specify the --checkout /workspace option when running zmake commands.
The final firmware image will be populated on your host filesystem at workspace/src/platform/ec/build/zephyr/skyrim/output/ec.bin.
To run host-based emulation tests using Zephyr's Twister inside the container:
cd /workspace/src/platform/ec
python3 ./twister (prefixing with python3 is required to bypass the vpython3 shebang):python3 ./twister -ivc -s hibernate_z5.default
To run tests against a Dagwood board and EC Add-in-card (AIC) connected to the host, you can use the run_dagwood_tests.py helper script. It automatically handles device forwarding and configures twister with the required parameters (toolchain, flash command, etc.).
Test results are available on your host filesystem at workspace/src/platform/ec/twister-out/.
For Realtek boards, the container automatically builds and pre-installs the rtkupdate utility and the rts5915_flash_upload.bin monitor binary on startup.
To run all EC-AIC tests on realtek/rts5912:
./run_dagwood_tests.py -p realtek/rts5912
For Nuvoton boards, the container automatically builds and pre-installs the uartupdatetool utility and the npcx_monitor.bin monitor binary on startup.
To run all EC-AIC tests on npcx9/npcx9m7f:
./run_dagwood_tests.py -p npcx9/npcx9m7f
You can override the defaults using the script options:
./run_dagwood_tests.py -p realtek/rts5912 -T zephyr/test/ec-aic -T zephyr/test/another-test
./run_dagwood_tests.py -p realtek/rts5912 -s aic.i2c -s another.scenario
./run_dagwood_tests.py -p realtek/rts5912 -d /dev/ttyACM0
For help on options:
./run_dagwood_tests.py -h
You can trigger builds or run tests directly from your host machine:
For builds:
docker run --rm \ -v $(pwd)/workspace:/workspace \ ec-builder zmake --checkout /workspace build skyrim
For Twister tests:
docker run --rm \ -v $(pwd)/workspace:/workspace \ ec-builder bash -c "cd /workspace/src/platform/ec && python3 ./twister -ivc -s hibernate_z5.default"
The container includes openocd and the hal_stm32 Zephyr module to build and flash the Dagwood fixture MCU firmware directly using an attached ST-Link debugger:
Inside the container:
cd /workspace/src/platform/dagwood # Build and flash via OpenOCD & ST-Link: ./build_from_chroot.py -f # Or flash existing build without rebuilding: ./build_from_chroot.py --flash-only
Direct from host via run_docker.sh:
./run_docker.sh bash -c "cd /workspace/src/platform/dagwood && ./build_from_chroot.py -f"
For local development, build the Docker image locally and run it without passing --update. Because the container mounts the persistent local workspace/ directory rather than the outer checkout, incremental development via Docker requires making (or syncing) code changes in workspace/ and copying build outputs out of workspace/. Skipping --update keeps existing checkouts and local edits untouched for fast, offline startup.
CI workflows can download a prebuilt Docker image independently of the host SDK checkout and always pass --update. On initial run, this populates the workspace checkouts from the image cache and then fetches the latest upstream changes before running builds or tests.