summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--arch/src/lib.rs1
-rw-r--r--devices/Cargo.toml1
-rw-r--r--devices/src/lib.rs13
-rw-r--r--devices/src/pci/mod.rs3
-rw-r--r--devices/src/usb/host_backend/context.rs7
-rw-r--r--devices/src/usb/host_backend/mod.rs2
-rw-r--r--devices/src/usb/mod.rs2
-rw-r--r--devices/src/usb/xhci/mod.rs12
-rw-r--r--devices/src/usb/xhci/xhci.rs387
-rw-r--r--devices/src/usb/xhci/xhci_controller.rs277
-rw-r--r--seccomp/arm/xhci.policy40
-rw-r--r--seccomp/x86_64/xhci.policy38
-rw-r--r--src/linux.rs16
-rw-r--r--src/main.rs212
-rw-r--r--vm_control/src/lib.rs15
16 files changed, 999 insertions, 29 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 253604d..ed06b35 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,9 +25,9 @@ default-no-sandbox = []
 wl-dmabuf = ["devices/wl-dmabuf", "gpu_buffer", "resources/wl-dmabuf"]
 gpu = ["devices/gpu"]
 usb-emulation = ["usb_util"]
-sandboxed-libusb = ["usb_util/sandboxed-libusb"]
 tpm = ["devices/tpm"]
 gpu-forward = ["render_node_forward"]
+sandboxed-libusb = ["devices/sandboxed-libusb", "usb_util/sandboxed-libusb", "vm_control/sandboxed-libusb"]
 
 [dependencies]
 arch = { path = "arch" }
diff --git a/arch/src/lib.rs b/arch/src/lib.rs
index 38bc2c7..f089972 100644
--- a/arch/src/lib.rs
+++ b/arch/src/lib.rs
@@ -190,6 +190,7 @@ pub fn generate_pci_root(
             pid_labels.insert(proxy.pid() as u32, proxy.debug_label());
             Arc::new(Mutex::new(proxy))
         } else {
+            device.on_sandboxed();
             Arc::new(Mutex::new(device))
         };
         root.add_device(arced_dev.clone());
diff --git a/devices/Cargo.toml b/devices/Cargo.toml
index 352135b..13ccb22 100644
--- a/devices/Cargo.toml
+++ b/devices/Cargo.toml
@@ -7,6 +7,7 @@ authors = ["The Chromium OS Authors"]
 wl-dmabuf = []
 gpu = ["gpu_buffer", "gpu_display", "gpu_renderer"]
 tpm = ["tpm2"]
+sandboxed-libusb = []
 
 [dependencies]
 audio_streams = "*"
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 442939e..dae8d3f 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -23,14 +23,11 @@ extern crate sync;
 extern crate sys_util;
 #[cfg(feature = "tpm")]
 extern crate tpm2;
+extern crate usb_util;
 extern crate vhost;
 extern crate virtio_sys;
 extern crate vm_control;
 
-#[allow(dead_code)]
-#[macro_use]
-mod register_space;
-
 mod bus;
 mod cmos;
 mod i8042;
@@ -39,10 +36,10 @@ mod pci;
 mod pit;
 pub mod pl030;
 mod proxy;
+#[macro_use]
+mod register_space;
 mod serial;
-#[allow(dead_code)]
-mod usb;
-#[allow(dead_code)]
+pub mod usb;
 mod utils;
 pub mod virtio;
 
@@ -59,4 +56,6 @@ pub use self::pl030::Pl030;
 pub use self::proxy::Error as ProxyError;
 pub use self::proxy::ProxyDevice;
 pub use self::serial::Serial;
+pub use self::usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
+pub use self::usb::xhci::xhci_controller::XhciController;
 pub use self::virtio::VirtioPciDevice;
diff --git a/devices/src/pci/mod.rs b/devices/src/pci/mod.rs
index 3aab059..791161a 100644
--- a/devices/src/pci/mod.rs
+++ b/devices/src/pci/mod.rs
@@ -15,7 +15,8 @@ mod pci_root;
 pub use self::ac97::Ac97Dev;
 pub use self::pci_configuration::{
     PciBarConfiguration, PciBarPrefetchable, PciBarRegionType, PciCapability, PciCapabilityID,
-    PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface, PciSubclass,
+    PciClassCode, PciConfiguration, PciHeaderType, PciProgrammingInterface, PciSerialBusSubClass,
+    PciSubclass,
 };
 pub use self::pci_device::Error as PciDeviceError;
 pub use self::pci_device::PciDevice;
diff --git a/devices/src/usb/host_backend/context.rs b/devices/src/usb/host_backend/context.rs
index 3fd8d3d..260aad1 100644
--- a/devices/src/usb/host_backend/context.rs
+++ b/devices/src/usb/host_backend/context.rs
@@ -129,11 +129,14 @@ struct PollfdChangeHandler {
 
 impl LibUsbPollfdChangeHandler for PollfdChangeHandler {
     fn add_poll_fd(&self, fd: RawFd, events: c_short) {
-        self.event_loop.add_event(
+        match self.event_loop.add_event(
             &MaybeOwnedFd::Borrowed(fd),
             WatchingEvents::new(events as u32),
             self.event_handler.clone(),
-        );
+        ) {
+            Err(e) => error!("cannot add event to event loop: {}", e),
+            Ok(_) => {}
+        }
     }
 
     fn remove_poll_fd(&self, fd: RawFd) {
diff --git a/devices/src/usb/host_backend/mod.rs b/devices/src/usb/host_backend/mod.rs
index 8951a08..ea0f44c 100644
--- a/devices/src/usb/host_backend/mod.rs
+++ b/devices/src/usb/host_backend/mod.rs
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 pub mod context;
-mod error;
+pub mod error;
 pub mod host_backend_device_provider;
 pub mod host_device;
 mod hotplug;
diff --git a/devices/src/usb/mod.rs b/devices/src/usb/mod.rs
index 78d93bd..738c693 100644
--- a/devices/src/usb/mod.rs
+++ b/devices/src/usb/mod.rs
@@ -6,5 +6,5 @@ extern crate usb_util;
 
 #[macro_use]
 mod log;
-mod host_backend;
+pub mod host_backend;
 pub mod xhci;
diff --git a/devices/src/usb/xhci/mod.rs b/devices/src/usb/xhci/mod.rs
index 3aabdc6..c652e90 100644
--- a/devices/src/usb/xhci/mod.rs
+++ b/devices/src/usb/xhci/mod.rs
@@ -10,12 +10,18 @@ mod intr_resample_handler;
 mod ring_buffer;
 mod ring_buffer_controller;
 mod ring_buffer_stop_cb;
-pub mod scatter_gather_buffer;
 mod transfer_ring_controller;
-pub mod usb_hub;
+mod xhci;
+#[allow(dead_code)]
 mod xhci_abi;
+#[allow(dead_code)]
 mod xhci_abi_schema;
+#[allow(dead_code)]
+mod xhci_regs;
+
+pub mod scatter_gather_buffer;
+pub mod usb_hub;
 pub mod xhci_backend_device;
 pub mod xhci_backend_device_provider;
-mod xhci_regs;
+pub mod xhci_controller;
 pub mod xhci_transfer;
diff --git a/devices/src/usb/xhci/xhci.rs b/devices/src/usb/xhci/xhci.rs
new file mode 100644
index 0000000..b69397e
--- /dev/null
+++ b/devices/src/usb/xhci/xhci.rs
@@ -0,0 +1,387 @@
+// 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::command_ring_controller::{CommandRingController, CommandRingControllerError};
+use super::device_slot::{DeviceSlots, Error as DeviceSlotError};
+use super::interrupter::{Error as InterrupterError, Interrupter};
+use super::intr_resample_handler::IntrResampleHandler;
+use super::ring_buffer_stop_cb::RingBufferStopCallback;
+use super::usb_hub::UsbHub;
+use super::xhci_backend_device_provider::XhciBackendDeviceProvider;
+use super::xhci_regs::*;
+use std::fmt::{self, Display};
+use std::sync::Arc;
+use sync::Mutex;
+use sys_util::{EventFd, GuestAddress, GuestMemory};
+use usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
+use utils::{Error as UtilsError, EventLoop, FailHandle};
+
+#[derive(Debug)]
+pub enum Error {
+    StartEventLoop(UtilsError),
+    GetDeviceSlot(u8),
+    StartResampleHandler,
+    SendInterrupt(InterrupterError),
+    EnableInterrupter(InterrupterError),
+    SetModeration(InterrupterError),
+    SetupEventRing(InterrupterError),
+    SetEventHandlerBusy(InterrupterError),
+    StartProvider,
+    RingDoorbell(DeviceSlotError),
+    CreateCommandRingController(CommandRingControllerError),
+}
+
+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 {
+            StartEventLoop(e) => write!(f, "failed to start event loop: {}", e),
+            GetDeviceSlot(i) => write!(f, "failed to get device slot: {}", i),
+            StartResampleHandler => write!(f, "failed to start resample handler"),
+            SendInterrupt(e) => write!(f, "failed to send interrupter: {}", e),
+            EnableInterrupter(e) => write!(f, "failed to enable interrupter: {}", e),
+            SetModeration(e) => write!(f, "failed to set interrupter moderation: {}", e),
+            SetupEventRing(e) => write!(f, "failed to setup event ring: {}", e),
+            SetEventHandlerBusy(e) => write!(f, "failed to set event handler busy: {}", e),
+            StartProvider => write!(f, "failed to start backend provider"),
+            RingDoorbell(e) => write!(f, "failed to ring doorbell: {}", e),
+            CreateCommandRingController(e) => {
+                write!(f, "failed to create command ring controller: {}", e)
+            }
+        }
+    }
+}
+
+/// xHCI controller implementation.
+pub struct Xhci {
+    fail_handle: Arc<FailHandle>,
+    regs: XhciRegs,
+    interrupter: Arc<Mutex<Interrupter>>,
+    command_ring_controller: Arc<CommandRingController>,
+    device_slots: DeviceSlots,
+    // resample handler and device provider only lives on EventLoop to handle corresponding events.
+    // By design, event loop only hold weak reference. We need to keep a strong reference here to
+    // keep it alive.
+    #[allow(dead_code)]
+    intr_resample_handler: Arc<IntrResampleHandler>,
+    #[allow(dead_code)]
+    device_provider: HostBackendDeviceProvider,
+}
+
+impl Xhci {
+    /// Create a new xHCI controller.
+    pub fn new(
+        fail_handle: Arc<FailHandle>,
+        mem: GuestMemory,
+        device_provider: HostBackendDeviceProvider,
+        irq_evt: EventFd,
+        irq_resample_evt: EventFd,
+        regs: XhciRegs,
+    ) -> Result<Arc<Self>> {
+        let (event_loop, _join_handle) =
+            EventLoop::start(Some(fail_handle.clone())).map_err(Error::StartEventLoop)?;
+        let interrupter = Arc::new(Mutex::new(Interrupter::new(mem.clone(), irq_evt, &regs)));
+        let event_loop = Arc::new(event_loop);
+        let intr_resample_handler =
+            IntrResampleHandler::start(&event_loop, interrupter.clone(), irq_resample_evt)
+                .ok_or(Error::StartResampleHandler)?;
+        let hub = Arc::new(UsbHub::new(&regs, interrupter.clone()));
+
+        let mut device_provider = device_provider;
+        device_provider
+            .start(fail_handle.clone(), event_loop.clone(), hub.clone())
+            .map_err(|_| Error::StartProvider)?;
+
+        let device_slots = DeviceSlots::new(
+            fail_handle.clone(),
+            regs.dcbaap.clone(),
+            hub.clone(),
+            interrupter.clone(),
+            event_loop.clone(),
+            mem.clone(),
+        );
+        let command_ring_controller = CommandRingController::new(
+            mem.clone(),
+            event_loop.clone(),
+            device_slots.clone(),
+            interrupter.clone(),
+        )
+        .map_err(Error::CreateCommandRingController)?;
+        let xhci = Arc::new(Xhci {
+            fail_handle,
+            regs,
+            intr_resample_handler,
+            interrupter,
+            command_ring_controller,
+            device_slots,
+            device_provider,
+        });
+        Self::init_reg_callbacks(&xhci);
+        Ok(xhci)
+    }
+
+    fn init_reg_callbacks(xhci: &Arc<Xhci>) {
+        // All the callbacks will hold a weak reference to avoid memory leak. Thos weak upgrade
+        // should never fail.
+        let xhci_weak = Arc::downgrade(xhci);
+        xhci.regs.usbcmd.set_write_cb(move |val: u32| {
+            // All the weak reference upgrade should never fail. xhci hold reference to the
+            // registers, callback won't be invoked if xhci is gone.
+            let xhci = xhci_weak.upgrade().unwrap();
+            let r = xhci.usbcmd_callback(val);
+            xhci.handle_register_callback_result(r, 0)
+        });
+
+        let xhci_weak = Arc::downgrade(xhci);
+        xhci.regs.crcr.set_write_cb(move |val: u64| {
+            let xhci = xhci_weak.upgrade().unwrap();
+            let r = xhci.crcr_callback(val);
+            xhci.handle_register_callback_result(r, 0)
+        });
+
+        for i in 0..xhci.regs.portsc.len() {
+            let xhci_weak = Arc::downgrade(xhci);
+            xhci.regs.portsc[i].set_write_cb(move |val: u32| {
+                let xhci = xhci_weak.upgrade().unwrap();
+                let r = xhci.portsc_callback(i as u32, val);
+                xhci.handle_register_callback_result(r, 0)
+            });
+        }
+
+        for i in 0..xhci.regs.doorbells.len() {
+            let xhci_weak = Arc::downgrade(xhci);
+            xhci.regs.doorbells[i].set_write_cb(move |val: u32| {
+                let xhci = xhci_weak.upgrade().unwrap();
+                let r = xhci.doorbell_callback(i as u32, val);
+                xhci.handle_register_callback_result(r, ());
+                val
+            });
+        }
+
+        let xhci_weak = Arc::downgrade(xhci);
+        xhci.regs.iman.set_write_cb(move |val: u32| {
+            let xhci = xhci_weak.upgrade().unwrap();
+            let r = xhci.iman_callback(val);
+            xhci.handle_register_callback_result(r, ());
+            val
+        });
+
+        let xhci_weak = Arc::downgrade(xhci);
+        xhci.regs.imod.set_write_cb(move |val: u32| {
+            let xhci = xhci_weak.upgrade().unwrap();
+            let r = xhci.imod_callback(val);
+            xhci.handle_register_callback_result(r, ());
+            val
+        });
+
+        let xhci_weak = Arc::downgrade(xhci);
+        xhci.regs.erstsz.set_write_cb(move |val: u32| {
+            let xhci = xhci_weak.upgrade().unwrap();
+            let r = xhci.erstsz_callback(val);
+            xhci.handle_register_callback_result(r, ());
+            val
+        });
+
+        let xhci_weak = Arc::downgrade(xhci);
+        xhci.regs.erstba.set_write_cb(move |val: u64| {
+            let xhci = xhci_weak.upgrade().unwrap();
+            let r = xhci.erstba_callback(val);
+            xhci.handle_register_callback_result(r, ());
+            val
+        });
+
+        let xhci_weak = Arc::downgrade(xhci);
+        xhci.regs.erdp.set_write_cb(move |val: u64| {
+            let xhci = xhci_weak.upgrade().unwrap();
+            let r = xhci.erdp_callback(val);
+            xhci.handle_register_callback_result(r, ());
+            val
+        });
+    }
+
+    fn handle_register_callback_result<T>(&self, r: Result<T>, t: T) -> T {
+        match r {
+            Ok(v) => v,
+            Err(e) => {
+                error!("xhci controller failed: {}", e);
+                self.fail_handle.fail();
+                t
+            }
+        }
+    }
+
+    // Callback for usbcmd register write.
+    fn usbcmd_callback(&self, value: u32) -> Result<u32> {
+        if (value & USB_CMD_RESET) > 0 {
+            usb_debug!("xhci_controller: reset controller");
+            self.reset()?;
+            return Ok(value & (!USB_CMD_RESET));
+        }
+
+        if (value & USB_CMD_RUNSTOP) > 0 {
+            usb_debug!("xhci_controller: clear halt bits");
+            self.regs.usbsts.clear_bits(USB_STS_HALTED);
+        } else {
+            usb_debug!("xhci_controller: halt device");
+            self.halt()?;
+            self.regs.crcr.clear_bits(CRCR_COMMAND_RING_RUNNING);
+        }
+
+        // Enable interrupter if needed.
+        let enabled = (value & USB_CMD_INTERRUPTER_ENABLE) > 0
+            && (self.regs.iman.get_value() & IMAN_INTERRUPT_ENABLE) > 0;
+        usb_debug!("xhci_controller: interrupter enable?: {}", enabled);
+        self.interrupter
+            .lock()
+            .set_enabled(enabled)
+            .map_err(Error::EnableInterrupter)?;
+        Ok(value)
+    }
+
+    // Callback for crcr register write.
+    fn crcr_callback(&self, value: u64) -> Result<u64> {
+        usb_debug!("xhci_controller: write to crcr {:x}", value);
+        let value = if (self.regs.crcr.get_value() & CRCR_COMMAND_RING_RUNNING) == 0 {
+            self.command_ring_controller
+                .set_dequeue_pointer(GuestAddress(value & CRCR_COMMAND_RING_POINTER));
+            self.command_ring_controller
+                .set_consumer_cycle_state((value & CRCR_RING_CYCLE_STATE) > 0);
+            value
+        } else {
+            error!("Write to crcr while command ring is running");
+            self.regs.crcr.get_value()
+        };
+        Ok(value)
+    }
+
+    // Callback for portsc register write.
+    fn portsc_callback(&self, index: u32, value: u32) -> Result<u32> {
+        let mut value = value;
+        usb_debug!(
+            "xhci_controller: write to portsc index {} value {:x}",
+            index,
+            value
+        );
+        // xHCI spec 4.19.5. Note: we might want to change this logic if we support USB 3.0.
+        if (value & PORTSC_PORT_RESET) > 0 || (value & PORTSC_WARM_PORT_RESET) > 0 {
+            // Libusb onlys support blocking call to reset and "usually incurs a noticeable
+            // delay.". We are faking a reset now.
+            value &= !PORTSC_PORT_LINK_STATE_MASK;
+            value &= !PORTSC_PORT_RESET;
+            value |= PORTSC_PORT_ENABLED;
+            value |= PORTSC_PORT_RESET_CHANGE;
+            self.interrupter
+                .lock()
+                .send_port_status_change_trb((index + 1) as u8)
+                .map_err(Error::SendInterrupt)?;
+        }
+        Ok(value)
+    }
+
+    // Callback for doorbell register write.
+    fn doorbell_callback(&self, index: u32, value: u32) -> Result<()> {
+        usb_debug!(
+            "xhci_controller: write to doorbell index {} value {:x}",
+            index,
+            value
+        );
+        let target = (value & DOORBELL_TARGET) as u8;
+        let stream_id: u16 = (value >> DOORBELL_STREAM_ID_OFFSET) as u16;
+        if (self.regs.usbcmd.get_value() & USB_CMD_RUNSTOP) > 0 {
+            // First doorbell is for command ring.
+            if index == 0 {
+                if target != 0 || stream_id != 0 {
+                    return Ok(());
+                }
+                usb_debug!("doorbell to command ring");
+                self.regs.crcr.set_bits(CRCR_COMMAND_RING_RUNNING);
+                self.command_ring_controller.start();
+            } else {
+                usb_debug!("doorbell to device slot");
+                self.device_slots
+                    .slot(index as u8)
+                    .ok_or(Error::GetDeviceSlot(index as u8))?
+                    .ring_doorbell(target, stream_id)
+                    .map_err(Error::RingDoorbell)?;
+            }
+        }
+        Ok(())
+    }
+
+    // Callback for iman register write.
+    fn iman_callback(&self, value: u32) -> Result<()> {
+        usb_debug!("xhci_controller: write to iman {:x}", value);
+        let enabled = ((value & IMAN_INTERRUPT_ENABLE) > 0)
+            && ((self.regs.usbcmd.get_value() & USB_CMD_INTERRUPTER_ENABLE) > 0);
+        self.interrupter
+            .lock()
+            .set_enabled(enabled)
+            .map_err(Error::EnableInterrupter)
+    }
+
+    // Callback for imod register write.
+    fn imod_callback(&self, value: u32) -> Result<()> {
+        usb_debug!("xhci_controller: write to imod {:x}", value);
+        self.interrupter
+            .lock()
+            .set_moderation(
+                (value & IMOD_INTERRUPT_MODERATION_INTERVAL) as u16,
+                (value >> IMOD_INTERRUPT_MODERATION_COUNTER_OFFSET) as u16,
+            )
+            .map_err(Error::SetModeration)
+    }
+
+    // Callback for erstsz register write.
+    fn erstsz_callback(&self, value: u32) -> Result<()> {
+        usb_debug!("xhci_controller: write to erstz {:x}", value);
+        self.interrupter
+            .lock()
+            .set_event_ring_seg_table_size((value & ERSTSZ_SEGMENT_TABLE_SIZE) as u16)
+            .map_err(Error::SetupEventRing)
+    }
+
+    // Callback for erstba register write.
+    fn erstba_callback(&self, value: u64) -> Result<()> {
+        usb_debug!("xhci_controller: write to erstba {:x}", value);
+        self.interrupter
+            .lock()
+            .set_event_ring_seg_table_base_addr(GuestAddress(
+                value & ERSTBA_SEGMENT_TABLE_BASE_ADDRESS,
+            ))
+            .map_err(Error::SetupEventRing)
+    }
+
+    // Callback for erdp register write.
+    fn erdp_callback(&self, value: u64) -> Result<()> {
+        usb_debug!("xhci_controller: write to erdp {:x}", value);
+        let mut interrupter = self.interrupter.lock();
+        interrupter
+            .set_event_ring_dequeue_pointer(GuestAddress(value & ERDP_EVENT_RING_DEQUEUE_POINTER))
+            .map_err(Error::SetupEventRing)?;
+        interrupter
+            .set_event_handler_busy((value & ERDP_EVENT_HANDLER_BUSY) > 0)
+            .map_err(Error::SetEventHandlerBusy)
+    }
+
+    fn reset(&self) -> Result<()> {
+        self.regs.usbsts.set_bits(USB_STS_CONTROLLER_NOT_READY);
+        let usbsts = self.regs.usbsts.clone();
+        self.device_slots.stop_all_and_reset(move || {
+            usbsts.clear_bits(USB_STS_CONTROLLER_NOT_READY);
+        });
+        Ok(())
+    }
+
+    fn halt(&self) -> Result<()> {
+        let usbsts = self.regs.usbsts.clone();
+        self.device_slots
+            .stop_all(RingBufferStopCallback::new(move || {
+                usbsts.set_bits(USB_STS_HALTED);
+            }));
+        Ok(())
+    }
+}
diff --git a/devices/src/usb/xhci/xhci_controller.rs b/devices/src/usb/xhci/xhci_controller.rs
new file mode 100644
index 0000000..f920704
--- /dev/null
+++ b/devices/src/usb/xhci/xhci_controller.rs
@@ -0,0 +1,277 @@
+// 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 pci::{
+    PciBarConfiguration, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
+    PciInterruptPin, PciProgrammingInterface, PciSerialBusSubClass,
+};
+use register_space::{Register, RegisterSpace};
+use resources::SystemAllocator;
+use std::mem;
+use std::os::unix::io::RawFd;
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::Arc;
+use sys_util::{EventFd, GuestMemory};
+use usb::host_backend::host_backend_device_provider::HostBackendDeviceProvider;
+use usb::xhci::xhci::Xhci;
+use usb::xhci::xhci_backend_device_provider::XhciBackendDeviceProvider;
+use usb::xhci::xhci_regs::{init_xhci_mmio_space_and_regs, XhciRegs};
+use utils::FailHandle;
+
+const XHCI_BAR0_SIZE: u64 = 0x10000;
+
+#[derive(Clone, Copy)]
+enum UsbControllerProgrammingInterface {
+    Usb3HostController = 0x30,
+}
+
+impl PciProgrammingInterface for UsbControllerProgrammingInterface {
+    fn get_register_value(&self) -> u8 {
+        *self as u8
+    }
+}
+
+/// Use this handle to fail xhci controller.
+pub struct XhciFailHandle {
+    usbcmd: Register<u32>,
+    usbsts: Register<u32>,
+    xhci_failed: AtomicBool,
+}
+
+impl XhciFailHandle {
+    pub fn new(regs: &XhciRegs) -> XhciFailHandle {
+        XhciFailHandle {
+            usbcmd: regs.usbcmd.clone(),
+            usbsts: regs.usbsts.clone(),
+            xhci_failed: AtomicBool::new(false),
+        }
+    }
+}
+
+impl FailHandle for XhciFailHandle {
+    /// Fail this controller. Will set related registers and flip failed bool.
+    fn fail(&self) {
+        // set run/stop to stop.
+        const USBCMD_STOPPED: u32 = 0;
+        // Set host system error bit.
+        const USBSTS_HSE: u32 = 1 << 2;
+        self.usbcmd.set_value(USBCMD_STOPPED);
+        self.usbsts.set_value(USBSTS_HSE);
+
+        self.xhci_failed.store(true, Ordering::SeqCst);
+        error!("xhci controller stopped working");
+    }
+
+    /// Returns true if xhci is already failed.
+    fn failed(&self) -> bool {
+        self.xhci_failed.load(Ordering::SeqCst)
+    }
+}
+
+// Xhci controller should be created with backend device provider. Then irq should be assigned
+// before initialized. We are not making `failed` as a state here to optimize performance. Cause we
+// need to set failed in other threads.
+enum XhciControllerState {
+    Unknown,
+    Created {
+        device_provider: HostBackendDeviceProvider,
+    },
+    IrqAssigned {
+        device_provider: HostBackendDeviceProvider,
+        irq_evt: EventFd,
+        irq_resample_evt: EventFd,
+    },
+    Initialized {
+        mmio: RegisterSpace,
+        // Xhci init could fail.
+        #[allow(dead_code)]
+        xhci: Option<Arc<Xhci>>,
+        fail_handle: Arc<FailHandle>,
+    },
+}
+
+/// xHCI PCI interface implementation.
+pub struct XhciController {
+    config_regs: PciConfiguration,
+    mem: GuestMemory,
+    bar0: u64, // bar0 in config_regs will be changed by guest. Not sure why.
+    state: XhciControllerState,
+}
+
+impl XhciController {
+    /// Create new xhci controller.
+    pub fn new(mem: GuestMemory, usb_provider: HostBackendDeviceProvider) -> Self {
+        let config_regs = PciConfiguration::new(
+            0x01b73, // fresco logic, (google = 0x1ae0)
+            0x1000,  // fresco logic pdk. This chip has broken msi. See kernel xhci-pci.c
+            PciClassCode::SerialBusController,
+            &PciSerialBusSubClass::USB,
+            Some(&UsbControllerProgrammingInterface::Usb3HostController),
+            PciHeaderType::Device,
+            0,
+            0,
+        );
+        XhciController {
+            config_regs,
+            mem,
+            bar0: 0,
+            state: XhciControllerState::Created {
+                device_provider: usb_provider,
+            },
+        }
+    }
+
+    /// Init xhci controller when it's forked.
+    pub fn init_when_forked(&mut self) {
+        match mem::replace(&mut self.state, XhciControllerState::Unknown) {
+            XhciControllerState::IrqAssigned {
+                device_provider,
+                irq_evt,
+                irq_resample_evt,
+            } => {
+                let (mmio, regs) = init_xhci_mmio_space_and_regs();
+                let fail_handle: Arc<FailHandle> = Arc::new(XhciFailHandle::new(&regs));
+                let xhci = match Xhci::new(
+                    fail_handle.clone(),
+                    self.mem.clone(),
+                    device_provider,
+                    irq_evt,
+                    irq_resample_evt,
+                    regs,
+                ) {
+                    Ok(xhci) => Some(xhci),
+                    Err(_) => {
+                        error!("fail to init xhci");
+                        fail_handle.fail();
+                        return;
+                    }
+                };
+
+                self.state = XhciControllerState::Initialized {
+                    mmio,
+                    xhci,
+                    fail_handle,
+                }
+            }
+            _ => {
+                error!("xhci controller is in a wrong state");
+                return;
+            }
+        }
+    }
+}
+
+impl PciDevice for XhciController {
+    fn debug_label(&self) -> String {
+        "xhci controller".to_owned()
+    }
+
+    fn keep_fds(&self) -> Vec<RawFd> {
+        match self.state {
+            XhciControllerState::Created {
+                ref device_provider,
+            } => device_provider.keep_fds(),
+            _ => {
+                error!("xhci controller is in a wrong state");
+                vec![]
+            }
+        }
+    }
+
+    fn assign_irq(
+        &mut self,
+        irq_evt: EventFd,
+        irq_resample_evt: EventFd,
+        irq_num: u32,
+        irq_pin: PciInterruptPin,
+    ) {
+        match mem::replace(&mut self.state, XhciControllerState::Unknown) {
+            XhciControllerState::Created { device_provider } => {
+                self.config_regs.set_irq(irq_num as u8, irq_pin);
+                self.state = XhciControllerState::IrqAssigned {
+                    device_provider,
+                    irq_evt,
+                    irq_resample_evt,
+                }
+            }
+            _ => {
+                error!("xhci controller is in a wrong state");
+                return;
+            }
+        }
+    }
+
+    fn allocate_io_bars(
+        &mut self,
+        resources: &mut SystemAllocator,
+    ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> {
+        // xHCI spec 5.2.1.
+        let bar0_addr = resources
+            .allocate_mmio_addresses(XHCI_BAR0_SIZE)
+            .ok_or(PciDeviceError::IoAllocationFailed(XHCI_BAR0_SIZE))?;
+        let bar0_config = PciBarConfiguration::default()
+            .set_register_index(0)
+            .set_address(bar0_addr)
+            .set_size(XHCI_BAR0_SIZE);
+        self.config_regs
+            .add_pci_bar(&bar0_config)
+            .map_err(|e| PciDeviceError::IoRegistrationFailed(bar0_addr, e))?;
+        self.bar0 = bar0_addr;
+        Ok(vec![(bar0_addr, XHCI_BAR0_SIZE)])
+    }
+
+    fn config_registers(&self) -> &PciConfiguration {
+        &self.config_regs
+    }
+
+    fn config_registers_mut(&mut self) -> &mut PciConfiguration {
+        &mut self.config_regs
+    }
+
+    fn read_bar(&mut self, addr: u64, data: &mut [u8]) {
+        let bar0 = self.bar0;
+        if addr < bar0 || addr > bar0 + XHCI_BAR0_SIZE {
+            return;
+        }
+        match self.state {
+            XhciControllerState::Initialized {
+                ref mmio,
+                xhci: _,
+                fail_handle: _,
+            } => {
+                // Read bar would still work even if it's already failed.
+                mmio.read(addr - bar0, data);
+            }
+            _ => {
+                error!("xhci controller is in a wrong state");
+                return;
+            }
+        }
+    }
+
+    fn write_bar(&mut self, addr: u64, data: &[u8]) {
+        let bar0 = self.bar0;
+        if addr < bar0 || addr > bar0 + XHCI_BAR0_SIZE {
+            return;
+        }
+        match self.state {
+            XhciControllerState::Initialized {
+                ref mmio,
+                xhci: _,
+                ref fail_handle,
+            } => {
+                if !fail_handle.failed() {
+                    mmio.write(addr - bar0, data);
+                }
+            }
+            _ => {
+                error!("xhci controller is in a wrong state");
+                return;
+            }
+        }
+    }
+    fn on_device_sandboxed(&mut self) {
+        self.init_when_forked();
+    }
+}
diff --git a/seccomp/arm/xhci.policy b/seccomp/arm/xhci.policy
new file mode 100644
index 0000000..0c69bc0
--- /dev/null
+++ b/seccomp/arm/xhci.policy
@@ -0,0 +1,40 @@
+# Copyright 2018 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.
+
+openat: 1
+@include /usr/share/policy/crosvm/common_device.policy
+
+stat64: 1
+fcntl64: 1
+lstat64: 1
+readlinkat: 1
+getdents64: 1
+getrandom: 1
+name_to_handle_at: 1
+access: 1
+gettid: 1
+clock_gettime: 1
+timerfd_create: 1
+getsockname: 1
+pipe: 1
+setsockopt: 1
+bind: 1
+fcntl: 1
+socket: arg0 == AF_NETLINK
+stat: 1
+uname: 1
+# The following ioctls are:
+# 0x4004550d == USBDEVFS_REAPURBNDELAY32
+# 0x550b     == USBDEVFS_DISCARDURB
+# 0x8004550f == USBDEVFS_CLAIMINTERFACE
+# 0x80045510 == USBDEVFS_RELEASEINTERFACE
+# 0x8004551a == USBDEVFS_GET_CAPABILITIES
+# 0x802c550a == USBDEVFS_SUBMITURB
+# 0xc0105500 == USBDEVFS_CONTROL
+ioctl: arg1 == 0xc0105500 || arg1 == 0x802c550a || arg1 == 0x8004551a || arg1 == 0x4004550d || arg1 == 0x8004550f || arg1 == 0x80045510 || arg1 == 0x550b
+fstat: 1
+sigaltstack: 1
+recvmsg: 1
+getrandom: 1
+getdents: 1
diff --git a/seccomp/x86_64/xhci.policy b/seccomp/x86_64/xhci.policy
new file mode 100644
index 0000000..0f133d9
--- /dev/null
+++ b/seccomp/x86_64/xhci.policy
@@ -0,0 +1,38 @@
+# Copyright 2018 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.
+
+# xhci need "openat" to enumerate device. "openat" is disabled in comman_device policy.
+openat: 1
+@include /usr/share/policy/crosvm/common_device.policy
+
+lstat: 1
+gettid: 1
+readlinkat: 1
+timerfd_create: 1
+name_to_handle_at: 1
+access: 1
+timerfd_create: 1
+getsockname: 1
+pipe: 1
+setsockopt: 1
+bind: 1
+fcntl: 1
+socket: arg0 == AF_NETLINK
+stat: 1
+uname: 1
+# The following ioctls are:
+# 0x4008550d == USBDEVFS_REAPURBNDELAY
+# 0x41045508 == USBDEVFS_GETDRIVER
+# 0x550b     == USBDEVFS_DISCARDURB
+# 0x8004550f == USBDEVFS_CLAIMINTERFACE
+# 0x80045510 == USBDEVFS_RELEASEINTERFACE
+# 0x8004551a == USBDEVFS_GET_CAPABILITIES
+# 0x8038550a == USBDEVFS_SUBMITURB
+# 0xc0185500 == USBDEVFS_CONTROL
+ioctl: arg1 == 0xc0185500 || arg1 == 0x41045508 || arg1 == 0x8004550f || arg1 == 0x4008550d || arg1 == 0x8004551a || arg1 == 0x550b || arg1 == 0x80045510 || arg1 == 0x8038550a
+fstat: 1
+sigaltstack: 1
+recvmsg: 1
+getrandom: 1
+getdents: 1
diff --git a/src/linux.rs b/src/linux.rs
index 37910f4..4674241 100644
--- a/src/linux.rs
+++ b/src/linux.rs
@@ -25,7 +25,7 @@ use libc::{self, c_int, gid_t, uid_t};
 use audio_streams::DummyStreamSource;
 use byteorder::{ByteOrder, LittleEndian};
 use devices::virtio::{self, VirtioDevice};
-use devices::{self, PciDevice, VirtioPciDevice};
+use devices::{self, HostBackendDeviceProvider, PciDevice, VirtioPciDevice, XhciController};
 use io_jail::{self, Minijail};
 use kvm::*;
 use libcras::CrasClient;
@@ -44,7 +44,7 @@ use sys_util::{
 #[cfg(feature = "gpu-forward")]
 use sys_util::{GuestAddress, MemoryMapping, Protection};
 use vhost;
-use vm_control::{VmRequest, VmResponse, VmRunMode};
+use vm_control::{UsbControlSocket, VmRequest, VmResponse, VmRunMode};
 
 use crate::{Config, DiskOption, TouchDeviceOption};
 
@@ -80,6 +80,7 @@ pub enum Error {
     CreateTapDevice(NetError),
     CreateTimerFd(sys_util::Error),
     CreateTpmStorage(PathBuf, io::Error),
+    CreateUsbProvider(devices::usb::host_backend::error::Error),
     DetectImageType(qcow::Error),
     DeviceJail(io_jail::Error),
     DevicePivotRoot(io_jail::Error),
@@ -151,6 +152,7 @@ impl Display for Error {
             CreateTpmStorage(p, e) => {
                 write!(f, "failed to create tpm storage dir {}: {}", p.display(), e)
             }
+            CreateUsbProvider(e) => write!(f, "failed to create usb provider: {}", e),
             DetectImageType(e) => write!(f, "failed to detect disk image type: {}", e),
             DeviceJail(e) => write!(f, "failed to jail device: {}", e),
             DevicePivotRoot(e) => write!(f, "failed to pivot root device: {}", e),
@@ -765,6 +767,7 @@ fn create_devices(
     wayland_device_socket: UnixSeqpacket,
     balloon_device_socket: UnixSeqpacket,
     disk_device_sockets: &mut Vec<UnixSeqpacket>,
+    usb_provider: HostBackendDeviceProvider,
 ) -> DeviceResult<Vec<(Box<PciDevice>, Option<Minijail>)>> {
     let stubs = create_virtio_devices(
         &cfg,
@@ -802,6 +805,9 @@ fn create_devices(
             simple_jail(&cfg, "null_audio_device.policy")?,
         ));
     }
+    // Create xhci controller.
+    let usb_controller = Box::new(XhciController::new(mem.clone(), usb_provider));
+    pci_devices.push((usb_controller, simple_jail(&cfg, "xhci.policy")?));
 
     Ok(pci_devices)
 }
@@ -1068,6 +1074,8 @@ pub fn run_config(cfg: Config) -> Result<()> {
         info!("crosvm entering multiprocess mode");
     }
 
+    let (usb_control_socket, usb_provider) =
+        HostBackendDeviceProvider::new().map_err(|e| Error::CreateUsbProvider(e))?;
     // Masking signals is inherently dangerous, since this can persist across clones/execs. Do this
     // before any jailed devices have been spawned, so that we can catch any of them that fail very
     // quickly.
@@ -1130,6 +1138,7 @@ pub fn run_config(cfg: Config) -> Result<()> {
             wayland_device_socket,
             balloon_device_socket,
             &mut disk_device_sockets,
+            usb_provider,
         )
     })
     .map_err(Error::BuildVm)?;
@@ -1174,6 +1183,7 @@ pub fn run_config(cfg: Config) -> Result<()> {
         control_sockets,
         balloon_host_socket,
         &disk_host_sockets,
+        usb_control_socket,
         sigchld_fd,
         _render_node_host,
         sandbox,
@@ -1186,6 +1196,7 @@ fn run_control(
     mut control_sockets: Vec<MsgSocket<VmResponse, VmRequest>>,
     balloon_host_socket: UnixSeqpacket,
     disk_host_sockets: &[MsgSocket<VmRequest, VmResponse>],
+    usb_control_socket: UsbControlSocket,
     sigchld_fd: SignalFd,
     _render_node_host: RenderNodeHost,
     sandbox: bool,
@@ -1465,6 +1476,7 @@ fn run_control(
                                     &mut run_mode_opt,
                                     &balloon_host_socket,
                                     disk_host_sockets,
+                                    &usb_control_socket,
                                 );
                                 if let Err(e) = socket.send(&response) {
                                     error!("failed to send VmResponse: {}", e);
diff --git a/src/main.rs b/src/main.rs
index a0cc1c2..d48b166 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -43,18 +43,22 @@ pub mod panic_hook;
 #[cfg(feature = "plugin")]
 pub mod plugin;
 
-use std::fs::OpenOptions;
+use std::fmt;
+use std::fs::{File, OpenOptions};
 use std::net;
-use std::os::unix::io::RawFd;
-use std::path::PathBuf;
+use std::num::ParseIntError;
+use std::os::unix::io::{FromRawFd, RawFd};
+use std::path::{Path, PathBuf};
 use std::string::String;
 use std::thread::sleep;
 use std::time::Duration;
 
 use msg_socket::{MsgReceiver, MsgSender, MsgSocket};
 use qcow::QcowFile;
-use sys_util::{getpid, kill_process_group, net::UnixSeqpacket, reap_child, syslog};
-use vm_control::{VmRequest, VmResponse};
+use sys_util::{
+    getpid, kill_process_group, net::UnixSeqpacket, reap_child, syslog, validate_raw_fd,
+};
+use vm_control::{MaybeOwnedFd, UsbControlCommand, UsbControlResult, VmRequest, VmResponse};
 
 use crate::argument::{print_help, set_arguments, Argument};
 
@@ -777,8 +781,11 @@ fn run_vm(args: std::env::Args) -> std::result::Result<(), ()> {
     }
 }
 
-fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> {
-    let mut return_result = Ok(());
+fn handle_request(
+    request: &VmRequest,
+    args: std::env::Args,
+) -> std::result::Result<VmResponse, ()> {
+    let mut return_result = Err(());
     for socket_path in args {
         match UnixSeqpacket::connect(&socket_path) {
             Ok(s) => {
@@ -792,7 +799,7 @@ fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result
                     continue;
                 }
                 match socket.recv() {
-                    Ok(response) => info!("request response was {}", response),
+                    Ok(response) => return_result = Ok(response),
                     Err(e) => {
                         error!(
                             "failed to send request to socket at2 '{}': {}",
@@ -813,6 +820,12 @@ fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result
     return_result
 }
 
+fn vms_request(request: &VmRequest, args: std::env::Args) -> std::result::Result<(), ()> {
+    let response = handle_request(request, args)?;
+    info!("request response was {}", response);
+    Ok(())
+}
+
 fn stop_vms(args: std::env::Args) -> std::result::Result<(), ()> {
     if args.len() == 0 {
         print_help("crosvm stop", "VM_SOCKET...", &[]);
@@ -930,13 +943,193 @@ fn disk_cmd(mut args: std::env::Args) -> std::result::Result<(), ()> {
     vms_request(&request, args)
 }
 
+enum ModifyUsbError {
+    ArgMissing(&'static str),
+    ArgParse(&'static str, String),
+    ArgParseInt(&'static str, String, ParseIntError),
+    FailedFdValidate(sys_util::Error),
+    PathDoesNotExist(PathBuf),
+    SocketFailed,
+    UnexpectedResponse(VmResponse),
+    UnknownCommand(String),
+    UsbControl(UsbControlResult),
+}
+
+impl fmt::Display for ModifyUsbError {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        use self::ModifyUsbError::*;
+
+        match self {
+            ArgMissing(a) => write!(f, "argument missing: {}", a),
+            ArgParse(name, value) => {
+                write!(f, "failed to parse argument {} value `{}`", name, value)
+            }
+            ArgParseInt(name, value, e) => write!(
+                f,
+                "failed to parse integer argument {} value `{}`: {}",
+                name, value, e
+            ),
+            FailedFdValidate(e) => write!(f, "failed to validate file descriptor: {}", e),
+            PathDoesNotExist(p) => write!(f, "path `{}` does not exist", p.display()),
+            SocketFailed => write!(f, "socket failed"),
+            UnexpectedResponse(r) => write!(f, "unexpected response: {}", r),
+            UnknownCommand(c) => write!(f, "unknown command: `{}`", c),
+            UsbControl(e) => write!(f, "{}", e),
+        }
+    }
+}
+
+type ModifyUsbResult<T> = std::result::Result<T, ModifyUsbError>;
+
+fn parse_bus_id_addr(v: &str) -> ModifyUsbResult<(u8, u8, u16, u16)> {
+    debug!("parse_bus_id_addr: {}", v);
+    let mut ids = v.split(":");
+    match (ids.next(), ids.next(), ids.next(), ids.next()) {
+        (Some(bus_id), Some(addr), Some(vid), Some(pid)) => {
+            let bus_id = bus_id
+                .parse::<u8>()
+                .map_err(|e| ModifyUsbError::ArgParseInt("bus_id", bus_id.to_owned(), e))?;
+            let addr = addr
+                .parse::<u8>()
+                .map_err(|e| ModifyUsbError::ArgParseInt("addr", addr.to_owned(), e))?;
+            let vid = u16::from_str_radix(&vid, 16)
+                .map_err(|e| ModifyUsbError::ArgParseInt("vid", vid.to_owned(), e))?;
+            let pid = u16::from_str_radix(&pid, 16)
+                .map_err(|e| ModifyUsbError::ArgParseInt("pid", pid.to_owned(), e))?;
+            Ok((bus_id, addr, vid, pid))
+        }
+        _ => Err(ModifyUsbError::ArgParse(
+            "BUS_ID_ADDR_BUS_NUM_DEV_NUM",
+            v.to_owned(),
+        )),
+    }
+}
+
+fn raw_fd_from_path(path: &Path) -> ModifyUsbResult<RawFd> {
+    if !path.exists() {
+        return Err(ModifyUsbError::PathDoesNotExist(path.to_owned()));
+    }
+    let raw_fd = path
+        .file_name()
+        .and_then(|fd_osstr| fd_osstr.to_str())
+        .map_or(
+            Err(ModifyUsbError::ArgParse(
+                "USB_DEVICE_PATH",
+                path.to_string_lossy().into_owned(),
+            )),
+            |fd_str| {
+                fd_str.parse::<libc::c_int>().map_err(|e| {
+                    ModifyUsbError::ArgParseInt("USB_DEVICE_PATH", fd_str.to_owned(), e)
+                })
+            },
+        )?;
+    validate_raw_fd(raw_fd).map_err(|e| ModifyUsbError::FailedFdValidate(e))
+}
+
+fn usb_attach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
+    let val = args
+        .next()
+        .ok_or(ModifyUsbError::ArgMissing("BUS_ID_ADDR_BUS_NUM_DEV_NUM"))?;
+    let (bus, addr, vid, pid) = parse_bus_id_addr(&val)?;
+    let dev_path = PathBuf::from(
+        args.next()
+            .ok_or(ModifyUsbError::ArgMissing("usb device path"))?,
+    );
+    let usb_file: Option<File> = if dev_path == Path::new("-") {
+        None
+    } else if dev_path.parent() == Some(Path::new("/proc/self/fd")) {
+        // Special case '/proc/self/fd/*' paths. The FD is already open, just use it.
+        // Safe because we will validate |raw_fd|.
+        Some(unsafe { File::from_raw_fd(raw_fd_from_path(&dev_path)?) })
+    } else {
+        Some(
+            OpenOptions::new()
+                .read(true)
+                .write(true)
+                .open(&dev_path)
+                .map_err(|_| ModifyUsbError::UsbControl(UsbControlResult::FailedToOpenDevice))?,
+        )
+    };
+
+    let request = VmRequest::UsbCommand(UsbControlCommand::AttachDevice {
+        bus,
+        addr,
+        vid,
+        pid,
+        fd: usb_file.map(|f| MaybeOwnedFd::Owned(f)),
+    });
+    let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
+    match response {
+        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
+        r => Err(ModifyUsbError::UnexpectedResponse(r)),
+    }
+}
+
+fn usb_detach(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
+    let port: u8 = args
+        .next()
+        .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
+            p.parse::<u8>()
+                .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
+        })?;
+    let request = VmRequest::UsbCommand(UsbControlCommand::DetachDevice { port });
+    let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
+    match response {
+        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
+        r => Err(ModifyUsbError::UnexpectedResponse(r)),
+    }
+}
+
+fn usb_list(mut args: std::env::Args) -> ModifyUsbResult<UsbControlResult> {
+    let port: u8 = args
+        .next()
+        .map_or(Err(ModifyUsbError::ArgMissing("PORT")), |p| {
+            p.parse::<u8>()
+                .map_err(|e| ModifyUsbError::ArgParseInt("PORT", p.to_owned(), e))
+        })?;
+    let request = VmRequest::UsbCommand(UsbControlCommand::ListDevice { port });
+    let response = handle_request(&request, args).map_err(|_| ModifyUsbError::SocketFailed)?;
+    match response {
+        VmResponse::UsbResponse(usb_resp) => Ok(usb_resp),
+        r => Err(ModifyUsbError::UnexpectedResponse(r)),
+    }
+}
+
+fn modify_usb(mut args: std::env::Args) -> std::result::Result<(), ()> {
+    if args.len() < 3 {
+        print_help("crosvm usb",
+                   "[attach BUS_ID:ADDR:VENDOR_ID:PRODUCT_ID [USB_DEVICE_PATH|-] | detach PORT | list PORT] VM_SOCKET...", &[]);
+        return Err(());
+    }
+
+    // This unwrap will not panic because of the above length check.
+    let command = args.next().unwrap();
+    let result = match command.as_ref() {
+        "attach" => usb_attach(args),
+        "detach" => usb_detach(args),
+        "list" => usb_list(args),
+        other => Err(ModifyUsbError::UnknownCommand(other.to_owned())),
+    };
+    match result {
+        Ok(response) => {
+            println!("{}", response);
+            Ok(())
+        }
+        Err(e) => {
+            println!("error {}", e);
+            Err(())
+        }
+    }
+}
+
 fn print_usage() {
     print_help("crosvm", "[stop|run]", &[]);
     println!("Commands:");
     println!("    stop - Stops crosvm instances via their control sockets.");
     println!("    run  - Start a new crosvm instance.");
     println!("    create_qcow2  - Create a new qcow2 disk image file.");
-    println!("    disk - Manage attached virtual disk devices.")
+    println!("    disk - Manage attached virtual disk devices.");
+    println!("    usb - Manage attached virtual USB devices.");
 }
 
 fn crosvm_main() -> std::result::Result<(), ()> {
@@ -966,6 +1159,7 @@ fn crosvm_main() -> std::result::Result<(), ()> {
         Some("balloon") => balloon_vms(args),
         Some("create_qcow2") => create_qcow2(args),
         Some("disk") => disk_cmd(args),
+        Some("usb") => modify_usb(args),
         Some(c) => {
             println!("invalid subcommand: {:?}", c);
             print_usage();
diff --git a/vm_control/src/lib.rs b/vm_control/src/lib.rs
index ee24b61..c6115ad 100644
--- a/vm_control/src/lib.rs
+++ b/vm_control/src/lib.rs
@@ -220,6 +220,7 @@ impl VmRequest {
         run_mode: &mut Option<VmRunMode>,
         balloon_host_socket: &UnixSeqpacket,
         disk_host_sockets: &[MsgSocket<VmRequest, VmResponse>],
+        usb_control_socket: &UsbControlSocket,
     ) -> VmResponse {
         match *self {
             VmRequest::Exit => {
@@ -316,8 +317,18 @@ impl VmRequest {
                 }
             }
             VmRequest::UsbCommand(ref cmd) => {
-                error!("not implemented yet");
-                VmResponse::Ok
+                let res = usb_control_socket.send(cmd);
+                if let Err(e) = res {
+                    error!("fail to send command to usb control socket: {}", e);
+                    return VmResponse::Err(SysError::new(EIO));
+                }
+                match usb_control_socket.recv() {
+                    Ok(response) => VmResponse::UsbResponse(response),
+                    Err(e) => {
+                        error!("fail to recv command from usb control socket: {}", e);
+                        return VmResponse::Err(SysError::new(EIO));
+                    }
+                }
             }
         }
     }