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.
This commit is contained in:
@@ -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).
|
||||
|
||||
+111
-141
@@ -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).
|
||||
|
||||
+5
-6
@@ -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
|
||||
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
|
||||
+27
-40
@@ -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 <zImage>` — sentinel-asserting boot; runs in CI inside the
|
||||
kernel-build job.
|
||||
- `portal-scenario.sh <zImage>` (needs `FLARE_EDGE=<checkout>`) — 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 <zImage>` — 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 <zImage>` (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 <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.
|
||||
All take the virt-fragment `<zImage>`; `FLARE_EDGE=<checkout>` 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user