diff --git a/RESEARCH.md b/RESEARCH.md index 984e8e7..5b35082 100644 --- a/RESEARCH.md +++ b/RESEARCH.md @@ -351,6 +351,40 @@ first, white=1 / black=0, rows every 64 px. Word 0 = marker: 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). + Recovery: power-cycle, boot USB (unchanged), `dd if=mmcblk0p1-pmos-backup.img of=/dev/mmcblk0p1 bs=4M conv=fsync`. diff --git a/krane-fb-stub-payload.bin b/krane-fb-stub-payload.bin index 5218e33..866cde4 100644 Binary files a/krane-fb-stub-payload.bin and b/krane-fb-stub-payload.bin differ diff --git a/krane-fb-stub.bin b/krane-fb-stub.bin index b5e5a40..3dd769a 100755 Binary files a/krane-fb-stub.bin and b/krane-fb-stub.bin differ diff --git a/main.c b/main.c index ad1275e..bb89838 100644 --- a/main.c +++ b/main.c @@ -1,19 +1,21 @@ /* * krane-fb-stub — freestanding, position-independent payload main. * - * Checkpoint sequence (from the task spec): - * 1. RED — stub entered and running (depthcharge jumped here) - * 2. YELLOW — DTB parsed, /firmware/coreboot node found - * 3. GREEN — "LBIO" signature verified, LB_TAG_FRAMEBUFFER record found - * 4. BLUE — previous fills visibly succeeded; hold blue forever - * On any failure: spin forever on the LAST SUCCESSFUL color. + * Success path: red 2s -> yellow 2s -> green 2s -> blue held. + * Failure path: 5 slow backlight blinks, then a bit-band diagnostic + * dump rendered into the live scanout buffer (see + * fail_dump below); decode offline from a photo. * - * Deviation from the prompt (documented in README): the framebuffer - * address is only knowable after parsing the DTB and the coreboot table, - * so "red on entry" is physically impossible. Parsing happens first - * (fast, <1ms); if any parse stage fails the screen keeps showing - * depthcharge's own output (the dev-mode menu), which is an even - * stronger "stuck at stage N" signal than a stuck color. + * Runtime facts established on the device (see RESEARCH.md): + * - depthcharge enters with x0 = flattened DTB (fixed up), x1-x3 = 0, + * MMU off (src/arch/arm/boot64.c: handoff(fdt, 0, 0, 0)). + * - The dev menu was drawn via cbgfx, which REJECTS pa == 0, so the + * runtime framebuffer address is the one programmed into + * OVL_L0_ADDR (0x14008F40) by mtk_display_init() — read it instead + * of trusting the LBIO record. + * - OVL_EN is at +0x000C (NOT 0x0F00), GPIO dout block at +0x0100 + * (NOT 0x0140); both earlier offsets were misreads, [REVERSED] in + * RESEARCH.md. */ typedef unsigned char u8; @@ -79,8 +81,7 @@ struct fbinfo { /* * Walk the FDT at `dt`, find /firmware/coreboot, return reg pair #1 * (coreboot table address + size). Cell counts tracked from - * #address-cells/#size-cells properties; krane root is 2/2 and - * depthcharge's fixup adds them on /firmware as well. + * #address-cells/#size-cells properties; krane root is 2/2. */ static int find_coreboot_reg(const void *dt, u64 *addr, u32 *size) { @@ -151,12 +152,9 @@ static int find_coreboot_reg(const void *dt, u64 *addr, u32 *size) * blue @33/34, reserved @35/36, orientation @37, flags @38, pad @39 * sizeof(struct lb_framebuffer) == 40 (host-verified with real header). * - * **physical_address == 0 is LEGITIMATE on this device**: coreboot 4.14 - * kukui calls fb_new_framebuffer_info_from_edid(edid, 0) with no carveout, - * so the LBIO record really says pa=0 and depthcharge's UI draws its menu - * into DRAM address 0, which the (never-stopped) OVL scans out. Do NOT - * reject it; paint at address 0 — the same 9.2 MB region depthcharge's own - * display_cleanup black-fills at handoff. + * physical_address == 0 is tolerated: upstream coreboot 4.14 kukui passes + * fb_addr=0, and the true scanout address then comes from OVL_L0_ADDR + * (see read_scanout_addr / cmain). The geometry fields are always valid. */ static int find_framebuffer(u64 table, struct fbinfo *fb) { @@ -180,7 +178,6 @@ static int find_framebuffer(u64 table, struct fbinfo *fb) if (tag == LB_TAG_FRAMEBUFFER) { if (rsize < 40) return -1; - /* pa == 0 is legitimate here (see block comment). */ fb->pa = (u64)rd32le(rec + 8) | (u64)rd32le(rec + 12) << 32; fb->xres = rd32le(rec + 16); @@ -243,27 +240,26 @@ static void fill_screen(const struct fbinfo *f, u64 val) /* ---- display revival ---------------------------------------------------- * - * Verified against the DEVICE's depthcharge (v0.0.22-10476, 2021/2022): - * display_cleanup() runs at CleanupOnHandoff before jumping here: - * 1. clear_screen(black) — LBIO framebuffer painted black - * 2. backlight_update(false) — GPIO 43 (DISP_PWM) and GPIO 176 - * (EN_LCD_BL) driven low - * 3. mtk_display_stop() — OVL_EN=0 and OVL0_2L_EN=0 - * The DSI link, panel and display MTCMOS stay up (no panel poweroff in this - * firmware generation). So the stub just needs to undo exactly those three - * steps — no DSI/panel re-init required. + * Device-era depthcharge (v0.0.22-10476/10566) display_cleanup() at + * CleanupOnHandoff runs before jumping here: + * 1. disable_graphics_buffer() + clear_screen(black) + * 2. backlight_update(false) — GPIO 43 (DISP_PWM), GPIO 176 (EN_LCD_BL) + * 3. mtk_display_stop() — OVL_EN=0 at +0x000C, OVL0_2L_EN=0 at + * +0x100C + * We undo 2 and 3. OVL_L0_ADDR is not touched by stop(), so the scanout + * address survives; re-enabling the engines resumes fetching from it. */ #define DISP_OVL0_BASE 0x14008000u #define DISP_REG_OVL_EN 0x000Cu /* mtk_ddp.c, both mt8173/mt8183 */ -#define DISP_REG_OVL_L0_ADDR 0x0F40u /* scanout address, survives stop */ +#define DISP_REG_OVL_L0_ADDR 0x0F40u /* live scanout address */ #define DISP_REG_OVL0_2L_EN 0x100Cu /* 2021 mtk_ddp.c */ #define GPIO_BASE 0x10005000u /* GpioRegs (mt8183.h): dir[6]@0x000, rsv00[160], dout[6]@0x100, * rsv01[160], din[6]@0x200; GpioValRegs = 16 B: val@0, set@4, rst@8. - * **[REVERSED]** Round-1 note claimed dout@0x140 — wrong, backlight never - * fired in any round; read back from the device-era header. */ + * **[REVERSED]** Round-1 note claimed dout@0x140 — wrong; read back from + * the device-era header. */ #define GPIO_DOUT_SET(pin) (GPIO_BASE + 0x100 + ((pin) / 32) * 16 + 4) #define GPIO_DOUT_RST(pin) (GPIO_BASE + 0x100 + ((pin) / 32) * 16 + 8) #define GPIO_DOUT_BIT(pin) (1u << ((pin) % 32)) @@ -276,16 +272,19 @@ static void wr32(u64 addr, u32 val) *(volatile u32 *)addr = val; } -/* ---- main checkpoint sequence ------------------------------------------- */ -/* - * The krane panel (BOE TV101WUM_NL6) is 1200x1920 xRGB8888 — the menu - * buffer depthcharge scanned out (OVL_L0_ADDR, programmed from the LBIO - * record's physical_address) has exactly that geometry. So painting the - * parsed LBIO pa IS repainting the still-registered scanout buffer. - * Note: we deliberately never READ OVL registers — MMIO reads to a gated - * display module can data-abort (qemu -M virt aborts on such reads too, - * which killed an earlier stage-0 design); writes are always safe. - */ +/* The scanout framebuffer address depthcharge actually used: programmed + * into OVL_L0_ADDR by mtk_display_init() and left there by stop(). This + * is ground truth — the LBIO record may disagree. */ +static u32 read_scanout_addr(void) +{ + return rd32le((const void *)(DISP_OVL0_BASE + DISP_REG_OVL_L0_ADDR)); +} + +/* Plausible MT8183 DRAM address (qemu: unmapped read yields 0 / -1). */ +static int plausible_fb(u32 a) +{ + return a >= 0x40000000u; +} static void blink_backlight(int times) { @@ -302,11 +301,9 @@ static void blink_backlight(int times) static void stage0(void) { /* - * First instructions after entry, before any parsing. On this - * firmware the OVL is never stopped and the backlight never disabled - * (display_init_required() is false — see RESEARCH.md Round 5), so - * these writes are redundant; they exist to keep the stub correct if - * a future firmware does tear the display down. + * First instructions after entry, before any parsing: undo the + * display teardown (OVL engines + backlight) so that anything we + * paint is visible. No MMIO reads here. */ wr32(DISP_OVL0_BASE + DISP_REG_OVL_EN, 1); wr32(DISP_OVL0_BASE + DISP_REG_OVL0_2L_EN, 1); @@ -317,20 +314,19 @@ static void stage0(void) /* ---- failure diagnostics ------------------------------------------------- * * The parse failed. Paint the failing values as bit-bands directly into - * the scanout buffer (DRAM address 0 — the OVL is still scanning it; this - * is exactly where depthcharge's own clear_screen() erased the menu). - * No parse result is needed to know that address. Format: one row per + * the live scanout buffer (read from OVL_L0_ADDR — the same buffer the + * menu was displayed from, and the one clear_screen() blackened at + * handoff). No parse result is needed to find it. Format: one row per * u32 word, 32 cells of 32x32 px, MSB first, white=1 / black=0, rows - * every 64 px. The user photographs the screen; the values are decoded - * offline. Marker word 0xC0DE00xx identifies the failure stage. + * every 64 px. Photograph the panel; decode offline. Word 0 is the + * marker 0xC0DE00xx identifying the failure stage. */ -#define DIAG_FB_BASE 0x0ull /* scanout address on this fw */ -#define DIAG_FB_WIDTH 1200 /* menu geometry, 32bpp xRGB */ +#define DIAG_FB_WIDTH 1200 /* menu geometry, 32bpp xRGB */ -static void render_diag(const u32 *d, int n) +static void render_diag(u32 base, const u32 *d, int n) { - volatile u32 *fb = (volatile u32 *)DIAG_FB_BASE; + volatile u32 *fb = (volatile u32 *)(u64)base; for (int i = 0; i < n; i++) { u32 y0 = 40 + (u32)i * 64; @@ -356,11 +352,15 @@ static void fail_dump(u32 marker, const u32 *words, int n) diag[i] = 0; diag[0] = marker; - for (int i = 0; i < n && i < 15; i++) + for (int i = 0; i < n && i < 14; i++) diag[1 + i] = words[i]; - blink_backlight(5); /* audible-free "I ran" signal */ - render_diag(diag, 16); + blink_backlight(5); /* "I ran" signal */ + + u32 l0 = read_scanout_addr(); + if (plausible_fb(l0)) + diag[15] = l0; + render_diag(l0, diag, 16); halt(); } @@ -390,21 +390,29 @@ void cmain(u64 dtb) fail_dump(0xC0DE0001u, w, 6); } + u32 l0 = read_scanout_addr(); + int rc = find_framebuffer(lbio_addr, &fb); if (rc) { /* Dump the LBIO location + raw table header + raw words at - * several offsets, exactly as found in memory. */ + * several offsets + the live scanout address. */ const volatile u32 *t = (const volatile u32 *)lbio_addr; - u32 w[12] = { + u32 w[13] = { (u32)lbio_addr, (u32)(lbio_addr >> 32), lbio_size, t[0], t[1], t[5], /* magic, header_bytes, entries */ t[6], t[7], /* rec0 tag, size (if hb=24) */ t[8], t[9], /* raw words at +32, +36 */ t[16], t[17], /* raw words at +64, +68 */ + l0, }; - fail_dump(0xC0DE0002u | (u32)rc, w, 12); + fail_dump(0xC0DE0002u | (u32)rc, w, 13); } + /* The LBIO record may not carry the real scanout address; the OVL + * L0_ADDR register does (depthcharge programmed it for the menu). */ + if (!fb.pa && plausible_fb(l0)) + fb.pa = l0; + /* Checkpoint 1: red — parsed; LBIO framebuffer record is sane. */ checkpoint(&fb, 1, 0, 0); /* red */ /* Checkpoint 2: yellow. */