From ceb1a5799a482a3e6c0fbf70be4af3797241acb9 Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Tue, 7 Jul 2026 11:11:25 +0300 Subject: [PATCH] =?UTF-8?q?xhcid:=20P2-C=20slice=202=20=E2=80=94=20TT=20me?= =?UTF-8?q?tadata=20+=20non-recursive=20stall=20clear?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the next recovery slice after the first active P2-C pass: 1. Persist parent-hub / TT metadata in PortState - parent_hub_slot_id: Option - parent_port_num: Option - behind_highspeed_hub: bool These are derived at attach time from PortId::parent() plus the parent port's protocol_speed, matching the Linux 7.1 TT decision rule: LS/FS device behind HS hub. 2. Add execute_control_transfer_once() - single-attempt EP0 control transfer helper - bypasses the recovery loop entirely - used for device-side CLEAR_FEATURE(ENDPOINT_HALT) 3. Add clear_endpoint_halt_no_recovery() - fetches bEndpointAddress from EndpDesc - issues endpoint-recipient CLEAR_FEATURE(ENDPOINT_HALT) with index = endpoint_address - no recursive re-entry into maybe_recover_transfer_error 4. Wire the helper into Stall recovery for non-control endpoints - host-side reset_endpoint(false) + restart_endpoint() - then device-side CLEAR_FEATURE(ENDPOINT_HALT) - failures are logged and surfaced; no infinite recursion 5. Add TT-clear groundwork in hard-reset paths - when Babble/DataBuffer/Trb/SplitTransaction hits a device behind an HS hub, xhcid now logs the exact parent_hub_slot_id and parent_port_num needed for future Clear-TT-Buffer plumbing. Cross-reference: - Linux 7.1 drivers/usb/host/xhci-ring.c * finish_td() * xhci_halted_host_endpoint() - Linux 7.1 drivers/usb/core/hub.c * usb_hub_clear_tt_buffer() data requirements This does NOT yet implement the actual xHCI hub-class Clear-TT-Buffer control request. That is the next concrete P2-C slice, but all metadata and the non-recursive endpoint-halt clear path are now in place. --- drivers/usb/xhcid/src/xhci/mod.rs | 24 ++++++ drivers/usb/xhcid/src/xhci/scheme.rs | 115 +++++++++++++++++++++++++-- 2 files changed, 132 insertions(+), 7 deletions(-) diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs index 8daf9a8faa..22bc6b09c5 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ -311,6 +311,9 @@ unsafe impl Sync for Xhci {} struct PortState { slot: u8, protocol_speed: &'static ProtocolSpeed, + parent_hub_slot_id: Option, + parent_port_num: Option, + behind_highspeed_hub: bool, cfg_idx: Option, input_context: Mutex>>, dev_desc: Option, @@ -884,9 +887,30 @@ impl Xhci { // TODO: Should the descriptors be cached in PortState, or refetched? + let (parent_hub_slot_id, parent_port_num, behind_highspeed_hub) = + if let Some((parent_port, port_num_on_parent)) = port_id.parent() { + match self.port_states.get(&parent_port) { + Some(parent_state) => { + let child_ls_fs = protocol_speed.is_lowspeed() || protocol_speed.is_fullspeed(); + let parent_hs = parent_state.protocol_speed.is_highspeed(); + ( + Some(parent_state.slot), + Some(port_num_on_parent), + child_ls_fs && parent_hs, + ) + } + None => (None, None, false), + } + } else { + (None, None, false) + }; + let mut port_state = PortState { slot, protocol_speed, + parent_hub_slot_id, + parent_port_num, + behind_highspeed_hub, input_context: Mutex::new(input), dev_desc: None, cfg_idx: None, diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs index 24bbb3754e..8b777ace7a 100644 --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ -664,12 +664,17 @@ impl Xhci { if control_path { self.reset_port(port_num)?; } else { - // Avoid recursive async control-transfer re-entry here. - // Linux does CLEAR_FEATURE(ENDPOINT_HALT) after host-side - // reset; for this first recovery slice we do the host-side - // reset/restart only and surface the error upward. self.reset_endpoint(port_num, endp_num, false).await?; self.restart_endpoint(port_num, endp_num).await?; + if let Err(err) = self.clear_endpoint_halt_no_recovery(port_num, endp_num).await { + warn!( + "{}: CLEAR_FEATURE(ENDPOINT_HALT) after stall failed on port {} ep {}: {}", + context, + port_num, + endp_num, + err + ); + } } Ok(false) } @@ -684,9 +689,17 @@ impl Xhci { port_num, endp_num ); - // NOTE: Linux also clears the TT buffer for SplitTransaction / Babble - // when a high-speed hub is involved. We don't have TT-clear plumbing yet; - // that follow-up remains tracked in P2-C. + if let Some(state) = self.port_states.get(&port_num) { + if state.behind_highspeed_hub { + warn!( + "{}: TT-clear still pending for port {} (parent_hub_slot_id={:?}, parent_port_num={:?})", + context, + port_num, + state.parent_hub_slot_id, + state.parent_port_num, + ); + } + } hard_reset_endpoint.await?; Ok(false) } @@ -694,6 +707,94 @@ impl Xhci { } } + async fn execute_control_transfer_once( + &self, + port_num: PortId, + setup: usb::Setup, + tk: TransferKind, + mut d: D, + ) -> Result + where + D: FnMut(&mut Trb, bool) -> ControlFlow, + { + let future = { + let mut port_state = self.port_state_mut(port_num)?; + let slot = port_state.slot; + + let mut endpoint_state = port_state + .endpoint_states + .get_mut(&0) + .ok_or(Error::new(EIO))?; + + let ring = endpoint_state.ring().ok_or(Error::new(EIO))?; + + let first_index = ring.next_index(); + let (cmd, cycle) = (&mut ring.trbs[first_index], ring.cycle); + cmd.setup(setup, tk, cycle); + + if tk != TransferKind::NoData { + loop { + let (trb, cycle) = ring.next(); + match d(trb, cycle) { + ControlFlow::Break => break, + ControlFlow::Continue => continue, + } + } + } + + let last_index = ring.next_index(); + let (cmd, cycle) = (&mut ring.trbs[last_index], ring.cycle); + + let interrupter = 0; + let input = tk != TransferKind::In; + let ioc = true; + let ch = false; + let ent = false; + cmd.status(interrupter, input, ioc, ch, ent, cycle); + + self.next_transfer_event_trb( + RingId::default_control_pipe(port_num), + ring, + &ring.trbs[first_index], + &ring.trbs[last_index], + EventDoorbell::new(self, usize::from(slot), Self::def_control_endp_doorbell()), + ) + }; + + let trbs = future.await; + let event_trb = trbs.event_trb; + let status_trb = trbs.src_trb.ok_or(Error::new(EIO))?; + + handle_transfer_event_trb("CONTROL_TRANSFER_ONCE", &event_trb, &status_trb)?; + Ok(event_trb) + } + + async fn clear_endpoint_halt_no_recovery( + &self, + port_num: PortId, + endp_num: u8, + ) -> Result<()> { + let endp_idx = endp_num.checked_sub(1).ok_or(Error::new(EIO))?; + let port_state = self.port_states.get(&port_num).ok_or(Error::new(EBADFD))?; + let endp_desc = port_state.get_endp_desc(endp_idx).ok_or(Error::new(EBADFD))?; + let endpoint_address = u16::from(endp_desc.address); + + self.execute_control_transfer_once( + port_num, + usb::Setup { + kind: 0b0000_0010, // endpoint recipient + request: 0x01, // CLEAR_FEATURE + value: 0x00, // ENDPOINT_HALT + index: endpoint_address, + length: 0, + }, + TransferKind::NoData, + |_, _| ControlFlow::Break, + ) + .await?; + Ok(()) + } + async fn new_if_desc( &self, port_id: PortId,