Root cause of the G.1 follow-up boot-time attach race: on pathological controller behavior the xhci_poll_events() drain loop had no hard ceiling. ERDP is written back only when the loop exits, so the controller cannot reclaim event TRBs mid-drain; if it keeps producing events the head can chase the software dequeue pointer forever. xhci_poll_events() never returns, sk_repl_idle() never reaches its bot_msc_attach_pending check, and a fresh USB BOT device that finished SET_CONFIGURATION is left flagged-but-never- attached while the guest appears hung. Fix: bound the drain to a full ring (XHCI_EVT_RING_MAX_DRAIN = 256), so xhci_poll_events() always terminates and always writes ERDP each call. Unprocessed events keep their cycle bit and are re-read next poll; nothing is dropped. On the healthy path one drain processes only the one-or-few events the controller posts per chained command, so the bound never triggers except in the pathological case it breaks. Beyond the G.1 additions: a new macro in include/starkernel/xhci.h and a bounded loop in src/starkernel/usb/xhci.c. Builds clean on amd64. Verified across six consecutive fresh QEMU boots (previously intermittently hung).
2174 lines
111 KiB
C
2174 lines
111 KiB
C
/*
|
|
* xhci.c — xHCI USB host controller driver for StarKernel: PCI discovery
|
|
* (Milestone 2b), controller bring-up (2c), polled Event Ring servicing
|
|
* (2d), and Command Ring submission + PORTSC connect/disconnect detection
|
|
* (2e, in progress). Enumeration/BOT read-write (2f-2g) follow in later
|
|
* increments.
|
|
*
|
|
* Memory model: BAR0 is mapped identity (virtual address == physical
|
|
* address), matching virtio_blk.c's precedent and pci_map_bar()'s own
|
|
* documented behavior (amd64: vmm_map_range(phys, phys, ...); other
|
|
* arches: no-op, UEFI identity map already covers it).
|
|
*/
|
|
|
|
#ifndef __STARKERNEL__
|
|
#error "xhci.c is kernel-only"
|
|
#endif
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "starkernel/pci.h"
|
|
#include "starkernel/xhci.h"
|
|
#include "starkernel/xhci_driver.h"
|
|
#include "starkernel/kmalloc.h"
|
|
#include "starkernel/timer.h"
|
|
#include "console.h"
|
|
|
|
/* Conservative fixed BAR0 mapping size. xHCI has no self-describing
|
|
* capability-region length the way virtio PCI capabilities do (that's
|
|
* virtio_blk.c's approach, not available here) — 64 KiB comfortably
|
|
* covers Capability + Operational + Port registers + Runtime + Doorbell
|
|
* Array + typical extended-capability space for QEMU's qemu-xhci and for
|
|
* real hardware controllers with modest port counts. Revisit if a real
|
|
* device's actual BAR size (via PCI BAR-sizing probe, not yet
|
|
* implemented in pci.c) proves this insufficient. */
|
|
#define XHCI_BAR0_MAP_SIZE 0x10000ull
|
|
|
|
int xhci_find_and_map(xhci_dev_t *dev)
|
|
{
|
|
if (!dev) return -1;
|
|
|
|
if (pci_find_first(XHCI_PCI_VENDOR_ID, XHCI_PCI_DEVICE_ID, &dev->pci) != 0) {
|
|
console_println("xhci: no controller found on PCI bus 0");
|
|
return -1;
|
|
}
|
|
|
|
pci_enable(&dev->pci);
|
|
|
|
dev->bar0_phys = pci_bar(&dev->pci, 0);
|
|
if (!dev->bar0_phys) {
|
|
console_println("xhci: BAR0 read failed (zero or I/O BAR)");
|
|
return -1;
|
|
}
|
|
|
|
if (pci_map_bar(dev->bar0_phys, XHCI_BAR0_MAP_SIZE) != 0) {
|
|
console_println("xhci: BAR0 mapping failed");
|
|
return -2;
|
|
}
|
|
|
|
dev->cap = (xhci_cap_regs_t *)(uintptr_t)dev->bar0_phys;
|
|
dev->op = (xhci_op_regs_t *)((uint8_t *)dev->cap + dev->cap->cap_length);
|
|
dev->runtime = (xhci_runtime_regs_t *)((uint8_t *)dev->cap +
|
|
(dev->cap->rts_off & ~0x1Fu));
|
|
dev->doorbell = (xhci_doorbell_t *)((uint8_t *)dev->cap +
|
|
(dev->cap->db_off & ~0x3u));
|
|
|
|
uint32_t hcs1 = dev->cap->hcs_params1;
|
|
dev->max_slots = XHCI_HCSPARAMS1_MAX_SLOTS(hcs1);
|
|
dev->max_intrs = XHCI_HCSPARAMS1_MAX_INTRS(hcs1);
|
|
dev->max_ports = XHCI_HCSPARAMS1_MAX_PORTS(hcs1);
|
|
dev->max_scratchpad_bufs = XHCI_HCSPARAMS2_MAX_SCRATCHPAD_BUFS(dev->cap->hcs_params2);
|
|
|
|
console_println("xhci: controller found, BAR0 mapped");
|
|
return 0;
|
|
}
|
|
|
|
/* Spin-count-bounded busy-wait, matching kernel_main.c's established
|
|
* pattern (heartbeat_ticks() elapsed + a hard spin-count safety cap, not
|
|
* just one or the other). Polls *reg for (val & mask) to equal want_set
|
|
* (0 or 1), re-reading the register itself each iteration. */
|
|
static int xhci_wait_bit(volatile uint32_t *reg, uint32_t mask, int want_set,
|
|
uint64_t max_ticks)
|
|
{
|
|
uint64_t start = heartbeat_ticks();
|
|
uint64_t spins = 0;
|
|
for (;;) {
|
|
uint32_t val = *reg;
|
|
int is_set = (val & mask) != 0;
|
|
if (is_set == want_set) return 0;
|
|
spins++;
|
|
if (heartbeat_ticks() - start >= max_ticks || spins >= 100000000ULL) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* console_println() only takes a string literal -- no formatted print
|
|
* exists on this driver's console path. Matches the established pattern
|
|
* elsewhere in this kernel (e.g. src/starkernel/vm/parity.c's
|
|
* print_hex64()) rather than adding one: a small static hex-dump helper,
|
|
* "label: 0xXXXXXXXX". Debugging register values without this is
|
|
* guesswork. */
|
|
static void xhci_log_hex32(const char *label, uint32_t val)
|
|
{
|
|
char buf[11];
|
|
buf[0] = '0';
|
|
buf[1] = 'x';
|
|
for (int i = 9; i >= 2; i--) {
|
|
int d = val & 0xF;
|
|
buf[i] = (d < 10) ? ('0' + d) : ('a' + d - 10);
|
|
val >>= 4;
|
|
}
|
|
buf[10] = '\0';
|
|
console_puts(label);
|
|
console_println(buf);
|
|
}
|
|
|
|
/* Only one controller is supported (matches xhci_dev_t's own doc comment);
|
|
* latched at the end of a successful xhci_bringup() for
|
|
* xhci_poll_events()'s use. */
|
|
static xhci_dev_t *g_xhci_dev = NULL;
|
|
|
|
/* Forward declaration -- xhci_bringup() below issues one Enable Slot as a
|
|
* command-ring smoke test; the implementation lives after xhci_bringup()
|
|
* (see the "Command Ring submission" section) so it can stay close to
|
|
* xhci_poll_events(), the read side of the same ring pair. */
|
|
int xhci_cmd_enable_slot(xhci_dev_t *dev);
|
|
int xhci_cmd_disable_slot(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
|
uint32_t port_id, uint32_t speed);
|
|
int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_cmd_reset_endpoint(xhci_dev_t *dev, uint32_t slot_id, uint32_t ep_id);
|
|
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);
|
|
int xhci_bot_send_read10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
|
|
uint16_t num_blocks, uint32_t block_size);
|
|
int xhci_bot_send_write10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
|
|
uint16_t num_blocks, uint32_t block_size);
|
|
int xhci_bot_send_test_unit_ready(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_bot_send_read_capacity10(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_bot_get_capacity(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_bot_read_data_in(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_bot_write_data_out(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_bot_receive_csw(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id);
|
|
int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t length);
|
|
int xhci_ep0_set_configuration(xhci_dev_t *dev, uint32_t slot_id, uint8_t config_value);
|
|
|
|
/* Forward declaration -- xhci_bringup() below calls this once the
|
|
* controller is running, to catch a device that was already connected
|
|
* (present on the QEMU command line at launch, not hot-plugged after boot).
|
|
* The implementation lives after xhci_poll_events() since it shares
|
|
* xhci_port_regs() and the same connect-handling logic. See xhci_scan_
|
|
* ports_for_already_connected()'s own doc comment for why this exists. */
|
|
static void xhci_scan_ports_for_already_connected(xhci_dev_t *dev);
|
|
|
|
int xhci_bringup(xhci_dev_t *dev)
|
|
{
|
|
if (!dev || !dev->op) return -2;
|
|
|
|
/* 1. If running, stop first (Run/Stop must be cleared before HCRST is
|
|
* guaranteed to behave per spec on some implementations). */
|
|
if (dev->op->usb_cmd & XHCI_USBCMD_RUN) {
|
|
dev->op->usb_cmd &= ~XHCI_USBCMD_RUN;
|
|
if (xhci_wait_bit(&dev->op->usb_sts, XHCI_USBSTS_HCH, 1, 10000) != 0) {
|
|
console_println("xhci: timeout waiting for halt before reset");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* 2. Host Controller Reset. HCRST self-clears; CNR (Controller Not
|
|
* Ready) must also clear before touching any other operational
|
|
* register. */
|
|
dev->op->usb_cmd |= XHCI_USBCMD_HCRST;
|
|
if (xhci_wait_bit(&dev->op->usb_cmd, XHCI_USBCMD_HCRST, 0, 10000) != 0) {
|
|
console_println("xhci: timeout waiting for HCRST to self-clear");
|
|
return -1;
|
|
}
|
|
if (xhci_wait_bit(&dev->op->usb_sts, XHCI_USBSTS_CNR, 0, 10000) != 0) {
|
|
console_println("xhci: timeout waiting for CNR to clear");
|
|
return -1;
|
|
}
|
|
|
|
/* 3. Device Context Base Address Array — (max_slots+1) x 8-byte
|
|
* pointers, 64-byte aligned, zeroed (DCBAAP requires 64-byte
|
|
* alignment per spec; kmalloc_aligned enforces it). */
|
|
size_t dcbaa_bytes = (size_t)(dev->max_slots + 1) * sizeof(uint64_t);
|
|
dev->dcbaa = kmalloc_aligned(dcbaa_bytes, 64);
|
|
if (!dev->dcbaa) {
|
|
console_println("xhci: DCBAA allocation failed");
|
|
return -2;
|
|
}
|
|
for (size_t i = 0; i < dcbaa_bytes / sizeof(uint64_t); i++) {
|
|
((uint64_t *)dev->dcbaa)[i] = 0;
|
|
}
|
|
|
|
/* 3b. Scratchpad buffers, only if the controller asks for them (slot 0
|
|
* of the DCBAA points at the scratchpad buffer array, not a device
|
|
* context, when max_scratchpad_bufs > 0). PAGESIZE register: bit N
|
|
* set means 2^(N+12)-byte pages; use the lowest set bit found. */
|
|
if (dev->max_scratchpad_bufs > 0) {
|
|
uint32_t pagesize_bits = dev->op->page_size;
|
|
uint32_t page_bytes = 4096u;
|
|
for (uint32_t b = 0; b < 16; b++) {
|
|
if (pagesize_bits & (1u << b)) { page_bytes = 1u << (b + 12); break; }
|
|
}
|
|
size_t arr_bytes = (size_t)dev->max_scratchpad_bufs * sizeof(uint64_t);
|
|
dev->scratchpad_arr = kmalloc_aligned(arr_bytes, 64);
|
|
if (!dev->scratchpad_arr) {
|
|
console_println("xhci: scratchpad array allocation failed");
|
|
return -2;
|
|
}
|
|
for (uint32_t i = 0; i < dev->max_scratchpad_bufs; i++) {
|
|
void *buf = kmalloc_aligned(page_bytes, page_bytes);
|
|
if (!buf) {
|
|
console_println("xhci: scratchpad buffer allocation failed");
|
|
return -2;
|
|
}
|
|
((uint64_t *)dev->scratchpad_arr)[i] = (uint64_t)(uintptr_t)buf;
|
|
}
|
|
((uint64_t *)dev->dcbaa)[0] = (uint64_t)(uintptr_t)dev->scratchpad_arr;
|
|
}
|
|
|
|
dev->op->dcbaap = (uint64_t)(uintptr_t)dev->dcbaa;
|
|
|
|
/* 4. Command Ring — XHCI_RING_TRB_COUNT TRBs, 64-byte aligned, zeroed.
|
|
* Initial Ring Cycle State = 1 (software convention; the ring is
|
|
* "owned" by software until the first TRB with a matching cycle bit
|
|
* is consumed). CRCR low bits carry RCS, not the TRBs themselves. */
|
|
dev->cmd_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
|
|
if (!dev->cmd_ring) {
|
|
console_println("xhci: command ring allocation failed");
|
|
return -2;
|
|
}
|
|
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
|
|
dev->cmd_ring[i].parameter = 0;
|
|
dev->cmd_ring[i].status = 0;
|
|
dev->cmd_ring[i].control = 0;
|
|
}
|
|
dev->cmd_ring_cycle = 1;
|
|
dev->cmd_ring_enq = 0;
|
|
|
|
/* Last slot is a permanent Link TRB back to index 0 (xHCI 1.2 spec
|
|
* §4.9.2 — software must terminate every ring segment with one; without
|
|
* it the controller reads uninitialised memory past the segment instead
|
|
* of wrapping). Toggle Cycle (TC) tells the controller to flip its own
|
|
* consumer cycle state when it processes this TRB, matching the
|
|
* producer-side toggle xhci_submit_command() below performs on wrap. */
|
|
dev->cmd_ring[XHCI_RING_TRB_COUNT - 1].parameter =
|
|
(uint64_t)(uintptr_t)dev->cmd_ring;
|
|
dev->cmd_ring[XHCI_RING_TRB_COUNT - 1].control =
|
|
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_TC | XHCI_TRB_CONTROL_CYCLE;
|
|
|
|
dev->op->crcr = ((uint64_t)(uintptr_t)dev->cmd_ring & XHCI_CRCR_PTR_MASK) |
|
|
XHCI_CRCR_RCS;
|
|
|
|
/* 5. Event Ring — one segment (Event Ring Segment Table with a single
|
|
* 16-byte entry: base address + size), wired to Interrupter 0.
|
|
* Interrupter Register Sets start at runtime_base + 0x20; each is
|
|
* sizeof(xhci_intr_regs_t) apart, but only Interrupter 0 is used
|
|
* (single-interrupter design decided in 2a). */
|
|
dev->evt_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
|
|
if (!dev->evt_ring) {
|
|
console_println("xhci: event ring allocation failed");
|
|
return -2;
|
|
}
|
|
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
|
|
dev->evt_ring[i].parameter = 0;
|
|
dev->evt_ring[i].status = 0;
|
|
dev->evt_ring[i].control = 0;
|
|
}
|
|
dev->evt_ring_cycle = 1;
|
|
dev->evt_ring_deq = 0;
|
|
|
|
/* Event Ring Segment Table entry layout: u64 base + u32 size + u32
|
|
* reserved = 16 bytes. One segment is enough (ERST Max >= 1 always). */
|
|
dev->evt_ring_seg_table = kmalloc_aligned(16, 64);
|
|
if (!dev->evt_ring_seg_table) {
|
|
console_println("xhci: event ring segment table allocation failed");
|
|
return -2;
|
|
}
|
|
uint64_t *erst = (uint64_t *)dev->evt_ring_seg_table;
|
|
erst[0] = (uint64_t)(uintptr_t)dev->evt_ring; /* base address */
|
|
erst[1] = (uint64_t)XHCI_RING_TRB_COUNT; /* size, low 32 bits used */
|
|
|
|
dev->intr0 = (xhci_intr_regs_t *)((uint8_t *)dev->runtime + 0x20);
|
|
xhci_intr_regs_t *intr0 = dev->intr0;
|
|
intr0->erstsz = 1;
|
|
intr0->erstba = (uint64_t)(uintptr_t)dev->evt_ring_seg_table;
|
|
intr0->erdp = ((uint64_t)(uintptr_t)dev->evt_ring & XHCI_ERDP_PTR_MASK);
|
|
|
|
/* 6. Enable device slots (all of them — no reason to restrict for a
|
|
* single-drive-at-a-time driver) and start the controller. */
|
|
dev->op->config = XHCI_CONFIG_MAX_SLOTS_EN(dev->max_slots);
|
|
dev->op->usb_cmd |= XHCI_USBCMD_RUN;
|
|
|
|
if (xhci_wait_bit(&dev->op->usb_sts, XHCI_USBSTS_HCH, 0, 10000) != 0) {
|
|
console_println("xhci: controller did not leave halted state after RUN");
|
|
return -3;
|
|
}
|
|
|
|
/* Milestone 2e: per-port Enable Slot correlation state -- fixed array,
|
|
* see xhci_dev_t's own doc comment; no allocation needed. */
|
|
for (uint32_t i = 0; i < XHCI_MAX_TRACKED_PORTS; i++) dev->port_slot_id[i] = 0;
|
|
dev->pending_connect_port_id = 0;
|
|
dev->pending_connect_speed = 0;
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
dev->pending_connect_slot_id = 0;
|
|
dev->pending_disable_slot_id = 0;
|
|
dev->input_ctx = NULL;
|
|
dev->device_ctx = NULL;
|
|
dev->ep0_ring = NULL;
|
|
dev->ep0_ring_cycle = 1;
|
|
dev->ep0_ring_enq = 0;
|
|
dev->pending_transfer_slot_id = 0;
|
|
dev->transfer_purpose = XHCI_XFER_NONE;
|
|
dev->config_total_length = 0;
|
|
dev->bulk_in_ep_addr = 0;
|
|
dev->bulk_in_max_packet = 0;
|
|
dev->bulk_out_ep_addr = 0;
|
|
dev->bulk_out_max_packet = 0;
|
|
dev->bulk_in_ring = NULL;
|
|
dev->bulk_in_ring_cycle = 1;
|
|
dev->bulk_in_ring_enq = 0;
|
|
dev->bulk_out_ring = NULL;
|
|
dev->bulk_out_ring_cycle = 1;
|
|
dev->bulk_out_ring_enq = 0;
|
|
dev->bot_next_tag = 1;
|
|
dev->bot_last_tag = 0;
|
|
dev->bot_expected_data_len = 0;
|
|
dev->bot_cmd_kind = BOT_CMD_NONE;
|
|
dev->bot_last_status = BOT_STATUS_IDLE;
|
|
dev->bot_tur_chain_target = BOT_TUR_CHAIN_NONE;
|
|
dev->bot_tur_retries = 0;
|
|
dev->bot_read10_lba = 0;
|
|
dev->bot_read10_num_blocks = 0;
|
|
dev->bot_read10_block_size = 0;
|
|
dev->bot_cap_last_lba = 0;
|
|
dev->bot_cap_block_size = 0;
|
|
dev->bot_msc_attach_pending = 0;
|
|
dev->bot_msc_attach_slot_id = 0;
|
|
dev->bot_msc_attached = 0;
|
|
dev->bot_msc_detach_pending = 0;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
dev->next_action_slot_id = 0;
|
|
dev->next_action_length = 0;
|
|
dev->next_action_config_value = 0;
|
|
|
|
console_println("xhci: controller running");
|
|
/* Milestone 2e prep: HCCPARAMS1.CSZ decides 32- vs 64-byte Slot/
|
|
* Endpoint/Input Context layout for Address Device -- must be read
|
|
* live, not assumed, before any context structure is designed. */
|
|
xhci_log_hex32("xhci: hcc_params1=", dev->cap->hcc_params1);
|
|
console_println(XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)
|
|
? "xhci: context size = 64 bytes"
|
|
: "xhci: context size = 32 bytes");
|
|
|
|
g_xhci_dev = dev;
|
|
|
|
/* Catch a device already connected at launch -- xhci_poll_events() is
|
|
* purely event-ring-driven (Port Status Change events only), and a
|
|
* device present on the QEMU command line before this controller reset
|
|
* never generates one (nothing "changed" from the controller's
|
|
* perspective once it starts looking). g_xhci_dev must be set first --
|
|
* this reuses the same connect-handling path xhci_poll_events() uses,
|
|
* which reads it as a singleton rather than taking dev as an arg. */
|
|
xhci_scan_ports_for_already_connected(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
xhci_dev_t *xhci_get_dev(void)
|
|
{
|
|
return g_xhci_dev;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Command Ring submission -- Milestone 2e. Shared by Enable Slot now and
|
|
* Address Device next; xhci_poll_events() above is the read side of this
|
|
* same ring pair, already verified live for Port Status Change events.
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
/* extra_control_bits ORs additional fields into the TRB's control dword
|
|
* beyond type/cycle -- e.g. Address Device's Slot ID at bits[31:24]
|
|
* (Enable Slot needs none, passes 0). Never includes the cycle bit itself;
|
|
* that's computed here from cmd_ring_cycle so callers can't get it wrong. */
|
|
static void xhci_submit_command(xhci_dev_t *dev, uint64_t parameter,
|
|
uint32_t status, uint32_t trb_type,
|
|
uint32_t extra_control_bits)
|
|
{
|
|
xhci_trb_t *trb = &dev->cmd_ring[dev->cmd_ring_enq];
|
|
trb->parameter = parameter;
|
|
trb->status = status;
|
|
trb->control = (trb_type << XHCI_TRB_CONTROL_TYPE_SHIFT) | extra_control_bits |
|
|
(dev->cmd_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
|
|
|
dev->cmd_ring_enq++;
|
|
if (dev->cmd_ring_enq == XHCI_RING_TRB_COUNT - 1) {
|
|
/* About to hand the Link TRB to the controller -- its cycle bit
|
|
* must match the producer cycle state at the moment of handoff,
|
|
* and the producer state flips here too (this is the wrap). */
|
|
dev->cmd_ring[XHCI_RING_TRB_COUNT - 1].control =
|
|
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_TC |
|
|
(dev->cmd_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
|
dev->cmd_ring_enq = 0;
|
|
dev->cmd_ring_cycle ^= 1u;
|
|
}
|
|
|
|
/* Doorbell 0 targets the Command Ring (XHCI_DB_TARGET(0)); write-only,
|
|
* one write per new TRB posted -- xhci.h's own doc comment on the
|
|
* Doorbell Array. */
|
|
dev->doorbell[0] = XHCI_DB_TARGET(0);
|
|
}
|
|
|
|
int xhci_cmd_enable_slot(xhci_dev_t *dev)
|
|
{
|
|
if (!dev || !dev->cmd_ring) return -1;
|
|
xhci_submit_command(dev, 0, 0, XHCI_TRB_TYPE_ENABLE_SLOT_CMD, 0);
|
|
console_println("xhci: enable slot command submitted");
|
|
return 0;
|
|
}
|
|
|
|
int xhci_cmd_disable_slot(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->cmd_ring) return -1;
|
|
/* Slot ID goes in control[31:24], same field Address Device uses --
|
|
* no parameter/status payload needed, this command just names a slot. */
|
|
xhci_submit_command(dev, 0, 0, XHCI_TRB_TYPE_DISABLE_SLOT_CMD, slot_id << 24);
|
|
console_println("xhci: disable slot command submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* Default EP0 Max Packet Size by PORTSC.Port Speed, used before any device
|
|
* descriptor has been read (xHCI 1.2 spec's own recommended defaults --
|
|
* the real value comes from bMaxPacketSize0 once 2f reads the device
|
|
* descriptor and issues an Evaluate Context to correct it if needed). */
|
|
static uint32_t xhci_default_ep0_max_packet(uint32_t speed)
|
|
{
|
|
switch (speed) {
|
|
case 4: return 512; /* SuperSpeed */
|
|
case 3: return 64; /* High Speed */
|
|
case 2: return 8; /* Low Speed */
|
|
default: return 64; /* Full Speed (1) and anything unrecognised */
|
|
}
|
|
}
|
|
|
|
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
|
uint32_t port_id, uint32_t speed)
|
|
{
|
|
if (!dev || !dev->cmd_ring || !dev->dcbaa) return -1;
|
|
|
|
if (XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)) {
|
|
console_println("xhci: 64-byte contexts required, not implemented -- refusing");
|
|
return -2;
|
|
}
|
|
|
|
/* Lazily allocate once; reused across every connect (single-device
|
|
* scope -- see xhci_dev_t's doc comment). All three re-initialised
|
|
* fully below regardless of whether this is the first call. */
|
|
if (!dev->input_ctx) {
|
|
dev->input_ctx = kmalloc_aligned(
|
|
sizeof(xhci_input_ctrl_ctx32_t) + sizeof(xhci_slot_ctx32_t) +
|
|
sizeof(xhci_ep_ctx32_t), 64);
|
|
if (!dev->input_ctx) return -1;
|
|
}
|
|
if (!dev->device_ctx) {
|
|
dev->device_ctx = kmalloc_aligned(
|
|
sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t), 64);
|
|
if (!dev->device_ctx) return -1;
|
|
}
|
|
if (!dev->ep0_ring) {
|
|
dev->ep0_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
|
|
if (!dev->ep0_ring) return -1;
|
|
}
|
|
|
|
/* EP0 Transfer Ring: same fixed-ring-plus-Link-TRB pattern as the
|
|
* Command Ring (xhci_bringup()'s own comment on why). Freshly
|
|
* reinitialised on every call, not just the first -- cheap (4KiB) and
|
|
* avoids carrying stale TRBs from a previous connect. */
|
|
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
|
|
dev->ep0_ring[i].parameter = 0;
|
|
dev->ep0_ring[i].status = 0;
|
|
dev->ep0_ring[i].control = 0;
|
|
}
|
|
dev->ep0_ring[XHCI_RING_TRB_COUNT - 1].parameter = (uint64_t)(uintptr_t)dev->ep0_ring;
|
|
dev->ep0_ring[XHCI_RING_TRB_COUNT - 1].control =
|
|
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_TC | XHCI_TRB_CONTROL_CYCLE;
|
|
dev->ep0_ring_cycle = 1;
|
|
dev->ep0_ring_enq = 0;
|
|
|
|
/* Device Context: Slot Context followed by EP0 Context, no Input
|
|
* Control Context (that only exists in the Input Context below).
|
|
* DCBAA[slot_id] must point here, per spec -- zeroed since the
|
|
* controller writes this on Address Device success, software must not
|
|
* pre-fill it. */
|
|
uint8_t *dctx = (uint8_t *)dev->device_ctx;
|
|
for (size_t i = 0; i < sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t); i++) dctx[i] = 0;
|
|
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)dev->device_ctx;
|
|
|
|
/* Input Context: Input Control Context, then Slot Context, then EP0
|
|
* Context -- this is what the command TRB's parameter points at (never
|
|
* the Device Context; conflating the two is the standard mistake
|
|
* here). */
|
|
uint8_t *ictx = (uint8_t *)dev->input_ctx;
|
|
size_t total = sizeof(xhci_input_ctrl_ctx32_t) + sizeof(xhci_slot_ctx32_t) +
|
|
sizeof(xhci_ep_ctx32_t);
|
|
for (size_t i = 0; i < total; i++) ictx[i] = 0;
|
|
|
|
xhci_input_ctrl_ctx32_t *ctrl = (xhci_input_ctrl_ctx32_t *)ictx;
|
|
ctrl->add_flags = XHCI_INPUT_CTRL_ADD_SLOT | XHCI_INPUT_CTRL_ADD_EP0;
|
|
|
|
xhci_slot_ctx32_t *slot = (xhci_slot_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t));
|
|
slot->dword0 = (speed << XHCI_SLOT_CTX_SPEED_SHIFT) | (1u << XHCI_SLOT_CTX_CONTEXT_ENTRIES_SHIFT);
|
|
slot->dword1 = port_id << XHCI_SLOT_CTX_ROOT_PORT_SHIFT;
|
|
slot->dword2 = 0u << XHCI_SLOT_CTX_INTR_TARGET_SHIFT; /* Interrupter 0 */
|
|
|
|
xhci_ep_ctx32_t *ep0 = (xhci_ep_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t) +
|
|
sizeof(xhci_slot_ctx32_t));
|
|
ep0->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
|
|
(XHCI_EP_CTX_TYPE_CONTROL_BIDI << XHCI_EP_CTX_TYPE_SHIFT) |
|
|
(xhci_default_ep0_max_packet(speed) << XHCI_EP_CTX_MAX_PACKET_SHIFT);
|
|
ep0->tr_dequeue_ptr = ((uint64_t)(uintptr_t)dev->ep0_ring) | 1u; /* DCS = 1 */
|
|
ep0->dword4 = 8u; /* Average TRB Length -- spec's own recommended default for EP0 */
|
|
|
|
xhci_submit_command(dev, (uint64_t)(uintptr_t)dev->input_ctx, 0,
|
|
XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD, slot_id << 24);
|
|
console_println("xhci: address device command submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* Initialise one bulk Transfer Ring in place -- same fixed-ring-plus-
|
|
* Link-TRB pattern as ep0_ring/the Command Ring, factored out since
|
|
* Configure Endpoint needs to do this twice (IN and OUT). */
|
|
static void xhci_init_bulk_ring(xhci_trb_t *ring)
|
|
{
|
|
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
|
|
ring[i].parameter = 0;
|
|
ring[i].status = 0;
|
|
ring[i].control = 0;
|
|
}
|
|
ring[XHCI_RING_TRB_COUNT - 1].parameter = (uint64_t)(uintptr_t)ring;
|
|
ring[XHCI_RING_TRB_COUNT - 1].control =
|
|
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_TC | XHCI_TRB_CONTROL_CYCLE;
|
|
}
|
|
|
|
int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->cmd_ring || !dev->input_ctx || !dev->device_ctx) return -1;
|
|
if (dev->bulk_in_ep_addr == 0 || dev->bulk_out_ep_addr == 0) return -1;
|
|
|
|
if (XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)) {
|
|
console_println("xhci: 64-byte contexts required, not implemented -- refusing");
|
|
return -2;
|
|
}
|
|
|
|
if (!dev->bulk_in_ring) {
|
|
dev->bulk_in_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
|
|
if (!dev->bulk_in_ring) return -1;
|
|
}
|
|
if (!dev->bulk_out_ring) {
|
|
dev->bulk_out_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
|
|
if (!dev->bulk_out_ring) return -1;
|
|
}
|
|
xhci_init_bulk_ring(dev->bulk_in_ring);
|
|
dev->bulk_in_ring_cycle = 1;
|
|
dev->bulk_in_ring_enq = 0;
|
|
xhci_init_bulk_ring(dev->bulk_out_ring);
|
|
dev->bulk_out_ring_cycle = 1;
|
|
dev->bulk_out_ring_enq = 0;
|
|
|
|
uint32_t in_dci = XHCI_EP_ADDR_TO_DCI(dev->bulk_in_ep_addr);
|
|
uint32_t out_dci = XHCI_EP_ADDR_TO_DCI(dev->bulk_out_ep_addr);
|
|
uint32_t max_dci = (in_dci > out_dci) ? in_dci : out_dci;
|
|
|
|
/* DCBAA[slot_id]'s Device Context also needs to grow to hold the new
|
|
* EP Contexts -- it's currently sized for Slot+EP0 only (from Address
|
|
* Device). Unlike the Input Context below, this one's *existing*
|
|
* content must be preserved, not zeroed: xHCI 1.2 spec section 4.6.6
|
|
* only has the controller write the DCIs actually named in this
|
|
* command's Add/Drop flags (EP0's entry here is neither), so it
|
|
* expects to find EP0's live output state (its current TR Dequeue
|
|
* Pointer in particular) still intact in the Device Context it reads
|
|
* -- swapping in a freshly zeroed buffer would hand the controller a
|
|
* blank EP0 out from under an endpoint it isn't being asked to touch. */
|
|
size_t old_device_ctx_bytes = sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t);
|
|
size_t new_device_ctx_bytes = (1 + (size_t)max_dci) * sizeof(xhci_ep_ctx32_t);
|
|
void *new_device_ctx = kmalloc_aligned(new_device_ctx_bytes, 64);
|
|
if (!new_device_ctx) return -1;
|
|
uint8_t *ndctx = (uint8_t *)new_device_ctx;
|
|
for (size_t i = 0; i < new_device_ctx_bytes; i++) ndctx[i] = 0;
|
|
const uint8_t *odctx = (const uint8_t *)dev->device_ctx;
|
|
for (size_t i = 0; i < old_device_ctx_bytes; i++) ndctx[i] = odctx[i];
|
|
dev->device_ctx = new_device_ctx;
|
|
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)dev->device_ctx;
|
|
|
|
/* dev->input_ctx is reused from Address Device -- same allocation
|
|
* (96 bytes: Input Control Ctx + Slot Ctx + one EP Ctx) is too small
|
|
* to also hold two more EP Contexts. Configure Endpoint's Input
|
|
* Context must span every DCI up to max_dci (xHCI 1.2 spec section
|
|
* 6.2.5.1: "the Input Context data structure shall contain output
|
|
* context data structures... up to the value of the Context Entries
|
|
* field"), not just the ones actually being added -- unused slots
|
|
* between EP0 (DCI 1) and the bulk endpoints are left zeroed
|
|
* (Add/Drop flags for those DCIs are 0, so the controller ignores
|
|
* their content). Reallocated here rather than growing the existing
|
|
* 96-byte block in place. */
|
|
size_t ctx_count = 1 /* Slot */ + max_dci; /* DCI 1..max_dci, one xhci_ep_ctx32_t each */
|
|
size_t total = sizeof(xhci_input_ctrl_ctx32_t) + ctx_count * sizeof(xhci_ep_ctx32_t);
|
|
void *new_input_ctx = kmalloc_aligned(total, 64);
|
|
if (!new_input_ctx) return -1;
|
|
dev->input_ctx = new_input_ctx;
|
|
|
|
uint8_t *ictx = (uint8_t *)dev->input_ctx;
|
|
for (size_t i = 0; i < total; i++) ictx[i] = 0;
|
|
|
|
xhci_input_ctrl_ctx32_t *ctrl = (xhci_input_ctrl_ctx32_t *)ictx;
|
|
ctrl->add_flags = XHCI_INPUT_CTRL_ADD_SLOT | (1u << in_dci) | (1u << out_dci);
|
|
|
|
/* Slot Context: copied from the already-addressed device's own
|
|
* Device Context (Route String/Speed/Root Hub Port/Interrupter
|
|
* Target aren't retained anywhere else by this point in enumeration
|
|
* -- see this function's own doc comment), Context Entries updated
|
|
* to the highest DCI now in use. dword3 (Device Address, Slot State)
|
|
* is an Output-only field and stays zero, matching Address Device's
|
|
* own Input Context handling. */
|
|
xhci_slot_ctx32_t *dev_slot = (xhci_slot_ctx32_t *)dev->device_ctx;
|
|
xhci_slot_ctx32_t *in_slot = (xhci_slot_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t));
|
|
uint32_t entries_mask = ~((uint32_t)0x1Fu << XHCI_SLOT_CTX_CONTEXT_ENTRIES_SHIFT);
|
|
in_slot->dword0 = (dev_slot->dword0 & entries_mask) |
|
|
(max_dci << XHCI_SLOT_CTX_CONTEXT_ENTRIES_SHIFT);
|
|
in_slot->dword1 = dev_slot->dword1;
|
|
in_slot->dword2 = dev_slot->dword2;
|
|
|
|
/* EP Contexts for the two bulk endpoints, at their own DCI slot
|
|
* within the Input Context (index = DCI - 1, since the Slot Context
|
|
* occupies index -1 relative to DCI numbering -- DCI 1 is the first
|
|
* EP Context). Average TRB Length is a scheduling hint, not a
|
|
* correctness constraint (spec: "should approximate the length of
|
|
* the transfers that will be enqueued") -- 1024 is a placeholder;
|
|
* revisit once real CBW/data/CSW transfer sizes are known (2g's next
|
|
* items). Max Burst Size is left 0 (single-burst) since SuperSpeed
|
|
* Endpoint Companion descriptor parsing isn't implemented yet --
|
|
* matches every real device this driver has been tested against
|
|
* under QEMU's emulation, revisit if a real high-throughput SS
|
|
* device needs it. */
|
|
xhci_ep_ctx32_t *in_ep = (xhci_ep_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t) +
|
|
(size_t)in_dci * sizeof(xhci_ep_ctx32_t));
|
|
in_ep->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
|
|
(XHCI_EP_CTX_TYPE_BULK_IN << XHCI_EP_CTX_TYPE_SHIFT) |
|
|
((uint32_t)dev->bulk_in_max_packet << XHCI_EP_CTX_MAX_PACKET_SHIFT);
|
|
in_ep->tr_dequeue_ptr = ((uint64_t)(uintptr_t)dev->bulk_in_ring) | 1u; /* DCS = 1 */
|
|
in_ep->dword4 = 1024u;
|
|
|
|
xhci_ep_ctx32_t *out_ep = (xhci_ep_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t) +
|
|
(size_t)out_dci * sizeof(xhci_ep_ctx32_t));
|
|
out_ep->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
|
|
(XHCI_EP_CTX_TYPE_BULK_OUT << XHCI_EP_CTX_TYPE_SHIFT) |
|
|
((uint32_t)dev->bulk_out_max_packet << XHCI_EP_CTX_MAX_PACKET_SHIFT);
|
|
out_ep->tr_dequeue_ptr = ((uint64_t)(uintptr_t)dev->bulk_out_ring) | 1u; /* DCS = 1 */
|
|
out_ep->dword4 = 1024u;
|
|
|
|
xhci_submit_command(dev, (uint64_t)(uintptr_t)dev->input_ctx, 0,
|
|
XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD, slot_id << 24);
|
|
console_println("xhci: configure endpoint command submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* G.1 / §F.14 stall recovery: Reset Endpoint command. ep_id is a full
|
|
* bEndpointAddress (bit 7 = direction) of the stalled bulk endpoint -- the
|
|
* xHC is told which endpoint to un-halt via its Endpoint ID (the DCI) in
|
|
* parameter[31:24], exactly the field layout Reset Endpoint uses (xHCI 1.2
|
|
* spec table 6-88): parameter[31:24] = Endpoint ID, control[31:24] = Slot
|
|
* ID. Mirrors the existing xhci_cmd_disable_slot() shape (submit, ring
|
|
* doorbell 0, don't wait) -- this touches no doorbell but [0]. */
|
|
int xhci_cmd_reset_endpoint(xhci_dev_t *dev, uint32_t slot_id, uint32_t ep_id)
|
|
{
|
|
if (!dev || !dev->cmd_ring) return -1;
|
|
uint32_t dci = XHCI_EP_ADDR_TO_DCI(ep_id);
|
|
if (dci == 0) return -1; /* EP0 (DCI 1) is the only non-bulk case -- refuse */
|
|
xhci_submit_command(dev, (uint64_t)dci << 24, 0,
|
|
XHCI_TRB_TYPE_RESET_ENDPOINT_CMD, slot_id << 24);
|
|
console_println("xhci: reset endpoint command submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* G.1 / §F.14 stall recovery: Set TR Dequeue Pointer command. new_dequeue
|
|
* is a pointer into the stalled endpoint's Transfer Ring (the current
|
|
* producer slot, past any failed TRBs) and dcs is the ring's current Cycle
|
|
* State -- both written into parameter exactly as the spec's Set TR
|
|
* Dequeue Pointer (xHCI 1.2 table 6-91) defines: parameter[63:4] = the
|
|
* dequeue pointer, parameter[0] = DCS, control[31:24] = Slot ID. */
|
|
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)
|
|
{
|
|
if (!dev || !dev->cmd_ring) return -1;
|
|
/* ep_id names the endpoint whose ring new_dequeue points into. The Set
|
|
* TR Dequeue Pointer TRB itself carries no Endpoint ID field -- the
|
|
* ring the controller should update is implied by the dequeue pointer
|
|
* + the controller already having Reset this endpoint (G.1's sequence
|
|
* runs Reset Endpoint before this) -- so ep_id is a documentation /
|
|
* call-site-clarity parameter, kept for symmetry with its sibling.
|
|
* Either way the controller knows which ring because RESET ENDPOINT
|
|
* names it first. */
|
|
(void)ep_id;
|
|
uint64_t param = (new_dequeue & ~0xFULL) | (uint64_t)(dcs ? 1u : 0u);
|
|
xhci_submit_command(dev, param, 0,
|
|
XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD, slot_id << 24);
|
|
console_println("xhci: set TR dequeue pointer command submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* Enqueue one Normal TRB to the bulk OUT Transfer Ring -- same fixed-
|
|
* ring-plus-Link-TRB wraparound pattern as xhci_ep0_enqueue_trb(),
|
|
* operating on bulk_out_ring/bulk_out_ring_enq/bulk_out_ring_cycle
|
|
* instead of the EP0 ring's fields. A CBW is always exactly one TRB
|
|
* (no Setup/Data/Status split -- that's a control-transfer-only
|
|
* concept), so unlike xhci_ep0_enqueue_trb() this rings the doorbell
|
|
* itself rather than leaving that to a caller assembling a group. */
|
|
static void xhci_bulk_out_enqueue_and_ring(xhci_dev_t *dev, uint32_t slot_id,
|
|
uint64_t parameter, uint32_t status,
|
|
uint32_t control_bits)
|
|
{
|
|
xhci_trb_t *trb = &dev->bulk_out_ring[dev->bulk_out_ring_enq];
|
|
trb->parameter = parameter;
|
|
trb->status = status;
|
|
trb->control = control_bits | (dev->bulk_out_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
|
|
|
dev->bulk_out_ring_enq++;
|
|
if (dev->bulk_out_ring_enq == XHCI_RING_TRB_COUNT - 1) {
|
|
dev->bulk_out_ring[XHCI_RING_TRB_COUNT - 1].control =
|
|
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_TC |
|
|
(dev->bulk_out_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
|
dev->bulk_out_ring_enq = 0;
|
|
dev->bulk_out_ring_cycle ^= 1u;
|
|
}
|
|
|
|
/* Doorbell target is the bulk OUT endpoint's own DCI, not target 1
|
|
* (EP0) -- distinct rings need distinct doorbell targets so the
|
|
* controller knows which Transfer Ring just gained a new TRB. */
|
|
dev->doorbell[slot_id] = XHCI_DB_TARGET(XHCI_EP_ADDR_TO_DCI(dev->bulk_out_ep_addr));
|
|
}
|
|
|
|
int xhci_bot_send_read10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
|
|
uint16_t num_blocks, uint32_t block_size)
|
|
{
|
|
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
|
|
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
|
|
|
|
uint32_t data_len = (uint32_t)num_blocks * block_size;
|
|
if (data_len > sizeof(dev->bot_data_buf)) return -1;
|
|
|
|
dev->bot_cmd_kind = BOT_CMD_READ10;
|
|
|
|
usb_bot_cbw_t *cbw = &dev->bot_cbw;
|
|
cbw->dCBWSignature = USB_BOT_CBW_SIGNATURE;
|
|
cbw->dCBWTag = dev->bot_next_tag++;
|
|
dev->bot_last_tag = cbw->dCBWTag;
|
|
dev->bot_expected_data_len = data_len;
|
|
cbw->dCBWDataTransferLength = data_len;
|
|
cbw->bmCBWFlags = USB_BOT_CBW_FLAG_DATA_IN; /* READ(10): device -> host data stage */
|
|
cbw->bCBWLUN = USB_BOT_CBW_LUN_DEFAULT;
|
|
cbw->bCBWCBLength = SCSI_CDB_LEN_READ10;
|
|
for (uint32_t i = 0; i < sizeof(cbw->CBWCB); i++) cbw->CBWCB[i] = 0;
|
|
|
|
/* SCSI READ(10) CDB (SBC-3 section 5.13): opcode, then LBA and
|
|
* Transfer Length as big-endian fields -- SCSI multi-byte fields are
|
|
* big-endian on the wire regardless of host or USB byte order, unlike
|
|
* every other multi-byte value in this driver (TRBs, contexts, CBW
|
|
* itself), which are all little-endian. Written byte-by-byte rather
|
|
* than via a struct + byte-swap helper, matching this codebase's
|
|
* existing preference for explicit field layout over struct-based
|
|
* binary formats wherever the layout isn't naturally what a C struct
|
|
* would produce (see usb_bot_cbw_t's own doc comment, and the
|
|
* Interface/Endpoint descriptor offset macros). */
|
|
cbw->CBWCB[0] = SCSI_CMD_READ10;
|
|
cbw->CBWCB[1] = 0; /* flags: no FUA/DPO/RDPROTECT for this increment */
|
|
cbw->CBWCB[2] = (uint8_t)(lba >> 24);
|
|
cbw->CBWCB[3] = (uint8_t)(lba >> 16);
|
|
cbw->CBWCB[4] = (uint8_t)(lba >> 8);
|
|
cbw->CBWCB[5] = (uint8_t)(lba);
|
|
cbw->CBWCB[6] = 0; /* group number */
|
|
cbw->CBWCB[7] = (uint8_t)(num_blocks >> 8);
|
|
cbw->CBWCB[8] = (uint8_t)(num_blocks);
|
|
cbw->CBWCB[9] = 0; /* control */
|
|
|
|
dev->transfer_purpose = XHCI_XFER_CBW_SENT;
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
|
|
/* IOC set -- the CBW is always exactly one TRB, so it alone signals
|
|
* "this transfer is done" (matching the EP0 control-read pattern of
|
|
* IOC on the one TRB whose completion means something). Length is
|
|
* USB_BOT_CBW_LENGTH (31), not sizeof(*cbw) -- see usb_bot_cbw_t's
|
|
* own doc comment on why. */
|
|
xhci_bulk_out_enqueue_and_ring(dev, slot_id, (uint64_t)(uintptr_t)cbw,
|
|
USB_BOT_CBW_LENGTH,
|
|
(XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC);
|
|
console_println("xhci: CBW (READ10) submitted");
|
|
return 0;
|
|
}
|
|
|
|
int xhci_bot_send_write10(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
|
|
uint16_t num_blocks, uint32_t block_size)
|
|
{
|
|
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
|
|
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
|
|
|
|
uint32_t data_len = (uint32_t)num_blocks * block_size;
|
|
if (data_len > sizeof(dev->bot_data_buf)) return -1;
|
|
|
|
dev->bot_cmd_kind = BOT_CMD_WRITE10;
|
|
|
|
usb_bot_cbw_t *cbw = &dev->bot_cbw;
|
|
cbw->dCBWSignature = USB_BOT_CBW_SIGNATURE;
|
|
cbw->dCBWTag = dev->bot_next_tag++;
|
|
dev->bot_last_tag = cbw->dCBWTag;
|
|
dev->bot_expected_data_len = data_len;
|
|
cbw->dCBWDataTransferLength = data_len;
|
|
cbw->bmCBWFlags = 0; /* WRITE(10): host -> device data stage, unlike
|
|
* READ10's USB_BOT_CBW_FLAG_DATA_IN above */
|
|
cbw->bCBWLUN = USB_BOT_CBW_LUN_DEFAULT;
|
|
cbw->bCBWCBLength = SCSI_CDB_LEN_WRITE10;
|
|
for (uint32_t i = 0; i < sizeof(cbw->CBWCB); i++) cbw->CBWCB[i] = 0;
|
|
|
|
/* SCSI WRITE(10) CDB (SBC-3 section 5.32) -- identical layout to
|
|
* READ(10)'s CDB above, only the opcode differs. Same big-endian
|
|
* field packing, see xhci_bot_send_read10()'s own comment for why. */
|
|
cbw->CBWCB[0] = SCSI_CMD_WRITE10;
|
|
cbw->CBWCB[1] = 0; /* flags: no FUA/DPO for this increment */
|
|
cbw->CBWCB[2] = (uint8_t)(lba >> 24);
|
|
cbw->CBWCB[3] = (uint8_t)(lba >> 16);
|
|
cbw->CBWCB[4] = (uint8_t)(lba >> 8);
|
|
cbw->CBWCB[5] = (uint8_t)(lba);
|
|
cbw->CBWCB[6] = 0; /* group number */
|
|
cbw->CBWCB[7] = (uint8_t)(num_blocks >> 8);
|
|
cbw->CBWCB[8] = (uint8_t)(num_blocks);
|
|
cbw->CBWCB[9] = 0; /* control */
|
|
|
|
dev->transfer_purpose = XHCI_XFER_CBW_SENT;
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
|
|
xhci_bulk_out_enqueue_and_ring(dev, slot_id, (uint64_t)(uintptr_t)cbw,
|
|
USB_BOT_CBW_LENGTH,
|
|
(XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC);
|
|
console_println("xhci: CBW (WRITE10) submitted");
|
|
return 0;
|
|
}
|
|
|
|
int xhci_bot_send_test_unit_ready(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
|
|
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
|
|
|
|
dev->bot_cmd_kind = BOT_CMD_TEST_UNIT_READY;
|
|
|
|
usb_bot_cbw_t *cbw = &dev->bot_cbw;
|
|
cbw->dCBWSignature = USB_BOT_CBW_SIGNATURE;
|
|
cbw->dCBWTag = dev->bot_next_tag++;
|
|
dev->bot_last_tag = cbw->dCBWTag;
|
|
dev->bot_expected_data_len = 0; /* no data stage -- BOT spec section 6.3 */
|
|
cbw->dCBWDataTransferLength = 0;
|
|
cbw->bmCBWFlags = 0; /* direction is irrelevant when length is 0 */
|
|
cbw->bCBWLUN = USB_BOT_CBW_LUN_DEFAULT;
|
|
cbw->bCBWCBLength = SCSI_CDB_LEN_TEST_UNIT_READY;
|
|
for (uint32_t i = 0; i < sizeof(cbw->CBWCB); i++) cbw->CBWCB[i] = 0;
|
|
cbw->CBWCB[0] = SCSI_CMD_TEST_UNIT_READY; /* opcode 0x00, every other CDB byte reserved/zero */
|
|
|
|
dev->transfer_purpose = XHCI_XFER_CBW_SENT;
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
|
|
xhci_bulk_out_enqueue_and_ring(dev, slot_id, (uint64_t)(uintptr_t)cbw,
|
|
USB_BOT_CBW_LENGTH,
|
|
(XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC);
|
|
console_println("xhci: CBW (TEST UNIT READY) submitted");
|
|
return 0;
|
|
}
|
|
|
|
int xhci_bot_send_read_capacity10(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
|
|
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
|
|
|
|
dev->bot_cmd_kind = BOT_CMD_READ_CAPACITY10;
|
|
|
|
usb_bot_cbw_t *cbw = &dev->bot_cbw;
|
|
cbw->dCBWSignature = USB_BOT_CBW_SIGNATURE;
|
|
cbw->dCBWTag = dev->bot_next_tag++;
|
|
dev->bot_last_tag = cbw->dCBWTag;
|
|
dev->bot_expected_data_len = SCSI_READ_CAPACITY10_DATA_LEN;
|
|
cbw->dCBWDataTransferLength = SCSI_READ_CAPACITY10_DATA_LEN;
|
|
cbw->bmCBWFlags = USB_BOT_CBW_FLAG_DATA_IN; /* READ CAPACITY(10): device -> host data stage */
|
|
cbw->bCBWLUN = USB_BOT_CBW_LUN_DEFAULT;
|
|
cbw->bCBWCBLength = SCSI_CDB_LEN_READ_CAPACITY10;
|
|
for (uint32_t i = 0; i < sizeof(cbw->CBWCB); i++) cbw->CBWCB[i] = 0;
|
|
cbw->CBWCB[0] = SCSI_CMD_READ_CAPACITY10; /* opcode 0x25, standard "report
|
|
* capacity" form -- LBA field
|
|
* and PMI bit left zero, see
|
|
* this constant's own doc
|
|
* comment in xhci.h */
|
|
|
|
dev->transfer_purpose = XHCI_XFER_CBW_SENT;
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
|
|
xhci_bulk_out_enqueue_and_ring(dev, slot_id, (uint64_t)(uintptr_t)cbw,
|
|
USB_BOT_CBW_LENGTH,
|
|
(XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC);
|
|
console_println("xhci: CBW (READ CAPACITY10) submitted");
|
|
return 0;
|
|
}
|
|
|
|
int xhci_bot_read_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
|
|
uint16_t num_blocks, uint32_t block_size)
|
|
{
|
|
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
|
|
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
|
|
if ((uint32_t)num_blocks * block_size > sizeof(dev->bot_data_buf)) return -1;
|
|
|
|
dev->bot_read10_lba = lba;
|
|
dev->bot_read10_num_blocks = num_blocks;
|
|
dev->bot_read10_block_size = block_size;
|
|
dev->bot_tur_chain_target = BOT_TUR_CHAIN_READ10;
|
|
dev->bot_tur_retries = 0;
|
|
|
|
return xhci_bot_send_test_unit_ready(dev, slot_id);
|
|
}
|
|
|
|
int xhci_bot_write_block(xhci_dev_t *dev, uint32_t slot_id, uint32_t lba,
|
|
uint16_t num_blocks, uint32_t block_size)
|
|
{
|
|
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
|
|
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
|
|
if ((uint32_t)num_blocks * block_size > sizeof(dev->bot_data_buf)) return -1;
|
|
|
|
dev->bot_write10_lba = lba;
|
|
dev->bot_write10_num_blocks = num_blocks;
|
|
dev->bot_write10_block_size = block_size;
|
|
dev->bot_tur_chain_target = BOT_TUR_CHAIN_WRITE10;
|
|
dev->bot_tur_retries = 0;
|
|
|
|
return xhci_bot_send_test_unit_ready(dev, slot_id);
|
|
}
|
|
|
|
int xhci_bot_get_capacity(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->bulk_out_ring || !dev->bulk_in_ring) return -1;
|
|
if (dev->bulk_out_ep_addr == 0 || dev->bulk_in_ep_addr == 0) return -1;
|
|
|
|
dev->bot_tur_chain_target = BOT_TUR_CHAIN_READ_CAPACITY10;
|
|
dev->bot_tur_retries = 0;
|
|
|
|
return xhci_bot_send_test_unit_ready(dev, slot_id);
|
|
}
|
|
|
|
int xhci_bot_wait_for_idle(xhci_dev_t *dev, uint32_t max_iters)
|
|
{
|
|
if (!dev) return BOT_STATUS_FAILED;
|
|
for (uint32_t i = 0; i < max_iters; i++) {
|
|
if (dev->bot_cmd_kind == BOT_CMD_NONE) return (int)dev->bot_last_status;
|
|
xhci_poll_events();
|
|
}
|
|
return BOT_STATUS_TIMEOUT;
|
|
}
|
|
|
|
/* Enqueue one Normal TRB to the bulk IN Transfer Ring and ring its
|
|
* doorbell -- same shape as xhci_bulk_out_enqueue_and_ring() (a BOT
|
|
* Data-In or CSW read is, like a CBW send, always exactly one TRB), just
|
|
* targeting bulk_in_ring/bulk_in_ep_addr instead of the OUT side. */
|
|
static void xhci_bulk_in_enqueue_and_ring(xhci_dev_t *dev, uint32_t slot_id,
|
|
uint64_t parameter, uint32_t status,
|
|
uint32_t control_bits)
|
|
{
|
|
xhci_trb_t *trb = &dev->bulk_in_ring[dev->bulk_in_ring_enq];
|
|
trb->parameter = parameter;
|
|
trb->status = status;
|
|
trb->control = control_bits | (dev->bulk_in_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
|
|
|
dev->bulk_in_ring_enq++;
|
|
if (dev->bulk_in_ring_enq == XHCI_RING_TRB_COUNT - 1) {
|
|
dev->bulk_in_ring[XHCI_RING_TRB_COUNT - 1].control =
|
|
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_TC |
|
|
(dev->bulk_in_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
|
dev->bulk_in_ring_enq = 0;
|
|
dev->bulk_in_ring_cycle ^= 1u;
|
|
}
|
|
|
|
dev->doorbell[slot_id] = XHCI_DB_TARGET(XHCI_EP_ADDR_TO_DCI(dev->bulk_in_ep_addr));
|
|
}
|
|
|
|
int xhci_bot_read_data_in(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->bulk_in_ring) return -1;
|
|
|
|
dev->transfer_purpose = XHCI_XFER_BOT_DATA_IN;
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
|
|
xhci_bulk_in_enqueue_and_ring(dev, slot_id, (uint64_t)(uintptr_t)dev->bot_data_buf,
|
|
dev->bot_expected_data_len,
|
|
(XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC);
|
|
console_println("xhci: BOT Data-In read submitted");
|
|
return 0;
|
|
}
|
|
|
|
int xhci_bot_write_data_out(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->bulk_out_ring) return -1;
|
|
|
|
dev->transfer_purpose = XHCI_XFER_BOT_DATA_OUT;
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
|
|
xhci_bulk_out_enqueue_and_ring(dev, slot_id, (uint64_t)(uintptr_t)dev->bot_data_buf,
|
|
dev->bot_expected_data_len,
|
|
(XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC);
|
|
console_println("xhci: BOT Data-Out write submitted");
|
|
return 0;
|
|
}
|
|
|
|
int xhci_bot_receive_csw(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->bulk_in_ring) return -1;
|
|
|
|
dev->transfer_purpose = XHCI_XFER_CSW_RECEIVED;
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
|
|
/* Length is USB_BOT_CSW_LENGTH (13), not sizeof(dev->bot_csw) -- same
|
|
* padding hazard as the CBW, see usb_bot_csw_t's own doc comment. */
|
|
xhci_bulk_in_enqueue_and_ring(dev, slot_id, (uint64_t)(uintptr_t)&dev->bot_csw,
|
|
USB_BOT_CSW_LENGTH,
|
|
(XHCI_TRB_TYPE_NORMAL << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC);
|
|
console_println("xhci: CSW receive submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* Enqueue one TRB to the EP0 Transfer Ring without ringing the doorbell
|
|
* -- Setup/Data/Status stage TRBs are enqueued as a group, then the
|
|
* doorbell is rung once after all three are posted, matching how a real
|
|
* xHCI control transfer is submitted (the controller processes queued
|
|
* TRBs as a unit once notified, not one doorbell ring per TRB). Same
|
|
* fixed-ring-plus-Link-TRB wraparound pattern as xhci_submit_command(),
|
|
* operating on ep0_ring/ep0_ring_enq/ep0_ring_cycle instead of the
|
|
* Command Ring's fields. */
|
|
static void xhci_ep0_enqueue_trb(xhci_dev_t *dev, uint64_t parameter,
|
|
uint32_t status, uint32_t control_bits)
|
|
{
|
|
xhci_trb_t *trb = &dev->ep0_ring[dev->ep0_ring_enq];
|
|
trb->parameter = parameter;
|
|
trb->status = status;
|
|
trb->control = control_bits | (dev->ep0_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
|
|
|
dev->ep0_ring_enq++;
|
|
if (dev->ep0_ring_enq == XHCI_RING_TRB_COUNT - 1) {
|
|
dev->ep0_ring[XHCI_RING_TRB_COUNT - 1].control =
|
|
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_TC |
|
|
(dev->ep0_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
|
|
dev->ep0_ring_enq = 0;
|
|
dev->ep0_ring_cycle ^= 1u;
|
|
}
|
|
}
|
|
|
|
/* Shared submission for any "device-to-host, standard, device recipient,
|
|
* IN data stage" control read -- both GET_DESCRIPTOR(Device) and
|
|
* GET_DESCRIPTOR(Configuration) are this same shape, differing only in
|
|
* wValue/wLength/destination buffer. Does not set dev->transfer_purpose
|
|
* or dev->pending_transfer_slot_id -- callers do that themselves so the
|
|
* purpose is set before the doorbell rings (avoids a window where a
|
|
* stray Transfer Event could be misread against a not-yet-set purpose,
|
|
* even though this driver is polled and that window can't actually be
|
|
* hit by anything external in practice). */
|
|
static void xhci_ep0_control_read(xhci_dev_t *dev, uint8_t bRequest,
|
|
uint16_t wValue, uint16_t wIndex,
|
|
uint8_t *buf, uint16_t len)
|
|
{
|
|
usb_setup_packet_t setup = {
|
|
.bmRequestType = USB_DIR_DEVICE_TO_HOST,
|
|
.bRequest = bRequest,
|
|
.wValue = wValue,
|
|
.wIndex = wIndex,
|
|
.wLength = len
|
|
};
|
|
uint64_t setup_bits;
|
|
memcpy(&setup_bits, &setup, sizeof(setup_bits));
|
|
|
|
/* Setup Stage: IDT set (parameter IS the 8-byte packet, not a
|
|
* pointer), TRT = IN Data Stage since this request reads data back. */
|
|
xhci_ep0_enqueue_trb(dev, setup_bits, 8u,
|
|
(XHCI_TRB_TYPE_SETUP_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IDT |
|
|
(XHCI_SETUP_TRT_IN_DATA << XHCI_TRB_CONTROL_TRT_SHIFT));
|
|
|
|
/* Data Stage: parameter is a real pointer here (not immediate) --
|
|
* points at the caller's buffer. DIR=IN matches the Setup Stage's
|
|
* TRT. */
|
|
xhci_ep0_enqueue_trb(dev, (uint64_t)(uintptr_t)buf, len,
|
|
(XHCI_TRB_TYPE_DATA_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_DIR_IN);
|
|
|
|
/* Status Stage: DIR=OUT (opposite of the Data Stage's IN) -- the
|
|
* status handshake always runs the reverse direction. IOC set here
|
|
* only: this is the sole TRB of the three whose completion signals
|
|
* "the whole control transfer is done" to xhci_poll_events(). */
|
|
xhci_ep0_enqueue_trb(dev, 0, 0,
|
|
(XHCI_TRB_TYPE_STATUS_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC);
|
|
}
|
|
|
|
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->ep0_ring) return -1;
|
|
|
|
dev->transfer_purpose = XHCI_XFER_DEVICE_DESC;
|
|
xhci_ep0_control_read(dev, USB_REQ_GET_DESCRIPTOR,
|
|
(uint16_t)(USB_DESC_TYPE_DEVICE << 8), 0,
|
|
dev->device_descriptor, sizeof(dev->device_descriptor));
|
|
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
/* Doorbell Array is indexed by slot ID; target 1 = Default Control
|
|
* Endpoint (EP0)'s Device Context Index, per xHCI 1.2 spec table
|
|
* 6-25 -- distinct from doorbell[0], which is always the Command
|
|
* Ring regardless of slot. */
|
|
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
|
|
console_println("xhci: get device descriptor submitted");
|
|
return 0;
|
|
}
|
|
|
|
int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t length)
|
|
{
|
|
if (!dev || !dev->ep0_ring) return -1;
|
|
|
|
/* Cap to the fixed buffer size -- a device whose real Configuration
|
|
* descriptor set exceeds this would be truncated, not overflowed;
|
|
* 128 bytes comfortably covers a single-interface Mass Storage
|
|
* device (Config 9 + Interface 9 + 2 Endpoints * 7 = 32 bytes
|
|
* typical), so this is a defensive cap, not an expected path. */
|
|
if (length > sizeof(dev->config_descriptor)) {
|
|
length = (uint16_t)sizeof(dev->config_descriptor);
|
|
}
|
|
|
|
dev->transfer_purpose = (length <= 9) ? XHCI_XFER_CONFIG_DESC_SHORT
|
|
: XHCI_XFER_CONFIG_DESC_FULL;
|
|
xhci_ep0_control_read(dev, USB_REQ_GET_DESCRIPTOR,
|
|
(uint16_t)(USB_DESC_TYPE_CONFIG << 8), 0,
|
|
dev->config_descriptor, length);
|
|
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
|
|
console_println("xhci: get config descriptor submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* Shared submission for a "no Data Stage" control transfer -- Setup Stage
|
|
* only (TRT = XHCI_SETUP_TRT_NO_DATA), then Status Stage. Per USB 2.0
|
|
* spec section 8.5.3, a control transfer with no Data Stage always uses
|
|
* an IN Status Stage (the reverse of a normal OUT request's OUT status),
|
|
* so DIR_IN is set unconditionally here -- this helper isn't generic
|
|
* across OUT-data and no-data requests, only the latter. */
|
|
static void xhci_ep0_control_write_nodata(xhci_dev_t *dev, uint8_t bRequest,
|
|
uint16_t wValue, uint16_t wIndex)
|
|
{
|
|
usb_setup_packet_t setup = {
|
|
.bmRequestType = USB_DIR_HOST_TO_DEVICE,
|
|
.bRequest = bRequest,
|
|
.wValue = wValue,
|
|
.wIndex = wIndex,
|
|
.wLength = 0
|
|
};
|
|
uint64_t setup_bits;
|
|
memcpy(&setup_bits, &setup, sizeof(setup_bits));
|
|
|
|
xhci_ep0_enqueue_trb(dev, setup_bits, 8u,
|
|
(XHCI_TRB_TYPE_SETUP_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IDT |
|
|
(XHCI_SETUP_TRT_NO_DATA << XHCI_TRB_CONTROL_TRT_SHIFT));
|
|
|
|
xhci_ep0_enqueue_trb(dev, 0, 0,
|
|
(XHCI_TRB_TYPE_STATUS_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC |
|
|
XHCI_TRB_CONTROL_DIR_IN);
|
|
}
|
|
|
|
int xhci_ep0_set_configuration(xhci_dev_t *dev, uint32_t slot_id, uint8_t config_value)
|
|
{
|
|
if (!dev || !dev->ep0_ring) return -1;
|
|
|
|
dev->transfer_purpose = XHCI_XFER_SET_CONFIG;
|
|
xhci_ep0_control_write_nodata(dev, USB_REQ_SET_CONFIGURATION, config_value, 0);
|
|
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
|
|
console_println("xhci: set configuration submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* G.1 / §F.14 stall recovery: CLEAR_FEATURE(ENDPOINT_HALT) on a specific
|
|
* endpoint. Reuses the existing no-data control-transfer machinery
|
|
* (xhci_ep0_control_write_nodata(), the same Setup+Status shape as
|
|
* SET_CONFIGURATION) -- just a different request payload: bRequest =
|
|
* USB_REQ_CLEAR_FEATURE, wValue = USB_FEATURE_ENDPOINT_HALT, wIndex = the
|
|
* endpoint's own bEndpointAddress (standard, host-to-device, endpoint-
|
|
* recipient -- bmRequestType inferred by the helper as 0x00). This is the
|
|
* USB-level clear that clears the device's halt condition and resets its
|
|
* data toggle after the two xHCI command steps (Reset Endpoint, Set TR
|
|
* Dequeue Pointer) have un-halted the xHC side. */
|
|
int xhci_ep0_clear_endpoint_halt(xhci_dev_t *dev, uint32_t slot_id, uint8_t ep_addr)
|
|
{
|
|
if (!dev || !dev->ep0_ring) return -1;
|
|
|
|
dev->transfer_purpose = XHCI_XFER_CLEAR_HALT;
|
|
xhci_ep0_control_write_nodata(dev, USB_REQ_CLEAR_FEATURE,
|
|
USB_FEATURE_ENDPOINT_HALT, ep_addr);
|
|
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
|
|
console_println("xhci: clear endpoint halt submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* G.1 / §F.14 stall recovery escalation: Bulk-Only Transport Mass Storage
|
|
* Reset. bmRequestType = 0x21 (class, interface recipient), bRequest =
|
|
* 0xFF, no data stage -- also built on xhci_ep0_control_write_nodata(), but
|
|
* that helper hardcodes bmRequestType = 0x00 (standard, host-to-device,
|
|
* device recipient). A BOT Mass Storage Reset is class + interface
|
|
* recipient, so it can't reuse the helper directly; the request bytes are
|
|
* stamped into the Setup packet by hand here (matching the same explicit-
|
|
* layout, no-padding discipline usb_setup_packet_t is documented for). */
|
|
int xhci_ep0_bot_mass_storage_reset(xhci_dev_t *dev, uint32_t slot_id)
|
|
{
|
|
if (!dev || !dev->ep0_ring) return -1;
|
|
|
|
usb_setup_packet_t setup = {
|
|
.bmRequestType = (USB_REQ_TYPE_CLASS << 5) | USB_RECIP_INTERFACE,
|
|
.bRequest = USB_BOT_MASS_STORAGE_RESET,
|
|
.wValue = 0,
|
|
.wIndex = 0,
|
|
.wLength = 0
|
|
};
|
|
uint64_t setup_bits;
|
|
memcpy(&setup_bits, &setup, sizeof(setup_bits));
|
|
|
|
xhci_ep0_enqueue_trb(dev, setup_bits, 8u,
|
|
(XHCI_TRB_TYPE_SETUP_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IDT |
|
|
(XHCI_SETUP_TRT_NO_DATA << XHCI_TRB_CONTROL_TRT_SHIFT));
|
|
xhci_ep0_enqueue_trb(dev, 0, 0,
|
|
(XHCI_TRB_TYPE_STATUS_STAGE << XHCI_TRB_CONTROL_TYPE_SHIFT) |
|
|
XHCI_TRB_CONTROL_IOC |
|
|
XHCI_TRB_CONTROL_DIR_IN);
|
|
|
|
dev->transfer_purpose = XHCI_XFER_BOT_RESET;
|
|
dev->pending_transfer_slot_id = slot_id;
|
|
dev->doorbell[slot_id] = XHCI_DB_TARGET(1);
|
|
console_println("xhci: BOT mass storage reset submitted");
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------
|
|
* Milestone 2d: Event Ring servicing, polled from sk_repl_idle().
|
|
*
|
|
* Only one controller is supported (matches xhci_dev_t's own doc comment),
|
|
* so xhci_poll_events() is a self-contained singleton call, no argument
|
|
* needed -- it recovers the device pointer latched by xhci_bringup() above
|
|
* rather than taking one. See xhci_driver.h's own doc comment for why this
|
|
* is polled rather than interrupt-driven (a real, checked-live finding,
|
|
* not a shortcut: the amd64 PCI INTx routing formula tried first turned
|
|
* out to be simply wrong).
|
|
* ------------------------------------------------------------------------- */
|
|
|
|
/* Port Register Set array lives at Operational base + 0x400 (xhci.h's own
|
|
* doc comment on xhci_port_regs_t) -- not reachable through xhci_op_regs_t
|
|
* itself since it isn't a contiguous struct member. port_id is 1-based,
|
|
* matching XHCI_PSC_EVT_PORT_ID()'s decode and the spec's own numbering. */
|
|
static xhci_port_regs_t *xhci_port_regs(xhci_dev_t *dev, uint32_t port_id)
|
|
{
|
|
if (port_id < 1 || port_id > dev->max_ports) return NULL;
|
|
return (xhci_port_regs_t *)((uint8_t *)dev->op + XHCI_PORT_REGS_OFFSET +
|
|
(port_id - 1) * sizeof(xhci_port_regs_t));
|
|
}
|
|
|
|
/* Shared "device connected" handling -- factored out of xhci_poll_events()'s
|
|
* PORT_STATUS_CHANGE_EVT case so xhci_scan_ports_for_already_connected()
|
|
* (called once from xhci_bringup(), see its own doc comment) can drive the
|
|
* exact same Enable Slot sequence for a device that was already attached at
|
|
* controller bring-up, not just one detected via a later hotplug event. */
|
|
static void xhci_handle_port_connected(xhci_dev_t *dev, uint32_t port_id, uint32_t portsc)
|
|
{
|
|
console_println("xhci: port status change -- device connected");
|
|
/* Milestone 2e prep: Address Device requires the port in Default
|
|
* state. USB3 links train and enable themselves; USB2 needs software
|
|
* to drive PORTSC.PR and wait for PRC/PED before the device will
|
|
* respond to addressing -- not yet known which this driver's ports
|
|
* need, so log raw PORTSC and PED rather than assume. */
|
|
xhci_log_hex32("xhci: portsc=", portsc);
|
|
console_println((portsc & XHCI_PORTSC_PED)
|
|
? "xhci: port enabled (PED set)"
|
|
: "xhci: port not yet enabled (PED clear)");
|
|
/* Only one Enable Slot in flight at a time (see xhci_dev_t's doc
|
|
* comment) -- if another connect's slot request is still outstanding,
|
|
* this one is dropped rather than queued. Acceptable for this
|
|
* milestone's single-device testing scope; revisit if multi-port
|
|
* simultaneous connects become a real scenario. */
|
|
if (port_id > XHCI_MAX_TRACKED_PORTS) {
|
|
console_println("xhci: port beyond tracked range -- enable slot skipped");
|
|
} else if (dev->connect_state == XHCI_CONN_IDLE) {
|
|
dev->pending_connect_port_id = port_id;
|
|
dev->pending_connect_speed = XHCI_PORTSC_SPEED(portsc);
|
|
dev->connect_state = XHCI_CONN_AWAIT_ENABLE_SLOT;
|
|
xhci_cmd_enable_slot(dev);
|
|
} else {
|
|
console_println("xhci: enable slot already pending -- dropped");
|
|
}
|
|
}
|
|
|
|
/* xhci_scan_ports_for_already_connected -- called once from xhci_bringup(),
|
|
* right after the controller starts running. xhci_poll_events() only reacts
|
|
* to Port Status Change *events*, and a device present on the QEMU command
|
|
* line before this controller reset never generates one (nothing "changed"
|
|
* from the controller's perspective once it starts looking) -- confirmed by
|
|
* reading the spec's event model, not assumed. Without this scan, such a
|
|
* device stays invisible to the guest forever, since no later event will
|
|
* ever announce it either.
|
|
*
|
|
* Scans tracked ports for CCS (Current Connect Status) directly, and drives
|
|
* the first connected one found through the same Enable Slot path
|
|
* xhci_poll_events() uses -- matching that path's own single-outstanding-
|
|
* connect limitation, which is fine here too: this driver's real use case
|
|
* is exactly one thumbdrive already attached at boot, not several. */
|
|
static void xhci_scan_ports_for_already_connected(xhci_dev_t *dev)
|
|
{
|
|
uint32_t max_port = dev->max_ports;
|
|
if (max_port > XHCI_MAX_TRACKED_PORTS) max_port = XHCI_MAX_TRACKED_PORTS;
|
|
|
|
for (uint32_t port_id = 1; port_id <= max_port; port_id++) {
|
|
xhci_port_regs_t *port = xhci_port_regs(dev, port_id);
|
|
if (!port) continue;
|
|
uint32_t portsc = port->portsc;
|
|
if (!(portsc & XHCI_PORTSC_CCS)) continue;
|
|
|
|
console_println("xhci: device already connected at bring-up");
|
|
xhci_handle_port_connected(dev, port_id, portsc);
|
|
/* Acknowledge CSC the same way xhci_poll_events() does, in case
|
|
* the controller latched it during reset -- harmless if it was
|
|
* never set. */
|
|
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_CSC;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* G.1 / §F.14: clean terminal failure of the BOT command currently in
|
|
* flight, used by every stall-recovery bail-out path. Explicitly sets
|
|
* bot_last_status = BOT_STATUS_FAILED and bot_cmd_kind = BOT_CMD_NONE -- a
|
|
* clean signal to a synchronous caller blocked in
|
|
* xhci_bot_wait_for_idle() (which would otherwise just time out, leaving
|
|
* state ambiguous), rather than relying on the outer timeout the way the
|
|
* pre-G.1 un-recovered stall did. */
|
|
static void xhci_stall_fail(xhci_dev_t *dev)
|
|
{
|
|
console_println("xhci: stall recovery exhausted -- failing command cleanly");
|
|
dev->bot_last_status = BOT_STATUS_FAILED;
|
|
dev->bot_cmd_kind = BOT_CMD_NONE;
|
|
dev->stall_retry_action = XHCI_NEXT_ACTION_NONE;
|
|
dev->bot_reset_clear_remaining = 0;
|
|
}
|
|
|
|
/* G.1 / §F.14: is `purpose` a bulk (BOT) transfer rather than an EP0
|
|
* control transfer? Stall recovery is defined only for the bulk endpoints
|
|
* (CBW_SENT and BOT_DATA_OUT run on bulk_out; BOT_DATA_IN and CSW_RECEIVED
|
|
* on bulk_in) -- an EP0 control transfer that stalls during enumeration is
|
|
* a different, out-of-scope case this driver deliberately does not attempt
|
|
* to recover. */
|
|
static int xhci_bulk_purpose_stalled(xhci_dev_t *dev, uint32_t purpose)
|
|
{
|
|
(void)dev;
|
|
switch (purpose) {
|
|
case XHCI_XFER_CBW_SENT:
|
|
case XHCI_XFER_BOT_DATA_IN:
|
|
case XHCI_XFER_BOT_DATA_OUT:
|
|
case XHCI_XFER_CSW_RECEIVED:
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* G.1 / §F.14: start stall recovery for a bulk transfer that completed
|
|
* with XHCI_COMPLETION_CODE_STALL_ERROR in xhci_poll_events()'s transfer-
|
|
* event handler. Identifies the stalled endpoint from `purpose` (which
|
|
* bulk ring it ran on) and the command-stage to retry once recovery
|
|
* completes, then either (a) kicks off the basic xHCI Reset Endpoint /
|
|
* Set TR Dequeue Pointer / CLEAR_FEATURE(ENDPOINT_HALT) recovery, or on a
|
|
* repeated stall (b) escalates to a full BOT Mass Storage Reset. Recovery
|
|
* is bounded by bot_stall_recoveries vs XHCI_BOT_STALL_MAX_RECOVERIES;
|
|
* exhausting it bails out clean per the helper above. Called with purpose
|
|
* already read (it is cleared by the caller immediately after this call). */
|
|
static void xhci_handle_bulk_stall(xhci_dev_t *dev, uint32_t slot_id,
|
|
uint32_t purpose)
|
|
{
|
|
switch (purpose) {
|
|
case XHCI_XFER_CBW_SENT:
|
|
dev->stall_ep_addr = dev->bulk_out_ep_addr;
|
|
/* Re-send the whole SCSI command from its CBW -- which takes
|
|
* the existing TUR/READ/WRITE/CAPACITY entry points, picked
|
|
* by what's in flight. */
|
|
switch (dev->bot_cmd_kind) {
|
|
case BOT_CMD_WRITE10:
|
|
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_SEND_WRITE10;
|
|
break;
|
|
case BOT_CMD_READ_CAPACITY10:
|
|
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10;
|
|
break;
|
|
case BOT_CMD_TEST_UNIT_READY:
|
|
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_SEND_TUR;
|
|
break;
|
|
default: /* BOT_CMD_READ10 (and anything unexpected) */
|
|
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_SEND_READ10;
|
|
break;
|
|
}
|
|
break;
|
|
case XHCI_XFER_BOT_DATA_OUT:
|
|
dev->stall_ep_addr = dev->bulk_out_ep_addr;
|
|
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_DATA_OUT;
|
|
break;
|
|
case XHCI_XFER_BOT_DATA_IN:
|
|
dev->stall_ep_addr = dev->bulk_in_ep_addr;
|
|
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_DATA_IN;
|
|
break;
|
|
case XHCI_XFER_CSW_RECEIVED:
|
|
dev->stall_ep_addr = dev->bulk_in_ep_addr;
|
|
dev->stall_retry_action = XHCI_NEXT_ACTION_BOT_CSW_RECEIVE;
|
|
break;
|
|
default:
|
|
/* Unreachable -- xhci_bulk_purpose_stalled() gates entry. */
|
|
return;
|
|
}
|
|
dev->stall_dci = XHCI_EP_ADDR_TO_DCI(dev->stall_ep_addr);
|
|
|
|
if (dev->bot_stall_recoveries >= XHCI_BOT_STALL_MAX_RECOVERIES) {
|
|
xhci_stall_fail(dev);
|
|
return;
|
|
}
|
|
|
|
dev->bot_stall_recoveries++;
|
|
console_println("xhci: bulk endpoint STALL -- starting recovery");
|
|
if (dev->bot_stall_recoveries == 1) {
|
|
/* First recovery: the basic xHCI-level Reset Endpoint sequence
|
|
* (commands are submitted synchronously -- they ring doorbell 0,
|
|
* which is safe from inside event processing; only transfer-ring
|
|
* doorbells are deferred). */
|
|
dev->connect_state = XHCI_CONN_AWAIT_RESET_ENDPOINT;
|
|
if (xhci_cmd_reset_endpoint(dev, slot_id, dev->stall_ep_addr) != 0) {
|
|
console_println("xhci: reset endpoint submit failed -- stalling out clean");
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
xhci_stall_fail(dev);
|
|
}
|
|
} else {
|
|
/* Repeated stall: escalate to a full BOT Mass Storage Reset + clear
|
|
* halt on both endpoints -- deferred, since BOT Mass Storage Reset
|
|
* is a control transfer that rings doorbell[slot] (must not happen
|
|
* inside event processing). */
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_RESET;
|
|
dev->next_action_slot_id = slot_id;
|
|
}
|
|
}
|
|
|
|
void xhci_poll_events(void)
|
|
{
|
|
xhci_dev_t *dev = g_xhci_dev;
|
|
if (!dev) return;
|
|
|
|
uint32_t evt_processed = 0;
|
|
while (evt_processed < XHCI_EVT_RING_MAX_DRAIN &&
|
|
((dev->evt_ring[dev->evt_ring_deq].control & XHCI_TRB_CONTROL_CYCLE) != 0)
|
|
== (dev->evt_ring_cycle != 0)) {
|
|
xhci_trb_t *trb = &dev->evt_ring[dev->evt_ring_deq];
|
|
uint32_t type = XHCI_TRB_TYPE(trb->control);
|
|
|
|
switch (type) {
|
|
case XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVT: {
|
|
/* Milestone 2e: identify which port changed and whether it
|
|
* now reads connected or disconnected. Slot allocation/
|
|
* addressing on connect is the next increment -- this only
|
|
* detects and acknowledges the change for now. */
|
|
uint32_t port_id = XHCI_PSC_EVT_PORT_ID(trb->parameter);
|
|
xhci_port_regs_t *port = xhci_port_regs(dev, port_id);
|
|
if (!port) {
|
|
console_println("xhci: port status change event (bad port id)");
|
|
break;
|
|
}
|
|
uint32_t portsc = port->portsc;
|
|
if (portsc & XHCI_PORTSC_CCS) {
|
|
xhci_handle_port_connected(dev, port_id, portsc);
|
|
} else {
|
|
console_println("xhci: port status change -- device disconnected");
|
|
if (port_id >= 1 && port_id <= XHCI_MAX_TRACKED_PORTS &&
|
|
dev->port_slot_id[port_id - 1] != 0) {
|
|
uint32_t disconnecting_slot_id = dev->port_slot_id[port_id - 1];
|
|
/* Stop tracking the slot immediately so a future
|
|
* connect on this port isn't confused for one
|
|
* already in progress -- independent of whether
|
|
* the Disable Slot command below can be issued
|
|
* right now. */
|
|
dev->port_slot_id[port_id - 1] = 0;
|
|
/* Milestone 2h hot-detach: fire on the disconnect
|
|
* itself, independent of whether Disable Slot can
|
|
* be sent right now -- see bot_msc_detach_pending's
|
|
* own doc comment for why this point, not Disable
|
|
* Slot completion. */
|
|
if (dev->bot_msc_attached) {
|
|
dev->bot_msc_attached = 0;
|
|
dev->bot_msc_detach_pending = 1;
|
|
}
|
|
if (dev->connect_state == XHCI_CONN_IDLE) {
|
|
dev->pending_disable_slot_id = disconnecting_slot_id;
|
|
dev->connect_state = XHCI_CONN_AWAIT_DISABLE_SLOT;
|
|
xhci_cmd_disable_slot(dev, disconnecting_slot_id);
|
|
} else {
|
|
/* Same single-outstanding-command limitation
|
|
* as Enable Slot above -- the slot's DCBAA
|
|
* entry is simply left stale (harmless: it is
|
|
* never looked at again since port_slot_id[]
|
|
* no longer references it, and a genuinely
|
|
* concurrent connect/disconnect pair isn't
|
|
* this driver's current scope). */
|
|
console_println("xhci: disable slot skipped -- command ring busy");
|
|
}
|
|
}
|
|
}
|
|
/* Acknowledge only CSC (RW1CS): preserve PP, write 0 for
|
|
* PED/PR (writing 1 there disables the port / starts a new
|
|
* reset -- not intended here) and for every other _C bit
|
|
* (writing 0 leaves them untouched, not cleared) -- the
|
|
* same discipline this driver already applies to ERDP.EHB. */
|
|
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_CSC;
|
|
break;
|
|
}
|
|
case XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT: {
|
|
uint32_t code = XHCI_EVT_COMPLETION_CODE(trb->status);
|
|
uint32_t slot_id = XHCI_EVT_SLOT_ID(trb->control);
|
|
/* Correlates to connect_state, not to the Command TRB
|
|
* Pointer in trb->parameter -- Enable Slot and Address
|
|
* Device are issued sequentially for a given connect (see
|
|
* xhci_dev_t's doc comment), never concurrently, so
|
|
* connect_state alone identifies which command this
|
|
* completion answers. A real Command TRB Pointer match
|
|
* becomes necessary once commands for different connects
|
|
* can overlap in flight. */
|
|
if (dev->connect_state == XHCI_CONN_AWAIT_ENABLE_SLOT) {
|
|
uint32_t port_id = dev->pending_connect_port_id;
|
|
if (code == XHCI_COMPLETION_CODE_SUCCESS &&
|
|
port_id >= 1 && port_id <= XHCI_MAX_TRACKED_PORTS) {
|
|
dev->port_slot_id[port_id - 1] = slot_id;
|
|
dev->pending_connect_slot_id = slot_id;
|
|
console_println("xhci: enable slot succeeded");
|
|
dev->connect_state = XHCI_CONN_AWAIT_ADDRESS_DEVICE;
|
|
if (xhci_cmd_address_device(dev, slot_id, port_id,
|
|
dev->pending_connect_speed) != 0) {
|
|
console_println("xhci: address device setup failed");
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
dev->pending_connect_port_id = 0;
|
|
}
|
|
} else {
|
|
console_println("xhci: enable slot failed");
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
dev->pending_connect_port_id = 0;
|
|
}
|
|
} else if (dev->connect_state == XHCI_CONN_AWAIT_ADDRESS_DEVICE) {
|
|
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
|
|
console_println("xhci: address device succeeded");
|
|
/* Milestone 2f: enumeration starts here -- the
|
|
* device now has a USB address and EP0 is
|
|
* usable for control transfers. Deferred (see
|
|
* xhci_dev_t's doc comment on next_action) rather
|
|
* than called directly here. */
|
|
dev->next_action = XHCI_NEXT_ACTION_GET_DEVICE_DESC;
|
|
dev->next_action_slot_id = dev->pending_connect_slot_id;
|
|
} else {
|
|
console_println("xhci: address device failed");
|
|
}
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
dev->pending_connect_port_id = 0;
|
|
} else if (dev->connect_state == XHCI_CONN_AWAIT_DISABLE_SLOT) {
|
|
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
|
|
/* DCBAA[slot_id] cleared on success only -- if the
|
|
* controller reports failure, leave it: the slot
|
|
* may still be in a state where zeroing its
|
|
* context pointer out from under the controller
|
|
* is unsafe, and port_slot_id[] no longer
|
|
* references this slot either way, so nothing
|
|
* else in this driver will look at it again. */
|
|
((uint64_t *)dev->dcbaa)[dev->pending_disable_slot_id] = 0;
|
|
console_println("xhci: disable slot succeeded");
|
|
} else {
|
|
console_println("xhci: disable slot failed");
|
|
}
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
dev->pending_disable_slot_id = 0;
|
|
} else if (dev->connect_state == XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT) {
|
|
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
|
|
console_println("xhci: configure endpoint succeeded");
|
|
/* Chain into SET_CONFIGURATION -- next_action_
|
|
* slot_id/next_action_config_value are still the
|
|
* values staged when this Configure Endpoint was
|
|
* itself deferred (see the XFER_CONFIG_DESC_FULL
|
|
* handler above); only next_action's enum tag
|
|
* gets cleared on consumption, not the payload
|
|
* fields, so they're still valid to reuse here. */
|
|
dev->next_action = XHCI_NEXT_ACTION_SET_CONFIG;
|
|
} else {
|
|
console_println("xhci: configure endpoint failed");
|
|
}
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
} else if (dev->connect_state == XHCI_CONN_AWAIT_RESET_ENDPOINT) {
|
|
/* G.1 / §F.14 stall recovery, command step 1 of 2: the
|
|
* Reset Endpoint command has completed. On success,
|
|
* the xHC endpoint is back in the Stopped state and we
|
|
* immediately issue step 2, Set TR Dequeue Pointer, to
|
|
* reposition the stalled Transfer Ring's dequeue past
|
|
* the failed TRB (at the ring's current producer slot,
|
|
* with the producer's cycle as DCS) so a freshly
|
|
* enqueued retry TRB is consumed cleanly. stall_dci
|
|
* tells which ring. */
|
|
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
|
|
console_println("xhci: reset endpoint succeeded");
|
|
uintptr_t new_dequeue;
|
|
uint32_t dcs;
|
|
if (dev->stall_ep_addr & USB_EP_ADDR_DIR_MASK) {
|
|
new_dequeue = (uintptr_t)&dev->bulk_in_ring[dev->bulk_in_ring_enq];
|
|
dcs = dev->bulk_in_ring_cycle;
|
|
} else {
|
|
new_dequeue = (uintptr_t)&dev->bulk_out_ring[dev->bulk_out_ring_enq];
|
|
dcs = dev->bulk_out_ring_cycle;
|
|
}
|
|
dev->connect_state = XHCI_CONN_AWAIT_SET_TR_DEQUEUE;
|
|
if (xhci_cmd_set_tr_dequeue_pointer(dev, slot_id,
|
|
dev->stall_ep_addr,
|
|
new_dequeue, dcs) != 0) {
|
|
console_println("xhci: set tr dequeue pointer submit failed");
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
/* Recovery couldn't even be issued -- clean
|
|
* terminal failure so the synchronous waiter
|
|
* doesn't spin forever on a wedged command. */
|
|
xhci_stall_fail(dev);
|
|
}
|
|
} else {
|
|
console_println("xhci: reset endpoint failed -- stalling out clean");
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
xhci_stall_fail(dev);
|
|
}
|
|
} else if (dev->connect_state == XHCI_CONN_AWAIT_SET_TR_DEQUEUE) {
|
|
/* G.1 / §F.14 stall recovery, command step 2 of 2: Set
|
|
* TR Dequeue Pointer has completed. The xHC side is
|
|
* now fully recovered; the device side still needs its
|
|
* halt cleared via control transfer, so chain into the
|
|
* deferred CLEAR_FEATURE(ENDPOINT_HALT). */
|
|
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
|
|
console_println("xhci: set TR dequeue pointer succeeded");
|
|
} else {
|
|
console_println("xhci: set TR dequeue pointer failed -- stalling out clean");
|
|
xhci_stall_fail(dev);
|
|
}
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
if (dev->bot_last_status != BOT_STATUS_FAILED) {
|
|
dev->bot_reset_clear_remaining = 1;
|
|
dev->next_action = XHCI_NEXT_ACTION_CLEAR_HALT;
|
|
dev->next_action_slot_id = slot_id;
|
|
}
|
|
} else {
|
|
console_println("xhci: command completion event");
|
|
}
|
|
break;
|
|
}
|
|
case XHCI_TRB_TYPE_TRANSFER_EVENT: {
|
|
uint32_t code = XHCI_EVT_COMPLETION_CODE(trb->status);
|
|
if (dev->pending_transfer_slot_id != 0) {
|
|
uint32_t xfer_slot_id = dev->pending_transfer_slot_id;
|
|
uint32_t purpose = dev->transfer_purpose;
|
|
dev->pending_transfer_slot_id = 0;
|
|
dev->transfer_purpose = XHCI_XFER_NONE;
|
|
|
|
if (code != XHCI_COMPLETION_CODE_SUCCESS) {
|
|
/* G.1 / §F.14: distinguish a true STALL (the one
|
|
* recoverable xHCI transfer completion) from every
|
|
* other failure. A stall on a bulk endpoint enters
|
|
* recovery; a plain failure (or a stall on an EP0
|
|
* control transfer, which this driver only meets
|
|
* during enumeration and does not attempt to
|
|
* recover) keeps today's behavior -- log and bail. */
|
|
if (code == XHCI_COMPLETION_CODE_STALL_ERROR &&
|
|
xhci_bulk_purpose_stalled(dev, purpose)) {
|
|
xhci_handle_bulk_stall(dev, xfer_slot_id, purpose);
|
|
} else {
|
|
console_println("xhci: control transfer failed");
|
|
}
|
|
break;
|
|
}
|
|
|
|
switch (purpose) {
|
|
case XHCI_XFER_DEVICE_DESC: {
|
|
console_println("xhci: device descriptor received");
|
|
/* USB 2.0 spec table 9-8 layout. Logged --
|
|
* 2f's own punch list asked whether vendor/
|
|
* product IDs are even needed, or class-only
|
|
* detection suffices; this surfaces the real
|
|
* values, doesn't decide it. */
|
|
uint32_t id_vendor = dev->device_descriptor[8] |
|
|
((uint32_t)dev->device_descriptor[9] << 8);
|
|
uint32_t id_product = dev->device_descriptor[10] |
|
|
((uint32_t)dev->device_descriptor[11] << 8);
|
|
xhci_log_hex32("xhci: idVendor=", id_vendor);
|
|
xhci_log_hex32("xhci: idProduct=", id_product);
|
|
xhci_log_hex32("xhci: bDeviceClass=", dev->device_descriptor[4]);
|
|
/* Chain: request just the Configuration
|
|
* descriptor's 9-byte header first, to learn
|
|
* wTotalLength before requesting everything.
|
|
* Deferred (see xhci_dev_t's doc comment on
|
|
* next_action) rather than called directly --
|
|
* a doorbell rung synchronously here, still
|
|
* inside this event-processing loop and
|
|
* before ERDP is updated, hung the guest
|
|
* outright (confirmed live via checkpoint
|
|
* logging, amd64 QEMU, 2026-08-22). */
|
|
dev->next_action = XHCI_NEXT_ACTION_GET_CONFIG_DESC;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
dev->next_action_length = 9;
|
|
break;
|
|
}
|
|
case XHCI_XFER_CONFIG_DESC_SHORT: {
|
|
uint16_t total_len = (uint16_t)(dev->config_descriptor[USB_CONFIG_OFF_TOTAL_LENGTH] |
|
|
((uint16_t)dev->config_descriptor[USB_CONFIG_OFF_TOTAL_LENGTH + 1] << 8));
|
|
dev->config_total_length = total_len;
|
|
xhci_log_hex32("xhci: config wTotalLength=", total_len);
|
|
dev->next_action = XHCI_NEXT_ACTION_GET_CONFIG_DESC;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
dev->next_action_length = total_len;
|
|
break;
|
|
}
|
|
case XHCI_XFER_CONFIG_DESC_FULL: {
|
|
console_println("xhci: full config descriptor received");
|
|
/* Walk the concatenated descriptor stream
|
|
* (Config + Interface + Endpoint descriptors
|
|
* back to back) looking for the Interface
|
|
* descriptor -- its fixed offset within the
|
|
* stream isn't guaranteed, has to be found by
|
|
* bDescriptorType, not assumed. */
|
|
uint16_t len = dev->config_total_length;
|
|
if (len > sizeof(dev->config_descriptor)) len = (uint16_t)sizeof(dev->config_descriptor);
|
|
uint16_t off = 0;
|
|
int found = 0;
|
|
while (off + 2 <= len) {
|
|
uint8_t desc_len = dev->config_descriptor[off + USB_DESC_OFF_LENGTH];
|
|
uint8_t desc_type = dev->config_descriptor[off + USB_DESC_OFF_TYPE];
|
|
if (desc_len == 0) break; /* malformed -- avoid an infinite loop */
|
|
if (desc_type == USB_DESC_TYPE_INTERFACE &&
|
|
off + USB_IFACE_OFF_PROTOCOL < len) {
|
|
uint8_t iface_class = dev->config_descriptor[off + USB_IFACE_OFF_CLASS];
|
|
uint8_t iface_subclass = dev->config_descriptor[off + USB_IFACE_OFF_SUBCLASS];
|
|
uint8_t iface_protocol = dev->config_descriptor[off + USB_IFACE_OFF_PROTOCOL];
|
|
xhci_log_hex32("xhci: bInterfaceClass=", iface_class);
|
|
xhci_log_hex32("xhci: bInterfaceSubClass=", iface_subclass);
|
|
xhci_log_hex32("xhci: bInterfaceProtocol=", iface_protocol);
|
|
if (iface_class == USB_CLASS_MASS_STORAGE &&
|
|
iface_subclass == USB_SUBCLASS_SCSI &&
|
|
iface_protocol == USB_PROTOCOL_BOT) {
|
|
console_println("xhci: confirmed Mass Storage / SCSI / BOT device");
|
|
/* Walk the Endpoint descriptors that
|
|
* follow this Interface descriptor,
|
|
* stopping at the next Interface
|
|
* descriptor (start of a different
|
|
* interface's endpoints) or end of
|
|
* the stream. Only bulk endpoints
|
|
* are of interest for BOT. */
|
|
uint16_t ep_off = (uint16_t)(off + desc_len);
|
|
while (ep_off + 2 <= len) {
|
|
uint8_t ep_desc_len = dev->config_descriptor[ep_off + USB_DESC_OFF_LENGTH];
|
|
uint8_t ep_desc_type = dev->config_descriptor[ep_off + USB_DESC_OFF_TYPE];
|
|
if (ep_desc_len == 0) break; /* malformed -- avoid an infinite loop */
|
|
if (ep_desc_type == USB_DESC_TYPE_INTERFACE) break;
|
|
if (ep_desc_type == USB_DESC_TYPE_ENDPOINT &&
|
|
ep_off + USB_EP_OFF_MAX_PACKET_SIZE + 1 < len) {
|
|
uint8_t ep_addr = dev->config_descriptor[ep_off + USB_EP_OFF_ADDRESS];
|
|
uint8_t ep_attr = dev->config_descriptor[ep_off + USB_EP_OFF_ATTRIBUTES];
|
|
uint16_t ep_max_packet = (uint16_t)(dev->config_descriptor[ep_off + USB_EP_OFF_MAX_PACKET_SIZE] |
|
|
((uint16_t)dev->config_descriptor[ep_off + USB_EP_OFF_MAX_PACKET_SIZE + 1] << 8));
|
|
if ((ep_attr & USB_EP_ATTR_TYPE_MASK) == USB_EP_TYPE_BULK) {
|
|
if (ep_addr & USB_EP_ADDR_DIR_MASK) {
|
|
dev->bulk_in_ep_addr = ep_addr;
|
|
dev->bulk_in_max_packet = ep_max_packet;
|
|
xhci_log_hex32("xhci: bulk IN endpoint=", ep_addr);
|
|
} else {
|
|
dev->bulk_out_ep_addr = ep_addr;
|
|
dev->bulk_out_max_packet = ep_max_packet;
|
|
xhci_log_hex32("xhci: bulk OUT endpoint=", ep_addr);
|
|
}
|
|
}
|
|
}
|
|
ep_off = (uint16_t)(ep_off + ep_desc_len);
|
|
}
|
|
/* Deferred (see xhci_dev_t's
|
|
* next_action doc comment) rather
|
|
* than called directly here --
|
|
* same doorbell-ordering hazard
|
|
* as the device/config descriptor
|
|
* chaining above. next_action_
|
|
* config_value is staged now but
|
|
* not consumed until SET_CONFIG
|
|
* actually runs, after Configure
|
|
* Endpoint completes below. */
|
|
dev->next_action_config_value =
|
|
dev->config_descriptor[USB_CONFIG_OFF_CONFIG_VALUE];
|
|
if (dev->bulk_in_ep_addr == 0 || dev->bulk_out_ep_addr == 0) {
|
|
/* Can't Configure Endpoint
|
|
* without knowing both bulk
|
|
* endpoints -- go straight to
|
|
* SET_CONFIGURATION so the
|
|
* device is still usable for
|
|
* whatever doesn't need BOT
|
|
* (nothing, today, but this
|
|
* keeps the two concerns
|
|
* separate rather than
|
|
* failing enumeration
|
|
* outright). */
|
|
console_println("xhci: warning -- BOT device missing a bulk IN or OUT endpoint, skipping configure endpoint");
|
|
dev->next_action = XHCI_NEXT_ACTION_SET_CONFIG;
|
|
} else {
|
|
dev->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT;
|
|
}
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
} else {
|
|
console_println("xhci: not a Mass Storage/SCSI/BOT device -- not usable as a drive");
|
|
}
|
|
found = 1;
|
|
break;
|
|
}
|
|
off = (uint16_t)(off + desc_len);
|
|
}
|
|
if (!found) {
|
|
console_println("xhci: no Interface descriptor found in config set");
|
|
}
|
|
break;
|
|
}
|
|
case XHCI_XFER_SET_CONFIG: {
|
|
console_println("xhci: device configured");
|
|
/* Milestone 2h: hand off to sk_repl_idle(),
|
|
* the only safe place to run the synchronous
|
|
* capacity query + block-subsystem attach --
|
|
* see bot_msc_attach_pending's own doc
|
|
* comment in xhci_driver.h. */
|
|
dev->bot_msc_attach_pending = 1;
|
|
dev->bot_msc_attach_slot_id = xfer_slot_id;
|
|
break;
|
|
}
|
|
case XHCI_XFER_CBW_SENT: {
|
|
console_println("xhci: CBW send completed");
|
|
/* Deferred (see xhci_dev_t's next_action doc
|
|
* comment) rather than called directly --
|
|
* same doorbell-ordering hazard as every
|
|
* other chained request in this driver.
|
|
* dCBWDataTransferLength == 0 (TEST UNIT
|
|
* READY) means no data stage exists -- BOT
|
|
* spec section 6.3 -- so skip straight to CSW
|
|
* receive. Otherwise the direction depends on
|
|
* which command this CBW was for: WRITE(10)
|
|
* needs a Data-Out stage (bulk_out_ring),
|
|
* every other data-bearing command here
|
|
* (READ10, READ CAPACITY10) needs Data-In. */
|
|
if (dev->bot_expected_data_len == 0) {
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_CSW_RECEIVE;
|
|
} else if (dev->bot_cmd_kind == BOT_CMD_WRITE10) {
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_DATA_OUT;
|
|
} else {
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_DATA_IN;
|
|
}
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
break;
|
|
}
|
|
case XHCI_XFER_BOT_DATA_IN: {
|
|
console_println("xhci: BOT Data-In read completed");
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_CSW_RECEIVE;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
break;
|
|
}
|
|
case XHCI_XFER_BOT_DATA_OUT: {
|
|
console_println("xhci: BOT Data-Out write completed");
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_CSW_RECEIVE;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
break;
|
|
}
|
|
case XHCI_XFER_CSW_RECEIVED: {
|
|
/* USB Mass Storage Class BOT spec section 5.2:
|
|
* a valid CSW must have the right signature
|
|
* and echo the CBW's own tag -- checked before
|
|
* trusting bCSWStatus at all, since a garbled
|
|
* or misaligned CSW read could otherwise be
|
|
* misread as a clean pass. */
|
|
if (dev->bot_csw.dCSWSignature != USB_BOT_CSW_SIGNATURE) {
|
|
console_println("xhci: CSW signature mismatch -- discarding");
|
|
dev->bot_last_status = BOT_STATUS_FAILED;
|
|
dev->bot_cmd_kind = BOT_CMD_NONE;
|
|
break;
|
|
} else if (dev->bot_csw.dCSWTag != dev->bot_last_tag) {
|
|
console_println("xhci: CSW tag mismatch -- discarding");
|
|
dev->bot_last_status = BOT_STATUS_FAILED;
|
|
dev->bot_cmd_kind = BOT_CMD_NONE;
|
|
break;
|
|
}
|
|
|
|
int csw_pass = (dev->bot_csw.bCSWStatus == USB_BOT_CSW_STATUS_PASS);
|
|
if (csw_pass) {
|
|
console_println("xhci: CSW status = PASS");
|
|
} else if (dev->bot_csw.bCSWStatus == USB_BOT_CSW_STATUS_FAILED) {
|
|
console_println("xhci: CSW status = FAILED");
|
|
} else {
|
|
console_println("xhci: CSW status = PHASE ERROR");
|
|
}
|
|
xhci_log_hex32("xhci: CSW data residue=", dev->bot_csw.dCSWDataResidue);
|
|
|
|
/* TEST UNIT READY completions chain into the
|
|
* actual READ(10) once PASS, or retry (bounded)
|
|
* on 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 doc comment in
|
|
* xhci.h). Every other command kind is
|
|
* terminal here: bot_last_status/bot_cmd_kind
|
|
* reset so a synchronous caller waiting in
|
|
* xhci_bot_wait_for_idle() (Milestone 2h) sees
|
|
* the command as finished. */
|
|
if (dev->bot_cmd_kind == BOT_CMD_TEST_UNIT_READY) {
|
|
if (csw_pass) {
|
|
if (dev->bot_tur_chain_target == BOT_TUR_CHAIN_READ_CAPACITY10) {
|
|
console_println("xhci: unit ready -- issuing READ CAPACITY10");
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10;
|
|
} else if (dev->bot_tur_chain_target == BOT_TUR_CHAIN_WRITE10) {
|
|
console_println("xhci: unit ready -- issuing WRITE10");
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_SEND_WRITE10;
|
|
} else {
|
|
console_println("xhci: unit ready -- issuing READ10");
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_SEND_READ10;
|
|
}
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
} else if (dev->bot_tur_retries < XHCI_BOT_TUR_MAX_RETRIES) {
|
|
dev->bot_tur_retries++;
|
|
console_println("xhci: unit not ready -- retrying TEST UNIT READY");
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_SEND_TUR;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
} else {
|
|
console_println("xhci: unit still not ready -- giving up");
|
|
dev->bot_last_status = BOT_STATUS_FAILED;
|
|
dev->bot_cmd_kind = BOT_CMD_NONE;
|
|
}
|
|
} else if (dev->bot_cmd_kind == BOT_CMD_READ_CAPACITY10) {
|
|
if (csw_pass) {
|
|
const uint8_t *d = dev->bot_data_buf;
|
|
dev->bot_cap_last_lba = ((uint32_t)d[0] << 24) | ((uint32_t)d[1] << 16) |
|
|
((uint32_t)d[2] << 8) | (uint32_t)d[3];
|
|
dev->bot_cap_block_size = ((uint32_t)d[4] << 24) | ((uint32_t)d[5] << 16) |
|
|
((uint32_t)d[6] << 8) | (uint32_t)d[7];
|
|
xhci_log_hex32("xhci: READ CAPACITY last LBA=", dev->bot_cap_last_lba);
|
|
xhci_log_hex32("xhci: READ CAPACITY block size=", dev->bot_cap_block_size);
|
|
}
|
|
dev->bot_last_status = csw_pass ? BOT_STATUS_PASS : BOT_STATUS_FAILED;
|
|
dev->bot_cmd_kind = BOT_CMD_NONE;
|
|
} else {
|
|
/* BOT_CMD_READ10 or BOT_CMD_WRITE10
|
|
* (BOT_CMD_NONE shouldn't reach here) --
|
|
* terminal either way. */
|
|
dev->bot_last_status = csw_pass ? BOT_STATUS_PASS : BOT_STATUS_FAILED;
|
|
dev->bot_cmd_kind = BOT_CMD_NONE;
|
|
}
|
|
break;
|
|
}
|
|
case XHCI_XFER_CLEAR_HALT: {
|
|
/* G.1 / §F.14: CLEAR_FEATURE(ENDPOINT_HALT)
|
|
* completed. This is the device-side clear; the
|
|
* xHC side was already recovered by Reset
|
|
* Endpoint + Set TR Dequeue Pointer. On success
|
|
* this may be one of two CLEAR_FEATUREs owed in
|
|
* a BOT-reset escalation (bot_reset_clear_remaining
|
|
* counts how many remain; >1 means "also clear
|
|
* the other bulk endpoint"). When all clears are
|
|
* done, the recovery is complete and the stalled
|
|
* command stage is retried (stall_retry_action).
|
|
* A CLEAR_FEATURE that itself fails or stalls
|
|
* is the "step 3 itself stalls" escalation case
|
|
* from §F.14 -- escalate to a full BOT Mass
|
|
* Storage Reset. */
|
|
if (code != XHCI_COMPLETION_CODE_SUCCESS) {
|
|
console_println("xhci: clear endpoint halt failed -- escalating to BOT reset");
|
|
dev->next_action = XHCI_NEXT_ACTION_BOT_RESET;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
break;
|
|
}
|
|
console_println("xhci: clear endpoint halt succeeded");
|
|
if (dev->bot_reset_clear_remaining > 1) {
|
|
dev->bot_reset_clear_remaining--;
|
|
/* Flip to the other bulk endpoint for the
|
|
* second CLEAR_FEATURE owed in a BOT-reset
|
|
* escalation. */
|
|
dev->stall_ep_addr = (dev->stall_ep_addr & USB_EP_ADDR_DIR_MASK)
|
|
? dev->bulk_out_ep_addr
|
|
: dev->bulk_in_ep_addr;
|
|
dev->next_action = XHCI_NEXT_ACTION_CLEAR_HALT;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
} else {
|
|
dev->bot_reset_clear_remaining = 0;
|
|
/* Recovery done -- resume the rest of the
|
|
* command chain at the stalled stage. */
|
|
if (dev->stall_retry_action != XHCI_NEXT_ACTION_NONE) {
|
|
dev->next_action = dev->stall_retry_action;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case XHCI_XFER_BOT_RESET: {
|
|
/* G.1 / §F.14: BOT Mass Storage Reset completed.
|
|
* Per BOT spec 5.3.4's full procedure this is
|
|
* followed by CLEAR_FEATURE(ENDPOINT_HALT) on
|
|
* *both* bulk endpoints before the original
|
|
* command is retried; stage that as two
|
|
* chained CLEAR_HALT transfers. */
|
|
if (code != XHCI_COMPLETION_CODE_SUCCESS) {
|
|
console_println("xhci: BOT reset failed -- stalling out clean");
|
|
xhci_stall_fail(dev);
|
|
break;
|
|
}
|
|
console_println("xhci: BOT reset succeeded -- clearing both endpoints");
|
|
dev->bot_reset_clear_remaining = 2;
|
|
dev->stall_ep_addr = dev->bulk_in_ep_addr;
|
|
dev->next_action = XHCI_NEXT_ACTION_CLEAR_HALT;
|
|
dev->next_action_slot_id = xfer_slot_id;
|
|
break;
|
|
}
|
|
default:
|
|
console_println("xhci: transfer event");
|
|
break;
|
|
}
|
|
} else {
|
|
console_println("xhci: transfer event");
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
dev->evt_ring_deq++;
|
|
if (dev->evt_ring_deq == XHCI_RING_TRB_COUNT) {
|
|
dev->evt_ring_deq = 0;
|
|
dev->evt_ring_cycle ^= 1u;
|
|
}
|
|
evt_processed++;
|
|
}
|
|
|
|
/* Event Ring dequeue-pointer update (xHCI 1.2 spec §4.9.4): write the
|
|
* new dequeue pointer back to ERDP with bit3 (EHB, Event Handler Busy,
|
|
* RW1C) set -- writing 1 to EHB is what clears it, per spec, not a
|
|
* read-modify-write of the current value. Skipping this leaves the
|
|
* controller believing the event handler is still busy and it will
|
|
* not post further events on this interrupter. IMAN.IP/USBSTS.EINT
|
|
* are deliberately not touched here: this driver never sets
|
|
* USBCMD.INTE/IMAN.IE (polled, not interrupt-driven -- see this
|
|
* function's own doc comment), so those RW1C bits never latch and
|
|
* have nothing to clear. */
|
|
dev->intr0->erdp = ((uint64_t)(uintptr_t)&dev->evt_ring[dev->evt_ring_deq]
|
|
& XHCI_ERDP_PTR_MASK) | XHCI_ERDP_EHB;
|
|
|
|
/* Deferred chained request, if event processing above set one --
|
|
* see xhci_dev_t's own doc comment on why this must happen here,
|
|
* after ERDP is updated, not synchronously inside the loop above. */
|
|
if (dev->next_action == XHCI_NEXT_ACTION_GET_DEVICE_DESC) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_ep0_get_device_descriptor(dev, next_slot_id) != 0) {
|
|
console_println("xhci: deferred device descriptor request setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_GET_CONFIG_DESC) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
uint16_t next_length = dev->next_action_length;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_ep0_get_config_descriptor(dev, next_slot_id, next_length) != 0) {
|
|
console_println("xhci: deferred config descriptor request setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
dev->connect_state = XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT;
|
|
if (xhci_cmd_configure_endpoint(dev, next_slot_id) != 0) {
|
|
console_println("xhci: deferred configure endpoint request setup failed");
|
|
dev->connect_state = XHCI_CONN_IDLE;
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_SET_CONFIG) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
uint8_t next_config_value = dev->next_action_config_value;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_ep0_set_configuration(dev, next_slot_id, next_config_value) != 0) {
|
|
console_println("xhci: deferred set configuration request setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_DATA_IN) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_bot_read_data_in(dev, next_slot_id) != 0) {
|
|
console_println("xhci: deferred BOT Data-In read setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_DATA_OUT) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_bot_write_data_out(dev, next_slot_id) != 0) {
|
|
console_println("xhci: deferred BOT Data-Out write setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_CSW_RECEIVE) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_bot_receive_csw(dev, next_slot_id) != 0) {
|
|
console_println("xhci: deferred CSW receive setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_SEND_TUR) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_bot_send_test_unit_ready(dev, next_slot_id) != 0) {
|
|
console_println("xhci: deferred TEST UNIT READY retry setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_SEND_READ10) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_bot_send_read10(dev, next_slot_id, dev->bot_read10_lba,
|
|
dev->bot_read10_num_blocks,
|
|
dev->bot_read10_block_size) != 0) {
|
|
console_println("xhci: deferred READ10 setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_SEND_READ_CAPACITY10) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_bot_send_read_capacity10(dev, next_slot_id) != 0) {
|
|
console_println("xhci: deferred READ CAPACITY10 setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_SEND_WRITE10) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_bot_send_write10(dev, next_slot_id, dev->bot_write10_lba,
|
|
dev->bot_write10_num_blocks,
|
|
dev->bot_write10_block_size) != 0) {
|
|
console_println("xhci: deferred WRITE10 setup failed");
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_CLEAR_HALT) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_ep0_clear_endpoint_halt(dev, next_slot_id, dev->stall_ep_addr) != 0) {
|
|
console_println("xhci: deferred clear endpoint halt setup failed");
|
|
xhci_stall_fail(dev);
|
|
}
|
|
} else if (dev->next_action == XHCI_NEXT_ACTION_BOT_RESET) {
|
|
uint32_t next_slot_id = dev->next_action_slot_id;
|
|
dev->next_action = XHCI_NEXT_ACTION_NONE;
|
|
if (xhci_ep0_bot_mass_storage_reset(dev, next_slot_id) != 0) {
|
|
console_println("xhci: deferred BOT mass storage reset setup failed");
|
|
xhci_stall_fail(dev);
|
|
}
|
|
}
|
|
}
|