xhci_poll_events()'s Port Status Change connect branch now calls xhci_cmd_enable_slot() directly (the earlier boot-time smoke test call is gone), tracked via a new dev->pending_connect_port_id -- since this driver only ever has one command outstanding at a time, that alone identifies which port a later Command Completion Event answers, without needing to match the Command TRB Pointer yet. On success the returned Slot ID is recorded in a new dev->port_slot_id[], a fixed uint32_t[XHCI_MAX_TRACKED_PORTS] (32) indexed by port. Disconnect clears the port's tracked slot (real teardown -- Disable Slot, DCBAA clear, Section U callback -- is still a later increment). Fixed array, not heap-allocated: a first attempt sized port_slot_id dynamically via kmalloc_aligned(dev->max_ports * sizeof(uint32_t), 64) inside xhci_bringup() and it crashed amd64 with a page fault (IFETCH at RIP=CR2=0xA0000, the legacy VGA hole) during the unrelated Mama-VM-birth phase afterward -- a heap-corruption signature, not chased to root cause. Switching to a fixed array (matching this driver's existing preference for fixed over dynamic allocation) made the crash go away; the crashing boot's log is kept (logs/20260822-102516/) as the evidence trail. Verified live via QMP hotplug, all three architectures: connect -> "enable slot command submitted" -> "enable slot succeeded", with a disconnect/reconnect cycle repeating cleanly and no port wedge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
444 lines
20 KiB
C
444 lines
20 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;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
console_println("xhci: controller running");
|
|
|
|
g_xhci_dev = 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");
|
|
/* 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->pending_connect_port_id == 0) {
|
|
dev->pending_connect_port_id = port_id;
|
|
xhci_cmd_enable_slot(dev);
|
|
} else {
|
|
console_println("xhci: enable slot already pending -- dropped");
|
|
}
|
|
} 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) {
|
|
/* Real teardown (Disable Slot command, DCBAA entry
|
|
* clear, callback to Section U's code) is a later
|
|
* increment -- for now just stop tracking the slot
|
|
* so a future connect on this port isn't confused
|
|
* for one already in progress. */
|
|
dev->port_slot_id[port_id - 1] = 0;
|
|
}
|
|
}
|
|
/* 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 the single in-flight Enable Slot, not to
|
|
* the Command TRB Pointer in trb->parameter -- this driver
|
|
* only ever has one command outstanding (see
|
|
* xhci_dev_t's doc comment), so pending_connect_port_id
|
|
* alone is enough to identify which port this completion
|
|
* belongs to; a real Command TRB Pointer match becomes
|
|
* necessary once Address Device commands can also be
|
|
* in flight concurrently with Enable Slot. */
|
|
if (dev->pending_connect_port_id != 0) {
|
|
uint32_t port_id = dev->pending_connect_port_id;
|
|
dev->pending_connect_port_id = 0;
|
|
if (code == XHCI_COMPLETION_CODE_SUCCESS &&
|
|
port_id >= 1 && port_id <= XHCI_MAX_TRACKED_PORTS) {
|
|
dev->port_slot_id[port_id - 1] = slot_id;
|
|
console_println("xhci: enable slot succeeded");
|
|
} else {
|
|
console_println("xhci: enable slot failed");
|
|
}
|
|
} else {
|
|
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;
|
|
}
|