Minimal freestanding arm64 binary that depthcharge boots as a kernel: parses depthcharge's /firmware/coreboot DTB node, walks the coreboot table, finds LB_TAG_FRAMEBUFFER, and paints red/yellow/green/blue checkpoints (~2s each) into the live boot-splash framebuffer. Verified against coreboot tables header, Linux arm64 booting.rst, and depthcharge's fit.c/boot64.c. Host parser tests pass against the live /sys/firmware/fdt; full red->yellow->green->blue sequence verified end-to-end under qemu-system-aarch64; packed image verifies with the ChromeOS devkeys.
10 KiB
krane-fb-stub
Minimal arm64 "hello framebuffer" payload for the Lenovo IdeaPad Duet
Chromebook (MT8183, google,krane sku176). It validates the
depthcharge → custom-payload pipeline before any U-Boot bring-up work:
it locates the boot-splash framebuffer handed over by coreboot/depthcharge
and paints solid colors at checkpoints.
Checkpoint convention
Parsed first (instantaneous), then drawn — see Deviations:
| stage | success signal | failure signal |
|---|---|---|
| 1 | screen RED ~2s | screen keeps depthcharge output — DTB parse failed |
| 2 | YELLOW ~2s | (yellow implies DTB parsed) |
| 3 | GREEN ~2s | stuck on yellow — LBIO/fb record bad |
| 4 | BLUE, held forever | stuck on green — fb fill faulted |
If the stub never runs at all (header rejected, load failed), depthcharge falls back to its own screen — no color ever appears.
Files
stub.S— 64-byte arm64 Image header + entry stub (own stack, no PLT).main.c— FDT walker, coreboot-table walker, framebuffer painter.linker.ld— flat layout,.bssasserted empty (image_size must cover all state because depthcharge does not zero-fill beyond the file).build.sh— build + relocation/bss checks +image_sizepatch.host_test.c— host-compiled test of both parsers against the live/sys/firmware/fdtand a synthetic LBIO table.qemu_test.py— end-to-end smoke test underqemu-system-aarch64(synthetic coreboot table + DTB; asserts red→yellow→green→blue timeline).build-payload.sh— packskrane-fb-stub.bin+krane-sku176.dtbinto a signed depthcharge image via mkdepthcharge.krane-fb-stub.bin— raw arm64 Image (18 KB).krane-fb-stub-payload.bin— packed, devkey-signed depthcharge image, verified withfutility vbutil_kernel --verify.krane-sku176.dtb— the exact krane sku176 DTB from the currently-booting FIT on this unit (extracted withdumpimagefrom the p1 FIT, config conf-27mt8183-kukui-krane-sku176).
Verification performed (per the prompt's "verify against source" list)
1. coreboot table layout — CHECKED, matches prompt
Cloned coreboot main; read
src/commonlib/include/commonlib/coreboot_tables.h:
physical_addressislb_uint64_t=__aligned(4) uint64_t(LB_ENTRY_ALIGN == 4). On little-endian AArch64 this is a plain 8-byte LE u64 at offset 8 ofstruct lb_framebuffer; the "hi/lo split" is only an alignment trick, not separate halves in memory.- Full struct is 40 bytes:
tag@0, size@4, physical_address@8, x_resolution@16, y_resolution@20, bytes_per_line@24, bits_per_pixel@28, red 29/30, green 31/32, blue 33/34, reserved 35/36, orientation@37, flags@38, pad@39. Verified by compiling asizeof/offsetofprobe against the real header on the host (sizeof == 40). lb_headermatches the prompt:signature[4] "LBIO", thenheader_bytes, header_checksum, table_bytes, table_checksum, table_entries(LE u32 each). Records start afterheader_bytes.LB_TAG_FRAMEBUFFER = 0x12confirmed.- Cross-check on this live unit: parsing the live
/sys/firmware/fdt(which contains depthcharge's injected/firmware/corebootnode) yields LBIO at0xffed9000, size0x380— matches the prompt.
2. arm64 Image header — CHECKED against Linux booting.rst
- 64-byte header, magic
0x644d5241("ARM\x64") at0x38, LE fields. code0must branch past the header; entry is at the image start withx0=DTB phys addr,x1..x3=0, MMU off, DAIF masked.image_sizemust be exact (non-zero), little-endian.- Cross-checked against depthcharge's consumer:
src/arch/arm/boot64.creads the same header (magic check,image_size,text_offsetasload_offsetfor KASLR slot math).
3. depthcharge behavior — CHECKED against chromiumos depthcharge source
src/arch/arm/boot64.c: image is decompressed/relocated to a random 2 MiB-aligned slot; the Image header magic is verified; entry is the first byte of the (relocated) image; x0 = flat DTB, x1..x3 = 0 (boot64_asm.Szeroes x1-x3, disables MMU,br x25).- => the stub must be position-independent (built
-fPIC, checked for zero dynamic relocations) and carry its own stack (done, 16 KiB inside the image). - Prompt assumption contradicted by source: a FIT config without an
FDT subimage is skipped (
fit.c: "config %s has no FDT, skipping"), and with no match depthcharge gives up. So the packed FIT does include a DTB — the real krane-sku176 DTB taken from the working FIT — while depthcharge still injects its own/firmware/corebootnode at boot viasrc/boot/coreboot.cinstall_coreboot_data(it adds#address-cells,#size-cells,ranges,compatible, and theregpairs to/firmware/coreboot). Our DTB is only a config-selection key; the runtime DTB the stub receives at x0 is depthcharge's fixed-up tree. - depthcharge injects
/firmware/corebootreg = (table addr, CBMEM addr) insrc/boot/coreboot.c; the krane root DTS uses#address-cells=2/#size-cells=2, so the first reg pair decodes to LBIO @0xffed9000 size 0x380 — matching the prompt. - Existing kernel p1 body confirmed to be a FIT (
d00dfeedat body offset 0), body load address 0x100000, so FIT (--format fit, the arm default in mkdepthcharge) is the right container.
4. mkdepthcharge input format — CHECKED, answers the prompt's question
mkdepthchargebuilds the FIT itself (mkimage -f auto -T kernel -O linux -C none -d <vmlinuz> -b <dtbs>, then patches the kernel subimage type tokernel_noload), then signs withvbutil_kernel.- A raw arm64 Image binary can be passed directly as
-d/VMLINUZ — no manual uImage wrapping. mkdepthcharge auto-detects arm64 from theARM\x64magic at file offset 0x38 and defaults to FIT format for arm. - Packed command (see
build-payload.sh):mkdepthcharge -A arm64 -o <out> -n <desc> -d krane-fb-stub.bin -b krane-sku176.dtb - The packed image verifies with the devkeys and carries one config
(
conf-1, description "krane-sku176") withkernel_noload+flat_dtsubimages, compressionnone— the same shape as the working kernel.
Deviations from the prompt (all deliberate, explained)
- Red-on-entry is physically impossible. The framebuffer address is only discoverable by parsing the DTB (depthcharge injects it) and the coreboot table. Parsing is sub-millisecond, so the sequence is: parse → red → yellow → green → blue. A pre-parse failure leaves depthcharge's dev-mode screen visible, which is a strictly better "stuck at stage N" indicator than a stuck color (a stuck color would hide which parse failed; an unpainted screen + depthcharge's own on-screen error identifies the stage).
- A DTB is included in the FIT (contrary to "no DTB"): this unit's depthcharge requires an fdt subimage in the chosen config. We embed the genuine krane sku176 DTB; depthcharge's fixup adds the coreboot node at runtime, exactly as the prompt's hardware facts describe.
- Colors are derived from the mask_pos/mask_size fields of the framebuffer record rather than hard-coded xRGB — works for any format the panel reports (krane reports 32bpp xRGB: red@16/8, green@8/8, blue@0/8).
Testing done (host)
host_test(host C): FDT parser against the LIVE/sys/firmware/fdt(finds LBIO @0xffed9000 size 0x380 — exact match to the prompt's manually-confirmed values), synthetic coreboot table layout test, color composition for all four checkpoints, and full-screen fill bounds (first/last pixel written, no overflow pastyres*bpl).- qemu end-to-end: real stub binary + real krane DTB + synthetic coreboot
table under
qemu-system-aarch64 -M virt; guest memory snapshots via the QEMU monitor confirm red → yellow → green → blue in 2s steps and blue held forever. All 4 checks pass (qemu_test.py). - Packed payload verified with
futility vbutil_kernel --verify(body verification succeeded, devkeys).
Firmware state confirmed on this unit
dev_boot_usb = 1— read directly from the device's VBNV flash region (RW_NVRAM @ 0x80000 in the 8 MB SPI, latest 16-byte vbnv blob at 0x81bb0; CRC8-vbnv valid;DEV_FLAGS_OFFSETbit0 set, signed-only off). crossystem could not run because distro flashrom lacks thehostprogrammer;flashrom -p internalworks and the blob was decoded from the read-back image.- Boot chain evidence: p1 body is a FIT with 27 configs including
mt8183-kukui-krane-sku176; depthcharge FIT config matching is bycompatiblefrom the config nodes, and our packed FIT's conf-1 carries that description (mt8183-kukui-krane-sku176) as its fdt subimage, so this unit's depthcharge (with kukui compat strings) selects it.
Build & pack
./build.sh # -> krane-fb-stub.bin (+ relocation/bss checks)
sh build-payload.sh # -> krane-fb-stub-payload.bin (signed, verifiable)
Flashing (USB only — safety rules honored)
Nothing has been written to internal storage. The next step is to write
krane-fb-stub-payload.bin to the USB stick's kernel partition
(/dev/sda1, current pmOS kernel partition on the stick — back that up
first), then boot the ChromeOS dev-mode menu → USB. The on-disk write step
is deliberately left for explicit human confirmation, per the task's
safety rules.
Known limitations / notes
- The stub never returns; it spins on WFE holding blue (or halts unpainted on a parse failure).
- No timer driver: delays use the architected generic timer registers (CNTFRQ/CNTPCT) — mandatory in the boot protocol anyway.
- No cache maintenance is done for the framebuffer writes: we enter with the D-cache off, and writes to the (DMA'd) display controller read physical memory; on this platform the splash buffer is already coherent.
- The
0x380/0xffed9000constants from the prompt are used ONLY in the host tests as expected values, never in the stub logic.