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:
co-authored by
Claude Sonnet 5
parent
96d55fcd87
commit
92ce1f85dd
+191
-6
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user