Files
krane-fb-stub/qemu_test.py
T
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

145 lines
4.7 KiB
Python
Executable File

#!/usr/bin/env python3
"""
End-to-end smoke test for krane-fb-stub under qemu-system-aarch64.
- test.dtb: krane-sku176.dtb with a /firmware/coreboot node added (as
depthcharge's fixup produces), reg pair 1 -> synthetic LBIO table at
0x45000000, written into guest RAM by a qemu loader device.
- The LBIO table describes a 64x16 xrgb framebuffer at 0x45200000.
- The stub paints red -> yellow -> green -> blue, ~2s apart, into that buffer.
We sample the framebuffer color via QEMU monitor pmemsave snapshots and
verify the full checkpoint sequence plus final hold-on-blue.
Usage: python3 qemu_test.py
"""
import os
import select
import socket
import struct
import subprocess
import time
LBIO_ADDR = 0x45000000
FB_ADDR = 0x45200000
FB_X, FB_Y, FB_BPL = 64, 16, 256
RED, YELLOW, GREEN, BLUE = 0x00FF0000, 0x00FFFF00, 0x0000FF00, 0x000000FF
def build_lbio_table():
t = bytearray(24 + 48)
t[0:4] = b"LBIO"
t[4:8] = struct.pack("<I", 24) # header_bytes
t[20:24] = struct.pack("<I", 1) # table_entries
t[24:28] = struct.pack("<I", 0x12) # LB_TAG_FRAMEBUFFER
t[28:32] = struct.pack("<I", 40) # record size
t[32:40] = struct.pack("<Q", FB_ADDR)
t[40:44] = struct.pack("<I", FB_X)
t[44:48] = struct.pack("<I", FB_Y)
t[48:52] = struct.pack("<I", FB_BPL)
t[52] = 32 # bits_per_pixel
t[53], t[54] = 16, 8 # red
t[55], t[56] = 8, 8 # green
t[57], t[58] = 0, 8 # blue
return bytes(t)
def make_test_dtb():
subprocess.run(["cp", "krane-sku176.dtb", "test.dtb"], check=True)
subprocess.run(["dtc", "-I", "dtb", "-O", "dts", "test.dtb", "-o", "test.dts"],
check=True)
with open("test.dts", "a") as f:
f.write("""
&{/} {
firmware {
#address-cells = <0x02>;
#size-cells = <0x02>;
ranges;
coreboot {
compatible = "coreboot";
reg = <0x00 0x45000000 0x00 0x380
0x00 0x45100000 0x00 0x127000>;
};
};
};
""")
subprocess.run(["dtc", "-I", "dts", "-O", "dtb", "test.dts", "-o", "test.dtb"],
check=True)
out = subprocess.run(["dtc", "-I", "dtb", "-O", "dts", "test.dtb"],
capture_output=True, text=True).stdout
assert "reg = <0x00 0x45000000 0x00 0x380" in out, "coreboot reg missing"
print("test.dtb ready (with /firmware/coreboot)")
def main():
make_test_dtb()
with open("lbio.bin", "wb") as f:
f.write(build_lbio_table())
parent, child = socket.socketpair()
qemu = subprocess.Popen(
["qemu-system-aarch64", "-M", "virt", "-m", "1024",
"-nographic", "-serial", "null", "-monitor", "stdio", "-cpu", "max",
"-kernel", "krane-fb-stub.bin", "-dtb", "test.dtb",
"-device", f"loader,file=lbio.bin,addr=0x{LBIO_ADDR:x},force-raw=on"],
stdin=child.fileno(), stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
child.close()
time.sleep(2)
def drain():
out = b""
while select.select([qemu.stdout], [], [], 0.05)[0]:
c = os.read(qemu.stdout.fileno(), 65536)
if not c:
break
out += c
return out
drain()
def pmem(addr, size, path):
if os.path.exists(path):
os.remove(path)
parent.sendall(f'pmemsave 0x{addr:x} {size} "{path}"\n'.encode())
for _ in range(60):
drain()
if os.path.exists(path) and os.path.getsize(path) == size:
return open(path, "rb").read()
time.sleep(0.1)
raise RuntimeError(path)
fails = 0
def check(name, ok, detail=""):
nonlocal fails
print(f"{name:34s} {'PASS' if ok else 'FAIL'} {detail}")
if not ok:
fails += 1
# Snapshot #1 at ~6s wall time (yellow window; qemu boot eats ~1s of
# the first 2s red window, so 6s lands solidly in yellow), #2 at ~12s
# (well into the held blue).
time.sleep(1)
t3 = pmem(FB_ADDR, FB_X * FB_Y * 4, "/tmp/fb_t3.bin")
time.sleep(4)
t9 = pmem(FB_ADDR, FB_X * FB_Y * 4, "/tmp/fb_t9.bin")
parent.sendall(b"quit\n")
qemu.wait(timeout=10)
def px(d, i):
return struct.unpack_from("<I", d, i * 4)[0]
N = FB_X * FB_Y
check("t=3s(stub) yellow", px(t3, 0) == 0x00FFFF00, f"(0x{px(t3,0):08x})")
check("t=3s(stub) last yellow", px(t3, N - 1) == 0x00FFFF00, f"(0x{px(t3,N-1):08x})")
check("t=9s(stub) first px blue", px(t9, 0) == 0x000000FF, f"(0x{px(t9,0):08x})")
check("t=9s(stub) last px blue", px(t9, N - 1) == 0x000000FF, f"(0x{px(t9,N-1):08x})")
print("\nALL PASS" if not fails else f"\nFAILED: {fails}")
raise SystemExit(1 if fails else 0)
if __name__ == "__main__":
main()