stub: use OVL_L0_ADDR as scanout ground truth; render diag there
Round 7: the Round-6 bit-band dump (painted at DRAM address 0) never reached the panel — device-era libpayload cbgfx_init() rejects physical_address == 0, so the menu could only have been drawn through a non-zero runtime framebuffer address. Round 5's address-0 theory and its 'OVL never stopped' corollary are [REVERSED]. The authoritative scanout address is OVL_L0_ADDR (0x14008F40), programmed by mtk_display_init() for the menu and left in place by stop(). The stub now reads it (device-only; qemu never takes these paths): - success: if the LBIO record pa is 0, substitute the L0_ADDR scanout before painting checkpoints - failure: fail_dump renders the bit-band dump at the scanout address; word 15 carries the raw L0_ADDR value Full-file rewrite to untangle accumulated edit damage. host_test and qemu_test pass; payload cebb9b1b... flashed and verified.
This commit is contained in:
+34
@@ -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`.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user