USB: P6-C isochronous transfer support — remove ENOSYS, add Trb::isoch()

Cross-referenced with Linux 7.1 drivers/usb/host/xhci-ring.c
xhci_queue_isoc_tx() (lines 4055-4317).

trb.rs:
- Trb::isoch() — constructs Isoch Transfer TRBs (type=6).
  Parameters: buffer, len, cycle, td_size, interrupter, isp,
  chain, ioc, tlbpc (Transfer Last Burst Packet Count, bits 16-19),
  sia_frame_id (Schedule In Advance / Frame ID, bits 20-31).
  TLBPC=1 default (one packet per burst), SIA=0 default
  (controller decides scheduling).  ISP set for IN endpoints.

scheme.rs:
- Removed ENOSYS gate on isoch endpoints (~line 1704).
- transfer() branches on is_isoch: uses trb.isoch() for isoch
  endpoints, trb.normal() for bulk/interrupt.
- bytes_transferred: for isoch, uses buffer length directly
  (event.transfer_length() carries Frame ID, not remaining bytes
  per xHCI spec §4.15.2 Transfer Event TRB).
- Error recovery: isoch codes (IsochBuffer, RingUnderrun,
  RingOverrun, MissedService) fall through to no-retry in
  maybe_recover_transfer_error — correct, isoch never retries.

This unblocks USB Audio Class (P6-C) and the redbear-usbaudiod
real driver (last remaining P1-D stub).
This commit is contained in:
Red Bear OS
2026-07-07 14:55:03 +03:00
parent 0d8f3aadc0
commit c89af69d08
2 changed files with 79 additions and 26 deletions
+52 -26
View File
@@ -1700,10 +1700,7 @@ impl<const N: usize> Xhci<N> {
.ok_or(Error::new(EBADFD))?;
let direction = endp_desc.direction();
if endp_desc.is_isoch() {
return Err(Error::new(ENOSYS));
}
let is_isoch = endp_desc.is_isoch();
if EndpDirection::from(direction) != endp_desc.direction() {
return Err(Error::new(EBADF));
@@ -1760,27 +1757,47 @@ impl<const N: usize> Xhci<N> {
|trb, cycle| {
let len = cmp::min(bytes_left, max_transfer_size as usize) as u32;
// set the interrupt on completion (IOC) flag for the last trb.
let ioc = bytes_left <= max_transfer_size as usize;
let chain = !ioc;
let interrupter = 0;
let ent = false;
let isp = true;
let bei = false;
trb.normal(
buffer,
len,
cycle,
estimated_td_size,
interrupter,
ent,
isp,
chain,
ioc,
idt,
bei,
);
if is_isoch {
// Isochronous TRB: TLBPC=1 (one packet per burst),
// SIA=0 (schedule in advance — controller decides when).
let interrupter = 0u8;
let isp = direction == EndpDirection::In;
let tlbpc = 1u8;
let sia = 0u32;
trb.isoch(
buffer,
len,
cycle,
0, // td_size
interrupter,
isp,
chain,
ioc,
tlbpc,
sia,
);
} else {
let interrupter = 0;
let ent = false;
let isp = true;
let bei = false;
trb.normal(
buffer,
len,
cycle,
estimated_td_size,
interrupter,
ent,
isp,
chain,
ioc,
idt,
bei,
);
}
bytes_left -= len as usize;
@@ -1794,10 +1811,19 @@ impl<const N: usize> Xhci<N> {
.await?;
//self.event_handler_finished();
let bytes_transferred = dma_buf
.as_ref()
.map(|buf| buf.len() as u32 - event.transfer_length())
.unwrap_or(0);
let bytes_transferred = if is_isoch {
// Isoch event TRBs carry the Frame ID in the transfer_length
// field, not the remaining byte count. Return the buffer
// length as the transfer size — isoch transfers either succeed
// or fail entirely (the controller reports IsochBuffer on
// overrun, RingUnderrun/RingOverrun on ring exhaustion).
dma_buf.as_ref().map(|buf| buf.len() as u32).unwrap_or(0)
} else {
dma_buf
.as_ref()
.map(|buf| buf.len() as u32 - event.transfer_length())
.unwrap_or(0)
};
Ok((event.completion_code(), bytes_transferred, dma_buf))
}
+27
View File
@@ -446,6 +446,33 @@ impl Trb {
| ((TrbType::Normal as u32) << 10),
)
}
pub fn isoch(
&mut self,
buffer: u64,
len: u32,
cycle: bool,
td_size: u8,
interrupter: u8,
isp: bool,
chain: bool,
ioc: bool,
tlbpc: u8,
sia_frame_id: u32,
) {
assert!(td_size <= 0x1F);
assert!(tlbpc <= 0xF);
self.set(
buffer,
len | (u32::from(td_size) << 17) | (u32::from(interrupter) << 22),
u32::from(cycle)
| (u32::from(isp) << 2)
| (u32::from(chain) << 4)
| (u32::from(ioc) << 5)
| ((TrbType::Isoch as u32) << 10)
| (u32::from(tlbpc) << 16)
| ((sia_frame_id & 0xFFF) << 20),
)
}
pub fn is_command_trb(&self) -> bool {
let valid_trb_types = [
TrbType::NoOpCmd as u8,