diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 233cb91..da124a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 quirk — build calls bare python) + - name: provision `python` (SDK quirk, the 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 diff --git a/README.md b/README.md index 5092d76..31ae408 100644 --- a/README.md +++ b/README.md @@ -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,25 +74,18 @@ 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. - ## License **GPL-2.0-only**, repo-wide (`LICENSE`; a per-file SPDX identifier governs diff --git a/build/build-kernel.sh b/build/build-kernel.sh index f0caccd..0b62b15 100755 --- a/build/build-kernel.sh +++ b/build/build-kernel.sh @@ -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; } diff --git a/build/fetch-kernel-tarball.sh b/build/fetch-kernel-tarball.sh index 6d16cbe..d96f913 100644 --- a/build/fetch-kernel-tarball.sh +++ b/build/fetch-kernel-tarball.sh @@ -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 @@ -17,7 +17,7 @@ TB="${1:?usage: fetch-kernel-tarball.sh }" # 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 diff --git a/docs/architecture.md b/docs/architecture.md index 3af6c7d..e9f05cd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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=` | (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 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/` (§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). diff --git a/docs/ci-cd.md b/docs/ci-cd.md index 6902140..ebb278d 100644 --- a/docs/ci-cd.md +++ b/docs/ci-cd.md @@ -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). | diff --git a/docs/decisions/0001-kernel-base.md b/docs/decisions/0001-kernel-base.md index 9306f37..f28b699 100644 --- a/docs/decisions/0001-kernel-base.md +++ b/docs/decisions/0001-kernel-base.md @@ -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 diff --git a/docs/decisions/0002-mcdc-tiering.md b/docs/decisions/0002-mcdc-tiering.md index 1e08c7c..a2cfffc 100644 --- a/docs/decisions/0002-mcdc-tiering.md +++ b/docs/decisions/0002-mcdc-tiering.md @@ -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//test/` host harness (a `Makefile` + - `test_.c`) that calls the one shared `drivers/enforce-mcdc.sh` — which derives + `test_.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%. diff --git a/docs/decisions/0003-standalone-repo.md b/docs/decisions/0003-standalone-repo.md index 2f38e6d..05cf795 100644 --- a/docs/decisions/0003-standalone-repo.md +++ b/docs/decisions/0003-standalone-repo.md @@ -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. diff --git a/docs/decisions/0004-ci-runner.md b/docs/decisions/0004-ci-runner.md index ffe161f..11a2f4f 100644 --- a/docs/decisions/0004-ci-runner.md +++ b/docs/decisions/0004-ci-runner.md @@ -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. diff --git a/docs/decisions/0005-driver-source-of-truth.md b/docs/decisions/0005-driver-source-of-truth.md index 7fa4928..aa474e3 100644 --- a/docs/decisions/0005-driver-source-of-truth.md +++ b/docs/decisions/0005-driver-source-of-truth.md @@ -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. diff --git a/docs/decisions/0006-qemu-device-sim.md b/docs/decisions/0006-qemu-device-sim.md index 23f69dd..41976c4 100644 --- a/docs/decisions/0006-qemu-device-sim.md +++ b/docs/decisions/0006-qemu-device-sim.md @@ -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 diff --git a/docs/decisions/0007-public-repo-hosted-kernel-build.md b/docs/decisions/0007-public-repo-hosted-kernel-build.md index 504b112..36b8099 100644 --- a/docs/decisions/0007-public-repo-hosted-kernel-build.md +++ b/docs/decisions/0007-public-repo-hosted-kernel-build.md @@ -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. diff --git a/docs/decisions/0008-panel-devenv-positioning.md b/docs/decisions/0008-panel-devenv-positioning.md index e3bee8d..523feef 100644 --- a/docs/decisions/0008-panel-devenv-positioning.md +++ b/docs/decisions/0008-panel-devenv-positioning.md @@ -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 diff --git a/docs/npu-graphics-feasibility.md b/docs/npu-graphics-feasibility.md index dd189dc..1c53b4d 100644 --- a/docs/npu-graphics-feasibility.md +++ b/docs/npu-graphics-feasibility.md @@ -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 128–256MB 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.5–1 TOPS-class, 128–256MB-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. diff --git a/docs/workflows/freshness-contract.md b/docs/workflows/freshness-contract.md index c747afd..453e778 100644 --- a/docs/workflows/freshness-contract.md +++ b/docs/workflows/freshness-contract.md @@ -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 diff --git a/docs/workflows/relay-drive.md b/docs/workflows/relay-drive.md index d22a295..6f2bdef 100644 --- a/docs/workflows/relay-drive.md +++ b/docs/workflows/relay-drive.md @@ -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 diff --git a/drivers/README.md b/drivers/README.md index 9510e10..17bc967 100644 --- a/drivers/README.md +++ b/drivers/README.md @@ -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 `.{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 build/.c.gcov +gate: the Makefile calls `bash ../../enforce-mcdc.sh build/.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 diff --git a/drivers/freshness/freshness.c b/drivers/freshness/freshness.c index b5eee56..9540cbb 100644 --- a/drivers/freshness/freshness.c +++ b/drivers/freshness/freshness.c @@ -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; diff --git a/drivers/freshness/freshness.h b/drivers/freshness/freshness.h index cb0d07f..fbcd157 100644 --- a/drivers/freshness/freshness.h +++ b/drivers/freshness/freshness.h @@ -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 /* 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); diff --git a/drivers/freshness/test/Makefile b/drivers/freshness/test/Makefile index f41e32a..ea609bc 100644 --- a/drivers/freshness/test/Makefile +++ b/drivers/freshness/test/Makefile @@ -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 diff --git a/drivers/freshness/test/test_freshness.c b/drivers/freshness/test/test_freshness.c index f0185a5..2b2dc01 100644 --- a/drivers/freshness/test/test_freshness.c +++ b/drivers/freshness/test/test_freshness.c @@ -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); } diff --git a/drivers/relays/relays.h b/drivers/relays/relays.h index 0ea97a0..a21987f 100644 --- a/drivers/relays/relays.h +++ b/drivers/relays/relays.h @@ -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 --------------------------------------------- diff --git a/drivers/relays/test/Makefile b/drivers/relays/test/Makefile index 5d04d9f..5fec911 100644 --- a/drivers/relays/test/Makefile +++ b/drivers/relays/test/Makefile @@ -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. diff --git a/drivers/relays/test/test_relays.c b/drivers/relays/test/test_relays.c index c399df4..78cb2cb 100644 --- a/drivers/relays/test/test_relays.c +++ b/drivers/relays/test/test_relays.c @@ -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" diff --git a/kernel/docs/bringup.md b/kernel/docs/bringup.md index 6f8bc4c..f16cc2f 100644 --- a/kernel/docs/bringup.md +++ b/kernel/docs/bringup.md @@ -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.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; + 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) diff --git a/kernel/docs/m2-boot-on-c8a3.md b/kernel/docs/m2-boot-on-c8a3.md index 97e516a..6c67b01 100644 --- a/kernel/docs/m2-boot-on-c8a3.md +++ b/kernel/docs/m2-boot-on-c8a3.md @@ -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 tries→0, 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. diff --git a/kernel/rv1106-enablement/CAPABILITIES-AUDIT.md b/kernel/rv1106-enablement/CAPABILITIES-AUDIT.md index 7a10279..840702c 100644 --- a/kernel/rv1106-enablement/CAPABILITIES-AUDIT.md +++ b/kernel/rv1106-enablement/CAPABILITIES-AUDIT.md @@ -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 | 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. | +| **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). diff --git a/kernel/rv1106-enablement/DRIVER-PARITY.md b/kernel/rv1106-enablement/DRIVER-PARITY.md index 133c49f..d383005 100644 --- a/kernel/rv1106-enablement/DRIVER-PARITY.md +++ b/kernel/rv1106-enablement/DRIVER-PARITY.md @@ -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 0–4) | +| 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 (DWC3→xhci, 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 | diff --git a/kernel/rv1106-enablement/OVERNIGHT-PLAN.md b/kernel/rv1106-enablement/OVERNIGHT-PLAN.md index 27f6bd2..381fe17 100644 --- a/kernel/rv1106-enablement/OVERNIGHT-PLAN.md +++ b/kernel/rv1106-enablement/OVERNIGHT-PLAN.md @@ -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. diff --git a/kernel/rv1106-enablement/OVERNIGHT-RESULTS.md b/kernel/rv1106-enablement/OVERNIGHT-RESULTS.md index 791a6d0..8cc6529 100644 --- a/kernel/rv1106-enablement/OVERNIGHT-RESULTS.md +++ b/kernel/rv1106-enablement/OVERNIGHT-RESULTS.md @@ -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.1→8.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 bootcount→loader). +- 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_bsp→fdrv(→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`). diff --git a/kernel/rv1106-enablement/PORT-STATUS.md b/kernel/rv1106-enablement/PORT-STATUS.md index 3a0b640..1710fb5 100644 --- a/kernel/rv1106-enablement/PORT-STATUS.md +++ b/kernel/rv1106-enablement/PORT-STATUS.md @@ -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.10→6.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 `` (kernel +3. **Header split**: `panic_notifier_list` moved to `` (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 (M1–M6 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 ``` diff --git a/kernel/rv1106-enablement/PROVENANCE.md b/kernel/rv1106-enablement/PROVENANCE.md index d350243..fcfbee9 100644 --- a/kernel/rv1106-enablement/PROVENANCE.md +++ b/kernel/rv1106-enablement/PROVENANCE.md @@ -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.) diff --git a/kernel/rv1106-enablement/REMAINING-PORTS.md b/kernel/rv1106-enablement/REMAINING-PORTS.md index 03437b8..48e8aa3 100644 --- a/kernel/rv1106-enablement/REMAINING-PORTS.md +++ b/kernel/rv1106-enablement/REMAINING-PORTS.md @@ -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, RAND0–7 0x0020–0x3C) 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. diff --git a/kernel/rv1106-enablement/adc/SARADC-FIX.md b/kernel/rv1106-enablement/adc/SARADC-FIX.md index 15911d0..731dcce 100644 --- a/kernel/rv1106-enablement/adc/SARADC-FIX.md +++ b/kernel/rv1106-enablement/adc/SARADC-FIX.md @@ -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). diff --git a/kernel/rv1106-enablement/audio/PORT-PROGRESS.md b/kernel/rv1106-enablement/audio/PORT-PROGRESS.md index f1aeee3..3420cdd 100644 --- a/kernel/rv1106-enablement/audio/PORT-PROGRESS.md +++ b/kernel/rv1106-enablement/audio/PORT-PROGRESS.md @@ -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--y := .o` instead of the vendor's -`snd-soc--objs := .o` — same semantics, current tree's naming convention followed): +`snd-soc--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 ` — 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 ` with `#include `. 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 ` — `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 `: 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 ` with `#include `. 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 `: `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 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). diff --git a/kernel/rv1106-enablement/build-m2.sh b/kernel/rv1106-enablement/build-m2.sh index c8b5f90..9522a9f 100755 --- a/kernel/rv1106-enablement/build-m2.sh +++ b/kernel/rv1106-enablement/build-m2.sh @@ -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"). diff --git a/kernel/rv1106-enablement/configs/m2-earlycon.fragment b/kernel/rv1106-enablement/configs/m2-earlycon.fragment index bd23b57..d15ff54 100644 --- a/kernel/rv1106-enablement/configs/m2-earlycon.fragment +++ b/kernel/rv1106-enablement/configs/m2-earlycon.fragment @@ -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 diff --git a/kernel/rv1106-enablement/display/README.md b/kernel/rv1106-enablement/display/README.md index e44680e..a420944 100644 --- a/kernel/rv1106-enablement/display/README.md +++ b/kernel/rv1106-enablement/display/README.md @@ -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. diff --git a/kernel/rv1106-enablement/display/VERIFIED.md b/kernel/rv1106-enablement/display/VERIFIED.md index c2d3236..026a697 100644 --- a/kernel/rv1106-enablement/display/VERIFIED.md +++ b/kernel/rv1106-enablement/display/VERIFIED.md @@ -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 vop↔panel 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. diff --git a/kernel/rv1106-enablement/dts/README.md b/kernel/rv1106-enablement/dts/README.md index a31fa6d..990f619 100644 --- a/kernel/rv1106-enablement/dts/README.md +++ b/kernel/rv1106-enablement/dts/README.md @@ -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, gpio0–4, 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. diff --git a/kernel/rv1106-enablement/dts/rv1106-warden-m2.dts b/kernel/rv1106-enablement/dts/rv1106-warden-m2.dts index 4186525..aff2d96 100644 --- a/kernel/rv1106-enablement/dts/rv1106-warden-m2.dts +++ b/kernel/rv1106-enablement/dts/rv1106-warden-m2.dts @@ -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 / { - 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 { diff --git a/kernel/rv1106-enablement/dts/rv1106-warden.dts b/kernel/rv1106-enablement/dts/rv1106-warden.dts index 568dc44..e1e05df 100644 --- a/kernel/rv1106-enablement/dts/rv1106-warden.dts +++ b/kernel/rv1106-enablement/dts/rv1106-warden.dts @@ -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"; }; diff --git a/kernel/rv1106-enablement/gmac/PORT-DONE.md b/kernel/rv1106-enablement/gmac/PORT-DONE.md index e915a75..5d8d490 100644 --- a/kernel/rv1106-enablement/gmac/PORT-DONE.md +++ b/kernel/rv1106-enablement/gmac/PORT-DONE.md @@ -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. diff --git a/kernel/rv1106-enablement/mailbox/PLAN.md b/kernel/rv1106-enablement/mailbox/PLAN.md index 2d03d84..96de36d 100644 --- a/kernel/rv1106-enablement/mailbox/PLAN.md +++ b/kernel/rv1106-enablement/mailbox/PLAN.md @@ -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 Linux→controller 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`, diff --git a/kernel/rv1106-enablement/mailbox/VERIFIED.md b/kernel/rv1106-enablement/mailbox/VERIFIED.md index 733520e..d364e6c 100644 --- a/kernel/rv1106-enablement/mailbox/VERIFIED.md +++ b/kernel/rv1106-enablement/mailbox/VERIFIED.md @@ -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). diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/Makefile b/kernel/rv1106-enablement/mailbox/scr1-echo/Makefile index 54678a7..6821689 100644 --- a/kernel/rv1106-enablement/mailbox/scr1-echo/Makefile +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/Makefile @@ -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. diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/link.lds b/kernel/rv1106-enablement/mailbox/scr1-echo/link.lds index 0ff7a5e..198b5a7 100644 --- a/kernel/rv1106-enablement/mailbox/scr1-echo/link.lds +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/link.lds @@ -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 diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/load-echo-fw.sh b/kernel/rv1106-enablement/mailbox/scr1-echo/load-echo-fw.sh index d594a7f..57926da 100755 --- a/kernel/rv1106-enablement/mailbox/scr1-echo/load-echo-fw.sh +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/load-echo-fw.sh @@ -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 diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/main.c b/kernel/rv1106-enablement/mailbox/scr1-echo/main.c index ceda3fc..b3c1ef2 100644 --- a/kernel/rv1106-enablement/mailbox/scr1-echo/main.c +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/main.c @@ -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) diff --git a/kernel/rv1106-enablement/mailbox/scr1-echo/mbox-roundtrip.sh b/kernel/rv1106-enablement/mailbox/scr1-echo/mbox-roundtrip.sh index 96c7899..abd85de 100755 --- a/kernel/rv1106-enablement/mailbox/scr1-echo/mbox-roundtrip.sh +++ b/kernel/rv1106-enablement/mailbox/scr1-echo/mbox-roundtrip.sh @@ -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} diff --git a/kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md b/kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md index 7f2e372..35fd7db 100644 --- a/kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md +++ b/kernel/rv1106-enablement/npu/OPEN-NPU-PLAN.md @@ -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 1→0 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)< 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`._ diff --git a/kernel/rv1106-enablement/npu/PORT-PLAN.md b/kernel/rv1106-enablement/npu/PORT-PLAN.md index da06af7..b1d595e 100644 --- a/kernel/rv1106-enablement/npu/PORT-PLAN.md +++ b/kernel/rv1106-enablement/npu/PORT-PLAN.md @@ -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.10→6.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 — -M1–M3 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 #include #include @@ -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.10→6.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 `), and are the actual vendor driver — the +(`Felix Zeng `), 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). diff --git a/kernel/rv1106-enablement/npu/PORT-PROGRESS.md b/kernel/rv1106-enablement/npu/PORT-PROGRESS.md index 7264463..ff7c0e4 100644 --- a/kernel/rv1106-enablement/npu/PORT-PROGRESS.md +++ b/kernel/rv1106-enablement/npu/PORT-PROGRESS.md @@ -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 `) 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 ` 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 #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 | `` 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 | `` and the `pfn_t` wrapper type (`__pfn_to_pfn_t()`, `PFN_DEV`) were removed entirely from mainline. Dropped the include; `vmf_insert_mixed()` (the only call site reachable at `KERNEL_VERSION(4,15,0) <= LINUX_VERSION_CODE`, which is our branch) now takes a plain `unsigned long pfn` directly: `pfn = page_to_pfn(...)` was already computing that raw value, so the fix is just passing `pfn` instead of `__pfn_to_pfn_t(pfn, PFN_DEV)`. | | 2 | `vmap()`/`vunmap()`/`VM_MAP` used to be pulled in transitively; 6.18 needs `` included explicitly. Added it. | -| 3 | `%zu` format specifier for `rknpu_obj->size` (`-Werror=format=`): the field is declared `unsigned long`, not `size_t`, on this target — changed to `%lu`. (Pure `-Wformat` pickiness, not a real 5.10-vs-6.18 delta; the vendor's own type just doesn't match `%zu` on this ABI and newer GCC/kernel `-Werror` catches it.) | +| 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 \ /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.2–1.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`. diff --git a/kernel/rv1106-enablement/npu/VERIFIED.md b/kernel/rv1106-enablement/npu/VERIFIED.md index f6ddfe0..6686c71 100644 --- a/kernel/rv1106-enablement/npu/VERIFIED.md +++ b/kernel/rv1106-enablement/npu/VERIFIED.md @@ -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 diff --git a/kernel/rv1106-enablement/npu/rknpu_version_test.c b/kernel/rv1106-enablement/npu/rknpu_version_test.c index fd0f83f..7a38bc7 100644 --- a/kernel/rv1106-enablement/npu/rknpu_version_test.c +++ b/kernel/rv1106-enablement/npu/rknpu_version_test.c @@ -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/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 diff --git a/kernel/rv1106-enablement/pvtm/PORT-DONE.md b/kernel/rv1106-enablement/pvtm/PORT-DONE.md index 03d99bf..1b38d68 100644 --- a/kernel/rv1106-enablement/pvtm/PORT-DONE.md +++ b/kernel/rv1106-enablement/pvtm/PORT-DONE.md @@ -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 ` 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.) diff --git a/kernel/rv1106-enablement/rga/PORT-DONE.md b/kernel/rv1106-enablement/rga/PORT-DONE.md index f3e3ade..81e2e96 100644 --- a/kernel/rv1106-enablement/rga/PORT-DONE.md +++ b/kernel/rv1106-enablement/rga/PORT-DONE.md @@ -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()` → `MODULE_IMPORT_NS("DMA_BUF")` (string form). +6. `MODULE_IMPORT_NS()` -> `MODULE_IMPORT_NS("DMA_BUF")` (string form). diff --git a/kernel/rv1106-enablement/rga/PORT-PLAN.md b/kernel/rv1106-enablement/rga/PORT-PLAN.md index 6e1c01b..715f0bd 100644 --- a/kernel/rv1106-enablement/rga/PORT-PLAN.md +++ b/kernel/rv1106-enablement/rga/PORT-PLAN.md @@ -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 `` and `` 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 `/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 diff --git a/kernel/rv1106-enablement/rng-otp/PORT-DONE.md b/kernel/rv1106-enablement/rng-otp/PORT-DONE.md index a96688f..0e47d69 100644 --- a/kernel/rv1106-enablement/rng-otp/PORT-DONE.md +++ b/kernel/rv1106-enablement/rng-otp/PORT-DONE.md @@ -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. diff --git a/kernel/rv1106-enablement/rtc/README.md b/kernel/rv1106-enablement/rtc/README.md index 4750bb3..1a07d19 100644 --- a/kernel/rv1106-enablement/rtc/README.md +++ b/kernel/rv1106-enablement/rtc/README.md @@ -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.10→6.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. diff --git a/kernel/rv1106-enablement/timer/PLAN.md b/kernel/rv1106-enablement/timer/PLAN.md index 0b314f1..a1e8891 100644 --- a/kernel/rv1106-enablement/timer/PLAN.md +++ b/kernel/rv1106-enablement/timer/PLAN.md @@ -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 = ; 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. diff --git a/kernel/rv1106-enablement/touch/VERIFIED.md b/kernel/rv1106-enablement/touch/VERIFIED.md index 13dd1db..98fe13c 100644 --- a/kernel/rv1106-enablement/touch/VERIFIED.md +++ b/kernel/rv1106-enablement/touch/VERIFIED.md @@ -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`. diff --git a/kernel/rv1106-enablement/usb/rv1106_usb2phy_cfg.c.frag b/kernel/rv1106-enablement/usb/rv1106_usb2phy_cfg.c.frag index 356d09c..216a46d 100644 --- a/kernel/rv1106-enablement/usb/rv1106_usb2phy_cfg.c.frag +++ b/kernel/rv1106-enablement/usb/rv1106_usb2phy_cfg.c.frag @@ -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[] = { { diff --git a/kernel/rv1106-enablement/wifi/PORT-PLAN.md b/kernel/rv1106-enablement/wifi/PORT-PLAN.md index 17c5c9f..523a48b 100644 --- a/kernel/rv1106-enablement/wifi/PORT-PLAN.md +++ b/kernel/rv1106-enablement/wifi/PORT-PLAN.md @@ -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` (M0–M3 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-spin→sleep 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:1457–1474). 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 # firmware→driver 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 M1–M3 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.10→6.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.14–6.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 async→cfg80211 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.10→6.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.10→6.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.10→6.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 bsp→fdrv→btlpm 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 — M1–M3 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 M1–M3 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. diff --git a/kernel/rv1106-enablement/wifi/PORT-PROGRESS.md b/kernel/rv1106-enablement/wifi/PORT-PROGRESS.md index acb7f9e..98ed131 100644 --- a/kernel/rv1106-enablement/wifi/PORT-PROGRESS.md +++ b/kernel/rv1106-enablement/wifi/PORT-PROGRESS.md @@ -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. -- **`` (vendor-only, PORT-PLAN.md §3.7)** — removed +- **`` (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 bsp→fdrv 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 M1–M3 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. diff --git a/kernel/rv1106-enablement/wifi/VERIFIED-on-c8a3.md b/kernel/rv1106-enablement/wifi/VERIFIED-on-c8a3.md index 25d937b..76c808c 100644 --- a/kernel/rv1106-enablement/wifi/VERIFIED-on-c8a3.md +++ b/kernel/rv1106-enablement/wifi/VERIFIED-on-c8a3.md @@ -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: ... link/ether ` - `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.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`). +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. diff --git a/kernel/rv1106-enablement/wifi/kbuild/aic8800/Kconfig b/kernel/rv1106-enablement/wifi/kbuild/aic8800/Kconfig index f6bef83..ee0f44f 100644 --- a/kernel/rv1106-enablement/wifi/kbuild/aic8800/Kconfig +++ b/kernel/rv1106-enablement/wifi/kbuild/aic8800/Kconfig @@ -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" diff --git a/kernel/rv1106-enablement/wifi/kbuild/aic8800/Makefile b/kernel/rv1106-enablement/wifi/kbuild/aic8800/Makefile index b7432c6..7c0be0b 100644 --- a/kernel/rv1106-enablement/wifi/kbuild/aic8800/Makefile +++ b/kernel/rv1106-enablement/wifi/kbuild/aic8800/Makefile @@ -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/ diff --git a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_bsp/Makefile b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_bsp/Makefile index 64f8480..9e6b9d1 100755 --- a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_bsp/Makefile +++ b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_bsp/Makefile @@ -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 diff --git a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_btlpm/Kconfig b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_btlpm/Kconfig index 872a606..e02e0c7 100755 --- a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_btlpm/Kconfig +++ b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_btlpm/Kconfig @@ -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/. diff --git a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_btlpm/Makefile b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_btlpm/Makefile index 21750dd..93c5066 100755 --- a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_btlpm/Makefile +++ b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_btlpm/Makefile @@ -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 diff --git a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_fdrv/Kconfig b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_fdrv/Kconfig index 33728e7..dc48e68 100755 --- a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_fdrv/Kconfig +++ b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_fdrv/Kconfig @@ -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/. diff --git a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_fdrv/Makefile b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_fdrv/Makefile index 26138f0..583183a 100755 --- a/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_fdrv/Makefile +++ b/kernel/rv1106-enablement/wifi/kbuild/aic8800/aic8800_fdrv/Makefile @@ -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 diff --git a/kernel/rv1106-enablement/wifi/wifi-dt-block.dtsi b/kernel/rv1106-enablement/wifi/wifi-dt-block.dtsi index 09b4d2e..3d74dd9 100644 --- a/kernel/rv1106-enablement/wifi/wifi-dt-block.dtsi +++ b/kernel/rv1106-enablement/wifi/wifi-dt-block.dtsi @@ -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>; diff --git a/patches/20-arch-dts-bindings.patch b/patches/20-arch-dts-bindings.patch index d10e8d4..207b8aa 100644 --- a/patches/20-arch-dts-bindings.patch +++ b/patches/20-arch-dts-bindings.patch @@ -1835,7 +1835,7 @@ index 000000000..418652552 +#include + +/ { -+ 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>; diff --git a/patches/README.md b/patches/README.md index 0b330a3..db08dc1 100644 --- a/patches/README.md +++ b/patches/README.md @@ -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. diff --git a/qemu/README.md b/qemu/README.md index f371482..8b7810a 100644 --- a/qemu/README.md +++ b/qemu/README.md @@ -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 ``; `FLARE_EDGE=` 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. diff --git a/qemu/blkdevparts.conf b/qemu/blkdevparts.conf index 4778504..c685f36 100644 --- a/qemu/blkdevparts.conf +++ b/qemu/blkdevparts.conf @@ -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)' diff --git a/qemu/configs/virt.fragment b/qemu/configs/virt.fragment index e833b3a..95411d4 100644 --- a/qemu/configs/virt.fragment +++ b/qemu/configs/virt.fragment @@ -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 diff --git a/qemu/lib.sh b/qemu/lib.sh index 66324b7..3014ee0 100644 --- a/qemu/lib.sh +++ b/qemu/lib.sh @@ -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 diff --git a/qemu/mkimage.sh b/qemu/mkimage.sh index 47869f7..dad66a9 100755 --- a/qemu/mkimage.sh +++ b/qemu/mkimage.sh @@ -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 diff --git a/qemu/mkinitramfs.sh b/qemu/mkinitramfs.sh index 9146188..72fef08 100755 --- a/qemu/mkinitramfs.sh +++ b/qemu/mkinitramfs.sh @@ -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: diff --git a/qemu/payload/README.md b/qemu/payload/README.md index 753d01e..ba2fae4 100644 --- a/qemu/payload/README.md +++ b/qemu/payload/README.md @@ -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 /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/.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. diff --git a/qemu/rootfs/etc/rc b/qemu/rootfs/etc/rc index a991a4a..93be4a7 100755 --- a/qemu/rootfs/etc/rc +++ b/qemu/rootfs/etc/rc @@ -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 diff --git a/qemu/rootfs/etc/warden-lib.sh b/qemu/rootfs/etc/warden-lib.sh index 6e24d10..864ec19 100644 --- a/qemu/rootfs/etc/warden-lib.sh +++ b/qemu/rootfs/etc/warden-lib.sh @@ -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/ symlinks from sysfs uevents — the +# Populate /dev/block/by-name/ 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 diff --git a/qemu/rootfs/init b/qemu/rootfs/init index 948f165..d5605dd 100755 --- a/qemu/rootfs/init +++ b/qemu/rootfs/init @@ -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 diff --git a/qemu/rootfs/sbin/init b/qemu/rootfs/sbin/init index ad89734..238ad8b 100755 --- a/qemu/rootfs/sbin/init +++ b/qemu/rootfs/sbin/init @@ -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 diff --git a/qemu/rs485-bridge/Cargo.toml b/qemu/rs485-bridge/Cargo.toml index 6950b87..0069818 100644 --- a/qemu/rs485-bridge/Cargo.toml +++ b/qemu/rs485-bridge/Cargo.toml @@ -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] diff --git a/qemu/rs485-bridge/benches/bridge_bench.rs b/qemu/rs485-bridge/benches/bridge_bench.rs index b8f8fd2..ff8de8a 100644 --- a/qemu/rs485-bridge/benches/bridge_bench.rs +++ b/qemu/rs485-bridge/benches/bridge_bench.rs @@ -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. //! diff --git a/qemu/rs485-bridge/src/lib.rs b/qemu/rs485-bridge/src/lib.rs index 22f0b22..a413a40 100644 --- a/qemu/rs485-bridge/src/lib.rs +++ b/qemu/rs485-bridge/src/lib.rs @@ -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(); diff --git a/qemu/rs485-bridge/src/main.rs b/qemu/rs485-bridge/src/main.rs index 6e1cc7e..bb2f3aa 100644 --- a/qemu/rs485-bridge/src/main.rs +++ b/qemu/rs485-bridge/src/main.rs @@ -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; } diff --git a/qemu/run.sh b/qemu/run.sh index e74e985..8d52919 100755 --- a/qemu/run.sh +++ b/qemu/run.sh @@ -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 [options] [-- ] @@ -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" diff --git a/qemu/tests/boot-smoke.sh b/qemu/tests/boot-smoke.sh index 15338e4..7aff466 100755 --- a/qemu/tests/boot-smoke.sh +++ b/qemu/tests/boot-smoke.sh @@ -11,16 +11,16 @@ QDIR="$(cd "$HERE/.." && pwd)" # qemu/ ZIMAGE="${1:-}" if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then - echo "FATAL: usage: $0 [initramfs] — zImage missing or not a file: '${ZIMAGE:-}'" >&2 + echo "FATAL: usage: $0 [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" \ diff --git a/qemu/tests/clock-sanity.sh b/qemu/tests/clock-sanity.sh index c2eb5fb..5ea1569 100755 --- a/qemu/tests/clock-sanity.sh +++ b/qemu/tests/clock-sanity.sh @@ -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 diff --git a/qemu/tests/ota-apply.sh b/qemu/tests/ota-apply.sh index 973343e..cde4a1b 100755 --- a/qemu/tests/ota-apply.sh +++ b/qemu/tests/ota-apply.sh @@ -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 — the virt.fragment kernel variant" >&2 + echo "FATAL: usage: $0 : 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="" diff --git a/qemu/tests/portal-scenario.sh b/qemu/tests/portal-scenario.sh index 89e101f..acadabe 100755 --- a/qemu/tests/portal-scenario.sh +++ b/qemu/tests/portal-scenario.sh @@ -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 # 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 — the virt.fragment kernel variant" >&2 + echo "FATAL: usage: $0 : 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 diff --git a/qemu/tests/real-image-boot.sh b/qemu/tests/real-image-boot.sh index 296e12a..d9a6b13 100755 --- a/qemu/tests/real-image-boot.sh +++ b/qemu/tests/real-image-boot.sh @@ -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 — '$f' missing" >&2 + echo "FATAL: usage: $0 : '$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 diff --git a/qemu/tests/ui-shot.sh b/qemu/tests/ui-shot.sh index 4b4fdb7..0389209 100755 --- a/qemu/tests/ui-shot.sh +++ b/qemu/tests/ui-shot.sh @@ -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 [out-dir] — the virt.fragment kernel variant" >&2 + echo "FATAL: usage: $0 [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 } diff --git a/sim/benches/sim_bench.rs b/sim/benches/sim_bench.rs index c4bf66a..9a190f0 100644 --- a/sim/benches/sim_bench.rs +++ b/sim/benches/sim_bench.rs @@ -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`). diff --git a/sim/src/cru.rs b/sim/src/cru.rs index 38f2760..e716273 100644 --- a/sim/src/cru.rs +++ b/sim/src/cru.rs @@ -2,17 +2,17 @@ //! //! `reboot -f` does NOT reset the RV1106 (no PSCI/restart handler). The canonical //! reset is the CRU global-first software reset (rung 1), with the DesignWare -//! watchdog as a backstop (rung 2) — the ladder in flared's `devmem::hard_reset`. -//! This model lets that ladder, and the boot-mode → MaskRom recovery maneuver, be +//! watchdog as a backstop (rung 2): the ladder in flared's `devmem::hard_reset`. +//! This model lets that ladder, and the boot-mode -> MaskRom recovery maneuver, be //! exercised entirely on the host: run the pokes against a [`SimBus`], then //! [`CruSim::poll`] to see which rung fired and what boot mode a warm reset lands in. //! //! It bakes in the two hardware facts that cost real hardware time: -//! * the CRU global-reset register is `0xff3b0c08` magic `0xfdb9` — the offset +//! * the CRU global-reset register is `0xff3b0c08` magic `0xfdb9`: the offset //! `0xff3a0614` from *other* Rockchip SoCs is a **silent no-op** here (the model //! ignores it, so a regression that reverts to the wrong offset fails a test); //! * the boot-mode register `0xff020200` **survives a warm reset** and is cleared -//! only by a power-on reset — the mechanism that makes "set MaskRom, then reset" +//! only by a power-on reset: the mechanism that makes "set MaskRom, then reset" //! drop the SoC into BootROM download without the BOOT button. use crate::membus::MemBus; @@ -20,7 +20,7 @@ use crate::membus::MemBus; /// Correct RV1106 global-first software reset (confirmed on hardware 2026-08-14). pub const CRU_GLB_SRST_FST: u64 = 0xff3b_0c08; pub const CRU_GLB_SRST_MAGIC: u32 = 0xfdb9; -/// Wrong offset carried over from other Rockchip SoCs — a silent no-op on RV1106. +/// Wrong offset carried over from other Rockchip SoCs: a silent no-op on RV1106. pub const CRU_WRONG_OFFSET: u64 = 0xff3a_0614; /// DesignWare watchdog (rung 2 backstop). @@ -212,7 +212,7 @@ mod tests { } /// The boot-mode register survives a (warm) reset: set MaskRom, reset via CRU, - /// and the model lands in MaskRom — the on-demand BootROM-download maneuver. + /// and the model lands in MaskRom: the on-demand BootROM-download maneuver. #[test] fn maskrom_survives_warm_reset() { let bus = SimBus::new(); @@ -224,7 +224,7 @@ mod tests { } /// A power-on reset clears the boot-mode register (unlike a warm reset), so a - /// stale MaskRom request does not strand the device — it boots Normal. + /// stale MaskRom request does not strand the device: it boots Normal. #[test] fn power_on_reset_clears_maskrom_request() { let bus = SimBus::new(); diff --git a/sim/src/hpmcu.rs b/sim/src/hpmcu.rs index 2146000..d1623ed 100644 --- a/sim/src/hpmcu.rs +++ b/sim/src/hpmcu.rs @@ -2,11 +2,11 @@ //! //! A faithful software port of `flare-edge/hpmcu/watchdog/main.c`'s poll loop: //! it reads the Linux-owned mailbox words (magic + heartbeat counter), writes the -//! MCU-owned state word, and fires (records a CRU reset) on the same deadlines — +//! MCU-owned state word, and fires (records a CRU reset) on the same deadlines: //! boot-grace if userspace never arms it, heartbeat-timeout if a live heartbeat //! stops. Because it runs on a [`MemBus`], the *same* flared arm/beat protocol can //! be driven against it in a host unit test, with a virtual clock, in -//! microseconds — the validation that was missing when a boot-loaded build of this +//! microseconds: the validation that was missing when a boot-loaded build of this //! firmware had to be tested by flashing a panel. //! //! Deadlines are modelled in whole seconds (the firmware's cycle math exists only @@ -215,7 +215,7 @@ mod tests { fn flared_arms_within_boot_grace_no_boot_loop() { // The exact safety property the boot-loaded watchdog needs: on a healthy // boot, flared comes up well before the 300s boot grace, arms the MCU, and - // keeps beating — so it transitions BOOT -> ARMED and never fires. (A + // keeps beating, so it transitions BOOT -> ARMED and never fires. (A // failure here would be the boot-loop we must never ship.) let bus = SimBus::new(); let mut m = mcu(&bus); @@ -247,7 +247,7 @@ mod tests { let bus = SimBus::new(); let mut m = mcu(&bus); let mut counter = 0u32; - // Beat through t=600 (inclusive) — the last heartbeat lands at 600s. + // Beat through t=600 (inclusive): the last heartbeat lands at 600s. for now in (0..=600).step_by(5) { counter += 1; arm_beat(&bus, counter); diff --git a/sim/src/lib.rs b/sim/src/lib.rs index 7ed505e..138d9be 100644 --- a/sim/src/lib.rs +++ b/sim/src/lib.rs @@ -1,4 +1,4 @@ -//! warden-sim — host-side hardware simulator for WardenOS. +//! warden-sim: host-side hardware simulator for WardenOS. //! //! Lets driver and supervisor logic run and be tested on the host, with no panel, //! by modelling the RV1106 hardware the vendor SDK cannot: the register/SRAM bus diff --git a/sim/src/membus.rs b/sim/src/membus.rs index cb51ede..8876f80 100644 --- a/sim/src/membus.rs +++ b/sim/src/membus.rs @@ -5,13 +5,13 @@ //! it as a 32-bit poke or peek at a physical address. `MemBus` is that operation, //! abstracted so the same driver/supervisor code runs against either: //! -//! * the **real** backend — an mmap of `/dev/mem` (lives in flared's +//! * the **real** backend: an mmap of `/dev/mem` (lives in flared's //! `devmem.rs`; it will implement this trait so its logic is host-testable), or -//! * the **sim** backend — [`SimBus`], an in-memory word map. +//! * the **sim** backend: [`SimBus`], an in-memory word map. //! //! `SimBus` is `Clone` + internally `Arc>`, so the simulated MCU core //! and the "Linux side" can each hold a handle and read/write the *same* shared -//! memory — exactly the two-core mailbox the real system uses — with no +//! memory (exactly the two-core mailbox the real system uses) with no //! cache-maintenance dance to model (the real mailbox sits in the GRF uncached //! window). @@ -36,7 +36,7 @@ impl SimBus { Self::default() } - /// Snapshot every written word (address-sorted) — for test assertions/dumps. + /// Snapshot every written word (address-sorted), for test assertions/dumps. pub fn dump(&self) -> Vec<(u64, u32)> { let g = self.words.lock().unwrap(); let mut v: Vec<(u64, u32)> = g.iter().map(|(&a, &w)| (a, w)).collect(); diff --git a/sim/src/modbus.rs b/sim/src/modbus.rs index dcd7750..bdc876f 100644 --- a/sim/src/modbus.rs +++ b/sim/src/modbus.rs @@ -1,16 +1,16 @@ -//! Modbus RTU **slave** simulator — the device end of the RS-485 seam. +//! Modbus RTU **slave** simulator: the device end of the RS-485 seam. //! //! flare-edge's `warden-modbus` is the *master/scanner*: it probes RS-485 for //! VFDs and PDUs, identifies them, and reads their register maps. To harden that //! master to MC/DC we need something for it to talk to that behaves like a real -//! slave — correct CRC framing, the data-plane function codes, exception replies, +//! slave: correct CRC framing, the data-plane function codes, exception replies, //! and the annoying real-world faults (a cheap sensor that ignores a function, a //! device that NAKs an unsupported code). This is that slave, in host memory: //! feed it a request frame, get the response frame (or `None` when a real slave //! would stay silent). No serial port, no hardware, fully deterministic. //! -//! Scope is the data plane — read/write of holding & input registers, coils, and -//! discrete inputs (FC 0x01–0x06, 0x0F, 0x10) plus Report Slave ID (0x11) — which +//! Scope is the data plane: read/write of holding & input registers, coils, and +//! discrete inputs (FC 0x01-0x06, 0x0F, 0x10) plus Report Slave ID (0x11), which //! is what a VFD/PDU register poll actually exercises. Identification via MEI //! (0x2B/0x0E) is a documented follow-up. @@ -22,7 +22,7 @@ pub mod exc { } /// Modbus RTU CRC16 (poly 0xA001, low byte first on the wire). Identical to the -/// master's `crc16` — the two must agree or nothing frames. +/// master's `crc16`: the two must agree or nothing frames. pub fn crc16(bytes: &[u8]) -> u16 { let mut crc: u16 = 0xFFFF; for &b in bytes { @@ -63,7 +63,7 @@ pub struct ModbusSlave { discrete: Vec, slave_id: Vec, /// Silently drop this many upcoming requests (models a device that ignores a - /// function, or a flaky bus) — the master must time out and move on. + /// function, or a flaky bus): the master must time out and move on. drop_next: usize, /// Force every function to answer with this exception (models a device that /// NAKs everything but a narrow set) until cleared. @@ -303,7 +303,7 @@ impl ModbusSlave { } } -/// Build an RTU request frame (with CRC) — convenience for tests and for driving +/// Build an RTU request frame (with CRC): convenience for tests and for driving /// the master's parser. `pdu` is everything between the address and the CRC /// (i.e. `fc` followed by its data). pub fn request(address: u8, pdu: &[u8]) -> Vec { @@ -408,7 +408,7 @@ mod tests { } /// A device that ignores the next request (the "cheap sensor" the master - /// comment warns about) — the master must fall through to the next function. + /// comment warns about): the master must fall through to the next function. #[test] fn drop_next_models_a_silent_device() { let mut s = ModbusSlave::new(1, 8, 0); diff --git a/sim/src/npu.rs b/sim/src/npu.rs index e5bb8dd..56ff87a 100644 --- a/sim/src/npu.rs +++ b/sim/src/npu.rs @@ -1,13 +1,13 @@ -//! NPU load model — the `/proc/rknpu/load` surface. +//! NPU load model: the `/proc/rknpu/load` surface. //! //! The rknpu driver exposes utilisation at `/proc/rknpu/load` as `"NPU load: N%"`, -//! and the file exists only once `rknpu.ko` is loaded — so a *missing* file means +//! and the file exists only once `rknpu.ko` is loaded, so a *missing* file means //! the driver is absent, not idle (sysmon reports absent as 0 and labels the //! screen). This models both a present NPU at a chosen load and an absent one, and //! mirrors sysmon's parse (`strchr(buf, ':')` then the leading integer) so the //! driver's reader can be exercised against realistic text. //! -//! (Only `/proc/rknpu/load` is modelled. `/proc/rknpu/volt` is deliberately NOT — +//! (Only `/proc/rknpu/load` is modelled. `/proc/rknpu/volt` is deliberately NOT: //! reading it SIGSEGVs the reader on this board, so no code should ever open it.) /// A modelled NPU. `present == false` models rknpu.ko not loaded (no proc file). diff --git a/sim/src/rga.rs b/sim/src/rga.rs index 83b9452..ba7ff0e 100644 --- a/sim/src/rga.rs +++ b/sim/src/rga.rs @@ -1,9 +1,9 @@ -//! RGA 2D blitter model — a recording `improcess` fake. +//! RGA 2D blitter model: a recording `improcess` fake. //! //! `warden_rga.c` offloads copies/scales/format-conversions to the RGA via //! librga's `improcess(src, dst, ..., IM_SYNC)`, and falls back to the CPU draw //! path when it returns anything but `IM_STATUS_SUCCESS`. The blit *pixels* are -//! not modelled — what matters for testing is the **dispatch** logic: which ops +//! not modelled. What matters for testing is the **dispatch** logic: which ops //! get sent, with what geometry/format, and that a non-success status drives the //! CPU fallback. So the sim records each requested op and returns a programmable //! status. It rides its own call seam (behind the driver's `#if WARDEN_USE_RGA`), @@ -18,7 +18,7 @@ pub struct Rect { pub h: i32, } -/// A surface descriptor — the subset of im2d `rga_buffer_t` the dispatch cares +/// A surface descriptor: the subset of im2d `rga_buffer_t` the dispatch cares /// about (dimensions + pixel format). #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Surface { @@ -141,7 +141,7 @@ mod tests { r.improcess(surf(10, 10), surf(10, 10), rect(10, 10), rect(10, 10)), ImStatus::Failed ); - // the op is still recorded — the driver dispatched it, then fell back. + // the op is still recorded: the driver dispatched it, then fell back. assert_eq!(r.count(), 1); } diff --git a/tools/config-lint/README.md b/tools/config-lint/README.md index 86904e2..ead8428 100644 --- a/tools/config-lint/README.md +++ b/tools/config-lint/README.md @@ -2,7 +2,7 @@ Static target-config gates: catches flash-time config faults the behavioural sim cannot: mistakes in the *memory map*, not the logic. The first check is the one that would have caught the -**c8a3 brick** — a boot-loaded coprocessor firmware dropped at `0x40000`, which is +**c8a3 brick**: a boot-loaded coprocessor firmware dropped at `0x40000`, which is a `reserved-memory` carve-out on Thunder-Boot boards but plain kernel RAM on the 86 Panel, so the MCU and the kernel fought over the same DRAM and the board hung before eth0. @@ -15,7 +15,7 @@ Every address the idblock loader drops MCU firmware to must sit inside a hpmcu/mcu/amp entry) in `[LOADER_OPTION]`, with its `LOAD_ADDR` from `[LOADERn_PARAM]`. - **Reserved ranges** come from the devicetree: every `reg = ` inside a - `reserved-memory { … }` node. + `reserved-memory { ... }` node. A load outside all reservations is a failure (non-zero exit). @@ -37,6 +37,6 @@ found; `2` = usage/IO error. The suite encodes the brick as a regression: the real Thunder-Boot `.ini` (Hpmcu @ `0x40000`) *fails* against a DT with no `rtos@40000` node and *passes* -once the reservation is added — and the 86 Panel's non-TB loader (no boot-loaded MCU) -always passes. See `../../docs/architecture.md` §5 and, for the hardware hazard, +once the reservation is added, and the 86 Panel's non-TB loader (no boot-loaded MCU) +always passes. See `../../docs/architecture.md` section 5 and, for the hardware hazard, the `boot-loaded-mcu-0x40000-hazard` note. diff --git a/tools/config-lint/src/lib.rs b/tools/config-lint/src/lib.rs index 8da09c2..22bedfe 100644 --- a/tools/config-lint/src/lib.rs +++ b/tools/config-lint/src/lib.rs @@ -1,10 +1,10 @@ -//! config-lint — static target-config checks the behavioural sim cannot cover. +//! config-lint: static target-config checks the behavioural sim cannot cover. //! //! The c8a3 brick was a memory-map fault, not a logic bug: the boot-loaded MCU's //! load address (`0x40000`) is a `reserved-memory` carve-out on Thunder-Boot //! boards but plain kernel RAM on ours, so the coprocessor firmware and the kernel -//! fought over the same DRAM and the board hung before eth0. No sim catches that — -//! it needs a static check against the target devicetree: **every address the +//! fought over the same DRAM and the board hung before eth0. No sim catches that. +//! It needs a static check against the target devicetree: **every address the //! idblock loader drops MCU firmware to must sit inside a `reserved-memory` node.** //! //! This module parses the two authoritative artifacts (the rkbin loader `.ini` @@ -38,7 +38,7 @@ pub struct Finding { pub msg: String, } -/// A byte that can appear inside a devicetree node/property identifier — used to +/// A byte that can appear inside a devicetree node/property identifier: used to /// require a token boundary before a `reg` property, so substrings like `reg-names` /// or a `region-*` node label are not matched as the `reg` property itself. fn is_ident_byte(b: u8) -> bool { @@ -54,19 +54,19 @@ fn parse_addr(s: &str) -> Option { } } -/// Loaders that are part of the normal RK boot chain — DDR init, SPL/miniloader, the -/// secure monitor, U-Boot — and are NOT boot-loaded coprocessor firmware, so their +/// Loaders that are part of the normal RK boot chain (DDR init, SPL/miniloader, the +/// secure monitor, U-Boot) and are NOT boot-loaded coprocessor firmware, so their /// LOAD_ADDR (if any) is the vendor boot flow, not a DRAM carve-out that must be /// reserved. Anything NOT on this allowlist that declares a LOAD_ADDR is treated as /// a coprocessor load and checked (**fail closed**): a future MCU named /// "Rtos"/"Bl32"/"M0" must not slip through an MCU-*name* allowlist the way the -/// original `hpmcu`/`mcu`/`amp` substring test would have — that is exactly the +/// original `hpmcu`/`mcu`/`amp` substring test would have: that is exactly the /// c8a3 0x40000-brick class this tool exists to catch. /// /// Matched as a WHOLE normalized name (separators stripped), never a substring, so a -/// coprocessor whose vendor name merely *contains* a boot-chain word — "AudioLoader" -/// ⊃ "loader", "SplRtos" ⊃ "spl", "Bl32" (≠ "bl31") — is still checked, not waved -/// through. Over-inclusion here would only over-report (the safe direction); a missed +/// coprocessor whose vendor name merely *contains* a boot-chain word ("AudioLoader" +/// contains "loader", "SplRtos" contains "spl", "Bl32" is not "bl31") is still checked, +/// not waved through. Over-inclusion here would only over-report (the safe direction); a missed /// coprocessor is the dangerous one. fn is_known_safe_loader(name: &str) -> bool { let n: String = name @@ -185,8 +185,8 @@ pub fn parse_reserved_ranges(dt: &str) -> Vec { j += 1; } // scan `reg = ` PROPERTY tokens inside [start, j). Match `reg` as - // a whole token — identifier boundary before it, `=` after optional space — - // so a `reg-names` property or a `region-*@…` node label (both contain the + // a whole token (identifier boundary before it, `=` after optional space), + // so a `reg-names` property or a `region-*@...` node label (both contain the // substring "reg") is not misparsed into a bogus range. let block = &dt[start..j.min(dt.len())]; let bb = block.as_bytes(); @@ -234,8 +234,8 @@ pub fn check(loads: &[McuLoad], reserved: &[Range]) -> Vec { .map(|l| Finding { load: l.clone(), msg: format!( - "{}={} loads MCU firmware at {:#x} but no reserved-memory node covers it \ - — kernel/MCU DRAM collision (the 0x40000 brick class)", + "{}={} loads MCU firmware at {:#x} but no reserved-memory node covers it: \ + kernel/MCU DRAM collision (the 0x40000 brick class)", l.loader, l.name, l.load_addr ), }) @@ -338,7 +338,7 @@ FLAG=0x10007 "#; /// Regression (correctness review): `reg` must match as a whole property token, - /// not a bare substring — the `region-*` label, `reg-names`, and the + /// not a bare substring: the `region-*` label, `reg-names`, and the /// `interrupts` cells must NOT be misparsed into extra ranges. #[test] fn reg_token_not_substring() { @@ -349,7 +349,7 @@ FLAG=0x10007 } // A future coprocessor loader named nothing like hpmcu/mcu/amp, at an - // unreserved DRAM address — the exact case an MCU-name allowlist would miss. + // unreserved DRAM address: the exact case an MCU-name allowlist would miss. const UNKNOWN_MCU_INI: &str = r#" [LOADER_OPTION] NUM=2 @@ -378,7 +378,7 @@ LOAD_ADDR=0x40000 } // Coprocessor names that merely *contain* a safe boot-chain word, each with a - // LOAD_ADDR: "AudioLoader" ⊃ "loader", "SplRtos" ⊃ "spl", "Bl32" (≠ "bl31"). + // LOAD_ADDR: "AudioLoader" contains "loader", "SplRtos" contains "spl", "Bl32" is not "bl31". const SUBSTRING_TRAP_INI: &str = r#" [LOADER_OPTION] NUM=4 @@ -399,7 +399,7 @@ LOAD_ADDR=0x70000 "#; /// Regression (2nd-pass correctness review): the safe allowlist matches a WHOLE - /// normalized name, never a substring — a coprocessor whose name contains a + /// normalized name, never a substring: a coprocessor whose name contains a /// boot-chain word must NOT be waved through. FlashData (a real boot component, /// no LOAD_ADDR here) stays safe; the three coprocessors are all checked. #[test] diff --git a/tools/config-lint/src/main.rs b/tools/config-lint/src/main.rs index 336e606..f043920 100644 --- a/tools/config-lint/src/main.rs +++ b/tools/config-lint/src/main.rs @@ -1,13 +1,13 @@ -//! config-lint CLI — the MCU-load-vs-reserved-memory gate as a CI check. +//! config-lint CLI: the MCU-load-vs-reserved-memory gate as a CI check. //! //! Usage: //! config-lint --ini --dt //! //! The `--dt` argument accepts a `.dts`/`.dtsi` or the flattened output of -//! `dtc -I dtb -O dts built.dtb` (preferred in CI — it resolves includes and +//! `dtc -I dtb -O dts built.dtb` (preferred in CI: it resolves includes and //! overlays, so it reflects what the board will actually boot). Exits non-zero //! and prints each offending MCU load if any lands outside a `reserved-memory` -//! node — the 0x40000 brick class. +//! node: the 0x40000 brick class. use std::process::ExitCode; use warden_config_lint::{check, parse_ini_mcu_loads, parse_reserved_ranges}; @@ -56,7 +56,7 @@ fn main() -> ExitCode { let findings = check(&loads, &reserved); if loads.is_empty() { - println!("config-lint: no boot-loaded MCU firmware in {ini_path} — nothing to reserve."); + println!("config-lint: no boot-loaded MCU firmware in {ini_path}: nothing to reserve."); } else { println!( "config-lint: {} MCU load(s) in {ini_path}, {} reserved-memory range(s) in {dt_path}.", @@ -66,11 +66,11 @@ fn main() -> ExitCode { } if findings.is_empty() { - println!("config-lint: OK — every MCU load is inside a reserved-memory node."); + println!("config-lint: OK: every MCU load is inside a reserved-memory node."); ExitCode::SUCCESS } else { for f in &findings { - eprintln!("config-lint: FAIL — {}", f.msg); + eprintln!("config-lint: FAIL: {}", f.msg); } ExitCode::FAILURE } diff --git a/tools/flowgen.py b/tools/flowgen.py index 1e2d219..71d4d98 100644 --- a/tools/flowgen.py +++ b/tools/flowgen.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -"""flowgen — generate mermaid flowcharts for the workflows the SDK tests. +"""flowgen: generate mermaid flowcharts for the workflows the SDK tests. future-features-2 asks that the test harness "produce flowcharts of every workflow and process that it tests so a user can understand them better", each carrying its benchmark. This emits one `docs/workflows/.md` per workflow: an outcome-first -flowchart (from the modelled decision path) plus the workflow's metric — a benchmark +flowchart (from the modelled decision path) plus the workflow's metric: a benchmark ns/op for the sim-modelled hardware workflows, or the MC/DC result for the Tier-1 driver workflows. @@ -86,7 +86,7 @@ WORKFLOWS = [ "name": "relay-drive", "title": "Relay Drive", "outcome": "A relay is exported transparently and driven without disturbing a held contact.", - "metric": ("mcdc", "relays.c — 40/40 conditions, 100% MC/DC (CI-enforced)"), + "metric": ("mcdc", "relays.c: 40/40 conditions, 100% MC/DC (CI-enforced)"), "mermaid": """flowchart TD A[warden_relay_set idx,on] --> B{idx < COUNT?} B -- no --> Z[no-op] @@ -104,7 +104,7 @@ WORKFLOWS = [ "name": "freshness-contract", "title": "UI Freshness Contract", "outcome": "The UI never shows a stale number: it holds briefly, then marks unknown.", - "metric": ("mcdc", "freshness.c — 66/66 conditions, 100% MC/DC (CI-enforced)"), + "metric": ("mcdc", "freshness.c: 66/66 conditions, 100% MC/DC (CI-enforced)"), "mermaid": """flowchart TD A[produce] --> B{result} B -- OK --> V[render value, save last]