Files
LithosAnanake/src/starkernel/usb/xhci.c
T
Robert Allan JamesandClaude Sonnet 5 dd043bbfeb Artemis Milestone 2e: PORTSC connect/disconnect detection, verified live
xhci_poll_events()'s Port Status Change branch now decodes the Port ID
from the event TRB (XHCI_PSC_EVT_PORT_ID, new in xhci.h), reads that
port's PORTSC.CCS via a new xhci_port_regs() helper, and logs connect vs.
disconnect. Acknowledges by writing back only PP (preserved) and CSC (the
bit being cleared) -- PED/PR/other _C bits written 0 so nothing is
accidentally disabled, reset, or silently cleared, matching the RW1C
discipline already used for ERDP.EHB in 2d.

Verified with the real target scenario via QMP hotplug on all three
architectures: boot with the xHCI controller present but no USB device
attached (confirmed zero port activity at ok>), then live
attach/detach/re-attach of a virtual USB thumb drive
(disk/usb-thumbdrive-test.img via usb-storage on xhci0.0). Full
connect->disconnect->connect cycle confirmed clean (no port wedge) on
amd64; single connect confirmed on aarch64 and riscv64.

Still open: correlating Command Completion Events back to their issuing
command, driving Enable Slot/Address Device from this connect path
(currently only a boot-time smoke test), and the callback surface into
Section U's higher-level code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
2026-08-22 10:11:47 -04:00

405 lines
17 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 "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;
}
}
}
/* 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_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;
}
console_println("xhci: controller running");
g_xhci_dev = dev;
/* Milestone 2e smoke test: prove the write path (TRB enqueue, cycle
* bit, doorbell ring) before building slot allocation on top of it.
* A real Enable Slot is harmless to issue speculatively -- it just
* reserves a Device Slot Context the driver doesn't use yet -- and its
* Command Completion Event is the only live proof that a TRB written by
* software was actually consumed by the controller. Real connect-driven
* Enable Slot calls (Milestone 2e proper) replace/reuse this call site
* once Port Status Change handling exists. */
xhci_cmd_enable_slot(dev);
return 0;
}
/* -------------------------------------------------------------------------
* 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.
* ------------------------------------------------------------------------- */
static void xhci_submit_command(xhci_dev_t *dev, uint64_t parameter,
uint32_t status, uint32_t trb_type)
{
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) |
(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);
console_println("xhci: enable slot command 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));
}
void xhci_poll_events(void)
{
xhci_dev_t *dev = g_xhci_dev;
if (!dev) return;
while (((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) {
console_println("xhci: port status change -- device connected");
} else {
console_println("xhci: port status change -- device disconnected");
}
/* 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:
/* No commands are issued yet (Milestone 2e is the first
* command-ring user) -- logged for the same reason. */
console_println("xhci: command completion event");
break;
case XHCI_TRB_TYPE_TRANSFER_EVENT:
/* No transfer rings exist yet (Milestone 2g) -- logged. */
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;
}
}
/* 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;
}