/* * 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 #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 */