diff --git a/drivers/usb/xhcid/src/xhci/mod.rs b/drivers/usb/xhcid/src/xhci/mod.rs index 22bc6b09c5..e41d8ac6a9 100644 --- a/drivers/usb/xhcid/src/xhci/mod.rs +++ b/drivers/usb/xhcid/src/xhci/mod.rs @@ -313,6 +313,7 @@ struct PortState { protocol_speed: &'static ProtocolSpeed, parent_hub_slot_id: Option, parent_port_num: Option, + parent_port_id: Option, behind_highspeed_hub: bool, cfg_idx: Option, input_context: Mutex>>, @@ -887,7 +888,7 @@ impl Xhci { // TODO: Should the descriptors be cached in PortState, or refetched? - let (parent_hub_slot_id, parent_port_num, behind_highspeed_hub) = + let (parent_hub_slot_id, parent_port_num, parent_port_id, 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) => { @@ -896,13 +897,14 @@ impl Xhci { ( Some(parent_state.slot), Some(port_num_on_parent), + Some(parent_port), child_ls_fs && parent_hs, ) } - None => (None, None, false), + None => (None, None, None, false), } } else { - (None, None, false) + (None, None, None, false) }; let mut port_state = PortState { @@ -910,6 +912,7 @@ impl Xhci { protocol_speed, parent_hub_slot_id, parent_port_num, + parent_port_id, behind_highspeed_hub, input_context: Mutex::new(input), dev_desc: None, diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs index 8b777ace7a..7c0be0d436 100644 --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ -691,13 +691,20 @@ impl Xhci { ); 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, - ); + // TT-clear MUST happen before endpoint reset per + // Linux 7.1 finish_td() ordering. Queue the + // hub-class CLEAR_TT_BUFFER request through the + // parent hub; if it fails, log the error and + // continue with the endpoint reset regardless. + if let Err(e) = self.clear_tt_buffer_once(port_num, endp_num).await { + warn!( + "{}: CLEAR_TT_BUFFER failed for port {} (parent_hub_slot_id={:?}): {}", + context, + port_num, + state.parent_hub_slot_id, + e, + ); + } } } hard_reset_endpoint.await?; @@ -795,6 +802,73 @@ impl Xhci { Ok(()) } + /// Clear the Transaction Translator buffer on the parent hub. + /// + /// Cross-referenced with Linux 7.1 `drivers/usb/core/hub.c` + /// `usb_hub_clear_tt_buffer()` and `drivers/usb/host/xhci-ring.c` + /// `xhci_clear_hub_tt_buffer()`. + /// + /// This is a hub-class control request sent to the parent hub + /// device, not to the stalled endpoint's device. The request + /// tells the HS hub to flush any stale state in its TT so the + /// LS/FS device behind it can be re-enumerated correctly. + async fn clear_tt_buffer_once( + &self, + child_port: PortId, + endp_num: u8, + ) -> Result<()> { + let state = self.port_states.get(&child_port).ok_or(Error::new(EBADFD))?; + let parent_slot_id = state.parent_hub_slot_id.ok_or(Error::new(EIO))?; + let parent_port_num = state.parent_port_num.ok_or(Error::new(EIO))?; + let parent_port_id = state.parent_port_id.ok_or(Error::new(EBADFD))?; + + // Read the endpoint descriptor to get the endpoint number + // and direction. Fall back to endp_num if the descriptor + // is not yet cached. + let ep_addr = state + .get_endp_desc(endp_num.checked_sub(1).ok_or(Error::new(EIO))?) + .map(|d| d.address) + .unwrap_or(endp_num); + let ep_number = ep_addr & 0x0F; + let direction_bit = if ep_addr & 0x80 != 0 { 1u16 } else { 0u16 }; + let dev_addr = state.slot as u16; + + // Linux builds `devinfo` as: + // (ep_number) | (dev_addr << 4) | (BULK << 11) | (IN << 15) + // We use BULK (3) as the transfer type because TT-clear for + // interrupt pipes is a separate slower path. + let devinfo = u16::from(ep_number) + | (dev_addr << 4) + | (3u16 << 11) // BULK + | (direction_bit << 15); + + // TT port: for single-TT hubs Linux uses 1; for multi-TT it + // uses the actual parent_port_num. We use parent_port_num + // unconditionally (it is 1-indexed like the spec expects). + let tt_port = u16::from(parent_port_num); + let _ = parent_port_num; // suppress unused warning + + info!( + "CLEAR_TT_BUFFER: parent_hub_port={} parent_slot={} devinfo={:04X} tt_port={}", + parent_port_id, parent_slot_id, devinfo, tt_port + ); + + self.execute_control_transfer_once( + parent_port_id, + usb::Setup { + kind: 0b0010_0010, // class request, host-to-device + request: 0x08, // CLEAR_TT_BUFFER + value: devinfo, + index: tt_port, + length: 0, + }, + TransferKind::NoData, + |_, _| ControlFlow::Break, + ) + .await?; + Ok(()) + } + async fn new_if_desc( &self, port_id: PortId,