The increment 2 final review's minors 1-4 (Captain Bob, 2026-10-27: "fix 1-4"): 1. A protective MBR with no "EFI PART" header is GPT_BAD, not GPT_NONE: the MBR says it is a GPT disk, and a GPT disk is never attached whole. 2. A disk whose block 0 cannot be read is GPT_EIO: "GPT: a USB drive could not be read -- not used", nothing attached (it was read raw, attached whole, and a raw Zuse drive behind it logged her in). 3. The entry array's LBA is checked against the disk's size directly. 4. gpt_bad_entries: the entry array changed, the header valid -- refused. Harness modes gpt_no_header, gpt_read_error (blkdebug EIO on sector 0), gpt_bad_entries. The plan carries the amendment. Red (amd64): gpt_no_header 070335, gpt_read_error 070717 FAIL; gpt_bad_entries 070048 PASS (existing check, now pinned). Green: every GPT mode on amd64 071113-072933; gpt_zuse, gpt_read_error, gpt_no_header on aarch64 073222-074047 and riscv64 074526-075535; the identity gate's and increment 1's sessions 080036-082345, emergency 083130 (082635 was run without EMERGENCY_CONSOLE_ENABLED=1 -- a harness invocation error). Accepted: suite EXIT 0; 093121, 093352, 093750, POST 538/538, dict_hash 0xfec8c47cfc6f437f; test-32, 0 failures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
278 lines
9.8 KiB
C
278 lines
9.8 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()).
|
|
*
|
|
* 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 <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;
|
|
|
|
/* 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();
|
|
}
|