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