Files
bfe-core1106-sdk/docs/architecture.md
NoahandClaude Opus 5 120016f54a Enable bridging in the kernel config
The vendor 5.10 kernel is built without CONFIG_BRIDGE, and that absence has
shaped the firmware: the panel's access point has to be its own subnet with its
own DHCP pool instead of joining the LAN, and warden_router_set_zone() treats a
second LAN port as impossible. Both carry a code comment saying "this kernel has
no bridge".

Turning it on is what lets a Client-mode panel run an access point the way an
ordinary ceiling AP does -- wlan0 and the wired uplink in one broadcast domain,
addresses handed out by whatever router is already upstream.

Verified on the bench panel: before, `ip link add type bridge` returned
"Operation not supported"; on this kernel the bridge is created and eth0
enslaves into it (/sys/class/net/br0/brif/eth0). WireGuard, iptables and VLAN
still work.

Also fixes a warden-modbus path in architecture.md left stale by the apps/ move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T2D2KtdgwbhbF6Mo64eUrn
2026-09-03 18:16:37 -06:00

167 lines
9.1 KiB
Markdown

# 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 | `apps/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).