xhcid: P2-C slice 2 — TT metadata + non-recursive stall clear
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<u8>
- parent_port_num: Option<u8>
- 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.
This commit is contained in:
@@ -311,6 +311,9 @@ unsafe impl<const N: usize> Sync for Xhci<N> {}
|
||||
struct PortState<const N: usize> {
|
||||
slot: u8,
|
||||
protocol_speed: &'static ProtocolSpeed,
|
||||
parent_hub_slot_id: Option<u8>,
|
||||
parent_port_num: Option<u8>,
|
||||
behind_highspeed_hub: bool,
|
||||
cfg_idx: Option<u8>,
|
||||
input_context: Mutex<Dma<InputContext<N>>>,
|
||||
dev_desc: Option<DevDesc>,
|
||||
@@ -884,9 +887,30 @@ 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) =
|
||||
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,
|
||||
|
||||
@@ -664,12 +664,17 @@ impl<const N: usize> Xhci<N> {
|
||||
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<const N: usize> Xhci<N> {
|
||||
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<const N: usize> Xhci<N> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn execute_control_transfer_once<D>(
|
||||
&self,
|
||||
port_num: PortId,
|
||||
setup: usb::Setup,
|
||||
tk: TransferKind,
|
||||
mut d: D,
|
||||
) -> Result<Trb>
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user