xhcid: P2-C slice 3 — actual TT-buffer clear via hub-class control request

Completes the TT-clear recovery path started in slice 2.  Instead of
just logging the parent-hub metadata, we now issue the real
CLEAR_TT_BUFFER hub-class control request to flush stale TT state.

  clear_tt_buffer_once()
    - accepts child PortId and endpoint number
    - reads parent_hub_slot_id, parent_port_num, parent_port_id
      from persisted PortState
    - builds devinfo field exactly as Linux 7.1 does:
        (ep_number) | (dev_addr << 4) | (BULK << 11) | (IN << 15)
    - uses TT port from parent_port_num (1-indexed)
    - sends class-request CLEAR_TT_BUFFER via one-shot EP0 helper
    - propagates errors as warnings; endpoint reset continues anyway

  Call site (hard-reset recovery for Babble/DataBuffer/Trb/Split):
    - TT-clear runs BEFORE endpoint reset per Linux 7.1 finish_td()
      ordering
    - only triggers when behind_highspeed_hub is true
    - uses the stored parent_port_id directly (no CHashMap scan)

  PortState gains parent_port_id: Option<PortId>
    - persisted alongside parent_hub_slot_id and parent_port_num
    - avoids scanning port_states at TT-clear time (CHashMap has
      no iterator)

Cross-reference: Linux 7.1
  - drivers/usb/core/hub.c: usb_hub_clear_tt_buffer()
  - drivers/usb/host/xhci-ring.c: xhci_clear_hub_tt_buffer()
  - driver_interface.rs: PortId definition

This completes the first implementation of P2-C error recovery:
  - UsbTransaction: bounded soft retry (3x)
  - Resource: bounded retry/backoff
  - Stall: reset/restart + non-recursive device-side clear-halt
  - Babble/DataBuffer/Trb/SplitTransaction: TT-clear (if behind HS hub)
    + hard endpoint reset
This commit is contained in:
Red Bear OS
2026-07-07 11:45:25 +03:00
parent ceb1a5799a
commit 61b1510a46
2 changed files with 87 additions and 10 deletions
+6 -3
View File
@@ -313,6 +313,7 @@ struct PortState<const N: usize> {
protocol_speed: &'static ProtocolSpeed,
parent_hub_slot_id: Option<u8>,
parent_port_num: Option<u8>,
parent_port_id: Option<PortId>,
behind_highspeed_hub: bool,
cfg_idx: Option<u8>,
input_context: Mutex<Dma<InputContext<N>>>,
@@ -887,7 +888,7 @@ impl<const N: usize> Xhci<N> {
// 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<const N: usize> Xhci<N> {
(
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<const N: usize> Xhci<N> {
protocol_speed,
parent_hub_slot_id,
parent_port_num,
parent_port_id,
behind_highspeed_hub,
input_context: Mutex::new(input),
dev_desc: None,
+81 -7
View File
@@ -691,13 +691,20 @@ impl<const N: usize> Xhci<N> {
);
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<const N: usize> Xhci<N> {
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,