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:
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user