Files
LithosAnanake/include/starkernel/xhci_driver.h
T
Robert Allan JamesandClaude Sonnet 5 9e81de3f43
Build / build-amd64-iso (push) Waiting to run
Build / build-aarch64-iso (push) Waiting to run
Build / build-riscv64-img (push) Waiting to run
xHCI/BOT driver: genuine multi-device support (FABRIC-3.md §VII)
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
2026-09-05 22:14:14 -04:00

1031 lines
54 KiB
C

/*
* xhci_driver.h — xHCI USB host controller driver public API for StarKernel
*
* Register-layout definitions live in xhci.h; this header is the driver's
* own state and public entry points, matching virtio_blk.h's split.
*/
#ifndef STARKERNEL_XHCI_DRIVER_H
#define STARKERNEL_XHCI_DRIVER_H
#include <stdint.h>
#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) -- 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 */
xhci_cap_regs_t *cap; /* BAR0 + 0 */
xhci_op_regs_t *op; /* BAR0 + cap->cap_length */
xhci_runtime_regs_t *runtime; /* BAR0 + cap->rts_off */
xhci_doorbell_t *doorbell; /* BAR0 + cap->db_off */
uint32_t max_slots;
uint32_t max_ports;
uint32_t max_intrs;
uint32_t max_scratchpad_bufs;
/* Set up by xhci_bringup(); NULL/0 until then. */
void *dcbaa; /* Device Context Base Address Array */
void *scratchpad_arr; /* array of scratchpad buffer pointers, if any */
xhci_trb_t *cmd_ring; /* Command Ring, XHCI_RING_TRB_COUNT TRBs;
* index XHCI_RING_TRB_COUNT-1 is a
* permanent Link TRB back to index 0 */
uint32_t cmd_ring_cycle; /* current Command Ring Cycle State (RCS) */
uint32_t cmd_ring_enq; /* next free Command Ring index (0..COUNT-2) */
xhci_trb_t *evt_ring; /* Event Ring, XHCI_RING_TRB_COUNT TRBs */
void *evt_ring_seg_table; /* Event Ring Segment Table (1 entry) */
uint32_t evt_ring_cycle; /* current Event Ring Cycle State */
uint32_t evt_ring_deq; /* current Event Ring dequeue index */
xhci_intr_regs_t *intr0; /* Interrupter 0 register set, cached
* by xhci_bringup() for
* xhci_poll_events() */
/* Milestone 2e: connect -> Enable Slot correlation. port_slot_id is
* indexed by port_id - 1 (1-based port IDs, matching PORTSC/Port
* Status Change Event numbering); 0 means no slot allocated for that
* port yet. Fixed-size, not heap-allocated -- XHCI_MAX_TRACKED_PORTS
* comfortably covers any real or emulated root hub's port count
* without adding a new kmalloc_aligned() call to xhci_bringup(); ports
* beyond this bound (checked against both this array and max_ports)
* are simply not tracked, matching this driver's existing preference
* for fixed allocations over dynamic growth (xhci.h's own ring-sizing
* rationale). Only one Enable Slot is ever in flight at a time (this
* driver issues commands synchronously with respect to connect events,
* not a queue) -- pending_connect_port_id is 0 when idle, or the
* port_id whose Command Completion Event is still outstanding. */
uint32_t port_slot_id[XHCI_MAX_TRACKED_PORTS];
uint32_t pending_connect_port_id;
uint32_t pending_connect_speed; /* PORTSC.Port Speed at connect time */
/* 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 -- 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,
XHCI_CONN_AWAIT_ADDRESS_DEVICE,
XHCI_CONN_AWAIT_DISABLE_SLOT,
XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT,
XHCI_CONN_AWAIT_RESET_ENDPOINT,
XHCI_CONN_AWAIT_SET_TR_DEQUEUE
} connect_state;
uint32_t pending_connect_slot_id;
/* 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;
/* 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;
/* 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
* other buffer in this driver) -- built/overwritten fresh each call,
* not preserved between calls. bot_next_tag is a free-running counter
* for dCBWTag; bot_last_tag latches the tag of the CBW currently in
* flight, so the CSW stage can verify dCSWTag matches (BOT spec
* requirement) without needing to re-derive it. bot_data_buf is a
* fixed 1024-byte Data-In destination -- sized to cover exactly one
* Forth block (BLKIO_FORTH_BLOCK_SIZE, block_subsystem.c's own unit)
* as two consecutive 512-byte SCSI blocks, which is what
* xhci_bot_read_block() actually requests once Milestone 2h's blkio
* backend calls this path with real block-subsystem-driven sizes; grown
* from the single-512-byte-block buffer of the increment that first
* added it. bot_expected_data_len is the byte count the Data-In stage
* was told to read, staged at CBW build time and consumed once the
* Data-In TRB is actually enqueued. */
usb_bot_cbw_t bot_cbw;
usb_bot_csw_t bot_csw;
uint32_t bot_next_tag;
uint32_t bot_last_tag;
uint8_t bot_data_buf[1024];
uint32_t bot_expected_data_len;
/* Milestone 2g follow-up: TEST UNIT READY unit-init sequence, ahead of
* a real READ(10). bot_cmd_kind says which SCSI command the CBW/CSW
* currently in flight actually is, since XHCI_XFER_CSW_RECEIVED alone
* doesn't distinguish a TUR completion from a READ10 completion --
* both go through the identical CBW->Data-In(if any)->CSW chain.
* bot_tur_retries counts TUR attempts that came back FAILED/PHASE
* ERROR (a fresh SCSI target's standard first-command UNIT ATTENTION
* behavior, not a driver defect -- see SCSI_CMD_TEST_UNIT_READY's own
* doc comment in xhci.h); capped at XHCI_BOT_TUR_MAX_RETRIES. The
* pending bot_read10_* fields latch a caller's requested READ(10) so
* it can be issued once TUR reports PASS -- xhci_bot_read_block() is
* the entry point that stages these and kicks off TUR first, rather
* than callers driving xhci_bot_send_read10() directly.
*
* Milestone 2h adds BOT_CMD_READ_CAPACITY10 (see
* xhci_bot_send_read_capacity10()) and bot_last_status: every command
* kind now resets bot_cmd_kind to BOT_CMD_NONE and sets
* bot_last_status once its own CSW is fully processed (TEST UNIT
* READY is the one exception -- a PASS or an in-progress retry both
* stay non-terminal, chaining into the next command instead). This is
* what lets xhci_bot_wait_for_idle() -- a synchronous busy-wait,
* called from OUTSIDE xhci_poll_events(), never from within it --
* detect "this command's whole chain is finished" without needing to
* know which specific command it was waiting on. */
enum {
BOT_CMD_NONE = 0,
BOT_CMD_TEST_UNIT_READY,
BOT_CMD_READ10,
BOT_CMD_READ_CAPACITY10,
BOT_CMD_WRITE10
} bot_cmd_kind;
enum {
BOT_STATUS_IDLE = 0,
BOT_STATUS_PASS,
BOT_STATUS_FAILED,
BOT_STATUS_TIMEOUT
} bot_last_status;
/* Which command a TUR-PASS should chain into -- TEST UNIT READY's own
* completion handling can't tell READ10 and READ CAPACITY10 apart
* otherwise, since both now go through the identical TUR-first
* sequencing xhci_bot_read_block()/xhci_bot_get_capacity() both use.
* Set by whichever of those two entry points kicked off the TUR. */
enum {
BOT_TUR_CHAIN_NONE = 0,
BOT_TUR_CHAIN_READ10,
BOT_TUR_CHAIN_READ_CAPACITY10,
BOT_TUR_CHAIN_WRITE10
} bot_tur_chain_target;
uint32_t bot_tur_retries;
uint32_t bot_read10_lba;
uint16_t bot_read10_num_blocks;
uint32_t bot_read10_block_size;
/* WRITE(10) mirror of bot_read10_* above -- kept as separate fields
* rather than renaming/reusing the read ones, so the already-tested
* READ10 path is never touched by this addition (FABRIC-2.md §F.1). */
uint32_t bot_write10_lba;
uint16_t bot_write10_num_blocks;
uint32_t bot_write10_block_size;
/* Latched from a successful READ CAPACITY(10) Data-In reply -- see
* SCSI_CMD_READ_CAPACITY10's own doc comment in xhci.h for field
* meaning. Untouched (stale) on a FAILED/TIMEOUT completion; callers
* must check xhci_bot_wait_for_idle()'s return value, not just read
* these blindly. */
uint32_t bot_cap_last_lba;
uint32_t bot_cap_block_size;
/* Milestone 2 / G.1 / §F.14: bulk-endpoint stall recovery. A bulk
* transfer that completes with XHCI_COMPLETION_CODE_STALL_ERROR leaves
* the xHC endpoint in the Halted state and the device endpoint in its
* own halt; neither can drive new transfers until explicitly cleared.
* This driver runs exactly one bulk transfer at a time, so a single
* recovery thread driven by bot_stall_recoveries + the stall_* fields
* below fully describes the recovery — there is no concurrency to
* serialize. The recovery itself is the BOT-spec standard sequence:
* xHCI Reset Endpoint -> Set TR Dequeue Pointer -> USB
* CLEAR_FEATURE(ENDPOINT_HALT), escalating to a Bulk-Only Mass Storage
* Reset + CLEAR_FEATURE on both bulk endpoints on a repeated stall,
* then the original command stage is retried from scratch. The two
* xHCI command steps are correlated via connect_state's two new
* AWAIT_ values; the CLEAR_FEATURE / BOT-reset control transfers are
* correlated via transfer_purpose's two new XHCI_XFER_* values; the
* deferred issue + final re-issue ride next_action's two new
* XHCI_NEXT_ACTION_* values — see xhci_poll_events()'s completion
* handlers for the state machine that consumes these. */
uint32_t bot_stall_recoveries; /* full recoveries performed for the
current command chain, capped at
XHCI_BOT_STALL_MAX_RECOVERIES */
uint32_t stall_dci; /* Device Context Index of the stalled bulk ep */
uint8_t stall_ep_addr; /* bEndpointAddress (bit7=dir) of the stalled bulk ep */
uint8_t bot_reset_clear_remaining; /* CLEAR_FEATUREs still owed in a
BOT-reset escalation (2 = both eps) */
uint32_t stall_retry_action; /* XHCI_NEXT_ACTION_* stage to re-issue once
recovery completes */
/* Milestone 2h: set by the SET_CONFIGURATION completion handler
* (inside xhci_poll_events()'s own call frame, so it only sets a flag
* -- no doorbell ring, no xhci_bot_wait_for_idle() call, both unsafe
* from there) once a device is confirmed Mass Storage/SCSI/BOT and
* configured. Consumed by sk_repl_idle() strictly after its own
* xhci_poll_events() call has returned, which is the only place safe
* 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(). */
/* 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). */
/* 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;
/*
* xhci_find_and_map — locate the xHCI controller on PCI bus 0, enable it
* (I/O+MEM+bus-master), map its BAR0 MMIO region, and
* fill in the four register-region pointers in *dev.
*
* dev must point to a zero-initialised xhci_dev_t.
*
* Returns 0 on success.
* Returns -1 if no xHCI device was found on the PCI bus.
* Returns -2 if the BAR0 mapping failed.
*/
int xhci_find_and_map(xhci_dev_t *dev);
/*
* xhci_bringup — reset the controller, allocate and program the DCBAA,
* Command Ring, and Event Ring (Interrupter 0), then start
* the controller (RUN/STOP=1) and confirm it left the
* halted state.
*
* Must be called after a successful xhci_find_and_map(). Does not enable
* interrupts (USBCMD.INTE / IMAN.IE) -- this driver is polled, not
* interrupt-driven (see xhci_poll_events()'s own doc comment for why).
*
* Returns 0 on success.
* Returns -1 on reset timeout.
* Returns -2 on allocation failure.
* 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 -- 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);
/*
* xhci_get_dev — return the module-static xhci_dev_t* xhci_poll_events()
* itself reads (only one controller is supported), or NULL
* if xhci_bringup() has not completed successfully yet.
*
* Milestone 2h: callers outside this driver (sk_repl_idle(), eventually
* the block-subsystem attach glue) have no other way to reach the device
* handle -- every earlier caller of this driver's API already had one in
* hand (kernel_main.c's own local xhci_dev_t), which doesn't help code
* that only runs later, on a hotplug event it wasn't the one to observe.
*/
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
* connect/disconnect and acknowledges CSC; Command
* Completion and Transfer Event are logged only (slot
* allocation and BOT transfers are later increments).
* Advances the Event Ring dequeue pointer and clears
* ERDP.EHB when done.
*
* Polled, not interrupt-driven: an initial attempt at IRQ delivery
* (Milestone 2d's first draft) found the amd64 PCI INTx routing formula
* gives a demonstrably wrong GSI (checked live via QMP query-pci: xHCI at
* PCI slot 4 reports IRQ 10, the formula predicted 16), and the
* aarch64/riscv64 slot/pin-derived source IDs were unverified at the new
* slot this controller occupies. Rather than guess further at chipset
* PIRQ routing, this matches Section U item 6's own design intent
* (Captain Bob: "interrupt-driven, coarse cadence, cheap early-exit...
* quick check blocks... done") via sk_repl_idle()'s existing coarse-cadence
* hook instead of a per-arch IRQ path -- USB insertion is a human-timescale
* event, not a hot path, so polling costs nothing meaningful here.
*
* No arguments and no return value -- only one xHCI controller is
* supported, so the caller needs no device handle. A no-op if
* xhci_bringup() has not completed successfully (dev pointer not yet
* latched).
*/
void xhci_poll_events(void);
/*
* xhci_cmd_enable_slot — submit an Enable Slot command TRB to the Command
* Ring and ring doorbell 0. Does not wait for or
* read the resulting Command Completion Event -- it
* arrives asynchronously via xhci_poll_events(),
* which correlates the returned Slot ID back to
* dev->pending_connect_port_id and records it in
* dev->port_slot_id[].
*
* Called from xhci_poll_events()'s own Port Status Change handling on a
* real connect event -- not called directly by other code.
*
* Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set
* up (xhci_bringup() has not completed).
*/
int xhci_cmd_enable_slot(xhci_dev_t *dev);
/*
* xhci_cmd_disable_slot — submit a Disable Slot command TRB for slot_id
* and ring doorbell 0. Does not wait for or read
* the resulting Command Completion Event -- it
* arrives asynchronously via xhci_poll_events(),
* which clears DCBAA[slot_id] on success.
*
* Called from xhci_poll_events()'s own Port Status Change handling on a
* real disconnect event, for a slot that was actually addressed -- not
* called directly by other code.
*
* Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set
* up.
*/
int xhci_cmd_disable_slot(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_cmd_address_device — build the Input Context (Slot + EP0, add-only),
* program DCBAA[slot_id] with the Device Context,
* allocate the EP0 Transfer Ring, and submit an
* Address Device command TRB.
*
* speed is the PORTSC.Port Speed value read live at the connect this call
* is servicing (xHCI 1.2 spec table 7-13 speed IDs) -- used to pick EP0's
* default Max Packet Size before any device descriptor has been read.
*
* Refuses (-2) if HCCPARAMS1.CSZ indicates 64-byte contexts -- only
* 32-byte contexts are implemented (see xhci.h's own doc comment on
* xhci_slot_ctx32_t).
*
* Called from xhci_poll_events()'s Command Completion handling once Enable
* Slot succeeds -- not called directly by other code.
*
* Returns 0 if the command was posted, -1 on allocation failure, -2 if
* 64-byte contexts are required.
*/
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
uint32_t port_id, uint32_t speed);
/*
* xhci_cmd_configure_endpoint — build the Input Context (Slot + the two
* bulk EP Contexts, add-only), allocate the
* bulk Transfer Rings, and submit a
* Configure Endpoint command TRB for
* slot_id.
*
* Per xHCI 1.2 spec section 4.3.5, this must be issued after enumeration
* has identified the endpoints a device's chosen configuration/interface
* actually uses, and before the USB SET_CONFIGURATION request is sent to
* the device -- the reverse of that order (which this driver used before
* this increment) works against QEMU's lenient emulation but is not
* spec-correct. Requires dev->bulk_in_ep_addr/bulk_out_ep_addr to already
* be populated (2f's config descriptor walk) -- refuses if either is
* still 0 (not found).
*
* The Slot Context's Route String/Speed/Root Hub Port/Interrupter Target
* fields are copied from the already-addressed device's own Device
* Context (populated by a prior successful Address Device) rather than
* reconstructed from scratch -- those values aren't retained anywhere
* else by the time enumeration reaches this point (pending_connect_port_id
* is cleared as soon as Address Device completes). Only Context Entries is
* changed, to the highest DCI now in use.
*
* Called from xhci_poll_events()'s deferred next_action dispatch once the
* full Configuration descriptor has confirmed a Mass Storage/BOT interface
* and identified both bulk endpoints -- not called directly by other code.
*
* Returns 0 if the command was posted, -1 on allocation failure or missing
* prerequisite state, -2 if 64-byte contexts are required.
*/
int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_cmd_reset_endpoint — submit a Reset Endpoint command TRB for
* slot_id's endpoint `ep_id` (a bEndpointAddress,
* bit 7 = direction). Transitions that endpoint
* from the Halted state back to Stopped in the
* xHC's internal context — the xHCI-level first
* step of G.1 / §F.14 stall recovery, mirroring
* xhci_cmd_disable_slot()'s shape exactly (submit,
* ring doorbell 0, don't wait). The device-side
* halt is cleared separately by
* xhci_ep0_clear_endpoint_halt() once the two
* xHCI command steps (Reset Endpoint, then Set TR
* Dequeue Pointer) have completed.
*
* Called from xhci_poll_events()'s transfer-event STALL handler -- not
* called directly by other code.
*
* Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set
* up.
*/
int xhci_cmd_reset_endpoint(xhci_dev_t *dev, uint32_t slot_id, uint32_t ep_id);
/*
* xhci_cmd_set_tr_dequeue_pointer — submit a Set TR Dequeue Pointer command
* TRB for slot_id's endpoint ep_id,
* repositioning its Transfer Ring's
* dequeue pointer to `new_dequeue` (a
* pointer into the ring, e.g. the ring's
* current producer slot) with cycle state
* `dcs`. The xHCI-level second step of
* G.1 / §F.14 stall recovery: after Reset
* Endpoint has un-halted the ring, this
* tells the controller where to resume /
* discard from so a freshly enqueued TRB
* is consumed cleanly.
*
* Called from xhci_poll_events()'s command-completion handler once Reset
* Endpoint succeeds -- not called directly by other code.
*
* Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set
* up.
*/
int xhci_cmd_set_tr_dequeue_pointer(xhci_dev_t *dev, uint32_t slot_id,
uint32_t ep_id, uint64_t new_dequeue,
uint32_t dcs);
/*
* xhci_bot_send_read10 — build a Command Block Wrapper for a SCSI
* READ(10) and submit it on the bulk OUT Transfer
* Ring; the Data-In stage and CSW receive follow
* automatically once this CBW's own completion
* arrives (see xhci_bot_read_data_in()/
* xhci_bot_receive_csw() below), same deferred-
* chaining pattern as device descriptor -> config
* descriptor -> Configure Endpoint -> SET_CONFIG.
*
* lba is the starting Logical Block Address, num_blocks the SCSI transfer
* length (blocks, not bytes -- READ(10)'s own field), block_size the
* device's actual bytes-per-block, used only to compute
* dCBWDataTransferLength (the data stage's total byte length CBW
* declares up front, not carried in the CDB itself). num_blocks*block_size
* must fit in dev->bot_data_buf (512 bytes, this increment's whole scope
* -- see xhci_dev_t's own doc comment) -- refuses otherwise.
*
* Does not wait for or read any of the three stages' Transfer Events --
* they arrive asynchronously via xhci_poll_events(), correlated via
* dev->transfer_purpose, same pattern as every other transfer in this
* driver. The final result (CSW signature/tag/status validated) is only
* ever logged, not returned to any caller -- there is no synchronous
* "did the read succeed" API yet; that's 2h's problem once something
* actually needs the data back.
*
* Requires bulk_out_ep_addr/bulk_out_ring and bulk_in_ep_addr/
* bulk_in_ring to already be populated (2f/2g's config descriptor walk
* and Configure Endpoint command) -- refuses if any prerequisite is
* missing.
*
* Returns 0 if the CBW was posted, -1 if a prerequisite is missing or
* the requested transfer size exceeds dev->bot_data_buf.
*
* Low-level primitive -- sets dev->bot_cmd_kind = BOT_CMD_READ10 but does
* not run TEST UNIT READY first. Most callers want xhci_bot_read_block()
* below instead; this is called directly only by xhci_poll_events()'s own
* deferred dispatch (XHCI_NEXT_ACTION_BOT_SEND_READ10, once a prior TUR
* has reported PASS) and by xhci_bot_read_block() itself when unit-ready
* confirmation isn't wanted.
*/
int xhci_bot_send_read10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size);
/*
* xhci_bot_send_write10 — build a Command Block Wrapper for a SCSI
* WRITE(10) and submit it on the bulk OUT
* Transfer Ring; the Data-Out stage and CSW
* receive follow automatically once this CBW's
* own completion arrives (see
* xhci_bot_write_data_out()/xhci_bot_receive_csw()
* below) -- direct mirror of
* xhci_bot_send_read10() above, same deferred-
* chaining pattern, opposite data direction.
*
* Unlike READ(10), the caller must have already placed the num_blocks*
* block_size bytes to be written into dev->bot_data_buf *before* calling
* this -- there is no separate "stage the payload" step, matching how
* xhci_bot_read_block()'s caller reads the result back out of
* bot_data_buf only *after* the whole chain completes. bmCBWFlags is 0
* (host -> device data stage), not USB_BOT_CBW_FLAG_DATA_IN -- the one
* CBW-level difference from xhci_bot_send_read10(). CDB layout (SBC-3
* section 5.32) is otherwise identical to READ(10)'s: opcode, then LBA
* and Transfer Length as the same big-endian fields.
*
* lba/num_blocks/block_size have the same meaning and the same
* num_blocks*block_size <= sizeof(dev->bot_data_buf) bound as
* xhci_bot_send_read10(). Requires the same bulk endpoint/ring
* prerequisites -- refuses if any are missing.
*
* Returns 0 if the CBW was posted, -1 if a prerequisite is missing or
* the requested transfer size exceeds dev->bot_data_buf.
*
* Low-level primitive -- sets dev->bot_cmd_kind = BOT_CMD_WRITE10 but
* does not run TEST UNIT READY first. Most callers want
* xhci_bot_write_block() below instead; this is called directly only by
* xhci_poll_events()'s own deferred dispatch
* (XHCI_NEXT_ACTION_BOT_SEND_WRITE10, once a prior TUR has reported
* PASS).
*/
int xhci_bot_send_write10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size);
/*
* xhci_bot_send_test_unit_ready — build a Command Block Wrapper for SCSI
* TEST UNIT READY (6-byte CDB, no data
* stage) and submit it on the bulk OUT
* Transfer Ring. Sets
* dev->bot_expected_data_len = 0 so
* xhci_poll_events()'s CBW-completion
* handler skips the Data-In stage and
* goes straight to CSW receive, per BOT
* spec section 6.3 (host expects no
* data). dev->bot_cmd_kind is set to
* BOT_CMD_TEST_UNIT_READY so the CSW
* handler knows to interpret the result
* as a unit-ready check, not a data
* command.
*
* Requires the same bulk endpoint/ring prerequisites as
* xhci_bot_send_read10() -- refuses if any are missing.
*
* Called by xhci_bot_read_block() to start its TUR-then-READ10 sequence,
* and by xhci_poll_events()'s own deferred dispatch
* (XHCI_NEXT_ACTION_BOT_SEND_TUR) to retry a failed TUR -- not intended
* to be called directly by other code.
*
* Returns 0 if the CBW was posted, -1 if a prerequisite is missing.
*/
int xhci_bot_send_test_unit_ready(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_read_block — the real entry point for reading a block from the
* attached SCSI device. Latches lba/num_blocks/
* block_size into dev->bot_read10_*, resets
* dev->bot_tur_retries to 0, and issues a TEST
* UNIT READY first rather than a bare READ(10).
*
* A freshly attached SCSI target conventionally fails its first command
* with CHECK CONDITION/UNIT ATTENTION until that condition is drained
* (see SCSI_CMD_TEST_UNIT_READY's own doc comment in xhci.h) -- this
* function's whole purpose is absorbing that via a bounded number of TUR
* retries (XHCI_BOT_TUR_MAX_RETRIES) before the actual READ(10) is ever
* sent, rather than making every caller reimplement that sequencing.
* xhci_poll_events()'s deferred dispatch chains TUR -> (retry TUR |
* READ10) -> Data-In -> CSW automatically once this call kicks it off;
* the eventual result (PASS/FAILED, or "gave up after N TUR retries") is
* only ever logged, matching xhci_bot_send_read10()'s own current scope
* -- there is still no synchronous "did the read succeed, here's the
* data" API (2h's problem, per xhci_bot_send_read10()'s doc comment).
*
* Returns 0 if TEST UNIT READY was posted, -1 if a prerequisite is
* missing or the requested transfer size exceeds dev->bot_data_buf (the
* same check xhci_bot_send_read10() performs, done up front here so a
* bad request is rejected before spending a TUR round-trip on it).
*/
int xhci_bot_read_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size);
/*
* xhci_bot_write_block — the real entry point for writing a block to the
* attached SCSI device. Direct mirror of
* xhci_bot_read_block() above: latches
* lba/num_blocks/block_size into
* dev->bot_write10_*, resets dev->bot_tur_retries
* to 0, and issues a TEST UNIT READY first rather
* than a bare WRITE(10), for the same first-command
* UNIT ATTENTION reason.
*
* As with xhci_bot_send_write10(), the caller must have already placed
* the payload bytes into dev->bot_data_buf before calling this.
*
* Returns 0 if TEST UNIT READY was posted, -1 if a prerequisite is
* missing or the requested transfer size exceeds dev->bot_data_buf (same
* check xhci_bot_send_write10() performs, done up front here so a bad
* request is rejected before spending a TUR round-trip on it).
*/
int xhci_bot_write_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
uint16_t num_blocks, uint32_t block_size);
/*
* xhci_bot_send_read_capacity10 — build a Command Block Wrapper for SCSI
* READ CAPACITY(10) (SBC-3 section 5.14,
* opcode 0x25, see xhci.h) and submit it
* on the bulk OUT Transfer Ring. Sets
* dev->bot_cmd_kind = BOT_CMD_READ_CAPACITY10
* and dev->bot_expected_data_len =
* SCSI_READ_CAPACITY10_DATA_LEN (8) so
* the existing CBW-completion handler
* runs the Data-In stage (unlike TEST
* UNIT READY, this command does have a
* short reply). On a PASS CSW,
* dev->bot_cap_last_lba/bot_cap_block_size
* are parsed from the 8-byte reply and
* bot_cmd_kind resets to BOT_CMD_NONE --
* this command is always terminal, it
* never chains into anything else.
*
* Requires the same bulk endpoint/ring prerequisites as
* xhci_bot_send_read10()/xhci_bot_send_test_unit_ready() -- refuses if any
* are missing.
*
* Low-level primitive -- does not run TEST UNIT READY first. Sent to a
* freshly attached device with no TUR ahead of it, this eats the same
* first-command UNIT ATTENTION READ(10) used to before xhci_bot_read_block()
* existed (confirmed live -- see this driver's own Milestone 2h capture
* log). Most callers want xhci_bot_get_capacity() below instead; this is
* called directly only by xhci_poll_events()'s own deferred dispatch
* (XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10, once a prior TUR has
* reported PASS).
*
* Returns 0 if the CBW was posted, -1 if a prerequisite is missing.
*/
int xhci_bot_send_read_capacity10(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_get_capacity — the real entry point for learning a device's
* block size/capacity. Sets
* dev->bot_tur_chain_target =
* BOT_TUR_CHAIN_READ_CAPACITY10, resets
* dev->bot_tur_retries, and issues a TEST UNIT
* READY first rather than a bare READ CAPACITY(10)
* -- same reasoning as xhci_bot_read_block(),
* and the same TUR retry budget
* (XHCI_BOT_TUR_MAX_RETRIES).
*
* Returns 0 if TEST UNIT READY was posted, -1 if a prerequisite is
* missing (same checks xhci_bot_send_read_capacity10() performs, done up
* front here so a bad request is rejected before spending a TUR
* round-trip on it).
*/
int xhci_bot_get_capacity(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_wait_for_idle — busy-wait for the BOT command currently in
* flight (bot_cmd_kind != BOT_CMD_NONE) to reach
* a terminal state, by calling xhci_poll_events()
* in a loop up to max_iters times.
*
* This is Milestone 2h's synchronous bridge over an otherwise fully
* asynchronous, polled driver -- block_subsystem.c's blkio_read()/
* blkio_info() etc. are ordinary synchronous function calls with no way
* to "come back later" for a result, so something has to spin until the
* driver's own event-driven state machine finishes.
*
* MUST NOT be called from inside xhci_poll_events() itself, or from any
* function xhci_poll_events() calls (a next_action dispatch, a Transfer
* Event handler) -- xhci_poll_events() is not reentrant, and this
* function's own busy-wait loop calls it again on every iteration; doing
* so from within an already-running call would recurse into live Event
* Ring/ERDP processing (the same class of hazard this driver's
* next_action deferral mechanism exists to avoid for doorbell rings, see
* xhci_dev_t's own doc comment on next_action). Only call this from a
* context that is definitely outside that call frame -- a caller in
* sk_repl_idle() invoked strictly after its own xhci_poll_events() call
* has already returned, for example.
*
* Returns BOT_STATUS_PASS/BOT_STATUS_FAILED (dev->bot_last_status, as left
* by whichever command was in flight) once bot_cmd_kind returns to
* BOT_CMD_NONE, or BOT_STATUS_TIMEOUT if max_iters is exhausted first
* (the command may still complete later -- this driver has no cancel
* operation, the caller just stops waiting). Returns BOT_STATUS_FAILED
* immediately if dev is NULL.
*/
int xhci_bot_wait_for_idle(xhci_dev_t *dev, uint32_t max_iters);
/*
* xhci_bot_read_data_in — submit a Normal TRB on the bulk IN Transfer
* Ring to read dev->bot_expected_data_len bytes
* into dev->bot_data_buf.
*
* Called from xhci_poll_events()'s deferred next_action dispatch once a
* CBW's own Command completion (XHCI_XFER_CBW_SENT) succeeds -- not
* called directly by other code.
*
* Returns 0 if the TRB was posted, -1 if bulk_in_ring isn't set up.
*/
int xhci_bot_read_data_in(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_write_data_out — submit a Normal TRB on the bulk OUT Transfer
* Ring to write dev->bot_expected_data_len
* bytes from dev->bot_data_buf. Direct mirror
* of xhci_bot_read_data_in() above, opposite
* ring/direction.
*
* Called from xhci_poll_events()'s deferred next_action dispatch once a
* WRITE(10) CBW's own Command completion (XHCI_XFER_CBW_SENT) succeeds --
* not called directly by other code.
*
* Returns 0 if the TRB was posted, -1 if bulk_out_ring isn't set up.
*/
int xhci_bot_write_data_out(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_bot_receive_csw — submit a Normal TRB on the bulk IN Transfer Ring
* to read the 13-byte Command Status Wrapper into
* dev->bot_csw.
*
* Called from xhci_poll_events()'s deferred next_action dispatch once the
* Data-In stage's own Transfer Event (XHCI_XFER_BOT_DATA_IN) succeeds --
* not called directly by other code. The CSW's own completion
* (XHCI_XFER_CSW_RECEIVED) is where signature/tag/status validation
* against dev->bot_last_tag actually happens, in xhci_poll_events()
* itself, not here.
*
* Returns 0 if the TRB was posted, -1 if bulk_in_ring isn't set up.
*/
int xhci_bot_receive_csw(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_ep0_get_device_descriptor — issue a standard GET_DESCRIPTOR
* (Device) control transfer (Setup +
* Data-IN + Status-OUT stages) on
* slot_id's EP0, reading the 18-byte
* result into dev->device_descriptor.
* Sets dev->transfer_purpose so
* xhci_poll_events() knows how to
* interpret the completion.
*
* Called once Address Device succeeds -- not called directly by other
* code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not
* set up.
*/
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id);
/*
* xhci_ep0_get_config_descriptor — issue a GET_DESCRIPTOR (Configuration)
* control transfer for `length` bytes,
* reading into dev->config_descriptor
* (capped to its fixed size). Used
* twice per device: once for a short
* 9-byte read (just the Configuration
* descriptor header, to learn
* wTotalLength) and once for the full
* read once that length is known --
* xhci_poll_events() chains the second
* call automatically on the first
* read's success.
*
* Called once the device descriptor read succeeds -- not called directly
* by other code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not
* set up.
*/
int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t length);
/*
* xhci_ep0_set_configuration — issue a SET_CONFIGURATION control transfer
* (Setup + Status stage only, no Data stage)
* with wValue = config_value. Moves the
* device from Addressed into Configured
* state -- required before any endpoint
* other than EP0 (i.e. the bulk IN/OUT
* endpoints 2g needs) can be used.
*
* Called once the Configuration descriptor read confirms a Mass Storage/
* SCSI/BOT device, with config_value = that descriptor's own
* bConfigurationValue field -- not called directly by other code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not
* set up.
*/
int xhci_ep0_set_configuration(xhci_dev_t *dev, uint32_t slot_id, uint8_t config_value);
/*
* xhci_ep0_clear_endpoint_halt — issue a CLEAR_FEATURE(ENDPOINT_HALT)
* standard control request (Setup + Status
* only, no Data stage, via the existing
* xhci_ep0_control_write_nodata() machinery)
* with wValue = ENDPOINT_HALT and wIndex =
* ep_addr. This is the USB-level step of
* G.1 / §F.14 stall recovery that clears
* the *device's* own halt condition (and
* resets its data toggle), so the endpoint
* will actually drive new transfers after
* the xHC-side Reset Endpoint + Set TR
* Dequeue Pointer commands have run.
* Sets transfer_purpose = XHCI_XFER_CLEAR_HALT
* so xhci_poll_events() can complete the
* recovery and re-issue the stalled command.
*
* Called from xhci_poll_events()'s deferred next_action dispatch (the
* XHCI_NEXT_ACTION_CLEAR_HALT branch, which may run it more than once in a
* BOT-Reset escalation to clear both bulk endpoints) -- not called directly
* by other code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not set
* up.
*/
int xhci_ep0_clear_endpoint_halt(xhci_dev_t *dev, uint32_t slot_id, uint8_t ep_addr);
/*
* xhci_ep0_bot_mass_storage_reset — issue the Bulk-Only Transport class
* request Mass Storage Reset
* (bmRequestType = 0x21 class/interface,
* bRequest = 0xFF, no Data stage, again via
* the existing xhci_ep0_control_write_nodata()
* machinery). The escalation step of G.1 /
* §F.14 stall recovery: BOT spec section
* 5.3.4's full reset of a wedged command
* sequence, followed by
* xhci_ep0_clear_endpoint_halt() on *both*
* bulk endpoints before the original
* command is retried from scratch. Sets
* transfer_purpose = XHCI_XFER_BOT_RESET.
*
* Called from xhci_poll_events()'s deferred next_action dispatch (the
* XHCI_NEXT_ACTION_BOT_RESET branch) -- not called directly by other code.
*
* Returns 0 if the transfer was posted, -1 if dev/dev->ep0_ring is not set
* up.
*/
int xhci_ep0_bot_mass_storage_reset(xhci_dev_t *dev, uint32_t slot_id);
#endif /* STARKERNEL_XHCI_DRIVER_H */