From 54f961c6b6750b0d3715988c996eb4c6f53d3c83 Mon Sep 17 00:00:00 2001 From: BFE Engineering Date: Tue, 25 Aug 2026 07:25:49 -0600 Subject: [PATCH] =?UTF-8?q?kernel/rv1106:=20PVTM=20VERIFIED=20=E2=80=94=20?= =?UTF-8?q?both=20monitors=20probe=20+=20read=20(7th=20driver)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Whole-driver port (mainline has no rockchip pvtm): copied vendor rockchip_pvtm.c + pvtm.h. 6.18 deltas: thermal_zone opaque -> thermal_zone_get_temp; of_device.h. Real blocker: vendor of_match_table wrapped rv1106 entries in #ifdef CONFIG_CPU_RV1106 (nonexistent in mainline) -> devices+driver present but never bound, probe silent. Dropped the guard -> both bind. Verified: ff240000.pvtm + ff390000.pvtm probed; /sys/kernel/debug/pvtm/{core,pmu}/ value read ring-osc counts (71682 90462 / 35772). pvtm/PORT-DONE.md. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017wB8KB3MMQztRDXCMCkPrf --- kernel/rv1106-enablement/DRIVER-PARITY.md | 1 + kernel/rv1106-enablement/mailbox/PLAN.md | 252 +++++++++++++++ kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md | 303 ++++++++++++++++++ kernel/rv1106-enablement/pvtm/PORT-DONE.md | 26 ++ 4 files changed, 582 insertions(+) create mode 100644 kernel/rv1106-enablement/mailbox/PLAN.md create mode 100644 kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md create mode 100644 kernel/rv1106-enablement/pvtm/PORT-DONE.md diff --git a/kernel/rv1106-enablement/DRIVER-PARITY.md b/kernel/rv1106-enablement/DRIVER-PARITY.md index 568bb73..43cc52c 100644 --- a/kernel/rv1106-enablement/DRIVER-PARITY.md +++ b/kernel/rv1106-enablement/DRIVER-PARITY.md @@ -42,6 +42,7 @@ c8a3, not just compiled. | RGA 2D (rga2) | rga2 | ported (vendor char-dev) | ✅ /dev/rga, hw 3.3.87975 | | I2S audio (i2s-tdm) | i2s | rv1126 fallback (=y) | ✅ cpu DAI registers (part of the card below) | | Audio codec (acodec) | rockchip,rv1106-codec | ported (rv1106_codec.c) | ✅ card `rv1106-acodec`, pcmC0D0p/c (`audio/`); audible test @ bench | +| PVTM (core+pmu ring-osc) | rockchip,rv1106-*-pvtm | ported (vendor, no mainline) | ✅ both probe; debugfs reads (`pvtm/`) | | FIQ debugger (ttyFIQ0) | fiq_glue | rockchip | ⬜ optional (we use ttyS2) | Legend: ✅ verified on hardware · 🔨 built, not yet verified · ⬜ not started. diff --git a/kernel/rv1106-enablement/mailbox/PLAN.md b/kernel/rv1106-enablement/mailbox/PLAN.md new file mode 100644 index 0000000..74b421a --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/PLAN.md @@ -0,0 +1,252 @@ +# 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.c` is + upstream in mainline 6.18 and **already binds on our exact kernel** via the + generic `rockchip,rk3368-mailbox` **fallback compatible** with **zero patching** + — recorded in `CAPABILITIES-AUDIT.md:30`, confirmed by source read. RV1106's DT + declares both instances with that fallback string. Gated today only by + `status="disabled"` + `CONFIG_ROCKCHIP_MBOX` being 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 (xPack `riscv-none-embed-gcc` + 10.2.0, `-march=rv32imc`), loaded via `/dev/mem`-mmap by `flared/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 upstream `mailbox_client.h` API 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`, no + `Hpmcu=` loader stage) — a clean slate, not even the stock camera-AE blob. +- **Usable MCU code SRAM is a hard 8KB**: `hpmcu_sram` at offset `0x3e000` inside + `system_sram@ff6c0000` → absolute **`0xFF6FE000`, 8KB** (`reg=<0x3e000 0x2000>`, + `rv1106.dtsi:1149-1151`), shared region with 248KB `rkisp_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`) hold `0x1e001e` → write firmware + to `0xff6fe000` → `SGRF_HPMCU_BOOT_ADDR` (`0xff076000+0x44`) = load addr → + release CORECRU to `0x1e0000`. 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 example + `battery-ipc/stream.c:380-425` (sends `struct MBOX_CMD_DAT {cmd,data}` over + `MBOX_CH_1`, client name `"mcu-status"` — exactly matches the Linux + `struct 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 via `rk3368-mailbox` fallback, zero patch. +- No `drivers/rpmsg/rockchip*` and no `drivers/remoteproc/rockchip*` in mainline + (GitHub API enumeration of both dirs — none Rockchip). Vendor `rockchip_rpmsg.c` + never merged; `lore.kernel.org` 403-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. + +1. Kernel config: `CONFIG_ROCKCHIP_MBOX=y` + `CONFIG_MAILBOX_TEST=y` (mainline + driver, **no patch** — `rockchip-mailbox.c` binds on the fallback compatible). +2. Board DTS: override `&mailbox { status = "okay"; };` (the `@ff5c0000` instance — + main CRU clock, `GIC_SPI 1`, no PMU-domain complications). Add a + `compatible = "mailbox-test"` node with `mboxes = <&mailbox N>` on a **free + channel — avoid channel 1** (vendor reserves it for `"amp-rx"` semantics). +3. **Verify on serial console** (via the `warden-c8a3` `_b`-slot one-shot loop): + confirm `rockchip-mailbox` probes clean in `dmesg`; write an 8-byte `{cmd,data}` + pair to the debugfs `message` file (`mailbox-test.c`); then read back the + physical `A2B_CMD(N)`/`A2B_DAT(N)` registers directly with busybox `devmem` at + `0xff5c0008+8N` / `0xff5c000c+8N` and 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. + +1. **SCR1 echo firmware** (additive to our existing bare-metal firmware, must fit + the 8KB `hpmcu_sram` budget): on A2B IRQ (or a tight poll of `A2B_STATUS` — poll + is simpler and safe for a bring-up echo, avoids MCU IRQ-controller setup), read + `A2B_CMD(x)`/`A2B_DAT(x)`, write them back to `B2A_CMD(x)`/`B2A_DAT(x)`, set the + `B2A_STATUS` bit, clear `A2B_STATUS`. Copy the register sequence from vendor + `hal_mbox.c` / `battery-ipc/stream.c`. Load and start the core with the + **proven `hpmcu.rs` load/release sequence** (SRAM path, `0xff6fe000` — **not** + the `0x40000` boot path, hazard §2). +2. **Linux client**: a minimal in-tree `mbox_client` (stock `mailbox_client.h`: + `mbox_request_channel_byname` / `mbox_send_message` + an rx callback reading via + the exported `rockchip_mbox_read_msg()`), or — to avoid writing a kernel client + for the first proof — reuse `mailbox-test`'s debugfs send and read the B2A + registers from userspace with `devmem`. Prefer the `mailbox-test` route for + M-MBOX-2's first light; promote to a real `mbox_client` once the round-trip is + green. +3. **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 by + `devmem` reading `B2A_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@ff378000` purpose** — 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.c` exact `/dev/mem` sequence** — inferred, not read line-by-line; + moot, since our `hpmcu.rs` is an independent hardware-validated reimplementation. + +--- +_Cross-refs: `../CAPABILITIES-AUDIT.md:30`, `../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`._ diff --git a/kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md b/kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md new file mode 100644 index 0000000..ec4a2eb --- /dev/null +++ b/kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md @@ -0,0 +1,303 @@ +# Open NPU to 100% — plan, feasibility, and the honest ceiling + +**Goal as stated:** take the RV1106 NPU to "100% — open source, verified on +hardware." **Bottom line up front:** two very different things live under that +sentence, and only one of them is reachable soon. + +1. **Open, on-hardware-verified _kernel driver_** (`/dev/dri/cardN` binds, answers + a version-query ioctl on our self-built Linux 6.18.46). **Achievable now**, + low-single-digit engineer-days, license-clean GPL forward-port. This is the + `PORT-PLAN.md` work in this directory — a real, bounded milestone (M6). +2. **Open _userspace_ that runs a real model** (an open compiler/encoder emitting a + valid `regcmd` stream this driver can submit, no closed `librknnrt`, no `.rknn` + blob). **Not achievable on any near horizon.** It is a from-scratch, + per-SoC register reverse-engineering project with **zero existing public prior + art for RV1106/RV1103** — the least-covered tier of the entire RKNPU family. + +This document scopes both, states the most ambitious end state that is actually +achievable, and defines the concrete first milestone toward open compute. +`PORT-PLAN.md` remains the authoritative, file-by-file plan for item (1); this +document is the strategic wrapper and the item-(2) reality check. Read both. + +--- + +## 1. Brutally honest feasibility + +### 1.1 "Port the GPL kernel driver" — TRACTABLE (do it) + +- The vendor driver (`flare-edge/sdk/sysdrv/source/kernel/drivers/rknpu/`, v0.9.2, + `DRIVER_DATE 20230825`) is **SPDX GPL-2.0 at the file level**, `MODULE_LICENSE("GPL v2")` + (`rknpu_drv.c:2176`), authored by Rockchip (Felix Zeng), mirrored at + `github.com/airockchip/rknpu`. Legitimately open, cleanly forward-portable. +- It is a **single multi-SoC codebase that already targets RV1106 natively**: + `of_match` entry `{ .compatible = "rockchip,rv1106-rknpu", .data = &rv1106_rknpu_config }` + (`rknpu_drv.c:196-198`) + a dedicated config struct (`rknpu_drv.c:143-160`: + `dma_mask=32`, `pc_data_amount_scale=2`, `pc_task_number_bits=16`, + `pc_task_status_offset=0x3c`, single-core `irqs`/`resets` arrays, + `nbuf_phyaddr=0/nbuf_size=0`). +- The DT node is **already fully specified** in the base tree, only disabled — + `npu@ff660000`, `reg=<0xff660000 0x10000>`, `GIC_SPI 109`, clocks + `ACLK_RKNN`/`HCLK_RKNN`, `assigned-clock-rates=<420000000>`, resets + `SRST_A_RKNN`/`SRST_H_RKNN`, `status="disabled"` (`rv1106.dtsi:1127-1138`). One + board-DTS override (`&npu { status="okay"; }`) enables it. +- **No IOMMU** on this board (`"rknpu iommu device-tree entry not found!, using + non-iommu mode"` — CMA/contiguous DMA only), **no power-domains** (single-rail), + **no OPP table** (fixed clock). This _shrinks_ the port: the multi-domain/DVFS/ + thermal code is all provably dead for RV1106's DT. +- The one real build blocker is four vendor-only downstream headers + (`soc/rockchip/rockchip_{iommu,opp_select,system_monitor,ipa}.h`) absent from + mainline 6.18 — all four back **dead-code call sites** for this SoC, fixable with + small local compat-shim stubs (same pattern already used for `clk-rv1106.c`'s + `panic_notifier_list` move). Full delta in `PORT-PLAN.md §2.4`. + +**Verdict: real, bounded, worth doing.** Same class as the RGA port. Ends at a +clean probe + `RKNPU_GET_DRV_VERSION`/`RKNPU_GET_HW_VERSION` ioctl round-trip on +hardware — **and stops there**, because of §1.2. + +### 1.2 "Open userspace that runs a model" — HARD → effectively BLOCKED for RV1106 + +The kernel driver's entire hardware contract is: power/clock/reset, DMA/GEM buffer +management, then it drops a **pointer + length to a userspace-authored `regcmd` +blob** into four registers and pulses "go," then fields one IRQ. **It never +inspects the regcmd contents.** (Traced through `rknpu_job.c:266-364`, +`rknpu_job_subcore_commit_pc()`.) + +The four PC registers the driver programs per task (offsets into the 0xFF660000 +block, from `rknpu_ioctl.h:21-39` used via `REG_WRITE` in `rknpu_job.c`): + +| Offset | Name | Meaning | +|---|---|---| +| `0x00`/`0x04` | VERSION / VERSION_NUM | read-only HW version | +| `0x08` | PC_OP_EN | pulse 1→0 kicks the job; `0x1`=slave mode before commit | +| `0x10` | PC_DATA_ADDR | device address of the regcmd buffer | +| `0x14` | PC_DATA_AMOUNT | `(regcfg_amount + 4 + scale-1)/scale - 1` | +| `0x20`/`0x24`/`0x28`/`0x2c` | INT_MASK / INT_CLEAR / INT_STATUS / INT_RAW_STATUS | interrupt handshake | +| `0x30` | PC_TASK_CONTROL | `((0x6\|pingpong)<`) | +| NPU_GRF | `0xFF018000` | 32KB | +| NPU_SGRF | `0xFF074000` | 8KB | +| NPU_CRU | `0xFF3B6000` | 8KB | +| NPU_CBUF (scratch SRAM) | `0xFF680000` | 256KB (unused: config sets `nbuf=0`) | + +IRQ `GIC_SPI 109 LEVEL_HIGH`, node `ff660000.npu`. Clocks `ACLK_RKNN`/`HCLK_RKNN` +off the CRU matrix; only measured clock point anywhere is **NPU 500MHz** (Rockchip +internal power-test xlsx, "typical IPC workload" corner). A commented-out board-DTS +override would set 700MHz — never applied. + +### UAPI (the seam an open userspace must target) + +Two memory-manager paths, Kconfig-selected. **RV1106's shipping defconfig picks +`CONFIG_ROCKCHIP_RKNPU_DMA_HEAP=y`** (`/dev/rknpu` misc device, bare `IOCTL_RKNPU_*` +codes, magic `'r'`) — but `PORT-PLAN.md` deliberately chooses **DRM_GEM** for the +6.18 port (`/dev/dri/cardN`, `DRM_IOCTL_RKNPU_*`, `DRM_RENDER_ALLOW`), because M4 +already pulls DRM in for the panel and DRM_GEM is the vendor default. Same struct +payloads either way. Six ioctls: `RKNPU_ACTION`, `RKNPU_SUBMIT`, +`RKNPU_MEM_{CREATE,MAP,DESTROY,SYNC}` (`rknpu_ioctl.h:288-322`). + +```c +struct rknpu_task { // rknpu_ioctl.h:218-228 + __u32 flags, op_idx, enable_mask; + __u32 int_mask, int_clear, int_status; + __u32 regcfg_amount, regcfg_offset; + __u64 regcmd_addr; // <-- points at the OPAQUE compute program +} __packed; +struct rknpu_submit { // rknpu_ioctl.h:260-274 + __u32 flags, timeout, task_start, task_number, task_counter; + __s32 priority; + __u64 task_obj_addr, regcfg_obj_addr, task_base_addr, user_data; + __u32 core_mask; __s32 fence_fd; + struct rknpu_subcore_task subcore_task[5]; +}; +``` + +An open userspace must produce: **(a) the `struct rknpu_task[]` list — fully +inferable from the kernel driver alone, trivial;** and **(b) the regcmd byte stream +each task points to — NOT inferable from the driver or any published TRM.** (b) is +the whole problem. + +### `/proc/rknpu` — safety (hardware-confirmed) + +- `/proc/rknpu/load` — **safe** (50 clean reads verified); the Monitor page polls it. +- `/proc/rknpu/freq`, `/power` — exposed, unverified. +- `/proc/rknpu/volt` — **CONFIRMED SIGSEGV** (null deref — no regulator wired). Never poll. + +### Provenance / policy + +`PROVENANCE.md`: the kernel driver is portable GPL; the closed piece is the +userspace RKNN runtime + regcmd format (a blob). **Per directive we do not ship +that blob.** `CAPABILITIES-AUDIT.md:32` rates NPU "not worth shipping" until an +open encoder exists. + +### URLs + +- Vendor driver: `github.com/airockchip/rknpu`, `github.com/rockchip-linux/rknpu` +- Mainline Rocket: `drivers/accel/rocket/` (RK3588-only); blog: + `blog.tomeuvizoso.net/2025/07/rockchip-npu-update-6-we-are-in-mainline.html` +- Teflon: `docs.mesa3d.org/teflon.html`; NLnet: `nlnet.nl/project/Rockchip-NPU-driver/` +- RK3576 RE: `github.com/gahingwoo/{linux-rk3576-npu,charsiu,kiln}` (active, unmerged) +- RK3588 RE: `github.com/phhusson/rknpu-reverse-engineering` (dormant, no license) +- Official closed docs (RV1106/RV1103 quick-start PDFs, API-only): `github.com/airockchip/rknn-toolkit2` `doc/` + +--- + +## 3. Milestones + +### M-NPU-1 — Open kernel driver, verified on hardware (ACHIEVABLE — the real deliverable) + +Follows `PORT-PLAN.md` in full. Summary of the gates: + +1. **Build**: `CONFIG_ROCKCHIP_RKNPU=y` + `_DRM_GEM=y` + `_DEBUG_FS`/`_PROC_FS`; + compile `rknpu_{drv,job,gem,reset,iommu,debugger}.o` clean into `built-in.a`. + Prove the four §2.4 compat shims (`compat/soc/rockchip/rockchip_{iommu,opp_select, + system_monitor,ipa}.h`) against real 6.18 headers — this is where they get + verified, not just inspected. +2. **DT**: board override `&npu { status="okay"; }`; `dtc -W` clean; re-check the + "place the fdt high" DTB-placement fix (bigger `built-in.a`). +3. **Boot** via the `warden-c8a3` `_b`-slot one-shot loop (never touches `_a`, + auto-reverts on hang): `dmesg | grep -i rknpu` shows clean probe — clock/reset/ + IRQ acquired, no panic, no `-EPROBE_DEFER` stall. +4. **Node**: `/dev/dri/cardN` (or `renderD1xx`) appears — classic DRM node, **not** + `/dev/accel/`. +5. **Verify ioctl**: host-buildable C program opens the node, issues + `DRM_IOCTL_RKNPU_ACTION {.flags=RKNPU_GET_DRV_VERSION}`, checks `value` decodes + to `0.9.2`; `RKNPU_GET_HW_VERSION` returns something plausible. Exercises full + dispatch → power-get/put → clock/reset with no regcmd dependency. +6. **Capture the durable delta** as a patch series in this dir (compat shims + DT + fragment + config fragment); vendor source stays in `research/linux-6.18.46/`. + +**Effort:** low-single-digit engineer-days. **Risk:** a surprise vendor-only symbol +not surfaced by the read-through; DTB placement. **This is "NPU driver = 100% open, +verified."** Done here for the driver half. + +### M-NPU-2 — First open compute: capture-and-replay one op (SPIKE, only if wanted) + +The concrete first milestone toward open _compute_, and the honest Tier-B ceiling. +It does **not** require decoding the ISA. + +1. On a unit with the closed stack available (build host / a dev panel with the + closed `librknnrt` + a trivial single-conv or single-matmul `.rknn`), capture + the regcmd buffer(s) the runtime DMAs in — via `strace` of the `RKNPU_MEM_*`/ + `RKNPU_SUBMIT` ioctls + `GEM_FLINK`/`GEM_OPEN` buffer dumps (phhusson's method), + plus the input/output tensor buffers. +2. Write an **open host tool** that: allocates the same GEM buffers via + `RKNPU_MEM_CREATE`, writes the captured regcmd bytes + captured input tensor, + builds the `struct rknpu_task[]` (fully open, §2), and submits via + `DRM_IOCTL_RKNPU_SUBMIT` on our M-NPU-1 driver. +3. **Verify**: output tensor matches the closed runtime's output **byte-for-byte** + on hardware, and matches a CPU/NEON reference for the same op. Record the + **measured job-submission latency** (ioctl+DMA+blocking-IRQ) — a number that + exists nowhere today and settles the "NPU vs NEON dispatch overhead" argument. + +**Deliverable:** proof the open submit path drives real compute + first latency +number. **Not** a general runtime — the regcmd is a fixed captured blob for one op +shape. **Value:** low; do only as a time-boxed research spike behind a real +use case. **Never ship a captured Rockchip regcmd as a product artifact** (it is +their compiler's output) — this stays in `research/`. + +### M-NPU-3+ — Open regcmd encoder (SCOPE ONLY — do not staff without a strategic reason) + +The Tier-C from-scratch RE project. Method (the only demonstrated one): +differentially decode captured regcmd streams across many hand-built minimal ONNX +models (isolate one op/param at a time), reconstruct the compute-engine register +semantics, build an encoder for a first op, then grow the op set. Precedent cost: +~1 person-year got RK3588 to "exploratory, no compiler"; RK3568 needed byte-level +diffing and still isn't merged; **RV1106 starts from literal zero.** Track here if +ever begun; otherwise this is the documented reason open NPU compute is deferred. + +--- + +## 4. Risks / open questions + +- **The core risk is misframing.** "The driver is open source" must not be sold as + "the NPU is open-source-usable." M-NPU-1 delivers an open driver that can submit + jobs; it delivers **no open way to produce a valid job.** Guard this in every + status line. +- Additional vendor-only symbols beyond the four headers may surface only at + compile — M-NPU-1 gate 1 is the real test. +- **SKU unconfirmed** (G2 128MB / 0.5-TOPS vs G3 256MB / 1.0-TOPS; datasheet Rev 2.0 + retracted the split to "both 1.0 TOPS"). Determines the compute ceiling if + M-NPU-2/3 are ever pursued; needs an on-hardware chip-ID/TOPS register read. +- **No open regcmd project for RV1106 exists** (re-confirm periodically — community + moves fast, esp. the `gahingwoo` RK3576 work; but it is the wrong generation). +- **No NPU use case is currently established for this board** (no camera; RGA2 + + RKIVE already cover the 2D/CNN-shaped image tasks better — see + `npu-graphics-feasibility.md`). Absent a concrete non-visual classifier need, + M-NPU-2 and beyond have no pull, and M-NPU-1 alone (driver binds, load graph + works, no blob) is the correct stopping point. + +--- +_Cross-refs: `PORT-PLAN.md` (authoritative file-by-file kernel port), +`../../docs/npu-graphics-feasibility.md`, `../CAPABILITIES-AUDIT.md:32`, +`../PROVENANCE.md`, `../DRIVER-PARITY.md:41`, `../REMAINING-PORTS.md §6`._ diff --git a/kernel/rv1106-enablement/pvtm/PORT-DONE.md b/kernel/rv1106-enablement/pvtm/PORT-DONE.md new file mode 100644 index 0000000..f5ba37a --- /dev/null +++ b/kernel/rv1106-enablement/pvtm/PORT-DONE.md @@ -0,0 +1,26 @@ +# PVTM (Process-Voltage-Temperature Monitor) — ✅ VERIFIED on warden-c8a3 (2026-08-25) + +Whole-driver port: mainline 6.18 has **no** rockchip pvtm driver; the vendor +`drivers/soc/rockchip/rockchip_pvtm.c` (GPL-2.0, 1046L) supports rv1106. Copied it in ++ `include/linux/soc/rockchip/pvtm.h`; Kconfig `ROCKCHIP_PVTM` + Makefile; `=y`. + +## API-delta fixes (6.18) +- `struct thermal_zone_device` is now opaque → replaced `pvtm->tz->ops->get_temp(...)` + with the public `thermal_zone_get_temp(pvtm->tz, &cur_temp)`. +- added `#include ` for `of_match_device()`. +- copied the vendor-only header `linux/soc/rockchip/pvtm.h`. + +## The real blocker (why it first probed silently) +The vendor of_match_table wraps the rv1106 entries in `#ifdef CONFIG_CPU_RV1106` — a +vendor per-SoC symbol that does **not exist** in mainline. Result: devices +(`ff240000.pvtm`, `ff390000.pvtm`) were created + the driver registered, but the +compatibles were compiled out of the match table, so nothing bound and probe never +ran (no dmesg at all). **Fix: drop the `#ifdef CONFIG_CPU_RV1106` guard** — our tree +only builds for rv1106, so the entries are unconditional. (Watch for this guard in any +other vendor driver ported by verbatim copy.) + +## Evidence +`rockchip-pvtm ff240000.pvtm: pvtm@0 probed` + `ff390000.pvtm: pvtm@0 probed`; +`/sys/kernel/debug/pvtm/core/value = pvtm: 71682 90462`, +`/sys/kernel/debug/pvtm/pmu/value = pvtm: 35772` (ring-oscillator counts, the PVT +signal). Exports `rockchip_get_pvtm_value()` for future DVFS voltage margining.