Artemis Milestone 2e: Address Device implemented, worked first try, 3-arch

Adds Slot/Endpoint/Input Control Context structs (32-byte layout only --
HCCPARAMS1.CSZ checked live and confirmed 0 against this driver's QEMU
target; 64-byte contexts refuse rather than silently mis-laying-out),
xhci_cmd_address_device(), and a new dev->connect_state
(idle/await-enable-slot/await-address-device) sequencing Enable Slot and
Address Device per connect. Input Context (what the command TRB's
parameter points at) and Device Context (what DCBAA[slot_id] points at)
are separate 64-byte-aligned allocations, lazily created once and reused
across every connect -- single-device driver scope, no free path needed.
A new EP0 Transfer Ring uses the same fixed-ring-plus-Link-TRB pattern as
the Command Ring.

Two facts checked live before writing any context code, not assumed:
HCCPARAMS1.CSZ (32-byte, confirmed) and PORTSC.PED at connect time
(already set -- PORTSC=0x00021203, SuperSpeed -- the test device
self-enables via USB3 link training, so no port-reset state machine was
needed this increment; USB2 would need one, untested). Both diagnostics
also added console_puts/println-based hex logging (xhci_log_hex32()) --
console_println() only takes string literals, no formatted print existed
on this driver's console path before now.

Verified live via QMP hotplug, all three architectures, succeeded on the
first attempt with no debugging needed: "enable slot succeeded" ->
"address device command submitted" -> "address device succeeded" on
every boot.

Also fixes a FABRIC-2.md dependency-direction error from the previous
commit (Address Device is 2f's prerequisite, not the reverse).

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 11:44:15 -04:00
co-authored by Claude Sonnet 5
parent 6d330efdd8
commit 2b7743027c
12 changed files with 45556 additions and 22 deletions
+186 -14
View File
@@ -94,6 +94,27 @@ static int xhci_wait_bit(volatile uint32_t *reg, uint32_t mask, int want_set,
}
}
/* console_println() only takes a string literal -- no formatted print
* exists on this driver's console path. Matches the established pattern
* elsewhere in this kernel (e.g. src/starkernel/vm/parity.c's
* print_hex64()) rather than adding one: a small static hex-dump helper,
* "label: 0xXXXXXXXX". Debugging register values without this is
* guesswork. */
static void xhci_log_hex32(const char *label, uint32_t val)
{
char buf[11];
buf[0] = '0';
buf[1] = 'x';
for (int i = 9; i >= 2; i--) {
int d = val & 0xF;
buf[i] = (d < 10) ? ('0' + d) : ('a' + d - 10);
val >>= 4;
}
buf[10] = '\0';
console_puts(label);
console_println(buf);
}
/* Only one controller is supported (matches xhci_dev_t's own doc comment);
* latched at the end of a successful xhci_bringup() for
* xhci_poll_events()'s use. */
@@ -104,6 +125,8 @@ static xhci_dev_t *g_xhci_dev = NULL;
* (see the "Command Ring submission" section) so it can stay close to
* xhci_poll_events(), the read side of the same ring pair. */
int xhci_cmd_enable_slot(xhci_dev_t *dev);
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
uint32_t port_id, uint32_t speed);
int xhci_bringup(xhci_dev_t *dev)
{
@@ -255,8 +278,23 @@ int xhci_bringup(xhci_dev_t *dev)
* 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;
dev->pending_connect_speed = 0;
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_connect_slot_id = 0;
dev->input_ctx = NULL;
dev->device_ctx = NULL;
dev->ep0_ring = NULL;
dev->ep0_ring_cycle = 1;
dev->ep0_ring_enq = 0;
console_println("xhci: controller running");
/* Milestone 2e prep: HCCPARAMS1.CSZ decides 32- vs 64-byte Slot/
* Endpoint/Input Context layout for Address Device -- must be read
* live, not assumed, before any context structure is designed. */
xhci_log_hex32("xhci: hcc_params1=", dev->cap->hcc_params1);
console_println(XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)
? "xhci: context size = 64 bytes"
: "xhci: context size = 32 bytes");
g_xhci_dev = dev;
return 0;
@@ -268,13 +306,18 @@ int xhci_bringup(xhci_dev_t *dev)
* same ring pair, already verified live for Port Status Change events.
* ------------------------------------------------------------------------- */
/* extra_control_bits ORs additional fields into the TRB's control dword
* beyond type/cycle -- e.g. Address Device's Slot ID at bits[31:24]
* (Enable Slot needs none, passes 0). Never includes the cycle bit itself;
* that's computed here from cmd_ring_cycle so callers can't get it wrong. */
static void xhci_submit_command(xhci_dev_t *dev, uint64_t parameter,
uint32_t status, uint32_t trb_type)
uint32_t status, uint32_t trb_type,
uint32_t extra_control_bits)
{
xhci_trb_t *trb = &dev->cmd_ring[dev->cmd_ring_enq];
trb->parameter = parameter;
trb->status = status;
trb->control = (trb_type << XHCI_TRB_CONTROL_TYPE_SHIFT) |
trb->control = (trb_type << XHCI_TRB_CONTROL_TYPE_SHIFT) | extra_control_bits |
(dev->cmd_ring_cycle ? XHCI_TRB_CONTROL_CYCLE : 0);
dev->cmd_ring_enq++;
@@ -299,11 +342,110 @@ static void xhci_submit_command(xhci_dev_t *dev, uint64_t parameter,
int xhci_cmd_enable_slot(xhci_dev_t *dev)
{
if (!dev || !dev->cmd_ring) return -1;
xhci_submit_command(dev, 0, 0, XHCI_TRB_TYPE_ENABLE_SLOT_CMD);
xhci_submit_command(dev, 0, 0, XHCI_TRB_TYPE_ENABLE_SLOT_CMD, 0);
console_println("xhci: enable slot command submitted");
return 0;
}
/* Default EP0 Max Packet Size by PORTSC.Port Speed, used before any device
* descriptor has been read (xHCI 1.2 spec's own recommended defaults --
* the real value comes from bMaxPacketSize0 once 2f reads the device
* descriptor and issues an Evaluate Context to correct it if needed). */
static uint32_t xhci_default_ep0_max_packet(uint32_t speed)
{
switch (speed) {
case 4: return 512; /* SuperSpeed */
case 3: return 64; /* High Speed */
case 2: return 8; /* Low Speed */
default: return 64; /* Full Speed (1) and anything unrecognised */
}
}
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
uint32_t port_id, uint32_t speed)
{
if (!dev || !dev->cmd_ring || !dev->dcbaa) return -1;
if (XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)) {
console_println("xhci: 64-byte contexts required, not implemented -- refusing");
return -2;
}
/* Lazily allocate once; reused across every connect (single-device
* scope -- see xhci_dev_t's doc comment). All three re-initialised
* fully below regardless of whether this is the first call. */
if (!dev->input_ctx) {
dev->input_ctx = kmalloc_aligned(
sizeof(xhci_input_ctrl_ctx32_t) + sizeof(xhci_slot_ctx32_t) +
sizeof(xhci_ep_ctx32_t), 64);
if (!dev->input_ctx) return -1;
}
if (!dev->device_ctx) {
dev->device_ctx = kmalloc_aligned(
sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t), 64);
if (!dev->device_ctx) return -1;
}
if (!dev->ep0_ring) {
dev->ep0_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
if (!dev->ep0_ring) return -1;
}
/* EP0 Transfer Ring: same fixed-ring-plus-Link-TRB pattern as the
* Command Ring (xhci_bringup()'s own comment on why). Freshly
* reinitialised on every call, not just the first -- cheap (4KiB) and
* avoids carrying stale TRBs from a previous connect. */
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
dev->ep0_ring[i].parameter = 0;
dev->ep0_ring[i].status = 0;
dev->ep0_ring[i].control = 0;
}
dev->ep0_ring[XHCI_RING_TRB_COUNT - 1].parameter = (uint64_t)(uintptr_t)dev->ep0_ring;
dev->ep0_ring[XHCI_RING_TRB_COUNT - 1].control =
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_TC | XHCI_TRB_CONTROL_CYCLE;
dev->ep0_ring_cycle = 1;
dev->ep0_ring_enq = 0;
/* Device Context: Slot Context followed by EP0 Context, no Input
* Control Context (that only exists in the Input Context below).
* DCBAA[slot_id] must point here, per spec -- zeroed since the
* controller writes this on Address Device success, software must not
* pre-fill it. */
uint8_t *dctx = (uint8_t *)dev->device_ctx;
for (size_t i = 0; i < sizeof(xhci_slot_ctx32_t) + sizeof(xhci_ep_ctx32_t); i++) dctx[i] = 0;
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)dev->device_ctx;
/* Input Context: Input Control Context, then Slot Context, then EP0
* Context -- this is what the command TRB's parameter points at (never
* the Device Context; conflating the two is the standard mistake
* here). */
uint8_t *ictx = (uint8_t *)dev->input_ctx;
size_t total = sizeof(xhci_input_ctrl_ctx32_t) + sizeof(xhci_slot_ctx32_t) +
sizeof(xhci_ep_ctx32_t);
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));
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));
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);
ep0->tr_dequeue_ptr = ((uint64_t)(uintptr_t)dev->ep0_ring) | 1u; /* DCS = 1 */
ep0->dword4 = 8u; /* Average TRB Length -- spec's own recommended default for EP0 */
xhci_submit_command(dev, (uint64_t)(uintptr_t)dev->input_ctx, 0,
XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD, slot_id << 24);
console_println("xhci: address device command submitted");
return 0;
}
/* -------------------------------------------------------------------------
* Milestone 2d: Event Ring servicing, polled from sk_repl_idle().
*
@@ -352,6 +494,17 @@ void xhci_poll_events(void)
uint32_t portsc = port->portsc;
if (portsc & XHCI_PORTSC_CCS) {
console_println("xhci: port status change -- device connected");
/* Milestone 2e prep: Address Device requires the port
* in Default state. USB3 links train and enable
* themselves; USB2 needs software to drive PORTSC.PR
* and wait for PRC/PED before the device will respond
* to addressing -- not yet known which this driver's
* ports need, so log raw PORTSC and PED rather than
* assume. */
xhci_log_hex32("xhci: portsc=", portsc);
console_println((portsc & XHCI_PORTSC_PED)
? "xhci: port enabled (PED set)"
: "xhci: port not yet enabled (PED clear)");
/* 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
@@ -361,8 +514,10 @@ void xhci_poll_events(void)
* 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) {
} else if (dev->connect_state == XHCI_CONN_IDLE) {
dev->pending_connect_port_id = port_id;
dev->pending_connect_speed = XHCI_PORTSC_SPEED(portsc);
dev->connect_state = XHCI_CONN_AWAIT_ENABLE_SLOT;
xhci_cmd_enable_slot(dev);
} else {
console_println("xhci: enable slot already pending -- dropped");
@@ -390,24 +545,41 @@ void xhci_poll_events(void)
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) {
/* Correlates to connect_state, not to the Command TRB
* Pointer in trb->parameter -- Enable Slot and Address
* Device are issued sequentially for a given connect (see
* xhci_dev_t's doc comment), never concurrently, so
* connect_state alone identifies which command this
* completion answers. A real Command TRB Pointer match
* becomes necessary once commands for different connects
* can overlap in flight. */
if (dev->connect_state == XHCI_CONN_AWAIT_ENABLE_SLOT) {
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;
dev->pending_connect_slot_id = slot_id;
console_println("xhci: enable slot succeeded");
dev->connect_state = XHCI_CONN_AWAIT_ADDRESS_DEVICE;
if (xhci_cmd_address_device(dev, slot_id, port_id,
dev->pending_connect_speed) != 0) {
console_println("xhci: address device setup failed");
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_connect_port_id = 0;
}
} else {
console_println("xhci: enable slot failed");
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_connect_port_id = 0;
}
} else if (dev->connect_state == XHCI_CONN_AWAIT_ADDRESS_DEVICE) {
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
console_println("xhci: address device succeeded");
} else {
console_println("xhci: address device failed");
}
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_connect_port_id = 0;
} else {
console_println("xhci: command completion event");
}