Files
krane-fb-stub/krane-fb-stub-agent-prompt.md
vhaudiquet 64e19c213a krane-fb-stub: arm64 framebuffer test payload for depthcharge
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.
2026-08-29 19:21:28 +02:00

200 lines
9.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Task: Build and test a minimal ARM64 "hello framebuffer" stub for a Lenovo IdeaPad Duet (MT8183 / Krane)
## Goal
Produce the smallest possible standalone ARM64 binary that depthcharge (the
ChromeOS firmware bootloader) can load as if it were a Linux kernel, which
does nothing but locate the already-initialized boot-splash framebuffer and
fill it with solid colors at distinct checkpoints. This validates the whole
depthcharge → custom-payload pipeline before any real U-Boot bring-up work
begins. **No U-Boot involved yet** — this is a from-scratch freestanding
binary, as small and dependency-free as possible.
## Hardware / firmware facts already confirmed on this exact device — do not re-derive these
- Device: Lenovo IdeaPad Duet Chromebook. SoC: MediaTek MT8183.
Board: Google "krane", specifically **krane sku176**.
- Confirmed from the live devicetree `/firmware/coreboot` node on this exact
unit (dumped via `dtc -I dtb -O dts` on `/sys/firmware/fdt` under the
currently-running kernel):
```
firmware {
ranges;
coreboot {
ram-code = <0x06>;
sku-id = <0xb0>; /* = 176 decimal, confirms sku176 */
board-id = <0x06>;
reg = <0x00 0xffed9000 0x00 0x380 0x00 0xffed9000 0x00 0x127000>;
compatible = "coreboot";
};
};
```
With `#address-cells = 2, #size-cells = 2`, this `reg` decodes as two
(address, size) pairs:
- **coreboot table (LBIO)**: address `0xffed9000`, size `0x380` (896 bytes)
- **CBMEM area**: address `0xffed9000`, size `0x127000` (~1.15 MB)
- Firmware boot chain on this device: Boot ROM → coreboot → TF-A BL31 →
depthcharge → "kernel" (whatever is packed into the ChromeOS kernel
partition). Reference: https://trustedfirmware-a.readthedocs.io/en/latest/plat/mt8183.html
- Partition layout on `/dev/mmcblk0`:
- `p1` = type "ChromeOS kernel" (GUID `FE3A2A5D-4F32-41A7-B725-ACCC3285A309`),
small. This is what depthcharge actually boots — verified via
`futility vbutil_kernel --verify /dev/mmcblk0p1 --verbose`.
- `p2` = ext4, mounted `/boot` in the Ubuntu rootfs — **not read by firmware
at boot time**, just staging space.
- `p3` = ext4, Ubuntu root filesystem.
- The device currently shows the ChromeOS dev-mode boot menu (USB/internal
boot choice) before handoff, confirming coreboot's `panel_krane.c`
(https://github.com/coreboot/coreboot/blob/main/src/mainboard/google/kukui/panel_krane.c)
has already brought up the MIPI-DSI panel and a linear framebuffer is live
and DMA'd by the time any payload we supply would run.
- `dev_boot_usb` should be confirmed as `1` (`crossystem dev_boot_usb`) before
any USB-boot testing.
## Coreboot table format — verify against source before trusting field order
Reference header: `src/commonlib/include/commonlib/coreboot_tables.h` in
https://github.com/coreboot/coreboot (clone this repo locally and grep it —
do not trust field order from this prompt without checking).
Known so far:
```c
struct lb_header {
uint8_t signature[4]; /* "LBIO" */
uint32_t header_bytes;
uint32_t header_checksum;
uint32_t table_bytes;
uint32_t table_checksum;
uint32_t table_entries;
};
struct lb_record {
uint32_t tag;
uint32_t size;
};
#define LB_TAG_FRAMEBUFFER 0x12
struct lb_framebuffer {
uint32_t tag;
uint32_t size;
uint64_t physical_address; /* verify: coreboot uses lb_uint64_t, a
32-bit-aligned split hi/lo struct on
some versions — confirm packing */
uint32_t x_resolution;
uint32_t y_resolution;
uint32_t bytes_per_line;
uint8_t bits_per_pixel;
uint8_t red_mask_pos;
uint8_t red_mask_size;
uint8_t green_mask_pos;
uint8_t green_mask_size;
uint8_t blue_mask_pos;
uint8_t blue_mask_size;
uint8_t reserved_mask_pos;
uint8_t reserved_mask_size;
uint8_t orientation;
/* struct lb_framebuffer_flags flags; possible trailing padding —
confirm exact struct size against sizeof() in the real header */
};
```
**Action required**: before writing the parser, confirm (a) whether
`physical_address` is a plain `uint64_t` or coreboot's split `lb_uint64_t`
(hi/lo 32-bit halves) in the version you clone, and (b) the exact total
struct size/padding, by reading the header directly and/or writing a tiny
host-side C program that does `printf("%zu\n", sizeof(struct lb_framebuffer))`
against the real, included header.
## What the stub must do
1. Entry point conforms to the **arm64 Linux kernel boot protocol** (this is
what depthcharge expects to jump into): entered with MMU and D-cache off,
`x0` = physical address of a DTB blob, `x1``x3` = 0. The binary itself
must also carry a valid **arm64 "Image" header** (magic `ARM\x64` at byte
offset 0x38, `code0`/`code1` branch-past-header instructions, `text_offset`,
`image_size`, etc.) — confirm the exact header layout against the Linux
kernel's `Documentation/arch/arm64/booting.rst` (or
`arch/arm64/kernel/head.S`) in a cloned `torvalds/linux` tree, since
depthcharge's loader is written to accept real Linux Image binaries and
the packing step below (`mkdepthcharge`) will otherwise choke or
misinterpret the payload.
2. Parse the DTB at `x0` (a small/partial hand-rolled parser is fine — you
only need to find one node — but using `libfdt` if it's easy to statically
link in a freestanding way is also acceptable) to locate `/firmware/coreboot`
and extract the first `reg` pair (LBIO table address + size). Do **not**
hardcode `0xffed9000`/`0x380` in the shipped stub logic — read them from
the DTB at runtime, since this is the general mechanism; the constants
above are only for your own manual verification/testing during
development.
3. Verify the `"LBIO"` signature at that address, walk `lb_record` entries
using `header_bytes`/`table_entries`, find the one with
`tag == LB_TAG_FRAMEBUFFER (0x12)`.
4. Using `physical_address`, `x_resolution`, `y_resolution`, `bytes_per_line`,
`bits_per_pixel`: fill the entire visible framebuffer with a single solid
color, per the checkpoint sequence below, with a fixed delay (spin-loop is
fine, no timer driver needed) between each so a human watching the screen
can see each stage.
## Checkpoint color convention (in order, ~2 seconds each, hold last color forever)
1. **Red** — stub entered and running (proves depthcharge jumped here
correctly and code is executing).
2. **Yellow** — DTB parsed, `/firmware/coreboot` node found.
3. **Green** — LBIO signature verified, framebuffer record found.
4. **Blue** — framebuffer fill of the *previous* checkpoint colors succeeded
(i.e., blue only appears if red/yellow/green were each visibly, correctly
drawn — this is your final "everything worked" signal).
5. If any step fails, **halt on the last successful color** (infinite loop,
do not proceed) rather than showing a "fail" color — the *stuck* color
itself tells us which stage broke.
## Build and packaging
- Toolchain: `aarch64-linux-gnu-gcc` (freestanding: `-ffreestanding -nostdlib
-static`, no libc), or hand-written assembly if simpler given the small
scope.
- Package with `mkdepthcharge` (from https://github.com/alpernebbi/depthcharge-tools,
wraps `mkimage`/`vbutil_kernel`) targeting **arm64**, using the ChromeOS
devkeys (`/usr/share/vboot/devkeys/kernel.keyblock` +
`kernel_data_key.vbprivk`) already trusted by this device's firmware.
Confirm against `mkdepthcharge --help` and its source whether a raw
freestanding binary needs to be wrapped in a FIT/uImage first (via
`mkimage`) or can be passed directly — do not assume.
- **Do not pass a real DTB for depthcharge to hand to us for real** — we want
depthcharge's own normal DTB selection behavior (it already knows how to
pick/pass the correct krane DTB when booting a "kernel"), so build the FIT
the same way `mkdepthcharge` would for a normal kernel+dtb pair, just with
our stub binary in place of vmlinuz and no initramfs needed.
## Safety rules — non-negotiable
1. **Never write the packed image directly to `/dev/mmcblk0p1`.** Every test
cycle targets a **USB stick** first (`dev_boot_usb=1` must be set; boot via
the ChromeOS dev-mode menu's USB option). Only after a stub has been
confirmed working from USB — visually, by a human watching the screen —
should writing to internal storage even be discussed, and that should be a
separate, explicitly-confirmed step, not something done automatically as
part of a build/test loop.
2. Keep the current, known-working pmOS kernel image backed up on the USB
stick itself (not only on the internal eMMC), so recovery never depends on
anything that might get overwritten.
3. If a build step, packing step, or the arm64 Image header requirements are
ambiguous or contradicted by what you find in source, **stop and report
the ambiguity** rather than guessing and flashing — getting the on-disk
format wrong here produces a hang with zero diagnostic output, which is
the one failure mode this whole exercise exists to avoid.
4. Treat every numeric fact in this prompt (struct layouts, offsets, table
address) as "reported, needs verification against source," not as
ground truth — cross-check each against the actual cloned coreboot/Linux
trees before relying on it in code.
## Deliverables
1. Stub source (assembly + minimal C, or pure assembly), with comments
explaining each stage against the checkpoint list above.
2. Build script producing the raw binary.
3. Packing command(s) producing a bootable USB disk image.
4. A short README noting which of the "verify against source" items above
were checked, what was found, and any deviations from this prompt's
assumptions.