diff --git a/README.md b/README.md index bfbebe6..0134af4 100644 --- a/README.md +++ b/README.md @@ -10,36 +10,32 @@ A modern, open development environment for the **Luckfox Pico 86 Panel** hermetic build, a QEMU device simulator, register-level hardware models, and MC/DC-hardened drivers (Modified Condition/Decision Coverage — the avionics-grade test bar). It replaces the vendor stack — a ~2 GB, twice-forked -SDK pinned to Linux 5.10 — with tooling that is tested, benchmarked, -reproducible, and honest about what runs on real silicon versus what is -simulated. - -Originally built as the SDK for WardenOS (BlueFlare Energy's wall-panel -firmware), but nothing here requires it: if you have an 86 Panel, this repo -gives you a modern kernel and a way to develop for the board without flashing -it on every change. +SDK pinned to Linux 5.10 — and is honest about what runs on real silicon +versus what is simulated. Born as the SDK for WardenOS (BlueFlare Energy's +wall-panel firmware); nothing here requires it. ## Why -The vendor SDK bakes in absolute paths, silently drops Kconfig options, and -offers **no way to test hardware-dependent -code off the device** — every change means flashing a panel. That is slow and -occasionally destructive: a coprocessor load address that collided with -unreserved kernel RAM bricked a bench unit, a mistake a static memory-map -check would have caught before any flash (`tools/config-lint` now is that -check). This SDK exists so the board is buildable, testable, and hardenable -**without a panel in the loop**, on a maintained kernel. +The vendor SDK: + +- bakes in absolute paths and silently drops Kconfig options; +- offers no way to test hardware-dependent code off the device — every change + means flashing a panel; +- let a memory-map mistake brick a bench unit (a coprocessor load address in + unreserved kernel RAM) that a static check would have caught — + `tools/config-lint` is now that check. + +This SDK makes the board buildable, testable, and hardenable **without a +panel in the loop**, on a maintained kernel. ## What Works -A self-built **Linux 6.18.46**, forward-ported from the vendor 5.10.160 tree -as a subsystem-split patch series (`patches/`) and hardware-verified on a -bench panel: clk, pinctrl, eMMC, GMAC, TRNG, OTP, SARADC/TSADC, RTC, USB -host, PWM/backlight, VOP display, GT911 touch, AIC8800 wifi, RGA, I2S audio, -the HPMCU (RISC-V watchdog coprocessor) mailbox, the open NPU driver, and -PVTM. Mainline alone was not -viable (no RV1106 device tree, clock, display, RGA, NPU, or flash-boot support -upstream); see `docs/decisions/0001-kernel-base.md`. +A self-built **Linux 6.18.46**, forward-ported from vendor 5.10.160 as a +subsystem-split patch series (`patches/`) and hardware-verified on a bench +panel: clk, pinctrl, eMMC, GMAC, TRNG, OTP, SARADC/TSADC, RTC, USB host, +PWM/backlight, VOP display, GT911 touch, AIC8800 wifi, RGA, I2S audio, the +HPMCU (RISC-V watchdog coprocessor) mailbox, the open NPU driver, and PVTM. +Mainline alone was not viable for RV1106 (ADR-0001). ## Quick Start @@ -64,71 +60,61 @@ done for d in drivers/*/test; do make -C "$d" check; done # 100% MC/DC gate (gcc >= 14) ``` -The kernel variant with the simulator's extra devices (PCI serial, watchdog, -WireGuard, display) adds one env var to step 1: -`WARDEN_KCONFIG_FRAGMENT=qemu/configs/virt.fragment`. See `qemu/README.md` -for the scenario tests (portal, OTA apply, display + touch, watchdog). +Add `WARDEN_KCONFIG_FRAGMENT=qemu/configs/virt.fragment` to step 1 for the +kernel variant with the simulator's extra devices; `qemu/README.md` has the +scenario tests (portal, OTA apply, display + touch, watchdog). ## Layout -``` -patches/ the RV1106 forward-port onto pristine linux-6.18.46 (subsystem-split) -build/ the hermetic kernel build (fetch pinned source -> apply patches -> zImage + dtb) -qemu/ the device simulator: QEMU -M virt boots the real kernel and real userspace; - A/B disk layout, RS485 bridge into sim/, scenario tests -sim/ register-level hardware models (Rust): membus, HPMCU, CRU, Modbus, RGA, NPU -drivers/ hardened hardware-facing drivers with HAL seams and 100% MC/DC harnesses -kernel/ forward-port provenance and bring-up records (point-in-time; patches/ is canonical) -tools/ config-lint (static memory-map gates) and dev tooling -docs/ architecture, ADRs (decisions/), CI/CD -``` +| Directory | Contents | +|---|---| +| `patches/` | the RV1106 forward-port onto pristine linux-6.18.46, subsystem-split | +| `build/` | hermetic kernel build: pinned fetch → apply patches → `zImage` + dtb | +| `qemu/` | device simulator: QEMU `-M virt` boots the real kernel and real userspace | +| `sim/` | register-level hardware models (Rust): membus, HPMCU, CRU, Modbus, RGA, NPU | +| `drivers/` | hardened hardware-facing drivers: HAL seams, 100% MC/DC harnesses | +| `kernel/` | forward-port provenance and bring-up records (`patches/` is canonical) | +| `tools/` | `config-lint` (static memory-map gates) and dev tooling | +| `docs/` | architecture, ADRs (`decisions/`), CI/CD | ## Architecture One thin **hardware abstraction seam** per block (a trait in Rust, a function -table in C). Firmware logic talks to the seam; the seam binds a real backend -on the device (`/dev/mem`, ioctls, serial, `/proc`) or a simulated backend on -the host. The same seam is what the driver-hardening effort measures MC/DC -against and what the simulator implements, so the two reinforce rather than -duplicate each other. Full detail: `docs/architecture.md`. +table in C): firmware logic talks to the seam; the seam binds a real backend +on the device or a simulated backend on the host. MC/DC is measured against +the same seam the simulator implements, so the two reinforce each other. +Full detail: `docs/architecture.md`. -Three simulators, by design not one: - -| Simulator | What it runs | What it proves | +| Simulator | Runs | Proves | |---|---|---| | `sim/` | register-level Rust models | driver and supervisor logic, with fault injection | -| `qemu/` | the real kernel + real userspace on `-M virt` | boot, init, daemons, networking, OTA, watchdog, display + touch | +| `qemu/` | the real kernel + userspace on `-M virt` | boot, init, daemons, networking, OTA, watchdog, display + touch | | `lvglsim` (downstream) | the LVGL UI on SDL | rendering and UI flows | -The device simulator covers the UI as well: with the production UI binary in -`qemu/payload/`, `run.sh --display on` opens the panel's 720x720 screen in a -window with mouse clicks landing as touch — device and UI in one VM -(headless + scripted: `qemu/tests/ui-shot.sh`). - -Boots and passes under emulation are never treated as on-silicon evidence; -the simulators narrow which claims need a panel, they do not replace it. +With the production UI binary in `qemu/payload/`, `run.sh --display on` opens +the panel's 720x720 screen in a window, mouse clicks landing as touch — +device and UI in one VM. Emulation results are never on-silicon evidence; +the simulators narrow which claims need a panel. ## Principles -- **Open**: open tools over closed ones (`rkdeveloptool`, source-built - components, an open simulator); GPL-2.0-only. -- **Hard**: every seam has a fault-injection path — recovery code is tested +- **Open** — open tools over closed ones; GPL-2.0-only. +- **Hard** — every seam has a fault-injection path; recovery code is tested against failure, not just success. -- **Modern**: the newest kernel the hardware can run, current toolchains, +- **Modern** — the newest kernel the hardware can run, current toolchains, Rust for new host-testable code, reproducible builds. ## Downstream WardenOS (the 86 Panel firmware this SDK was born for) consumes this repo -from its own private repo, flare-edge; issue references and checkout paths +from its private repo, flare-edge; issue references and checkout paths pointing there are context, not reachable links. The QEMU simulator runs its -production binaries unmodified — including real over-the-air updates against -a mock portal. +production binaries unmodified — real over-the-air updates included. ## License -**GPL-2.0-only**, repo-wide (see `LICENSE`; a per-file SPDX identifier -governs where present). The kernel material in `patches/` and `kernel/` is -derivative of the Linux kernel and GPL-2.0 vendor code; per-driver origin is -tracked in `kernel/rv1106-enablement/PROVENANCE.md`. Contributions are -accepted under the same license (inbound = outbound). +**GPL-2.0-only**, repo-wide (`LICENSE`; a per-file SPDX identifier governs +where present). `patches/` and `kernel/` are derivative of the Linux kernel +and GPL-2.0 vendor code; per-driver origin is tracked in +`kernel/rv1106-enablement/PROVENANCE.md`. Contributions are accepted under +the same license (inbound = outbound). diff --git a/docs/architecture.md b/docs/architecture.md index d5c45c7..3af6c7d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -22,175 +22,145 @@ tested (or not) differently. Today: | Relays / GPIO | `ui-src/.../relays.c` | `/sys/class/gpio` sysfs | **none** — literal paths | path absent → "unavailable" | | Slot metadata | `flared/src/slotctl.rs` | `misc` partition + `/proc/cmdline` | `WARDEN_MISC_DEV`, `WARDEN_CMDLINE_FILE` env overrides | (redirected to scratch files) | -Three patterns coexist: **compile-time `#if`** (RGA), **env-override** (modbus -socket, misc dev, cmdline, hpmcu fw), and **fails-soft-because-the-path-is-absent** -(NPU, relays, devmem-would-fault). The last is not a test seam — you cannot inject -"relay 1 is ON" or "NPU at 80%", only "absent". The SDK's job is to turn all of -these into **one deliberate seam per block** with a real backend and a sim backend. +Three patterns coexist: **compile-time `#if`** (RGA), **env-override** +(modbus socket, misc dev, cmdline, hpmcu fw), and +**fails-soft-when-the-path-is-absent** (NPU, relays, devmem-would-fault). The +last is not a test seam — you can inject "absent", never "relay 1 is ON" or +"NPU at 80%". The SDK's job: **one deliberate seam per block**, each with a +real backend and a sim backend. ## 2. Seam Taxonomy Two seam kinds cover everything above: -- **Register/SRAM seam → a trait.** `MemBus` (`sim/src/membus.rs`): `peek32/poke32` - at a physical address. Real backend = flared `devmem.rs` mmap; sim backend = - `SimBus` (in-memory word map, `Clone` so two "cores" alias shared memory). The - HPMCU watchdog and the CRU reset ladder both ride this. **Built.** -- **Resource-path seam → env-override + injection.** For file/socket/sysfs paths - (`/proc/rknpu/load`, `/sys/class/gpio/*`, `/dev/ttyS4`, `misc`), generalize the - existing `WARDEN_MISC_DEV`/`WARDEN_MODBUS_SOCK` pattern into one rule: **every - device/proc/sys path a driver opens is resolved through a single indirection - (`warden_hw_path("npu.load")` in C, an env-overridable const in Rust)**, so a - test points it at a fake file/fifo the sim writes. No LD_PRELOAD, no fake mounts. +- **Register/SRAM seam → a trait.** `MemBus` (`sim/src/membus.rs`): + `peek32/poke32` at a physical address. Real backend = flared `devmem.rs` + mmap; sim backend = `SimBus`. The HPMCU watchdog and the CRU reset ladder + both ride this. **Built.** +- **Resource-path seam → env-override + injection.** Every device/proc/sys + path a driver opens resolves through one indirection + (`warden_hw_path("npu.load")` in C, an env-overridable const in Rust) — + generalizing the existing `WARDEN_MISC_DEV`/`WARDEN_MODBUS_SOCK` pattern — + so a test points it at a fake file/fifo the sim writes. No LD_PRELOAD, no + fake mounts. -RGA stays compile-time — its `#if WARDEN_USE_RGA` already cleanly isolates the -librga/dma-heap calls behind the always-compiled LVGL draw-unit glue; the sim -backend is "a fake `improcess` that records the blits it was asked to do", swapped -behind the same `#if`, so the offload *dispatch* logic gets tested even though the -blit itself is modelled. +RGA stays compile-time: `#if WARDEN_USE_RGA` already isolates the +librga/dma-heap calls, and the sim backend (a recording `improcess` fake) +swaps in behind the same `#if`, so the *dispatch* logic is tested even though +the blit is modelled. ## 3. The Register Simulator -A host Rust library modelling the hardware the vendor SDK cannot, so driver and -supervisor logic runs in CI with no panel. +A host Rust library modelling the hardware the vendor SDK cannot, so driver +and supervisor logic runs in CI with no panel. All models are **done**: -- **`membus` — register/SRAM bus.** Done. `MemBus` trait + `SimBus`. -- **`hpmcu` — the RISC-V watchdog coprocessor.** Done. Faithful port of - `hpmcu/watchdog/main.c`'s state machine (boot-grace, heartbeat-timeout, disarm, - fire) against a `SimBus` mailbox, virtual clock, 7 tests including the - arm-within-grace no-boot-loop safety property. This is the model that would have - let the boot-loaded-watchdog logic be validated before the flash that bricked a - bench unit (though the *layout* fault — a load address in unreserved kernel RAM — - is a target-config check, §5, not a sim property). -- **`cru` — reset ladder.** Done. `CruSim` on `MemBus` (so `flared::devmem::hard_reset`'s - ladder is host-tested against the known glb_srst_fst / DW-watchdog registers), plus - the boot-mode register's survives-warm-reset / cleared-by-POR behaviour (the MaskRom - recovery maneuver). The matching firmware-side `Bus` seam on flared's `devmem` — so - the shipped ladder can be asserted to poke the confirmed offset, never the wrong-SoC - one — lands when flare-edge consumes this repo (§8 item 3, maintainer-gated), not yet on - flare-edge `main`. -- **`modbus` — RS-485 device end.** Done. `ModbusSlave`: a byte-in/byte-out RTU slave - (CRC16 byte-identical to the master, FC 0x01–0x06/0x0F/0x10/0x11, exception replies, - and fault injection — silent-drop and forced-NAK) so `warden-modbus`'s master can be - hardened to MC/DC against realistic device behaviour with no serial hardware. MEI - (0x2B/0x0E) identification is the documented follow-up. The same slave also serves - as the QEMU device sim's field bus: `qemu/rs485-bridge/` feeds it from a serial - chardev so the guest's real master polls it over what it believes is /dev/ttyS4 (§7). -- **`npu` — NPU load model.** Done. `NpuSim` models `/proc/rknpu/load` (the exact - "NPU load: N%" text the sysmon reads) behind the path seam, so the load-readout UI - is host-testable. NPU *compute* is explicitly out of scope — no inference runs here. -- **`rga` — 2D blitter offload.** Done. `RgaSim`, a recording `improcess` fake with a - programmable `IM_STATUS`, so the RGA offload-dispatch and CPU-fallback logic is - exercised behind the `#if WARDEN_USE_RGA` seam without librga; wired into the - `rga_improcess` benchmark. -- **Next:** MEI (0x2B/0x0E) Modbus identification; the Tier-2 driver *sources* - (`modbus_engine.c`, `warden_rga.c`) migrate in with the flare-edge unification - (ADR-0005) — their hardware ends are already modelled and tested above. +| Model | What it is | +|---|---| +| `membus` | `MemBus` trait + `SimBus` in-memory bus (`Clone`, so two "cores" alias shared memory) | +| `hpmcu` | the watchdog coprocessor's state machine (boot-grace, heartbeat-timeout, disarm, fire) on a `SimBus` mailbox with a virtual clock; 7 tests incl. the arm-within-grace no-boot-loop safety property | +| `cru` | reset ladder against the known glb_srst_fst / DW-watchdog registers, plus boot-mode register semantics (survives warm reset, cleared by POR — the MaskRom recovery maneuver) | +| `modbus` | byte-in/byte-out RTU slave: CRC16 byte-identical to the master, FC 0x01–0x06/0x0F/0x10/0x11, exception replies, silent-drop and forced-NAK fault injection; doubles as the QEMU sim's field bus via `qemu/rs485-bridge/` (§7) | +| `npu` | `/proc/rknpu/load` text model behind the path seam — the load-readout UI is host-testable; NPU *compute* is out of scope | +| `rga` | recording `improcess` fake with programmable `IM_STATUS`, exercising offload-dispatch and CPU-fallback; wired into the `rga_improcess` benchmark | -Integration with flare-edge: flared implements `MemBus` for `/dev/mem` and gains -`#[cfg(test)]` tests driving its real arm/beat logic against `HpmcuSim`. This needs -this repo reachable as a Cargo dependency in CI — i.e. a remote for this repo, -which is a **maintainer go-ahead item** (credential/remote creation). Until then the -firmware-side seam and a local test double land in flare-edge, unified with `sim/` -once the dependency exists. No duplication of *logic* — only the tiny trait. +**Next:** MEI (0x2B/0x0E) Modbus identification; the Tier-2 driver *sources* +migrate in with the flare-edge unification (ADR-0005) — their hardware ends +are already modelled here. + +**Integration with flare-edge — landed 2026-08-31** (flare-edge PR #110): +`warden-sim` is a flared dev-dependency (through the vendored submodule), and +unification tests in flared's own suite pin its real arm/beat and +reset-ladder logic to `HpmcuSim`/`CruSim`. ## 4. Driver Hardening -"100% MC/DC on 100% of drivers" is infeasible literally: ~97% of driver LOC is -vendor blobs (AIC8800 wifi = 88.5K lines). Tiered target: +"100% MC/DC on 100% of drivers" is infeasible literally — ~97% of driver LOC +is vendor blobs (AIC8800 wifi alone is 88.5K lines) — so the target is +tiered: -- **Tier 1 — our own hardware code → real MC/DC.** Method: the proven flare-edge `tests/uboot-ab` - pattern — extract the unit behind a small injectable seam, mock its world, build - `-fcondition-coverage`, enforce with the shared `drivers/enforce-mcdc.sh` (gcc-14 - `gcov --conditions`) in the CI `mcdc` job. **Done here now:** `relays.c` (40/40 - conditions) and `freshness.c` (66/66), both at 100% MC/DC and CI-enforced. **Migrate - in next:** the modbus master (`modbus_engine.c`) and the RGA wrapper's dispatch — - their hardware ends are already modelled and tested in `sim/` (`modbus`, `rga`); the - driver sources move in with the flare-edge unification (ADR-0005). The HPMCU - supervisor and devmem reset ladder are covered Rust-side (`sim/hpmcu`, `sim/cru`). -- **Tier 2 — near-mainline small drivers → branch coverage + fault injection.** -- **Tier 3 — vendor blobs (AIC8800, MPP/ISP/RGA libs) → fault-injection hardening - behind the seam,** not MC/DC. The AIC8800 SDIO-wedge Tier-1 fix + the designed - reset-on-ETIMEDOUT recovery are this tier: test the *recovery* path against an - injected wedge on the `MemBus`/SDIO seam, since the blob itself is untestable. +- **Tier 1 — our own hardware code → real 100% MC/DC.** Extract the unit + behind a small injectable seam, mock its world, build + `-fcondition-coverage`, enforce via the shared `drivers/enforce-mcdc.sh` + (gcc-14 `gcov --conditions`) in the CI `mcdc` job — the proven flare-edge + `tests/uboot-ab` method. **Done:** `relays.c` (40/40 conditions) and + `freshness.c` (66/66). **Next in:** the modbus master and the RGA + dispatch, with the ADR-0005 unification (`drivers/README.md`). +- **Tier 2 — near-mainline small drivers → branch coverage + fault + injection.** +- **Tier 3 — vendor blobs (AIC8800, MPP/ISP/RGA libs) → fault-injection + hardening behind the seam**, not MC/DC: e.g. the SDIO-wedge recovery is + tested against an injected wedge; the blob itself is untestable. Every seam gets a fault-injection mode (a wedged SDIO link, a stalled MCU -heartbeat, an RGA timeout, a GPIO write EIO) so recovery code is tested against -failure, not just the happy path. +heartbeat, an RGA timeout, a GPIO write EIO) so recovery code is tested +against failure, not just the happy path. ## 5. Target-Config Checks -The brick was a *memory-map* fault: the boot-loaded MCU's load address (`0x40000`) -is a reserved carve-out on Thunder-Boot boards but plain kernel RAM on ours. No -behavioural sim catches that — it needs a **static check against the target DT**: -"every address the MCU/coprocessor code loads to is inside a `reserved-memory` -node." This repo owns these config-lint checks (idblock loader `.ini` vs DT -reservations, partition table vs image sizes, vermagic vs kernel) as CI gates, so a -mistake is caught before a flash rather than on the bench. +The brick was a *memory-map* fault: the boot-loaded MCU's load address +(`0x40000`) is a reserved carve-out on Thunder-Boot boards but plain kernel +RAM on the 86 Panel. No behavioural sim catches that — it takes a **static +check against the target devicetree**, owned here as CI gates so mistakes +are caught before a flash, not on the bench. -**Built:** `tools/config-lint` implements the first and most important of these — -the MCU-load-vs-`reserved-memory` gate. It parses the rkbin loader `.ini` for -every `LOADERn=Hpmcu` firmware and its `[LOADERn_PARAM] LOAD_ADDR`, parses the -target devicetree (`.dts`, or `dtc -I dtb` output in CI) for `reserved-memory` -ranges, and fails if any MCU load lands outside a reservation. Its test suite -encodes the c8a3 brick itself: the real Thunder-Boot `.ini` (Hpmcu @ `0x40000`) -fails against a DT with no `rtos@40000` node and passes once the reservation is -added. **Next** target-config checks: partition-table-vs-image-size and -vermagic-vs-kernel. +- **Built:** `tools/config-lint` — every `LOADERn=Hpmcu` `LOAD_ADDR` in the + rkbin loader `.ini` must land inside a DT `reserved-memory` range. Its + test suite encodes the c8a3 brick itself (see its README). +- **Next:** partition-table-vs-image-size; vermagic-vs-kernel. ## 6. Kernel Forward-Port -A self-built **Linux 6.18.46**, forward-ported directly from the vendor 5.10.160 tree -onto our Buildroot LTS/uClibc base — **not** the plan44/OpenWrt 6.6 fork this section -originally reached for. **ADR-0001 records why that was superseded:** plan44 drops -Buildroot for OpenWrt/musl and ships no AIC8800 kmod, so it was a swap-out, not a -forward-port. Mainline alone was not viable either (no DT/clk/display/RGA/NPU/ -flash-boot upstream for RV1106); the port reuses the already-in-mainline rv1126 -register data where it matches and carries our deltas as the reviewable `patches/` -series. This is **done and hardware-verified on `warden-c8a3`** — clk, pinctrl, eMMC, -GMAC, TRNG, OTP, SARADC/TSADC, RTC, USB host, PWM/backlight, VOP display, GT911 touch, -AIC8800 wifi, RGA, I2S audio, HPMCU mailbox, the open NPU driver, and PVTM all boot. -The dominant risk was the struct-ABI break (the VLAN saga), mitigated by shipping the -kernel move as one matched boot+oem image, never a partial reflash. -`build/build-kernel.sh` (the hermetic build) and the `patches-apply` CI gate keep the -series honest against pristine 6.18.46; provenance is in `patches/README.md` and -`kernel/rv1106-enablement/`. +**Done and hardware-verified** on the bench panel; the full peripheral set +in the README's "What Works" boots. + +- **Base:** a direct forward-port of the vendor 5.10.160 tree onto pristine + 6.18.46, keeping the Buildroot LTS/uClibc userspace. +- **Not plan44/OpenWrt 6.6** (ADR-0001): that fork swaps Buildroot for + OpenWrt/musl and ships no AIC8800 kmod — a platform swap, not a port. +- **Not mainline alone:** no RV1106 DT/clk/display/RGA/NPU/flash-boot + upstream; already-mainline rv1126 register data is reused where it + matches. +- **Dominant risk** was the struct-ABI break (the VLAN saga) — mitigated by + shipping kernel moves as one matched boot+oem image, never a partial + reflash. +- **Kept honest** by the hermetic `build/build-kernel.sh` and the + `patches-apply` CI gate against pristine 6.18.46; provenance in + `patches/README.md` and `kernel/rv1106-enablement/`. ## 7. Device Emulation -The third simulator, deliberately not named "sim": a QEMU VM (`-M virt,highmem=off`, -one Cortex-A7, 256M — the RV1106G3's shape) that boots the real forward-ported -kernel and real userspace, entering at `-kernel zImage` because everything below -(BootROM, idblock/DDR-init, U-Boot, the BCB A/B machinery) is closed blobs plus -mask ROM. The canonical RV1106 zImage boots virt unmodified; an additive kconfig -fragment (`qemu/configs/virt.fragment` via `WARDEN_KCONFIG_FRAGMENT`) adds the -scenario devices (PCI serial for RS485, i6300esb watchdog, WireGuard, -virtio-gpu/input for the 720x720 UI). The virtio disk carries the device's exact -12-partition `blkdevparts=` A/B layout and the `/dev/block/by-name/` contract. +A QEMU VM (`-M virt,highmem=off`, one Cortex-A7, 256M — the RV1106G3's +shape) boots the real forward-ported kernel and real userspace, entering at +`-kernel zImage`: everything below is closed rkbin blobs plus mask ROM. +Details, scenarios, and the emulated-vs-not table: `qemu/README.md` and +ADR-0006. -Where the seams meet: the guest runs the *real* binaries (static musl flared, -the LVGL fbdev UI); the RS485 bridge (`qemu/rs485-bridge/`) connects a QEMU -serial chardev to `sim/`'s `ModbusSlave`, so the register-level models serve as -the VM's field bus — behavior lives in one place, `sim/`, and the VM consumes -it. Scenario tests: `qemu/tests/boot-smoke.sh` (CI, in kernel-build), -`portal-scenario.sh` (check-in + OTA offer download against flare-edge's mock -portal), `ui-shot.sh` (QMP screendump + touch injection). Division of labour -with the other sims: NPU/RGA/HPMCU *behavior* stays `sim/`; UI *rendering -development* stays `lvglsim`; the VM is where processes, the kernel, and the -network meet. §5 still applies — no behavioural sim, this one included, catches -memory-map faults; and "boots under emulation" is never on-silicon evidence. +- The canonical RV1106 zImage boots virt unmodified; an additive fragment + (`qemu/configs/virt.fragment` via `WARDEN_KCONFIG_FRAGMENT`) adds the + scenario devices (PCI serial, i6300esb watchdog, WireGuard, + virtio-gpu/input for the 720x720 UI). +- The virtio disk carries the device's exact 12-partition `blkdevparts=` + A/B layout and the `/dev/block/by-name/` contract. +- The guest runs the *real* binaries; `qemu/rs485-bridge/` connects a QEMU + serial chardev to `sim/`'s `ModbusSlave`, so bus behavior lives once, in + `sim/`, and the VM consumes it. +- Division of labour: NPU/RGA/HPMCU *behavior* stays `sim/`; UI *rendering + development* stays `lvglsim`; the VM is where processes, the kernel, and + the network meet. §5 still applies — no behavioural sim catches memory-map + faults — and "boots under emulation" is never on-silicon evidence. ## 8. Order of Work -1. **Simulator core** — `membus`, `hpmcu`, the `cru` reset ladder, `modbus`, plus the - `rga`/`npu` models. **Done.** -2. **C-driver MC/DC harnesses** — `relays.c` and `freshness.c` at 100% MC/DC, CI-gated - via the shared `drivers/enforce-mcdc.sh`. **Done** (the first C coverage gate). -3. **flared devmem/hpmcu seam + tests** — firmware-side trait, unified with `sim/` - once flare-edge consumes this repo (a separate, maintainer-gated step). **Pending.** -4. **Config-lint CI gates** (§5) — the brick-class of bug. **Done.** -5. **Hermetic kernel build** (`build/build-kernel.sh` + the `patches-apply` gate). **Done.** -6. **Kernel 5.10→6.18.46 forward-port** (§6, ADR-0001). **Done** (hardware-verified). -7. **QEMU device sim** (§7, ADR-0006) — boot smoke, A/B disk harness, RS485 bridge, - portal/watchdog/clock scenarios, display+touch. **Done** (emulation-verified; - booting the real flare-edge rootfs+oem image pair is a documented later milestone). +1. **Simulator core** — `membus`, `hpmcu`, `cru`, `modbus`, `rga`, `npu`. + **Done.** +2. **C-driver MC/DC harnesses** — `relays.c` + `freshness.c` at 100%, + CI-gated. **Done.** +3. **flared devmem/hpmcu seam + tests** — unified with `sim/`. **Done** + (2026-08-31, flare-edge PR #110). +4. **Config-lint CI gates** (§5) — the brick class of bug. **Done.** +5. **Hermetic kernel build** + the `patches-apply` gate. **Done.** +6. **Kernel 5.10 → 6.18.46 forward-port** (§6, ADR-0001). **Done** + (hardware-verified). +7. **QEMU device sim** (§7, ADR-0006) — boot smoke, A/B disk harness, RS485 + bridge, portal/watchdog/clock/OTA scenarios, display+touch, real-image + boot. **Done** (emulation-verified). diff --git a/docs/ci-cd.md b/docs/ci-cd.md index b21fc62..6902140 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -20,12 +20,11 @@ execute code on private infrastructure (ADR-0007). ## Runner History -`kernel-build` originally ran on a repo-scoped self-hosted runner (ADR-0004, -2026-08-25, verified end-to-end) because hosted minutes were metered on the -private repo. Going public made hosted minutes free and made a self-hosted -registration a liability, so ADR-0007 moved the job to `ubuntu-latest` and -retired the registration. Site-specific install records for that runner live -in our private deployment log, not here. +`kernel-build` originally ran on a repo-scoped self-hosted runner (ADR-0004) +because hosted minutes were metered on the private repo. Going public made +hosted minutes free and a self-hosted registration a fork-PR liability, so +ADR-0007 retired it. That runner's site records live in the private +deployment log, not here. ## Badges diff --git a/patches/README.md b/patches/README.md index d8dfcd4..0b330a3 100644 --- a/patches/README.md +++ b/patches/README.md @@ -31,8 +31,7 @@ with `../build/warden_defconfig` → `zImage` + `rv1106-warden.dtb`). Everything in this directory is a derivative work of the Linux kernel and of GPL-2.0 vendor kernel code: **GPL-2.0-only** (or the per-file SPDX identifier -where one is present), the same license the whole repository carries. Per-driver -origin and license are tracked in `kernel/rv1106-enablement/PROVENANCE.md`. +where one is present), the same license the whole repository carries. ## Provenance & regeneration diff --git a/qemu/README.md b/qemu/README.md index c223a74..f371482 100644 --- a/qemu/README.md +++ b/qemu/README.md @@ -1,14 +1,11 @@ # The Device Simulator -A QEMU virtual machine that boots the real forward-ported kernel (`build/` + -`patches/`) and real userspace, so the 86 Panel — init, daemons, networking, -OTA, watchdog, display — can be developed and tested with no board attached. -It is the third simulator in the stack (the root README's table has the -three-way split), deliberately not named "sim": where `sim/` models -registers, this runs the whole machine above the kernel entry point on real -binaries — bring your own, or drop prebuilt payloads in `payload/`. - -Decision record: `docs/decisions/0006-qemu-device-sim.md`. +A QEMU virtual machine that boots the real forward-ported kernel and real +userspace: the 86 Panel — init, daemons, networking, OTA, watchdog, display — +developed and tested with no board attached. The third simulator in the stack +(three-way split: root README), deliberately not named "sim": it runs the +whole machine above the kernel entry point on real binaries — bring your own, +or drop prebuilt payloads in `payload/`. Decision record: ADR-0006. ## The Boundary @@ -63,37 +60,27 @@ stage-2 init when present. ## Scenarios -- `boot-smoke.sh ` — sentinel-asserting boot; runs in CI inside the - kernel-build job. -- `portal-scenario.sh ` (needs `FLARE_EDGE=`) — the real - flared in the VM against the desk mock portal: authenticated check-in, - firmware desired-state pull, and download of a real signed tier-1 `.wfw` - offer. Verify/stage/APPLYING run as a dry run (no `WARDEN_FW_ALLOW_APPLY`); - flipping it on inside the VM is the documented stretch — apply writes - `/dev/block/by-name/rootfs_b` inside disk.img, then `--slot _b` boots it. -- `ui-shot.sh ` — display+touch: boots headless with virtio-gpu, - QMP-screendumps the 720x720 UI, taps the Metrics tab via `input-send-event` - (a 200 ms hold — an instantaneous press+release lands inside one LVGL poll - and never clicks), and asserts the frame changed. `qmp.py` is the tiny QMP - client. -- `ota-apply.sh ` (needs `FLARE_EDGE`) — the FULL apply loop the - portal scenario stops short of: a real signed tier-1 `.wfw` whose payload - is a bootable rootfs is pulled, verified, WRITTEN to rootfs_b - (`run.sh --allow-apply` gates it per boot), the AvbABData in `misc` is - flipped (mkimage provisions real A/B metadata), and the harness reboots - slot `_b` and asserts the applied version is running. The BCB slot CHOICE - and the physical reset stay emulated by the harness (ADR-0006 boundary); - the VM exports `WARDEN_HARD_RESET=0` so flared's post-apply reset surfaces - as a clean reported error instead of a /dev/mem fault. -- `real-image-boot.sh ` — the real-image - milestone: an ACTUAL flare-edge build (matched pair, placed by - `mkimage.sh --rootfs-image/--oem-image`) boots through its own init chain - to a getty on the VM console; real daemons start. RV1106-only init steps - degrade as documented, and binaries predating known fixes reproduce their - bugs faithfully (a feature: the VM is a time machine for field issues). -- Watchdog: `run.sh --watchdog`, arm `/dev/watchdog` in the guest, don't pet — - the VM resets ~30 s later (verified). Do NOT combine with a flared payload - expecting survival: flared pets only while the UI heartbeat is fresh. +All take the virt-fragment ``; `FLARE_EDGE=` where noted. + +| Scenario | Needs | Proves | +|---|---|---| +| `boot-smoke.sh` | — | sentinel-asserting boot; runs in CI inside kernel-build | +| `portal-scenario.sh` | `FLARE_EDGE` | real flared against the desk mock portal: authenticated check-in, desired-state pull, signed tier-1 `.wfw` download; verify/stage/APPLYING as a dry run (no `WARDEN_FW_ALLOW_APPLY`) | +| `ota-apply.sh` | `FLARE_EDGE` | the FULL apply: the `.wfw`'s bootable rootfs payload is written to rootfs_b (`run.sh --allow-apply` gates it per boot), the AvbABData in `misc` flips, and slot `_b` boots the applied version | +| `ui-shot.sh` | — | display+touch, headless: QMP-screendumps the 720x720 UI, taps the Metrics tab via `input-send-event`, asserts the frame changed (`qmp.py` is the QMP client) | +| `real-image-boot.sh` | matched `rootfs.img` + `oem.img` | an ACTUAL flare-edge build (placed by `mkimage.sh --rootfs-image/--oem-image`) boots its own init chain to getty; binaries predating known fixes reproduce their bugs faithfully — a time machine for field issues | +| watchdog (`run.sh --watchdog`) | — | arm `/dev/watchdog`, don't pet: the VM resets ~30 s later (verified) | + +Scenario fine print: + +- OTA: the BCB slot CHOICE and the physical reset stay emulated by the + harness (ADR-0006 boundary); the VM exports `WARDEN_HARD_RESET=0` so + flared's post-apply reset surfaces as a reported error, not a /dev/mem + fault. +- Touch injection holds 200 ms — an instantaneous press+release lands inside + one LVGL poll and never clicks. +- Watchdog + a flared payload don't mix: flared pets only while the UI + heartbeat is fresh. ## Gotchas