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 20:41:57 -0800
committerchrome-bot <chrome-bot@chromium.org>2019-03-17 21:33:08 -0700
commit100e6e48ad292406fb6f0a7eeb85465850bc28c7 (patch)
tree30f59fcb07b7cbee649a07390cd2397cfb65c463 /devices/src/usb/xhci
parentc324429b467d530fbeadef1fc9b527bb23ce1632 (diff)
downloadcrosvm-100e6e48ad292406fb6f0a7eeb85465850bc28c7.tar
crosvm-100e6e48ad292406fb6f0a7eeb85465850bc28c7.tar.gz
crosvm-100e6e48ad292406fb6f0a7eeb85465850bc28c7.tar.bz2
crosvm-100e6e48ad292406fb6f0a7eeb85465850bc28c7.tar.lz
crosvm-100e6e48ad292406fb6f0a7eeb85465850bc28c7.tar.xz
crosvm-100e6e48ad292406fb6f0a7eeb85465850bc28c7.tar.zst
crosvm-100e6e48ad292406fb6f0a7eeb85465850bc28c7.zip
implement xhci and add it to pci bus
Implement xhci controller, setup seccomp filters and add xhci to pci
bus.

CQ-DEPEND=CL:1512761
BUG=chromium:831850
TEST=local build
Change-Id: I5c05452ece66e99d3a670e259e095fca616e835d
Reviewed-on: https://chromium-review.googlesource.com/1512762
Commit-Ready: ChromeOS CL Exonerator Bot <chromiumos-cl-exonerator@appspot.gserviceaccount.com>
Tested-by: Jingkui Wang <jkwang@google.com>
Reviewed-by: Jingkui Wang <jkwang@google.com>
Diffstat (limited to 'devices/src/usb/xhci')
-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
3 files changed, 673 insertions, 3 deletions
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();
+    }
+}