From fc4292918fd4e8bf89febe5ddf64872c8acbf062 Mon Sep 17 00:00:00 2001 From: vhaudiquet Date: Sat, 29 Aug 2026 20:52:53 +0200 Subject: [PATCH] RESEARCH.md: full lab notebook of display investigation Chronological record of the research: source verification (coreboot tables, arm64 Image header, depthcharge FIT loader), the first black- screen attempt, the depth-charge-version forensics that identified the handoff teardown (black fill + backlight GPIOs + OVL_EN off), the register-level facts used for the revival fix, flash state, and a post-fix diagnostic decision tree. --- RESEARCH.md | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 RESEARCH.md diff --git a/RESEARCH.md b/RESEARCH.md new file mode 100644 index 0000000..99b4efe --- /dev/null +++ b/RESEARCH.md @@ -0,0 +1,175 @@ +# Research log — krane display pipeline & stub bring-up + +This file is the running lab notebook for the krane-fb-stub project: every +investigation done against the actual sources (coreboot, Linux, depthcharge — +both mainline trees and the *device-era* code as recovered from the SPI flash +version strings), what was found, and how each finding changed the payload. +Ordered chronologically; conclusions marked **[REVERSED]** were later +contradicted by deeper evidence. + +--- + +## Phase 0 — Task framing + +Goal: smallest freestanding arm64 binary that depthcharge boots as a Linux +kernel, locates the boot-splash framebuffer, and paints checkpoint colors +(red → yellow → green → blue, ~2 s each, hold blue), to validate the +depthcharge → custom-payload pipeline before any U-Boot bring-up. + +Device ground truth (verified live): + +- Lenovo IdeaPad Duet, MT8183, board `google,krane`, **sku176** + (`sku-id = 0xb0` in `/firmware/coreboot` of the live FDT). +- coreboot table (LBIO) at `0xffed9000`, size `0x380`; CBMEM region follows. + Confirmed both by `dtc -I dtb -O dts /sys/firmware/fdt` (kernel's copy of + depthcharge's fixed-up tree) and by sysfs + (`/sys/devices/platform/ffed9000.coreboot/coreboot7` carries + `MODALIAS=coreboot:t00000012` = LB_TAG_FRAMEBUFFER present). +- Boot chain: BootROM → coreboot → TF-A BL31 → depthcharge → our payload. +- Partition layout: `mmcblk0p1` ChromeOS-kernel (what depthcharge boots + internally), `sda1` (USB stick, pmOS) is the currently-running system. +- Device firmware strings extracted from the 8 MB SPI (`flashrom -p internal`): + - `v1.9308_26_0.0.22-10476-g8c7859a3c9` (RO, built ~Feb 2021) + - `v1.9308_26_0.0.22-10566-g8417b00957`, built `Mon May 30 03:26:46 UTC 2022` + (RW — the slot that actually runs) + Both are ChromeOS depthcharge ≈ v0.0.22 (R93-era, early 2021/2022). + +## Phase 1 — Format verification (all checked against source, not the prompt) + +### coreboot tables (`src/commonlib/include/commonlib/coreboot_tables.h`) + +- `physical_address` is `lb_uint64_t` = `__aligned(4) uint64_t` + (`LB_ENTRY_ALIGN == 4`). On LE AArch64 that is a plain 8-byte LE value at + offset 8 of `struct lb_framebuffer` — the "hi/lo split" is an alignment + trick, not separate fields. +- `sizeof(struct lb_framebuffer) == 40` (host-compiled probe against the real + header). Offsets: tag 0, size 4, physical_address 8, xres 16, yres 20, + bytes_per_line 24, bpp 28, red 29/30, green 31/32, blue 33/34, + reserved 35/36, orientation 37, flags 38, pad 39. +- `lb_header` / `lb_record` match the prompt. Records start at + `header_bytes`, walked `table_entries` times. +- Live cross-check: parsing the live `/sys/firmware/fdt` yields LBIO at + `0xffed9000`, size `0x380` — matches the values confirmed by hand with dtc. + +### arm64 Image header (Linux `booting.rst` + depthcharge `boot64.c`) + +- 64-byte header, magic `ARM\x64` (0x644d5241) at 0x38, code0 must branch + past the header; entry with x0 = DTB, x1–x3 = 0, MMU off, DAIF masked. +- `image_size` must be exact and little-endian (depthcharge checks the magic + and uses `image_size` for KASLR slot math). + +### depthcharge mainline (as of this repo's clone) + +- `boot64.c`: kernel image is decompressed to a random 2 MiB-aligned slot; + the Image magic is verified; entry = first byte; x0 = flat DTB, x1–x3 = 0. + → stub must be **position-independent** (build enforces zero dynamic + relocations) and carry its own stack (16 KiB in-image). +- **Contradicts the prompt**: `fit.c` *skips* FIT configs without an FDT + subimage ("config %s has no FDT, skipping") and gives up with no match. + So the packed FIT must contain a DTB. We embed the genuine + `mt8183-kukui-krane-sku176` DTB (config conf-27 of the running p1 FIT) + purely as a config-selection key; depthcharge injects + `/firmware/coreboot` at boot via `src/boot/coreboot.c::install_coreboot_data` + (adds `#address-cells`, `#size-cells`, `ranges`, `compatible`, `reg`), + so the runtime DTB at x0 always has the node we parse. +- `mkdepthcharge` builds the FIT itself (`mkimage -f auto -T kernel + -O linux -C none -d -b `, then patches the kernel subimage + to `kernel_noload`), so a raw arm64 Image goes in directly — no manual + uImage wrapping. Verified: the packed image has the same shape as the + working pmOS FIT (`kernel_noload` + `flat_dt`, compression none). + +## Round 1 (eMMC flash, first attempt) — BLACK SCREEN, menu vanished + +Observed: depthcharge dev-menu exits → black screen, nothing ever painted. +Had to force power-cycle; recovered via USB boot (backup `~/mmcblk0p1-pmos- +backup.img`, sha256 `20951a54…`, taken *before* the first flash). + +### Investigation + +The black screen was initially read as "stub crashed". The decisive evidence +came from identifying the **exact firmware versions in the SPI dump**: + +- depthcharge `v1.9308_26_0.0.22-10566-g8417b00957` (RW slot, 2022-05-30) + — i.e. ChromeOS R93-era (2021/2022), not mainline 2026. +- coreboot `4.14`-era (built ~Feb 2021 / May 2022). + +Checking the chromiumos/depthcharge git history (`git log -S`) for the code +that ran *on the device*: + +1. `69725330` (2020-09-22, "display: Black out screen in display_cleanup()", + in R93): `display_cleanup()` at `CleanupOnHandoff` **before jumping to + the payload** does: + 1. `clear_screen(black)` — erases the menu from the LBIO framebuffer; + 2. `backlight_update(0)` — drives `DISP_PWM` (GPIO 43) and + `EN_LCD_BL` (GPIO 176) low; + 3. calls `display_ops->stop` → 2021-era `mtk_display_stop()`: + `OVL_EN=0`, `OVL0_2L_EN=0` — the overlay fetch engine is stopped. +2. The DSI link, panel power and display MTCMOS remain up — panel/DSI + poweroff only exists in 2025+ code (`e9f50b81`, `9be6dc52`, Dec 2025). + Also `LB_TAG_PANEL_POWEROFF` is emitted only by skywalker boards. + +**Conclusion:** the stub *did* run, painted red→yellow→green→blue and halted +on blue — into a framebuffer nothing was scanning, with the backlight off. +"Painted in the dark." The prompt's premise ("framebuffer is live and DMA'd +by the time any payload runs") is false for this firmware generation. + +### Key register facts established (all from device-era sources) + +- OVL0 base `0x14008000`; `DISP_REG_OVL_EN = 0x0F00`, `DISP_REG_OVL0_2L_EN = + 0x100C` (from 2021 `mtk_ddp.c`; 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. + +## Flash state + +- `mmcblk0p1` reflashed with the revival payload: + sha256 `edb65b29191692186a6fde2058df05a1999c257e19b60cf01cf4ee63a6933237`, + verified on-device (`cmp` first 159,744 bytes + `vbutil_kernel --verify` + → body verification succeeded). +- 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. + +## Diagnostic decision tree (after this fix) + +| observation | meaning | +|---|---| +| menu exits → black immediately, no color | stub ran but died in DTB/LBIO parse (or OVL revive didn't take) | +| colors appear but sequence stops early | fill/mask problem — stuck color identifies stage | +| red → yellow → green → blue, blue held | pipeline fully validated | + +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 is provably non-zero at runtime + (depthcharge's menu rendered through it), even though current mainline + coreboot passes `fb_addr=0` for kukui. Reading the live LBIO record from + Linux is currently blocked (pmOS kernel: `IO_STRICT_DEVMEM` → `/dev/mem` + EPERM, no `/proc/kcore`, sysfs coreboot driver exposes only tags). If the + revival fix doesn't produce colors, next step is a stub-side diagnostic + (e.g., blink backlight GPIO as a debug signal) or kexec-ing a small + reader. +- qemu test updated to the same expectation (colors + held blue); it + exercises parser + fill logic, not the MMIO revival writes.