Ship one downloadable .img that boots correctly on every supported Raspberry
Pi, instead of asking users to pick the right per-board image. Each board must load
only its own OS into RAM — never the whole file — so the per-boot RAM footprint is
identical to today's single-board images.
This document is a build-test-PR proposal for upstream SeedSigner OS.
Each supported board is a separate Buildroot target under opt/<board>/ producing its
own image. The four release targets differ almost entirely in CPU architecture, set
at compile time:
| Target | -march |
DTS built (BR2_LINUX_KERNEL_INTREE_DTS_NAME) |
Boards |
|---|---|---|---|
pi0 |
ARMv6 arm1176jzf_s |
bcm2708-rpi-b b-plus zero-w zero |
Pi 1 B/B+, Zero, Zero W |
pi2 |
ARMv7 cortex_a7 +NEON |
bcm2709-rpi-2-b |
Pi 2 B |
pi02w |
ARMv8 cortex_a53 +NEON |
bcm2710-rpi-zero-2-w 3-b 3-b-plus / bcm2837-rpi-3-b |
Zero 2 W, Pi 3 B/B+ |
pi4 |
ARMv8 cortex_a72 +NEON |
bcm2711-rpi-4-b |
Pi 4 B |
Two facts about the current design make this proposal simple and safe:
-
Within one architecture, one image already spans several boards via multiple device trees.
pi0already carries the Zero, Zero W, and Pi 1 DTBs; the GPU bootloader selects the matching.dtbby board revision. This proposal is the same mechanism applied across architectures. -
The rootfs is RAM-resident, embedded in the kernel. Every board sets
CONFIG_INITRAMFS_SOURCE="${BR_BINARIES_DIR}/rootfs.cpio"(seeopt/<board>/board/kernel.config), so the entire rootfs is compiled intozImage. There is no rootfs partition and noroot=; the whole shipped image is just the FAT boot partition. At boot the firmware loads onezImageand the kernel unpacks its embedded initramfs into RAM — which is what lets the card be removed after boot.This initramfs is large relative to the smallest board: per
opt/pi0-dev/board/boot_config.txt, the image "unpacks a ~230MB initramfs into RAM on a 512MB board." Loading more than one board's rootfs into RAM is therefore not an option — the design must load exactly one.
The Raspberry Pi GPU bootloader reads config.txt from the boot partition, evaluates
conditional model filters, and loads only the files the matched section names. Two
documented behaviors carry the design (see References):
- Model filter tokens select a section by board:
[pi0](Zero),[pi0w](Zero W),[pi02](Zero 2 W),[pi1],[pi2],[pi3],[pi4],[all]. os_prefixprepends a subdirectory to operating-system files, defined by the docs as "kernels,initramfs,cmdline.txt,.dtbsand overlays." So a singleos_prefix=pi4/routes the kernel, DTBs, and overlays for that board into their own namespace.
Because the firmware only reads the files named by the active section, the other boards'
zImages are never touched — they sit inert on the FAT card.
# --- board selection: route each model to its build's subdirectory ---
[pi1]
os_prefix=armv6/
[pi0]
os_prefix=armv6/
[pi0w]
os_prefix=armv6/
[pi02]
os_prefix=pi02w/
[pi3]
os_prefix=pi02w/
[pi2]
os_prefix=pi2/
[pi4]
os_prefix=pi4/
# --- shared settings ---
[all]
kernel=zImage
dtparam=spi=on
dtoverlay=ov5647 # OV5647 (v1). imx219 for v2 modules; both .dtbo ship per subdir.
gpu_mem=32
disable_splash=1
boot_delay=0Per-board settings that differ today migrate into the matching model section rather than
[all], e.g.:
pi0—force_turbo=1,start_file=start.elf/fixup_file=fixup.dat(full firmware; the cut-downstart_cd.elflacks the camera ISP service).pi4—start_file=start4.elf/fixup_file=fixup4.dat,dtoverlay=disable-wifi,dtoverlay=disable-bt.
Everything lives on the single FAT boot partition — there are no rootfs partitions, so the MBR four-primary-partition limit is a non-issue.
/config.txt
/bootcode.bin /start.elf /fixup.dat # ARMv6/v7/A53 boards
/start4.elf /fixup4.dat # Pi 4 (coexist; each board uses its own)
/armv6/ zImage bcm2708-rpi-{zero,zero-w,b,b-plus}.dtb overlays/{ov5647,imx219}.dtbo …
/pi02w/ zImage bcm2710-rpi-{zero-2-w,3-b,3-b-plus}.dtb overlays/…
/pi2/ zImage bcm2709-rpi-2-b.dtb overlays/…
/pi4/ zImage bcm2711-rpi-4-b.dtb overlays/…
The GPU firmware blobs live at the FAT root, outside os_prefix (it only affects OS
files, not firmware). start.elf and start4.elf coexist on one partition — this is
exactly how stock Raspberry Pi OS ships.
| Filter token(s) | os_prefix |
Build |
|---|---|---|
[pi1], [pi0], [pi0w] |
armv6/ |
pi0 |
[pi2] |
pi2/ |
pi2 |
[pi02], [pi3] |
pi02w/ |
pi02w |
[pi4] |
pi4/ |
pi4 |
At boot the firmware:
- reads the board revision;
- matches exactly one model section and its
os_prefix; - loads only
<os_prefix>zImage(+ that board's DTB and overlays) into RAM; - never reads the other three
zImages.
Peak RAM = one board's kernel + its unpacked ~230MB rootfs — identical to the current single-board image. Bundling the other three builds costs FAT storage on the card, not RAM.
opt/<board>/board/post-image-seedsigner.sh currently rebuilds the camera overlays from
the board's own kernel tree to override the rpi-firmware copies, because an overlay whose
symbols don't resolve against the running .dtb is silently dropped and libcamera then
enumerates no cameras. Namespacing each build's kernel, DTBs, and overlays under its own
os_prefix subdirectory makes cross-version contamination structurally impossible — each
board only ever sees files built together.
The per-board Buildroot targets stay byte-for-byte unchanged (same arch, same NEON, same rootfs). Add a top-level "combine" step that runs after the four release builds:
- Create one FAT boot partition sized for four kernel+embedded-rootfs sets plus firmware (≈ 4 × current per-board boot payload; see Size below).
- For each release target, copy its
zImage,.dtbs, andoverlays/into<os_prefix>/on that partition. - Copy the shared GPU firmware blobs (
bootcode.bin,start.elf/fixup.dat,start4.elf/fixup4.dat) to the FAT root. - Emit the merged
config.txt(model sections +[all]). - Name the artifact e.g.
seedsigner_os_multiboard.img.
This reuses each target's existing post-image-seedsigner.sh output; the combine step is
purely an assembly pass over already-built binaries. Dev targets (*-dev) stay separate.
One FAT partition ≈ 4 × (kernel-with-embedded-rootfs + DTBs) + firmware ≈ ~200 MB. One download replaces four. Well within FAT32 limits and an acceptable trade for dropping the board-selection step.
On-hardware boot is the acceptance gate — one physical board per bucket:
| Board | Expected os_prefix |
Verify |
|---|---|---|
| Pi Zero or Zero W | armv6/ |
boots, display + camera enumerate |
| Pi Zero 2 W | pi02w/ |
boots on A53 build (NEON path), camera enumerate |
| Pi 2 B | pi2/ |
boots, display + camera |
| Pi 4 B | pi4/ |
boots from start4.elf, wifi/bt disabled, camera |
For each: confirm the app starts and a scan works, and confirm the other three zImages
were not loaded (free RAM matches the single-board baseline for that board).
os_prefixfails silently. If a section'sos_prefixpoints at a path whose kernel the firmware can't find, it is ignored rather than erroring. Paths must be exact; a typo shows up only as a wrong/failed boot. This is the main reason per-bucket hardware testing is mandatory, not optional.- Pi 2 v1.2 ships a BCM2837 (A53) and enumerates like a Pi 3, so
[pi2]may not catch it; it would land in thepi02wbucket instead. Confirm the intended routing on that revision, or add an explicit filter. - Pi 400 / CM4 are BCM2711 like the Pi 4. If they are in scope, add
[pi400]/[cm4]→pi4/; otherwise they are intentionally unsupported. [pi3]vs[pi3+]— verify the Pi 3 B+ matches the token used; fold intopi02w/.- Boot partition size must be set from the summed payload, not the current per-board size.
- Keep the four per-board builds untouched; introduce the combine step as additive, so the existing single-board images remain buildable and reproducible.
- Preserve reproducibility: the combine step must stage files deterministically (sorted
globbing, fixed timestamps) the same way the current
post-image-seedsigner.shdoes. - Frame the user-facing win: one image, board auto-selected at boot, no per-board download decision.
- Raspberry Pi
config.txt— conditional filters andos_prefix: https://www.raspberrypi.com/documentation/computers/config_txt.html ("Any value inos_prefixis prepended to the name of any operating system files loaded by the firmware, where 'operating system files' is defined to mean kernels,initramfs,cmdline.txt,.dtbsand overlays.") - In-repo:
opt/<board>/configs/<board>_defconfig(arch + DTS lists),opt/<board>/board/kernel.config(CONFIG_INITRAMFS_SOURCE— rootfs embedded in kernel),opt/<board>/board/boot_config.txt(per-board firmware/overlay settings; ~230MB-into-RAM note in the-devvariants),opt/<board>/board/post-image-seedsigner.sh(boot-partition assembly and the overlay/DTB-match workaround).