fix(usb): the xHCI controller is found by class on any bus, sized, taken from the firmware, and 64-byte contexts laid out
boards/BRINGUP.md A1, FABRIC-3 SXL. It was matched by QEMU's own IDs on bus 0 alone, so no real board's was found. Now pci_find_class() reads every bus/slot/function; pci_bar_size() sizes BAR0; the USB Legacy Support capability hands the controller over from the BIOS, SMIs off; CSZ=1 contexts are placed 64 bytes apart instead of refused. XHCI_QEMU_ARGS lets QEMU place the controller as real hardware does. Test first: an NEC controller behind a PCIe root port -- no attach before, Zuse minted after, on all three ISAs. Standard acceptance unchanged; five hot-plugged identities attach. Not rehearsable: firmware handoff, CSZ=1. Still open: only the first controller found is used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0e2c60dac3
commit
080b7012ba
+98
-42
@@ -27,24 +27,77 @@
|
||||
#include "starkernel/arch.h"
|
||||
#include "log.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
|
||||
/* BAR0 is mapped at the size the BAR itself reports (pci_bar_size(), the
|
||||
* standard probe), never less than 64 KiB: the Capability, Operational,
|
||||
* Port, Runtime and Doorbell registers and the extended capabilities all
|
||||
* live in it, and a real controller's BAR is commonly larger than QEMU's
|
||||
* (2026-10-14, boards/BRINGUP.md A1). */
|
||||
#define XHCI_BAR0_MIN_MAP_SIZE 0x10000ull
|
||||
|
||||
/* xHCI is PCI class 0x0C (serial bus), subclass 0x03 (USB), programming
|
||||
* interface 0x30 (xHCI), whoever makes it. Until 2026-10-14 the controller
|
||||
* was matched by QEMU's own vendor and device (1B36:000D) on bus 0 alone,
|
||||
* so no real board's was ever found (A1). */
|
||||
#define XHCI_PCI_CLASS 0x0Cu
|
||||
#define XHCI_PCI_SUBCLASS 0x03u
|
||||
#define XHCI_PCI_PROG_IF 0x30u
|
||||
|
||||
static int xhci_wait_bit(volatile uint32_t *reg, uint32_t mask, int want_set,
|
||||
uint64_t max_ticks);
|
||||
static void xhci_log_hex32(const char *prefix, uint32_t v);
|
||||
|
||||
/* Take the controller from the firmware (xHCI 1.2 section 4.22.1): a BIOS
|
||||
* that drives USB itself (a keyboard in its setup screen, booting from a
|
||||
* stick) owns the controller until the OS asks for it through the USB
|
||||
* Legacy Support extended capability, and its SMIs keep firing behind the
|
||||
* OS's back until they are switched off. QEMU's controller has no such
|
||||
* capability; every PC's has. */
|
||||
#define XHCI_XCAP_LEGACY 1u
|
||||
#define XHCI_LEGSUP_BIOS_OWNED (1u << 16)
|
||||
#define XHCI_LEGSUP_OS_OWNED (1u << 24)
|
||||
static void xhci_take_from_bios(xhci_dev_t *dev)
|
||||
{
|
||||
uint32_t off = (dev->cap->hcc_params1 >> 16) & 0xFFFFu; /* in dwords */
|
||||
unsigned guard = 0;
|
||||
|
||||
while (off && guard++ < 64) {
|
||||
volatile uint32_t *cap = (volatile uint32_t *)((uint8_t *)dev->cap + (size_t)off * 4u);
|
||||
uint32_t v = *cap;
|
||||
if ((v & 0xFFu) == XHCI_XCAP_LEGACY) {
|
||||
if (v & XHCI_LEGSUP_BIOS_OWNED) {
|
||||
*cap = v | XHCI_LEGSUP_OS_OWNED;
|
||||
if (xhci_wait_bit(cap, XHCI_LEGSUP_BIOS_OWNED, 0, 100) != 0) {
|
||||
/* The firmware did not let go within a second: take
|
||||
* it anyway, as Linux does. */
|
||||
*cap = (*cap & ~XHCI_LEGSUP_BIOS_OWNED) | XHCI_LEGSUP_OS_OWNED;
|
||||
log_message(LOG_WARN, "xhci: firmware did not hand the controller over -- taken");
|
||||
}
|
||||
} else {
|
||||
*cap = v | XHCI_LEGSUP_OS_OWNED;
|
||||
}
|
||||
/* USBLEGCTLSTS: every SMI enable off (bits 0, 4, 13-15), every
|
||||
* SMI status cleared (write-1-to-clear, bits 29-31). */
|
||||
cap[1] = (cap[1] & ~((1u << 0) | (1u << 4) | (7u << 13))) | (7u << 29);
|
||||
log_message(LOG_DEBUG, "xhci: controller taken from the firmware");
|
||||
return;
|
||||
}
|
||||
if (((v >> 8) & 0xFFu) == 0) return;
|
||||
off += (v >> 8) & 0xFFu;
|
||||
}
|
||||
}
|
||||
|
||||
int xhci_find_and_map(xhci_dev_t *dev)
|
||||
{
|
||||
uint64_t map_size;
|
||||
|
||||
if (!dev) return -1;
|
||||
|
||||
if (pci_find_first(XHCI_PCI_VENDOR_ID, XHCI_PCI_DEVICE_ID, &dev->pci) != 0) {
|
||||
log_message(LOG_DEBUG, "xhci: no controller found on PCI bus 0");
|
||||
if (pci_find_class(XHCI_PCI_CLASS, XHCI_PCI_SUBCLASS, XHCI_PCI_PROG_IF, 0, &dev->pci) != 0) {
|
||||
log_message(LOG_DEBUG, "xhci: no xHCI controller on any PCI bus");
|
||||
return -1;
|
||||
}
|
||||
xhci_log_hex32("xhci: controller vendor/device=",
|
||||
((uint32_t)dev->pci.vendor_id << 16) | dev->pci.device_id);
|
||||
|
||||
pci_enable(&dev->pci);
|
||||
|
||||
@@ -54,7 +107,9 @@ int xhci_find_and_map(xhci_dev_t *dev)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (pci_map_bar(dev->bar0_phys, XHCI_BAR0_MAP_SIZE) != 0) {
|
||||
map_size = pci_bar_size(&dev->pci, 0);
|
||||
if (map_size < XHCI_BAR0_MIN_MAP_SIZE) map_size = XHCI_BAR0_MIN_MAP_SIZE;
|
||||
if (pci_map_bar(dev->bar0_phys, map_size) != 0) {
|
||||
log_message(LOG_ERROR, "xhci: BAR0 mapping failed");
|
||||
return -2;
|
||||
}
|
||||
@@ -72,6 +127,8 @@ int xhci_find_and_map(xhci_dev_t *dev)
|
||||
dev->max_ports = XHCI_HCSPARAMS1_MAX_PORTS(hcs1);
|
||||
dev->max_scratchpad_bufs = XHCI_HCSPARAMS2_MAX_SCRATCHPAD_BUFS(dev->cap->hcs_params2);
|
||||
|
||||
xhci_take_from_bios(dev);
|
||||
|
||||
log_message(LOG_DEBUG, "xhci: controller found, BAR0 mapped");
|
||||
return 0;
|
||||
}
|
||||
@@ -466,6 +523,18 @@ static uint32_t xhci_default_ep0_max_packet(uint32_t speed)
|
||||
}
|
||||
}
|
||||
|
||||
/* Every context -- Input Control, Slot, Endpoint -- is 32 bytes, or 64
|
||||
* where HCCPARAMS1.CSZ says so (xHCI 1.2 section 6.2.1); a 64-byte
|
||||
* context is the same fields in its first 32 bytes, then reserved space.
|
||||
* So the 32-byte structs (xhci.h) stay right for every field, and only
|
||||
* where each context begins depends on CSZ. Until 2026-10-14 a CSZ=1
|
||||
* controller was refused outright; QEMU's has CSZ=0, many real ones have
|
||||
* CSZ=1 (boards/BRINGUP.md A1). */
|
||||
static size_t xhci_ctx_bytes(const xhci_dev_t *dev)
|
||||
{
|
||||
return XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1) ? 64u : 32u;
|
||||
}
|
||||
|
||||
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
||||
uint32_t port_id, uint32_t speed)
|
||||
{
|
||||
@@ -474,24 +543,18 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
||||
xhci_msc_slot_t *ms = xhci_msc_slot_for(dev, slot_id);
|
||||
if (!ms) return -1;
|
||||
|
||||
if (XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)) {
|
||||
log_message(LOG_ERROR, "xhci: 64-byte contexts required, not implemented -- refusing");
|
||||
return -2;
|
||||
}
|
||||
const size_t cs = xhci_ctx_bytes(dev); /* 32 or 64, by HCCPARAMS1.CSZ */
|
||||
|
||||
/* Lazily allocate once per slot; reused across a slot's own re-attach
|
||||
* (FABRIC-3.md §VII, 2026-09-05: per-slot now, not single-device-wide).
|
||||
* All three re-initialised fully below regardless of whether this is
|
||||
* the first call for this slot. */
|
||||
if (!ms->input_ctx) {
|
||||
ms->input_ctx = kmalloc_aligned(
|
||||
sizeof(xhci_input_ctrl_ctx32_t) + sizeof(xhci_slot_ctx32_t) +
|
||||
sizeof(xhci_ep_ctx32_t), 64);
|
||||
ms->input_ctx = kmalloc_aligned(3 * cs, 64); /* Input Control, Slot, EP0 */
|
||||
if (!ms->input_ctx) return -1;
|
||||
}
|
||||
if (!ms->device_ctx) {
|
||||
ms->device_ctx = kmalloc_aligned(
|
||||
sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t), 64);
|
||||
ms->device_ctx = kmalloc_aligned(2 * cs, 64); /* Slot, EP0 */
|
||||
if (!ms->device_ctx) return -1;
|
||||
}
|
||||
if (!ms->ep0_ring) {
|
||||
@@ -522,7 +585,7 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
||||
* controller writes this on Address Device success, software must not
|
||||
* pre-fill it. */
|
||||
uint8_t *dctx = (uint8_t *)ms->device_ctx;
|
||||
for (size_t i = 0; i < sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t); i++) dctx[i] = 0;
|
||||
for (size_t i = 0; i < 2 * cs; i++) dctx[i] = 0;
|
||||
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)ms->device_ctx;
|
||||
|
||||
/* Input Context: Input Control Context, then Slot Context, then EP0
|
||||
@@ -530,20 +593,18 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
|
||||
* the Device Context; conflating the two is the standard mistake
|
||||
* here). */
|
||||
uint8_t *ictx = (uint8_t *)ms->input_ctx;
|
||||
size_t total = sizeof(xhci_input_ctrl_ctx32_t) + sizeof(xhci_slot_ctx32_t) +
|
||||
sizeof(xhci_ep_ctx32_t);
|
||||
size_t total = 3 * cs;
|
||||
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));
|
||||
xhci_slot_ctx32_t *slot = (xhci_slot_ctx32_t *)(ictx + cs);
|
||||
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));
|
||||
xhci_ep_ctx32_t *ep0 = (xhci_ep_ctx32_t *)(ictx + 2 * cs);
|
||||
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);
|
||||
@@ -578,10 +639,7 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
||||
if (!dev || !dev->cmd_ring || !ms || !ms->input_ctx || !ms->device_ctx) return -1;
|
||||
if (ms->bulk_in_ep_addr == 0 || ms->bulk_out_ep_addr == 0) return -1;
|
||||
|
||||
if (XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)) {
|
||||
log_message(LOG_ERROR, "xhci: 64-byte contexts required, not implemented -- refusing");
|
||||
return -2;
|
||||
}
|
||||
const size_t cs = xhci_ctx_bytes(dev); /* 32 or 64, by HCCPARAMS1.CSZ */
|
||||
|
||||
if (!ms->bulk_in_ring) {
|
||||
ms->bulk_in_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
|
||||
@@ -612,8 +670,8 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
||||
* 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);
|
||||
size_t old_device_ctx_bytes = 2 * cs;
|
||||
size_t new_device_ctx_bytes = (1 + (size_t)max_dci) * cs;
|
||||
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;
|
||||
@@ -624,7 +682,7 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
||||
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)ms->device_ctx;
|
||||
|
||||
/* ms->input_ctx is reused from Address Device -- same allocation
|
||||
* (96 bytes: Input Control Ctx + Slot Ctx + one EP Ctx) is too small
|
||||
* (three contexts: Input Control, Slot, EP0) 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
|
||||
@@ -633,9 +691,9 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
||||
* 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);
|
||||
* block in place. */
|
||||
size_t ctx_count = 1 /* Slot */ + max_dci; /* DCI 1..max_dci, one context each */
|
||||
size_t total = cs + ctx_count * cs;
|
||||
void *new_input_ctx = kmalloc_aligned(total, 64);
|
||||
if (!new_input_ctx) return -1;
|
||||
ms->input_ctx = new_input_ctx;
|
||||
@@ -654,7 +712,7 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
||||
* 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 *)ms->device_ctx;
|
||||
xhci_slot_ctx32_t *in_slot = (xhci_slot_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t));
|
||||
xhci_slot_ctx32_t *in_slot = (xhci_slot_ctx32_t *)(ictx + cs);
|
||||
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);
|
||||
@@ -673,16 +731,14 @@ int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
|
||||
* 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));
|
||||
xhci_ep_ctx32_t *in_ep = (xhci_ep_ctx32_t *)(ictx + cs + (size_t)in_dci * cs);
|
||||
in_ep->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
|
||||
(XHCI_EP_CTX_TYPE_BULK_IN << XHCI_EP_CTX_TYPE_SHIFT) |
|
||||
((uint32_t)ms->bulk_in_max_packet << XHCI_EP_CTX_MAX_PACKET_SHIFT);
|
||||
in_ep->tr_dequeue_ptr = ((uint64_t)(uintptr_t)ms->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));
|
||||
xhci_ep_ctx32_t *out_ep = (xhci_ep_ctx32_t *)(ictx + cs + (size_t)out_dci * cs);
|
||||
out_ep->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
|
||||
(XHCI_EP_CTX_TYPE_BULK_OUT << XHCI_EP_CTX_TYPE_SHIFT) |
|
||||
((uint32_t)ms->bulk_out_max_packet << XHCI_EP_CTX_MAX_PACKET_SHIFT);
|
||||
|
||||
Reference in New Issue
Block a user