Artemis Milestone 2g: Configure Endpoint command

Adds the xHCI Configure Endpoint command for the two bulk endpoints
identified by the previous increment, and fixes control-transfer
sequencing to match the spec: xHCI 1.2 section 4.3.5 requires Configure
Endpoint before SET_CONFIGURATION is sent to the device, the reverse of
the order this driver used through 2f (which happened to work against
QEMU's lenient qemu-xhci emulation but wasn't spec-correct).

New XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD, EP Context type constants for
Bulk IN/OUT, and an XHCI_EP_ADDR_TO_DCI() macro (DCI = 2*EndpointNumber
+ Direction) in xhci.h. xhci_cmd_configure_endpoint() builds the Input
Context (Slot + one EP Context per DCI up to the highest bulk endpoint
in use) and submits the command via the existing next_action deferral
mechanism, correlated on completion via a new
XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT connect_state, then chains into the
existing SET_CONFIGURATION path.

Two allocations had to grow beyond what Address Device sized them for:
the Input Context (previously room for one EP Context only) and, less
obviously, the Device Context that DCBAA[slot_id] itself points at --
the controller only touches DCIs named in a command's own Add/Drop
flags, so growing that buffer required copying its existing Slot+EP0
content forward rather than zeroing it, to avoid handing the controller
a blank EP0 out from under an endpoint this command isn't touching.
Bulk Transfer Rings (bulk_in_ring/bulk_out_ring) are allocated and
wired into the new EP Contexts but not yet exercised by an actual
transfer -- CBW/CSW submission is next.

Verified live via QMP hotplug, all three architectures, byte-identical:
bulk endpoint identification -> configure endpoint command submitted ->
configure endpoint succeeded -> the existing set configuration ->
device configured chain, then a clean disconnect/disable-slot teardown
afterward with the larger Device Context installed.

FABRIC-2.md Section X Milestone 2g's endpoint identify+configure
checklist item marked fully done.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4
This commit is contained in:
Robert Allan James
2026-08-25 08:40:35 -04:00
co-authored by Claude Sonnet 5
parent 96d55fcd87
commit 92ce1f85dd
12 changed files with 27511 additions and 17 deletions
+191 -6
View File
@@ -129,6 +129,7 @@ int xhci_cmd_enable_slot(xhci_dev_t *dev);
int xhci_cmd_disable_slot(xhci_dev_t *dev, uint32_t slot_id);
int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
uint32_t port_id, uint32_t speed);
int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id);
int xhci_ep0_get_device_descriptor(xhci_dev_t *dev, uint32_t slot_id);
int xhci_ep0_get_config_descriptor(xhci_dev_t *dev, uint32_t slot_id, uint16_t length);
int xhci_ep0_set_configuration(xhci_dev_t *dev, uint32_t slot_id, uint8_t config_value);
@@ -299,6 +300,12 @@ int xhci_bringup(xhci_dev_t *dev)
dev->bulk_in_max_packet = 0;
dev->bulk_out_ep_addr = 0;
dev->bulk_out_max_packet = 0;
dev->bulk_in_ring = NULL;
dev->bulk_in_ring_cycle = 1;
dev->bulk_in_ring_enq = 0;
dev->bulk_out_ring = NULL;
dev->bulk_out_ring_cycle = 1;
dev->bulk_out_ring_enq = 0;
dev->next_action = XHCI_NEXT_ACTION_NONE;
dev->next_action_slot_id = 0;
dev->next_action_length = 0;
@@ -473,6 +480,144 @@ int xhci_cmd_address_device(xhci_dev_t *dev, uint32_t slot_id,
return 0;
}
/* Initialise one bulk Transfer Ring in place -- same fixed-ring-plus-
* Link-TRB pattern as ep0_ring/the Command Ring, factored out since
* Configure Endpoint needs to do this twice (IN and OUT). */
static void xhci_init_bulk_ring(xhci_trb_t *ring)
{
for (uint32_t i = 0; i < XHCI_RING_TRB_COUNT; i++) {
ring[i].parameter = 0;
ring[i].status = 0;
ring[i].control = 0;
}
ring[XHCI_RING_TRB_COUNT - 1].parameter = (uint64_t)(uintptr_t)ring;
ring[XHCI_RING_TRB_COUNT - 1].control =
(XHCI_TRB_TYPE_LINK << XHCI_TRB_CONTROL_TYPE_SHIFT) |
XHCI_TRB_CONTROL_TC | XHCI_TRB_CONTROL_CYCLE;
}
int xhci_cmd_configure_endpoint(xhci_dev_t *dev, uint32_t slot_id)
{
if (!dev || !dev->cmd_ring || !dev->input_ctx || !dev->device_ctx) return -1;
if (dev->bulk_in_ep_addr == 0 || dev->bulk_out_ep_addr == 0) return -1;
if (XHCI_HCCPARAMS1_CSZ(dev->cap->hcc_params1)) {
console_println("xhci: 64-byte contexts required, not implemented -- refusing");
return -2;
}
if (!dev->bulk_in_ring) {
dev->bulk_in_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
if (!dev->bulk_in_ring) return -1;
}
if (!dev->bulk_out_ring) {
dev->bulk_out_ring = (xhci_trb_t *)kmalloc_aligned(XHCI_RING_BYTES, 64);
if (!dev->bulk_out_ring) return -1;
}
xhci_init_bulk_ring(dev->bulk_in_ring);
dev->bulk_in_ring_cycle = 1;
dev->bulk_in_ring_enq = 0;
xhci_init_bulk_ring(dev->bulk_out_ring);
dev->bulk_out_ring_cycle = 1;
dev->bulk_out_ring_enq = 0;
uint32_t in_dci = XHCI_EP_ADDR_TO_DCI(dev->bulk_in_ep_addr);
uint32_t out_dci = XHCI_EP_ADDR_TO_DCI(dev->bulk_out_ep_addr);
uint32_t max_dci = (in_dci > out_dci) ? in_dci : out_dci;
/* DCBAA[slot_id]'s Device Context also needs to grow to hold the new
* EP Contexts -- it's currently sized for Slot+EP0 only (from Address
* Device). Unlike the Input Context below, this one's *existing*
* content must be preserved, not zeroed: xHCI 1.2 spec section 4.6.6
* only has the controller write the DCIs actually named in this
* command's Add/Drop flags (EP0's entry here is neither), so it
* expects to find EP0's live output state (its current TR Dequeue
* 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);
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;
for (size_t i = 0; i < new_device_ctx_bytes; i++) ndctx[i] = 0;
const uint8_t *odctx = (const uint8_t *)dev->device_ctx;
for (size_t i = 0; i < old_device_ctx_bytes; i++) ndctx[i] = odctx[i];
dev->device_ctx = new_device_ctx;
((uint64_t *)dev->dcbaa)[slot_id] = (uint64_t)(uintptr_t)dev->device_ctx;
/* dev->input_ctx is reused from Address Device -- same allocation
* (96 bytes: Input Control Ctx + Slot Ctx + one EP Ctx) 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
* context data structures... up to the value of the Context Entries
* field"), not just the ones actually being added -- unused slots
* 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);
void *new_input_ctx = kmalloc_aligned(total, 64);
if (!new_input_ctx) return -1;
dev->input_ctx = new_input_ctx;
uint8_t *ictx = (uint8_t *)dev->input_ctx;
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 | (1u << in_dci) | (1u << out_dci);
/* Slot Context: copied from the already-addressed device's own
* Device Context (Route String/Speed/Root Hub Port/Interrupter
* Target aren't retained anywhere else by this point in enumeration
* -- see this function's own doc comment), Context Entries updated
* to the highest DCI now in use. dword3 (Device Address, Slot State)
* 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 *)dev->device_ctx;
xhci_slot_ctx32_t *in_slot = (xhci_slot_ctx32_t *)(ictx + sizeof(xhci_input_ctrl_ctx32_t));
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);
in_slot->dword1 = dev_slot->dword1;
in_slot->dword2 = dev_slot->dword2;
/* EP Contexts for the two bulk endpoints, at their own DCI slot
* within the Input Context (index = DCI - 1, since the Slot Context
* occupies index -1 relative to DCI numbering -- DCI 1 is the first
* EP Context). Average TRB Length is a scheduling hint, not a
* correctness constraint (spec: "should approximate the length of
* the transfers that will be enqueued") -- 1024 is a placeholder;
* revisit once real CBW/data/CSW transfer sizes are known (2g's next
* items). Max Burst Size is left 0 (single-burst) since SuperSpeed
* Endpoint Companion descriptor parsing isn't implemented yet --
* 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));
in_ep->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
(XHCI_EP_CTX_TYPE_BULK_IN << XHCI_EP_CTX_TYPE_SHIFT) |
((uint32_t)dev->bulk_in_max_packet << XHCI_EP_CTX_MAX_PACKET_SHIFT);
in_ep->tr_dequeue_ptr = ((uint64_t)(uintptr_t)dev->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));
out_ep->dword1 = (3u << XHCI_EP_CTX_CERR_SHIFT) |
(XHCI_EP_CTX_TYPE_BULK_OUT << XHCI_EP_CTX_TYPE_SHIFT) |
((uint32_t)dev->bulk_out_max_packet << XHCI_EP_CTX_MAX_PACKET_SHIFT);
out_ep->tr_dequeue_ptr = ((uint64_t)(uintptr_t)dev->bulk_out_ring) | 1u; /* DCS = 1 */
out_ep->dword4 = 1024u;
xhci_submit_command(dev, (uint64_t)(uintptr_t)dev->input_ctx, 0,
XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD, slot_id << 24);
console_println("xhci: configure endpoint command submitted");
return 0;
}
/* Enqueue one TRB to the EP0 Transfer Ring without ringing the doorbell
* -- Setup/Data/Status stage TRBs are enqueued as a group, then the
* doorbell is rung once after all three are posted, matching how a real
@@ -805,6 +950,21 @@ void xhci_poll_events(void)
}
dev->connect_state = XHCI_CONN_IDLE;
dev->pending_disable_slot_id = 0;
} else if (dev->connect_state == XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT) {
if (code == XHCI_COMPLETION_CODE_SUCCESS) {
console_println("xhci: configure endpoint succeeded");
/* Chain into SET_CONFIGURATION -- next_action_
* slot_id/next_action_config_value are still the
* values staged when this Configure Endpoint was
* itself deferred (see the XFER_CONFIG_DESC_FULL
* handler above); only next_action's enum tag
* gets cleared on consumption, not the payload
* fields, so they're still valid to reuse here. */
dev->next_action = XHCI_NEXT_ACTION_SET_CONFIG;
} else {
console_println("xhci: configure endpoint failed");
}
dev->connect_state = XHCI_CONN_IDLE;
} else {
console_println("xhci: command completion event");
}
@@ -924,19 +1084,36 @@ void xhci_poll_events(void)
}
ep_off = (uint16_t)(ep_off + ep_desc_len);
}
if (dev->bulk_in_ep_addr == 0 || dev->bulk_out_ep_addr == 0) {
console_println("xhci: warning -- BOT device missing a bulk IN or OUT endpoint");
}
/* Deferred (see xhci_dev_t's
* next_action doc comment) rather
* than called directly here --
* same doorbell-ordering hazard
* as the device/config descriptor
* chaining above. */
dev->next_action = XHCI_NEXT_ACTION_SET_CONFIG;
dev->next_action_slot_id = xfer_slot_id;
* chaining above. next_action_
* config_value is staged now but
* not consumed until SET_CONFIG
* actually runs, after Configure
* Endpoint completes below. */
dev->next_action_config_value =
dev->config_descriptor[USB_CONFIG_OFF_CONFIG_VALUE];
if (dev->bulk_in_ep_addr == 0 || dev->bulk_out_ep_addr == 0) {
/* Can't Configure Endpoint
* without knowing both bulk
* endpoints -- go straight to
* SET_CONFIGURATION so the
* device is still usable for
* whatever doesn't need BOT
* (nothing, today, but this
* keeps the two concerns
* separate rather than
* failing enumeration
* outright). */
console_println("xhci: warning -- BOT device missing a bulk IN or OUT endpoint, skipping configure endpoint");
dev->next_action = XHCI_NEXT_ACTION_SET_CONFIG;
} else {
dev->next_action = XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT;
}
dev->next_action_slot_id = xfer_slot_id;
} else {
console_println("xhci: not a Mass Storage/SCSI/BOT device -- not usable as a drive");
}
@@ -1003,6 +1180,14 @@ void xhci_poll_events(void)
if (xhci_ep0_get_config_descriptor(dev, next_slot_id, next_length) != 0) {
console_println("xhci: deferred config descriptor request setup failed");
}
} else if (dev->next_action == XHCI_NEXT_ACTION_CONFIGURE_ENDPOINT) {
uint32_t next_slot_id = dev->next_action_slot_id;
dev->next_action = XHCI_NEXT_ACTION_NONE;
dev->connect_state = XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT;
if (xhci_cmd_configure_endpoint(dev, next_slot_id) != 0) {
console_println("xhci: deferred configure endpoint request setup failed");
dev->connect_state = XHCI_CONN_IDLE;
}
} else if (dev->next_action == XHCI_NEXT_ACTION_SET_CONFIG) {
uint32_t next_slot_id = dev->next_action_slot_id;
uint8_t next_config_value = dev->next_action_config_value;