From 8677f8d06107b9710fa885b639abb13a5f23e732 Mon Sep 17 00:00:00 2001 From: BFE Engineering Date: Mon, 31 Aug 2026 08:19:51 -0600 Subject: [PATCH 1/2] docs: tighten README and core docs Second conciseness pass per review feedback: bullets and tables over prose (vendor-SDK failings, repo layout, sim models, kernel-port rationale, QEMU scenarios), duplicated content cut (provenance pointer, scenario prose the qemu README already carries). Also fixes two stale claims in architecture.md: the flared seam unification and real-image boot both landed (flare-edge #110, PR #8) but were still marked pending. --- README.md | 120 +++++++++------------ docs/architecture.md | 252 +++++++++++++++++++------------------------ docs/ci-cd.md | 11 +- patches/README.md | 3 +- qemu/README.md | 67 +++++------- 5 files changed, 197 insertions(+), 256 deletions(-) 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 From e3a6026ba5a3c2d478180dda33dbebf00a936b73 Mon Sep 17 00:00:00 2001 From: BFE Engineering Date: Mon, 31 Aug 2026 08:27:42 -0600 Subject: [PATCH 2/2] docs: README leads with the vendor-SDK comparison Drop the Why / What Works sections and the product-origin story; the text before Quick Start is now one comparison table of the upgrades over the vendor SDK. Coverage methodology stays in the internal docs as the reliability standard rather than the project's identity. --- README.md | 60 +++++++++++++++++++------------------------------------ 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 0134af4..5092d76 100644 --- a/README.md +++ b/README.md @@ -6,42 +6,24 @@ ![Coverage](.github/badges/coverage.svg) A modern, open development environment for the **Luckfox Pico 86 Panel** -(Rockchip RV1106): a current Linux kernel as a reviewable patch series, a -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 — 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. +(Rockchip RV1106), replacing the vendor SDK — and honest about what runs on +real silicon versus what is simulated. -## Why - -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 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). +| | Vendor SDK | This repo | +|---|---|---| +| **Kernel** | 5.10.160, twice-forked, frozen | **6.18.46** — a reviewable, subsystem-split patch series onto pristine upstream; full peripheral set (display, touch, wifi, audio, NPU, ...) hardware-verified on a bench panel | +| **Build** | ~2 GB tree, absolute paths baked in, Kconfig options silently dropped | one hermetic script: sha256-pinned source fetch, fail-closed patch apply and config fragments | +| **Off-device testing** | none — every change means flashing a panel | register-level hardware models (`sim/`) plus a QEMU device VM booting the real kernel, real daemons, and the real UI with display + touch | +| **Config safety** | memory-map mistakes reach hardware (one bricked a bench unit) | static gates (`tools/config-lint`) catch them before any flash | +| **CI** | none | hosted pipeline: tests, coverage, benchmarks, patch-apply gate, kernel build with an in-CI QEMU boot smoke | +| **Flashing tools** | closed (`upgrade_tool`) | open (`rkdeveloptool`) | +| **License** | mixed | **GPL-2.0-only**, with a per-driver provenance ledger | ## Quick Start Requirements: `gcc-arm-linux-gnueabihf`, `qemu-system-arm`, `curl`, `cpio`, `mkfs.ext4`, a bare `python` on PATH (Debian/Ubuntu: `python-is-python3`), -gcc >= 14 (for the MC/DC gate), and Rust (for the simulators' tests). +gcc >= 14 (driver harnesses), and Rust (for the simulators' tests). ```sh # 1. Build the kernel: fetch pinned pristine 6.18.46, apply patches/, emit @@ -57,7 +39,7 @@ bash qemu/run.sh --kernel $HOME/kbuild-out/linux-6.18.46/arch/arm/boot/zImage -- for d in sim tools/config-lint qemu/rs485-bridge; do (cd "$d" && cargo test) done -for d in drivers/*/test; do make -C "$d" check; done # 100% MC/DC gate (gcc >= 14) +for d in drivers/*/test; do make -C "$d" check; done # driver harnesses (gcc >= 14) ``` Add `WARDEN_KCONFIG_FRAGMENT=qemu/configs/virt.fragment` to step 1 for the @@ -72,7 +54,7 @@ scenario tests (portal, OTA apply, display + touch, watchdog). | `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 | +| `drivers/` | hardened hardware-facing drivers: HAL seams, test 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 | @@ -81,9 +63,9 @@ scenario tests (portal, OTA apply, display + touch, watchdog). 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 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`. +on the device or a simulated backend on the host. The driver test harnesses +measure against the same seam the simulator implements, so the two reinforce +each other. Full detail: `docs/architecture.md`. | Simulator | Runs | Proves | |---|---|---| @@ -106,10 +88,10 @@ the simulators narrow which claims need a panel. ## Downstream -WardenOS (the 86 Panel firmware this SDK was born for) consumes this repo -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 — real over-the-air updates included. +A private firmware repo (flare-edge) consumes this SDK; issue references and +checkout paths pointing there are engineering context, not reachable links. +The QEMU simulator runs its production binaries unmodified — real +over-the-air updates included. ## License