Artemis Milestone 2e: PORTSC connect/disconnect detection, verified live

xhci_poll_events()'s Port Status Change branch now decodes the Port ID
from the event TRB (XHCI_PSC_EVT_PORT_ID, new in xhci.h), reads that
port's PORTSC.CCS via a new xhci_port_regs() helper, and logs connect vs.
disconnect. Acknowledges by writing back only PP (preserved) and CSC (the
bit being cleared) -- PED/PR/other _C bits written 0 so nothing is
accidentally disabled, reset, or silently cleared, matching the RW1C
discipline already used for ERDP.EHB in 2d.

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

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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
This commit is contained in:
Robert Allan James
2026-08-22 10:11:47 -04:00
co-authored by Claude Sonnet 5
parent bb84eba7e3
commit dd043bbfeb
11 changed files with 27197 additions and 22 deletions
+25 -8
View File
@@ -3205,8 +3205,8 @@ Final three-arch acceptance (probe-free, `clean qemu`): all three boot to `ok>`
`logs/20260822-091923/amd64/`, `logs/20260822-092035/aarch64/`, `logs/20260822-092229/riscv64/`.
**2e. Hotplug detection (the actual trigger for everything in Section U)**
- [ ] On a Port Status Change event, read the corresponding Port Register to determine
connect vs. disconnect
- [x] On a Port Status Change event, read the corresponding Port Register to determine
connect vs. disconnect**done 2026-08-22**, see writeup below
- [ ] On connect: allocate a Device Slot (Enable Slot command via the Command Ring), address
the device (Address Device command), read its device descriptor
- [ ] On disconnect: tear down the corresponding device slot and signal to whatever higher-
@@ -3233,12 +3233,29 @@ Closed that gap first as its own discriminating step:
starts running from `ok>`'s idle loop — proof the controller actually consumed a
software-written TRB via doorbell 0, not just that software believes it did.
`logs/20260822-093311/amd64/`, `logs/20260822-093412/aarch64/`, `logs/20260822-093549/riscv64/`.
- Still open, unchanged from the checklist above: reading PORTSC to distinguish connect from
disconnect, correlating the Command Completion Event back to its issuing command (currently
only logged, slot ID/context bookkeeping not read from the event TRB), Address Device, and
the callback surface into Section U's higher-level code. The Enable Slot call above is a
speculative smoke test, not yet triggered by a real connect event — that wiring is the next
increment.
**PORTSC connect/disconnect read, done 2026-08-22.** Port Status Change Event TRBs carry the
1-based Port ID in `parameter[31:24]` (`XHCI_PSC_EVT_PORT_ID()`, new in `xhci.h`); a new
`xhci_port_regs()` helper computes the Port Register Set address (Operational base + 0x400 +
0x10*port_index) and `xhci_poll_events()`'s PSC branch now reads `PORTSC.CCS` to log
`device connected` or `device disconnected`, then acknowledges by writing back only `PP`
(preserved) and `CSC` (the one RW1CS bit being cleared) — `PED`/`PR`/every other `_C` bit
written as 0 so nothing is accidentally disabled, reset, or silently cleared, matching the
same RW1C discipline already applied to `ERDP.EHB` in 2d.
Verified live via QMP hotplug, all three architectures, using the real scenario: boot with the
xHCI controller present but **no USB device attached** (confirmed zero port activity at
`ok>`), then `device_add usb-storage,bus=xhci0.0` → immediate `device connected`, then
`device_del` → immediate `device disconnected`, then a second `device_add` → immediate
`device connected` again with no wedge (proves the CSC acknowledgment doesn't stick the port).
Full connect→disconnect→connect cycle confirmed on amd64
(`logs/20260822-100654/amd64/`); single connect confirmed on aarch64
(`logs/20260822-100814/aarch64/`) and riscv64 (`logs/20260822-100940/riscv64/`).
Still open: correlating the Command Completion Event back to its issuing command (currently
only logged, slot ID/context bookkeeping not read from the event TRB), actually calling Enable
Slot / Address Device from the connect path above (today's Enable Slot call remains the
boot-time smoke test from the previous increment, not yet triggered by a real connect event),
and the callback surface into Section U's higher-level code.
**2f. USB device enumeration (post-connect, before it's usable as storage)**
- [ ] Request and parse the device descriptor (confirm vendor/product IDs are even needed,
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-22T13:40:29Z -->
<!-- Generated by mkcapsule --manifest 2026-08-22T14:11:17Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
+5
View File
@@ -202,6 +202,11 @@ typedef struct {
#define XHCI_EVT_SLOT_ID(control) (((uint32_t)(control) >> 24) & 0xFFu)
#define XHCI_COMPLETION_CODE_SUCCESS 1u
/* Port Status Change Event TRB layout (xHCI 1.2 spec table 6-34):
* parameter[31:24] = Port ID (1-based, matches PORTSC array indexing
* 1..MaxPorts); parameter[23:0] and the rest of the TRB are reserved. */
#define XHCI_PSC_EVT_PORT_ID(parameter) (((uint32_t)(parameter) >> 24) & 0xFFu)
/* -------------------------------------------------------------------------
* Ring sizing decided up front per Milestone 2's punch list (2a).
*
+6 -4
View File
@@ -78,10 +78,12 @@ int xhci_bringup(xhci_dev_t *dev);
/*
* xhci_poll_events read Interrupter 0's Event Ring, dispatching each TRB
* by type (Port Status Change, Command Completion;
* other types logged and skipped -- Milestone 2e/2g
* consume them), then advance the Event Ring dequeue
* pointer and clear ERDP.EHB.
* by type: Port Status Change reads PORTSC to log
* connect/disconnect and acknowledges CSC; Command
* Completion and Transfer Event are logged only (slot
* allocation and BOT transfers are later increments).
* Advances the Event Ring dequeue pointer and clears
* ERDP.EHB when done.
*
* Polled, not interrupt-driven: an initial attempt at IRQ delivery
* (Milestone 2d's first draft) found the amd64 PCI INTx routing formula
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
+39 -9
View File
@@ -1,8 +1,9 @@
/*
* xhci.c xHCI USB host controller driver for StarKernel: PCI discovery
* (Milestone 2b), controller bring-up (2c), and polled Event Ring
* servicing (2d). Hotplug/enumeration/BOT read-write (2e-2g) follow in
* later increments.
* (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
@@ -321,6 +322,17 @@ int xhci_cmd_enable_slot(xhci_dev_t *dev)
* 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;
@@ -332,13 +344,31 @@ void xhci_poll_events(void)
uint32_t type = XHCI_TRB_TYPE(trb->control);
switch (type) {
case XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVT:
/* Hotplug trigger -- full port-read/slot-enable handling is
* Milestone 2e's scope, not this one's. Logged so the
* event-ring path is visibly exercised before 2e consumes
* it for real. */
console_println("xhci: port status change event");
case XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVT: {
/* Milestone 2e: identify which port changed and whether it
* now reads connected or disconnected. Slot allocation/
* addressing on connect is the next increment -- this only
* detects and acknowledges the change for now. */
uint32_t port_id = XHCI_PSC_EVT_PORT_ID(trb->parameter);
xhci_port_regs_t *port = xhci_port_regs(dev, port_id);
if (!port) {
console_println("xhci: port status change event (bad port id)");
break;
}
uint32_t portsc = port->portsc;
if (portsc & XHCI_PORTSC_CCS) {
console_println("xhci: port status change -- device connected");
} else {
console_println("xhci: port status change -- device disconnected");
}
/* Acknowledge only CSC (RW1CS): preserve PP, write 0 for
* PED/PR (writing 1 there disables the port / starts a new
* reset -- not intended here) and for every other _C bit
* (writing 0 leaves them untouched, not cleared) -- the
* same discipline this driver already applies to ERDP.EHB. */
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_CSC;
break;
}
case XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT:
/* No commands are issued yet (Milestone 2e is the first
* command-ring user) -- logged for the same reason. */