xHCI/BOT driver: genuine multi-device support (FABRIC-3.md §VII)
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run

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
This commit is contained in:
Robert Allan James
2026-09-05 22:14:14 -04:00
co-authored by Claude Sonnet 5
parent cc6fcb6a0b
commit 9e81de3f43
55 changed files with 144832 additions and 572 deletions
+117 -2
View File
@@ -965,7 +965,51 @@ puts a concrete, purchasable board under that already-named milestone.
[Xilinx/AMD — Zynq-7000 SoC Data Sheet (DS190)](https://www.mouser.com/datasheet/2/903/ds190-Zynq-7000-Overview-1595492.pdf),
[PCBSync — XC7Z010 vs XC7Z020 comparison](https://pcbsync.com/xilinx-xc7z010/).
## VII. xHCI/BOT driver: single-device architecture must go multi-device — PLANNED, NOT YET BUILT
## VII. xHCI/BOT driver: single-device architecture must go multi-device — CLOSED 2026-09-05
**Closure note, 2026-09-05:** implemented and live-verified per §VII.6's punch list, items 1-5
and 7 (item 6, file consolidation, was explicitly optional and not done; item 8, resume
minting, is the next task). Per-slot registry (`xhci_msc_slot_t`/`dev->msc_slots`, sized off
`dev->max_slots`) now holds every persistent per-device field; the boot-time scan no longer
`break`s after the first connected port; a dropped connect/disconnect during a busy Command
Ring is now queued (`dev->pending_events[]`) and drained instead of lost;
`blkio_usb.c`/`repl.c`'s own singletons became per-slot registries the same way.
**A second, more severe bug found only through live multi-device testing, not scoped in the
original plan:** `transfer_purpose`/`pending_transfer_slot_id` and `next_action`/
`next_action_slot_id` were *also* single scalars shared across the whole controller, not just
the fields named in §VII.2's original inventory. With two devices genuinely enumerating
concurrently, a second device's own EP0/bulk completion could silently overwrite the first
device's still-outstanding one before its real hardware completion arrived — the first device
then stalled forever with no error, no timeout, nothing to see in the log except the *other*
device's own progress. Confirmed live: a two-device boot (Zuse's drive + a blank second drive)
hung indefinitely at the exact point this predicts, recovered by moving both fields to
`xhci_msc_slot_t` (`transfer_purpose`) and driving `next_action` dispatch as a per-slot scan
at the end of `xhci_poll_events()`, and — critically — reading the Transfer Event TRB's own
real Slot ID field (`XHCI_EVT_SLOT_ID(trb->control)`, xHCI 1.2 table 6-38) instead of trusting
external bookkeeping for which device a completion belongs to.
**Verified live, all three architectures, `clean qemu` (mandatory acceptance), single-device
path unchanged:** amd64/aarch64/riscv64 all reach `(zuse) ok>` with the existing single
`ZUSEDISK` attach path, no regression.
**Verified live, amd64, the actual fix (item 7b):** Zuse's drive + a second blank drive
(`bob-thumb-ident.img`) both attached from boot (`-device usb-storage,bus=xhci0.0,...` twice).
Diagnostic probes (written, run, reverted — not committed) confirmed both slots independently
progress through Enable Slot → Address Device → GET_DEVICE_DESC → GET_CONFIG_DESC →
Configure Endpoint → SET_CONFIGURATION without either corrupting or stalling the other; Zuse's
own device completed the full chain, attached, and authenticated (`Zuse: identity confirmed
from attached thumbdrive`) while the second device was still independently mid-enumeration —
concurrent progress, not serialized-and-got-lucky. The second (blank) drive's own full
attach-to-completion was not separately confirmed to finish within the bounded test window
(TCG timing, not a correctness question) — not a red flag, since the property being tested
(no cross-device corruption/starvation) was already established by both devices advancing
independently.
**Not done, out of scope for this pass:** true simultaneous minting of all 8 identity drives
(§VII.6 item 8) — next task. Sequential QMP hot-swap (the original mint script) remains valid
too; simultaneous attach is now also an option, not the only one.
**Status 2026-09-05: design + punch list only. No code has been touched. Halted pending
Captain Bob's explicit "okay" to begin implementation** — this section exists to write the
@@ -1224,4 +1268,75 @@ each item names the exact functions/fields it touches, traced against real code,
scenario item 4's fix specifically enables) or the original QMP hot-swap sequencing (still
valid, just no longer the only option) — Bob's call at that point, not assumed now.
**Halt point:** implementation does not begin until Bob says so.
**Halt point — lifted 2026-09-05:** Bob's go-ahead ("okay, go ahead and implement it") authorized
implementation; see the closure note at the top of this section for what shipped and what was
found along the way.
## VIII. Headless-until-login console policy, and the KILL/g_repl_active_vm dangling-pointer
fix — CLOSED 2026-09-05
Two smaller, unrelated fixes landed the same day as the xHCI work above and are documented
here together since they were committed in the same pass, per direct instruction.
### VIII.1 — Headless-until-login console
**Decision, live-directed by Bob mid-session:** *"you have to be sure that there is no
console for the running system unless a 'thumbdrive' is present. no thumbdrive, no prompt.
when a user logs in by inserting a thumbdrive a vm with the users identity. the USE word will
allow that user's console to attach to the human name for that vm"* and *"see, it should be
headless if no console vm is present."* Zuse's own login is nothing special except that she
has no ACLs (*"there should be nothing special about zuse as a user except zuse has no
acl's"*) — her attach path uses the exact same gate as a regular WIREBIND user.
**Design:** `EMERGENCY_CONSOLE_ENABLED` (`Kconfig.heartbeat`, default flipped `y``n`) is
reused and extended from its narrower original scope (VM-fault-handler recovery only) to also
gate boot-time console visibility. When `n` (production default): the kernel boots completely
silent — no banner, no prompt, nothing on the console — until a real identity logs in via an
attached thumbdrive (WIREBIND user or Zuse's own attach/genesis-mint, identical treatment),
and a runtime VM error halts rather than dropping to a recovery REPL (no fallthrough surface).
When `y` (debug/recovery builds only, opt-in): behavior is unchanged from before this fix —
console shows immediately at boot, REPL errors recover and continue.
**Mechanism:** `sk_console_mark_login()`/`sk_console_login_occurred()` (`repl.c`/`repl.h`) —
one shared flag, set by either login path (`capsule_wirebind.c`'s regular-user console-VM
birth call site, `capsule_zuse_boot.c`'s `install_and_activate()`, the shared success point
for both genesis-mint and re-attach). `sk_repl_headless_wait(VM *mama)` (new, `repl.c`) spins
on `heartbeat_service()` + `sk_repl_idle()` (so USB/WIREBIND detection keeps running) until
the flag is set, called from `kernel_main.c` immediately before the existing `sk_repl(mama)`
call, gated `#if !EMERGENCY_CONSOLE_ENABLED`. `sk_repl_run()`/`sk_repl_step()`'s error
branches were also wired to a previously-dead `sk_fault_handler()` (defined but never called
anywhere — confirmed via grep before flipping the default exposed the resulting
`-Werror=unused-function`), matching `sk_repl()`'s own doc comment's original, never-
implemented intent: `#if !EMERGENCY_CONSOLE_ENABLED` halts on error, `#else` recovers.
**Verified live, both directions, amd64:** positive — with a thumbdrive attached, console is
silent until login, first prompt shown is always pre-authenticated (`(zuse) ok>` or the
equivalent WIREBIND-user prompt), never a bare `ok>`. Negative — with `ZUSEDISK=` empty (no
thumbdrive at all), a 60-second bounded boot produced zero occurrences of "Emergency CLI" or
any `ok>` prompt in the serial log, and no leaked `qemu-system-*` process. Not yet verified on
aarch64/riscv64 specifically for the headless behavior (the 3-arch runs done this session all
used an attached Zuse drive) — the code path is architecture-generic (no `ARCH_*` gating), so
this is a documentation gap, not a known defect.
### VIII.2 — `KILL`/`g_repl_active_vm` dangling-pointer fix
**Bug** (flagged report-only in `project_dictionary_superpower_audit_20260903`, fixed now by
direct instruction): `mama_word_kill()`'s call site never checked whether the VM being killed
was the one the console's `USE` word had currently attached to
(`g_repl_active_vm`/`sk_repl_get_active_vm()`). `EJECT`/`UNCLEAN` (`capsule_wirebind.c`)
already reset-before-kill for this exact reason; plain `KILL` did not, leaving
`g_repl_active_vm` dangling — the next command dispatched through the console would fault
against a freed/retired VM.
**Fix** (`mama_forth_words.c`, `mama_word_kill()`): widened the existing `VMRegistryEntry
entry` lookup's scope slightly and added, immediately after the existing Hera-guarded
`vm_physics_retire()` call: `if (entry.vm_ptr && sk_repl_get_active_vm() == (VM *)
entry.vm_ptr) { sk_repl_set_active_vm((VM *) 0); }` — detaches the console back to Hera's own
dictionary before the killed VM's memory can be reused, same as the existing
`EJECT`/`UNCLEAN` precedent.
**Verified:** three-arch `clean qemu` acceptance (this session's runs, alongside the xHCI and
headless-console work) — no regression in existing `KILL`/`USE` behavior. Not separately
stress-tested against the specific dangling-pointer scenario (kill the actively-`USE`'d VM,
then dispatch another command) — the fix is small and directly addresses the confirmed gap,
but that specific repro was not re-run live after the fix landed.
+14 -9
View File
@@ -66,15 +66,20 @@ config HEARTBEAT_SLOPE_VALIDATION_FREQUENCY
revalidation runs.
config EMERGENCY_CONSOLE_ENABLED
bool "Interactive fault handler / error-recovery REPL (EMERGENCY_CONSOLE_ENABLED)"
default y
bool "Interactive fault handler / debug-escape REPL (EMERGENCY_CONSOLE_ENABLED)"
default n
help
y = REPL recovers from errors and continues (interactive
fallthrough surface). Set to n for production, embedded, or
high-security builds where an error should not hand control back
to an interactive console. Applies to both the hosted VM and the
kernel build; the kernel's emergency_console VM flag additionally
applies only to the bare `ok>` REPL -- zuse sessions are always
subject to ACL regardless of this setting.
Decided 2026-09-05: no console for the running system unless a
thumbdrive is present -- n (the new default) is the production/
high-security posture. When n: the kernel boots headless (no
banner, no prompt, no interactive surface at all) until a real
identity logs in via an attached thumbdrive (a regular user's
WIREBIND login or Zuse's own attach/genesis-mint -- neither is
special), and a runtime error halts the VM rather than handing
control back to an interactive console (no fallthrough surface).
Set to y only for debugging/recovery builds: the kernel shows its
interactive console immediately at boot regardless of any
thumbdrive, and REPL errors recover and continue instead of
halting. Applies to both the hosted VM and the kernel build.
endmenu
+3 -1
View File
@@ -270,7 +270,9 @@ DOE_SEED ?= 12345
# EMERGENCY_CONSOLE_ENABLED is blanket-forwarded (unlike the opt-in-only
# knobs below), so it's repointed through the Kconfig bridge the same way
# as the hosted Makefile's STRICT_PTR (Phase 3) -- Phase 4 heartbeat family.
$(eval $(call kconfig_bool,EMERGENCY_CONSOLE_ENABLED,1))
# Default flipped 0 (was 1) 2026-09-05: headless-until-login is now the
# production default (Kconfig.heartbeat's own default n, same decision).
$(eval $(call kconfig_bool,EMERGENCY_CONSOLE_ENABLED,0))
LOADER_CFLAGS := $(LOADER_BASE_CFLAGS) -I$(KERNEL_SRC)/vm
KERNEL_CFLAGS := $(COMMON_CFLAGS) -D__STARKERNEL__ -I$(KERNEL_SRC)/vm
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-05T06:22:51Z -->
<!-- Generated by mkcapsule --manifest 2026-09-06T01:54:05Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8 -2
View File
@@ -13,8 +13,14 @@
* BAM/reloc zero-page writes and an explicit block content write both
* survived a cold reboot and read back correctly.
*
* Only one USB MSC device is supported (single-outstanding-transaction
* scope, matching the xHCI driver it sits on).
* FABRIC-3.md §VII (2026-09-05): multiple simultaneously-attached USB MSC
* devices are now supported -- each open() call is backed by its own
* per-slot state (blkio_usb.c's own registry, keyed by xHCI slot ID,
* mirroring xhci_dev_t's msc_slots[]). The underlying xHCI/BOT command
* machinery still runs one bulk transfer at a time across the whole
* controller (real xHCI semantics, not a limitation introduced here) --
* see xhci_msc_slot_t's own doc comment for the persistent-vs-in-flight
* distinction this rests on.
*/
#ifndef STARKERNEL_BLKIO_USB_H
+42
View File
@@ -135,6 +135,48 @@ int sk_console_key_available(void);
*/
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
+218 -137
View File
@@ -12,8 +12,147 @@
#include "starkernel/pci.h"
#include "starkernel/xhci.h"
/* Deferred chaining: a doorbell ring (new control transfer) must never
* happen synchronously from inside xhci_poll_events()'s event-processing
* loop, before ERDP has been updated for the event currently being
* handled -- confirmed live (amd64 QEMU) to hang the guest outright when
* tried (a doorbell rung mid-acknowledgment of the previous event,
* evidenced by checkpoint logging showing execution stop exactly at the
* doorbell MMIO write). Chained requests (device descriptor -> short
* config read -> full config read, and every BOT command stage) instead
* set a slot's own next_action during event processing; the actual
* doorbell ring happens once per pending slot, after the main loop and
* the ERDP write, from a small dispatch at the end of xhci_poll_events().
* FABRIC-3.md §VII (2026-09-05): this used to be one scalar field on
* xhci_dev_t itself -- a real bug, found live during this fix's own
* multi-device verification: two devices enumerating in the same
* xhci_poll_events() call (each draining its own Command/Transfer Event in
* the same XHCI_EVT_RING_MAX_DRAIN batch) could have the second device's
* assignment silently overwrite the first's before the single dispatch at
* the end ran, permanently stalling whichever device's step was lost --
* no error, no retry, no timeout, just silence. Moved into xhci_msc_slot_t
* (below) so each device's own deferred step is independent; the
* end-of-poll dispatch now iterates every slot instead of reading one
* scalar. */
/* Which EP0/bulk request a slot's own outstanding Transfer Event
* completion refers to -- FABRIC-3.md §VII (2026-09-05): moved off
* xhci_dev_t (was a single shared scalar there, correlated only via
* another shared scalar, pending_transfer_slot_id) into xhci_msc_slot_t.
* This was a real, live bug: a device's own EP0/bulk ring completes
* independently of any other device's, so a shared "which slot is this
* for" tracker could be overwritten by a second device's transfer before
* the first device's real hardware completion arrived, permanently
* misattributing or losing it -- confirmed live during this fix's own
* multi-device verification (two devices attached at boot, one silently
* stalled at GET_DEVICE_DESC forever while the other churned). The fix:
* xhci_poll_events()'s Transfer Event handler now reads the real Slot ID
* out of the TRB itself (XHCI_EVT_SLOT_ID(trb->control), same field the
* spec already defines for this purpose, table 6-38) and looks up that
* slot's own transfer_purpose here, instead of trusting a shared scalar
* populated earlier by whichever device happened to submit last. */
typedef enum {
XHCI_XFER_NONE = 0,
XHCI_XFER_DEVICE_DESC,
XHCI_XFER_CONFIG_DESC_SHORT,
XHCI_XFER_CONFIG_DESC_FULL,
XHCI_XFER_SET_CONFIG,
XHCI_XFER_CBW_SENT,
XHCI_XFER_BOT_DATA_IN,
XHCI_XFER_BOT_DATA_OUT,
XHCI_XFER_CSW_RECEIVED,
XHCI_XFER_CLEAR_HALT,
XHCI_XFER_BOT_RESET
} xhci_transfer_purpose_t;
typedef enum {
XHCI_NEXT_ACTION_NONE = 0,
XHCI_NEXT_ACTION_GET_DEVICE_DESC,
XHCI_NEXT_ACTION_GET_CONFIG_DESC,
XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT,
XHCI_NEXT_ACTION_SET_CONFIG,
XHCI_NEXT_ACTION_BOT_DATA_IN,
XHCI_NEXT_ACTION_BOT_DATA_OUT,
XHCI_NEXT_ACTION_BOT_CSW_RECEIVE,
XHCI_NEXT_ACTION_BOT_SEND_TUR,
XHCI_NEXT_ACTION_BOT_SEND_READ10,
XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10,
XHCI_NEXT_ACTION_BOT_SEND_WRITE10,
XHCI_NEXT_ACTION_CLEAR_HALT,
XHCI_NEXT_ACTION_BOT_RESET
} xhci_next_action_t;
/* FABRIC-3.md §VII (2026-09-05): per-slot device state, formerly scalar
* fields directly on xhci_dev_t (see git history / FABRIC-3.md §VII.2 for
* the exact single-device-scope comments each field carried before this).
* One instance per xHCI device slot (1..max_slots) -- xhci_dev_t now holds
* an array of these (msc_slots[]), sized off the controller's own reported
* max_slots (xhci_dev_t.max_slots, from HCSPARAMS1, already read correctly
* at xhci_find_and_map() time -- see xhci_msc_slot_for()'s own doc comment)
* rather than a hand-picked constant. Index 0 is unused (xHCI slot IDs are
* 1-based, matching DCBAA's own [0]-reserved layout), matching the
* existing dcbaa allocation's own (max_slots+1)-entry sizing exactly. */
typedef struct {
uint32_t in_use; /* 0 = this slot index is not a live device */
/* Address Device / enumeration -- was xhci_dev_t's own input_ctx/
* device_ctx/ep0_ring* before this fix; single-drive-at-a-time scope
* removed, each attached device now gets its own. */
void *input_ctx; /* Input Control Ctx + Slot Ctx + EP0 Ctx (96 bytes, 32-byte contexts) */
void *device_ctx; /* Slot Ctx + EP0 Ctx (64 bytes) -- DCBAA[slot_id] points here */
xhci_trb_t *ep0_ring; /* EP0 Transfer Ring, XHCI_RING_TRB_COUNT TRBs */
uint32_t ep0_ring_cycle;
uint32_t ep0_ring_enq;
/* Descriptors -- was xhci_dev_t's own device_descriptor/config_descriptor/
* config_total_length. */
uint8_t device_descriptor[18];
uint8_t config_descriptor[128];
uint16_t config_total_length;
/* Bulk endpoints -- was xhci_dev_t's own bulk_in_ep_addr/bulk_out_ep_addr/
* bulk_in_ring/bulk_out_ring and their max-packet/cycle/enqueue fields. */
uint8_t bulk_in_ep_addr;
uint16_t bulk_in_max_packet;
uint8_t bulk_out_ep_addr;
uint16_t bulk_out_max_packet;
xhci_trb_t *bulk_in_ring;
uint32_t bulk_in_ring_cycle;
uint32_t bulk_in_ring_enq;
xhci_trb_t *bulk_out_ring;
uint32_t bulk_out_ring_cycle;
uint32_t bulk_out_ring_enq;
/* MSC (block-subsystem) attach bookkeeping -- was xhci_dev_t's own
* bot_msc_attach_pending/bot_msc_attached/bot_msc_detach_pending
* (bot_msc_attach_slot_id is gone entirely -- the array index it used
* to name is now the slot itself). Each is independently meaningful
* per attached device now (was a single flag per kind, one device
* assumed) -- sk_repl_idle() scans every slot for these each idle
* tick, cheap and bounded by max_slots. */
uint8_t bot_msc_attach_pending;
uint8_t bot_msc_attached;
uint8_t bot_msc_detach_pending;
/* This slot's own outstanding EP0/bulk transfer -- see the doc comment
* on xhci_transfer_purpose_t above for why this must be per-slot. */
xhci_transfer_purpose_t transfer_purpose;
/* This slot's own deferred doorbell-ring action -- see the doc comment
* on xhci_next_action_t above for why this must be per-slot. */
xhci_next_action_t next_action;
uint16_t next_action_length;
uint8_t next_action_config_value; /* SET_CONFIGURATION's wValue,
* staged by the
* CONFIG_DESC_FULL handler
* once bConfigurationValue
* is known */
} xhci_msc_slot_t;
/* Driver state for one xHCI controller instance. Only one controller is
* supported (matches virtio_blk's single-device precedent). */
* supported (matches virtio_blk's single-device precedent) -- this is a
* per-CONTROLLER limit, not a per-DEVICE one; msc_slots[] below is what
* makes multiple simultaneously-attached USB devices on that one
* controller actually work (FABRIC-3.md §VII, 2026-09-05). */
typedef struct {
PciDevice pci;
uint64_t bar0_phys; /* physical MMIO base, BAR0 */
@@ -59,14 +198,35 @@ typedef struct {
uint32_t pending_connect_port_id;
uint32_t pending_connect_speed; /* PORTSC.Port Speed at connect time */
/* Milestone 2e: Address Device. This driver only ever addresses one
* device at a time (single-drive-at-a-time scope), so these are
* single, reused allocations rather than per-slot -- lazily allocated
* on the first connect that reaches xhci_cmd_address_device(), then
* reinitialised (not reallocated) on every subsequent connect. connect
/* FABRIC-3.md §VII item 3 (2026-09-05): a connect or disconnect that
* arrives while the Command Ring already has an outstanding command
* (connect_state != XHCI_CONN_IDLE) used to be silently dropped --
* "enable slot already pending -- dropped" / "disable slot skipped --
* command ring busy". Real commands are still issued one at a time
* (xHCI Command Ring semantics, not a driver limitation -- see
* xhci_msc_slot_t's own doc comment on why this in-flight state stays
* scalar), but the event that couldn't be served right away is now
* queued here instead of discarded, and drained one entry at a time
* every time connect_state returns to XHCI_CONN_IDLE. Sized off
* XHCI_MAX_TRACKED_PORTS (a port can only ever contribute one pending
* event at a time -- a second PORTSC change on the same port before
* the first is drained simply overwrites its queue slot, matching a
* real port's own single-current-state nature). */
struct {
uint8_t valid;
uint8_t is_connect; /* 1 = connect (port_id/portsc valid), 0 = disconnect (slot_id valid) */
uint32_t port_id;
uint32_t portsc;
uint32_t slot_id;
} pending_events[XHCI_MAX_TRACKED_PORTS];
/* Milestone 2e: Address Device command-completion correlation. connect
* state tracks which command a still-outstanding completion event
* belongs to, since Enable Slot and Address Device are issued
* sequentially, not concurrently, for a given connect. */
* sequentially, not concurrently, for a given connect -- this stays
* scalar (one Command Ring, one outstanding command) even after
* FABRIC-3.md §VII; see the pending_events[] queue above for how a
* second connect while this is busy is now handled. */
enum {
XHCI_CONN_IDLE = 0,
XHCI_CONN_AWAIT_ENABLE_SLOT,
@@ -77,84 +237,27 @@ typedef struct {
XHCI_CONN_AWAIT_SET_TR_DEQUEUE
} connect_state;
uint32_t pending_connect_slot_id;
/* Milestone 2e/2g: disconnect teardown. Same single-outstanding-
* command assumption as Enable Slot/Address Device above -- a
* disconnect that arrives while another Command Ring command is
* already outstanding is dropped rather than queued (matches the
* existing "enable slot already pending -- dropped" precedent).
* pending_disable_slot_id is captured at disconnect time, since the
* port's own tracked slot ID (port_slot_id[]) is cleared immediately
* on disconnect so a fresh connect on the same port isn't confused
* for one already in progress -- by the time the Disable Slot
* command's completion arrives, the port array no longer has it. */
/* Milestone 2e/2g: disconnect teardown. pending_disable_slot_id is
* captured at disconnect time, since the port's own tracked slot ID
* (port_slot_id[]) is cleared immediately on disconnect so a fresh
* connect on the same port isn't confused for one already in progress
* -- by the time the Disable Slot command's completion arrives, the
* port array no longer has it. */
uint32_t pending_disable_slot_id;
void *input_ctx; /* Input Control Ctx + Slot Ctx + EP0 Ctx (96 bytes, 32-byte contexts) */
void *device_ctx; /* Slot Ctx + EP0 Ctx (64 bytes) -- DCBAA[slot_id] points here */
xhci_trb_t *ep0_ring; /* EP0 Transfer Ring, XHCI_RING_TRB_COUNT TRBs */
uint32_t ep0_ring_cycle;
uint32_t ep0_ring_enq;
/* Milestone 2f: EP0 control transfers. Like connect_state, this
* driver only ever has one control transfer outstanding at a time --
* pending_transfer_slot_id is 0 when idle, else the slot ID whose
* Transfer Event (posted only by the Status Stage TRB, which alone
* has IOC set) is still outstanding. transfer_purpose says which
* request that is, since xhci_poll_events() needs to know which
* buffer to interpret and what (if anything) to chain next on
* success -- e.g. a successful short Configuration descriptor read
* chains into a full-length read once wTotalLength is known.
* device_descriptor is the full 18-byte standard USB device
* descriptor; config_descriptor holds the Configuration descriptor
* and everything after it in the same read (Interface + Endpoint
* descriptors, concatenated, per USB spec) -- fixed 128 bytes,
* comfortably covers a single-interface Mass Storage device's full
* descriptor set without a dynamic allocation. All reused (not
* per-slot), matching this driver's single-device scope. */
enum {
XHCI_XFER_NONE = 0,
XHCI_XFER_DEVICE_DESC,
XHCI_XFER_CONFIG_DESC_SHORT,
XHCI_XFER_CONFIG_DESC_FULL,
XHCI_XFER_SET_CONFIG,
XHCI_XFER_CBW_SENT,
XHCI_XFER_BOT_DATA_IN,
XHCI_XFER_BOT_DATA_OUT,
XHCI_XFER_CSW_RECEIVED,
XHCI_XFER_CLEAR_HALT,
XHCI_XFER_BOT_RESET
} transfer_purpose;
uint32_t pending_transfer_slot_id;
uint8_t device_descriptor[18];
uint8_t config_descriptor[128];
uint16_t config_total_length;
/* FABRIC-3.md §VII (2026-09-05): per-slot device state (Address Device
* results, descriptors, bulk endpoints/rings, MSC attach flags) lives
* in msc_slots[] now, one xhci_msc_slot_t per possible device slot --
* see that type's own doc comment. Allocated by xhci_bringup() once
* max_slots is known (kmalloc_aligned(), same sizing input and
* allocation pattern the dcbaa allocation just above it already
* uses -- (max_slots+1) entries, index 0 unused). NULL until
* xhci_bringup() completes. */
xhci_msc_slot_t *msc_slots;
/* Milestone 2g: bulk endpoints, discovered by walking the Endpoint
* descriptors that follow the confirmed Mass Storage/BOT Interface
* descriptor in config_descriptor. bEndpointAddress in full (not just
* the endpoint number) -- bit 7 is needed later to pick the right
* Doorbell target / EP Context DCI, and callers that want direction
* alone can just mask it. 0 means "not found yet" for both --
* endpoint address 0 is always EP0 (control), never a valid bulk
* endpoint address, so it's a safe not-found sentinel. */
uint8_t bulk_in_ep_addr;
uint16_t bulk_in_max_packet;
uint8_t bulk_out_ep_addr;
uint16_t bulk_out_max_packet;
/* Milestone 2g: bulk endpoint Transfer Rings, one per direction --
* same fixed-ring-plus-Link-TRB pattern as ep0_ring, lazily allocated
* once and reused across every connect (single-device scope, matching
* every other ring in this driver). Not usable for actual transfers
* until xhci_cmd_configure_endpoint() succeeds -- allocating them
* early (rather than only after success) keeps the allocation site in
* one place and lets the Input Context's EP Contexts point at real,
* already-initialised rings before the command is even submitted. */
xhci_trb_t *bulk_in_ring;
uint32_t bulk_in_ring_cycle;
uint32_t bulk_in_ring_enq;
xhci_trb_t *bulk_out_ring;
uint32_t bulk_out_ring_cycle;
uint32_t bulk_out_ring_enq;
/* transfer_purpose (and the pending_transfer_slot_id scalar that used
* to correlate it) now live per-slot in msc_slots[] -- see
* xhci_transfer_purpose_t's own doc comment for why. */
/* Milestone 2g: Bulk-Only Transport. bot_cbw/bot_csw are reused across
* every command (single-outstanding-transfer scope, matching every
@@ -282,61 +385,17 @@ typedef struct {
* to actually act on it -- calls blkio_usb_open_msc() (READ CAPACITY(10)
* + xhci_bot_wait_for_idle(), both requiring that same "outside
* xhci_poll_events()" constraint) then blk_subsys_attach_device(). */
uint8_t bot_msc_attach_pending;
uint32_t bot_msc_attach_slot_id;
/* Set by sk_repl_idle() once blk_subsys_attach_device() actually
* succeeds (not by the SET_CONFIGURATION handler itself -- attach can
* still fail, e.g. a bad capacity query, in which case there is
* nothing to detach later). Read by the PORTSC disconnect handler
* below to decide whether this disconnect needs a block-subsystem
* detach at all -- a device that never successfully attached (or that
* was already detached) produces no spurious detach flag. */
uint8_t bot_msc_attached;
/* Set by the PORTSC disconnect handler (see xhci_poll_events()'s own
* disconnect handling) only when bot_msc_attached is set -- same
* flag+consume-in-sk_repl_idle() shape as bot_msc_attach_pending,
* chosen deliberately over hooking the Disable Slot completion:
* disconnect is the unambiguous signal, while Disable Slot is only
* even issued when connect_state == XHCI_CONN_IDLE (see the "command
* ring busy" skip path) and would silently miss a detach otherwise.
* No xhci_bot_wait_for_idle() call is needed for detach itself (no
* device round-trip -- it's local block_subsystem.c bookkeeping), but
* consuming it in sk_repl_idle() anyway matches the attach path's own
* shape and keeps xhci.c decoupled from block_subsystem.c. */
uint8_t bot_msc_detach_pending;
/* bot_msc_attach_pending/bot_msc_attached/bot_msc_detach_pending now
* live per-slot in msc_slots[] (see xhci_msc_slot_t's own doc comment)
* -- set by the SET_CONFIGURATION completion handler / PORTSC
* disconnect handler inside xhci_poll_events(), consumed by
* sk_repl_idle() strictly after its own xhci_poll_events() call has
* returned (blkio_usb_open_msc()'s xhci_bot_wait_for_idle() call, and
* detach's blk_subsys_detach_device() call, both require that). */
/* Deferred chaining: a doorbell ring (new control transfer) must
* never happen synchronously from inside xhci_poll_events()'s event-
* processing loop, before ERDP has been updated for the event
* currently being handled -- confirmed live (amd64 QEMU) to hang the
* guest outright when tried (a doorbell rung mid-acknowledgment of
* the previous event, evidenced by checkpoint logging showing
* execution stop exactly at the doorbell MMIO write). Chained
* requests (device descriptor -> short config read -> full config
* read) instead set these fields during event processing; the actual
* doorbell ring happens once, after the main loop and the ERDP
* write, from a small dispatch at the end of xhci_poll_events(). */
enum {
XHCI_NEXT_ACTION_NONE = 0,
XHCI_NEXT_ACTION_GET_DEVICE_DESC,
XHCI_NEXT_ACTION_GET_CONFIG_DESC,
XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT,
XHCI_NEXT_ACTION_SET_CONFIG,
XHCI_NEXT_ACTION_BOT_DATA_IN,
XHCI_NEXT_ACTION_BOT_DATA_OUT,
XHCI_NEXT_ACTION_BOT_CSW_RECEIVE,
XHCI_NEXT_ACTION_BOT_SEND_TUR,
XHCI_NEXT_ACTION_BOT_SEND_READ10,
XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10,
XHCI_NEXT_ACTION_BOT_SEND_WRITE10,
XHCI_NEXT_ACTION_CLEAR_HALT,
XHCI_NEXT_ACTION_BOT_RESET
} next_action;
uint32_t next_action_slot_id;
uint16_t next_action_length;
uint8_t next_action_config_value; /* SET_CONFIGURATION's wValue, staged by
* the CONFIG_DESC_FULL handler once
* bConfigurationValue is known */
/* next_action/next_action_length/next_action_config_value now live
* per-slot in msc_slots[] -- see xhci_next_action_t's own doc comment
* (above xhci_msc_slot_t) for why this moved off xhci_dev_t. */
} xhci_dev_t;
/*
@@ -368,7 +427,11 @@ int xhci_find_and_map(xhci_dev_t *dev);
* Returns -3 if the controller failed to leave the halted state after RUN.
* On success, latches dev into the module-static pointer xhci_poll_events()
* reads -- only one controller is supported, matching virtio_blk's
* single-device precedent.
* single-device precedent -- and allocates dev->msc_slots (FABRIC-3.md
* §VII, 2026-09-05), sized (max_slots+1) entries, same sizing input and
* kmalloc_aligned() pattern as the DCBAA allocation just above it in
* xhci_bringup() itself. Returns -2 (allocation failure) if that
* allocation fails, same as the existing DCBAA/ring allocation failures.
*/
int xhci_bringup(xhci_dev_t *dev);
@@ -385,6 +448,24 @@ int xhci_bringup(xhci_dev_t *dev);
*/
xhci_dev_t *xhci_get_dev(void);
/*
* xhci_msc_slot_for — bounds-checked lookup into dev->msc_slots[slot_id]
* (FABRIC-3.md §VII, 2026-09-05). Every function in
* this driver that used to read/write one of
* xhci_dev_t's own single-device fields (Address
* Device state, descriptors, bulk endpoints/rings,
* MSC attach flags) now reaches the right device's
* own copy through this, keyed by the slot_id
* parameter every one of those functions already
* took.
*
* Returns NULL if dev/dev->msc_slots is not set up (xhci_bringup() has not
* completed) or slot_id is 0 or exceeds dev->max_slots -- callers must
* check before dereferencing, same discipline as every other NULL-capable
* lookup in this driver (xhci_port_regs(), xhci_get_dev()).
*/
xhci_msc_slot_t *xhci_msc_slot_for(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_poll_events — read Interrupter 0's Event Ring, dispatching each TRB
* by type: Port Status Change reads PORTSC to log
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -203,6 +203,11 @@ void capsule_wirebind_try_attach(struct blkio_dev *dev,
g_wirebind_attached_dev = dev;
memcpy(g_wirebind_attached_username, username, sizeof(g_wirebind_attached_username));
/* Decided 2026-09-05: no console for the running system unless a
* thumbdrive is present -- this successful console+user VM birth is
* exactly that login. See repl.h's own doc comment. */
sk_console_mark_login();
/* Register the pairing in the console's own routing table, index 3
* -- the fixed convention sk_repl_dispatch_line() (repl.c) uses. */
{
@@ -16,6 +16,7 @@
#include "starkernel/zuse_genesis_marker.h"
#include "starkernel/user_identity_seed.h"
#include "starkernel/console.h"
#include "starkernel/repl.h" /* sk_console_mark_login() -- FABRIC-2.md headless-until-login gate */
#include "block_subsystem.h" /* compute_crc64(), blk_meta_zone_read/write */
#include "blkio.h"
#include <string.h>
@@ -74,6 +75,13 @@ static void install_and_activate(VM *mama_vm, const uint8_t seed[32], const uint
* convention. */
vm_interpret(mama_vm, "ACL-ZUSE-BOOT");
g_zuse_attached_this_device = 1;
/* Decided 2026-09-05: no console for the running system unless a
* thumbdrive is present. Zuse's own login is not special here --
* "nothing special about zuse as a user except zuse has no ACLs,"
* per direct instruction -- so this is the same shared signal
* capsule_wirebind.c's own successful login sets. See repl.h. */
sk_console_mark_login();
}
void capsule_zuse_boot_try_attach(struct blkio_dev *dev,
+14 -3
View File
@@ -571,9 +571,20 @@ void mama_word_kill(VM *vm)
{
VMRegistryEntry entry;
if (capsule_vm_find_by_name_nocase(name_buf, &entry) == 0 &&
entry.state == VM_STATE_LIVE &&
!vm_uuid_is_hera(entry.vm_id)) {
vm_physics_retire(entry.vm_id);
entry.state == VM_STATE_LIVE) {
if (!vm_uuid_is_hera(entry.vm_id)) {
vm_physics_retire(entry.vm_id);
}
/* FABRIC-2.md §F.10, real bug found and reported 2026-08-27,
* fixed 2026-09-05: capsule_vm_kill() never touches
* g_repl_active_vm -- killing the VM the console is currently
* USE'd onto left it dangling (the REPL would fault on the
* next command dispatched through it). EJECT/UNCLEAN
* (capsule_wirebind.c) already reset-before-kill this way;
* plain KILL's own call site never did. */
if (entry.vm_ptr && sk_repl_get_active_vm() == (VM *) entry.vm_ptr) {
sk_repl_set_active_vm((VM *) 0);
}
}
}
+15
View File
@@ -929,6 +929,21 @@ static void kernel_main_deep(BootInfo *boot_info) {
}
console_println("");
/* Decided 2026-09-05: no console for the running system unless a
* thumbdrive is present -- headless by default (EMERGENCY_CONSOLE_
* ENABLED off), reusing that flag's own existing "does this build
* expose an unauthenticated interactive escape surface" posture
* (Kconfig.heartbeat) rather than adding a second, overlapping one.
* When off, sk_repl_headless_wait() runs the same idle-tick services
* (heartbeat, USB/WIREBIND/Zuse-attach detection) with no banner, no
* prompt, no input surface at all, until sk_console_mark_login()
* fires from either login path -- neither is treated as special, per
* direct instruction. When on (the debug/recovery escape hatch),
* this is skipped entirely and the console shows up immediately,
* exactly as before this change. */
#if !EMERGENCY_CONSOLE_ENABLED
sk_repl_headless_wait(mama);
#endif
sk_repl(mama);
#endif
+103 -20
View File
@@ -37,6 +37,7 @@
#include "starkernel/arch.h"
#include "starkernel/xhci_driver.h"
#include "starkernel/blkio_usb.h"
#include "starkernel/kmalloc.h"
#include "starkernel/homeblocks_sig.h"
#include "starkernel/capsule_birth.h"
#include "starkernel/capsule_zuse_boot.h"
@@ -96,6 +97,19 @@ static VM *g_repl_active_vm = (void *)0;
void sk_repl_set_active_vm(VM *vm) { g_repl_active_vm = vm; }
VM *sk_repl_get_active_vm(void) { return g_repl_active_vm; }
/*===========================================================================
* Headless-until-login gate, decided 2026-09-05: no console for the
* running system unless a thumbdrive is present. One shared flag, set by
* either login path (capsule_wirebind.c's regular-user console-VM birth,
* capsule_zuse_boot.c's own attach/genesis-mint) -- neither is special,
* per direct instruction. See repl.h's own doc comments.
*===========================================================================*/
static int g_console_login_occurred = 0;
void sk_console_mark_login(void) { g_console_login_occurred = 1; }
int sk_console_login_occurred(void) { return g_console_login_occurred; }
/*===========================================================================
* Currently attached home-blocks device: mirrors g_repl_active_vm's own
* shape (FABRIC-2.md §F.9's own precedent for this exact accessor). Set
@@ -228,14 +242,33 @@ static void sk_repl_idle(VM *active_vm)
* busy-wait -- which blkio_usb_open_msc() uses internally -- must
* never run from inside xhci_poll_events()'s own call frame). */
xhci_dev_t *xdev = xhci_get_dev();
static blkio_dev_t usb_blk_dev; /* single-device scope, matching the xHCI
* driver's own; referenced by both the
* attach and detach handling below. */
if (xdev && xdev->bot_msc_attach_pending) {
xdev->bot_msc_attach_pending = 0;
uint32_t slot_id = xdev->bot_msc_attach_slot_id;
/* FABRIC-3.md §VII (2026-09-05): was `static blkio_dev_t usb_blk_dev`
* ("single-device scope, matching the xHCI driver's own") -- now a
* per-slot registry, same sizing/allocation precedent as xhci_dev_t's
* own msc_slots[] (sized off xdev->max_slots, allocated once on first
* idle tick after xdev is known, since this file has no bringup-time
* hook of its own). Every slot with a pending attach/detach flag is
* serviced this tick, not just one -- a single `if` here used to mean
* a second device's pending flag would sit unnoticed until the first's
* flag was consumed and cleared. */
static blkio_dev_t *usb_blk_dev_slots = (void *)0;
static uint32_t usb_blk_dev_slot_count = 0;
if (xdev && (!usb_blk_dev_slots || usb_blk_dev_slot_count < xdev->max_slots + 1)) {
size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(blkio_dev_t);
blkio_dev_t *fresh = (blkio_dev_t *)kmalloc_aligned(bytes, 64);
if (fresh) {
memset(fresh, 0, bytes);
usb_blk_dev_slots = fresh;
usb_blk_dev_slot_count = xdev->max_slots + 1;
}
}
for (uint32_t slot_id = 1; xdev && usb_blk_dev_slots && slot_id <= xdev->max_slots; slot_id++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot_id);
if (!ms || !ms->bot_msc_attach_pending) continue;
ms->bot_msc_attach_pending = 0;
blkio_dev_t *usb_blk_dev = &usb_blk_dev_slots[slot_id];
int rc = blkio_usb_open_msc(&usb_blk_dev, xdev, slot_id);
int rc = blkio_usb_open_msc(usb_blk_dev, xdev, slot_id);
if (rc == 0) {
/* FABRIC-2.md Milestone 4: warn on blank/foreign/unrecognized
* media -- the "warn" half. No "refuse" half yet: blkio_usb.c
@@ -254,11 +287,22 @@ static void sk_repl_idle(VM *active_vm)
* interim value (FABRIC-2.md §F.8/§F.13). */
homeblocks_sig_t sig;
homeblocks_sig_result_t sig_rc =
homeblocks_sig_check(&usb_blk_dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig);
homeblocks_sig_check(usb_blk_dev, HOMEBLOCKS_SIG_START_FBLOCK, &sig);
switch (sig_rc) {
case HOMEBLOCKS_SIG_OK:
log_message(LOG_DEBUG, "xhci: USB drive recognized as a home-blocks drive");
g_homeblocks_dev = &usb_blk_dev;
/* FABRIC-3.md §VII (2026-09-05): g_homeblocks_dev/
* g_attached_blk_dev stay single "most recently
* attached" pointers by deliberate, scoped choice --
* the multi-device fix's target was the driver/backend
* corrupting each other's live state when two devices
* are attached at once (fixed above and in xhci.c/
* blkio_usb.c), not making every console-facing FORTH
* word (RUNCAP et al, mama_forth_words.c) multi-device
* aware -- the console still interacts with one device
* at a time, matching its own single-active-REPL
* design. Revisit if a real use case needs otherwise. */
g_homeblocks_dev = usb_blk_dev;
g_homeblocks_sig = sig;
g_homeblocks_sig_valid = 1;
break;
@@ -284,7 +328,7 @@ static void sk_repl_idle(VM *active_vm)
* be detected before the REPL's own idle polling exists to
* detect it). No-ops immediately if Zuse already has a real
* identity this boot. */
capsule_zuse_boot_try_attach(&usb_blk_dev, sig_rc, &sig, (VM *)sk_get_mama_vm());
capsule_zuse_boot_try_attach(usb_blk_dev, sig_rc, &sig, (VM *)sk_get_mama_vm());
/* FABRIC-2.md §F.5/§F.23 (WIREBIND): the real thumbdrive-
* attach call site for a regular (non-Zuse) identity --
@@ -295,12 +339,12 @@ static void sk_repl_idle(VM *active_vm)
* itself no-ops for a genesis-mode Zuse drive (no cert
* region) or before Zuse has authenticated this boot. */
if (sig_rc == HOMEBLOCKS_SIG_OK) {
capsule_wirebind_try_attach(&usb_blk_dev, &sig, (VM *)sk_get_mama_vm());
capsule_wirebind_try_attach(usb_blk_dev, &sig, (VM *)sk_get_mama_vm());
}
}
if (rc == 0 && blk_subsys_attach_device(&usb_blk_dev) == BLK_OK) {
xdev->bot_msc_attached = 1;
g_attached_blk_dev = &usb_blk_dev;
if (rc == 0 && blk_subsys_attach_device(usb_blk_dev) == BLK_OK) {
ms->bot_msc_attached = 1;
g_attached_blk_dev = usb_blk_dev;
} else {
log_message(LOG_ERROR, "xhci: USB MSC block-subsystem attach failed");
}
@@ -312,15 +356,21 @@ static void sk_repl_idle(VM *active_vm)
* no device round-trip, so it wouldn't strictly need to run outside
* xhci_poll_events()'s own call frame -- but handling it here anyway
* matches the attach path's shape and keeps xhci.c decoupled from
* block_subsystem.c (see bot_msc_detach_pending's own doc comment). */
if (xdev && xdev->bot_msc_detach_pending) {
xdev->bot_msc_detach_pending = 0;
blk_subsys_detach_device(&usb_blk_dev);
if (g_homeblocks_dev == &usb_blk_dev) {
* block_subsystem.c (see bot_msc_detach_pending's own doc comment).
* Per-slot loop now (FABRIC-3.md §VII, 2026-09-05), same reasoning as
* the attach loop above. */
for (uint32_t slot_id = 1; xdev && usb_blk_dev_slots && slot_id <= xdev->max_slots; slot_id++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot_id);
if (!ms || !ms->bot_msc_detach_pending) continue;
ms->bot_msc_detach_pending = 0;
blkio_dev_t *usb_blk_dev = &usb_blk_dev_slots[slot_id];
blk_subsys_detach_device(usb_blk_dev);
if (g_homeblocks_dev == usb_blk_dev) {
g_homeblocks_dev = (void *)0;
g_homeblocks_sig_valid = 0;
}
if (g_attached_blk_dev == &usb_blk_dev) {
if (g_attached_blk_dev == usb_blk_dev) {
g_attached_blk_dev = (void *)0;
}
@@ -576,6 +626,25 @@ int sk_console_getkey(VM *active_vm)
}
}
/* sk_repl_headless_wait - see repl.h's own doc comment. Same idle-service
* shape as sk_console_getkey() above, minus the key-reading entirely: no
* banner, no prompt, no console_getc()/readline of any kind -- this is
* exactly the "no console for the running system unless a thumbdrive is
* present" boundary, decided 2026-09-05. Exits the moment
* sk_console_login_occurred() becomes true. */
void sk_repl_headless_wait(VM *mama)
{
while (!sk_console_login_occurred()) {
heartbeat_service();
uint64_t now = heartbeat_ticks();
if (now - g_last_beat_tick >= SK_IDLE_BEAT_INTERVAL) {
g_last_beat_tick = now;
sk_repl_idle(mama);
}
arch_relax();
}
}
/* ?TERMINAL's real body (sf_terminal_ready(), shim.c): non-blocking peek --
* a single poll, no idle-servicing loop (a false result must return
* immediately, not block). Buffers a found byte in g_console_pending_key so
@@ -911,8 +980,16 @@ int sk_repl_step(VM *vm)
vm->abort_requested = 0;
if (vm->error) {
#if EMERGENCY_CONSOLE_ENABLED
console_puts(" ERROR\n");
vm->error = 0;
#else
/* Wired 2026-09-05: sk_fault_handler() existed but was never
* called from here -- the "halts VM on error" half of this
* function's own doc comment was aspirational, not real, until
* now. Real for the headless-until-login default. */
sk_fault_handler(vm);
#endif
} else {
console_puts(" ok\n");
}
@@ -955,8 +1032,14 @@ void sk_repl_run(VM *vm)
active->abort_requested = 0;
if (active->error) {
#if EMERGENCY_CONSOLE_ENABLED
console_puts(" ERROR\n");
active->error = 0;
#else
/* Wired 2026-09-05, same as sk_repl_step()'s matching branch
* above -- sk_fault_handler() existed but was never called. */
sk_fault_handler(active);
#endif
} else {
console_puts(" ok\n");
}
+41 -14
View File
@@ -12,6 +12,7 @@
#include <string.h>
#include "starkernel/blkio_usb.h"
#include "starkernel/kmalloc.h"
#include "console.h"
typedef struct {
@@ -22,16 +23,39 @@ typedef struct {
uint32_t total_forth_blocks;
} BlkioUsbState;
/* Singleton — one USB MSC device (single-outstanding-transaction scope,
* matching the xHCI driver this backend sits on). */
static BlkioUsbState g_usb_blk;
/* FABRIC-3.md §VII (2026-09-05): was a single static instance ("one USB MSC
* device, single-outstanding-transaction scope, matching the xHCI driver
* this backend sits on") -- now a per-slot registry, same shape and same
* sizing precedent as xhci_dev_t's own msc_slots (registry sized off the
* controller's own max_slots, allocated once on first use here since this
* file has no bringup-time hook of its own to do it earlier). Indexed
* directly by xHCI slot ID, index 0 unused, matching msc_slots[]. */
static BlkioUsbState *g_usb_blk_slots = NULL;
static uint32_t g_usb_blk_slot_count = 0;
static BlkioUsbState *usb_blk_state_for(xhci_dev_t *xdev, uint32_t slot_id) {
if (!xdev || slot_id < 1 || slot_id > xdev->max_slots) return NULL;
if (!g_usb_blk_slots || g_usb_blk_slot_count < xdev->max_slots + 1) {
size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(BlkioUsbState);
BlkioUsbState *fresh = (BlkioUsbState *)kmalloc_aligned(bytes, 64);
if (!fresh) return NULL;
memset(fresh, 0, bytes);
g_usb_blk_slots = fresh;
g_usb_blk_slot_count = xdev->max_slots + 1;
}
return &g_usb_blk_slots[slot_id];
}
static int usb_blk_open(blkio_dev_t *dev, const blkio_params_t *p) {
(void)p;
if (!dev) return BLKIO_EINVAL;
dev->state = &g_usb_blk;
/* FABRIC-3.md §VII (2026-09-05): blkio_open() zeroes dev->state before
* calling this (see blkio.h), so the per-slot BlkioUsbState* this
* device should use travels in through p->opaque -- set by
* blkio_usb_open_msc() below -- rather than a single module-global. */
if (!dev || !p || !p->opaque) return BLKIO_EINVAL;
BlkioUsbState *s = (BlkioUsbState *)p->opaque;
dev->state = s;
dev->forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
dev->total_blocks = g_usb_blk.total_forth_blocks;
dev->total_blocks = s->total_forth_blocks;
return BLKIO_OK;
}
@@ -110,6 +134,9 @@ static const blkio_vtable_t g_usb_blk_vtable = {
int blkio_usb_open_msc(blkio_dev_t *dev_out, xhci_dev_t *xdev, uint32_t slot_id) {
if (!dev_out || !xdev) return -1;
BlkioUsbState *s = usb_blk_state_for(xdev, slot_id);
if (!s) return -1;
if (xhci_bot_get_capacity(xdev, slot_id) != 0) return -1;
if (xhci_bot_wait_for_idle(xdev, 100000u) != BOT_STATUS_PASS) return -1;
@@ -123,11 +150,11 @@ int blkio_usb_open_msc(blkio_dev_t *dev_out, xhci_dev_t *xdev, uint32_t slot_id)
uint32_t blocks_per_fblock = BLKIO_FORTH_BLOCK_SIZE / block_size;
uint64_t total_forth_blocks = total_scsi_blocks / blocks_per_fblock;
g_usb_blk.xdev = xdev;
g_usb_blk.slot_id = slot_id;
g_usb_blk.scsi_block_size = block_size;
g_usb_blk.scsi_blocks_per_fblock = blocks_per_fblock;
g_usb_blk.total_forth_blocks = (total_forth_blocks > 0xFFFFFFFFull)
s->xdev = xdev;
s->slot_id = slot_id;
s->scsi_block_size = block_size;
s->scsi_blocks_per_fblock = blocks_per_fblock;
s->total_forth_blocks = (total_forth_blocks > 0xFFFFFFFFull)
? 0xFFFFFFFFu
: (uint32_t)total_forth_blocks;
@@ -135,8 +162,8 @@ int blkio_usb_open_msc(blkio_dev_t *dev_out, xhci_dev_t *xdev, uint32_t slot_id)
blkio_params_t params;
params.forth_block_size = BLKIO_FORTH_BLOCK_SIZE;
params.total_blocks = g_usb_blk.total_forth_blocks;
params.opaque = NULL;
params.total_blocks = s->total_forth_blocks;
params.opaque = s;
return blkio_open(dev_out, &g_usb_blk_vtable, &params);
}
+490 -383
View File
File diff suppressed because it is too large Load Diff