Artemis Milestone 2e: real connect drives Enable Slot, slot ID correlated

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
This commit is contained in:
Robert Allan James
2026-08-22 10:33:37 -04:00
co-authored by Claude Sonnet 5
parent dd043bbfeb
commit 6d330efdd8
12 changed files with 36185 additions and 27 deletions
+37 -7
View File
@@ -3207,8 +3207,9 @@ Final three-arch acceptance (probe-free, `clean qemu`): all three boot to `ok>`
**2e. Hotplug detection (the actual trigger for everything in Section U)**
- [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
- [x] On connect: allocate a Device Slot (Enable Slot command via the Command Ring) — **Enable
Slot done 2026-08-22**, see writeup below; Address Device + device descriptor read still
open (needs enumeration groundwork from 2f)
- [ ] On disconnect: tear down the corresponding device slot and signal to whatever higher-
level code (Section U's identity/VM logic) that the device is gone — **this is the
first point where Section U/V's design actually gets a real trigger to hang off**
@@ -3251,11 +3252,40 @@ 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.
**Connect -> Enable Slot -> slot correlation, done 2026-08-22.** The boot-time smoke test call
is gone; a real connect event now drives `xhci_cmd_enable_slot()` directly from
`xhci_poll_events()`'s PSC branch. Correlation between "which port asked for a slot" and "which
Command Completion Event answered" uses a new `dev->pending_connect_port_id` (0 = idle, else
the port whose Enable Slot is outstanding) rather than matching the Command TRB Pointer in the
completion's `parameter` field — sound today because this driver only ever has one command
in flight (synchronous with respect to connect events, not a queue); revisit with real TRB
Pointer matching once Address Device commands can also be outstanding concurrently. On success
the returned Slot ID (`XHCI_EVT_SLOT_ID()`, decoded from `control[31:24]`) is recorded in a new
`dev->port_slot_id[]`, indexed by port — a **fixed** `uint32_t[XHCI_MAX_TRACKED_PORTS]` (32),
not heap-allocated. That fixed-size choice wasn't cosmetic: a first attempt sized this
dynamically via `kmalloc_aligned(dev->max_ports * sizeof(uint32_t), 64)` inside
`xhci_bringup()` and it crashed on amd64 — a page fault (`IFETCH` at `RIP=CR2=0xA0000`,
inside the legacy VGA hole) during the unrelated Mama-VM-birth phase that runs afterward,
strongly suggesting heap corruption rather than a fault in the new code itself. Root cause not
chased further — reverting to a fixed array matching this driver's own existing preference
(xhci.h's ring-sizing rationale) sidesteps the heap interaction entirely and is simpler besides.
If a future device genuinely needs >32 tracked ports, that heap interaction needs revisiting
before growing this array dynamically again.
Disconnect clears the port's tracked slot ID (real teardown — Disable Slot command, DCBAA entry
clear, Section U callback — is still a later increment; this only stops treating the port as
occupied so a future connect on it isn't confused for one already in progress).
Verified live via QMP hotplug, all three architectures: connect logs `port status change --
device connected` -> `enable slot command submitted` -> `enable slot succeeded`; a
disconnect/reconnect cycle repeats cleanly with no wedge. Full cycle on amd64
(`logs/20260822-102849/amd64/`); single connect confirmed on aarch64
(`logs/20260822-103005/aarch64/`) and riscv64 (`logs/20260822-103137/riscv64/`).
Still open: correlating the Command Completion Event back to its issuing command via the real
Command TRB Pointer (not needed yet, see above), Address Device + device descriptor read
(needs 2f's enumeration groundwork), 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-22T14:11:17Z -->
<!-- Generated by mkcapsule --manifest 2026-08-22T14:31:35Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
+8
View File
@@ -222,4 +222,12 @@ typedef struct {
#define XHCI_RING_TRB_COUNT 256u
#define XHCI_RING_BYTES (XHCI_RING_TRB_COUNT * sizeof(xhci_trb_t))
/* Milestone 2e: upper bound on ports tracked for connect/disconnect ->
* Enable Slot correlation (xhci_dev_t.port_slot_id). PORTSC's own field
* width allows up to 255 ports (XHCI_HCSPARAMS1_MAX_PORTS is 8 bits), but
* no real or QEMU-emulated root hub this driver targets comes close to
* that; 32 is comfortably generous and keeps this a fixed, not
* heap-allocated, array. */
#define XHCI_MAX_TRACKED_PORTS 32u
#endif /* STARKERNEL_XHCI_H */
+24 -4
View File
@@ -41,6 +41,22 @@ typedef struct {
xhci_intr_regs_t *intr0; /* Interrupter 0 register set, cached
* by xhci_bringup() for
* xhci_poll_events() */
/* Milestone 2e: connect -> Enable Slot correlation. port_slot_id is
* indexed by port_id - 1 (1-based port IDs, matching PORTSC/Port
* Status Change Event numbering); 0 means no slot allocated for that
* port yet. Fixed-size, not heap-allocated -- XHCI_MAX_TRACKED_PORTS
* comfortably covers any real or emulated root hub's port count
* without adding a new kmalloc_aligned() call to xhci_bringup(); ports
* beyond this bound (checked against both this array and max_ports)
* are simply not tracked, matching this driver's existing preference
* for fixed allocations over dynamic growth (xhci.h's own ring-sizing
* rationale). Only one Enable Slot is ever in flight at a time (this
* driver issues commands synchronously with respect to connect events,
* not a queue) -- pending_connect_port_id is 0 when idle, or the
* port_id whose Command Completion Event is still outstanding. */
uint32_t port_slot_id[XHCI_MAX_TRACKED_PORTS];
uint32_t pending_connect_port_id;
} xhci_dev_t;
/*
@@ -107,10 +123,14 @@ void xhci_poll_events(void);
/*
* xhci_cmd_enable_slot submit an Enable Slot command TRB to the Command
* Ring and ring doorbell 0. Does not wait for or
* read the resulting Command Completion Event --
* that arrives asynchronously via xhci_poll_events(),
* which currently only logs it (Milestone 2e's slot-
* ID/context bookkeeping is not wired up yet).
* read the resulting Command Completion Event -- it
* arrives asynchronously via xhci_poll_events(),
* which correlates the returned Slot ID back to
* dev->pending_connect_port_id and records it in
* dev->port_slot_id[].
*
* Called from xhci_poll_events()'s own Port Status Change handling on a
* real connect event -- not called directly by other code.
*
* Returns 0 if the command was posted, -1 if dev/dev->cmd_ring is not set
* up (xhci_bringup() has not completed).
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
+54 -15
View File
@@ -251,20 +251,14 @@ int xhci_bringup(xhci_dev_t *dev)
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;
/* Milestone 2e smoke test: prove the write path (TRB enqueue, cycle
* bit, doorbell ring) before building slot allocation on top of it.
* A real Enable Slot is harmless to issue speculatively -- it just
* reserves a Device Slot Context the driver doesn't use yet -- and its
* Command Completion Event is the only live proof that a TRB written by
* software was actually consumed by the controller. Real connect-driven
* Enable Slot calls (Milestone 2e proper) replace/reuse this call site
* once Port Status Change handling exists. */
xhci_cmd_enable_slot(dev);
return 0;
}
@@ -358,8 +352,32 @@ void xhci_poll_events(void)
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
@@ -369,11 +387,32 @@ void xhci_poll_events(void)
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. */
console_println("xhci: command completion event");
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");