From c89af69d08aca1390710fa0fa0e490d971fe4aeb Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Tue, 7 Jul 2026 14:55:03 +0300 Subject: [PATCH] =?UTF-8?q?USB:=20P6-C=20isochronous=20transfer=20support?= =?UTF-8?q?=20=E2=80=94=20remove=20ENOSYS,=20add=20Trb::isoch()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- drivers/usb/xhcid/src/xhci/scheme.rs | 78 ++++++++++++++++++---------- drivers/usb/xhcid/src/xhci/trb.rs | 27 ++++++++++ 2 files changed, 79 insertions(+), 26 deletions(-) diff --git a/drivers/usb/xhcid/src/xhci/scheme.rs b/drivers/usb/xhcid/src/xhci/scheme.rs index a06bdf2c51..4ffbd1b417 100644 --- a/drivers/usb/xhcid/src/xhci/scheme.rs +++ b/drivers/usb/xhcid/src/xhci/scheme.rs @@ -1700,10 +1700,7 @@ impl Xhci { .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 Xhci { |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 Xhci { .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)) } diff --git a/drivers/usb/xhcid/src/xhci/trb.rs b/drivers/usb/xhcid/src/xhci/trb.rs index e0e5dc79fe..ef8beaa76b 100644 --- a/drivers/usb/xhcid/src/xhci/trb.rs +++ b/drivers/usb/xhcid/src/xhci/trb.rs @@ -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,