Work in progress on StarForth-v4.0.0; not yet accepted (plan 2026-10-29-stick-5, Task 1). - zuse_genesis_marker.h: the marker's version 2 -- the proxy's key and the real Zuse's -- beside version 1. - capsule_zuse_boot.c: capsule_zuse_boot_check_key() (the identity-record half of the check) and capsule_zuse_boot_genesis_mint() (the mint half of v3's genesis) split out; v3's two callers unchanged in behaviour. - sk_v4_marker.c: the v4 boot reads version 1 as a proxy with no real Zuse, and writes version 2. - sk_v4_identity.c: no marker and a blank identity partition -> genesis: mint, read back, then the marker; the proxy logs in. A marker with a real key: no session for the proxy (the fixed screen is Task 5). - qemu_session.py: boot()/shutdown(), so a mode can boot twice; modes genesis and stick_genesis; stick_boot now expects genesis and the prompt. Logs: genesis on amd64 before the change (both boots: "no genesis on this disk", FAIL) and after (minted, then recognised: PASS). The DoE CSV of 032532 is from a run whose QEMU never started (no display; its empty log directory was removed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
403 lines
16 KiB
C
403 lines
16 KiB
C
/* 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_key(), against the genesis marker's key).
|
|
*
|
|
* Artemis and Zuse come from one disk, this system's stick
|
|
* (docs/v4.0.0/STICK.md 4; plan 2026-10-27-stick-4-one-disk.md, Task 4): its
|
|
* Artemis partition is attached first -- the first disk slot, where Zuse's
|
|
* genesis marker is read -- then its identity partition is checked as an
|
|
* identity drive always has been. No other drive is used on v4; drives of
|
|
* users minted by this system's Zuse come with the users design after INSTALL.
|
|
*
|
|
* Genesis (plan 2026-10-29-stick-5, Task 1; STICK.md 6): a stick whose
|
|
* Artemis fence has no marker and whose identity partition is blank mints its
|
|
* own Zuse at its first boot -- the stick's proxy Zuse, full Zuse while no real
|
|
* Zuse exists -- reads her back, then writes the version 2 marker
|
|
* (sk_v4_marker.c) naming her.
|
|
*
|
|
* Not here (the plan's "not in this gate"): WIREBIND identities, EJECT,
|
|
* ACL-ZUSE-BOOT.
|
|
*/
|
|
#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/artemis_sig.h"
|
|
#include "starkernel/capsule_zuse_boot.h"
|
|
#include "starkernel/kbd_input.h"
|
|
#include "starkernel/gpt.h"
|
|
#include "starkernel/v4/sk_v4_marker.h"
|
|
#ifdef ARCH_AMD64
|
|
#include "starkernel/ioapic.h"
|
|
#include "starkernel/i8042.h"
|
|
#endif
|
|
#include "block_subsystem.h"
|
|
#include <string.h>
|
|
|
|
#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;
|
|
/* The genesis marker on the stick's Artemis fence (version 2: the proxy and
|
|
* the real Zuse), read when the stick's Artemis partition attaches. */
|
|
static sk_v4_marker_t g_marker;
|
|
/* The proxy is recognised but a real Zuse exists: no session for the proxy
|
|
* (STICK.md 6a; the fixed screen comes with the plan's Task 5). */
|
|
static int g_proxy_fallback;
|
|
|
|
/* A drive in a USB slot: the raw disk; when it is this system's stick, its
|
|
* StarshipOS identity and Artemis partitions as devices of their own; and
|
|
* which of them were given to the block subsystem (a 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; /* the identity partition, once attached */
|
|
blkio_dev_t art;
|
|
gpt_part_dev_t art_state;
|
|
blkio_dev_t *art_used; /* the Artemis partition, once attached */
|
|
} 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 const BootDisk *g_boot_disk; /* the loader's: the disk it booted from */
|
|
static int g_stick_in; /* a stick's Artemis partition is attached */
|
|
|
|
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(const BootDisk *boot_disk)
|
|
{
|
|
unsigned xi;
|
|
static xhci_dev_t xhci_devs[XHCI_MAX_CONTROLLERS];
|
|
|
|
/* Zuse's public key comes later: from the genesis marker on the stick's
|
|
* Artemis partition, once it is attached (sk_v4_drive_arrived) */
|
|
g_boot_disk = boot_disk;
|
|
|
|
/* 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();
|
|
}
|
|
|
|
/* Is the disk whose GPT was just read (gpt_read gave GPT_OK) this system's
|
|
* stick? It has both StarshipOS partitions, and it is the disk the loader
|
|
* booted from -- the same DiskGUID -- or, when the boot was not from a stick
|
|
* (an ISO, an ESP image), the first such disk while none is attached. */
|
|
static int sk_v4_is_this_stick(unsigned nparts)
|
|
{
|
|
uint8_t guid[16];
|
|
|
|
if (gpt_find(g_gpt_parts, nparts, gpt_type_starship_identity) < 0 ||
|
|
gpt_find(g_gpt_parts, nparts, gpt_type_starship_artemis) < 0)
|
|
return 0;
|
|
if (g_boot_disk && g_boot_disk->is_stick) {
|
|
gpt_disk_guid(guid);
|
|
return memcmp(guid, g_boot_disk->disk_guid, 16) == 0;
|
|
}
|
|
return !g_stick_in;
|
|
}
|
|
|
|
/* Zuse's session begins on @p dev: the console is hers, and the screen with
|
|
* it (docs/v4.0.0/STICK.md 2). @p line says how she was found. */
|
|
static void sk_v4_login(blkio_dev_t *dev, const uint8_t seed[32], const uint8_t pubkey[32],
|
|
const char *line)
|
|
{
|
|
console_set_vm_name("Hera");
|
|
console_set_user_prefix_provider(sk_v4_user_prefix);
|
|
console_fb_mirror(1);
|
|
console_fb_enable_ttf();
|
|
console_println(line);
|
|
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;
|
|
}
|
|
|
|
/* Genesis on the stick (STICK.md 6): the proxy Zuse minted onto the blank
|
|
* identity partition @p dev, read back, and only then named in the version 2
|
|
* marker on @p art's fence. A failure anywhere leaves no marker, and the
|
|
* system headless. Returns 1 when she is logged in. */
|
|
static int sk_v4_genesis(blkio_dev_t *dev, blkio_dev_t *art)
|
|
{
|
|
uint8_t seed[32], pubkey[32], rseed[32], rpub[32];
|
|
homeblocks_sig_t sig;
|
|
homeblocks_sig_result_t sig_rc;
|
|
uint32_t lbn, nblk;
|
|
|
|
/* Artemis takes the marker: its volume is formatted at build (STICK.md 4);
|
|
* confirmed here only because its signature checked out on attach, and a
|
|
* no-op on a volume already formatted */
|
|
if (blk_get_device_range(art, &lbn, &nblk) != BLK_OK || blk_subsys_confirm_format(lbn) != BLK_OK) {
|
|
console_println("Zuse: genesis mint failed");
|
|
return 0;
|
|
}
|
|
if (capsule_zuse_boot_genesis_mint(dev, seed, pubkey) != 0) {
|
|
console_println("Zuse: genesis mint failed");
|
|
return 0;
|
|
}
|
|
/* read her back: what the marker will name must be on the stick */
|
|
sig_rc = homeblocks_sig_check(dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig);
|
|
if (capsule_zuse_boot_check_key(dev, sig_rc, &sig, pubkey, rseed, rpub) != ZUSE_DRIVE_HERS ||
|
|
memcmp(rseed, seed, 32) != 0) {
|
|
console_println("Zuse: genesis mint failed -- not read back");
|
|
return 0;
|
|
}
|
|
memset(&g_marker, 0, sizeof(g_marker));
|
|
g_marker.present = 1;
|
|
memcpy(g_marker.proxy, pubkey, 32);
|
|
if (sk_v4_marker_write(&g_marker) != 0) {
|
|
memset(&g_marker, 0, sizeof(g_marker));
|
|
console_println("Zuse: genesis marker write failed -- headless");
|
|
return 0;
|
|
}
|
|
sk_v4_login(dev, seed, pubkey, "Zuse: genesis minted onto attached thumbdrive");
|
|
return 1;
|
|
}
|
|
|
|
/* A drive that has arrived. This system's stick: its Artemis partition is
|
|
* attached (the first disk slot, the block subsystem's), Zuse's public key is
|
|
* read from its genesis marker, then its identity partition is attached and,
|
|
* if it is Zuse's, her session begins. Any other drive is not used. */
|
|
static int sk_v4_drive_arrived(sk_v4_drive_t *d)
|
|
{
|
|
blkio_dev_t *dev;
|
|
homeblocks_sig_t sig;
|
|
homeblocks_sig_result_t sig_rc;
|
|
artemis_sig_t asig;
|
|
uint8_t seed[32], pubkey[32];
|
|
zuse_drive_t which;
|
|
unsigned nparts;
|
|
int gpt_rc;
|
|
|
|
d->used = (blkio_dev_t *)0;
|
|
d->art_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 || !sk_v4_is_this_stick(nparts)) {
|
|
console_println("USB: a drive that is not this system's stick -- not used");
|
|
return 1;
|
|
}
|
|
if (gpt_part_open(&d->art, &d->art_state, &d->raw,
|
|
&g_gpt_parts[gpt_find(g_gpt_parts, nparts, gpt_type_starship_artemis)]) != BLKIO_OK ||
|
|
gpt_part_open(&d->part, &d->part_state, &d->raw,
|
|
&g_gpt_parts[gpt_find(g_gpt_parts, nparts, gpt_type_starship_identity)]) != BLKIO_OK) {
|
|
console_println("GPT: the stick's partitions are not on 1 KiB blocks -- not used");
|
|
return 1;
|
|
}
|
|
|
|
/* Artemis first: formatted and signed at build (STICK.md 4); a
|
|
* signature that does not check out is not used, nor is the stick */
|
|
if (artemis_sig_check(&d->art, &asig) != ARTEMIS_SIG_OK) {
|
|
console_println("Artemis: the stick's signature is bad -- not used");
|
|
return 1;
|
|
}
|
|
if (blk_subsys_attach_device(&d->art) != BLK_OK) {
|
|
console_println("Artemis: the stick's partition could not be attached");
|
|
return 0;
|
|
}
|
|
d->art_used = &d->art;
|
|
g_stick_in = 1;
|
|
console_println("Artemis: the stick's partition attached");
|
|
/* the genesis marker on Artemis's fence: the proxy's key (and the real
|
|
* Zuse's, once she is minted) */
|
|
(void)sk_v4_marker_read(&g_marker);
|
|
g_zuse_root_known = g_marker.present;
|
|
if (g_marker.present) memcpy(g_zuse_root_pubkey, g_marker.proxy, 32);
|
|
|
|
/* then the identity partition, as an identity drive always has been */
|
|
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 1;
|
|
}
|
|
d->used = dev;
|
|
if (g_zuse_session) return 1;
|
|
|
|
/* no marker: a blank identity partition is the stick's first boot */
|
|
if (!g_marker.present) {
|
|
if (sig_rc == HOMEBLOCKS_SIG_BLANK) (void)sk_v4_genesis(dev, &d->art);
|
|
return 1;
|
|
}
|
|
which = capsule_zuse_boot_check_key(dev, sig_rc, &sig, g_marker.proxy, seed, pubkey);
|
|
if (which != ZUSE_DRIVE_HERS) return 1;
|
|
if (g_marker.has_real) {
|
|
g_proxy_fallback = 1; /* a real Zuse exists: the proxy gets no session */
|
|
return 1;
|
|
}
|
|
sk_v4_login(dev, seed, pubkey, "Zuse: identity confirmed from attached thumbdrive");
|
|
return 1;
|
|
}
|
|
|
|
/* A drive that has left: its identity partition is let go, then its Artemis
|
|
* partition -- in that order, because blk_subsys_detach_device() removes
|
|
* only the chain's tail, and the identity partition was attached last. */
|
|
static void sk_v4_drive_left(sk_v4_drive_t *d)
|
|
{
|
|
blkio_dev_t *dev = d->used;
|
|
|
|
if (dev) {
|
|
d->used = (blkio_dev_t *)0;
|
|
blk_subsys_detach_device(dev);
|
|
}
|
|
if (d->art_used) {
|
|
blk_subsys_detach_device(d->art_used);
|
|
d->art_used = (blkio_dev_t *)0;
|
|
g_stick_in = 0;
|
|
g_proxy_fallback = 0;
|
|
memset(&g_marker, 0, sizeof(g_marker));
|
|
}
|
|
if (!dev || 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_halt(); /* headless, until Zuse: wait for the next tick, not a spin (#151) */
|
|
}
|
|
sk_v4_discard_input();
|
|
}
|