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:
Red Bear OS
2026-07-07 11:11:25 +03:00
parent 27021d15d3
commit ceb1a5799a
2 changed files with 132 additions and 7 deletions
+24
View File
@@ -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,
+108 -7
View File
@@ -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,