USB: P4 slice 2 — xHCI stream_id support + UAS tagged command queuing

Infrastructure:
- XhciEndpCtlReq::Transfer gains stream_id: u16 field (serde default=0
  for backward compatibility)
- scheme.rs execute_transfer: fixed hardcoded stream_id=1 ring lookup
  to use caller-provided stream_id
- transfer() method gains stream_id parameter; all existing callers
  pass 0 (non-stream endpoints)
- driver_interface: generic_transfer_stream() with stream_id parameter,
  transfer_write_sid() / transfer_read_sid() public stream-aware methods

UAS (usbscsid):
- init() detects stream support via endp_desc.log_max_streams()
- use_streams=true when endpoint supports streams, qdepth=MAX_CMNDS(256)
- send_command() uses stream_id = tag+1 (stream 0 reserved per UAS spec)
- transfer_write_sid/transfer_read_sid used for stream-capable endpoints
- Fallback to standard transfer_write/read for non-stream operation
- All four pipes (cmd/status/data_in/data_out) pass matching stream_id

Cross-referenced with Linux 7.1 xhci-ring.c stream ring management and
uas.c tagged command submission.
This commit is contained in:
Red Bear OS
2026-07-07 14:47:01 +03:00
parent 69a8e406c6
commit 0d8f3aadc0
3 changed files with 72 additions and 14 deletions
+29 -10
View File
@@ -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"));
}
+36
View File
@@ -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<PortTransferStatus, XhciClientHandleError> {
self.generic_transfer_stream(direction, f, expected_len, 0)
}
fn generic_transfer_stream<F: FnOnce(&mut File) -> io::Result<usize>>(
&mut self,
direction: XhciEndpCtlDirection,
f: F,
expected_len: u32,
stream_id: u16,
) -> result::Result<PortTransferStatus, XhciClientHandleError> {
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<PortTransferStatus, XhciClientHandleError> {
self.generic_transfer(XhciEndpCtlDirection::NoData, |_| Ok(0), 0)
}
pub fn transfer_write_sid(
&mut self,
buf: &[u8],
stream_id: u16,
) -> result::Result<PortTransferStatus, XhciClientHandleError> {
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<PortTransferStatus, XhciClientHandleError> {
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,
+7 -4
View File
@@ -1080,7 +1080,7 @@ impl<const N: usize> Xhci<N> {
true,
stream_ctx_array
.rings
.get_mut(&1)
.get_mut(&stream_id)
.ok_or(Error::new(EBADF))?,
),
};
@@ -1615,6 +1615,7 @@ impl<const N: usize> Xhci<N> {
endp_idx,
Some(dma_buffer),
PortReqDirection::DeviceToHost,
0,
)
.await?;
@@ -1648,6 +1649,7 @@ impl<const N: usize> Xhci<N> {
endp_idx,
Some(dma_buffer),
PortReqDirection::HostToDevice,
0,
)
.await?;
Ok((completion_code, bytes_transferred))
@@ -1683,6 +1685,7 @@ impl<const N: usize> Xhci<N> {
endp_idx: u8,
dma_buf: Option<Dma<[u8]>>,
direction: PortReqDirection,
stream_id: u16,
) -> Result<(u8, u32, Option<Dma<[u8]>>)> {
// TODO: Check that only readable enpoints are read, etc.
let endp_num = endp_idx + 1;
@@ -1742,7 +1745,7 @@ impl<const N: usize> Xhci<N> {
(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<const N: usize> Xhci<N> {
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));