Files
bfe-core1106-sdk/docs/architecture.md
T
BFE Engineering 8677f8d061 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.
2026-08-31 08:19:51 -06:00

167 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture
How the SDK makes the 86 Panel buildable, testable, and hardenable without a
board in the loop. The seam inventory below comes from a full survey of the
downstream WardenOS firmware — the SDK's first consumer — not aspiration; the
file paths in it point into that (private) codebase and serve as engineering
context.
## 1. The Problem
The firmware touches RV1106 hardware through a *grab-bag* of mechanisms, each
tested (or not) differently. Today:
| Block | Where | Access | Test seam today | Fails on host by |
|---|---|---|---|---|
| Registers / SRAM (CRU reset, HPMCU mailbox) | `flared/src/devmem.rs`, `hpmcu.rs` | `/dev/mem` mmap `peek/poke32` | **none** — zero tests | (would fault; not exercised) |
| HPMCU / RISC-V coproc | `flared/src/hpmcu.rs` | via devmem + firmware blob load | `WARDEN_HPMCU_FW` redirects the blob path only | env gate disables it |
| NPU load | `ui-src/.../sysmon.c` | read `/proc/rknpu/load` | **none** — literal path | file absent → "NPU absent" |
| RGA (2D blit) | `ui-src/.../warden_rga.c` | `librga improcess` + dma-heap ioctl | compile-time `#if WARDEN_USE_RGA` | `#if` off → LVGL software path |
| RS485 daemon | `warden-modbus/modbus_engine.c` | `open("/dev/ttyS4")` | recompile `-DRS485_PORT=<pty>` | (recompiled for a pty) |
| RS485 panel client | `ui-src/.../modbus.c` | `AF_UNIX` socket | `WARDEN_MODBUS_SOCK` env override | socket 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) |
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`. 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: `#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. All models are **done**:
| 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 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) |
| `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 |
**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 alone is 88.5K lines) — so the target is
tiered:
- **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.
## 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 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` — 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
**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
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.
- 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`, `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).