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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wB8KB3MMQztRDXCMCkPrf
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
54f961c6b6
commit
7e76553bd7
@@ -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): `<flare-edge>/research/linux-6.18.46/`
|
||||
- Vendor source (read-only, copy-from):
|
||||
`<flare-edge>/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
|
||||
<drm/rknpu_ioctl.h>`) 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 <SYMBOL>` 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 <stub> #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 | `<linux/pfn_t.h>` 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 `<linux/vmalloc.h>` 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="<flare-edge>/sdk/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:/usr/bin:/bin"
|
||||
export ARCH=arm CROSS_COMPILE=arm-rockchip830-linux-uclibcgnueabihf-
|
||||
cd <flare-edge>/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="<flare-edge>/sdk/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:/usr/bin:/bin"
|
||||
arm-rockchip830-linux-uclibcgnueabihf-gcc \
|
||||
-I<flare-edge>/research/linux-6.18.46/include/uapi \
|
||||
-Wall -O2 -static \
|
||||
-o rknpu_version_test \
|
||||
<warden-sdk>/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`.
|
||||
@@ -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.
|
||||
@@ -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<kernel-tree>/include/uapi
|
||||
* so both <drm/drm.h> and <drm/rknpu_ioctl.h> (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="<flare-edge>/sdk/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf/bin:/usr/bin:/bin"
|
||||
* arm-rockchip830-linux-uclibcgnueabihf-gcc \
|
||||
* -I<flare-edge>/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 <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <drm/rknpu_ioctl.h>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user