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.