diff options
author | Jingkui Wang <jkwang@google.com> | 2019-03-08 00:17:58 -0800 |
---|---|---|
committer | chrome-bot <chrome-bot@chromium.org> | 2019-03-16 15:25:22 -0700 |
commit | bbd77ff220bebdd296420909ba2c0fc38620973b (patch) | |
tree | 75a4c394dd311d1c7198b4e96329e97310dd667e /devices/src/usb/xhci | |
parent | 0a5bf14261edd60641b75aeb60247001b5a3056f (diff) | |
download | crosvm-bbd77ff220bebdd296420909ba2c0fc38620973b.tar crosvm-bbd77ff220bebdd296420909ba2c0fc38620973b.tar.gz crosvm-bbd77ff220bebdd296420909ba2c0fc38620973b.tar.bz2 crosvm-bbd77ff220bebdd296420909ba2c0fc38620973b.tar.lz crosvm-bbd77ff220bebdd296420909ba2c0fc38620973b.tar.xz crosvm-bbd77ff220bebdd296420909ba2c0fc38620973b.tar.zst crosvm-bbd77ff220bebdd296420909ba2c0fc38620973b.zip |
usb: add usb hub, transfer and usb backend interface
Those are bridges between xhci and backend. CQ-DEPEND=CL:1510818 BUG=chromium:831850 TEST=cargo test Change-Id: I04feab449d48b0c908aeebfda08d1869239cbe6f Reviewed-on: https://chromium-review.googlesource.com/1510819 Commit-Ready: Jingkui Wang <jkwang@google.com> Tested-by: kokoro <noreply+kokoro@google.com> Tested-by: Zach Reizner <zachr@chromium.org> Reviewed-by: Zach Reizner <zachr@chromium.org>
Diffstat (limited to 'devices/src/usb/xhci')
-rw-r--r-- | devices/src/usb/xhci/mod.rs | 6 | ||||
-rw-r--r-- | devices/src/usb/xhci/usb_hub.rs | 278 | ||||
-rw-r--r-- | devices/src/usb/xhci/xhci_backend_device.rs | 33 | ||||
-rw-r--r-- | devices/src/usb/xhci/xhci_backend_device_provider.rs | 22 | ||||
-rw-r--r-- | devices/src/usb/xhci/xhci_transfer.rs | 441 |
5 files changed, 780 insertions, 0 deletions
diff --git a/devices/src/usb/xhci/mod.rs b/devices/src/usb/xhci/mod.rs index 1d3ed60..d2b748c 100644 --- a/devices/src/usb/xhci/mod.rs +++ b/devices/src/usb/xhci/mod.rs @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +extern crate usb_util; + mod event_ring; mod interrupter; mod intr_resample_handler; @@ -9,6 +11,10 @@ mod ring_buffer; mod ring_buffer_controller; mod ring_buffer_stop_cb; mod scatter_gather_buffer; +mod usb_hub; mod xhci_abi; mod xhci_abi_schema; +mod xhci_backend_device; +mod xhci_backend_device_provider; mod xhci_regs; +mod xhci_transfer; diff --git a/devices/src/usb/xhci/usb_hub.rs b/devices/src/usb/xhci/usb_hub.rs new file mode 100644 index 0000000..dd0a6d9 --- /dev/null +++ b/devices/src/usb/xhci/usb_hub.rs @@ -0,0 +1,278 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use super::interrupter::{Error as InterrupterError, Interrupter}; +use super::xhci_backend_device::{BackendType, XhciBackendDevice}; +use super::xhci_regs::{ + XhciRegs, MAX_PORTS, PORTSC_CONNECT_STATUS_CHANGE, PORTSC_CURRENT_CONNECT_STATUS, + PORTSC_PORT_ENABLED, PORTSC_PORT_ENABLED_DISABLED_CHANGE, USB2_PORTS_END, USB2_PORTS_START, + USB3_PORTS_END, USB3_PORTS_START, USB_STS_PORT_CHANGE_DETECT, +}; +use register_space::Register; +use std::fmt::{self, Display}; +use std::sync::{Arc, MutexGuard}; +use sync::Mutex; + +#[derive(Debug)] +pub enum Error { + AllPortsAttached, + AlreadyDetached(u8), + Attach { + port_id: u8, + reason: InterrupterError, + }, + Detach { + port_id: u8, + reason: InterrupterError, + }, + NoSuchDevice { + bus: u8, + addr: u8, + vid: u16, + pid: u16, + }, + NoSuchPort(u8), +} + +type Result<T> = std::result::Result<T, Error>; + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + AllPortsAttached => write!(f, "all suitable ports already attached"), + AlreadyDetached(port_id) => write!(f, "device already detached from port {}", port_id), + Attach { port_id, reason } => { + write!(f, "failed to attach device to port {}: {}", port_id, reason) + } + Detach { port_id, reason } => write!( + f, + "failed to detach device from port {}: {}", + port_id, reason + ), + NoSuchDevice { + bus, + addr, + vid, + pid, + } => write!( + f, + "device {}:{}:{:04x}:{:04x} is not attached", + bus, addr, vid, pid + ), + NoSuchPort(port_id) => write!(f, "port {} does not exist", port_id), + } + } +} + +/// A port on usb hub. It could have a device connected to it. +pub struct UsbPort { + ty: BackendType, + port_id: u8, + portsc: Register<u32>, + usbsts: Register<u32>, + interrupter: Arc<Mutex<Interrupter>>, + backend_device: Mutex<Option<Box<XhciBackendDevice>>>, +} + +impl UsbPort { + /// Create a new usb port that has nothing connected to it. + pub fn new( + ty: BackendType, + port_id: u8, + portsc: Register<u32>, + usbsts: Register<u32>, + interrupter: Arc<Mutex<Interrupter>>, + ) -> UsbPort { + UsbPort { + ty, + port_id, + portsc, + usbsts, + interrupter, + backend_device: Mutex::new(None), + } + } + + fn port_id(&self) -> u8 { + self.port_id + } + + /// Detach current connected backend. Returns false when there is no backend connected. + pub fn detach(&self) -> Result<()> { + let mut locked = self.backend_device.lock(); + if locked.is_none() { + return Err(Error::AlreadyDetached(self.port_id)); + } + usb_debug!("device detached from port {}", self.port_id); + *locked = None; + self.send_device_disconnected_event() + .map_err(|reason| Error::Detach { + port_id: self.port_id, + reason, + }) + } + + /// Get current connected backend. + pub fn get_backend_device(&self) -> MutexGuard<Option<Box<XhciBackendDevice>>> { + self.backend_device.lock() + } + + fn is_attached(&self) -> bool { + self.backend_device.lock().is_some() + } + + fn reset(&self) -> std::result::Result<(), InterrupterError> { + if self.is_attached() { + self.send_device_connected_event()?; + } + Ok(()) + } + + fn attach(&self, device: Box<XhciBackendDevice>) -> std::result::Result<(), InterrupterError> { + usb_debug!("A backend is connected to port {}", self.port_id); + let mut locked = self.backend_device.lock(); + assert!(locked.is_none()); + *locked = Some(device); + self.send_device_connected_event() + } + + /// Inform the guest kernel there is device connected to this port. It combines first few steps + /// of USB device initialization process in xHCI spec 4.3. + pub fn send_device_connected_event(&self) -> std::result::Result<(), InterrupterError> { + // xHCI spec 4.3. + self.portsc.set_bits( + PORTSC_CURRENT_CONNECT_STATUS + | PORTSC_PORT_ENABLED + | PORTSC_CONNECT_STATUS_CHANGE + | PORTSC_PORT_ENABLED_DISABLED_CHANGE, + ); + self.usbsts.set_bits(USB_STS_PORT_CHANGE_DETECT); + self.interrupter + .lock() + .send_port_status_change_trb(self.port_id) + } + + /// Inform the guest kernel that device has been detached. + pub fn send_device_disconnected_event(&self) -> std::result::Result<(), InterrupterError> { + // xHCI spec 4.3. + self.portsc + .set_bits(PORTSC_CONNECT_STATUS_CHANGE | PORTSC_PORT_ENABLED_DISABLED_CHANGE); + self.portsc.clear_bits(PORTSC_CURRENT_CONNECT_STATUS); + self.usbsts.set_bits(USB_STS_PORT_CHANGE_DETECT); + self.interrupter + .lock() + .send_port_status_change_trb(self.port_id) + } +} + +/// UsbHub is a set of usb ports. +pub struct UsbHub { + ports: Vec<Arc<UsbPort>>, +} + +impl UsbHub { + /// Create usb hub with no device attached. + pub fn new(regs: &XhciRegs, interrupter: Arc<Mutex<Interrupter>>) -> UsbHub { + let mut ports = Vec::new(); + // Each port should have a portsc register. + assert_eq!(MAX_PORTS as usize, regs.portsc.len()); + + for i in USB2_PORTS_START..USB2_PORTS_END { + ports.push(Arc::new(UsbPort::new( + BackendType::Usb2, + i + 1, + regs.portsc[i as usize].clone(), + regs.usbsts.clone(), + interrupter.clone(), + ))); + } + + for i in USB3_PORTS_START..USB3_PORTS_END { + ports.push(Arc::new(UsbPort::new( + BackendType::Usb3, + i + 1, + regs.portsc[i as usize].clone(), + regs.usbsts.clone(), + interrupter.clone(), + ))); + } + UsbHub { ports } + } + + /// Try to detach device of bus, addr, vid, pid + pub fn try_detach(&self, bus: u8, addr: u8, vid: u16, pid: u16) -> Result<()> { + for port in &self.ports { + if !port + .get_backend_device() + .as_ref() + .map(|d| { + d.host_bus() == bus + && d.host_address() == addr + && d.get_vid() == vid + && d.get_pid() == pid + }) + .unwrap_or(false) + { + continue; + } + return port.detach(); + } + Err(Error::NoSuchDevice { + bus, + addr, + vid, + pid, + }) + } + + /// Reset all ports. + pub fn reset(&self) -> Result<()> { + usb_debug!("reseting usb hub"); + for p in &self.ports { + p.reset().map_err(|reason| Error::Detach { + port_id: p.port_id(), + reason, + })?; + } + Ok(()) + } + + /// Get a specific port of the hub. + pub fn get_port(&self, port_id: u8) -> Option<Arc<UsbPort>> { + if port_id == 0 || port_id > MAX_PORTS { + return None; + } + let port_index = (port_id - 1) as usize; + Some(self.ports.get(port_index)?.clone()) + } + + /// Connect backend to next empty port. + pub fn connect_backend(&self, backend: Box<XhciBackendDevice>) -> Result<u8> { + usb_debug!("Trying to connect backend to hub"); + for port in &self.ports { + if port.is_attached() { + continue; + } + if port.ty != backend.get_backend_type() { + continue; + } + let port_id = port.port_id(); + port.attach(backend) + .map_err(|reason| Error::Attach { port_id, reason })?; + return Ok(port_id); + } + Err(Error::AllPortsAttached) + } + + /// Disconnect device from port. Returns false if port id is not valid or could not be + /// disonnected. + pub fn disconnect_port(&self, port_id: u8) -> Result<()> { + match self.get_port(port_id) { + Some(port) => port.detach(), + None => Err(Error::NoSuchPort(port_id)), + } + } +} diff --git a/devices/src/usb/xhci/xhci_backend_device.rs b/devices/src/usb/xhci/xhci_backend_device.rs new file mode 100644 index 0000000..51fefa2 --- /dev/null +++ b/devices/src/usb/xhci/xhci_backend_device.rs @@ -0,0 +1,33 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use super::xhci_transfer::XhciTransfer; + +/// Address of this usb device, as in Set Address standard usb device request. +pub type UsbDeviceAddress = u32; + +/// The type USB device provided by the backend device. +#[derive(PartialEq, Eq)] +pub enum BackendType { + Usb2, + Usb3, +} + +/// Xhci backend device is a virtual device connected to xHCI controller. It handles xhci transfers. +pub trait XhciBackendDevice: Send { + /// Returns the type of USB device provided by this device. + fn get_backend_type(&self) -> BackendType; + /// Returns host bus number of this device. + fn host_bus(&self) -> u8; + /// Returns host address of this device. + fn host_address(&self) -> u8; + /// Get vendor id of this device. + fn get_vid(&self) -> u16; + /// Get product id of this device. + fn get_pid(&self) -> u16; + /// Submit a xhci transfer to backend. + fn submit_transfer(&mut self, transfer: XhciTransfer) -> std::result::Result<(), ()>; + /// Set address of this backend. + fn set_address(&mut self, address: UsbDeviceAddress); +} diff --git a/devices/src/usb/xhci/xhci_backend_device_provider.rs b/devices/src/usb/xhci/xhci_backend_device_provider.rs new file mode 100644 index 0000000..2c9d4bf --- /dev/null +++ b/devices/src/usb/xhci/xhci_backend_device_provider.rs @@ -0,0 +1,22 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use super::usb_hub::UsbHub; +use std::os::unix::io::RawFd; +use std::sync::Arc; +use utils::{EventLoop, FailHandle}; + +/// Xhci backend provider will run on an EventLoop and connect new devices to usb ports. +pub trait XhciBackendDeviceProvider: Send { + /// Start the provider on EventLoop. + fn start( + &mut self, + fail_handle: Arc<FailHandle>, + event_loop: Arc<EventLoop>, + hub: Arc<UsbHub>, + ) -> std::result::Result<(), ()>; + + /// Keep fds that should be kept open. + fn keep_fds(&self) -> Vec<RawFd>; +} diff --git a/devices/src/usb/xhci/xhci_transfer.rs b/devices/src/usb/xhci/xhci_transfer.rs new file mode 100644 index 0000000..9dc64ea --- /dev/null +++ b/devices/src/usb/xhci/xhci_transfer.rs @@ -0,0 +1,441 @@ +// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use super::interrupter::{Error as InterrupterError, Interrupter}; +use super::scatter_gather_buffer::{Error as BufferError, ScatterGatherBuffer}; +use super::usb_hub::{Error as HubError, UsbPort}; +use super::usb_util::types::UsbRequestSetup; +use super::usb_util::usb_transfer::TransferStatus; +use super::xhci_abi::{ + AddressedTrb, Error as TrbError, EventDataTrb, SetupStageTrb, TransferDescriptor, TrbCast, + TrbCompletionCode, TrbType, +}; +use super::xhci_regs::MAX_INTERRUPTER; +use std::cmp::min; +use std::fmt::{self, Display}; +use std::mem; +use std::sync::{Arc, Weak}; +use sync::Mutex; +use sys_util::{Error as SysError, EventFd, GuestMemory}; + +#[derive(Debug)] +pub enum Error { + TrbType(TrbError), + CastTrb(TrbError), + TransferLength(TrbError), + BadTrbType(TrbType), + WriteCompletionEvent(SysError), + CreateBuffer(BufferError), + DetachPort(HubError), + SendInterrupt(InterrupterError), + SubmitTransfer, +} + +type Result<T> = std::result::Result<T, Error>; + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + match self { + TrbType(e) => write!(f, "cannot get trb type: {}", e), + CastTrb(e) => write!(f, "cannot cast trb: {}", e), + TransferLength(e) => write!(f, "cannot get transfer length: {}", e), + BadTrbType(t) => write!(f, "unexpected trb type: {:?}", t), + WriteCompletionEvent(e) => write!(f, "cannot write completion event: {}", e), + CreateBuffer(e) => write!(f, "cannot create transfer buffer: {}", e), + DetachPort(e) => write!(f, "cannot detach from port: {}", e), + SendInterrupt(e) => write!(f, "cannot send interrupter: {}", e), + SubmitTransfer => write!(f, "failed to submit transfer to backend"), + } + } +} + +/// Type of usb endpoints. +#[derive(PartialEq, Clone, Copy, Debug)] +pub enum TransferDirection { + In, + Out, + Control, +} + +/// Current state of xhci transfer. +pub enum XhciTransferState { + Created, + /// When transfer is submitted, it will contain a transfer callback, which should be invoked + /// when the transfer is cancelled. + Submitted { + cancel_callback: Box<FnMut() + Send>, + }, + Cancelling, + Cancelled, + Completed, +} + +impl XhciTransferState { + /// Try to cancel this transfer, if it's possible. + pub fn try_cancel(&mut self) { + match mem::replace(self, XhciTransferState::Created) { + XhciTransferState::Submitted { + mut cancel_callback, + } => { + *self = XhciTransferState::Cancelling; + cancel_callback(); + } + XhciTransferState::Cancelling => { + error!("Another cancellation is already issued."); + } + _ => { + *self = XhciTransferState::Cancelled; + } + } + } +} + +/// Type of a transfer received handled by transfer ring. +pub enum XhciTransferType { + // Normal means bulk transfer or interrupt transfer, depending on endpoint type. + // See spec 4.11.2.1. + Normal(ScatterGatherBuffer), + // See usb spec for setup stage, data stage and status stage, + // see xHCI spec 4.11.2.2 for corresponding trbs. + SetupStage(UsbRequestSetup), + DataStage(ScatterGatherBuffer), + StatusStage, + // See xHCI spec 4.11.2.3. + Isochronous(ScatterGatherBuffer), + // See xHCI spec 6.4.1.4. + Noop, +} + +impl XhciTransferType { + /// Analyze transfer descriptor and return transfer type. + pub fn new(mem: GuestMemory, td: TransferDescriptor) -> Result<XhciTransferType> { + // We can figure out transfer type from the first trb. + // See transfer descriptor description in xhci spec for more details. + match td[0].trb.trb_type().map_err(Error::TrbType)? { + TrbType::Normal => { + let buffer = ScatterGatherBuffer::new(mem, td).map_err(Error::CreateBuffer)?; + Ok(XhciTransferType::Normal(buffer)) + } + TrbType::SetupStage => { + let trb = td[0].trb.cast::<SetupStageTrb>().map_err(Error::CastTrb)?; + Ok(XhciTransferType::SetupStage(UsbRequestSetup::new( + trb.get_request_type(), + trb.get_request(), + trb.get_value(), + trb.get_index(), + trb.get_length(), + ))) + } + TrbType::DataStage => { + let buffer = ScatterGatherBuffer::new(mem, td).map_err(Error::CreateBuffer)?; + Ok(XhciTransferType::DataStage(buffer)) + } + TrbType::StatusStage => Ok(XhciTransferType::StatusStage), + TrbType::Isoch => { + let buffer = ScatterGatherBuffer::new(mem, td).map_err(Error::CreateBuffer)?; + Ok(XhciTransferType::Isochronous(buffer)) + } + TrbType::Noop => Ok(XhciTransferType::Noop), + t => Err(Error::BadTrbType(t)), + } + } +} + +/// Xhci Transfer manager holds reference to all ongoing transfers. Can cancel them all if +/// needed. +#[derive(Clone)] +pub struct XhciTransferManager { + transfers: Arc<Mutex<Vec<Weak<Mutex<XhciTransferState>>>>>, +} + +impl XhciTransferManager { + /// Create a new manager. + pub fn new() -> XhciTransferManager { + XhciTransferManager { + transfers: Arc::new(Mutex::new(Vec::new())), + } + } + + /// Build a new XhciTransfer. Endpoint id is the id in xHCI device slot. + pub fn create_transfer( + &self, + mem: GuestMemory, + port: Arc<UsbPort>, + interrupter: Arc<Mutex<Interrupter>>, + slot_id: u8, + endpoint_id: u8, + transfer_trbs: TransferDescriptor, + completion_event: EventFd, + ) -> XhciTransfer { + assert!(!transfer_trbs.is_empty()); + let transfer_dir = { + if endpoint_id == 0 { + TransferDirection::Control + } else if (endpoint_id % 2) == 0 { + TransferDirection::Out + } else { + TransferDirection::In + } + }; + let t = XhciTransfer { + manager: self.clone(), + state: Arc::new(Mutex::new(XhciTransferState::Created)), + mem, + port, + interrupter, + transfer_completion_event: completion_event, + slot_id, + endpoint_id, + transfer_dir, + transfer_trbs, + }; + self.transfers.lock().push(Arc::downgrade(&t.state)); + t + } + + /// Cancel all current transfers. + pub fn cancel_all(&self) { + self.transfers.lock().iter().for_each(|t| { + let state = match t.upgrade() { + Some(state) => state, + None => { + error!("transfer is already cancelled or finished"); + return; + } + }; + state.lock().try_cancel(); + }); + } + + fn remove_transfer(&self, t: &Arc<Mutex<XhciTransferState>>) { + let mut transfers = self.transfers.lock(); + match transfers.iter().position(|wt| match wt.upgrade() { + Some(wt) => Arc::ptr_eq(&wt, t), + None => false, + }) { + None => error!("attempted to remove unknow transfer"), + Some(i) => { + transfers.swap_remove(i); + } + } + } +} + +/// Xhci transfer denotes a transfer initiated by guest os driver. It will be submitted to a +/// XhciBackendDevice. +pub struct XhciTransfer { + manager: XhciTransferManager, + state: Arc<Mutex<XhciTransferState>>, + mem: GuestMemory, + port: Arc<UsbPort>, + interrupter: Arc<Mutex<Interrupter>>, + slot_id: u8, + // id of endpoint in device slot. + endpoint_id: u8, + transfer_dir: TransferDirection, + transfer_trbs: TransferDescriptor, + transfer_completion_event: EventFd, +} + +impl Drop for XhciTransfer { + fn drop(&mut self) { + self.manager.remove_transfer(&self.state); + } +} + +impl fmt::Debug for XhciTransfer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "xhci_transfer slot id: {}, endpoint id {}, transfer_dir {:?}, transfer_trbs {:?}", + self.slot_id, self.endpoint_id, self.transfer_dir, self.transfer_trbs + ) + } +} + +impl XhciTransfer { + /// Get state of this transfer. + pub fn state(&self) -> &Arc<Mutex<XhciTransferState>> { + &self.state + } + + /// Get transfer type. + pub fn get_transfer_type(&self) -> Result<XhciTransferType> { + XhciTransferType::new(self.mem.clone(), self.transfer_trbs.clone()) + } + + /// Get endpoint number. + pub fn get_endpoint_number(&self) -> u8 { + // See spec 4.5.1 for dci. + self.endpoint_id / 2 + } + + /// get transfer direction. + pub fn get_transfer_dir(&self) -> TransferDirection { + self.transfer_dir + } + + /// This functions should be invoked when transfer is completed (or failed). + pub fn on_transfer_complete( + &self, + status: &TransferStatus, + bytes_transferred: u32, + ) -> Result<()> { + match status { + TransferStatus::NoDevice => { + usb_debug!("device disconnected, detaching from port"); + // If the device is gone, we don't need to send transfer completion event, cause we + // are going to destroy everything related to this device anyway. + self.port.detach().map_err(Error::DetachPort)?; + return Ok(()); + } + TransferStatus::Cancelled => { + // TODO(jkwang) According to the spec, we should send a stopped event here. But + // kernel driver does not do anything meaningful when it sees a stopped event. + return self + .transfer_completion_event + .write(1) + .map_err(Error::WriteCompletionEvent); + } + TransferStatus::Completed => { + self.transfer_completion_event + .write(1) + .map_err(Error::WriteCompletionEvent)?; + } + _ => { + // Transfer failed, we are not handling this correctly yet. Guest kernel might see + // short packets for in transfer and might think control transfer is successful. It + // will eventually find out device is in a wrong state. + self.transfer_completion_event + .write(1) + .map_err(Error::WriteCompletionEvent)?; + } + } + + let mut edtla: u32 = 0; + // TODO(jkwang) Send event based on Status. + // As noted in xHCI spec 4.11.3.1 + // Transfer Event Trb only occurs under the following conditions: + // 1. If the Interrupt On Completion flag is set. + // 2. When a short transfer occurs during the execution of a Transfer TRB and the + // Interrupter-on-Short Packet flag is set. + // 3. If an error occurs during the execution of a Transfer Trb. + for atrb in &self.transfer_trbs { + edtla += atrb.trb.transfer_length().map_err(Error::TransferLength)?; + if atrb.trb.interrupt_on_completion() { + // For details about event data trb and EDTLA, see spec 4.11.5.2. + if atrb.trb.trb_type().map_err(Error::TrbType)? == TrbType::EventData { + let tlength = min(edtla, bytes_transferred); + self.interrupter + .lock() + .send_transfer_event_trb( + TrbCompletionCode::Success, + atrb.trb + .cast::<EventDataTrb>() + .map_err(Error::CastTrb)? + .get_event_data(), + tlength, + true, + self.slot_id, + self.endpoint_id, + ) + .map_err(Error::SendInterrupt)?; + } else { + // For Short Transfer details, see xHCI spec 4.10.1.1. + if edtla > bytes_transferred { + usb_debug!("on transfer complete short packet"); + let residual_transfer_length = edtla - bytes_transferred; + self.interrupter + .lock() + .send_transfer_event_trb( + TrbCompletionCode::ShortPacket, + atrb.gpa, + residual_transfer_length, + true, + self.slot_id, + self.endpoint_id, + ) + .map_err(Error::SendInterrupt)?; + } else { + usb_debug!("on transfer complete success"); + self.interrupter + .lock() + .send_transfer_event_trb( + TrbCompletionCode::Success, + atrb.gpa, + 0, // transfer length + true, + self.slot_id, + self.endpoint_id, + ) + .map_err(Error::SendInterrupt)?; + } + } + } + } + Ok(()) + } + + /// Send this transfer to backend if it's a valid transfer. + pub fn send_to_backend_if_valid(self) -> Result<()> { + if self.validate_transfer()? { + // Backend should invoke on transfer complete when transfer is completed. + let port = self.port.clone(); + let mut backend = port.get_backend_device(); + match &mut *backend { + Some(backend) => backend + .submit_transfer(self) + .map_err(|_| Error::SubmitTransfer)?, + None => { + error!("backend is already disconnected"); + self.transfer_completion_event + .write(1) + .map_err(Error::WriteCompletionEvent)?; + } + } + } else { + error!("invalid td on transfer ring"); + self.transfer_completion_event + .write(1) + .map_err(Error::WriteCompletionEvent)?; + } + Ok(()) + } + + // Check each trb in the transfer descriptor for invalid or out of bounds + // parameters. Returns true iff the transfer descriptor is valid. + fn validate_transfer(&self) -> Result<bool> { + let mut valid = true; + for atrb in &self.transfer_trbs { + if !trb_is_valid(&atrb) { + self.interrupter + .lock() + .send_transfer_event_trb( + TrbCompletionCode::TrbError, + atrb.gpa, + 0, + false, + self.slot_id, + self.endpoint_id, + ) + .map_err(Error::SendInterrupt)?; + valid = false; + } + } + Ok(valid) + } +} + +fn trb_is_valid(atrb: &AddressedTrb) -> bool { + let can_be_in_transfer_ring = match atrb.trb.can_be_in_transfer_ring() { + Ok(v) => v, + Err(e) => { + error!("unknown error {:?}", e); + return false; + } + }; + can_be_in_transfer_ring && (atrb.trb.interrupter_target() < MAX_INTERRUPTER) +} |