Per-slot registry (xhci_msc_slot_t/dev->msc_slots, sized off the controller's own reported max_slots) replaces the single-device scalar fields the driver carried since Milestones 2e-2h. Boot-time port scan no longer stops at the first connected device; a connect/disconnect that arrives while the Command Ring is busy is now queued and drained instead of dropped. blkio_usb.c and repl.c's own single-device state (device descriptor buffers, blkio_dev_t, attach bookkeeping) became per-slot registries the same way. Live multi-device testing (not just compiling) surfaced a second, more severe bug outside the original plan: transfer_purpose and next_action were also single scalars shared across the whole controller. Two devices enumerating concurrently could have one's completion silently overwrite the other's still-outstanding one, permanently stalling it with no error. Fixed by moving both per-slot and, critically, reading the Transfer Event TRB's own real Slot ID field instead of trusting external bookkeeping. Verified live, all three architectures, mandatory clean-qemu acceptance: existing single-device path unchanged, and two devices attached simultaneously (amd64) both progress independently through enumeration without corrupting or stalling each other. Also in this pass (implemented and verified in earlier turns this session, committed together per direct instruction): - Headless-until-login console policy: no prompt/banner until a real identity logs in via an attached thumbdrive (WIREBIND or Zuse, neither special), reusing EMERGENCY_CONSOLE_ENABLED as the debug/recovery escape hatch (now default-off). - KILL/g_repl_active_vm dangling-pointer fix: killing the VM the console is currently USE'd onto now detaches back to Hera first, matching the existing EJECT/UNCLEAN precedent. FABRIC-3.md §VII/§VIII carry full closure notes for all three. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4
185 lines
7.0 KiB
C
185 lines
7.0 KiB
C
/*
|
||
StarForth — Steady-State Virtual Machine Runtime
|
||
|
||
Copyright (c) 2023–2025 Robert A. James
|
||
All rights reserved.
|
||
|
||
Licensed under the StarForth License, Version 1.0
|
||
*/
|
||
|
||
/**
|
||
* repl.h - Emergency FORTH REPL for LithosAnanke kernel
|
||
*/
|
||
|
||
#ifndef STARKERNEL_REPL_H
|
||
#define STARKERNEL_REPL_H
|
||
|
||
#include "vm.h"
|
||
#include "starkernel/homeblocks_sig.h"
|
||
|
||
struct blkio_dev;
|
||
|
||
#ifdef __cplusplus
|
||
extern "C" {
|
||
#endif
|
||
|
||
/**
|
||
* sk_repl - Run the emergency FORTH REPL on the serial console.
|
||
*
|
||
* Blocks until vm->halted is set (BYE word) or the VM encounters a halt.
|
||
* Runs with interrupts enabled; the APIC heartbeat continues to fire.
|
||
*
|
||
* @param vm Mama VM instance (must be fully initialised)
|
||
*/
|
||
void sk_repl(VM *vm);
|
||
|
||
/**
|
||
* sk_repl_run - Bare REPL loop (no banner).
|
||
*
|
||
* Same as sk_repl but skips the version/welcome banner. Used by START
|
||
* to enter a child VM's interpreter loop without reprinting the header.
|
||
*
|
||
* @param vm Fully initialised VM instance
|
||
*/
|
||
void sk_repl_run(VM *vm);
|
||
|
||
/**
|
||
* sk_repl_step - Execute one REPL turn on a VM and return.
|
||
*
|
||
* Prints the VM's prompt, reads one line, interprets it, prints ok/ERROR,
|
||
* then returns. Used by the Compudynamics VM-STEP primitive so Hera can
|
||
* give a single REPL quantum to a child VM without surrendering control
|
||
* for the full sk_repl_run() loop.
|
||
*
|
||
* @param vm Fully initialised VM instance
|
||
* @return 1 if the VM is still running, 0 if it halted during this turn
|
||
*/
|
||
int sk_repl_step(VM *vm);
|
||
|
||
/**
|
||
* sk_repl_set_active_vm - Redirect REPL input to a different VM (USE word).
|
||
*
|
||
* Pass NULL to restore default dispatch (Mama's VM).
|
||
* The change takes effect on the next REPL iteration.
|
||
*
|
||
* @param vm Target VM, or NULL for default
|
||
*/
|
||
void sk_repl_set_active_vm(VM *vm);
|
||
|
||
/**
|
||
* sk_repl_get_active_vm - Return the current USE-redirected VM, or NULL.
|
||
*/
|
||
VM *sk_repl_get_active_vm(void);
|
||
|
||
/**
|
||
* sk_repl_get_homeblocks_dev / sk_repl_get_homeblocks_sig - The currently
|
||
* attached home-blocks USB drive, or NULL if none is attached / the
|
||
* attached drive didn't check out as HOMEBLOCKS_SIG_OK (FABRIC-2.md
|
||
* §F.6/§F.9/§F.18). Both return NULL together; never one without the
|
||
* other.
|
||
*/
|
||
struct blkio_dev *sk_repl_get_homeblocks_dev(void);
|
||
const homeblocks_sig_t *sk_repl_get_homeblocks_sig(void);
|
||
|
||
/**
|
||
* sk_repl_get_attached_blk_dev - The currently attached USB block
|
||
* device, regardless of home-blocks recognition (FABRIC-2.md
|
||
* §F.8/§F.19) -- MINT's own target, since a blank/unminted drive never
|
||
* sets sk_repl_get_homeblocks_dev() above. NULL if nothing is attached.
|
||
*/
|
||
struct blkio_dev *sk_repl_get_attached_blk_dev(void);
|
||
|
||
/**
|
||
* sk_console_getkey - Real body of the standard dictionary's KEY word
|
||
* (called from shim.c's getchar()). Blocks until a key is available from
|
||
* either input source (serial console or the PS2/virtio keyboard-event
|
||
* bridge), servicing the heartbeat/idle loop while waiting so a KEY call
|
||
* from inside any word never stalls the heartbeat. No echo -- that's the
|
||
* caller's responsibility, same as any standard KEY.
|
||
*
|
||
* @param active_vm VM whose idle dispatch runs while waiting (see
|
||
* sk_repl_idle()'s own doc comment on why this is a
|
||
* parameter rather than read via sk_repl_get_active_vm())
|
||
* @return the key read, as an unsigned byte value
|
||
*/
|
||
int sk_console_getkey(VM *active_vm);
|
||
|
||
/**
|
||
* sk_console_key_available - Real body of the standard dictionary's
|
||
* ?TERMINAL word (called from sf_terminal_ready()). Non-blocking peek:
|
||
* returns 1 if a key is ready without consuming it (a following
|
||
* sk_console_getkey() returns that exact key), 0 otherwise.
|
||
*/
|
||
int sk_console_key_available(void);
|
||
|
||
/**
|
||
* sk_console_readline - Real body of the standard dictionary's
|
||
* QUERY/EXPECT words (called from shim.c's fgets()). Reads one line from
|
||
* the console with echo and backspace support, servicing the heartbeat/
|
||
* idle loop while waiting -- the same line editor the REPL's own prompt
|
||
* uses internally, so a mid-word EXPECT behaves identically to typing at
|
||
* "ok>" itself.
|
||
*
|
||
* @param buf Destination buffer
|
||
* @param size Buffer capacity, including the NUL terminator
|
||
* @param active_vm VM whose idle dispatch runs while waiting
|
||
* @param reanchor_prompt Nonzero to re-print the "ok> " prompt whenever
|
||
* an idle bottom half (heartbeat, USB attach/detach) writes
|
||
* to the console while this readline blocks at a bare,
|
||
* untyped prompt -- keeps the top-level prompt as the last
|
||
* thing shown once the chatter dies down. Callers whose
|
||
* prompt line is their own (shim.c's fgets(), i.e.
|
||
* QUERY/EXPECT/ACCEPT) pass 0 so "ok> " never gets stamped
|
||
* onto their mid-word input context.
|
||
* @return number of characters placed in buf, not counting the NUL
|
||
*/
|
||
int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt);
|
||
|
||
/**
|
||
* sk_console_mark_login - Record that a real identity has logged in via an
|
||
* attached thumbdrive, decided in conversation 2026-09-05: no console for
|
||
* the running system unless a thumbdrive is present -- headless until the
|
||
* first successful login, regardless of which path performs it (a regular
|
||
* user's WIREBIND console-VM birth, capsule_wirebind.c, or Zuse's own
|
||
* attach/genesis-mint, capsule_zuse_boot.c). Both call this on their own
|
||
* success path; neither is treated as special here, per direct instruction
|
||
* ("nothing special about zuse as a user except zuse has no ACLs") --
|
||
* this is one shared signal, not a Zuse-specific carve-out. Idempotent
|
||
* (a second login, e.g. a second WIREBIND user later, is a harmless no-op).
|
||
*/
|
||
void sk_console_mark_login(void);
|
||
|
||
/**
|
||
* sk_console_login_occurred - Whether sk_console_mark_login() has ever
|
||
* been called this boot. Read by sk_repl_headless_wait()'s own exit
|
||
* condition; exposed publicly for anything else that needs to know
|
||
* whether the console is unlocked yet.
|
||
*/
|
||
int sk_console_login_occurred(void);
|
||
|
||
/**
|
||
* sk_repl_headless_wait - Idle-service loop with no interactive surface
|
||
* at all: no banner, no prompt, no console_getc()/readline. Runs
|
||
* heartbeat_service() and the same SK_IDLE_BEAT_INTERVAL-gated
|
||
* sk_repl_idle(mama) cadence sk_console_readline()'s own idle branch
|
||
* uses -- so USB/WIREBIND/Zuse-attach detection, the heartbeat, and all
|
||
* other idle-tick subsystems keep running -- until sk_console_login_
|
||
* occurred() becomes true, at which point it returns. Called from
|
||
* kernel_main.c in place of an immediate sk_repl(mama) call when
|
||
* EMERGENCY_CONSOLE_ENABLED is off (the new default, 2026-09-05): no
|
||
* thumbdrive, no prompt, per direct instruction. When
|
||
* EMERGENCY_CONSOLE_ENABLED is on (the debug/recovery escape hatch),
|
||
* kernel_main.c skips this and calls sk_repl(mama) immediately instead,
|
||
* exactly as before this change.
|
||
*
|
||
* @param mama Hera's own VM instance -- the idle-dispatch target,
|
||
* same as every other sk_repl_idle() caller uses.
|
||
*/
|
||
void sk_repl_headless_wait(VM *mama);
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
#endif /* STARKERNEL_REPL_H */
|