Sprint 4 Design Pack — GWP-175
Parent catalog: GWP-163 (post-v0.1 deferred) Cross-ref: GWP-227 (firmware-update-model umbrella; consumes pieces of this tree) Depth: Design pack only — NO implementation Status: Planning → In Progress (design only). Implementation gated by GWP-152 ship per the GWP-163 catalog rule.
Gate acknowledgement
Section titled “Gate acknowledgement”GWP-163 says: “Design planning (ADR / gameplay spec / UI wireframe) for deferred items CAN happen now in parallel with v0.1 execution. Implementation cannot.” This pack is design-only. The PR ships this document plus an optional kn86-firmware/README.md skeleton with an explicit “design-only — implementation gated by GWP-152” notice. No .c, .h, .rs, Cargo.toml, CMakeLists.txt, or systemd-unit files are added under kn86-firmware/ in this PR.
Story narrative
Section titled “Story narrative”The directory kn86-firmware/ is already named in two Accepted ADRs (ADR-0011 §“Configuration location” and §“Action items”; ADR-0020 §F3 / GWP-227 plumbing) but does not yet exist on disk. Today the device-side concerns are scattered across three trees:
tools/sd-provision/pi-gen-stages/stage-kn86-{base,runtime,firmware}/— pi-gen overlay stages that drop systemd units, udev rules, device-tree overlays,config.txtsnippets, and a/opt/nosh/skeleton into the rootfs at image-build time. These stages are correctly classified as packaging, not as the source of the things they package. (Confirmed by readingstage-kn86-runtime/00-kn86-runtime/01-run.sh, which says verbatim “The real nOSh binary install is a separate sprint task. This sub-stage only lays down the directory tree + a placeholder marker.”)tools/kn86fw/— the Rust crate that assembles a built slot’s bootfs+rootfs into the.kn86fwfield-update payload (per ADR-0020). This is the packaging tool for field updates, not the source of any binary inside that payload.kn86-emulator/— the canonical home of the nOSh runtime source, but compiled for the desktop emulator target (SDL3 on macOS/Linux). The same source tree is intended to cross-compile to the Pi target, but no Pi-target build descriptor exists in-repo today.
The gap GWP-175 exists to close: there is no canonical home for the Pi-target build of nOSh runtime, the boot-time updater gate (kn86-updater-gate daemon + updater_config.h per ADR-0011 §“Configuration location”), or the updater initramfs (kn86-firmware/updater/ per ADR-0011 Phase 2 / ADR-0020 F3). Today an engineer asked “where does the Pi build of nOSh live?” would correctly answer “nowhere yet.” kn86-firmware/ is that home.
The sibling-tree topology already establishes the naming convention:
| Tree | Target hardware | Build tool | What it produces |
|---|---|---|---|
pico2-firmware/ | RP2350 bare-metal (Pico 2 coprocessor) | CMake + Pico SDK | kn86_coproc.uf2 |
firmware/kn86-keyboard/ | RP2040-class (KB2040 keyboard) | QMK | kn86-keyboard.uf2 |
kn86-emulator/ | desktop (macOS/Linux) | CMake + SDL3 | kn86emu desktop binary |
kn86-firmware/ (this proposal) | Pi Zero 2 W (Linux userland + initramfs) | CMake + cross-toolchain (recommended) | nosh ELF for /opt/nosh/bin/, kn86-updater-gate daemon, updater_config.h, updater-initramfs cpio |
The naming inconsistency between pico2-firmware/ (top-level) and firmware/kn86-keyboard/ (under firmware/) is pre-existing and not in scope to fix here; this design adopts the top-level form (kn86-firmware/) because (a) ADR-0011 already commits the path verbatim and (b) a project-wide tree rename is its own separate ticket if Josh wants the four firmwares co-located.
Terminology — what “firmware” means here
Section titled “Terminology — what “firmware” means here”Per CLAUDE.md “Terminology”: on Pi Zero 2 W, the literal Pi firmware (VideoCore GPU bootloader) is vendor-supplied and we don’t touch it. So kn86-firmware/ is not firmware in the bare-metal sense the way pico2-firmware/ is. It is the device-side userland sources — the nOSh runtime binary built for the Pi target, plus a small set of early-boot binaries (the updater gate daemon, the updater-mode runtime) that behave firmware-adjacent because they own the boot flow before the rootfs is fully up. The README skeleton landed by this PR is explicit about that distinction so future readers don’t have to re-derive it.
If the name kn86-firmware/ proves misleading in practice (open question 1 below), the alternative is kn86-runtime/ or kn86-pi/. Recommendation: keep kn86-firmware/ because two Accepted ADRs already cite the path; renaming costs more in stale-reference cleanup than the precision gain is worth.
Anti-duplication check
Section titled “Anti-duplication check”I evaluated whether kn86-firmware/ would duplicate tools/sd-provision/pi-gen-stages/stage-kn86-runtime/. It does not. The two trees have orthogonal responsibilities:
| Concern | Lives in pi-gen stage | Lives in kn86-firmware/ |
|---|---|---|
| Source code that compiles to a binary | No (stage is shell + static files only) | Yes — nOSh runtime cross-compiled for Pi, updater-gate daemon, updater-mode binaries |
systemd unit .service / .path / .timer files | Yes (files/etc/systemd/system/*) | No (units stay where they are; this tree only produces the binaries the units ExecStart=) |
| udev rules | Yes (files/etc/udev/rules.d/*) | No |
Device-tree overlay binaries / config.txt snippets | Yes (stage-kn86-firmware/) | No |
01-run.sh install hooks (chroot-aware) | Yes | No (this tree’s CI publishes binaries; the pi-gen stage’s 01-run.sh install -v -m’s them into the rootfs) |
| Updater initramfs cpio source | No | Yes (kn86-firmware/updater/ per ADR-0011 §“Action items” Wave 2) |
| Boot-time attention-gesture configuration header | No | Yes (kn86-firmware/include/updater_config.h per ADR-0011 §“Configuration location”) |
The contract between the two trees is one-way: kn86-firmware/ produces release artifacts (binaries + cpio); tools/sd-provision/pi-gen-stages/stage-kn86-runtime/01-run.sh consumes them via install -v -m calls into ${ROOTFS_DIR}. This is the same contract pico2-firmware/ already has with stage-kn86-firmware/ (which deposits kn86-pico.uf2 at /lib/firmware/).
Conclusion: no merge or rename of the pi-gen stages. Create kn86-firmware/ as a new sibling tree.
GWP-227 cross-reference: ADR-0020 §“Action items” assigns the updater-image work (kn86-firmware/updater/) and the updater_config.h header to that umbrella’s Wave 2. GWP-175 (this ticket) defines the container tree those subtasks land in; GWP-227 is the umbrella that schedules the work that fills it. No scope conflict — GWP-175 ships the directory layout + README; GWP-227 fills the directories with content when v0.1 ships.
Proposed directory layout
Section titled “Proposed directory layout”kn86-firmware/ # Pi Zero 2 W device userland sources├── README.md # this PR — design-only marker├── CMakeLists.txt # top-level CMake, Pi-target cross-compile├── cmake/│ └── pi-zero-2-w-arm64.toolchain.cmake # cross-toolchain for arm64 / Cortex-A53├── include/│ └── updater_config.h # ADR-0011 §"Configuration location" header├── nosh/ # Pi-target build of the nOSh runtime│ ├── CMakeLists.txt│ └── (no sources — sources live in kn86-emulator/src/; this is a build descriptor│ that pulls them in with a Pi-target compile profile and SDL3-on-Pi link)├── updater/ # ADR-0011 Phase 2 Wave 2 — updater image│ ├── CMakeLists.txt│ ├── gate/ # kn86-updater-gate daemon (early-boot)│ ├── runtime/ # kn86-updater binary + g_mass_storage orchestrator│ ├── initramfs/ # cpio assembly script + busybox-style root│ └── kernel-cmdline.txt # updater kernel cmdline (dwc2 dr_mode=peripheral)├── splash/ # boot-splash assets (ADR-0011-aligned amber/black)│ └── (PNG / RGB565 / KMS console fbcon snippet)└── tests/ └── (host-side unit tests for updater_config.h key-combo parsing, initramfs assembly determinism, etc.)What this tree is NOT
Section titled “What this tree is NOT”- Not the home of
noshsource code. Sources stay inkn86-emulator/src/.kn86-firmware/nosh/CMakeLists.txtis a Pi-target build descriptor that references the sibling tree. This avoids forking the source — same.cfiles, two compile profiles. (If that proves awkward at implementation time, the alternative is to liftkn86-emulator/src/to a top-levelnosh-src/and have bothkn86-emulator/andkn86-firmware/nosh/reference it. Open question 3.) - Not the home of any pi-gen stage scripts. Those stay in
tools/sd-provision/pi-gen-stages/. - Not the home of
tools/kn86fw/. The Rust packager stays where it is; it consumes the.imgproduced by pi-gen (which contains the binaries this tree built). - Not the home of QMK keyboard sources (
firmware/kn86-keyboard/) or Pico 2 coprocessor sources (pico2-firmware/). Those are bare-metal targets with their own toolchains.
Build target: CMake + cross-toolchain
Section titled “Build target: CMake + cross-toolchain”Recommendation: CMake. Three targets considered:
| Option | Pro | Con | Verdict |
|---|---|---|---|
| CMake | Already the build system for kn86-emulator/ and pico2-firmware/. Cross-toolchain files are a well-trodden path on Pi targets. Same engineers can context-switch trivially. | Marginally heavier than shell for the initramfs-assembly subtree. | Pick. |
| Cargo (Rust) | Aligns with tools/kn86fw/ and the Tauri flasher (ADR-0011 §“Desktop flasher architecture”). | The runtime sources are C11; rewriting them to Rust is out-of-scope for GWP-175 and would re-litigate ADR-0001. The updater gate daemon could be Rust, but the rest cannot be without huge scope creep. | Reject — wrong scope. |
| Shell (Makefile) | Lowest ceremony for the initramfs subtree. | Doesn’t compose with C cross-compile; would force a bifurcated build (make for initramfs, CMake for binaries). | Reject — fragmentation. |
The toolchain file (cmake/pi-zero-2-w-arm64.toolchain.cmake) targets arm64 Cortex-A53 with the Debian 13 trixie sysroot per ADR-0026. Cross-compile from x86_64 Linux in the same --platform=linux/amd64 Docker container the pi-gen build already uses (docs/device/os/system-image-build.md §“Build-host setup”). No second build environment.
CMake top-level builds three artifacts:
| Target | Output | Consumed by |
|---|---|---|
kn86-firmware-nosh | nosh ELF (arm64) | stage-kn86-runtime/00-kn86-runtime/01-run.sh install’s it to /opt/nosh/bin/nosh |
kn86-firmware-updater-gate | kn86-updater-gate ELF (arm64) | New stage hook in stage-kn86-base or similar — early-boot init wiring |
kn86-firmware-updater-image | kn86-updater-initramfs.cpio.gz | Bootfs slot installer (lives next to the kernel) |
Versioning: each artifact gets KN86_BUILD_ID baked in via the same env var the pi-gen stage already uses (stage-kn86-runtime/00-kn86-runtime/01-run.sh line ~70). Same release-CI plumbing, no new secrets.
Relationship map
Section titled “Relationship map” +--------------------------------------------+ | kn86-emulator/src/ (canonical nOSh src) | +-------------+------------------------------+ | +-----------------+-----------------+ | | v v +---------------------+ +-------------------------------+ | kn86-emulator/ | | kn86-firmware/nosh/ | | (desktop CMake | | (Pi-target cross-compile | | build, SDL3) | | CMake build, SDL3-on-Pi) | +---------------------+ +---------------+---------------+ | | produces /opt/nosh/bin/nosh ELF v +-----------------+-----------------+ | tools/sd-provision/pi-gen-stages/ | | stage-kn86-runtime/01-run.sh | | (install -v -m to ROOTFS_DIR) | +-----------------+-----------------+ | v +-----------------+-----------------+ | tools/sd-provision/build-image.sh | | pi-gen → .img with all binaries | +-----------------+-----------------+ | v +-----------------+-----------------+ | tools/kn86fw/ (Rust packager) | | .img slot → .kn86fw payload | +-----------------------------------+The dotted line “this is design-only” is everywhere in this diagram today: the Pi-target nOSh build does not exist, the updater binaries do not exist, the install hooks for them in stage-kn86-runtime/01-run.sh use a placeholder file. None of that changes with this PR. This PR only writes the design doc + a README skeleton announcing the tree’s scope.
Migration path (when implementation eventually unblocks)
Section titled “Migration path (when implementation eventually unblocks)”When GWP-152 ships and GWP-227 schedules the implementation work, the migration is additive — nothing existing moves:
kn86-firmware/include/updater_config.h— new file. ADR-0011 already specifies the contents (combo keys, hold ms, scan window).kn86-firmware/cmake/pi-zero-2-w-arm64.toolchain.cmake— new file. Standard CMake cross-toolchain pattern.kn86-firmware/nosh/CMakeLists.txt— new file. References../../kn86-emulator/src/for sources. Thekn86-emulator/CMakeLists.txtdoes not change in this step.kn86-firmware/updater/— new subtree. Per ADR-0011 §“Action items” Phase 2 Wave 2.tools/sd-provision/pi-gen-stages/stage-kn86-runtime/00-kn86-runtime/01-run.sh— replace the placeholderinstall ... files/opt/nosh/PLACEHOLDER ...withinstall -v -m 755 ${KN86_FIRMWARE_BUILD}/nosh /opt/nosh/bin/noshplus equivalents for the updater binaries. This is a ~10-line edit, not a stage rewrite.- No file in any sibling tree moves.
kn86-emulator/src/stays put.tools/kn86fw/stays put.pico2-firmware/stays put.firmware/kn86-keyboard/stays put.
The only rename / move candidate is the long-term question of co-locating the four firmware trees (open question 2). That is not part of GWP-175; it would be its own ticket.
Acceptance criteria expanded
Section titled “Acceptance criteria expanded”- Design pack lands at
docs/plans/sprints/2026-04-30-gwp-175-kn86-firmware-design.mdwith all sections present (story, anti-duplication, layout, build target, relationship map, migration, open questions). Pass when this file is committed. - Optional
kn86-firmware/README.mdskeleton carries an explicit “design-only — implementation gated by GWP-152” notice, names the three artifact targets the tree will eventually produce, links to ADR-0011 §“Configuration location” and ADR-0020 §F3, and references CLAUDE.md “Canonical Hardware Specification” for Pi Zero 2 W details (does not restate). Pass when the README exists with those four things and contains zero source files in sibling subdirectories. - No production firmware code is added.
git diff main -- kn86-firmware/shows onlyREADME.md(and this design doc, which is not underkn86-firmware/). Pass on visual review of the PR diff. - Anti-duplication confirmed. Reviewers can grep the existing
tools/sd-provision/pi-gen-stages/tree and confirm none of the file types proposed forkn86-firmware/already exist there. Pass on the table in the “Anti-duplication check” section above being verifiable byfind. - ADR cross-references are accurate. ADR-0011 line 131 (
kn86-firmware/include/updater_config.h), line 425 (kn86-firmware/updater/), line 446 (include path), line 447 (updater path) all point at the layout this design proposes. Verified bygrep -n kn86-firmware docs/adr/ADR-0011-device-firmware-update-system.md. - Notion task moves to Review with PR URL on the GitHub PR property before this design pack is considered done. PM (Josh or the orchestrator agent) takes Review → Done after PR merges.
Edge cases
Section titled “Edge cases”- Engineer assumes
kn86-firmware/nosh/is a separate fork of the runtime. Mitigation: README skeleton is explicit that the directory is a build descriptor only and that source lives inkn86-emulator/src/. The CMakeLists.txt eventually landed there will reference the sibling tree path-relatively. Without this notice, the natural reading of “nosh subdirectory in the firmware tree” is “this is where nosh source lives,” which would invite a fork. - pi-gen stage
01-run.shruns beforekn86-firmware/has produced its artifacts. Mitigation: release-CI ordering. The build pipeline runskn86-firmware/cross-compile before invokingtools/sd-provision/build-image.sh, the same way it runspico2-firmware/build before pi-gen pullskn86-pico.uf2into/lib/firmware/. Documented indocs/device/os/release-setup.md(currently emulator-only — gets extended when implementation lands). - Top-level
firmware/directory already exists for the keyboard. Mitigation: this design adds a top-levelkn86-firmware/(matchingpico2-firmware/’s placement, and matching the path baked into ADR-0011). The naming inconsistency withfirmware/kn86-keyboard/is pre-existing and explicitly out-of-scope (see open question 2). Reviewers should not request a rename of the existing keyboard tree as part of this PR. updater_config.hABI breakage. When GWP-227 lands the actual header, any future change to the combo keys is a one-line header edit (per ADR-0011 §“Why a header, not a config file”) — but this means the value ofKN86_UPDATER_COMBO_KEYSis part of the deck’s behavioral contract with the operator. Treat header edits as user-visible changes and route them through ADR amendments to ADR-0011, not silently. This is an authoring-convention edge case worth calling out in the README skeleton.
Engineering hand-off notes
Section titled “Engineering hand-off notes”No engineering brief — this is a design pack only. When GWP-227 schedules implementation:
- Owner: Platform Engineering (per CLAUDE.md “Agent Roles” — Linux system image, systemd units, kiosk mode).
- Pre-reqs: GWP-152 ships (v0.1 unblocks). ADR-0026 trixie pin is final.
tools/sd-provision/pi-gen-pin.envcarries the validated pin. - First implementation PR: scaffold
cmake/pi-zero-2-w-arm64.toolchain.cmake+kn86-firmware/CMakeLists.txt+kn86-firmware/nosh/CMakeLists.txt. Verify cross-compile produces a runnable arm64 ELF. No updater work yet. - Second implementation PR:
kn86-firmware/include/updater_config.h+kn86-firmware/updater/gate/daemon. Wire intostage-kn86-baseearly-boot. - Third PR: updater initramfs (
kn86-firmware/updater/runtime/+kn86-firmware/updater/initramfs/). This is the bulk of GWP-227 F3. - Test strategy: every PR adds a
kn86-firmware/tests/host-side test that runs in CI. The first PR’s test verifies the cross-toolchain finds a sysroot. The updater-gate PR’s test verifiesupdater_config.hparses cleanly under both arm64 (target) and x86_64 (host, for unit tests of the combo-key matcher).
No code in this PR. The PR diff contains exactly two new files: this design doc and kn86-firmware/README.md.
Open questions for Josh (decidable)
Section titled “Open questions for Josh (decidable)”- Confirm tree name. Three options:
kn86-firmware/(matches ADR-0011 + ADR-0020 verbatim; preferred),kn86-runtime/(more accurate per CLAUDE.md “Terminology” — “firmware” is misleading on Linux userland), orkn86-pi/(parallelspico2-firmware/by hardware target). Recommendation:kn86-firmware/— renaming costs more in stale-ADR-reference cleanup than the precision gain. Decidable yes/no. - Defer or address the firmware-tree co-location question. Today:
pico2-firmware/(top-level),firmware/kn86-keyboard/(underfirmware/), proposedkn86-firmware/(top-level). A future cleanup ticket could move all three underfirmware/{pi,pico2,keyboard}/. Recommendation: defer. This PR matches the ADR-0011 path; a separate ticket can do the rename if Josh wants the tidy hierarchy. Decidable: file follow-up ticket now, or leave the inconsistency standing. - Source-tree placement of the nOSh runtime. Two options: (a) leave sources in
kn86-emulator/src/and havekn86-firmware/nosh/CMakeLists.txtreference them path-relatively (proposed); (b) lift sources to a top-levelnosh-src/(or similar) and have bothkn86-emulator/andkn86-firmware/nosh/reference it as a peer. Recommendation: (a) for v1, revisit if the cross-tree reference proves awkward. Decidable: pick (a) or (b) for the implementation PRs. - Pico 2 firmware UF2 staging path. Today
stage-kn86-firmwaredepositskn86-pico.uf2at/lib/firmware/kn86-pico.uf2(persystem-image-build.mdline 40). Shouldkn86-firmware/also host the build descriptor for the Pico 2 UF2 (i.e., become the single Pi-side pre-image build), or keeppico2-firmware/independent and let release-CI orchestrate both? Recommendation: keep them independent. They are different targets (Cortex-A53 + Linux vs RP2350 bare-metal) and forcing them into one CMake top-level adds complexity. Decidable: yes (independent) / no (consolidate later).