summary refs log tree commit diff
path: root/devices/src/usb/xhci
diff options
context:
space:
mode:
authorJingkui Wang <jkwang@google.com>2019-03-08 00:17:58 -0800
committerchrome-bot <chrome-bot@chromium.org>2019-03-16 15:25:22 -0700
commitbbd77ff220bebdd296420909ba2c0fc38620973b (patch)
tree75a4c394dd311d1c7198b4e96329e97310dd667e /devices/src/usb/xhci
parent0a5bf14261edd60641b75aeb60247001b5a3056f (diff)
downloadcrosvm-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.rs6
-rw-r--r--devices/src/usb/xhci/usb_hub.rs278
-rw-r--r--devices/src/usb/xhci/xhci_backend_device.rs33
-rw-r--r--devices/src/usb/xhci/xhci_backend_device_provider.rs22
-rw-r--r--devices/src/usb/xhci/xhci_transfer.rs441
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)
+}