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
+7 -7
View File
@@ -2,7 +2,7 @@
#
# Policy (mirrors flare-edge): only GitHub-owned actions get the repo token; the one
# third-party helper (taiki-e/install-action) is pinned and never handed a token.
# Every job runs on GitHub-hosted runners no self-hosted runner may be reachable
# Every job runs on GitHub-hosted runners: no self-hosted runner may be reachable
# from this repo's workflows (ADR-0007: public-repo fork PRs would otherwise be
# able to run code on private infrastructure). kernel-build is dispatch-only.
name: ci
@@ -146,7 +146,7 @@ jobs:
# Automatic artifact GC (mirrors flare-edge). An exceeded account-wide Actions
# storage quota blocks ALL new runs (startup_failure), not just uploads, so
# before a new kernel artifact is uploaded, drop older kernel-rv1106 artifacts
# beyond the newest few storage stays bounded across dispatches. gh + the
# beyond the newest few: storage stays bounded across dispatches. gh + the
# built-in token (no third-party action); needs actions:write to delete.
# Best-effort: it never fails the run, so it can't block the build that needs it.
prune-artifacts:
@@ -177,8 +177,8 @@ jobs:
kernel-build:
# Full hermetic build on a GitHub-hosted runner (ADR-0007; supersedes the
# self-hosted half of ADR-0004 a self-hosted runner must never be reachable
# from a public repo's workflows). Manual-dispatch by design a full kernel
# self-hosted half of ADR-0004: a self-hosted runner must never be reachable
# from a public repo's workflows). Manual-dispatch by design: a full kernel
# build is heavy; trigger via `gh workflow run ci.yml` / the Actions UI.
if: github.event_name == 'workflow_dispatch'
needs: [prune-artifacts]
@@ -191,7 +191,7 @@ jobs:
sudo apt-get update -qq
sudo apt-get install -y -qq gcc-arm-linux-gnueabihf qemu-system-arm \
cpio bc bison flex libssl-dev ccache
- name: provision `python` (SDK quirkbuild calls bare python)
- name: provision `python` (SDK quirk: build calls bare python)
run: |
mkdir -p "$RUNNER_TEMP/bin"
ln -sf "$(command -v python3)" "$RUNNER_TEMP/bin/python"
@@ -225,7 +225,7 @@ jobs:
# (zImage alone emits no Module.symvers); only these dirs' .ko files
# are collected/uploaded.
WARDEN_MODULES_COLLECT: drivers/net/wireless/aic8800
# KERNEL_TARBALL is exported from the SHELL so $HOME expands — a literal
# KERNEL_TARBALL is exported from the SHELL so $HOME expands. A literal
# `~` in a YAML env: value is never tilde-expanded and broke every
# dispatch until caught in review.
run: |
@@ -242,7 +242,7 @@ jobs:
"$RUNNER_TEMP/kbuild-out/linux-6.18.46/arch/arm/boot/zImage"
# Best-effort: the build IS the gate. Uploading the zImage/dtb to GitHub
# artifact storage can fail on an account-wide storage-quota hit (recalculated
# every 6-12h) that has nothing to do with this build — don't red-X a good
# every 6-12h) that has nothing to do with this build. Don't red-X a good
# kernel build over it.
- uses: actions/upload-artifact@v4
continue-on-error: true
+10 -10
View File
@@ -6,14 +6,14 @@
![Coverage](.github/badges/coverage.svg)
A modern, open development environment for the **Luckfox Pico 86 Panel**
(Rockchip RV1106), replacing the vendor SDK and honest about what runs on
(Rockchip RV1106), replacing the vendor SDK, and honest about what runs on
real silicon versus what is simulated.
| | Vendor SDK | This repo |
|---|---|---|
| **Kernel** | 5.10.160, twice-forked, frozen | **6.18.46** a reviewable, subsystem-split patch series onto pristine upstream; full peripheral set (display, touch, wifi, audio, NPU, ...) hardware-verified on a bench panel |
| **Kernel** | 5.10.160, twice-forked, frozen | **6.18.46**: a reviewable, subsystem-split patch series onto pristine upstream; full peripheral set (display, touch, wifi, audio, NPU, ...) hardware-verified on a bench panel |
| **Build** | ~2 GB tree, absolute paths baked in, Kconfig options silently dropped | one hermetic script: sha256-pinned source fetch, fail-closed patch apply and config fragments |
| **Off-device testing** | none every change means flashing a panel | register-level hardware models (`sim/`) plus a QEMU device VM booting the real kernel, real daemons, and the real UI with display + touch |
| **Off-device testing** | none; every change means flashing a panel | register-level hardware models (`sim/`) plus a QEMU device VM booting the real kernel, real daemons, and the real UI with display + touch |
| **Config safety** | memory-map mistakes reach hardware (one bricked a bench unit) | static gates (`tools/config-lint`) catch them before any flash |
| **CI** | none | hosted pipeline: tests, coverage, benchmarks, patch-apply gate, kernel build with an in-CI QEMU boot smoke |
| **Flashing tools** | closed (`upgrade_tool`) | open (`rkdeveloptool`) |
@@ -51,7 +51,7 @@ scenario tests (portal, OTA apply, display + touch, watchdog).
| Directory | Contents |
|---|---|
| `patches/` | the RV1106 forward-port onto pristine linux-6.18.46, subsystem-split |
| `build/` | hermetic kernel build: pinned fetch apply patches `zImage` + dtb |
| `build/` | hermetic kernel build: pinned fetch -> apply patches -> `zImage` + dtb |
| `qemu/` | device simulator: QEMU `-M virt` boots the real kernel and real userspace |
| `sim/` | register-level hardware models (Rust): membus, HPMCU, CRU, Modbus, RGA, NPU |
| `drivers/` | hardened hardware-facing drivers: HAL seams, test harnesses |
@@ -74,24 +74,24 @@ each other. Full detail: `docs/architecture.md`.
| `lvglsim` (downstream) | the LVGL UI on SDL | rendering and UI flows |
With the production UI binary in `qemu/payload/`, `run.sh --display on` opens
the panel's 720x720 screen in a window, mouse clicks landing as touch
the panel's 720x720 screen in a window, mouse clicks landing as touch:
device and UI in one VM. Emulation results are never on-silicon evidence;
the simulators narrow which claims need a panel.
## Principles
- **Open** open tools over closed ones; GPL-2.0-only.
- **Hard** every seam has a fault-injection path; recovery code is tested
- **Open**: open tools over closed ones; GPL-2.0-only.
- **Hard**: every seam has a fault-injection path; recovery code is tested
against failure, not just success.
- **Modern** the newest kernel the hardware can run, current toolchains,
- **Modern**: the newest kernel the hardware can run, current toolchains,
Rust for new host-testable code, reproducible builds.
## Downstream
A private firmware repo (flare-edge) consumes this SDK; issue references and
checkout paths pointing there are engineering context, not reachable links.
The QEMU simulator runs its production binaries unmodified — real
over-the-air updates included.
The QEMU simulator runs its production binaries unmodified,
real over-the-air updates included.
## License
+11 -11
View File
@@ -26,8 +26,8 @@
# Module.symvers, so per-dir M= builds cannot link) and
# collects the listed dirs' modules into $WORK/modules-out.
#
# Requires: `python` (not python3) on PATH the SDK quirk; the CI runner provides
# a project-local venv. Builds are SERIAL on the shared SDK box never run two.
# Requires: `python` (not python3) on PATH: the SDK quirk; the CI runner provides
# a project-local venv. Builds are SERIAL on the shared SDK box: never run two.
set -euo pipefail
KVER=6.18.46
@@ -51,7 +51,7 @@ trap '[ "${WORK_OWNED:-0}" = 1 ] && rm -rf "$WORK"' EXIT
log() { printf '\033[36m== %s\033[0m\n' "$*"; }
command -v python >/dev/null || { echo "need 'python' (not python3) on PATH SDK quirk" >&2; exit 1; }
command -v python >/dev/null || { echo "need 'python' (not python3) on PATH: SDK quirk" >&2; exit 1; }
# 1. obtain + verify the pristine tarball
mkdir -p "$WORK"
@@ -66,7 +66,7 @@ rm -rf "$SRC"
log "extracting pristine"
tar -C "$WORK" -xf "$TB"
# 3. apply the patch series in order (fail loudly never echo a lie)
# 3. apply the patch series in order (fail loudly: never echo a lie)
log "applying patch series"
for p in "$PATCHES"/*.patch; do
if git -C "$SRC" apply --whitespace=nowarn "$p" 2>/dev/null; then
@@ -89,7 +89,7 @@ SENTINEL="$SRC/arch/arm/boot/dts/rockchip/rv1106-warden.dts"
[ -f "$SENTINEL" ] || {
echo "FATAL: patch series did not apply (missing $SENTINEL)." >&2
echo " Is \$WORK inside a git repo? git apply silently ignores out-of-subdir" >&2
echo " paths there point WORK at a dir outside any checkout (e.g. \$RUNNER_TEMP)." >&2
echo " paths there: point WORK at a dir outside any checkout (e.g. \$RUNNER_TEMP)." >&2
exit 1
}
log "patch series applied ($(basename "$SENTINEL") present)"
@@ -98,7 +98,7 @@ log "patch series applied ($(basename "$SENTINEL") present)"
log "configuring (warden_defconfig)"
cp "$HERE/warden_defconfig" "$SRC/.config"
# Optional kconfig fragment overlay (e.g. qemu/configs/virt.fragment for the
# QEMU -M virt device-sim variant). Fail closed if set but unreadable never
# QEMU -M virt device-sim variant). Fail closed if set but unreadable: never
# silently build the wrong kernel. Unset => the canonical RV1106 build,
# byte-identical to a build without this hook.
if [ -n "${WARDEN_KCONFIG_FRAGMENT:-}" ]; then
@@ -112,7 +112,7 @@ if [ -n "${WARDEN_KCONFIG_FRAGMENT:-}" ]; then
fi
# CROSS_COMPILE defaults to the Luckfox SDK uclibc prefix (set SDK_TC to its bin/),
# but the kernel is freestanding, so a caller may override with a generic arm cross
# toolchain instead e.g. CROSS_COMPILE=arm-linux-gnueabihf- (in Debian's
# toolchain instead: e.g. CROSS_COMPILE=arm-linux-gnueabihf- (in Debian's
# gcc-arm-linux-gnueabihf), which the CI runner already has on PATH.
export ARCH=arm
export CROSS_COMPILE="${CROSS_COMPILE:-arm-rockchip830-linux-uclibcgnueabihf-}"
@@ -122,7 +122,7 @@ command -v "${CROSS_COMPILE}gcc" >/dev/null \
make -C "$SRC" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" olddefconfig >/dev/null
# Fragment took-effect assertion: merge_config -m only pastes text, and
# olddefconfig silently resolves any symbol whose dependencies are unmet
# olddefconfig silently resolves any symbol whose dependencies are unmet:
# a fragment option could be dropped without a word. Verify every explicit
# request in the fragment survived into the final .config; fail loud if not.
if [ -n "${WARDEN_KCONFIG_FRAGMENT:-}" ]; then
@@ -137,7 +137,7 @@ if [ -n "${WARDEN_KCONFIG_FRAGMENT:-}" ]; then
"# CONFIG_"*" is not set")
# A disable succeeded if the symbol is NOT set: Kconfig writes either
# the literal "is not set" line or (when dependencies gate the symbol
# out) nothing at all both are valid outcomes. Only "still =value"
# out) nothing at all: both are valid outcomes. Only "still =value"
# is a failed disable. (A typo'd symbol disables nothing and is
# harmless by construction.)
opt="${line#\# }"; opt="${opt% is not set}"
@@ -165,13 +165,13 @@ make -C "$SRC" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" CC="$KCC" -j"$JOBS" \
# Optional module set (issue #4: the panel loaded a stale 5.10 .ko because
# this build never produced matched 6.18 modules). Full `make modules` is
# required zImage alone emits no Module.symvers, so a per-directory M=
# required: zImage alone emits no Module.symvers, so a per-directory M=
# build cannot resolve even core symbols. FAILS CLOSED if a listed dir
# yields no modules.
if [ -n "${WARDEN_MODULES_COLLECT:-}" ]; then
MODOUT="$WORK/modules-out"
rm -rf "$MODOUT"; mkdir -p "$MODOUT"
log "building modules (full set needed for Module.symvers)"
log "building modules (full set: needed for Module.symvers)"
make -C "$SRC" ARCH=arm CROSS_COMPILE="$CROSS_COMPILE" CC="$KCC" -j"$JOBS" modules
for d in $WARDEN_MODULES_COLLECT; do
[ -d "$SRC/$d" ] || { echo "FATAL: WARDEN_MODULES_COLLECT dir '$d' not in tree" >&2; exit 1; }
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Fetch (with retries) and sha256-verify the pristine kernel tarball into $1.
# Single source of truth for the URL + verification used by build-kernel.sh
# and both CI jobs (patches-apply, kernel-build) a KVER bump edits this file
# and both CI jobs (patches-apply, kernel-build): a KVER bump edits this file
# and build-kernel.sh only. FAILS CLOSED: a missing pin refuses to proceed.
#
# Usage: fetch-kernel-tarball.sh <destination-path>
@@ -17,7 +17,7 @@ TB="${1:?usage: fetch-kernel-tarball.sh <destination-path>}"
# Pin first: a forgotten pin on a KVER bump should refuse BEFORE burning a
# 140MB download it will then reject anyway.
[ -f "$SHA_FILE" ] || {
echo "FATAL: no pinned sha256 for linux-$KVER (expected $SHA_FILE) refusing an unverified tarball" >&2
echo "FATAL: no pinned sha256 for linux-$KVER (expected $SHA_FILE): refusing an unverified tarball" >&2
exit 1
}
if [ ! -f "$TB" ]; then
+36 -36
View File
@@ -2,7 +2,7 @@
How the SDK makes the 86 Panel buildable, testable, and hardenable without a
board in the loop. The seam inventory below comes from a full survey of the
downstream WardenOS firmware the SDK's first consumer not aspiration; the
downstream WardenOS firmware (the SDK's first consumer), not aspiration; the
file paths in it point into that (private) codebase and serve as engineering
context.
@@ -13,19 +13,19 @@ tested (or not) differently. Today:
| Block | Where | Access | Test seam today | Fails on host by |
|---|---|---|---|---|
| Registers / SRAM (CRU reset, HPMCU mailbox) | `flared/src/devmem.rs`, `hpmcu.rs` | `/dev/mem` mmap `peek/poke32` | **none** zero tests | (would fault; not exercised) |
| Registers / SRAM (CRU reset, HPMCU mailbox) | `flared/src/devmem.rs`, `hpmcu.rs` | `/dev/mem` mmap `peek/poke32` | **none**, zero tests | (would fault; not exercised) |
| HPMCU / RISC-V coproc | `flared/src/hpmcu.rs` | via devmem + firmware blob load | `WARDEN_HPMCU_FW` redirects the blob path only | env gate disables it |
| NPU load | `ui-src/.../sysmon.c` | read `/proc/rknpu/load` | **none** literal path | file absent "NPU absent" |
| RGA (2D blit) | `ui-src/.../warden_rga.c` | `librga improcess` + dma-heap ioctl | compile-time `#if WARDEN_USE_RGA` | `#if` off LVGL software path |
| NPU load | `ui-src/.../sysmon.c` | read `/proc/rknpu/load` | **none**, literal path | file absent -> "NPU absent" |
| RGA (2D blit) | `ui-src/.../warden_rga.c` | `librga improcess` + dma-heap ioctl | compile-time `#if WARDEN_USE_RGA` | `#if` off -> LVGL software path |
| RS485 daemon | `warden-modbus/modbus_engine.c` | `open("/dev/ttyS4")` | recompile `-DRS485_PORT=<pty>` | (recompiled for a pty) |
| RS485 panel client | `ui-src/.../modbus.c` | `AF_UNIX` socket | `WARDEN_MODBUS_SOCK` env override | socket absent "unavailable" |
| Relays / GPIO | `ui-src/.../relays.c` | `/sys/class/gpio` sysfs | **none** literal paths | path absent "unavailable" |
| RS485 panel client | `ui-src/.../modbus.c` | `AF_UNIX` socket | `WARDEN_MODBUS_SOCK` env override | socket absent -> "unavailable" |
| Relays / GPIO | `ui-src/.../relays.c` | `/sys/class/gpio` sysfs | **none**, literal paths | path absent -> "unavailable" |
| Slot metadata | `flared/src/slotctl.rs` | `misc` partition + `/proc/cmdline` | `WARDEN_MISC_DEV`, `WARDEN_CMDLINE_FILE` env overrides | (redirected to scratch files) |
Three patterns coexist: **compile-time `#if`** (RGA), **env-override**
(modbus socket, misc dev, cmdline, hpmcu fw), and
**fails-soft-when-the-path-is-absent** (NPU, relays, devmem-would-fault). The
last is not a test seam you can inject "absent", never "relay 1 is ON" or
last is not a test seam: you can inject "absent", never "relay 1 is ON" or
"NPU at 80%". The SDK's job: **one deliberate seam per block**, each with a
real backend and a sim backend.
@@ -33,14 +33,14 @@ real backend and a sim backend.
Two seam kinds cover everything above:
- **Register/SRAM seam a trait.** `MemBus` (`sim/src/membus.rs`):
- **Register/SRAM seam -> a trait.** `MemBus` (`sim/src/membus.rs`):
`peek32/poke32` at a physical address. Real backend = flared `devmem.rs`
mmap; sim backend = `SimBus`. The HPMCU watchdog and the CRU reset ladder
both ride this. **Built.**
- **Resource-path seam env-override + injection.** Every device/proc/sys
- **Resource-path seam -> env-override + injection.** Every device/proc/sys
path a driver opens resolves through one indirection
(`warden_hw_path("npu.load")` in C, an env-overridable const in Rust)
generalizing the existing `WARDEN_MISC_DEV`/`WARDEN_MODBUS_SOCK` pattern
(`warden_hw_path("npu.load")` in C, an env-overridable const in Rust),
generalizing the existing `WARDEN_MISC_DEV`/`WARDEN_MODBUS_SOCK` pattern,
so a test points it at a fake file/fifo the sim writes. No LD_PRELOAD, no
fake mounts.
@@ -58,36 +58,36 @@ and supervisor logic runs in CI with no panel. All models are **done**:
|---|---|
| `membus` | `MemBus` trait + `SimBus` in-memory bus (`Clone`, so two "cores" alias shared memory) |
| `hpmcu` | the watchdog coprocessor's state machine (boot-grace, heartbeat-timeout, disarm, fire) on a `SimBus` mailbox with a virtual clock; 7 tests incl. the arm-within-grace no-boot-loop safety property |
| `cru` | reset ladder against the known glb_srst_fst / DW-watchdog registers, plus boot-mode register semantics (survives warm reset, cleared by POR the MaskRom recovery maneuver) |
| `modbus` | byte-in/byte-out RTU slave: CRC16 byte-identical to the master, FC 0x010x06/0x0F/0x10/0x11, exception replies, silent-drop and forced-NAK fault injection; doubles as the QEMU sim's field bus via `qemu/rs485-bridge/` (§7) |
| `npu` | `/proc/rknpu/load` text model behind the path seam the load-readout UI is host-testable; NPU *compute* is out of scope |
| `cru` | reset ladder against the known glb_srst_fst / DW-watchdog registers, plus boot-mode register semantics (survives warm reset, cleared by POR, the MaskRom recovery maneuver) |
| `modbus` | byte-in/byte-out RTU slave: CRC16 byte-identical to the master, FC 0x01-0x06/0x0F/0x10/0x11, exception replies, silent-drop and forced-NAK fault injection; doubles as the QEMU sim's field bus via `qemu/rs485-bridge/` (section 7) |
| `npu` | `/proc/rknpu/load` text model behind the path seam; the load-readout UI is host-testable; NPU *compute* is out of scope |
| `rga` | recording `improcess` fake with programmable `IM_STATUS`, exercising offload-dispatch and CPU-fallback; wired into the `rga_improcess` benchmark |
**Next:** MEI (0x2B/0x0E) Modbus identification; the Tier-2 driver *sources*
migrate in with the flare-edge unification (ADR-0005) their hardware ends
migrate in with the flare-edge unification (ADR-0005); their hardware ends
are already modelled here.
**Integration with flare-edge landed 2026-08-31** (flare-edge PR #110):
**Integration with flare-edge, landed 2026-08-31** (flare-edge PR #110):
`warden-sim` is a flared dev-dependency (through the vendored submodule), and
unification tests in flared's own suite pin its real arm/beat and
reset-ladder logic to `HpmcuSim`/`CruSim`.
## 4. Driver Hardening
"100% MC/DC on 100% of drivers" is infeasible literally ~97% of driver LOC
is vendor blobs (AIC8800 wifi alone is 88.5K lines) so the target is
"100% MC/DC on 100% of drivers" is infeasible literally (~97% of driver LOC
is vendor blobs; AIC8800 wifi alone is 88.5K lines), so the target is
tiered:
- **Tier 1 our own hardware code real 100% MC/DC.** Extract the unit
- **Tier 1, our own hardware code -> real 100% MC/DC.** Extract the unit
behind a small injectable seam, mock its world, build
`-fcondition-coverage`, enforce via the shared `drivers/enforce-mcdc.sh`
(gcc-14 `gcov --conditions`) in the CI `mcdc` job the proven flare-edge
`tests/uboot-ab` method. **Done:** `relays.c` (40/40 conditions) and
(gcc-14 `gcov --conditions`) in the CI `mcdc` job (the proven flare-edge
`tests/uboot-ab` method). **Done:** `relays.c` (40/40 conditions) and
`freshness.c` (66/66). **Next in:** the modbus master and the RGA
dispatch, with the ADR-0005 unification (`drivers/README.md`).
- **Tier 2 near-mainline small drivers branch coverage + fault
- **Tier 2, near-mainline small drivers -> branch coverage + fault
injection.**
- **Tier 3 vendor blobs (AIC8800, MPP/ISP/RGA libs) fault-injection
- **Tier 3, vendor blobs (AIC8800, MPP/ISP/RGA libs) -> fault-injection
hardening behind the seam**, not MC/DC: e.g. the SDIO-wedge recovery is
tested against an injected wedge; the blob itself is untestable.
@@ -99,11 +99,11 @@ against failure, not just the happy path.
The brick was a *memory-map* fault: the boot-loaded MCU's load address
(`0x40000`) is a reserved carve-out on Thunder-Boot boards but plain kernel
RAM on the 86 Panel. No behavioural sim catches that it takes a **static
RAM on the 86 Panel. No behavioural sim catches that; it takes a **static
check against the target devicetree**, owned here as CI gates so mistakes
are caught before a flash, not on the bench.
- **Built:** `tools/config-lint` every `LOADERn=Hpmcu` `LOAD_ADDR` in the
- **Built:** `tools/config-lint`: every `LOADERn=Hpmcu` `LOAD_ADDR` in the
rkbin loader `.ini` must land inside a DT `reserved-memory` range. Its
test suite encodes the c8a3 brick itself (see its README).
- **Next:** partition-table-vs-image-size; vermagic-vs-kernel.
@@ -116,11 +116,11 @@ in the README's "What Works" boots.
- **Base:** a direct forward-port of the vendor 5.10.160 tree onto pristine
6.18.46, keeping the Buildroot LTS/uClibc userspace.
- **Not plan44/OpenWrt 6.6** (ADR-0001): that fork swaps Buildroot for
OpenWrt/musl and ships no AIC8800 kmod a platform swap, not a port.
OpenWrt/musl and ships no AIC8800 kmod; a platform swap, not a port.
- **Not mainline alone:** no RV1106 DT/clk/display/RGA/NPU/flash-boot
upstream; already-mainline rv1126 register data is reused where it
matches.
- **Dominant risk** was the struct-ABI break (the VLAN saga) mitigated by
- **Dominant risk** was the struct-ABI break (the VLAN saga), mitigated by
shipping kernel moves as one matched boot+oem image, never a partial
reflash.
- **Kept honest** by the hermetic `build/build-kernel.sh` and the
@@ -129,7 +129,7 @@ in the README's "What Works" boots.
## 7. Device Emulation
A QEMU VM (`-M virt,highmem=off`, one Cortex-A7, 256M the RV1106G3's
A QEMU VM (`-M virt,highmem=off`, one Cortex-A7, 256M: the RV1106G3's
shape) boots the real forward-ported kernel and real userspace, entering at
`-kernel zImage`: everything below is closed rkbin blobs plus mask ROM.
Details, scenarios, and the emulated-vs-not table: `qemu/README.md` and
@@ -146,21 +146,21 @@ ADR-0006.
`sim/`, and the VM consumes it.
- Division of labour: NPU/RGA/HPMCU *behavior* stays `sim/`; UI *rendering
development* stays `lvglsim`; the VM is where processes, the kernel, and
the network meet. §5 still applies no behavioural sim catches memory-map
faults and "boots under emulation" is never on-silicon evidence.
the network meet. section 5 still applies (no behavioural sim catches memory-map
faults), and "boots under emulation" is never on-silicon evidence.
## 8. Order of Work
1. **Simulator core** `membus`, `hpmcu`, `cru`, `modbus`, `rga`, `npu`.
1. **Simulator core**: `membus`, `hpmcu`, `cru`, `modbus`, `rga`, `npu`.
**Done.**
2. **C-driver MC/DC harnesses** `relays.c` + `freshness.c` at 100%,
2. **C-driver MC/DC harnesses**: `relays.c` + `freshness.c` at 100%,
CI-gated. **Done.**
3. **flared devmem/hpmcu seam + tests** unified with `sim/`. **Done**
3. **flared devmem/hpmcu seam + tests**: unified with `sim/`. **Done**
(2026-08-31, flare-edge PR #110).
4. **Config-lint CI gates** (§5) — the brick class of bug. **Done.**
4. **Config-lint CI gates** (section 5): the brick class of bug. **Done.**
5. **Hermetic kernel build** + the `patches-apply` gate. **Done.**
6. **Kernel 5.10 6.18.46 forward-port** (§6, ADR-0001). **Done**
6. **Kernel 5.10 -> 6.18.46 forward-port** (section 6, ADR-0001). **Done**
(hardware-verified).
7. **QEMU device sim** (§7, ADR-0006) boot smoke, A/B disk harness, RS485
7. **QEMU device sim** (section 7, ADR-0006): boot smoke, A/B disk harness, RS485
bridge, portal/watchdog/clock/OTA scenarios, display+touch, real-image
boot. **Done** (emulation-verified).
+3 -3
View File
@@ -1,7 +1,7 @@
# CI/CD
`.github/workflows/ci.yml` every job runs on GitHub-hosted `ubuntu-latest`.
No self-hosted runner is (or may be) reachable from this repo's workflows
`.github/workflows/ci.yml`: every job runs on GitHub-hosted `ubuntu-latest`.
No self-hosted runner is (or may be) reachable from this repo's workflows:
on a public repo, a fork PR that gets one approved run could otherwise
execute code on private infrastructure (ADR-0007).
@@ -14,7 +14,7 @@ execute code on private infrastructure (ADR-0007).
| `bench` | ubuntu-latest | Smoke-runs the sim + rs485-bridge micro-benchmarks; emits ns/op trend JSON. |
| `patches-apply` | ubuntu-latest | Fetches pristine linux-6.18.46 (cached, sha256-verified) and applies `patches/*` in order. |
| `qemu-tools` | ubuntu-latest | shellcheck on `qemu/**.sh`; builds the initramfs (pinned busybox) and the A/B disk image. |
| `kernel-build` | ubuntu-latest, **dispatch-only** | apt-installs the cross toolchain + qemu, `build/build-kernel.sh` `zImage` + `rv1106-warden.dtb`, QEMU `-M virt` boot smoke (fail-closed), artifact upload (best-effort). Trigger: `gh workflow run ci.yml`. |
| `kernel-build` | ubuntu-latest, **dispatch-only** | apt-installs the cross toolchain + qemu, `build/build-kernel.sh` -> `zImage` + `rv1106-warden.dtb`, QEMU `-M virt` boot smoke (fail-closed), artifact upload (best-effort). Trigger: `gh workflow run ci.yml`. |
| `prune-artifacts` | ubuntu-latest, dispatch-only | Deletes `kernel-rv1106` artifacts beyond the newest 3. |
| `badges` | ubuntu-latest | Renders loc/tests/coverage shields on push to `main` (`[skip ci]` + `paths-ignore` loop guard). |
+2 -2
View File
@@ -1,4 +1,4 @@
# ADR 0001 Kernel Forward-Port
# ADR 0001: Kernel Forward-Port
**Status:** Accepted (2026-08-25). Supersedes the README's original plan44/6.6 goal.
@@ -18,7 +18,7 @@ deltas as a reviewable patch series in `patches/`.
- Done and **hardware-verified on `warden-c8a3`**: clk, pinctrl, eMMC, GMAC, TRNG,
OTP, SARADC/TSADC, RTC, USB host, PWM/backlight, VOP display, GT911 touch, AIC8800
wifi, RGA, I2S audio, HPMCU mailbox, open NPU driver, PVTM.
- **uClibc stays load-bearing** — RGA/MPP/ISP/NPU userspace ship as uClibc-only
- **uClibc stays required**: RGA/MPP/ISP/NPU userspace ship as uClibc-only
blobs; a glibc swap breaks media (flare-edge #51, wontfix). Any kernel bump
inherits this.
- Kernel bumps risk struct-ABI breaks for out-of-tree modules (the AIC8800/VLAN
+6 -6
View File
@@ -1,4 +1,4 @@
# ADR 0002 Tiered MC/DC
# ADR 0002: Tiered MC/DC
**Status:** Accepted (2026-08-25).
@@ -11,23 +11,23 @@ that code would be theatre, not assurance.
## Decision
A **two-tier** policy, measured against the Hardware Abstraction Seam:
- **Tier 1 our own hardware-facing code real 100% MC/DC.** `modbus_engine.c`,
- **Tier 1, our own hardware-facing code -> real 100% MC/DC.** `modbus_engine.c`,
`relays.c`, `warden_rga.c` (wrapper), `hpmcu.rs`, `devmem.rs`, `freshness.c`, plus
the two smallest near-mainline drivers where feasible. Enforced in CI
(`gcc-14 -fcondition-coverage` + `gcov-14 --conditions`). Rust uses
`cargo-llvm-cov` line/region coverage for now true `--mcdc` needs a nightly
`cargo-llvm-cov` line/region coverage for now; true `--mcdc` needs a nightly
toolchain (`-Z coverage-options=condition`) and is deferred on that tooling skew;
the C drivers carry the literal MC/DC gate.
- **Tier 2 ported/vendor drivers fault-injection + branch coverage + benchmarks**
- **Tier 2, ported/vendor drivers -> fault-injection + branch coverage + benchmarks**
against the simulator, behind a stable seam. Explicitly NOT literal MC/DC.
## Consequences
- Matches the user's framing: "as many drivers as possible at 100% MC/DC; for the
rest, a very serious testing and benchmarking system."
- The seam is the shared object: the same thing MC/DC is measured against and the
simulator implements the two goals reinforce, not duplicate.
simulator implements; the two goals reinforce, not duplicate.
- Every Tier-1 file gets a `drivers/<name>/test/` host harness (a `Makefile` +
`test_<name>.c`) that calls the one shared `drivers/enforce-mcdc.sh` which derives
`test_<name>.c`) that calls the one shared `drivers/enforce-mcdc.sh`, which derives
the driver name from the `.gcov` file, so there is a single gate to maintain, not a
per-driver copy. The CI `mcdc` job auto-discovers any `drivers/*/test/Makefile` and
fails below 100%.
+5 -5
View File
@@ -1,13 +1,13 @@
# ADR 0003 Standalone Repo
# ADR 0003: Standalone Repo
**Status:** Accepted (2026-08-25). Repo-visibility half superseded by ADR-0007
(2026-08-30) warden-sdk went public; the "private for now" consequence below
(2026-08-30): warden-sdk went public; the "private for now" consequence below
no longer holds. Original decision kept for the record.
## Context
Our real SDK changes lived as uncommitted edits in a 2GB opaque vendor fork, with
no CI, tests, or versioning of their own. The SDK requirement (future-features-2
§SDK) calls for "its own repo, held to firmware standards."
no CI, tests, or versioning of their own. The SDK requirement (future-features-2,
SDK section) calls for "its own repo, held to firmware standards."
## Decision
A **private** `warden-sdk` GitHub repo (now `blueflare-energy/bfe-core1106-sdk` and public
@@ -17,7 +17,7 @@ on a passing review run, green CI, and the maintainer's fresh explicit go-ahead.
## Consequences
- flare-edge consumes this repo later (flared depending on `warden-sim`, drivers
built from here) a separate, maintainer-gated integration step; flare-edge is not
built from here), a separate, maintainer-gated integration step; flare-edge is not
edited by the SDK-completion effort.
- Private for now (references bench devices / in-progress hardening); can be opened
later once scrubbed, matching how `flare-deployment` is handled.
+3 -3
View File
@@ -1,12 +1,12 @@
# ADR 0004 Self-Hosted Runner
# ADR 0004: Self-Hosted Runner
**Status:** Superseded in part by ADR-0007 (2026-08-30) `kernel-build` moved
**Status:** Superseded in part by ADR-0007 (2026-08-30): `kernel-build` moved
to GitHub-hosted runners for the public repo and the self-hosted registration
is retired. Original decision below, kept for the record. (2026-08-25.)
## Context
The heavy kernel/firmware build needs the SDK toolchain and Buildroot's baked-in
absolute paths impractical on GitHub-hosted runners. flare-edge already builds on
absolute paths, impractical on GitHub-hosted runners. flare-edge already builds on
a repo-scoped self-hosted runner on `bfe-mpc-0640` (label `flare-edge`); a repo-scoped
registration cannot be shared across repos by label alone.
@@ -1,4 +1,4 @@
# ADR 0005 Driver Source of Truth
# ADR 0005: Driver Source of Truth
**Status:** Accepted (2026-08-25).
@@ -18,4 +18,4 @@ formalized as a patch series in `patches/`. flare-edge consumes this repo later
- Realizes the seam architecture (ADR-referenced in `docs/architecture.md`).
- flare-edge is not edited now; a later integration step points flare-edge at these.
- Risk: temporary duplication of shared constants between the two repos until the
integration lands tracked, acceptable for the bring-up window.
integration lands; tracked, acceptable for the bring-up window.
+6 -6
View File
@@ -1,4 +1,4 @@
# ADR 0006 QEMU Device Simulator
# ADR 0006: QEMU Device Simulator
**Status:** Accepted (2026-08-29).
@@ -6,7 +6,7 @@
The two existing simulators cannot test the *device*: `lvglsim` (flare-edge)
is an SDL rendering harness, and `sim/` models registers behind driver seams.
Init ordering, the daemons as real processes, networking/enrollment against
FLARE, OTA, and the watchdog were testable only on a bench panel flare-edge's
FLARE, OTA, and the watchdog were testable only on a bench panel; flare-edge's
fault suite marks five scenarios "HIL, human prompts", its Playwright e2e needs
a live panel on the LAN, and its OTA desk test stops at "reached APPLYING".
@@ -14,12 +14,12 @@ Two ways to emulate the panel were considered:
1. **A custom RV1106 QEMU board model.** Nothing exists upstream or in the
community, so this means writing VOP/CRU/GRF/eMMC/HPMCU device models from
scratch and maintaining them against QEMU months of work that duplicates
scratch and maintaining them against QEMU: months of work that duplicates
what `sim/` already models in Rust. It still could not run the boot chain:
BootROM is mask ROM and the DDR-init/idblock stages are closed rkbin blobs.
2. **The generic `-M virt` machine, entering at `-kernel zImage`.** The
forward-ported 6.18.46 config is multi_v7-derived and already carries
`ARCH_VIRT` plus the full virtio set the canonical RV1106 zImage boots
`ARCH_VIRT` plus the full virtio set; the canonical RV1106 zImage boots
virt **unmodified** (verified 2026-08-29). Peripherals become virtio
substitutes; SoC-block behavior stays in `sim/`, bridged in (the RS485
chardev bridge) rather than re-modeled.
@@ -28,11 +28,11 @@ Two ways to emulate the panel were considered:
Option 2. `qemu/` holds the harness: `qemu-system-arm -M virt,highmem=off
-cpu cortex-a7 -smp 1 -m 256M` (the RV1106G3's shape), one canonical kernel
image plus an optional additive config fragment (`qemu/configs/virt.fragment`
via the `WARDEN_KCONFIG_FRAGMENT` hook PCI/pci-serial/i6300esb/WireGuard/
via the `WARDEN_KCONFIG_FRAGMENT` hook: PCI/pci-serial/i6300esb/WireGuard/
virtio-gpu/virtio-input; the RV1106 build is byte-identical with the variable
unset). The VM carries the device's real 12-partition `blkdevparts=` A/B
layout on a virtio disk and populates the `/dev/block/by-name/` contract.
The name is `qemu/`, not any variant of "sim" the wikis already warn that
The name is `qemu/`, not any variant of "sim"; the wikis already warn that
"sim" is two different things.
Notable mechanics: `highmem=off` because the non-LPAE 32-bit kernel cannot
@@ -1,4 +1,4 @@
# ADR 0007 Hosted-Only CI
# ADR 0007: Hosted-Only CI
**Status:** Accepted (2026-08-30). Supersedes the runner half of ADR-0004.
@@ -8,7 +8,7 @@ alignment with the stack philosophy). Two facts change the ADR-0004 calculus:
1. **A self-hosted runner on a public repo is a standing hazard.** A fork PR
can modify workflow files; once any run of theirs is approved, workflows
can target the repo's registered self-hosted runners i.e. arbitrary code
can target the repo's registered self-hosted runners, i.e. arbitrary code
on the private host, which also serves production. GitHub's own guidance is
to never attach self-hosted runners to public repos, and personal-account
repos have no runner groups to scope the risk away.
@@ -1,11 +1,11 @@
# ADR 0008 Panel Development Environment
# ADR 0008: Panel Development Environment
**Status:** Accepted (2026-08-30).
## Context
warden-sdk was written as the SDK for WardenOS, and its documentation framed
it that way: a support repo for one product. Since going public (ADR-0007,
GPL-2.0-only), the actual audience is wider anyone with a Luckfox Pico 86
GPL-2.0-only), the actual audience is wider: anyone with a Luckfox Pico 86
Panel gets a maintained 6.18 kernel, an off-device development loop, and a
device simulator out of this repo, none of which exists elsewhere for this
board. The product-first framing undersold that and confused the entry point
@@ -16,8 +16,8 @@ Position warden-sdk as **a modern, open development environment for the
Luckfox Pico 86 Panel (RV1106)**. WardenOS is documented as the downstream
consumer it is, not the purpose. Documentation follows three rules: lead with
the board, not the product; keep private-repo references clearly marked as
context; keep titles short a heading names a section, it does not summarize
it.
context; keep titles short (a heading names a section,, it does not summarize
it).
## Consequences
- README and top-level docs lead with the hardware and the developer loop
+87 -87
View File
@@ -1,10 +1,10 @@
# NPU Graphics Feasibility
> Point-in-time engineering study, written while scoping the `rknpu.ko` 6.18
> port for the downstream WardenOS firmware "our boards" / "this product"
> port for the downstream WardenOS firmware; "our boards" / "this product"
> below refer to that context. The hardware conclusions apply to any 86 Panel.
**Bottom line: no, not for 3D rendering not "slower," but genuinely not how the
**Bottom line: no, not for 3D rendering; not "slower," but genuinely not how the
hardware works past the first pipeline stage. The RKNPU on RV1106 is a
fixed-function, INT8-only, command-stream tensor accelerator with no rasterizer,
no texture unit, no per-pixel programmability, and no framebuffer output; only
@@ -20,7 +20,7 @@ non-visual inference, not for graphics.**
This document answers a single question raised while planning that port: given
we're about to carry `rknpu.ko` forward to a new kernel, could the NPU pull any
graphics weight on a GPU-less SoC? Facts are cited to their source the
graphics weight on a GPU-less SoC? Facts are cited to their source: the
hardware wiki (`luckfox-pico-86-panel/`), the product wiki
(`flare-edge-construction/`), the vendored SDK kernel driver source
(`flare-edge/sdk/sysdrv/source/kernel/drivers/rknpu/`), the RV1106 devicetree,
@@ -32,7 +32,7 @@ or flagged as general knowledge / needing TRM confirmation.
### Identity and Generation
- It is the **RKNPU** Rockchip's 4th-generation NPU IP, exposed to tooling as
- It is the **RKNPU**, Rockchip's 4th-generation NPU IP, exposed to tooling as
the "RKNPU2" software generation (same toolchain family as RK3566/68/88), but
RV1103/RV1106 sit in that family's **INT8-only tier**: RKNN-Toolkit2 conversion
requires `quantize=8`, with no mixed-precision or FP path available on this
@@ -40,7 +40,7 @@ or flagged as general knowledge / needing TRM confirmation.
(`luckfox-pico-86-panel/npu.md:7`, `soc-rv1106.md`)
- The chip's own datasheet lists it as supporting mixed **INT4/INT8/INT16**
precision at the IP-family level, and states it "supports creating simple
custom operators" (the RKNN custom-op extension) but this is an extension
custom operators" (the RKNN custom-op extension), but this is an extension
mechanism for adding a new *operator* to the compiled-graph model, not general
programmability (see below). (`luckfox-pico-86-panel/npu.md:7`,
`raw/web-camera-isp-npu.md:9`)
@@ -52,7 +52,7 @@ or flagged as general knowledge / needing TRM confirmation.
*older* 0.5/1.0 split. Which figure applies to a given 86-Panel unit also
depends on its SKU (0208/0408 = G2, 1208/1408 = G3), which isn't confirmed for
our boards. **Any TOPS number in this document should be read as "half to one
TOPS, disputed" not a precise spec.** (`luckfox-pico-86-panel/npu.md:8`,
TOPS, disputed", not a precise spec.** (`luckfox-pico-86-panel/npu.md:8`,
`soc-rv1106.md:31-45`)
- One real measured clock point exists at all: a leaked-but-mirrored Rockchip
internal power-test report states a "typical IPC workload" corner of **NPU
@@ -62,7 +62,7 @@ or flagged as general knowledge / needing TRM confirmation.
### Architecture
This is the load-bearing fact for everything downstream. Reading the vendored
Everything downstream rests on this fact. Reading the vendored
kernel driver source directly (`flare-edge/sdk/sysdrv/source/kernel/drivers/rknpu/`):
- The driver's register offsets (`rknpu_ioctl.h`) center on a **"PC" (program
@@ -74,38 +74,38 @@ kernel driver source directly (`flare-edge/sdk/sysdrv/source/kernel/drivers/rknp
the hardware executes that command stream to completion, raising an interrupt
the driver waits on (`wait_event_timeout(...msecs_to_jiffies(args->timeout))`
in `rknpu_job.c`). This is **not** a shader core fetching and executing
arbitrary instructions per invocation it's closer to a DMA-fed
arbitrary instructions per invocation; it's closer to a DMA-fed
fixed-function pipeline being told "run this pre-built op sequence over these
buffers."
- **The actual op-code semantics of that regcmd stream are not publicly
documented.** The hardware wiki is explicit: register-level detail (TRM Part 2)
"does not exist publicly confirmed by exhausting all known Rockchip doc
"does not exist publicly, confirmed by exhausting all known Rockchip doc
mirrors," including Rockchip's own NPU SDK guide, which is "pure userspace-API
reference with zero register offsets." NPU access on this SoC is
"architecturally gated behind the RKNN userspace API stack, not just
under-documented." (`luckfox-pico-86-panel/npu.md:14`) This means: **no one
outside Rockchip's compiler team can hand-write a regcmd stream that does
something novel** (e.g., a rasterizer) only the closed RKNN-Toolkit2
something novel** (e.g., a rasterizer); only the closed RKNN-Toolkit2
compiler emits valid ones, by lowering a supported ONNX graph (conv, pool,
elementwise, activation, and similar tensor ops) into that command format.
What "operators" the compiler can lower is itself the real ISA surface, and it
is a CNN operator set, not a general instruction set. *(Flag: this document's
claim that no rasterization/gather/sampling primitive exists in the regcmd ISA
is reasoned from the RKNN operator taxonomy and general 4th-gen RKNPU
architecture, not from register-level ground truth the TRM that would settle
architecture, not from register-level ground truth; the TRM that would settle
it definitively does not exist publicly. Treat as high-confidence, not
certain.)*
- **Is there a lower-level submit path than the RKNN runtime?** Technically yes
`DRM_IOCTL_RKNPU_SUBMIT` / `IOCTL_RKNPU_SUBMIT` accepts a raw
- **Is there a lower-level submit path than the RKNN runtime?** Technically yes:
`DRM_IOCTL_RKNPU_SUBMIT` / `IOCTL_RKNPU_SUBMIT` accepts a raw
`regcfg_obj_addr`/`regcmd_addr` task list directly; nothing in the kernel
driver *requires* going through `librknnrt`. But this is the same interface
the RKNN runtime itself calls internally the driver has no knowledge of
the RKNN runtime itself calls internally; the driver has no knowledge of
"operators" at all, only "a command buffer and some memory handles." Since the
command-buffer format is closed, this ioctl is not a usable "write your own
compute kernel" door for us; it's an implementation detail of the vendor
runtime we'd be building on top of via the RKNN C API regardless.
- Custom operators (per the datasheet's "creating simple custom operators")
extend the *model graph* with a new node type still compiled by the RKNN
extend the *model graph* with a new node type, still compiled by the RKNN
toolchain into the same regcmd format, still constrained to whatever
primitive operations the hardware's fixed-function units implement
underneath. It is not a route to arbitrary per-element or per-pixel code.
@@ -113,75 +113,75 @@ kernel driver source directly (`flare-edge/sdk/sysdrv/source/kernel/drivers/rknp
### Data Types and Memory
- **INT8-only quantization tier** for RV1106/RV1103 (`quantize=8` mandatory at
conversion time) inputs/outputs must be int8 and strictly 4-D.
conversion time); inputs/outputs must be int8 and strictly 4-D.
LayerNormalization and ReduceL2 aren't supported by the RKNN parser at all and
must run on the Cortex-A7 before/after the NPU call.
(`luckfox-pico-86-panel/npu.md:35`, `raw/web-camera-isp-npu.md:43`)
- **No dedicated VRAM.** The NPU shares the same in-package DDR3L as everything
else 128MB (G2) or 256MB (G3) total, shared with Linux, any RGA/ISP buffers,
else: 128MB (G2) or 256MB (G3) total, shared with Linux, any RGA/ISP buffers,
and NPU weights/activations. There is a small on-chip scratch: `NPU_CBUF`
256KB SRAM plus an optional SRAM/NBUF allocation path in the driver
(`rknpu_gem.c`, `RKNPU_MEM_TRY_ALLOC_SRAM`/`_NBUF`) a cache for
(`rknpu_gem.c`, `RKNPU_MEM_TRY_ALLOC_SRAM`/`_NBUF`), a cache for
weights/activations, not a general framebuffer-sized memory.
(`luckfox-pico-86-panel/npu.md:10`, `soc-rv1106.md:47-98`)
- **No IOMMU wired on this board.** Boot log shows `"rknpu iommu device-tree
entry not found!, using non-iommu mode"` every buffer the NPU touches today
entry not found!, using non-iommu mode"`; every buffer the NPU touches today
must be physically-contiguous DMA memory, the same constraint RGA has on this
chip. (`luckfox-pico-86-panel/npu.md:26`)
- **Real-world binding constraint is RAM, not TOPS**: forum/GitHub evidence
shows YOLOv5s running but YOLOv8/YOLOv10 hitting memory errors on RV1106
the 128256MB shared-DDR budget is the practical ceiling well before compute
shows YOLOv5s running but YOLOv8/YOLOv10 hitting memory errors on RV1106;
the 128-256MB shared-DDR budget is the practical ceiling well before compute
is. (`luckfox-pico-86-panel/npu.md:38`)
### Software Stack
1. **Kernel driver** (`rknpu.ko`, currently v0.9.2 on our shipped firmware)
1. **Kernel driver** (`rknpu.ko`, currently v0.9.2 on our shipped firmware):
exposes `/dev/rknpu` (a DRM device or a misc device, selectable at build time
via `ROCKCHIP_RKNPU_DRM_GEM` vs `ROCKCHIP_RKNPU_DMA_HEAP`, see
`drivers/rknpu/Kconfig`), handles memory allocation (`RKNPU_MEM_CREATE` /
`_MAP` / `_DESTROY` / `_SYNC` ioctls), job submission
(`RKNPU_SUBMIT`), and misc actions (frequency/voltage/power, bandwidth
priority `RKNPU_ACTION` ioctl enum in `rknpu_ioctl.h`).
2. **RKNN userspace runtime** (on-device C API the *only* supported on-target
priority; `RKNPU_ACTION` ioctl enum in `rknpu_ioctl.h`).
2. **RKNN userspace runtime** (on-device C API, the *only* supported on-target
API on RV1106/RV1103; the Python API is PC-side verification only): `rknn_init()`
`rknn_query()` `rknn_create_mem()` `rknn_set_io_mem()` `memcpy()` input
`rknn_run()` dequantize `float = (int_output - zero_point) * scale`.
-> `rknn_query()` -> `rknn_create_mem()` -> `rknn_set_io_mem()` -> `memcpy()` input
-> `rknn_run()` -> dequantize `float = (int_output - zero_point) * scale`.
(`luckfox-pico-86-panel/npu.md:34`)
3. **RKNN-Toolkit2** (PC-side, x86 only, Python, Ubuntu-only wheels) the
offline compiler: train (PyTorch/TF) export ONNX convert/quantize to a
3. **RKNN-Toolkit2** (PC-side, x86 only, Python, Ubuntu-only wheels): the
offline compiler: train (PyTorch/TF) -> export ONNX -> convert/quantize to a
`.rknn` file. This step is where the regcmd command stream actually gets
generated; it happens once, offline, not per-frame. (`luckfox-pico-86-panel/npu.md:33`)
**On our product today**: `rknpu.ko` is loaded (`S24npu` init script) and
`/proc/rknpu/load` is polled purely to drive the Monitor page's NPU-utilization
graph there is no evidence anywhere in the platform wiki's raw notes of an
`/proc/rknpu/load` is polled purely to drive the Monitor page's NPU-load
graph; there is no evidence anywhere in the platform wiki's raw notes of an
actual `rknn_init()`/`rknn_run()` inference workload ever having been run on
this product. The NPU is live and idle from a compute standpoint.
(`luckfox-pico-86-panel/npu.md:28`) `/proc/rknpu/volt` is a confirmed SIGSEGV
footgun on this board (no regulator wired) never poll it.
footgun on this board (no regulator wired): never poll it.
(`luckfox-pico-86-panel/npu.md:27`)
---
## 2. 3D Rendering, Stage by Stage
A conventional 3D pipeline: **vertex transform primitive assembly
rasterization depth test texture sampling per-pixel shading
framebuffer write.** Verdict per stage, given everything in §1:
A conventional 3D pipeline: **vertex transform -> primitive assembly ->
rasterization -> depth test -> texture sampling -> per-pixel shading ->
framebuffer write.** Verdict per stage, given everything in section 1:
| Stage | Maps to NPU? | Why / how |
|---|---|---|
| **Vertex transform** (MVP matrix × vertices) | **Yes, in principle** | This is exactly GEMM/matmul the NPU's actual strength as a tensor accelerator. A batch of vertices as an input tensor, a weight-like MVP matrix, one matmul op. This is the *only* stage that's hardware-shape-compatible. |
| **Primitive assembly** (grouping vertices into triangles) | **No** | Not a tensor op at all it's index-buffer bookkeeping/control flow. Trivial on a CPU, meaningless to express as a conv/pool/elementwise graph. |
| **Rasterization** (triangle scan-conversion, edge functions, coverage) | **No not how the hardware works** | There is no scan-conversion primitive in the RKNN operator set or (as far as the undocumented regcmd ISA can be inferred) the hardware's fixed-function units. A CNN accelerator computes dense/windowed reductions over a tensor; it has no per-primitive geometric test. This is not "slow," it's absent. |
| **Depth test** (per-pixel z-buffer compare-and-write) | **No** | Requires a read-compare-conditional-write per pixel against arbitrary prior state not an operation in the conv/pool/elementwise/activation vocabulary, and there's no depth-buffer-shaped hardware resource on this IP. |
| **Vertex transform** (MVP matrix x vertices) | **Yes, in principle** | This is exactly GEMM/matmul, the NPU's actual strength as a tensor accelerator. A batch of vertices as an input tensor, a weight-like MVP matrix, one matmul op. This is the *only* stage that's hardware-shape-compatible. |
| **Primitive assembly** (grouping vertices into triangles) | **No** | Not a tensor op at all; it's index-buffer bookkeeping/control flow. Trivial on a CPU, meaningless to express as a conv/pool/elementwise graph. |
| **Rasterization** (triangle scan-conversion, edge functions, coverage) | **No, not how the hardware works** | There is no scan-conversion primitive in the RKNN operator set or (as far as the undocumented regcmd ISA can be inferred) the hardware's fixed-function units. A CNN accelerator computes dense/windowed reductions over a tensor; it has no per-primitive geometric test. This is not "slow," it's absent. |
| **Depth test** (per-pixel z-buffer compare-and-write) | **No** | Requires a read-compare-conditional-write per pixel against arbitrary prior state; not an operation in the conv/pool/elementwise/activation vocabulary, and there's no depth-buffer-shaped hardware resource on this IP. |
| **Texture sampling** (bilinear/nearest fetch from an image by UV) | **No (high confidence, needs TRM to fully settle)** | No "gather/sample" op appears anywhere in the documented RKNN operator taxonomy for this tier. Convolution can *read* a spatial neighborhood, but that's not the same primitive as an arbitrary-address texture fetch with wrapping/filtering. |
| **Per-pixel shading** (arbitrary per-fragment program) | **No** | The NPU executes one fixed, precompiled graph over a whole tensor it cannot run per-pixel conditional/arbitrary code. You could contrive a *specific* visual effect that literally is a small CNN (see §3), but that's not "shading" in the pipeline sense it's a different, narrower thing wearing the name. |
| **Framebuffer write** (write final pixels to the display's scanout buffer) | **No** | The NPU has no display/scanout connection at all no DRM plane, no VOP link. Its only output path is writing tensor data to a DDR buffer, which is not a display pixel format. Something else (CPU or RGA) has to dequantize (`int8 float pixel`) and repack it into an actual framebuffer format before it's visible and even RGA doesn't consume NPU tensor layouts directly (see §4). |
| **Per-pixel shading** (arbitrary per-fragment program) | **No** | The NPU executes one fixed, precompiled graph over a whole tensor; it cannot run per-pixel conditional/arbitrary code. You could contrive a *specific* visual effect that literally is a small CNN (see section 3), but that's not "shading" in the pipeline sense; it's a different, narrower thing wearing the name. |
| **Framebuffer write** (write final pixels to the display's scanout buffer) | **No** | The NPU has no display/scanout connection at all: no DRM plane, no VOP link. Its only output path is writing tensor data to a DDR buffer, which is not a display pixel format. Something else (CPU or RGA) has to dequantize (`int8 -> float -> pixel`) and repack it into an actual framebuffer format before it's visible, and even RGA doesn't consume NPU tensor layouts directly (see section 4). |
### Verdict
**No full pipeline is possible on this hardware five of six stages have no
**No full pipeline is possible on this hardware: five of six stages have no
mapping at all, not a slow one.** A "hybrid" design where only vertex transform
runs on the NPU and everything else (rasterize, depth-test, texture, shade,
write) runs on the Cortex-A7 is the only thing even worth evaluating, and it
@@ -189,60 +189,60 @@ doesn't clear the bar either:
- **Dispatch cost is real and not free.** Submission goes through an ioctl,
a DMA of the command/data buffers, and a **blocking wait on a hardware
interrupt** (`wait_event_timeout` in `rknpu_job.c`) this is a job-queue
interrupt** (`wait_event_timeout` in `rknpu_job.c`); this is a job-queue
round trip through the kernel, not a same-cycle instruction. No on-hardware
latency number exists in the wiki or SDK for this board (flagged as needing
measurement, not asserted here), but the *shape* of the interface ioctl +
DMA + IRQ wait is categorically heavier than a same-thread function call.
- **The scale doesn't justify it.** This is a 720×720 wall-panel UI rendering
simple chrome, not a game engine any "3D" element (an isometric icon, a
measurement, not asserted here), but the *shape* of the interface (ioctl +
DMA + IRQ wait) is categorically heavier than a same-thread function call.
- **The scale doesn't justify it.** This is a 720x720 wall-panel UI rendering
simple chrome, not a game engine; any "3D" element (an isometric icon, a
rotating gauge) involves a handful to a few hundred vertices per frame. A
Cortex-A7 with NEON does a few-hundred-vertex 4×4 matrix transform in low
Cortex-A7 with NEON does a few-hundred-vertex 4x4 matrix transform in low
single-digit microseconds; there is no plausible world where paying an
ioctl/DMA/IRQ round trip to a shared-DDR accelerator beats that, even before
accounting for the INT8 quantization step (packing/unpacking float vertex and
matrix data to/from int8 with scale/zero-point, and the *dynamic range*
problem an MVP matrix spans near-to-far-plane depth ranges that quantize
problem: an MVP matrix spans near-to-far-plane depth ranges that quantize
very poorly to INT8 without per-frame requantization).
- **The offline-compile step doesn't fit a live camera-driven UI anyway.** RKNN
models are compiled ahead-of-time by RKNN-Toolkit2 on a PC; while the runtime
*can* accept different input tensor values per `rknn_run()` call (so a
"run this fixed matmul graph on today's MVP matrix" model is technically
legitimate), any change to the graph shape itself is a PC-side recompile, not
a runtime option a real constraint for anything beyond the most rigid,
a runtime option, a real constraint for anything beyond the most rigid,
pre-planned use of the matmul stage.
**Net: don't chase this.** Even the one stage that's shape-compatible isn't a
net win at this UI's scale, and the other five stages are not partial-credit
net win at this UI's scale, and the other five stages are not partial-credit;
they are the wrong tool, full stop.
---
## 3. CNN-Shaped Image Tasks
Setting 3D aside a CNN accelerator's real strength is convolution, which
Setting 3D aside: a CNN accelerator's real strength is convolution, which
*does* map to some classic image-processing tasks. Evaluated against this
specific 0.51 TOPS-class, 128256MB-shared-DDR, no-camera product:
specific 0.5-1 TOPS-class, 128-256MB-shared-DDR, no-camera product:
| Task | Technically fits an NPU? | Realistic on this product? |
|---|---|---|
| **Blur / sharpen / edge detection** (convolution kernels) | Yes this is literally what conv2d does | **No RKIVE already exists for this, and is a better fit.** RKIVE (Rockchip Intelligent Video Engine) is a *separate*, fixed-function classic-CV block at `0xFFAD0000` Canny edge, Sobel, morphology (erode/dilate), histogram, connected-components, optical flow, block matching that sits **completely idle** on this board today, needs no model-compile step, and is architecturally the intended hardware for exactly this class of filter. (`soc-rv1106.md:108`, `raw/web-camera-isp-npu.md:13`) Reaching for the NPU (compile a model, quantize INT8, pay job-submit overhead) to do a blur that RKIVE or even plain CPU already does more directly is solving an already-solved problem the hard way. This product also has direct, recorded evidence that *any* blur is expensive without a GPU: a 42px soft "flare" shadow effect measurably tanked LVGL's DRM-backend performance and was replaced with a cheap 2px border (`flare-edge-construction/design-system.md:22`) the fix that shipped was "don't blur," not "blur on a different accelerator." |
| **Super-resolution / upscaling** | Yes small SR CNNs (ESPCN-class) exist and run on comparable RKNPU2-family chips | **No use case.** The panel renders its own UI natively at its native 720×720 resolution there is no lower-resolution source content needing upscaling, and no camera feed to upscale (the 86-Panel has zero camera hardware confirmed by schematic inspection, `luckfox-pico-86-panel/modernization-roadmap.md:76`). Dead on arrival for lack of an input, not for lack of hardware capability. |
| **Style transfer** | Yes it literally is a CNN | **No oversized and pointless.** Fast-neural-style-class networks are typically larger than YOLOv5s, which is already near this board's practical RAM ceiling (YOLOv8/v10 already error out on RV1106's shared DDR `npu.md:38`). There's also no product need for a stylized-UI-render feature. |
| **Segmentation-driven UI effects** | Yes, in principle | **Moot no camera, no visual input of any kind to segment.** |
| **2D affine transforms** (rotate/scale/skew as matrix math) | Yes, technically a small matmul | **No RGA already does this natively, in fixed-function hardware, cheaper.** RGA2-Enhance on this board already does scale (bicubic up / averaging down, to 16× either direction), rotate (90/180/270° on input windows), crop, and color/format conversion as dedicated blit-engine operations no model compile, no INT8 quantization, no job-submit-and-IRQ-wait round trip, just a register-programmed blit. It is already wired into LVGL (the Monitor-page double-buffer-sync offload, verified 20%8% CPU on real hardware) and proven in production. (`luckfox-pico-86-panel/rga.md`) |
| **Blur / sharpen / edge detection** (convolution kernels) | Yes; this is literally what conv2d does | **No; RKIVE already exists for this, and is a better fit.** RKIVE (Rockchip Intelligent Video Engine) is a *separate*, fixed-function classic-CV block at `0xFFAD0000` (Canny edge, Sobel, morphology (erode/dilate), histogram, connected-components, optical flow, block matching) that sits **completely idle** on this board today, needs no model-compile step, and is architecturally the intended hardware for exactly this class of filter. (`soc-rv1106.md:108`, `raw/web-camera-isp-npu.md:13`) Reaching for the NPU (compile a model, quantize INT8, pay job-submit overhead) to do a blur that RKIVE or even plain CPU already does more directly is solving an already-solved problem the hard way. This product also has direct, recorded evidence that *any* blur is expensive without a GPU: a 42px soft "flare" shadow effect measurably tanked LVGL's DRM-backend performance and was replaced with a cheap 2px border (`flare-edge-construction/design-system.md:22`); the fix that shipped was "don't blur," not "blur on a different accelerator." |
| **Super-resolution / upscaling** | Yes; small SR CNNs (ESPCN-class) exist and run on comparable RKNPU2-family chips | **No use case.** The panel renders its own UI natively at its native 720x720 resolution; there is no lower-resolution source content needing upscaling, and no camera feed to upscale (the 86-Panel has zero camera hardware, confirmed by schematic inspection, `luckfox-pico-86-panel/modernization-roadmap.md:76`). Dead on arrival for lack of an input, not for lack of hardware capability. |
| **Style transfer** | Yes; it literally is a CNN | **No, oversized and pointless.** Fast-neural-style-class networks are typically larger than YOLOv5s, which is already near this board's practical RAM ceiling (YOLOv8/v10 already error out on RV1106's shared DDR, `npu.md:38`). There's also no product need for a stylized-UI-render feature. |
| **Segmentation-driven UI effects** | Yes, in principle | **Moot: no camera, no visual input of any kind to segment.** |
| **2D affine transforms** (rotate/scale/skew as matrix math) | Yes, technically a small matmul | **No; RGA already does this natively, in fixed-function hardware, cheaper.** RGA2-Enhance on this board already does scale (bicubic up / averaging down, to 16x either direction), rotate (90/180/270° on input windows), crop, and color/format conversion as dedicated blit-engine operations: no model compile, no INT8 quantization, no job-submit-and-IRQ-wait round trip, just a register-programmed blit. It is already wired into LVGL (the Monitor-page double-buffer-sync offload, verified 20%->8% CPU on real hardware) and proven in production. (`luckfox-pico-86-panel/rga.md`) |
### Prior Rejection
The product wiki records that the keyboard's touch-bias correction (snapping an
ambiguous tap to the nearest key) was **explicitly evaluated for NPU
acceleration and not taken** the team asked "Is the key bias implementation
acceleration and not taken**: the team asked "Is the key bias implementation
feasible using the NPU on this device?" and shipped plain nearest-key-rectangle
geometry instead, using LVGL's own buttonmatrix internals.
(`flare-edge-construction/design-system.md:73`) That is exactly the right call
for the reasons in this document: a tiny, cheap, well-defined 2D geometric
problem has no business going through a tensor accelerator's compile-and-submit
pipeline. Nothing in this research changes that conclusion if anything it
pipeline. Nothing in this research changes that conclusion; if anything it
generalizes it.
### Realistic Verdict
@@ -250,11 +250,11 @@ generalizes it.
None of the CNN-shaped graphical tasks clear the bar for this specific product.
Where a hardware assist genuinely helps (2D blit/scale/rotate/blend, classic
CV filters), this SoC already has two purpose-built, cheaper, proven-or-idle
accelerators (RGA, RKIVE) that are the architecturally correct answer not the
accelerators (RGA, RKIVE) that are the architecturally correct answer, not the
NPU. The NPU's actual realistic value on this product remains what the platform
wiki already concluded independent of this research: **small, non-visual
inference** (audio classification off the on-die codec, RS-485/sensor anomaly
detection, touch-gesture-pattern classification) not graphics of any kind.
detection, touch-gesture-pattern classification), not graphics of any kind.
(`luckfox-pico-86-panel/npu.md:46-50`)
---
@@ -265,54 +265,54 @@ detection, touch-gesture-pattern classification) — not graphics of any kind.
- **This is a forward-port of Rockchip's out-of-tree vendor driver, not a
from-scratch write.** The driver already carries version-gated compatibility
shims for kernel APIs that changed across versions e.g. `rknpu_iommu.c` has
shims for kernel APIs that changed across versions, e.g. `rknpu_iommu.c` has
`#if KERNEL_VERSION(6, 1, 0) > LINUX_VERSION_CODE` / `#if KERNEL_VERSION(5, 10, 0)
<= LINUX_VERSION_CODE` branches for IOVA/dma_limit API differences showing
<= LINUX_VERSION_CODE` branches for IOVA/dma_limit API differences, showing
Rockchip's own driver source is written to track multiple kernel versions,
which is a good sign for portability in principle but confirms real API-level
work is needed, not a recompile.
- **No mainline path exists to lean on.** The open-source "Rocket" NPU driver
(`accel/rocket`) covers RK3588 and (as of a 2026-07-15 reverse-engineering
effort) RK3576 but RV1106's 4th-generation NPU IP is a **different
effort) RK3576, but RV1106's 4th-generation NPU IP is a **different
generation** and is not covered by Rocket, and neither active mainline RV1106
patch series (Simon Glass's SoC/clk/pinctrl series, Vladislav Leonov's
peripheral series) touches NPU, RGA, ISP, VENC, or display at all.
(`luckfox-pico-86-panel/mainline-kernel.md:38`, `modernization-roadmap.md:48,96`)
**NPU use on this chip requires the proprietary RKNPU2 vendor runtime
indefinitely** there is no future where an open driver + open compiler
indefinitely**: there is no future where an open driver + open compiler
replaces it.
- **Memory manager choice matters for the port.** The driver's Kconfig offers
two mutually exclusive memory managers: `ROCKCHIP_RKNPU_DRM_GEM` (needs the
DRM subsystem DRM GEM/fence APIs have moved substantially between 5.10 and
DRM subsystem; DRM GEM/fence APIs have moved substantially between 5.10 and
6.18) or `ROCKCHIP_RKNPU_DMA_HEAP` (needs `DMABUF_HEAPS_ROCKCHIP_CMA_HEAP`).
Whichever is chosen inherits whatever DRM/dma-buf/dma-fence API churn exists
across that kernel gap the same class of surface RGA's port would also have
across that kernel gap, the same class of surface RGA's port would also have
to cross.
- **IOMMU status is a live design choice, not a given.** Today this board runs
the NPU in **non-IOMMU mode** (no DT entry) same physically-contiguous-only
the NPU in **non-IOMMU mode** (no DT entry), same physically-contiguous-only
memory constraint RGA has on this chip. The kernel driver does support an
IOMMU path (`rknpu_iommu.c`), so wiring it up is possible but is new scope,
not something the port inherits for free.
- **Reset/clock plumbing**: `SRST_A_RKNN`/`SRST_H_RKNN` resets and
`ACLK_RKNN`/`HCLK_RKNN` clocks off the shared CRU "matrix" clock ladder, node
`npu@ff660000` in `rv1106.dtsi` (`status = "disabled"` at the base dtsi level;
our board enables it downstream) unremarkable, same pattern as every other
our board enables it downstream); unremarkable, same pattern as every other
RV1106 peripheral node.
- **Bottom line on effort class**: this is the same class of work already
scoped for RGA in the modernization roadmap "carrying Rockchip's out-of-tree
driver forward against a newer kernel ABI" bounded, evidence-backed, but
scoped for RGA in the modernization roadmap ("carrying Rockchip's out-of-tree
driver forward against a newer kernel ABI"): bounded, evidence-backed, but
real engineering, not a version-string bump. (`luckfox-pico-86-panel/rga.md:86`)
### Direct Submit Path
**It needs the full toolchain.** As established in §1, the raw
**It needs the full toolchain.** As established in section 1, the raw
`DRM_IOCTL_RKNPU_SUBMIT` path exists at the kernel-ioctl level, but the
command-buffer format it consumes is generated exclusively by the closed
RKNN-Toolkit2 compiler and is not publicly documented at the register level.
There is no supported "hand-roll a compute kernel" door here any real
There is no supported "hand-roll a compute kernel" door here; any real
workload (an inference model, or a hypothetical matmul-as-graphics use) has to
go: train/define ONNX RKNN-Toolkit2 compile (PC, offline) ship the
`.rknn` blob RKNN C API (`rknn_init`/`rknn_run`) on-device. This is a heavier,
go: train/define -> ONNX -> RKNN-Toolkit2 compile (PC, offline) -> ship the
`.rknn` blob -> RKNN C API (`rknn_init`/`rknn_run`) on-device. This is a heavier,
slower-to-iterate loop than driving RGA (which is a direct, synchronous
`im2d`-style C API call with no offline compile step at all) or writing plain
CPU code.
@@ -321,18 +321,18 @@ CPU code.
Worth restating plainly since it's the thing the NPU would be compared against:
**RGA2-Enhance already does everything this panel's UI plausibly needs from 2D
hardware acceleration** blit/copy, scale (bicubic, up to 16×), rotate
hardware acceleration**: blit/copy, scale (bicubic, up to 16x), rotate
(90/180/270° on input), full CSC (BT601/BT709), blend (Porter-Duff), colorkey,
ROP, fill, dither, mosaic, and a purpose-built OSD compositing path and it's
ROP, fill, dither, mosaic, and a purpose-built OSD compositing path, and it's
already integrated into LVGL with a measured, shipped production win (Monitor
page 20%8% CPU). (`luckfox-pico-86-panel/rga.md`) There is no 2D graphics gap
page 20%->8% CPU). (`luckfox-pico-86-panel/rga.md`) There is no 2D graphics gap
on this product that would motivate reaching for the NPU instead. One small,
suggestive detail: RGA's feature bitmask includes `RGA_NN_QUANTIZE` a hint
suggestive detail: RGA's feature bitmask includes `RGA_NN_QUANTIZE`, a hint
that RGA's real intended role in Rockchip's own IPC/camera reference designs is
*feeding* the NPU (resize/convert/quantize a frame before inference), not the
NPU feeding graphics back to RGA. That's the direction Rockchip designed this
silicon to work in, and it lines up with everything else in this document: RGA
pixels, NPU tensors, and the two don't trade places.
-> pixels, NPU -> tensors, and the two don't trade places.
---
@@ -341,11 +341,11 @@ silicon to work in, and it lines up with everything else in this document: RGA
| | Worth prototyping? | Why |
|---|---|---|
| **NPU for any part of 3D rendering** | **No** | Not "how the hardware works" past vertex-transform, and even that stage isn't a net win at this UI's vertex counts once job-submit overhead and INT8 quantization of matrices/vertices are accounted for. |
| **NPU for 2D image filters (blur/sharpen/edge)** | **No** | RKIVE is idle, purpose-built, and needs no model-compile step strictly better fit if this is ever wanted at all. |
| **NPU for 2D image filters (blur/sharpen/edge)** | **No** | RKIVE is idle, purpose-built, and needs no model-compile step; strictly better fit if this is ever wanted at all. |
| **NPU for super-resolution / style transfer / segmentation effects** | **No** | No input source (no camera) or no product need; also RAM-marginal on this SKU regardless. |
| **NPU for 2D affine transforms (rotate/scale) as "graphics"** | **No** | RGA already does this in fixed-function hardware, cheaper, already proven in production. |
| **Porting `rknpu.ko` to 6.18** | **Conditionally yes but scope it for inference, not graphics** | Same bounded, evidence-backed effort class as the RGA port; keeps the door open for the platform wiki's actual identified NPU opportunity (a small non-visual classifier: audio, RS-485/sensor anomaly detection, touch-gesture patterns). Do not justify or scope the port around a graphics capability it doesn't unlock one. |
| **A first NPU spike, if one is wanted for team familiarity** | **Only the already-identified real use case** | A tiny non-visual model (e.g. an RS-485 anomaly classifier) not a graphics stunt. This is the same conclusion the platform wiki already reached independent of this research. |
| **Porting `rknpu.ko` to 6.18** | **Conditionally yes, but scope it for inference, not graphics** | Same bounded, evidence-backed effort class as the RGA port; keeps the door open for the platform wiki's actual identified NPU opportunity (a small non-visual classifier: audio, RS-485/sensor anomaly detection, touch-gesture patterns). Do not justify or scope the port around a graphics capability; it doesn't unlock one. |
| **A first NPU spike, if one is wanted for team familiarity** | **Only the already-identified real use case** | A tiny non-visual model (e.g. an RS-485 anomaly classifier), not a graphics stunt. This is the same conclusion the platform wiki already reached independent of this research. |
### Falsifiers
@@ -353,20 +353,20 @@ Flagging explicitly, per the instruction to distinguish settled facts from
things needing verification:
- **TRM Part 2 (register-level RKNPU documentation) does not exist publicly.**
Everything in §2's rasterization/texture/per-pixel-shading "no" verdicts is
Everything in section 2's rasterization/texture/per-pixel-shading "no" verdicts is
reasoned from the RKNN operator taxonomy and general 4th-generation RKNPU
architecture knowledge, not from a register-level ground truth because that
architecture knowledge, not from a register-level ground truth, because that
ground truth isn't published anywhere, including to Luckfox's own engineers
per a direct forum admission. (`soc-rv1106.md:114`) If Rockchip ever publishes
register-level detail, or if a full RKNN supported-operator list surfaces with
a gather/sample-style op this research didn't find, revisit.
- **No on-hardware NPU job-submission latency number exists anywhere in the
wiki or SDK for this board.** The "dispatch overhead beats any small win"
argument in §2 is architectural (ioctl + DMA + blocking IRQ wait, vs. a
argument in section 2 is architectural (ioctl + DMA + blocking IRQ wait, vs. a
same-thread NEON call) and is high-confidence, but a real measured number
would strengthen or could in principle narrow it. Not asserted as measured
here.
- **Which SKU (G2 vs G3) our boards actually carry is unconfirmed**, which
leaves the exact TOPS ceiling and RAM budget open doesn't change any verdict
leaves the exact TOPS ceiling and RAM budget open; doesn't change any verdict
in this document (nothing here turns on the TOPS number), but is worth closing
out anyway during the driver port.
+1 -1
View File
@@ -2,7 +2,7 @@
> **Outcome tested:** The UI never shows a stale number: it holds briefly, then marks unknown.
**Coverage**: freshness.c 66/66 conditions, 100% MC/DC (CI-enforced)
**Coverage**: freshness.c: 66/66 conditions, 100% MC/DC (CI-enforced)
```mermaid
flowchart TD
+1 -1
View File
@@ -2,7 +2,7 @@
> **Outcome tested:** A relay is exported transparently and driven without disturbing a held contact.
**Coverage**: relays.c 40/40 conditions, 100% MC/DC (CI-enforced)
**Coverage**: relays.c: 40/40 conditions, 100% MC/DC (CI-enforced)
```mermaid
flowchart TD
+4 -4
View File
@@ -2,10 +2,10 @@
Per **ADR-0002** (tiered MC/DC) and **ADR-0005** (source-of-truth), our own
hardware-facing code migrates here behind a HAL seam and is hardened. "100% MC/DC on
100% of drivers" is infeasible (97% of kernel-driver LOC is vendor blobs AIC8800
100% of drivers" is infeasible (~97% of kernel-driver LOC is vendor blobs; AIC8800
alone is 88.5K lines); the realistic, honest target is tiered.
## Tier 1 100% MC/DC
## Tier 1: 100% MC/DC
Self-contained logic with a clean seam, measured to **100% MC/DC** (gcc-14
`-fcondition-coverage`) by the CI `mcdc` job (`make -C drivers/*/test check`):
@@ -18,12 +18,12 @@ Self-contained logic with a clean seam, measured to **100% MC/DC** (gcc-14
**Adding a Tier-1 driver:** copy `<name>.{c,h}` here, put the hardware/OS calls behind
a small injectable seam, then mirror `relays/test/` (a fake backend for the logic
branches + a real backend over a scratch tree for the plumbing). Reuse the shared
gate the Makefile calls `bash ../../enforce-mcdc.sh <gcov.log> build/<name>.c.gcov
gate: the Makefile calls `bash ../../enforce-mcdc.sh <gcov.log> build/<name>.c.gcov
build/test.rc` (it derives the driver name from the `.gcov` file, so there is no
per-driver copy to keep in sync). The CI `mcdc` job picks up any
`drivers/*/test/Makefile` automatically.
## Tier 2 Fault Injection
## Tier 2: Fault Injection
Drivers too large or too vendor/UI-coupled for literal MC/DC get fault-injection,
branch coverage, and benchmarks against the simulator instead. Their **hardware side
+5 -5
View File
@@ -1,5 +1,5 @@
/*
* The UI Freshness Contract engine (flare-edge ADR-0004) core, LVGL-free.
* The UI Freshness Contract engine (flare-edge ADR-0004): core, LVGL-free.
* See freshness.h for the contract. LVGL binding lives in freshness_lv.c.
*/
#include "freshness.h"
@@ -13,7 +13,7 @@
/* Max simultaneous live bindings. Bindings belong to visible pages; the whole
* navigable set of a screen is small, so this is generous. A full table drops
* the binding (returns NULL) rather than silently overflowing the LVGL layer
* the binding (returns NULL) rather than silently overflowing: the LVGL layer
* turns that into a visible fault, never a stale value. */
#ifndef FRESH_MAX
#define FRESH_MAX 96
@@ -40,7 +40,7 @@ struct warden_fresh {
/* A fixed table scanned in full: bindings are torn down all at once by
* warden_fresh_reset (like the screen timers), never individually, so a running
* high-water bound would only hide the free-slot arms from tests without saving
* real work the visible set per screen is a handful. */
* real work: the visible set per screen is a handful. */
static struct warden_fresh s_vals[FRESH_MAX];
warden_fresh_render_t warden_fresh_decide(warden_fresh_result_t produced,
@@ -122,7 +122,7 @@ warden_fresh_t *warden_fresh_bind(void *page, warden_fresh_produce_cb produce,
v->used = true;
return v;
}
return NULL; /* table full caller surfaces a fault, never a stale value */
return NULL; /* table full: caller surfaces a fault, never a stale value */
}
void warden_fresh_set_visible(void *page, bool visible)
@@ -175,7 +175,7 @@ uint32_t warden_fresh_count(void)
uint32_t warden_fresh_min_budget_ms(void)
{
/* `seen` not `best == 0` marks "nothing scanned yet", so a legitimate
/* `seen` (not `best == 0`) marks "nothing scanned yet", so a legitimate
* zero-tolerance binding (max_stale_ms == 0, "must be fresh every tick") wins
* the minimum instead of being mistaken for the empty sentinel and widened. */
uint32_t best = 0;
+8 -8
View File
@@ -1,5 +1,5 @@
/*
* The UI Freshness Contract engine (flare-edge ADR-0004) core, LVGL-free.
* The UI Freshness Contract engine (flare-edge ADR-0004): core, LVGL-free.
*
* The panel is read on-site to judge whether hardware is healthy, so a silently
* *stale* number is worse than a missing one: a stale IP or hashrate reads as
@@ -7,7 +7,7 @@
* sanctioned way to show a live value. It guarantees a bound value is refreshed
* (a) the instant its page becomes visible, (b) periodically while visible
* within a declared max-staleness, and (c) promptly when a declared source
* changes and it renders a value whose source cannot be evaluated as an
* changes, and it renders a value whose source cannot be evaluated as an
* explicit UNKNOWN, never as its confident last-known number.
*
* This header is deliberately LVGL-free so the engine and every producer are
@@ -21,14 +21,14 @@
#include <stdint.h>
/* The mark shown when a value's source cannot be evaluated (em dash). The LVGL
* layer additionally dims the widget. Public so the label wrapper and tests can
* layer also dims the widget. Public so the label wrapper and tests can
* reference the same literal. */
#define WARDEN_FRESH_UNKNOWN_MARK "\xE2\x80\x94"
/* What a producer reports after being asked to produce the current value. */
typedef enum {
FRESH_OK = 0, /* wrote the current value into buf */
FRESH_UNKNOWN, /* source unavailable no value can be produced now */
FRESH_UNKNOWN, /* source unavailable: no value can be produced now */
FRESH_SAME, /* source read fine; value unchanged (cheap re-render) */
} warden_fresh_result_t;
@@ -40,11 +40,11 @@ typedef warden_fresh_result_t (*warden_fresh_produce_cb)(char *buf, size_t n,
/* What the engine decided the widget should show this cycle. */
typedef enum {
FRESH_RENDER_VALUE = 0, /* show the produced/last-good value */
FRESH_RENDER_UNKNOWN, /* show the explicit-unknown mark ("—", dimmed) */
FRESH_RENDER_UNKNOWN, /* show the explicit-unknown mark (an em dash, dimmed) */
FRESH_RENDER_NOCHANGE, /* leave the widget exactly as it is */
} warden_fresh_render_t;
/* The one pure decision at the heart of the contract no state, no I/O, no
/* The one pure decision at the heart of the contract: no state, no I/O, no
* LVGL, so every branch is unit-testable. `showing_unknown` is whether the
* widget is currently displaying the UNKNOWN mark (so recovery from a stale
* blip re-renders the value even when the producer reports it unchanged). */
@@ -80,13 +80,13 @@ void warden_fresh_tick(uint32_t now_ms);
/* A producer of change fired: refresh every visible value bound to `source`. */
void warden_fresh_invalidate(const char *source, uint32_t now_ms);
/* Drop all bindings called on a theme/screen rebuild, like the screen timers. */
/* Drop all bindings: called on a theme/screen rebuild, like the screen timers. */
void warden_fresh_reset(void);
/* Number of live bindings (introspection / tests). */
uint32_t warden_fresh_count(void);
/* Smallest max_stale_ms among visible bindings, or 0 if none lets the LVGL
/* Smallest max_stale_ms among visible bindings, or 0 if none: lets the LVGL
* layer size the shared tick to the tightest budget actually on screen. */
uint32_t warden_fresh_min_budget_ms(void);
+2 -2
View File
@@ -1,7 +1,7 @@
# MC/DC unit harness for drivers/freshness/freshness.c (the UI stale-number guard).
#
# make check build, run, FAIL unless freshness.c hits 100% MC/DC + all checks.
# make report per-condition gcov annotation.
# make check : build, run, FAIL unless freshness.c hits 100% MC/DC + all checks.
# make report : per-condition gcov annotation.
# make clean
#
# FRESH_MAX is forced to 2 so the "binding table full -> NULL" path is reachable
+2 -2
View File
@@ -1,6 +1,6 @@
/* MC/DC harness for drivers/freshness/freshness.c (built with -DFRESH_MAX=2).
*
* freshness.c is pure logic with produce/render callbacks no hardware seam
* freshness.c is pure logic with produce/render callbacks: no hardware seam
* needed, the callbacks ARE the seam. We drive the decision function directly and
* the bind/tick/invalidate state machine through programmable fakes, covering
* every decision (incl. the compound `used && visible`, `!produce || !render`,
@@ -45,7 +45,7 @@ static void test_decide(void) {
EXPECT(warden_fresh_decide(FRESH_UNKNOWN, true, false, 200, 100) == FRESH_RENDER_UNKNOWN);
EXPECT(warden_fresh_decide(FRESH_UNKNOWN, true, false, 50, 100) == FRESH_RENDER_NOCHANGE);
/* boundary: age == max_stale is NOT stale (guards a `>`->`>=` regression that
* MC/DC alone would not catch both outcomes are already covered above). */
* MC/DC alone would not catch: both outcomes are already covered above). */
EXPECT(warden_fresh_decide(FRESH_UNKNOWN, true, false, 100, 100) == FRESH_RENDER_NOCHANGE);
}
+1 -1
View File
@@ -35,7 +35,7 @@ bool warden_relay_get(uint32_t idx);
/** Drive the output. Exports and sets the direction on first use. */
void warden_relay_set(uint32_t idx, bool on);
/** True if the GPIO is exported and usable the page says so if it is not. */
/** True if the GPIO is exported and usable: the page says so if it is not. */
bool warden_relay_available(uint32_t idx);
/* --- Hardware-abstraction seam ---------------------------------------------
+2 -2
View File
@@ -1,8 +1,8 @@
# MC/DC unit harness for drivers/relays/relays.c.
#
# make check build, run, and FAIL unless relays.c hits 100% MC/DC
# make check : build, run, and FAIL unless relays.c hits 100% MC/DC
# (condition) coverage and every unit check passes.
# make report per-condition gcov annotation (build/relays.c.gcov).
# make report : per-condition gcov annotation (build/relays.c.gcov).
# make clean
#
# Requires gcc >= 14 (for -fcondition-coverage) and its matching gcov.
+2 -2
View File
@@ -1,10 +1,10 @@
/* MC/DC harness for drivers/relays/relays.c.
*
* Two layers, one binary, so the combined run covers every decision in relays.c:
* 1. unit tests through a FAKE relay_io exercise the decision logic, incl.
* 1. unit tests through a FAKE relay_io: exercise the decision logic, incl.
* the export->node-appears path a passive tree cannot model.
* 2. integration tests through the real sysfs backend + $WARDEN_GPIO_ROOT
* pointed at a scratch tree exercise the backend's fopen/stat branches.
* pointed at a scratch tree: exercise the backend's fopen/stat branches.
*/
#include "../relays.h"
+19 -19
View File
@@ -1,4 +1,4 @@
# RV1106 Linux 6.18 forward-port (self-built, from vendor 5.10)
# RV1106 -> Linux 6.18 forward-port (self-built, from vendor 5.10)
**Decision (2026-08-23):** forward-port the RV1106 SoC enablement from the
Rockchip **vendor 5.10** tree straight to **Linux 6.18 LTS**, ourselves, using **no
@@ -6,7 +6,7 @@ plan44 code**, built on **our Buildroot 2025.02 LTS + uClibc**. We own the tree.
## The reality this rests on (verified, not assumed)
Vanilla mainline (6.6.93 and 6.18) has **zero** RV1106 support no devicetree, no
Vanilla mainline (6.6.93 and 6.18) has **zero** RV1106 support: no devicetree, no
clock driver, no pinctrl data, not one `rv1106` reference in `arch/arm` or
`drivers/`. So "the newest kernel" is a generic ARM kernel that cannot boot this
SoC. What we forward-port is the **entire RV1106 BSP**: ~120 files in the vendor
@@ -16,48 +16,48 @@ it is delivered incrementally, console-first.
## Method (how each piece is ported)
- **Framework drivers sibling-delta.** clk and pinctrl are driven by the shared
- **Framework drivers -> sibling-delta.** clk and pinctrl are driven by the shared
rockchip framework (`drivers/clk/rockchip/clk.c`, `pinctrl-rockchip.c`). RV1106's
siblings **rv1126 and rv1108 exist in BOTH vendor-5.10 and mainline-6.18**, so
their 5.106.18 delta is the exact recipe for how the framework API changed
we apply that recipe to rv1106's data (`clk-rv1106.c` = 1284 lines;
their 5.10->6.18 delta is the exact recipe for how the framework API changed.
We apply that recipe to rv1106's data (`clk-rv1106.c` = 1284 lines;
`rv1106-cru.h` clock IDs; the rv1106 pinctrl table). This turns "adapt to APIs
we have to guess" into "copy a diff a sibling already proves."
- **SoC-unique code forward-port + build-fix-build.** `mach-rockchip`
- **SoC-unique code -> forward-port + build-fix-build.** `mach-rockchip`
(machine, `rockchip_hptimer`, `rv1106_pm`/`rv1106_sleep.S`), the CRU/GRF glue,
and the board DT have no sibling; port directly and iterate on the 6.18 build.
- **Mainline-present drivers just wire DT + clocks.** 8250-dw UART, dw_mmc
- **Mainline-present drivers -> just wire DT + clocks.** 8250-dw UART, dw_mmc
(SDIO/eMMC), dw-apb i2c, rockchip gpio/pinctrl core, pwm, dw-wdt watchdog are all
in mainline 6.18 — we do **not** port them, we supply the DT nodes + clock/reset
in mainline 6.18. We do **not** port them, we supply the DT nodes + clock/reset
phandles and let the mainline drivers bind.
- **Out-of-tree drivers port the driver.** AIC8800 wifi (plan44 has none; ours),
RGA/MPP kernel bits (assess vs need the 86-Panel has no camera), carrying our
- **Out-of-tree drivers -> port the driver.** AIC8800 wifi (plan44 has none; ours),
RGA/MPP kernel bits (assess vs need: the 86-Panel has no camera), carrying our
Tier-1 SDIO-wakeup fix forward.
- **Our 4 kernel patches** (panel-mcu-reset/logo, watchdog-enable, usb-otg
dual-role, recovery-splash) re-apply against 6.18 (line offsets + some DT
dual-role, recovery-splash) -> re-apply against 6.18 (line offsets + some DT
bindings changed).
## Bring-up milestones (console-first order)
- **M0 buildable base.** 6.18.46 tree + our Buildroot/uClibc toolchain builds a
- **M0: buildable base.** 6.18.46 tree + our Buildroot/uClibc toolchain builds a
generic ARM kernel; a `rv1106_defconfig` forward-ported from vendor 5.10.
- **M1 SoC compiles.** `mach-rockchip` RV1106 select + `clk-rv1106` +
- **M1: SoC compiles.** `mach-rockchip` RV1106 select + `clk-rv1106` +
rv1106 pinctrl data + `rv1106-cru.h` compile clean against 6.18 (no boot yet).
- **M2 earlycon boot.** DT (`rv1106.dtsi` core) + clk + pinctrl + 8250-dw
- **M2: earlycon boot.** DT (`rv1106.dtsi` core) + clk + pinctrl + 8250-dw ->
kernel prints to the RV1106 UART. The first "it's alive."
- **M3 rootfs boot.** dw_mmc + DT eMMC/SD rootfs, userspace up on our Buildroot.
- **M4 display.** VOP2 + the RGB666 720×720 panel + GT911 touch + PWM backlight
- **M3: rootfs boot.** dw_mmc + DT -> eMMC/SD rootfs, userspace up on our Buildroot.
- **M4: display.** VOP2 + the RGB666 720x720 panel + GT911 touch + PWM backlight ->
the LVGL UI renders.
- **M5 wifi + our patches.** AIC8800 SDIO driver ported (+ Tier-1 fix); the 4
- **M5: wifi + our patches.** AIC8800 SDIO driver ported (+ Tier-1 fix); the 4
custom kernel patches re-applied.
- **M6 the rest.** RGA (UI accel), watchdog, HPMCU coprocessor, USB-OTG dual role.
- **M6: the rest.** RGA (UI accel), watchdog, HPMCU coprocessor, USB-OTG dual role.
## Regression (so a 6.18 port doesn't reintroduce the struct-ABI bug class)
The warden-sdk simulator + MC/DC harness are how we hold the line: `HpmcuSim` and
the register/`MemBus` seam test the supervisor/reset logic off the ported kernel;
the `relays-mcdc` pattern extends to each ported driver we own; and a
**config-lint CI gate** (the class that bricked c8a3 a load address in unreserved
**config-lint CI gate** (the class that bricked c8a3: a load address in unreserved
kernel RAM) checks the DT reservations against the drivers on every build.
## Layout (this repo)
+25 -25
View File
@@ -3,39 +3,39 @@
Booting the self-built 6.18 kernel on real hardware (warden-c8a3), 2026-08-24.
The build side (zImage + dtb) is in `../rv1106-enablement/`; this is the on-target
half. **The safe-test framework and the boot-image format below are the reusable
findings they worked on the first hardware try.**
findings: they worked on the first hardware try.**
## The safe test path (A/B slot _b, never touch _a)
c8a3 is the desk-connected bench Warden: reachable over its USB-gadget ethernet
(dev-build dropbear; address and bench credentials live in the private
deployment notes use `scp -O`, no sftp-server), plus the serial console at
deployment notes: use `scp -O`, no sftp-server), plus the serial console at
115200 and remotely switchable power. It runs an A/B
firmware (boot_a=mmcblk0p5 / boot_b=mmcblk0p6, 32 MiB each; rootfs_a/_b; AvbABData
in `misc` sector 4 / byte 2048).
The test never risks the working system:
1. Back up `boot_b` (`dd .../by-name/boot_b -> /userdata/boot_b.bak`).
2. Write the test `boot.img` to **boot_b only** `_a` (the shipped WardenOS) is
2. Write the test `boot.img` to **boot_b only**. `_a` (the shipped WardenOS) is
untouched.
3. Flip AvbABData to a **one-shot _b**: `craft_ab.py 14 0 1 15 1 0 1` A(prio14,
3. Flip AvbABData to a **one-shot _b**: `craft_ab.py 14 0 1 15 1 0 1` -> A(prio14,
tries0,ok1) B(prio15,tries1,ok0); write to `misc` sector 4. U-Boot's SPL then
picks _b once, decrements tries0, and boots it.
picks _b once, decrements tries->0, and boots it.
4. If the test kernel fails/hangs, U-Boot auto-reverts to `_a` (SPL: "slot boot
failed, resetting" next boot `A/B-slot: _a, successful: 1`). A hung kernel
failed, resetting" -> next boot `A/B-slot: _a, successful: 1`). A hung kernel
just needs a Zigbee power-cycle; `_a` boots WardenOS back. **Zero manual
recovery needed verified across three failed attempts.**
recovery needed: verified across three failed attempts.**
`earlycon=uart8250,mmio32,0xff4c0000` is confirmed correct: it is in the live
5.10 cmdline, and U-Boot's own DDR probe reports `Adding bank: 0x0 - 0x10000000`
(256 MiB) so the M2 DT's memory node and earlycon are right.
(256 MiB), so the M2 DT's memory node and earlycon are right.
## The boot-image format (this was the whole fight)
rockchip U-Boot (this build) needs a very specific `boot.img`, NOT a plain FIT:
- **External-data FIT.** `mkimage -f its -E -p 0x800`. The FDT metadata stays tiny
(totalsize 1536 B, matching boot_a); the kernel/fdt/resource **data is
(totalsize ~ 1536 B, matching boot_a); the kernel/fdt/resource **data is
appended** after it. A normal embedded-data FIT (totalsize = whole image) is
rejected with `FIT: No fit blob` / `No FIT image`.
- **A `resource` (multi) sub-image is mandatory.** rockchip reads the DTB via the
@@ -43,62 +43,62 @@ rockchip U-Boot (this build) needs a very specific `boot.img`, NOT a plain FIT:
with `resource_tool --pack`. Without it: `Failed to load DTB, ret=-19`.
- **Sysmem sentinel load addresses.** fdt `load=0xffffff00`, kernel
`load=entry=0xffffff01`; U-Boot's sysmem places them (it chose kernel@0x8000,
fdt@0xc00000). Real low addresses collided "No fit blob".
fdt@0xc00000). Real low addresses collided -> "No fit blob".
- `CONFIG_FIT_SIGNATURE` is **off** in this U-Boot, so the image need not be
signed. Template: `sdk/sysdrv/source/kernel/boot.its`; builder recipe:
`sdk/project/scripts/mk-fitimage.sh` (`mkimage -E -p 0x800`).
With the correct format, U-Boot loaded my kernel + my DTB and printed my DT model
string (`Model: WardenOS 86-Panel (RV1106) M2 earlycon bring-up`), then
string (`Model: WardenOS 86-Panel (RV1106), M2 earlycon bring-up`), then
`Starting kernel ...`.
## Result: [x] M2 achieved the 6.18 kernel boots on hardware
## Result: [x] M2 achieved, the 6.18 kernel boots on hardware
Six attempts, each auto-recovering to `_a`, then a clean boot:
```
[0.000000] Linux version 6.18.46 #2 SMP
[0.000000] Linux version 6.18.46 ... #2 SMP
[0.000000] CPU: ARMv7 Processor [410fc075]
[0.000000] OF: fdt: Machine model: WardenOS 86-Panel (RV1106) M2 earlycon bring-up
[0.000000] OF: fdt: Machine model: WardenOS 86-Panel (RV1106), M2 earlycon bring-up
[0.000000] earlycon: uart8250 at MMIO32 0xff4c0000
[0.000000] cma: Reserved 64 MiB at 0x0c000000
[0.040693] Calibrating delay loop 48.00 BogoMIPS arch timer up
[0.040693] Calibrating delay loop ... 48.00 BogoMIPS <- arch timer up
[0.343810] pinctrl core: initialized pinctrl subsystem
[1.968703] ff4c0000.serial: ttyS2 is a 16550A 8250 on our CRU clock
[1.968703] ff4c0000.serial: ttyS2 ... is a 16550A <- 8250 on our CRU clock
```
Two more bugs, found via the DEBUG_LL rebuild (`DEBUG_LL_UART_8250`, PHYS
0xff4c0000, shift 2, 32-bit word + `earlyprintk` the decompressor prints
0xff4c0000, shift 2, 32-bit word + `earlyprintk`, the decompressor prints
pre-MMU), then fixed:
- **DTB overrun `r2=0` / `invalid dtb`.** The multi_v7 zImage decompresses to
- **DTB overrun -> `r2=0` / `invalid dtb`.** The multi_v7 zImage decompresses to
~20 MiB from 0x8000, overrunning the DTB at 0xc00000, so the decompressor handed
the kernel a null DTB pointer. Fix: place the fdt high `load=0x08000000` in the
the kernel a null DTB pointer. Fix: place the fdt high (`load=0x08000000`) in the
FIT `.its` (see `boot5.its`). The real fix is a lean defconfig; multi_v7 is bloat.
- **`grf_ctx` NULL deref in clk-rv1106.** `rockchip_clk_register_branches(grf_ctx,)`
- **`grf_ctx` NULL deref in clk-rv1106.** `rockchip_clk_register_branches(grf_ctx,...)`
crashed because the minimal DT had no `rockchip,rv1106-grf-cru` node to set
`grf_ctx`. Fix: add the `grf-clock-controller` child to the grf syscon (now in
`dts/rv1106-warden-m2.dts`).
**Console baud gotcha:** `console=ttyS2,115200`. earlycon is readable at 115200
(U-Boot leaves uart2 there), but the vendor's 1.5M console rate is garbage on the
CP2102 bench adapter so the M2 DT pins 115200 for readable bring-up; production
CP2102 bench adapter, so the M2 DT pins 115200 for readable bring-up; production
overrides to 1.5M.
## M3 (same session): [x] the full WardenOS runs on the 6.18 kernel
Adding the eMMC `dw_mmc` node (`mmc@ffa90000`, clocks from cru + grf_cru) was the
only change M3 needed the mmc/ext4 drivers are already in-config. The kernel
only change M3 needed: the mmc/ext4 drivers are already in-config. The kernel
enumerated the eMMC at HS200, mounted the ext4 rootfs, ran `/sbin/init`, and
started every WardenOS daemon. A serial root login confirms `uname -a`
started every WardenOS daemon. A serial root login confirms `uname -a` ->
`Linux warden-c8a3 6.18.46 armv7l`, with `warden-flared/-modbus/-mikrotik/-asic/
-ui/-flight` all running. Expected M4/M5 gaps show cleanly: the 5.10 aic8800 `.ko`
won't load (vermagic M5), and there's no backlight/framebuffer yet ( M4).
won't load (vermagic -> M5), and there's no backlight/framebuffer yet (-> M4).
**Console lesson applied:** with `console=ttyS2,115200` the whole boot is readable
on the CP2102 (the 1.5M vendor rate is garbage on it). Serial login uses the same
`c8a3_run.py` helper (dev-build bench credentials, see private deployment notes)
as the 5.10 firmware the userspace is unchanged.
as the 5.10 firmware: the userspace is unchanged.
Next: M4 (VOP2 display + panel + touch), M5 (AIC8800 SDIO port + our 4 patches),
M6 (RGA/watchdog/HPMCU/USB-OTG); plus a lean defconfig + Buildroot-on-6.18 cleanup.
+21 -21
View File
@@ -1,52 +1,52 @@
# RV1106 hardware-capabilities audit nothing left on the table
# RV1106 hardware-capabilities audit: nothing left on the table
Every block in the vendor SoC DT (`rv1106.dtsi`), classified. Goal: a 6.18 driver
for every capability the hardware actually has, open-source, verified. Camera/ISP
is the only whole class deliberately skipped the 86-Panel has no camera.
is the only whole class deliberately skipped: the 86-Panel has no camera.
## Done / at parity (verified on warden-c8a3)
CRU clk · pinctrl (+ioc/pmuioc) · GIC · arch timer · pl330 DMA · 8250 uart (×3) ·
CRU clk · pinctrl (+ioc/pmuioc) · GIC · arch timer · pl330 DMA · 8250 uart (x3) ·
dw_mmc eMMC · i2c · dw-wdt · **RTC** · **tsadc** · **RGA** (rga2, hw 3.3.87975) ·
PWM backlight · **USB host** (dwc3/xhci) + usb2phy · grf/pmu syscons.
## Verified this run (2026-08-25) all on warden-c8a3, self-built 6.18.46
- **AIC8800 wifi** wlan0 up, scanned the site AP at 43 dBm (modules; `wifi/VERIFIED-on-c8a3.md`).
- **TRNG** /dev/hwrng, real HW entropy (`rng-otp/`).
- **OTP/nvmem** rockchip-otp0 reads chip id (`rng-otp/`).
- **GMAC** eth0 Link Up 100 Mbps/Full (`gmac/`).
- **SARADC** iio:device0 reads 2 ch; the 22 was vref, not clk (`adc/SARADC-FIX.md`).
## Verified this run (2026-08-25), all on warden-c8a3, self-built 6.18.46
- **AIC8800 wifi**: wlan0 up, scanned the site AP at -43 dBm (modules; `wifi/VERIFIED-on-c8a3.md`).
- **TRNG**: /dev/hwrng, real HW entropy (`rng-otp/`).
- **OTP/nvmem**: rockchip-otp0 reads chip id (`rng-otp/`).
- **GMAC**: eth0 Link Up 100 Mbps/Full (`gmac/`).
- **SARADC**: iio:device0 reads 2 ch; the -22 was vref, not clk (`adc/SARADC-FIX.md`).
## VOP display VERIFIED this run (2026-08-25)
## VOP display: VERIFIED this run (2026-08-25)
Full WardenOS Dashboard renders on the 86-Panel on 6.18 (`_b`), webcam-verified,
pixel-identical to stock `_a`. Two VOP driver bugs were the final black-screen
cause: `rgb_dclk_pol` hardcoded inverted (panel needs 0), and the wrong primary
scanout window (rv1106 uses **WIN1**, not rv1126's WIN2). Full chain + `_a`-vs-`_b`
register diff in `display/VERIFIED.md`.
## GT911 touch VERIFIED this run (2026-08-25)
## GT911 touch: VERIFIED this run (2026-08-25)
UI responds to taps/swipes on the panel (confirmed by hand at the panel); GT911 detected
(`ID 911, version 1060`), `/dev/input/event0` held by warden-ui. Fix:
`CONFIG_TOUCHSCREEN_GOODIX=y` (built-in the rootfs `goodix.ko` is a 5.10 build
`CONFIG_TOUCHSCREEN_GOODIX=y` (built-in, the rootfs `goodix.ko` is a 5.10 build
that can't load on 6.18) + GT911 node on `&i2c3`. Details in `touch/VERIFIED.md`.
## In flight
- **i2s-tdm** DAI builds; needs the codec + card (below).
- **AIC8800 BT** module built (6.18 vermagic); HCI bring-up not yet exercised.
- **i2s-tdm**: DAI builds; needs the codec + card (below).
- **AIC8800 BT**: module built (6.18 vermagic); HCI bring-up not yet exercised.
## audio VERIFIED this run
## audio: VERIFIED this run
card `rv1106-acodec` + pcmC0D0p/c (`audio/`); audible test @ bench with display.
## Remaining blocks final status (all real capabilities now verified)
## Remaining blocks: final status (all real capabilities now verified)
| Block | verdict | note |
|---|---|---|
| **mailbox** (HPMCU) | VERIFIED | A7RISC-V-SCR1 round-trip, 5/5 exact echoes (`mailbox/VERIFIED.md`). Took 3 hardware-found fixes: rv1106 num_chans=1 (1 shared IRQ, not 4), CLK_CORE_MCU IGNORE_UNUSED (6.18 was gating the coprocessor clock), + an open SCR1 echo firmware with A2B_INTEN. The /dev/mem SRAM watchdog stays as a separate dead-man's-switch. |
| **NPU** (rknpu) | [x] open driver VERIFIED; compute deferred | open GPL rknpu 0.9.2 driver, /dev/dri/card1, version ioctl PASS (`npu/VERIFIED.md`). Open *compute* (a regcmd compiler) is a from-scratch ~person-year register-RE project no RV1106 prior art, no public TRM Part 2, mainline accel/rocket+Teflon are RK3588-only. Ship the driver, no blob. |
| **mailbox** (HPMCU) | VERIFIED | A7<->RISC-V-SCR1 round-trip, 5/5 exact echoes (`mailbox/VERIFIED.md`). Took 3 hardware-found fixes: rv1106 num_chans=1 (1 shared IRQ, not 4), CLK_CORE_MCU IGNORE_UNUSED (6.18 was gating the coprocessor clock), + an open SCR1 echo firmware with A2B_INTEN. The /dev/mem SRAM watchdog stays as a separate dead-man's-switch. |
| **NPU** (rknpu) | [x] open driver VERIFIED; compute deferred | open GPL rknpu 0.9.2 driver, /dev/dri/card1, version ioctl PASS (`npu/VERIFIED.md`). Open *compute* (a regcmd compiler) is a from-scratch ~person-year register-RE project: no RV1106 prior art, no public TRM Part 2, mainline accel/rocket+Teflon are RK3588-only. Ship the driver, no blob. |
| **pvtm** | VERIFIED | both core+pmu PVT monitors probe; debugfs ring-osc reads (`pvtm/PORT-DONE.md`). |
| **crypto-v3** (accel) | deferred (documented) | ~100 KB whole-subsystem replacement of mainline's rk3288 crypto + heavy crypto-API deltas; the **CPU crypto extensions (AES/SHA, batch2 =y) already cover the functional need** an offload optimization, not a capability gap. |
| **crypto-v3** (accel) | deferred (documented) | ~100 KB whole-subsystem replacement of mainline's rk3288 crypto + heavy crypto-API deltas; the **CPU crypto extensions (AES/SHA, batch2 =y) already cover the functional need**, an offload optimization, not a capability gap. |
| camera/ISP, SPI | N/A | no such hardware on the 86-Panel. |
**Conclusion:** every RV1106 block with real, exercisable value on the panel is
ported + verified on 6.18 including the mailbox and the open NPU driver. The only
ported + verified on 6.18, including the mailbox and the open NPU driver. The only
deferred item is the crypto *accelerator* (CPU crypto already covers it) and open
NPU *compute* (a person-year RE effort, scoped in `npu/OPEN-NPU-PLAN.md`). No
unexplored gap remains.
@@ -55,5 +55,5 @@ unexplored gap remains.
cif · csi2-dphy · mipi-csi2 · rkisp (all camera/ISP) · SPI (no on-board SPI device).
## Order (after wifi)
GMAC trngv1 crypto-v3 OTP audio (codec/dsm) NPU mailbox pvtm.
GMAC -> trngv1 -> crypto-v3 -> OTP -> audio (codec/dsm) -> NPU -> mailbox -> pvtm.
GMAC + trngv1 first: highest real value (wired net + hardware entropy).
+14 -14
View File
@@ -8,12 +8,12 @@ c8a3, not just compiled.
| Driver / node | 5.10 evidence | mainline? | 6.18 status |
|---|---|---|---|
| CRU clock (clk-rv1106) | | ported | [x] M2 |
| pinctrl-rockchip (rv1106) | | ported | [x] M2 |
| CRU clock (clk-rv1106) | n/a | ported | [x] M2 |
| pinctrl-rockchip (rv1106) | n/a | ported | [x] M2 |
| GIC-400 / arch_timer | arch_timer | mainline | [x] M2 |
| dw_mmc (eMMC) | dw-mci | mainline | [x] M3 |
| 8250 uart2 (console) | ttyS2 | mainline | [x] M2 |
| GPIO (rockchip, ×5 banks) | gpio-rockchip | mainline | [x] batch1 (chips 04) |
| GPIO (rockchip, x5 banks) | gpio-rockchip | mainline | [x] batch1 (chips 0-4) |
| DMA (pl330, ff420000) | ff420000.dma-controller | mainline | [x] batch1 |
| uart1 / uart4 | ttyS1, ttyS4 | mainline | [x] batch1 |
| I2C (dw-apb, ff460000=i2c3) | ff460000.i2c | mainline | [x] batch1 (i2c-3) |
@@ -23,22 +23,22 @@ c8a3, not just compiled.
| TRNG (rng@ff448000) | rockchip,trngv1 | mainline (rk3588 IP) | [x] /dev/hwrng, real entropy (`rng-otp/`) |
| OTP/nvmem (ff3d0000) | rockchip,rv1106-otp | ported (px30_otp_read) | [x] rockchip-otp0, reads chip id |
| GMAC (ffa80000) | rockchip,rv1106-gmac | ported (dwmac-rk rv1106_ops) | [x] eth0 Link Up 100M/Full (`gmac/`) |
| GPIO_SYSFS (legacy /sys/class/gpio) | | mainline (config) | [ ] goodix script needs it |
| PWM (rockchip) | | mainline (=m) | [ ] batch2 =y (backlight) |
| RTC (rv1106-rtc) | | ported (vendor driver) | [x] /dev/rtc0 registers + reads |
| USB2 phy (inno, rv1106) | rockchip_usb2phy_* | ported (data, no tuning) | [x] probes USB up |
| USB host (DWC3xhci, ffb00000) | xhci-hcd:usb1 | mainline | [x] xhci host registered |
| GPIO_SYSFS (legacy /sys/class/gpio) | n/a | mainline (config) | [ ] goodix script needs it |
| PWM (rockchip) | n/a | mainline (=m) | [ ] batch2 =y (backlight) |
| RTC (rv1106-rtc) | n/a | ported (vendor driver) | [x] /dev/rtc0 registers + reads |
| USB2 phy (inno, rv1106) | rockchip_usb2phy_* | ported (data, no tuning) | [x] probes -> USB up |
| USB host (DWC3->xhci, ffb00000) | xhci-hcd:usb1 | mainline | [x] xhci host registered |
| USB OTG gadget (DWC3, eth0) | eth0 | mainline dwc3 | [wip] host works; eth0 needs dr_mode=peripheral |
| crypto (aes/ccm/ctr/arc4) | modules | mainline | [ ] batch2 (config =y) |
| PSCI node (removed) | — | — | [x] deleted (no secure monitor SMC fault) |
| PSCI node (removed) | n/a | n/a | [x] deleted (no secure monitor -> SMC fault) |
| VOP display (ff990000) | ff990000.vop | ported (rv1126 sibling) | [wip] binds+DRM+card0; connector WIP |
| PWM backlight (pwm1) | | mainline (rk3328 fallback) | [x] backlight up (brightness) |
| RGB666 720×720 panel | | panel-dpi | [wip] probes; bus_format + connector WIP |
| PWM backlight (pwm1) | n/a | mainline (rk3328 fallback) | [x] backlight up (brightness) |
| RGB666 720x720 panel | n/a | panel-dpi | [wip] probes; bus_format + connector WIP |
| GT911 touch (goodix) | goodix, gt911 | mainline | [ ] M4 (needs GPIO_SYSFS [x] + node) |
| GPIO_SYSFS / crypto / CFG80211 | | mainline (config) | [x] =y (batch2) |
| AIC8800 wifi (bsp/fdrv) | aic8800_* | **out-of-tree** | [x] M5 wlan0 up, scanned the site AP at 43dBm (modules, `wifi/VERIFIED-on-c8a3.md`) |
| GPIO_SYSFS / crypto / CFG80211 | n/a | mainline (config) | [x] =y (batch2) |
| AIC8800 wifi (bsp/fdrv) | aic8800_* | **out-of-tree** | [x] M5: wlan0 up, scanned the site AP at -43dBm (modules, `wifi/VERIFIED-on-c8a3.md`) |
| AIC8800 BT (btlpm) | aic8800_btlpm | **out-of-tree** | [wip] module built (6.18 vermagic); HCI bring-up not yet exercised |
| NPU (rknpu, ff660000) | rknpu, ff660000.npu | **out-of-tree** | [x] open GPL driver VERIFIED on hardware `/dev/dri/card1`, `rknpu_version_test` PASS (power/clock/reset path exercised); open *compute* (regcmd) remains a from-scratch RE project (`npu/VERIFIED.md`, `npu/OPEN-NPU-PLAN.md`) |
| NPU (rknpu, ff660000) | rknpu, ff660000.npu | **out-of-tree** | [x] open GPL driver VERIFIED on hardware: `/dev/dri/card1`, `rknpu_version_test` PASS (power/clock/reset path exercised); open *compute* (regcmd) remains a from-scratch RE project (`npu/VERIFIED.md`, `npu/OPEN-NPU-PLAN.md`) |
| RGA 2D (rga2) | rga2 | ported (vendor char-dev) | [x] /dev/rga, hw 3.3.87975 |
| I2S audio (i2s-tdm) | i2s | rv1126 fallback (=y) | [x] cpu DAI registers (part of the card below) |
| Audio codec (acodec) | rockchip,rv1106-codec | ported (rv1106_codec.c) | [x] card `rv1106-acodec`, pcmC0D0p/c (`audio/`); audible test @ bench |
+13 -13
View File
@@ -1,23 +1,23 @@
# Overnight autonomous kernel-completion workflow
**Directive (2026-08-24 night):** port/enable **every** remaining RV1106
hardware capability on the self-built 6.18 kernel, **open-source-first** we want
hardware capability on the self-built 6.18 kernel, **open-source-first**: we want
*source* we can read, harden, and bend to our needs (vendor SDK source, upstream,
community repos, or reverse-engineering), never binary blobs. Not just ports
community repos, or reverse-engineering), never binary blobs. Not just ports,
**new drivers if necessary**. Leave zero hardware capability or customization on
the table. Fully autonomous, runs overnight; the display's last mile is deferred
to tomorrow morning (needs eyes on the panel).
## Open-source sourcing policy (in order of preference)
1. **Vendor SDK source** `flare-edge/sdk/sysdrv/source/kernel/drivers/`. Every
1. **Vendor SDK source**: `flare-edge/sdk/sysdrv/source/kernel/drivers/...`. Every
driver below ships as C source there (aic8800, rknpu, rga, rtc, tsadc, i2s all
have source the *only* closed pieces are userspace runtimes like RKNN, not
the kernel drivers). Port 5.10 6.18.
2. **Mainline / sibling** reuse rv1126/px30 data where the IP matches (clk,
have source: the *only* closed pieces are userspace runtimes like RKNN, not
the kernel drivers). Port 5.10 -> 6.18.
2. **Mainline / sibling**: reuse rv1126/px30 data where the IP matches (clk,
pinctrl, vop, saradc, usb2phy all used this).
3. **Community open drivers** deep web research (AICSemi upstream/github, the
3. **Community open drivers**: deep web research (AICSemi upstream/github, the
NPU reverse-engineering efforts, etc.) for a cleaner or better-understood base.
4. **Reverse-engineering** register maps from the TRM + the vendor source, as a
4. **Reverse-engineering**: register maps from the TRM + the vendor source, as a
last resort or to harden.
For each: understand it, prefer the most upstream-aligned source, harden it, and
add a regression check where feasible.
@@ -36,17 +36,17 @@ add a regression check where feasible.
| 8 | **eth0 USB gadget** | dr_mode + configfs | `eth0` to xps |
| 9 | **Capabilities audit** | TRM + SDK sweep | enable/port everything else |
### Capabilities audit (#9) nothing left on the table
### Capabilities audit (#9): nothing left on the table
Enumerate every RV1106 block and confirm a 6.18 driver: crypto accelerator + TRNG,
mailbox/HPMCU integration, DSMC/flexbus, SFC/SPI-NOR, remaining SPI, CAN, PWM
(fan/other), DMA2, DDR monitor, the second/other UARTs, GMAC (if wired), pvtm,
otp/nvmem, dsmc. Anything with hardware present and no driver port or write it.
otp/nvmem, dsmc. Anything with hardware present and no driver -> port or write it.
## Execution model
Pipeline: research (Sonnet subagents, in background) ∥ port (me) build flash to
c8a3 slot _b verify on hardware commit to warden-sdk. Serial bottleneck is
Pipeline: research (Sonnet subagents, in background) ∥ port (me) -> build -> flash to
c8a3 slot _b -> verify on hardware -> commit to warden-sdk. Serial bottleneck is
c8a3; research/porting overlaps verification. **Resilience:** krecover is hardened
(serial-primary + Pi/rkdeveloptool bootcount-reset fallback); on a port that won't
land, capture the state, mark it in DRIVER-PARITY.md, and move on never leave
land, capture the state, mark it in DRIVER-PARITY.md, and move on; never leave
c8a3 stuck, never block the whole run on one driver. Commit after every landed
driver so progress is durable.
+14 -14
View File
@@ -1,4 +1,4 @@
# Overnight kernel-enablement run results (2026-08-24 night 08-25)
# Overnight kernel-enablement run: results (2026-08-24 night -> 08-25)
Goal: port/enable **every** remaining RV1106 hardware capability on the
self-built Linux 6.18.46, open-source-first, verify on hardware, so the display's
@@ -7,37 +7,37 @@ last mile can start in the morning. Runs on warden-c8a3 (`_b` slot = our 6.18).
## Verified on hardware this run
| Driver | Evidence |
|---|---|
| **AIC8800 wifi** (M5) | wlan0 up, `iw scan` found the site AP at 43 dBm + others. Modules (built-in deadlocks the two-stage SDIO bring-up). |
| **AIC8800 wifi** (M5) | wlan0 up, `iw scan` found the site AP at -43 dBm + others. Modules (built-in deadlocks the two-stage SDIO bring-up). |
| **TRNG** | `/dev/hwrng`, `rng_current=rockchip-rng`, real entropy. |
| **OTP/nvmem** | `rockchip-otp0` reads chip id ("MR1"). |
| **GMAC** (wired eth) | `eth0: Link is Up - 100 Mbps/Full`. |
| **SARADC** | `iio:device0` reads 2 channels (adc-keys). |
| **Audio** | card `rv1106-acodec`, `/dev/snd/pcmC0D0p`+`pcmC0D0c`. Audible test bench. |
| **Audio** | card `rv1106-acodec`, `/dev/snd/pcmC0D0p`+`pcmC0D0c`. Audible test -> bench. |
(These join the earlier-verified set: clk, pinctrl, GIC, timer, DMA, eMMC, uart×3,
i2c, wdt, RTC, tsadc, RGA, USB host, PWM backlight. VOP display binds connector
(These join the earlier-verified set: clk, pinctrl, GIC, timer, DMA, eMMC, uartx3,
i2c, wdt, RTC, tsadc, RGA, USB host, PWM backlight. VOP display binds: connector
is the morning task.)
## Cross-cutting fixes made this run
- **XZ compression** (`CONFIG_KERNEL_XZ`): the growing kernel overran U-Boot's
DTB-at-0xc00000 load boundary (`Sysmem Error`, FLARE-AB fell to _a). zImage
12.18.3 MB, ~4 MB headroom. See `kernel-618-btest-cycle` memory + wifi doc.
12.1->8.3 MB, ~4 MB headroom. See `kernel-618-btest-cycle` memory + wifi doc.
- **Module vs built-in**: wifi must be modules (SDIO two-stage bring-up races the
mmc probe when built-in sequential initcall deadlock). Audio/GMAC/etc. are
mmc probe when built-in: sequential initcall deadlock). Audio/GMAC/etc. are
built-in and fine.
- Hardened the c8a3 `_b`-test `_a`-recover cycle (compact-hex AVB serial write +
readback while stable; Pi golden-env fallback for bootcountloader).
- Hardened the c8a3 `_b`-test -> `_a`-recover cycle (compact-hex AVB serial write +
readback while stable; Pi golden-env fallback for bootcount->loader).
## Not done deferred with reasons (see CAPABILITIES-AUDIT.md)
## Not done: deferred with reasons (see CAPABILITIES-AUDIT.md)
mailbox (binds via rk3368 fallback but no client to exercise), crypto-v3 (CPU
crypto extensions already cover it; 100 KB port), NPU (no open userspace),
pvtm (DVFS-only). Camera/ISP/SPI: no hardware. **No unexplored capability gap.**
## Morning (needs a human at the bench)
1. **Display connector** VOP binds + DRM card0 + panel probes, but no connector
1. **Display connector**: VOP binds + DRM card0 + panel probes, but no connector
link yet; needs eyes on the panel (pixels can't be verified over serial).
2. **Audible audio** `speaker-test`/`aplay` through the acodec.
3. Wifi **boot-time auto-load** (modules currently insmod'd by hand) a loader
that inserts aic8800_bspfdrv(btlpm) from the rootfs.
2. **Audible audio**: `speaker-test`/`aplay` through the acodec.
3. Wifi **boot-time auto-load** (modules currently insmod'd by hand): a loader
that inserts aic8800_bsp->fdrv(->btlpm) from the rootfs.
Provenance: every ported driver is GPL-2.0 kernel source (`PROVENANCE.md`).
+47 -47
View File
@@ -1,6 +1,6 @@
# RV1106 6.18 forward-port live status
# RV1106 -> 6.18 forward-port: live status
> **CANONICAL SOURCE.** This directory is a point-in-time **port-provenance layer**
> **CANONICAL SOURCE.** This directory is a point-in-time **port-provenance layer**:
> what was ported, the milestones, verification evidence, and standalone test programs
> (e.g. `npu/rknpu_version_test.c`). It is **not** the source of truth for the kernel
> delta. The canonical, current delta is **`../../patches/*.patch`** (applied by
@@ -8,125 +8,125 @@
> `flare-edge/research/linux-6.18.46/`. The `.c`/`.dts`/`.frag` snapshots here are
> historical port evidence and may **lag** the final series (e.g. `clk/clk-rv1106.c`
> predates the last HPMCU-clock `CLK_IGNORE_UNUSED` fix that is in `patches/10-clk-rv1106.patch`).
> Do not hand-edit them or treat them as current change kernel behaviour in `patches/`.
> Do not hand-edit them or treat them as current: change kernel behaviour in `patches/`.
Target: **Linux 6.18.46** (vanilla, `flare-edge/research/linux-6.18.46/`), forward-ported
from the **vendor 5.10.160** tree, no plan44 code, built with our
`arm-rockchip830-...-gcc 8.3` (confirmed: **gcc 8.3 builds 6.18 fine**).
## Confirmed feasibility facts
- gcc 8.3 (our SDK toolchain) compiles the 6.18 kernel no toolchain bump needed to start.
- gcc 8.3 (our SDK toolchain) compiles the 6.18 kernel: no toolchain bump needed to start.
- `multi_v7_defconfig` (ARM + rockchip) configures; `CLK_RV1106=y` wires in cleanly.
- rv1106's siblings **rv1126/rv1108 exist in both trees**, so their 5.106.18 delta is a
- rv1106's siblings **rv1126/rv1108 exist in both trees**, so their 5.10->6.18 delta is a
working template for the framework API changes.
## M1 clock driver (`clk-rv1106.c`, 1294 lines): [x] COMPILES CLEAN on 6.18
Build-fix loop against 6.18 `clk-rv1106.o` (85732 bytes) builds with no errors.
## M1, clock driver (`clk-rv1106.c`, 1294 lines): [x] COMPILES CLEAN on 6.18
Build-fix loop against 6.18: `clk-rv1106.o` (85732 bytes) builds with no errors.
Fixed (captured in `clk/`):
1. **Kconfig + Makefile hooks** added `CONFIG_CLK_RV1106` (mirrors CLK_RV1126).
2. **CRU register macros** ported all 55 `RV1106_*` register-accessor `#define`s from the
1. **Kconfig + Makefile hooks**: added `CONFIG_CLK_RV1106` (mirrors CLK_RV1126).
2. **CRU register macros**: ported all 55 `RV1106_*` register-accessor `#define`s from the
vendor `clk.h` into 6.18's `clk.h` (mainline has none).
3. **Header split** `panic_notifier_list` moved to `<linux/panic_notifier.h>` (kernel
3. **Header split**: `panic_notifier_list` moved to `<linux/panic_notifier.h>` (kernel
5.18); added the include.
4. **`rockchip_clk_register_armclk` signature change** 5.10 took
4. **`rockchip_clk_register_armclk` signature change**: 5.10 took
`(num_parents, parent_clk, alt_parent_clk)`; 6.18 takes `(parent_names[], num_parents)`
and drives the mux from `reg_data.mux_core_main/alt`. Adapted the call to the 6.18 form
using a parent-names array (sibling-delta from rv1126). **PORT-VERIFY**: the mux input
list `{ "gpll","cpll","apll" }` and the `mux_core_main/alt=2` mapping are a best-effort
from the 5.10 intent (main=apll) they set the **CPU clock source**, so they must be
from the 5.10 intent (main=apll); they set the **CPU clock source**, so they must be
checked against the RV1106 `CORECLKSEL_CON` register map (TRM) and validated on hardware
before trusting a boot. A wrong mux silently breaks boot.
5. **`CLK_FRAC_DIVIDER_NO_LIMIT`** Rockchip downstream-only frac-divider flag (6 uses on
5. **`CLK_FRAC_DIVIDER_NO_LIMIT`**: Rockchip downstream-only frac-divider flag (6 uses on
the UART frac clocks); mainline has no min/max opt-out, mapped to 0 (default limit).
**PORT-VERIFY**: UART fractional baud accuracy.
## M1 pinctrl (`pinctrl-rockchip.c/.h`): [x] COMPILES CLEAN on 6.18
## M1, pinctrl (`pinctrl-rockchip.c/.h`): [x] COMPILES CLEAN on 6.18
`pinctrl-rockchip.o` (173688 bytes) builds no-errors. Transplanted from vendor 5.10 (effort S,
zero API drift the survey's assessment held): added `RV1106` to the type enum; a 159-line
zero API drift, the survey's assessment held): added `RV1106` to the type enum; a 159-line
block of `RV1106_DRV/PULL/SMT_*` macros + 3 `rv1106_calc_*_reg_and_bit()` functions; `case
RV1106:` in the 3 pull functions + the RK3568 drive-strength group; `rv1106_pin_banks[]` +
`rv1106_pin_ctrl`; and the `rockchip,rv1106-pinctrl` of_device_id (dropping the vendor's
`#ifdef CONFIG_CPU_RV1106` guard mainline compiles all SoCs unconditionally). Captured as
`#ifdef CONFIG_CPU_RV1106` guard: mainline compiles all SoCs unconditionally). Captured as
`pinctrl/0001-rv1106-pinctrl.patch`.
- **PORT-VERIFY RESOLVED (iomux offsets):** the DRV/PULL/SMT per-bank offsets are confirmed by
a THIRD independent source the upstream Simon Glass v3 pinctrl patch. Its
a THIRD independent source: the upstream Simon Glass v3 pinctrl patch. Its
`rv1106_{drv,pull,smt}_offsets[]` match ours exactly once the 0x10000-strided per-bank IOC
base is applied (e.g. vendor GPIO2 DRV `0x100C0` = `0x10000 + 0xc0`). Retires the "do not
guess" iomux risk without hardware. (Note upstream uses per-bank IOC regmaps + low offsets;
we use the vendor's full-offset form same absolute register, both compile.)
- **Still PORT-VERIFY:** GPIO4 bank pin-count (`pin_banks` says 24, DT `gpio-ranges` says 32)
we use the vendor's full-offset form: same absolute register, both compile.)
- **Still PORT-VERIFY:** GPIO4 bank pin-count (`pin_banks` says 24, DT `gpio-ranges` says 32),
carried from vendor unchanged; needs TRM/hardware.
## M1 mach: DONE
## M1, mach: DONE
`mach-rockchip` RV1106/RV1103 SoC recognition added as a DT-compat entry (no
`CPU_RV1106` symbol recreated). Captured as `mach/0001-rv1106-soc-recognition.patch`.
**M1 is complete: clk + pinctrl + mach all compile clean on 6.18.**
## M2 earlycon build: DONE (boot pending hardware)
## M2, earlycon build: DONE (boot pending hardware)
The first full kernel build with our SoC drivers, 2026-08-24:
- **`multi_v7_defconfig` + `configs/m2-earlycon.fragment` builds an 11.8 MB zImage**
with `clk-rv1106.o` (85732 B) and `pinctrl-rockchip.o` (173688 B, our rv1106 data)
compiled *into the full tree* no rv1106 warnings/errors. Reproducible via
compiled *into the full tree*: no rv1106 warnings/errors. Reproducible via
`build-m2.sh`.
- **`dts/rv1106-warden-m2.dts` `rv1106-warden-m2.dtb` compiles clean** (W=1, no dtc
warnings). A deliberately minimal DT CPU (cortex-a7), GIC-400, arch timer, 256 MiB
RAM, GRF, the CRU (our clk-rv1106), and uart2 (`snps,dw-apb-uart`, the console)
- **`dts/rv1106-warden-m2.dts` -> `rv1106-warden-m2.dtb` compiles clean** (W=1, no dtc
warnings). A deliberately minimal DT: CPU (cortex-a7), GIC-400, arch timer, 256 MiB
RAM, GRF, the CRU (our clk-rv1106), and uart2 (`snps,dw-apb-uart`, the console),
with `earlycon=uart8250,mmio32,0xff4c0000` so the first print reuses the loader's
divisor before any clock/pinctrl probe.
**Still PORT-VERIFY before a boot is trusted:** memory size/base (256 MiB @ 0x0 assumed
the loader usually patches this); the CPU clock mux in clk-rv1106 (§M1 item 4); the
**Still PORT-VERIFY before a boot is trusted:** memory size/base (256 MiB @ 0x0 assumed,
the loader usually patches this); the CPU clock mux in clk-rv1106 (section M1 item 4); the
console baud (1.5M assumed). A wrong DDR/clock value silently hangs before or just after
earlycon.
## M2 boot: DONE "it's alive" on warden-c8a3 (2026-08-24)
## M2, boot: DONE: "it's alive" on warden-c8a3 (2026-08-24)
The self-built **Linux 6.18.46 boots on real RV1106 hardware**, through our ported
drivers, verified over the serial console. It reaches earlycon, the arch timer
(BogoMIPS calibrated), **our `clk-rv1106` CRU driver**, pinctrl, and the mainline
8250 bound to uart2 **clocked by our CRU** (`ttyS2 16550A`), then hands off to the
8250 bound to uart2 **clocked by our CRU** (`ttyS2 ... 16550A`), then hands off to the
real console and mounts a rootfs. See `../docs/m2-boot-on-c8a3.md` for the method
(the A/B `_b`-slot safe-test framework worked first try) and the boot-image format
(external-data FIT + resource, `mkimage -E -p 0x800`).
Three bring-up bugs were found and fixed on hardware, all captured in the DT:
1. **boot.img format** rockchip U-Boot needs the external-data FIT + a `resource`
1. **boot.img format**: rockchip U-Boot needs the external-data FIT + a `resource`
(multi) sub-image with `rk-kernel.dtb`, else `No fit blob` / `Failed to load DTB`.
2. **DTB overrun** the bloated multi_v7 zImage decompresses to ~20 MiB and overran
2. **DTB overrun**: the bloated multi_v7 zImage decompresses to ~20 MiB and overran
the DTB at 0xc00000, so the kernel got `r2=0` (`invalid dtb`). Fix: place the fdt
high (`load=0x08000000`). A lean RV1106 defconfig would also fix this and is the
right long-term move.
3. **grf-cru NULL deref** `clk-rv1106` registers a second branch set against a GRF
3. **grf-cru NULL deref**: `clk-rv1106` registers a second branch set against a GRF
clock-controller (`grf_ctx`), set only by a `rockchip,rv1106-grf-cru` node. The
minimal DT omitted it `grf_ctx` NULL panic in `rockchip_clk_register_branches`.
minimal DT omitted it -> `grf_ctx` NULL -> panic in `rockchip_clk_register_branches`.
Fix: add the `grf-clock-controller` child to the grf syscon.
**clk-rv1106 PORT-VERIFY (armclk mux, PLL rates) is now partially retired**: the CRU
comes up far enough to clock the UART and the arch timer on hardware. A wrong CPU
mux/PLL would show later (cpufreq / peripheral rates), still to be checked.
## M3 rootfs boot: DONE the full WardenOS runs on the 6.18 kernel (2026-08-24)
Adding the eMMC (`dw_mmc`) node to the DT was all M3 needed the drivers are already
## M3, rootfs boot: DONE: the full WardenOS runs on the 6.18 kernel (2026-08-24)
Adding the eMMC (`dw_mmc`) node to the DT was all M3 needed: the drivers are already
in the config. On hardware:
```
dwmmc_rockchip ffa90000.mmc: Version ID is 270a our dw_mmc bound
mmc0: new HS200 MMC card ... mmcblk0: 8GTF4R 7.28 GiB eMMC at HS200 (198 MHz)
mmcblk0: p1(env)...p10(rootfs_b)...p12(userdata) partitions enumerated
EXT4-fs (mmcblk0p10): mounted ... VFS: Mounted root ext4 rootfs mounted
dwmmc_rockchip ffa90000.mmc: Version ID is 270a <- our dw_mmc bound
mmc0: new HS200 MMC card ... mmcblk0: 8GTF4R 7.28 GiB <- eMMC at HS200 (198 MHz)
mmcblk0: p1(env)...p10(rootfs_b)...p12(userdata) <- partitions enumerated
EXT4-fs (mmcblk0p10): mounted ... VFS: Mounted root <- ext4 rootfs mounted
Run /sbin/init as init process
Starting warden-modbus / -mikrotik / -asic / -flared / Warden UI
```
A root shell over the serial console confirms it: `uname -a` **`Linux warden-c8a3
A root shell over the serial console confirms it: `uname -a` -> **`Linux warden-c8a3
6.18.46 armv7l`**, with `warden-flared`, `warden-modbus`, `warden-mikrotik`,
`warden-asic`, `warden-ui`, `warden-flight` all in `ps`. The eMMC node's ciu-drive/
ciu-sample clocks come from `grf_cru`, so the M2 grf-cru fix was a prerequisite.
The expected M4/M5 gaps show as clean failures: the out-of-tree 5.10 `aic8800`
wifi/BT `.ko` won't load on 6.18 (vermagic needs the driver ported, **M5**), and
`warden-ui: no backlight device` (no display/VOP node yet **M4**).
wifi/BT `.ko` won't load on 6.18 (vermagic, needs the driver ported, **M5**), and
`warden-ui: no backlight device` (no display/VOP node yet, **M4**).
## M4/M5/M6 NEXT
- **M4 display**: VOP2 + the RGB666 720×720 panel + GT911 touch + PWM backlight
## M4/M5/M6: NEXT
- **M4 display**: VOP2 + the RGB666 720x720 panel + GT911 touch + PWM backlight ->
the LVGL UI renders (the UI process already runs, it just has no framebuffer).
- **M5 wifi**: port the AIC8800 SDIO driver to 6.18 (+ the Tier-1 fix) so wlan0/usb0
come back; re-apply our 4 kernel patches.
@@ -137,16 +137,16 @@ wifi/BT `.ko` won't load on 6.18 (vermagic — needs the driver ported, **M5**),
## Upstream tracking (decision 2026-08-23)
Base stays the vendor forward-port (applies to 6.18; we control it). The unmerged upstream
RV1106 series (~35 patches, "New" state, does NOT apply cleanly to 6.18 pinctrl v3 failed at
RV1106 series (~35 patches, "New" state, does NOT apply cleanly to 6.18, pinctrl v3 failed at
`pinctrl-rockchip.c:3390`) is used as a **correctness oracle** for boot-critical PORT-VERIFY
values (already retired the iomux offsets) and tracked for eventual convergence when it merges.
## Honest scope
This is the first driver of ~120 in the RV1106 BSP. The clk port alone is a multi-cycle
effort of the four delta classes above; the whole bring-up (M1M6 in `../docs/bringup.md`)
effort of the four delta classes above; the whole bring-up (M1-M6 in `../docs/bringup.md`)
is a multi-month kernel effort. Progress is real and the method is proven; correctness of
boot-critical pieces (clock mux, PLL rates, DDR, pinctrl iomux) requires the TRM and
on-hardware validation which waits on c8a3's recovery and, ultimately, careful bring-up.
on-hardware validation: which waits on c8a3's recovery and, ultimately, careful bring-up.
## Layout
```
+9 -9
View File
@@ -1,17 +1,17 @@
# Driver provenance & openness ledger
Standing directive: **every** driver we run on the self-built 6.18 kernel must be
open source we can read, harden, and bend not a binary blob and this applies
open source we can read, harden, and bend (not a binary blob) and this applies
to the drivers *already* ported, not just the new ones. This ledger records, for
each, where the source came from and under what license. Every entry is GPL-2.0
kernel source; nothing here is a binary-only kernel module.
## Legend
- **vendor-src** C source from the Rockchip 5.10 vendor SDK
(`flare-edge/sdk/sysdrv/source/kernel/`), forward-ported 5.10 6.18.
- **mainline-sibling** 6.18 mainline driver, extended with an rv1106 data table
- **vendor-src**: C source from the Rockchip 5.10 vendor SDK
(`flare-edge/sdk/sysdrv/source/kernel/`), forward-ported 5.10 -> 6.18.
- **mainline-sibling**: 6.18 mainline driver, extended with an rv1106 data table
derived from a sibling SoC (rv1126/px30/rk3562) whose IP block matches.
- All Rockchip vendor kernel code is a GPL-2.0 fork of mainline Linux GPL-clean.
- All Rockchip vendor kernel code is a GPL-2.0 fork of mainline Linux -> GPL-clean.
## Already-ported drivers (audited for openness per directive)
| Driver | Origin | License | Openness notes |
@@ -30,18 +30,18 @@ kernel source; nothing here is a binary-only kernel module.
|---|---|---|---|
| AIC8800 wifi/BT | vendor-src (AICSemi) | `MODULE_LICENSE("GPL")` | full full-MAC source; 50 files use GPL-only kernel/cfg80211/sdio symbols; **no** proprietary/redistribution restriction in-tree |
## The one unavoidable blob and why it is *not* kernel code
## The one unavoidable blob, and why it is *not* kernel code
- **`aic8800dc_fw`** (`/oem/usr/ko/`) is device firmware that the GPL host driver
*uploads to the wifi chip's on-die processor*. It never runs on the A7; it is not
linked into the kernel. This is the standard Linux firmware split (cf. every
ath10k/mt76 device) and is the only binary in the wifi path the driver itself
ath10k/mt76 device) and is the only binary in the wifi path: the driver itself
is source. No open re-implementation of the AIC on-die MAC firmware exists;
reverse-engineering it is out of scope for the panel and buys nothing (the host
driver is where our control + hardening lives).
## NPU the openness line we will not cross
## NPU: the openness line we will not cross
The **kernel** rknpu driver is portable GPL source (`npu/PORT-PLAN.md`). What is
closed is the *userspace* RKNN runtime + the regcmd stream format a binary blob.
closed is the *userspace* RKNN runtime + the regcmd stream format, a binary blob.
Per directive we do **not** ship that blob; if NPU compute is ever wanted, the open
path is a from-scratch/reverse-engineered regcmd encoder, tracked separately, never
a vendored binary runtime. (See `npu/PORT-PLAN.md` and the graphics investigation.)
+16 -16
View File
@@ -1,12 +1,12 @@
# Remaining ports concrete execution steps (scoped from source)
# Remaining ports: concrete execution steps (scoped from source)
Scoped against mainline 6.18 + vendor 5.10 source while the wifi transplant builds.
Execute in this order once the tree is free (after wifi lands + verifies). Each:
port in tree build flash c8a3 _b verify (self, no visual) commit.
port in tree -> build -> flash c8a3 _b -> verify (self, no visual) -> commit.
## 1. TRNG (hardware RNG) TRIVIAL (~2 lines), HIGH value
## 1. TRNG (hardware RNG): TRIVIAL (~2 lines), HIGH value
**Finding:** rv1106 `rockchip,trngv1` registers (CTRL 0x0000, STAT 0x0004, MODE
0x0008, ISTAT 0x0014, RAND07 0x00200x3C) are **byte-identical** to rk3588, which
0x0008, ISTAT 0x0014, RAND0-7 0x0020-0x3C) are **byte-identical** to rk3588, which
mainline `hw_random/rockchip-rng.c` already drives via `rk3588_rng_read` +
`rk3588_soc_data`.
**Port:** add `{ .compatible = "rockchip,rv1106-rng", .data = &rk3588_soc_data }`
@@ -16,11 +16,11 @@ to the OF table; give the DT node `compatible = "rockchip,rv1106-rng",
**Verify:** `/dev/hwrng` present; `dd if=/dev/hwrng bs=32 count=1 | xxd` non-zero;
`cat /sys/class/misc/hw_random/rng_current` = rockchip.
## 2. GMAC (wired ethernet) MEDIUM, HIGH value
## 2. GMAC (wired ethernet): MEDIUM, HIGH value
**Finding:** node `ethernet@ffa80000` = `snps,dwmac-4.20a` + `rockchip,rv1106-gmac`,
**phy-mode rmii**, `phy-handle=&rmii_phy` (ethernet-phy@2). The **integrated EPHY
(0x1234d400) is ALREADY in mainline** `net/phy/rockchip.c` no PHY port needed.
Bandgap trim `macphy_bgs` comes from OTP (soft dep PHY works without it, slightly
(0x1234d400) is ALREADY in mainline** `net/phy/rockchip.c` -> no PHY port needed.
Bandgap trim `macphy_bgs` comes from OTP (soft dep: PHY works without it, slightly
worse analog). Vendor glue lives in `dwmac-rk.c`; mainline is `dwmac-rockchip.c`.
**Port:** add an rv1106 `struct rockchip_gmac_ops` to mainline `dwmac-rockchip.c`
(GRF bits for RMII mode + `set_to_rmii`/`set_rmii_speed` from vendor `dwmac-rk.c`
@@ -29,30 +29,30 @@ above). Kconfig: `STMMAC_ETH=y`, `DWMAC_ROCKCHIP=y`, `ROCKCHIP_PHY=y`.
**Verify:** `ip link` shows the MAC; `ethtool eth?`; link-up on a patch cable if the
bench has one, else MDIO read of PHY ID via the driver's probe log.
## 3. OTP / nvmem SMALL, MEDIUM value (also feeds GMAC bandgap + chip-id)
## 3. OTP / nvmem: SMALL, MEDIUM value (also feeds GMAC bandgap + chip-id)
**Finding:** vendor `nvmem/rockchip-otp.c` has `rv1106_otp_clocks[]` + an rv1106
read path; mainline has px30/rk3308/rk3576/rk3588 but no rv1106.
**Port:** add `rv1106_data` (reg_read + clocks/offset/size) from vendor to mainline
`rockchip-otp.c` + compatible `rockchip,rv1106-otp`. Kconfig `NVMEM_ROCKCHIP_OTP=y`.
**Verify:** nvmem device in `/sys/bus/nvmem/devices/`; read chip-id cell.
## 4. Audio (codec + DSM + card) MEDIUM, MEDIUM value
## 4. Audio (codec + DSM + card): MEDIUM, MEDIUM value
i2s-tdm DAI already builds (rv1126 fallback). Port vendor `rv1106_codec.c` (acodec)
+ `rk_dsm.c` (digital speaker) + a `simple-audio-card`/`rockchip,rv1106-codec` node.
**Verify:** `aplay -l` shows a card; `speaker-test` writes without error (audible
check is tomorrow's on-panel item).
## 5. crypto-v3 (accelerator) LARGER, MEDIUM value
## 5. crypto-v3 (accelerator): LARGER, MEDIUM value
Vendor `crypto/rockchip/rk_crypto_v3*.c` (v3 core + ahash + skcipher); mainline has
only v1 (rk3288). Port the v3 files + `rockchip,crypto-v3` compatible. Larger
surface; do after the quick wins. **Verify:** `/proc/crypto` lists rk hw entries;
`tcrypt` or an openssl-engine smoke test.
## 6. NPU kernel driver MEDIUM, LOW-MED value (`npu/PORT-PLAN.md`)
Kernel driver only; no open userspace regcmd encoder (openness line no blob).
## 6. NPU kernel driver: MEDIUM, LOW-MED value (`npu/PORT-PLAN.md`)
Kernel driver only; no open userspace regcmd encoder (openness line, no blob).
## 7. mailbox (HPMCU) SMALL, MED value proper coproc mbox vs /dev/mem hack.
## 8. pvtm SMALL, LOW value DVFS monitors; only if DVFS is pursued.
## 7. mailbox (HPMCU): SMALL, MED value, proper coproc mbox vs /dev/mem hack.
## 8. pvtm: SMALL, LOW value, DVFS monitors; only if DVFS is pursued.
Order rationale: TRNG (near-free, high value) GMAC (high value, PHY already
mainline) OTP (small, feeds GMAC) audio crypto NPU mailbox pvtm.
Order rationale: TRNG (near-free, high value) -> GMAC (high value, PHY already
mainline) -> OTP (small, feeds GMAC) -> audio -> crypto -> NPU -> mailbox -> pvtm.
+4 -4
View File
@@ -1,14 +1,14 @@
# SARADC VERIFIED on warden-c8a3 (2026-08-25); the -22 was vref, not clk
# SARADC: VERIFIED on warden-c8a3 (2026-08-25); the -22 was vref, not clk
The rockchip_saradc probe failed `-22` NOT at clk_set_rate (no "failed to set
adc clk rate" ever printed) but at `regulator_get_voltage(info->vref)` with no
adc clk rate" ever printed) but at `regulator_get_voltage(info->vref)`: with no
`vref-supply` in DT the driver got a **dummy** regulator, and
`regulator_get_voltage(dummy)` returns -EINVAL, which probe returns directly (so
only the generic "probe failed with error -22" showed). The earlier
clk-rv1106-divider theory was wrong (the divider is HIWORD settable, xin24m is
registered; clk_set_rate would clamp, not fail).
**Fix (DT only):** add the 1.8 V reference the vendor 86-panel uses
**Fix (DT only):** add the 1.8 V reference the vendor 86-panel uses:
```dts
/ { vcc_1v8: vcc-1v8 { compatible = "regulator-fixed"; regulator-name = "vcc_1v8";
regulator-always-on; regulator-boot-on;
@@ -16,4 +16,4 @@ registered; clk_set_rate would clamp, not fail).
&saradc { vref-supply = <&vcc_1v8>; status = "okay"; };
```
**Evidence:** `iio:device0` (`ff3c0000.saradc`), `in_voltage0_raw=1023`,
`in_voltage1_raw=246` both channels read real analog values (the adc-keys path).
`in_voltage1_raw=246`, both channels read real analog values (the adc-keys path).
+29 -29
View File
@@ -3,7 +3,7 @@
Status: **zImage + rockchip/rv1106-warden.dtb build cleanly, 0 errors, 0 warnings**, with
`SND_SOC_RV1106=y` (acodec) and `SND_SOC_RK_DSM=y` (dsm) both built in, and the
`simple-audio-card` sound card wired in the board DT. Verified against `System.map` (host
`grep`, not cross-`nm` see Verification below). **Not flashed or run on hardware** this is
`grep`, not cross-`nm`: see Verification below). **Not flashed or run on hardware**: this is
a build-only port; on-target audio verification is explicitly deferred to the parent
session / bench.
@@ -17,7 +17,7 @@ Trees involved:
speaker/headphone, tied together by a mainline `simple-audio-card`. The digital speaker
modulator (`dsm`) was also ported and builds cleanly, but is left **disabled** in the board DT,
matching the vendor 86-Panel board file (`rv1106-luckfox-pico-86panel-ipc.dtsi`), which also
ships `dsm` and `dsm_sound` disabled the panel's actual audio path is acodec-only.
ships `dsm` and `dsm_sound` disabled: the panel's actual audio path is acodec-only.
## Files copied (verbatim from vendor, then patched in place)
@@ -25,10 +25,10 @@ From `sdk/sysdrv/source/kernel/sound/soc/codecs/` to the 6.18 tree's `sound/soc/
| File | Lines | Notes |
|---|---|---|
| `rv1106_codec.c` | 2317 | Analog codec (acodec) driver the priority target |
| `rv1106_codec.h` | | Register/bitfield definitions, copied unmodified |
| `rv1106_codec.c` | 2317 | Analog codec (acodec) driver: the priority target |
| `rv1106_codec.h` | n/a | Register/bitfield definitions, copied unmodified |
| `rk_dsm.c` | 653 | Digital speaker modulator (DSM) driver |
| `rk_dsm.h` | | Register/bitfield definitions, copied unmodified |
| `rk_dsm.h` | n/a | Register/bitfield definitions, copied unmodified |
## Kconfig / Makefile wiring
@@ -39,11 +39,11 @@ From `sdk/sysdrv/source/kernel/sound/soc/codecs/` to the 6.18 tree's `sound/soc/
`select REGMAP_MMIO`) after `SND_SOC_RTQ9128`, before `SND_SOC_SDW_MOCKUP` (alphabetical
slot for "RV").
- Neither entry restricts to `ARM64` (unlike `SND_SOC_RK3308`'s existing
`depends on ARM64 || COMPILE_TEST` in this tree) RV1106 is Cortex-A7 / ARM32-only, so an
`depends on ARM64 || COMPILE_TEST` in this tree): RV1106 is Cortex-A7 / ARM32-only, so an
ARM64 dependency would make the symbol unselectable on this board's `ARCH=arm` build.
`sound/soc/codecs/Makefile` (6.18 uses `snd-soc-<name>-y := <objs>.o` instead of the vendor's
`snd-soc-<name>-objs := <objs>.o` same semantics, current tree's naming convention followed):
`snd-soc-<name>-objs := <objs>.o`, same semantics, current tree's naming convention followed):
- `snd-soc-rk-dsm-y := rk_dsm.o` + `obj-$(CONFIG_SND_SOC_RK_DSM) += snd-soc-rk-dsm.o`
- `snd-soc-rv1106-y := rv1106_codec.o` + `obj-$(CONFIG_SND_SOC_RV1106) += snd-soc-rv1106.o`
@@ -60,12 +60,12 @@ CONFIG_SND_SOC_RV1106=y
CONFIG_SND_SOC_RK_DSM=y
CONFIG_SND_SIMPLE_CARD=y
CONFIG_SND_SIMPLE_CARD_UTILS=y
CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y (already y DMA glue for the DAI)
CONFIG_SND_SOC_GENERIC_DMAENGINE_PCM=y (already y: DMA glue for the DAI)
```
Note: `SOUND`/`SND`/`SND_SOC` were `=m` in the pre-port `.config` (the whole ALSA/ASoC
subsystem was module-only). They had to be flipped to `=y` first a tristate symbol can't be
`y` while its dependency chain is capped at `m` otherwise `SND_SOC_RV1106=y` would silently
subsystem was module-only). They had to be flipped to `=y` first: a tristate symbol can't be
`y` while its dependency chain is capped at `m`; otherwise `SND_SOC_RV1106=y` would silently
clamp to `m` under `olddefconfig`, and `System.map` (built-in symbols only) would show nothing
even with `grep -c rv1106_codec` returning 0, which is exactly the silent-drop failure mode the
task asked to guard against. `REGMAP_MMIO` and `MFD_SYSCON` were already `=y` (pulled in by
@@ -80,14 +80,14 @@ actual delta surface was small. Six fixes total, all found by iterating single-o
| # | File | Change |
|---|---|---|
| 1 | `rv1106_codec.c` | Dropped `#include <linux/rockchip/grf.h>` vendor-BSP-only header, not present in mainline. Nothing in the file actually needs symbols from it: `PERI_GRF_PERI_CON1` is defined locally in the driver and the GRF is accessed generically via `syscon_regmap_lookup_by_phandle()` + `regmap_write()`. Documented in a comment at the top of the include block. |
| 2 | `rv1106_codec.c` | Replaced `#include <linux/of_gpio.h>` with `#include <linux/gpio/consumer.h>`. The driver calls `devm_gpiod_get_optional()` / `gpiod_direction_output()` (the `gpiod_*` consumer API), which live in `gpio/consumer.h`, not `of_gpio.h` (legacy integer-GPIO header) this only happened to resolve on 5.10 via a transitive include chain that no longer holds in 6.18. |
| 3 | `rv1106_codec.c` | Added `#include <linux/of_device.h>` `of_match_device()` is declared there, not pulled in transitively by `of_platform.h` any more; without it the call was an implicit-function-declaration error (`-Werror`) plus a pointer-from-int warning on the assignment. |
| 4 | `rv1106_codec.c` | `snd_soc_get_volsw_range()` / `snd_soc_put_volsw_range()` (used in `rv1106_codec_mic_gain_get/put` and `rv1106_codec_hpmix_gain_get/put`) no longer exist as separate mainline entry points they were folded away when `SOC_SINGLE_RANGE`/`SOC_SINGLE_RANGE_TLV` were made simple aliases over `SOC_SINGLE_VALUE`. The unified `snd_soc_get_volsw()`/`snd_soc_put_volsw()` already honour the `xmin` field of `struct soc_mixer_control`, so they're a drop-in replacement; confirmed by checking how the affected controls are declared (`SOC_SINGLE_EXT_TLV`, min=0) behavior is identical. 4 call sites fixed. |
| 1 | `rv1106_codec.c` | Dropped `#include <linux/rockchip/grf.h>`: vendor-BSP-only header, not present in mainline. Nothing in the file actually needs symbols from it: `PERI_GRF_PERI_CON1` is defined locally in the driver and the GRF is accessed generically via `syscon_regmap_lookup_by_phandle()` + `regmap_write()`. Documented in a comment at the top of the include block. |
| 2 | `rv1106_codec.c` | Replaced `#include <linux/of_gpio.h>` with `#include <linux/gpio/consumer.h>`. The driver calls `devm_gpiod_get_optional()` / `gpiod_direction_output()` (the `gpiod_*` consumer API), which live in `gpio/consumer.h`, not `of_gpio.h` (legacy integer-GPIO header): this only happened to resolve on 5.10 via a transitive include chain that no longer holds in 6.18. |
| 3 | `rv1106_codec.c` | Added `#include <linux/of_device.h>`: `of_match_device()` is declared there, not pulled in transitively by `of_platform.h` any more; without it the call was an implicit-function-declaration error (`-Werror`) plus a pointer-from-int warning on the assignment. |
| 4 | `rv1106_codec.c` | `snd_soc_get_volsw_range()` / `snd_soc_put_volsw_range()` (used in `rv1106_codec_mic_gain_get/put` and `rv1106_codec_hpmix_gain_get/put`) no longer exist as separate mainline entry points: they were folded away when `SOC_SINGLE_RANGE`/`SOC_SINGLE_RANGE_TLV` were made simple aliases over `SOC_SINGLE_VALUE`. The unified `snd_soc_get_volsw()`/`snd_soc_put_volsw()` already honour the `xmin` field of `struct soc_mixer_control`, so they're a drop-in replacement; confirmed by checking how the affected controls are declared (`SOC_SINGLE_EXT_TLV`, min=0): behavior is identical. 4 call sites fixed. |
| 5 | `rv1106_codec.c` | `SND_SOC_DAIFMT_CBS_CFS` -> `SND_SOC_DAIFMT_CBC_CFC`, `SND_SOC_DAIFMT_CBM_CFM` -> `SND_SOC_DAIFMT_CBP_CFP` in `rv1106_set_dai_fmt()`'s clock-role switch. Mainline renamed the old master/slave (`CBM`/`CBS`) constants to the consumer/provider naming (`CBC`/`CBP`) some releases back; `SND_SOC_DAIFMT_MASTER_MASK` itself still exists as a compat alias for `CLOCK_PROVIDER_MASK`, so only the two case labels needed renaming. Verified the semantic mapping against the branch bodies (codec-slave -> `IO_MODE_SLAVE`/`MODE_SLAVE` register bits = consumer = `CBC_CFC`; codec-master -> `..._MASTER` bits = provider = `CBP_CFP`). |
| 6 | `rv1106_codec.c` + `rk_dsm.c` | `struct platform_driver.remove` is `void (*remove)(struct platform_device *)` in 6.18 (was `int (*remove)(struct platform_device *)`, transitional `.remove_new` before that). Changed `rv1106_platform_remove()` and `rk_dsm_platform_remove()` from `static int ... { ...; return 0; }` to `static void ...` (dropped the trailing `return 0;`). This was an `-Werror=incompatible-pointer-types` build error, not a warning. |
That's the complete fix list nothing else needed touching. No vendor-only headers other than
That's the complete fix list: nothing else needed touching. No vendor-only headers other than
`rockchip/grf.h` were pulled in; `linux/mfd/syscon.h`, `linux/reset.h`, `linux/clk.h`,
`linux/regulator/consumer.h`, `sound/pcm_params.h`, `sound/soc.h`, `sound/tlv.h`,
`sound/dmaengine_pcm.h` all exist unchanged in mainline and needed no stubbing.
@@ -95,13 +95,13 @@ That's the complete fix list — nothing else needed touching. No vendor-only he
## rk_dsm (digital speaker modulator) decision
**Kept in, not dropped.** It only needed fix #6 above (the same `platform_driver.remove` void
signature change as the acodec) a one-line-class fix, well under the "drop if it fights the
signature change as the acodec), a one-line-class fix, well under the "drop if it fights the
API a lot" threshold in the task brief. `SND_SOC_RK_DSM=y` is set and `rk_dsm.c` links cleanly
into `zImage` (17 `rk_dsm`-prefixed symbols in `System.map`, including
`rk_dsm_platform_remove`, `rk_dsm_hw_params`, `rk_dsm_set_dai_fmt`).
However, the DSM node (`dsm: codec-digital@ff340000`) and its companion `dsm_sound` machine
card are **not wired up** in `rv1106-warden.dts` `&dsm { status = "disabled"; };` is set
card are **not wired up** in `rv1106-warden.dts`: `&dsm { status = "disabled"; };` is set
explicitly, matching the vendor `rv1106-luckfox-pico-86panel-ipc.dtsi`, which itself ships `dsm`
disabled and `dsm_sound` as `status = "disabled"`. The 86-Panel's speaker/headphone path is the
acodec, not the DSM Class-D path; DSM is present on other RV1106 boards (bare speaker modules)
@@ -111,7 +111,7 @@ turn it on) but is not part of this board's active audio graph.
## Device tree changes (`arch/arm/boot/dts/rockchip/rv1106-warden.dts`)
`i2s0_8ch` was already `status = "okay"` pre-port (from an earlier "sweep" commit) and already
carries `#sound-dai-cells = <0>` in the base `rv1106.dtsi` node no change needed there.
carries `#sound-dai-cells = <0>` in the base `rv1106.dtsi` node: no change needed there.
The base `rv1106.dtsi` `acodec: acodec@ff480000` node (mirrored from the vendor's own
`arch/arm/boot/dts/rv1106.dtsi`, which has the same gap) does **not** carry
@@ -149,7 +149,7 @@ Verified against the decompiled built `.dtb` (`dtc -I dtb -O dts`):
- `acodec@ff480000`: `status = "okay"`, `#sound-dai-cells = <0x00>`, `phandle = <0x5a>`
- `i2s@ffae0000` (i2s0_8ch): `status = "okay"`, `#sound-dai-cells = <0x00>`, `phandle = <0x59>`
- `codec-digital@ff340000` (dsm): `status = "disabled"`
- `acodec-sound` node present with `sound-dai = <0x59>` (cpu) / `sound-dai = <0x5a>` (codec)
- `acodec-sound` node present with `sound-dai = <0x59>` (cpu) / `sound-dai = <0x5a>` (codec):
phandles correctly resolve to the i2s0_8ch/acodec nodes above.
## Build status
@@ -168,10 +168,10 @@ both the touched-file single-object builds and the final combined `zImage`+dtb b
nothing). `arch/arm/boot/zImage` and `arch/arm/boot/dts/rockchip/rv1106-warden.dtb` both
produced. XZ compression and the existing console/earlycon config were left untouched.
## Verification (build-only see hardware watch-list below for what's deferred)
## Verification (build-only, see hardware watch-list below for what's deferred)
Per the task instructions, `System.map` (produced by the **host** toolchain's `nm`, ground
truth for built-in linkage not the cross-`nm`, which mis-lists symbols) was checked directly
truth for built-in linkage, not the cross-`nm`, which mis-lists symbols) was checked directly
with `grep`:
```
@@ -184,23 +184,23 @@ $ grep -c soc_codec_dev_rv1106 System.map
```
54 `rv1106_codec`-prefixed symbols and 17 `rk_dsm`-prefixed symbols are linked into the kernel
image both drivers are genuinely built in (`=y`), not silently dropped to module or excluded.
image: both drivers are genuinely built in (`=y`), not silently dropped to module or excluded.
`soc_codec_dev_rv1106` (the `snd_soc_component_driver` struct) is present, confirming the
component registration path is compiled in.
## Hardware-verify watch-list (deferred to the parent session / bench NOT done here)
## Hardware-verify watch-list (deferred to the parent session / bench, NOT done here)
This session did not flash or touch any hardware, per the task boundary. When the parent
session verifies on a real 86-Panel:
- `aplay -l` should list a card named **`rv1106-acodec`** (from
`simple-audio-card,name = "rv1106-acodec"` in the DT) this is the ALSA card name to look
`simple-audio-card,name = "rv1106-acodec"` in the DT): this is the ALSA card name to look
for, not a device path.
- Expect one playback + one capture PCM stream under that card (`rv1106-hifi` DAI: playback 1-2ch,
capture 1-4ch, 8kHz-192kHz, S16_LE/S20_3LE/S24_LE/S32_LE).
- `amixer -c <card> controls` should surface the vendor mixer controls carried over unchanged
from the BSP driver (ADC MIC Left/Right Gain, ADC ALC Left/Right Volume, HPF cutoff, DAC/lineout
gain, mic bias, etc.) these are pure regmap register pokes, unaffected by this port's API-shim
gain, mic bias, etc.): these are pure regmap register pokes, unaffected by this port's API-shim
fixes, so if the card enumerates at all they should already work correctly.
- Watch dmesg for probe-order issues: `acodec` depends on `PCLK_ACODEC`, `MCLK_ACODEC_TX`,
`MCLK_I2S0_8CH_TX` clocks and `SRST_P_ACODEC` reset all resolving before probe; a probe
@@ -211,20 +211,20 @@ session verifies on a real 86-Panel:
"ASoC: ... error getting cpu/codec dai info" at parse time) before suspecting the codec driver
itself, since the DT wiring is the newest/least-proven part of this port.
- The actual audible test (does sound come out of the speaker/headphone jack) is explicitly
**deferred to the bench** this session only proves the driver builds, links, and the DT
**deferred to the bench**: this session only proves the driver builds, links, and the DT
graph resolves; it does not and cannot prove the analog output path works on real silicon.
---
## Parent-session verify + fix (2026-08-25)
**Gap found:** the subagent set `SND_SOC_RV1106=y` + `SND_SIMPLE_CARD=y` but left
the **cpu DAI** `CONFIG_SND_SOC_ROCKCHIP_I2S_TDM=m` (module). On the bare-kernel
_b boot no modules load, so i2s0_8ch never registered a DAI the simple-audio-card
_b boot no modules load, so i2s0_8ch never registered a DAI -> the simple-audio-card
stuck in `deferred probe pending: asoc-simple-card: parse error`, no card.
**Fix:** `CONFIG_SND_SOC_ROCKCHIP=y` + `CONFIG_SND_SOC_ROCKCHIP_I2S_TDM=y` (the
i2s0_8ch node already carries the `rockchip,rv1126-i2s-tdm` fallback compatible +
`#sound-dai-cells=<0>`, so the mainline driver binds it).
**VERIFIED on c8a3:** `/proc/asound/cards` `0 [rv1106acodec]: simple-card -
rv1106-acodec`; `aplay -l` `card 0: rv1106acodec, device 0:
**VERIFIED on c8a3:** `/proc/asound/cards` -> `0 [rv1106acodec]: simple-card -
rv1106-acodec`; `aplay -l` -> `card 0: rv1106acodec, device 0:
ffae0000.i2s-rv1106-hifi`; `/dev/snd/` has `controlC0 pcmC0D0p pcmC0D0c` (playback
+ capture). Audible speaker test deferred to the bench (with the display).
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# build-m2.sh reproducible M2 (earlycon) build of the RV1106 6.18 forward-port.
# build-m2.sh: reproducible M2 (earlycon) build of the RV1106 -> 6.18 forward-port.
#
# Produces: a zImage with our ported SoC drivers, and rv1106-warden-m2.dtb.
# Boot is a separate on-hardware step (see PORT-STATUS.md "M2 boot").
@@ -1,4 +1,4 @@
# M2 earlycon config fragment for the RV1106 Linux 6.18 forward-port.
# M2 earlycon config fragment for the RV1106 -> Linux 6.18 forward-port.
#
# Base: multi_v7_defconfig (PORT-STATUS confirms it configures and CLK_RV1106
# wires in cleanly). Apply this fragment on top, then `make olddefconfig`:
@@ -8,7 +8,7 @@
# make ARCH=arm CROSS_COMPILE=... olddefconfig zImage
#
# This builds an 11.8 MB zImage with our ported SoC drivers compiled in
# (clk-rv1106.o, pinctrl-rockchip.o with the rv1106 data) verified 2026-08-24.
# (clk-rv1106.o, pinctrl-rockchip.o with the rv1106 data), verified 2026-08-24.
# --- our ported RV1106 SoC drivers ---
CONFIG_ARCH_ROCKCHIP=y
+10 -10
View File
@@ -1,13 +1,13 @@
# M4 display (VOP) port to 6.18
# M4: display (VOP) port to 6.18
The RV1106 VOP is the older RV-series "lite" VOP (`rockchip,rv1106-vop`, VOP_VERSION
2.0xc), driven by mainline's `rockchip_drm_vop.c` (VOP1), **not** VOP2. Its sibling
**rv1126** is already in mainline 6.18, so the register data is a small reuse like
**rv1126** is already in mainline 6.18, so the register data is a small reuse, like
the clk/pinctrl ports.
## The driver delta (`rockchip_vop_reg.c`)
`rv1106_vop_data.c.frag` add this `vop_data` (reuses rv1126's common/modeset/
`rv1106_vop_data.c.frag`: add this `vop_data` (reuses rv1126's common/modeset/
output/misc/intr/win sub-structs; only the version, the smaller 1280² raster, and
the **`VOP_FEATURE_INTERNAL_RGB` feature** differ), plus the match entry:
@@ -15,18 +15,18 @@ the **`VOP_FEATURE_INTERNAL_RGB` feature** differ), plus the match entry:
{ .compatible = "rockchip,rv1106-vop", .data = &rv1106_vop },
```
`VOP_FEATURE_INTERNAL_RGB` is essential it is what makes `vop_bind` call
`VOP_FEATURE_INTERNAL_RGB` is essential: it is what makes `vop_bind` call
`rockchip_rgb_init()` for the parallel-RGB output (rv1126 routes through MIPI and
omits it).
## The DT (in `../dts/rv1106-warden.dts`)
- `&vop` enabled + **named resets** `resets = <&cru SRST_H_VOP>, <&cru SRST_D_VOP>;
reset-names = "ahb", "dclk";` the mainline driver requires these (the vendor
reset-names = "ahb", "dclk";`, the mainline driver requires these (the vendor
node omits them; without them: `vop_bind: failed to get ahb reset`).
- `&vop_out_rgb` repointed straight to the panel (mainline has no separate rgb
node `rockchip_rgb_init` is a helper the VOP calls).
- `panel` (`panel-dpi`, 720×720, 30 MHz) + `pwm-backlight` on `&pwm1`.
node: `rockchip_rgb_init` is a helper the VOP calls).
- `panel` (`panel-dpi`, 720x720, 30 MHz) + `pwm-backlight` on `&pwm1`.
## Verified on warden-c8a3 (2026-08-24)
@@ -36,14 +36,14 @@ rockchip-drm display-subsystem: bound ff990000.vop
[drm] Initialized rockchip 1.0.0 for display-subsystem on minor 0
```
`/dev/dri/card0` is present and the **PWM backlight is up** (`/sys/class/backlight`,
brightness settable). **The VOP driver port is validated** the register data,
brightness settable). **The VOP driver port is validated**: the register data,
version, feature, and resets are right.
## RESOLVED full UI renders on the panel (2026-08-25)
## RESOLVED: full UI renders on the panel (2026-08-25)
The connector *and* the deeper black-screen chain that followed it are fixed; the
86-Panel now draws the full WardenOS Dashboard on 6.18 (`_b`), verified by webcam.
The two final root causes were VOP driver bugs `rgb_dclk_pol` hardcoded inverted,
The two final root causes were VOP driver bugs: `rgb_dclk_pol` hardcoded inverted,
and the wrong primary scanout window (rv1106 scans out via **WIN1**, not WIN2).
See **`VERIFIED.md`** for the complete bring-up chain, the `_a`-vs-`_b` register
diff that pinned it down, and every file changed.
+19 -19
View File
@@ -1,8 +1,8 @@
# Display (VOP + RGB panel) VERIFIED on warden-c8a3 (2026-08-25)
# Display (VOP + RGB panel): VERIFIED on warden-c8a3 (2026-08-25)
The 86-Panel renders the **full WardenOS Dashboard UI** on our self-built Linux
6.18.46 (`_b` slot), pixel-identical to the stock 5.10 `_a` slot. Verified by
webcam pointed at the physical panel (pixels can't be checked over serial/ssh)
webcam pointed at the physical panel (pixels can't be checked over serial/ssh);
`warden-ui -b DRM` drawing the Dashboard: nav sidebar, "Wellhead 03" site card,
Mesh/WAN/LAN/RS485 status rows, correct colors, no banding, no colour swap.
@@ -13,10 +13,10 @@ black-screen chain that followed it).
1. **Connector never created** (`Cannot find any crtc`): `CONFIG_ROCKCHIP_RGB`
was not set, AND the vendor `&rgb` bridge node's dangling endpoint
(`rgb_in_vop -> vop_out_rgb`) read to fw_devlink as a voppanel dependency
(`rgb_in_vop -> vop_out_rgb`) read to fw_devlink as a vop<->panel dependency
cycle, so the VOP probed before the panel. Fix: enable `ROCKCHIP_RGB` +
`/delete-node/ &rgb;` so `vop_out_rgb <-> panel_in_vop` is the only graph link
(rockchip_rgb_init then defers + retries and finds the panel `LVDS-1`).
(rockchip_rgb_init then defers + retries and finds the panel -> `LVDS-1`).
2. **Backlight dark**: `&pwm1` had `pinctrl-names = "active"`; mainline
pwm-rockchip relies on the driver core auto-applying the **`"default"`** state,
so the PWM pin was never muxed. Fix: rename to `"default"`.
@@ -37,22 +37,22 @@ black-screen chain that followed it).
## The two root causes of the final "backlit-black" (VOP driver bugs)
With connector + backlight + pins + panel-init all correct, the screen was still
physically black (backlight on, `LVDS-1` connected, VOP streaming, fb0 720×720,
`modetest`/splash written) the classic "everything healthy, glass dark" state.
physically black (backlight on, `LVDS-1` connected, VOP streaming, fb0 720x720,
`modetest`/splash written): the classic "everything healthy, glass dark" state.
Found by dumping the VOP register block `0xff990000` on the **working `_a` slot**
and diffing against `_b` (same SoC, same register map):
**Bug 1 DCLK polarity inverted.** `rockchip_drm_vop.c` hardcodes
**Bug 1: DCLK polarity inverted.** `rockchip_drm_vop.c` hardcodes
`rgb_dclk_pol = 1` for the LVDS/RGB output. The panel latches pixel data on the
**non-inverted** edge `_a` reads `PX30_DSP_CTRL0` (0xff990020) = `0x1`
**non-inverted** edge: `_a` reads `PX30_DSP_CTRL0` (0xff990020) = `0x1`
(rgb_dclk_pol bit1 = **0**). The vendor 5.10 derives it as
`(bus_flags & PIXDATA_DRIVE_NEGEDGE) ? 1 : 0`, which is 0 for this panel. With the
inverted clock the panel samples RGB on the wrong edge black. **Fix:** set
inverted clock the panel samples RGB on the wrong edge -> black. **Fix:** set
`rgb_dclk_pol` to 0 in the `DRM_MODE_CONNECTOR_LVDS` case.
**Bug 2 wrong primary scanout window.** The rv1106 VOP scans out through
**Bug 2: wrong primary scanout window.** The rv1106 VOP scans out through
**WIN1**, but our port reused rv1126's win table (`win0`-overlay + **`win2`**-primary).
The vendor `rv1106_vop_win_data` is `{ NULL-win0, rk3366_lit_win1_data-primary }`
The vendor `rv1106_vop_win_data` is `{ NULL-win0, rk3366_lit_win1_data-primary }`,
i.e. WIN1 (`PX30_WIN1_*` at 0x090). Mainline was configuring WIN2, which never
reaches this SoC's RGB interface, so WIN1 stayed all-zero and nothing scanned out.
Register evidence (`_a` working vs `_b` broken, before the fix):
@@ -62,26 +62,26 @@ Register evidence (`_a` working vs `_b` broken, before the fix):
| 0x090 | WIN1_CTRL0 (enable) | `0x00000001` | `0x00000000` |
| 0x098 | WIN1_VIR (stride) | `0x000002D0` (720) | `0x00000000` |
| 0x0a0 | WIN1_MST (fb addr) | `0x0F900000` | `0x00000000` |
| 0x0a4 | WIN1_DSP_INFO (size)| `0x02CF02CF` (720×720) | `0x00EF013F` (stale) |
| 0x0a4 | WIN1_DSP_INFO (size)| `0x02CF02CF` (720x720) | `0x00EF013F` (stale) |
**Fix:** `rv1106_vop_win_data[] = { { .phy = &px30_win1_data, PRIMARY } }`
(`px30_win1_data` already exists in mainline over `PX30_WIN1_*` just wasn't wired
for rv1106). After the fix `_b` reads WIN1_CTRL0=1, WIN1_DSP_INFO=0x02CF02CF
(`px30_win1_data` already exists in mainline over `PX30_WIN1_*`, just wasn't wired
for rv1106). After the fix `_b` reads WIN1_CTRL0=1, WIN1_DSP_INFO=0x02CF02CF:
matching `_a`.
## Files changed (in research/linux-6.18.46)
- `drivers/gpu/drm/rockchip/rockchip_drm_vop.c` `rgb_dclk_pol` 1 0 (LVDS case).
- `drivers/gpu/drm/rockchip/rockchip_vop_reg.c` `rv1106_vop_win_data` uses
- `drivers/gpu/drm/rockchip/rockchip_drm_vop.c`: `rgb_dclk_pol` 1 -> 0 (LVDS case).
- `drivers/gpu/drm/rockchip/rockchip_vop_reg.c`: `rv1106_vop_win_data` uses
`px30_win1_data` as the single PRIMARY window (was rv1126's win0+win2).
- `drivers/gpu/drm/panel/panel-simple.c` `panel_dpi_probe()` reads DT `bus-format`.
- `arch/arm/boot/dts/rockchip/rv1106-warden.dts` `&vop` pinctrl `<&lcd_pins>`;
- `drivers/gpu/drm/panel/panel-simple.c`: `panel_dpi_probe()` reads DT `bus-format`.
- `arch/arm/boot/dts/rockchip/rv1106-warden.dts`: `&vop` pinctrl `<&lcd_pins>`;
`&pwm1` pinctrl "default"; `/delete-node/ &rgb`; panel `bus-format` RGB666,
no reset-gpios.
## Known separate issue (NOT display)
`_b` still cold-reboots periodically (pre-existing `_b` general-stability issue,
independent of the display the panel renders correctly the whole time it is up).
independent of the display: the panel renders correctly the whole time it is up).
Tracked separately; the high-bootlimit env (`bootlimit=10000`) keeps these reboots
from tripping `altbootcmd=download` into the loader during bring-up.
+8 -8
View File
@@ -1,10 +1,10 @@
# RV1106 6.18 devicetree
- **`rv1106-warden.dts`** our board DT. `#include`s the vendor `rv1106.dtsi`
(the full SoC: pinctrl, gpio×5, dmac, cru+grf_cru, uart, emmc, i2c, saradc,
watchdog, usb, vop, npu, rga, ), then enables only what the wall-HMI needs.
- **`rv1106-warden.dts`**: our board DT. `#include`s the vendor `rv1106.dtsi`
(the full SoC: pinctrl, gpiox5, dmac, cru+grf_cru, uart, emmc, i2c, saradc,
watchdog, usb, vop, npu, rga, ...), then enables only what the wall-HMI needs.
No camera/ISP/CSI. Grows one driver batch at a time (see `../DRIVER-PARITY.md`).
- **`rv1106-warden-m2.dts`** the earlier standalone minimal DT used to bring up
- **`rv1106-warden-m2.dts`**: the earlier standalone minimal DT used to bring up
M2/M3 before the full-SoC transplant; kept for reference.
## Transplanting the vendor SoC DT onto 6.18 (recipe)
@@ -18,9 +18,9 @@ with the vendor's (it has extra BOOT_CHARGING/UMS/PANIC/WATCHDOG constants the
DT uses). Then the two hardware deltas:
1. **Delete the `psci` node** from `rv1106.dtsi`. RV1106 has no secure monitor in
our boot chain, so `arm,psci-1.0` + `method="smc"` makes the SMC call fault
`Oops - bad mode` `Attempted to kill the idle task`. (Confirmed on c8a3.)
2. Enable the peripherals in the board DTS (`&uart2`, `&emmc`, ).
our boot chain, so `arm,psci-1.0` + `method="smc"` makes the SMC call fault ->
`Oops - bad mode` -> `Attempted to kill the idle task`. (Confirmed on c8a3.)
2. Enable the peripherals in the board DTS (`&uart2`, `&emmc`, ...).
Verified on warden-c8a3: pinctrl, gpio04, pl330 DMA, uart1/2/4, i2c3, dw-wdt all
Verified on warden-c8a3: pinctrl, gpio0-4, pl330 DMA, uart1/2/4, i2c3, dw-wdt all
probe; full WardenOS userspace boots.
@@ -1,21 +1,21 @@
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Minimal RV1106 devicetree for the WardenOS 86-Panel M2 "earlycon" bring-up
* Minimal RV1106 devicetree for the WardenOS 86-Panel, M2 "earlycon" bring-up
* of the self-built Linux 6.18 forward-port (warden-sdk kernel/docs/bringup.md).
*
* Deliberately NOT the full vendor rv1106.dtsi: just the core needed to reach
* "it's alive" on the debug UART CPU, GIC, arch timer, RAM, the CRU (our
* "it's alive" on the debug UART: CPU, GIC, arch timer, RAM, the CRU (our
* ported clk-rv1106), GRF, and uart2 (the console, driven by mainline 8250_dw).
* earlycon writes uart2's MMIO directly at the bootloader's divisor, so it prints
* before the clock/pinctrl drivers probe; the real ttyS console then needs the
* CRU to hand out SCLK_UART2, which is exactly what this validates on hardware.
*
* PORT-VERIFY (must be checked against the TRM / on hardware before trusting a
* boot a wrong value here silently hangs the board):
* boot; a wrong value here silently hangs the board):
* - memory size/base (256 MiB @ 0x0 assumed for the 86-Panel; the loader
* usually patches this confirm against the real DDR init);
* usually patches this; confirm against the real DDR init);
* - uart2 is the console at 0xff4c0000 (matches the vendor ttyFIQ0 base);
* - CRU needs no assigned-clocks here (PLLs keep their loader rates) fine for
* - CRU needs no assigned-clocks here (PLLs keep their loader rates), fine for
* earlycon/console, revisit for the full clock tree.
*/
/dts-v1/;
@@ -25,7 +25,7 @@
#include <dt-bindings/clock/rv1106-cru.h>
/ {
model = "WardenOS 86-Panel (RV1106) M2 earlycon bring-up";
model = "WardenOS 86-Panel (RV1106), M2 earlycon bring-up";
compatible = "rockchip,rv1106";
#address-cells = <1>;
#size-cells = <1>;
@@ -47,7 +47,7 @@
memory@0 {
device_type = "memory";
reg = <0x00000000 0x10000000>; /* 256 MiB PORT-VERIFY */
reg = <0x00000000 0x10000000>; /* 256 MiB: PORT-VERIFY */
};
xin24m: oscillator {
@@ -3,7 +3,7 @@
* WardenOS 86-Panel board DT for the self-built Linux 6.18 forward-port.
*
* Includes the full vendor rv1106.dtsi (SoC nodes + pinctrl + gpio + dmac), then
* enables only what our wall-HMI needs no camera/ISP/CSI. Grows one driver
* enables only what our wall-HMI needs: no camera/ISP/CSI. Grows one driver
* batch at a time toward full parity with the 5.10 kernel (DRIVER-PARITY.md).
*/
/dts-v1/;
@@ -31,7 +31,7 @@
reset-gpios = <&gpio1 RK_PA2 GPIO_ACTIVE_LOW>;
};
/* SARADC voltage reference (1.8V) the driver reads this for scale;
/* SARADC voltage reference (1.8V): the driver reads this for scale;
without it saradc probes -22 (regulator_get_voltage on the dummy). */
vcc_1v8: vcc-1v8 {
compatible = "regulator-fixed";
@@ -57,7 +57,7 @@
status = "okay";
};
/* --- M5: AIC8800DC wifi/BT SDIO (on &sdmmc, mmc@ffaa0000 NOT &sdio) --- */
/* --- M5: AIC8800DC wifi/BT SDIO (on &sdmmc, mmc@ffaa0000; NOT &sdio) --- */
&sdmmc {
max-frequency = <50000000>;
bus-width = <4>;
@@ -95,7 +95,7 @@
status = "okay";
};
/* --- M4: display (VOP + RGB 720x720 panel + PWM backlight) --- */
/* --- M4: display (VOP + RGB -> 720x720 panel + PWM backlight) --- */
&display_subsystem {
status = "okay";
};
+6 -6
View File
@@ -1,4 +1,4 @@
# GMAC (wired 10/100 ethernet) VERIFIED on warden-c8a3 (2026-08-25)
# GMAC (wired 10/100 ethernet): VERIFIED on warden-c8a3 (2026-08-25)
**Result: `eth0: Link is Up - 100Mbps/Full - flow control rx/tx`** on our
self-built 6.18.46. The 86-Panel's RMII MAC + on-die 10/100 FEPHY works; a real
@@ -31,17 +31,17 @@ static const struct rk_gmac_ops rv1106_ops = {
## Adaptations vs the vendor 5.10 ops
- Mainline `rk_gmac_ops` uses `.set_speed(bsp_priv, interface, speed)` returning
int (vendor: `.set_rmii_speed(bsp_priv, speed)` void) adapted.
int (vendor: `.set_rmii_speed(bsp_priv, speed)` void), adapted.
- Split `.integrated_phy_power(up)` into mainline's `.integrated_phy_powerup` /
`.integrated_phy_powerdown`, each calling mainline's single-reg
`rk_gmac_integrated_fephy_power{up,down}(priv, CON0)`.
- **Bandgap trim OMITTED**: the vendor also wrote an OTP-derived bandgap value to
MACPHY_CON1; that's an analog optimisation the FEPHY runs without, and mainline's
fephy helper doesn't carry it. Link came up 100M/Full without it. (If signal
integrity ever needs it, the `bgs` OTP cell now reads see rng-otp/.)
integrity ever needs it, the `bgs` OTP cell now reads: see rng-otp/.)
## DT / config
- `&gmac { status = "okay"; }` the dtsi `ethernet@ffa80000` node already has
- `&gmac { status = "okay"; }`: the dtsi `ethernet@ffa80000` node already has
clocks/resets/`phy-mode="rmii"`/`phy-handle=&rmii_phy` + the integrated
`ethernet-phy@2` (`phy-is-integrated`). `rockchip,grf=<&grf>` (the big syscon
covers the 0x60xxx VOGRF offsets). The `bgs`/`txlevel` nvmem-cells are present
@@ -49,8 +49,8 @@ static const struct rk_gmac_ops rv1106_ops = {
- Kconfig: `STMMAC_ETH`, `STMMAC_PLATFORM`, `DWMAC_ROCKCHIP`, `ROCKCHIP_PHY` (=y).
## Evidence
`rk_gmac-dwmac ffa80000.ethernet eth0: configuring for phy/rmii link mode`
`rk_gmac-dwmac ffa80000.ethernet eth0: configuring for phy/rmii link mode` ->
`eth0: Link is Up - 100Mbps/Full - flow control rx/tx`. PHY bound at
`stmmac-0:02` (integrated). The internal FEPHY reports id 0044.1400; it bound to
the Generic PHY (mainline `net/phy/rockchip.c` INTERNAL_EPHY_ID is 0x1234d400)
the Generic PHY (mainline `net/phy/rockchip.c` INTERNAL_EPHY_ID is 0x1234d400):
`phy-is-integrated` + c22 was sufficient for a full-duplex 100M link.
+56 -56
View File
@@ -1,4 +1,4 @@
# HPMCU mailbox to 100% plan, feasibility, hardware round-trip verify
# HPMCU mailbox to 100%: plan, feasibility, hardware round-trip verify
**Goal:** a working, fully-open A7 ↔ HPMCU (SCR1) mailbox link on our self-built
Linux 6.18.46, **verified by a message round-trip over serial on hardware.**
@@ -11,21 +11,21 @@ the SCR1 core**. The only real work is: (1) enable the controller in DT + config
(2) write a ~dozen-line SCR1 echo handler, (3) write a small Linux client, and
(4) run the round-trip on `warden-c8a3` (the recovery-capable rig) over serial.
The one substantive design decision **hardware mailbox IP vs. our current
`/dev/mem` polled-SRAM scheme** is settled below: build the real mailbox link
The one substantive design decision (**hardware mailbox IP vs. our current
`/dev/mem` polled-SRAM scheme**) is settled below: build the real mailbox link
(it is the open, IRQ-driven, general-purpose answer), but keep the proven SRAM
watchdog exactly as-is (different threat model, different job).
---
## 1. Feasibility honest assessment
## 1. Feasibility: honest assessment
**Fully feasible. Two halves, both open, both with working reference code to copy.**
- **Controller (Linux side): non-issue.** `drivers/mailbox/rockchip-mailbox.c` is
upstream in mainline 6.18 and **already binds on our exact kernel** via the
generic `rockchip,rk3368-mailbox` **fallback compatible** with **zero patching**
recorded in `CAPABILITIES-AUDIT.md`'s Remaining-blocks table (mailbox row), confirmed by source read. RV1106's DT
generic `rockchip,rk3368-mailbox` **fallback compatible** with **zero patching**:
recorded in `CAPABILITIES-AUDIT.md`'s Remaining-blocks table (mailbox row), confirmed by source read. RV1106's DT
declares both instances with that fallback string. Gated today only by
`status="disabled"` + `CONFIG_ROCKCHIP_MBOX` being absent from the defconfig.
- **HPMCU firmware (MCU side): we already do the hard part.** WardenOS has a
@@ -33,30 +33,30 @@ watchdog exactly as-is (different threat model, different job).
the vendor's own `sysdrv/source/mcu/` toolchain (xPack `riscv-none-embed-gcc`
10.2.0, `-march=rv32imc`), loaded via `/dev/mem`-mmap by `flared/src/hpmcu.rs`,
running a real state machine and firing a real CRU reset. Adding a mailbox echo
handler to that firmware is small, additive work and the vendor ships a
handler to that firmware is small, additive work, and the vendor ships a
register-level template for the MCU side (`hal_mbox.c`) plus a working two-ended
example (`battery-ipc/stream.c` + `rockchip_thunderboot_service.c`).
- **The only genuinely new/greenfield piece** is that no general Linux-side mailbox
_client_ for RV1106 exists in-tree (the one real client,
`rockchip_thunderboot_service.c`, is a hardwired one-shot `{0xf00d,0xdeadbeef}`
"MCU done" signal, and isn't in our board's DT). But a mailbox client using the
stock upstream `mailbox_client.h` API is a small, well-understood piece of code
stock upstream `mailbox_client.h` API is a small, well-understood piece of code,
not a driver port. `mailbox-test.c` (stock kernel debugfs exerciser, present in
our tree, `compatible="mailbox-test"`) lets us prove the Linuxcontroller path
our tree, `compatible="mailbox-test"`) lets us prove the Linux->controller path
**before writing any client at all.**
**Confidence in a verifiable open link: high.** The only area flagged risky is
_starting the SCR1 with our own entry point_ and we have already solved and
_starting the SCR1 with our own entry point_, and we have already solved and
hardware-proven exactly that (the `hpmcu.rs` load/release sequence), so it is a
known quantity here, not the open problem it would be for a team starting cold.
**Decision hardware mailbox vs. `/dev/mem` SRAM:** build the **hardware mailbox**
**Decision, hardware mailbox vs. `/dev/mem` SRAM:** build the **hardware mailbox**
as the open, general, IRQ-driven bidirectional channel (this is "the real mailbox
client" the task wants). **Keep the existing SRAM polled-word watchdog untouched**
client" the task wants). **Keep the existing SRAM polled-word watchdog untouched**:
ADR-0002 deliberately chose it as a dead-man's-switch (no IRQ, no dependency on the
mailbox controller being up, survives A7 hangs by design). They coexist: the SRAM
word is the safety watchdog; the mailbox is the general IPC channel. Do **not** rip
out `hpmcu.rs`'s watchdog to route it through the mailbox that would trade a
out `hpmcu.rs`'s watchdog to route it through the mailbox: that would trade a
proven fail-safe for a more complex path with no safety gain.
**Do NOT (for a first cut):** port `rockchip_rpmsg.c` or NXP `rpmsg-lite`. Both are
@@ -65,7 +65,7 @@ files only for RK3308/RK3568; `rockchip_rpmsg.c` matches only `rk3562/rk3568-rpm
neither is upstream in mainline; `CONFIG_RPMSG_ROCKCHIP` is `# not set` on every
RV1106 defconfig). They add virtio/vring machinery we don't need to prove a link.
If a richer multi-message channel is later wanted, layer a small hand-rolled ring
buffer in `hpmcu_sram` on top of the doorbell that's the natural next step, not
buffer in `hpmcu_sram` on top of the doorbell: that's the natural next step, not
a full rpmsg port.
---
@@ -76,13 +76,13 @@ a full rpmsg port.
| Node | Base | IRQ | Clock | Consumer | Use |
|---|---|---|---|---|---|
| `mailbox@ff5c0000` | `0xff5c0000` (`reg` size `0x200`) | `GIC_SPI 1` | `PCLK_MAILBOX` | `thunder-boot-service` (ch1, `"amp-rx"`) not on our board | **HPMCU-connected use this one** |
| `pmu_mailbox@ff378000` | `0xff378000` (`0x200`) | `GIC_SPI 114` | `PCLK_PMU_MAILBOX` | none anywhere | PMU-domain, undocumented purpose **avoid** |
| `mailbox@ff5c0000` | `0xff5c0000` (`reg` size `0x200`) | `GIC_SPI 1` | `PCLK_MAILBOX` | `thunder-boot-service` (ch1, `"amp-rx"`), not on our board | **HPMCU-connected: use this one** |
| `pmu_mailbox@ff378000` | `0xff378000` (`0x200`) | `GIC_SPI 114` | `PCLK_PMU_MAILBOX` | none anywhere | PMU-domain, undocumented purpose: **avoid** |
**`@ff5c0000` is the HPMCU-connected instance, proven from both ends:** the Linux
`thunder_boot_service` node's `resets` are literally named `SRST_CORE_MCU*`; the
MCU-side vendor CMSIS header hard-codes `#define MBOX_BASE 0xFF5C0000U`
(`.../hal/lib/CMSIS/Device/RV1106/Include/rv1106.h:815`) the same physical
(`.../hal/lib/CMSIS/Device/RV1106/Include/rv1106.h:815`): the same physical
address the A7 sees. Both clock IDs are real CRU gates (`PCLK_MAILBOX` at
`clk-rv1106.c:343`, `PCLK_PMU_MAILBOX` at `:737`), so the fallback-compatible match
is trustworthy: the register block is IP-identical to rk3368's.
@@ -101,113 +101,113 @@ MAILBOX_B2A_DAT(x) 0x34 + x*8
```
**4 channels, 32-bit `{cmd,data}` per message per direction. Doorbell + 8-byte
payload NOT a bulk channel.** Sender writes CMD then DAT (two `writel_relaxed`,
fire-and-forget, no ack/poll `rockchip-mailbox.c:46-70`); the peer's STATUS bit
payload, NOT a bulk channel.** Sender writes CMD then DAT (two `writel_relaxed`,
fire-and-forget, no ack/poll: `rockchip-mailbox.c:46-70`); the peer's STATUS bit
raises an IRQ; receiver reads CMD/DAT, dispatches to the registered client via
`mbox_chan_received_data`, clears the STATUS bit to ack. `#mbox-cells = <1>`
(channel index). Exported `rockchip_mbox_read_msg()` (`EXPORT_SYMBOL_GPL`,
`rockchip-mailbox.c:108-125`) pulls the last `{cmd,data}` pair out for a client.
Only OF match in the driver: `"rockchip,rk3368-mailbox"` `.num_chans = 4`.
Only OF match in the driver: `"rockchip,rk3368-mailbox"` -> `.num_chans = 4`.
### HPMCU / SCR1 facts (from our own hardware-validated work)
- SCR1 = Syntacore RV32IMC, machine-mode only, 16KB unified cache, "HPMCU".
- **On our board the SCR1 is idle from reset** (non-TB `RV1106MINIALL.ini`, no
`Hpmcu=` loader stage) a clean slate, not even the stock camera-AE blob.
`Hpmcu=` loader stage), a clean slate, not even the stock camera-AE blob.
- **Usable MCU code SRAM is a hard 8KB**: `hpmcu_sram` at offset `0x3e000` inside
`system_sram@ff6c0000` absolute **`0xFF6FE000`, 8KB** (`reg=<0x3e000 0x2000>`,
`system_sram@ff6c0000` -> absolute **`0xFF6FE000`, 8KB** (`reg=<0x3e000 0x2000>`,
`rv1106.dtsi:1149-1151`), shared region with 248KB `rkisp_sram`. Our firmware +
mailbox echo handler must fit this.
- **Proven load/release sequence** (`flared/src/hpmcu.rs:26-76`, hardware-validated):
`CORECRU_CORESOFTRST_CON01` (`0xff3b8000+0xa04`) hold `0x1e001e` write firmware
to `0xff6fe000` `SGRF_HPMCU_BOOT_ADDR` (`0xff076000+0x44`) = load addr
`CORECRU_CORESOFTRST_CON01` (`0xff3b8000+0xa04`) hold `0x1e001e` -> write firmware
to `0xff6fe000` -> `SGRF_HPMCU_BOOT_ADDR` (`0xff076000+0x44`) = load addr ->
release CORECRU to `0x1e0000`. This is the WiFi-independent way we start the core
with our own entry point the piece source-level analysis flagged as unknown is
with our own entry point: the piece source-level analysis flagged as unknown is
already solved and proven here.
- **MCU-side reference to copy:** `hal_mbox.c`/`hal_mbox.h` (generic 4-channel
register driver, `HAL_MBOX_Init`/send/ack) + working example
`battery-ipc/stream.c:380-425` (sends `struct MBOX_CMD_DAT {cmd,data}` over
`MBOX_CH_1`, client name `"mcu-status"` exactly matches the Linux
`MBOX_CH_1`, client name `"mcu-status"`: exactly matches the Linux
`struct rockchip_mbox_msg`).
### Current comms mechanism (what exists today, keep it)
WardenOS's watchdog uses a **software mailbox in shared SRAM**, not the mailbox IP:
last 256 bytes of `hpmcu_sram` at absolute base **`0xFF6FFF00`** `+0x00 magic`
last 256 bytes of `hpmcu_sram` at absolute base **`0xFF6FFF00`**, `+0x00 magic`
(Linux: `WARD`/`DISA`), `+0x04 counter` (heartbeat), `+0x08 mcu_state`
(`BOOT/ARMD/DISA/FIRE`), `+0x0c`/`+0x10` debug. 5s heartbeat, fires after 90s
no-advance. CRU reset via `GLB_SRST_FST` at **`0xff3b0c08`** magic `0xfdb9` (the
only working whole-SoC reset `reboot -f` is a no-op: no PSCI/restart handler).
only working whole-SoC reset, `reboot -f` is a no-op: no PSCI/restart handler).
Reset constant is CI-guarded across 4 sites. **`dd of=/dev/mem` faults for the SRAM
region on this ARM kernel only the `mmap()` path writes** (busybox `devmem` or
region on this ARM kernel: only the `mmap()` path writes** (busybox `devmem` or
`libc::mmap`); `STRICT_DEVMEM` is off in our config.
### The `0x40000` boot-load hazard (load-bearing — for any _boot-time_ MCU path only)
### The `0x40000` boot-load hazard (boot-time MCU paths only)
Boot-loading firmware to the DDR carve-out `0x40000` (240KB) **bricked warden-c8a3
on 2026-08-23** because our non-TB kernel DT does **not** reserve `0x40000` it
collides with kernel RAM early-boot hang before eth0. **This mailbox plan avoids
on 2026-08-23** because our non-TB kernel DT does **not** reserve `0x40000`: it
collides with kernel RAM -> early-boot hang before eth0. **This mailbox plan avoids
the hazard entirely** by using the runtime SRAM-load path (`0xff6fe000`), not a
boot-time DDR load. If a boot-time path is ever pursued, a `reserved-memory` DT node
for `0x40000/0x3c000` must be added and verified via `/proc/iomem` **before**
flashing the idblock. (Standing memory: `boot-loaded-mcu-0x40000-hazard.md`
flashing the idblock. (Standing memory: `boot-loaded-mcu-0x40000-hazard.md`,
worth promoting into `riscv-mcu.md` open-questions; not yet captured there.)
### URLs / upstream status
- `drivers/mailbox/rockchip-mailbox.c` upstream mainline v6.18 (Bootlin Elixir
- `drivers/mailbox/rockchip-mailbox.c`: upstream mainline v6.18 (Bootlin Elixir
confirms), binds via `rk3368-mailbox` fallback, zero patch.
- No `drivers/rpmsg/rockchip*` and no `drivers/remoteproc/rockchip*` in mainline
(GitHub API enumeration of both dirs none Rockchip). Vendor `rockchip_rpmsg.c`
(GitHub API enumeration of both dirs, none Rockchip). Vendor `rockchip_rpmsg.c`
never merged; `lore.kernel.org` 403-walled, submission history unpinnable.
- Community RV1106 HPMCU prior art beyond LED-blink: **~zero** (GitHub search
`rv1106 hpmcu` / `rv1106 scr1 coprocessor` only SDK/rkbin mirrors). ADR-0002:
`rv1106 hpmcu` / `rv1106 scr1 coprocessor` -> only SDK/rkbin mirrors). ADR-0002:
"expect to be first." Our PR #25 firmware is the only known custom SCR1 code.
---
## 3. The simplest fully-open, hardware-verifiable path (milestones)
### M-MBOX-1 Controller wiring proof (Tier 1: today, ZERO HPMCU risk, no firmware)
### M-MBOX-1, Controller wiring proof (Tier 1: today, ZERO HPMCU risk, no firmware)
Proves DT status/clock/IRQ/probe/send all work on real hardware without touching
the SCR1 at all.
1. Kernel config: `CONFIG_ROCKCHIP_MBOX=y` + `CONFIG_MAILBOX_TEST=y` (mainline
driver, **no patch** `rockchip-mailbox.c` binds on the fallback compatible).
2. Board DTS: override `&mailbox { status = "okay"; };` (the `@ff5c0000` instance
driver, **no patch**, `rockchip-mailbox.c` binds on the fallback compatible).
2. Board DTS: override `&mailbox { status = "okay"; };` (the `@ff5c0000` instance,
main CRU clock, `GIC_SPI 1`, no PMU-domain complications). Add a
`compatible = "mailbox-test"` node with `mboxes = <&mailbox N>` on a **free
channel avoid channel 1** (vendor reserves it for `"amp-rx"` semantics).
channel: avoid channel 1** (vendor reserves it for `"amp-rx"` semantics).
3. **Verify on serial console** (via the `warden-c8a3` `_b`-slot one-shot loop):
confirm `rockchip-mailbox` probes clean in `dmesg`; write an 8-byte `{cmd,data}`
pair to the debugfs `message` file (`mailbox-test.c`); then read back the
physical `A2B_CMD(N)`/`A2B_DAT(N)` registers directly with busybox `devmem` at
`0xff5c0008+8N` / `0xff5c000c+8N` and confirm the value landed. **Proves the AP
`0xff5c0008+8N` / `0xff5c000c+8N` and confirm the value landed. **Proves the AP->
controller send path end-to-end, serial-only, no risk to the HPMCU.**
### M-MBOX-2 Real round-trip: SCR1 echo + Linux client (Tier 2: the deliverable)
### M-MBOX-2, Real round-trip: SCR1 echo + Linux client (Tier 2: the deliverable)
The actual "message round-trip over serial on hardware" the task asks for.
1. **SCR1 echo firmware** (additive to our existing bare-metal firmware, must fit
the 8KB `hpmcu_sram` budget): on A2B IRQ (or a tight poll of `A2B_STATUS` poll
the 8KB `hpmcu_sram` budget): on A2B IRQ (or a tight poll of `A2B_STATUS`, poll
is simpler and safe for a bring-up echo, avoids MCU IRQ-controller setup), read
`A2B_CMD(x)`/`A2B_DAT(x)`, write them back to `B2A_CMD(x)`/`B2A_DAT(x)`, set the
`B2A_STATUS` bit, clear `A2B_STATUS`. Copy the register sequence from vendor
`hal_mbox.c` / `battery-ipc/stream.c`. Load and start the core with the
**proven `hpmcu.rs` load/release sequence** (SRAM path, `0xff6fe000` **not**
the `0x40000` boot path, hazard §2).
**proven `hpmcu.rs` load/release sequence** (SRAM path, `0xff6fe000`, **not**
the `0x40000` boot path, hazard section 2).
2. **Linux client**: a minimal in-tree `mbox_client` (stock `mailbox_client.h`:
`mbox_request_channel_byname` / `mbox_send_message` + an rx callback reading via
the exported `rockchip_mbox_read_msg()`), or to avoid writing a kernel client
for the first proof reuse `mailbox-test`'s debugfs send and read the B2A
the exported `rockchip_mbox_read_msg()`), or (to avoid writing a kernel client
for the first proof) reuse `mailbox-test`'s debugfs send and read the B2A
registers from userspace with `devmem`. Prefer the `mailbox-test` route for
M-MBOX-2's first light; promote to a real `mbox_client` once the round-trip is
green.
3. **Verify on serial console** (`warden-c8a3`, `_b`-slot): send a known
`{cmd,data}` (e.g. `{0x1234, 0xcafef00d}`) from Linux; confirm the SCR1 echoed
it back either via the client's rx callback logging the received pair, or by
it back, either via the client's rx callback logging the received pair, or by
`devmem` reading `B2A_CMD(N)`/`B2A_DAT(N)` (`0xff5c0030+8N`/`0xff5c0034+8N`) and
matching it byte-for-byte against what was sent. Capture the serial transcript
as the evidence. **This is the 100%, verified, fully-open mailbox link.**
@@ -217,7 +217,7 @@ the working `_a` slot, auto-reverts on hang). The SRAM-load path sidesteps the
`0x40000` brick hazard. The existing SRAM watchdog keeps running throughout,
independent of the mailbox, as it did during the 2026-08-14 validation.
### M-MBOX-3 (Optional, later) richer channel
### M-MBOX-3: (Optional, later) richer channel
Only if a real multi-message need appears: layer a small ring buffer in
`hpmcu_sram`, mailbox used as the doorbell ("look at address X"). Still **do not**
@@ -227,21 +227,21 @@ adopt rpmsg/virtio unless the payload complexity genuinely demands it.
## 4. Risks / open questions
- **SCR1 IRQ setup** for M-MBOX-2 a polled echo avoids configuring the MCU's
- **SCR1 IRQ setup**: for M-MBOX-2 a polled echo avoids configuring the MCU's
interrupt controller; fine for bring-up. IRQ-driven B2A on the Linux side works
regardless (that's the controller's job). Note as a simplification, not a gap.
- **Channel choice** use a channel ≠ 1 (ch1 = vendor `"amp-rx"` reservation).
- **`pmu_mailbox@ff378000` purpose** undocumented, no consumer anywhere; leave
- **Channel choice**: use a channel ≠ 1 (ch1 = vendor `"amp-rx"` reservation).
- **`pmu_mailbox@ff378000` purpose**: undocumented, no consumer anywhere; leave
disabled, do not use, until a TRM section or Rockchip engineer clarifies.
- **RS-485 UART reachability from the HPMCU** never checked (pinmux vs Linux
- **RS-485 UART reachability from the HPMCU**: never checked (pinmux vs Linux
ownership); irrelevant to the mailbox link but open for future MCU apps.
- **Whether Rockchip-official (non-RE) MCU/mailbox docs exist under NDA/partner
access** never asked Luckfox support directly; worth a query.
- **`mcutool.c` exact `/dev/mem` sequence** inferred, not read line-by-line;
access**: never asked Luckfox support directly; worth a query.
- **`mcutool.c` exact `/dev/mem` sequence**: inferred, not read line-by-line;
moot, since our `hpmcu.rs` is an independent hardware-validated reimplementation.
---
_Cross-refs: `../CAPABILITIES-AUDIT.md`, `../REMAINING-PORTS.md §7`,
_Cross-refs: `../CAPABILITIES-AUDIT.md`, `../REMAINING-PORTS.md section 7`,
`../../luckfox-pico-86-panel/riscv-mcu.md`,
`.../raw/followup-riscv-mcu.md`,
`flare-edge/major-app-additions/docs/decisions/0002-hpmcu-watchdog.md`,
+9 -9
View File
@@ -1,11 +1,11 @@
# HPMCU mailbox [x] 100% VERIFIED on warden-c8a3 (2026-08-25)
# HPMCU mailbox: [x] 100% VERIFIED on warden-c8a3 (2026-08-25)
A fully-open A7 HPMCU (RISC-V SCR1) hardware-mailbox round-trip on our self-built
A fully-open A7 <-> HPMCU (RISC-V SCR1) hardware-mailbox round-trip on our self-built
Linux 6.18.46. Open kernel driver + open SCR1 firmware, **zero blobs**.
## Evidence (serial, _b slot = our 6.18)
SCR1 echo firmware running: `DBG_STATE = 0x584F424D` ("MBOX"). Five round-trips,
Linux mailbox SCR1 mailbox Linux, **all exact**:
Linux -> mailbox -> SCR1 -> mailbox -> Linux, **all exact**:
```
sent 0x0000beef/0x600df00d -> B2A 0x0000BEEF/0x600DF00D e=4 OK
sent 0x0000c0de/0x12345678 -> B2A 0x0000C0DE/0x12345678 e=5 OK
@@ -15,7 +15,7 @@ sent 0x0000aa55/0x55aa55aa -> B2A 0x0000AA55/0x55AA55AA e=8 OK
```
Echo counter increments 1:1 with sends; both CMD and DAT echo back verbatim.
## The three fixes it took (none in the research plan found on hardware)
## The three fixes it took (none in the research plan, found on hardware)
1. **Controller IRQ count** (`rockchip-mailbox.c`): the rv1106 mailbox has ONE
shared IRQ (GIC_SPI 1), but the rk3368 driver-data assumes one IRQ per channel
(num_chans=4) and probe failed `IRQ index 1 not found`. Added an
@@ -27,7 +27,7 @@ Echo counter increments 1:1 with sends; both CMD and DAT echo back verbatim.
released core never executed (DBG_STATE stayed 0). Marked it `CLK_IGNORE_UNUSED`.
(5.10 happened to leave it on.)
3. **A2B doorbell semantics** (SCR1 firmware + send order): the MCU-side receiver
must set `A2B_INTEN` or an A7 write to A2B_CMD never raises A2B_STATUS the echo
must set `A2B_INTEN` or an A7 write to A2B_CMD never raises A2B_STATUS: the echo
firmware now sets `A2B_INTEN` at init. And the A2B_DAT write is the doorbell, so
the sender writes CMD first, then DAT (the mainline order), so both are current
when the SCR1 reads them.
@@ -35,8 +35,8 @@ Echo counter increments 1:1 with sends; both CMD and DAT echo back verbatim.
## Loading the firmware (no reflash, no brick)
The SCR1 echo firmware (`scr1-echo/`, 154 B) is loaded at runtime into hpmcu_sram
(0xFF6FE000) via the proven `flared/hpmcu.rs` sequence (`load-echo-fw.sh`): GRF
uncached peripheral window (0xff040024/28) CORECRU hold firmware to SRAM
SGRF boot addr CORECRU release. **Do NOT kill warden-flared** (it one-shot-loads
uncached peripheral window (0xff040024/28) -> CORECRU hold -> firmware to SRAM ->
SGRF boot addr -> CORECRU release. **Do NOT kill warden-flared** (it one-shot-loads
at boot then just beats + pets the dw-wdt; killing it resets the board). We reset
+ reload the SCR1; flared does not re-load. The SRAM-load path avoids the 0x40000
boot-load brick hazard entirely.
@@ -47,7 +47,7 @@ HPMCU-connected instance). The existing /dev/mem SRAM watchdog is untouched (a
separate dead-man's-switch); the mailbox is the general open IPC channel.
## Files
- `scr1-echo/main.c` + `start.S` + `link.lds` + `Makefile` the open SCR1 echo fw.
- `scr1-echo/load-echo-fw.sh`, `mbox-roundtrip.sh`, `echo-fw-words.txt` load + test.
- `scr1-echo/main.c` + `start.S` + `link.lds` + `Makefile`: the open SCR1 echo fw.
- `scr1-echo/load-echo-fw.sh`, `mbox-roundtrip.sh`, `echo-fw-words.txt`: load + test.
- Kernel deltas: `rockchip-mailbox.c` (rv1106 num_chans=1), `clk-rv1106.c`
(CLK_CORE_MCU IGNORE_UNUSED), `rv1106-warden.dts` (&mailbox okay).
@@ -1,4 +1,4 @@
# HPMCU mailbox-echo firmware bare-metal RV32IMC for the RV1106 SCR1 core.
# HPMCU mailbox-echo firmware: bare-metal RV32IMC for the RV1106 SCR1 core.
# Same xPack riscv-none-embed-gcc 10.2.0 + flags as the watchdog firmware.
# Point XPACK at the xpack riscv toolchain bin/ inside a flare-edge SDK checkout.
@@ -1,5 +1,5 @@
/*
* HPMCU watchdog supervisor linked into the hpmcu_sram carve-out.
* HPMCU watchdog supervisor: linked into the hpmcu_sram carve-out.
*
* The RV1106's 256K on-chip SRAM at 0xFF6C0000 is split by the kernel DT
* (rv1106.dtsi sram@ff6c0000) into rkisp_sram (0x0..0x3e000) and hpmcu_sram
@@ -14,7 +14,7 @@
FW=${1:-/userdata/echo-fw-words.txt}
# GRF uncached peripheral window (covers CRU + this SRAM + the mailbox
# 0xff5c0000) WITHOUT this the MCU's peripheral/SRAM accesses are cached and
# 0xff5c0000): WITHOUT this the MCU's peripheral/SRAM accesses are cached and
# invisible to Linux. hpmcu.rs: GRF_BASE 0xff040000 +0x24/+0x28 = 0xff000/0xffc00.
devmem 0xff040024 32 0xff000
devmem 0xff040028 32 0xffc00
@@ -20,7 +20,7 @@
* watchdog SRAM mailbox relies on).
*
* Loaded + started by the proven flared/hpmcu.rs SRAM sequence
* (0xFF6FE000 load addr, CORECRU reset hold/release) NOT the 0x40000
* (0xFF6FE000 load addr, CORECRU reset hold/release), NOT the 0x40000
* boot-load path (that bricks a non-TB board; see boot-loaded-mcu-0x40000
* hazard). Fits the 8K hpmcu_sram budget (this is a few hundred bytes).
*/
@@ -29,7 +29,7 @@
#define REG32(a) (*(volatile uint32_t *)(uintptr_t)(a))
/* Mailbox IP (HPMCU-connected instance) offsets match rockchip-mailbox.c. */
/* Mailbox IP (HPMCU-connected instance): offsets match rockchip-mailbox.c. */
#define MBOX_BASE 0xFF5C0000u
#define A2B_INTEN REG32(MBOX_BASE + 0x00)
#define A2B_STATUS REG32(MBOX_BASE + 0x04)
@@ -1,6 +1,6 @@
#!/bin/sh
# Prove the A7<->HPMCU mailbox round-trip on channel 0. Write A2B_CMD first, then
# A2B_DAT the A2B_DAT write is the doorbell (raises A2B_STATUS on the MCU side,
# A2B_DAT: the A2B_DAT write is the doorbell (raises A2B_STATUS on the MCU side,
# whose echo firmware has enabled A2B_INTEN), so both CMD and DAT are current when
# the SCR1 reads them. The SCR1 echoes {cmd,dat} verbatim into B2A_CMD/B2A_DAT.
CMD=${1:-0x00001234}
+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>
+4 -4
View File
@@ -1,21 +1,21 @@
# PVTM (Process-Voltage-Temperature Monitor) VERIFIED on warden-c8a3 (2026-08-25)
# PVTM (Process-Voltage-Temperature Monitor): VERIFIED on warden-c8a3 (2026-08-25)
Whole-driver port: mainline 6.18 has **no** rockchip pvtm driver; the vendor
`drivers/soc/rockchip/rockchip_pvtm.c` (GPL-2.0, 1046L) supports rv1106. Copied it in
+ `include/linux/soc/rockchip/pvtm.h`; Kconfig `ROCKCHIP_PVTM` + Makefile; `=y`.
## API-delta fixes (6.18)
- `struct thermal_zone_device` is now opaque replaced `pvtm->tz->ops->get_temp(...)`
- `struct thermal_zone_device` is now opaque -> replaced `pvtm->tz->ops->get_temp(...)`
with the public `thermal_zone_get_temp(pvtm->tz, &cur_temp)`.
- added `#include <linux/of_device.h>` for `of_match_device()`.
- copied the vendor-only header `linux/soc/rockchip/pvtm.h`.
## The real blocker (why it first probed silently)
The vendor of_match_table wraps the rv1106 entries in `#ifdef CONFIG_CPU_RV1106` a
The vendor of_match_table wraps the rv1106 entries in `#ifdef CONFIG_CPU_RV1106`: a
vendor per-SoC symbol that does **not exist** in mainline. Result: devices
(`ff240000.pvtm`, `ff390000.pvtm`) were created + the driver registered, but the
compatibles were compiled out of the match table, so nothing bound and probe never
ran (no dmesg at all). **Fix: drop the `#ifdef CONFIG_CPU_RV1106` guard** our tree
ran (no dmesg at all). **Fix: drop the `#ifdef CONFIG_CPU_RV1106` guard**; our tree
only builds for rv1106, so the entries are unconditional. (Watch for this guard in any
other vendor driver ported by verbatim copy.)
+10 -10
View File
@@ -1,27 +1,27 @@
# RGA port DONE, verified on hardware (2026-08-24)
# RGA port: DONE, verified on hardware (2026-08-24)
Ported the vendor char-dev RGA driver (`drivers/video/rockchip/rga3/`, the
multicore `CONFIG_ROCKCHIP_MULTI_RGA` driver WardenOS's librga uses `/dev/rga`,
multicore `CONFIG_ROCKCHIP_MULTI_RGA` driver: WardenOS's librga uses `/dev/rga`,
not V4L2) to 6.18. Open source (vendor C source). On warden-c8a3:
```
rga: rga2, irq = 55, match scheduler
rga: rga2 hardware loaded successfully, hw_version:3.3.87975
rga: rga2 probe successfully /dev/rga present, /dev/dma_heap present
rga: rga2 probe successfully -> /dev/rga present, /dev/dma_heap present
```
## Build tree wiring
`drivers/video/rockchip/{Kconfig,Makefile}` created (source rga3/); wired into
`drivers/video/{Kconfig,Makefile}`. `CONFIG_ROCKCHIP_MULTI_RGA=y`.
`&rga2 { status="okay"; }`. For `/dev/dma_heap/cma`: use the kernel's default 64 MiB
CMA + `CONFIG_DMABUF_HEAPS{,_CMA,_SYSTEM}=y` do NOT add a `linux,cma` DT node
CMA + `CONFIG_DMABUF_HEAPS{,_CMA,_SYSTEM}=y`; do NOT add a `linux,cma` DT node
(a mis-aligned/duplicate one hangs the boot at reserved-memory init).
## 5.10 6.18 API deltas fixed (all mechanical)
1. `platform_driver.remove`: `int` `void` (`rga_drv_remove`, drop the returns).
2. `hrtimer_init(&t,)` + `t.function=fn` `hrtimer_setup(&t, fn, CLOCK_MONOTONIC,
HRTIMER_MODE_REL)` the scheduler tick; verified under the live probe.
## 5.10 -> 6.18 API deltas fixed (all mechanical)
1. `platform_driver.remove`: `int` -> `void` (`rga_drv_remove`, drop the returns).
2. `hrtimer_init(&t,...)` + `t.function=fn` -> `hrtimer_setup(&t, fn, CLOCK_MONOTONIC,
HRTIMER_MODE_REL)`: the scheduler tick; verified under the live probe.
3. `iommu_map` / `iommu_map_sg`: added the new `gfp` arg (`GFP_KERNEL`).
4. `get_user_pages_remote`: dropped the removed `vmas` arg.
5. `MAX_ORDER` (removed) compat `#define MAX_ORDER (MAX_PAGE_ORDER + 1)` in
5. `MAX_ORDER` (removed) -> compat `#define MAX_ORDER (MAX_PAGE_ORDER + 1)` in
`rga3/include/rga_drv.h` (preserves the old exclusive semantics everywhere).
6. `MODULE_IMPORT_NS(<token>)` `MODULE_IMPORT_NS("DMA_BUF")` (string form).
6. `MODULE_IMPORT_NS(<token>)` -> `MODULE_IMPORT_NS("DMA_BUF")` (string form).
+67 -67
View File
@@ -1,4 +1,4 @@
# M6 RGA 2D accelerator port to 6.18
# M6: RGA 2D accelerator port to 6.18
Target: `/dev/rga` present and working on the self-built Linux 6.18.46 RV1106 port
(`../PORT-STATUS.md`, `../DRIVER-PARITY.md` row "RGA 2D (rga2)"), at parity with the
@@ -6,42 +6,42 @@ running vendor 5.10 kernel and with zero WardenOS UI-side code changes.
## Decision: port the vendor char-dev driver. Do not touch mainline V4L2 rga.
**WardenOS depends on the vendor `/dev/rga` ioctl uapi via librga's IM2D API not
**WardenOS depends on the vendor `/dev/rga` ioctl uapi via librga's IM2D API, not
V4L2.** `ui-src/src/warden/warden_rga.c` (`flare-edge/major-app-additions/`) includes
`<rga/rga.h>` and `<rga/im2d.h>` and calls `wrapbuffer_fd_t()`, `improcess(..., IM_SYNC)`
and `querystring(RGA_VERSION)` (warden_rga.c:46-48, 377-390, 458, 614-631) the
and `querystring(RGA_VERSION)` (warden_rga.c:46-48, 377-390, 458, 614-631): the
Rockchip **im2d/librga** user-space API, which talks to the kernel purely through the
vendor char-device ioctl protocol (`RGA_IOC_REQUEST_SUBMIT` etc., see below). There is
no V4L2 (`/dev/videoN`, `VIDIOC_*`) code anywhere in WardenOS's RGA path, and no
V4L2-backed librga build exists upstream to switch to even if we wanted one. Porting the
vendor driver is therefore not a preference, it is the only path that keeps the existing
UI code (and its measured 20%8% CPU win, warden_rga.c:419) working unmodified.
UI code (and its measured 20%->8% CPU win, warden_rga.c:419) working unmodified.
The candidate alternative adding an `rv1106` compatible string to mainline's
`drivers/media/platform/rockchip/rga/` V4L2 M2M driver is rejected on both counts the
The candidate alternative (adding an `rv1106` compatible string to mainline's
`drivers/media/platform/rockchip/rga/` V4L2 M2M driver) is rejected on both counts the
task asked to check:
1. **Register incompatibility.** Mainline's driver (current `torvalds/linux` master,
representative of 6.18) matches only `rockchip,rk3288-rga`, `rockchip,rk3399-rga`
(both mapped to one `rga2_hw` struct, `.features = FLIP | ROTATE | BG_COLOR` only
(both mapped to one `rga2_hw` struct, `.features = FLIP | ROTATE | BG_COLOR` only:
no scaling, no format conversion) and `rockchip,rk3588-rga3`. Per Rockchip's own FAQ
("Although RGA on both RK3399 and RV1126 is RGA2-ENHANCE, their sub versions are
different" `librga/docs/Rockchip_FAQ_RGA_EN.md`, Q2.10) RK3399's RGA is already
different"; `librga/docs/Rockchip_FAQ_RGA_EN.md`, Q2.10) RK3399's RGA is already
**RGA2-ENHANCE**, a newer/richer core than RK3288's baseline RGA2 that mainline's
`rga2_hw` was written against so mainline's claim of rk3399 compatibility is itself
`rga2_hw` was written against, so mainline's claim of rk3399 compatibility is itself
the reduced-feature subset, not proof of a shared register map. RV1106 is a *third*
point in that family: our own vendor driver (`rga_drv.c:1402-1409`) special-cases the
exact hardware version string `"3.3.87975"` to a distinct `rga2e_1106_data` table,
separate from the generic `rga2e_data` and from the IOMMU-capable `rga2e_iommu_data`
used by other RGA2-ENHANCE chips i.e. even Rockchip's own driver does not treat
used by other RGA2-ENHANCE chips, i.e. even Rockchip's own driver does not treat
RV1106 as register-identical to its closest relatives, let alone to RK3288/RK3399's
older baseline core. A pending upstream series (Jianfeng Liu,
`20240322052915.3507937-1-liujianfeng1994@gmail.com`, "media: rockchip: rga: Add
rk3568 support") explicitly states "RGA2 on rk3568 is the same core as RGA2 on
rk3288" confirming mainline's whole `rga2_hw` lineage targets the *old* core, not
rk3288", confirming mainline's whole `rga2_hw` lineage targets the *old* core, not
the ENHANCE family RV1106 belongs to.
2. **Feature/uapi mismatch.** Even where mainline's driver runs, it only implements
flip/rotate/solid-fill via V4L2 M2M no im2d, no `/dev/rga` char device, no
flip/rotate/solid-fill via V4L2 M2M: no im2d, no `/dev/rga` char device, no
`wrapbuffer_fd_t`/`improcess` surface. Adopting it would mean rewriting
`warden_rga.c`'s draw-unit and buffer-sync-copy paths against `VIDIOC_*` ioctls from
scratch, on hardware nobody has shown is even electrically the same core.
@@ -51,30 +51,30 @@ task asked to check:
Real path: `flare-edge/sdk/sysdrv/source/kernel/drivers/video/rockchip/rga3/`
(`sdk` is a symlink to `<flare-edge>/sdk`). This is Rockchip's
**"multicore" RGA driver** (`CONFIG_ROCKCHIP_MULTI_RGA`, module name `rga3.ko`/built-in
`rga3.o`) open source, `SPDX-License-Identifier: GPL-2.0`, driver version **1.3.1**
`rga3.o`): open source, `SPDX-License-Identifier: GPL-2.0`, driver version **1.3.1**
(`include/rga_drv.h:88-94`; the current `airockchip/librga` upstream ships a matching
**1.3.3** for RK3588 on 6.1/6.4 Armbian kernels same lineage, already proven building
**1.3.3** for RK3588 on 6.1/6.4 Armbian kernels, same lineage, already proven building
against 6.x elsewhere). There are three other RGA driver trees in the SDK
(`drivers/video/rockchip/rga/`, `rga2/`, and the mainline-style
`drivers/media/platform/rockchip/rga/`) **none of these are built** for RV1106
`drivers/media/platform/rockchip/rga/`). **None of these are built** for RV1106
(`grep CONFIG_ROCKCHIP_MULTI_RGA arch/arm/configs/*rv1106*defconfig` is the only RGA
symbol set; `drivers/media/platform/rockchip/rga` isn't referenced by any rv1106
defconfig or DT). Ignore them; `rga3/` is the one true source.
| File | Role |
|---|---|
| `rga_drv.c` | probe/remove, misc char-dev (`"rga"` `/dev/rga`), ioctl dispatch, IRQ, clocks, hrtimer scheduler tick |
| `rga_drv.c` | probe/remove, misc char-dev (`"rga"` -> `/dev/rga`), ioctl dispatch, IRQ, clocks, hrtimer scheduler tick |
| `rga_common.c`, `rga_job.c`, `rga_mm.c` | job/request lifecycle, buffer-descriptor management |
| `rga2_reg_info.c`, `rga3_reg_info.c`, `rga_hw_config.c` | per-core register programming (the actual "how to talk to the silicon" this is what a mainline single-core driver does NOT have for RGA2-ENHANCE) |
| `rga2_reg_info.c`, `rga3_reg_info.c`, `rga_hw_config.c` | per-core register programming (the actual "how to talk to the silicon"; this is what a mainline single-core driver does NOT have for RGA2-ENHANCE) |
| `rga_dma_buf.c` | dma-buf import/map (`dma_buf_attach` + `dma_buf_map_attachment`, `iosys_map`) |
| `rga_iommu.c` | IOMMU attach **not exercised on RV1106** (see below) |
| `rga_fence.c` | `dma_fence`/`sync_file` for the async ioctl path **not needed**, WardenOS is sync-only (see Scope cuts) |
| `rga_iommu.c` | IOMMU attach: **not exercised on RV1106** (see below) |
| `rga_fence.c` | `dma_fence`/`sync_file` for the async ioctl path: **not needed**, WardenOS is sync-only (see Scope cuts) |
| `rga_debugger.c` | optional procfs/debugfs introspection |
| `include/rga.h` | uapi: ioctl numbers or `RGA_IOC_MAGIC='r'` `RGA_IOC_GET_DRVIER_VERSION`, `RGA_IOC_GET_HW_VERSION`, `RGA_IOC_IMPORT_BUFFER`, `RGA_IOC_REQUEST_CREATE/SUBMIT/CONFIG/CANCEL`, plus the legacy `RGA_BLIT_SYNC`(0x5017)/`RGA_BLIT_ASYNC`/`RGA_GET_VERSION` numeric ioctls. **This is the exact uapi `librga.so` (userspace) calls** nothing here changes; we port the kernel side only. |
| `include/rga.h` | uapi: ioctl numbers or `RGA_IOC_MAGIC='r'`, `RGA_IOC_GET_DRVIER_VERSION`, `RGA_IOC_GET_HW_VERSION`, `RGA_IOC_IMPORT_BUFFER`, `RGA_IOC_REQUEST_CREATE/SUBMIT/CONFIG/CANCEL`, plus the legacy `RGA_BLIT_SYNC`(0x5017)/`RGA_BLIT_ASYNC`/`RGA_GET_VERSION` numeric ioctls. **This is the exact uapi `librga.so` (userspace) calls**: nothing here changes; we port the kernel side only. |
**DT match for our chip:** `rga2_dt_ids[]` (`rga_drv.c:1249-1258`) matches
`compatible = "rockchip,rga2_core0"` `rk3588_rga2_match_data` (clock names
`"aclk_rga2","hclk_rga2","clk_rga2"`, `rga_drv.c:1195-1199`) this is **exactly** our
`compatible = "rockchip,rga2_core0"` -> `rk3588_rga2_match_data` (clock names
`"aclk_rga2","hclk_rga2","clk_rga2"`, `rga_drv.c:1195-1199`); this is **exactly** our
vendor DT node:
```c
// rv1106.dtsi:1154-1161
@@ -87,25 +87,25 @@ rga2: rga@ff980000 {
status = "disabled";
};
```
No `resets =`, no `iommus =` on this node confirmed correct: `rga_drv.c` never calls
No `resets =`, no `iommus =` on this node; confirmed correct: `rga_drv.c` never calls
`reset_control_*` at all (unlike VOP, which needed named resets added for mainline,
`../display/README.md`), and probe only calls `rga_iommu_probe()` when
`scheduler->data->mmu == RGA_IOMMU` (`rga_drv.c:1420-1425`) for RV1106's matched
`scheduler->data->mmu == RGA_IOMMU` (`rga_drv.c:1420-1425`), for RV1106's matched
`rga2e_1106_data` (selected by exact HW-version-string match `"3.3.87975"`,
`rga_drv.c:1402-1409`) that table has no IOMMU, so the branch never runs. **RV1106's RGA2
is physically-contiguous-only**, which is exactly why `warden_rga.c` allocates its
canvas/scanout buffers from `/dev/dma_heap/cma` (CMA dma-buf heap) rather than any
generic malloc no driver-side change needed here, the existing WardenOS allocation
generic malloc: no driver-side change needed here, the existing WardenOS allocation
strategy already matches the hardware constraint.
**IRQ name:** `dev_driver_string(dev)` = the platform_driver's `.driver.name`, which for
the `rga2_dt_ids` match is literally `"rga2"` (`rga_drv.c:1478-1481`) this is the exact
the `rga2_dt_ids` match is literally `"rga2"` (`rga_drv.c:1478-1481`); this is the exact
string that shows up as `rga2` in `/proc/interrupts` on the running 5.10 image, confirming
the task's framing and that we're looking at the right driver.
**DT enable status (5.10, for parity):** the shipped 86-Panel board DT already turns this
node on `rv1106g-luckfox-pico-86panel.dts` `rv1106-luckfox-pico-86panel-ipc.dtsi`
`#include "rv1106-evb.dtsi"` `&rga2 { status = "okay"; };` (`rv1106-evb.dtsi:59-61`).
node on, `rv1106g-luckfox-pico-86panel.dts` -> `rv1106-luckfox-pico-86panel-ipc.dtsi` ->
`#include "rv1106-evb.dtsi"` -> `&rga2 { status = "okay"; };` (`rv1106-evb.dtsi:59-61`).
`sdk-patches/kernel/configs/flare-edge.config:21-29` (flare-edge repo) is the config
fragment: `CONFIG_ROCKCHIP_MULTI_RGA=y` (built-in, not `=m`, so devtmpfs creates
`/dev/rga` with no insmod step) + `CONFIG_DMABUF_HEAPS=y` + `CONFIG_DMABUF_HEAPS_CMA=y`.
@@ -117,9 +117,9 @@ fragment: `CONFIG_ROCKCHIP_MULTI_RGA=y` (built-in, not `=m`, so devtmpfs creates
(Apache-2.0 licensed headers, `rga.h`/`im2d.h`). It links against the kernel uapi in
`include/rga.h` above, which is unchanged by this port. Once `/dev/rga` exists with the
same ioctl numbers, the existing `librga.so` and the existing `warden_rga.c` binary/object
need **no changes** this is a pure kernel-side port.
need **no changes**: this is a pure kernel-side port.
## API-delta checklist, 5.10 6.18 (checked against the real target tree)
## API-delta checklist, 5.10 -> 6.18 (checked against the real target tree)
Checked directly against `flare-edge/research/linux-6.18.46/` (the tree M1-M3 already
build and boot on), not guessed:
@@ -127,12 +127,12 @@ build and boot on), not guessed:
| # | Delta | Evidence | Fix |
|---|---|---|---|
| 1 | **`platform_driver.remove` is now `void`, not `int`.** | `linux-6.18.46/include/linux/platform_device.h:233`: `void (*remove)(struct platform_device *);`. Vendor `rga_drv_remove()` (`rga_drv.c`) returns `int`. Used by all 3 `platform_driver` structs (rga3_core0/1, rga2). | Change signature to `void`, drop the `return ret;`/`return 0;`, keep the body. Mechanical, 1 function + 3 struct references. |
| 2 | **`hrtimer_init()` is gone; `hrtimer_setup()` merges init+callback.** | `grep hrtimer_init` on `linux-6.18.46/include/linux/hrtimer.h` returns nothing callable only `hrtimer_setup()`/`hrtimer_setup_on_stack()` (`hrtimer.h:230-235`). Vendor `rga_drv.c:362-366` does the old split form: `hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); timer.function = hrtimer_handler;`. | `hrtimer_setup(&timer, hrtimer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL);` one call site, but it's the RGA scheduler tick (`rga_drv.c:327-371`), so build-clean is necessary but not sufficient: exercise it under load (the blit test below) since a hrtimer regression shows up as stalled/duplicate completions, not a compile error. **This is the delta most likely to bite in a way the compiler won't catch treat it as the highest-scrutiny item.** |
| 3 | `dma_buf_attach()` / `dma_buf_map_attachment()` | `linux-6.18.46/include/linux/dma-buf.h:571,588` signatures (`dmabuf, dev`) / (`attach, dir`) `sg_table` are **unchanged** from what `rga_dma_buf.c:443,450,494,501` already calls. Vendor code already uses `struct iosys_map` (post-5.18 API) at `rga_dma_buf.c:398`, so it's already ahead of 5.10 baseline here. | No change needed; compile-verify only. |
| 4 | `class_create()` losing its `owner` arg (6.4) | Not applicable `rga3/` has **zero** `class_create` calls (grep across the whole dir + `include/`); it registers `/dev/rga` via `misc_register()` (`rga_drv.c:1520`), which needs no class. | Nothing to fix. (Flagged in the task brief as a risk; verified moot for this driver.) |
| 5 | IOMMU API drift (`iommu_domain_alloc`, `iommu_attach_device`, rockchip IOMMU v2) | `rga_iommu.c` uses `iommu_group_get`/`iommu_get_domain_for_dev` but as established above, **RV1106 never calls `rga_iommu_probe()`** (its match_data has no `RGA_IOMMU` flag). | Out of scope for RV1106; don't even need to build-fix `rga_iommu.c`'s IOMMU-attach path correctness, only that it compiles (dead code on our chip). |
| 6 | `proc_ops`/`debugfs_create_file` | `rga_debugger.c:499,553,597,605,633,686` already uses `struct proc_ops` (post-5.6) and plain `debugfs_create_file`/`proc_create_data` both stable, unchanged APIs in 6.18. | No change needed. Optional subsystem anyway (see Scope cuts). |
| 7 | GRF/syscon regmap lookups | None `rga3/` has zero `syscon`/`regmap`/`rockchip,grf` references, unlike VOP which needed the `grf_ctx` fix in `PORT-STATUS.md` M2 item 3. | Nothing to fix; simpler than the VOP port in this respect. |
| 2 | **`hrtimer_init()` is gone; `hrtimer_setup()` merges init+callback.** | `grep hrtimer_init` on `linux-6.18.46/include/linux/hrtimer.h` returns nothing callable: only `hrtimer_setup()`/`hrtimer_setup_on_stack()` (`hrtimer.h:230-235`). Vendor `rga_drv.c:362-366` does the old split form: `hrtimer_init(&timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); timer.function = hrtimer_handler;`. | `hrtimer_setup(&timer, hrtimer_handler, CLOCK_MONOTONIC, HRTIMER_MODE_REL);`, one call site, but it's the RGA scheduler tick (`rga_drv.c:327-371`), so build-clean is necessary but not sufficient: exercise it under load (the blit test below) since a hrtimer regression shows up as stalled/duplicate completions, not a compile error. **This is the delta most likely to bite in a way the compiler won't catch: treat it as the highest-scrutiny item.** |
| 3 | `dma_buf_attach()` / `dma_buf_map_attachment()` | `linux-6.18.46/include/linux/dma-buf.h:571,588`: signatures (`dmabuf, dev`) / (`attach, dir`) -> `sg_table` are **unchanged** from what `rga_dma_buf.c:443,450,494,501` already calls. Vendor code already uses `struct iosys_map` (post-5.18 API) at `rga_dma_buf.c:398`, so it's already ahead of 5.10 baseline here. | No change needed; compile-verify only. |
| 4 | `class_create()` losing its `owner` arg (6.4) | Not applicable: `rga3/` has **zero** `class_create` calls (grep across the whole dir + `include/`); it registers `/dev/rga` via `misc_register()` (`rga_drv.c:1520`), which needs no class. | Nothing to fix. (Flagged in the task brief as a risk; verified moot for this driver.) |
| 5 | IOMMU API drift (`iommu_domain_alloc`, `iommu_attach_device`, rockchip IOMMU v2) | `rga_iommu.c` uses `iommu_group_get`/`iommu_get_domain_for_dev`, but as established above, **RV1106 never calls `rga_iommu_probe()`** (its match_data has no `RGA_IOMMU` flag). | Out of scope for RV1106; don't even need to build-fix `rga_iommu.c`'s IOMMU-attach path correctness, only that it compiles (dead code on our chip). |
| 6 | `proc_ops`/`debugfs_create_file` | `rga_debugger.c:499,553,597,605,633,686` already uses `struct proc_ops` (post-5.6) and plain `debugfs_create_file`/`proc_create_data`, both stable, unchanged APIs in 6.18. | No change needed. Optional subsystem anyway (see Scope cuts). |
| 7 | GRF/syscon regmap lookups | None: `rga3/` has zero `syscon`/`regmap`/`rockchip,grf` references, unlike VOP which needed the `grf_ctx` fix in `PORT-STATUS.md` M2 item 3. | Nothing to fix; simpler than the VOP port in this respect. |
## Scope cuts (reduce port surface to exactly what WardenOS uses)
@@ -140,15 +140,15 @@ build and boot on), not guessed:
explicit: "The RGA blit uses the synchronous path. No fences" (warden_rga.h:23), and
every call site uses `improcess(..., IM_SYNC)` (warden_rga.c:388,629). The
`Makefile`'s `rga3-$(CONFIG_ROCKCHIP_RGA_ASYNC) += rga_fence.o` means leaving this
config off **excludes `rga_fence.c` (dma_fence/sync_file) from the build entirely**
config off **excludes `rga_fence.c` (dma_fence/sync_file) from the build entirely**:
removing an entire API-delta surface (dma_fence context allocation, sync_file
lifetime) that WardenOS never exercises. Matches the vendor default (`ROCKCHIP_RGA_ASYNC`
defaults to `y` in Kconfig but is safe to turn off; verify nothing else in the SDK
userspace on this image needs it nothing does; MPP/ISP aren't used on the 86-Panel).
userspace on this image needs it: nothing does; MPP/ISP aren't used on the 86-Panel).
- **`CONFIG_ROCKCHIP_RGA_PROC_FS`/`_DEBUG_FS`/`_DEBUGGER`: leave off initially.** Useful
for bring-up (dumps registered buffers/jobs) but not required for `/dev/rga` to exist
or work; add later if debugging needs it.
- **Only `rga3/` (`CONFIG_ROCKCHIP_MULTI_RGA`) is ported** not `drivers/video/rockchip/rga/`
- **Only `rga3/` (`CONFIG_ROCKCHIP_MULTI_RGA`) is ported**, not `drivers/video/rockchip/rga/`
or `rga2/` (the older single-core drivers), matching the vendor 5.10 build exactly.
## File / config / DT plan
@@ -164,7 +164,7 @@ drivers/video/rockchip/rga3/ # forward-ported rga_drv.c, rga_common
# rga3_reg_info.c, rga2_reg_info.c,
# rga_hw_config.c, rga_mm.c, rga_dma_buf.c,
# rga_iommu.c, rga_policy.c, Kconfig, Makefile
# (rga_fence.c, rga_debugger.c omitted
# (rga_fence.c, rga_debugger.c omitted,
# see Scope cuts)
```
@@ -184,8 +184,8 @@ obj-$(CONFIG_ROCKCHIP_MULTI_RGA) += rga3.o
(drops the `rga3-$(CONFIG_ROCKCHIP_RGA_ASYNC) += rga_fence.o` and
`rga3-$(CONFIG_ROCKCHIP_RGA_DEBUGGER) += rga_debugger.o` lines from the vendor Makefile.)
Apply the two API-delta fixes from the table above (`platform_driver.remove` void,
`hrtimer_init`+`.function=` `hrtimer_setup()`) during the transplant, same
Apply the two API-delta fixes from the table above (`platform_driver.remove` -> void,
`hrtimer_init`+`.function=` -> `hrtimer_setup()`) during the transplant, same
build-fix-build loop already used for `clk-rv1106.c`/`pinctrl-rockchip.c`
(`PORT-STATUS.md` M1).
@@ -197,34 +197,34 @@ CONFIG_DMABUF_HEAPS=y
CONFIG_DMABUF_HEAPS_CMA=y
```
(`CONFIG_DMABUF_HEAPS` is currently `# CONFIG_DMABUF_HEAPS is not set` in
`linux-6.18.46/.config` confirmed by grep so this is a required addition, not
`linux-6.18.46/.config` (confirmed by grep), so this is a required addition, not
already-on. `CONFIG_ARCH_ROCKCHIP=y` is already set.) `=y` not `=m`, matching the
vendor fragment's own rationale (`flare-edge.config:23`: builtin so devtmpfs creates
the node with no insmod step).
### 3. Devicetree two additions to `dts/rv1106-warden.dts`
### 3. Devicetree: two additions to `dts/rv1106-warden.dts`
**(a) Enable the node.** `rv1106.dtsi` is already `#include`d wholesale by
`rv1106-warden.dts` (`dts/README.md`), and it already carries the `rga2` node
byte-for-byte (clocks, IRQ, compatible) just disabled. Add the same one-line override
byte-for-byte (clocks, IRQ, compatible), just disabled. Add the same one-line override
the vendor board DT uses:
```dts
&rga2 {
status = "okay";
};
```
No clock/reset/iommu properties to add all three RGA2E clocks
No clock/reset/iommu properties to add: all three RGA2E clocks
(`HCLK_RGA2E`=269, `ACLK_RGA2E`=270, `CLK_CORE_RGA2E`=271) are **already wired** in the
ported `clk-rv1106.c` (`clk/clk-rv1106.c:924-930`, landed in M1, `PORT-STATUS.md`) off
the same `hclk_vo_root`/`aclk_vo_root` parents that VOP already uses successfully on
hardware (M4, verified) — this is the lowest-risk clock story of any M6 driver.
hardware (M4, verified). This is the lowest-risk clock story of any M6 driver.
**(b) Add the CMA reserved-memory pool currently missing from the ported DT.**
**(b) Add the CMA reserved-memory pool, currently missing from the ported DT.**
`warden_rga.c`'s canvas allocator opens `/dev/dma_heap/cma`
(`warden_rga.c:70,269-274`), which requires a `linux,cma`/`shared-dma-pool`
reserved-memory node to exist. On the vendor 5.10 board DT this lives in
`rv1106-luckfox-pico-86panel-ipc.dtsi` (**not** `rv1106.dtsi`, and confirmed **not yet
present** in `dts/rv1106-warden.dts` grep for `reserved-memory`/`linux,cma` there
present** in `dts/rv1106-warden.dts`; grep for `reserved-memory`/`linux,cma` there
returns nothing):
```dts
// rv1106-luckfox-pico-86panel-ipc.dtsi:91-107
@@ -246,15 +246,15 @@ reserved_memory: reserved-memory {
Port just the `linux,cma` child (the sibling `drm-logo`/`mmc_ecsd` reserved regions
belong to the display/boot-logo and eMMC-ECSD milestones respectively, not RGA) into
`rv1106-warden.dts`. Without this the RGA kernel driver itself will still probe and
`/dev/rga` will still appear (the driver has no CMA dependency of its own DMA-BUF
`/dev/rga` will still appear (the driver has no CMA dependency of its own: DMA-BUF
import works from any dma-buf exporter), but WardenOS's own canvas/scanout allocator
will fail open and the UI falls back to the CPU path silently (`warden_rga.c:269-274`
returns `-1` on `open()` failure) so this step is required for the *offload* to be
returns `-1` on `open()` failure), so this step is required for the *offload* to be
observably working, even though it isn't required for `/dev/rga` to exist.
### 4. Rootfs / userspace
None needed `librga.so`/`.a` for `rv1106_arm-rockchip830-linux-uclibcgnueabihf`
None needed: `librga.so`/`.a` for `rv1106_arm-rockchip830-linux-uclibcgnueabihf`
already ships in the SDK (`sdk/media/rga/release_rga_rv1106_.../lib/`) and is already
what the current Buildroot overlay installs and what `warden_rga.c` already links
against with `WARDEN_USE_RGA=1`. No rebuild of librga or of WardenOS's C code is
@@ -263,11 +263,11 @@ implied by this kernel port.
## Verify steps
1. **Build:** `rga3.o` compiles with zero warnings/errors against `linux-6.18.46`
(same evidentiary bar as `PORT-STATUS.md`'s M1 entries "COMPILES CLEAN").
(same evidentiary bar as `PORT-STATUS.md`'s M1 entries, "COMPILES CLEAN").
2. **Boot + node:** on warden-c8a3 (A/B `_b`-slot loop, `../docs/m2-boot-on-c8a3.md`),
confirm dmesg shows `rga2, irq = 87, match scheduler` and
`rga2 hardware loaded successfully, hw_version:3.3.87975.` (the exact version string
`rga_drv_probe` selects `rga2e_1106_data` on, `rga_drv.c:1403`) a different version
`rga_drv_probe` selects `rga2e_1106_data` on, `rga_drv.c:1403`): a different version
string here would mean the wrong match-data table and is a stop-ship signal.
`ls -l /dev/rga` exists with no manual `mknod`/`insmod`.
3. **CMA heap:** `ls /dev/dma_heap/cma` exists (needs step 3(b) above).
@@ -275,24 +275,24 @@ implied by this kernel port.
check is `librga`'s own CLI/test harness if the SDK ships one, or a ~20-line C
program using the already-present `librga.so`: allocate two small dma-buf CMA
buffers via `/dev/dma_heap/cma`, fill one with a known pattern, call
`improcess(src, dst, ..., IM_SYNC)` for a straight copy, and memcmp the result
this exercises exactly the ioctl path (`RGA_IOC_REQUEST_SUBMIT`/`RGA_BLIT_SYNC`) and
`improcess(src, dst, ..., IM_SYNC)` for a straight copy, and memcmp the result.
This exercises exactly the ioctl path (`RGA_IOC_REQUEST_SUBMIT`/`RGA_BLIT_SYNC`) and
the hrtimer-driven completion path (API-delta #2) that WardenOS's real usage
exercises, without needing DRM/VOP (M4) to be finished first.
5. **On-target UI evidence (once M4/display lands):** boot the real WardenOS build with
`WARDEN_USE_RGA=1`, open the graph/Monitor page, confirm `warden_rga_available()`
reports true (`querystring(RGA_VERSION)` succeeds `warden_rga.c:458-466`) and the
reports true (`querystring(RGA_VERSION)` succeeds, `warden_rga.c:458-466`) and the
Monitor page's "RGA" load metric moves during graph scroll (`warden_rga_load_pct()`,
`warden_rga.c:226-241`) the same on-target evidence bar as every other change in
`warden_rga.c:226-241`); the same on-target evidence bar as every other change in
this repo (repo policy: "UI/daemon changes are verified on a real panel").
## Summary of residual risk (PORT-VERIFY-class items)
- **hrtimer_setup() correctness under load** (delta #2) compiles clean is not enough;
- **hrtimer_setup() correctness under load** (delta #2): compiles clean is not enough;
needs the blit-test loop run repeatedly/concurrently to rule out a scheduler-tick
regression.
- **CMA pool sizing** 10 MiB was sized against the 5.10 image's actual usage (graph
canvas + scanout mirror at 720×720×4B 2 MiB each); carry the same size unless a
- **CMA pool sizing**: 10 MiB was sized against the 5.10 image's actual usage (graph
canvas + scanout mirror at 720x720x4B ~ 2 MiB each); carry the same size unless a
future accounting shows it's tight.
- **Driver-parity table** (`../DRIVER-PARITY.md`) should move `RGA 2D (rga2)` from [ ] to
[wip]/[x] as these steps land, same convention as every other M-milestone row
@@ -311,10 +311,10 @@ implied by this kernel port.
- Target kernel tree (ground truth for the API-delta table):
`flare-edge/research/linux-6.18.46/{include/linux/platform_device.h,hrtimer.h,dma-buf.h}`,
`.config`
- [airockchip/librga](https://github.com/airockchip/librga) upstream userspace IM2D
- [airockchip/librga](https://github.com/airockchip/librga): upstream userspace IM2D
library (Apache-2.0), RV1106 target support, 1.3.3 driver version on RK3588/6.1-6.4
- [librga FAQ RGA hardware family notes](https://github.com/airockchip/librga/blob/main/docs/Rockchip_FAQ_RGA_EN.md) (Q2.10: RK3399/RV1126 both RGA2-ENHANCE, differing sub-versions, ROP cut on RV1126)
- [torvalds/linux drivers/media/platform/rockchip/rga/rga.c](https://raw.githubusercontent.com/torvalds/linux/master/drivers/media/platform/rockchip/rga/rga.c) mainline V4L2 driver's `of_device_id` table (rk3288-rga, rk3399-rga `rga2_hw`; rk3588-rga3 `rga3_hw`)
- [LWN "media: platform: rga: Add RGA3 support"](https://lwn.net/Articles/1041152/) RK3588 RGA3 mainlining, one `/dev/video` per core, no multicore scheduling in-kernel
- [lore.kernel.org "media: rockchip: rga: Add rk3568 support" (Jianfeng Liu)](https://lore.kernel.org/lkml/20240322052915.3507937-1-liujianfeng1994@gmail.com/) "RGA2 on rk3568 is the same core as RGA2 on rk3288" (confirms mainline's RGA2 lineage is the older/baseline core, not RGA2-ENHANCE)
- CNX Software, [Rockchip RK3588 mainline Linux support](https://www.cnx-software.com/2024/12/21/rockchip-rk3588-mainline-linux-support-current-status-and-future-work-for-2025/) RGA2 V4L2 mainlining timeline context
- [librga FAQ: RGA hardware family notes](https://github.com/airockchip/librga/blob/main/docs/Rockchip_FAQ_RGA_EN.md) (Q2.10: RK3399/RV1126 both RGA2-ENHANCE, differing sub-versions, ROP cut on RV1126)
- [torvalds/linux: drivers/media/platform/rockchip/rga/rga.c](https://raw.githubusercontent.com/torvalds/linux/master/drivers/media/platform/rockchip/rga/rga.c); mainline V4L2 driver's `of_device_id` table (rk3288-rga, rk3399-rga -> `rga2_hw`; rk3588-rga3 -> `rga3_hw`)
- [LWN, "media: platform: rga: Add RGA3 support"](https://lwn.net/Articles/1041152/): RK3588 RGA3 mainlining, one `/dev/video` per core, no multicore scheduling in-kernel
- [lore.kernel.org, "media: rockchip: rga: Add rk3568 support" (Jianfeng Liu)](https://lore.kernel.org/lkml/20240322052915.3507937-1-liujianfeng1994@gmail.com/): "RGA2 on rk3568 is the same core as RGA2 on rk3288" (confirms mainline's RGA2 lineage is the older/baseline core, not RGA2-ENHANCE)
- CNX Software, [Rockchip RK3588 mainline Linux support](https://www.cnx-software.com/2024/12/21/rockchip-rk3588-mainline-linux-support-current-status-and-future-work-for-2025/): RGA2 V4L2 mainlining timeline context
@@ -1,6 +1,6 @@
# TRNG + OTP port (batch A) 6.18
# TRNG + OTP port (batch A): 6.18
## TRNG (hardware RNG) VERIFIED on warden-c8a3 (2026-08-25)
## TRNG (hardware RNG): VERIFIED on warden-c8a3 (2026-08-25)
rv1106's `rockchip,trngv1` is the same standalone TRNG_V1 IP as rk3588 (identical
register map). Mainline `drivers/char/hw_random/rockchip-rng.c` already drives it.
@@ -12,18 +12,18 @@ register map). Mainline `drivers/char/hw_random/rockchip-rng.c` already drives i
rv1106's clock/reset names need no special handling.) Kconfig
`HW_RANDOM_ROCKCHIP=y` (already set).
**DT** (`rv1106-warden.dts`): override the dtsi `rng@ff448000` node
**DT** (`rv1106-warden.dts`): override the dtsi `rng@ff448000` node:
```dts
&rng { compatible = "rockchip,rv1106-rng"; status = "okay"; };
```
**Evidence:** `/dev/hwrng` present, `rng_current = rockchip-rng`,
`dd if=/dev/hwrng bs=16` `c697 503d f9db 6b84 50e4 e1ee f232 b2ae` (real HW
`dd if=/dev/hwrng bs=16` -> `c697 503d f9db 6b84 50e4 e1ee f232 b2ae` (real HW
entropy, non-zero). Hardware entropy source for the panel's crypto/keys.
## OTP / nvmem VERIFIED on warden-c8a3 (2026-08-25)
Reads real data: `dd .../rockchip-otp0/nvmem bs=1 count=16 | xxd`
`5211 02fe 084d 5231 0000 0000 3b15 0000` (contains "MR1" chip id) no timeout.
## OTP / nvmem: VERIFIED on warden-c8a3 (2026-08-25)
Reads real data: `dd .../rockchip-otp0/nvmem bs=1 count=16 | xxd` ->
`5211 02fe 084d 5231 0000 0000 3b15 0000` (contains "MR1" chip id), no timeout.
Mainline `drivers/nvmem/rockchip-otp.c` gains an `rv1106_data` + compatible.
**Delta:**
@@ -42,7 +42,7 @@ static const struct rockchip_data rv1106_data = {
Kconfig `NVMEM_ROCKCHIP_OTP=y`. **DT:** `&otp { status = "okay"; };` (the dtsi
`otp@ff3d0000` node already carries compatible + the 6 clocks/resets).
**First try** used `.reg_read = rk3588_otp_read` `timeout during read setup`
(rk3588 uses a different addressing path). Corrected to `px30_otp_read` the
**First try** used `.reg_read = rk3588_otp_read` -> `timeout during read setup`
(rk3588 uses a different addressing path). Corrected to `px30_otp_read`: the
mainline user-mode OTPC_USER read, the same sequence the vendor 5.10 driver used
for rv1106 (its `rk3568_otp_read`). Confirmed: cell reads return real data.
+3 -3
View File
@@ -1,7 +1,7 @@
# RTC rockchip,rv1106-rtc
# RTC: rockchip,rv1106-rtc
`rtc-rockchip.c` the vendor internal-RTC driver (open source, from the SDK),
ported to 6.18. Only 5.106.18 API delta: `rtc_register_device`
`rtc-rockchip.c`: the vendor internal-RTC driver (open source, from the SDK),
ported to 6.18. Only 5.10->6.18 API delta: `rtc_register_device` ->
`devm_rtc_register_device` (paired with the existing `devm_rtc_allocate_device`).
Enable `CONFIG_RTC_DRV_ROCKCHIP=y` + `&rtc { status="okay"; }`. Verified on
warden-c8a3: `/dev/rtc0` registers and reads.
+5 -5
View File
@@ -1,4 +1,4 @@
# arch-timer / vDSO clock fix plan (issue #3)
# arch-timer / vDSO clock fix: plan (issue #3)
Status: DIAGNOSED off-board, fix gated on two bench measurements.
@@ -9,7 +9,7 @@ The same kernel family under `qemu-system-arm -M virt` gives musl
measurement, `qemu/tests/clockprobe`). The generic 6.18 armv7 vDSO is
therefore CORRECT; the board symptom (musl reads ~12% high, kernel time
right) is RV1106-specific. The boot chain runs in the secure world and is
closed rkbin the NS view of the CPU timer registers (CNTFRQ, CNTVOFF) is
closed rkbin: the NS view of the CPU timer registers (CNTFRQ, CNTVOFF) is
whatever it left behind, and only the arch-counter path (vDSO,
`arch_sys_counter`) trusts them.
@@ -28,14 +28,14 @@ distinguish these.)
## The fix (both cases, one DT override)
Append to the BOARD dts (`rv1106-warden.dts` never the vendor dtsi) an
Append to the BOARD dts (`rv1106-warden.dts`, never the vendor dtsi) an
override on the armv7-timer node:
arm,cpu-registers-not-fw-configured;
clock-frequency = <MEASURED_HZ>;
The property makes the driver use the physical counter, ignore CNTVOFF, and
take the frequency from DT the documented remedy for firmware that does
take the frequency from DT: the documented remedy for firmware that does
not configure the CPU timer registers. `MEASURED_HZ` comes from bench
answer 1 (do NOT guess; a wrong value makes every clock wrong instead of
one path). Ship as an update to the arch/dts patch in `patches/`.
@@ -50,5 +50,5 @@ one path). Ship as an update to the arch/dts patch in `patches/`.
## Bench access note
2026-08-30: c8a3 is physically dark (CP2102 console silent through two
remote power cycles; both network paths down) needs hands at the bench
remote power cycles; both network paths down), needs hands at the bench
before the measurements can run.
+9 -9
View File
@@ -1,4 +1,4 @@
# GT911 capacitive touch VERIFIED on warden-c8a3 (2026-08-25)
# GT911 capacitive touch: VERIFIED on warden-c8a3 (2026-08-25)
Touch works on our self-built 6.18.46 (`_b`): the WardenOS UI responds to
taps/swipes (confirmed by hand on the physical panel). Objective evidence:
@@ -8,7 +8,7 @@ Goodix-TS 3-0014: ID 911, version: 1060
input: Goodix Capacitive TouchScreen as .../i2c-3/3-0014/input/input0
```
`/dev/input/event0` is created and held open by `warden-ui` (pid 517, exclusive
EVIOCGRAB which is why a second reader sees 0 bytes; the events go to the UI).
EVIOCGRAB: which is why a second reader sees 0 bytes; the events go to the UI).
## Root cause on `_b`
@@ -20,12 +20,12 @@ built struct module size at run time
The **stock rootfs ships `goodix.ko` built for the 5.10 kernel**; it cannot load
on our 6.18 (`struct module` layout mismatch). Our 6.18 `.config` did **not** have
the driver at all (`# CONFIG_TOUCHSCREEN_GOODIX is not set`), and the DTS had no
GT911 node so nothing drove the GT911.
GT911 node, so nothing drove the GT911.
## Fix
1. **`CONFIG_TOUCHSCREEN_GOODIX=y`** build the mainline Goodix driver *into* the
kernel (no module no vermagic/struct mismatch; the stale rootfs `.ko` still
1. **`CONFIG_TOUCHSCREEN_GOODIX=y`**: build the mainline Goodix driver *into* the
kernel (no module -> no vermagic/struct mismatch; the stale rootfs `.ko` still
fails to insmod but is now harmless).
2. **GT911 DT node** under `&i2c3` (matches the vendor 86-panel wiring):
```
@@ -43,15 +43,15 @@ GT911 node — so nothing drove the GT911.
Polarity note: mainline goodix drives reset **logical 0 = hold, 1 = release**, and
the GT911 reset is physically active-low, so `reset-gpios` is **ACTIVE_HIGH**
(logical==physical) NOT the ACTIVE_LOW the vendor 5.10 driver used. Confirmed
(logical==physical), NOT the ACTIVE_LOW the vendor 5.10 driver used. Confirmed
against mainline gt911 DT examples (sun7i-a20-wexler-tab7200 etc.): both irq-gpios
and reset-gpios are ACTIVE_HIGH; reg 0x14 needs irq-gpios for address select.
Non-fatal: `Direct firmware load for goodix_911_cfg.bin failed (-2)` the GT911
Non-fatal: `Direct firmware load for goodix_911_cfg.bin failed (-2)`; the GT911
uses its flashed internal config; touch works without a cfg.bin.
## Files changed (research/linux-6.18.46)
- `.config` `CONFIG_TOUCHSCREEN_GOODIX=y`.
- `arch/arm/boot/dts/rockchip/rv1106-warden.dts` GT911 node on `&i2c3`,
- `.config`: `CONFIG_TOUCHSCREEN_GOODIX=y`.
- `arch/arm/boot/dts/rockchip/rv1106-warden.dts`: GT911 node on `&i2c3`,
`tp_rst`/`tp_irq` pin groups under `&pinctrl`.
@@ -1,7 +1,7 @@
* RV1106 USB2 PHY: single OTG port at 0xff3e0000 (ported from the vendor driver;
* fields map 1:1 to 6.18 except utmi_iddig -> utmi_id, and the 5.10-only
* iddig_output/iddig_en/bvalid_grf_sel are dropped). Signal-quality phy_tuning is
* left off for now (guarded, so NULL is safe) the phy is functional without it.
* left off for now (guarded, so NULL is safe): the phy is functional without it.
*/
static const struct rockchip_usb2phy_cfg rv1106_phy_cfgs[] = {
{
+133 -133
View File
@@ -1,50 +1,50 @@
# AIC8800 WiFi+BT SDIO driver 5.10 6.18 port plan
# AIC8800 WiFi+BT SDIO driver: 5.10 -> 6.18 port plan
Target: the M5 milestone of `../PORT-STATUS.md` / `../../docs/bringup.md` port
Target: the M5 milestone of `../PORT-STATUS.md` / `../../docs/bringup.md`, port
the AIC8800 SDIO WiFi+BT driver onto the self-built **Linux 6.18.46** kernel that
already boots WardenOS on `warden-c8a3` (M0M3 done; M4 display in progress). No
plan44 code (per the 2026-08-23 decision in `bringup.md`) this is a direct
already boots WardenOS on `warden-c8a3` (M0-M3 done; M4 display in progress). No
plan44 code (per the 2026-08-23 decision in `bringup.md`): this is a direct
forward-port of our own vendor source, same method already proven for
clk/pinctrl/mach/mmc.
## 0. Source recommendation
**Port from our own vendor SDK source**, not from any external fork:
`flare-edge/sdk/sysdrv/drv_ko/wifi/aic8800dc/` (§1 below is the full map). This
is not a default-to-what-we-have choice it's the correct one on the evidence:
`flare-edge/sdk/sysdrv/drv_ko/wifi/aic8800dc/` (section 1 below is the full map). This
is not a default-to-what-we-have choice; it's the correct one on the evidence:
- **Nothing more upstream exists.** AICSemi has no public upstream driver repo
with a stable, discoverable URL, and there is no aic8800 code in Linux
`staging` or any LKML patch series (mainline/staging status: **absent**
`staging` or any LKML patch series (mainline/staging status: **absent**,
confirmed by a dedicated web-research pass; no counter-evidence found).
There is nothing more "canonical" to port from than the vendor source we
already have.
- **Our tree already carries two hardware-verified fixes newer forks don't
have**: the `queue_sz`-zero console-flood clamp
(`flare-edge/*/sdk-patches/wifi/patches/0001-aic8800dc-no-zero-timeout-spin-and-ratelimit.patch`)
and the SDIO-wakeup busy-spinsleep Tier-1 hardening (live in the vendor tree
and the SDIO-wakeup busy-spin->sleep Tier-1 hardening (live in the vendor tree
today per `luckfox-pico-86-panel/wifi-bluetooth-aic8800.md`, tracked as
`future-features-2/sdk-patches/wifi/patches/0002-aic8800dc-sdio-wakeup-sleep-not-spin.patch`
**not yet copied into this branch's `sdk-patches/`; carry both forward into
`future-features-2/sdk-patches/wifi/patches/0002-aic8800dc-sdio-wakeup-sleep-not-spin.patch`,
**not yet copied into this branch's `sdk-patches/`; carry both forward into
the ported tree, and file the housekeeping gap separately**). Starting from a
third-party fork would mean re-discovering and re-fixing both bugs on
hardware.
- **The vendor source already anticipates part of the delta.** `rwnx_compat.h`
has `#if LINUX_VERSION_CODE` shims up to `KERNEL_VERSION(5, 15, 60)` e.g.
has `#if LINUX_VERSION_CODE` shims up to `KERNEL_VERSION(5, 15, 60)`, e.g.
`rwnx_main.c`'s `net_device_ops` already switches between `.ndo_do_ioctl` and
`.ndo_siocdevprivate` at the 5.15 boundary (rwnx_main.c:14571474). That
`.ndo_siocdevprivate` at the 5.15 boundary (rwnx_main.c:1457-1474). That
removes one whole item from the delta checklist below at zero cost.
- **A cleaner community fork exists but is not itself a base it's a reference.**
- **A cleaner community fork exists but is not itself a base: it's a reference.**
`radxa-pkg/aic8800` (github.com/radxa-pkg/aic8800) is an actively maintained,
GPL-3.0, DKMS-packaged build of the same AICSemi driver family, explicitly
patched for kernel **6.12/6.13** on Rockchip SDIO boards (Rock 3C/5C), and is
the only community effort found that has already absorbed the post-5.15
cfg80211/netdev/timer churn this port needs. **Use it the same way the
pinctrl port used the upstream RV1106 patch series as a correctness
pinctrl port used the upstream RV1106 patch series, as a correctness
oracle to diff against**, not as code we vendor: it targets AIC8800D80 SDIO/USB/PCIe
variants generically, not our AIC8800DC + our two local hardening patches,
and an Armbian forum thread reports open regressions on it at the 6.12+
boundary so treat its fixes as a second opinion on the wiphy-lock and
boundary, so treat its fixes as a second opinion on the wiphy-lock and
timer-rename hunks, verify each independently.
- **No mainline/staging path exists to lean on instead.** Confirmed by the
research pass: no aic8800 in `drivers/net/wireless/`, `staging/`, or any
@@ -72,7 +72,7 @@ aic8800dc/
Makefile # obj-$(CONFIG_AIC8800_BTLPM_SUPPORT) += aic8800_btlpm/
# obj-$(CONFIG_AIC8800_WLAN_SUPPORT) += aic8800_fdrv/
# obj-$(CONFIG_AIC_WLAN_SUPPORT) += aic8800_bsp/
# (link order = bsp, fdrv, btlpm matches the insmod order below)
# (link order = bsp, fdrv, btlpm, matches the insmod order below)
aic8800_bsp/ # SDIO bus glue + firmware bootstrap ("bsp" = board support package)
aic_bsp_main.c # module_init/exit; per-chip firmware filename tables (fw_u02,
# fw_8800dc_u01, fw_8800dc_u02, ...); aicbsp_probe_semaphore
@@ -80,7 +80,7 @@ aic8800dc/
# wildcard match + internal vendor/device ID probe:
# SDIO_VENDOR_ID_AIC8800DC=0xc8a1, SDIO_DEVICE_ID_AIC8800DC=0xc08d
# (aicsdio.c:75,80); calls rockchip_wifi_power()/
# rockchip_wifi_set_carddetect() (aicsdio.c:515-580) see §3.7
# rockchip_wifi_set_carddetect() (aicsdio.c:515-580), see section 3.7
aic8800dc_compat.c/.h, aic8800d80_compat.c/.h # per-chip-variant glue
aicwf_txq_prealloc.c, md5.c, aic_bsp_driver.c/.h, aicwf_firmware_array.c/.h
aic8800_fdrv/ # the actual cfg80211 full-MAC driver ("fdrv" = fullmac driver)
@@ -88,51 +88,51 @@ aic8800dc/
# (5732), wiphy_register() (6035); struct net_device_ops
# rwnx_netdev_ops (1457) / rwnx_netdev_monitor_ops (1477);
# 3 rtnl_lock()/rtnl_unlock() pairs (5429,6063,6097)
rwnx_msg_rx.c # firmwaredriver event handling incl. cfg80211_connect_result()
rwnx_msg_rx.c # firmware->driver event handling incl. cfg80211_connect_result()
# (line 958) and cfg80211_roamed() (1006,1010)
rwnx_cfgfile.c, rwnx_tx.c, rwnx_rx.c, rwnx_txq.c # datapath
aicwf_sdio.c # the actual SDIO transport (`aicwf_sdio_driver`, line 1216)
aicwf_sdio.c # the actual SDIO transport (`aicwf_sdio_driver`, line 1216),
# THIS is the file the task's "aicwf_sdio" driver name refers to;
# it lives inside aic8800_fdrv/, not a separate directory. Also
# calls rockchip_wifi_power()/set_carddetect() (1260-1331) and
# contains the Tier-1 wakeup-sleep hardening (§0) around the
# contains the Tier-1 wakeup-sleep hardening (section 0) around the
# `aicwf_sdio_wakeup()` retry loop (~line 1363 in the older
# line numbering cited by the wiki; grep for `usleep_range`).
aicwf_tcp_ack.c, aicwf_rx_prealloc.c, aic_priv_cmd.c, aic_vendor.c
rwnx_compat.h # vendor compat shim layer, #if LINUX_VERSION_CODE guards up to
# 5.15.60 ONLY everything past that (timer renames, wiphy
# 5.15.60 ONLY: everything past that (timer renames, wiphy
# locking, netif_rx_ni removal) is new territory, not covered.
usb_host.c/.h, rwnx_pci.*, rwnx_mesh.* # dead code on this board (no USB/PCI variant used)
aic8800_btlpm/ # Bluetooth low-power-mode / HCI wake companion module
aic8800_btlpm.c, aic_bluetooth_main.c, lpm.c, rfkill.c # standard rfkill_register(), no
# Rockchip-specific RFKILL_RK dependency (checked, none found)
aic8800dc_fw/ # firmware blobs, see §5
aic8800dc_fw/ # firmware blobs, see section 5
```
**Modules actually `insmod`ed on the running 5.10 panel** (from
`sdk/sysdrv/drv_ko/wifi/insmod_wifi.sh:109-124`, the `#aic8800` stanza gated on
`/proc/device-tree/model` containing `"W"` or an SDIO uevent match):
```
cfg80211.ko libarc4.ko ctr.ko ccm.ko libaes.ko aes_generic.ko
aic8800_bsp.ko (sleep 0.2s) aic8800_fdrv.ko (sleep 2s) aic8800_btlpm.ko (sleep 0.1s)
cfg80211.ko -> libarc4.ko -> ctr.ko -> ccm.ko -> libaes.ko -> aes_generic.ko
-> aic8800_bsp.ko (sleep 0.2s) -> aic8800_fdrv.ko (sleep 2s) -> aic8800_btlpm.ko (sleep 0.1s)
```
`rkwifi_server` is deliberately never started (WardenOS owns `wlan0` directly
`rkwifi_server` is deliberately never started (WardenOS owns `wlan0` directly,
see `insmod_wifi.sh:126-137` and `wifi-bluetooth-aic8800.md`); nothing to
replicate there. The crypto modules (arc4/ctr/ccm/aes) are dependencies of the
driver's internal key-handling, not aic8800-specific confirm they're already
driver's internal key-handling, not aic8800-specific, confirm they're already
`=y`/reachable in the 6.18 config (crypto is currently listed as "[ ] batch2" in
`../DRIVER-PARITY.md`; flip alongside this work).
**DT node correction to the task's framing.** The task description assumed
**DT node: correction to the task's framing.** The task description assumed
`sdio: mmc@ff9a0000`. **That is wrong for this board.** The AIC8800 is wired to
**`sdmmc: mmc@ffaa0000`** (mmc1), not `sdio: mmc@ff9a0000` (mmc2) confirmed
**`sdmmc: mmc@ffaa0000`** (mmc1), not `sdio: mmc@ff9a0000` (mmc2), confirmed
directly in `rv1106g-luckfox-pico-86panel.dts:83-97` (comment literally reads
`/**********SDIO-WIFI**********/` over the `&sdmmc` node) and independently in
`luckfox-pico-86-panel/wifi-bluetooth-aic8800.md:11`/`hardware-86-panel.md:69`.
`&sdio` (mmc@ff9a0000) stays `status = "disabled"` and is unused on this board.
There is **no separate DT child node** for the aic8800 chip itself it's
There is **no separate DT child node** for the aic8800 chip itself: it's
discovered purely by SDIO bus-scan + vendor/device ID match inside the driver
(`aicsdio.c`, no `of_match_table` anywhere in the tree checked, none found);
(`aicsdio.c`, no `of_match_table` anywhere in the tree, checked, none found);
the only DT surface is the MMC controller node plus the power-sequencing node:
```dts
sdio_pwrseq: sdio-pwrseq { /* rv1106g-luckfox-pico-86panel.dts:20-23 */
@@ -156,50 +156,50 @@ sdio_pwrseq: sdio-pwrseq { /* rv1106g-luckfox-pico-86panel.
```
The `sdmmc0_{clk,cmd,bus4,det}` pin groups are defined in
`sdk/sysdrv/source/kernel/arch/arm/boot/dts/rv1106-pinctrl.dtsi:700-731` (a
separate include from `rv1106.dtsi`) port that block, it isn't in the DT
files the M1M3 work already ported.
separate include from `rv1106.dtsi`): port that block, it isn't in the DT
files the M1-M3 work already ported.
## 2. Open-source landscape (web research)
- **AICSemi upstream**: no discoverable, stable public GitHub org/repo carrying
aic8800_bsp/fdrv/btlpm as canonical upstream vendor releases exist only
aic8800_bsp/fdrv/btlpm as canonical upstream, vendor releases exist only
as SoC-vendor SDK drops (Rockchip's, in our case). Nothing more upstream to
point at than what we have.
- **Mainline/staging**: **absent.** No aic8800 anywhere in `drivers/net/wireless/`,
`drivers/staging/`, or any LKML/patchwork series as of this research pass.
- **Cleanest newer-kernel fork found**: `radxa-pkg/aic8800`
(github.com/radxa-pkg/aic8800) actively maintained, GPL-3.0, DKMS-packaged,
(github.com/radxa-pkg/aic8800), actively maintained, GPL-3.0, DKMS-packaged,
patched for kernel **6.12/6.13** on Rockchip SDIO boards (Radxa Rock 3C/5C),
covering SDIO/USB/PCIe AIC8800D80 variants. Firmware ships as a companion
`aic8800-firmware` package into `/lib/firmware/aic8800_fw/`. Treat as a
**reference/oracle for the API-delta hunks** (§3), not a vendoring source
see §0 for why. An Armbian forum thread ("AIC8800 wifi sdio module not
**reference/oracle for the API-delta hunks** (section 3), not a vendoring source,
see section 0 for why. An Armbian forum thread ("AIC8800 wifi sdio module not
working with kernel 6.12+") reports it has its own open regressions at that
boundary, so cross-check rather than trust each hunk blindly.
- **Other community efforts** (Armbian, LuckFox's own OpenWrt branch, generic
BananaPi/OpenWrt feeds): no aic8800 kmod bundled by default even where the DT
wiring exists `luckfox-pico-86-panel/alternative-bsps.md:20` documents this
wiring exists, `luckfox-pico-86-panel/alternative-bsps.md:20` documents this
exact gap for LuckFox's own OpenWrt target (`cortexa7.mk` ships `kmod-rknpu-rockchip`
only, no `kmod-aic8800`). Confirms there is no ready-made newer-kernel package
to pull instead of porting.
- **plan44's OpenWrt RV1106 fork** (`flare-edge/research/plan44-openwrt/`,
Linux 6.6, 152 RV1106 patches): checked directly **contains no aic8800
Linux 6.6, 152 RV1106 patches): checked directly, **contains no aic8800
code at all** (`find ... -ipath '*aic8800*'` empty). Confirms
`bringup.md`'s "AIC8800 wifi (plan44 has none; ours)" and the "no plan44
code" decision doesn't cost us anything here there's nothing to take.
code" decision doesn't cost us anything here: there's nothing to take.
## 3. 5.10 6.18 API-delta checklist
## 3. 5.10 -> 6.18 API-delta checklist
Grounded in two passes: (a) direct kernel.org/bootlin/LWN research on 5.106.18
Grounded in two passes: (a) direct kernel.org/bootlin/LWN research on 5.10->6.18
API history, (b) `grep` evidence from the actual vendor source (file:line cited
below) so this is a checklist against *our* code, not a generic survey.
### 3.1 Timers confirmed hard breaks, exact call sites found
`del_timer_sync`/`del_timer` **`timer_delete_sync`/`timer_delete`**: renamed
### 3.1 Timers: confirmed hard breaks, exact call sites found
`del_timer_sync`/`del_timer` -> **`timer_delete_sync`/`timer_delete`**: renamed
in `9b13df3fb64e` (landed v6.2-rc1) as a **compat-wrapped** rename; **the
compat wrapper was removed in v6.15-rc1**, so by 6.18 the old names **do not
exist**. `from_timer()` **`timer_container_of()`**: a real mainline rename
(~6.146.15 treewide timer-API cleanup; exact tag unconfirmed, verify against
exist**. `from_timer()` -> **`timer_container_of()`**: a real mainline rename
(~6.14-6.15 treewide timer-API cleanup; exact tag unconfirmed, verify against
the actual 6.18.46 headers already unpacked at
`flare-edge/research/linux-6.18.46/include/linux/timer.h`). Every call site in
the driver, found by direct grep (not estimated):
@@ -209,11 +209,11 @@ the driver, found by direct grep (not estimated):
| `from_timer()` | `aic8800_bsp/aicsdio.c:1442`; `aic8800_fdrv/aicwf_sdio.c:279,317,2933`; `aic8800_fdrv/rwnx_main.c:1724`; `aic8800_fdrv/rwnx_rx.c:1751,2027` |
| `del_timer_sync()` | `aic8800_bsp/aicsdio.c:1677`; `aic8800_fdrv/aicwf_sdio.c:1048,1054,1297,1303,3152`; `aic8800_fdrv/rwnx_main.c:2181`; `aic8800_fdrv/rwnx_rx.c:1501,1526` |
| `del_timer()` (non-sync) | `aic8800_fdrv/aicwf_tcp_ack.c:108,367,401,462`; `aic8800_fdrv/rwnx_rx.c:1921,2607`; `aic8800_btlpm/aic8800_btlpm.c:580,603,951`; `aic8800_btlpm/lpm.c:558,581,935` |
| `setup_timer()` (pre-4.14 dead branch) | `aic8800_fdrv/aicwf_tcp_ack.c:84` already `#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)` guarded against a live `timer_setup()` branch; **delete the dead `#if` branch**, don't port it |
| `setup_timer()` (pre-4.14 dead branch) | `aic8800_fdrv/aicwf_tcp_ack.c:84`: already `#if LINUX_VERSION_CODE < KERNEL_VERSION(4,14,0)` guarded against a live `timer_setup()` branch; **delete the dead `#if` branch**, don't port it |
| `timer_setup()` calls (unaffected, just listed for completeness) | `aicsdio.c:2059`; `aicwf_sdio.c:3564,3589,3590`; `rwnx_main.c:1841,6118`; `rwnx_rx.c:1433,2510`; `aicwf_tcp_ack.c:87`; both btlpm files:1104/1054 |
Mechanical fix: sed-rename `from_timer``timer_container_of`, `del_timer_sync`
`timer_delete_sync`, `del_timer``timer_delete` across the ~20 call sites above.
Mechanical fix: sed-rename `from_timer`->`timer_container_of`, `del_timer_sync`->
`timer_delete_sync`, `del_timer`->`timer_delete` across the ~20 call sites above.
`timer_setup()` itself is unchanged.
### 3.2 netdev
@@ -221,25 +221,25 @@ Mechanical fix: sed-rename `from_timer`→`timer_container_of`, `del_timer_sync`
around 5.14/5.15)**: **already handled** by the vendor. `rwnx_main.c:1457-1474`
already has `#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0)` /
`#else .ndo_siocdevprivate = rwnx_do_ioctl,` for both `rwnx_netdev_ops` and
`rwnx_netdev_monitor_ops`. Zero work needed the guard picks the 6.18-correct
`rwnx_netdev_monitor_ops`. Zero work needed: the guard picks the 6.18-correct
member automatically. **Verify only** that `rwnx_do_ioctl`'s body (line 1368)
still compiles against the `ndo_siocdevprivate` signature
(`int (*)(struct net_device *, struct ifreq *, void __user *, int)` vs the
old ioctl signature) check on the actual 6.18.46 headers.
old ioctl signature), check on the actual 6.18.46 headers.
- **`netif_rx_ni()` removed (5.18, merged into plain `netif_rx()`, safe from any
context)**: three call sites, **not** version-guarded: `rwnx_rx.c:404,598,1651`.
Mechanical fix: `netif_rx_ni(rx_skb)` `netif_rx(rx_skb)`.
Mechanical fix: `netif_rx_ni(rx_skb)` -> `netif_rx(rx_skb)`.
- **`netif_napi_add()` weight arg dropped (~6.1)**: **not used** anywhere in
this driver (checked, no `netif_napi_add`/NAPI in the tree the driver does
this driver (checked, no `netif_napi_add`/NAPI in the tree, the driver does
its own kthread-based RX processing, not NAPI polling). No action.
### 3.3 cfg80211 the hard, non-mechanical part
### 3.3 cfg80211: the hard, non-mechanical part
**Verified directly against `flare-edge/research/linux-6.18.46/include/net/cfg80211.h`**
(the actual target tree, not a guess from release notes):
- **`cfg80211_connect_result()`**: **confirmed unchanged and safe.** In 6.18.46
it's a `static inline` that just forwards to `cfg80211_connect_bss()`
(`cfg80211.h:8654-8661`), with the exact same 8-argument signature the driver
already calls at `rwnx_msg_rx.c:958`. `cfg80211_roamed()` also **confirmed
already calls at `rwnx_msg_rx.c:958`. `cfg80211_roamed()`, also **confirmed
unchanged**: 6.18.46 signature is
`cfg80211_roamed(struct net_device *dev, struct cfg80211_roam_info *info, gfp_t gfp)`
(`cfg80211.h:8748`), matching the driver's call at `rwnx_msg_rx.c:1006,1010`
@@ -248,98 +248,98 @@ Mechanical fix: sed-rename `from_timer`→`timer_container_of`, `del_timer_sync`
`cfg80211_connect_bss()` directly is optional cleanup, not a requirement.
- **`wiphy_new()`/`wiphy_new_nm()`**: **confirmed unchanged.**
`wiphy_new(const struct cfg80211_ops *ops, int sizeof_priv)`
(`cfg80211.h:6250-6253`, inline wrapper over `wiphy_new_nm(ops, sizeof_priv, NULL)`)
matches `rwnx_main.c:5732`'s `wiphy_new(&rwnx_cfg80211_ops, sizeof(struct rwnx_hw))`
(`cfg80211.h:6250-6253`, inline wrapper over `wiphy_new_nm(ops, sizeof_priv, NULL)`):
matches `rwnx_main.c:5732`'s `wiphy_new(&rwnx_cfg80211_ops, sizeof(struct rwnx_hw))`
exactly. No signature-driven work needed.
- **Wiphy locking overhaul real, confirmed present, and now precisely scoped
- **Wiphy locking overhaul, real, confirmed present, and now precisely scoped
(not a guess).** `cfg80211.h` (~6266, 6325-6362) confirms `wiphy_lock()`/
`wiphy_unlock()`/`lockdep_assert_wiphy()` and a `struct wiphy_work` deferred-work
mechanism all exist in 6.18.46, and the load-bearing sentence, directly
from the `wiphy_lock()` doc comment **"When cfg80211 ops are called, the
mechanism all exist in 6.18.46, and (the decisive sentence, directly
from the `wiphy_lock()` doc comment) **"When cfg80211 ops are called, the
wiphy is already locked."** That means:
- The driver's `cfg80211_ops` callbacks themselves (`rwnx_cfg80211_scan`,
`_connect`, `_disconnect`, `_add_key`, `_mgmt_tx`, etc., `rwnx_main.c:5365-5382`)
need **no new locking** cfg80211 core now takes the wiphy mutex before
need **no new locking**: cfg80211 core now takes the wiphy mutex before
calling into any of them, where 5.10-era cfg80211 relied on the caller
holding RTNL instead.
- The real risk is the **other direction**: this driver calls
`cfg80211_scan_done()` / `cfg80211_connect_result()` / `cfg80211_roamed()`
from **firmware-event handling, not from inside an ops callback**
from **firmware-event handling, not from inside an ops callback**,
`rwnx_msg_rx.c` (async, driven by SDIO RX) and `rwnx_main.c:1212,2082,2168`
(also async paths, not the ops entry points). Multiple `cfg80211.h` doc
comments for adjacent notification APIs state "the caller must hold ...
wiphy mutex" (e.g. `cfg80211.h:9428`: "Caller must hold wiphy mutex,
therefore must only be called from sleepable context") the pattern that
therefore must only be called from sleepable context"), the pattern that
replaced the old "hold RTNL" requirement. **Concretely: every
`cfg80211_*` notification call reached from `rwnx_msg_rx.c` /
the async paths in `rwnx_main.c` needs `wiphy_lock(wiphy)` /
`wiphy_unlock(wiphy)` wrapped around it that wasn't there before** (5.10
only needed `rtnl_lock()`, which this driver's 3 existing
`rtnl_lock()`/`rtnl_unlock()` sites at `rwnx_main.c:5429/5433,
6063/6075, 6097/6102` already show it knows how to take defensively the
6063/6075, 6097/6102` already show it knows how to take defensively: the
fix is adding the wiphy-mutex equivalent at the async notification sites,
not at those 3 sites, which are netdev-registration paths and likely stay
RTNL-only).
- This is still the single highest-effort item in the port not because the
- This is still the single highest-effort item in the port, not because the
contract is unknown (it's now confirmed above), but because applying it
correctly means auditing every asynccfg80211 call site in `rwnx_msg_rx.c`
correctly means auditing every async->cfg80211 call site in `rwnx_msg_rx.c`
and the async branches of `rwnx_main.c` (1212, 2082, 2168, 2445-2645) one
by one, and because a wrong lock order (wiphy mutex vs. RTNL vs. this
driver's own internal locks/semaphores) produces lockdep splats or
deadlocks that only show up under real traffic, not at compile time. Cross-
check each hunk against how `radxa-pkg/aic8800` (§0) handled the same
transition on its 6.12/6.13 port it hit this exact wall first.
check each hunk against how `radxa-pkg/aic8800` (section 0) handled the same
transition on its 6.12/6.13 port: it hit this exact wall first.
### 3.4 SDIO/MMC
No breaking changes found in `sdio_driver`, `sdio_claim_host`/`release_host`,
`sdio_readb`/`writesb`, `sdio_set_block_size` between 5.10 and 6.18 (low
research depth on this axis treat as low-risk, smoke-test rather than
research depth on this axis, treat as low-risk, smoke-test rather than
line-audit). The mainline `dw_mmc`/`dw_mmc-rockchip` host driver is already
proven on 6.18 for eMMC (`../DRIVER-PARITY.md`: `dw_mmc (eMMC) | mainline | [x] M3`)
and `CONFIG_MMC_DW_ROCKCHIP=y` is already in the live `.config` the SDIO
and `CONFIG_MMC_DW_ROCKCHIP=y` is already in the live `.config`, the SDIO
*controller* side of this port is de-risked; only the AIC8800 *card driver*
above the `sdmmc` bus is new work.
### 3.5 proc_ops
`file_operations``proc_ops` for procfs landed in **5.6** already true at the
`file_operations`->`proc_ops` for procfs landed in **5.6**: already true at the
5.10 baseline this driver was written against, and no further proc_ops changes
were found 5.106.18. The three files using `proc_ops`/`proc_create`
were found 5.10->6.18. The three files using `proc_ops`/`proc_create`
(`aicwf_sdio.c`, `aic8800_btlpm.c`, `lpm.c`) should need no change here.
### 3.6 DMA
No breaking coherent/streaming DMA API changes found 5.106.18 relevant to this
No breaking coherent/streaming DMA API changes found 5.10->6.18 relevant to this
driver; SDIO drivers ride MMC-core DMA rather than calling
`dma_alloc_coherent`/`dma_map_single` directly for the card-side data path.
Low risk, not independently line-audited flag if the build surfaces anything.
Low risk, not independently line-audited: flag if the build surfaces anything.
### 3.7 Vendor/Rockchip-only helpers real mainline gap, not a version delta
### 3.7 Vendor/Rockchip-only helpers: real mainline gap, not a version delta
`rockchip_wifi_power()` / `rockchip_wifi_set_carddetect()` (declared in
`include/linux/rfkill-wlan.h`, implemented in `net/rfkill/rfkill-wlan.c` in the
**vendor 5.10 tree only** this is a Rockchip-BSP-vendor subsystem, not
**vendor 5.10 tree only**: this is a Rockchip-BSP-vendor subsystem, not
mainline Linux, and does not exist in `flare-edge/research/linux-6.18.46/`).
Call sites: `aic8800_bsp/aicsdio.c:515,517,556,580` and
`aic8800_fdrv/aicwf_sdio.c:1260,1262,1329,1331` (all under
`#ifdef CONFIG_PLATFORM_ROCKCHIP`, which is true for this board). **This is not
a rename there is nothing to rename to.** Recommended fix: **stub these
a rename: there is nothing to rename to.** Recommended fix: **stub these
calls out entirely** rather than port `rfkill-wlan.c`. The DT already declares
a standard mainline `mmc-pwrseq-simple` (`sdio_pwrseq`, §1) bound via
a standard mainline `mmc-pwrseq-simple` (`sdio_pwrseq`, section 1) bound via
`mmc-pwrseq = <&sdio_pwrseq>`, which the mainline MMC core already drives at
bus-scan/power-up time through `drivers/mmc/core/pwrseq_simple.c` a real
bus-scan/power-up time through `drivers/mmc/core/pwrseq_simple.c`, a real
mainline mechanism doing the same job (GPIO power/reset sequencing) these
vendor calls were a pre-DT-pwrseq-era stand-in for. `rockchip_wifi_set_carddetect()`
is likewise redundant for a `non-removable` MMC device, which mainline already
auto-rescans. Treat every call site as `#if 0`/deleted, not ported.
`get_cpu_version`/`rockchip_soc_id`-style helpers: **not called anywhere** in
this driver (checked, no hits) the task's assumption that this driver calls
this driver (checked, no hits), the task's assumption that this driver calls
such a helper does not hold; no action needed. `module_param`, `kthread_run`/
`kthread_should_stop`: stable, unaffected used extensively (RX/TX kthreads in
`kthread_should_stop`: stable, unaffected, used extensively (RX/TX kthreads in
`aicwf_sdio.c`), no changes needed.
### 3.8 Firmware loading
`request_firmware`/`request_firmware_nowait`/`release_firmware`: stable
5.106.18, no signature changes found. Note this driver's actual firmware load
5.10->6.18, no signature changes found. Note this driver's actual firmware load
path is `AIC_FW_PATH`-relative direct file read via its own loader
(`aic_load_fw`/`CONFIG_USE_FW_REQUEST` is `?= n` in the Makefile the vendor
(`aic_load_fw`/`CONFIG_USE_FW_REQUEST` is `?= n` in the Makefile, the vendor
driver does **not** use the standard `request_firmware()` API by default, it
reads firmware files from a configurable path itself). Confirm this stays true
after the port; it's a deliberate vendor choice, not a version-driven gap.
@@ -347,31 +347,31 @@ after the port; it's a deliberate vendor choice, not a version-driven gap.
## 4. File / config / DT port plan
### 4.1 Files to bring into the 6.18 tree
Copy `aic8800dc/{aic8800_bsp,aic8800_fdrv,aic8800_btlpm}/*.{c,h}` (source only
Copy `aic8800dc/{aic8800_bsp,aic8800_fdrv,aic8800_btlpm}/*.{c,h}` (source only:
exclude every generated `.o`/`.ko`/`.mod.*`/`.cmd`/`Module.symvers`/
`modules.order` artifact already sitting in the vendor tree from prior
out-of-tree builds) into the 6.18 tree at:
```
flare-edge/research/linux-6.18.46/drivers/net/wireless/aic8800/
Kconfig # new: top-level "source" wrapper, see 4.2
Makefile # obj-y for the 3 subdirs, preserving bspfdrvbtlpm link order
Makefile # obj-y for the 3 subdirs, preserving bsp->fdrv->btlpm link order
aic8800_bsp/ (from aic8800dc/aic8800_bsp/)
aic8800_fdrv/ (from aic8800dc/aic8800_fdrv/, minus usb_host.c/rwnx_pci.*/rwnx_mesh.* dead
aic8800_fdrv/ (from aic8800dc/aic8800_fdrv/, minus usb_host.c/rwnx_pci.*/rwnx_mesh.*: dead
code on this board's SDIO-only, non-mesh config; keep out unless a build
error proves them referenced elsewhere)
aic8800_btlpm/ (from aic8800dc/aic8800_btlpm/)
```
This is exactly the path the vendor's own `Kconfig` already assumes
(`source "drivers/net/wireless/aic8800/aic8800_fdrv/Kconfig"`, §1) — no path
rewriting needed inside the sub-Kconfigs. Firmware blobs (§5) go to
(`source "drivers/net/wireless/aic8800/aic8800_fdrv/Kconfig"`, section 1), no path
rewriting needed inside the sub-Kconfigs. Firmware blobs (section 5) go to
`aic8800dc_fw/` under the rootfs firmware path, not into the kernel tree.
Apply, in this order, on top of the copied source: the `0001` queue_sz clamp
patch, and the Tier-1 SDIO-wakeup-sleep patch (currently only tracked under
`flare-edge/future-features-2/sdk-patches/wifi/patches/0002-aic8800dc-sdio-wakeup-sleep-not-spin.patch`
pull it from there; it is *not* in this branch's own `sdk-patches/wifi/`, a
`flare-edge/future-features-2/sdk-patches/wifi/patches/0002-aic8800dc-sdio-wakeup-sleep-not-spin.patch`:
pull it from there; it is *not* in this branch's own `sdk-patches/wifi/`, a
separate housekeeping gap worth closing regardless of this port). Then apply
the API-delta fixes from §3.
the API-delta fixes from section 3.
### 4.2 Kconfig wiring
Add one line to `flare-edge/research/linux-6.18.46/drivers/net/wireless/Kconfig`
@@ -380,13 +380,13 @@ list, `Kconfig:22-38`):
```
source "drivers/net/wireless/aic8800/Kconfig"
```
New `drivers/net/wireless/aic8800/Kconfig` carry the vendor's `aic8800dc/Kconfig`
New `drivers/net/wireless/aic8800/Kconfig`, carry the vendor's `aic8800dc/Kconfig`
content forward nearly verbatim (it already has the right shape: an
`AIC_WLAN_SUPPORT` bool gate, an `AIC_FW_PATH` string default, and `source`
lines for the fdrv/btlpm sub-Kconfigs) but change the two `tristate` symbols in
`aic8800_fdrv/Kconfig` (`AIC8800_WLAN_SUPPORT`) and `aic8800_btlpm/Kconfig`
(`AIC8800_BTLPM_SUPPORT`) to **default `y`**, and set `AIC_WLAN_SUPPORT`
default `y` per the task's requirement, everything built in, not modular,
default `y`, per the task's requirement, everything built in, not modular,
since the 6.18 rootfs has no working module-loading pipeline yet
(`../PORT-STATUS.md` M3 note: the old 5.10 `.ko`s already fail on 6.18 from
vermagic mismatch, and a rebuilt 6.18 module tree for Buildroot doesn't exist
@@ -395,7 +395,7 @@ yet either).
### 4.3 Config symbols (`=y`, built-in)
```
CONFIG_WIRELESS=y # already =y in the live 6.18 .config
CONFIG_CFG80211=y # already =y flipped in "batch2" per ../DRIVER-PARITY.md
CONFIG_CFG80211=y # already =y, flipped in "batch2" per ../DRIVER-PARITY.md
CONFIG_WLAN=y # already =y
CONFIG_MMC=y # already =y
CONFIG_MMC_DW_ROCKCHIP=y # already =y
@@ -403,67 +403,67 @@ CONFIG_RFKILL=y # already =y
CONFIG_AIC_WLAN_SUPPORT=y # new
CONFIG_AIC8800_WLAN_SUPPORT=y # new
CONFIG_AIC8800_BTLPM_SUPPORT=y # new
CONFIG_BT=y # currently =m in the live .config flip to =y for the same
CONFIG_BT=y # currently =m in the live .config, flip to =y for the same
# no-working-module-pipeline reason as the aic8800 pieces
CONFIG_BT_HCIUART=y # currently =m flip alongside CONFIG_BT
CONFIG_BT_HCIUART=y # currently =m, flip alongside CONFIG_BT
CONFIG_BT_HCIUART_H4=y # already =y (H4 is the transport this board's BT actually uses,
# per hardware-86-panel.md: UART1/ttyS1, hciattach -s 1500000
# ... any 1500000 flow nosleep)
CONFIG_CRYPTO_ARC4=y CONFIG_CRYPTO_CTR=y CONFIG_CRYPTO_CCM=y CONFIG_CRYPTO_AES=y # driver's
# internal key-handling deps, currently "[ ] batch2" in
# ../DRIVER-PARITY.md confirm =y, not =m, alongside this work
# ../DRIVER-PARITY.md, confirm =y, not =m, alongside this work
```
Do **not** enable `CONFIG_MAC80211` for this driver confirmed by source
inspection (§3.3 note, no `ieee80211_hw`/mac80211 symbol usage anywhere in
Do **not** enable `CONFIG_MAC80211` for this driver: confirmed by source
inspection (section 3.3 note, no `ieee80211_hw`/mac80211 symbol usage anywhere in
`aic8800_fdrv/`) that this is a pure `cfg80211_ops` full-MAC driver; mac80211
was only ever needed for the *other* vendor WiFi chips in the shared
`sdk/sysdrv/drv_ko/wifi/` tree (RTL8189FS etc.), not this one. Leaving it out
avoids pulling in a subsystem this port doesn't need. Do **not** carry
`CONFIG_RFKILL_RK` confirmed unused (`aic8800_btlpm/rfkill.c` calls the
`CONFIG_RFKILL_RK`: confirmed unused (`aic8800_btlpm/rfkill.c` calls the
standard mainline `rfkill_register()`, no Rockchip-specific rfkill hook).
### 4.4 DT changes
On the board DT that M4/M5 work extends
(`warden-sdk/kernel/rv1106-enablement/dts/rv1106-warden.dts` or its successor
(`warden-sdk/kernel/rv1106-enablement/dts/rv1106-warden.dts` or its successor,
follow the pattern of the M3 `&emmc` addition in `rv1106-warden-m2.dts:126-142`):
1. Port the `sdmmc0_{clk,cmd,bus4,det}` pinctrl group from
`sdk/sysdrv/source/kernel/arch/arm/boot/dts/rv1106-pinctrl.dtsi:700-731`
(not yet in the ported tree M1M3 only needed the eMMC/uart2/eth pin
(not yet in the ported tree: M1-M3 only needed the eMMC/uart2/eth pin
groups).
2. Add the `sdmmc: mmc@ffaa0000` node (clocks `HCLK_SDMMC`/`CCLK_SRC_SDMMC` off
`&cru`, `SCLK_SDMMC_DRV`/`SCLK_SDMMC_SAMPLE` off `&grf_cru` the same
`&cru`, `SCLK_SDMMC_DRV`/`SCLK_SDMMC_SAMPLE` off `&grf_cru`, the same
`grf_cru` dependency the M2 eMMC fix already established, so no new
prerequisite) and the `sdio_pwrseq` node, both transplanted verbatim from
§1's block (interrupt `GIC_SPI 52`, per `rv1106.dtsi:1403-1412` in the
section 1's block (interrupt `GIC_SPI 52`, per `rv1106.dtsi:1403-1412` in the
vendor tree).
3. `status = "okay"` on `&sdmmc`, matching the vendor board DT exactly (§1).
4. **Do not** touch `&sdio` (mmc@ff9a0000) leave `disabled`, it's genuinely
unused hardware on this board (§1 correction).
3. `status = "okay"` on `&sdmmc`, matching the vendor board DT exactly (section 1).
4. **Do not** touch `&sdio` (mmc@ff9a0000): leave `disabled`, it's genuinely
unused hardware on this board (section 1 correction).
### 4.5 Driver init-order note
The 5.10 module load order (`insmod_wifi.sh`, §1) is
`aic8800_bsp (200ms) aic8800_fdrv (2s!) aic8800_btlpm`, with real sleep
delays between each. Built-in (`=y`), there is no equivalent explicit delay
The 5.10 module load order (`insmod_wifi.sh`, section 1) is
`aic8800_bsp -> (200ms) -> aic8800_fdrv -> (2s!) -> aic8800_btlpm`, with real sleep
delays between each. Built-in (`=y`), there is no equivalent explicit delay:
initcall order is controlled by link order (the vendor Makefile's `obj-y` list
is already `aic8800_btlpm/ aic8800_fdrv/ aic8800_bsp/` in Makefile-declaration
order but Kbuild links `obj-y` in Makefile order regardless of which appears
first in the `ifeq` block **preserve `aic8800_bsp` before `aic8800_fdrv`
first in the `ifeq` block, **preserve `aic8800_bsp` before `aic8800_fdrv`
before `aic8800_btlpm` in the new `drivers/net/wireless/aic8800/Makefile`'s
`obj-y` list**, mirroring the working insmod order) and by each subsystem's
declared `module_init()`/initcall level (all three currently use plain
`module_init()`, which becomes `device_initcall` level when built-in same
`module_init()`, which becomes `device_initcall` level when built-in, same
level for all three, so link order is what decides relative sequencing among
them). The observed 2-second gap between `aic8800_bsp` and `aic8800_fdrv` on
the running 5.10 system is suspicious enough (firmware download + chip
bring-up time) that if the built-in probe races ahead of firmware readiness,
watch for it specifically during bring-up (§6) — this is a real risk the
watch for it specifically during bring-up (section 6): this is a real risk the
static-link change introduces that modular loading didn't have, not just a
formality.
## 5. Firmware notes
Firmware is not open source (binary blobs, as expected for WiFi/BT RF/PHY
patches) but is freely redistributable no export-control/NDA marking found on
patches) but is freely redistributable: no export-control/NDA marking found on
the files themselves or in the vendor tree's licensing. 21 files,
`sdk/sysdrv/drv_ko/wifi/aic8800dc/aic8800dc_fw/` (484 KB total):
```
@@ -476,25 +476,25 @@ fw_patch_{8800dc_u02,8800dc_u02_ext0,8800dc_u02h}.bin # BT patch firmware
fw_patch_table_8800dc_{u02,u02h}.bin # BT patch tables
lmacfw_rf_8800dc.bin # RF test-mode firmware
```
Per `aic_bsp_main.c`'s `fw_8800dc_u02[]` table (§1), the exact subset loaded at
Per `aic_bsp_main.c`'s `fw_8800dc_u02[]` table (section 1), the exact subset loaded at
runtime depends on `AICBSP_CPMODE_WORK` vs `_TEST` and on `CONFIG_SDIO_BT`
(=n in the vendor Makefile WiFi and BT firmware are loaded/attached
(=n in the vendor Makefile: WiFi and BT firmware are loaded/attached
separately, not as one combo blob, despite the combo chip). **Firmware load
path**: `AIC_FW_PATH` Kconfig default is `/oem/usr/ko/aic8800dc_fw` this is a
path**: `AIC_FW_PATH` Kconfig default is `/oem/usr/ko/aic8800dc_fw`, this is a
5.10-era Buildroot-rootfs-layout artifact (the `/oem` partition), not a kernel
concept; on the 6.18 rootfs, point this at wherever WardenOS's 6.18 Buildroot
userspace places firmware (likely `/lib/firmware/aic8800dc/` if following
standard mainline convention, or keep `/oem/usr/ko/aic8800dc_fw` if the 6.18
rootfs partition layout is unchanged from 5.10 confirm against whatever the
rootfs partition layout is unchanged from 5.10, confirm against whatever the
M4/M5 rootfs build actually produces, this is a rootfs-layout decision, not a
kernel one). Not found in `linux-firmware.git` (the mainline firmware
project) AIC8800 firmware has not been upstreamed there; continue shipping
project): AIC8800 firmware has not been upstreamed there; continue shipping
it the way the vendor tree already does (bundled alongside the driver, loaded
by direct file read per §3.8, not `request_firmware()`).
by direct file read per section 3.8, not `request_firmware()`).
**Chip variant**: **AIC8800DC**, confirmed authoritative by the board config
(`RK_ENABLE_WIFI_CHIP=AIC8800DC`) and the SDIO device ID match in source
(`aicsdio.c:75,80`: vendor `0xc8a1`, device `0xc08d`) not the dual-band
(`aicsdio.c:75,80`: vendor `0xc8a1`, device `0xc08d`), not the dual-band
AIC8800D80 seen on other LuckFox boards. 2.4 GHz only in practice (confirmed
on hardware per `wifi-bluetooth-aic8800.md:10`: `iw phy` shows 1 band, 0
5 GHz channels, despite AIC8800DC being marketed dual-band-capable elsewhere).
@@ -504,34 +504,34 @@ is authoritative.
## 6. Verify steps
Follow the existing A/B `_b`-slot hardware-verification loop
(`warden-sdk/kernel/docs/m2-boot-on-c8a3.md`), same method M1M3 already used:
(`warden-sdk/kernel/docs/m2-boot-on-c8a3.md`), same method M1-M3 already used:
1. **Build**: driver compiles clean into the 6.18.46 tree (`make ... modules`
is not the target it's `=y`, so this is just `make zImage`/whatever M2's
is not the target, it's `=y`, so this is just `make zImage`/whatever M2's
`build-m2.sh` wraps, succeeding with the new `drivers/net/wireless/aic8800/`
objects linked into `vmlinux`/the zImage).
2. **Probe**: boot on `warden-c8a3`, confirm in `dmesg`: the `sdmmc` MMC host
binds (`dwmmc_rockchip ffaa0000.mmc: ...`, same pattern M3 already proved
for `ffa90000.mmc`/eMMC), then an SDIO card enumerates on it, then
`aic8800_bsp`'s probe fires (vendor/device ID match, firmware file opens
succeed watch specifically for `AIC_FW_PATH` resolution failures, §5),
succeed, watch specifically for `AIC_FW_PATH` resolution failures, section 5),
then `aic8800_fdrv` attaches and **`wlan0` appears** in `ip link`.
3. **cfg80211 sanity**: `iw phy` shows the expected single 2.4 GHz band/14
channels (matching the known-good 5.10 baseline in
`wifi-bluetooth-aic8800.md:10` a mismatch here is a signal something in
`wifi-bluetooth-aic8800.md:10`, a mismatch here is a signal something in
the cfg80211/wiphy port is wrong, not a chip regression).
4. **Scan**: `iw dev wlan0 scan` returns nearby APs exercises
4. **Scan**: `iw dev wlan0 scan` returns nearby APs, exercises
`rwnx_cfg80211_scan`/`cfg80211_scan_done()` and, indirectly, whether the
wiphy-locking port (§3.3) is functioning rather than deadlocking.
wiphy-locking port (section 3.3) is functioning rather than deadlocking.
5. **Connect**: associate to a real AP (`wpa_supplicant`/`wpa_cli`, matching
WardenOS's own `wlan0` ownership model do **not** start `rkwifi_server`,
§1) and confirm `COMPLETED` state plus a DHCP lease exercises
WardenOS's own `wlan0` ownership model, do **not** start `rkwifi_server`,
section 1) and confirm `COMPLETED` state plus a DHCP lease, exercises
`rwnx_cfg80211_connect`/`cfg80211_connect_result()`.
6. **Known-regression checks** (carry forward, don't re-discover): confirm the
two hardening patches (§0/§4.1) are actually effective no console-flood
two hardening patches (section 0/section 4.1) are actually effective, no console-flood
"cmd timed-out" spam under load, and no CPU-pinning busy-spin if the SDIO
link is stressed (`aicwf_bustx_thr` should sleep, not spin, on a wakeup
failure). Confirm `iw dev wlan0 set power_save off` still behaves as
expected (a wall-powered panel, no reason to want power-save
expected (a wall-powered panel, no reason to want power-save:
`wifi-bluetooth-aic8800.md:41`).
7. **BT** (secondary to WiFi but same milestone): confirm `aic8800_btlpm`
attaches and `hciattach -s 1500000 /dev/ttyS1 any 1500000 flow nosleep`
@@ -551,12 +551,12 @@ Follow the existing A/B `_b`-slot hardware-verification loop
`flare-edge/research/linux-6.18.46/.config` (live, post-M3, batch2 CFG80211=y),
`flare-edge/research/linux-6.18.46/include/net/cfg80211.h` (directly read to
verify `wiphy_new`, `cfg80211_connect_result`, `wiphy_lock`/`lockdep_assert_wiphy`
against the actual target tree, not release notes — §3.3),
against the actual target tree, not release notes, section 3.3),
`flare-edge/research/plan44-openwrt/` (checked, no aic8800),
`warden-sdk/kernel/rv1106-enablement/{PORT-STATUS.md,DRIVER-PARITY.md,OVERNIGHT-PLAN.md}`,
`warden-sdk/kernel/docs/{bringup.md,m2-boot-on-c8a3.md}`,
`luckfox-pico-86-panel/{wifi-bluetooth-aic8800.md,hardware-86-panel.md,alternative-bsps.md,sdk-patches.md,boot-chain.md}`.
- Web (via research pass, see §0/§2): github.com/radxa-pkg/aic8800 +
- Web (via research pass, see section 0/section 2): github.com/radxa-pkg/aic8800 +
deepwiki.com/radxa-pkg/aic8800; forum.armbian.com topic 50332 ("AIC8800 wifi
sdio module not working with kernel 6.12+"); kernel.org/patchwork commits
`9b13df3fb64e` (timer_delete rename), `2655926aea9b` (netif_rx_ni removal),
@@ -564,6 +564,6 @@ Follow the existing A/B `_b`-slot hardware-verification loop
LKML/lkml.iu.edu mirrors for the timer-rename and wiphy-guard series.
Patchwork/LWN direct fetches were partially blocked by anti-bot walls during
research, so the *exact commit/version* the wiphy-lock migration landed in
is not pinned precisely but its **presence and current contract in the
is not pinned precisely, but its **presence and current contract in the
actual 6.18.46 tree we're porting to is directly confirmed** (previous
paragraph), which is the fact that actually matters for this port.
+56 -56
View File
@@ -1,6 +1,6 @@
# AIC8800 wifi+BT port M5 progress (2026-08-24)
# AIC8800 wifi+BT port: M5 progress (2026-08-24)
Status: **DONE zImage + rv1106-warden.dtb build clean with the driver
Status: **DONE, zImage + rv1106-warden.dtb build clean with the driver
built in.** Not flashed/booted (parent session verifies on hardware).
## Build status
@@ -15,14 +15,14 @@ Clean rebuild (all aic8800 sources touched to force recompilation): **0
errors**, 104 warnings, all benign vendor-code style (`101
-Wmissing-prototypes` on internal non-`static` helper functions that were
never prototyped in headers, `3 -Wempty-body` on an existing `if
(...);`-with-no-body debug macro expansion) none are correctness issues
(...);`-with-no-body debug macro expansion), none are correctness issues
and none were introduced by version-delta fixes. `zImage is ready`;
`rv1106-warden.dtb` compiles with only the pre-existing, unrelated `&rgb`
graph-endpoint warning (already known from M4 display work, not
wifi-related). Confirmed via `nm vmlinux.unstripped` that the driver is
actually linked in, not silently dropped: `aicbsp_sdio_driver`,
`aicwf_sdio_driver`, `rwnx_cfg80211_ops` all present, and the three
`module_init()`s appear as `device_initcall`s in the correct link order
`module_init()`s appear as `device_initcall`s in the correct link order,
`__initcall__kmod_aic8800_fdrv__...rwnx_mod_init` after bsp's init and
`__initcall__kmod_aic8800_btlpm__...aic_bluetooth_mod_init` last.
@@ -37,57 +37,57 @@ Dropped as dead code for this board (SDIO-only, no USB/PCIe, `CONFIG_SDIO_BT=n`)
`usb_host.{c,h}`, `rwnx_pci.{c,h}`, `aicwf_usb.c`, `btsdio.c`, `aic_btsdio.c`.
**One exception the plan flagged correctly**: `rwnx_mesh.{c,h}` was
initially dropped on the same "dead code" theory but had to be **restored**
`rwnx_tx.c`'s `NL80211_IFTYPE_MESH_POINT` switch-case block is not
initially dropped on the same "dead code" theory but had to be **restored**,
`rwnx_tx.c`'s `NL80211_IFTYPE_MESH_POINT` switch-case block is not
preprocessor-gated, so `rwnx_mesh.h`'s types (`struct rwnx_mesh_path`,
`struct rwnx_mesh_proxy`) and prototypes are needed to compile even though
this board never creates a mesh-type interface. Restored both files
(1KB each) and added `rwnx_mesh.o` back to the fdrv `Makefile` object list.
`usb_host.c`/`rwnx_pci.c` did NOT need restoring no build error ever
`usb_host.c`/`rwnx_pci.c` did NOT need restoring: no build error ever
referenced them.
New in-tree Kbuild wiring (not copied from vendor, vendor Makefiles are
out-of-tree `KDIR=` external-module style and don't apply directly):
- `drivers/net/wireless/aic8800/Kconfig` top-level `AIC_WLAN_SUPPORT`
- `drivers/net/wireless/aic8800/Kconfig`, top-level `AIC_WLAN_SUPPORT`
bool + `AIC_FW_PATH` string, `source`s the fdrv/btlpm sub-Kconfigs.
Wired into `drivers/net/wireless/Kconfig` (new `source` line, alongside
the existing vendor Kconfig list).
- `drivers/net/wireless/aic8800/Makefile` `obj-y` list preserving
bsp fdrv btlpm link order (per PORT-PLAN.md §4.5, since these are
- `drivers/net/wireless/aic8800/Makefile`, `obj-y` list preserving
bsp -> fdrv -> btlpm link order (per PORT-PLAN.md section 4.5, since these are
built-in `=y` now and initcall order follows link order). Wired into
`drivers/net/wireless/Makefile` (`obj-$(CONFIG_AIC_WLAN_SUPPORT) += aic8800/`).
- `aic8800_fdrv/Kconfig`, `aic8800_btlpm/Kconfig` vendor's `tristate`
- `aic8800_fdrv/Kconfig`, `aic8800_btlpm/Kconfig`, vendor's `tristate`
symbols changed to `default y` (built-in, not modular; the 6.18 rootfs
has no working module pipeline yet).
- Three new in-tree `Makefile`s (`aic8800_bsp/`, `aic8800_fdrv/`,
`aic8800_btlpm/`) translating the vendor's `ccflags-$(CONFIG_X) += -DX`
pattern into fixed `-D` flags for the one build configuration this board
actually uses (SDIO, Rockchip, no USB/PCI/mesh-in-practice, TCP-ACK
filter, preallocated TXQ, RF test support see each Makefile's ccflags
filter, preallocated TXQ, RF test support, see each Makefile's ccflags
for the full list, matching the vendor Makefile defaults). `CONFIG_AIC_FW_PATH`
needs no `-D`: it's now a real Kconfig string symbol, so
`include/generated/autoconf.h` already defines the `CONFIG_AIC_FW_PATH`
C-string macro the driver expects (`aicsdio.c:59`, `aic_bsp_driver.h:348`).
## API-delta fixes (file change)
## API-delta fixes (file -> change)
### Plan-anticipated mechanical renames (applied via scoped `sed`, all call sites)
- `from_timer()` `timer_container_of()` 7 sites: `aicsdio.c`,
`aicwf_sdio.c` (×3), `rwnx_main.c`, `rwnx_rx.c` (×2).
- `del_timer_sync()` `timer_delete_sync()` 10 sites: `aicsdio.c`,
`aicwf_sdio.c` (×5), `rwnx_main.c` (×2, one more than the plan's count
- `from_timer()` -> `timer_container_of()`, 7 sites: `aicsdio.c`,
`aicwf_sdio.c` (x3), `rwnx_main.c`, `rwnx_rx.c` (x2).
- `del_timer_sync()` -> `timer_delete_sync()`, 10 sites: `aicsdio.c`,
`aicwf_sdio.c` (x5), `rwnx_main.c` (x2, one more than the plan's count,
`rwnx_main.c:6177` wasn't in the plan's list but grep found it),
`rwnx_rx.c` (×2).
- `del_timer()` `timer_delete()` 9 sites: `aicwf_tcp_ack.c` (×4),
`rwnx_rx.c` (×2), `aic8800_btlpm/aic8800_btlpm.c` (×3, dead not in this
`rwnx_rx.c` (x2).
- `del_timer()` -> `timer_delete()`, 9 sites: `aicwf_tcp_ack.c` (x4),
`rwnx_rx.c` (x2), `aic8800_btlpm/aic8800_btlpm.c` (x3, dead, not in this
build's object list, fixed anyway for tree hygiene), `aic8800_btlpm/lpm.c`
(×3, same `lpm.c` isn't compiled, `CONFIG_SUPPORT_LPM=n`).
- `netif_rx_ni()` `netif_rx()` 3 sites, all `rwnx_rx.c`.
(x3, same, `lpm.c` isn't compiled, `CONFIG_SUPPORT_LPM=n`).
- `netif_rx_ni()` -> `netif_rx()`: 3 sites, all `rwnx_rx.c`.
### Deltas beyond the plan's list (found by the compiler, fixed against 6.18 headers)
- **`cfg80211_rx_spurious_frame()` / `cfg80211_rx_unexpected_4addr_frame()`**
(`rwnx_rx.c`, 2 sites): gained a `link_id` param before `gfp`; passed
`-1` ("not applicable", per the header's own doc comment this driver
`-1` ("not applicable", per the header's own doc comment, this driver
has no MLO).
- **`cfg80211_ch_switch_notify()` / `cfg80211_ch_switch_started_notify()`**
(`rwnx_main.c`, 2 sites): the vendor's own version-gated compat shim
@@ -100,12 +100,12 @@ out-of-tree `KDIR=` external-module style and don't apply directly):
edit the historical shim; `link_id = 0` (mandatory for non-MLO per the
header).
- **`cfg80211_ops` struct-initializer type mismatches** (`rwnx_main.c`,
7 callbacks all gained new params, all effectively "not applicable" for
7 callbacks, all gained new params, all effectively "not applicable" for
this single-radio, non-MLO, SDIO full-MAC driver, so the new params are
accepted and ignored):
- `change_beacon`: now takes `struct cfg80211_ap_update *` (wraps the old
`cfg80211_beacon_data` as `.beacon`, plus FILS/S1G/unsol fields unused
here) unwrapped with `&update->beacon` so `rwnx_build_bcn()` and its
here); unwrapped with `&update->beacon` so `rwnx_build_bcn()` and its
other 3 callers stay untouched.
- `set_monitor_channel`: gained a `struct net_device *dev` param. Rather
than thread an unused `dev` through the well-used internal 2-arg
@@ -123,41 +123,41 @@ out-of-tree `KDIR=` external-module style and don't apply directly):
`unsigned int link_id`; passed `0` (mandatory for non-MLO per doc comment).
- **`wakeup_source_create()`/`_add()`/`_remove()`/`_destroy()`** (`rwnx_wakelock.c`):
removed from the public API entirely (still exist internally in
`drivers/base/power/wakeup.c` but are no longer `EXPORT_SYMBOL`'d)
`drivers/base/power/wakeup.c` but are no longer `EXPORT_SYMBOL`'d);
`wakeup_source_register(dev, name)` / `wakeup_source_unregister(ws)` is
now the only supported entry point (confirmed: `wakeup_source_register()`'s
own implementation is literally `create()` + conditional sysfs +
`add()`). Rewrote `rwnx_wakeup_init()`/`_deinit()` to call
`wakeup_source_register(NULL, name)` / `wakeup_source_unregister(ws)`
`wakeup_source_register(NULL, name)` / `wakeup_source_unregister(ws)`:
`dev=NULL` registers an anonymous source not tied to a `struct device`,
matching the old `wakeup_source_create()` behavior used here. (The file
already had a correct modern `rwnx_wakeup_register()`/`_unregister()`
pair for a different call path this fix makes `rwnx_wakeup_init()`
pair for a different call path; this fix makes `rwnx_wakeup_init()`
consistent with it.)
- **`MODULE_IMPORT_NS(bare_token)` `MODULE_IMPORT_NS("string")`** 3
- **`MODULE_IMPORT_NS(bare_token)` -> `MODULE_IMPORT_NS("string")`**: 3
sites (`aic_bsp_driver.c`, `rwnx_platform.c`, `rwnx_main.c`), all
importing the same non-mainline `VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver`
namespace token (an Android-GKI-kernel artifact confirmed absent from
namespace token (an Android-GKI-kernel artifact, confirmed absent from
our mainline `fs/` tree, so this is now inert modinfo metadata, not a
real namespace gate). `MODULE_INFO()`/`MODULE_IMPORT_NS()` now require a
quoted string per current `include/linux/module.h`.
- **`rwnx_platform.c`: unconditional `#include "rwnx_pci.h"`** the header
- **`rwnx_platform.c`: unconditional `#include "rwnx_pci.h"`**, the header
was dropped with `rwnx_pci.c` (dead code, no PCIe on this board), but this
one `#include` wasn't behind any guard. Guarded it behind
`AICWF_PCIE_SUPPORT` (matching the guard its only two callers,
`rwnx_platform_{,un}register_drv()`, already use at their call sites in
`rwnx_main.c`) and guarded the two functions' `rwnx_pci_{,un}register_drv()`
bodies the same way, returning `0`/no-op for the SDIO-only build instead.
- **`<linux/rfkill-wlan.h>` (vendor-only, PORT-PLAN.md §3.7)** removed
- **`<linux/rfkill-wlan.h>` (vendor-only, PORT-PLAN.md section 3.7)**: removed
from 3 files (`aicsdio.c`, `aicwf_sdio.c`, `rwnx_main.c`). Traced every
real `rockchip_wifi_power()`/`_set_carddetect()` call site first: all of
them are either already `#if 0`'d out in the vendor source
(`aicwf_sdio.c`) or gated behind `CONFIG_PLATFORM_ROCKCHIP2` (a symbol
this build never defines only plain `ROCKCHIP`), so none needed
this build never defines, only plain `ROCKCHIP`), so none needed
stubbing individually; only the unconditional header `#include`s needed
removing. The DT's `mmc-pwrseq-simple` node does the power/reset
sequencing instead, as planned.
- **`CONFIG_RFTEST` added to `aic8800_fdrv`'s ccflags** not itself a
- **`CONFIG_RFTEST` added to `aic8800_fdrv`'s ccflags**: not itself a
version delta, but needed: `aic_priv_cmd.c`'s RF-test-mode enum
(`SET_TX`, `RDWR_EFUSE_*`, ~50 constants) and struct typedefs
(`cmd_rf_settx_t` etc.) are defined inline in the file under `#ifdef
@@ -172,31 +172,31 @@ The vendor `aic8800_bsp` and `aic8800_fdrv` were always built as two
**independent `.ko` modules**, each with its own private symbol namespace.
Built in-tree as `=y`, both link into one `vmlinux`, and it turns out
`aic8800_bsp` carries a **complete second, self-contained copy** of large
parts of `aic8800_fdrv`'s machinery its own SDIO byte/frame transport
parts of `aic8800_fdrv`'s machinery: its own SDIO byte/frame transport
(`aicwf_sdio_*`, `aicwf_bus_*`, `aicwf_frame_*`, `aicwf_tx_*`,
`aicwf_rx_*`, `crc8_ponl_107`), its own message/debug-command layer
(`rwnx_send_dbg_*_req`, `rwnx_cmd_mgr_{init,deinit}`,
`rwnx_rx_handle_msg`), and a few globals (`chip_mcu_id`, `chip_sub_id`,
`aic_fw_path`, `testmode`) used for the bsp-side firmware bring-up phase
`aic_fw_path`, `testmode`), used for the bsp-side firmware bring-up phase
before fdrv's own driver instance re-probes and takes over the SDIO
function for real runtime operation. **49 symbols total**, `ld` caught
every one as "multiple definition" at the final `vmlinux.o` link (the
per-subdirectory `built-in.a` build had already succeeded this class of
per-subdirectory `built-in.a` build had already succeeded: this class of
error only surfaces at the whole-image link, which is worth knowing for
anyone repeating this kind of port).
Verified via `aic_bsp_export.h` (the real, narrow, intentional
`aicbsp_*`-prefixed bspfdrv API surface) that none of the 49 are part of
`aicbsp_*`-prefixed bsp->fdrv API surface) that none of the 49 are part of
the actual cross-module contract, and via `grep -rlw` that `fdrv` has its
own **complete** definition of every one (not an `extern` pointing at
bsp's copy) confirming these are two truly independent implementations,
bsp's copy), confirming these are two truly independent implementations,
not one legitimately shared. Fix: renamed all 49 symbols with an
`aicbsp_priv_` prefix, scoped strictly to `aic8800_bsp/*.{c,h}` (both
definitions and bsp-internal call sites via whole-word `sed`); `fdrv`'s
copies are untouched. One of the 49, `md5.c`'s MD5 functions, got a
different (smaller-footprint) fix: `aic8800_bsp/md5.c` and
`aic8800_fdrv/md5.c` are **byte-identical** files, so `md5.o` was simply
dropped from `aic8800_fdrv`'s object list instead of renamed `fdrv`'s
dropped from `aic8800_fdrv`'s object list instead of renamed, `fdrv`'s
calls resolve against `bsp`'s copy at link time since bsp links first.
## Config symbols set
@@ -208,11 +208,11 @@ CONFIG_AIC8800_BTLPM_SUPPORT=y
CONFIG_AIC_FW_PATH="/oem/usr/ko/aic8800dc_fw"
CONFIG_WIRELESS=y CONFIG_CFG80211=y CONFIG_WLAN=y (already y pre-port)
CONFIG_MMC=y CONFIG_MMC_DW_ROCKCHIP=y CONFIG_RFKILL=y (already y pre-port)
CONFIG_BT=y (was =m, flipped to =y no module pipeline yet)
CONFIG_BT=y (was =m, flipped to =y, no module pipeline yet)
CONFIG_BT_HCIUART=y (was =m, flipped to =y)
CONFIG_BT_HCIUART_H4=y (already y)
CONFIG_CRYPTO_ARC4=y CONFIG_CRYPTO_CTR=y CONFIG_CRYPTO_CCM=y CONFIG_CRYPTO_AES=y
# CONFIG_MAC80211 is not set (explicitly left off pure cfg80211 full-MAC
# CONFIG_MAC80211 is not set (explicitly left off, pure cfg80211 full-MAC
driver, confirmed no mac80211/ieee80211_hw
symbol usage anywhere in aic8800_fdrv)
```
@@ -231,12 +231,12 @@ ported drivers' symbols (`CONFIG_CLK_RV1106`, `CONFIG_PINCTRL_ROCKCHIP`,
`non-removable`, `rockchip,default-sample-phase = <90>`,
`supports-sdio`, `mmc-pwrseq = <&sdio_pwrseq>`, pinctrl
`sdmmc0_clk`/`_cmd`/`_bus4`/`_det`, `status = "okay"`.
- `&sdio` (mmc@ff9a0000) left untouched stays `disabled`, genuinely
- `&sdio` (mmc@ff9a0000) left untouched: stays `disabled`, genuinely
unused hardware on this board.
**Note**: the `&sdmmc` node itself (`mmc@ffaa0000`) in `rv1106.dtsi` and
the `sdmmc0_*` pinctrl groups in `rv1106-pinctrl.dtsi` were **already
present** in this tree from earlier M1M3 work (not added by this task)
present** in this tree from earlier M1-M3 work (not added by this task),
this task only added the board-DTS *enablement* (`status = "okay"` +
properties) and the new `sdio_pwrseq` node. Verified in the compiled
`.dtb` (decompiled with `dtc -I dtb -O dts`) that both nodes carry the
@@ -245,7 +245,7 @@ correct phandles/properties.
## Hardening patches
Both hardware-verified patches from the plan are **already present in the
vendor SDK source** we copied from (not merely trackable-but-unapplied)
vendor SDK source** we copied from (not merely trackable-but-unapplied),
confirmed two ways: `patch -p5 --dry-run` on both reported "Reversed (or
previously applied) patch detected" against the freshly-copied tree, and
`grep` found their exact markers already in place:
@@ -260,7 +260,7 @@ previously applied) patch detected" against the freshly-copied tree, and
No action needed beyond copying the source. **Known housekeeping gap
(pre-existing, not fixed by this task, flagged by the plan)**: patch 0002
is still only tracked under `future-features-2/sdk-patches/wifi/patches/`,
not this branch's own `sdk-patches/wifi/patches/` worth closing
not this branch's own `sdk-patches/wifi/patches/`, worth closing
separately since the fix is live in the vendor tree either way.
## Constraints honored
@@ -269,35 +269,35 @@ Only touched: the wifi driver tree (`drivers/net/wireless/aic8800/`), its
Kconfig/Makefile wiring (`drivers/net/wireless/{Kconfig,Makefile}`), the
board DTS wifi nodes (`rv1106-warden.dts`, additive only), and
wifi/BT/crypto-related `.config` symbols. Did not touch any other driver,
DT node, or unrelated config symbol spot-checked after the port that
DT node, or unrelated config symbol: spot-checked after the port that
`CONFIG_CLK_RV1106`, `CONFIG_PINCTRL_ROCKCHIP`, `CONFIG_DRM_ROCKCHIP`,
`CONFIG_MMC_DW_ROCKCHIP` are all still `=y`, and the `&rgb`/VOP-related dtc
warning is the same pre-existing one from M4 (not new). Never touched
hardware no kflash, no reboot, no `/dev/ttyUSB2`, no Pi.
hardware: no kflash, no reboot, no `/dev/ttyUSB2`, no Pi.
## What the parent should watch for on hardware
Per PORT-PLAN.md §6's verify sequence:
Per PORT-PLAN.md section 6's verify sequence:
1. `dmesg`: `dwmmc_rockchip ffaa0000.mmc: ...` binding (mirrors the M3
eMMC pattern on `ffa90000.mmc`), then an SDIO card enumerating on it
(vendor/device ID `0xc8a1`/`0xc08d`), then `aic8800_bsp`'s probe firing.
2. **Firmware path**: `AIC_FW_PATH` defaults to `/oem/usr/ko/aic8800dc_fw`
a 5.10-era Buildroot `/oem` partition path. Confirm the 6.18 rootfs
2. **Firmware path**: `AIC_FW_PATH` defaults to `/oem/usr/ko/aic8800dc_fw`,
a 5.10-era Buildroot `/oem` partition path. Confirm the 6.18 rootfs
actually has firmware there (or update `CONFIG_AIC_FW_PATH` to wherever
it landed) watch specifically for firmware-open failures in dmesg as
it landed), watch specifically for firmware-open failures in dmesg as
the first failure mode, before assuming a driver/DT bug.
3. `wlan0` should appear in `ip link` once `aic8800_fdrv` attaches.
4. `iw phy` should show one 2.4 GHz band, 14 channels (matches the known
5.10 baseline a mismatch signals a cfg80211/wiphy port bug, not a
5.10 baseline: a mismatch signals a cfg80211/wiphy port bug, not a
chip issue).
5. `iw dev wlan0 scan` and a real AP association exercise the
wiphy-locking behavior (§3.3 of the plan) this is the area with the
wiphy-locking behavior (section 3.3 of the plan): this is the area with the
least direct verification in this port (the driver's own ops callbacks
need no new locking per the plan's analysis, since cfg80211 core now
holds the wiphy mutex before calling in; the async-context notification
calls from `rwnx_msg_rx.c` were flagged as the highest-residual-risk
area and were NOT touched by this build-fix pass beyond the signature
changes above if scan/connect hang or lockdep splats appear, look
changes above, if scan/connect hang or lockdep splats appear, look
there first).
6. Confirm the two hardening patches are actually effective under load: no
"cmd timed-out" console flood, no CPU-pinning busy-spin on a stressed/
@@ -307,6 +307,6 @@ Per PORT-PLAN.md §6's verify sequence:
8. Given the scale of the bsp/fdrv duplicate-symbol rename (49 symbols),
if anything behaves subtly wrong in the bsp-side firmware bring-up
phase specifically (vs. fdrv's runtime path), double-check the rename
didn't miss a cross-file reference within `aic8800_bsp/` it was done
didn't miss a cross-file reference within `aic8800_bsp/`: it was done
with whole-word `sed` scoped to that directory and re-verified by a
clean rebuild, but it touched every `.c`/`.h` in that subtree.
@@ -1,4 +1,4 @@
# AIC8800 wifi VERIFIED on warden-c8a3 (self-built 6.18.46), 2026-08-25
# AIC8800 wifi: VERIFIED on warden-c8a3 (self-built 6.18.46), 2026-08-25
**Result: wifi works end-to-end on our self-built Linux 6.18.46.** Modules built
from the ported source (vermagic `6.18.46 SMP mod_unload ARMv7 p2v8`), loaded on
@@ -6,39 +6,39 @@ the panel, downloaded firmware to the AIC8800DC, created `wlan0`, and completed
live RF scan.
## Evidence (serial console, _b slot = our 6.18 kernel)
- `insmod aic8800_bsp.ko` firmware download OK: `aicwf_patch_config_8800dc done`,
- `insmod aic8800_bsp.ko` -> firmware download OK: `aicwf_patch_config_8800dc done`,
`Start app: 00120000`, BSP_RC=0.
- `insmod aic8800_fdrv.ko` `ieee80211 phy0: HT supp 1, VHT supp 1, HE supp 1`,
- `insmod aic8800_fdrv.ko` -> `ieee80211 phy0: HT supp 1, VHT supp 1, HE supp 1`,
FDRV_RC=0.
- `wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> ... link/ether <device MAC>`
- `iw dev wlan0 scan` found real APs (SSIDs/BSSIDs redacted for publication):
- **the site AP at 2412 MHz, 43 dBm**
- a neighboring guest AP at 2412 MHz, 73 dBm
- +several more, correct signal strengths RF path fully functional.
- **the site AP at 2412 MHz, -43 dBm**
- a neighboring guest AP at 2412 MHz, -73 dBm
- +several more, correct signal strengths -> RF path fully functional.
## Why MODULES, not built-in (=y)
Built-in device_initcalls run BEFORE the dw_mmc/SDIO controller probes. Initcalls
are sequential: `aicbsp_init` blocked 3.37.5 s doing the eager chip bring-up, and
the mmc controller only probed at 7.9 s (SDIO card at 8.2 s) AFTER aicbsp had
already given up (`aicsdio.c:597` 2 s `down_timeout` `sdio_unregister_driver`).
are sequential: `aicbsp_init` blocked 3.3-7.5 s doing the eager chip bring-up, and
the mmc controller only probed at 7.9 s (SDIO card at 8.2 s), AFTER aicbsp had
already given up (`aicsdio.c:597` 2 s `down_timeout` -> `sdio_unregister_driver`).
Extending the timeout can't help (aicbsp blocks the very mmc probe that would
enumerate the card a deadlock). Loaded as modules AFTER boot (mmc up, card at
enumerate the card, a deadlock). Loaded as modules AFTER boot (mmc up, card at
4 s), `insmod aic8800_bsp` registers the SDIO driver against an already-present
card probe fires immediately firmware download fdrv wlan0. This is the
card -> probe fires immediately -> firmware download -> fdrv -> wlan0. This is the
vendor-proven flow.
## Kernel-size fix (needed to boot the wifi kernel at all)
The wifi kernel grew the gzip zImage to 12.12 MB; rockchip U-Boot loads the kernel
blob at 0x8000 and relocates the DTB to 0xc00000 (12 MB), so a >~11.95 MB zImage
overruns the FDT at U-Boot load time (`Sysmem Error: KERNEL overlap with FDT`) and
FLARE-AB falls back to _a. Switched `CONFIG_KERNEL_GZIP` `CONFIG_KERNEL_XZ`:
zImage 12.12 MB 8.15 MB (module build), ~4 MB headroom under the FDT. Also the
FLARE-AB falls back to _a. Switched `CONFIG_KERNEL_GZIP` -> `CONFIG_KERNEL_XZ`:
zImage 12.12 MB -> 8.15 MB (module build), ~4 MB headroom under the FDT. Also the
right call for a firmware kernel. (Uncompressed Image is ~30 MB; the ARM
decompressor relocates the FDT at runtime, so only the U-Boot LOAD-time overlap
mattered proven by the kernel booting once the zImage fit.)
mattered: proven by the kernel booting once the zImage fit.)
## Boot-time auto-load (follow-up, deployment layer not the kernel port)
## Boot-time auto-load (follow-up, deployment layer, not the kernel port)
Modules were insmod'd manually for this verify. Production auto-load needs a
loader that inserts `aic8800_bsp.ko` `aic8800_fdrv.ko` ( `aic8800_btlpm.ko`) in
loader that inserts `aic8800_bsp.ko` -> `aic8800_fdrv.ko` (-> `aic8800_btlpm.ko`) in
order from wherever they're staged; the vendor `insmod_wifi.sh` references a
different variant (`aic_load_fw.ko`/`bcmdhd.ko`, absent here). Track in the rootfs.
@@ -3,12 +3,12 @@
# AICSemi AIC8800DC SDIO WiFi+BT driver (WardenOS M5 port).
#
# Ported from the vendor 5.10.160 out-of-tree source
# (flare-edge/sdk/sysdrv/drv_ko/wifi/aic8800dc/) see
# (flare-edge/sdk/sysdrv/drv_ko/wifi/aic8800dc/), see
# warden-sdk/kernel/rv1106-enablement/wifi/PORT-PLAN.md and PORT-PROGRESS.md.
# Modules (=m), loaded late by /oem/usr/ko/insmod_wifi.sh: the AIC8800 SDIO
# bring-up is two-stage (bsp downloads firmware, chip re-enumerates, fdrv
# attaches). Built-in device_initcalls run BEFORE the dw_mmc/SDIO controller
# probes initcalls are sequential, so aicbsp_init blocks [3.3-7.5]s while the
# probes: initcalls are sequential, so aicbsp_init blocks [3.3-7.5]s while the
# mmc controller only probes at [7.9]s afterward, and the eager bring-up
# deadlocks (it waits for a card the blocked mmc probe hasn't enumerated).
# Late module load, after the mmc-pwrseq enumerates the card, is the
@@ -28,7 +28,7 @@ config AIC_FW_PATH
default "/oem/usr/ko/aic8800dc_fw"
help
Path to the firmware & config files. The driver reads these
directly (not via request_firmware()) see PORT-PLAN.md §3.8.
directly (not via request_firmware()), see PORT-PLAN.md section 3.8.
if AIC_WLAN_SUPPORT
source "drivers/net/wireless/aic8800/aic8800_fdrv/Kconfig"
@@ -2,7 +2,7 @@
#
# Link order matters: bsp before fdrv before btlpm, mirroring the working
# insmod order (insmod_wifi.sh) now that these are built-in (=y) instead of
# modules see PORT-PLAN.md §4.5.
# modules, see PORT-PLAN.md section 4.5.
obj-$(CONFIG_AIC_WLAN_SUPPORT) += aic8800_bsp/
obj-$(CONFIG_AIC8800_WLAN_SUPPORT) += aic8800_fdrv/
obj-$(CONFIG_AIC8800_BTLPM_SUPPORT) += aic8800_btlpm/
@@ -1,9 +1,9 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# aic8800_bsp SDIO bus glue + firmware bootstrap, ported in-tree from the
# aic8800_bsp: SDIO bus glue + firmware bootstrap, ported in-tree from the
# vendor out-of-tree Makefile (flare-edge/sdk/sysdrv/drv_ko/wifi/aic8800dc/
# aic8800_bsp/Makefile). CONFIG_AIC_FW_PATH is now a real Kconfig string
# symbol (see ../Kconfig) so it needs no -D here the driver picks it up
# symbol (see ../Kconfig) so it needs no -D here; the driver picks it up
# from include/generated/autoconf.h directly (aicsdio.c, aic_bsp_driver.h).
#
# Vendor CONFIG_* build-time knobs below are preserved as fixed -D flags
@@ -3,5 +3,5 @@ config AIC8800_BTLPM_SUPPORT
default y
help
This is support for the aic8800 bluetooth low-power-mode / HCI
wake companion module (aic8800_btlpm). Built-in (y) by default
wake companion module (aic8800_btlpm). Built-in (y) by default,
see the aic8800 port notes in warden-sdk/kernel/rv1106-enablement/wifi/.
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# aic8800_btlpm Bluetooth low-power-mode / HCI wake companion module,
# aic8800_btlpm: Bluetooth low-power-mode / HCI wake companion module,
# ported in-tree from the vendor out-of-tree Makefile
# (flare-edge/sdk/sysdrv/drv_ko/wifi/aic8800dc/aic8800_btlpm/Makefile).
# lpm.c stays out of the build (CONFIG_SUPPORT_LPM was already off in the
@@ -3,5 +3,5 @@ config AIC8800_WLAN_SUPPORT
default y
help
This is support for the aic8800 cfg80211 full-MAC wifi driver
(aic8800_fdrv). Built-in (y) by default see the aic8800 port
(aic8800_fdrv). Built-in (y) by default, see the aic8800 port
notes in warden-sdk/kernel/rv1106-enablement/wifi/.
@@ -1,17 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# aic8800_fdrv the cfg80211 full-MAC driver ("fdrv"), ported in-tree from
# aic8800_fdrv: the cfg80211 full-MAC driver ("fdrv"), ported in-tree from
# the vendor out-of-tree Makefile (flare-edge/sdk/sysdrv/drv_ko/wifi/
# aic8800dc/aic8800_fdrv/Makefile). SDIO-only build (this board has no
# USB/PCI variant) usb_host.c and rwnx_pci.c are dropped from the tree
# entirely (PORT-PLAN.md §4.1 flagged both as candidates to drop "unless a
# build error proves them referenced elsewhere" that held for these two).
# USB/PCI variant): usb_host.c and rwnx_pci.c are dropped from the tree
# entirely (PORT-PLAN.md section 4.1 flagged both as candidates to drop "unless a
# build error proves them referenced elsewhere", that held for these two).
# rwnx_mesh.c was ALSO dropped initially on the same theory but had to be
# restored: rwnx_tx.c's NL80211_IFTYPE_MESH_POINT switch-case is not
# preprocessor-gated, so rwnx_mesh.h's types/prototypes are needed to
# compile even though this board never creates a mesh-type interface.
# md5.o: fdrv carries its OWN copy (vendor ships md5.c in both aic8800_bsp/ and
# aic8800_fdrv/). As separate modules each .ko needs its own MD5 bsp does not
# aic8800_fdrv/). As separate modules each .ko needs its own MD5: bsp does not
# EXPORT_SYMBOL(MD5Init/...), so fdrv.ko cannot resolve them from aic8800_bsp.ko
# (modpost: "MD5Final undefined"). The two copies are byte-identical and live in
# separate module namespaces, so there is no collision. (When these were briefly
@@ -20,7 +20,7 @@
status = "okay";
};
/* --- M5: AIC8800DC wifi/BT SDIO (on &sdmmc, mmc@ffaa0000 NOT &sdio) --- */
/* --- M5: AIC8800DC wifi/BT SDIO (on &sdmmc, mmc@ffaa0000, NOT &sdio) --- */
&sdmmc {
max-frequency = <50000000>;
bus-width = <4>;
+1 -1
View File
@@ -1835,7 +1835,7 @@ index 000000000..418652552
+#include <dt-bindings/clock/rv1106-cru.h>
+
+/ {
+ model = "WardenOS 86-Panel (RV1106) M2 earlycon bring-up";
+ model = "WardenOS 86-Panel (RV1106), M2 earlycon bring-up";
+ compatible = "rockchip,rv1106";
+ #address-cells = <1>;
+ #size-cells = <1>;
+3 -3
View File
@@ -6,8 +6,8 @@ patch applies cleanly onto pristine 6.18.46 (`git apply --check`), and the whole
reproduces the 223-file / ~136K-line delta that boots and is hardware-verified on
`warden-c8a3` (see `../kernel/rv1106-enablement/`).
Build it with `../build/build-kernel.sh` (fetch pristine apply these configure
with `../build/warden_defconfig` `zImage` + `rv1106-warden.dtb`).
Build it with `../build/build-kernel.sh` (fetch pristine -> apply these -> configure
with `../build/warden_defconfig` -> `zImage` + `rv1106-warden.dtb`).
## The Series
@@ -43,4 +43,4 @@ subsystem. Licensing/origin of each block is tracked in
Whitespace: `60-wifi-aic8800.patch` carries the vendor blob's trailing-whitespace
verbatim; `git apply --whitespace=nowarn` (the build wrapper) accepts it. Do not
re-flow vendor code it must stay diffable against future vendor drops.
re-flow vendor code; it must stay diffable against future vendor drops.
+13 -13
View File
@@ -1,17 +1,17 @@
# The Device Simulator
A QEMU virtual machine that boots the real forward-ported kernel and real
userspace: the 86 Panel init, daemons, networking, OTA, watchdog, display
userspace: the 86 Panel (init, daemons, networking, OTA, watchdog, display)
developed and tested with no board attached. The third simulator in the stack
(three-way split: root README), deliberately not named "sim": it runs the
whole machine above the kernel entry point on real binaries bring your own,
whole machine above the kernel entry point on real binaries: bring your own,
or drop prebuilt payloads in `payload/`. Decision record: ADR-0006.
## The Boundary
There is no RV1106 machine model in QEMU and everything below the kernel is
closed rkbin blobs plus mask ROM, so the VM **enters at `-kernel zImage`** on
`-M virt,highmem=off` (single Cortex-A7, 256M the RV1106G3's shape).
`-M virt,highmem=off` (single Cortex-A7, 256M: the RV1106G3's shape).
| Emulated / substituted | Not emulated (stays bench / `sim/` territory) |
|---|---|
@@ -28,7 +28,7 @@ closed rkbin blobs plus mask ROM, so the VM **enters at `-kernel zImage`** on
**"Boots/works under emulation" is never evidence of "works on silicon."**
The VM narrows which claims need a panel; on-device claims still need
on-device evidence. Conversely, the VM is the first environment that runs
production binaries on a non-RV1106 memory map it found flare-edge #106
production binaries on a non-RV1106 memory map: it found flare-edge #106
(fatal SIGBUS in flared's HPMCU probe) and #107 (Y2038 time_t truncation)
on its first two boots of real userspace.
@@ -54,7 +54,7 @@ bash qemu/run.sh --kernel $HOME/kbuild-out/linux-6.18.46/arch/arm/boot/zImage --
```
Payload: drop static musl armv7 binaries into `qemu/payload/` (see its
README) `warden-flared`, `warden-modbus`, and `warden-ui` (the LVGL
README); `warden-flared`, `warden-modbus`, and `warden-ui` (the LVGL
fbdev+evdev build from flare-edge `tools/build-ui-vm.sh`) are started by
stage-2 init when present.
@@ -64,12 +64,12 @@ All take the virt-fragment `<zImage>`; `FLARE_EDGE=<checkout>` where noted.
| Scenario | Needs | Proves |
|---|---|---|
| `boot-smoke.sh` | | sentinel-asserting boot; runs in CI inside kernel-build |
| `boot-smoke.sh` | - | sentinel-asserting boot; runs in CI inside kernel-build |
| `portal-scenario.sh` | `FLARE_EDGE` | real flared against the desk mock portal: authenticated check-in, desired-state pull, signed tier-1 `.wfw` download; verify/stage/APPLYING as a dry run (no `WARDEN_FW_ALLOW_APPLY`) |
| `ota-apply.sh` | `FLARE_EDGE` | the FULL apply: the `.wfw`'s bootable rootfs payload is written to rootfs_b (`run.sh --allow-apply` gates it per boot), the AvbABData in `misc` flips, and slot `_b` boots the applied version |
| `ui-shot.sh` | | display+touch, headless: QMP-screendumps the 720x720 UI, taps the Metrics tab via `input-send-event`, asserts the frame changed (`qmp.py` is the QMP client) |
| `real-image-boot.sh` | matched `rootfs.img` + `oem.img` | an ACTUAL flare-edge build (placed by `mkimage.sh --rootfs-image/--oem-image`) boots its own init chain to getty; binaries predating known fixes reproduce their bugs faithfully a time machine for field issues |
| watchdog (`run.sh --watchdog`) | | arm `/dev/watchdog`, don't pet: the VM resets ~30 s later (verified) |
| `ui-shot.sh` | - | display+touch, headless: QMP-screendumps the 720x720 UI, taps the Metrics tab via `input-send-event`, asserts the frame changed (`qmp.py` is the QMP client) |
| `real-image-boot.sh` | matched `rootfs.img` + `oem.img` | an ACTUAL flare-edge build (placed by `mkimage.sh --rootfs-image/--oem-image`) boots its own init chain to getty; binaries predating known fixes reproduce their bugs faithfully, a time machine for field issues |
| watchdog (`run.sh --watchdog`) | - | arm `/dev/watchdog`, don't pet: the VM resets ~30 s later (verified) |
Scenario fine print:
@@ -77,18 +77,18 @@ Scenario fine print:
harness (ADR-0006 boundary); the VM exports `WARDEN_HARD_RESET=0` so
flared's post-apply reset surfaces as a reported error, not a /dev/mem
fault.
- Touch injection holds 200 ms an instantaneous press+release lands inside
- Touch injection holds 200 ms: an instantaneous press+release lands inside
one LVGL poll and never clicks.
- Watchdog + a flared payload don't mix: flared pets only while the UI
heartbeat is fresh.
## Gotchas
- AF_UNIX socket paths cap at ~108 chars keep `--rs485`/`--qmp` paths short.
- AF_UNIX socket paths cap at ~108 chars; keep `--rs485`/`--qmp` paths short.
- A serial port that is closed discards incoming bytes: hold ONE fd open
across write and read when scripting the guest side of the RS485 bridge.
- `highmem=off` and `-global virtio-mmio.force-legacy=false` are load-bearing
(32-bit ECAM reach; virtio-1-only gpu/input) both live ONLY in run.sh,
- `highmem=off` and `-global virtio-mmio.force-legacy=false` are required
(32-bit ECAM reach; virtio-1-only gpu/input); both live ONLY in run.sh,
which every script (boot smoke included) delegates to.
- Never pass `earlyprintk`: DEBUG_UART_PHYS is the RV1106's 0xff4c0000.
+2 -2
View File
@@ -1,10 +1,10 @@
# The device's canonical 12-partition A/B layout, expressed for the VM's
# virtio disk (vda). On hardware the same string names mmcblk0 and is baked
# into the U-Boot env source: flare-edge docs/decisions/0003-partition-layout.md.
# into the U-Boot env; source: flare-edge docs/decisions/0003-partition-layout.md.
# There is no MBR/GPT anywhere: U-Boot and Linux both parse this string, which
# is why handing it to the VM kernel on the cmdline reproduces the exact
# partition map (vda9 = rootfs_a = hardware mmcblk0p9).
#
# Sourced by qemu/mkimage.sh (computes byte offsets from it) and qemu/run.sh
# (passes it verbatim in -append). Single source of truth edit only here.
# (passes it verbatim in -append). Single source of truth: edit only here.
WARDEN_BLKDEVPARTS='vda:32K(env),512K@32K(idblock),512K(uboot),512K(misc),32M(boot_a),32M(boot_b),128M(oem_a),128M(oem_b),1G(rootfs_a),1G(rootfs_b),32M(recovery),1G(userdata)'
+3 -3
View File
@@ -1,4 +1,4 @@
# QEMU -M virt kernel variant merged onto build/warden_defconfig via
# QEMU -M virt kernel variant, merged onto build/warden_defconfig via
# WARDEN_KCONFIG_FRAGMENT (see build/build-kernel.sh). The RV1106 zImage stays
# canonical and byte-identical when the variable is unset.
#
@@ -18,7 +18,7 @@ CONFIG_PCI_HOST_GENERIC=y
# shows up as ttyS0; stage-2 init aliases it to the device's /dev/ttyS4).
CONFIG_SERIAL_8250_PCI=y
# /dev/watchdog for flared's watchdog_loop() untestable on both existing
# /dev/watchdog for flared's watchdog_loop(), untestable on both existing
# sims. i6300esb is the watchdog QEMU offers on arm virt (PCI device):
# -device i6300esb -action watchdog=reset.
CONFIG_WATCHDOG=y
@@ -28,7 +28,7 @@ CONFIG_I6300ESB_WDT=y
CONFIG_WIREGUARD=y
# Display + touch: virtio-gpu scanout with fbdev emulation (the VM UI build
# uses LVGL's fbdev backend no libdrm needed in the guest), virtio-tablet
# uses LVGL's fbdev backend; no libdrm needed in the guest), virtio-tablet
# for absolute-coordinate touch injection via QMP.
CONFIG_FB=y
CONFIG_DRM_VIRTIO_GPU=y
+2 -2
View File
@@ -9,7 +9,7 @@ qemu_log() { printf '\033[36m== %s\033[0m\n' "$*"; }
# Fetch (or accept via $BUSYBOX) the pinned static armv7 busybox and verify it
# against qemu/busybox.sha256. FAILS CLOSED: a missing pin refuses to build,
# never silently skips verification mirroring build/build-kernel.sh's
# never silently skips verification, mirroring build/build-kernel.sh's
# tarball handling. Sets $BB to the verified binary's path.
qemu_get_busybox() {
local sha_file="$QEMU_DIR/busybox.sha256"
@@ -19,7 +19,7 @@ qemu_get_busybox() {
# Pin first: a missing pin refuses BEFORE downloading, same ordering as
# build/fetch-kernel-tarball.sh.
[ -f "$sha_file" ] || {
echo "FATAL: no pinned sha256 for busybox (expected $sha_file) refusing to build from an unverified binary" >&2
echo "FATAL: no pinned sha256 for busybox (expected $sha_file): refusing to build from an unverified binary" >&2
exit 1
}
if [ ! -f "$BB" ]; then
+4 -4
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env bash
# Build the VM's virtio disk image carrying the device's canonical 12-partition
# A/B layout (qemu/blkdevparts.conf the same string U-Boot and Linux parse on
# A/B layout (qemu/blkdevparts.conf: the same string U-Boot and Linux parse on
# hardware; there is no MBR/GPT). Every partition is placed at the exact offset
# the cmdline string declares; rootfs_a/rootfs_b/oem_a/oem_b/userdata get ext4,
# the boot-chain partitions (env/idblock/uboot/misc/boot_a/boot_b/recovery)
# stay zeroed the VM enters at -kernel and never reads them.
# stay zeroed: the VM enters at -kernel and never reads them.
#
# Built entirely UNPRIVILEGED: per-partition mkfs.ext4 -d (no loop mounts, no
# sudo), then dd'd into a sparse raw image.
@@ -72,7 +72,7 @@ for p in "$QEMU_DIR"/payload/*; do
install -m 0755 "$p" "$ROOT/usr/bin/$(basename "$p")"
done
# Firmware version stamp same path the device build writes; flared reads its
# Firmware version stamp: same path the device build writes; flared reads its
# running version here (downgrade rules key off it).
printf '%s\n' "$FW_VERSION" > "$ROOT/etc/warden-firmware-version"
@@ -144,7 +144,7 @@ place_partition() {
*) echo "FATAL: unknown partition name '$name' in blkdevparts.conf" >&2; exit 1 ;;
esac
DISK_END_TRACK "$off" "$size"
# dd in 4K blocks every offset in the canonical layout is 4K-aligned;
# dd in 4K blocks: every offset in the canonical layout is 4K-aligned;
# assert rather than assume, a misaligned write would corrupt a neighbor.
if [ $((off % 4096)) -ne 0 ] || [ $((size % 4096)) -ne 0 ]; then
echo "FATAL: partition $name not 4K-aligned (off=$off size=$size)" >&2
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Build the QEMU device-sim initramfs: the pinned static busybox + qemu/rootfs/.
# The busybox binary is the ONLY external input (sha256-pinned, fail-closed
# The busybox binary is the ONLY external input (sha256-pinned, fail-closed:
# see qemu/lib.sh).
#
# Env:
+3 -3
View File
@@ -1,7 +1,7 @@
# Guest Payloads
Drop **static musl armv7** binaries in this directory (contents are
gitignored binaries are never committed); `qemu/mkimage.sh` copies everything
gitignored; binaries are never committed); `qemu/mkimage.sh` copies everything
in this directory (except this README) into `/usr/bin/` of both rootfs slots.
Static musl is the same target the device uses for its Rust daemons, so the
exact production binaries run unmodified in the VM.
@@ -21,6 +21,6 @@ cp <flare-edge>/target/armv7-unknown-linux-musleabihf/release/warden-flared qemu
```
Stage-2 init starts `warden-flared`, `warden-modbus`, and `warden-ui` (the
UI additionally needs `--display on|headless` + the virt.fragment kernel for
UI also needs `--display on|headless` + the virt.fragment kernel for
/dev/fb0) automatically when present (logs land in `/tmp/<name>.log` inside
the guest). An empty payload is valid the image boots busybox-only.
the guest). An empty payload is valid: the image boots busybox-only.
+6 -6
View File
@@ -1,13 +1,13 @@
#!/bin/busybox sh
# shellcheck shell=dash
# Stage-1 rc: sourced by /init (still PID 1, initramfs root) when a virtio
# disk is present. Emulates U-Boot's slot choice mount the validated slot's
# disk is present. Emulates U-Boot's slot choice: mount the validated slot's
# rootfs and switch_root into it. This is an EMULATION of the A/B selection
# outcome, not the BCB/bootcount mechanism itself.
#
# Every guarded failure path `return`s to /init (valid in a sourced script;
# /init then falls through to shell/poweroff). The final exec is the one
# unguardable step: if switch_root itself fails to launch, the shell PID 1
# unguardable step: if switch_root itself fails to launch, the shell (PID 1)
# exits and the kernel panics; the applet-existence check below catches the
# only preventable variant of that.
@@ -19,22 +19,22 @@ slot="$(warden_slot)"
root="/dev/block/by-name/rootfs${slot}"
if [ ! -e "$root" ]; then
echo "rc: $root missing staying in initramfs"
echo "rc: $root missing: staying in initramfs"
return 0
fi
mkdir -p /mnt
if ! mount -t ext4 "$root" /mnt; then
echo "rc: mount of $root failed staying in initramfs"
echo "rc: mount of $root failed: staying in initramfs"
return 0
fi
if [ ! -x /mnt/sbin/init ]; then
echo "rc: $root has no /sbin/init staying in initramfs"
echo "rc: $root has no /sbin/init: staying in initramfs"
umount /mnt
return 0
fi
if ! command -v switch_root >/dev/null; then
echo "rc: busybox lacks switch_root staying in initramfs"
echo "rc: busybox lacks switch_root: staying in initramfs"
umount /mnt
return 0
fi
+3 -3
View File
@@ -1,11 +1,11 @@
# shellcheck shell=sh
# Shared helpers for the VM's stage-1 (/init + /etc/rc, initramfs) and stage-2
# (/sbin/init, disk rootfs) boot scripts. Present in both filesystems because
# both are staged from the same qemu/rootfs/ skeleton. ONE copy of each rule
# both are staged from the same qemu/rootfs/ skeleton. ONE copy of each rule:
# the slot-validation drift between two hand-copied parsers was a real
# review finding.
# Populate /dev/block/by-name/<PARTNAME> symlinks from sysfs uevents the
# Populate /dev/block/by-name/<PARTNAME> symlinks from sysfs uevents: the
# contract flare-edge's slotctl.rs relies on. blkdevparts= gives every vda
# partition a PARTNAME.
warden_populate_by_name() {
@@ -26,7 +26,7 @@ warden_populate_by_name() {
}
# Parse warden.slot= from the cmdline (whole-token, never substring) and
# VALIDATE it echoes "_a" or "_b", falling back to _a with a warning.
# VALIDATE it: echoes "_a" or "_b", falling back to _a with a warning.
warden_slot() {
slot="_a"
# shellcheck disable=SC2013 # cmdline TOKENS are the unit here, not lines
+2 -2
View File
@@ -2,7 +2,7 @@
# shellcheck shell=dash
# WardenOS QEMU device sim: initramfs /init (PID 1).
#
# Phase-1 duty: prove the kernel booted on -M virt print the sentinel the
# Phase-1 duty: prove the kernel booted on -M virt; print the sentinel the
# smoke test greps for, then power off (PSCI SYSTEM_OFF, so qemu exits).
# `warden.shell` on the kernel cmdline drops to an interactive shell instead.
@@ -19,7 +19,7 @@ mount -t sysfs sysfs /sys
echo "WARDEN-QEMU-BOOT-OK"
# With a virtio disk attached, hand over to the stage-1 rc (by-name symlinks,
# slot select, switch_root). It only returns on failure then fall through to
# slot select, switch_root). It only returns on failure; then fall through to
# the diskless shell/poweroff behavior below.
if [ -b /dev/vda ]; then
# shellcheck source=qemu/rootfs/etc/rc disable=SC1091
+4 -4
View File
@@ -2,7 +2,7 @@
# shellcheck shell=dash
# Stage-2 init: PID 1 on the disk rootfs (rootfs_a or rootfs_b), reached via
# switch_root from the initramfs. Brings up the minimum a WardenOS userspace
# needs mounts, by-name symlinks, network, serial alias then starts any
# needs (mounts, by-name symlinks, network, serial alias), then starts any
# payload daemons and holds. This stands in for the device's BusyBox SysV
# /etc/init.d/S* sequence; it is deliberately tiny, not a model of it.
@@ -17,7 +17,7 @@ mount -t tmpfs tmpfs /tmp
# shellcheck source=qemu/rootfs/etc/warden-lib.sh disable=SC1091
. /etc/warden-lib.sh
# Fresh devtmpfs repopulate the by-name contract; same VALIDATED slot rule
# Fresh devtmpfs: repopulate the by-name contract; same VALIDATED slot rule
# as stage 1 (shared helper, so the two can never drift).
warden_populate_by_name
slot="$(warden_slot)"
@@ -45,7 +45,7 @@ mkdir -p /userdata/warden
[ -c /dev/ttyS0 ] && ln -sf /dev/ttyS0 /dev/ttyS4
# Network: slirp user-mode net on eth0 (DHCP, fallback to QEMU's static map).
# The fallback keys off the interface actually having an address udhcpc
# The fallback keys off the interface actually having an address: udhcpc
# exiting 0 only proves a lease, not that the hook script applied it.
ip link set lo up
if [ -e /sys/class/net/eth0 ]; then
@@ -64,7 +64,7 @@ echo "WARDEN-QEMU-ROOTFS-OK slot=${slot}"
# Payload daemons (dropped into /usr/bin by qemu/mkimage.sh from qemu/payload/).
# WARDEN_FLARE_INSECURE=1: the VM's portal is the desk mock over plain HTTP.
# This is a dev instrument a production device build never sets it.
# This is a dev instrument: a production device build never sets it.
export WARDEN_FLARE_INSECURE=1
# No HPMCU on -M virt: the mailbox SRAM (0xff6fff00) is unmapped bus space
# here, and flared's /dev/mem poke dies with an external abort (SIGBUS). The
+1 -1
View File
@@ -2,7 +2,7 @@
name = "warden-rs485-bridge"
version = "0.1.0"
edition = "2021"
description = "Bridges a QEMU serial chardev (unix socket) to the warden-sim Modbus RTU slave, so the VM guest's RS-485 master polls the same simulated field bus the unit tests do fault injection included."
description = "Bridges a QEMU serial chardev (unix socket) to the warden-sim Modbus RTU slave, so the VM guest's RS-485 master polls the same simulated field bus the unit tests do, fault injection included."
license = "GPL-2.0-only"
[lib]
+1 -1
View File
@@ -1,4 +1,4 @@
//! Micro-benchmarks for the RS-485 bridge dispatch path same dependency-free
//! Micro-benchmarks for the RS-485 bridge dispatch path: same dependency-free
//! fixed-iteration pattern as sim/benches/sim_bench.rs: human timings to
//! stdout, one JSON line per benchmark to stderr for CI trend capture.
//!
+6 -6
View File
@@ -4,8 +4,8 @@
//! what it believes is /dev/ttyS4); this bridge is the wire and every slave on
//! it. Frames are delimited by an inter-frame gap of silence: RTU's 3.5-char
//! rule cannot survive a socket transport, so a wall-clock gap stands in for it.
//! A mis-split frame fails CRC inside `handle_frame`, which answers `None`
//! exactly a real slave staying silent and the master already treats silence
//! A mis-split frame fails CRC inside `handle_frame`, which answers `None`
//! (exactly a real slave staying silent), and the master already treats silence
//! as a timeout, so the failure mode degrades to a dropped poll, never a
//! phantom reply.
//!
@@ -26,7 +26,7 @@ pub const DEFAULT_GAP: Duration = Duration::from_millis(10);
/// Accumulation cap: a Modbus RTU ADU is at most 256 bytes, so anything past
/// 2x that without an inter-frame gap is a misbehaving master streaming
/// continuously — drop the buffer instead of growing without bound.
/// continuously. Drop the buffer instead of growing without bound.
const MAX_PENDING: usize = 512;
/// The shared bus: the slave plus its declared dimensions. The sim's register
@@ -75,11 +75,11 @@ pub fn pump_serial(
if buf.len() > MAX_PENDING {
// Rate-limit the log and back off for one gap so a master
// streaming continuously cannot peg a core and flood
// stderr mirroring the accept-loop backoff.
// stderr, mirroring the accept-loop backoff.
discards += 1;
if discards == 1 || discards.is_multiple_of(256) {
eprintln!(
"rs485: {} bytes buffered with no inter-frame gap \
"rs485: {} bytes buffered with no inter-frame gap, \
discarding (misbehaving master? {} discards so far)",
buf.len(),
discards
@@ -152,7 +152,7 @@ pub fn handle_control_line(line: &str, bus: &Bus) -> String {
return format!("err trailing arguments after '{cmd}'");
}
// Each arm states its own bound (bus.regs for register space, bus.bits for
// bit space) INLINE a previous string-keyed lookup defaulted silently to
// bit space) INLINE: a previous string-keyed lookup defaulted silently to
// the bit bound, which would have handed a future `get-input` command the
// wrong range and reintroduced the out-of-range panic this check prevents.
let mut s = bus.slave.lock().unwrap();
+4 -4
View File
@@ -3,7 +3,7 @@
//! control listener.
//!
//! Typical use (matches qemu/run.sh --rs485). Put the sockets in a private
//! per-run directory (mktemp -d) short (AF_UNIX caps paths at ~108 chars)
//! per-run directory (mktemp -d): short (AF_UNIX caps paths at ~108 chars)
//! and not guessable/pre-creatable by other local users, unlike a fixed
//! /tmp name:
//!
@@ -40,7 +40,7 @@ fn main() {
eprintln!("{name} needs a value");
usage()
});
// A following flag means the value was omitted — report the real
// A following flag means the value was omitted. Report the real
// problem instead of swallowing the flag as a bogus value.
if v.starts_with("--") {
eprintln!("{name} needs a value, got flag '{v}'");
@@ -70,7 +70,7 @@ fn main() {
if let Some(path) = control {
// Clear a stale socket from a previous run. A failure here that is not
// "nothing to remove" (e.g. someone else's file behind /tmp's sticky
// bit) will make the bind below fail — surface both errors.
// bit) will make the bind below fail. Surface both errors.
let removed = std::fs::remove_file(&path);
let listener = UnixListener::bind(&path).unwrap_or_else(|e| {
eprintln!("FATAL: cannot bind control socket {path}: {e}");
@@ -89,7 +89,7 @@ fn main() {
let conn = match conn {
Ok(c) => c,
Err(e) => {
eprintln!("rs485: control accept failed: {e} backing off");
eprintln!("rs485: control accept failed: {e}, backing off");
std::thread::sleep(Duration::from_millis(200));
continue;
}
+7 -7
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Launch the WardenOS device VM (qemu-system-arm -M virt, single Cortex-A7,
# 256M the RV1106G3's shape). See qemu/README.md for what this does and does
# 256M: the RV1106G3's shape). See qemu/README.md for what this does and does
# not emulate.
#
# Usage: run.sh --kernel <zImage> [options] [-- <extra qemu args>]
@@ -10,7 +10,7 @@
# when present; pass --no-disk for a diskless initramfs boot)
# --no-disk boot without a disk (initramfs shell/smoke behavior)
# --slot _a|_b rootfs slot to boot (default _a)
# --rtc DATE guest RTC base, e.g. 2021-01-01 reproduces the no-RTC
# --rtc DATE guest RTC base, e.g. 2021-01-01: reproduces the no-RTC
# "device boots believing 2021" incident class
# --rs485 SOCK unix socket chardev for the RS485/Modbus bridge
# (pci-serial: needs the virt.fragment kernel)
@@ -23,7 +23,7 @@
# --api-port N hostfwd 127.0.0.1:N -> guest :28443 (default 28443; 0 disables)
# --shell interactive shell in the guest instead of daemon hold
# --allow-apply let flared ACTUALLY apply OTA firmware (writes rootfs_b
# inside disk.img safe in the VM, never the default)
# inside disk.img, safe in the VM, never the default)
set -euo pipefail
QEMU_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -63,7 +63,7 @@ if [ -z "$KERNEL" ] || [ ! -f "$KERNEL" ]; then
exit 1
fi
[ -f "$INITRD" ] || {
echo "FATAL: initramfs not found at $INITRD run qemu/mkinitramfs.sh" >&2
echo "FATAL: initramfs not found at $INITRD: run qemu/mkinitramfs.sh" >&2
exit 1
}
case "$SLOT" in _a|_b) ;; *) echo "FATAL: --slot must be _a or _b" >&2; exit 1 ;; esac
@@ -72,14 +72,14 @@ if [ "$NO_DISK" -eq 0 ] && [ -z "$DISK" ] && [ -f "$OUT/disk.img" ]; then
DISK="$OUT/disk.img"
fi
if [ -n "$DISK" ] && [ ! -f "$DISK" ]; then
echo "FATAL: disk image $DISK not found run qemu/mkimage.sh (or pass --no-disk)" >&2
echo "FATAL: disk image $DISK not found: run qemu/mkimage.sh (or pass --no-disk)" >&2
exit 1
fi
# NOTE: never add `earlyprintk` the config's DEBUG_UART_PHYS is the RV1106's
# NOTE: never add `earlyprintk`: the config's DEBUG_UART_PHYS is the RV1106's
# 0xff4c0000, which does not exist on -M virt.
APPEND="console=ttyAMA0 rdinit=/init"
# Port 0 disables a forward — a boot smoke needs no host ports and must not
# Port 0 disables a forward. A boot smoke needs no host ports and must not
# fail on a busy default port.
NETDEV="user,id=n0"
[ "$SSH_PORT" != 0 ] && NETDEV="$NETDEV,hostfwd=tcp:127.0.0.1:${SSH_PORT}-:22"
+4 -4
View File
@@ -11,16 +11,16 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/
ZIMAGE="${1:-}"
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
echo "FATAL: usage: $0 <zImage> [initramfs] zImage missing or not a file: '${ZIMAGE:-}'" >&2
echo "FATAL: usage: $0 <zImage> [initramfs]: zImage missing or not a file: '${ZIMAGE:-}'" >&2
exit 1
fi
INITRD="${2:-$QDIR/out/initramfs.cpio.gz}"
[ -f "$INITRD" ] || {
echo "FATAL: initramfs not found at $INITRD run qemu/mkinitramfs.sh first" >&2
echo "FATAL: initramfs not found at $INITRD: run qemu/mkinitramfs.sh first" >&2
exit 1
}
command -v qemu-system-arm >/dev/null || {
echo "FATAL: qemu-system-arm not on PATH (apt-get install qemu-system-arm) see qemu/README.md" >&2
echo "FATAL: qemu-system-arm not on PATH (apt-get install qemu-system-arm): see qemu/README.md" >&2
exit 1
}
@@ -29,7 +29,7 @@ trap 'rm -f "$LOG"' EXIT
# Delegate the qemu invocation to run.sh (--no-disk) so the machine shape
# (-M virt,highmem=off, cpu, memory, virtio topology) lives in exactly one
# place the two hand-copied invocations had already drifted once.
# place: the two hand-copied invocations had already drifted once.
# timeout -k: a wedged qemu that ignores SIGTERM gets SIGKILLed 10s later
# instead of holding the job until the workflow-level timeout.
timeout -k 10 180 bash "$QDIR/run.sh" \
+3 -3
View File
@@ -2,7 +2,7 @@
# Clock-sanity scenario (issue #3 regression guard): boot the VM, run the
# musl-static clockprobe in the guest, and assert the vDSO monotonic RATE
# matches the kernel's /proc/uptime within 1%. Under QEMU -M virt this passes
# on the current kernel (measured 0.99963) a regression here means the
# on the current kernel (measured 0.99963): a regression here means the
# generic vDSO path broke. The RV1106 *board* leg of issue #3 is a separate,
# bench-only measurement; this scenario cannot see board-specific CNTFRQ or
# CNTVOFF misprogramming.
@@ -21,7 +21,7 @@ if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
exit 1
fi
command -v qemu-system-arm >/dev/null || {
echo "FATAL: qemu-system-arm not on PATH see qemu/README.md" >&2
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
exit 1
}
command -v arm-linux-gnueabihf-gcc >/dev/null || {
@@ -58,7 +58,7 @@ for _attempt in 1 2 3; do
sleep 3
kill -0 "$QEMU_PID" 2>/dev/null && break
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
echo "== hostfwd port collision on base $PORT retrying"
echo "== hostfwd port collision on base $PORT, retrying"
QEMU_PID=""
continue
fi
+8 -8
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# FULL OTA apply scenario the loop the desk e2e stops short of: the real
# FULL OTA apply scenario (the loop the desk e2e stops short of): the real
# flared inside the VM downloads a real signed tier-1 .wfw whose payload is a
# bootable rootfs, verifies it, and ACTUALLY WRITES rootfs_b (safe: it is a
# region inside disk.img); the harness then reboots into slot _b and asserts
@@ -7,7 +7,7 @@
#
# Documented emulation gaps (ADR-0006): the BCB slot CHOICE and the physical
# reset are performed by the harness (cmdline slot + a fresh qemu boot), not
# by U-Boot/CRU — those stay bench territory.
# by U-Boot/CRU. Those stay bench territory.
#
# FAILS CLOSED on missing prerequisites.
#
@@ -22,7 +22,7 @@ PATH="$PATH:/usr/sbin:/sbin"
ZIMAGE="${1:-}"
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
echo "FATAL: usage: $0 <zImage> the virt.fragment kernel variant" >&2
echo "FATAL: usage: $0 <zImage>: the virt.fragment kernel variant" >&2
exit 1
fi
if [ -z "${FLARE_EDGE:-}" ] || [ ! -f "$FLARE_EDGE/tools/mock-flare-portal.py" ]; then
@@ -34,7 +34,7 @@ fi
exit 1
}
command -v qemu-system-arm >/dev/null || {
echo "FATAL: qemu-system-arm not on PATH see qemu/README.md" >&2
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
exit 1
}
@@ -52,7 +52,7 @@ API_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(24))')"
PORT=$((20000 + RANDOM % 20000))
# 0. The offer's payload is a REAL bootable rootfs: the same skeleton the
# disk uses, stamped with the NEW version booting it is the proof.
# disk uses, stamped with the NEW version: booting it is the proof.
QEMU_DIR="$QDIR"
OUT="$QDIR/out"
# shellcheck source=qemu/lib.sh disable=SC1091
@@ -105,7 +105,7 @@ for _attempt in 1 2 3; do
sleep 3
kill -0 "$QEMU_PID" 2>/dev/null && break
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
echo "== hostfwd port collision on base $VMBASE retrying"
echo "== hostfwd port collision on base $VMBASE, retrying"
QEMU_PID=""
continue
fi
@@ -117,7 +117,7 @@ if [ -z "$QEMU_PID" ] || ! kill -0 "$QEMU_PID" 2>/dev/null; then
fi
# 4. wait for the apply to conclude. flared logs to its in-guest file, not
# the console but the next check-in REPORTS the outcome to the portal:
# the console, but the next check-in REPORTS the outcome to the portal:
# detail "hard reset failed after slot flip" is the exact post-apply state
# under the gated reset (write done, AvbABData flipped, reboot refused).
deadline=$((SECONDS + 420))
@@ -140,7 +140,7 @@ grep -aq "GET /api/v1/devices/$DEVICE_ID/firmware/assets/.* -> 200" "$WORK/mock.
echo "FATAL: staged without a portal asset download?!" >&2
exit 1
}
echo "== apply staged (asset downloaded, rootfs_b written, reset gated) rebooting into _b"
echo "== apply staged (asset downloaded, rootfs_b written, reset gated), rebooting into _b"
kill "$QEMU_PID" 2>/dev/null || true
wait "$QEMU_PID" 2>/dev/null || true
QEMU_PID=""
+10 -10
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env bash
# End-to-end device scenario: the REAL warden-flared, running inside the VM,
# checks in to flare-edge's mock FLARE portal on the host and pulls its
# firmware desired-state the exact device-initiated HTTPS(-shaped) flow a
# firmware desired-state: the exact device-initiated HTTPS(-shaped) flow a
# panel performs, with zero flare-edge code changes (the portal URL is a state
# file; 10.0.2.2 is slirp's host alias).
#
# FAILS CLOSED on every missing prerequisite never a soft skip.
# FAILS CLOSED on every missing prerequisite: never a soft skip.
#
# Usage: portal-scenario.sh <zImage-virt>
# Env: FLARE_EDGE path to a flare-edge checkout (provides mock-flare-portal.py)
@@ -16,7 +16,7 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/
ZIMAGE="${1:-}"
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
echo "FATAL: usage: $0 <zImage> the virt.fragment kernel variant" >&2
echo "FATAL: usage: $0 <zImage>: the virt.fragment kernel variant" >&2
exit 1
fi
if [ -z "${FLARE_EDGE:-}" ] || [ ! -f "$FLARE_EDGE/tools/mock-flare-portal.py" ]; then
@@ -24,11 +24,11 @@ if [ -z "${FLARE_EDGE:-}" ] || [ ! -f "$FLARE_EDGE/tools/mock-flare-portal.py" ]
exit 1
fi
[ -x "$QDIR/payload/warden-flared" ] || {
echo "FATAL: no qemu/payload/warden-flared build a static musl armv7 flared (see qemu/payload/README.md)" >&2
echo "FATAL: no qemu/payload/warden-flared: build a static musl armv7 flared (see qemu/payload/README.md)" >&2
exit 1
}
command -v qemu-system-arm >/dev/null || {
echo "FATAL: qemu-system-arm not on PATH see qemu/README.md" >&2
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
exit 1
}
@@ -56,7 +56,7 @@ FW_SIGNING_KEY_FILE="$FLARE_EDGE/tools/testdata/fw-dev-key.seed" \
WARDEN_UBOOT_VERSION=2017.09 \
bash "$FLARE_EDGE/tools/mk-wfw.sh" "$WORK/rootfs-payload.img" 1 0.0.2 "$WORK/offer.wfw"
# 1. mock portal on the host, our device pre-registered (no pairing needed
# 1. mock portal on the host, our device pre-registered (no pairing needed:
# the same credential-seeding shortcut fw-e2e-test.sh uses), offering the .wfw.
python3 "$FLARE_EDGE/tools/mock-flare-portal.py" \
--port "$PORT" --device "$DEVICE_ID:$API_KEY" \
@@ -79,7 +79,7 @@ echo "== mock portal on :$PORT, device $DEVICE_ID"
# 2. image seeded with the portal URL + credentials.
bash "$QDIR/mkinitramfs.sh"
# All four enrolment keys flare::enrolment() returns None (and the report
# All four enrolment keys: flare::enrolment() returns None (and the report
# loop parks forever) unless flare.site is present too.
bash "$QDIR/mkimage.sh" \
--portal-url "http://10.0.2.2:$PORT" \
@@ -88,7 +88,7 @@ bash "$QDIR/mkimage.sh" \
--state "flare.site=qemu-devsim"
# 3. boot the VM headless (daemons run; console log to file). Random hostfwd
# ports can collide with another process — detect the early qemu bind
# ports can collide with another process. Detect the early qemu bind
# failure and retry with a fresh base rather than failing spuriously.
QEMU_PID=""
for _attempt in 1 2 3; do
@@ -103,7 +103,7 @@ for _attempt in 1 2 3; do
break
fi
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
echo "== hostfwd port collision on base $VMBASE retrying"
echo "== hostfwd port collision on base $VMBASE, retrying"
QEMU_PID=""
continue
fi
@@ -116,7 +116,7 @@ if [ -z "$QEMU_PID" ] || ! kill -0 "$QEMU_PID" 2>/dev/null; then
exit 1
fi
# 4. assert: rootfs up, and the portal saw from OUR device id an
# 4. assert: rootfs up, and the portal saw (from OUR device id) an
# authenticated check-in, the firmware desired-state pull, and the signed
# .wfw asset download (i.e. flared accepted the offer and fetched it; the
# verify+stage+APPLYING that follow are a dry run without
+4 -4
View File
@@ -8,7 +8,7 @@
# RV1106-only init steps degrade on virt (backlight, goodix, npu, the 5.10
# /oem modules fail vermagic), and binaries older than the flare-edge #106
# fix reproduce that crash faithfully. Interactive login uses the image's own
# credentials deliberately not recorded here.
# credentials: deliberately not recorded here.
#
# FAILS CLOSED on missing prerequisites.
#
@@ -21,12 +21,12 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/
ZIMAGE="${1:-}"; ROOTFS="${2:-}"; OEM="${3:-}"
for f in "$ZIMAGE" "$ROOTFS" "$OEM"; do
if [ -z "$f" ] || [ ! -f "$f" ]; then
echo "FATAL: usage: $0 <zImage> <rootfs.img> <oem.img> '$f' missing" >&2
echo "FATAL: usage: $0 <zImage> <rootfs.img> <oem.img>: '$f' missing" >&2
exit 1
fi
done
command -v qemu-system-arm >/dev/null || {
echo "FATAL: qemu-system-arm not on PATH see qemu/README.md" >&2
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
exit 1
}
@@ -51,7 +51,7 @@ for _attempt in 1 2 3; do
sleep 3
kill -0 "$QEMU_PID" 2>/dev/null && break
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
echo "== hostfwd port collision on base $PORT retrying"
echo "== hostfwd port collision on base $PORT, retrying"
QEMU_PID=""
continue
fi
+9 -9
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# Display + touch scenario: boot the VM headless with virtio-gpu, wait for the
# LVGL UI (fbdev build) to render a real frame, then inject an absolute touch
# tap on the Metrics tab (virtio-tablet) and ASSERT the frame changed the
# tap on the Metrics tab (virtio-tablet) and ASSERT the frame changed: the
# device-level analogue of flare-edge's Xvfb/xdotool sim-test.sh. Readiness is
# polled from screendumps on bounded deadlines, never guessed with fixed
# sleeps: TCG renders CPU-bound and a loaded host can be arbitrarily slow.
@@ -17,15 +17,15 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/
ZIMAGE="${1:-}"
OUTDIR="${2:-$QDIR/out}"
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
echo "FATAL: usage: $0 <zImage> [out-dir] the virt.fragment kernel variant" >&2
echo "FATAL: usage: $0 <zImage> [out-dir]: the virt.fragment kernel variant" >&2
exit 1
fi
[ -x "$QDIR/payload/warden-ui" ] || {
echo "FATAL: no qemu/payload/warden-ui build it with flare-edge tools/build-ui-vm.sh" >&2
echo "FATAL: no qemu/payload/warden-ui: build it with flare-edge tools/build-ui-vm.sh" >&2
exit 1
}
command -v qemu-system-arm >/dev/null || {
echo "FATAL: qemu-system-arm not on PATH see qemu/README.md" >&2
echo "FATAL: qemu-system-arm not on PATH: see qemu/README.md" >&2
exit 1
}
@@ -41,7 +41,7 @@ trap cleanup EXIT
bash "$QDIR/mkinitramfs.sh"
bash "$QDIR/mkimage.sh"
# Random hostfwd ports can collide — detect qemu's early bind failure and
# Random hostfwd ports can collide. Detect qemu's early bind failure and
# retry with a fresh base rather than failing spuriously.
for _attempt in 1 2 3; do
PORT=$((21000 + RANDOM % 20000))
@@ -53,7 +53,7 @@ for _attempt in 1 2 3; do
sleep 3
kill -0 "$QEMU_PID" 2>/dev/null && break
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
echo "== hostfwd port collision on base $PORT retrying"
echo "== hostfwd port collision on base $PORT, retrying"
QEMU_PID=""
continue
fi
@@ -96,7 +96,7 @@ EOF
# The VM can die mid-poll (OOM, crash): check liveness before every QMP
# call so the failure is OUR message + console evidence, not a python
# traceback and preserve the console log before the trap removes $WORK.
# traceback, and preserve the console log before the trap removes $WORK.
vm_alive_or_die() {
kill -0 "$QEMU_PID" 2>/dev/null && return 0
echo "FATAL: VM exited during the screendump poll" >&2
@@ -121,7 +121,7 @@ done
}
# Tap the "Metrics" tab: pixel (373,40) of 720x720 scaled to the QMP absolute
# range 0..32767 — switching tabs must repaint the content area. Poll for the
# range 0..32767. Switching tabs must repaint the content area. Poll for the
# repaint rather than guessing a delay.
vm_alive_or_die
qmp tap 16975 1820
@@ -142,7 +142,7 @@ cp "$WORK/shot1.ppm" "$OUTDIR/ui-shot1.ppm"
cp "$WORK/shot2.ppm" "$OUTDIR/ui-shot2.ppm" 2>/dev/null || true
[ "$changed" = 1 ] || {
echo "FATAL: tapping the Metrics tab did not change the frame within 90s touch is not reaching the UI" >&2
echo "FATAL: tapping the Metrics tab did not change the frame within 90s: touch is not reaching the UI" >&2
cp "$WORK/console.log" "$OUTDIR/ui-shot-console.log" || true
exit 1
}
+1 -1
View File
@@ -3,7 +3,7 @@
//! Dependency-free (`harness = false`): a fixed-iteration timing loop, so the sim
//! crate keeps zero deps and CI can capture a stable ns/op number per model with no
//! criterion tree to compile. Human-readable timings go to stdout; one JSON line per
//! benchmark goes to stderr for CI trend capture (`bench: ns_per_op `).
//! benchmark goes to stderr for CI trend capture (`bench: ... ns_per_op ...`).
//!
//! Run: `cargo bench` (or `cargo run --release --bench sim_bench`).

Some files were not shown because too many files have changed in this diff Show More