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
+16
-4
@@ -134,6 +134,18 @@ ARTDISK ?= disk/artemis.img
|
||||
# to today's scale (FABRIC-3.md §VII.4). Override if a future test needs more, e.g.
|
||||
# `make -f kernel/Makefile qemu XHCI_PORTS=32`.
|
||||
XHCI_PORTS ?= 16
|
||||
# XHCI_QEMU_ARGS -- the USB controller QEMU is given. Empty (the default) is
|
||||
# qemu-xhci on the root bus, as always. Set it to rehearse a controller the
|
||||
# way real hardware has one -- another vendor, behind a bridge (2026-10-14,
|
||||
# boards/BRINGUP.md A1), e.g.
|
||||
# XHCI_QEMU_ARGS="-device pcie-root-port,id=rp1,bus=pcie.0,chassis=1,addr=0x6 \
|
||||
# -device nec-usb-xhci,id=xhci0,bus=rp1,p2=16,p3=16"
|
||||
# Keep id=xhci0: ZUSEDISK and QEMU_EXTRA attach to bus xhci0.0.
|
||||
XHCI_QEMU_ARGS ?=
|
||||
XHCI_DEFAULT_ROOT := -device qemu-xhci,id=xhci0,p2=$(XHCI_PORTS),p3=$(XHCI_PORTS)
|
||||
XHCI_DEFAULT_SLOT4 := -device qemu-xhci,id=xhci0,addr=0x4,p2=$(XHCI_PORTS),p3=$(XHCI_PORTS)
|
||||
XHCI_ARGS_ROOT := $(if $(strip $(XHCI_QEMU_ARGS)),$(XHCI_QEMU_ARGS),$(XHCI_DEFAULT_ROOT))
|
||||
XHCI_ARGS_SLOT4 := $(if $(strip $(XHCI_QEMU_ARGS)),$(XHCI_QEMU_ARGS),$(XHCI_DEFAULT_SLOT4))
|
||||
# ZUSEDISK -- Zuse's own minted USB thumbdrive, attached by default so a
|
||||
# plain `make qemu` lands already authenticated into the Zuse identity
|
||||
# (Captain Bob, 2026-08-28). Attached at QEMU launch time via -drive/
|
||||
@@ -961,7 +973,7 @@ ifeq ($(ARCH),amd64)
|
||||
-cdrom $(QEMU_ISO) -boot d \
|
||||
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
|
||||
-device virtio-blk-pci,drive=artdisk \
|
||||
-device qemu-xhci,id=xhci0,p2=$(XHCI_PORTS),p3=$(XHCI_PORTS) \
|
||||
$(XHCI_ARGS_ROOT) \
|
||||
$(ZUSEDISK_QEMU_ARGS) \
|
||||
-object rng-random,id=rng0,filename=/dev/urandom \
|
||||
-device virtio-rng-pci,rng=rng0 \
|
||||
@@ -1045,7 +1057,7 @@ else ifeq ($(ARCH),aarch64)
|
||||
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
|
||||
-device virtio-blk-pci,drive=artdisk \
|
||||
-device virtio-keyboard-pci,addr=0x3 \
|
||||
-device qemu-xhci,id=xhci0,addr=0x4,p2=$(XHCI_PORTS),p3=$(XHCI_PORTS) \
|
||||
$(XHCI_ARGS_SLOT4) \
|
||||
$(ZUSEDISK_QEMU_ARGS) \
|
||||
-object rng-random,id=rng0,filename=/dev/urandom \
|
||||
-device virtio-rng-pci,rng=rng0,addr=0x5 \
|
||||
@@ -1135,7 +1147,7 @@ else ifeq ($(ARCH),riscv64)
|
||||
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
|
||||
-device virtio-blk-pci,drive=artdisk,addr=0x1 \
|
||||
-device virtio-keyboard-pci,addr=0x3 \
|
||||
-device qemu-xhci,id=xhci0,addr=0x4,p2=$(XHCI_PORTS),p3=$(XHCI_PORTS) \
|
||||
$(XHCI_ARGS_SLOT4) \
|
||||
$(ZUSEDISK_QEMU_ARGS) \
|
||||
-object rng-random,id=rng0,filename=/dev/urandom \
|
||||
-device virtio-rng-pci,rng=rng0,addr=0x5 \
|
||||
@@ -1246,7 +1258,7 @@ rpi5-rehearsal:
|
||||
-drive id=artdisk,file=$(ARTDISK),format=raw,if=none \
|
||||
-device virtio-blk-pci,drive=artdisk \
|
||||
-device virtio-keyboard-pci,addr=0x3 \
|
||||
-device qemu-xhci,id=xhci0,addr=0x4,p2=$(XHCI_PORTS),p3=$(XHCI_PORTS) \
|
||||
$(XHCI_ARGS_SLOT4) \
|
||||
$(ZUSEDISK_QEMU_ARGS) \
|
||||
-object rng-random,id=rng0,filename=/dev/urandom \
|
||||
-device virtio-rng-pci,rng=rng0,addr=0x5 \
|
||||
|
||||
@@ -72,6 +72,15 @@ int pci_init(void *rsdp);
|
||||
*/
|
||||
int pci_find_first(uint16_t vendor_id, uint16_t device_id, PciDevice *out);
|
||||
|
||||
/*
|
||||
* pci_find_class — the index-th function (0 = the first) on any bus,
|
||||
* slot and function whose class code, subclass and programming interface
|
||||
* match: e.g. 0x0C 0x03 0x30 is an xHCI controller of any vendor.
|
||||
* Returns 0 and fills *out, or -1 if there are not that many.
|
||||
*/
|
||||
int pci_find_class(uint8_t class_code, uint8_t subclass, uint8_t prog_if,
|
||||
unsigned index, PciDevice *out);
|
||||
|
||||
/* Config space accessors */
|
||||
uint32_t pci_read32 (const PciDevice *d, uint16_t offset);
|
||||
uint16_t pci_read16 (const PciDevice *d, uint16_t offset);
|
||||
@@ -93,6 +102,12 @@ void pci_enable(const PciDevice *d);
|
||||
*/
|
||||
uint64_t pci_bar(const PciDevice *d, int bar_idx);
|
||||
|
||||
/*
|
||||
* pci_bar_size — the size of memory BAR bar_idx, by the standard sizing
|
||||
* probe (decoding off meanwhile). 0 for an I/O BAR or none.
|
||||
*/
|
||||
uint64_t pci_bar_size(const PciDevice *d, int bar_idx);
|
||||
|
||||
/*
|
||||
* pci_map_bar — make the BAR MMIO region accessible.
|
||||
* amd64: calls vmm_map_range(phys, phys, size, WR|CD).
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
* byte-wise access. The xHCI spec's register layout is naturally aligned at
|
||||
* every offset, so this translates directly without padding tricks.
|
||||
*
|
||||
* QEMU's `qemu-xhci` device identifies as PCI vendor 0x1B36 (Red Hat, Inc.),
|
||||
* device 0x000D — confirmed live via QMP `query-pci` against a real running
|
||||
* instance (not assumed from memory), 2026-08-22.
|
||||
* The controller is found by its PCI class (0x0C/0x03/0x30, xhci.c), on any
|
||||
* bus, from any vendor. Until 2026-10-14 it was matched by QEMU's own
|
||||
* qemu-xhci IDs, 1B36:000D, on bus 0 alone (boards/BRINGUP.md A1).
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_XHCI_H
|
||||
@@ -27,12 +27,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* PCI identification
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define XHCI_PCI_VENDOR_ID 0x1B36u /* Red Hat, Inc. (QEMU qemu-xhci) */
|
||||
#define XHCI_PCI_DEVICE_ID 0x000Du
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Capability Registers (BAR0 + 0)
|
||||
@@ -413,13 +407,12 @@ typedef struct {
|
||||
#define XHCI_PSC_EVT_PORT_ID(parameter) (((uint32_t)(parameter) >> 24) & 0xFFu)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Slot Context, Endpoint Context, Input Control Context — 32-byte layout
|
||||
* only (xHCI 1.2 spec tables 6-6, 6-9, 6-5). HCCPARAMS1.CSZ selects 32- vs
|
||||
* 64-byte contexts; confirmed live (CSZ=0) against this driver's target
|
||||
* QEMU qemu-xhci controller (2026-08-22) before writing these -- 64-byte
|
||||
* contexts (CSZ=1) are NOT implemented here. xhci_cmd_address_device()
|
||||
* checks CSZ itself and refuses rather than silently mis-laying-out a
|
||||
* 64-byte-context controller as 32-byte.
|
||||
* Slot Context, Endpoint Context, Input Control Context — their fields
|
||||
* (xHCI 1.2 spec tables 6-6, 6-9, 6-5). HCCPARAMS1.CSZ selects 32- vs
|
||||
* 64-byte contexts; a 64-byte one holds these same fields in its first 32
|
||||
* bytes, so these structs serve both, and xhci.c places each context 32 or
|
||||
* 64 bytes apart (xhci_ctx_bytes(); since 2026-10-14 -- a CSZ=1 controller
|
||||
* was refused before).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -538,9 +538,8 @@ int xhci_cmd_disable_slot(xhci_dev_t *dev, uint32_t slot_id);
|
||||
* is servicing (xHCI 1.2 spec table 7-13 speed IDs) -- used to pick EP0's
|
||||
* default Max Packet Size before any device descriptor has been read.
|
||||
*
|
||||
* Refuses (-2) if HCCPARAMS1.CSZ indicates 64-byte contexts -- only
|
||||
* 32-byte contexts are implemented (see xhci.h's own doc comment on
|
||||
* xhci_slot_ctx32_t).
|
||||
* Lays the contexts out 32 or 64 bytes apart, as HCCPARAMS1.CSZ says
|
||||
* (xhci.c's xhci_ctx_bytes(); 64-byte contexts refused until 2026-10-14).
|
||||
*
|
||||
* Called from xhci_poll_events()'s Command Completion handling once Enable
|
||||
* Slot succeeds -- not called directly by other code.
|
||||
|
||||
+87
-1
@@ -12,7 +12,9 @@
|
||||
* ARCH_AMD64 : vmm_map_range(phys, phys, size, WR|CD) before first access.
|
||||
* others : no-op; the UEFI identity map covers PCI MMIO windows.
|
||||
*
|
||||
* Only bus 0 is scanned (sufficient for all QEMU virtio devices).
|
||||
* pci_find_first() scans bus 0 only (sufficient for all QEMU virtio
|
||||
* devices); pci_find_class() scans every bus (2026-10-14, for real USB
|
||||
* controllers, which sit behind bridges).
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
@@ -75,6 +77,11 @@ typedef struct {
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static uint64_t g_ecam_base = 0;
|
||||
/* Buses the configuration space reaches: through ECAM, those the MCFG
|
||||
* entry names (its window covers no others -- reading past it is reading
|
||||
* whatever lies beyond); through port I/O (amd64), all 256. */
|
||||
static uint8_t g_bus_first = 0;
|
||||
static uint8_t g_bus_last = 255;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Port I/O helpers (amd64 only)
|
||||
@@ -276,6 +283,8 @@ static int parse_mcfg(void *rsdp) {
|
||||
for (j = 0; j < n_alloc; j++) {
|
||||
if (ents[j].segment_group == 0) {
|
||||
g_ecam_base = ents[j].base_address;
|
||||
g_bus_first = ents[j].start_bus;
|
||||
g_bus_last = ents[j].end_bus;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -341,6 +350,53 @@ int pci_find_first(uint16_t vendor_id, uint16_t device_id, PciDevice *out) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Every function on every bus the configuration space reaches, in order,
|
||||
* for the index-th whose class, subclass and programming interface match
|
||||
* (2026-10-14, boards/BRINGUP.md A1). Every bus is read, not only those a
|
||||
* bridge is found to lead to: simple, and the same answer, since an
|
||||
* unassigned bus reads as nothing there (vendor 0xFFFF). Function 0 says
|
||||
* whether functions 1-7 exist (header type bit 7). */
|
||||
int pci_find_class(uint8_t class_code, uint8_t subclass, uint8_t prog_if,
|
||||
unsigned index, PciDevice *out) {
|
||||
unsigned bus;
|
||||
unsigned seen = 0;
|
||||
|
||||
#ifdef ARCH_AMD64
|
||||
unsigned first = 0, last = 255; /* port I/O: every bus */
|
||||
#else
|
||||
unsigned first = g_bus_first, last = g_bus_last;
|
||||
if (!g_ecam_base) return -1;
|
||||
#endif
|
||||
for (bus = first; bus <= last; bus++) {
|
||||
uint8_t slot;
|
||||
for (slot = 0; slot < 32; slot++) {
|
||||
uint8_t fn, fns;
|
||||
uint32_t id0 = cfg_read32((uint8_t)bus, slot, 0, PCI_CFG_VENDOR_ID);
|
||||
if ((id0 & 0xFFFFu) == 0xFFFFu) continue;
|
||||
fns = (cfg_read8((uint8_t)bus, slot, 0, PCI_CFG_HEADER_TYPE) & 0x80u) ? 8 : 1;
|
||||
for (fn = 0; fn < fns; fn++) {
|
||||
uint32_t id = fn ? cfg_read32((uint8_t)bus, slot, fn, PCI_CFG_VENDOR_ID) : id0;
|
||||
uint32_t cr;
|
||||
if ((id & 0xFFFFu) == 0xFFFFu) continue;
|
||||
cr = cfg_read32((uint8_t)bus, slot, fn, PCI_CFG_CLASS_REV);
|
||||
if ((uint8_t)(cr >> 24) != class_code || (uint8_t)(cr >> 16) != subclass ||
|
||||
(uint8_t)(cr >> 8) != prog_if)
|
||||
continue;
|
||||
if (seen++ != index) continue;
|
||||
if (out) {
|
||||
out->bus = (uint8_t)bus;
|
||||
out->device = slot;
|
||||
out->function = fn;
|
||||
out->vendor_id = (uint16_t)(id & 0xFFFFu);
|
||||
out->device_id = (uint16_t)(id >> 16);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t pci_read32(const PciDevice *d, uint16_t off) {
|
||||
return cfg_read32(d->bus, d->device, d->function, off);
|
||||
}
|
||||
@@ -379,6 +435,36 @@ uint64_t pci_bar(const PciDevice *d, int bar_idx) {
|
||||
return lo & ~0xFu;
|
||||
}
|
||||
|
||||
/* A memory BAR's size, by the standard probe: all ones written, what
|
||||
* stays writable read back, the original restored -- with memory decoding
|
||||
* off meanwhile, so that the BAR never answers at the probe's address.
|
||||
* 0 for an I/O BAR or one that implements nothing. (2026-10-14, A1.) */
|
||||
uint64_t pci_bar_size(const PciDevice *d, int bar_idx) {
|
||||
uint16_t off = (uint16_t)(PCI_CFG_BAR0 + bar_idx * 4);
|
||||
uint16_t cmd = pci_read16(d, PCI_CFG_COMMAND);
|
||||
uint32_t lo = pci_read32(d, off), hi = 0, slo, shi = 0xFFFFFFFFu;
|
||||
int is64 = !(lo & PCI_BAR_TYPE_MASK) && (lo & 0x6u) == PCI_BAR_MEM_64;
|
||||
uint64_t mask;
|
||||
|
||||
if (lo & PCI_BAR_TYPE_MASK) return 0;
|
||||
pci_write16(d, PCI_CFG_COMMAND, (uint16_t)(cmd & ~PCI_CMD_MEM_SPACE));
|
||||
pci_write32(d, off, 0xFFFFFFFFu);
|
||||
slo = pci_read32(d, off);
|
||||
pci_write32(d, off, lo);
|
||||
if (is64) {
|
||||
hi = pci_read32(d, (uint16_t)(off + 4));
|
||||
pci_write32(d, (uint16_t)(off + 4), 0xFFFFFFFFu);
|
||||
shi = pci_read32(d, (uint16_t)(off + 4));
|
||||
pci_write32(d, (uint16_t)(off + 4), hi);
|
||||
}
|
||||
pci_write16(d, PCI_CFG_COMMAND, cmd);
|
||||
|
||||
mask = ((uint64_t)shi << 32) | (slo & ~0xFu);
|
||||
if (!is64) mask |= 0xFFFFFFFF00000000ull;
|
||||
if ((mask & 0xFFFFFFFFFFFFFFF0ull) == 0) return 0;
|
||||
return ~mask + 1;
|
||||
}
|
||||
|
||||
int pci_map_bar(uint64_t phys_addr, uint64_t size) {
|
||||
#ifdef ARCH_AMD64
|
||||
if (!phys_addr || !size) return -1;
|
||||
|
||||
+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