# 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.