feat(build): QEMU boots the stick; the data stick over USB

qemu-stick boots the amd64 stick image (build/boards/pc/lithos-pc.img,
copied per run) as a USB drive on q35, exactly as hardware would.
DATASTICK=1 adds the data stick to any qemu run as a second USB drive.
qemu_session.py gains --target and the stick_boot and datastick modes.

Checks: stick_boot (the stick's own identity partition attaches, headless
on a blank identity); datastick on amd64/aarch64/riscv64 (both drives
found, the data stick's 500 MiB identity attaches); the seven GPT modes,
the identity-gate and increment 1 sessions, emergency; the suite; three
acceptance boots to [zuse@Hera] ok> with POST 538/538 and
dict_hash=0xfec8c47cfc6f437f; test-32 0 failures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-27 16:31:27 -04:00
co-authored by Claude Opus 5.5
parent e177c5c6b3
commit c416c805cb
66 changed files with 5147 additions and 5 deletions
+33 -2
View File
@@ -44,6 +44,10 @@ the stick's, plan 2026-10-26-stick-1-screen-and-keyboard.md):
gpt_read_error the raw Zuse drive with reads of its sector 0 failing (blkdebug): not used,
said so, not attached
virtio_kbd aarch64/riscv64: QEMU's virtio keyboard types 7 ok
stick_boot --target qemu-stick (amd64): the PC stick booted as a USB stick through OVMF; its
blank identity partition attached (500 MiB, not the 2 GiB disk); headless
datastick --make "DATASTICK=1 ...": the data stick over USB beside Zuse's drive; Zuse
still logs in; the data stick's identity partition attached
Needs only Python 3. One QEMU at a time (.claude/CLAUDE.md).
"""
@@ -59,13 +63,14 @@ ap.add_argument("arch")
ap.add_argument("--label", default=None)
ap.add_argument("--make", default="")
ap.add_argument("--no-zuse", action="store_true")
ap.add_argument("--target", default="qemu") # qemu-stick: boot the PC stick (amd64)
a = ap.parse_args()
NO_ZUSE_MODES = {"gpt_zuse", "gpt_hotplug", "gpt_foreign", "gpt_bad_crc", "gpt_bad_entries", "gpt_no_header",
"gpt_read_error", "headless", "hotplug", "midpull", "waitpull", "typeahead", "emergency",
"alttab_after_logout", "alttab_before_pull", "emergency_pull",
"screen_dark_until_login", "screen_console_after_login", "screen_dark_after_logout",
"kbd_typeahead"}
"kbd_typeahead", "stick_boot"}
label = a.label or f"{a.mode}-{a.arch}"
work = os.path.join(ROOT, "build", "qemu-session", label)
shutil.rmtree(work, ignore_errors=True)
@@ -76,7 +81,7 @@ art = os.path.join(work, "artemis.img")
zimg = os.path.join(work, "zuse-thumb-ident.img")
attach_zuse = not (a.no_zuse or a.mode in NO_ZUSE_MODES)
make_args = ["make", "-f", "kernel/Makefile", f"ARCH={a.arch}", "clean", "qemu", f"ARTDISK={art}",
make_args = ["make", "-f", "kernel/Makefile", f"ARCH={a.arch}", "clean", a.target, f"ARTDISK={art}",
f"ZUSEDISK={zimg if attach_zuse else ''}"] + shlex.split(a.make)
if a.mode == "v3_keys":
make_args.append("KERNEL_ARGS=system=v3")
@@ -285,6 +290,32 @@ elif m == "zuse":
type_line("3 4 + .")
check("7 ok, and a prompt", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()) is not None)
elif m in ("stick_boot", "datastick"):
# a 500 MiB identity partition: about 380000 user blocks (64 MiB gave 48724); the whole
# 2 GiB stick would be over 1.5 million
def ident_attached():
# "use", not "user": the kernel cuts log messages at 63 characters (LOG_LINE_MAX 64,
# v3/include/vm.h), and with a seven-digit LBN the attach line ends "(383542 use"
users = [int(u) for u in re.findall(r"blk: disk .*?\((\d+) use", text())]
return any(300000 < u < 512000 for u in users), users
if m == "stick_boot":
check("System: StarForth v4", re.search(r"System: StarForth v4", text()) is not None)
time.sleep(20)
found, users = ident_attached()
check(f"the stick's identity partition attached, not the disk (user blocks {users})", found)
check("no ok> for 30 s (a blank identity: headless)", not wait_for(ANYPROMPT, 30))
else:
check("Zuse: identity confirmed", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120))
check("[zuse@Hera] ok>", wait_for(PROMPT, 60))
n = count(PROMPT)
type_line("3 4 + .")
check("7 ok", wait_for(PROMPT, 60, n) and re.search(r"7\s+ok", text()[-400:]) is not None)
check(f"two USB drives found ({count(r'blkio_usb: MSC device ready')})",
count(r"blkio_usb: MSC device ready") >= 2)
found, users = ident_attached()
check(f"the data stick's identity partition attached (user blocks {users})", found)
check("no fault", count(r"EXCEPTION|PANIC|panic") == 0)
elif m == "hotplug":
check("no ok> for 20 s with no drive", not wait_for(ANYPROMPT, 20))
add_zuse(1)