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,