# Research log — krane display pipeline & stub bring-up This file is the running lab notebook for the krane-fb-stub project: every investigation done against the actual sources (coreboot, Linux, depthcharge — both mainline trees and the *device-era* code as recovered from the SPI flash version strings), what was found, and how each finding changed the payload. Ordered chronologically; conclusions marked **[REVERSED]** were later contradicted by deeper evidence. --- ## Phase 0 — Task framing Goal: smallest freestanding arm64 binary that depthcharge boots as a Linux kernel, locates the boot-splash framebuffer, and paints checkpoint colors (red → yellow → green → blue, ~2 s each, hold blue), to validate the depthcharge → custom-payload pipeline before any U-Boot bring-up. Device ground truth (verified live): - Lenovo IdeaPad Duet, MT8183, board `google,krane`, **sku176** (`sku-id = 0xb0` in `/firmware/coreboot` of the live FDT). - coreboot table (LBIO) at `0xffed9000`, size `0x380`; CBMEM region follows. Confirmed both by `dtc -I dtb -O dts /sys/firmware/fdt` (kernel's copy of depthcharge's fixed-up tree) and by sysfs (`/sys/devices/platform/ffed9000.coreboot/coreboot7` carries `MODALIAS=coreboot:t00000012` = LB_TAG_FRAMEBUFFER present). - Boot chain: BootROM → coreboot → TF-A BL31 → depthcharge → our payload. - Partition layout: `mmcblk0p1` ChromeOS-kernel (what depthcharge boots internally), `sda1` (USB stick, pmOS) is the currently-running system. - Device firmware strings extracted from the 8 MB SPI (`flashrom -p internal`): - `v1.9308_26_0.0.22-10476-g8c7859a3c9` (RO, built ~Feb 2021) - `v1.9308_26_0.0.22-10566-g8417b00957`, built `Mon May 30 03:26:46 UTC 2022` (RW — the slot that actually runs) Both are ChromeOS depthcharge ≈ v0.0.22 (R93-era, early 2021/2022). ## Phase 1 — Format verification (all checked against source, not the prompt) ### coreboot tables (`src/commonlib/include/commonlib/coreboot_tables.h`) - `physical_address` is `lb_uint64_t` = `__aligned(4) uint64_t` (`LB_ENTRY_ALIGN == 4`). On LE AArch64 that is a plain 8-byte LE value at offset 8 of `struct lb_framebuffer` — the "hi/lo split" is an alignment trick, not separate fields. - `sizeof(struct lb_framebuffer) == 40` (host-compiled probe against the real header). Offsets: tag 0, size 4, physical_address 8, xres 16, yres 20, bytes_per_line 24, bpp 28, red 29/30, green 31/32, blue 33/34, reserved 35/36, orientation 37, flags 38, pad 39. - `lb_header` / `lb_record` match the prompt. Records start at `header_bytes`, walked `table_entries` times. - Live cross-check: parsing the live `/sys/firmware/fdt` yields LBIO at `0xffed9000`, size `0x380` — matches the values confirmed by hand with dtc. ### arm64 Image header (Linux `booting.rst` + depthcharge `boot64.c`) - 64-byte header, magic `ARM\x64` (0x644d5241) at 0x38, code0 must branch past the header; entry with x0 = DTB, x1–x3 = 0, MMU off, DAIF masked. - `image_size` must be exact and little-endian (depthcharge checks the magic and uses `image_size` for KASLR slot math). ### depthcharge mainline (as of this repo's clone) - `boot64.c`: kernel image is decompressed to a random 2 MiB-aligned slot; the Image magic is verified; entry = first byte; x0 = flat DTB, x1–x3 = 0. → stub must be **position-independent** (build enforces zero dynamic relocations) and carry its own stack (16 KiB in-image). - **Contradicts the prompt**: `fit.c` *skips* FIT configs without an FDT subimage ("config %s has no FDT, skipping") and gives up with no match. So the packed FIT must contain a DTB. We embed the genuine `mt8183-kukui-krane-sku176` DTB (config conf-27 of the running p1 FIT) purely as a config-selection key; depthcharge injects `/firmware/coreboot` at boot via `src/boot/coreboot.c::install_coreboot_data` (adds `#address-cells`, `#size-cells`, `ranges`, `compatible`, `reg`), so the runtime DTB at x0 always has the node we parse. - `mkdepthcharge` builds the FIT itself (`mkimage -f auto -T kernel -O linux -C none -d -b `, then patches the kernel subimage to `kernel_noload`), so a raw arm64 Image goes in directly — no manual uImage wrapping. Verified: the packed image has the same shape as the working pmOS FIT (`kernel_noload` + `flat_dt`, compression none). ## Round 1 (eMMC flash, first attempt) — BLACK SCREEN, menu vanished Observed: depthcharge dev-menu exits → black screen, nothing ever painted. Had to force power-cycle; recovered via USB boot (backup `~/mmcblk0p1-pmos- backup.img`, sha256 `20951a54…`, taken *before* the first flash). ### Investigation The black screen was initially read as "stub crashed". The decisive evidence came from identifying the **exact firmware versions in the SPI dump**: - depthcharge `v1.9308_26_0.0.22-10566-g8417b00957` (RW slot, 2022-05-30) — i.e. ChromeOS R93-era (2021/2022), not mainline 2026. - coreboot `4.14`-era (built ~Feb 2021 / May 2022). Checking the chromiumos/depthcharge git history (`git log -S`) for the code that ran *on the device*: 1. `69725330` (2020-09-22, "display: Black out screen in display_cleanup()", in R93): `display_cleanup()` at `CleanupOnHandoff` **before jumping to the payload** does: 1. `clear_screen(black)` — erases the menu from the LBIO framebuffer; 2. `backlight_update(0)` — drives `DISP_PWM` (GPIO 43) and `EN_LCD_BL` (GPIO 176) low; 3. calls `display_ops->stop` → 2021-era `mtk_display_stop()`: `OVL_EN=0`, `OVL0_2L_EN=0` — the overlay fetch engine is stopped. 2. The DSI link, panel power and display MTCMOS remain up — panel/DSI poweroff only exists in 2025+ code (`e9f50b81`, `9be6dc52`, Dec 2025). Also `LB_TAG_PANEL_POWEROFF` is emitted only by skywalker boards. **Conclusion:** the stub *did* run, painted red→yellow→green→blue and halted on blue — into a framebuffer nothing was scanning, with the backlight off. "Painted in the dark." The prompt's premise ("framebuffer is live and DMA'd by the time any payload runs") is false for this firmware generation. ### Key register facts established (all from device-era sources) - OVL0 base `0x14008000`; `DISP_REG_OVL_EN = 0x000C`, `DISP_REG_OVL0_2L_EN = 0x100C` (2021 `mtk_ddp.c` — **[REVERSED]** this log first claimed 0x0F00 for OVL_EN; see Round 2). The 0xF40 "L0_ADDR" register still holds the menu's scanout address after stop — it is not reset. - GPIO controller at `0x10005000`; `dout` block at +0x140, 16 bytes per 32-pin group, `set` at +4 / `rst` at +8. Backlight pins: `DISP_PWM = 43` (group 1, bit 11 → set reg `0x10005154`), `EN_LCD_BL = PERIPHERAL_EN13 = 176` (group 5, bit 16 → `0x10005194`). - Panel: sku176 → `(sku_id >> 4) & 0xF = 11` → **BOE TV101WUM_NL6** (1200×1920, RGB888, 4 lanes, pixel clock 159.425 MHz → DSI data rate ≈ 956 Mbps). Not needed for the fix (panel stays initialized). ### The fix (`main.c::display_revive()`) Minimal inverse of the three teardown steps, executed before painting: 1. `OVL_EN = 1`, `OVL0_2L_EN = 1` (offsets from device-era mtk_ddp.c); 2. GPIO `dout.set` writes for pins 43 and 176 (GPIO base `0x10005000`, dout block +0x140, 16 B per 32-pin group — layout from depthcharge's `mtk_gpio.c`/`mt8183.h`); 3. repaint — `OVL_L0_ADDR` still holds the address depthcharge used for the menu, which is the same LBIO-record address our parser extracts. No DSI/panel re-init, no MTCMOS, no clock touching. ## Round 2 (post-revival reboot) — STILL BLACK: wrong OVL_EN offset Observed: reboot into internal drive → pitch black after depthcharge boots `mmcblk0p1`. No colors. p1 verified intact afterwards (first 159,744 bytes hash to the payload; `vbutil_kernel --verify` on-device OK), so the stub ran. ### Live-environment re-checks - `/dev/mem` LBIO read still blocked: pmOS kernel has `CONFIG_STRICT_DEVMEM` (confirmed in `/boot/config`), `0xffed9000` sits in `fae00000-ffdfffff : reserved` per `/proc/iomem` → `dd if=/dev/mem` gives "Bad address". kexec remains the only live-read route; not needed — see below. - **Open item resolved without hardware access**: the runtime LBIO `physical_address` is provably non-zero. Depthcharge's device-era `mtk_display_init()` (`src/drivers/video/mtk_ddp.c`) programs `OVL_L0_ADDR` from `lib_sysinfo.framebuffer.physical_address` (i.e. the LBIO record) and the dev menu was visibly rendered through it; `src/drivers/video/display.c` also gates display handling on `physical_address != 0`. Mainline coreboot passes `fb_addr=0` for kukui (`fb_new_framebuffer_info_from_edid(edid, 0)`), but the device-era coreboot published a real address. ### Root cause The stub's `display_revive()` re-enabled the overlay at the wrong register: it wrote `OVL_EN` at `0x14008000 + 0x0F00`, but the actual offset in depthcharge's `mtk_ddp.c` — confirmed in the 2021 revision (`497450b4`, Jan 2021, inside the RO build window), the current tree, the merge commit that unified mt8173/mt8183 (`74376061`, 2019), and Linux `drivers/gpu/drm/mediatek/mtk_disp_ovl.c` — is `0x000C`. The Round-1 "0x0F00 from device-era mtk_ddp.c" note was a misread (0x0F00-era MT8173 offset / confusion with the 0x0F40 L0_ADDR neighborhood). Consequence: depthcharge's stop wrote `OVL_EN=0` at `0x000C`, the stub revived at `0x0F00` (a different, wrong register), the overlay engine stayed disabled, and every fill landed in a buffer nothing scanned out. Painting in the dark, again. ### Fix `main.c::display_revive()`: `DISP_REG_OVL_EN` corrected to `0x000C`. `OVL0_2L_EN = 0x100C` was already correct. Backlight GPIO writes were correct (match `kukui_backlight_update`). ### Verification payload `krane-fb-stub-payload.bin` sha256 `9e7cf29d…` (an intermediate `41270e53…` rebuild was flashed once but built from the stale WIP tree before the fix was synced into it — same wrong code as Round 1; caught by disassembly before reboot), flashed to `mmcblk0p1`, `cmp` + `vbutil_kernel --verify` OK. Awaiting reboot. ## Flash state - `mmcblk0p1` reflashed with the Round-3 diagnostic payload: sha256 `9d7859dc04dd9c445143f6b45600bb972cf5fc1497c06a4f9db549e3d2971e73`, verified on-device (`cmp` first 159,744 bytes + `vbutil_kernel --verify` → body verification succeeded). Earlier: Round-1 `edb65b29…`, Round-2 `9e7cf29d…` (both functionally identical wrong-offset code; `41270e53…` was a stale-tree rebuild caught by disassembly). - USB stick `sda1` untouched throughout (sha256 watched: `c4ee2044…`). - eMMC backup: `~/mmcblk0p1-pmos-backup.img` (sha256 `20951a54…`). **Copy it onto the USB stick before rebooting** so recovery never depends on eMMC. ## Round 3 — still black with corrected offset; diagnostic stub Reboot with payload `9e7cf29d…` (correct `OVL_EN=0x000C`): still pitch black. Fact check: all four teardown steps of device-era `display_cleanup` are now provably undone — `clear_screen(black)` (repainted), the backlight GPIOs (matches `kukui_backlight_update`), `OVL_EN`/`OVL0_2L_EN` (correct offsets), and `disable_graphics_buffer()` (a libpayload double-buffer helper, no hardware effect). Device-era `init_screen()` sequencing (`ui/display.c`) proves the dev menu was drawn into the LBIO `physical_address` and scanned out via `OVL_L0_ADDR` = same address, so the stub's fills go to the right buffer. Revival logic is correct on paper — yet black. **The unexcluded branch: the stub may never execute.** "Menu vanished" only proves depthcharge reached handoff — cleanup runs unconditionally. And the internal-boot path was never baseline-proven: pmOS always ran from USB; no custom image has ever been observed booting from p1 on this device. Every black screen so far is equally consistent with "payload never ran". ### Diagnostic design (payload `9d7859dc…`) Payload now paints/blinks BEFORE trusting any parsing: 1. First instructions: `OVL_EN=1`, `OVL0_2L_EN=1`, backlight GPIOs on. 2. Parse (<1 ms). Failure → 5 slow backlight blinks, spin. 3. Success → red → yellow → green → blue, blue held. Decision tree: colors = pipeline validated; 5 blinks on black = parse failed at runtime; nothing at all = payload never handed off (boot path). ### Abandoned: OVL_L0_ADDR stage-0 read An earlier design read `OVL_L0_ADDR` (0x14008F40) pre-parse to paint the menu buffer without knowing `pa`. Rejected: on qemu `-M virt`, READS of unassigned MMIO data-abort (writes are silently dropped) — it killed the test instantly; the same risk exists on real hardware if a display clock gate is off, and a fault there is indistinguishable from "never ran". The stub now contains no MMIO reads at all. ## Round 4 — still black, no backlight: GPIO dout offset was wrong too Reboot with the Round-3 diagnostic payload (`9d7859dc…`): pitch black, no blinks, **no backlight at all**. Stage-0 turns the backlight on as its first act, and the backlight demonstrably works via these same GPIOs (the dev menu had it) — so either the payload never ran, or the GPIO writes missed. Re-derived the register layout from the device-era header (`src/drivers/gpio/mt8183.h`) instead of the Round-1 notes: GpioRegs: dir[6]@0x000, rsv00[160], dout[6]@0x100, rsv01[160], din[6]@0x200; GpioValRegs = 16 B (val@0, set@4, rst@8) **The Round-1 note's `dout @ +0x140` was wrong** — actual offset `+0x100`. Consequences of the old addresses: the "pin 43 set" write to `0x10005154` actually set bit 11 of group 5 = **pin 172 (I2S1_MCK)**; the "pin 176 set" write to `0x10005194` hit a reserved region. Correct set/rst addresses: pin 43 → `0x10005114`/`0x10005118`, pin 176 → `0x10005154`/`0x10005158`. The Round-3 blink was additionally broken: it wrote 0 to a *set* register (a no-op) instead of writing the bit to the *rst* register. Both fixed. This also reframes Rounds 2–4: the OVL revival (correct offsets) and the fills may have been **working the whole time** — with the backlight never enabled, an LCD shows nothing regardless of what is scanned out. The only proven failures were the two register-offset misreads in the Round-1 research notes (`OVL_EN 0x0F00`, `dout 0x140`), both now [REVERSED] and fixed; the stub itself has never been disproven. Payload `55ccc1bd…` flashed (cmp + vbutil OK). host/qemu tests pass. ## Diagnostic decision tree (after this fix) | observation | meaning | |---|---| | black, no colors, no blink | payload never handed off — boot-path problem (vboot/mkdepthcharge), investigate p1 boot | | black + 5 slow backlight blinks (NOW FUNCTIONAL — see Round 4) | stub ran, parse failed at runtime | | colors appear but sequence stops early | fill/mask problem — stuck color identifies stage | | red → yellow → green → blue, blue held | pipeline fully validated | ## Round 5 — blinks decoded: LBIO physical_address really IS 0 The Round-4 payload's diagnostic worked: 3–5 backlight blinks then steady backlit black = `parse_fail()`. The stub runs, stage-0 works, the GPIO fix works — the parse rejected the LBIO framebuffer record. Root cause found in the actual coreboot **4.14** source (fetched from the 4.14 tag): - `src/mainboard/google/kukui/mainboard.c` (4.14) is identical to mainline: `fb_new_framebuffer_info_from_edid(edid, 0)` — and 4.14's `edid_fill_fb.c` passes `fb_addr` through verbatim, no carveout, no allocation. **The LBIO record on this device genuinely has physical_address = 0.** How the menu still renders: libpayload cbgfx draws into `phys_to_virt(pa)` = DRAM address 0, and coreboot's display pipeline scans out address 0. With `pa = 0`, depthcharge's `display_init_required()` is false, so board.c never registers display ops — meaning at handoff `display_cleanup` runs but `backlight_update` and `stop` are no-ops: **the OVL was never stopped and the backlight never disabled by depthcharge.** The "menu vanishing" was just the 9.2 MB black fill at address 0 (safe for the payload: depthcharge itself does it after the kernel slot is chosen, and the stub demonstrably survived every round). **Round-2's "open item resolved" reasoning was wrong** — menu rendering does NOT prove pa != 0, because address 0 works as a framebuffer region. The original Phase-1 note ("mainline coreboot passes fb_addr=0 for kukui") was correct all along. All five black screens so far trace to the stub's `!fb->pa` rejection of a legitimate record (plus the two Round-1 register offset misreads, which additionally kept the backlight dark). Fix: `find_framebuffer` accepts `pa == 0` and the stub paints at address 0 — the buffer the still-running OVL scans. OVL revival and backlight writes are kept but are now believed to be redundant on this firmware. Payload `b25d9132…` flashed (cmp + vbutil OK). host_test gained a pa==0 regression test; qemu_test unchanged (its synthetic record uses pa!=0). ## Round 6 — pa==0 accepted but parse STILL fails: bit-band diagnostic dump Reboot with `b25d9132…` (pa==0 accepted): **5 blinks again** — parse_fail persists, and with pa==0 accepted the failure is somewhere else in find_coreboot_reg or find_framebuffer. Every local reproduction passes: host_test parses the live fdt (21/21 checks incl. the new pa==0 test), qemu end-to-end passes, and the runtime tree shape is provably identical to the live fdt (same fixup code; krane DTB has no pre-existing /firmware node; root cells 2/2). The remaining unknowns are the actual runtime values — what is really at x0 and what the real LBIO bytes are. Key enabler: on failure the scanout address is KNOWN without any parse — DRAM address 0 (Round 5; the visible menu erase proves the OVL scans it). So the stub can render diagnostic data directly on the panel. ### New failure path (payload `e619166a…`) `fail_dump()`: 5 blinks (execution proof, same signature), then paint a bit-band dump into address 0: one row per u32, 32 cells of 32×32 px, MSB first, white=1 / black=0, rows every 64 px. Word 0 = marker: - `0xC0DE0001` — find_coreboot_reg failed; words 1-2 = x0 pointer, words 3-6 = raw DTB header (magic, totalsize, off_struct, off_strings, off_mem_rsvmap). - `0xC0DE0002 | rc` — find_framebuffer failed; words 1-3 = LBIO addr/size, words 4-6 = raw table magic/header_bytes/entries, words 7-12 = raw words at table+24..+68 (first record headers). User photographs the panel; values are decoded offline. ## Round 7 — diag at address 0 invisible: cbgfx rejects pa==0 Reboot with `e619166a…`: 5 blinks, then **no bit-bands** — the render into DRAM address 0 never reached the panel. That kills the "OVL scans address 0" corollary of Round 5, and with it the whole address-0 theory: the device-era libpayload `cbgfx_init()` explicitly REJECTS `physical_address == 0` (`CBGFX_ERROR_FRAMEBUFFER_ADDR`). The menu could never have been drawn through a zero pa — so the runtime framebuffer address is NON-zero, and the LBIO record's pa (whatever it holds) is not necessarily what the panel scans. The authoritative scanout address is `OVL_L0_ADDR` (0x14008F40): it is programmed by depthcharge's `mtk_display_init()` for the menu and left in place by `stop()`. Round 5's "OVL never stopped / backlight never disabled" reasoning is also [REVERSED] — with display ops registered, `display_cleanup` really did disable the OVL and kill the backlight (which is why Round 4's "no backlight at all" was observed). ### Payload `cebb9b1b…` (full file rewrite) - `read_scanout_addr()` reads `OVL_L0_ADDR`; guarded as plausible DRAM (`>= 0x40000000`). - Success path: if the LBIO record's pa is 0, substitute the scanout address, then paint checkpoints into the buffer the OVL actually scans (after stage0 re-enables the engines and backlight). - Failure path: `fail_dump` blinks 5× then renders the bit-band dump AT THE SCANOUT ADDRESS; word 15 additionally carries the raw L0_ADDR value. Markers unchanged (`0xC0DE0001` DTB stage, `0xC0DE0002|rc` LBIO stage). The OVL register READ is device-only (qemu -M virt aborts on reads to unassigned MMIO; qemu never takes the failure/substitution path, so the test is unaffected). ## Round 8 — hex-digit diagnostic dump The bit-band dump WAS visible at the L0_ADDR scanout (proving both the scanout substitution and the render path), but 32 coarse squares per row proved unreadable from a photo, and the apparent row/column confusion made transcription unreliable. The dump rendering is replaced with **giant hex digits**: one u32 per line, 8 digits of a 3x5 cell font scaled x10 (30x50 px), MSB nibble first, white on depthcharge's black background. Readable in any panel orientation, transcribable as text. Same data, same markers (`0xC0DE0001` DTB stage, `0xC0DE0002|rc` LBIO stage, word 15 = raw OVL_L0_ADDR value). Payload `10760e3c…` flashed (cmp + vbutil OK). host_test and qemu_test pass. ## Round 9 — dump decoded: x0's FDT buffer is empty; LBIO fallback The hex dump worked. Transcription (`C0DE0001` = DTB stage failure): x0 = 0x5F800000 dt[0..3] = 0, 0, 0, 0 (no FDT magic — buffer is zeros) OVL_L0_ADDR = 0xFD536000 (real scanout, top-of-DRAM reserved region, matches /proc/iomem fae00000-ffdfffff) x0 = `_fit_fdt_start` (device-era src/arch/arm/fit.c: the flattened tree is placed in depthcharge's own `_fit_fdt_start.._fit_fdt_end` buffer and that pointer is handed off). The kernel later reserves exactly `5f800000-5f815fff` for its FDT (size ≈ the live fdt's 86738 bytes), so the pointer is right — but for the payload boot the buffer content reads as zeros. Why the kernel sees a valid FDT there while the payload sees zeros is still OPEN (cache/flush difference vs the pmOS kernel boot is the leading suspicion; the pmOS kernel is ~30 MB vs our 18 KB stub — decompression footprint differs). For the stub this does not matter: **Fallback added**: if `find_coreboot_reg` fails, use the fixed coreboot table address `0xffed9000` (coreboot memlayout constant on this board; confirmed by the kernel's own coreboot driver, sysfs tags, and `/sys/firmware/fdt`). The stub no longer depends on the DTB at all. `find_framebuffer` failure still dumps the raw LBIO bytes. Payload `adbea06f…` flashed (cmp + vbutil OK). host_test and qemu_test pass. ## Round 10 — SUCCESS: full pipeline validated Payload `adbea06f…`: **red → yellow → green → blue, blue held.** The depthcharge → custom-payload → display pipeline works end to end: 1. depthcharge boots the dev-signed FIT from `mmcblk0p1` and hands off (x0 = FDT buffer, x1-x3 = 0, MMU off); 2. stage0 re-enables OVL (+0x000C / +0x100C) and backlight (GPIO dout 0x10005114 / 0x10005154); 3. the LBIO table at the fixed 0xffed9000 parses (record: pa handled via OVL_L0_ADDR substitution = 0xFD536000); 4. fills at the live scanout are visible. Effective gotchas that cost nine rounds, all [REVERSED] or resolved: OVL_EN offset (0x0F00→0x000C), GPIO dout offset (0x140→0x100), LBIO pa=0 legitimacy (cbgfx rejects it; scanout comes from OVL_L0_ADDR), and the empty x0 FDT buffer (bypassed with the fixed table address; root cause still open and RELEVANT FOR U-BOOT). Next phase: U-Boot as the FIT kernel payload. Handoff prompt for the next agent: `/home/vhaudiquet/u-boot-krane-prompt.md` (milestones: krane defconfig/board, Image-header wrapper + flash flow, framebuffer vidconsole reusing the validated revival/scanout code, then eMMC + bootflow; all work upstreamable; x0-FDT-zeros trap documented). Recovery: power-cycle, boot USB (unchanged), `dd if=mmcblk0p1-pmos-backup.img of=/dev/mmcblk0p1 bs=4M conv=fsync`. ## Open items - `physical_address` in the LBIO record — **resolved in Round 2**: provably non-zero at runtime (depthcharge's device-era `mtk_display_init` programs `OVL_L0_ADDR` from the LBIO record and the menu rendered through it; the `/dev/mem` read attempt failed on `CONFIG_STRICT_DEVMEM` + reserved-RAM, "Bad address", not EPERM as first assumed). - qemu test updated to the same expectation (colors + held blue); it exercises parser + fill logic, not the MMIO revival writes. ## Round 11 — U-Boot as payload: target, driver, first flash (result pending) First mainline-U-Boot flash after the Round-10 pipeline validation. ### Build (milestone 1) - Branch `krane` in `/home/vhaudiquet/u-boot` (mainline main @ `527115ef`); commits `29aec558` (board/defconfig/MAINTAINERS), `923cc754` (dtc include order), `f0470c9b` (video driver). - `configs/mt8183_kukui_krane_defconfig` forked from pumpkin: keeps `CONFIG_POSITION_INDEPENDENT=y`, DEBUG_UART (0x11002000 @ 26 MHz, goes nowhere — absence of colors is not failure), MTK serial/MMC/WDT; drops fastboot/TPHY/mtu3/USB. `CONFIG_BOOTDELAY=-1` so the first boot stops at the prompt instead of autobooting pmOS from p3. - DT: `CONFIG_OF_UPSTREAM=y`, `CONFIG_DEFAULT_DEVICE_TREE="mediatek/ mt8183-kukui-krane-sku176"` — **with OF_UPSTREAM the name must include the vendor dir**; no arch/arm/dts registration needed. - **New trap found while building**: U-Boot's legacy `include/dt-bindings/clock/mt8183-clk.h` (BSP numbering, `CLK_TOP_CLK26M=0`) shadows the upstream `dts/upstream/include/dt-bindings/clock/mt8183-clk.h` (`CLK_TOP_CLK26M=112`) for OF_UPSTREAM builds — the krane tree failed with undefined `CLK_CAM_LARB6`, and even fixing those macros would have silently produced wrong clock numbers everywhere. Fix: `scripts/Makefile.lib` prefers `dts/upstream/include` when `CONFIG_OF_UPSTREAM=y`. Pumpkin's in-tree dts (different binding generation) keeps working because its config does not set OF_UPSTREAM. ### Video driver (milestone 3, flashed before first reboot) `drivers/video/mt8183_scanout.c` (`CONFIG_VIDEO_MT8183_SCANOUT`, checkpatch-clean), binds the upstream `ovl0@14008000` node (`mediatek,mt8183-disp-ovl`; the two 2L overlays match a different compatible, so exactly one probe). probe(): 1. revival identical to the stub's stage0: `OVL_EN=1` (+0x000C), `OVL0_2L_EN=1` (+0x100C), GPIO dout set for pins 43/176; 2. LBIO parse at the fixed 0xffed9000 (ported from the stub's Round-10-validated `find_framebuffer`; host regression test compiled the function standalone against `lbio.bin`: record parsed, bad signature rejected); 3. scanout = LBIO pa if >= 0x40000000, else `OVL_L0_ADDR` (+0x0F40) (Round-9 ground truth: 0xFD536000); else probe fails gracefully; 4. geometry from the record (xRGB 32bpp required; U-Boot's BPP32 console default composes pixels exactly as r<<16|g<<8|b — checked in video-uclass `video_index_to_colour`); 5. both the coreboot table and the fb sit above the DTB's 2 GiB DRAM window (memory@40000000 is 0x80000000 long), so both regions are identity-mapped with `mmu_map_region()` first (MMIO < 0x20000000 is already mapped Device by `arch/arm/mach-mediatek/armv8-mem-map.c`); `video_set_flush_dcache(true)` for the console writes. Revival happens BEFORE parsing, so "backlit black" = ran but parse/ probe failed, "dark" = never reached the driver — same decision tree as the stub. No autoboot: U-Boot banner + prompt should stay on panel. ### Wrap + flash (milestone 2) `krane-fb-stub/build-uboot-payload.sh`: 64-byte arm64 Image header (code0 `b +0x40` = 0x14000010, image_size = 64 + len(u-boot.bin), flags bit3, magic at 0x38) prepended to `u-boot.bin`, mkdepthcharge with the krane DTB, `vbutil_kernel --verify`. (Header-bytes were checked with `od` after an edit mishap dropped the flags word — always re-verify the raw header bytes.) depthcharge decompresses the FIT and jumps to the first byte — U-Boot's `_start` lands at +0x40, PC-relative (PIC) so the arbitrary 2 MiB slot is fine. - Payload `krane-uboot-payload.bin` sha256 `1fc74a0a…`, flashed to `mmcblk0p1` (dd 4M conv=fsync), `cmp` OK (589824 bytes), on-device `vbutil_kernel --verify` → body verification succeeded. - Backup `mmcblk0p1-pmos-backup.img` (sha256 `20951a54…`) copied onto the USB stick (sda3) — recovery no longer depends on eMMC. ### Expected observation Panel shows the U-Boot banner (white/light-gray text on black, portrait 1200×1920) and the prompt, frozen (BOOTDELAY=-1). Nothing on panel = boot path or early crash; backlit black = reached handoff but driver probe failed. The x0-FDT-zeros trap is bypassed: U-Boot uses its embedded DTB and never reads the handoff FDT. ## Round 12 — first U-Boot boot: pitch black; diagnostic wrapper Reboot with Round-11 payload `1fc74a0a…`: **pitch black, no backlight**. Per the Round-11 decision tree the video driver's probe never ran (the OVL/backlight revival is its first act). So either the image never executed (but Round 10 proved the handoff path, and only the payload contents changed) or U-Boot died between entry and the video probe — a wide window (PIE fixup, relocation, DM scan, and notably `initr_env` (MMC/clock probe) runs before `stdio_add_devices` in board_r.c, i.e. before video probe and the banner). ### Localization flash (payload `9955943c…`) The wrapper itself now carries the life sign, independent of U-Boot: `uboot-wrapper.S` (linked at +0x40 inside the Image, immediate-encoded PIC, no relocations) runs before U-Boot proper: 1. OVL revival + backlight (identical writes to the stub's stage0); 2. reads OVL_L0_ADDR, fills the scanout (guarded >= 0x40000000) with full-screen green (0x8ca000 bytes = 1200*1920*4); 3. branches (imm26 patched at build time) to U-Boot's entry at +0x40+wrapper_len (116 bytes). Layout verified by disassembly before flashing: header (code0 b +0x40, image_size 0x6d9ac, flags bit3, magic), wrapper instruction sequence, patched tail branch, U-Boot intact at +0xB4. Payload flashed, cmp + on-device vbutil verify OK. ### Decision tree | observation | meaning | |---|---| | green screen + backlight | wrapper ran; U-Boot crashed before video probe | | dark, no backlight | wrapper never executed — handoff/boot-path problem with THIS image | | U-Boot banner | everything works (banner replaces the green) | ## Round 13 — green screen: U-Boot crashes pre-video-probe; init checkpoints Reboot with `9955943c…`: **full-screen green, stable** — the wrapper ran and U-Boot proper died between entry and the video probe (the video uclass would have `video_clear`ed to black for the banner). The crash window is wide: PIE fixup, relocation, DM scan, and notably `initr_env`/`initr_mmc` run BEFORE `stdio_add_devices` in board_r.c. ### Checkpoint flash (payload `fc48d83b…`) U-Boot itself now paints checkpoints into the live scanout from board code (`mt8183.c`, TEMPORARY, gated on `CONFIG_BOARD_EARLY_INIT_F/R`, `dcache_status()`-guarded with `mmu_map_region`+flush for the post-relocation one): | band | checkpoint | position | |---|---|---| | full green | wrapper | before U-Boot (Round 12) | | orange top | `board_early_init_f` | after initf_dm, before get_clocks/timer/serial/dram_init | | blue bottom | `dram_init_banksize` | after dram_init, right before relocation | | red top | `board_early_init_r` | post-relocation, before dm/mmc/env/console/video | Read: last paint reached = code survived through that checkpoint; the crash/hang is in the next window. Success = banner on black. ## Round 14 — all green decoded: U-Boot's 4K-alignment requirement Reboot with `fc48d83b…`: **all green, no bands** — U-Boot hung before `board_early_init_f`. Root cause found in start.S: with CONFIG_POSITION_INDEPENDENT, U-Boot verifies `adr _start` & 0xfff == 0 (it uses ADRP+ADD with lo12 relocations during the PIE fixup) and otherwise loops in `wfi` forever — a silent hang, no exception. Our wrapper pushed U-Boot's entry to load_base+0xB4: misaligned by construction. ### Fix (payload `faee130f…`) `build-uboot-payload.sh` now pads the wrapper so U-Boot starts at the next 4 KiB boundary (0x1000) inside the 2 MiB-aligned image; wrapper tail branch patched accordingly (0x140003d4 → 0x1000, verified by disassembly). Keep this invariant for every future layout change. ## Round 15 — all black again (Round 14 anomaly); blink-train wrapper Reboot with `faee130f…` (4K-aligned U-Boot): **all black, no backlight**. Unexpected: the wrapper is byte-identical to Rounds 12/13 except its tail-branch immediate, and it demonstrably painted green in both. Black without any wrapper trace was only ever explained by "payload never ran". Candidates for a genuine regression: none found in the wrapper path; no U-Boot driver can touch the backlight GPIOs (no MTK GPIO driver in-tree, PINCTRL off); nothing before the video probe touches display HW. Working hypothesis: the Round-14 boot transiently did not hand off (or the report reflects a state we cannot yet distinguish). Note: a build slip was caught before flashing — the diagnostic hooks were gc-section'ed out because `.config` was stale; defconfig was re-run and the hook symbols verified with `nm` (board_early_init_f, board_early_init_r, dram_init_banksize, misc_init_r all present). ### Round-15 payload (`b1c5c380…`, flashed, cmp + vbutil OK) Maximally distinguishable phases: - wrapper: **magenta** full screen + backlight, then **3 slow backlight blinks** (~250 ms each via the arch timer), then branch to U-Boot. Repeating blink trains = WDT reset loop; a single train then colors = wrapper ran; nothing at all = never ran. - U-Boot checkpoints unchanged: orange top (`board_early_init_f`), blue bottom (`dram_init_banksize`), red top (`board_early_init_r`). - new: **yellow band** + backlight re-assert in `misc_init_r` (CONFIG_MISC_INIT_R) — proves full init past the banner stage. ### Decision tree | observation | meaning | |---|---| | nothing, no blink | payload not handed off (depthcharge-side) | | blink train once, then magenta persists | U-Boot crashed before `board_early_init_f` (PIE fixup / early board_init_f / initf_dm) | | blink train repeating periodically | reset loop (WDT) — identify the phase by the colors that flicker | | magenta + orange/blue/red/yellow bands | crash in the next window after the last band | | banner on black | success | ## Round 16 — bisecting the R14/R15 handoff failure R15 (`b1c5c380…`): nothing at all — no magenta, no blink train. Since the wrapper + header bytes at 0x40 are functionally identical across R13 (ran) and R14/R15 (did not run), the failure is depthcharge-side. Only depthcharge-visible structural change between R13 and R14: the mkdepthcharge body grew from 0x80000 to 0x81000 when the 4 KiB alignment padding landed (R15: 0x82000). Verified the device-era depthcharge (R93, `0.0.22-10566-g8417b00957`) sources are not locally available (clone is mainline-only; googlesource fetch failed); mainline KERNEL_SIZE is 0x20000000, so a 512 KiB constraint has no known mechanism yet — but the correlation is 2-for-2. ### Discriminating flash (payload `d418252a…`) R13 layout exactly (wrapper v1 = green fill, no blinks, U-Boot @0xB4) but with R14's body size (0x81000, via the larger R15 U-Boot): | observation | conclusion | |---|---| | green (persistent) | body size innocent; the 0x1000 offset somehow guilty — re-bisect | | black | FIT body > 0x80000 breaks this depthcharge — fix = keep FIT under 512 KiB (minimal FDT subimage) with U-Boot @0x1000 | ## Round 17 — R16 green decodes body-size theory; total-size bisect R16 (R13 layout, body 0x81000): **green** — body size ruled out (R16 and R14 share body 0x81000). Updated correlation: total wrapped-image size / image_size field: ran ≤ 449772, dead ≥ 453432. Device-era sources recovered far enough to rule out boot64/fit_decompress size checks (kukui r93/12573-B code identical modulo cosmetics; bounds 0x5f800000/0x60000000/0x80000000 — kend≈0x4006xxxx passes; the loader is DETERMINISTIC: first CB_MEM_RAM range, kstart=0x40000000 — the "random 2 MiB slot" note from Phase 1 was mainline behavior, [REVERSED] for the device). ### Test A (payload a27f0354…, flashed, cmp+vbutil OK) R16's exact known-good content (wrapper v1 green, U-Boot @0xB4, contiguous — wfi hang expected) + 8 KiB zero tail padding → total 457964, image_size=457964, body 0x83000. Isolates TOTAL SIZE from the 0x1000-offset/padding variable: | observation | conclusion | |---|---| | green | total size innocent → the 0x1000 alignment padding/placement is the killer (re-test padding content) | | black | total size (image_size value) alone gates the handoff | ## Round 18 — total size ruled out; wrapper-only isolator R17 (R16 content + 8 KiB tail pad, total 457964 > dead 453432): **green** — total size ruled out. The only remaining structural difference between ran (R12/13/16/17: U-Boot contiguous at 0xB4) and dead (R14/15: U-Boot at 0x1000 behind interior zero pad): the interior pad + shifted placement. Also discovered: the U-Boot binary does NOT start at _start — `arch/arm/cpu/u-boot.lds` places 0x40 bytes of `.vectors` first, so link _start = 0x4C000140 (all prior alignment math shifts by 0x40; the PIE requirement is runtime _start ≡ link _start mod 4K AND start.S's wfi check demands runtime _start ≡ 0 mod 4K, so link _start must be 4K-aligned too). ### Test B (payload 9485c59e…, flashed, cmp+vbutil OK, body 0x14000) 4 KiB image: header + wrapper v3 (magenta + 3 blinks + ~5 s hold) + zeros to 0x1000 + NO U-Boot, tail branch patched to self (hang): | observation | conclusion | |---|---| | magenta + blinks + hang | interior pad innocent; the trigger is U-Boot content/placement at 0x1000 | | black, nothing | depthcharge rejects the padded image before any execution | ## Round 19 — Test B decoded: wrapper runs in padded layout; correct PIE alignment + post-clear checkpoint Test B (`9485c59e…`): **wrapper color + 3 blinks + color held** — the wrapper runs fine even in the padded/0x1000 layout, with NO U-Boot present. Depthcharge is fully exonerated: it copies and jumps every payload. Combined with R14/R15 (black), the story closes: U-Boot (at 0x1000, aligned) RAN in R14/R15, reached the video probe and its `video_clear()` (black over the wrapper color, backlight back on), and died somewhere between the clear and a visible banner. The user's "no backlight" was a backlit-black misread (matches R5's physics). (The R15 blink train was evidently too brief to register.) ### Alignment math corrected ([REVERSED] earlier notes) The U-Boot file does NOT start at _start: a 4-byte stub precedes it (nm: link _start = CONFIG_TEXT_BASE + 4). Two constraints must hold simultaneously: 1. start.S wfi check: runtime _start ≡ 0 (mod 4K); 2. PIE fixup (adrp+add lo12): runtime _start ≡ link _start (mod 4K). ⇒ link _start must be 4K-aligned: CONFIG_TEXT_BASE=0x4C000FFC → link _start=0x4C001000; U-Boot file placed at image offset 0xEC0 → runtime _start=0x40001000. Verified: nm shows 0x4c001000; the payload script now asserts it against u-boot.sym. (Earlier R14/R15 layouts had runtime _start page offset 0x40 → wfi-hang — but green should have persisted; the observed black is only explained by the video_clear path, which means R14's U-Boot PASSED the wfi check... [OPEN] the R14/R15 images had link _start=0x4C000140 (page offset 0x140) and runtime 0x1040 (0x40) — mismatched mod 4K by 0x100, so even past the wfi check the PIE fixup would corrupt pointers; either way the video_clear observation stands as the only black-producing mechanism.) ### Round 19 payload (`1f598c7a…`, flashed, cmp+vbutil OK) - CONFIG_TEXT_BASE=0x4C000FFC, U-Boot file at image 0xEC0 (correct alignment for both constraints); - new checkpoint: WHITE band painted at the END of video_post_probe (after the vidconsole child is bound+probed) — post-clear deaths are now distinguishable from banner-stage deaths; - all earlier checkpoints retained (orange/blue/red bands, misc_init_r yellow). ### Decision tree | observation | meaning | |---|---| | magenta + blinks + magenta persists | U-Boot died BEFORE the video probe (bands show how far) | | black + white band | video probe completed; died between post_probe and banner | | black, no white band | died inside video_post_probe after video_clear (bind/probe of vidconsole) | | banner (white on black) | works | ## Round 20 — post-probe black decoded: U-Boot reaches video_clear; two-band instrumentation Round 19 (`1f598c7a…`): wrapper color + blinks, then **black, no white band**. Decoded: U-Boot runs (correct alignment), reaches the video uclass `video_clear()` (the only black-painter — that's the blue→black transition), and dies/ends before the white band at the end of video_post_probe. The window contains only: colours setup, vidconsole bind ("vidconsole0"), device_probe of the console child, cyclic registration. A GRACEFUL failure there (bind/probe error → video probe fails) leaves exactly the observed state: cleared black, no banner (goes nowhere — no console), backlight on. Also noted: CONFIG_CYCLIC=y makes video-uclass line 732's `gd->flags && GD_FLG_RELOC` typo live (harmless-looking cyclic_register every 10 ms). ### Round 20 payload (`90f0de99…`, flashed, cmp+vbutil OK) Two-band instrumentation inside video_post_probe: - **cyan** band right after `video_clear()` — visible ⇒ the clear and the fb mapping work, death is later in this function or after; - **white** band after the vidconsole child is bound+probed (existing). ### Decision tree | observation | meaning | |---|---| | black only (no cyan) | died between clear and cyan — video_set_default_colors/... (near-impossible) | | cyan only | death/graceful-failure in bind or probe of vidconsole0 | | cyan + white | died between post_probe end and the banner (jumptable/console_init_r/puts) | | banner | works | ## Round 21 — [REVERSED R19/R20] root cause: build script patched delay's `ret`, not the tail branch User report R20 ("blue fill, one blink, black, backlight off, ~1 s, NO 5 s hold") exposed a build bug that invalidates the R19/R20 interpretation — U-Boot never executed at all in R19/R20: - The wrapper's tail `b .` is NOT the last instruction: v3 puts the `delay` subroutine (8 insns incl. `ret`) after it. The script patched `wrapper[-4:]` = delay's `ret`, overwriting it with the branch. - Effect: the FIRST blink's `bl delay` never returns — it branches to the U-Boot file's first word (0x00000000 = UDF) with the backlight in the blink-OFF phase → screen black, backlight off, hang. Blue fill + one dark blink = exactly the R19/R20 observations. The "reached video_clear" decode of R19/R20 is [REVERSED]; the cyan/white bands were never reached (kept in the tree — still useful). - Test B (R18) was immune because its wrapper-only image left `b .` as the self-branch (patching `ret` was harmless there). R12–R17 ran because wrapper v1/v2 had the tail branch as the last instruction. ### Fixes (payload `cff0e0e9…`, flashed, cmp+vbutil OK) 1. Script now SEARCHES the wrapper for the single `b .` (0x14000000) and asserts exactly one occurrence; patch target = the real branch. 2. Placement recomputed from the ELF: `_start` is NOT at file offset 0 — the image leads with 0xFFC bytes (vectors/stub) so link _start = 0x4C001000 = file offset 0x1000 (TEXT_BASE 0x4C000FFC). U-Boot file placed at image 0x1000 → runtime _start = 0x40002000 (4K-aligned); PIE delta = -0xBFFFF000 (4K-aligned). Both alignment constraints hold for the first time in one layout. 3. Verified in the packed image: `b .`@0x118 → 0x140007ba → target image 0x2000 = runtime 0x40002000 ✓; `ret`@0x138 intact ✓. ### Expected behavior Blue fill → 3 blinks (~1.5 s) → 5 s blue hold → U-Boot takes over. If U-Boot boots: banner (white on black). If it dies early, the orange/blue/red board-init bands and the post-clear cyan / post-probe white bands localize it (Round 19/20 instrumentation still in place). ## Round 22 — wrapper fully healthy; U-Boot dies before board_init_f's first band Round 21 (`cff0e0e9…`): **blue, 3 blinks, blue held forever** — the wrapper now runs its complete sequence (fill, blink train, 5 s hold) and jumps to 0x40002000. Blue persisting = nothing in U-Boot painted anything: death (or non-execution) BEFORE board_early_init_f's orange band. start.S review: the 4K wfi check passes (runtime _start 0x40002000); the PIE fixup applies the full delta to relative relocs and our delta (-0xBFFFF000) is 4K-aligned, so adrp+lo12 stays valid; crt0's SP (INIT_SP_RELATIVE, adrp __bss_start + 0x80000) is also 4K-delta-safe. So the hang is somewhere in [PIE fixup .. initcalls before board_early_init_f] — a path no payload has ever reached on this board (R12–R17 all died at the 4K wfi check). ### Round 22 payload (`21c3f792…`, flashed, cmp+vbutil OK) New checkpoint: **green band (y=400)** painted at the very top of board_init_f (common/board_f.c, temporary) — before the entire initcall sequence. ### Decision tree | observation | death localized to | |---|---| | blue hold only (no green) | start.S / crt0 / PIE fixup (pure asm window) | | green only | initcalls before early_init_f: fdtdec_setup (embedded DTB!), log, bootstage, bloblist, arch/mach_cpu_init, initf_dm (clock driver probes) | | green + orange | after early_init_f, before dram_init_banksize | | green + orange + blue | after dram_init_banksize, before relocation | ## Round 23 — [REVERSED R22 analysis] true root cause: 4-byte linker fill before _start skews the PIE fixup R22 (`21c3f792…`): blue hold only — board_init_f's green band never painted. Root cause found in the disassembly, not guessed: - `start.o`'s input `.text` section is 8-byte aligned, but CONFIG_TEXT_BASE=0x4C000FFC is only 4-aligned → ld inserts a 4-byte FILL at the start of .text → `_start` lands at TEXT_BASE+4, while `_TEXT_BASE` (the word start.S's PIE fixup loads as the link base) and `__image_copy_start` remain at TEXT_BASE. - start.S: `pie_fixup: adr x0,_start; ldr x1,_TEXT_BASE; subs x9,x0,x1` → x9 = true_delta + 4 → EVERY relative relocation skewed by 4 → corrupted gd/function/fdt pointers → death before board_init_f. (The `adrp + #:lo12:` pairs used for the rela bounds also assume delta ≡ 0 mod 4K — with the skew, both mechanisms break.) - This retroactively explains the R12–R22 alignment confusion: the "+4" was never a stub/vectors mystery — it was a linker fill that appears iff CONFIG_TEXT_BASE is not 8-aligned. - [REVERSED] the "vectors/stub bytes precede _start" note from R18/19; the 0xFFC leading bytes in old u-boot.bin dumps were the fill plus ELF file-offset artifacts, and the R22 "dies in pure asm" conclusion was wrong in mechanism (it died IN the fixup's corrupted pointers). ### Fix (payload `18cf3e93…`, flashed, cmp+vbutil OK) - CONFIG_TEXT_BASE=0x4C001000 (8-aligned) → no fill: `_start == __image_copy_start == _TEXT_BASE == 0x4C001000`, file offset 0 = `b reset` (verified in u-boot-nodtb.bin and in the packed image at 0x1000). - File placed at image offset 0x1000 → runtime _start = 0x40001000, start.S 4K check passes; PIE delta = -0xC000000 (4K-aligned). - Payload script now derives the file offset from __image_copy_start (not ELF section offsets) and ASSERTS `_start == __image_copy_start` so a fill regression fails at build time instead of on device. ### Expected blue → 3 blinks → 5 s hold → green band (board_init_f) → orange/blue/ red bands → cyan/white (video probe) → banner. ## Round 24 — C environment alive; video probe is the failing stage Round 23 (`18cf3e93…`): blue → blinks → hold → bands. The user reported (top to bottom): two red-ish stripes, yellow, green — i.e. board_init_f (green), board_early_init_f (orange, reads red-ish), board_early_init_r (red), misc_init_r (yellow) ALL ran; dram_init blue is at the very bottom (y=1820, easy to miss). CRITICAL: the screen was NOT cleared and no cyan/white band appeared → the video uclass post_probe (whose FIRST action after colour setup is video_clear) never ran → the video driver's probe FAILED or the device never bound. Everything else (banner needs the vidconsole) follows from that. Note: the "yellow" seen may be either misc_init_r (y=300) or the post-clear cyan band (0x00ffff00 = yellow in xRGB, y=260) — irrelevant since no clear happened. ### Round 24 payload (`03fc7b4d…`, flashed, cmp+vbutil OK) New checkpoints (mt8183_scanout.c + video-uclass.c video_post_bind): - **purple** (y=470): a video device got BOUND (driver matched DT) - **magenta** (y=500): driver probe entered - **gray** (y=620): geometry resolved (coreboot LBIO parsed + scanout addr valid) - **red bands** at failure exits: y=780 (ovl reg read failed), y=800 (coreboot LBIO not found), y=820 (pixel format mismatch), y=840 (no plausible scanout address) ### Decision tree | observation | meaning | |---|---| | no purple | video driver never bound (DT compatible / DM scan issue) | | purple, no magenta | bound but probe not called (uclass sequencing) | | magenta + red@800 | coreboot table/LBIO not found (map/parse) | | magenta + red@820 | LBIO found but format mismatch | | magenta + red@840 | no plausible scanout address | | magenta + gray | geometry OK; failure later (clear/probe tail) | | cyan/white appear | video_clear + post_probe reached — back on the R19/20 tree | ## Round 25 — [REVERSED] not a probe failure: the video device was never probed (console env wiring) Round 24 (`03fc7b4d…`): **purple, no magenta, no red error bands** — the driver matched the DT node and bound, but device_probe was never called, and none of the probe's error paths ran. Root cause is in common code, not the driver: - stdio_add_devices()'s probe-all loop over UCLASS_VIDEO is compiled OUT when CONFIG_SYS_CONSOLE_IS_IN_ENV=y — and that defaults to y via CONSOLE_MUX (inherited from the pumpkin defconfig fork). - With SYS_CONSOLE_IS_IN_ENV, console_init_r() reads env stdout/stderr and muxes via iomux_doenv(). There was NO environment (and the env backend was MMC — inherited, wrong for a depthcharge payload), so the names were NULL, console_search_dev() never saw the string "vidconsole", the lazy stdio_probe_device(name, UCLASS_VIDEO) never fired, and the console fell back to serial only. The video device was never probed → no clear, no cyan/white, no banner. Screen keeps the init bands. ### Fix (payload `e4bdd875…`, flashed, cmp+vbutil OK) - CONFIG_ENV_IS_NOWHERE=y (drops the MMC env dependency inherited from pumpkin — correct for a payload stage, deterministic default env); - new CONFIG_ENV_SOURCE_FILE="krane" + board/mediatek/mt8183/krane.env: stdin=serial stdout=serial,vidconsole stderr=serial,vidconsole so console_init_r() → iomux_doenv() → console_search_dev ("vidconsole") → stdio_probe_device → probes the video device (driver probe → clear → vidconsole registered) → banner muxed to serial + screen. - R24 instrumentation kept for this round. ### Expected blue → blinks → hold → green/orange/blue/red/yellow bands → magenta (probe entered) → screen CLEARS black → gray (geometry) → cyan/white (video clear/probe) → **white-on-black banner** (or a red failure band: y=800 lbio, y=820 fmt, y=840 scanout). ## Round 26 — video probe SUCCEEDED (clear ran); post-console hang reset via WDT; WDT disabled for observability Round 25 (`e4bdd875…`): bands → **screen cleared black** → reset back to the depthcharge menu. Decode: - The clear proves console env wiring now works: krane.env's stdout=serial,vidconsole → console_init_r → iomux_doenv → console_search_dev("vidconsole") → stdio_probe_device → video driver probed (magenta/gray painted, then erased by the full-screen video_clear) → post-probe cyan/white should have painted too (thin, y=200/260 — possibly missed in the crash window). - The reset is the MTK watchdog: CONFIG_WDT_MTK autostarts a 60 s watchdog at initr_watchdog (early board_r); it is serviced by the cyclic scheduler only once main_loop() runs. A hang anywhere between the video probe (still in board_r console init) and the first schedule() → 60 s → WDT reset → depthcharge menu. User's "immediately" = unattended reset. ### Round 26 payload (`08ee5c1f…`, flashed, cmp+vbutil OK) - **# CONFIG_WATCHDOG_AUTOSTART is not set** — the WDT is probed but not started, so a hang now freezes the screen with bands visible instead of resetting. (Temporary bring-up measure; upstream will want the WDT on.) - New checkpoint: **light-gray band (y=900)** at main_loop entry (common/main.c) — everything before it completed. ### Decision tree | observation | meaning | |---|---| | black screen stays black, no reset, no gray | hang between video probe and main_loop (console tail / late board_r / initr_mmc...) | | black + gray band, frozen | main_loop reached, hang in preboot/cli_init/cli_loop — inspect further | | banner + prompt (white on black) | WORKING | ## Round 27 — pink band in board_late_init; WDT-off reset anomaly needs timing Round 26 (`08ee5c1f…`): user reports "same as before": bands → black (clear) → menu. Ambiguity: the report may describe the R25 payload (WDT autostart ON, 60 s → reset explains it) or the R26 payload (WDT autostart OFF — then the reset source is NOT U-Boot's watchdog: coreboot's mtk_wdt_init DISABLES the WDT, depthcharge doesn't arm it, so a reset would need another mechanism). The black→menu GAP will discriminate: ~55-65 s ⇒ watchdog; a few seconds ⇒ something else. The missing light-gray band (main_loop entry, y=900) keeps the death window at [misc_init_r (yellow, AFTER console_init_r!) .. main_loop]. Refined understanding of the observed order: the video probe runs INSIDE console_init_r (lazy stdio_probe_device via iomux), so the screen clears BEFORE misc_init_r's yellow paints — yellow on black is expected, and the pre-clear bands (green/orange/red + probe's magenta/gray/cyan/white) are erased by the clear. Board_r tail: console_init_r → console_announce_r (first vidconsole TEXT!) → arch/misc_init_r → interrupt/timer init → board_late_init → EVT_LAST_STAGE_INIT → initr_mem → run_main_loop. No text has ever been reported — the first vidconsole puts (console_announce banner or "In:/Out:/Err:") may itself be the crash point. ### Round 27 payload (`948d5e2b…`, flashed, cmp+vbutil OK) - New **pink band (y=860)** in board_late_init (CONFIG_BOARD_LATE_INIT enabled). Sequence of late checkpoints: yellow (misc_init_r) → pink (board_late_init) → light-gray (main_loop). - WDT still off (autostart=n). ## Round 28 — reset mechanism identified: sync abort → panic → PSCI reset; PANIC_HANG to capture the dump Round 26/27 analysis (user: reset in <1 s after the black screen, WDT autostart already off). The reset mechanism is now proven from source, not guessed: - coreboot's mtk_wdt_init() DISABLES the MTK WDT (clears WDT_ENABLE); depthcharge never touches it; U-Boot's mtk_wdt_probe() even stops it. Hardware watchdog ruled out. - arch/arm/mach-mediatek/mt8183/init.c: reset_cpu() → psci_system_reset() (SMC into BL31) — instant warm reset. - lib/panic.c: CONFIG_PANIC_HANG unset → panic() → do_reset() → reset_cpu() → PSCI. - arch/arm/lib/interrupts_64.c: do_sync() → show_regs → panic("Resetting CPU ..."). ⇒ ANY synchronous abort after the video probe = instant reset to the depthcharge menu, <1 s. Matches the observation exactly. (Also retroactively plausible for earlier "resets".) ### Round 28 payload (`d1e78b0f…`, flashed, cmp+vbutil OK) CONFIG_PANIC_HANG=y — on an abort, U-Boot now prints the full exception dump ("Synchronous Abort" handler, esr, FAR, registers, PC) through the vidconsole and HANGS with the text visible instead of resetting. Plus R27's pink (board_late_init, y=860) and R26's light-gray (main_loop, y=900) bands. ### Ask to the user Reboot, select Internal storage. Expect: bands → black → then either frozen black with WHITE TEXT (transcribe it! esr/FAR/ELR tell us the exact fault), or a hang at some band stage. Report the band stack (yellow/pink/gray) and any text. ## Round 29 — crash localized inside the driver probe; step-by-step bands Round 28 (`d1e78b0f…`, PANIC_HANG): **purple (bind) + magenta (probe entered), frozen; no gray, no pink, no text.** Decode: - The crash is inside mt8183_scanout_probe, between entry (magenta, y=500) and geometry resolution (gray, y=620) — and NOT in any of the handled error paths (no red bands). - Why no abort text despite PANIC_HANG: at this point the vidconsole doesn't exist yet, so printf output goes to the pre-console buffer and the dump is invisible. Expected — not a contradiction. - Note the magenta band itself proves post-reloc mmu_map_region + MMIO read of 0x14008f40 + fb write work (dcache on). Remaining suspects between magenta and gray: revival MMIO writes (same 4K page as the working read — unlikely), mmu_map_region(0xffed9000, 4K) (first non-fb, non-DRAM mapping post-reloc — prime suspect), find_framebuffer() reads of the coreboot table, format check. ### Round 29 payload (`35376f63…`, flashed, cmp+vbutil OK) Sub-checkpoints inside the probe (all 20-row bands below magenta): - **mint** y=520: revival writes done - **violet** y=540: coreboot table mapped - **pale yellow** y=560: LBIO found - **pale cyan** y=580: format OK - red bands on the handled failures unchanged (y=780/800/820/840) ### Ask Reboot and report which of mint/violet/pale-yellow/pale-cyan appear below the magenta band. The first missing one identifies the aborting statement. ## Round 30 — real text logging: fb font logger + abort dump on panel Round 29 result: probe entered (magenta), then frozen — but the user's color report ("light green") is ambiguous between mint (y=520) and pale yellow (y=560), and band combinatorics are getting unreadable. User proposed real logs — agreed; implemented: - mt8183_scanout.c now has krane_fb_log() / krane_fb_log_hex(): a minimal framebuffer text logger using the built-in 8x16 font (video_font.h), drawing white text into the live scanout (bottom rows, y>=1700), with a static cursor and per-call dcache flush. - Probe steps now emit text: PROBE → REVIVE → MAPTBL → LBIO → FMT → GEO (bands kept for redundancy). - krane_fb_log_abort(esr, elr): reads FAR (CurrentEL-aware mrs) and draws "ABORT! ESR=0x… ELR=0x… FAR=0x…" on the panel; wired into arch/arm/lib/interrupts_64.c do_sync() (TEMPORARY). With CONFIG_PANIC_HANG=y the screen freezes with the dump visible — even though the pre-console printf buffer stays invisible. ### Round 30 payload (`60f5dba8…`, flashed, cmp+vbutil OK) ### Ask Reboot and report: (a) the text line(s) at the bottom (PROBE REVIVE MAPTBL LBIO FMT GEO — where does the sequence stop?), and (b) if an "ABORT! ESR=0x… ELR=0x… FAR=0x…" appears, transcribe the three hex values. ## Round 31 — [ROOT CAUSE] page-table pool exhausted: mmu_map_region(coreboot table) → panic Round 30 result: "PROBE REVIVE" only — the hang is exactly at mmu_map_region(COREBOOT_TABLE_ADDR=0xffed9000, SZ_4K). No ABORT! text drawn ⇒ not a fault ⇒ a HANG. The only hang inside mmu_map_region: create_table() → panic("Insufficient RAM for page table") → hang(). Why: get_page_table_size() budgets only for the STATIC memory map (count_ranges × 2 + 4 spare tables). Post-reloc dynamic mappings (the fb 0xFD536000/0x8ca000 → several L3 tables, then the 4K table region at 0xffed9000 → yet another L3) exhausted the pool. This retroactively explains the R25/R26 "<1 s reset to menu": with PANIC_HANG off, that same panic() → do_reset() → reset_cpu() → psci_system_reset() — the reset the user saw. Root cause found for the whole "dead/black/reset" saga since Round 12. ### Fix (payload `34b9660f…`, flashed, cmp+vbutil OK) mt8183.c: board override of the __weak get_page_table_size() → 0x40000 (64 tables), sized for the driver's dynamic mappings. This override is the intended extension mechanism and is upstreamable. ### Expected PROBE REVIVE MAPTBL LBIO FMT GEO → screen clears → banner (white on black) + U-Boot prompt. If anything still fails, the ABORT!/panic text now lands on the panel. ## Round 32 — MILESTONE 3 ACHIEVED: banner + prompt live on the panel Round 31 (`34b9660f…`) works: the user reports the U-Boot banner and the prompt rendered white-on-black on the krane panel, via the standard video uclass + vidconsole on the depthcharge scanout. The full boot chain to a working framebuffer console is: depthcharge → payload (arm64 Image header + wrapper v3 @0x40 + U-Boot @0x1000, runtime _start 0x40001000) → wrapper (revive display, 3 blinks, 5 s hold) → U-Boot start.S (4K check ✓, PIE fixup, delta -0xC000000) → board_init_f → relocation → board_r → console_init_r → krane.env stdout=serial,vidconsole → iomux → stdio_probe_device → mt8183_scanout probe (revive OVL + backlight, LBIO parse, fallback OVL_L0_ADDR=0xFD536000) → video_clear → vidconsole0 → banner. Key fixes that made it work (final state): 1. TEXT_BASE=0x4C001000 (8-aligned ⇒ no linker fill ⇒ _start == __image_copy_start == _TEXT_BASE; PIE delta 4K-aligned). 2. Payload layout: U-Boot file at image offset 0x1000 ⇒ runtime _start 0x40001000 (both 4K constraints). 3. ENV_IS_NOWHERE + krane.env: stdout=serial,vidconsole (the video device only probes via the iomux lazy path). 4. get_page_table_size() board override (0x40000) — dynamic mmu_map_region calls (fb + coreboot table) exhausted the default budget ⇒ panic (reset without PANIC_HANG, hang with it). Still WIP/temporary in the u-boot tree (to revert before upstreaming): - all diag band/text instrumentation (mt8183_scanout.c krane_*, video-uclass.c, board_f.c, main.c, interrupts_64.c hook), - CONFIG_PANIC_HANG=y, CONFIG_BOARD_LATE_INIT=y, - CONFIG_WATCHDOG_AUTOSTART=n (decide: re-enable for production). Next: milestone 4 — distro_bootcmd booting pmOS from USB (sda), then cleanup/upstreaming pass (single-purpose commits, MAINTAINERS, checkpatch, revert diagnostics). ## Round 33 — USB stack enabled (xHCI + T-PHY + keyboard); payload `35345ec7…` Milestone 4 step 1: make the pogo keyboard (USB HID behind the GL610 hub on `usb_host`) work at the prompt. Krane defconfig had been forked from pumpkin minus all USB/PHY configs. ### Source survey (all mainline drivers, no new driver needed) - U-Boot `drivers/usb/host/xhci-mtk.c` binds `mediatek,mtk-xhci` (matches the `usb@11200000` child of `ssusb@11201000`) and expects reg-names `mac` + `ippc` and a `phys` list ON THE XHCI NODE. The upstream mt8183.dtsi models the block for the Linux mtu3 driver: `ippc` (0x11203e00) and the `phys` live on the PARENT mtu3 node, the xhci child has only `mac`. U-Boot has no mtu3 driver. → new `arch/arm/dts/mt8183-kukui-krane-sku176-u-boot.dtsi` (auto-included at the END of the upstream dts by scripts/Makefile.lib `dtsi_include_list`, so `&label` refs work) adds `ippc` to `reg`/`reg-names` and `phys = <&u2port0 PHY_TYPE_USB2>, <&u3port0 PHY_TYPE_USB3>` to `usb_host`. Not a DT fork; upstream file untouched. Verified in the compiled dt.dtb. - `drivers/phy/phy-mtk-tphy.c` binds `mediatek,generic-tphy-v2` (mt8183 u3phy node is already status-okay with both ports). - Regulators: kukui DT gives `vusb33-supply = <&mt6358_vusb_reg>`, but U-Boot has no MT6358 PMIC/regulator driver. Safe: on missing regulator, `device_get_supply_regulator` fails (debug only) and `regulator_set_enable(NULL)` returns -ENOSYS, which `xhci_mtk_ldos_enable` tolerates (vusb33 is a 3.3 V LDO the firmware already keeps on; no VBUS regulator exists in the DT for usb_host). No PMIC work needed. - MMU: MTK armv8-mem-map pre-maps 0x0–0x20000000 as device; xhci (0x11200000), ippc (0x11203e00) and phy (0x11f40000) need NO dynamic mappings — the R31 get_page_table_size headroom is untouched. - Enumeration→keyboard path: `U_BOOT_USB_DEVICE(usb_kbd, kbd_id_table)` in common/usb_kbd.c matches HID boot-protocol keyboards during `usb_scan_device`; the bound UCLASS_KEYBOARD probe registers stdio "usbkbd" and re-runs `iomux_doenv(stdin)` when the env `stdin` contains the name. - Timing: no `initr_usb` exists in board_r.c anymore — USB is only enumerated on `usb start`. CONFIG_PREBOOT has a built-in default of "usb start" when USB_KEYBOARD=y (boot/Kconfig), so `CONFIG_USE_PREBOOT=y` suffices: preboot runs at main_loop entry (after console_init_r, so probe errors print to the panel/vidconsole; keyboard not yet in the stdin mux at console_init_r is fine — iomux_err falls back to serial, then usb_kbd probe re-muxes stdin). SYS_CONSOLE_ENV_OVERWRITE defaults n, so krane.env's `stdin=serial,usbkbd` survives until the re-mux. ### Changes (u-boot `krane` branch) - configs/mt8183_kukui_krane_defconfig: USE_PREBOOT, CMD_USB, USB, DM_USB, USB_XHCI_HCD, USB_XHCI_MTK, USB_KEYBOARD, USB_STORAGE (for the sda distro boot), PHY, PHY_MTK_TPHY. Defconfig re-run done. - arch/arm/dts/mt8183-kukui-krane-sku176-u-boot.dtsi: new overlay (ippc + phys onto usb_host). - board/mediatek/mt8183/krane.env: stdin=serial,usbkbd. - Build OK; `_start` still 0x4C001000 == __image_copy_start; dtb verified (mac+ippc+phys on usb@11200000). Payload `35345ec7…` flashed, cmp OK, vbutil_kernel body verification succeeded. ### Decision tree for the reboot | observation | meaning | |---|---| | banner → `usb start` scanning lines → prompt; typing works | WORKING | | banner → "Scanning bus … failed, error N" or xhci/phy error text, then prompt | enumeration failed at stage N (transcribe the error line) | | banner → prompt, no usb lines at all | preboot never ran | | reset to menu <1 s after banner | sync abort in USB probe (PANIC_HANG should freeze instead — check it is still set) | | prompt but keys do nothing | keyboard not enumerated (pogo not seated?) or stdin mux — re-seat keyboard and power-cycle | Awaiting user reboot report. ## Round 34 — [ROOT CAUSE] mtu3 node never bound; "No USB controllers found" Round 33 report: "no usb controller detected" — matches usb-uclass.c:467 `No USB controllers found`, printed when `controllers_initialized == 0`, i.e. NO UCLASS_USB bus device was even probed (a probed-but-failed controller would have printed "Bus usb@11200000: probe failed, error N"). So the xHCI node was never BOUND. Not a driver/pin problem: xhci-mtk.o was linked, the dtb had mac+ippc+phys, and pinctrl is irrelevant to the controller. ### Root cause (found in source, drivers/core/root.c) `dm_scan_fdt_node()` walks ONLY the immediate subnodes of a bound node. Recursion into a node's children happens solely when some driver binds that node (bus drivers recurse via dm_scan_fdt_dev, e.g. the generic simple-bus driver's uclass post_bind, drivers/core/simple-bus.c). `usb@11200000` is a child of `ssusb@11201000` ("mediatek,mtu3"), which has NO U-Boot driver ⇒ its subtree was never scanned ⇒ usb_host never bound ⇒ zero UCLASS_USB devices. MMC/video bind because they are direct children of /soc (simple-bus). ### Fix (u-boot `75fe5bc9`, payload `9c55f520…`) The R33 overlay now also sets `compatible = "mediatek,mt8183-mtu3", "mediatek,mtu3", "simple-mfd"` on ssusb: the generic simple-bus driver binds the node (CONFIG_SIMPLE_BUS=y) and its post_bind scans the xhci child. No MMIO touched by that driver (empty `ranges` → plat read skipped harmlessly). Build OK, `_start` still 0x4C001000, dtb verified (simple-mfd present). Flashed `9c55f520…`, cmp OK, vbutil body verification succeeded. ### Decision tree for the reboot | observation | meaning | |---|---| | banner → `Bus usb@11200000: ...` lines → `N USB Device(s) found` → prompt; typing works | WORKING | | banner → `Bus usb@11200000: probe failed, error -N` | xhci/phy/clk probe failed at stage N — transcribe | | banner → `Scanning bus ... failed, error N` | root-hub enumeration failed — transcribe | | `Bus usb@11200000: No USB Device found` then prompt | controller OK, nothing on the bus — re-seat the keyboard on the pogo pins, power-cycle | | `No USB controllers found` again | binding still failing — next step: krane_fb_log in lists_bind_fdt path | Awaiting user reboot report. ## Round 35 — Round 34 VERIFIED (keyboard works); console rotated to landscape User confirmed: USB scans properly and the pogo keyboard works at the prompt — Milestone 4 step 1 done (payload `9c55f520…` verified live). Milestone 3+4a: banner + prompt + keyboard input on the panel. ### Rotation request: portrait console → landscape Panel is the BOE TV101WUM-NL6, natively 1200×1920 portrait. Upstream DT marks the panel node `rotation = <270>` (mounted rotated in the chassis); pmOS/ChromeOS force landscape with the keyboard attached. Mainline already supports this without touching the scanout path: `CONFIG_CONSOLE_ROTATION` builds the rotated text drivers vidconsole1/2/3 (drivers/video/console_rotate.c); when the video driver sets `priv->rot`, video-uclass binds `vidconsole` and the rotated console swaps geometry itself (console_core.c: rot%2 ⇒ cols = ysize/8 = 240, rows = xsize/16 = 75). The first vidconsole's stdio name is still "vidconsole", so krane.env needs no change. ### Change (u-boot `69787732`, payload `4fcd863d…`) - mt8183_scanout probe: `uc_priv->rot = 1` (90° clockwise) with a comment explaining the 270° panel mount. Guess between rot=1 and rot=3 (both landscape-upright; they differ by which side the keyboard hinge ends up on): first try rot=1, flip to 3 if the user reports upside-down text. - defconfig: CONFIG_CONSOLE_ROTATION=y (CONSOLE_NORMAL stays; the uclass picks the driver from rot). - fb geometry, LBIO parse, revive path untouched. Build OK, `_start` still 0x4C001000. Flashed `4fcd863d…`, cmp OK, vbutil body verification succeeded. ### Decision tree for the reboot | observation | meaning | |---|---| | banner + prompt readable in landscape with keyboard; keys work | DONE | | landscape but text upside down | switch rot=1 → rot=3 in mt8183_scanout.c | | still portrait | vidconsole1 not bound — check CONSOLE_ROTATION made it into the build | Awaiting user reboot report. ## Round 36 — [ROOT CAUSE] R35 defconfig edit dropped CONFIG_USB; rot 1→3 Round 35 report: landscape text upside down; USB/keyboard completely dead — not even "No USB controllers found"; banner position unchanged. ### USB regression: my R35 defconfig edit overwrote CONFIG_USB=y The R35 `PUT 77.=77` used a line number from a view where lines 77-80 were elided, assuming 77 was CONFIG_USB=y. It actually was (R33 had placed the USB block at 77-82), so the edit replaced CONFIG_USB=y with a duplicate CONFIG_VIDEO/CONFIG_VIDEO_MT8183_SCANOUT pair. Result: `menuconfig USB` unset after the defconfig re-run → no UCLASS_USB, no `usb` command (CMD_USB depends on USB_HOST→USB), no preboot default (PREBOOT="usb start" defaults only with USB_KEYBOARD). USE_PREBOOT=y survived (explicit line), so preboot ran "usb start" → unknown-command error, easily missed. "Not even No USB controllers found" is the tell: that message lives inside usb_init(), which was no longer built. The banner stayed put because rot=1 vs rot=3 both start at a corner; the console HAD rotated (upside-down landscape). Lesson: after ANY defconfig edit + re-run, grep the FULL set of load-bearing options (USB/PHY/CONSOLE/PREBOOT), not just the new one. ### Fix (u-boot `055563bd`, payload `4b3ce2a2…`) - defconfig: restore CONFIG_USB=y, drop the duplicated VIDEO lines. .config verified for the complete set: USB, DM_USB, USB_XHCI_HCD, USB_XHCI_MTK, USB_KEYBOARD, USB_STORAGE, CMD_USB, USE_PREBOOT, PREBOOT="usb start", PHY_MTK_TPHY, CONSOLE_ROTATION. - mt8183_scanout.c: rot=1 → rot=3 (270° CW) per the upside-down report; comment updated. - Build OK, `_start` 0x4C001000, vidconsole3 symbols linked. Flashed `4b3ce2a2…`, cmp OK, vbutil body verification succeeded. ### Decision tree for the reboot | observation | meaning | |---|---| | landscape text upright (keyboard hinge side correct), `Bus usb@11200000: N USB Device(s) found`, keys work | DONE — rotation settled | | landscape text STILL upside down | rot semantics flipped vs assumption — try rot=1 again (would contradict R35, re-check) | | portrait again | vidconsole3 not bound — transcribe the banner line | | USB lines present but no keys | keyboard enumeration issue — transcribe the `Bus ...` line | Awaiting user reboot report. ## Round 37 — Round 36 VERIFIED (landscape + keyboard work); stub-free payload User confirmed Round 36 fully works (landscape upright, keyboard functional). User took over the config: `.config` hand-tuned with VIDEO_FONT_16X32 (bigger console font), CONFIG_CHROMEOS=y, VIDEO_ANSI off — the defconfig re-run must therefore NOT be executed blindly anymore (documented in U-BOOT.md). ### Stub removal (payload `cc014efa…`) The entry shim kept only the load-bearing part: OVL_EN/OVL0_2L_EN=1, backlight GPIOs 43/176 on, patched branch to U-Boot. Removed the diagnostic phase (magenta fill, 3-blink train, 5 s hold, delay subroutine). The shim itself cannot be removed: depthcharge jumps to payload+0x40 and U-Boot's PIE fixup needs runtime _start 4K-aligned so U-Boot stays at image offset 0x1000. The build script's single `b .` search-and-patch still applies (the patched branch is now the only instruction after the revival writes). ### Verification U-Boot built with the user's .config untouched; `_start` still 0x4C001000 == __image_copy_start; wrapper now 64 bytes (0x40..0x1000), runtime _start 0x40001000. Payload `cc014efa…` flashed, cmp OK, vbutil body verification succeeded. ### Handoff New `U-BOOT.md` in this repo: boot chain, payload layout constants, build/flash/verify command sequences, recovery paths, U-Boot tree state (kept features vs WIP/temporary), rotation and keyboard notes. The user owns the U-Boot config and further development. ## Round 38 — real display pipeline: DSI host + MIPI TX PHY + BOE panel drivers Goal: replace the firmware-handoff revival with a real cold bring-up of the MT8183 display pipeline (MMSYS → OVL0 → OVL0_2L → RDMA0 → COLOR0 → CCORR0 → AAL0 → GAMMA0 → DITHER0 → DSI0 → panel), upstreamable, on krane-updates (commits on top of 1ae771d9f90). ### Sources ported (numbered findings) 1. **MMSYS clock gates** (`clk-mt8183.c`): the in-tree clock driver had NO mmsys provider (grep CLK_MM/mmsys empty). Added CG_CON0 (0x100) / CG_CON1 (0x110) gate groups with set/clr at +4/+8 and the full CLK_MM_* gate list, ported from Linux `drivers/clk/mediatek/ clk-mt8183-mm.c` (v6.6). Legacy vs upstream binding headers number CLK_MM_* identically (OVL0=19, DSI0_MM=31…), so the C-side gate ids match the DTB cells. Parents: mm_sel → legacy CLK_TOP_MUX_MM(85), dpi0_sel → CLK_TOP_MUX_DPI0(110), f26m → CLK_TOP_F26M_CK_D2(4). 2. **GPIO**: no MT8183 pinctrl/gpio driver in-tree (pinctrl-mtk-common has no mt8183 table). Wrote a minimal `drivers/gpio/mt8183_gpio.c` (dir/dout/din only, pinmux left to firmware) from the device-era depthcharge `src/drivers/gpio/mt8183.h` GpioRegs layout: dir[6], dout[6], din[6] as GpioValRegs (val@0, set@4, rst@8, 16 B/group), blocks at 0x000/0x100/0x200. Pin 43 = DISP_PWM, 45 = LCM_RST, 66/166/36 = the three panel rail enables, 176 = PERIPHERAL_EN13. The set/rst semantics for pins 43/176 were already proven on device (Round 4). 3. **MIPI TX PHY** (`drivers/phy/phy-mtk-mipi-tx.c`): UCLASS_PHY, PLL programming + analog lane bring-up ported from Linux `drivers/phy/mediatek/phy-mtk-mipi-dsi-mt8183.c`: txdiv/txdiv0 by rate (956.55 Mbps → txdiv0=2, VCO≈3.83 GHz), SDM_PWR_ON → ISO_EN clear → pcw = rate*txdiv<<24/26MHz → POSDIV → PLL_EN; lane_con BG_CORE_EN/BG_LPF_EN sequence; lanes switched off until the host enables them; CK_CKMODE_EN set. Data rate passed via generic_phy_set_mode(PHY_MODE_MIPI_DPHY, bps). The Linux efuse lane calibration is NOT ported — coreboot 4.14 runs uncalibrated lanes on this device (its mtk_mipi_dphy.c programs fixed values), so calibration is an optional refinement. drive-strength-microamp defaults to 4600 uA (Linux default). 4. **DSI host** (`drivers/video/mtk_dsi.c`): UCLASS_DSI_HOST. Register map from Linux `mtk_dsi.c` = coreboot `dsi_common.h` (identical offsets). D-PHY timing formulas ported from both (same math). Video timing (VSA/VBP/VFP/VACT, HSA/HBP/HFP word counts, PSCTRL custom header 0xb<<26, SIZE_CON, D-PHY turnaround taken out of HFP/HBP) ported from coreboot `dsi.c` — the code proven on this device. Command FIFO programming (short/long packets, BTA for reads, HSTX bit for non-LPM) from Linux mtk_dsi_cmdq(). Data rate = pixelclock*bpp/lanes (no mipi ratio; coreboot uses 100/100, mainline Linux dropped the ratio entirely). Flow: clocks (mmsys gates via clk uclass) → phy set_mode/init/power_on → DSI reset (FORCE_COMMIT USE_MMSYS|ALWAYS + CON_CTRL pulse) → phy timing → rxtx → 1 ms → DPHY reset pulse → HS clk off → video timing → HS clk on; [panel init commands in command mode]; enable() → video mode + start. NOTE: coreboot never sets DSI_EN (CON_CTRL bit 1) and works on this hardware; followed coreboot. 5. **Panel** (`drivers/video/panel_boe_tv101wum.c`): UCLASS_PANEL for "boe,tv101wum-nl6". Timing = Linux boe_tv101wum_nl6_default_mode (159.425 MHz, 100/40/24 / 10/14/4). Init command stream ported VERBATIM from coreboot 4.14 `panel_params/panel-BOE_TV101WUM_NL6.c` (packed {cmd,len,data…} stream, ends with 150 ms delay; NO explicit sleep-out/display-on — the panel comes up in video mode, as proven by every coreboot boot on this device). Power order from Linux boe_panel_prepare(): avdd/avee/pp1800 (fixed regulators, GPIO enables now real through the new GPIO driver), 10 ms, reset pulse high 1.5 ms / low 8 ms, init DCS, then backlight phandle (pwm-backlight) if compiled in. On this board the DCS stream is the coreboot one, so no 0x11/0x29 are sent. 6. **Display driver** (`drivers/video/mt8183_display.c`): UCLASS_VIDEO on the ovl0 node. Stage machine [dsi] phase prints: clocks (CG_CON0 ALL + CG_CON1 DSI0 clears + SMI_LARB0 +0x380 = 0) → panel (uclass probe) → dsi-init → dsi-enable → pipeline (MMSYS routing/mutex, OVL ROI/bgclr, RDMA0 size+FIFO 5 KiB, COLOR0 bypass, PQ relay, OVL L0 con/src_size/pitch/addr, engines on, backlight GPIOs). All routing bits/mutex/registers from coreboot ddp.h/ddp_common.h. **Naming trap**: coreboot's `DISP_OVL1_BASE` (0x14009000) is the upstream DT's `ovl_2l0` (OVL0_2L) — OVL0_2L is the second engine of the main path, which is why coreboot programs ROI on "disp_ovl[0]" and "[1]". Framebuffer at 0xFC000000 (above the 2 GiB DTB DRAM window; same region class as the firmware scanout at 0xFD536000), FDT memreserve + LMB (commit 1ae771d9f90 pattern). Fallback: any failing stage → mt8183_disp_setup_handoff() (the old revival path, now shared code in mt8183_disp.c) with a log line naming the stage. 7. **Kconfig**: choice VIDEO_MT8183_SCANOUT (old behavior) vs VIDEO_MT8183_DISPLAY (default; selects VIDEO_MTK_DSI, PHY_MTK_MIPI_TX, PANEL_BOE_TV101WUM; needs VIDEO_MIPI_DSI, PANEL, PHY). Defconfig adds MT8183_GPIO, POWER, DM_REGULATOR, DM_REGULATOR_FIXED (POWER was explicitly off in the krane defconfig; without it the fixed regulators cannot bind). ### Known gaps / decisions - SCPSYS display power domain: no MT8183 power-domain driver in-tree (mtk-power-domain.c has no mt8183 compatible). The bring-up relies on the display MTCMOS being on (true on every path that reaches U-Boot here). Documented in the driver. - Backlight is still the two GPIOs (DISP_PWM 43 + EN_LCD_BL 176) driven as board glue, not the pwm-backlight node: driving a real PWM on pin 43 would need the MT8183 pinctrl mux (no pinctrl driver in-tree), and the backlight node's power-supply chain (reg_vsys ← mt6358) has no PMIC driver. The pwm-backlight phandle is wired and used when BACKLIGHT_PWM is available. - The panel node in the upstream DT has no pp3300-supply; Linux enables a dummy there. Only avdd/avee/pp1800 are handled. - OVL layer input format = coreboot's RGBA8888 value for the 32-bit XRGB surface (proven on device); naming is MTK-internal. ### Payload `krane-uboot-payload.bin` sha256 `d29192c06266663f6b2bb2fa683a8acdb180a9d9049358d1c7fb6f17b28bb39c` (the hash differs between rebuilds — U-Boot embeds a build timestamp; verify via vbutil instead), vbutil body verification succeeded, `_start == 0x4C001000 == __image_copy_start` verified. Flash recipe unchanged (U-BOOT.md). Serial now shows `[dsi] phase 0/9/F` lines describing which path ran. ## Round 39 — cold bring-up: stream dies at DSI re-init and never returns User observations, three flashes: - Flash A (initial): sub-ms white band top (portrait), then black lit. - Flash B (reset polarity fixed: pulse ends released, of_to_plat releases reset at panel probe): sub-ms wide dark-gray artifact while backlight ramps, then black, backlight on, NO reset (PANIC_HANG=y works — no abort → no magenta). - Flash C (PHY analog → coreboot LANE_CON 0x3fff0180/0x00c0, timing → coreboot HSA24/HBP40/VSA4/VBP14): "blinking band then black". Decoded so far: - The brief artifact = the firmware scanout still running while we paint/mid-fill; the stream then dies for good. - Black + backlight + no magenta = U-Boot console runs blind: bring-up "succeeds", banner drawn into the new 0xFC000000 fb, but the DSI link never re-transmits after our re-init. The failure is inside the DSI/PHY re-init itself (link dead), not an abort. - The DSI re-init kills the firmware stream the moment we stop/start the host (mtk_dsi_reset → command mode → video restart), so after that point ALL panel-side diagnostics are invisible: our bands paint into the old fb, which nothing scans anymore. Instrumentation blind spot: any post-first-DSI-touch failure looks like "black, lit". Audit findings during Round 39 (both fixed in flash C): 1. My PHY used Linux-style analog init (per-lane RTCODE + HSTX LDO ref). Linux's per-lane RTCODE regs are written from efuse calibration data; uncalibrated Linux path != coreboot path. Replaced with coreboot's proven LANE_CON sequence (embeds bandgap + lane impedance defaults). drive-strength property dropped. 2. Sync/back-porch split: Linux HSA40/HBP24/VSA14/VBP4 vs coreboot HSA24/HBP40/VSA4/VBP14 (same totals). Sync-pulse video mode is sensitive to this split; aligned to coreboot. Open questions for flash D (not yet ruled out): - mm_sel (TOP mux 0x40[10:8]) must be set to a source ≥ 956.55 Mbps × lanes/4 ... actually DSI0_IF digital clock comes from mm_sel; the DT "hs" clock is mipi_tx0 PLL (a clock OUTPUT of the PHY). We never program the mm_sel mux parent — we rely on firmware's setting. coreboot never touches muxes either (relies on defaults), so probably fine. - CG_CON1 bit 7 (DISP_26M / CLK_MM_26M): coreboot does NOT clear it (CG_CON1_DISP_DSI0 = bits 0|1 only); we match. - MIPID0_26M: an apmixedsys 26M gate feed consumed ONLY by mipi_tx0. The U-Boot clk driver has NO MIPID0 26M gate (apmixed_plls only). On this firmware generation it is on at handoff. NOT a suspect for the dead link (panel/DSI stays alive until WE touch it). ## Round 40 — cold bring-up WORKING, diagnostics removed, series cleaned Final boot log on device: "[dsi] phase 9: full bring-up done", U-Boot console on panel (landscape, rot=3 from DT), kernel boots after bootefi bootmgr with logs visible. Serial console via Suzy-Q works. Root causes found this round (in order): 1. **Panel driver NULL deref (the big one)**: boe_panel_send_init_ sequence() reads plat->device, but nothing ever set mplat->device. EVERY previous build aborted (PANIC_HANG) at the panel init stage, right after the reset pulse. Fixed: mt8183_display.c publishes mplat->device = &priv->device in STAGE_PANEL. 2. **CONFIG_BAUDRATE=921600**: payload reprogrammed the console to 921600 (8x) while BootROM/terminal run 115200. minicom -b does not reliably switch speeds; stty/picocom do. Fixed to 115200. 3. **mtk_serial HS0 sample regs**: _mtk_serial_setbrg wrote sample_count=0/sample_point=0xffffffff in low speed mode; coreboot leaves them untouched. Fixed upstreamable (serial: mtk commit). 4. **bpix line eaten by diag edits**: a temporary-diag edit removed uc_priv->bpix = VIDEO_BPP32 from mt8183_display_bringup(); with bpix=0 the console glyph path returns -ENOSYS ("Error: 1 bit/pixel mode, but BMP has 256"), no text renders, video_clear mis-fills. Restored. 5. DSI re-init kills the firmware scanout mid-boot: all panel-side diagnostics after the engine reset are invisible. Workaround during bring-up: minimal-touch DSI path (no engine/DPHY resets, no PLL reprogram; firmware state + panel init + video restart). The final cold path resets everything and works (panel reset + full init). Diagnostic techniques that worked: color bands into the live firmware scanout (visible only until the DSI reset), backlight blink post-codes (panel-independent), Suzy-Q serial (decisive). Final series on krane-updates (base 1ae771d9f90, checkpatch 0 errors): - fab1110ff35 clk: mt8183 MMSYS display gates - 7de03c4fa39 gpio: mt8183 GPIO driver - 58d57350b43 phy: MT8183 MIPI TX D-PHY - 0c6a4de7ce2 video: MT8183 MIPI DSI host - c91f8f93d5a video: BOE TV101WUM-NL6 panel - eaab63c84cb video: mt8183 display pipeline bring-up - 36c0a9919eb krane: select full display pipeline bring-up - 68b992ad2c2 serial: mtk sample-registers fix - 357608ac481 arm: mediatek: krane: 115200 serial console Known issues / follow-ups: - infra_clks array order vs legacy header IDs mismatch after index 51 (pre-existing upstream): needs id_offs_map; bites CLK_INFRA_USB, AP_MSDC0, I2C4/6/7/8 gate addressing. - efi_add_memory_map rejects 0xfc000000 (above DTB DRAM window): "reserving fdt memory region failed ... -22"; -17 for payload region is benign. Matters for kernel GOP handoff quality. - Kernel (pmOS) takes over the panel via fbcon/GOP; panel node is status=disabled in the kernel DT, so no kernel DSI driver probe. - Kernel "stuck at Running /init as init process" on serial: pmOS-side init issue, not display. - MAINTAINERS warnings from checkpatch: add entries when submitting. ## Round 41 — [ROOT CAUSE] serial stall = nonexistent console=ttyMT0; fixed to ttyS0,115200 Symptom recap: Ubuntu kernel (7.0.0-30-generic, grub/U-Boot path) printed early logs on Suzy-Q serial until a fixed point mid-log-line, then silence. Looked like a hang; was a console handoff, not a hang. Root cause: `console=ttyMT0,921600` in GRUB_CMDLINE_LINUX_DEFAULT names a device that does not exist on this kernel stack. Mainline 8250_mtk (CONFIG_SERIAL_8250_MT6577) registers its ports on the universal 8250 driver, device/console name "ttyS" — NOT "ttyMT" (ttyMT0 is the MTK *vendor* driver, drivers/tty/serial/mtk-serial.c, not in mainline). Ground truth from this same machine's pmOS kernel (6.12.87-mt81, same driver, same uart node): 11002000.serial: ttyS0 at MMIO 0x11002000 ... is a ST16650V2 printk: legacy console [ttyS0] enabled Failure sequence on the Ubuntu boot: 1. earlycon=uart8250,mmio32,0x11002000 prints at firmware rate (115200) — early logs readable. 2. console_init(): vt console enables (console=tty0 matched, CON_CONSDEV); printk unregisters all boot consoles → earlycon dies, cutting output mid-line (the "stall point", deterministic). 3. The pending ttyMT0 entry is retried at every later console registration and never matches (univ8250_console.match only accepts console=uart8250,... earlycon-style entries). No serial console is ever enabled → silence for the rest of boot. Kernel keeps running on tty0 only. Doc's open question answered: `console=ttyS0` WITHOUT a baud argument is NOT firmware-rate. serial8250_console_setup (8250_port.c) defaults `int baud = 9600` when options are absent and probing is off; only the console=uart8250,mmio32,... match path probes the hardware divisor (probe_baud) to keep the firmware rate. So the fix must state 115200 explicitly. (mtk8250_set_termios handles 115200 fine: uartclk 26 MHz, quot=14, highspeed register 0.) Fix applied (on this rootfs, U-Boot/grub boot path only — the running depthcharge boot is unaffected): - /etc/default/grub: GRUB_CMDLINE_LINUX_DEFAULT changed from "console=tty0 console=ttyMT0,921600 earlycon=uart8250,mmio32,0x11002000" to "console=tty0 console=ttyS0,115200 earlycon=uart8250,mmio32,0x11002000" - sudo grub-mkconfig -o /boot/grub/grub.cfg (backup: /etc/default/grub.bak-round41). Verified regenerated grub.cfg carries console=ttyS0,115200 and the devicetree /boot/dtb line (10_linux keeps it as long as /boot/dtb exists). Boot mechanics after fix: earlycon (115200) → dies when vt enables at console_init() → ttyS0 console matched/enabled at console_initcall (generic "ttyS" name match + .setup → uart_set_options 115200 on the legacy placeholder port, harmless on arm64; Rockchip/rk3399 console=ttyS2,1500000 uses the same path) → real port registers via mtk8250_probe ~0.7 s later, console follows serial8250_ports[0] automatically; hardware stays at U-Boot's 115200. Small window (~0.7 s) between console_init and 8250_mtk probe where serial messages are lost to the placeholder port; add keep_bootcon if that gap matters (duplicates output; also useful as a diagnostic: with keep_bootcon the whole log stays on serial regardless of handoff). Verification checklist (user, needs Ctrl+U in depthcharge): 1. Boot to grub → Ubuntu with terminal at 115200 8N1. 2. Expect: early logs (earlycon), brief gap, then full kernel log through userspace at 115200; /dev/console = ttyS0 (last console= wins). 3. If silence still occurs: add keep_bootcon to the cmdline and compare — if output continues, it is purely a console handoff issue; if not, capture the last ~50 lines and triage initcalls (initcall_debug). 4. Panel check (separate bug, believed fixed): frozen U-Boot console should show no noise blocks; kernel output on tty0 via the EFI framebuffer may appear on the panel once vt/simpledrm come up — that is expected, not a regression. No U-Boot-side change needed; no payload reflash required. krane-updates untouched. ## Round 42 — stall survives console fix: real hang; debug cmdline staged Round 41 fix (ttyS0,115200) did NOT change the symptom: output still cuts at the same point (~2.3s, mid-line, right after the "evm: security.evm attributes" message). Conclusion: the ttyS0 console WAS working from ~0.8s (mtk8250 probe registers port line 0, which is the same struct the console attached to, so the console follows the real port automatically; output between 0.8s and 2.3s already went through the working console at 115200). The stall is a genuine hang, not a console handoff artifact. Cut-point analysis: "evm: security.evm attributes reinitialized" is a late_initcall (evm_init_xattrs). What runs next, in order: 1. remaining late(_sync/_rosync) initcalls, 2. "Freeing unused kernel memory", free_initmem, 3. smp_init() — secondary CPU bring-up via PSCI (BL31) — AFTER all initcalls, immediately before "Run /init", 4. "Run /init as init process". The mid-line cut is consistent with either a late-initcall hang or a hang inside smp_init/PSCI cpu_on (nbcon printk kthread can be preempted mid-line by a hard hang). U-Boot-handoff state is a candidate for both (U-Boot payload leaves devices in non-reset state; secondary CPUs were parked by BL31 either way). Debug boot staged (2026-09-02): - /etc/default/grub GRUB_CMDLINE_LINUX_DEFAULT now: "console=tty0 console=ttyS0,115200 earlycon=uart8250,mmio32,0x11002000 keep_bootcon ignore_loglevel initcall_debug maxcpus=1" (backup: /etc/default/grub.bak-round42; grub.cfg regenerated). - keep_bootcon: earlycon survives the whole boot -> any output loss after this point is hardware/hang, never handoff. - initcall_debug + ignore_loglevel: last "calling " without a matching "initcall ... returned" names the hang. - maxcpus=1: tests the PSCI/secondary-CPU bring-up path. User checklist (needs Ctrl+U, terminal 115200 8N1, CAPTURE TO FILE): 1. Boot grub -> Ubuntu. WARNING: boot is now slow and verbose (initcall trace over 115200 serial, ~30-60s extra; earlycon + ttyS0 duplicate every line — expected, same UART). 2. Capture the FULL serial log to a file (e.g. picocom -b 115200 /dev/ttyUSB0 | tee boot42.log) from before U-Boot output starts. 3. Report the last ~50 lines. Key reads: - Last "calling ..." with no "returned" -> hung initcall (name it). - Last line = "smp: Bringing up secondary CPUs ..." -> PSCI/secondary CPU hang; next test drops maxcpus=1 and we go after the U-Boot handoff / TF-A PSCI state (compare with depthcharge boot of the same kernel; MT8183 is 4xA73+4xA53, all 8 boot fine via depthcharge on the pmOS kernel with the same BL31). - Output now survives past 2.3s to login -> the single-core change fixed it; then bisect smp/hotplug vs initcall. 4. If it STILL cuts mid-line at the same spot with earlycon kept alive: hang is in whatever the last complete "calling" line names, or in free_initmem/smp_init if no initcall_debug lines trail. A hard hang with a fully drained console that cuts mid-line would point at the printing path itself dying with the machine (clock/powerdomain kill during the print) — then we check whether the system is actually alive (add a delayed "reboot" via kernel cmdline? no — check via serial ping input: type a key; if the tty echoes, userspace is alive and only output died). No U-Boot changes made. krane-updates untouched. ## Round 43 — debug boot #2 result "nothing changed"; aliveness/panic probes staged User reports debug boot (keep_bootcon ignore_loglevel initcall_debug maxcpus=1) = "nothing changed", still cut mid-line at ~2.3s. Suspicion: initcall_debug would visibly flood the log from ~0.5s — an IDENTICAL log suggests the cmdline may not have been applied. Need the "Kernel command line:" line from the user's capture (kernel prints it early under ignore_loglevel) plus the last ~100 lines. New cmdline (added this round; backup /etc/default/grub.bak-round43): ... keep_bootcon ignore_loglevel initcall_debug maxcpus=1 panic=10 softlockup_panic=1 hung_task_panic=1 Rationale: CONFIG_LOCKUP_DETECTOR / CONFIG_DETECT_HUNG_TASK are on; hung task default timeout 120s. If the kernel is alive-but-stuck, a panic + 10s reboot follows within ~2min; the reboot brings back the U-Boot banner on serial — visible aliveness proof even if the UART dies mid-boot. Nothing after many minutes = hard hang or dead UART path. (Type a key on the terminal: tty echo = userspace alive.) ramoops dead end (for now): CONFIG_PSTORE_RAM=m (module, loads too late to catch an early panic) and the Ubuntu /boot/dtb has no ramoops node; pmOS kernel's own DT reserves 1 MiB ramoops at 0xffedb000. Could add a ramoops node to /boot/dtb + load ramoops.ko from initramfs as a later panic-capture path. Candidate explanations for an identical cut across 3 cmdlines: A. cmdline never applied (verify via "Kernel command line:" in capture). B. hard hang independent of cmdline content at a fixed early point (U-Boot handoff state: xHCI/eMMC/DSI left active; or a driver probing a device in non-reset state — initcall_debug will name it). C. system alive, UART dies at a fixed point (clock/mux/pinctrl) — indistinguishable from hang on serial alone; panic probes + key-echo test split this. ## Round 44 — fb-log.txt analyzed: boot never had the new cmdline; true root cause fb-log.txt (user capture, mtime 2026-09-02 15:05) shows the OLD cmdline boot: 0 "calling" initcall_debug lines (would be thousands), no "printk: legacy console [ttyS0] enabled" at the 1.787s ttyS0 port registration, no earlycon disable message. The Kernel command line printk itself is truncated mid-line ("root=0") — the capture pipeline drops bytes (other lines spliced: "[0x410fd034]28f3628b...", "0x...bbc00000ce(s) found"). So all Round 42/43 debug params were never exercised; and the Round 41 fix has plausibly never been tested either. The log instead pins the REAL stall mechanism: - 1.787s: 8250_mtk probes, registers ttyS0. Old cmdline has console= only tty0+ttyMT0 → port is NOT a console → uart_configure_port() powers it OFF (serial_core: "power down all ports by default, except the console", uart_change_pm(UART_PM_STATE_OFF)) → 8250_mtk runtime suspend gates the UART clock. - earlycon keeps printing raw MMIO into a now-unpowered UART → output dies silently at a fixed point (~2.38s, probe+autosuspend delta), mid-line. Kernel continues fine on tty0. This explains: identical cut across all previous boots, why it starts exactly at 8250_mtk probe + delay, and why pmOS (no console= → all consoles default-enabled → ttyS0 becomes console → port stays powered) never stalls. The Round 41 fix (console=ttyS0,115200) attaches the console at probe → port stays powered → serial should survive. It just has never actually been booted. Next boot protocol (Round 45): 1. Lossless capture, no terminal in the path: stty -F /dev/ttyACMx 115200 raw -echo cat /dev/ttyACMx > boot45.log (Ctrl+C after). Verify: grep -c "calling " boot45.log (expect thousands); grep "console \[ttyS0\] enabled" (expect present right after the 11002000.serial line). 2. If the cmdline STILL doesn't apply (no "calling" lines): grub is serving a stale config — at the grub menu press "e" on Ubuntu and boot the edited entry (Ctrl-X), or move the params directly into /boot/efi/EFI/BOOT/grub.cfg. 3. If params verified and output still dies: check key-echo + panic reboot probes (Round 43) — then it is genuinely the UART path/hang, not console power-off. ## Round 45 — [ROOT CAUSE #2] standalone grub image with hardcoded cmdline; grub-install redone picocom capture (fb-log.txt, 15:23) finally delivered a CLEAN "Kernel command line:" line: it read "console=ttyMT0,921600" — the ORIGINAL cmdline, no Round 41-43 params ever reached the kernel in any boot. Root cause of the delivery failure: /boot/efi/EFI/BOOT/BOOTAA64.EFI was a grub-mkstandalone image (905 KB, built Aug 31) with a memdisk-embedded grub.cfg containing hardcoded menuentries ("linux (hd0,gpt3)/boot/ vmlinuz-7.0.0-30-generic ... console=ttyMT0,921600 earlycon=..."). It never read the ESP stub nor /boot/grub/grub.cfg — every grub-mkconfig since was a no-op. The 209-byte ESP stub existed but was dead code (standalone image prefix = (memdisk)/boot/grub). This also explains the BOOT_IMAGE=(hd0,gpt3)/... form in the kernel log (matches the embedded entry verbatim). Fix: sudo grub-install --target=arm64-efi --efi-directory=/boot/efi --boot-directory=/boot --bootloader-id=BOOT --no-nvram (grub 2.14), then cp grubaa64.efi over BOOTAA64.EFI (fallback path). New image: 2.9 MB monolithic, zero embedded cmdline occurrences, plus grub-install wrote a fresh EFI/BOOT/grub.cfg stub (search.fs_uuid e362f850 -> configfile /boot/grub/grub.cfg). Chain now: U-Boot bootmgr -> BOOTAA64.EFI -> /boot/grub/grub.cfg (ext4) -> Ubuntu entry with console=ttyS0,115200 keep_bootcon ignore_loglevel initcall_debug maxcpus=1 panic=10 softlockup_panic=1 hung_task_panic=1. Backup of the standalone image + old stub: /boot/efi/EFI/BOOT-standalone-bak45 (restore by copying back if ever needed). Round 44's serial-path analysis stands as the expected outcome: with console=ttyS0,115200 the port is a console at 8250_mtk probe time, so uart_configure_port keeps it powered and the "power down non-console ports" path that killed earlycon at ~2.4s never runs. Round 46 test protocol (user, picocom OK — capture was lossless): 1. Ctrl+U -> U-Boot -> grub -> Ubuntu. 2. First marker: "Kernel command line:" line must contain console=ttyS0, 115200 keep_bootcon initcall_debug. 3. Thousands of "calling ..." lines; "printk: legacy console [ttyS0] enabled" right after the 11002000.serial ttyS0 line. 4. Boot will be slow/verbose (115200 flood, earlycon+ttyS0 duplicate lines). If it reaches login: serial console fixed; then trim cmdline back (drop debug params) and re-verify a clean boot. 5. If output still dies: key-echo test + wait for panic-reboot probes (~2 min, hung_task 120s + panic=10). ## Round 46 — serial stall FIXED (kernel reaches initrd); initrd missing mmc devices Round 45 grub-install fixed the delivery: kernel boots past the old 2.4s stall all the way to the dracut initrd. Serial stall ROOT CAUSE confirmed as the console power-off path (Round 44): ttyS0 console attached at 8250_mtk probe keeps the port powered. Cmdline trimmed per user request — now: console=tty0 console=ttyS0,115200 earlycon=uart8250,mmio32,0x11002000 (backup /etc/default/grub.bak-round46). earlycon kept temporarily while the initrd issue is debugged; drop it at the end. NEW ISSUE: dracut initrd cannot find root by UUID; /dev has no mmc*. Static analysis says the initrd is complete: - usr/lib/modules/.../drivers/mmc/host/mtk-sd.ko.zst PRESENT (note: file is mtk-sd.ko, module name mtk_sd — earlier grep with "mtk_sd" missed it) - mmc_block, cqhci, mmc_hsq present; modules.alias has of:N*T*Cmediatek,mt8183-mmc -> mtk_sd; modules.dep lists deps - PMIC chain present: mtk-pmic-wrap, mt6397 (MFD), mt6358-regulator, mt6397-regulator, rtc-mt6397 - pinctrl-mt8183 / clk-mt8183 / infracfg are built-in (=y) - vermagic matches kernel image (same Jul 31 build), module signed - /boot/dtb (custom krane-fb-stub DTB) is byte-identical in structure to upstream /boot/efi/mt8183-kukui-krane-sku176.dtb (full-file diff EMPTY): mmc0 @11230000 okay, compatible mediatek,mt8183-mmc, clocks topckgen+infracfg phandles valid So the failure is runtime: either mtk_sd never got loaded by udev, or its probe fails/defers. Diagnostics for the dracut emergency shell: cat /proc/modules | grep -Ei 'mtk|mmc|pmic' modprobe mtk_sd && ls /dev/mmc* dmesg | grep -iE 'mtk-sd|msdc|mmc|pmic|regulator' ls /sys/bus/platform/devices | grep mmc If modprobe succeeds and /dev/mmcblk0 appears: just "exit" — dracut resumes, mounts root, boot completes; fetch dmesg/journal from the booted system afterwards to pin the root cause (probe defer vs error). Round 46 backup: /etc/default/grub.bak-round46. ## Round 47 — mt6358_regulator was the missing initrd load; native display path completed + plymouth User confirmed: modprobe mt6358_regulator in the dracut shell unblocked the initrd (regulators registered, mmc0 deferred probe resolved, root mounted). Boot then proceeded to systemd (Ubuntu 26.04.1 userspace on this rootfs) and stopped after "Starting wpa_supplicant.service" — open issue, suspected mt7663s/mt76 SDIO path (initrd dmesg showed msdc cmd52 errors on mmc1). Discriminator: press Enter on serial — login prompt = system alive, wpa-supplier-only stuck. Panel goes blank when the kernel takes over ("graphical console disappear"): NOT a reason to blacklist the display stack (rejected — native display is the goal). Real cause found: the initrd contained mediatek-drm/mtk_mmsys/mtk_mutex/DSI-phy (which reset the DSI link U-Boot left running -> panel dark) but NOT the panel/backlight/PWM modules, so nothing could re-light it. The DTB (/boot/dtb) has the full native path ENABLED: panel@0 boe,tv101wum-nl6 (avdd/avee/pp1800 fixed GPIO regulators), pwm-backlight on SoC pwm@11005000. The old "DSI/panel disabled in distro DTB" note does not apply to this DTB. Fixes applied: - /etc/dracut.conf.d/50-display.conf: add_drivers+=" panel-boe-tv101wum-nl6 pwm-mediatek pwm_bl " force_load="mt6358_regulator" (force_load because udev failed to load the already-present module at runtime in the previous initrd — root cause unknown, worked around.) - plymouth + plymouth-theme-spinner + plymouth-label installed via apt; dracut now embeds plymouthd (50plymouth) — initrd rebuilt (53 MB, 16:43). NOTE: apt's dracut trigger also runs update-initramfs, so future kernel/apt operations keep the config. - /etc/default/grub: cmdline now console=tty0 console=ttyS0,115200 earlycon=uart8250,mmio32,0x11002000 splash ("splash" only, no "quiet" — serial stays verbose for the wpa debug). grub.cfg regenerated. Expected next boot: panel lights during initrd (native DSI panel takes over from U-Boot firmware scanout — brief flicker), plymouth splash on the panel, serial stays verbose. If panel still dark: capture dmesg | grep -iE 'panel|dsi|drm|backlight' from serial login and check panel bind/defer. ## Round 48 — regulators still missing with force_load; deterministic pre-udev hook fb-log.txt (16:52 boot): plymouth-start ran, but the deferred tree was back — mmc0 (ldo_vio18), usb (ldo_vusb), gpu (buck_vgpu), i2c (vcn18/ vcamio), AND the whole MT8183 power-controller: mtk-power-controller: power-domain@2 failed to get power supply (domain-supply = MT6358 buck, coupled vproc pair) -> iommu, all larbs, ovl/rdma/dsi/mutex/aal/ccorr/color/gamma, pwm, backlight_lcd0 all defer on "supplier 10006000.syscon:power- controller not ready" So the ENTIRE deferred forest (eMMC + display + iommu + backlight) has a single root: MT6358 regulators not registering. force_load="mt6358_regulator" did NOT load it (no evidence of any generated load mechanism in the initrd). Fix (deterministic): dracut pre-udev hook. Gotchas found: - dracut 110-11 does NOT copy host /usr/lib/dracut/hooks into the image. - Runtime hookdir = /var/lib/dracut/hooks (dracut-lib.sh:367); stage dir is pre-udev (dash), per source_hook pre-udev in usr/bin/dracut-pre-udev. - Placed /var/lib/dracut/hooks/pre-udev/01-mtk-pmic.sh (host) with: modprobe mtk_pmic_wrap; modprobe mt6397; modprobe mt6358_regulator and /etc/dracut.conf.d/50-display.conf: install_items+=" " (install_items preserves the path; survives apt-triggered update-initramfs). Verified in rebuilt initrd: var/lib/dracut/hooks/pre-udev/01-mtk-pmic.sh present, executable. Manual escape if a boot still lands in dracut shell: modprobe mtk_pmic_wrap mt6397 mt6358_regulator, then exit. Expected: regulators register ~4s into initrd; mmc0, power-controller, display/iommu/backlight all unblock; panel lights; plymouth splash. Still open: wpa_supplicant hang (previous boot; suspect mt7663s/mt76 SDIO after msdc cmd52 errors on mmc1). Check with Enter-on-serial for login prompt, then journalctl. ## Round 49 — [ROOT CAUSE] soft lockup = live scanout faulting through re-enabled M4U; U-Boot quiesce committed fb-log.txt (17:13): eMMC fixed (regulators registered via pre-udev hook), boot went further than ever: initrd pivot, real-root systemd, wpa_supplicant [OK] (previous hang gone). Two issues surfaced: 1. UBSAN shift-out-of-bounds mt6358-regulator.c:384 — ffs(0)-1 = -1 in mt6358_get_buck_voltage_sel. Root: mt6358_volt_fixed_ops routes get_voltage_sel through the buck helper, but the fixed LDOs (vio18, vrf12, ...) never initialize da_vsel_reg/da_vsel_mask (v7.0 mainline has the same code). Non-fatal: read-only path, selector 0 == nominal for these LDOs. Upstreamable fix: use regulator_get_voltage_sel_regmap for fixed ops (they have valid vsel_reg/vsel_mask). NOT the lockup cause. 2. mtk-iommu fault storm: reads at iova 0xbe000xxx (the U-Boot framebuffer at 0xBE000000!) from master larb0/port0 — the display engine kept scanning out the U-Boot console while the kernel's M4U enabled translation; the region has no IOMMU mapping. Interrupt storm starved timer handling: CPU#1 rcu_exp_gp_kthr soft lockups (26/52/89s), rcu_preempt GP kthread starved on CPU4 (first A73, "timer wakeup didn't happen"). Boot wedged around 40s, right at NetworkManager/ModemManager startup. Fix (commit d3d3502de0a on krane-new-panel-driver, checkpatch 0/0): "video: mt8183: quiesce the display pipeline at ExitBootServices". - drivers/video/mt8183_display.c: board_quiesce_devices() — stops the DSI video stream + powers the D-PHY down (mtk_dsi_disable), stops the pipeline engines (OVL0/OVL0_2L/RDMA0/COLOR/PQ blocks/mutex), turns the backlight off and gates the MMSYS display clock domains. The kernel display driver does a full cold bring-up (Round 40), so nothing of the handoff state needs preserving. - drivers/video/mt8183_disp.c: mt8183_disp_disable_backlight() (inverse of enable; DOUT clear registers at +8 in the GPIO dout block). - drivers/video/mt8183_disp.h: DOUT_CLEAR macro + prototypes. U-Boot rebuilt; payload rebuilt and vbutil_kernel-verified: krane-uboot-payload.bin sha256 771a0fc6dfda12af9d6779b7637787dc5177b6db3e3f7cba7443be9911a09bb5. PENDING: dd to /dev/mmcblk0p1 (user confirmation per protocol), then boot via Ctrl+U. Expected next boot: panel goes dark after the kernel's EFI stub calls ExitBootServices (U-Boot hands over with the pipeline quiesced — no more frozen console, no fault storm), kernel brings the panel up natively (~5-10s), plymouth splash, full boot. The UBSAN warning remains (harmless; module fix is a follow-up). Round 49 addendum: payload flashed to /dev/mmcblk0p1 (dd verified with cmp against the source file, 860160 bytes). Ready for Ctrl+U boot test. ## Round 50 — [ROOT CAUSE] U-Boot quiesce works; new oops = mtk_smi larb runtime-resume before iommu bind; patched modules installed fb-log.txt (17:54): the IOMMU fault storm + RCU soft lockup are GONE (the U-Boot quiesce commit d3d3502de0a works). Boot got to coldplug, then: Internal error: Oops 0000000096000004, FAR=0x0, pc mtk_smi_larb_config_port_gen2_general+0xf0 [mtk_smi], lr mtk_smi_larb_resume+0xb8, via pm_runtime_get_suppliers from mtk_drm_init (mediatek_drm module load, udev-worker PID 266). Code bytes match mainline v7.0 drivers/memory/mtk-smi.c exactly: `ldr x1,[x28,#144]` (= larb->mmu, offset 144) then `ldr x1,[x1]` at +0xf0 -> NULL because larb->mmu is only set by mtk_smi_larb_bind(), the IOMMU component bind, which ran at 8.168s — AFTER the oops at 8.155s. mediatek_drm's probe runtime-resumes the larb through the device link/genpd before the IOMMU binds it. Unfixed in upstream master (checked mtk-smi.c master == v7.0). Also confirmed this boot: mt6358 UBSAN fires from mt6358_regulator_probe->regulator_register->machine_constraints_voltage (ops->get_voltage_sel on register: mt6358_get_buck_voltage_sel derefs da_vsel_mask which MT6358_REG_FIXED never sets). Fixed LDOs DO have valid vsel_reg/vsel_mask (MT6358_*_ANA_CON0 / GENMASK(3,0)), so the correct ops is regulator_get_voltage_sel_regmap (as mt6358_volt_range_ops uses for regmap reads elsewhere). Fix: rebuilt both modules out-of-tree against the Ubuntu headers (/usr/src/linux-headers-7.0.0-30-generic, Module.symvers, MODVERSIONS OK, vermagic matches, unsigned load = taint only, MODULE_SIG not forced). Source validated against the shipped modules before patching: rebuilt unpatched mtk-smi.ko reproduces the oops Code bytes at +0xf0. - mtk-smi.ko: guard in mtk_smi_larb_resume: if (!larb->mmu) return 0; after enabling clocks (no IOMMU master attached yet -> nothing to configure). Upstreamable: "memory: mtk-smi: skip MMU port config on larb runtime-resume before the IOMMU binds". - mt6358-regulator.ko: mt6358_volt_fixed_ops.get_voltage_sel -> regulator_get_voltage_sel_regmap (line 495; vproc/vsram buck ops untouched). Installed to /lib/modules/7.0.0-30-generic/kernel/drivers/{memory/ mtk-smi.ko.zst,regulator/mt6358-regulator.ko.zst}; originals kept as *.orig-round49 (NOTE: named round49, stamped before analysis); depmod run. BTF skipped (no vmlinux) — same as many Ubuntu modules. Expected next boot: no oops; mediatek_drm probes; panel lights natively; plymouth; boot to login. ## Round 51 — false alarm: same oops; initrd ships stale module copies; initrd rebuilt fb-log.txt (next boot): IDENTICAL oops (same pc +0xf0, same Code bytes, same UBSAN from mt6358_get_buck_voltage_sel). Cause: dracut initrd contains its own module copies (usr/lib/modules/7.0.0-30-generic/...) built Jul 31 — the modules we replaced under /lib/modules never load; initrd modules are what run during coldplug (before root pivot). The "more errors" = the oops printed twice (a second udev worker retried the mediatek_drm finit_module and hit the same fault) + dracut initqueue hang, collateral of the oops killing the worker handling the mmcblk uevent chain (root node never settled). Fix: dracut -f rebuild — initrd now carries the patched mtk-smi.ko.zst (114115 bytes, mine keeps DWARF that Ubuntu strips to dbgsym; loads fine) and patched mt6358-regulator.ko.zst. Stale .orig-round49 backup files also got copied into the initrd by dracut (harmless, never loaded). Next boot expectation: no oops, no UBSAN, initqueue completes, root mounts, mediatek_drm probes, panel lights natively. ## Round 52 — [WEDGE] boot reaches real root; CPU2 kworker spin + multi-CPU timer death; evidence-led prep for next boot fb-log.txt (post-Round-51 initrd): no oops, no UBSAN, no IOMMU storm — the module fixes hold. Boot reaches systemd, NetworkManager, wpa_supplicant. Display: backlight comes back (pwm_bl) but screen stays BLACK and mediatek_drm never registers an fbdev. Then CPU#2 soft lockups (26/52/119s, kworker/2:3), RCU stalls on 4/5/7, rcu_preempt kthread (cpu3) "timer wakeup didn't happen". Start of wedge ~13.8s. Key observations: - The soft-lockup STACK DUMPS never appear on the serial console (only the header lines) — dumps are lost somewhere in printk/console path. Rely on ramoops next boot instead. - The current initrd was MISSING mediatek_drm (my Round-51 rebuild dropped it vs the Jul 31 build). So this boot loaded mediatek_drm from the real root at ~13s — exactly when the wedge started. Both full-boot wedges (49, 52) time-correlate with mediatek_drm activity; the boot where its probe oopsed early (51) never wedged the CPUs. - PSCI CPUidle EXonerated: the running pmOS kernel uses psci_idle with the SAME WFI/cpu-sleep/cluster-sleep-0 states and the same stock ATF, up 17min+ fine. Not the cause despite first suspicion. - No unbounded loops found statically in mtk_crtc/mtk_dsi/mtk-mutex/ cmdq-mailbox/cpufreq/mtk-coupler; only the DSI IRQ handler do{}while(tmp & DSI_BUSY) (unbounded, irqs-off) — but a CPU stuck there could never report its own soft lockup, so it is not the reported kworker spin. U-Boot quiesce now clears DSI INTEN/INTSTA anyway (commit c112424b983, checkpatch clean; payload sha256 7ed2de1e0ab3ff80cb6f461889d06bbf3d9d773f555308d02107dcb124cea92b, flash PENDING user confirmation). - grub2-common/grub-initrd-fallback FAILED at exactly the wedge moment (collateral; recordfail cleared via grub-editenv). Prepared for the next boot (all in place): 1. cmdline: timer_migration=off (targets the timer-migration/tick failure class matching "timer wakeup didn't happen"; nohz/timer rework landed 6.13..7.0 while pmOS runs 6.12.87 stable with the backported fixes), sysrq_always_enabled, softlockup_panic=1 panic=10 (auto-evidence: wedge -> panic -> stacks -> warm reboot). 2. ramoops via DTB: /boot/dtb-krane-ramoops.dtb adds a reserved-memory region at 0xBFF00000 (1 MiB) + ramoops node (console 512K, dmesg 128K, pmsg 128K). grub entries use it via devicetree (10_linux picks /boot/dtb-7.0.0-30-generic first). pmOS kernel has PSTORE=n and a different DTB so it ignores the region. 3. initrd rebuilt: mediatek_drm + full display stack (mtk_mutex, mtk_mmsys, mtk_smi, mtk_iommu, dsi phy, cmdq) + ramoops (force_load) + scp.img.zst restored/added. scp remoteproc should now bind at initrd coldplug instead of failing with -2. 4. grub experiment entries: "Ubuntu 7.0 EXP-B: cpuidle.off=1" and "pmOS kernel via U-Boot (wedge bisect)" (pmOS kernel + pmOS DTB + pmOS initrd from the ESP, under our U-Boot). Ladder: default entry (timer_migration=off) -> if wedged+panicked, second boot archives /sys/fs/pstore via systemd-pstore (enabled) to /var/lib/systemd/pstore; read stacks from there. If still wedging without evidence, try EXP-B, then the pmOS-under-U-Boot entry to separate bootloader state from kernel regression. Black display analysis: pipeline is quiesced at ExitBootServices (by design), simpledrm fb0 exists but nothing scans it out; mediatek_drm did not complete bind in this boot (late load + wedge). With the display stack back in the initrd and the wedge fixed, the kernel should bring the panel up natively. If the wedge turns out to be INSIDE mediatek_drm probe, the ramoops stacks will show it. Round 52 addendum (evidence path locked in): CONFIG_PSTORE_CONSOLE and PSTORE_PMSG are NOT set in the Ubuntu kernel, so ramoops only produces a dmesg-ramoops record on PANIC. That is exactly what the new cmdline gives: softlockup_panic=1 -> full ring buffer (incl. the lockup stacks that never reached the serial console) -> dmesg-ramoops -> panic=10 -> warm reboot. Each boot, systemd-pstore (enabled, runs ~12.5s, before the 13.8s wedge point) archives the previous panic to /var/lib/systemd/pstore. Read results from there (or /sys/fs/pstore on a boot that completes) after the test. ## Round 53 — ramoops region collided with U-Boot's runtime data at DRAM top; moved to 0x60000000 fb-log.txt (early crash, 1.31s): efi_call_rts oops — "Unable to handle kernel paging request at 0xbff29f30", x0=0xbff29ee0. 0xbff2xxxx is U-Boot's EFI runtime services data: U-Boot relocates to the TOP of DRAM (0xBFF00000..0xC0000000 for 2 GiB), exactly where I placed the ramoops no-map region. The no-map carve-out removed those pages from the kernel's linear map; the first EFI runtime call after boot (efi_rts_wq, rtc-efi probe) dereferenced U-Boot's runtime data and the whole runtime services path died with it. Boot never reached the wedge test. Fix: ramoops moved to 0x60000000 (mid-DRAM; clear of the 0x50000000 shared-dma-pool at 0x50000000-0x52900000, the low kernel image, and the top-of-RAM U-Boot runtime area). Both /boot/dtb-krane-ramoops.dtb and /boot/dtb-7.0.0-30-generic rebuilt. Everything else (cmdline, initrd, grub entries) unchanged. Lesson: never reserve anything at the top of DRAM on this platform — that is U-Boot's relocation + EFI runtime + variable-store area. Round 53 addendum: U-Boot payload 7ed2de1e0ab3ff80cb6f461889d06bbf3d9d773f555308d02107dcb124cea92b (quiesce + DSI INTEN/INTSTA clearing, commits d3d3502de0a + c112424b983) flashed to /dev/mmcblk0p1, cmp-verified. ## Round 54 — no-watchdog silent lock both boots; new prime suspect: mt7663s wifi fw download deadlocking mtk-sd/eMMC I/O; hung_task_panic wired Two boots (default + EXP-B cpuidle.off=1) both lock at the same point: last kernel line = sbs uevent at ~12.8/13.2s, services continue to ModemManager start, then TOTAL silence — no softlockup, no RCU stall, no panic. A silent (sleeping) deadlock, not a spin: cpuidle is exonerated, and timer_migration=off turned out to be an UNKNOWN param on 7.0 ("will be passed to user space") so it never applied anyway. New leading theory: NetworkManager brings wlan0 up right there -> mt76 mt7663s firmware download over the SDIO link that shows CRC errors from boot (msdc cmd52 host->error=0x2) -> mtk-sd driver wedges -> eMMC I/O hangs (grub2-common/grub-initrd-fallback grubenv writes on eMMC FAIL in every wedged boot!) -> system sleeps forever. Round 52's spinning kworker/2:3 = mt76 fw download busy-wait; the current silent shape = same trigger, deeper sleep. Not yet proven. Prepared: - hung_task_panic=1 added to all Ubuntu entries (CONFIG_DETECT_HUNG_TASK + HUNG_TASK_BLOCKER are on): a 120s-stuck D-state task now panics with full stacks AND the blocker name into the ring buffer -> ramoops dmesg-ramoops -> auto-reboot -> systemd-pstore archives it. - EXP-C entry: module_blacklist=mt76,mt76_sdio,mt7663s, mt7663_usb_sdio,mt76_connac_lib,mt7615_common (wifi off) + same panic params. If EXP-C boots past the wedge point, wifi/SDIO is the trigger. - pmOS bisect entry fixed: the pmOS system moved to the USB drive (sda1 kernel FIT, sda2 /boot, sda3 root); the running kernel is 6.12.87-mt81, gzipped Image decompressed and staged as /boot/vmlinuz-pmos-6.12.87 (PE/EFI stub verified) + initramfs + dtb on eMMC, entry boots it via U-Boot. - NOTE: our shell session is a chroot into eMMC p3; the real running pmOS boots from the USB drive (pmos_root_uuid=ddc5b150 = sda3). ## Round 55 — EXP-C panicked with FULL STACKS: the wedge is CPUs going dead to IPIs at coldplug settle EXP-C (wifi blacklisted) locked like the others, but this time the softlockup detector FIRED and we finally have stacks: - watchdog: CPU#3 soft lockup 26s, udev-worker (PID 257), stack: smp_call_function_many_cond <- kick_all_cpus_sync <- flush_module_icache <- load_module <- finit_module. A udev module load broadcast an IPI and got no answer for 26s. - panic path: "SMP: failed to stop secondary CPUs 0-2,5-7" — SIX of eight CPUs were already unreachable when it panicked; only CPU3 (the loader) and CPU4 responded to the stop IPI. - Timeline: last normal log 8.69s (ccifreq deferral spam ending = coldplug settling); CPUs died in the ~8.7-10.3s window; report at 36.3s. The dead CPUs never softlockup-report themselves (no watchdog ticks at all — deeper than an IRQs-off spin: no timer interrupts / IPIs reaching them). - wifi-blacklist did NOT prevent the wedge -> mt76/SDIO is NOT the trigger. The wedge family across all boots = multi-CPU death at initrd coldplug settle; the visible symptom (spin vs silent sleep) depends on which task notices first. - ramoops was broken all along (-22 "failed to locate DT /reserved-memory resource"): v7.0 of_device_alloc creates MEM resources only from `reg`; a root-level ramoops node with memory-region never gets one. FIXED: moved the node into /reserved-memory with compatible="ramoops" + reg (upstream exynos pattern), record/console/pmsg sizes inside the node. Recompiled /boot/dtb-krane-ramoops.dtb + /boot/dtb-7.0.0-30-generic. Next panic will be archived to /var/lib/systemd/pstore by systemd-pstore. - New EXP-D entry: maxcpus=1 (if it boots fully, the death is in the per-CPU idle/PSCI/PM layer, not in drivers). - Available next: pmOS-kernel-via-U-Boot bisect entry (env vs kernel split), EXP-B cpuidle.off=1 (already shown insufficient alone). Interpretation candidate for the dead-CPU signature: CPUs stopped servicing IPIs AND their own timer ticks — PSCI/ATF-level CPU state (suspend that never returns) or clock/power gated out from under running CPUs around sync_state/coldplug settle. No proof yet. ## Round 56 — pmOS-6.12-via-U-Boot bisect made actually runnable without the USB drive Constraint discovered: the USB-C port is shared between the serial cable and the pmOS USB drive — both cannot be attached at once, so the pmOS rootfs (sda3) is unavailable for the bisect boot. Workaround: the running pmOS rootfs IS reachable via /proc/1/root, so: - Copied /proc/1/root/lib/modules/6.12.87-mt81 (18 MiB) to /lib/modules/ on the eMMC Ubuntu root. In the pmOS kernel mtk-sd, mtk-smi and mediatek-drm are BUILT-IN (its initramfs has only 24 modules), so the pmOS initramfs can mount eMMC p3 with no modules; wifi (mt7663s) etc. load from the copied tree at full coldplug. - Rewrote the 'pmOS 6.12 kernel via U-Boot (wedge bisect)' grub entry: /vmlinuz-pmos-6.12.87 (decompressed Image, PE/EFI stub verified) + /initramfs-pmos-6.12.87 + /dtb-pmos-krane.dtb, all on eMMC, with pmos_root_uuid=e362f850 (Ubuntu eMMC root) so the FULL coldplug window runs under the 6.12 kernel + Ubuntu userspace + U-Boot handoff. Dropped pmos_boot_uuid (this initramfs was rebuilt for the USB layout; FAT ESP mount could stall it). Added softlockup_panic=1 hung_task_panic=1 panic=10 so a 6.12 wedge panics with stacks on serial. - Interpretation: bisect boots fine -> 7.0 kernel bug. Bisect wedges the same way -> U-Boot handoff / ATF / DTB environment issue. - NOTE: the bisect boots UBUNTU userspace under a pmOS kernel — it is NOT the real pmOS; do not confuse the two after boot. depmod of the copied tree was done on pmOS originally; modules.dep present (935). ## Round 57 — EXP-D (maxcpus=1) BOOTS FULLY: wedge requires SMP; pmOS bisect entry had /boot path bug (fixed) - EXP-D maxcpus=1 boots through coldplug to serial login (panel still black — display issue is separate). Wedge does not occur with one CPU. Combined with EXP-B (cpuidle.off=1 wedged): the death is tied to multi-CPU bring-up/coupling, not to the idle framework itself. - Unexplained: sudo hang at the EXP-D login prompt (no kernel output, log ends at "[sudo: authenticate]"). Ask user to retry and WAIT >=2-3 min: hung_task_panic should panic with the D-state stack + blocker on serial (single CPU means nobody reports a CPU0 death, but a sleeping task is still catchable). - ramoops DID NOT register in the EXP-D boot (no probe message at all; module ramoops.ko.zst IS in the initrd; v7.0 has OF match table + reserved_mem_matches entry so the /reserved-memory node should get a device). Unresolved — have user check `ls /sys/fs/pstore` and `modprobe -v ramoops; dmesg|grep -i ramoops` on the next EXP-D boot. - pmOS-6.12-via-U-Boot entry: grub spammed file-not-found then fell through — ROOT CAUSE: my rewritten entry used root-level paths (/vmlinuz-pmos-...) but on eMMC the files are in /boot/. Fixed: /boot/vmlinuz-pmos-6.12.87, /boot/initramfs-pmos-6.12.87, /boot/dtb-pmos-krane.dtb. - Added EXP-E maxcpus=4 (big A73 cluster only, no LITTLE cpus): discriminates LITTLE-cluster involvement (cpufreq policy4, CCI, cpus 4-7) from big-cluster SMP. - Current best theory family: something in the multi-CPU bring-up/ cluster-coupling path (cpufreq/CCI/SVS/power) kills CPUs dead to IPIs at coldplug settle on 7.0; absent with maxcpus=1; not cpuidle (EXP-B); not wifi (EXP-C); not ramoops region (existed in wedging boots only since round 53, wedge predates it). ## Round 58 — pmOS 6.12 via U-Boot DIES too (env confirmed!); EXP-E hung later; cleanup-trio suspicion - pmOS 6.12 via U-Boot (all 8 CPUs, own DTB, eMMC root): all 8 CPUs boot (0.088s), eMMC enumerates (HS400 2.28s), mediatek-drm binds, fb1 created — then SILENT at ~3.02s: last prints clk banner (2.994) / genpd banner (3.002) / ALSA list (3.017); "Freeing unused kernel image (initmem) memory" never printed. THE SAME KERNEL BOOTS FINE VIA DEPTHCHARGE. => U-Boot handoff is a necessary condition. Environment, not (only) 7.0 kernel. - Window analysis: death sits in the late_initcall_sync tail, right where clk_disable_unused -> genpd_poweroff_unused -> regulator_init_complete run. regulator_init_complete silently force-disables boot-on-but-unclaimed regulators — U-Boot (display bring-up) leaves regulators/clocks/domains ON that depthcharge does not; the kernel then tears down something CPUs depend on. CPUs 1-7 die, CPU0 freezes shortly after (no free_initmem print). Unifying with 7.0: EXP-C full-SMP "CPUs dead to IPIs" and the ~10s module-load IPI spin = same teardown, different notice time; maxcpus=1 survives (nothing to tear down under other CPUs); EXP-E (maxcpus=4) PASSED the 2.49s cleanup (Freeing initmem + Run /init seen) and hung later at NM/ModemManager (~15s, wifi not blacklisted — mt76 fw download is back as a candidate for THAT hang, possibly a second, separate deadlock). - ALSO: U-Boot hands off at EL2 ("All CPU(s) started at EL2"), depthcharge at EL1 — another handoff delta to keep in mind. - Prepared: pmOS bisect entry now has initcall_debug + clk_ignore_unused + pd_ignore_unused + regulator_ignore_unused. Boot it: if it reaches login, the teardown trio is the killer and we bisect which of the three; the initcall_debug tail pins the exact hung function if it still dies. - Note: EXP-D ramoops still silent — check `ls /sys/fs/pstore` + `modprobe -v ramoops` on a working boot sometime. ## Round 59 — ignore-params FIXED the 3s death; two separate bugs now cleanly separated pmOS 6.12 via U-Boot with clk_ignore_unused + pd_ignore_unused + regulator_ignore_unused: SAILS through the 3s teardown death into full userspace (systemd starting Ubuntu services at 15s), then hangs at ~16.1s at NetworkManager/ModemManager start WITH wifi active — the classic point. initcall_debug lines did not appear on serial (KERN_DEBUG vs console loglevel mystery — unresolved, moot now). Bug matrix across experiments (wifi = mt76 bring-up): - teardown death (clk/genpd/regulator cleanup under U-Boot handoff, CPUs 1-7 killed, CPU0 freezes): pmOS 6.12 @3s pre-params. Fixed by the three ignore params. - Bug B (mt76 fw download deadlocks with >1 CPU): EXP-B (cpuidle.off, wifi on) ~14s; EXP-E (maxcpus=4, wifi on) ~15s; this boot (8 CPUs, wifi on) ~16.1s. Absent with wifi blacklisted (EXP-C reached the OTHER bug at 9-10s) and with 1 CPU (EXP-D wifi came up fine). - EXP-C (8 CPUs, wifi off): died 9-10s = first all-8-idle window => deep idle/domain-sleep death on 7.0 (cpuidle.off=1 should fix). So: Bug A' on 7.0 = deep idle (cluster/domain sleep) kills CPUs; Bug A on 6.12 = the teardown kills CPUs (only seen on clang-built pmOS kernel — Ubuntu gcc 7.0 passed teardown in EXP-D/E). maxcpus=1 avoids both (no domain idle states, no other CPUs). New entries (this round): - EXP-F (7.0): cpuidle.off=1 + mt76 module_blacklist, full 8 CPUs. If it boots fully -> both bugs confirmed, working 8-CPU system. - pmOS bisect entry: same three ignore params + cpuidle.off=1 + mt76 blacklist. If it boots fully -> 6.12 also working via U-Boot. Next after confirmation: live root-causing on the working system (disable cpuidle states one by one via sysfs to find the killer state; bisect mt76 with 8 CPUs), plus decide the real fix (DTB always-on marks? U-Boot handoff cleanup? mt76 fix?). ## Round 60 — second full panic nails the shape: individual CPUs die silently in hardirq context EXP-F reboot: same panic shape as EXP-C — udev module load spinning in kick_all_cpus_sync (CPU#5, started ~10.3s), but this time "SMP: failed to stop secondary CPUs 4,7": only cpus 4 and 7 were dead; 0-3,5,6 answered. Cp 4,7 = LITTLE cluster members, but 5,6 (same cluster) alive => NOT a cluster-wide clock/regulator kill. Individual random CPUs go silent at ~8-11s (initrd coldplug window). Interpretation: CPUs stuck in HARDIRQ context (explains: no IPI service, no timer ticks, no self softlockup report, no panic; a spinning hardirq handler never returns so hrtimers never fire). Trigger candidate: an IRQ handler with an unbounded wait loop (mtk_dsi irq do{}while-DSI_BUSY, cmdq mailbox, cros-ec rpmsg/spi) arming at coldplug under the U-Boot handoff hardware state. Downstream effects now unified: module-load IPI spins -> panics; mt76 fw-download work queued on a dead CPU's kworker -> the NM/ModemManager-era hangs (EXP-E, pmOS round 58 boot); silent freeze when no spinner reports. maxcpus=1 survival remains consistent (no IRQ spreading). Prepared EXP-G: EXP-F + irqaffinity=0 -> all external IRQs on CPU0; if a handler spins, CPU0 dies first/visibly. Ask user for sysrq (BREAK + w/t) during any wedge: 'l' backtrace of all CPUs would show the stuck hardirq handler directly. ## Round 61 — irqaffinity=0 did NOT protect: 7 of 8 CPUs died (0-2,4-7); cpuidle confirmed OFF; cascade model EXP-G (EXP-F + irqaffinity=0): CPU#3 spun in kick_all_cpus_sync (module load, started ~14.3s, further than before — real-root modules loading), "SMP: failed to stop secondary CPUs 0-2,4-7": SEVEN CPUs dead including CPU0 — but with all device IRQs pinned to CPU0 a spinning device handler would have killed only CPU0. => - cpuidle.off=1 IS effective ("failed to register cpuidle driver", "CPUidle PSCI: Failed to create psci-cpuidle device") — no PSCI suspend path exists in these boots at all. - Simple device-IRQ-storm-as-primary is dead too (CPU0 died anyway). Working model now: PRIMARY = CPU(s) stuck in a hardirq handler (any CPU; can hit several — EXP-F had 4,7); CASCADE = a stop_machine (jump-label/text patch during module probes) parks every other CPU's stopper thread in multi_cpu_stop with IRQs masked, waiting forever for the stuck one -> whole-machine silent death; the innocent module-load CPU then spins in kick_all_cpus_sync and softlockups. Consistent with 7-dead (EXP-G) and 2-dead (EXP-F) variants. picocom correction: C-a C-b = "set baudrate" (that was the prompt!). Serial BREAK in picocom = C-a C-j (pulse BREAK), then the sysrq letter (l = all-CPU backtrace, t = task dump) quickly after. Prepared EXP-H: EXP-F + threadirqs -> handlers run as kernel threads; a spinning handler becomes schedulable and the softlockup/hung-task detector NAMES it (stack + handler identity) instead of silently killing CPUs. This is the experiment that should finally reveal the killer function. ## Round 62 — EXP-H (threadirqs): same crash, new victim; pseudo-NMI prepared as the stack-revealing tool EXP-H: CPU#2 kworker/2:2 stuck 26s in smp_call_function_single <- rcu_barrier <- fqdir_free_fn (netns frag teardown work — another ALL-CPU barrier wait, not the cause). "failed to stop 0-1,3-7" — 7 CPUs dead again. threadirqs didn't change the class => the stuck CPUs are NOT in a plain device-IRQ handler (those would have become visible as threaded tasks). All panics share: reporting CPU waits in an smp_call/rcu_barrier on other CPUs that never service IPIs. Death window ~2.5-14s (coldplug storm), every full-SMP U-Boot boot, both kernels. maxcpus=1 immune. cpuidle confirmed off. irqaffinity=0 confirmed ineffective (CPU0 died). Display/iommu never bind on 7.0 under U-Boot (deferred), so the DSI-IRQ-loop theory is weakened. Ubuntu 7.0 has CONFIG_ARM64_PSEUDO_NMI=y but disabled by default ("watchdog: NMI not fully supported"). Prepared EXP-I: + irqchip.gicv3_pseudo_nmi=1 nmi_watchdog=1 -> hard lockup detector becomes live; CPUs stuck with IRQs masked (multi_cpu_stop, hardirq, anything) will SELF-REPORT their stacks via pseudo-NMI on serial. This should finally show where the dead CPUs are. Removed softlockup_panic from EXP-I so hardlockup reports print repeatedly instead of one soft-lockup panic cutting the dump short. sysrq during the ~26s wedge window also works: picocom C-a C-j (pulse BREAK) then 'l' (all-CPU backtrace). ## Round 63 — EXP-I null test (quirk), ftrace dump-on-panic prepared EXP-I: pseudo-NMI refused by an UPSTREAM QUIRK: the krane DTB's GIC node carries "mediatek,broken-save-restore-fw" ("broken MediaTek firmware that doesn't properly save and restore GIC priorities") and cpufeature.c disables pseudo-NMI on it — printed at 0.000000. NOT our wedge cause (pmOS idles/suspends constantly on the same DT without pseudo-NMI and never dies; the breakage only matters for priority-programmed NMI). No stacks obtained. Prepared EXP-J: ftrace=function + ftrace_dump_on_oops (both =y in Ubuntu kernel). Function tracing records every CPU's executed functions into per-CPU ring buffers; at the softlockup panic the kernel dumps ALL CPUs' buffers to serial — INCLUDING the frozen CPUs' last executed functions before they died. This should name the code the dead CPUs were running, no timing luck needed. Boot is slower (function tracing on); panic dump is LARGE (serial @115200 — let it run, could take minutes; do not interrupt). ## Round 64 — EXP-J dump partially captured: only CPU 6 (alive); logfile capture next ftrace dump-on-panic WORKS (trace lines after "SMP: stopping secondary CPUs"). User's terminal-buffer paste contained ONLY CPU 6's section ("6" in "6d.h3." = CPU 6 hex): alive & normal (timer, mmc, idle) through trace-ts 75720287-75726786us (~26s window before panic). Dump order = CPU 0 first -> dead CPUs' sections (0-5,7) were the EARLIEST output, lost to terminal scrollback while surviving CPUs kept dumping for 5+ min at 115200. This wedge: CPU#3 rcu_exp_gp_kthr stuck 26s (started ~78s, after login) — later + less deterministic than coldplug wedges (ftrace overhead shifts timing); same all-CPU-IPI-dead mechanism. NEXT: rerun EXP-J with picocom --logfile /tmp/fb-full.log (or | tee). Dead CPUs' final functions = first sections of dump. ## Round 65 — EXP-J full analysis: CPUs die ENTERING WFI idle; nohlt test prepared Full ftrace dump (131k lines captured, timestamp-merged across CPUs, covering trace-ts 34.82-34.925s — ~100ms before the mass freeze): Panic 60.7s CPU#1 rcu_exp_gp_kthr waiting on CPU6. Dead: 0,4-7. CONFIRMED DEATH POINTS (last trace event before silence): - CPU4: 34.880s check_and_switch_context <-__schedule (entering idle) - CPU7: 34.924s timer_base_try_to_set_idle <-tick_nohz_stop_tick (programming wake timer, entering NOHZ idle) - CPU5, CPU6: still running at capture cutoff (death later, uncaptured) CPUs 0-3 sections were beyond the cutoff. NOT killed mid-execution: cores go into WFI idle and NEVER WAKE — no trace, no IPI response (failed-to-stop SGIs), no timer wake, no watchdog. The GIC stops delivering to a WFI'd core. WHY cpuidle.off=1 DOESN'T HELP: it only removes the cpuidle framework; default idle is still cpu_do_idle (WFI). KEY HANDOFF DELTA (arm_arch_timer.c arch_timer_select_ppi): - depthcharge: EL1 entry -> hyp unavailable -> VIRT timer (CNTV) - U-Boot: EL2 entry -> hyp available -> PHYS NONSECURE timer (CNTP) (log: "cp15 timer running at 13.00MHz (phys)") — wake PPI path never exercised by depthcharge-launched kernels on this board. Firmware context: DT GIC node carries mediatek,broken-save-restore-fw (upstream quirk; kernel only uses it to disable pseudo-NMI) — known broken firmware save/restore of GIC state on this SoC family. 858921 note: workaround (= Cortex-A73 counter read) active ONLY on CPUs 4-7; dead sets always include big cores but also little cores (CPU0 has no workaround and dies too) — 858921 not the trigger. maxcpus=1/4 survive: big cluster offline AND fewer idle cores. NEXT: EXP-K = + nohlt (cpu_idle_force_poll=1; do_idle busy-polls, NEVER executes WFI). If boot reaches userspace with 8 CPUs and no wedge => WFI confirmed as trigger. Then: make U-Boot hand off at EL1 (kernel would pick CNTV like depthcharge) or find GIC/SPM wake fix. nohlt is power-hungry — diagnostic/permanent stopgap only. ## Round 66 — EXP-K: BUG A CONFIRMED (WFI trigger); silent hang at network.target = Bug C EXP-K (nohlt = cpu_idle_force_poll=1, CPUs busy-poll in idle, never WFI): the ~8-14s coldplug wedge DID NOT HAPPEN — boot sailed through to full userspace: NM up, network.target reached (~20s+), ccifreq spam ended normally ~8-9s. => CPUs die while EXECUTING WFI. Bug A root cause class: core enters WFI and its GIC redistributor/timer wake never fires again (U-Boot EL2 handoff -> kernel uses PHYS nonsecure timer PPI as wake source; depthcharge EL1 -> CNTV virt; broken-save-restore-fw firmware context). REMAINING silent hang: log stops right after "Reached target network.target" (no systemd-user-sessions line). Same point as EXP-F's silent hang (13.5s) and pmOS-6.12-via-U-Boot 16.1s — BUT mt76 is blacklisted in EXP-K => NOT Bug B (wifi-independent). Called it Bug C: SMP-dependent silent deadlock after NM start; silent because hung_task_panic was NOT set (softlockup needs a spinning CPU; this is a blocked/deadlock state) and sysrq BREAK+l on ttyS0 got no response in that state (serial IRQ possibly dead too, or full freeze). EXP-K2 prepared: same + hung_task_panic=1 hung_task_timeout_secs=10 -> 10s after a task hangs, panic prints ALL CPU stacks = names the deadlock. Boot EXP-K2 next; when it stops, WAIT ~15s for the auto-panic dump (no sysrq needed). ## Round 67 — VFS root panic was a grub entry mistake (mine), fixed Both reboot attempts: "Cannot open root device ... unknown-block(0,0), available partitions: (EMPTY)" + prepare_namespace in the panic trace = kernel got NO initramfs (prepare_namespace never runs when an initrd is present). Without initrd there are no modules -> no mtk_sd -> no block devices. EFI banner in both boots lacks the INITRD=0x... line. Cause: my EXP-K edit accidentally replaced the entry's initrd line with a second linux line and then deleted the original linux line -> entry booted with NO initrd at all. Fixed: initrd line restored, grub regenerated and verified (entry now has linux+initrd+devicetree). NOT a kernel regression. Also: the entry label stayed "EXP-K" (I edited in place; "EXP-K2" never existed as a label — the hung_task params were active in both boots). Next boot: EXP-K again (nohlt + hung_task_panic) — when output stops, wait ~15s for the auto hung-task panic dump. ## Round 68 — Bug C named: QCA Bluetooth firmware download (hci_uart); bt blacklist test ready eMMC journal (hostname duet) preserved the EXP-K death context that serial never showed: ath10k_sdio wifi loaded, ModemManager + wpa_supplicant started, then: Bluetooth: hci0: QCA Downloading qca/rampatch_00440302.bin kernel: ------------[ cut here ]------------ <- freeze, no more Bug C = firmware-download deadlock, same class as Bug B (mt76 SDIO fw download at NM time) but via QCA BT UART. With wifi blacklisted, the BT path (hci_uart) hits the equivalent bug at user-sessions time. Re-explains EXP-E hang and pmOS 16.1s hang. Also: hung_task_timeout_secs=10 is NOT a valid boot param (only sysctl kernel.hung_task_timeout_secs; default 120s in Ubuntu) — hung_task_panic WAS active but needs 120s to fire; reboots were too early. Boot param valid: hung_task_panic only. NEXT: EXP-K entry now blacklists hci_uart,btqca,bluetooth on top of mt76 + nohlt. If it reaches serial login with 8 CPUs => both remaining boot bugs are firmware-download deadlocks under U-Boot handoff (wifi SDIO + BT UART). Then: bisect WHICH SMP interaction breaks fw download (candidates: SDIO/UART DMA + per-CPU IRQ wake marginality, GIC-to-SPM wake path, or sg_table/DMA vs IOMMU-off). ## Round 69 — BT blacklist did NOT fix the user-space hang; sysctl dump prepared EXP-K with hci_uart/btqca/bluetooth blacklisted: same silent hang, stops around NM/hostnamed/ModemManager start (slightly earlier than the network.target stop of the previous EXP-K boot — placement varies). So Bug C is NOT (only) the QCA BT download — the journal's cut-here during rampatch download was likely collateral, not the cause. Firmware-download-class theory: NOT yet confirmed for C. hung_task_timeout_secs boot param is invalid (only sysctl exists; default 120s — user reboots too early to ever see the dump). Prepared: /etc/sysctl.d/99-krane-hungtask.conf (kernel.hung_task_timeout_secs=10, hung_task_all_cpu_backtrace=1, hung_task_warnings=100) — applies to every boot of this rootfs; 10s after a task hangs, panic prints ALL CPUs' stacks and names the blocker (v7.0 has debug_show_blocker = mutex owner). NEXT: reboot EXP-K (nohlt, wifi+bt blacklisted). At the hang WAIT AT LEAST 3 MINUTES. The panic dump is the deliverable. ## Round 70 — WFI theory DEAD; new unified suspect: mtk-cci-devfreq first rate switch EXP-K (nohlt) definitive panic: CPU#2 kworker/2:0 stuck 22s in rcu_barrier <- fqdir_free_fn, "failed to stop secondary CPUs 0-1,3-7" — ALL SEVEN OTHER CPUS DEAD WITH NO WFI EVER EXECUTED (deaths at ~14.3s). The "die at idle entry" ftrace reading was an artifact: the round-64 capture ended 3s BEFORE the deaths (boot 1 panicked at 104.7 = death ~78s; round-65 capture = boot 2, death ~34.6s). CPUs freeze mid-execution, idle mode irrelevant. CORRECTION of the record: round 65's "CPU4/7 died entering idle" was premature — last-traced-event != death point for CPUs 5/6/0-3. NEW UNIFIED SUSPECT: drivers/devfreq/mtk-cci-devfreq.c probe: each deferred retry raises VPROC to the highest CCI OPP voltage (mtk_ccifreq_set_voltage BEFORE devfreq registration!), then devm_devfreq_add_device defers -517 on CPUFREQ_PARENT_DEV (mtk-cpufreq module) = the x71 spam. When mtk-cpufreq finally registers (real-root module storm ~9-15s), ccifreq attaches and the passive governor immediately syncs CCI rate+voltage on the live system. A wedged CCI/PLL/VPROC switch freezes ALL cores mid-instruction (shared resource) — matches every panic signature. Spam-end -> deaths correlation holds in every boot (8.7s spam end; ftrace boot spam 34.6, deaths 34.88+; nohlt boot spam 9.5, deaths 14.3). maxcpus=1/4 survive = big-cluster policy/transition absent. Bug A and Bug C are probably THE SAME BUG. NEXT: EXP-K now also blacklists mtk_cci_devfreq + mtk_svs (cpufreq still allowed). If the boot reaches serial login with 8 CPUs => CCI DVFS stack = killer. Then bisect: cpufreq vs ccifreq, and WHY the switch wedges under U-Boot (clock state left by payload?). ## Round 71 — CCI/SVS exonerated (still dies, NEW victim set {3,5,6,7}); cpufreq is the last DVFS suspect EXP-K + mtk_cci_devfreq/mtk_svs blacklisted: STILL dies at ~14.3s (same rcu_barrier/fqdir_free_fn victim class) BUT the dead set changed for the first time: 3,5-7 — big cores 5,6,7 + little core 3, with CPU4 ALIVE (first time ever with 8 CPUs online). CCI/SVS exonerated as the trigger. Remaining DVFS piece in the 14s module storm: mediatek-cpufreq (module mediatek-cpufreq.ko; big-cluster policy init = switch to intermediate clock, reprogram main PLL, change shared VPROC/VSRAM rails). Explains: deaths spanning both clusters (shared rail), survivor variation, maxcpus=1/4 immunity (big policy never inits — EXP-E with 4 CPUs survived cpufreq and hit mt76 Bug B at NM instead), depthcharge immunity (clock tree left in expected state; Jul 28 journal: "CPU4: Running at unlisted initial frequency: 1199999 KHz, changing to 1248000" — U-Boot may leave a non-OPP-listed rate -> fatal big PLL jump). No cpufreq messages at all appear on serial in U-Boot boots before the freeze. NEXT: EXP-K + mediatek-cpufreq blacklisted. Serial login with 8 CPUs => big-cluster cpufreq policy init = the killer under U-Boot handoff. Then compare with U-Boot's leftover MPU rate (bootefi "CPU: ..." or /proc/cpuinfo) and design the real fix (U-Boot clock cutover or cpufreq driver quirk). ## Round 72 — cpufreq EXONERATED too; round-55 signature; EXP-L definitive trace prepared EXP-K + mediatek-cpufreq blacklisted: STILL dies — and the panic is the round-55 signature EXACTLY: udev-worker CPU#1 stuck 23s in smp_call_function_many_cond <- kick_all_cpus_sync <- flush_module_icache <- load_module <- finit_module. Dead set {4,5,6} (3 big cores; CPU7 alive for the first time). DVFS fully exonerated (cci, svs, cpufreq all blacklisted — still dies). nohlt also ruled out (round 70: dies without any WFI). The module-load IPIs reveal the freeze, they don't cause it; several modules were mid-load in parallel (cros_ec_keyb(+), hid_multitouch(+), hid_google_hammer(+), extcon(+), cros_ec_dev(+)). Invariants now: deaths at ~9-15s in the udev storm; victim sets vary but always include big cores; U-Boot handoff required; depthcharge immune; maxcpus=1/4 immune; idle mode irrelevant; DVFS irrelevant; irqaffinity/threadirqs irrelevant. EXP-L prepared: round-63 config EXACTLY (cpuidle.off + mt76 blacklist + ftrace=function + ftrace_dump_on_oops, NO nohlt) + trace_buf_size=128 (bounds each CPU ring -> dump ~30k lines, completes in minutes; frozen CPUs' tails = their true death points; normal WFI idle so idle CPUs still trace their entry). Boot with picocom --logfile; capture EVERYTHING until reboot.