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_in_num = pipes[2];
|
||||||
let data_out_num = pipes[3];
|
let data_out_num = pipes[3];
|
||||||
|
|
||||||
// Primary UAS support: no streams yet.
|
// Detect stream support: UAS on USB 3.0+ controllers can use
|
||||||
// Streams require xHCI stream context allocation which is
|
// xHCI streams for tagged command queuing (up to MAX_CMNDS
|
||||||
// tracked for P4 slice 2. Without streams, commands are
|
// concurrent commands). On USB 2.0 controllers, stream support
|
||||||
// sequential but still benefit from the 4-pipe model (no
|
// is not available and commands are queued sequentially.
|
||||||
// CBW/CSW round-trip overhead).
|
let use_streams = if_desc.endpoints.iter().any(|ep| ep.log_max_streams().is_some());
|
||||||
let use_streams = false;
|
|
||||||
let qdepth = if use_streams { MAX_CMNDS as u16 } else { 1u16 };
|
let qdepth = if use_streams { MAX_CMNDS as u16 } else { 1u16 };
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -192,6 +191,10 @@ impl<'a> Protocol for UasTransport<'a> {
|
|||||||
let tag = self.current_tag;
|
let tag = self.current_tag;
|
||||||
self.current_tag = self.current_tag.wrapping_add(1);
|
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 mut cdb = [0u8; 16];
|
||||||
let copy_len = command.len().min(16);
|
let copy_len = command.len().min(16);
|
||||||
cdb[..copy_len].copy_from_slice(&command[..copy_len]);
|
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) };
|
let cmd_bytes = unsafe { plain::as_bytes(&cmd_iu) };
|
||||||
|
|
||||||
// Send Command IU on the command pipe
|
// 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 {
|
if status.kind == PortTransferStatusKind::Stalled {
|
||||||
return Err(ProtocolError::EndpointStalled("uas cmd pipe stalled"));
|
return Err(ProtocolError::EndpointStalled("uas cmd pipe stalled"));
|
||||||
}
|
}
|
||||||
@@ -214,7 +221,11 @@ impl<'a> Protocol for UasTransport<'a> {
|
|||||||
// Data phase
|
// Data phase
|
||||||
match data {
|
match data {
|
||||||
DeviceReqData::In(buffer) if !buffer.is_empty() => {
|
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
|
if data_status.kind != PortTransferStatusKind::Success
|
||||||
&& data_status.kind != PortTransferStatusKind::ShortPacket
|
&& data_status.kind != PortTransferStatusKind::ShortPacket
|
||||||
{
|
{
|
||||||
@@ -222,7 +233,11 @@ impl<'a> Protocol for UasTransport<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeviceReqData::Out(buffer) if !buffer.is_empty() => {
|
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 {
|
if data_status.kind != PortTransferStatusKind::Success {
|
||||||
return Err(ProtocolError::ProtocolError("uas data-out failed"));
|
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
|
// Read Response IU or Sense IU from the status pipe
|
||||||
let mut response_buffer = [0u8; 20]; // max(ResponseIU, SenseIU)
|
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 {
|
if resp_status.kind == PortTransferStatusKind::Stalled {
|
||||||
return Err(ProtocolError::EndpointStalled("uas status pipe 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
|
/// the transfer will be considered complete by xhcid, and a non-pending status will be
|
||||||
/// returned.
|
/// returned.
|
||||||
count: u32,
|
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.
|
// TODO: Allow clients to specify what to reset.
|
||||||
/// Tells xhcid that the endpoint is going to be reset.
|
/// Tells xhcid that the endpoint is going to be reset.
|
||||||
@@ -804,10 +810,20 @@ impl XhciEndpHandle {
|
|||||||
direction: XhciEndpCtlDirection,
|
direction: XhciEndpCtlDirection,
|
||||||
f: F,
|
f: F,
|
||||||
expected_len: u32,
|
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> {
|
) -> result::Result<PortTransferStatus, XhciClientHandleError> {
|
||||||
let req = XhciEndpCtlReq::Transfer {
|
let req = XhciEndpCtlReq::Transfer {
|
||||||
direction,
|
direction,
|
||||||
count: expected_len,
|
count: expected_len,
|
||||||
|
stream_id,
|
||||||
};
|
};
|
||||||
self.ctl_req(&req)?;
|
self.ctl_req(&req)?;
|
||||||
|
|
||||||
@@ -845,6 +861,26 @@ impl XhciEndpHandle {
|
|||||||
pub fn transfer_nodata(&mut self) -> result::Result<PortTransferStatus, XhciClientHandleError> {
|
pub fn transfer_nodata(&mut self) -> result::Result<PortTransferStatus, XhciClientHandleError> {
|
||||||
self.generic_transfer(XhciEndpCtlDirection::NoData, |_| Ok(0), 0)
|
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<'_> {
|
fn transfer_stream(&mut self, total_len: u32) -> TransferStream<'_> {
|
||||||
TransferStream {
|
TransferStream {
|
||||||
bytes_to_transfer: total_len,
|
bytes_to_transfer: total_len,
|
||||||
|
|||||||
@@ -1080,7 +1080,7 @@ impl<const N: usize> Xhci<N> {
|
|||||||
true,
|
true,
|
||||||
stream_ctx_array
|
stream_ctx_array
|
||||||
.rings
|
.rings
|
||||||
.get_mut(&1)
|
.get_mut(&stream_id)
|
||||||
.ok_or(Error::new(EBADF))?,
|
.ok_or(Error::new(EBADF))?,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
@@ -1615,6 +1615,7 @@ impl<const N: usize> Xhci<N> {
|
|||||||
endp_idx,
|
endp_idx,
|
||||||
Some(dma_buffer),
|
Some(dma_buffer),
|
||||||
PortReqDirection::DeviceToHost,
|
PortReqDirection::DeviceToHost,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -1648,6 +1649,7 @@ impl<const N: usize> Xhci<N> {
|
|||||||
endp_idx,
|
endp_idx,
|
||||||
Some(dma_buffer),
|
Some(dma_buffer),
|
||||||
PortReqDirection::HostToDevice,
|
PortReqDirection::HostToDevice,
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok((completion_code, bytes_transferred))
|
Ok((completion_code, bytes_transferred))
|
||||||
@@ -1683,6 +1685,7 @@ impl<const N: usize> Xhci<N> {
|
|||||||
endp_idx: u8,
|
endp_idx: u8,
|
||||||
dma_buf: Option<Dma<[u8]>>,
|
dma_buf: Option<Dma<[u8]>>,
|
||||||
direction: PortReqDirection,
|
direction: PortReqDirection,
|
||||||
|
stream_id: u16,
|
||||||
) -> Result<(u8, u32, Option<Dma<[u8]>>)> {
|
) -> Result<(u8, u32, Option<Dma<[u8]>>)> {
|
||||||
// TODO: Check that only readable enpoints are read, etc.
|
// TODO: Check that only readable enpoints are read, etc.
|
||||||
let endp_num = endp_idx + 1;
|
let endp_num = endp_idx + 1;
|
||||||
@@ -1742,7 +1745,7 @@ impl<const N: usize> Xhci<N> {
|
|||||||
(buffer, idt, estimated_td_size)
|
(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);
|
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));
|
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 => {
|
state @ EndpIfState::Init => {
|
||||||
if direction == XhciEndpCtlDirection::NoData {
|
if direction == XhciEndpCtlDirection::NoData {
|
||||||
// Yield the result directly because no bytes have to be sent or received
|
// Yield the result directly because no bytes have to be sent or received
|
||||||
// beforehand.
|
// beforehand.
|
||||||
let (completion_code, bytes_transferred, _) = self
|
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?;
|
.await?;
|
||||||
if bytes_transferred > 0 {
|
if bytes_transferred > 0 {
|
||||||
return Err(Error::new(EIO));
|
return Err(Error::new(EIO));
|
||||||
|
|||||||
Reference in New Issue
Block a user