From a9afccf26dd177fbc7a7db65d1b78fb2b14636d0 Mon Sep 17 00:00:00 2001 From: goriainovstanislav Date: Thu, 11 Apr 2024 17:24:48 +0300 Subject: [PATCH] usb: Add support for USB Stream device passthrough Add alloc_streams and free_streams to UsbDevice trait and implement them for USB Host device, thus allowing stream device passthrough. Signed-off-by: goriainovstanislav --- devices/src/usb/descriptor.rs | 9 ++ devices/src/usb/mod.rs | 21 +++++ devices/src/usb/usbhost/host_usblib.rs | 4 +- devices/src/usb/usbhost/mod.rs | 88 ++++++++++++++++++- devices/src/usb/xhci/xhci_controller.rs | 110 +++++++++++++++++++++++- 5 files changed, 226 insertions(+), 6 deletions(-) diff --git a/devices/src/usb/descriptor.rs b/devices/src/usb/descriptor.rs index 55858cd4..6f396903 100644 --- a/devices/src/usb/descriptor.rs +++ b/devices/src/usb/descriptor.rs @@ -550,6 +550,15 @@ impl UsbDescriptorOps for UsbDeviceBase { let usb_ep = self.get_mut_endpoint(in_direction, ep); usb_ep.ep_type = iface.endpoints[e as usize].endpoint_desc.bmAttributes & USB_ENDPOINT_ATTR_TRANSFER_TYPE_MASK; + usb_ep + .set_max_packet_size(iface.endpoints[e as usize].endpoint_desc.wMaxPacketSize); + let extra = &iface.endpoints[e as usize].extra; + if !extra.is_empty() + && extra[1] == USB_DT_ENDPOINT_COMPANION + && extra[0] == USB_DT_SS_EP_COMP_SIZE + { + usb_ep.set_max_streams(extra[3]); + } } } Ok(()) diff --git a/devices/src/usb/mod.rs b/devices/src/usb/mod.rs index 110152b8..f6b53590 100644 --- a/devices/src/usb/mod.rs +++ b/devices/src/usb/mod.rs @@ -145,6 +145,7 @@ pub struct UsbEndpoint { pub ifnum: u8, pub halted: bool, pub max_packet_size: u32, + pub max_streams: u32, } impl UsbEndpoint { @@ -167,6 +168,16 @@ impl UsbEndpoint { self.max_packet_size = u32::from(size) * micro_frames; } + + pub fn set_max_streams(&mut self, raw: u8) { + let max_streams = raw & 0x1f; + + if max_streams != 0 { + self.max_streams = 1 << max_streams; + } else { + self.max_streams = 0; + } + } } /// USB device common structure. @@ -407,6 +418,16 @@ pub trait UsbDevice: Send + Sync { Ok(()) } + /// Allocate streams on this device. + fn alloc_streams(&mut self, _endpoints: &[UsbEndpoint], _streams_nr: u32) -> Result<()> { + Ok(()) + } + + /// Free streams on this device. + fn free_streams(&mut self, _endpoints: &[UsbEndpoint]) -> Result<()> { + Ok(()) + } + /// Cancel specified USB packet. fn cancel_packet(&mut self, packet: &Arc>); diff --git a/devices/src/usb/usbhost/host_usblib.rs b/devices/src/usb/usbhost/host_usblib.rs index 8c5542af..4ea06ccd 100644 --- a/devices/src/usb/usbhost/host_usblib.rs +++ b/devices/src/usb/usbhost/host_usblib.rs @@ -266,6 +266,7 @@ pub fn fill_transfer_by_type( transfer: *mut libusb_transfer, handle: Option<&mut DeviceHandle>, ep_number: u8, + stream: u32, node: *mut Node, transfer_type: TransferType, ) { @@ -298,10 +299,11 @@ pub fn fill_transfer_by_type( TransferType::Bulk => // SAFETY: the reason is as shown above. unsafe { - libusb1_sys::libusb_fill_bulk_transfer( + libusb1_sys::libusb_fill_bulk_stream_transfer( transfer, handle.unwrap().as_raw(), ep_number, + stream, buffer_ptr, size as i32, req_complete, diff --git a/devices/src/usb/usbhost/mod.rs b/devices/src/usb/usbhost/mod.rs index 4dcade11..b0fc4fad 100644 --- a/devices/src/usb/usbhost/mod.rs +++ b/devices/src/usb/usbhost/mod.rs @@ -24,7 +24,7 @@ use std::{ #[cfg(not(all(target_arch = "aarch64", target_env = "ohos")))] use anyhow::Context as anyhowContext; -use anyhow::{anyhow, Result}; +use anyhow::{anyhow, bail, Result}; use clap::Parser; use libc::c_int; use libusb1_sys::{ @@ -36,6 +36,8 @@ use rusb::{ TransferType, UsbContext, }; +use super::{USB_DT_ENDPOINT_COMPANION, USB_DT_SS_EP_COMP_SIZE}; + use crate::usb::{ config::{ USB_DEVICE_OUT_REQUEST, USB_DIRECTION_DEVICE_TO_HOST, USB_ENDPOINT_ATTR_BULK, @@ -653,6 +655,12 @@ impl UsbHost { usb_ep.ep_type = ep_type; usb_ep.ifnum = i as u8; usb_ep.halted = false; + + if let Some(extra) = ep.extra() { + if extra[1] == USB_DT_ENDPOINT_COMPANION && extra[0] == USB_DT_SS_EP_COMP_SIZE { + usb_ep.set_max_streams(extra[3]); + } + } } } } @@ -1048,9 +1056,79 @@ fn register_exit(usbhost: Arc>) { ); } +fn endpoints_to_numbers(endpoints: &[UsbEndpoint]) -> Vec { + let mut ep_numbers = Vec::new(); + + for ep in endpoints.iter() { + if ep.in_direction { + ep_numbers.push(ep.ep_number | USB_DIRECTION_DEVICE_TO_HOST); + } else { + ep_numbers.push(ep.ep_number); + } + } + + ep_numbers +} + impl UsbDevice for UsbHost { gen_base_func!(usb_device_base, usb_device_base_mut, UsbDeviceBase, base); + fn alloc_streams(&mut self, endpoints: &[UsbEndpoint], streams_nr: u32) -> Result<()> { + if self.handle.is_none() { + bail!( + "USB Host {} device failed to allocate {} streams: device handle is empty.", + self.device_id(), + streams_nr, + ); + } + + let mut ep_nrs = endpoints_to_numbers(endpoints); + + // SAFETY: Handle was checked to be Some and endpoints slice is guaranteed to outlive the + // libusb_alloc_streams function call. + let ret = unsafe { + libusb1_sys::libusb_alloc_streams( + self.handle.as_ref().unwrap().as_raw(), + streams_nr, + ep_nrs.as_mut_ptr(), + ep_nrs.len() as i32, + ) + }; + + if ret != streams_nr as i32 { + bail!( + "USB Host {} device failed to allocate {} streams: libusb error.", + self.device_id(), + streams_nr + ); + } + + Ok(()) + } + + fn free_streams(&mut self, endpoints: &[UsbEndpoint]) -> Result<()> { + if self.handle.is_none() { + bail!( + "USB Host {} device failed to free streams: device handle is empty.", + self.device_id(), + ); + } + + let mut ep_nrs = endpoints_to_numbers(endpoints); + + // SAFETY: Handle was checked to be Some and endpoints slice is guaranteed to outlive the + // libusb_free_streams function call. + unsafe { + libusb1_sys::libusb_free_streams( + self.handle.as_ref().unwrap().as_raw(), + ep_nrs.as_mut_ptr(), + ep_nrs.len() as i32, + ); + }; + + Ok(()) + } + fn realize(mut self) -> Result>> { info!("Open and init usbhost device: {:?}", self.config); self.open_usbdev()?; @@ -1183,6 +1261,7 @@ impl UsbDevice for UsbHost { host_transfer, self.handle.as_mut(), 0, + 0, &mut (*node) as *mut Node, TransferType::Control, ); @@ -1231,9 +1310,10 @@ impl UsbDevice for UsbHost { return; } - drop(locked_packet); - let mut ep_number = packet.lock().unwrap().ep_number; + let mut ep_number = locked_packet.ep_number; + let stream = locked_packet.stream; let host_transfer: *mut libusb_transfer; + drop(locked_packet); match self.base.get_endpoint(in_direction, ep_number).ep_type { USB_ENDPOINT_ATTR_BULK => { @@ -1255,6 +1335,7 @@ impl UsbDevice for UsbHost { host_transfer, self.handle.as_mut(), ep_number, + stream, &mut (*node) as *mut Node, TransferType::Bulk, ); @@ -1279,6 +1360,7 @@ impl UsbDevice for UsbHost { host_transfer, self.handle.as_mut(), ep_number, + stream, &mut (*node) as *mut Node, TransferType::Interrupt, ); diff --git a/devices/src/usb/xhci/xhci_controller.rs b/devices/src/usb/xhci/xhci_controller.rs index 14cd1351..1cae873f 100644 --- a/devices/src/usb/xhci/xhci_controller.rs +++ b/devices/src/usb/xhci/xhci_controller.rs @@ -19,7 +19,7 @@ use std::time::Duration; use anyhow::{anyhow, bail, Context, Result}; use byteorder::{ByteOrder, LittleEndian}; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use super::xhci_pci::XhciConfig; use super::xhci_regs::{XhciInterrupter, XhciOperReg}; @@ -30,7 +30,7 @@ use super::xhci_trb::{ TRB_TYPE_SHIFT, }; use crate::usb::{config::*, TransferOps}; -use crate::usb::{UsbDevice, UsbDeviceRequest, UsbError, UsbPacket, UsbPacketStatus}; +use crate::usb::{UsbDevice, UsbDeviceRequest, UsbEndpoint, UsbError, UsbPacket, UsbPacketStatus}; use address_space::{AddressAttr, AddressSpace, GuestAddress}; use machine_manager::event_loop::EventLoop; @@ -1504,6 +1504,7 @@ impl XhciDevice { error!("Invalid control context {:?}", ictl_ctx); return Ok(TRBCCode::TrbError); } + self.free_slot_streams(slot_id, ictl_ctx.add_flags | ictl_ctx.drop_flags)?; let octx = self.slots[(slot_id - 1) as usize].slot_ctx_addr; for i in 2..32 { if ictl_ctx.drop_flags & (1 << i) == 1 << i { @@ -1514,6 +1515,15 @@ impl XhciDevice { self.enable_endpoint(slot_id, i, ictx, octx.raw_value())?; } } + let res = self.alloc_slot_streams(slot_id, ictl_ctx.add_flags)?; + if res != TRBCCode::Success { + for i in 2..32 { + if ictl_ctx.add_flags & (1 << i) == (1 << i) { + self.disable_endpoint(slot_id, i)?; + } + } + return Ok(res); + } // From section 4.6.6 Configure Endpoint of the spec: // If all Endpoints are Disabled: // Set the Slot State in the Output Slot Context to Addressed. @@ -1540,6 +1550,102 @@ impl XhciDevice { Ok(TRBCCode::Success) } + fn alloc_slot_streams(&mut self, slot_id: u32, mask: u32) -> Result { + let (endpoints, ep_ctxs) = self.mask_to_endpoints(slot_id, mask)?; + + if endpoints.is_empty() { + return Ok(TRBCCode::Success); + } + + let mut streams_nr = 1 << (ep_ctxs[0].max_pstreams + 1); + let max_streams = endpoints[0].max_streams; + + for (ep, ctx) in std::iter::zip(endpoints.iter(), ep_ctxs.iter()) { + if max_streams != ep.max_streams { + error!( + "XHCI device failed to allocate streams on slot {}: \ + endpoints max streams differ.", + slot_id + ); + return Ok(TRBCCode::ResourceError); + } + + if streams_nr != 1 << (ctx.max_pstreams + 1) { + error!( + "XHCI device failed to allocate streams on slot {}: \ + endpoints stream numbers differ.", + slot_id + ); + return Ok(TRBCCode::ResourceError); + } + } + + streams_nr = std::cmp::min(streams_nr, max_streams); + let dev = self.get_usb_dev(slot_id, 0)?; + let mut locked_dev = dev.lock().unwrap(); + + if let Err(err) = locked_dev.alloc_streams(&endpoints, streams_nr) { + error!( + "XHCI device failed to allocate streams on slot {}: {:#?}", + slot_id, err + ); + Ok(TRBCCode::ResourceError) + } else { + Ok(TRBCCode::Success) + } + } + + fn free_slot_streams(&mut self, slot_id: u32, mask: u32) -> Result<()> { + let (endpoints, _) = self.mask_to_endpoints(slot_id, mask)?; + let dev = self.get_usb_dev(slot_id, 0)?; + let mut locked_dev = dev.lock().unwrap(); + + locked_dev.free_streams(&endpoints).with_context(|| { + format!( + "XHCI device failed to free streams on slot {}, the mask is {}.", + slot_id, mask + ) + })?; + Ok(()) + } + + fn mask_to_endpoints( + &mut self, + slot_id: u32, + mask: u32, + ) -> Result<(Vec, Vec<&XhciEpContext>)> { + let mut endpoints = Vec::new(); + let mut ep_ctxs = Vec::new(); + let dev = self.get_usb_dev(slot_id, 0)?; + let locked_dev = dev.lock().unwrap(); + let slot = &self.slots[slot_id as usize - 1]; + + for i in 2..32 { + if mask & (1 << i) != (1 << i) { + continue; + } + + let epctx = &slot.endpoints[i - 1]; + let (in_direction, ep_number) = endpoint_id_to_number(epctx.epid as u8); + let ep = *locked_dev + .usb_device_base() + .get_endpoint(in_direction, ep_number); + + if ep.max_streams == 0 || epctx.max_pstreams == 0 { + continue; + } + + debug!( + "Found stream endpoint on slot {}, epctx max_pstreams {}, ep max_streams {}.", + slot_id, epctx.max_pstreams, ep.max_streams + ); + ep_ctxs.push(epctx); + endpoints.push(ep); + } + + Ok((endpoints, ep_ctxs)) + } + fn evaluate_context(&mut self, slot_id: u32, trb: &XhciTRB) -> Result { trace::usb_xhci_evaluate_context(&slot_id); if !self.slots[(slot_id - 1) as usize].slot_state_is_valid(&self.mem_space)? { -- Gitee