Files
bfe-core1106-sdk/kernel/docs/m2-boot-on-c8a3.md
T
BFE EngineeringandClaude Fable 5 5155224594 public-readiness: scrub internal details, split licensing, hosted kernel-build
Preparing the repo to go public (ADR-0007):

- CI: kernel-build moves from the self-hosted runner to ubuntu-latest
  (installs its own cross toolchain + qemu, caches the pristine tarball).
  On a public repo a registered self-hosted runner is reachable from
  approved fork-PR workflows — i.e. arbitrary code on private
  infrastructure — and the build never actually needed the SDK host.
  ADR-0004 marked superseded-in-part; docs/ci-cd.md rewritten (site
  specifics now live only in the private deployment log).
- Licensing: LICENSE gains the GPL-2.0 carve-out for patches/ and the
  kernel source excerpts (Linux derivatives; per-driver provenance was
  already tracked in PROVENANCE.md); patches/README.md states it too.
- Scrubbed from the tip: bench-unit dev credentials and its gadget IP
  (m2-boot notes), the site AP SSID+BSSID and a neighboring AP's BSSID
  and the device WLAN MAC (wifi bring-up evidence — BSSIDs are
  geolocatable), the runner mesh IP. NOTE: these remain in git history;
  decision on a pre-publication history rewrite is separate.
- Emoji cleanup across 21 tracked files (kernel port docs, review report,
  enforce-mcdc.sh) per repo text conventions: status marks became
  [x]/[wip]/[ ]/OK plain text.
- "[maintainer]-gated" process phrasing normalized to "maintainer-gated"
  (attributions in dated evidence docs kept).

Verified: zero emojis tracked; scrub grep clean; patches carry no internal
references; ci.yml parses; shellcheck unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
2026-08-30 07:25:57 -06:00

5.7 KiB

M2 boot bring-up on warden-c8a3 (live notes)

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.

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 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 untouched.
  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.
  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 just needs a Zigbee power-cycle; _a boots WardenOS back. Zero manual 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.

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 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 RESC: path from a resource image containing rk-kernel.dtb (+ logos), built 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".
  • 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 Starting kernel ....

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] CPU: ARMv7 Processor [410fc075]
[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.343810] pinctrl core: initialized pinctrl subsystem
[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 pre-MMU), then fixed:

  • 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 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,…) 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 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 enumerated the eMMC at HS200, mounted the ext4 rootfs, ran /sbin/init, and started every WardenOS daemon. A serial root login confirms uname -aLinux 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).

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.

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.