diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs index 968a2eb837..e676355289 100644 --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ -3042,54 +3042,75 @@ impl Xhci { Ok(()) } pub async fn restart_endpoint(&self, port_num: PortId, endp_num: u8) -> Result<()> { - let mut port_state = self - .port_states - .get_mut(&port_num) - .ok_or(Error::new(EBADFD))?; - let slot = port_state.slot; + // Phase 1 (lock held): prime the transfer ring with a NoOp and + // compute the doorbell value. The port_states write guard MUST be + // dropped before the set_tr_deque_ptr() await below, because that + // function re-acquires a read guard on the same key — holding the + // write guard across it deadlocks (std RwLock read-while-write on + // one thread). + let (slot, deque_ptr_and_cycle, doorbell) = { + let mut port_state = self + .port_states + .get_mut(&port_num) + .ok_or(Error::new(EBADFD))?; + let slot = port_state.slot; - let mut endpoint_state = port_state - .endpoint_states - .get_mut(&endp_num) - .ok_or(Error::new(EBADFD))?; + let mut endpoint_state = port_state + .endpoint_states + .get_mut(&endp_num) + .ok_or(Error::new(EBADFD))?; - let (has_streams, ring) = match &mut endpoint_state.transfer { - &mut super::RingOrStreams::Ring(ref mut ring) => (false, ring), - &mut super::RingOrStreams::Streams(ref mut arr) => { - (true, arr.rings.get_mut(&1).ok_or(Error::new(EBADFD))?) - } + let (has_streams, ring) = match &mut endpoint_state.transfer { + &mut super::RingOrStreams::Ring(ref mut ring) => (false, ring), + &mut super::RingOrStreams::Streams(ref mut arr) => { + (true, arr.rings.get_mut(&1).ok_or(Error::new(EBADFD))?) + } + }; + + // Capture the NoOp's own address BEFORE advancing the enqueue + // index, so Set TR Dequeue Pointer primes the hardware dequeue + // AT the NoOp — the xHC executes it on restart, proving the + // ring is live again (Linux xhci_move_dequeue_past_td primes + // the dequeue at the first valid TRB the same way). + let deque_ptr_and_cycle = ring.register(); + let (cmd, cycle) = ring.next(); + cmd.transfer_no_op(0, false, false, false, cycle); + + let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EBADFD))?; + let endp_desc = dev_desc + .config_descs + .get(0) + .ok_or(Error::new(EIO))? + .interface_descs + .get(0) + .ok_or(Error::new(EIO))? + .endpoints + .get(endp_num as usize - 1) + .ok_or(Error::new(EBADFD))?; + + let doorbell = if endp_num != 0 { + let stream_id = 1u16; + + Self::endp_doorbell(endp_num, endp_desc, if has_streams { stream_id } else { 0 }) + } else { + Self::def_control_endp_doorbell() + }; + (slot, deque_ptr_and_cycle, doorbell) }; - let (cmd, cycle) = ring.next(); - cmd.transfer_no_op(0, false, false, false, cycle); - - let deque_ptr_and_cycle = ring.register(); - - let dev_desc = port_state.dev_desc.as_ref().ok_or(Error::new(EBADFD))?; - let endp_desc = dev_desc - .config_descs - .get(0) - .ok_or(Error::new(EIO))? - .interface_descs - .get(0) - .ok_or(Error::new(EIO))? - .endpoints - .get(endp_num as usize - 1) - .ok_or(Error::new(EBADFD))?; - - let doorbell = if endp_num != 0 { - let stream_id = 1u16; - - Self::endp_doorbell(endp_num, endp_desc, if has_streams { stream_id } else { 0 }) - } else { - Self::def_control_endp_doorbell() - }; - - self.dbs.lock().unwrap_or_else(|e| e.into_inner())[slot as usize].write(doorbell); - + // Phase 2 (lock released): move the hardware dequeue pointer, then + // ring the doorbell. xHCI spec 4.6.8/4.6.10: after Reset Endpoint + // the TR Dequeue Pointer is undefined, so Set TR Dequeue Pointer + // MUST complete before the doorbell transitions the endpoint + // Stopped→Running. Linux rings the doorbell from the Set TR + // Dequeue completion path (xhci_handle_cmd_set_deq) for the same + // reason. The previous order (doorbell first) ran the endpoint + // with an undefined dequeue pointer — undefined xHC behavior. self.set_tr_deque_ptr(port_num, endp_num, deque_ptr_and_cycle) .await?; + self.dbs.lock().unwrap_or_else(|e| e.into_inner())[slot as usize].write(doorbell); + Ok(()) } pub fn endp_direction(&self, port_num: PortId, endp_num: u8) -> Result {