Merge pull request #13 from blueflare-energy/docs-concise
docs: tighten README and core docs
This commit is contained in:
@@ -6,46 +6,24 @@
|
|||||||

|

|
||||||
|
|
||||||
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
@@ -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 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) |
|
||||||
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 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.
|
|
||||||
|
|
||||||
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 2 — near-mainline small drivers → branch coverage + fault injection.**
|
- **Tier 3 — vendor 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.10→6.18.46 forward-port** (§6, ADR-0001). **Done** (hardware-verified).
|
6. **Kernel 5.10 → 6.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
@@ -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
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user