Repository navigation
Add Raspberry pi image in ref int #403
Description
What
Provide a flashable image for raspberry pi 5 and also verify parts in ci
Acceptance Criteria (DoD)
- image artifact and build exists
How
- Plan Assisted by copilot
Raspberry Pi 5 (aarch64) Image — Implementation Plan
Goal
Add a Raspberry Pi 5 (aarch64) image to the S-CORE reference_integration repo,
built in-repo with Bazel, following the existing images/ebclfsa_aarch64 pattern.
The image deploys the S-CORE showcase binaries and is testable in CI.
Key Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Build system | Bazel, in-repo | Consistent with images/ebclfsa_aarch64; no Yocto/kas |
| CI test vehicle | Generic aarch64 rootfs (QEMU virt) |
QEMU has no raspi5 machine (Pi 5 = BCM2712/Cortex-A76; newest board is raspi4b). A real Pi 5 image cannot boot in QEMU. |
| Real Pi 5 validation | SD-flash docs + hardware checklist | No Pi 5 hardware in CI |
| Base OS | Prebuilt, pinned to upstream dated dirs by sha256; mirroring to eclipse-score only as fallback | Upstream retention is stable enough: Pi OS still serves 2024-03-15; Debian keeps dated dirs back to 2025-08. Mirroring = fallback only (and for Pi OS it implies redistributing non-free firmware → IP check first) |
| Toolchain | New linux-aarch64 config (generic aarch64-linux-gcc_12.2.0) |
eb-aarch64 is the EBcLfSA SDK (trixie, gcc 14 → glibc 2.41): dynamically-linked C++ would not run on older userlands, and targeting the ebclfsa platform for a Pi is semantically wrong |
| Distro | Same distro for both artifacts (user decision): Pi OS trixie + Debian 13 genericcloud | CI then tests the same userland/glibc as the Pi |
| Deploy | One shared offline injection (debugfs -w on the ext4 rootfs) for both images |
No boot-to-deploy: no slow TCG boot at build time, no sshpass/cloud-init-seed tooling (absent from the devcontainer) |
| Runner | Direct -kernel/-initrd boot (no UEFI firmware in CI) |
The devcontainer installs qemu-system-arm with --no-install-recommends → no qemu-efi-aarch64. Only Ubuntu publishes unpacked vmlinuz+initrd; Debian does not → extract them from the image's /boot via debugfs dump (see Runner section) |
The Two-Artifact Design
Because QEMU cannot boot a real Pi 5 image, we ship two artifacts:
images/rpi5_aarch64/— the real Pi 5 image (Raspberry Pi OS trixie lite
arm64). MBR layout: p1 FATbootfs(the EEPROM bootloader readsconfig.txt
from it — the Pi 5 is not UEFI/GPT), p2 ext4rootfs. Flashable to an SD card
for hardware validation. Not run in CI.images/aarch64_generic/— a generic aarch64 rootfs (Debian 13 genericcloud)
that boots in QEMUvirt. Used for automated CI build+boot+test.
Both deploy the same //showcases:showcases_pkg_tar binaries, built under the new
linux-aarch64 toolchain config.
Spike (Phase 0 — run first): boot the Pi OS rootfs itself in QEMU virt with a
Debian arm64 kernel+initrd (root=/dev/vda2). This decides whether
images/aarch64_generic/ exists at all: if it works, the generic artifact is the Pi
rootfs (maximum test fidelity) and the Debian base is dropped. (Unverified; fallback is
the Debian genericcloud image.)
Base Images (pinned by sha256; mirror only as fallback)
| Artifact | Base | Upstream URL | Size |
|---|---|---|---|
| Pi 5 | Raspberry Pi OS trixie lite arm64 2026-10-06 | https://downloads.raspberrypi.com/raspios_lite_arm64/images/raspios_lite_arm64-2026-10-06/2026-10-06-raspios-trixie-arm64-lite.img.xz |
525 MB (xz) |
| Generic CI | Debian 13 genericcloud arm64 | https://cloud.debian.org/images/cloud/trixie/20261015-2632/debian-13-genericcloud-arm64-20261015-2632.tar.xz (pin a dated dir, never latest/) |
214 MB (tar.xz, contains disk.raw) |
- Upstream pins are the primary source :
Pi OS still serves old dated dirs (2024-03-15 is still up); Debian keeps dated dirs
back to20250806-2196. Mirroring to an eclipse-score GitHub release is the
fallback only (user confirmed access if needed). - IP impact of mirroring: an eclipse-score release of the Pi OS image is
redistribution of its non-free firmware → the Eclipse IP check must happen
BEFORE any mirroring, not only before publishing our derived image.
Plan B: no redistribution of Pi OS (if the IP check is slow or negative)
Principle: Eclipse distributes only S-CORE code and build logic, never a Pi OS image or parts of it.
- No mirror. Pin the upstream URL
downloads.raspberrypi.comby sha256. CI fetches directly from upstream, so the image is not redistributed. - No
.img.xzCI artifact. Artifacts of a public repo can be downloaded by any logged-in GitHub user, so uploading one is redistribution too. CI builds and tests the image and uploads logs only. - "Bring your own image" for users. A Bazel target fetches the official Pi OS image from Raspberry Pi (pinned, sha256-verified) and injects the S-CORE overlays offline on the user's machine. The flashable image is created locally and never published by Eclipse.
Scope of the IP check under Plan B:
- the overlays (showcases, configs, apps);
- the injection script;
- the build logic.
Pi OS content (non-free firmware plus thousands of third-party Debian packages) stays out of scope because Eclipse does not distribute it.
To confirm with the Eclipse IP team:
- whether CI-side download and caching (no public artifact) counts as distribution;
- whether pinning a third-party image URL in a Bazel
http_fileneeds a review entry.
Pi 4 does not help. It loads the proprietary GPU firmware (start4.elf, fixup4.dat) from the SD card, while the Pi 5 keeps it in EEPROM (not yet checked against the image). A Pi 4 image therefore contains more non-free blobs. The Pi OS arm64 image is shared across Pi 3/4/5, so Pi 4 can go on the hardware checklist as "should also boot" without a separate target.
- Debian format: use
.tar.xz(214 MB), not.qcow2(338 MB) or.raw
(3 GiB — would not fit the 2 GiB GitHub release asset limit if we ever mirror).
The tar.xz containsdisk.rawdirectly → noqemu-imgneeded (open item 4
resolved). - Image layouts (inspected from the actual images):
- Pi OS lite 2026-10-06: MBR. p1 FAT at sector 16384 (512 MiB), p2 ext4 at
sector 1064960 (2.36 GiB, ~434 MB free). - Debian 13 genericcloud: GPT. p15 EFI, p1 ext4 root (2.9 GiB, ~2.1 GB free).
- Both ext4 filesystems carry
orphan_file+metadata_csum→ need
e2fsprogs ≥ 1.47; the devcontainer base (buildpack-deps:noble-curl)
ships 1.47.0.
- Pi OS lite 2026-10-06: MBR. p1 FAT at sector 16384 (512 MiB), p2 ext4 at
- Debian kernel/initrd are NOT published separately (no
unpacked/like Ubuntu).
In-image:/boot/vmlinuz-6.12.111+deb13-cloud-arm64+/boot/initrd.img-…→
extract withdebugfs dump(see Runner section). - Debian ships no SSH host keys (
/etc/sshhas no*_key); they are generated
on first boot by cloud-init → cloud-init must stay enabled (see Deploy). - Pi OS publishes
.sha256,.sig, and.sbom.xzsidecars — use the published
sha256 and the SBOM for the IP/FOSS check. - Corrections vs. the previous plan: the Pi OS arm64 does have a Lite variant
(the old "no Lite variant" note was wrong); the current release is trixie
(2026-10-06), not bookworm 2024-03-15. Trixie matches the Debian base of EBcL.
The old "~1 GB qcow2" size was wrong (338 MB).
Toolchain
- Register a generic aarch64 gcc toolchain in
bazel_common/score_gcc_toolchains.MODULE.bazel:
gcc.toolchain(name = "score_gcc_aarch64_toolchain", target_cpu = "aarch64", target_os = "linux", version = "12.2.0", use_default_package = True)- add to
use_repo(gcc, …). The version-matrix entryaarch64-linux-gcc_12.2.0exists
(url…/toolchains_gcc_packages/releases/download/v0.0.4/aarch64-unknown-linux-gnu_gcc12.tar.gz,
sha2567279b1adb50361b21f5266b001980b6febb35fa8d83170901196b9edae3f06d9).
- add to
.bazelrc: add one configbuild:linux-aarch64mirroringlinux-x86_64:
--config=_common+--extra_toolchains=@score_gcc_aarch64_toolchain//:aarch64-linux-gcc_12.2.0--platforms=@score_bazel_platforms//:aarch64-linux-gcc_12.2.0-posix.
Reuse@ferrocene_aarch64_ebclfsa//:rust_ferrocene_toolchainfor Rust (static — no glibc issue).
MODULE.bazel.lockrefresh is REQUIRED (registering a toolchain in a module
extension changes the module graph — per.github/instructions/module-bazel-lock.instructions.md).
The previous "no lockfile refresh" claim applied tohttp_fileadditions only.- Verify: build
//showcases:showcases_pkg_tarunder--config=linux-aarch64;
readelfthe binaries: glibc symbol versions ≤ 2.41 (the real bound — trixie
ships glibc 2.41; the old "≤ 2.36 / noble" criterion is stale, noble is out).
The v0.0.4 crosstool-ng sysroot is built withCT_GLIBC_V_2_31=y/
CT_LINUX_V_5_15=y→ binaries need at most GLIBC_2.31, so the bound is met
automatically; keep the check as a regression guard. Also check PT_LOAD
alignment for the 16K-page case.
Deploy: One Shared Offline Injection
One script, used by both images (both rootfs partitions are ext4):
- Read the partition offsets programmatically with
partx --show(util-linux,
in the devcontainer base) — must handle both MBR (Pi: rootfs = p2) and GPT
(Debian: rootfs = p1).sfdiskis NOT in the noble base manifest — don't rely
on it. ddthe ext4 rootfs partition out of the (raw) image at that offset.- Write the tar contents in with
debugfs -w(e2fsprogs 1.47, pure userspace —
no loop-mount/cap_sys_admin//dev/fuseneeded; all of those are unavailable in
the devcontainer). ddthe partition back into the copied image.
The real Phase 3 work — a tar→debugfs generator : a small generator (Python tarfile) that emits a debugfs -f
command file:
mkdirper directory;writeper file;symlinkfor symlinks;sif <path> mode/uid/gid/mtimefor permissions/ownership/timestamps — without
sif, files land as root with default modes, which breaks 0755 binaries.
cloud-init: keep it enabled, seed it offline:
- Write a NoCloud seed into
/var/lib/cloud/seed/nocloud/viadebugfs:
meta-data(instance-id) +user-data(cloud-config withssh_authorized_keys).
No ISO/mtools/extra tools needed. - Add
/etc/cloud/cloud.cfg.d/drop-in withdatasource_list: [NoCloud, None]
(avoids minutes of EC2/OpenStack metadata probing under TCG). - Pi OS trixie ships
cloud-init25.2 +rpi-cloud-init-mods+userconf-pi→
headless first boot is cloud-init there too (exact file names still to check).
Size check: the Pi rootfs has only ~434 MB free → the script must check
free space before writing and fail clearly (growing the fs would need a
partition-table edit + resize2fs; Pi OS grows the fs on first boot anyway).
Test SSH key: generated at build time, never committed (review): a genrule runs
ssh-keygen -t ed25519 -N ''; the public key goes into the NoCloud seed, the
private key is a runfile of the sh_test.
- No boot-to-deploy → no TCG boot at build time, no sshpass.
- Pi
bootfs(FAT) edits:mtoolsis not in the devcontainer, but with
cloud-init seeding viadebugfsit is likely needed only forconfig.txt
edits (verify on the Pi OS image) → open item.
Runner + Test
- Direct
-kernel/-initrdboot for the generic image. Debian does not
publish unpacked vmlinuz+initrd (only Ubuntu does) → extract them from the
deployed image's/bootwithdebugfs dumpin the same genrule that builds
the image (guarantees kernel + modules match the rootfs):
/boot/vmlinuz-6.12.111+deb13-cloud-arm64+/boot/initrd.img-….
Kernel cmdline:root=/dev/vda1 console=ttyAMA0(Debian root = p1; the Pi
rootfs would beroot=/dev/vda2). Avoids UEFI firmware entirely (absent in CI:
qemu-system-armis installed with--no-install-recommends).- Alternative: fetch UEFI firmware hermetically (pinned
http_file+ pad to 64 MB
in a genrule) — only needed if we drop the direct-boot approach.
- Alternative: fetch UEFI firmware hermetically (pinned
- Correction: the plan's old "finding 2" was a misread —
run_qemu.shuses a mux
chardev (-chardev stdio,mux=on+-serial chardev:char0), which is compatible
with-nographic; only a plain-serial stdiowould clash. - Do NOT reuse
run_qemu.shfor the test : it usessudoand requires
thesh_binarybash launcher. Use a small separate test runner (plain
qemu-system-aarch64, no sudo) with-smp 4(the script hardcodes 8). - Real test target :
bazel build …:rundoes not run tests. Add
ansh_testthat boots the generic image, waits for SSH (build-time-generated
key), runs the showcases, checks output, and powers off. Configure it for slow
TCG boots:size = "large"(ortimeout = "long") — boot + cloud-init under TCG
exceeds the 300 s moderate timeout;tags = ["exclusive"](hostfwd ports 2222/3333
are fixed — or pick a free port at runtime). - Drop the Pi
:run— a real Pi image cannot boot in QEMU;:runfor it is
meaningless.
CI
- TCG only:
ubuntu-latestis x86_64;/dev/kvmcannot accelerate an aarch64
guest → accept ~2.5 min per boot (one boot per test run, since deploy is offline). - Tag the image genrules
no-cache/no-remote-cacheso the GB-scale outputs are not
pushed into the 10 GB Actions disk-cache (saved on main). - Upload the Pi 5
.img.xzonly on main/release with a shortretention-days;
PRs upload logs only. - Workflow
.github/workflows/build_and_test_rpi5.yml(frombuild_and_test_ebclfsa.yml):- PR: build + test the generic image (
bazel test … --config=linux-aarch64). - main: also build the Pi 5 image, upload
.img.xz.
- PR: build + test the generic image (
Content Decisions
etc_configs: the ebclfsafastdev-imagegenrule lists
//feature_integration_tests/configs:etc_configsinsrcsbut never passes it to
build_image.sh— a bug not to copy. Decision: pass it (deploy the etc
configs). It is afilegroup(etc/logging.json,etc/mw_com_config.json),
andbuild_image.shonly accepts tars → wrap it in apkg_tarwith the
correctpackage_dir(check the expected runtime path in the showcases first).images/common/apps(datarouter etc.): decision: ship them, like
images/linux_x86_64does (it already wraps them in apkg_tar).
Docs
images/rpi5_aarch64/README.md— SD-flash instructions (ebclfsa pattern).- Add the platform to
docs/integration_process/step_4_platforms.rstand the
top-levelREADME.md.
FOSS / IP
- Pi OS contains non-free firmware blobs; publishing a derived image as an Eclipse
CI artifact needs an Eclipse IP check. Use the published.sbom.xzfor the review.
16K Pages
- Pi OS uses
kernel_2712.imgwith 16K pages; QEMU CI runs 4K. Check PT_LOAD
alignment (-z max-page-size) and any hardcoded 4096 in shared-memory code
(lola/mw::com). ThereadelfPT_LOAD ≥ 16K check stays in CI as a static
guard; the rest is listed in the hardware-validation checklist. - Optional extension (NOT MVP): Debian trixie ships
linux-image-*-arm64-16k;
a second test variant with that kernel +-cpu cortex-a76would cover the Pi 5
page size in CI. Its modules/initrd don't match the cloud image → follow-up.
Phases
Phase 0 — Spike (run first)
Boot the Pi OS trixie rootfs in QEMU virt with a Debian arm64 kernel+initrd
(root=/dev/vda2). Decides whether images/aarch64_generic/ exists at all (Pi
rootfs vs. Debian genericcloud) — before any mirroring/pinning of the Debian base.
Phase 1 — Toolchain
Register aarch64-linux-gcc_12.2.0 in bazel_common/score_gcc_toolchains.MODULE.bazel
use_repo; addbuild:linux-aarch64to.bazelrc; refreshMODULE.bazel.lock
and commit it; build//showcases:showcases_pkg_tarunder the new config;readelf
checks (glibc symbol versions ≤ 2.41, PT_LOAD alignment).
Phase 2 — Base images
Add new http_file entries in bazel_common/score_images.MODULE.bazel (the
bookworm/noble entries exist only in the local branch, not on main — the PR adds
them fresh): Pi OS trixie lite 2026-10-06 .img.xz + Debian 13 genericcloud
.tar.xz (dated dir, never latest/), pinned by sha256. Upstream pins are primary;
mirror to eclipse-score only as fallback (and only after the IP check for Pi OS).
http_file changes are lockfile-safe.
Phase 3 — Shared offline injection
One script (used by both images): partx --show offsets (MBR + GPT) → dd ext4
rootfs out → tar→debugfs generator (Python tarfile → debugfs -f with
mkdir/write/symlink/sif) → dd back; NoCloud cloud-init seed
(/var/lib/cloud/seed/nocloud/ + datasource_list: [NoCloud, None]); build-time
ssh-keygen key; free-space size check (Pi rootfs ~434 MB); mtools (or
hermetic equivalent) for the Pi FAT bootfs config.txt if needed.
Phase 4 — Runner + test
Direct -kernel/-initrd boot of the generic image (vmlinuz+initrd extracted from
/boot via debugfs dump in the image genrule); separate non-sudo test runner
(-smp 4); sh_test (size = "large", tags = ["exclusive"]) that boots, SSHes
in, runs the showcases, checks output, powers off. No :run for the Pi image.
Phase 5 — CI workflow
.github/workflows/build_and_test_rpi5.yml: PR = build+test generic (TCG);
main = + build Pi image, upload .img.xz (short retention); image genrules
no-cache/no-remote-cache.
Phase 6 — Docs + hardware validation
images/rpi5_aarch64/README.md flash steps; docs/integration_process/step_4_platforms.rst
- top-level
README.md; hardware-validation checklist (incl. 16K pages,
headless first boot, non-free firmware IP check).
Verification Criteria
bazel test --config=linux-aarch64 //images/aarch64_generic:<test>— boots in QEMU
virt, SSH works, showcases run, clean poweroff.bazel build --config=linux-aarch64 //images/rpi5_aarch64:image— produces a
flashable.imgof sane size;debugfs/filespot-check that the overlays are present.- CI workflow green on a PR.
MODULE.bazel.lockrefreshed + committed;bazel build --lockfile_mode=errorpasses.readelfon the deployed binaries: glibc symbol versions ≤ 2.41 (met automatically
by the 2.31 sysroot; regression guard) and PT_LOAD alignment ≥ 16K.
Open Items (resolve during implementation)
- Spike (Phase 0): boot the Pi OS trixie rootfs in QEMU
virt(Debian
kernel+initrd,root=/dev/vda2) → decides whether the generic artifact is the
Pi rootfs itself or the Debian genericcloud image. mtoolsfor the Pi FATbootfs— likely needed only forconfig.txt
edits now that the cloud-init seed goes in viadebugfs(verify on the Pi OS
image); hermetic tool (Bazelhttp_file) or a devcontainer PR.- Trixie headless first boot — confirmed cloud-init (ships
cloud-init
25.2 +rpi-cloud-init-mods+userconf-pi); exact seed file names still to
check. — resolved: use the Debianqemu-imgfor qcow2→raw.tar.xz(contains
disk.raw); no qcow2, noqemu-img.- Eclipse IP check — open; needed before any mirroring of the Pi OS image
(redistribution of non-free firmware), not only before publishing the derived
image (use the published.sbom.xz). - 16K-page alignment —
readelfPT_LOAD ≥ 16K guard in CI + hardcoded-4096
audit (lola/mw::com) in the hardware checklist; 16k-kernel test variant
(linux-image-*-arm64-16k+-cpu cortex-a76) optional, not MVP. - tar→debugfs generator (incl.
siffor modes/ownership/timestamps) — open;
the main Phase 3 effort. - Pi rootfs free space (~434 MB) — add a size check to the deploy script;
fail clearly if the overlays don't fit.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsBacklog
Activity
feat(toolchain): add generic linux-aarch64 toolchain
lurtz commentedon Oct 23, 2026
Some stuff I learned along the way and want to share.
In this issue you plan to download images from raspberrypi.com and debian.org. Githubs connection to the internet is not reliable. It works better, if you can use storage inside the Github network.
When building infrastructure for integration tests, I kept an eye on not creating huge copies of images. Thus data needed for testing is compiled into a separate ext4 and the image itself is untouched. This should also allow faster compile + debug cycles.
I wonder a bit about using a Raspberry PI 5, when QEMU has support for 4B. Then the same image could be used via QEMU and on hardware.