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:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user