From 7e76553bd778e94e026de06906675ebe82f09b8b Mon Sep 17 00:00:00 2001 From: BFE Engineering Date: Tue, 25 Aug 2026 08:36:56 -0600 Subject: [PATCH] kernel/rv1106: HPMCU mailbox 100% VERIFIED + open NPU driver VERIFIED MAILBOX (fully open A7<->RISC-V SCR1 round-trip, 5/5 exact echoes on c8a3): - rockchip-mailbox.c: rv1106 has 1 shared IRQ, not 4/channel -> added rv1106_drv_data{num_chans=1} + compatible (rk3368 fallback assumed 4 -> probe failed 'IRQ index 1 not found'). - clk-rv1106.c: CLK_CORE_MCU (SCR1 core clock) marked CLK_IGNORE_UNUSED - 6.18's clk_disable_unused() was switching off the coprocessor clock so a loaded firmware never ran (5.10 left it on). - Open SCR1 echo firmware (scr1-echo/, 154B RV32IMC) + load/test scripts; A2B_INTEN on the MCU side + CMD-then-DAT (DAT=doorbell) order. Loaded via the proven hpmcu.rs SRAM path; do NOT kill warden-flared (dw-wdt). mailbox/VERIFIED.md. NPU (open GPL rknpu 0.9.2 kernel driver on 6.18): - DT fix: base npu node lacks interrupt-names, driver requests IRQ byname 'npu_irq' -> probe bailed -ENXIO. Added interrupt-names='npu_irq'. - Verified: [drm] Initialized rknpu 0.9.2 on minor 1; /dev/dri/card1; rknpu_version_test PASS (0.9.2 + hw version, full power/clock/reset path). - Honest ceiling: open compute (regcmd compiler) is a from-scratch ~person-year RE project, no RV1106 prior art; ship the driver, no blob. npu/VERIFIED.md. DRIVER-PARITY + CAPABILITIES-AUDIT updated; dts snapshot refreshed. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_017wB8KB3MMQztRDXCMCkPrf --- .../rv1106-enablement/CAPABILITIES-AUDIT.md | 4 +- kernel/rv1106-enablement/DRIVER-PARITY.md | 3 +- .../rv1106-enablement/dts/rv1106-warden.dts | 23 ++ kernel/rv1106-enablement/mailbox/VERIFIED.md | 53 +++ .../mailbox/scr1-echo/Makefile | 37 ++ .../mailbox/scr1-echo/echo-fw-words.txt | 39 ++ .../mailbox/scr1-echo/hpmcu-mailbox-echo.bin | Bin 0 -> 154 bytes .../mailbox/scr1-echo/link.lds | 43 +++ .../mailbox/scr1-echo/load-echo-fw.sh | 46 +++ .../mailbox/scr1-echo/main.c | 98 +++++ .../mailbox/scr1-echo/mbox-roundtrip.sh | 14 + .../mailbox/scr1-echo/start.S | 27 ++ kernel/rv1106-enablement/npu/PORT-PROGRESS.md | 336 ++++++++++++++++++ kernel/rv1106-enablement/npu/VERIFIED.md | 43 +++ .../npu/rknpu_version_test.c | 137 +++++++ 15 files changed, 900 insertions(+), 3 deletions(-) create mode 100644 kernel/rv1106-enablement/mailbox/VERIFIED.md create mode 100644 kernel/rv1106-enablement/mailbox/scr1-echo/Makefile create mode 100644 kernel/rv1106-enablement/mailbox/scr1-echo/echo-fw-words.txt create mode 100755 kernel/rv1106-enablement/mailbox/scr1-echo/hpmcu-mailbox-echo.bin create mode 100644 kernel/rv1106-enablement/mailbox/scr1-echo/link.lds create mode 100755 kernel/rv1106-enablement/mailbox/scr1-echo/load-echo-fw.sh create mode 100644 kernel/rv1106-enablement/mailbox/scr1-echo/main.c create mode 100755 kernel/rv1106-enablement/mailbox/scr1-echo/mbox-roundtrip.sh create mode 100644 kernel/rv1106-enablement/mailbox/scr1-echo/start.S create mode 100644 kernel/rv1106-enablement/npu/PORT-PROGRESS.md create mode 100644 kernel/rv1106-enablement/npu/VERIFIED.md create mode 100644 kernel/rv1106-enablement/npu/rknpu_version_test.c diff --git a/kernel/rv1106-enablement/CAPABILITIES-AUDIT.md b/kernel/rv1106-enablement/CAPABILITIES-AUDIT.md index 3df5adc..2f6b2df 100644 --- a/kernel/rv1106-enablement/CAPABILITIES-AUDIT.md +++ b/kernel/rv1106-enablement/CAPABILITIES-AUDIT.md @@ -27,9 +27,9 @@ card `rv1106-acodec` + pcmC0D0p/c (`audio/`); audible test @ bench with display. ## ⬜ Remaining — final honest ledger (each is deferred for a stated reason, not an unexplored gap) | Block | verdict | reason | |---|---|---| -| **mailbox** (HPMCU) | available, not enabled | the rv1106 node carries a `rockchip,rk3368-mailbox` **fallback** → mainline's driver binds it with **zero code change**. But nothing in our kernel is a mailbox *client* (the coprocessor R5 path is /dev/mem today), so an enabled controller would register unexercised. Enable it the day a coprocessor mailbox client lands — DT `status="okay"` + `MAILBOX`/`ROCKCHIP_MBOX=y`, no port. | +| **mailbox** (HPMCU) | ✅ VERIFIED | the rv1106 node carries a `rockchip,rk3368-mailbox` **fallback** → mainline's driver binds it with **zero code change**. But nothing in our kernel is a mailbox *client* (the coprocessor R5 path is /dev/mem today), so an enabled controller would register unexercised. Enable it the day a coprocessor mailbox client lands — DT `status="okay"` + `MAILBOX`/`ROCKCHIP_MBOX=y`, no port. | | **crypto-v3** (accel) | deferred | ~100KB whole-subsystem replacement of mainline's rk3288 crypto + heavy crypto-API deltas, and the **CPU crypto extensions (AES/SHA, batch2 =y) already cover the functional need** — an offload optimization, not a capability gap. | -| **NPU** (rknpu) | not worth shipping | kernel driver ports, but **no open userspace regcmd runtime exists** — it would register with nothing able to submit jobs openly. Needs an open encoder first (`npu/PORT-PLAN.md`, graphics investigation). | +| **NPU** (rknpu) | ✅ open driver VERIFIED; compute deferred | kernel driver ports, but **no open userspace regcmd runtime exists** — it would register with nothing able to submit jobs openly. Needs an open encoder first (`npu/PORT-PLAN.md`, graphics investigation). | | **pvtm** | deferred | PVT monitors are only useful for DVFS, which we don't run. | | camera/ISP, SPI | N/A | no such hardware on the 86-Panel. | diff --git a/kernel/rv1106-enablement/DRIVER-PARITY.md b/kernel/rv1106-enablement/DRIVER-PARITY.md index 43cc52c..0fff95a 100644 --- a/kernel/rv1106-enablement/DRIVER-PARITY.md +++ b/kernel/rv1106-enablement/DRIVER-PARITY.md @@ -38,10 +38,11 @@ c8a3, not just compiled. | GPIO_SYSFS / crypto / CFG80211 | — | mainline (config) | ✅ =y (batch2) | | AIC8800 wifi (bsp/fdrv) | aic8800_* | **out-of-tree** | ✅ M5 — wlan0 up, scanned the site AP at −43dBm (modules, `wifi/VERIFIED-on-c8a3.md`) | | AIC8800 BT (btlpm) | aic8800_btlpm | **out-of-tree** | 🔨 module built (6.18 vermagic); HCI bring-up not yet exercised | -| NPU (rknpu, ff660000) | rknpu, ff660000.npu | **out-of-tree** | ⬜ M6 — plan: `npu/PORT-PLAN.md` | +| NPU (rknpu, ff660000) | rknpu, ff660000.npu | **out-of-tree** | 🔨 M6 built, 0 errors/0 warnings, 99 `rknpu`-prefixed symbols in `System.map`, `&npu {status="okay"}` in the dtb — **not yet flashed/probed on hardware** (build-only session; see `npu/PORT-PROGRESS.md`) | | 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 | +| HPMCU mailbox (ff5c0000) | rockchip,rv1106-mailbox | rk3368 fallback +rv1106 num_chans=1 | ✅ A7<->SCR1 round-trip, 5/5 exact (`mailbox/VERIFIED.md`) | | 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) | diff --git a/kernel/rv1106-enablement/dts/rv1106-warden.dts b/kernel/rv1106-enablement/dts/rv1106-warden.dts index 622e57e..568dc44 100644 --- a/kernel/rv1106-enablement/dts/rv1106-warden.dts +++ b/kernel/rv1106-enablement/dts/rv1106-warden.dts @@ -245,3 +245,26 @@ }; }; }; + +/* --- HPMCU mailbox (A7 <-> RISC-V SCR1). Controller binds via the rk3368 + * fallback compatible with zero driver patch; @ff5c0000 is the HPMCU-connected + * instance (SRST_CORE_MCU on the Linux side, MBOX_BASE 0xFF5C0000 on the MCU + * side). M-MBOX-1 proves the controller; M-MBOX-2 adds the SCR1 echo + round-trip. */ +&mailbox { + status = "okay"; +}; + +/* --- NPU (rknpu, ff660000). Kernel driver ported from the vendor 5.10 BSP + * (GPL source port -- see warden-sdk/kernel/rv1106-enablement/npu/PORT-PLAN.md + * and PORT-PROGRESS.md). compatible/reg/interrupts/clocks/resets are already + * fully specified in rv1106.dtsi's rv1106_rknpu_config-matching npu node -- + * this is only the status flip, same pattern as every other block enabled + * in this file. No iommus= property (this board's NPU runs non-iommu, + * matching the 5.10 boot-log finding), no power-domains (single-rail). */ +&npu { + /* the rknpu driver requests its IRQ by name ("npu_irq"); the base dtsi + * node declares the interrupt but no interrupt-names, so probe fails + * "IRQ npu_irq not found" without this. */ + interrupt-names = "npu_irq"; + status = "okay"; +}; diff --git a/kernel/rv1106-enablement/mailbox/VERIFIED.md b/kernel/rv1106-enablement/mailbox/VERIFIED.md new file mode 100644 index 0000000..793b2c3 --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/VERIFIED.md @@ -0,0 +1,53 @@ +# HPMCU mailbox — ✅ 100% VERIFIED on warden-c8a3 (2026-08-25) + +A fully-open A7 ↔ HPMCU (RISC-V SCR1) hardware-mailbox round-trip on our self-built +Linux 6.18.46. Open kernel driver + open SCR1 firmware, **zero blobs**. + +## Evidence (serial, _b slot = our 6.18) +SCR1 echo firmware running: `DBG_STATE = 0x584F424D` ("MBOX"). Five round-trips, +Linux → mailbox → SCR1 → mailbox → Linux, **all exact**: +``` +sent 0x0000beef/0x600df00d -> B2A 0x0000BEEF/0x600DF00D e=4 ✓ +sent 0x0000c0de/0x12345678 -> B2A 0x0000C0DE/0x12345678 e=5 ✓ +sent 0x0000face/0xdeadbeef -> B2A 0x0000FACE/0xDEADBEEF e=6 ✓ +sent 0x00001234/0xcafef00d -> B2A 0x00001234/0xCAFEF00D e=7 ✓ +sent 0x0000aa55/0x55aa55aa -> B2A 0x0000AA55/0x55AA55AA e=8 ✓ +``` +Echo counter increments 1:1 with sends; both CMD and DAT echo back verbatim. + +## The three fixes it took (none in the research plan — found on hardware) +1. **Controller IRQ count** (`rockchip-mailbox.c`): the rv1106 mailbox has ONE + shared IRQ (GIC_SPI 1), but the rk3368 driver-data assumes one IRQ per channel + (num_chans=4) and probe failed `IRQ index 1 not found`. Added an + `rv1106_drv_data { .num_chans = 1 }` + a `rockchip,rv1106-mailbox` match entry + (ahead of the rk3368 fallback). Channel 0 is all the doorbell needs. Controller + then probes clean and clocks the mailbox (pclk_mailbox on). +2. **SCR1 core clock** (`clk-rv1106.c`): `clk_core_mcu` (the coprocessor's 297 MHz + core clock) had flags 0, so 6.18's `clk_disable_unused()` switched it off and the + released core never executed (DBG_STATE stayed 0). Marked it `CLK_IGNORE_UNUSED`. + (5.10 happened to leave it on.) +3. **A2B doorbell semantics** (SCR1 firmware + send order): the MCU-side receiver + must set `A2B_INTEN` or an A7 write to A2B_CMD never raises A2B_STATUS — the echo + firmware now sets `A2B_INTEN` at init. And the A2B_DAT write is the doorbell, so + the sender writes CMD first, then DAT (the mainline order), so both are current + when the SCR1 reads them. + +## Loading the firmware (no reflash, no brick) +The SCR1 echo firmware (`scr1-echo/`, 154 B) is loaded at runtime into hpmcu_sram +(0xFF6FE000) via the proven `flared/hpmcu.rs` sequence (`load-echo-fw.sh`): GRF +uncached peripheral window (0xff040024/28) → CORECRU hold → firmware to SRAM → +SGRF boot addr → CORECRU release. **Do NOT kill warden-flared** (it one-shot-loads +at boot then just beats + pets the dw-wdt; killing it resets the board). We reset ++ reload the SCR1; flared does not re-load. The SRAM-load path avoids the 0x40000 +boot-load brick hazard entirely. + +## Config / DT +`CONFIG_ROCKCHIP_MBOX=y`; board DTS `&mailbox { status = "okay"; }` (the @ff5c0000 +HPMCU-connected instance). The existing /dev/mem SRAM watchdog is untouched (a +separate dead-man's-switch); the mailbox is the general open IPC channel. + +## Files +- `scr1-echo/main.c` + `start.S` + `link.lds` + `Makefile` — the open SCR1 echo fw. +- `scr1-echo/load-echo-fw.sh`, `mbox-roundtrip.sh`, `echo-fw-words.txt` — load + test. +- Kernel deltas: `rockchip-mailbox.c` (rv1106 num_chans=1), `clk-rv1106.c` + (CLK_CORE_MCU IGNORE_UNUSED), `rv1106-warden.dts` (&mailbox okay). diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/Makefile b/kernel/rv1106-enablement/mailbox/scr1-echo/Makefile new file mode 100644 index 0000000..5008b9f --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/Makefile @@ -0,0 +1,37 @@ +# HPMCU mailbox-echo firmware — bare-metal RV32IMC for the RV1106 SCR1 core. +# Same xPack riscv-none-embed-gcc 10.2.0 + flags as the watchdog firmware. + +XPACK ?= /sdk/sysdrv/source/mcu/prebuilts/gcc/linux-x86/riscv64/xpack-riscv-none-embed-gcc-10.2.0-1.2/bin +CROSS ?= $(XPACK)/riscv-none-embed- + +CC = $(CROSS)gcc +OBJCOPY = $(CROSS)objcopy +SIZE = $(CROSS)size + +CFLAGS = -march=rv32imc -mabi=ilp32 -mcmodel=medany -Os -g \ + -ffreestanding -nostdlib -fno-builtin \ + -ffunction-sections -fdata-sections -Wall -Wextra +LDFLAGS = -T link.lds -nostdlib -Wl,--gc-sections -Wl,-Map=mailbox-echo.map + +OBJS = start.o main.o + +all: hpmcu-mailbox-echo.bin + +hpmcu-mailbox-echo.elf: $(OBJS) link.lds + $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) + $(SIZE) $@ + +hpmcu-mailbox-echo.bin: hpmcu-mailbox-echo.elf + $(OBJCOPY) -O binary $< $@ + @ls -la $@ + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.o: %.S + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f *.o *.elf *.bin *.map + +.PHONY: all clean diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/echo-fw-words.txt b/kernel/rv1106-enablement/mailbox/scr1-echo/echo-fw-words.txt new file mode 100644 index 0000000..e636d7a --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/echo-fw-words.txt @@ -0,0 +1,39 @@ +0x00000297 +0x03228293 +0x30529073 +0x00002117 +0xef410113 +0x00000517 +0x08850513 +0x00000597 +0x08058593 +0x00b57663 +0x00052023 +0xbfdd0511 +0xa0012011 +0x584f4737 +0xff7007b7 +0x24d70713 +0xf0e7a423 +0xf007a623 +0xf007a823 +0x07b7473d +0xc398ff5c +0x06374681 +0x4885ff5c +0xff7005b7 +0x2e034311 +0x07930046 +0x47010086 +0x00e89833 +0x01c87533 +0x4388cd11 +0x0047ae83 +0xd7880685 +0x03d7a623 +0x01062223 +0xf0a5a623 +0xf0d5a823 +0x07a10705 +0xfc671ce3 +0x0000b7e9 diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/hpmcu-mailbox-echo.bin b/kernel/rv1106-enablement/mailbox/scr1-echo/hpmcu-mailbox-echo.bin new file mode 100755 index 0000000000000000000000000000000000000000..2666e040eb51454c3160dc38642caa2e89be3a77 GIT binary patch literal 154 zcmV;L0A>G|0ssJ$f+7QRkWw%gApigq0YUE<1pojO1%(Kg1pok(g#`#>cC`Q_AO!#s z1>L_9AOWB^M^9L{2XOxr2iGJcr04J>rU&pNs0Z*pN4E!D|CqypMmGjr|Ak1m1#tfn zLjx{G0FwuX00BocnCJjAb;tn`&4@#Tu15fc28h=qrq=@_A_f5?rls&BsMYWV2cZY! I9B2ILw;cjCd;kCd literal 0 HcmV?d00001 diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/link.lds b/kernel/rv1106-enablement/mailbox/scr1-echo/link.lds new file mode 100644 index 0000000..0ff7a5e --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/link.lds @@ -0,0 +1,43 @@ +/* + * HPMCU watchdog supervisor — linked into the hpmcu_sram carve-out. + * + * The RV1106's 256K on-chip SRAM at 0xFF6C0000 is split by the kernel DT + * (rv1106.dtsi sram@ff6c0000) into rkisp_sram (0x0..0x3e000) and hpmcu_sram + * (0x3e000..0x40000, i.e. 0xFF6FE000..0xFF700000). We live entirely inside + * the 8K hpmcu region: + * + * 0xFF6FE000 +0x0000 .text/.rodata/.data/.bss (this script: 0x1C00) + * 0xFF6FFC00 +0x1C00 stack (grows down from 0xFF6FFF00) + * 0xFF6FFF00 +0x1F00 heartbeat mailbox (shared with Linux, see main.c) + */ +OUTPUT_ARCH(riscv) +ENTRY(_start) + +MEMORY +{ + SRAM (rwx) : ORIGIN = 0xFF6FE000, LENGTH = 0x1C00 +} + +SECTIONS +{ + .text : { + *(.text.start) + *(.text*) + } > SRAM + + .rodata : { *(.rodata*) *(.srodata*) } > SRAM + .data : { *(.data*) *(.sdata*) } > SRAM + + .bss (NOLOAD) : { + . = ALIGN(4); + __bss_start = .; + *(.bss*) *(.sbss*) *(COMMON) + . = ALIGN(4); + __bss_end = .; + } > SRAM + + /* Stack: from the mailbox floor down toward .bss. */ + __stack_top = 0xFF6FFF00; + + /DISCARD/ : { *(.eh_frame) *(.riscv.attributes) *(.comment) } +} diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/load-echo-fw.sh b/kernel/rv1106-enablement/mailbox/scr1-echo/load-echo-fw.sh new file mode 100755 index 0000000..d594a7f --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/load-echo-fw.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# Load the SCR1 mailbox-echo firmware into hpmcu_sram (0xFF6FE000) and start the +# core, replicating flared/hpmcu.rs load_and_release() EXACTLY (the proven +# sequence): GRF uncached peripheral window -> CORECRU hold -> firmware to SRAM +# -> clear SRAM mailbox -> SGRF boot addr -> CORECRU release. SRAM path only +# (NOT the 0x40000 boot-load brick hazard). +# +# Do NOT kill warden-flared: it does a ONE-SHOT firmware load at boot then just +# beats the SRAM heartbeat + pets the kernel dw-wdt. Killing it stops the dw-wdt +# petting and the board resets ~15s later. We simply reset the SCR1 and reload; +# flared won't re-load over us (one-shot), and its heartbeat writes are to a +# different SRAM word than our echo firmware uses. (clk_core_mcu is kept alive by +# CLK_IGNORE_UNUSED in clk-rv1106.c, so the released core actually runs.) +FW=${1:-/userdata/echo-fw-words.txt} + +# GRF uncached peripheral window (covers CRU + this SRAM + the mailbox +# 0xff5c0000) — WITHOUT this the MCU's peripheral/SRAM accesses are cached and +# invisible to Linux. hpmcu.rs: GRF_BASE 0xff040000 +0x24/+0x28 = 0xff000/0xffc00. +devmem 0xff040024 32 0xff000 +devmem 0xff040028 32 0xffc00 + +# hold CORECRU MCU core in reset while we (re)write firmware +devmem 0xff3b8a04 32 0x001e001e + +# write firmware words to 0xFF6FE000 (devmem uses mmap -> SRAM writable) +i=0 +while read w; do + a=$(printf '0x%x' $((0xff6fe000 + i * 4))) + devmem "$a" 32 "$w" + i=$((i + 1)) +done < "$FW" + +# clear the SRAM mailbox/debug area (0xFF6FFF00, 8 words) so stale state can't +# be mistaken for a live echo +j=0 +while [ $j -lt 8 ]; do + devmem "$(printf '0x%x' $((0xff6fff00 + j * 4)))" 32 0 + j=$((j + 1)) +done + +# set HPMCU boot addr, then release from reset +devmem 0xff076044 32 0xff6fe000 +devmem 0xff3b8a04 32 0x001e0000 +sleep 1 +echo "loaded $i words; SCR1 released." +echo " DBG_STATE = $(devmem 0xff6fff08 32) (expect 0x584F424D = 'MBOX')" diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/main.c b/kernel/rv1106-enablement/mailbox/scr1-echo/main.c new file mode 100644 index 0000000..ceda3fc --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/main.c @@ -0,0 +1,98 @@ +/* + * HPMCU mailbox echo firmware for the RV1106 (Syntacore SCR1, RV32IMC). + * + * Purpose: prove a fully-open A7 <-> HPMCU hardware-mailbox round-trip on our + * self-built Linux 6.18. The A7 (Linux) writes a {cmd,dat} pair into the + * mailbox A2B registers; this core polls A2B_STATUS, reads the pair, echoes it + * verbatim into the B2A registers (which raises the B2A doorbell IRQ back to + * Linux), and acks. No RT-Thread, no interrupts on the MCU side, machine mode + * only: a polling loop, exactly like the watchdog firmware. + * + * The mailbox IP is at 0xFF5C0000 (the HPMCU-connected instance: Linux resets + * it via SRST_CORE_MCU, the vendor MCU CMSIS header hard-codes MBOX_BASE + * 0xFF5C0000). Register model is IP-identical to rk3368 (mainline + * rockchip-mailbox.c): writing A2B_CMD(x) is the doorbell (hardware sets the + * A2B_STATUS bit); writing B2A_CMD(x) signals Linux (B2A_STATUS + IRQ). + * + * Both the mailbox registers and hpmcu_sram sit in the GRF "peripheral + * uncached" window (0xFF000000..0xFFC00000) configured at MCU release, so both + * sides see each other's writes with no cache maintenance (same property the + * watchdog SRAM mailbox relies on). + * + * Loaded + started by the proven flared/hpmcu.rs SRAM sequence + * (0xFF6FE000 load addr, CORECRU reset hold/release) — NOT the 0x40000 + * boot-load path (that bricks a non-TB board; see boot-loaded-mcu-0x40000 + * hazard). Fits the 8K hpmcu_sram budget (this is a few hundred bytes). + */ + +#include + +#define REG32(a) (*(volatile uint32_t *)(uintptr_t)(a)) + +/* Mailbox IP (HPMCU-connected instance) — offsets match rockchip-mailbox.c. */ +#define MBOX_BASE 0xFF5C0000u +#define A2B_INTEN REG32(MBOX_BASE + 0x00) +#define A2B_STATUS REG32(MBOX_BASE + 0x04) +#define A2B_CMD(x) REG32(MBOX_BASE + 0x08 + (x) * 8) +#define A2B_DAT(x) REG32(MBOX_BASE + 0x0c + (x) * 8) +#define B2A_STATUS REG32(MBOX_BASE + 0x2C) +#define B2A_CMD(x) REG32(MBOX_BASE + 0x30 + (x) * 8) +#define B2A_DAT(x) REG32(MBOX_BASE + 0x34 + (x) * 8) + +#define NUM_CHANS 4 + +/* + * Liveness window in hpmcu_sram (reuse the watchdog mailbox layout at + * 0xFF6FFF00 so Linux tooling can read it the same way). We only touch the + * MCU-owned slots; the echo firmware does NOT run the watchdog, so it never + * touches MB_MAGIC/MB_COUNTER or fires a reset. + * + * +0x08 mcu_state : 'M','B','O','X' (0x584F424D LE) once the echo loop runs + * +0x0c last_cmd : the last cmd echoed (debug) + * +0x10 echo_cnt : number of messages echoed (heartbeat / proof-of-life) + */ +#define SRAM_DBG_BASE 0xFF6FFF00u +#define DBG_STATE REG32(SRAM_DBG_BASE + 0x08) +#define DBG_LASTCMD REG32(SRAM_DBG_BASE + 0x0c) +#define DBG_ECHOCNT REG32(SRAM_DBG_BASE + 0x10) + +#define STATE_MBOX 0x584F424Du /* "MBOX" (LE bytes 'M','B','O','X') */ + +void main(void) +{ + uint32_t echoes = 0; + + DBG_STATE = STATE_MBOX; + DBG_LASTCMD = 0; + DBG_ECHOCNT = 0; + + /* Enable the A2B doorbell for all channels: without A2B_INTEN set on the + * receiver (MCU) side, an A7 write to A2B_CMD does not raise A2B_STATUS, + * so our poll below never sees a message. (Symmetric to the A7 controller + * setting B2A_INTEN in its startup.) */ + A2B_INTEN = (1u << NUM_CHANS) - 1; + + for (;;) { + uint32_t status = A2B_STATUS; + int ch; + + for (ch = 0; ch < NUM_CHANS; ch++) { + if (status & (1u << ch)) { + uint32_t cmd = A2B_CMD(ch); + uint32_t dat = A2B_DAT(ch); + + /* Echo verbatim. Writing B2A_CMD raises the B2A + * doorbell (hardware sets B2A_STATUS -> Linux + * IRQ / readable status). */ + B2A_CMD(ch) = cmd; + B2A_DAT(ch) = dat; + + /* Ack: clear our A2B_STATUS bit (write-1-clear). */ + A2B_STATUS = (1u << ch); + + DBG_LASTCMD = cmd; + DBG_ECHOCNT = ++echoes; + } + } + } +} diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/mbox-roundtrip.sh b/kernel/rv1106-enablement/mailbox/scr1-echo/mbox-roundtrip.sh new file mode 100755 index 0000000..96c7899 --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/mbox-roundtrip.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# Prove the A7<->HPMCU mailbox round-trip on channel 0. Write A2B_CMD first, then +# A2B_DAT — the A2B_DAT write is the doorbell (raises A2B_STATUS on the MCU side, +# whose echo firmware has enabled A2B_INTEN), so both CMD and DAT are current when +# the SCR1 reads them. The SCR1 echoes {cmd,dat} verbatim into B2A_CMD/B2A_DAT. +CMD=${1:-0x00001234} +DAT=${2:-0xcafef00d} +echo "liveness: DBG_STATE=$(devmem 0xff6fff08 32) (want 0x584F424D 'MBOX') echoes_before=$(devmem 0xff6fff10 32)" +devmem 0xff5c0008 32 "$CMD" # A2B_CMD(0) +devmem 0xff5c000c 32 "$DAT" # A2B_DAT(0) -- doorbell +sleep 1 +echo "sent CMD=$CMD DAT=$DAT" +echo "echoed B2A_CMD=$(devmem 0xff5c0030 32) B2A_DAT=$(devmem 0xff5c0034 32)" +echo "echoes_after=$(devmem 0xff6fff10 32)" diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/start.S b/kernel/rv1106-enablement/mailbox/scr1-echo/start.S new file mode 100644 index 0000000..fc8964a --- /dev/null +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/start.S @@ -0,0 +1,27 @@ +/* + * HPMCU watchdog entry. The BootROM-style loader (or flared's runtime + * loader) points CORE_SGRF_HPMCU_BOOT_ADDR here and releases the SCR1 core + * from CORECRU reset; there is no CRT, no interrupts, machine mode only. + */ + .section .text.start, "ax" + .globl _start +_start: + /* Belt-and-braces: no traps expected, but if one fires, land on a + * tight loop at a known address rather than executing garbage. */ + la t0, _trap + csrw mtvec, t0 + + la sp, __stack_top + + /* Clear .bss */ + la a0, __bss_start + la a1, __bss_end +1: bgeu a0, a1, 2f + sw zero, 0(a0) + addi a0, a0, 4 + j 1b + +2: call main + /* main never returns; if it does, spin. */ +_trap: +3: j 3b diff --git a/kernel/rv1106-enablement/npu/PORT-PROGRESS.md b/kernel/rv1106-enablement/npu/PORT-PROGRESS.md new file mode 100644 index 0000000..905aecd --- /dev/null +++ b/kernel/rv1106-enablement/npu/PORT-PROGRESS.md @@ -0,0 +1,336 @@ +# RKNPU kernel driver: 5.10 (Rockchip vendor BSP, v0.9.2) -> 6.18.46 port + +Status: **zImage + rockchip/rv1106-warden.dtb build cleanly, 0 errors, 0 warnings**, +with `CONFIG_ROCKCHIP_RKNPU=y` (+ `_DRM_GEM`/`_DEBUG_FS`/`_PROC_FS=y`) built in, 99 +`rknpu`-prefixed symbols linked into `System.map`, and `&npu { status = "okay"; }` +in the board dtb. **Not flashed or probed on hardware** — per this task's explicit +boundary, this is a build-only port; on-target verification (dmesg probe log, +`/dev/dri/cardN`, the version-query ioctl round trip) is deferred to the parent +session. GPL source port only — no closed blob, same class of work as the RGA and +audio ports in this series (`../rga/PORT-DONE.md`, `../audio/PORT-PROGRESS.md`). + +Trees involved: +- Target (edited): `/research/linux-6.18.46/` +- Vendor source (read-only, copy-from): + `/sdk/sysdrv/source/kernel/drivers/rknpu/` + (v0.9.2, `DRIVER_DATE "20230825"`) + +Scope and strategic framing are in `PORT-PLAN.md` (file-by-file plan, authoritative) +and `OPEN-NPU-PLAN.md` (the "open driver, closed userspace" reality — this port +delivers exactly Tier A there: an open, on-hardware-verifiable *kernel* driver, and +nothing more; it does not and cannot unlock running a model). + +## File set copied + +Per `PORT-PLAN.md` §2.1's "Yes" column, copied verbatim from the vendor tree into +`drivers/rknpu/` (patched in place afterward — see API-delta table below): + +| File | Bring in | Notes | +|---|---|---| +| `rknpu_drv.c` / `include/rknpu_drv.h` | yes | probe/remove, of_match table (incl. the RV1106 entry + `rv1106_rknpu_config`), DRM driver registration, power get/put | +| `rknpu_job.c` / `include/rknpu_job.h` | yes | job submit, IRQ handler, PC task-list execution, `rknpu_get_hw_version()`/`rknpu_get_drv_version()` | +| `rknpu_gem.c` / `include/rknpu_gem.h` | yes | GEM memory manager (DRM_GEM path) | +| `rknpu_reset.c` / `include/rknpu_reset.h` | yes | `SRST_A_RKNN`/`SRST_H_RKNN` reset control — compiled unmodified, zero API drift | +| `rknpu_iommu.c` / `include/rknpu_iommu.h` | yes (dead code path) | 61 lines; `iommu_en` stays false on our non-IOMMU DT — compiled unmodified | +| `rknpu_debugger.c` / `include/rknpu_debugger.h` | yes | `/proc/rknpu/load` continuity target for the Monitor page — compiled unmodified | +| `rknpu_mem.c`, `rknpu_mm.c`, `rknpu_fence.c` | **no** (headers only) | DMA_HEAP / SRAM / FENCE variants — not enabled for this port (§2.2). Their headers (`rknpu_mem.h`, `rknpu_mm.h`, `rknpu_fence.h`) *are* copied because `rknpu_drv.h`/`rknpu_gem.h` include them unconditionally for struct/prototype declarations, but zero object code results: every call site into these three is either `#ifdef CONFIG_ROCKCHIP_RKNPU_{DMA_HEAP,SRAM,FENCE}` (compiled out, all three configs left off) or `if (IS_ENABLED(CONFIG_ROCKCHIP_RKNPU_SRAM) && ...)` (compile-time-constant-folded to dead code and dropped by the optimizer — confirmed: none of `rknpu_mm_*`/`rknpu_mem_*` appear in `System.map`) | + +UAPI header: `rknpu_ioctl.h` was split the way `drivers/accel/rocket/` (the mainline +Rocket driver, already vendored in this tree) does it — the real content lives at +`include/uapi/drm/rknpu_ioctl.h` (mainline convention for DRM ioctl UAPI headers), +and `drivers/rknpu/include/rknpu_ioctl.h` is a one-line redirect (`#include +`) so the vendor source's unmodified `#include "rknpu_ioctl.h"` +keeps working. This also makes the header directly usable by +`rknpu_version_test.c` (below) with a single `-I` flag and no libdrm dependency. + +## Kconfig / Makefile wiring + +`drivers/rknpu/Kconfig` (new) — trimmed from the vendor's own `Kconfig`: drops the +`ROCKCHIP_RKNPU_SRAM`/`_FENCE` options and the `DRM_GEM` vs `DMA_HEAP` `choice` +block entirely (DRM_GEM is the only memory manager this port wires up — see +`PORT-PLAN.md` §1 "Memory manager choice"). Sourced from `drivers/Kconfig` right +after `source "drivers/accel/Kconfig"` (RKNPU is a classic DRM device, not +`drivers/accel/` — see §1 of `PORT-PLAN.md`, "not `/dev/accel/`"). + +`drivers/rknpu/Makefile` (new) — trimmed from the vendor's own `Makefile`: only the +five always-on objects plus GEM (`rknpu_drv.o`, `rknpu_reset.o`, `rknpu_job.o`, +`rknpu_debugger.o`, `rknpu_iommu.o`, `rknpu_gem.o` gated on +`CONFIG_ROCKCHIP_RKNPU_DRM_GEM`); no edit needed for the never-built +`rknpu_mem.o`/`rknpu_mm.o`/`rknpu_fence.o` lines because they're simply omitted +(matches `PORT-PLAN.md` §2.1's note that no Makefile edit is required, just not +setting their Kconfig symbols). `ccflags-y` adds `compat/` to the include path +**ahead of** `include/` — see the compat-shim section below for why the ordering +matters. + +Wired into `drivers/Makefile`: `obj-$(CONFIG_ROCKCHIP_RKNPU) += rknpu/` placed +immediately after `obj-y += gpu/` (rknpu registers a classic DRM device, so — like +`gpu/` itself — it must come after `char/` and `iommu/` per the existing comments +there, and building after `gpu/` specifically ensures the DRM core is ready first). + +## Config symbols set + +Via `./scripts/config --enable ` then `make ARCH=arm CROSS_COMPILE=... +olddefconfig`, confirmed `=y` in `.config` afterward (no silent Kconfig-dependency +drop — all four symbols round-tripped through `olddefconfig` unchanged): + +``` +CONFIG_ROCKCHIP_RKNPU=y +CONFIG_ROCKCHIP_RKNPU_DRM_GEM=y +CONFIG_ROCKCHIP_RKNPU_DEBUG_FS=y +CONFIG_ROCKCHIP_RKNPU_PROC_FS=y +``` + +Prerequisites already satisfied pre-port: `CONFIG_DRM=y`, `CONFIG_ARCH_ROCKCHIP=y` +(both already on from the M4 display work). Left off per `PORT-PLAN.md` §2.2/§2.6: +`ROCKCHIP_RKNPU_DMA_HEAP`, `ROCKCHIP_RKNPU_SRAM`, `ROCKCHIP_RKNPU_FENCE` — RV1106 +needs none of them (single-core NPU, no NBUF, no OPP table, no multi-domain +fencing). + +## The four compat-shim headers (`drivers/rknpu/compat/soc/rockchip/`) + +All four vendor-only headers from `PORT-PLAN.md` §2.4 (absent from mainline) are +addressed, but **not uniformly** — one of the four needed a genuinely fresh shim +rather than a vendor-verbatim copy, for a reason the plan didn't anticipate: + +### `rockchip_iommu.h` — written fresh, NOT a copy of the vendor header + +This is the one real surprise of the port. The vendor header itself already +carries a working `#if IS_ENABLED(CONFIG_ROCKCHIP_IOMMU) ... #else #endif` +split, so naively copying it looked safe. **It is not**, in this specific tree: +`CONFIG_ROCKCHIP_IOMMU` is not a vacant symbol here — it's `=y` in our `.config` +already, for the *real*, unrelated mainline IOMMU driver +(`drivers/iommu/rockchip-iommu.c`, pulled in by the M4 display/VOP2 work). That +driver implements the standard `struct iommu_ops` and does **not** export a +function called `rockchip_iommu_is_enabled()` — confirmed by grepping +`drivers/iommu/rockchip-iommu.c` for the symbol (zero hits). Had the vendor +header been copied as-is, `IS_ENABLED(CONFIG_ROCKCHIP_IOMMU)` would have evaluated +true (not false, as the plan assumed) and selected the `extern bool +rockchip_iommu_is_enabled(struct device *dev);` declaration with **no definition +anywhere in the tree** — a link failure that would only show up at the very end of +a full kernel build, not at this file's own compile step. + +Fix: `drivers/rknpu/compat/soc/rockchip/rockchip_iommu.h` is a fresh 25-line file, +unconditional (no `#if IS_ENABLED(...)` at all), providing only the one symbol +`rknpu_drv.c` actually calls: `rockchip_iommu_is_enabled()` (used once, in +`rknpu_power_off()`, inside `if (rknpu_dev->multiple_domains)` — true only for +RK3588's 3-core NPU; RV1106's `rv1106_rknpu_config` never sets it, and the board DT +carries no `iommus=` property, so this is genuinely dead code, exactly as +`PORT-PLAN.md` §2.4 predicted — the fix just couldn't be "copy the vendor header," +it had to be "write an unconditional one that doesn't shadow-collide with this +tree's real `CONFIG_ROCKCHIP_IOMMU`." + +### `rockchip_opp_select.h`, `rockchip_system_monitor.h`, `rockchip_ipa.h` — copied verbatim, safe as-is + +Unlike `ROCKCHIP_IOMMU`, none of `CONFIG_ROCKCHIP_OPP`, `CONFIG_ROCKCHIP_SYSTEM_MONITOR`, +`CONFIG_ROCKCHIP_IPA` exist anywhere in mainline (grepped every `Kconfig` in the +tree and the `.config` — zero hits for all three), so these three vendor headers' +own `#if IS_ENABLED(...)`/`#if IS_REACHABLE(...)` gates always evaluate false and +their static-inline stub branches (`-EOPNOTSUPP`/`ERR_PTR(-ENOTSUPP)`/no-ops) are +always selected — no collision risk, copied byte-for-byte from +`sdk/sysdrv/source/kernel/include/soc/rockchip/`, with only an explanatory header +comment added to each. The full struct definitions (`struct rockchip_opp_info`, +`struct monitor_dev_info`, `struct ipa_power_model_data`) are still required +unconditionally, independent of which branch is taken: `rknpu_device` (in +`rknpu_drv.h`) embeds `struct rockchip_opp_info opp_info;` **by value**, and holds +`struct monitor_dev_info *mdev_info` / `struct ipa_power_model_data *model_data` +pointers dereferenced in the (dead-for-RV1106, but still compiled) DVFS code path. + +`compat/` is listed **first** in `ccflags-y` (ahead of `include/`), specifically so +these four shadow anything a future in-tree `soc/rockchip/` addition might +introduce — `rockchip_iommu.h` in particular must never resolve to a different, +unrelated header of the same name. + +### Devfreq/OPP/monitor/IPA code: compiles, but is entirely unreferenced for our build + +Worth recording because it looked like a real blocker mid-port and turned out not +to be one: `rknpu_drv.c` carries a large `#if KERNEL_VERSION(6,1,0) > +LINUX_VERSION_CODE / #else / #endif` split around `rknpu_devfreq_init()` (two +different implementations, one per era). For 6.18 the `#else` (>= 6.1) branch is +what's textually compiled, and it references file-scope statics (`npu_mdevp`, +`npu_devfreq_profile`, `npu_cooling_power`) that a naive manual read of the +surrounding `#if` nesting suggested might only be declared under the sibling +`< 6.1` branch. **This did not manifest as a build error** — `rknpu_drv.o` compiled +clean on the first fixed pass, which is the authoritative answer (per this task's +own "prove by compiling, not by inspection" instruction — a manual preprocessor +trace over ~700 lines of nested version gates is exactly the kind of thing to +distrust vs. the compiler). Confirmed after the fact: **none** of +`rknpu_devfreq_init`, `npu_devfreq_target`, `npu_devfreq_profile`, `npu_mdevp`, or +`npu_cooling_power` appear in `System.map` — the call site into +`rknpu_devfreq_init()` (in `rknpu_probe()`) is itself gated by the same `#if +KERNEL_VERSION(6,1,0) > LINUX_VERSION_CODE`, false for 6.18, so the whole devfreq +init path is unreachable and GCC drops the unused `static` functions entirely. +Net effect matches `PORT-PLAN.md`'s framing exactly — RV1106 has no OPP table and +no DVFS — just via straightforward dead-code elimination rather than anything this +port had to force. + +## 5.10 -> 6.18 API-delta fixes (all mechanical, all found by iterating single-object builds) + +### `rknpu_drv.c` + +| # | Change | +|---|---| +| 1 | `struct drm_driver` has no `.gem_prime_mmap` member any more (the legacy driver-level `gem_prime_*` fallback vtable was removed). Dropped the `.gem_prime_mmap = drm_gem_prime_mmap,` initializer for the `KERNEL_VERSION(6,1,0) <= LINUX_VERSION_CODE` branch — not a functionality loss, because per-object mmap is already wired via `drm_gem_object_funcs.mmap = rknpu_gem_mmap_obj` in `rknpu_gem.c`'s `rknpu_gem_object_funcs` (the mechanism this field used to be a fallback *from*, per `PORT-PLAN.md` §2.5). | +| 2 | `struct drm_driver` has no `.date` member any more (dropped from mainline DRM). Removed the `.date = DRIVER_DATE,` initializer; `.major`/`.minor`/`.patchlevel` already carry the version. | +| 3 | `hrtimer_init(&t, clock, mode)` + separate `t.function = fn` assignment -> combined `hrtimer_setup(&t, fn, clock, mode)` (same fix class as the RGA port's hrtimer change, `../rga/PORT-DONE.md` item 2). | +| 4 | `platform_driver.remove`: `int (*)(struct platform_device *)` -> `void (*)(struct platform_device *)`. Changed `rknpu_remove()` from `static int ... { ...; return 0; }` to `static void ...` (dropped the trailing `return 0;`) — same fix as the RGA and audio ports. | +| 5 | `MODULE_IMPORT_NS(DMA_BUF)` -> `MODULE_IMPORT_NS("DMA_BUF")` (quoted-string form; same fix as the RGA port item 6). | + +### `rknpu_gem.c` + +| # | Change | +|---|---| +| 1 | `` and the `pfn_t` wrapper type (`__pfn_to_pfn_t()`, `PFN_DEV`) were removed entirely from mainline. Dropped the include; `vmf_insert_mixed()` (the only call site reachable at `KERNEL_VERSION(4,15,0) <= LINUX_VERSION_CODE`, which is our branch) now takes a plain `unsigned long pfn` directly — `pfn = page_to_pfn(...)` was already computing that raw value, so the fix is just passing `pfn` instead of `__pfn_to_pfn_t(pfn, PFN_DEV)`. | +| 2 | `vmap()`/`vunmap()`/`VM_MAP` used to be pulled in transitively; 6.18 needs `` included explicitly. Added it. | +| 3 | `%zu` format specifier for `rknpu_obj->size` (`-Werror=format=`): the field is declared `unsigned long`, not `size_t`, on this target — changed to `%lu`. (Pure `-Wformat` pickiness, not a real 5.10-vs-6.18 delta; the vendor's own type just doesn't match `%zu` on this ABI and newer GCC/kernel `-Werror` catches it.) | +| 4 | `iommu_map()` gained a trailing `gfp_t gfp` argument (`iommu_map(domain, iova, paddr, size, prot)` -> `iommu_map(domain, iova, paddr, size, prot, gfp)`). Added `GFP_KERNEL` at both call sites (cache-buffer path and the per-sg-entry DDR path). | +| 5 | `vma->vm_flags` is a read-only field now (direct assignment is a compile error, not just deprecated) — replaced all four sites with `vm_flags_set()`/`vm_flags_clear()`: `rknpu_gem_mmap_pages()` (`VM_MIXEDMAP`), `rknpu_gem_mmap_cache()` (`VM_MIXEDMAP`), `rknpu_gem_mmap_buffer()` (`VM_DONTCOPY\|VM_DONTEXPAND\|VM_DONTDUMP\|VM_IO` set, `VM_PFNMAP` cleared). The three read-only accesses (`vm_get_page_prot(vma->vm_flags)` in `rknpu_gem_mmap`) needed no change — only assignment is blocked. | + +`rknpu_reset.c`, `rknpu_iommu.c`, `rknpu_debugger.c`, `rknpu_job.c` needed **zero** +changes — compiled clean against 6.18 unmodified, confirming `PORT-PLAN.md` §2.5's +assessment that most of the surface was already correctly version-gated by the +vendor. + +## Device tree change + +One board-DTS override appended to `arch/arm/boot/dts/rockchip/rv1106-warden.dts` +(base `rv1106.dtsi`'s `npu@ff660000` node — `compatible`, `reg`, `interrupts` +(`GIC_SPI 109`), `clocks` (`ACLK_RKNN`/`HCLK_RKNN`), `assigned-clock-rates += <420000000>`, `resets` (`SRST_A_RKNN`/`SRST_H_RKNN`) — left untouched, per +instructions, exactly as `PORT-PLAN.md` §2.3 specified): + +```dts +&npu { + status = "okay"; +}; +``` + +No `iommus=` property added (non-IOMMU mode, matches the 5.10 boot-log finding); +no `power-domains` (single-rail). Verified against the **compiled** dtb (`dtc -I +dtb -O dts`, not just the source): `npu@ff660000` shows `status = "okay"`, and all +other properties resolved correctly (`interrupts = <0x00 0x6d 0x04>` = `GIC_SPI 109 +IRQ_TYPE_LEVEL_HIGH`, matching the dtsi). `dtc` build output for the whole board dtb +carries exactly one warning, pre-existing and unrelated to NPU (a +`graph_endpoint`/VOP2-display bidirectionality note on `rv1106.dtsi:458`, not +touched by this port). + +## Build status + +Exact commands run (per the task's build recipe): + +```sh +export PATH="/sdk/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:/usr/bin:/bin" +export ARCH=arm CROSS_COMPILE=arm-rockchip830-linux-uclibcgnueabihf- +cd /research/linux-6.18.46 +make ARCH=arm CROSS_COMPILE=$CROSS_COMPILE zImage rockchip/rv1106-warden.dtb -j"$(nproc)" +``` + +Result: **exit 0**. `grep -iE "error|warn"` over the full build log returns exactly +one line — the pre-existing, NPU-unrelated dtc warning noted above. Zero errors, +zero rknpu-related warnings. `arch/arm/boot/zImage` (8.7 MB) and +`arch/arm/boot/dts/rockchip/rv1106-warden.dtb` (36.9 KB) both produced. XZ kernel +compression and the existing console/earlycon config were left untouched, per +instructions; no other in-flight work in this tree (mailbox, audio, wifi, gmac, +etc.) was reverted or altered. + +## Verification: rknpu is genuinely linked in (not silently dropped to a module) + +Per the task's explicit instruction, checked with **host** `grep` on `System.map` +(ground truth for built-in linkage — cross-`nm` mis-lists symbols on this +toolchain, same caveat as every other port in this series): + +``` +$ grep -cE 'rknpu' System.map +99 +$ grep -E '\brknpu_probe\b|\brknpu_remove\b|rknpu_driver\b|rknpu_of_match|rknpu_drm_driver\b' System.map +c0be4230 t rknpu_remove +c0be476c t rknpu_probe +c177d4a0 r rknpu_of_match +c2108e58 d rknpu_driver +c2108ef0 d rknpu_drm_driver +``` + +99 `rknpu`-prefixed symbols are linked into the kernel image, including the +`platform_driver`'s `probe`/`remove` entry points and the `of_match_table` +(`rknpu_of_match`, confirmed at the source level to still carry the +`"rockchip,rv1106-rknpu"` compatible + `rv1106_rknpu_config` entry, untouched by +this port's fixes) and the `drm_driver` struct itself. This is the probe/of_match +symbol presence the task asked to confirm. + +## Hardware test program: `rknpu_version_test.c` + +Written to `warden-sdk/kernel/rv1106-enablement/npu/rknpu_version_test.c` per the +task's spec. Dependency-free beyond the kernel tree's own UAPI headers — no +libdrm, no target sysroot headers: + +```sh +export PATH="/sdk/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:/usr/bin:/bin" +arm-rockchip830-linux-uclibcgnueabihf-gcc \ + -I/research/linux-6.18.46/include/uapi \ + -Wall -O2 -static \ + -o rknpu_version_test \ + /kernel/rv1106-enablement/npu/rknpu_version_test.c +``` + +**This exact command was run in this session** (build-only — the resulting binary +was not copied to or executed on any target) and produced a clean ARM EABI5 static +ELF binary with exit code 0. One expected, harmless warning appears: +`#warning "Attempt to use kernel headers from user space"` from +`include/uapi/linux/types.h` — the standard notice every raw-kernel-uapi-header +userspace build gets; it does not affect correctness (`__u32` etc. are still +correctly defined with `__KERNEL__` undefined). + +What it does: tries `/dev/dri/card0`, then `card1`, then `renderD128`; on the first +one that opens, issues `DRM_IOCTL_RKNPU_ACTION` with `.flags = RKNPU_GET_DRV_VERSION` +then again with `.flags = RKNPU_GET_HW_VERSION`, and prints both. Traced against the +driver source to get the exact semantics right: +- `RKNPU_GET_DRV_VERSION` returns `RKNPU_GET_DRV_VERSION_CODE(DRIVER_MAJOR, + DRIVER_MINOR, DRIVER_PATCHLEVEL)` = `MAJOR*10000 + MINOR*100 + PATCHLEVEL` + (`rknpu_drv.c:rknpu_get_drv_version()`) — for this port's unmodified + `DRIVER_MAJOR/MINOR/PATCHLEVEL = 0/9/2`, that's raw code `902`, which the test + program decodes back to `"0.9.2"` via the UAPI header's own + `RKNPU_GET_DRV_VERSION_{MAJOR,MINOR,PATCHLEVEL}()` macros. +- `RKNPU_GET_HW_VERSION` returns a raw value read directly off the NPU core's + `VERSION`/`VERSION_NUM` registers (`rknpu_job.c:rknpu_get_hw_version()`) — no + published decode table exists for it (per `PORT-PLAN.md` §3 step 5, "checks... + returns something plausible"); the test program just prints it in hex. + +**Expected output on a successful hardware run** (parent session): + +``` +opened /dev/dri/card0 (fd=3) +driver version: 0.9.2 (raw code 902) +hw version: 0x.... (raw) +PASS: /dev/dri/card0 answered both version-query ioctls -- probe, power-get/put, +and clock/reset all exercised. +``` + +A driver version that decodes to anything other than `0.9.2` would indicate a stale +build or a stub/mock intercepting the ioctl, not a real driver response — that's +the value of checking the decoded string, not just the ioctl return code. + +## Explicitly deferred to the parent session (not done here, per this task's boundary) + +- **No flashing, no boot, no `dmesg` check.** This session never touched hardware. +- Boot-time probe verification: `dmesg | grep -i rknpu` should show clean + clock/reset/IRQ acquisition, no panic, no permanent `-EPROBE_DEFER` (a single + deferral early at boot, before other clock/reset providers are up, would be + normal — same caveat class as the audio port's acodec probe-order note). + Compare against the 5.10 baseline probe log if available (`PORT-PLAN.md` §3 + step 3). +- `ls -la /dev/dri/` should show a new `cardN`/`renderD1xx` — classic DRM node + (this port intentionally does **not** produce a `/dev/accel/` node; see + `PORT-PLAN.md` §1 and `OPEN-NPU-PLAN.md` §1.3 for why mainline's own + `drivers/accel/rocket/` driver — RK3588/ARM64-only — is a different codebase + that doesn't reach RV1106 at all). +- Run `rknpu_version_test` (built above) against the real node; confirm the + decoded driver version prints `0.9.2` and the hw version is non-zero/plausible. +- **Not attempted, not required for this milestone**: `DRM_IOCTL_RKNPU_SUBMIT`, + any `.rknn` model, `librknnrt` — that's the closed-userspace question + `OPEN-NPU-PLAN.md` §1.2–1.4 covers; out of scope here by design (no blob is + shipped by this port, and none is needed to prove the kernel driver itself). + +## Also updated this session + +`../DRIVER-PARITY.md`'s NPU row: `⬜ M6 — plan: npu/PORT-PLAN.md` -> `🔨 M6 built, +0 errors/0 warnings, 99 rknpu-prefixed symbols in System.map, &npu +{status="okay"} in the dtb — not yet flashed/probed on hardware`. diff --git a/kernel/rv1106-enablement/npu/VERIFIED.md b/kernel/rv1106-enablement/npu/VERIFIED.md new file mode 100644 index 0000000..fa4e5d9 --- /dev/null +++ b/kernel/rv1106-enablement/npu/VERIFIED.md @@ -0,0 +1,43 @@ +# NPU (rknpu) open kernel driver — ✅ VERIFIED on warden-c8a3 (2026-08-25) + +The open GPL rknpu kernel driver runs on our self-built Linux 6.18.46. This is the +achievable open end state (Tier A in `OPEN-NPU-PLAN.md`); open *compute* remains a +from-scratch RE project (see the ceiling note below). + +## Evidence (serial, _b slot = our 6.18) +``` +[drm] Initialized rknpu 0.9.2 for ff660000.npu on minor 1 +/dev/dri/ -> card0 (VOP display) card1 (rknpu) renderD128 +$ rknpu_version_test + /dev/dri/card0: not an rknpu node (version ioctl: Invalid argument) + opened /dev/dri/card1 (fd=3) + driver version: 0.9.2 (raw code 902) + hw version: 0x54524548 (raw) + PASS: /dev/dri/card1 answered both version-query ioctls -- probe, + power-get/put, and clock/reset all exercised. +``` +The `RKNPU_GET_DRV_VERSION`/`RKNPU_GET_HW_VERSION` ioctls exercise the full +dispatch → power-domain get/put → clock/reset path, so a clean answer confirms the +whole driver bring-up, not just registration. + +## The fix that took it from probe-fail to PASS +The base dtsi `npu@ff660000` node declares its interrupt but has **no +`interrupt-names`**; the rknpu driver requests its IRQ by name (`"npu_irq"`), so +probe bailed `error -ENXIO: IRQ npu_irq not found` and never registered its DRM +device. Board DTS override adds `interrupt-names = "npu_irq";` (+ `status="okay"`). +(The rest of the port — GPL source, 4 compat-shim headers for dead-code vendor +headers, 10 mechanical 6.18 API deltas — is in `PORT-PROGRESS.md`.) + +Also: the version test must iterate DRM cards and keep the one that ANSWERS the +ioctl — the display card (card0) opens fine but returns EINVAL. Fixed in +`rknpu_version_test.c`. + +## The honest ceiling (why "100% open NPU" stops at the driver) +The kernel driver only DMAs an opaque userspace-authored `regcmd` blob into the PC +registers and pulses go — it never inspects the compute stream. The compute-engine +register map (TRM Part 2) is not public for RV1106, there is **zero open RE prior +art** for this NPU generation, and only the closed RKNN-Toolkit2 compiler emits +valid regcmd. Mainline `accel/rocket` + Mesa Teflon are RK3588-only (64-bit). So an +open compiler/runtime is a from-scratch, ~person-year register-RE project, scoped +(not staffed) in `OPEN-NPU-PLAN.md` (M-NPU-2 spike / M-NPU-3). We ship the open +driver; we do NOT ship any closed blob. diff --git a/kernel/rv1106-enablement/npu/rknpu_version_test.c b/kernel/rv1106-enablement/npu/rknpu_version_test.c new file mode 100644 index 0000000..fd0f83f --- /dev/null +++ b/kernel/rv1106-enablement/npu/rknpu_version_test.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * rknpu_version_test.c -- standalone hardware smoke test for the RKNPU + * kernel driver port (see PORT-PLAN.md / PORT-PROGRESS.md in this + * directory). + * + * Opens a DRM node (tries /dev/dri/card0, then card1, then renderD128), + * issues DRM_IOCTL_RKNPU_ACTION with RKNPU_GET_DRV_VERSION and then + * RKNPU_GET_HW_VERSION, and prints the decoded results. This exercises the + * full ioctl-dispatch -> power-get/put -> clock/reset path with zero + * dependency on a regcmd buffer or the (closed) RKNN runtime -- see + * OPEN-NPU-PLAN.md §1.4 Tier A and PORT-PLAN.md §3 step 5. + * + * Dependency-free beyond the UAPI header: build with + * -I/include/uapi + * so both and (and the linux/types.h, + * linux/ioctl.h they need) resolve entirely out of the kernel tree's own + * include/uapi/ -- no libdrm, no target sysroot headers required. + * + * Cross-compile (see PORT-PROGRESS.md for the full recipe): + * + * export PATH="/sdk/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:/usr/bin:/bin" + * arm-rockchip830-linux-uclibcgnueabihf-gcc \ + * -I/research/linux-6.18.46/include/uapi \ + * -Wall -O2 -static -o rknpu_version_test rknpu_version_test.c + * + * (add -static if the target rootfs's libc version/ABI is in doubt; drop it + * for a smaller dynamically-linked binary if the rootfs libc is known good.) + * + * Expected output on a working probe: + * + * opened /dev/dri/card0 (fd=3) + * driver version: 0.9.2 (raw code 902) + * hw version: 0x.... (raw) + * PASS: /dev/dri/card0 answered both version-query ioctls -- probe, + * power-get/put, and clock/reset all exercised. + * + * "0.9.2" is DRIVER_MAJOR/MINOR/PATCHLEVEL from the vendor source + * (rknpu_drv.h) unmodified by this port -- a match confirms the ioctl + * round-trip reached real driver code, not a stub. The hw version is a raw + * value read directly off the NPU core's VERSION/VERSION_NUM registers + * (rknpu_job.c:rknpu_get_hw_version()) -- any non-zero, non-0xffffffff + * value is a plausible "the register block is alive" signal; there is no + * published decode table for it beyond that (PORT-PLAN.md §3 step 5). + */ + +#include +#include +#include +#include +#include +#include + +#include + +static const char *const candidates[] = { + "/dev/dri/card0", + "/dev/dri/card1", + "/dev/dri/renderD128", + NULL, +}; + +static int do_action(int fd, __u32 flags, __u32 *value) +{ + struct rknpu_action act; + + memset(&act, 0, sizeof(act)); + act.flags = flags; + + if (ioctl(fd, DRM_IOCTL_RKNPU_ACTION, &act) < 0) + return -errno; + + *value = act.value; + return 0; +} + +int main(void) +{ + int fd = -1; + const char *path = NULL; + __u32 drv_version = 0; + __u32 hw_version = 0; + int ret; + int i; + + /* There may be several DRM cards (the VOP display registers card0, the + * NPU registers its own). Try each candidate and keep the FIRST that + * actually answers the RKNPU version ioctl -- opening successfully is not + * enough (the display card opens fine but returns EINVAL). */ + for (i = 0; candidates[i] != NULL; i++) { + int f = open(candidates[i], O_RDWR | O_CLOEXEC); + if (f < 0) { + fprintf(stderr, "open(%s): %s\n", candidates[i], + strerror(errno)); + continue; + } + if (do_action(f, RKNPU_GET_DRV_VERSION, &drv_version) == 0) { + fd = f; + path = candidates[i]; + break; + } + fprintf(stderr, "%s: not an rknpu node (version ioctl: %s)\n", + candidates[i], strerror(errno)); + close(f); + } + + if (fd < 0) { + fprintf(stderr, + "FAIL: no DRM node answered the RKNPU version ioctl -- " + "is CONFIG_ROCKCHIP_RKNPU probed? check " + "`dmesg | grep -i rknpu` and `ls -la /dev/dri/`\n"); + return 1; + } + + printf("opened %s (fd=%d)\n", path, fd); + ret = 0; + printf("driver version: %u.%u.%u (raw code %u)\n", + RKNPU_GET_DRV_VERSION_MAJOR(drv_version), + RKNPU_GET_DRV_VERSION_MINOR(drv_version), + RKNPU_GET_DRV_VERSION_PATCHLEVEL(drv_version), drv_version); + + ret = do_action(fd, RKNPU_GET_HW_VERSION, &hw_version); + if (ret) { + fprintf(stderr, + "FAIL: DRM_IOCTL_RKNPU_ACTION(RKNPU_GET_HW_VERSION): %s\n", + strerror(-ret)); + close(fd); + return 1; + } + printf("hw version: 0x%08x (raw)\n", hw_version); + + printf("PASS: %s answered both version-query ioctls -- probe, " + "power-get/put, and clock/reset all exercised.\n", path); + + close(fd); + return 0; +}