Files
bfe-core1106-sdk/docs/architecture.md
T
BFE Engineering 42fb386f60 docs: ASCII typography and style normalization across all repo text
Replace every em dash with real punctuation (rewrites, not hyphen swaps)
in docs, code comments, scripts, configs, and the port records; convert
en dashes, curly quotes, ellipsis glyphs, arrows, and section signs to
ASCII; drop machine-writing tell phrases from living docs. ADR titles
now use a colon. The M2 bring-up DTS model string carried an em dash
into the patch series and its record echoes; fixed at both, and the full
series re-verified to apply cleanly onto pristine 6.18.46. One comment
in freshness.h deliberately names the em dash glyph the UI renders as
the unknown mark; that is data, kept as prose naming it.

Verified: cargo tests (sim, config-lint, rs485-bridge), shellcheck,
both driver MC/DC harnesses, patches-apply.
2026-08-31 12:31:27 -06:00

9.1 KiB

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 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/ (section 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. section 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 (section 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 (section 6, ADR-0001). Done (hardware-verified).
  7. QEMU device sim (section 7, ADR-0006): boot smoke, A/B disk harness, RS485 bridge, portal/watchdog/clock/OTA scenarios, display+touch, real-image boot. Done (emulation-verified).