Files
BFE Engineering 42fb386f60 docs: ASCII typography and style normalization across all repo text
Replace every em dash with real punctuation (rewrites, not hyphen swaps)
in docs, code comments, scripts, configs, and the port records; convert
en dashes, curly quotes, ellipsis glyphs, arrows, and section signs to
ASCII; drop machine-writing tell phrases from living docs. ADR titles
now use a colon. The M2 bring-up DTS model string carried an em dash
into the patch series and its record echoes; fixed at both, and the full
series re-verified to apply cleanly onto pristine 6.18.46. One comment
in freshness.h deliberately names the em dash glyph the UI renders as
the unknown mark; that is data, kept as prose naming it.

Verified: cargo tests (sim, config-lint, rs485-bridge), shellcheck,
both driver MC/DC harnesses, patches-apply.
2026-08-31 12:31:27 -06:00

23 KiB

RKNPU kernel driver -> 6.18: port plan (M6 class)

Scope: port the kernel driver only (rknpu.ko's source, statically built into our tree) so /dev/dri/cardN (or renderD1xx) binds on the RV1106 NPU and answers a version-query ioctl. This does not run a model. See section 3 for why that's a separate, much bigger, and largely closed problem. Written against the same target as the rest of this port: Linux 6.18.46 vanilla (flare-edge/research/linux-6.18.46/), forward-ported from vendor 5.10.160, built with our arm-rockchip830-...-gcc 8.3 toolchain. See ../PORT-STATUS.md and ../../docs/bringup.md for the method and milestones this slots into (M6, listed in ../DRIVER-PARITY.md as "NPU (rknpu, ff660000) | out-of-tree | [ ] M6").

Builds on warden-sdk/docs/npu-graphics-feasibility.md, which already read this same driver source to answer a narrower question (can the NPU do graphics: no). This document answers the porting question that doc explicitly deferred.


1. What we're forward-porting (vendor SDK source)

Source: flare-edge/sdk/sysdrv/source/kernel/drivers/rknpu/, vendor version 0.9.2 (DRIVER_MAJOR/MINOR/PATCHLEVEL in include/rknpu_drv.h:34-36, DRIVER_DATE "20230825"), currently loaded on the shipping 5.10.160 kernel as rknpu.ko, IRQ ff660000.npu.

The driver already targets RV1106 natively: this is not a from-scratch adaptation

The vendor driver is a single multi-SoC codebase, not something written for RK3588 that we'd have to adapt. rknpu_drv.c's of_match table already carries a dedicated RV1106 entry and config struct:

// rknpu_drv.c:196-198
{ .compatible = "rockchip,rv1106-rknpu", .data = &rv1106_rknpu_config },

// rknpu_drv.c:143-160
static const struct rknpu_config rv1106_rknpu_config = {
	.dma_mask = DMA_BIT_MASK(32),
	.pc_data_amount_scale = 2,
	.pc_task_number_bits = 16,
	.pc_task_number_mask = 0xffff,
	.pc_task_status_offset = 0x3c,
	.pc_dma_ctrl = 0,
	.bw_enable = 1,
	.irqs = rknpu_irqs, .resets = rknpu_resets,
	.nbuf_phyaddr = 0, .nbuf_size = 0,        // no NBUF wired for this SoC
	.max_submit_number = (1 << 16) - 1,
};

And the devicetree side is already complete in rv1106.dtsi (base tree, status = "disabled"), nothing to author, only enable:

// arch/arm/boot/dts/rv1106.dtsi:1127-1138
npu: npu@ff660000 {
	compatible = "rockchip,rv1106-rknpu";
	reg = <0xff660000 0x10000>;
	interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&cru ACLK_RKNN>, <&cru HCLK_RKNN>;
	clock-names = "aclk", "hclk";
	assigned-clocks = <&cru ACLK_RKNN>;
	assigned-clock-rates = <420000000>;
	resets = <&cru SRST_A_RKNN>, <&cru SRST_H_RKNN>;
	reset-names = "srst_a", "srst_h";
	status = "disabled";
};

No power-domains property (RV1106's NPU is single-core, single-rail, unlike RK3588's 3-core NPU which needs genpd_dev_npu0/1/2) and no iommus property (matches the 5.10 boot-log finding already on record: "rknpu iommu device-tree entry not found!, using non-iommu mode", npu-graphics-feasibility.md:123-126). Both facts materially shrink the v1 port's scope. See section 2.3 and section 2.4.

The uapi/job model (for context, already characterized in the feasibility doc)

include/rknpu_ioctl.h: struct rknpu_task (regcfg_amount, regcfg_offset, regcmd_addr) + struct rknpu_submit (task_obj_addr, regcfg_obj_addr, core_mask, fence_fd), a register command-list (regcmd) task-queue model, submitted via DRM_IOCTL_RKNPU_SUBMIT and executed to completion with the driver blocking on a hardware IRQ (rknpu_job.c, wait_event_timeout). Six ioctls total: RKNPU_ACTION, RKNPU_SUBMIT, RKNPU_MEM_{CREATE,MAP,DESTROY,SYNC} (rknpu_ioctl.h:288-322). RKNPU_ACTION carries the version queries this plan's verify step uses: RKNPU_GET_HW_VERSION = 0, RKNPU_GET_DRV_VERSION = 1 (rknpu_ioctl.h:113-114).

Memory manager choice: DRM GEM (not DMA-heap)

Kconfig offers a mutually-exclusive choice: ROCKCHIP_RKNPU_DRM_GEM (default) vs ROCKCHIP_RKNPU_DMA_HEAP. Pick DRM_GEM: M4 (VOP2 display) already pulls the DRM core into this kernel for the panel, so there's no new subsystem cost, and DRM_GEM is the vendor's default/most-tested path. This also means the node that appears is a classic DRM char device (/dev/dri/cardN / renderD1xx via drm_dev_alloc/drm_dev_register, rknpu_drv.c:725,730), not the newer /dev/accel/ framework mainline's own rocket driver uses (section 3). Don't confuse the two node namespaces when verifying.


2. Files, config, and the concrete 5.10->6.18 API deltas

2.1 File list: v1 minimal port

File Bring in? Why
rknpu_drv.c / include/rknpu_drv.h Yes probe/remove, of_match table, DRM driver registration, power get/put
rknpu_job.c / include/rknpu_job.h Yes job submit, IRQ handler, PC task-list execution
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
rknpu_iommu.c / include/rknpu_iommu.h Yes, but dead code path 61 lines, self-contained, already version-gated to 6.1; harmless to carry even though iommu_en stays false on our non-IOMMU DT (section 2.4)
rknpu_debugger.c / include/rknpu_debugger.h Yes (optional) /proc/rknpu/load: the Monitor page already polls this on 5.10 (npu-graphics-feasibility.md:151-158); keep for continuity even though wiring the UI back up is out of scope here
rknpu_mem.c No only for ROCKCHIP_RKNPU_DMA_HEAP: we're not using that memory manager
rknpu_mm.c / include/rknpu_mm.h No SRAM/NBUF allocator (ROCKCHIP_RKNPU_SRAM, needs NO_GKI); rv1106_rknpu_config has nbuf_phyaddr=0, nbuf_size=0, dead weight on this SoC
rknpu_fence.c / include/rknpu_fence.h No (v1) ROCKCHIP_RKNPU_FENCE/SYNC_FILE: dma-fence cross-driver sync, not needed to prove basic binding; revisit if a real workload needs fenced submission later
Kconfig, Makefile Yes, trimmed drop the rknpu_mem.o/rknpu_mm.o/rknpu_fence.o conditional lines' configs (leave the Makefile structure as-is; it's already obj-$(CONFIG_...)-gated per file, so simply not enabling those Kconfig symbols is sufficient; no Makefile edit required)

2.2 Config symbols (built-in, matching this port's established pattern:

M1-M3 build everything statically to avoid the vermagic class of bug that blocks aic8800.ko today, PORT-STATUS.md:114-115)

CONFIG_ROCKCHIP_RKNPU=y
CONFIG_ROCKCHIP_RKNPU_DRM_GEM=y
CONFIG_ROCKCHIP_RKNPU_DEBUG_FS=y      # depends on DEBUG_FS (already on for bring-up)
CONFIG_ROCKCHIP_RKNPU_PROC_FS=y       # /proc/rknpu/load continuity
# leave off for v1: ROCKCHIP_RKNPU_DMA_HEAP, ROCKCHIP_RKNPU_SRAM, ROCKCHIP_RKNPU_FENCE
CONFIG_DRM=y                          # already required by M4 (VOP2)

2.3 DT change

One-line status flip on the board DT (the same pattern M2/M3 used for grf-clock-controller and the eMMC node: override in the board file, don't touch the base rv1106.dtsi):

&npu {
	status = "okay";
};

No new properties needed: compatible/reg/interrupts/clocks/resets are already correct and match the driver's own rv1106_rknpu_config exactly (section 1). Do not add an iommus = property for v1. See section 2.4.

2.4 The real build blocker: four vendor-only soc/rockchip/*.h headers

This is the one item in this plan that isn't "already handled by the vendor's own version gates", verified directly against our flare-edge/research/linux-6.18.46/ tree, not assumed:

// rknpu_drv.c:37-40 (inside #ifndef FPGA_PLATFORM, which is never defined for
// our build: grep of Makefile/Kconfig shows no FPGA_PLATFORM define anywhere)
#include <soc/rockchip/rockchip_iommu.h>
#include <soc/rockchip/rockchip_opp_select.h>
#include <soc/rockchip/rockchip_system_monitor.h>
#include <soc/rockchip/rockchip_ipa.h>

find flare-edge/research/linux-6.18.46/include -iname 'rockchip_{iommu,opp_select, system_monitor,ipa}.h' returns nothing. All four are Rockchip downstream-BSP convenience headers (DVFS/OPP-table selection, thermal/system-monitor registration, IPA power-model, and a vendor wrapper around the IOMMU-core API) that were never upstreamed. rknpu_drv.h:21-23 pulls in rockchip_opp_select.h unconditionally too, gated only by KERNEL_VERSION(5,10,0) <= LINUX_VERSION_CODE, true for 6.18, so it's compiled by default, not something a Kconfig toggle avoids.

All four are genuinely dead code for RV1106 at runtime, which is what makes this a small, well-scoped fix rather than a real feature to build:

  • rockchip_iommu_is_enabled() (rknpu_drv.c:902) is only called inside if (rknpu_dev->multiple_domains): true only for RK3588's 3-core NPU; RV1106 never sets it (no power-domains property, section 1).
  • The OPP/system-monitor/IPA calls drive dynamic frequency/voltage scaling and thermal cooling-device registration against an OPP table. RV1106's DT pins a single fixed clock rate (assigned-clock-rates = <420000000>) and has no operating-points-v2 table; none of this is exercised today either.

Fix: add small local compat shim headers (in this port's own include path, ahead of the vendor source's include search path) providing just the symbols these call sites reference, rockchip_iommu_is_enabled() returning false, and no-op/-ENOTSUPP stand-ins for the opp/monitor/ipa registration calls actually referenced in rknpu_drv.c. This is the same "compat shim for a header that moved or doesn't exist upstream" pattern already used for clk-rv1106.c's panic_notifier_list move (../PORT-STATUS.md:18-20), same class of fix, same low risk, because the code behind it is provably dead for this SoC's DT. Do not reach for #define FPGA_PLATFORM as a shortcut: that macro also guards the reset-control logic in rknpu_reset.c (nearly the whole file is #ifndef FPGA_PLATFORM), which we need live; it's too blunt an instrument here.

2.5 Surfaces already handled by the vendor driver's own version gates

(verified against 6.18.46 headers directly, not assumed)

The driver was already written to track multiple kernel versions (npu-graphics-feasibility.md:264-269 first flagged this). Checked what's actually still true at 6.18:

Vendor gate (rknpu_drv.c) 6.18 status (verified)
#if KERNEL_VERSION(6,1,0) > LINUX_VERSION_CODE around .gem_free_object_unlocked Correctly skipped: that field is gone from struct drm_driver in 6.18's include/drm/drm_drv.h (grepped, zero hits), and the driver's #else branch already uses the modern struct drm_gem_object_funcs (.free, .export, .get_sg_table, .vmap, .vunmap, .mmap, rknpu_gem.c:352-358)
DEFINE_DRM_GEM_FOPS(...) (6.1+) vs hand-rolled file_operations 6.1+ branch applies; macro is a standard DRM-core helper, present in 6.18
.gem_prime_mmap = drm_gem_prime_mmap (6.1+) vs a custom rknpu_gem_prime_mmap 6.1+ branch applies
struct drm_driver fields the vendor initializer sets (major, minor, patchlevel, driver_features, dumb_create, dumb_map_offset) All still present in 6.18's drm_drv.h (line-checked)
DRM_IOCTL_DEF_DRV(...) macro (ioctl table) Still defined in 6.18's include/drm/drm_ioctl.h:151
iommu_map() / iommu_unmap() / iommu_get_domain_for_dev() / iommu_attach_device() / iommu_detach_device() (rknpu_gem.c, rknpu_reset.c) iommu_map()'s extern signature is unchanged in 6.18's include/linux/iommu.h:914 (mainline did add a newer iommu_map_nosync() alongside it, but didn't remove the classic call), moot anyway since this path is dead on our non-IOMMU DT (section 2.4)
devm_reset_control_get, clk_bulk_data, pm_runtime_get_sync/put_sync/resume_and_get Stable mainline APIs across the whole 5.10->6.18 span; no gate needed

Net: outside the four-header fix in section 2.4, this is expected to be a build-fix-build pass, not a rewrite. Confirm by actually compiling into the tree (the checks above are header-presence/signature verification, not a build).

2.6 Explicitly deferred (not required to prove the driver binds)

  • IOMMU enablement. Stays off: matches current 5.10 runtime behavior and avoids the newer-kernel IOMMU-core churn entirely (mainline replaced iommu_domain_alloc(bus) with device-based iommu_paging_domain_alloc(dev) somewhere in the 6.x series, confirmed by grepping 6.18.46's iommu.h, which has the new call and no bus-based iommu_domain_alloc). Since our DT carries no iommus= property, this churn never gets compiled against in the first place.
  • dma-fence / ROCKCHIP_RKNPU_FENCE. Cross-driver sync primitive, not needed to answer a version-query ioctl.
  • DVFS / thermal cooling / multi-power-domain. RV1106-inapplicable per section 2.4; stubbed out, not implemented.
  • SRAM/NBUF allocator. Dead weight on this SoC's config table (section 2.1).

3. Verify steps (driver binding only, no model, no RKNN runtime)

Uses the same proven safe-test loop as M2/M3: the A/B _b-slot one-shot boot on warden-c8a3 (../../docs/m2-boot-on-c8a3.md), never touches the working _a slot, auto-reverts on hang.

  1. Build: CONFIG_ROCKCHIP_RKNPU=y (+ the symbols in section 2.2) added to the defconfig fragment; rknpu_drv.o/rknpu_job.o/rknpu_gem.o/rknpu_reset.o/ rknpu_iommu.o/rknpu_debugger.o compile clean into built-in.a; this is where the section 2.4 shim headers get proven, not just inspected.
  2. DT: npu@ff660000 flipped to okay; dtc -W clean, no warnings, no overrun of the existing DTB-placement fix from M2 (PORT-STATUS.md's "place the fdt high" note; a bigger built-in.a makes this worth re-checking).
  3. Boot (via the _b-slot loop): dmesg | grep -i rknpu shows the platform_driver probing without error, clock/reset/IRQ acquired, no panic, no -EPROBE_DEFER stall. Compare against the 5.10 baseline probe log for the same node if available.
  4. Node appears: ls -la /dev/dri/ shows a new cardN/renderD1xx for the npu, classic DRM char device (not /dev/accel/, see section 1).
  5. Trivial ioctl, not a model: a small host-buildable C program opens the DRM node and issues DRM_IOCTL_RKNPU_ACTION with {.flags = RKNPU_GET_DRV_VERSION} (rknpu_ioctl.h:114), checks the returned value decodes to 0.9.2 (RKNPU_GET_DRV_VERSION_MAJOR/MINOR/PATCHLEVEL macros, rknpu_ioctl.h:52-54), and/or RKNPU_GET_HW_VERSION returns something plausible. This exercises the full ioctl-dispatch -> power-get/put -> clock/reset path with zero dependency on a regcmd buffer or the RKNN runtime.
  6. Explicitly not required for "done" here: DRM_IOCTL_RKNPU_SUBMIT, any .rknn model, librknnrt. That's the userspace question: section 4.

Effort estimate

Smaller than M1 (clk/pinctrl: required inferring an unknown CPU-clock mux from a sibling diff) and smaller than the display work ahead in M4 (register-map guesswork against RV1126/RK3568 siblings). This one is closer in shape to M3 ("the eMMC node was all M3 needed", PORT-STATUS.md:99): the DT is already fully specified upstream, the driver's C source already has a dedicated, tested RV1106 config table and of_match entry (section 1), and the GEM/DRM surface is already correctly version-gated past 6.1 (section 2.5). The concentrated risk is (a) actually compiling the section 2.4 shims against real 6.18 headers rather than trusting the header-presence check above, and (b) the possibility of additional vendor-only symbols not surfaced by this read-through. Realistic order of magnitude: low-single-digit engineer-days to a clean probe + version-ioctl round trip on hardware, assuming no surprise blocks the way M2's boot-image format did.


4. The userspace-runtime reality: what porting the kernel driver does (and does NOT) unlock

The instruction that motivated this document was explicit: don't let "the driver is open source" imply the NPU becomes open-source-usable. It doesn't. This section updates npu-graphics-feasibility.md section 4's conclusion with the current (2026-08-24) state of every open effort found.

4.1 The kernel driver itself: genuinely open, and this is a real forward-port of Rockchip's own code

rknpu_drv.c et al. are SPDX GPL-2.0, authored by Rockchip (Felix Zeng <felix.zeng@rock-chips.com>), and are the actual vendor driver: the same driver every Rockchip Linux SDK ships, mirrored at github.com/airockchip/rknpu / github.com/rockchip-linux/rknpu. Porting it forward is legitimate, license-clean work, not a workaround. This is a different codebase from mainline's own driver (section 4.2): don't conflate "port the vendor driver" with "adopt mainline's accel/rocket"; they are unrelated implementations of the same hardware class, and only one of them (the vendor one) covers RV1106 at all.

4.2 Mainline accel/rocket: real, merged, and does not reach RV1106

  • Merged into mainline Linux and Mesa in 2025-07, Tomeu Vizoso, "Rockchip NPU update 6: We are in mainline!", following the LKML series "[PATCH v2 0/7] New DRM accel driver for Rockchip's RKNN NPU". Present verbatim in our own vendored flare-edge/research/linux-6.18.46/drivers/accel/rocket/.
  • Kconfig hard-excludes RV1106 by architecture, before generation is even a question: depends on (ARCH_ROCKCHIP && ARM64) || COMPILE_TEST (drivers/accel/rocket/Kconfig in our 6.18.46 tree). RV1106 is a 32-bit Cortex-A7 (arch/arm, confirmed on our own hardware: PORT-STATUS.md's boot log, CPU: ARMv7 Processor). This is a Kconfig dependency, not necessarily an unfixable technical wall on its own, but it signals no one has done the 32-bit validation work, on top of the register-level work below.
  • Hardware coverage, per the driver's own docs (Documentation/accel/rocket/index.rst in our tree): "Hardware currently supported: * RK3588." Nothing else, as shipped in 6.18.46.
  • RK3576: active, but incomplete, and not in our tree. A 2026-07-15 reverse-engineering effort ( CNX Software, code at gahingwoo/linux-rk3576-npu) got single-task inference working end-to-end on a Radxa ROCK 4D running Linux 7.1-rc5, but multi-task chained inference (any real multi-layer network) fails: only the first task per NPU power session actually computes. Not merged into the kernel we're building against. Cited here because it's the closest active precedent to "port Rocket to a new RKNPU generation," and even that isn't production-usable yet.
  • RK3568/RK3566: an out-of-tree community fork exists (Armbian forum, "ODROID-M1: RK3568 NPU on the open stack"), built on accel/rocket "with local fixes" atop the RK3588 Mesa merge request, requiring byte-level comparison against captured vendor command streams to work out weight-layout and CBUF differences from RK3588. Not merged upstream. Confirms the general pattern: porting Rocket to a new RKNPU generation is a bespoke reverse-engineering project per SoC, not a recompile, the same conclusion npu-graphics-feasibility.md already reached, now with two more data points (RK3576, RK3568) supporting it.
  • RV1106/RV1103: zero hits in this research. No mainline coverage, no known public fork, no known RE project targeting it specifically (unlike RK3568 and RK3576, which both have named, in-progress efforts). This is the least-covered tier of the RKNPU family in the open-source world today.

4.3 Mesa Teflon (the TFLite delegate): entirely downstream of Rocket's coverage

Merged into Mesa 24.1 (Phoronix, docs.mesa3d.org/teflon.html) as a Gallium3D frontend for TensorFlow Lite. Per its own docs: "Teflon only works with etnaviv or rocket gallium drivers." There is no Teflon path independent of a working Rocket kernel driver underneath it, so Teflon's real-world Rockchip coverage is exactly Rocket's: solid on RK3588, experimental/WIP on RK3576 and (unofficially) RK3568, absent for RV1106.

4.4 Independent RE efforts on the closed regcmd ISA: exploratory, not a compiler

phhusson/rknpu-reverse-engineering ("Because RKNPU only knows 4D") targets RK3588 specifically, is in an exploratory/documentation stage (structures like regcfg_amount/regcmd_addr identified, DRM device enumerated) with no compiled tool, compiler, or runtime output, and does not touch RV1106. This matches npu-graphics-feasibility.md's existing finding that the regcmd ISA is undocumented outside Rockchip and reasoned-about only, not published. No new project has changed that for any SoC generation, let alone this one.

4.5 Net conclusion (updated, still holds)

Porting rknpu.ko to 6.18 gets you an open, working kernel-level path: the char/DRM device, GEM memory management, clock/reset/power sequencing, and the raw DRM_IOCTL_RKNPU_SUBMIT job-queue mechanism. It does not get you an open way to produce a valid job for that queue. For RV1106 specifically, unlike RK3588 (has mainline Rocket + Teflon) or even RK3576/RK3568 (have active, imperfect RE efforts), there is no open compiler, no open runtime, and no known public reverse-engineering project of any kind. Every real inference workload on this NPU has to go through the closed pipeline (npu-graphics-feasibility.md section 1: RKNN-Toolkit2 on a PC -> .rknn blob -> librknnrt/RKNN C API on-device) for the foreseeable future; porting the kernel driver is worth doing (it's real, bounded, evidence-backed work, same class as RGA), but it does not change that reality, and shouldn't be scoped or sold as if it does.


Layout (this directory, once work starts)

npu/
  PORT-PLAN.md          this document
  compat/                (to add) local stub headers for section 2.4:
    soc/rockchip/rockchip_iommu.h
    soc/rockchip/rockchip_opp_select.h
    soc/rockchip/rockchip_system_monitor.h
    soc/rockchip/rockchip_ipa.h

The vendor driver source itself is not duplicated here (same convention as clk/, pinctrl/, mach/: vendor source stays forward-ported in flare-edge/research/linux-6.18.46/ as a scratch tree; only the durable delta ( compat shims, DT fragment, config fragment) belongs in this repo, captured as a patch series once M6 actually lands).