The diagnostic wrapper phase (magenta fill, 3-blink train, 5 s hold) is removed; the shim keeps only the load-bearing display revival (OVL_EN, OVL0_2L_EN, backlight GPIOs) and the patched branch to U-Boot. U-BOOT.md documents the boot chain, payload layout constants, build/flash/verify commands, recovery and the U-Boot tree state. U-Boot was rebuilt with the user's hand-tuned .config (16x32 font, CONFIG_CHROMEOS) without re-running the defconfig.
73 KiB
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 = 0xb0in/firmware/corebootof the live FDT). - coreboot table (LBIO) at
0xffed9000, size0x380; CBMEM region follows. Confirmed both bydtc -I dtb -O dts /sys/firmware/fdt(kernel's copy of depthcharge's fixed-up tree) and by sysfs (/sys/devices/platform/ffed9000.coreboot/coreboot7carriesMODALIAS=coreboot:t00000012= LB_TAG_FRAMEBUFFER present). - Boot chain: BootROM → coreboot → TF-A BL31 → depthcharge → our payload.
- Partition layout:
mmcblk0p1ChromeOS-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, builtMon 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_addressislb_uint64_t=__aligned(4) uint64_t(LB_ENTRY_ALIGN == 4). On LE AArch64 that is a plain 8-byte LE value at offset 8 ofstruct 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_recordmatch the prompt. Records start atheader_bytes, walkedtable_entriestimes.- Live cross-check: parsing the live
/sys/firmware/fdtyields LBIO at0xffed9000, size0x380— matches the values confirmed by hand with dtc.
arm64 Image header (Linux booting.rst + depthcharge boot64.c)
- 64-byte header, magic
ARM\x64(0x644d5241) at 0x38, code0 must branch past the header; entry with x0 = DTB, x1–x3 = 0, MMU off, DAIF masked. image_sizemust be exact and little-endian (depthcharge checks the magic and usesimage_sizefor KASLR slot math).
depthcharge mainline (as of this repo's clone)
boot64.c: kernel image is decompressed to a random 2 MiB-aligned slot; the Image magic is verified; entry = first byte; x0 = flat DTB, x1–x3 = 0. → stub must be position-independent (build enforces zero dynamic relocations) and carry its own stack (16 KiB in-image).- Contradicts the prompt:
fit.cskips 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 genuinemt8183-kukui-krane-sku176DTB (config conf-27 of the running p1 FIT) purely as a config-selection key; depthcharge injects/firmware/corebootat boot viasrc/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. mkdepthchargebuilds the FIT itself (mkimage -f auto -T kernel -O linux -C none -d <vmlinuz> -b <dtb>, then patches the kernel subimage tokernel_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:
69725330(2020-09-22, "display: Black out screen in display_cleanup()", in R93):display_cleanup()atCleanupOnHandoffbefore jumping to the payload does:clear_screen(black)— erases the menu from the LBIO framebuffer;backlight_update(0)— drivesDISP_PWM(GPIO 43) andEN_LCD_BL(GPIO 176) low;- calls
display_ops->stop→ 2021-eramtk_display_stop():OVL_EN=0,OVL0_2L_EN=0— the overlay fetch engine is stopped.
- The DSI link, panel power and display MTCMOS remain up — panel/DSI
poweroff only exists in 2025+ code (
e9f50b81,9be6dc52, Dec 2025). AlsoLB_TAG_PANEL_POWEROFFis 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(2021mtk_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;doutblock at +0x140, 16 bytes per 32-pin group,setat +4 /rstat +8. Backlight pins:DISP_PWM = 43(group 1, bit 11 → set reg0x10005154),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:
OVL_EN = 1,OVL0_2L_EN = 1(offsets from device-era mtk_ddp.c);- GPIO
dout.setwrites for pins 43 and 176 (GPIO base0x10005000, dout block +0x140, 16 B per 32-pin group — layout from depthcharge'smtk_gpio.c/mt8183.h); - repaint —
OVL_L0_ADDRstill 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/memLBIO read still blocked: pmOS kernel hasCONFIG_STRICT_DEVMEM(confirmed in/boot/config),0xffed9000sits infae00000-ffdfffff : reservedper/proc/iomem→dd if=/dev/memgives "Bad address". kexec remains the only live-read route; not needed — see below.- Open item resolved without hardware access: the runtime LBIO
physical_addressis provably non-zero. Depthcharge's device-eramtk_display_init()(src/drivers/video/mtk_ddp.c) programsOVL_L0_ADDRfromlib_sysinfo.framebuffer.physical_address(i.e. the LBIO record) and the dev menu was visibly rendered through it;src/drivers/video/display.calso gates display handling onphysical_address != 0. Mainline coreboot passesfb_addr=0for 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
mmcblk0p1reflashed with the Round-3 diagnostic payload: sha2569d7859dc04dd9c445143f6b45600bb972cf5fc1497c06a4f9db549e3d2971e73, verified on-device (cmpfirst 159,744 bytes +vbutil_kernel --verify→ body verification succeeded). Earlier: Round-1edb65b29…, Round-29e7cf29d…(both functionally identical wrong-offset code;41270e53…was a stale-tree rebuild caught by disassembly).- USB stick
sda1untouched throughout (sha256 watched:c4ee2044…). - eMMC backup:
~/mmcblk0p1-pmos-backup.img(sha25620951a54…). 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:
- First instructions:
OVL_EN=1,OVL0_2L_EN=1, backlight GPIOs on. - Parse (<1 ms). Failure → 5 slow backlight blinks, spin.
- Success → red → yellow → green → blue, blue held.
Decision tree: colors = pipeline validated; 5 blinks on black = parse failed at runtime; nothing at all = payload never handed off (boot path).
Abandoned: OVL_L0_ADDR stage-0 read
An earlier design read OVL_L0_ADDR (0x14008F40) pre-parse to paint the
menu buffer without knowing pa. Rejected: on qemu -M virt, READS of
unassigned MMIO data-abort (writes are silently dropped) — it killed the
test instantly; the same risk exists on real hardware if a display clock
gate is off, and a fault there is indistinguishable from "never ran". The
stub now contains no MMIO reads at all.
Round 4 — still black, no backlight: GPIO dout offset was wrong too
Reboot with the Round-3 diagnostic payload (9d7859dc…): pitch black, no
blinks, no backlight at all. Stage-0 turns the backlight on as its
first act, and the backlight demonstrably works via these same GPIOs (the
dev menu had it) — so either the payload never ran, or the GPIO writes
missed. Re-derived the register layout from the device-era header
(src/drivers/gpio/mt8183.h) instead of the Round-1 notes:
GpioRegs: dir[6]@0x000, rsv00[160], dout[6]@0x100, rsv01[160],
din[6]@0x200; GpioValRegs = 16 B (val@0, set@4, rst@8)
The Round-1 note's dout @ +0x140 was wrong — actual offset +0x100.
Consequences of the old addresses: the "pin 43 set" write to 0x10005154
actually set bit 11 of group 5 = pin 172 (I2S1_MCK); the "pin 176 set"
write to 0x10005194 hit a reserved region. Correct set/rst addresses:
pin 43 → 0x10005114/0x10005118, pin 176 → 0x10005154/0x10005158.
The Round-3 blink was additionally broken: it wrote 0 to a set register
(a no-op) instead of writing the bit to the rst register. Both fixed.
This also reframes Rounds 2–4: the OVL revival (correct offsets) and the
fills may have been working the whole time — with the backlight never
enabled, an LCD shows nothing regardless of what is scanned out. The only
proven failures were the two register-offset misreads in the Round-1
research notes (OVL_EN 0x0F00, dout 0x140), both now [REVERSED] and
fixed; the stub itself has never been disproven.
Payload 55ccc1bd… flashed (cmp + vbutil OK). host/qemu tests pass.
Diagnostic decision tree (after this fix)
| observation | meaning |
|---|---|
| black, no colors, no blink | payload never handed off — boot-path problem (vboot/mkdepthcharge), investigate p1 boot |
| black + 5 slow backlight blinks (NOW FUNCTIONAL — see Round 4) | stub ran, parse failed at runtime |
| colors appear but sequence stops early | fill/mask problem — stuck color identifies stage |
| red → yellow → green → blue, blue held | pipeline fully validated |
Round 5 — blinks decoded: LBIO physical_address really IS 0
The Round-4 payload's diagnostic worked: 3–5 backlight blinks then steady
backlit black = parse_fail(). The stub runs, stage-0 works, the GPIO fix
works — the parse rejected the LBIO framebuffer record. Root cause found
in the actual coreboot 4.14 source (fetched from the 4.14 tag):
src/mainboard/google/kukui/mainboard.c(4.14) is identical to mainline:fb_new_framebuffer_info_from_edid(edid, 0)— and 4.14'sedid_fill_fb.cpassesfb_addrthrough 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()readsOVL_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_dumpblinks 5× then renders the bit-band dump AT THE SCANOUT ADDRESS; word 15 additionally carries the raw L0_ADDR value. Markers unchanged (0xC0DE0001DTB stage,0xC0DE0002|rcLBIO 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:
- depthcharge boots the dev-signed FIT from
mmcblk0p1and hands off (x0 = FDT buffer, x1-x3 = 0, MMU off); - stage0 re-enables OVL (+0x000C / +0x100C) and backlight (GPIO dout 0x10005114 / 0x10005154);
- the LBIO table at the fixed 0xffed9000 parses (record: pa handled via OVL_L0_ADDR substitution = 0xFD536000);
- 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_addressin the LBIO record — resolved in Round 2: provably non-zero at runtime (depthcharge's device-eramtk_display_initprogramsOVL_L0_ADDRfrom the LBIO record and the menu rendered through it; the/dev/memread attempt failed onCONFIG_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
kranein/home/vhaudiquet/u-boot(mainline main @527115ef); commits29aec558(board/defconfig/MAINTAINERS),923cc754(dtc include order),f0470c9b(video driver). configs/mt8183_kukui_krane_defconfigforked from pumpkin: keepsCONFIG_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=-1so 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 upstreamdts/upstream/include/dt-bindings/clock/mt8183-clk.h(CLK_TOP_CLK26M=112) for OF_UPSTREAM builds — the krane tree failed with undefinedCLK_CAM_LARB6, and even fixing those macros would have silently produced wrong clock numbers everywhere. Fix:scripts/Makefile.libprefersdts/upstream/includewhenCONFIG_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():
- revival identical to the stub's stage0:
OVL_EN=1(+0x000C),OVL0_2L_EN=1(+0x100C), GPIO dout set for pins 43/176; - LBIO parse at the fixed 0xffed9000 (ported from the stub's
Round-10-validated
find_framebuffer; host regression test compiled the function standalone againstlbio.bin: record parsed, bad signature rejected); - scanout = LBIO pa if >= 0x40000000, else
OVL_L0_ADDR(+0x0F40) (Round-9 ground truth: 0xFD536000); else probe fails gracefully; - 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); - 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 byarch/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.binsha2561fc74a0a…, flashed tommcblk0p1(dd 4M conv=fsync),cmpOK (589824 bytes), on-devicevbutil_kernel --verify→ body verification succeeded. - Backup
mmcblk0p1-pmos-backup.img(sha25620951a54…) 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:
- OVL revival + backlight (identical writes to the stub's stage0);
- reads OVL_L0_ADDR, fills the scanout (guarded >= 0x40000000) with full-screen green (0x8ca000 bytes = 120019204);
- 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_cleared 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:
- start.S wfi check: runtime _start ≡ 0 (mod 4K);
- 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 thedelaysubroutine (8 insns incl.ret) after it. The script patchedwrapper[-4:]= delay'sret, overwriting it with the branch. - Effect: the FIRST blink's
bl delaynever 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 (patchingretwas harmless there). R12–R17 ran because wrapper v1/v2 had the tail branch as the last instruction.
Fixes (payload cff0e0e9…, flashed, cmp+vbutil OK)
- Script now SEARCHES the wrapper for the single
b .(0x14000000) and asserts exactly one occurrence; patch target = the real branch. - Placement recomputed from the ELF:
_startis 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. - Verified in the packed image:
b .@0x118 → 0x140007ba → target image 0x2000 = runtime 0x40002000 ✓;ret@0x138 intact ✓.
Expected behavior
Blue fill → 3 blinks (~1.5 s) → 5 s blue hold → U-Boot takes over. If U-Boot boots: banner (white on black). If it dies early, the orange/blue/red board-init bands and the post-clear cyan / post-probe white bands localize it (Round 19/20 instrumentation still in place).
Round 22 — wrapper fully healthy; U-Boot dies before board_init_f's first band
Round 21 (cff0e0e9…): blue, 3 blinks, blue held forever — the
wrapper now runs its complete sequence (fill, blink train, 5 s hold)
and jumps to 0x40002000. Blue persisting = nothing in U-Boot painted
anything: death (or non-execution) BEFORE board_early_init_f's orange
band. start.S review: the 4K wfi check passes (runtime _start
0x40002000); the PIE fixup applies the full delta to relative relocs
and our delta (-0xBFFFF000) is 4K-aligned, so adrp+lo12 stays valid;
crt0's SP (INIT_SP_RELATIVE, adrp __bss_start + 0x80000) is also
4K-delta-safe. So the hang is somewhere in [PIE fixup .. initcalls
before board_early_init_f] — a path no payload has ever reached on
this board (R12–R17 all died at the 4K wfi check).
Round 22 payload (21c3f792…, flashed, cmp+vbutil OK)
New checkpoint: green band (y=400) painted at the very top of board_init_f (common/board_f.c, temporary) — before the entire initcall sequence.
Decision tree
| observation | death localized to |
|---|---|
| blue hold only (no green) | start.S / crt0 / PIE fixup (pure asm window) |
| green only | initcalls before early_init_f: fdtdec_setup (embedded DTB!), log, bootstage, bloblist, arch/mach_cpu_init, initf_dm (clock driver probes) |
| green + orange | after early_init_f, before dram_init_banksize |
| green + orange + blue | after dram_init_banksize, before relocation |
Round 23 — [REVERSED R22 analysis] true root cause: 4-byte linker fill before _start skews the PIE fixup
R22 (21c3f792…): blue hold only — board_init_f's green band never
painted. Root cause found in the disassembly, not guessed:
start.o's input.textsection is 8-byte aligned, but CONFIG_TEXT_BASE=0x4C000FFC is only 4-aligned → ld inserts a 4-byte FILL at the start of .text →_startlands at TEXT_BASE+4, while_TEXT_BASE(the word start.S's PIE fixup loads as the link base) and__image_copy_startremain 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. (Theadrp + #:lo12:pairs used for the rela bounds also assume delta ≡ 0 mod 4K — with the skew, both mechanisms break.) - This retroactively explains the R12–R22 alignment confusion: the "+4" was never a stub/vectors mystery — it was a linker fill that appears iff CONFIG_TEXT_BASE is not 8-aligned.
- [REVERSED] the "vectors/stub bytes precede _start" note from R18/19; the 0xFFC leading bytes in old u-boot.bin dumps were the fill plus ELF file-offset artifacts, and the R22 "dies in pure asm" conclusion was wrong in mechanism (it died IN the fixup's corrupted pointers).
Fix (payload 18cf3e93…, flashed, cmp+vbutil OK)
- CONFIG_TEXT_BASE=0x4C001000 (8-aligned) → no fill:
_start == __image_copy_start == _TEXT_BASE == 0x4C001000, file offset 0 =b reset(verified in u-boot-nodtb.bin and in the packed image at 0x1000). - File placed at image offset 0x1000 → runtime _start = 0x40001000, start.S 4K check passes; PIE delta = -0xC000000 (4K-aligned).
- Payload script now derives the file offset from __image_copy_start
(not ELF section offsets) and ASSERTS
_start == __image_copy_startso 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):
- TEXT_BASE=0x4C001000 (8-aligned ⇒ no linker fill ⇒ _start == __image_copy_start == _TEXT_BASE; PIE delta 4K-aligned).
- Payload layout: U-Boot file at image offset 0x1000 ⇒ runtime _start 0x40001000 (both 4K constraints).
- ENV_IS_NOWHERE + krane.env: stdout=serial,vidconsole (the video device only probes via the iomux lazy path).
- 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.cbindsmediatek,mtk-xhci(matches theusb@11200000child ofssusb@11201000) and expects reg-namesmac+ippcand aphyslist ON THE XHCI NODE. The upstream mt8183.dtsi models the block for the Linux mtu3 driver:ippc(0x11203e00) and thephyslive on the PARENT mtu3 node, the xhci child has onlymac. U-Boot has no mtu3 driver. → newarch/arm/dts/mt8183-kukui-krane-sku176-u-boot.dtsi(auto-included at the END of the upstream dts by scripts/Makefile.libdtsi_include_list, so&labelrefs work) addsippctoreg/reg-namesandphys = <&u2port0 PHY_TYPE_USB2>, <&u3port0 PHY_TYPE_USB3>tousb_host. Not a DT fork; upstream file untouched. Verified in the compiled dt.dtb. drivers/phy/phy-mtk-tphy.cbindsmediatek,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_regulatorfails (debug only) andregulator_set_enable(NULL)returns -ENOSYS, whichxhci_mtk_ldos_enabletolerates (vusb33 is a 3.3 V LDO the firmware already keeps on; no VBUS regulator exists in the DT for usb_host). No PMIC work needed. - MMU: MTK armv8-mem-map pre-maps 0x0–0x20000000 as device; xhci (0x11200000), ippc (0x11203e00) and phy (0x11f40000) need NO dynamic mappings — the R31 get_page_table_size headroom is untouched.
- Enumeration→keyboard path:
U_BOOT_USB_DEVICE(usb_kbd, kbd_id_table)in common/usb_kbd.c matches HID boot-protocol keyboards duringusb_scan_device; the bound UCLASS_KEYBOARD probe registers stdio "usbkbd" and re-runsiomux_doenv(stdin)when the envstdincontains the name. - Timing: no
initr_usbexists in board_r.c anymore — USB is only enumerated onusb start. CONFIG_PREBOOT has a built-in default of "usb start" when USB_KEYBOARD=y (boot/Kconfig), soCONFIG_USE_PREBOOT=ysuffices: 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'sstdin=serial,usbkbdsurvives 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;
_startstill 0x4C001000 == __image_copy_start; dtb verified (mac+ippc+phys on usb@11200000). Payload35345ec7…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,
_startstill 0x4C001000. Flashed4fcd863d…, 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,
_start0x4C001000, vidconsole3 symbols linked. Flashed4b3ce2a2…, 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.