Files

2768 lines
145 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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, x1x3 = 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, x1x3 = 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 <vmlinuz> -b <dtb>`, 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 24: 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: 35 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). R12R17 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 (R12R17 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 R12R22 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 0x00x20000000 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<rot>` 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 <fn>" without a matching
"initcall <fn> ... 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+=" <that path> "
(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.