/* sk_v4_identity.c -- the identity gate for the v4 boot: no identity drive, * headless; Zuse's minted thumbdrive, a console. * docs/v4.0.0/plans/2026-10-26-engine-step4-identity-gate.md, ENGINE.md 2. * * As v3 (kernel_main.c, repl.c's sk_repl_headless_wait()/sk_repl_idle(), * capsule_zuse_boot.c), done by the kernel with no v3 VM behind it: Artemis * and Hera are the kernel's (ENGINE.md 2, Q1), so a drive that arrives is * given to the block subsystem here, and Zuse's session is kept here, for * the first node, Hera. The check that the drive is hers is v3's own * (capsule_zuse_boot_check()). * * Not here (the plan's "not in this gate"): WIREBIND identities, EJECT, * ACL-ZUSE-BOOT, and genesis -- which writes Artemis's disk, and the v4 boot * does not write it yet (Phase 5). */ #include "starkernel/v4/sk_v4_identity.h" #include "starkernel/console.h" #include "starkernel/arch.h" #include "starkernel/apic.h" #include "starkernel/timer.h" #include "starkernel/kmalloc.h" #include "starkernel/xhci_driver.h" #include "starkernel/blkio_usb.h" #include "starkernel/homeblocks_sig.h" #include "starkernel/capsule_zuse_boot.h" #include "starkernel/kbd_input.h" #include "starkernel/gpt.h" #ifdef ARCH_AMD64 #include "starkernel/ioapic.h" #include "starkernel/i8042.h" #endif #include "block_subsystem.h" #include #define SK_V4_IDLE_BEAT 100u /* ticks between idle beats, v3's SK_IDLE_BEAT_INTERVAL (1 s at 100 Hz) */ /* ZUSE'S STATE, for Hera. The cert is installed once and never replaced * (v3's vm_zuse_cert_install(), one-way); the session ends when her drive * leaves and begins again when it comes back. */ static int g_zuse_session; static int g_zuse_cert_installed; static uint8_t g_zuse_seed[32], g_zuse_pubkey[32]; static uint8_t g_zuse_root_pubkey[32]; static int g_zuse_root_known; static struct blkio_dev *g_zuse_dev; /* A drive in a USB slot: the raw disk; its StarshipOS identity partition as * a device of its own, when it has a GPT with one; and the one of the two * given to the block subsystem, if either was (a GPT disk is never given * whole: docs/v4.0.0/plans/2026-10-26-stick-2-gpt-reader.md, ruled). */ typedef struct { blkio_dev_t raw; blkio_dev_t part; gpt_part_dev_t part_state; blkio_dev_t *used; } sk_v4_drive_t; /* One per slot, per xHCI controller, as repl.c's g_usb_ctrl. */ static struct { sk_v4_drive_t *slots; uint32_t count; } g_usb[XHCI_MAX_CONTROLLERS]; #define SK_V4_GPT_PARTS 16u static gpt_part_t g_gpt_parts[SK_V4_GPT_PARTS]; static uint64_t g_last_beat; static int g_said_no_genesis; static const char *sk_v4_user_prefix(void) { return g_zuse_session ? "zuse" : (const char *)0; } int sk_v4_identity_present(void) { return g_zuse_session; } void sk_v4_identity_boot(void) { unsigned xi; static xhci_dev_t xhci_devs[XHCI_MAX_CONTROLLERS]; /* Zuse's public key, from the genesis marker on Artemis's disk */ g_zuse_root_known = capsule_zuse_boot_root_pubkey(g_zuse_root_pubkey) == 0; /* every xHCI controller, as kernel_main.c's v3 path: a drive already in * a port is found by the bring-up's own port scan */ for (xi = 0; xi < XHCI_MAX_CONTROLLERS; xi++) { int rc = xhci_find_and_map(&xhci_devs[xi], xi); if (rc == 1) break; if (rc == 0) (void)xhci_bringup(&xhci_devs[xi]); } /* the heartbeat, as kernel_main.c's v3 path starts it */ console_println("Starting heartbeat..."); apic_timer_start(); #ifdef ARCH_AMD64 i8042_drain_stale(); ioapic_unmask_legacy_irq(1); console_println("I/O APIC: keyboard IRQ1 unmasked"); #endif arch_enable_interrupts(); console_println("Heartbeat running."); g_last_beat = heartbeat_ticks(); } /* A drive that has arrived: given to the block subsystem (Artemis's, the * kernel's), then, if it is Zuse's, her session begins. */ static int sk_v4_drive_arrived(sk_v4_drive_t *d) { blkio_dev_t *dev = &d->raw; homeblocks_sig_t sig; homeblocks_sig_result_t sig_rc; uint8_t seed[32], pubkey[32]; zuse_drive_t which; unsigned nparts; int gpt_rc, idx; /* GPT first (docs/v4.0.0/STICK.md 4): a disk with a valid GPT is used * through its StarshipOS identity partition only; one with no such * partition, a damaged table or an unreadable first block is not used at * all; a disk with no GPT is read raw, as an identity drive always has * been. */ d->used = (blkio_dev_t *)0; gpt_rc = gpt_read(&d->raw, g_gpt_parts, SK_V4_GPT_PARTS, &nparts); if (gpt_rc == GPT_BAD) { console_println("GPT: a USB drive has a damaged partition table -- not used"); return 1; } if (gpt_rc == GPT_EIO) { console_println("GPT: a USB drive could not be read -- not used"); return 1; } if (gpt_rc == GPT_OK) { idx = gpt_find(g_gpt_parts, nparts, gpt_type_starship_identity); if (idx < 0) return 1; /* no StarshipOS partition: not ours to use */ if (gpt_part_open(&d->part, &d->part_state, &d->raw, &g_gpt_parts[idx]) != BLKIO_OK) { console_println("GPT: a USB drive's identity partition is not on 1 KiB blocks -- not used"); return 1; } dev = &d->part; } sig_rc = homeblocks_sig_check(dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig); if (blk_subsys_attach_device(dev) != BLK_OK) { console_println("xhci: USB MSC block-subsystem attach failed"); return 0; } d->used = dev; if (g_zuse_session) return 1; which = capsule_zuse_boot_check(dev, sig_rc, &sig, seed, pubkey); if (which == ZUSE_DRIVE_GENESIS) { if (!g_said_no_genesis) { console_println("Zuse: no genesis on this disk -- the system stays headless"); g_said_no_genesis = 1; } return 1; } if (which != ZUSE_DRIVE_HERS) return 1; console_set_vm_name("Hera"); console_set_user_prefix_provider(sk_v4_user_prefix); /* the screen is the console from here (docs/v4.0.0/STICK.md 2) */ console_fb_mirror(1); console_fb_enable_ttf(); console_println("Zuse: identity confirmed from attached thumbdrive"); if (!g_zuse_cert_installed) { memcpy(g_zuse_seed, seed, 32); memcpy(g_zuse_pubkey, pubkey, 32); g_zuse_cert_installed = 1; } memcpy(g_zuse_root_pubkey, pubkey, 32); /* as v3's install_and_activate() */ g_zuse_root_known = 1; g_zuse_dev = dev; g_zuse_session = 1; return 1; } static void sk_v4_drive_left(sk_v4_drive_t *d) { blkio_dev_t *dev = d->used; if (!dev) return; /* never given to the block subsystem */ d->used = (blkio_dev_t *)0; blk_subsys_detach_device(dev); if (dev != g_zuse_dev) return; g_zuse_session = 0; g_zuse_dev = (struct blkio_dev *)0; /* nothing of her session is left on the screen; it is dark again -- * except in a build with EMERGENCY_CONSOLE_ENABLED, whose console needs * no login and goes on running (the stick's increment 1 final review) */ #if !EMERGENCY_CONSOLE_ENABLED console_fb_clear(); console_fb_mirror(0); #endif console_println("Zuse: session ended -- reattach to re-authenticate"); } /* One idle beat: the xHCI event ring, then every drive that has arrived or * left since the last (repl.c's sk_repl_idle(), its USB part). */ static void sk_v4_idle(void) { uint32_t xc, slot; console_ensure_line_start(); { uint64_t tx_before = console_tx_count(); xhci_poll_events(); for (xc = 0; xc < xhci_dev_count() && xc < XHCI_MAX_CONTROLLERS; xc++) { xhci_dev_t *xdev = xhci_dev_at(xc); if (!xdev) continue; if (!g_usb[xc].slots || g_usb[xc].count < xdev->max_slots + 1) { size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(sk_v4_drive_t); sk_v4_drive_t *fresh = (sk_v4_drive_t *)kmalloc_aligned(bytes, 64); if (!fresh) continue; memset(fresh, 0, bytes); g_usb[xc].slots = fresh; g_usb[xc].count = xdev->max_slots + 1; } for (slot = 1; slot <= xdev->max_slots; slot++) { xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot); if (!ms || !ms->bot_msc_attach_pending) continue; ms->bot_msc_attach_pending = 0; if (blkio_usb_open_msc(&g_usb[xc].slots[slot].raw, xdev, slot) == 0 && sk_v4_drive_arrived(&g_usb[xc].slots[slot])) ms->bot_msc_attached = 1; } for (slot = 1; slot <= xdev->max_slots; slot++) { xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot); if (!ms || !ms->bot_msc_detach_pending) continue; ms->bot_msc_detach_pending = 0; sk_v4_drive_left(&g_usb[xc].slots[slot]); } } if (console_tx_count() == tx_before) console_cancel_deferred_line_start(); } } void sk_v4_identity_beat(void) { uint64_t now; heartbeat_service(); now = heartbeat_ticks(); if (now - g_last_beat >= SK_V4_IDLE_BEAT) { g_last_beat = now; sk_v4_idle(); } } void sk_v4_identity_check_now(void) { g_last_beat = heartbeat_ticks(); sk_v4_idle(); } /* What was typed at a headless machine -- on the serial line or any * keyboard -- is no one's: it is read and dropped, so that it does not run * as whoever logs in next (the identity gate's final review, item 4, * 2026-10-26). */ static void sk_v4_discard_input(void) { sk_input_drop(); } void sk_v4_identity_wait(void) { while (!g_zuse_session) { sk_v4_discard_input(); sk_v4_identity_beat(); arch_relax(); } sk_v4_discard_input(); }