Skip to content

✨ feat: Raspberry Pi support (Pi 4 + Pi 5)#33

Open
yeazelm wants to merge 3 commits intopapercomputeco:mainfrom
yeazelm:stereos-pi
Open

✨ feat: Raspberry Pi support (Pi 4 + Pi 5)#33
yeazelm wants to merge 3 commits intopapercomputeco:mainfrom
yeazelm:stereos-pi

Conversation

@yeazelm
Copy link
Copy Markdown
Collaborator

@yeazelm yeazelm commented May 7, 2026

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 -dev variants) build with make build-rpi4 / make build-rpi5 and flash via make flash-rpi4 / make flash-rpi5.

  • feat(boot): gates every VM-shaped tweak in modules/boot.nix (virtio initrd, GRUB EFI, vsock, suppressed console, no getty, growpart, IPv4LL fallback) behind isVmTarget = !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 via stereos.rpi.series (enum, default "rpi4"); Pi-4-only quirks (BCM43455 radio blacklist, disable-bt overlay for BT-on-PL011) gate on series == "rpi4". Adds formats/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, and rpi4MixtapeSpecs. 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 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) gates on stereos.rpi.series = "rpi5", set inline by rpi5MixtapeSpecs. Adds nixos-hardware flake input and modules/rpi5-kernel-overlay.nix.

Pi 5 design notes

  • 🧠 Kernel: ships the NixOS-built kernel as a gzipped 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 no ubootRaspberryPi5_64bit, so the standard U-Boot+extlinux flow isn't available — we replace kernel_2712.img directly and write our own cmdline.txt. The Pi 5 EEPROM detects gzip magic and decompresses on load; linuxPackages_rpi5 only installs the raw Image, hence the gzip step.
  • 🧠 D0 vs C1 silicon: ships the full bcm2712*-rpi-5-b.dtb glob and the entire overlays/ tree. Shipping only the C1 dtb panics 2 GB / rev-1.1 boards during pinctrl-bcm2712 probe with an Asynchronous SError — the EEPROM auto-selects bcm2712d0-rpi-5-b.dtb on D0 boards when both DTBs and overlays/bcm2712d0.dtbo are present.
  • 🧠 GPIO 14/15 UART: enable_uart=1 alone does not pinmux the RP1's UART0 to the GPIO header on Pi 5; the dtoverlay=uart0-pi5 overlay routes it. Device enumerates as ttyAMA0, matching Pi 4 (the Pi 5's ttyAMA10 is the dedicated debug-header UART, not GPIO 14/15).
  • 📌 nixos-hardware pin (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 at build time. Tracked in nixos-hardware#1859; fix in #1860 unmerged as of 2026-05.
  • 🧹 modules/rpi5-kernel-overlay.nix: sets makeModulesClosure to allowMissing = true. all-hardware.nix enumerates modules (dw_hdmi, …) that the rpi-vendor 6.12.x kernel builds as =y, so they aren't .ko files in /lib/modules and modprobe errors during the modules-shrunk step. Pattern borrowed from nvmd/nixos-raspberrypi.

Test plan

  • make build-rpi4 MIXTAPE=coder evaluates and builds an SD image
  • [X]make build-rpi5 MIXTAPE=coder evaluates and builds an SD image
  • [X]make build MIXTAPE=coder ARCH=aarch64-linux (VM target unchanged)
  • Booted on Raspberry Pi 5 D0 silicon (rev 1.1, 2 GB) — kernel, initrd, sysroot pivot, getty, networking, SSH all working
  • Booted on Raspberry Pi 4 — same surface
  • First-boot SSH-key drop-in (drop authorized_keys on the FAT partition before first boot)
  • make flash-rpi5 on Linux host

Part of PCC-429

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.
@yeazelm yeazelm requested a review from a team May 7, 2026 16:00
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 7, 2026

Greptile Summary

This PR adds Raspberry Pi 4 and Pi 5 as first-class stereOS image targets alongside existing VM builds. It introduces board-aware modules (modules/rpi.nix, modules/rpi-options.nix, modules/rpi-radios.nix), a custom SD-image format, a first-boot SSH key drop-in via the FAT FIRMWARE partition, and a flash-rpi.sh helper. The existing VM build path is unchanged: all VM-specific tweaks in modules/boot.nix are gated on !generic-extlinux-compatible.enable.

  • modules/boot.nix introduces isVmTarget to guard GRUB, virtio initrd, vsock, getty-suppression, and aggressive systemd timeouts — real-hardware builds get console, getty, honest wait-online, and normal timeouts.
  • Pi 5 bypasses U-Boot entirely: the NixOS-built kernel is gzip-compressed onto the FAT partition as kernel_2712.img with an explicit cmdline.txt baked at image-build time; nixos-hardware is pinned to the last commit before a known modDirVersion regression (tracked upstream).
  • modules/rpi-radios.nix blacklists BCM43455 BT/Wi-Fi on Pi 4 by default (firmware timeouts); the blacklist is suppressed on Pi 5 unconditionally.

Confidence Score: 5/5

Safe to merge; both Pi 4 and Pi 5 were boot-tested end-to-end with networking, SSH, and firstboot key injection, and the VM code path is demonstrably unchanged.

The change is a net-new hardware target with no modification to existing VM build paths. All new code is behind the isVmTarget / generic-extlinux-compatible gate. The two findings are cleanup-path quality issues that do not affect image correctness or boot reliability.

modules/rpi.nix — the lib.mkForce on boot.kernelParams warrants a second look as Pi 5 support matures and nixos-hardware modules evolve.

Important Files Changed

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
Loading
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

Comment thread scripts/run-vm.sh Outdated
Comment thread scripts/run-vm.sh Outdated
Comment thread modules/rpi.nix Outdated
Comment thread scripts/flash-rpi.sh Outdated
yeazelm added 2 commits May 7, 2026 09:09
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant