✨ feat: Raspberry Pi support (Pi 4 + Pi 5)#33
✨ feat: Raspberry Pi support (Pi 4 + Pi 5)#33yeazelm wants to merge 3 commits intopapercomputeco:mainfrom
Conversation
The boot module is heavily VM-tuned: virtio-only initrd modules,
GRUB EFI, vsock, aggressive systemd timeouts, suppressed kernel
console output, no getty, growpart on first boot, IPv4LL fallback
networking. These are all wrong for real hardware (Raspberry Pi).
Introduce `isVmTarget = !boot.loader.generic-extlinux-compatible.enable`
and gate every VM-shaped tweak behind it. The extlinux flag is the
existing NixOS sd-image signal that "this build targets a real SBC
and uses U-Boot/extlinux"; flipping it inverts boot.nix's posture
without adding a new option.
Two parallel changes that ride along:
• aarch64 kernel-console list ends with hvc0 (Apple-VF console)
only on VM targets; on real hardware modules/rpi.nix overrides
the list with real terminals so /dev/console maps to a usable
device.
• systemd-networkd configured with `RequiredForOnline=routable`
on real-hardware builds and `LinkLocalAddressing=no`. The
BCM54213 PHY on Pi 4 takes 30-50s to negotiate gigabit, so
network-online.target firing on a 169.254.x.x address would
let services like first-boot installers run with no internet.
On VM builds we keep the IPv4LL fallback so SLIRP/vmnet
bring-up isn't blocking.
No behavioral change for VM builds (everything still gated on,
just under an explicit flag now). Real-hardware images now boot
with the right kernel console wiring, getty enabled, real-DHCP
wait-online, and no aggressive systemd timeouts.
|
| Filename | Overview |
|---|---|
| modules/boot.nix | VM-target gate added via isVmTarget; all VM-only tweaks correctly guarded; real-hardware path restored cleanly. |
| modules/rpi.nix | Core Pi hardware module; lib.mkForce on boot.kernelParams silently discards any additions from other modules (including nixos-hardware), which could become problematic as the config evolves. |
| scripts/flash-rpi.sh | New flash helper; inject_ssh_key_linux's sudo umount lacks error-suppression — a busy-device failure exits the script after the flash succeeds, producing a confusing error and leaving /tmp/stereos-firmware behind. |
| modules/firstboot-keys.nix | Forgiving key import service; correctly orders before sshd.service and after RequiresMountsFor; no issues found. |
| formats/rpi-sd-image.nix | Wraps nixpkgs sd-image-aarch64, drops noauto on firmware mount, pre-creates /boot/firmware mountpoint, and seeds the SSH-key template; well-commented and correct. |
| modules/rpi5-kernel-overlay.nix | Overrides makeModulesClosure with allowMissing=true to handle built-in (=y) modules; well-documented pattern borrowed from nvmd/nixos-raspberrypi. |
| flake/images.nix | Adds rpi4MixtapeSpecs/rpi5MixtapeSpecs and mkSdImagePkgs helper; correctly gated to aarch64-linux only. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[mkMixtape] -->|aarch64-linux| B{Board target?}
B -->|VM| C[profiles/vm raw-efi + qcow2]
B -->|RPi 4| D[profiles/rpi.nix series=rpi4]
B -->|RPi 5| E[profiles/rpi.nix series=rpi5 + nixos-hardware + rpi5-kernel-overlay]
D --> F[sd-image-aarch64 U-Boot + extlinux]
E --> G[EEPROM-direct kernel_2712.img + cmdline.txt]
D --> H[config.txt Pi4 enable_uart + disable-bt]
E --> I[config.txt Pi5 pi5 block + uart0-pi5]
F & G --> J[FAT FIRMWARE partition]
J --> K[ssh_authorized_keys.txt]
K -->|first boot| M[firstboot-keys.service]
subgraph boot_nix[modules/boot.nix isVmTarget gate]
N{isVmTarget}
N -->|true| O[GRUB + virtio + vsock no getty + short timeouts]
N -->|false| P[getty + wait-online default timeouts]
end
C --> N
D --> N
E --> N
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 2
scripts/flash-rpi.sh:347-350
`sudo umount` can fail (device busy, kernel delay flushing dirty pages) and the script runs under `set -euo pipefail`, so a non-zero exit here causes the script to abort before `print_summary` even though the flash completed successfully. The user sees a confusing failure message with no summary, and `/tmp/stereos-firmware` is left as a stale mount point. Adding `|| true` matches the `sudo eject` treatment below and makes the cleanup best-effort.
```suggestion
sudo tee -a "$keys_file" < "$SSH_KEY" > /dev/null
echo "Key appended to $keys_file"
sudo umount "$mount_point" || true
sudo eject "$TARGET_DEVICE" 2>/dev/null || true
```
### Issue 2 of 2
modules/rpi.nix:764-767
`lib.mkForce` on a list type completely replaces every lower-priority definition — including any `boot.kernelParams` additions that `nixos-hardware.raspberry-pi-5` or future modules may set. For Pi 5 the result is baked directly into `cmdline.txt`, so silently dropped params will never appear at runtime. Using a tighter override like `lib.mkOverride 900` (above the default 100 but below `lib.mkForce` at 1000) would still win over `boot.nix`'s `mkMerge` while letting genuinely higher-priority additions survive.
Reviews (2): Last reviewed commit: "✨ feat(rpi): extend Pi support to Raspbe..." | Re-trigger Greptile
Adds the SD-image build path and supporting modules so stereOS can
boot on a Raspberry Pi alongside the existing VM mixtapes.
The architecture is board-aware from the start via
`stereos.rpi.series` (enum, default "rpi4"); only the Pi 4 build
target is wired up in this commit, and Pi-4-only quirks (BCM43455
radio blacklist, BT-on-PL011 disable-bt overlay) gate on
`series == "rpi4"`. A follow-up commit adds the Pi 5 build target
without further refactoring.
What's added:
• formats/rpi-sd-image.nix imports nixpkgs' sd-image-aarch64
and adds a first-boot SSH-keys
drop-in surface on the FAT firmware
partition.
• modules/rpi.nix board-aware Pi hardware module:
root-fs label, kernel-console fixup,
serial-console option (GPIO 14/15
PL011 → ttyAMA0), firmware-partition
population, emergency-shell access
for physical-console debugging.
• modules/rpi-options.nix declares stereos.rpi.series so
companion modules (rpi-radios, etc.)
can read it on every build.
• modules/rpi-radios.nix opt-out blacklist of the Pi 4's
BCM43455 BT/WiFi modules — the
combo-chip firmware load adds ~10s
of boot time and noisy timeouts.
Default off; enable the radios with
stereos.rpi.radios.enable = true.
• modules/firstboot-keys.nix forgiving SSH-key import from a
plain-text file on the FAT partition,
editable on macOS/Windows/Linux
without mounting ext4.
• profiles/rpi.nix bundles formats + firstboot-keys
for the rpi mixtape spec.
• scripts/flash-rpi.sh macOS/Linux SD-card flasher with
--board scoping, SSH-key injection,
and removable-disk detection.
• flake/images.nix rpi4MixtapeSpecs (aarch64-only) for
base/coder/base-dev/coder-dev,
mkSdImagePkgs helper. *-rpi4-sd
outputs join the existing per-system
package set.
• Makefile build-rpi4 / flash-rpi4 targets.
• scripts/run-vm.sh guard that catches Pi SD images
accidentally fed to QEMU and
redirects to flash-rpi.
Pi-4-only path requires no new flake inputs — uses nixpkgs'
sd-image-aarch64 + raspberrypifw + pkgs.linuxKernel.packages.linux_rpi3
(the upstream default kernel for sd-image-aarch64).
Wires up the rpi5 build target on top of the board-aware Pi
infrastructure. Pi-5-only logic in modules/rpi.nix (firmware-
partition contents, [pi5] config.txt block, NixOS-built kernel
as gzipped kernel_2712.img, NixOS-built initrd, explicit
toplevel cmdline) is reachable via stereos.rpi.series = "rpi5",
which the rpi5MixtapeSpecs set inline.
Why a NixOS-built kernel as kernel_2712.img instead of
raspberrypifw's prebuilt:
• The firmware-shipped kernel_2712.img is a different version
(e.g. 6.12.25-v8-16k+ in fw 1.20250430) than the kernel
nixos-hardware configures (linux-rpi 6.12.75-1+rpt1). Booting
the firmware kernel makes /lib/modules/<our-version>/ unusable
— modprobe fails for everything kernel-module-dependent.
• nixpkgs at our pinned rev has no ubootRaspberryPi5_64bit, so
the standard sd-image-aarch64 U-Boot+extlinux flow is
unavailable on Pi 5. We replace the prebuilt kernel directly
and write our own cmdline.txt.
• Pi 5 EEPROM detects the gzip magic on kernel_2712.img and
decompresses on load; linuxPackages_rpi5 only installs the
raw Image, so we gzip it during firmware-partition population.
D0 vs C1 silicon: the EEPROM auto-selects bcm2712d0-rpi-5-b.dtb
on D0 boards when both DTBs *and* overlays/bcm2712d0.dtbo are
present. Ship only the C1 dtb and a 2 GB / rev-1.1 board panics
during pinctrl-bcm2712 probe with an Asynchronous SError. We ship
the full bcm2712 dtb glob and the entire overlays/ tree.
GPIO 14/15 UART: enable_uart=1 by itself does not pinmux the
RP1's UART0 to the GPIO header on Pi 5. The dtoverlay=uart0-pi5
overlay does that routing; the device enumerates as ttyAMA0,
matching Pi 4's name (ttyAMA10 on Pi 5 is the dedicated
debug-header UART, not GPIO 14/15).
nixos-hardware pin: pinned to f1b7ff92cdd1 — the last commit
before nixos-hardware#1841 replaced the rpi-kernel postConfigure
sed with `LOCALVERSION = freeform ""`, which round-trips through
nixpkgs' kernel-config emitter as the literal two characters `""`
and breaks the modDirVersion sanity check. Tracked in
nixos-hardware#1859, fix in #1860 unmerged as of 2026-05.
Pi-5-only artifacts:
• flake.nix nixos-hardware flake input
• modules/rpi5-kernel-overlay.nix
overlay defaulting makeModulesClosure to
allowMissing=true; needed because all-
hardware.nix lists modules that the
rpi-vendor 6.12.x kernel builds as =y
(dw_hdmi, …) so they're not present as
.ko files in /lib/modules and modprobe
errors during the modules-shrunk step.
Pattern from nvmd/nixos-raspberrypi.
• flake/images.nix rpi5MixtapeSpecs (sets series + imports
nixos-hardware module + the kernel-
overlay), rpi5Pkgs.
• Makefile build-rpi5 / flash-rpi5 targets.
Adds Raspberry Pi 4 and Pi 5 as first-class stereOS image targets alongside the existing VM mixtapes. SD images for
base/coder(and their-devvariants) build withmake build-rpi4/make build-rpi5and flash viamake flash-rpi4/make flash-rpi5.feat(boot): gates every VM-shaped tweak inmodules/boot.nix(virtio initrd, GRUB EFI, vsock, suppressed console, no getty, growpart, IPv4LL fallback) behindisVmTarget = !boot.loader.generic-extlinux-compatible.enable. Real-hardware builds get a usable console, getty, real-DHCP wait-online, and no aggressive timeouts. No behavior change for VM builds.feat(rpi): Pi 4 SD-image build path. Architecture is board-aware from the start viastereos.rpi.series(enum, default"rpi4"); Pi-4-only quirks (BCM43455 radio blacklist,disable-btoverlay for BT-on-PL011) gate onseries == "rpi4". Addsformats/rpi-sd-image.nix,modules/rpi.nix,modules/rpi-options.nix,modules/rpi-radios.nix,modules/firstboot-keys.nix(FAT-partition SSH-key drop-in editable from any host OS),profiles/rpi.nix,scripts/flash-rpi.sh, andrpi4MixtapeSpecs. No new flake inputs — Pi 4 uses nixpkgs'sd-image-aarch64+raspberrypifw+linuxKernel.packages.linux_rpi3.feat(rpi): Pi 5 (BCM2712) on top of the board-aware infra. Pi-5-only logic inmodules/rpi.nix(firmware-partition contents,[pi5]config.txt block, NixOS-built kernel as gzippedkernel_2712.img, NixOS-built initrd, explicit toplevel cmdline) gates onstereos.rpi.series = "rpi5", set inline byrpi5MixtapeSpecs. Addsnixos-hardwareflake input andmodules/rpi5-kernel-overlay.nix.Pi 5 design notes
kernel_2712.img, not the firmware-partition's prebuilt one. The two are different versions (e.g. fw-shipped 6.12.25 vs nixos-hardware's 6.12.75) and a mismatch makes/lib/modules/<our-version>/unusable. nixpkgs at our pinned rev has noubootRaspberryPi5_64bit, so the standard U-Boot+extlinux flow isn't available — we replacekernel_2712.imgdirectly and write our owncmdline.txt. The Pi 5 EEPROM detects gzip magic and decompresses on load;linuxPackages_rpi5only installs the rawImage, hence the gzip step.bcm2712*-rpi-5-b.dtbglob and the entireoverlays/tree. Shipping only the C1 dtb panics 2 GB / rev-1.1 boards duringpinctrl-bcm2712probe with an Asynchronous SError — the EEPROM auto-selectsbcm2712d0-rpi-5-b.dtbon D0 boards when both DTBs andoverlays/bcm2712d0.dtboare present.enable_uart=1alone does not pinmux the RP1's UART0 to the GPIO header on Pi 5; thedtoverlay=uart0-pi5overlay routes it. Device enumerates asttyAMA0, matching Pi 4 (the Pi 5'sttyAMA10is the dedicated debug-header UART, not GPIO 14/15).nixos-hardwarepin (f1b7ff92cdd1): the last commit before nixos-hardware#1841 replaced the rpi-kernelpostConfiguresed withLOCALVERSION = freeform "", which round-trips through nixpkgs' kernel-config emitter as the literal two characters""and breaks themodDirVersionsanity check at build time. Tracked in nixos-hardware#1859; fix in #1860 unmerged as of 2026-05.modules/rpi5-kernel-overlay.nix: setsmakeModulesClosuretoallowMissing = true.all-hardware.nixenumerates modules (dw_hdmi, …) that the rpi-vendor 6.12.x kernel builds as=y, so they aren't.kofiles in/lib/modulesandmodprobeerrors during themodules-shrunkstep. Pattern borrowed fromnvmd/nixos-raspberrypi.Test plan
make build-rpi4 MIXTAPE=coderevaluates and builds an SD imagemake build-rpi5 MIXTAPE=coderevaluates and builds an SD imagemake build MIXTAPE=coder ARCH=aarch64-linux(VM target unchanged)authorized_keyson the FAT partition before first boot)make flash-rpi5on Linux hostPart of PCC-429