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.
This commit is contained in:
BFE Engineering
2026-08-31 12:31:27 -06:00
parent 80d9a9539b
commit 42fb386f60
111 changed files with 1261 additions and 1261 deletions
+54 -54
View File
@@ -1,18 +1,18 @@
# Open NPU to 100% plan, feasibility, and the honest ceiling
# Open NPU to 100%: plan, feasibility, and the honest ceiling
**Goal as stated:** take the RV1106 NPU to "100% open source, verified on
**Goal as stated:** take the RV1106 NPU to "100%, open source, verified on
hardware." **Bottom line up front:** two very different things live under that
sentence, and only one of them is reachable soon.
1. **Open, on-hardware-verified _kernel driver_** (`/dev/dri/cardN` binds, answers
a version-query ioctl on our self-built Linux 6.18.46). **Achievable now**,
low-single-digit engineer-days, license-clean GPL forward-port. This is the
`PORT-PLAN.md` work in this directory a real, bounded milestone (M6).
`PORT-PLAN.md` work in this directory, a real, bounded milestone (M6).
2. **Open _userspace_ that runs a real model** (an open compiler/encoder emitting a
valid `regcmd` stream this driver can submit, no closed `librknnrt`, no `.rknn`
blob). **Not achievable on any near horizon.** It is a from-scratch,
per-SoC register reverse-engineering project with **zero existing public prior
art for RV1106/RV1103** the least-covered tier of the entire RKNPU family.
art for RV1106/RV1103**, the least-covered tier of the entire RKNPU family.
This document scopes both, states the most ambitious end state that is actually
achievable, and defines the concrete first milestone toward open compute.
@@ -23,7 +23,7 @@ document is the strategic wrapper and the item-(2) reality check. Read both.
## 1. Brutally honest feasibility
### 1.1 "Port the GPL kernel driver" TRACTABLE (do it)
### 1.1 "Port the GPL kernel driver": TRACTABLE (do it)
- The vendor driver (`flare-edge/sdk/sysdrv/source/kernel/drivers/rknpu/`, v0.9.2,
`DRIVER_DATE 20230825`) is **SPDX GPL-2.0 at the file level**, `MODULE_LICENSE("GPL v2")`
@@ -35,26 +35,26 @@ document is the strategic wrapper and the item-(2) reality check. Read both.
`dma_mask=32`, `pc_data_amount_scale=2`, `pc_task_number_bits=16`,
`pc_task_status_offset=0x3c`, single-core `irqs`/`resets` arrays,
`nbuf_phyaddr=0/nbuf_size=0`).
- The DT node is **already fully specified** in the base tree, only disabled
- The DT node is **already fully specified** in the base tree, only disabled:
`npu@ff660000`, `reg=<0xff660000 0x10000>`, `GIC_SPI 109`, clocks
`ACLK_RKNN`/`HCLK_RKNN`, `assigned-clock-rates=<420000000>`, resets
`SRST_A_RKNN`/`SRST_H_RKNN`, `status="disabled"` (`rv1106.dtsi:1127-1138`). One
board-DTS override (`&npu { status="okay"; }`) enables it.
- **No IOMMU** on this board (`"rknpu iommu device-tree entry not found!, using
non-iommu mode"` CMA/contiguous DMA only), **no power-domains** (single-rail),
non-iommu mode"`, CMA/contiguous DMA only), **no power-domains** (single-rail),
**no OPP table** (fixed clock). This _shrinks_ the port: the multi-domain/DVFS/
thermal code is all provably dead for RV1106's DT.
- The one real build blocker is four vendor-only downstream headers
(`soc/rockchip/rockchip_{iommu,opp_select,system_monitor,ipa}.h`) absent from
mainline 6.18 all four back **dead-code call sites** for this SoC, fixable with
mainline 6.18: all four back **dead-code call sites** for this SoC, fixable with
small local compat-shim stubs (same pattern already used for `clk-rv1106.c`'s
`panic_notifier_list` move). Full delta in `PORT-PLAN.md §2.4`.
`panic_notifier_list` move). Full delta in `PORT-PLAN.md section 2.4`.
**Verdict: real, bounded, worth doing.** Same class as the RGA port. Ends at a
clean probe + `RKNPU_GET_DRV_VERSION`/`RKNPU_GET_HW_VERSION` ioctl round-trip on
hardware **and stops there**, because of §1.2.
hardware, **and stops there**, because of section 1.2.
### 1.2 "Open userspace that runs a model" HARD effectively BLOCKED for RV1106
### 1.2 "Open userspace that runs a model": HARD -> effectively BLOCKED for RV1106
The kernel driver's entire hardware contract is: power/clock/reset, DMA/GEM buffer
management, then it drops a **pointer + length to a userspace-authored `regcmd`
@@ -68,20 +68,20 @@ block, from `rknpu_ioctl.h:21-39` used via `REG_WRITE` in `rknpu_job.c`):
| Offset | Name | Meaning |
|---|---|---|
| `0x00`/`0x04` | VERSION / VERSION_NUM | read-only HW version |
| `0x08` | PC_OP_EN | pulse 10 kicks the job; `0x1`=slave mode before commit |
| `0x08` | PC_OP_EN | pulse 1->0 kicks the job; `0x1`=slave mode before commit |
| `0x10` | PC_DATA_ADDR | device address of the regcmd buffer |
| `0x14` | PC_DATA_AMOUNT | `(regcfg_amount + 4 + scale-1)/scale - 1` |
| `0x20`/`0x24`/`0x28`/`0x2c` | INT_MASK / INT_CLEAR / INT_STATUS / INT_RAW_STATUS | interrupt handshake |
| `0x30` | PC_TASK_CONTROL | `((0x6\|pingpong)<<pc_task_number_bits)\|task_number` (the `0x6` is an undecoded fixed mode field) |
| `0x34` | PC_DMA_BASE_ADDR | `task_base_addr` |
**This is the entire openly-documented register map** the wrapper/sequencer
**This is the entire openly-documented register map**: the wrapper/sequencer
only. **None of it describes the compute engine** (convolution/pooling/activation
config, feature/weight DMA descriptors). Those live _inside_ the opaque regcmd
buffer. The op-code semantics of that stream:
- **Have no public register-level documentation anywhere.** TRM Part 2 does not
exist publicly confirmed by exhausting Rockchip doc mirrors and confirmed
exist publicly, confirmed by exhausting Rockchip doc mirrors and confirmed
directly by a Luckfox engineer that **Luckfox itself was never given one.** Even
Rockchip's own 90-page ROCKIVA SDK guide is pure userspace-API reference with
zero register offsets. This is a deliberate architectural gate, not a
@@ -89,21 +89,21 @@ buffer. The op-code semantics of that stream:
- **Only the closed RKNN-Toolkit2 compiler emits valid regcmd streams.** The
on-device closed `librknnrt` parses `.rknn` blobs and issues the same
`RKNPU_MEM_*`/`RKNPU_SUBMIT` ioctls. `DRM_IOCTL_RKNPU_SUBMIT` technically accepts
a raw command buffer directly nothing _requires_ `librknnrt` but since the
a raw command buffer directly (nothing _requires_ `librknnrt`), but since the
buffer format is closed, that ioctl is **not** a "write your own kernel" door.
### 1.3 Why there is no shortcut through the existing open efforts
| Open effort | Chip(s) | Reaches RV1106? |
|---|---|---|
| Mainline `accel/rocket` (merged 2025-07, in our `research/linux-6.18.46/`) | RK3588 only, as shipped | **No.** `Kconfig`: `depends on (ARCH_ROCKCHIP && ARM64)`. RV1106 is 32-bit `arch/arm` excluded before the register question. `of_match` is hardcoded `"rockchip,rk3588-rknn-core"`. |
| Mesa **Teflon** (TFLite delegate) | Only etnaviv or rocket backends | **No.** Entirely downstream of Rocket its Rockchip reach _is_ Rocket's reach: RK3588 only. |
| `gahingwoo/linux-rk3576-npu` + `charsiu` (active, days-old as of 2026-08) | RK3576 | **No wrong generation.** MobileNetV1 end-to-end + a Llama-3.2-1B INT4 open runtime (`charsiu`, opens `/dev/accel/accel0`, emits its own register stream) reportedly work byte-exact but on RK3576's NPU, a **newer generation** than RV1106's. Unmerged (LKML "[PATCH v9] accel/rocket: RK3576" in review; unverifiable by second source Anubis-walled). |
| Mainline `accel/rocket` (merged 2025-07, in our `research/linux-6.18.46/`) | RK3588 only, as shipped | **No.** `Kconfig`: `depends on (ARCH_ROCKCHIP && ARM64)`. RV1106 is 32-bit `arch/arm`: excluded before the register question. `of_match` is hardcoded `"rockchip,rk3588-rknn-core"`. |
| Mesa **Teflon** (TFLite delegate) | Only etnaviv or rocket backends | **No.** Entirely downstream of Rocket, its Rockchip reach _is_ Rocket's reach: RK3588 only. |
| `gahingwoo/linux-rk3576-npu` + `charsiu` (active, days-old as of 2026-08) | RK3576 | **No, wrong generation.** MobileNetV1 end-to-end + a Llama-3.2-1B INT4 open runtime (`charsiu`, opens `/dev/accel/accel0`, emits its own register stream) reportedly work byte-exact, but on RK3576's NPU, a **newer generation** than RV1106's. Unmerged (LKML "[PATCH v9] accel/rocket: RK3576" in review; unverifiable by second source, Anubis-walled). |
| RK3568/RK3566 Armbian fork | RK3568 | **No.** Built by byte-level diff against captured vendor command streams; multi-task still imperfect; unmerged. |
| `phhusson/rknpu-reverse-engineering` | RK3588 only, dormant since 2024-03, no license | **No.** Exploratory `strings`/`strace`/GEM-dump notes; **no compiler, no encoder, no runtime.** ~1 person-year got RK3588 to "structures identified." |
| **RV1106/RV1103** | | **Zero hits of any kind.** No driver, no fork, no RE writeup, no captured traces. Least-covered tier in the family. |
| **RV1106/RV1103** | n/a | **Zero hits of any kind.** No driver, no fork, no RE writeup, no captured traces. Least-covered tier in the family. |
RV1106 sits in the RKNPU2 **"v1" family** (RK3566/68, RV1109/26, RV1103/06
RV1106 sits in the RKNPU2 **"v1" family** (RK3566/68, RV1109/26, RV1103/06,
INT8-only, `quantize=8` mandatory). RK3576/RK3588 are a **different, newer**
generation. Precedent (RK3568 needing byte-level RE off RK3588) shows this is a
**bespoke per-SoC RE project even between adjacent generations, not a recompile.**
@@ -112,36 +112,36 @@ generation. Precedent (RK3568 needing byte-level RE off RK3588) shows this is a
Ranked by realism:
- **Tier A ACHIEVABLE NOW (commit to this):** open GPL kernel driver, statically
- **Tier A, ACHIEVABLE NOW (commit to this):** open GPL kernel driver, statically
built into our 6.18.46, `/dev/dri/cardN` binds, `/proc/rknpu/load` live for the
Monitor page, `RKNPU_GET_HW_VERSION`/`RKNPU_GET_DRV_VERSION` verified on
hardware. **No blob shipped.** This is item (1); it is the honest "100% open" for
the _driver_.
- **Tier B ACHIEVABLE AS A RESEARCH SPIKE, bounded, low-value:** a **single
- **Tier B, ACHIEVABLE AS A RESEARCH SPIKE, bounded, low-value:** a **single
hand-written op** (one conv or one matmul) executed via a `regcmd` **captured**
from the closed stack and **replayed** through our open `DRM_IOCTL_RKNPU_SUBMIT`,
byte-for-byte, with an open host tool proving the open submit path can drive
byte-for-byte, with an open host tool: proving the open submit path can drive
real compute. This does **not** require understanding the regcmd ISA; it requires
capturing one. It is the concrete first milestone toward open compute (§3, M-NPU-2)
capturing one. It is the concrete first milestone toward open compute (section 3, M-NPU-2)
and the only way to get an on-hardware compute number without the RE project.
- **Tier C NOT ACHIEVABLE on any near horizon:** an open **compiler/encoder** that
- **Tier C, NOT ACHIEVABLE on any near horizon:** an open **compiler/encoder** that
emits novel regcmd streams for arbitrary models. This is the ~person-year+
from-scratch RE project (§3, M-NPU-3+). We should **scope it, not staff it**,
from-scratch RE project (section 3, M-NPU-3+). We should **scope it, not staff it**,
unless a strategic reason to own open NPU compute emerges.
**Honest recommendation:** ship Tier A. Do Tier B only as a time-boxed spike _if_ a
real non-visual inference use case (audio/RS-485/touch-gesture classifier the
real non-visual inference use case (audio/RS-485/touch-gesture classifier, the
only sensible NPU use here; no camera exists on this board) is actually wanted.
Treat Tier C as closed-vendor-runtime-only for the foreseeable future. This matches
the standing `PROVENANCE.md` policy: **we do not ship the closed runtime blob**;
the open path is a from-scratch regcmd encoder tracked separately, never a vendored
binary and until that encoder exists, open NPU _compute_ does not exist for us.
binary, and until that encoder exists, open NPU _compute_ does not exist for us.
---
## 2. Key technical facts (for the implementer)
### Register / memory map (TRM Part1 Part2 does not exist publicly)
### Register / memory map (TRM Part1: Part2 does not exist publicly)
| Block | Base | Size |
|---|---|---|
@@ -154,13 +154,13 @@ binary — and until that encoder exists, open NPU _compute_ does not exist for
IRQ `GIC_SPI 109 LEVEL_HIGH`, node `ff660000.npu`. Clocks `ACLK_RKNN`/`HCLK_RKNN`
off the CRU matrix; only measured clock point anywhere is **NPU 500MHz** (Rockchip
internal power-test xlsx, "typical IPC workload" corner). A commented-out board-DTS
override would set 700MHz never applied.
override would set 700MHz, never applied.
### UAPI (the seam an open userspace must target)
Two memory-manager paths, Kconfig-selected. **RV1106's shipping defconfig picks
`CONFIG_ROCKCHIP_RKNPU_DMA_HEAP=y`** (`/dev/rknpu` misc device, bare `IOCTL_RKNPU_*`
codes, magic `'r'`) but `PORT-PLAN.md` deliberately chooses **DRM_GEM** for the
codes, magic `'r'`), but `PORT-PLAN.md` deliberately chooses **DRM_GEM** for the
6.18 port (`/dev/dri/cardN`, `DRM_IOCTL_RKNPU_*`, `DRM_RENDER_ALLOW`), because M4
already pulls DRM in for the panel and DRM_GEM is the vendor default. Same struct
payloads either way. Six ioctls: `RKNPU_ACTION`, `RKNPU_SUBMIT`,
@@ -182,16 +182,16 @@ struct rknpu_submit { // rknpu_ioctl.h:260-274
};
```
An open userspace must produce: **(a) the `struct rknpu_task[]` list fully
An open userspace must produce: **(a) the `struct rknpu_task[]` list, fully
inferable from the kernel driver alone, trivial;** and **(b) the regcmd byte stream
each task points to NOT inferable from the driver or any published TRM.** (b) is
each task points to, NOT inferable from the driver or any published TRM.** (b) is
the whole problem.
### `/proc/rknpu` safety (hardware-confirmed)
### `/proc/rknpu`: safety (hardware-confirmed)
- `/proc/rknpu/load` **safe** (50 clean reads verified); the Monitor page polls it.
- `/proc/rknpu/freq`, `/power` exposed, unverified.
- `/proc/rknpu/volt` **CONFIRMED SIGSEGV** (null deref no regulator wired). Never poll.
- `/proc/rknpu/load`: **safe** (50 clean reads verified); the Monitor page polls it.
- `/proc/rknpu/freq`, `/power`: exposed, unverified.
- `/proc/rknpu/volt`: **CONFIRMED SIGSEGV** (null deref, no regulator wired). Never poll.
### Provenance / policy
@@ -215,26 +215,26 @@ open encoder exists.
## 3. Milestones
### M-NPU-1 Open kernel driver, verified on hardware (ACHIEVABLE the real deliverable)
### M-NPU-1: Open kernel driver, verified on hardware (ACHIEVABLE, the real deliverable)
Follows `PORT-PLAN.md` in full. Summary of the gates:
1. **Build**: `CONFIG_ROCKCHIP_RKNPU=y` + `_DRM_GEM=y` + `_DEBUG_FS`/`_PROC_FS`;
compile `rknpu_{drv,job,gem,reset,iommu,debugger}.o` clean into `built-in.a`.
Prove the four §2.4 compat shims (`compat/soc/rockchip/rockchip_{iommu,opp_select,
system_monitor,ipa}.h`) against real 6.18 headers this is where they get
Prove the four section 2.4 compat shims (`compat/soc/rockchip/rockchip_{iommu,opp_select,
system_monitor,ipa}.h`) against real 6.18 headers: this is where they get
verified, not just inspected.
2. **DT**: board override `&npu { status="okay"; }`; `dtc -W` clean; re-check the
"place the fdt high" DTB-placement fix (bigger `built-in.a`).
3. **Boot** via the `warden-c8a3` `_b`-slot one-shot loop (never touches `_a`,
auto-reverts on hang): `dmesg | grep -i rknpu` shows clean probe clock/reset/
auto-reverts on hang): `dmesg | grep -i rknpu` shows clean probe, clock/reset/
IRQ acquired, no panic, no `-EPROBE_DEFER` stall.
4. **Node**: `/dev/dri/cardN` (or `renderD1xx`) appears classic DRM node, **not**
4. **Node**: `/dev/dri/cardN` (or `renderD1xx`) appears, classic DRM node, **not**
`/dev/accel/`.
5. **Verify ioctl**: host-buildable C program opens the node, issues
`DRM_IOCTL_RKNPU_ACTION {.flags=RKNPU_GET_DRV_VERSION}`, checks `value` decodes
to `0.9.2`; `RKNPU_GET_HW_VERSION` returns something plausible. Exercises full
dispatch power-get/put clock/reset with no regcmd dependency.
dispatch -> power-get/put -> clock/reset with no regcmd dependency.
6. **Capture the durable delta** as a patch series in this dir (compat shims + DT
fragment + config fragment); vendor source stays in `research/linux-6.18.46/`.
@@ -242,32 +242,32 @@ Follows `PORT-PLAN.md` in full. Summary of the gates:
not surfaced by the read-through; DTB placement. **This is "NPU driver = 100% open,
verified."** Done here for the driver half.
### M-NPU-2 First open compute: capture-and-replay one op (SPIKE, only if wanted)
### M-NPU-2, First open compute: capture-and-replay one op (SPIKE, only if wanted)
The concrete first milestone toward open _compute_, and the honest Tier-B ceiling.
It does **not** require decoding the ISA.
1. On a unit with the closed stack available (build host / a dev panel with the
closed `librknnrt` + a trivial single-conv or single-matmul `.rknn`), capture
the regcmd buffer(s) the runtime DMAs in via `strace` of the `RKNPU_MEM_*`/
the regcmd buffer(s) the runtime DMAs in: via `strace` of the `RKNPU_MEM_*`/
`RKNPU_SUBMIT` ioctls + `GEM_FLINK`/`GEM_OPEN` buffer dumps (phhusson's method),
plus the input/output tensor buffers.
2. Write an **open host tool** that: allocates the same GEM buffers via
`RKNPU_MEM_CREATE`, writes the captured regcmd bytes + captured input tensor,
builds the `struct rknpu_task[]` (fully open, §2), and submits via
builds the `struct rknpu_task[]` (fully open, section 2), and submits via
`DRM_IOCTL_RKNPU_SUBMIT` on our M-NPU-1 driver.
3. **Verify**: output tensor matches the closed runtime's output **byte-for-byte**
on hardware, and matches a CPU/NEON reference for the same op. Record the
**measured job-submission latency** (ioctl+DMA+blocking-IRQ) a number that
**measured job-submission latency** (ioctl+DMA+blocking-IRQ), a number that
exists nowhere today and settles the "NPU vs NEON dispatch overhead" argument.
**Deliverable:** proof the open submit path drives real compute + first latency
number. **Not** a general runtime the regcmd is a fixed captured blob for one op
number. **Not** a general runtime: the regcmd is a fixed captured blob for one op
shape. **Value:** low; do only as a time-boxed research spike behind a real
use case. **Never ship a captured Rockchip regcmd as a product artifact** (it is
their compiler's output) this stays in `research/`.
their compiler's output), this stays in `research/`.
### M-NPU-3+ Open regcmd encoder (SCOPE ONLY do not staff without a strategic reason)
### M-NPU-3+: Open regcmd encoder (SCOPE ONLY, do not staff without a strategic reason)
The Tier-C from-scratch RE project. Method (the only demonstrated one):
differentially decode captured regcmd streams across many hand-built minimal ONNX
@@ -286,14 +286,14 @@ ever begun; otherwise this is the documented reason open NPU compute is deferred
jobs; it delivers **no open way to produce a valid job.** Guard this in every
status line.
- Additional vendor-only symbols beyond the four headers may surface only at
compile M-NPU-1 gate 1 is the real test.
compile: M-NPU-1 gate 1 is the real test.
- **SKU unconfirmed** (G2 128MB / 0.5-TOPS vs G3 256MB / 1.0-TOPS; datasheet Rev 2.0
retracted the split to "both 1.0 TOPS"). Determines the compute ceiling if
M-NPU-2/3 are ever pursued; needs an on-hardware chip-ID/TOPS register read.
- **No open regcmd project for RV1106 exists** (re-confirm periodically community
- **No open regcmd project for RV1106 exists** (re-confirm periodically, community
moves fast, esp. the `gahingwoo` RK3576 work; but it is the wrong generation).
- **No NPU use case is currently established for this board** (no camera; RGA2 +
RKIVE already cover the 2D/CNN-shaped image tasks better see
RKIVE already cover the 2D/CNN-shaped image tasks better, see
`npu-graphics-feasibility.md`). Absent a concrete non-visual classifier need,
M-NPU-2 and beyond have no pull, and M-NPU-1 alone (driver binds, load graph
works, no blob) is the correct stopping point.
@@ -301,4 +301,4 @@ ever begun; otherwise this is the documented reason open NPU compute is deferred
---
_Cross-refs: `PORT-PLAN.md` (authoritative file-by-file kernel port),
`../../docs/npu-graphics-feasibility.md`, `../CAPABILITIES-AUDIT.md`,
`../PROVENANCE.md`, `../DRIVER-PARITY.md:41`, `../REMAINING-PORTS.md §6`._
`../PROVENANCE.md`, `../DRIVER-PARITY.md:41`, `../REMAINING-PORTS.md section 6`._
+84 -84
View File
@@ -1,29 +1,29 @@
# RKNPU kernel driver 6.18 port plan (M6 class)
# 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 §3 for why that's a
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
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).
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
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 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
@@ -49,7 +49,7 @@ static const struct rknpu_config rv1106_rknpu_config = {
```
And the devicetree side is **already complete** in `rv1106.dtsi` (base tree,
`status = "disabled"`) nothing to author, only enable:
`status = "disabled"`), nothing to author, only enable:
```dts
// arch/arm/boot/dts/rv1106.dtsi:1127-1138
@@ -67,17 +67,17 @@ npu: npu@ff660000 {
};
```
No `power-domains` property (RV1106's NPU is single-core, single-rail unlike
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 §2.3 and §2.4.
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)
### 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**,
`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}`
@@ -88,19 +88,19 @@ verify step uses: `RKNPU_GET_HW_VERSION = 0`, `RKNPU_GET_DRV_VERSION = 1`
### 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
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 (§3). Don't confuse
`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.106.18 API deltas
## 2. Files, config, and the concrete 5.10->6.18 API deltas
### 2.1 File list v1 minimal port
### 2.1 File list: v1 minimal port
| File | Bring in? | Why |
|---|---|---|
@@ -108,15 +108,15 @@ the two node namespaces when verifying.
| `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 (§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) |
| `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
M1M3 build everything statically to avoid the vermagic class of bug that
### 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`)
```
@@ -131,7 +131,7 @@ 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
`grf-clock-controller` and the eMMC node: override in the board file, don't
touch the base `rv1106.dtsi`):
```dts
@@ -140,19 +140,19 @@ touch the base `rv1106.dtsi`):
};
```
No new properties needed `compatible`/`reg`/`interrupts`/`clocks`/`resets` are
already correct and match the driver's own `rv1106_rknpu_config` exactly (§1).
**Do not add an `iommus =` property for v1** — see §2.4.
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/`
version gates", verified directly against our `flare-edge/research/linux-6.18.46/`
tree, not assumed:
```c
// 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)
// 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>
@@ -160,33 +160,33 @@ tree, not assumed:
```
`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
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
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, §1).
`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
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
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,
`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
**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.
@@ -199,73 +199,73 @@ 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`) |
| `#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 (§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.106.18 span; no gate needed |
| `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 §2.4, this is expected to be a **build-fix-build
pass**, not a rewrite — confirm by actually compiling into the tree (the checks
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
- **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
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 §2.4;
- **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 (§2.1).
- **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)
## 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`
`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 §2.2) added to the
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 §2.4 shim headers get proven, not just inspected.
`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).
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,
`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 §1).
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
`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 — §4.
`.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
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
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 (§1), and the GEM/DRM surface is already correctly
version-gated past 6.1 (§2.5). The concentrated risk is (a) actually compiling
the §2.4 shims against real 6.18 headers rather than trusting the header-presence
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
@@ -273,29 +273,29 @@ 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
## 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 §4`'s conclusion with the current
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
(`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`](https://github.com/airockchip/rknpu) /
[`github.com/rockchip-linux/rknpu`](https://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** (§4.2) don't conflate "port
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
- Merged into mainline Linux and Mesa in **2025-07**, Tomeu Vizoso, ["Rockchip NPU
update 6: We are in mainline!"](https://blog.tomeuvizoso.net/2025/07/rockchip-npu-update-6-we-are-in-mainline.html),
following the LKML series
["[PATCH v2 0/7] New DRM accel driver for Rockchip's RKNN NPU"](https://lkml.iu.edu/hypermail/linux/kernel/2502.3/02497.html).
@@ -304,33 +304,33 @@ covers RV1106 at all.
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
`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
- **RK3576**: active, but incomplete, and not in our tree. A 2026-07-15
reverse-engineering effort (
[CNX Software](https://www.cnx-software.com/2026/07/15/reverse-engineering-brings-rk3576-npu-support-to-open-source-rocket-driver-for-mainline-linux/),
code at [`gahingwoo/linux-rk3576-npu`](https://github.com/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)
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,
- **RK3568/RK3566**: an out-of-tree community fork exists (Armbian forum,
["ODROID-M1: RK3568 NPU on the open stack"](https://forum.armbian.com/topic/61651-odroid-m1-rk3568-npu-on-the-open-stack-rocket-kernel-driver-mesa-teflon/)),
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
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
- **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.
@@ -341,7 +341,7 @@ Merged into Mesa 24.1 ([Phoronix](https://www.phoronix.com/news/Gallium3D-Teflon
[docs.mesa3d.org/teflon.html](https://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
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.
@@ -353,7 +353,7 @@ 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
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)
@@ -366,8 +366,8 @@ 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 §1`: RKNN-Toolkit2 on a PC `.rknn` blob
`librknnrt`/RKNN C API on-device) for the foreseeable future porting the kernel
(`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.
@@ -379,14 +379,14 @@ it does.
```
npu/
PORT-PLAN.md this document
compat/ (to add) local stub headers for §2.4:
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
`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).
+66 -66
View File
@@ -3,10 +3,10 @@
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
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
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:
@@ -16,27 +16,27 @@ Trees involved:
(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
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):
Per `PORT-PLAN.md` section 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`) |
| `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 (section 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
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"`
@@ -45,33 +45,33 @@ keeps working. This also makes the header directly usable by
## Kconfig / Makefile wiring
`drivers/rknpu/Kconfig` (new) trimmed from the vendor's own `Kconfig`: drops the
`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
block entirely (DRM_GEM is the only memory manager this port wires up, see
`PORT-PLAN.md` section 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/accel/`, see section 1 of `PORT-PLAN.md`, "not `/dev/accel/`").
`drivers/rknpu/Makefile` (new) trimmed from the vendor's own `Makefile`: only the
`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
(matches `PORT-PLAN.md` section 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
**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
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):
drop, all four symbols round-tripped through `olddefconfig` unchanged):
```
CONFIG_ROCKCHIP_RKNPU=y
@@ -81,52 +81,52 @@ 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
(both already on from the M4 display work). Left off per `PORT-PLAN.md` section 2.2/section 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
All four vendor-only headers from `PORT-PLAN.md` section 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
### `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`
`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
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
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
`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,"
`PORT-PLAN.md` section 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
### `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'
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
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
@@ -137,7 +137,7 @@ pointers dereferenced in the (dead-for-RV1106, but still compiled) DVFS code pat
`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,
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
@@ -149,18 +149,18 @@ 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
`< 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
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
`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
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)
@@ -169,34 +169,34 @@ port had to force.
| # | 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). |
| 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` section 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. |
| 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)`. |
| 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.) |
| 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. |
| 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
changes: compiled clean against 6.18 unmodified, confirming `PORT-PLAN.md` section 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`
(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):
= <420000000>`, `resets` (`SRST_A_RKNN`/`SRST_H_RKNN`), left untouched, per
instructions, exactly as `PORT-PLAN.md` section 2.3 specified):
```dts
&npu {
@@ -225,7 +225,7 @@ make ARCH=arm CROSS_COMPILE=$CROSS_COMPILE zImage rockchip/rv1106-warden.dtb -j"
```
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,
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
@@ -235,7 +235,7 @@ 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
(ground truth for built-in linkage, cross-`nm` mis-lists symbols on this
toolchain, same caveat as every other port in this series):
```
@@ -259,7 +259,7 @@ 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
task's spec. Dependency-free beyond the kernel tree's own UAPI headers, no
libdrm, no target sysroot headers:
```sh
@@ -271,11 +271,11 @@ arm-rockchip830-linux-uclibcgnueabihf-gcc \
<warden-sdk>/kernel/rv1106-enablement/npu/rknpu_version_test.c
```
**This exact command was run in this session** (build-only the resulting binary
**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
`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).
@@ -285,13 +285,13 @@ then again with `.flags = RKNPU_GET_HW_VERSION`, and prints both. Traced against
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
(`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...
`VERSION`/`VERSION_NUM` registers (`rknpu_job.c:rknpu_get_hw_version()`): no
published decode table exists for it (per `PORT-PLAN.md` section 3 step 5, "checks...
returns something plausible"); the test program just prints it in hex.
**Expected output on a successful hardware run** (parent session):
@@ -305,7 +305,7 @@ 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
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)
@@ -314,23 +314,23 @@ the value of checking the decoded string, not just the ioctl return code.
- 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
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` section 3
step 3).
- `ls -la /dev/dri/` should show a new `cardN`/`renderD1xx` classic DRM node
- `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
`PORT-PLAN.md` section 1 and `OPEN-NPU-PLAN.md` section 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.21.4 covers; out of scope here by design (no blob is
any `.rknn` model, `librknnrt`, that's the closed-userspace question
`OPEN-NPU-PLAN.md` section 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` -> `[wip] M6 built,
`../DRIVER-PARITY.md`'s NPU row: `[ ] M6, plan: npu/PORT-PLAN.md` -> `[wip] 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`.
{status="okay"} in the dtb, not yet flashed/probed on hardware`.
+6 -6
View File
@@ -1,4 +1,4 @@
# NPU (rknpu) open kernel driver VERIFIED on warden-c8a3 (2026-08-25)
# 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
@@ -17,7 +17,7 @@ $ rknpu_version_test
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
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
@@ -25,16 +25,16 @@ 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`.)
(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
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
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
@@ -9,7 +9,7 @@
* 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.
* OPEN-NPU-PLAN.md section 1.4 Tier A and PORT-PLAN.md section 3 step 5.
*
* Dependency-free beyond the UAPI header: build with
* -I<kernel-tree>/include/uapi
@@ -41,7 +41,7 @@
* 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).
* published decode table for it beyond that (PORT-PLAN.md section 3 step 5).
*/
#include <errno.h>