diff --git a/drivers/storage/usbscsid/src/protocol/uas.rs b/drivers/storage/usbscsid/src/protocol/uas.rs index 20c1f7b749..39d18d22f4 100644 --- a/drivers/storage/usbscsid/src/protocol/uas.rs +++ b/drivers/storage/usbscsid/src/protocol/uas.rs @@ -157,12 +157,11 @@ impl<'a> UasTransport<'a> { let data_in_num = pipes[2]; let data_out_num = pipes[3]; - // Primary UAS support: no streams yet. - // Streams require xHCI stream context allocation which is - // tracked for P4 slice 2. Without streams, commands are - // sequential but still benefit from the 4-pipe model (no - // CBW/CSW round-trip overhead). - let use_streams = false; + // Detect stream support: UAS on USB 3.0+ controllers can use + // xHCI streams for tagged command queuing (up to MAX_CMNDS + // concurrent commands). On USB 2.0 controllers, stream support + // is not available and commands are queued sequentially. + let use_streams = if_desc.endpoints.iter().any(|ep| ep.log_max_streams().is_some()); let qdepth = if use_streams { MAX_CMNDS as u16 } else { 1u16 }; Ok(Self { @@ -192,6 +191,10 @@ impl<'a> Protocol for UasTransport<'a> { let tag = self.current_tag; self.current_tag = self.current_tag.wrapping_add(1); + // Stream ID = tag + 1 (stream 0 is reserved). Matches UAS spec + // requirement that each command tag maps to a unique stream. + let stream_id = if self.use_streams { (tag as u16).wrapping_add(1) } else { 0 }; + let mut cdb = [0u8; 16]; let copy_len = command.len().min(16); cdb[..copy_len].copy_from_slice(&command[..copy_len]); @@ -206,7 +209,11 @@ impl<'a> Protocol for UasTransport<'a> { let cmd_bytes = unsafe { plain::as_bytes(&cmd_iu) }; // Send Command IU on the command pipe - let status = self.cmd.transfer_write(cmd_bytes)?; + let status = if self.use_streams { + self.cmd.transfer_write_sid(cmd_bytes, stream_id)? + } else { + self.cmd.transfer_write(cmd_bytes)? + }; if status.kind == PortTransferStatusKind::Stalled { return Err(ProtocolError::EndpointStalled("uas cmd pipe stalled")); } @@ -214,7 +221,11 @@ impl<'a> Protocol for UasTransport<'a> { // Data phase match data { DeviceReqData::In(buffer) if !buffer.is_empty() => { - let data_status = self.data_in.transfer_read(buffer)?; + let data_status = if self.use_streams { + self.data_in.transfer_read_sid(buffer, stream_id)? + } else { + self.data_in.transfer_read(buffer)? + }; if data_status.kind != PortTransferStatusKind::Success && data_status.kind != PortTransferStatusKind::ShortPacket { @@ -222,7 +233,11 @@ impl<'a> Protocol for UasTransport<'a> { } } DeviceReqData::Out(buffer) if !buffer.is_empty() => { - let data_status = self.data_out.transfer_write(buffer)?; + let data_status = if self.use_streams { + self.data_out.transfer_write_sid(buffer, stream_id)? + } else { + self.data_out.transfer_write(buffer)? + }; if data_status.kind != PortTransferStatusKind::Success { return Err(ProtocolError::ProtocolError("uas data-out failed")); } @@ -232,7 +247,11 @@ impl<'a> Protocol for UasTransport<'a> { // Read Response IU or Sense IU from the status pipe let mut response_buffer = [0u8; 20]; // max(ResponseIU, SenseIU) - let resp_status = self.status.transfer_read(&mut response_buffer)?; + let resp_status = if self.use_streams { + self.status.transfer_read_sid(&mut response_buffer, stream_id)? + } else { + self.status.transfer_read(&mut response_buffer)? + }; if resp_status.kind == PortTransferStatusKind::Stalled { return Err(ProtocolError::EndpointStalled("uas status pipe stalled")); } diff --git a/drivers/usb/xhcid/src/driver_interface.rs b/drivers/usb/xhcid/src/driver_interface.rs index c427355b50..5aa5fdca53 100644 --- a/drivers/usb/xhcid/src/driver_interface.rs +++ b/drivers/usb/xhcid/src/driver_interface.rs @@ -741,6 +741,12 @@ pub enum XhciEndpCtlReq { /// the transfer will be considered complete by xhcid, and a non-pending status will be /// returned. count: u32, + + /// xHCI stream ID. Zero for non-stream endpoints (control, standard + /// bulk/interrupt). Non-zero for stream-capable endpoints (UAS tagged + /// commands, isochronous streaming). Valid range: 0..65535. + #[serde(default)] + stream_id: u16, }, // TODO: Allow clients to specify what to reset. /// Tells xhcid that the endpoint is going to be reset. @@ -804,10 +810,20 @@ impl XhciEndpHandle { direction: XhciEndpCtlDirection, f: F, expected_len: u32, + ) -> result::Result { + self.generic_transfer_stream(direction, f, expected_len, 0) + } + fn generic_transfer_stream io::Result>( + &mut self, + direction: XhciEndpCtlDirection, + f: F, + expected_len: u32, + stream_id: u16, ) -> result::Result { let req = XhciEndpCtlReq::Transfer { direction, count: expected_len, + stream_id, }; self.ctl_req(&req)?; @@ -845,6 +861,26 @@ impl XhciEndpHandle { pub fn transfer_nodata(&mut self) -> result::Result { self.generic_transfer(XhciEndpCtlDirection::NoData, |_| Ok(0), 0) } + pub fn transfer_write_sid( + &mut self, + buf: &[u8], + stream_id: u16, + ) -> result::Result { + self.generic_transfer_stream( + XhciEndpCtlDirection::Out, + |data| data.write(buf), + buf.len() as u32, + stream_id, + ) + } + pub fn transfer_read_sid( + &mut self, + buf: &mut [u8], + stream_id: u16, + ) -> result::Result { + let len = buf.len() as u32; + self.generic_transfer_stream(XhciEndpCtlDirection::In, |data| data.read(buf), len, stream_id) + } fn transfer_stream(&mut self, total_len: u32) -> TransferStream<'_> { TransferStream { bytes_to_transfer: total_len, diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs index fcc8ea9137..a06bdf2c51 100644 --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ -1080,7 +1080,7 @@ impl Xhci { true, stream_ctx_array .rings - .get_mut(&1) + .get_mut(&stream_id) .ok_or(Error::new(EBADF))?, ), }; @@ -1615,6 +1615,7 @@ impl Xhci { endp_idx, Some(dma_buffer), PortReqDirection::DeviceToHost, + 0, ) .await?; @@ -1648,6 +1649,7 @@ impl Xhci { endp_idx, Some(dma_buffer), PortReqDirection::HostToDevice, + 0, ) .await?; Ok((completion_code, bytes_transferred)) @@ -1683,6 +1685,7 @@ impl Xhci { endp_idx: u8, dma_buf: Option>, direction: PortReqDirection, + stream_id: u16, ) -> Result<(u8, u32, Option>)> { // TODO: Check that only readable enpoints are read, etc. let endp_num = endp_idx + 1; @@ -1742,7 +1745,7 @@ impl Xhci { (buffer, idt, estimated_td_size) }; - let stream_id = 1u16; + let stream_id = stream_id; // use caller-provided stream_id let mut bytes_left = dma_buf.as_ref().map(|buf| buf.len()).unwrap_or(0); @@ -2909,13 +2912,13 @@ impl Xhci { return Err(Error::new(EBADF)); } }, - XhciEndpCtlReq::Transfer { direction, count } => match ep_if_state { + XhciEndpCtlReq::Transfer { direction, count, stream_id } => match ep_if_state { state @ EndpIfState::Init => { if direction == XhciEndpCtlDirection::NoData { // Yield the result directly because no bytes have to be sent or received // beforehand. let (completion_code, bytes_transferred, _) = self - .transfer(port_num, endp_num - 1, None, PortReqDirection::DeviceToHost) + .transfer(port_num, endp_num - 1, None, PortReqDirection::DeviceToHost, stream_id) .await?; if bytes_transferred > 0 { return Err(Error::new(EIO));