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
+1
-1
@@ -23,7 +23,7 @@ None has been fixed: each needs your word.
|
||||
|
||||
| # | What | Where | Effect on hardware |
|
||||
|---|---|---|---|
|
||||
| A1 | **The USB controller driver matches one device only**: QEMU's `qemu-xhci`, vendor `1B36`, device `000D`. | `kernel/include/starkernel/xhci.h:34`, `kernel/src/usb/xhci.c:44` | No real board's USB controller is found. No thumbdrive, so no identity can log in or be minted. |
|
||||
| A1 | **Fixed in part 2026-10-14.** The USB controller was matched by QEMU's own IDs (`1B36:000D`) on PCI bus 0 alone. Now: found by its PCI class (`0C/03/30`) on any bus, slot and function; its register window mapped at the size its BAR reports; taken from the firmware through the USB Legacy Support capability (SMIs off); and 64-byte contexts (`CSZ=1`, common on real controllers) laid out instead of refused. **Still open:** only the first controller found is used -- a PC with several (the SER5 has more than one) drives only that one's ports, so a stick or keyboard has to be in one of them. | `kernel/src/usb/xhci.c`, `kernel/src/pci/pci.c`; `FABRIC-3.md` XL | Rehearsed in QEMU with another vendor's controller behind a bridge, all three ISAs. Not rehearsable: the firmware handoff and 64-byte contexts (QEMU's controller has neither). |
|
||||
| A2 | **No prompt without a login.** The production default, `EMERGENCY_CONSOLE_ENABLED=n`, keeps the system headless until an identity logs in by thumbdrive. | `Kconfig.heartbeat:68`; `FABRIC-3.md` VIII.1 | With A1, the v3 system never shows a prompt on hardware. A bring-up build must set `EMERGENCY_CONSOLE_ENABLED=1`. |
|
||||
| A3 | **There is no USB keyboard driver.** Keys come from the i8042 PS/2 controller (amd64), a virtio keyboard (riscv64, QEMU only), or the serial port. | `kernel/src/arch/amd64/i8042.c`, `kernel/src/kernel_main.c:773`, `kernel/src/hal/console.c` | A USB keyboard does nothing unless the board's firmware goes on emulating PS/2 after the kernel takes over, which is not known for any of them. Plan to type over serial. |
|
||||
| A4 | **The only disk driver is virtio.** | `kernel/src/virtio/virtio_blk.c`; `kernel_main.c:493` | On hardware Artemis finds no disk ("Artemis: no virtio-blk disk"). What the fleet then does has not been tried even in QEMU: see step 3.3. |
|
||||
|
||||
@@ -5736,3 +5736,37 @@ needs ACPI; the Pi 5's is R4). The board image (`make boot_image TARGET=RASPI`)
|
||||
and ends at 0x834000. Standard acceptance, all three ISAs: POST 1052 passed, same hash
|
||||
(`logs/20261014-125305`, `-125427`, `-125635`).
|
||||
|
||||
## XL. The USB controller found as real hardware has it (`boards/BRINGUP.md` A1, 2026-10-14)
|
||||
|
||||
Captain Bob, 2026-10-14: "start on A1". The xHCI driver matched QEMU's own controller only --
|
||||
vendor and device `1B36:000D`, on PCI bus 0, function 0 -- so no real board's controller was ever
|
||||
found, and with it no thumbdrive, no login, no keyboard to come.
|
||||
|
||||
**What a real controller needs, done:**
|
||||
- **Found by class**, `0x0C/0x03/0x30`, from any vendor: new `pci_find_class()` (`pci.c`) reads
|
||||
every bus, slot and function the configuration space reaches (port I/O on amd64, all 256 buses;
|
||||
through ECAM, the MCFG entry's bus range, now recorded). Real controllers sit behind bridges.
|
||||
- **Mapped at its real size**: new `pci_bar_size()` (the standard all-ones probe, decoding off
|
||||
meanwhile); never less than the old fixed 64 KiB.
|
||||
- **Taken from the firmware** (xHCI 1.2 section 4.22.1): through the USB Legacy Support extended
|
||||
capability, OS-owned set and BIOS-owned waited out for a second (taken regardless after, as
|
||||
Linux does), every SMI enable off and SMI status cleared. QEMU's controller has no such
|
||||
capability; a PC's BIOS, driving USB for its own setup screen, does.
|
||||
- **64-byte contexts** (`HCCPARAMS1.CSZ=1`) laid out, not refused: every context is 32 or 64 bytes
|
||||
apart (`xhci_ctx_bytes()`); a 64-byte context holds the same fields in its first 32 bytes, so the
|
||||
existing structs stand.
|
||||
- `XHCI_QEMU_ARGS` (`kernel/Makefile`) replaces the QEMU controller, to rehearse one placed as real
|
||||
hardware places it.
|
||||
|
||||
**Verified.** Test first: QEMU's NEC controller (`nec-usb-xhci`, vendor 1033) behind a PCIe root
|
||||
port on bus 1, Zuse's drive on it -- before, nothing attached and no `zuse@Hera` prompt
|
||||
(`logs/20261014-131238`); after, the drive attaches and Zuse is minted, on amd64 (`-131934`),
|
||||
aarch64 (`-132056`) and riscv64 (`-132257`). Standard acceptance with the usual controller, all
|
||||
three: POST 1052 passed, hash `0x82f4ae7cb2e2d424` unchanged (`-132431`, `-132548`, `-132746`);
|
||||
five identities hot-plugged, all attached (`-132912`).
|
||||
|
||||
**Not rehearsable, so unproven until the SER5:** the firmware handoff and 64-byte contexts --
|
||||
QEMU's controller has neither. **Still open:** one controller only -- the first found. A PC with
|
||||
several (the SER5 has more than one) drives that one's ports alone; the stick and keyboard must be
|
||||
in them until the driver runs several (`xhci_get_dev()` is a single controller throughout).
|
||||
|
||||
|
||||
+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);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user