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
+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