- build-kernel.sh fragment assertion: survives a missing trailing newline (the read footgun, reproduced) and treats an absent symbol on a disable line as FATAL, symmetric with the enable arm. - fetch-kernel-tarball.sh checks the pin before downloading; both fetchers add --retry-connrefused. - mkimage rejects '.'/'..' state keys. - ui-shot: VM liveness checked before every QMP call, console.log preserved as evidence on every failure path, repaint deadline widened to 90s with the contended-runner rationale documented. - rs485-bridge: overflow discards back off one gap and rate-limit their log line, mirroring the accept-loop fix; clippy nit fixed. - .gitignore ignores *.elf/*.map so the untracked artifacts cannot silently return; CI shellcheck glob now covers build/ and the rootfs boot scripts (directives added for the deliberate in-guest source paths). - Docs: NPU parity row matches its sibling verification docs; line-pinned audit cross-references unpinned; CROSS_COMPILE documented in the build header; payload README lists warden-ui; ci-cd tense settled. Verified: guards negative-tested (bad state keys, no-newline fragment); boot smoke, portal scenario, ui-shot all PASS; 53 tests green; shellcheck clean across the widened glob; clippy zero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
15 KiB
HPMCU mailbox to 100% — plan, feasibility, hardware round-trip verify
Goal: a working, fully-open A7 ↔ HPMCU (SCR1) mailbox link on our self-built Linux 6.18.46, verified by a message round-trip over serial on hardware.
Bottom line up front: this is genuinely achievable and far more tractable than
the NPU. Every piece is open (GPL kernel driver + our own bare-metal SCR1 firmware),
the controller driver already binds on our 6.18 kernel with zero code change,
and we already have a hardware-validated way to load and run custom firmware on
the SCR1 core. The only real work is: (1) enable the controller in DT + config,
(2) write a ~dozen-line SCR1 echo handler, (3) write a small Linux client, and
(4) run the round-trip on warden-c8a3 (the recovery-capable rig) over serial.
The one substantive design decision — hardware mailbox IP vs. our current
/dev/mem polled-SRAM scheme — is settled below: build the real mailbox link
(it is the open, IRQ-driven, general-purpose answer), but keep the proven SRAM
watchdog exactly as-is (different threat model, different job).
1. Feasibility — honest assessment
Fully feasible. Two halves, both open, both with working reference code to copy.
- Controller (Linux side): non-issue.
drivers/mailbox/rockchip-mailbox.cis upstream in mainline 6.18 and already binds on our exact kernel via the genericrockchip,rk3368-mailboxfallback compatible with zero patching — recorded inCAPABILITIES-AUDIT.md's Remaining-blocks table (mailbox row), confirmed by source read. RV1106's DT declares both instances with that fallback string. Gated today only bystatus="disabled"+CONFIG_ROCKCHIP_MBOXbeing absent from the defconfig. - HPMCU firmware (MCU side): we already do the hard part. WardenOS has a
hardware-validated (2026-08-14, PR #25) bare-metal SCR1 firmware built with
the vendor's own
sysdrv/source/mcu/toolchain (xPackriscv-none-embed-gcc10.2.0,-march=rv32imc), loaded via/dev/mem-mmap byflared/src/hpmcu.rs, running a real state machine and firing a real CRU reset. Adding a mailbox echo handler to that firmware is small, additive work — and the vendor ships a register-level template for the MCU side (hal_mbox.c) plus a working two-ended example (battery-ipc/stream.c+rockchip_thunderboot_service.c). - The only genuinely new/greenfield piece is that no general Linux-side mailbox
client for RV1106 exists in-tree (the one real client,
rockchip_thunderboot_service.c, is a hardwired one-shot{0xf00d,0xdeadbeef}"MCU done" signal, and isn't in our board's DT). But a mailbox client using the stock upstreammailbox_client.hAPI is a small, well-understood piece of code — not a driver port.mailbox-test.c(stock kernel debugfs exerciser, present in our tree,compatible="mailbox-test") lets us prove the Linux→controller path before writing any client at all.
Confidence in a verifiable open link: high. The only area flagged risky is
starting the SCR1 with our own entry point — and we have already solved and
hardware-proven exactly that (the hpmcu.rs load/release sequence), so it is a
known quantity here, not the open problem it would be for a team starting cold.
Decision — hardware mailbox vs. /dev/mem SRAM: build the hardware mailbox
as the open, general, IRQ-driven bidirectional channel (this is "the real mailbox
client" the task wants). Keep the existing SRAM polled-word watchdog untouched —
ADR-0002 deliberately chose it as a dead-man's-switch (no IRQ, no dependency on the
mailbox controller being up, survives A7 hangs by design). They coexist: the SRAM
word is the safety watchdog; the mailbox is the general IPC channel. Do not rip
out hpmcu.rs's watchdog to route it through the mailbox — that would trade a
proven fail-safe for a more complex path with no safety gain.
Do NOT (for a first cut): port rockchip_rpmsg.c or NXP rpmsg-lite. Both are
real Rockchip patterns but genuinely un-ported to RV1106 (rpmsg-lite has platform
files only for RK3308/RK3568; rockchip_rpmsg.c matches only rk3562/rk3568-rpmsg;
neither is upstream in mainline; CONFIG_RPMSG_ROCKCHIP is # not set on every
RV1106 defconfig). They add virtio/vring machinery we don't need to prove a link.
If a richer multi-message channel is later wanted, layer a small hand-rolled ring
buffer in hpmcu_sram on top of the doorbell — that's the natural next step, not
a full rpmsg port.
2. Key technical facts
The two mailbox instances (both status="disabled" in base rv1106.dtsi)
| Node | Base | IRQ | Clock | Consumer | Use |
|---|---|---|---|---|---|
mailbox@ff5c0000 |
0xff5c0000 (reg size 0x200) |
GIC_SPI 1 |
PCLK_MAILBOX |
thunder-boot-service (ch1, "amp-rx") — not on our board |
HPMCU-connected — use this one |
pmu_mailbox@ff378000 |
0xff378000 (0x200) |
GIC_SPI 114 |
PCLK_PMU_MAILBOX |
none anywhere | PMU-domain, undocumented purpose — avoid |
@ff5c0000 is the HPMCU-connected instance, proven from both ends: the Linux
thunder_boot_service node's resets are literally named SRST_CORE_MCU*; the
MCU-side vendor CMSIS header hard-codes #define MBOX_BASE 0xFF5C0000U
(.../hal/lib/CMSIS/Device/RV1106/Include/rv1106.h:815) — the same physical
address the A7 sees. Both clock IDs are real CRU gates (PCLK_MAILBOX at
clk-rv1106.c:343, PCLK_PMU_MAILBOX at :737), so the fallback-compatible match
is trustworthy: the register block is IP-identical to rk3368's.
Register model (fixed offsets, hardcoded in the driver; identical both instances)
MAILBOX_A2B_INTEN 0x00 // AP -> MCU direction
MAILBOX_A2B_STATUS 0x04
MAILBOX_A2B_CMD(x) 0x08 + x*8 // x = channel 0..3
MAILBOX_A2B_DAT(x) 0x0c + x*8
MAILBOX_B2A_INTEN 0x28 // MCU -> AP direction
MAILBOX_B2A_STATUS 0x2C
MAILBOX_B2A_CMD(x) 0x30 + x*8
MAILBOX_B2A_DAT(x) 0x34 + x*8
4 channels, 32-bit {cmd,data} per message per direction. Doorbell + 8-byte
payload — NOT a bulk channel. Sender writes CMD then DAT (two writel_relaxed,
fire-and-forget, no ack/poll — rockchip-mailbox.c:46-70); the peer's STATUS bit
raises an IRQ; receiver reads CMD/DAT, dispatches to the registered client via
mbox_chan_received_data, clears the STATUS bit to ack. #mbox-cells = <1>
(channel index). Exported rockchip_mbox_read_msg() (EXPORT_SYMBOL_GPL,
rockchip-mailbox.c:108-125) pulls the last {cmd,data} pair out for a client.
Only OF match in the driver: "rockchip,rk3368-mailbox" → .num_chans = 4.
HPMCU / SCR1 facts (from our own hardware-validated work)
- SCR1 = Syntacore RV32IMC, machine-mode only, 16KB unified cache, "HPMCU".
- On our board the SCR1 is idle from reset (non-TB
RV1106MINIALL.ini, noHpmcu=loader stage) — a clean slate, not even the stock camera-AE blob. - Usable MCU code SRAM is a hard 8KB:
hpmcu_sramat offset0x3e000insidesystem_sram@ff6c0000→ absolute0xFF6FE000, 8KB (reg=<0x3e000 0x2000>,rv1106.dtsi:1149-1151), shared region with 248KBrkisp_sram. Our firmware + mailbox echo handler must fit this. - Proven load/release sequence (
flared/src/hpmcu.rs:26-76, hardware-validated):CORECRU_CORESOFTRST_CON01(0xff3b8000+0xa04) hold0x1e001e→ write firmware to0xff6fe000→SGRF_HPMCU_BOOT_ADDR(0xff076000+0x44) = load addr → release CORECRU to0x1e0000. This is the WiFi-independent way we start the core with our own entry point — the piece source-level analysis flagged as unknown is already solved and proven here. - MCU-side reference to copy:
hal_mbox.c/hal_mbox.h(generic 4-channel register driver,HAL_MBOX_Init/send/ack) + working examplebattery-ipc/stream.c:380-425(sendsstruct MBOX_CMD_DAT {cmd,data}overMBOX_CH_1, client name"mcu-status"— exactly matches the Linuxstruct rockchip_mbox_msg).
Current comms mechanism (what exists today, keep it)
WardenOS's watchdog uses a software mailbox in shared SRAM, not the mailbox IP:
last 256 bytes of hpmcu_sram at absolute base 0xFF6FFF00 — +0x00 magic
(Linux: WARD/DISA), +0x04 counter (heartbeat), +0x08 mcu_state
(BOOT/ARMD/DISA/FIRE), +0x0c/+0x10 debug. 5s heartbeat, fires after 90s
no-advance. CRU reset via GLB_SRST_FST at 0xff3b0c08 magic 0xfdb9 (the
only working whole-SoC reset — reboot -f is a no-op: no PSCI/restart handler).
Reset constant is CI-guarded across 4 sites. dd of=/dev/mem faults for the SRAM
region on this ARM kernel — only the mmap() path writes (busybox devmem or
libc::mmap); STRICT_DEVMEM is off in our config.
The 0x40000 boot-load hazard (load-bearing — for any boot-time MCU path only)
Boot-loading firmware to the DDR carve-out 0x40000 (240KB) bricked warden-c8a3
on 2026-08-23 because our non-TB kernel DT does not reserve 0x40000 — it
collides with kernel RAM → early-boot hang before eth0. This mailbox plan avoids
the hazard entirely by using the runtime SRAM-load path (0xff6fe000), not a
boot-time DDR load. If a boot-time path is ever pursued, a reserved-memory DT node
for 0x40000/0x3c000 must be added and verified via /proc/iomem before
flashing the idblock. (Standing memory: boot-loaded-mcu-0x40000-hazard.md —
worth promoting into riscv-mcu.md open-questions; not yet captured there.)
URLs / upstream status
drivers/mailbox/rockchip-mailbox.c— upstream mainline v6.18 (Bootlin Elixir confirms), binds viark3368-mailboxfallback, zero patch.- No
drivers/rpmsg/rockchip*and nodrivers/remoteproc/rockchip*in mainline (GitHub API enumeration of both dirs — none Rockchip). Vendorrockchip_rpmsg.cnever merged;lore.kernel.org403-walled, submission history unpinnable. - Community RV1106 HPMCU prior art beyond LED-blink: ~zero (GitHub search
rv1106 hpmcu/rv1106 scr1 coprocessor→ only SDK/rkbin mirrors). ADR-0002: "expect to be first." Our PR #25 firmware is the only known custom SCR1 code.
3. The simplest fully-open, hardware-verifiable path (milestones)
M-MBOX-1 — Controller wiring proof (Tier 1: today, ZERO HPMCU risk, no firmware)
Proves DT status/clock/IRQ/probe/send all work on real hardware without touching the SCR1 at all.
- Kernel config:
CONFIG_ROCKCHIP_MBOX=y+CONFIG_MAILBOX_TEST=y(mainline driver, no patch —rockchip-mailbox.cbinds on the fallback compatible). - Board DTS: override
&mailbox { status = "okay"; };(the@ff5c0000instance — main CRU clock,GIC_SPI 1, no PMU-domain complications). Add acompatible = "mailbox-test"node withmboxes = <&mailbox N>on a free channel — avoid channel 1 (vendor reserves it for"amp-rx"semantics). - Verify on serial console (via the
warden-c8a3_b-slot one-shot loop): confirmrockchip-mailboxprobes clean indmesg; write an 8-byte{cmd,data}pair to the debugfsmessagefile (mailbox-test.c); then read back the physicalA2B_CMD(N)/A2B_DAT(N)registers directly with busyboxdevmemat0xff5c0008+8N/0xff5c000c+8Nand confirm the value landed. Proves the AP→ controller send path end-to-end, serial-only, no risk to the HPMCU.
M-MBOX-2 — Real round-trip: SCR1 echo + Linux client (Tier 2: the deliverable)
The actual "message round-trip over serial on hardware" the task asks for.
- SCR1 echo firmware (additive to our existing bare-metal firmware, must fit
the 8KB
hpmcu_srambudget): on A2B IRQ (or a tight poll ofA2B_STATUS— poll is simpler and safe for a bring-up echo, avoids MCU IRQ-controller setup), readA2B_CMD(x)/A2B_DAT(x), write them back toB2A_CMD(x)/B2A_DAT(x), set theB2A_STATUSbit, clearA2B_STATUS. Copy the register sequence from vendorhal_mbox.c/battery-ipc/stream.c. Load and start the core with the provenhpmcu.rsload/release sequence (SRAM path,0xff6fe000— not the0x40000boot path, hazard §2). - Linux client: a minimal in-tree
mbox_client(stockmailbox_client.h:mbox_request_channel_byname/mbox_send_message+ an rx callback reading via the exportedrockchip_mbox_read_msg()), or — to avoid writing a kernel client for the first proof — reusemailbox-test's debugfs send and read the B2A registers from userspace withdevmem. Prefer themailbox-testroute for M-MBOX-2's first light; promote to a realmbox_clientonce the round-trip is green. - Verify on serial console (
warden-c8a3,_b-slot): send a known{cmd,data}(e.g.{0x1234, 0xcafef00d}) from Linux; confirm the SCR1 echoed it back — either via the client's rx callback logging the received pair, or bydevmemreadingB2A_CMD(N)/B2A_DAT(N)(0xff5c0030+8N/0xff5c0034+8N) and matching it byte-for-byte against what was sent. Capture the serial transcript as the evidence. This is the 100%, verified, fully-open mailbox link.
Risk controls: all boot testing via the _b-slot one-shot loop (never touches
the working _a slot, auto-reverts on hang). The SRAM-load path sidesteps the
0x40000 brick hazard. The existing SRAM watchdog keeps running throughout,
independent of the mailbox, as it did during the 2026-08-14 validation.
M-MBOX-3 — (Optional, later) richer channel
Only if a real multi-message need appears: layer a small ring buffer in
hpmcu_sram, mailbox used as the doorbell ("look at address X"). Still do not
adopt rpmsg/virtio unless the payload complexity genuinely demands it.
4. Risks / open questions
- SCR1 IRQ setup — for M-MBOX-2 a polled echo avoids configuring the MCU's interrupt controller; fine for bring-up. IRQ-driven B2A on the Linux side works regardless (that's the controller's job). Note as a simplification, not a gap.
- Channel choice — use a channel ≠ 1 (ch1 = vendor
"amp-rx"reservation). pmu_mailbox@ff378000purpose — undocumented, no consumer anywhere; leave disabled, do not use, until a TRM section or Rockchip engineer clarifies.- RS-485 UART reachability from the HPMCU — never checked (pinmux vs Linux ownership); irrelevant to the mailbox link but open for future MCU apps.
- Whether Rockchip-official (non-RE) MCU/mailbox docs exist under NDA/partner access — never asked Luckfox support directly; worth a query.
mcutool.cexact/dev/memsequence — inferred, not read line-by-line; moot, since ourhpmcu.rsis an independent hardware-validated reimplementation.
Cross-refs: ../CAPABILITIES-AUDIT.md, ../REMAINING-PORTS.md §7,
../../luckfox-pico-86-panel/riscv-mcu.md,
.../raw/followup-riscv-mcu.md,
flare-edge/major-app-additions/docs/decisions/0002-hpmcu-watchdog.md,
flare-edge/major-app-additions/flared/src/hpmcu.rs,
flare-edge/major-app-additions/hpmcu/watchdog/main.c,
flare-edge/sdk/.../drivers/mailbox/rockchip-mailbox.c,
flare-edge/sdk/.../mcu/rt-thread/.../hal/lib/hal/src/hal_mbox.c,
battery-ipc/stream.c. Standing memory: boot-loaded-mcu-0x40000-hazard.md.