Merge pull request #13 from blueflare-energy/docs-concise

docs: tighten README and core docs
This commit is contained in:
2026-08-31 12:04:29 -04:00
committed by GitHub
5 changed files with 192 additions and 269 deletions
+48 -80
View File
@@ -6,46 +6,24 @@
![Coverage](.github/badges/coverage.svg) ![Coverage](.github/badges/coverage.svg)
A modern, open development environment for the **Luckfox Pico 86 Panel** A modern, open development environment for the **Luckfox Pico 86 Panel**
(Rockchip RV1106): a current Linux kernel as a reviewable patch series, a (Rockchip RV1106), replacing the vendor SDK — and honest about what runs on
hermetic build, a QEMU device simulator, register-level hardware models, and real silicon versus what is simulated.
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 | | Vendor SDK | This repo |
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 | **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 |
it on every change. | **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 |
## Why | **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 |
The vendor SDK bakes in absolute paths, silently drops Kconfig options, and | **Flashing tools** | closed (`upgrade_tool`) | open (`rkdeveloptool`) |
offers **no way to test hardware-dependent | **License** | mixed | **GPL-2.0-only**, with a per-driver provenance ledger |
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.
## 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`.
## Quick Start ## Quick Start
Requirements: `gcc-arm-linux-gnueabihf`, `qemu-system-arm`, `curl`, `cpio`, Requirements: `gcc-arm-linux-gnueabihf`, `qemu-system-arm`, `curl`, `cpio`,
`mkfs.ext4`, a bare `python` on PATH (Debian/Ubuntu: `python-is-python3`), `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 ```sh
# 1. Build the kernel: fetch pinned pristine 6.18.46, apply patches/, emit # 1. Build the kernel: fetch pinned pristine 6.18.46, apply patches/, emit
@@ -61,74 +39,64 @@ 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 for d in sim tools/config-lint qemu/rs485-bridge; do
(cd "$d" && cargo test) (cd "$d" && cargo test)
done 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)
``` ```
The kernel variant with the simulator's extra devices (PCI serial, watchdog, Add `WARDEN_KCONFIG_FRAGMENT=qemu/configs/virt.fragment` to step 1 for the
WireGuard, display) adds one env var to step 1: kernel variant with the simulator's extra devices; `qemu/README.md` has the
`WARDEN_KCONFIG_FRAGMENT=qemu/configs/virt.fragment`. See `qemu/README.md` scenario tests (portal, OTA apply, display + touch, watchdog).
for the scenario tests (portal, OTA apply, display + touch, watchdog).
## Layout ## Layout
``` | Directory | Contents |
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) | `patches/` | the RV1106 forward-port onto pristine linux-6.18.46, subsystem-split |
qemu/ the device simulator: QEMU -M virt boots the real kernel and real userspace; | `build/` | hermetic kernel build: pinned fetch → apply patches → `zImage` + dtb |
A/B disk layout, RS485 bridge into sim/, scenario tests | `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 | `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 | `drivers/` | hardened hardware-facing drivers: HAL seams, test harnesses |
kernel/ forward-port provenance and bring-up records (point-in-time; patches/ is canonical) | `kernel/` | forward-port provenance and bring-up records (`patches/` is canonical) |
tools/ config-lint (static memory-map gates) and dev tooling | `tools/` | `config-lint` (static memory-map gates) and dev tooling |
docs/ architecture, ADRs (decisions/), CI/CD | `docs/` | architecture, ADRs (`decisions/`), CI/CD |
```
## Architecture ## Architecture
One thin **hardware abstraction seam** per block (a trait in Rust, a function 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 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 on the device or a simulated backend on the host. The driver test harnesses
the host. The same seam is what the driver-hardening effort measures MC/DC measure against the same seam the simulator implements, so the two reinforce
against and what the simulator implements, so the two reinforce rather than each other. Full detail: `docs/architecture.md`.
duplicate each other. Full detail: `docs/architecture.md`.
Three simulators, by design not one: | Simulator | Runs | Proves |
| Simulator | What it runs | What it proves |
|---|---|---| |---|---|---|
| `sim/` | register-level Rust models | driver and supervisor logic, with fault injection | | `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 | | `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 With the production UI binary in `qemu/payload/`, `run.sh --display on` opens
`qemu/payload/`, `run.sh --display on` opens the panel's 720x720 screen in a the panel's 720x720 screen in a window, mouse clicks landing as touch —
window with mouse clicks landing as touch — device and UI in one VM device and UI in one VM. Emulation results are never on-silicon evidence;
(headless + scripted: `qemu/tests/ui-shot.sh`). the simulators narrow which claims need a panel.
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.
## Principles ## Principles
- **Open**: open tools over closed ones (`rkdeveloptool`, source-built - **Open** open tools over closed ones; GPL-2.0-only.
components, an open simulator); GPL-2.0-only. - **Hard** — every seam has a fault-injection path; recovery code is tested
- **Hard**: every seam has a fault-injection path — recovery code is tested
against failure, not just success. 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. Rust for new host-testable code, reproducible builds.
## Downstream ## Downstream
WardenOS (the 86 Panel firmware this SDK was born for) consumes this repo A private firmware repo (flare-edge) consumes this SDK; issue references and
from its own private repo, flare-edge; issue references and checkout paths checkout paths pointing there are engineering context, not reachable links.
pointing there are context, not reachable links. The QEMU simulator runs its The QEMU simulator runs its production binaries unmodified — real
production binaries unmodified — including real over-the-air updates against over-the-air updates included.
a mock portal.
## License ## License
**GPL-2.0-only**, repo-wide (see `LICENSE`; a per-file SPDX identifier **GPL-2.0-only**, repo-wide (`LICENSE`; a per-file SPDX identifier governs
governs where present). The kernel material in `patches/` and `kernel/` is where present). `patches/` and `kernel/` are derivative of the Linux kernel
derivative of the Linux kernel and GPL-2.0 vendor code; per-driver origin is and GPL-2.0 vendor code; per-driver origin is tracked in
tracked in `kernel/rv1106-enablement/PROVENANCE.md`. Contributions are `kernel/rv1106-enablement/PROVENANCE.md`. Contributions are accepted under
accepted under the same license (inbound = outbound). the same license (inbound = outbound).
+111 -141
View File
@@ -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" | | 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) | | 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 Three patterns coexist: **compile-time `#if`** (RGA), **env-override**
socket, misc dev, cmdline, hpmcu fw), and **fails-soft-because-the-path-is-absent** (modbus socket, misc dev, cmdline, hpmcu fw), and
(NPU, relays, devmem-would-fault). The last is not a test seam — you cannot inject **fails-soft-when-the-path-is-absent** (NPU, relays, devmem-would-fault). The
"relay 1 is ON" or "NPU at 80%", only "absent". The SDK's job is to turn all of last is not a test seam — you can inject "absent", never "relay 1 is ON" or
these into **one deliberate seam per block** with a real backend and a sim backend. "NPU at 80%". The SDK's job: **one deliberate seam per block**, each with a
real backend and a sim backend.
## 2. Seam Taxonomy ## 2. Seam Taxonomy
Two seam kinds cover everything above: Two seam kinds cover everything above:
- **Register/SRAM seam → a trait.** `MemBus` (`sim/src/membus.rs`): `peek32/poke32` - **Register/SRAM seam → a trait.** `MemBus` (`sim/src/membus.rs`):
at a physical address. Real backend = flared `devmem.rs` mmap; sim backend = `peek32/poke32` at a physical address. Real backend = flared `devmem.rs`
`SimBus` (in-memory word map, `Clone` so two "cores" alias shared memory). The mmap; sim backend = `SimBus`. The HPMCU watchdog and the CRU reset ladder
HPMCU watchdog and the CRU reset ladder both ride this. **Built.** both ride this. **Built.**
- **Resource-path seam → env-override + injection.** For file/socket/sysfs paths - **Resource-path seam → env-override + injection.** Every device/proc/sys
(`/proc/rknpu/load`, `/sys/class/gpio/*`, `/dev/ttyS4`, `misc`), generalize the path a driver opens resolves through one indirection
existing `WARDEN_MISC_DEV`/`WARDEN_MODBUS_SOCK` pattern into one rule: **every (`warden_hw_path("npu.load")` in C, an env-overridable const in Rust) —
device/proc/sys path a driver opens is resolved through a single indirection generalizing the existing `WARDEN_MISC_DEV`/`WARDEN_MODBUS_SOCK` pattern —
(`warden_hw_path("npu.load")` in C, an env-overridable const in Rust)**, so a so a test points it at a fake file/fifo the sim writes. No LD_PRELOAD, no
test points it at a fake file/fifo the sim writes. No LD_PRELOAD, no fake mounts. fake mounts.
RGA stays compile-time — its `#if WARDEN_USE_RGA` already cleanly isolates the RGA stays compile-time: `#if WARDEN_USE_RGA` already isolates the
librga/dma-heap calls behind the always-compiled LVGL draw-unit glue; the sim librga/dma-heap calls, and the sim backend (a recording `improcess` fake)
backend is "a fake `improcess` that records the blits it was asked to do", swapped swaps in behind the same `#if`, so the *dispatch* logic is tested even though
behind the same `#if`, so the offload *dispatch* logic gets tested even though the the blit is modelled.
blit itself is modelled.
## 3. The Register Simulator ## 3. The Register Simulator
A host Rust library modelling the hardware the vendor SDK cannot, so driver and A host Rust library modelling the hardware the vendor SDK cannot, so driver
supervisor logic runs in CI with no panel. and supervisor logic runs in CI with no panel. All models are **done**:
- **`membus` — register/SRAM bus.** Done. `MemBus` trait + `SimBus`. | Model | What it is |
- **`hpmcu` — the RISC-V watchdog coprocessor.** Done. Faithful port of |---|---|
`hpmcu/watchdog/main.c`'s state machine (boot-grace, heartbeat-timeout, disarm, | `membus` | `MemBus` trait + `SimBus` in-memory bus (`Clone`, so two "cores" alias shared memory) |
fire) against a `SimBus` mailbox, virtual clock, 7 tests including the | `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 |
arm-within-grace no-boot-loop safety property. This is the model that would have | `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) |
let the boot-loaded-watchdog logic be validated before the flash that bricked a | `modbus` | byte-in/byte-out RTU slave: CRC16 byte-identical to the master, FC 0x010x06/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) |
bench unit (though the *layout* fault — a load address in unreserved kernel RAM — | `npu` | `/proc/rknpu/load` text model behind the path seam — the load-readout UI is host-testable; NPU *compute* is out of scope |
is a target-config check, §5, not a sim property). | `rga` | recording `improcess` fake with programmable `IM_STATUS`, exercising offload-dispatch and CPU-fallback; wired into the `rga_improcess` benchmark |
- **`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 0x010x06/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.
Integration with flare-edge: flared implements `MemBus` for `/dev/mem` and gains **Next:** MEI (0x2B/0x0E) Modbus identification; the Tier-2 driver *sources*
`#[cfg(test)]` tests driving its real arm/beat logic against `HpmcuSim`. This needs migrate in with the flare-edge unification (ADR-0005) — their hardware ends
this repo reachable as a Cargo dependency in CI — i.e. a remote for this repo, are already modelled here.
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/` **Integration with flare-edge — landed 2026-08-31** (flare-edge PR #110):
once the dependency exists. No duplication of *logic* — only the tiny trait. `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 ## 4. Driver Hardening
"100% MC/DC on 100% of drivers" is infeasible literally: ~97% of driver LOC is "100% MC/DC on 100% of drivers" is infeasible literally ~97% of driver LOC
vendor blobs (AIC8800 wifi = 88.5K lines). Tiered target: 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` - **Tier 1 — our own hardware code → real 100% MC/DC.** Extract the unit
pattern — extract the unit behind a small injectable seam, mock its world, build behind a small injectable seam, mock its world, build
`-fcondition-coverage`, enforce with the shared `drivers/enforce-mcdc.sh` (gcc-14 `-fcondition-coverage`, enforce via the shared `drivers/enforce-mcdc.sh`
`gcov --conditions`) in the CI `mcdc` job. **Done here now:** `relays.c` (40/40 (gcc-14 `gcov --conditions`) in the CI `mcdc` job — the proven flare-edge
conditions) and `freshness.c` (66/66), both at 100% MC/DC and CI-enforced. **Migrate `tests/uboot-ab` method. **Done:** `relays.c` (40/40 conditions) and
in next:** the modbus master (`modbus_engine.c`) and the RGA wrapper's dispatch — `freshness.c` (66/66). **Next in:** the modbus master and the RGA
their hardware ends are already modelled and tested in `sim/` (`modbus`, `rga`); the dispatch, with the ADR-0005 unification (`drivers/README.md`).
driver sources move in with the flare-edge unification (ADR-0005). The HPMCU - **Tier 2 — near-mainline small drivers → branch coverage + fault
supervisor and devmem reset ladder are covered Rust-side (`sim/hpmcu`, `sim/cru`). injection.**
- **Tier 2near-mainline small drivers → branch coverage + fault injection.** - **Tier 3vendor blobs (AIC8800, MPP/ISP/RGA libs) → fault-injection
- **Tier 3 — vendor blobs (AIC8800, MPP/ISP/RGA libs) → fault-injection hardening hardening behind the seam**, not MC/DC: e.g. the SDIO-wedge recovery is
behind the seam,** not MC/DC. The AIC8800 SDIO-wedge Tier-1 fix + the designed tested against an injected wedge; the blob itself is untestable.
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.
Every seam gets a fault-injection mode (a wedged SDIO link, a stalled MCU 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 heartbeat, an RGA timeout, a GPIO write EIO) so recovery code is tested
failure, not just the happy path. against failure, not just the happy path.
## 5. Target-Config Checks ## 5. Target-Config Checks
The brick was a *memory-map* fault: the boot-loaded MCU's load address (`0x40000`) The brick was a *memory-map* fault: the boot-loaded MCU's load address
is a reserved carve-out on Thunder-Boot boards but plain kernel RAM on ours. No (`0x40000`) is a reserved carve-out on Thunder-Boot boards but plain kernel
behavioural sim catches that — it needs a **static check against the target DT**: RAM on the 86 Panel. No behavioural sim catches that — it takes a **static
"every address the MCU/coprocessor code loads to is inside a `reserved-memory` check against the target devicetree**, owned here as CI gates so mistakes
node." This repo owns these config-lint checks (idblock loader `.ini` vs DT are caught before a flash, not on the bench.
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.
**Built:** `tools/config-lint` implements the first and most important of these — - **Built:** `tools/config-lint` — every `LOADERn=Hpmcu` `LOAD_ADDR` in the
the MCU-load-vs-`reserved-memory` gate. It parses the rkbin loader `.ini` for rkbin loader `.ini` must land inside a DT `reserved-memory` range. Its
every `LOADERn=Hpmcu` firmware and its `[LOADERn_PARAM] LOAD_ADDR`, parses the test suite encodes the c8a3 brick itself (see its README).
target devicetree (`.dts`, or `dtc -I dtb` output in CI) for `reserved-memory` - **Next:** partition-table-vs-image-size; vermagic-vs-kernel.
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.
## 6. Kernel Forward-Port ## 6. Kernel Forward-Port
A self-built **Linux 6.18.46**, forward-ported directly from the vendor 5.10.160 tree **Done and hardware-verified** on the bench panel; the full peripheral set
onto our Buildroot LTS/uClibc base — **not** the plan44/OpenWrt 6.6 fork this section in the README's "What Works" boots.
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 - **Base:** a direct forward-port of the vendor 5.10.160 tree onto pristine
forward-port. Mainline alone was not viable either (no DT/clk/display/RGA/NPU/ 6.18.46, keeping the Buildroot LTS/uClibc userspace.
flash-boot upstream for RV1106); the port reuses the already-in-mainline rv1126 - **Not plan44/OpenWrt 6.6** (ADR-0001): that fork swaps Buildroot for
register data where it matches and carries our deltas as the reviewable `patches/` OpenWrt/musl and ships no AIC8800 kmod — a platform swap, not a port.
series. This is **done and hardware-verified on `warden-c8a3`** — clk, pinctrl, eMMC, - **Not mainline alone:** no RV1106 DT/clk/display/RGA/NPU/flash-boot
GMAC, TRNG, OTP, SARADC/TSADC, RTC, USB host, PWM/backlight, VOP display, GT911 touch, upstream; already-mainline rv1126 register data is reused where it
AIC8800 wifi, RGA, I2S audio, HPMCU mailbox, the open NPU driver, and PVTM all boot. matches.
The dominant risk was the struct-ABI break (the VLAN saga), mitigated by shipping the - **Dominant risk** was the struct-ABI break (the VLAN saga) mitigated by
kernel move as one matched boot+oem image, never a partial reflash. shipping kernel moves as one matched boot+oem image, never a partial
`build/build-kernel.sh` (the hermetic build) and the `patches-apply` CI gate keep the reflash.
series honest against pristine 6.18.46; provenance is in `patches/README.md` and - **Kept honest** by the hermetic `build/build-kernel.sh` and the
`kernel/rv1106-enablement/`. `patches-apply` CI gate against pristine 6.18.46; provenance in
`patches/README.md` and `kernel/rv1106-enablement/`.
## 7. Device Emulation ## 7. Device Emulation
The third simulator, deliberately not named "sim": a QEMU VM (`-M virt,highmem=off`, A QEMU VM (`-M virt,highmem=off`, one Cortex-A7, 256M — the RV1106G3's
one Cortex-A7, 256M — the RV1106G3's shape) that boots the real forward-ported shape) boots the real forward-ported kernel and real userspace, entering at
kernel and real userspace, entering at `-kernel zImage` because everything below `-kernel zImage`: everything below is closed rkbin blobs plus mask ROM.
(BootROM, idblock/DDR-init, U-Boot, the BCB A/B machinery) is closed blobs plus Details, scenarios, and the emulated-vs-not table: `qemu/README.md` and
mask ROM. The canonical RV1106 zImage boots virt unmodified; an additive kconfig ADR-0006.
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.
Where the seams meet: the guest runs the *real* binaries (static musl flared, - The canonical RV1106 zImage boots virt unmodified; an additive fragment
the LVGL fbdev UI); the RS485 bridge (`qemu/rs485-bridge/`) connects a QEMU (`qemu/configs/virt.fragment` via `WARDEN_KCONFIG_FRAGMENT`) adds the
serial chardev to `sim/`'s `ModbusSlave`, so the register-level models serve as scenario devices (PCI serial, i6300esb watchdog, WireGuard,
the VM's field bus — behavior lives in one place, `sim/`, and the VM consumes virtio-gpu/input for the 720x720 UI).
it. Scenario tests: `qemu/tests/boot-smoke.sh` (CI, in kernel-build), - The virtio disk carries the device's exact 12-partition `blkdevparts=`
`portal-scenario.sh` (check-in + OTA offer download against flare-edge's mock A/B layout and the `/dev/block/by-name/` contract.
portal), `ui-shot.sh` (QMP screendump + touch injection). Division of labour - The guest runs the *real* binaries; `qemu/rs485-bridge/` connects a QEMU
with the other sims: NPU/RGA/HPMCU *behavior* stays `sim/`; UI *rendering serial chardev to `sim/`'s `ModbusSlave`, so bus behavior lives once, in
development* stays `lvglsim`; the VM is where processes, the kernel, and the `sim/`, and the VM consumes it.
network meet. §5 still applies — no behavioural sim, this one included, catches - Division of labour: NPU/RGA/HPMCU *behavior* stays `sim/`; UI *rendering
memory-map faults; and "boots under emulation" is never on-silicon evidence. 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 ## 8. Order of Work
1. **Simulator core**`membus`, `hpmcu`, the `cru` reset ladder, `modbus`, plus the 1. **Simulator core**`membus`, `hpmcu`, `cru`, `modbus`, `rga`, `npu`.
`rga`/`npu` models. **Done.** **Done.**
2. **C-driver MC/DC harnesses**`relays.c` and `freshness.c` at 100% MC/DC, CI-gated 2. **C-driver MC/DC harnesses**`relays.c` + `freshness.c` at 100%,
via the shared `drivers/enforce-mcdc.sh`. **Done** (the first C coverage gate). CI-gated. **Done.**
3. **flared devmem/hpmcu seam + tests** firmware-side trait, unified with `sim/` 3. **flared devmem/hpmcu seam + tests** — unified with `sim/`. **Done**
once flare-edge consumes this repo (a separate, maintainer-gated step). **Pending.** (2026-08-31, flare-edge PR #110).
4. **Config-lint CI gates** (§5) — the brick-class of bug. **Done.** 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.** 5. **Hermetic kernel build** + the `patches-apply` gate. **Done.**
6. **Kernel 5.106.18.46 forward-port** (§6, ADR-0001). **Done** (hardware-verified). 6. **Kernel 5.106.18.46 forward-port** (§6, ADR-0001). **Done**
7. **QEMU device sim** (§7, ADR-0006) — boot smoke, A/B disk harness, RS485 bridge, (hardware-verified).
portal/watchdog/clock scenarios, display+touch. **Done** (emulation-verified; 7. **QEMU device sim** (§7, ADR-0006) — boot smoke, A/B disk harness, RS485
booting the real flare-edge rootfs+oem image pair is a documented later milestone). bridge, portal/watchdog/clock/OTA scenarios, display+touch, real-image
boot. **Done** (emulation-verified).
+5 -6
View File
@@ -20,12 +20,11 @@ execute code on private infrastructure (ADR-0007).
## Runner History ## Runner History
`kernel-build` originally ran on a repo-scoped self-hosted runner (ADR-0004, `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 because hosted minutes were metered on the private repo. Going public made
private repo. Going public made hosted minutes free and made a self-hosted hosted minutes free and a self-hosted registration a fork-PR liability, so
registration a liability, so ADR-0007 moved the job to `ubuntu-latest` and ADR-0007 retired it. That runner's site records live in the private
retired the registration. Site-specific install records for that runner live deployment log, not here.
in our private deployment log, not here.
## Badges ## Badges
+1 -2
View File
@@ -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 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 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 where one is present), the same license the whole repository carries.
origin and license are tracked in `kernel/rv1106-enablement/PROVENANCE.md`.
## Provenance & regeneration ## Provenance & regeneration
+27 -40
View File
@@ -1,14 +1,11 @@
# The Device Simulator # The Device Simulator
A QEMU virtual machine that boots the real forward-ported kernel (`build/` + A QEMU virtual machine that boots the real forward-ported kernel and real
`patches/`) and real userspace, so the 86 Panel — init, daemons, networking, userspace: the 86 Panel — init, daemons, networking, OTA, watchdog, display —
OTA, watchdog, display — can be developed and tested with no board attached. developed and tested with no board attached. The third simulator in the stack
It is the third simulator in the stack (the root README's table has the (three-way split: root README), deliberately not named "sim": it runs the
three-way split), deliberately not named "sim": where `sim/` models whole machine above the kernel entry point on real binaries — bring your own,
registers, this runs the whole machine above the kernel entry point on real or drop prebuilt payloads in `payload/`. Decision record: ADR-0006.
binaries — bring your own, or drop prebuilt payloads in `payload/`.
Decision record: `docs/decisions/0006-qemu-device-sim.md`.
## The Boundary ## The Boundary
@@ -63,37 +60,27 @@ stage-2 init when present.
## Scenarios ## Scenarios
- `boot-smoke.sh <zImage>` — sentinel-asserting boot; runs in CI inside the All take the virt-fragment `<zImage>`; `FLARE_EDGE=<checkout>` where noted.
kernel-build job.
- `portal-scenario.sh <zImage>` (needs `FLARE_EDGE=<checkout>`) — the real | Scenario | Needs | Proves |
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` | `boot-smoke.sh` | — | sentinel-asserting boot; runs in CI inside kernel-build |
offer. Verify/stage/APPLYING run as a dry run (no `WARDEN_FW_ALLOW_APPLY`); | `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`) |
flipping it on inside the VM is the documented stretch — apply writes | `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 |
`/dev/block/by-name/rootfs_b` inside disk.img, then `--slot _b` boots it. | `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) |
- `ui-shot.sh <zImage>` — display+touch: boots headless with virtio-gpu, | `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 |
QMP-screendumps the 720x720 UI, taps the Metrics tab via `input-send-event` | watchdog (`run.sh --watchdog`) | — | arm `/dev/watchdog`, don't pet: the VM resets ~30 s later (verified) |
(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 Scenario fine print:
client.
- `ota-apply.sh <zImage>` (needs `FLARE_EDGE`) — the FULL apply loop the - OTA: the BCB slot CHOICE and the physical reset stay emulated by the
portal scenario stops short of: a real signed tier-1 `.wfw` whose payload harness (ADR-0006 boundary); the VM exports `WARDEN_HARD_RESET=0` so
is a bootable rootfs is pulled, verified, WRITTEN to rootfs_b flared's post-apply reset surfaces as a reported error, not a /dev/mem
(`run.sh --allow-apply` gates it per boot), the AvbABData in `misc` is fault.
flipped (mkimage provisions real A/B metadata), and the harness reboots - Touch injection holds 200 ms — an instantaneous press+release lands inside
slot `_b` and asserts the applied version is running. The BCB slot CHOICE one LVGL poll and never clicks.
and the physical reset stay emulated by the harness (ADR-0006 boundary); - Watchdog + a flared payload don't mix: flared pets only while the UI
the VM exports `WARDEN_HARD_RESET=0` so flared's post-apply reset surfaces heartbeat is fresh.
as a clean reported error instead of a /dev/mem fault.
- `real-image-boot.sh <zImage> <rootfs.img> <oem.img>` — 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.
## Gotchas ## Gotchas