summary refs log tree commit diff
diff options
context:
space:
mode:
authorDylan Reid <dgreid@chromium.org>2018-06-19 11:07:30 -0700
committerchrome-bot <chrome-bot@chromium.org>2018-10-01 11:30:05 -0700
commita158e310380bfcbb63fa5015d01c58fc0f0731da (patch)
treece84d117a3d5e902a57a01ffb1ca553031d0c844
parent948b5f7bc1a51ec4cd9fa36748eb3f4275bc2603 (diff)
downloadcrosvm-a158e310380bfcbb63fa5015d01c58fc0f0731da.tar
crosvm-a158e310380bfcbb63fa5015d01c58fc0f0731da.tar.gz
crosvm-a158e310380bfcbb63fa5015d01c58fc0f0731da.tar.bz2
crosvm-a158e310380bfcbb63fa5015d01c58fc0f0731da.tar.lz
crosvm-a158e310380bfcbb63fa5015d01c58fc0f0731da.tar.xz
crosvm-a158e310380bfcbb63fa5015d01c58fc0f0731da.tar.zst
crosvm-a158e310380bfcbb63fa5015d01c58fc0f0731da.zip
devices: Implement virtio PCI transport
Change-Id: Ieaa83205ba4e3f029f6d183a1b93799352551299
Signed-off-by: Daniel Verkamp <dverkamp@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1237364
Reviewed-by: Dylan Reid <dgreid@chromium.org>
-rw-r--r--devices/src/lib.rs1
-rw-r--r--devices/src/virtio/mod.rs3
-rw-r--r--devices/src/virtio/virtio_pci_common_config.rs292
-rw-r--r--devices/src/virtio/virtio_pci_device.rs404
4 files changed, 700 insertions, 0 deletions
diff --git a/devices/src/lib.rs b/devices/src/lib.rs
index 206f6a7..25cb94f 100644
--- a/devices/src/lib.rs
+++ b/devices/src/lib.rs
@@ -36,3 +36,4 @@ pub use self::pci::{PciConfigIo, PciConfigMmio, PciDevice, PciDeviceError, PciIn
 pub use self::proxy::ProxyDevice;
 pub use self::proxy::Error as ProxyError;
 pub use self::serial::Serial;
+pub use self::virtio::VirtioPciDevice;
diff --git a/devices/src/virtio/mod.rs b/devices/src/virtio/mod.rs
index d716f2c..a85b04b 100644
--- a/devices/src/virtio/mod.rs
+++ b/devices/src/virtio/mod.rs
@@ -14,6 +14,8 @@ mod net;
 mod gpu;
 mod p9;
 mod virtio_device;
+mod virtio_pci_common_config;
+mod virtio_pci_device;
 mod wl;
 
 pub mod vhost;
@@ -28,6 +30,7 @@ pub use self::net::*;
 pub use self::gpu::*;
 pub use self::p9::*;
 pub use self::virtio_device::*;
+pub use self::virtio_pci_device::*;
 pub use self::wl::*;
 
 const DEVICE_ACKNOWLEDGE: u32 = 0x01;
diff --git a/devices/src/virtio/virtio_pci_common_config.rs b/devices/src/virtio/virtio_pci_common_config.rs
new file mode 100644
index 0000000..8a50b35
--- /dev/null
+++ b/devices/src/virtio/virtio_pci_common_config.rs
@@ -0,0 +1,292 @@
+// 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.
+
+use byteorder::{ByteOrder, LittleEndian};
+use sys_util::GuestAddress;
+
+use super::*;
+
+/// Contains the data for reading and writing the common configuration structure of a virtio PCI
+/// device.
+///
+/// * Registers:
+/// ** About the whole device.
+/// le32 device_feature_select;     // read-write
+/// le32 device_feature;            // read-only for driver
+/// le32 driver_feature_select;     // read-write
+/// le32 driver_feature;            // read-write
+/// le16 msix_config;               // read-write
+/// le16 num_queues;                // read-only for driver
+/// u8 device_status;               // read-write (driver_status)
+/// u8 config_generation;           // read-only for driver
+/// ** About a specific virtqueue.
+/// le16 queue_select;              // read-write
+/// le16 queue_size;                // read-write, power of 2, or 0.
+/// le16 queue_msix_vector;         // read-write
+/// le16 queue_enable;              // read-write (Ready)
+/// le16 queue_notify_off;          // read-only for driver
+/// le64 queue_desc;                // read-write
+/// le64 queue_avail;               // read-write
+/// le64 queue_used;                // read-write
+pub struct VirtioPciCommonConfig {
+    pub driver_status: u8,
+    pub config_generation: u8,
+    pub device_feature_select: u32,
+    pub driver_feature_select: u32,
+    pub queue_select: u16,
+}
+
+impl VirtioPciCommonConfig {
+    pub fn read(
+        &mut self,
+        offset: u64,
+        data: &mut [u8],
+        queues: &mut Vec<Queue>,
+        device: &mut Box<VirtioDevice>,
+    ) {
+        match data.len() {
+            1 => {
+                let v = self.read_common_config_byte(offset);
+                data[0] = v;
+            }
+            2 => {
+                let v = self.read_common_config_word(offset, queues);
+                LittleEndian::write_u16(data, v);
+            }
+            4 => {
+                let v = self.read_common_config_dword(offset, device);
+                LittleEndian::write_u32(data, v);
+            }
+            8 => {
+                let v = self.read_common_config_qword(offset);
+                LittleEndian::write_u64(data, v);
+            }
+            _ => (),
+        }
+    }
+
+    pub fn write(
+        &mut self,
+        offset: u64,
+        data: &[u8],
+        queues: &mut Vec<Queue>,
+        device: &mut Box<VirtioDevice>,
+    ) {
+        match data.len() {
+            1 => self.write_common_config_byte(offset, data[0]),
+            2 => self.write_common_config_word(offset, LittleEndian::read_u16(data), queues),
+            4 => {
+                self.write_common_config_dword(offset, LittleEndian::read_u32(data), queues, device)
+            }
+            8 => self.write_common_config_qword(offset, LittleEndian::read_u64(data), queues),
+            _ => (),
+        }
+    }
+
+    fn read_common_config_byte(&self, offset: u64) -> u8 {
+        // The driver is only allowed to do aligned, properly sized access.
+        match offset {
+            0x14 => self.driver_status,
+            0x15 => self.config_generation,
+            _ => 0,
+        }
+    }
+
+    fn write_common_config_byte(&mut self, offset: u64, value: u8) {
+        match offset {
+            0x14 => self.driver_status = value,
+            _ => {
+                warn!("invalid virtio config byt access: 0x{:x}", offset);
+            }
+        }
+    }
+
+    fn read_common_config_word(&self, offset: u64, queues: &Vec<Queue>) -> u16 {
+        match offset {
+            0x10 => 0,                   // TODO msi-x (crbug/854765): self.msix_config,
+            0x12 => queues.len() as u16, // num_queues
+            0x16 => self.queue_select,
+            0x18 => self.with_queue(queues, |q| q.size).unwrap_or(0),
+            0x1c => if self.with_queue(queues, |q| q.ready).unwrap_or(false) {
+                1
+            } else {
+                0
+            },
+            0x1e => self.queue_select, // notify_off
+            _ => 0,
+        }
+    }
+
+    fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut Vec<Queue>) {
+        match offset {
+            0x10 => (), // TODO msi-x (crbug/854765): self.msix_config = value,
+            0x16 => self.queue_select = value,
+            0x18 => self.with_queue_mut(queues, |q| q.size = value),
+            0x1a => (), // TODO msi-x (crbug/854765): self.with_queue_mut(queues, |q| q.msix_vector = v),
+            0x1c => self.with_queue_mut(queues, |q| q.ready = value == 1),
+            _ => {
+                warn!("invalid virtio register word write: 0x{:x}", offset);
+            }
+        }
+    }
+
+    fn read_common_config_dword(&self, offset: u64, device: &Box<VirtioDevice>) -> u32 {
+        match offset {
+            0x00 => self.device_feature_select,
+            0x04 => {
+                // TODO(dverkamp): This hack (copied from MmioDevice) unconditionally
+                // reports support for VIRTIO_F_VERSION_1; once all devices have been
+                // fixed to report VIRTIO_F_VERSION_1, remove this workaround.
+                device.features(self.device_feature_select) |
+                        if self.device_feature_select == 1 { 0x1 } else { 0x0 }
+            },
+            0x08 => self.driver_feature_select,
+            _ => 0,
+        }
+    }
+
+    fn write_common_config_dword(
+        &mut self,
+        offset: u64,
+        value: u32,
+        queues: &mut Vec<Queue>,
+        device: &mut Box<VirtioDevice>,
+    ) {
+        fn hi(v: &mut GuestAddress, x: u32) {
+            *v = (*v & 0xffffffff) | ((x as u64) << 32)
+        }
+
+        fn lo(v: &mut GuestAddress, x: u32) {
+            *v = (*v & !0xffffffff) | (x as u64)
+        }
+
+        match offset {
+            0x00 => self.device_feature_select = value,
+            0x08 => self.driver_feature_select = value,
+            0x0c => device.ack_features(self.driver_feature_select, value),
+            0x20 => self.with_queue_mut(queues, |q| lo(&mut q.desc_table, value)),
+            0x24 => self.with_queue_mut(queues, |q| hi(&mut q.desc_table, value)),
+            0x28 => self.with_queue_mut(queues, |q| lo(&mut q.avail_ring, value)),
+            0x2c => self.with_queue_mut(queues, |q| hi(&mut q.avail_ring, value)),
+            0x30 => self.with_queue_mut(queues, |q| lo(&mut q.used_ring, value)),
+            0x34 => self.with_queue_mut(queues, |q| hi(&mut q.used_ring, value)),
+            _ => {
+                warn!("invalid virtio register dword write: 0x{:x}", offset);
+            }
+        }
+    }
+
+    fn read_common_config_qword(&self, _offset: u64) -> u64 {
+        0 // Assume the guest has no reason to read write-only registers.
+    }
+
+    fn write_common_config_qword(&mut self, offset: u64, value: u64, queues: &mut Vec<Queue>) {
+        match offset {
+            0x20 => self.with_queue_mut(queues, |q| q.desc_table = GuestAddress(value)),
+            0x28 => self.with_queue_mut(queues, |q| q.avail_ring = GuestAddress(value)),
+            0x30 => self.with_queue_mut(queues, |q| q.used_ring = GuestAddress(value)),
+            _ => {
+                warn!("invalid virtio register qword write: 0x{:x}", offset);
+            }
+        }
+    }
+
+    fn with_queue<U, F>(&self, queues: &Vec<Queue>, f: F) -> Option<U>
+    where
+        F: FnOnce(&Queue) -> U,
+    {
+        queues.get(self.queue_select as usize).map(f)
+    }
+
+    fn with_queue_mut<F: FnOnce(&mut Queue)>(&self, queues: &mut Vec<Queue>, f: F) {
+        if let Some(queue) = queues.get_mut(self.queue_select as usize) {
+            f(queue);
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use std::os::unix::io::RawFd;
+    use std::sync::Arc;
+    use std::sync::atomic::AtomicUsize;
+    use sys_util::{EventFd, GuestMemory};
+
+    struct DummyDevice(u32);
+    const QUEUE_SIZE: u16 = 256;
+    const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE];
+    const DUMMY_FEATURES: u32 = 0x5555_aaaa;
+    impl VirtioDevice for DummyDevice {
+        fn keep_fds(&self) -> Vec<RawFd> {
+            Vec::new()
+        }
+        fn device_type(&self) -> u32 {
+            return self.0;
+        }
+        fn queue_max_sizes(&self) -> &[u16] {
+            QUEUE_SIZES
+        }
+        fn activate(&mut self,
+                    _mem: GuestMemory,
+                    _interrupt_evt: EventFd,
+                    _status: Arc<AtomicUsize>,
+                    _queues: Vec<Queue>,
+                    _queue_evts: Vec<EventFd>) {
+        }
+        fn features(&self, _page: u32) -> u32 {
+            DUMMY_FEATURES
+        }
+    }
+
+    #[test]
+    fn write_base_regs() {
+        let mut regs = VirtioPciCommonConfig {
+            driver_status: 0xaa,
+            config_generation: 0x55,
+            device_feature_select: 0x0,
+            driver_feature_select: 0x0,
+            queue_select: 0xff,
+        };
+
+        let mut dev: Box<VirtioDevice> = Box::new(DummyDevice(0));
+        let mut queues = Vec::new();
+
+        // Can set all bits of driver_status.
+        regs.write(0x14, &[0x55], &mut queues, &mut dev);
+        let mut read_back = vec![0x00];
+        regs.read(0x14, &mut read_back, &mut queues, &mut dev);
+        assert_eq!(read_back[0], 0x55);
+
+        // The config generation register is read only.
+        regs.write(0x15, &[0xaa], &mut queues, &mut dev);
+        let mut read_back = vec![0x00];
+        regs.read(0x15, &mut read_back, &mut queues, &mut dev);
+        assert_eq!(read_back[0], 0x55);
+
+        // Device features is read-only and passed through from the device.
+        regs.write(0x04, &[0, 0, 0, 0], &mut queues, &mut dev);
+        let mut read_back = vec![0, 0, 0, 0];
+        regs.read(0x04, &mut read_back, &mut queues, &mut dev);
+        assert_eq!(LittleEndian::read_u32(&read_back), DUMMY_FEATURES);
+
+        // Feature select registers are read/write.
+        regs.write(0x00, &[1, 2, 3, 4], &mut queues, &mut dev);
+        let mut read_back = vec![0, 0, 0, 0];
+        regs.read(0x00, &mut read_back, &mut queues, &mut dev);
+        assert_eq!(LittleEndian::read_u32(&read_back), 0x0403_0201);
+        regs.write(0x08, &[1, 2, 3, 4], &mut queues, &mut dev);
+        let mut read_back = vec![0, 0, 0, 0];
+        regs.read(0x08, &mut read_back, &mut queues, &mut dev);
+        assert_eq!(LittleEndian::read_u32(&read_back), 0x0403_0201);
+
+        // 'queue_select' can be read and written.
+        regs.write(0x16, &[0xaa, 0x55], &mut queues, &mut dev);
+        let mut read_back = vec![0x00, 0x00];
+        regs.read(0x16, &mut read_back, &mut queues, &mut dev);
+        assert_eq!(read_back[0], 0xaa);
+        assert_eq!(read_back[1], 0x55);
+    }
+}
diff --git a/devices/src/virtio/virtio_pci_device.rs b/devices/src/virtio/virtio_pci_device.rs
new file mode 100644
index 0000000..e9b57ac
--- /dev/null
+++ b/devices/src/virtio/virtio_pci_device.rs
@@ -0,0 +1,404 @@
+// 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.
+
+use std;
+use std::os::unix::io::{AsRawFd, RawFd};
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+
+use super::*;
+use data_model::{DataInit, Le32};
+use pci::{
+    PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciDevice, PciDeviceError,
+    PciHeaderType, PciInterruptPin, PciSubclass,
+};
+use resources::SystemAllocator;
+use sys_util::{EventFd, GuestMemory, Result};
+
+use self::virtio_pci_common_config::VirtioPciCommonConfig;
+
+enum PciCapabilityType {
+    CommonConfig = 1,
+    NotifyConfig = 2,
+    IsrConfig = 3,
+    DeviceConfig = 4,
+    PciConfig = 5,
+}
+
+#[allow(dead_code)]
+#[repr(packed)]
+#[derive(Clone, Copy)]
+struct VirtioPciCap {
+    cap_len: u8,      // Generic PCI field: capability length
+    cfg_type: u8,     // Identifies the structure.
+    bar: u8,          // Where to find it.
+    padding: [u8; 3], // Pad to full dword.
+    offset: Le32,     // Offset within bar.
+    length: Le32,     // Length of the structure, in bytes.
+}
+// It is safe to implement DataInit; all members are simple numbers and any value is valid.
+unsafe impl DataInit for VirtioPciCap {}
+
+impl PciCapability for VirtioPciCap {
+    fn bytes(&self) -> &[u8] {
+        self.as_slice()
+    }
+
+    fn id(&self) -> PciCapabilityID {
+        PciCapabilityID::VendorSpecific
+    }
+}
+
+const VIRTIO_PCI_CAPABILITY_BYTES: u8 = 16;
+
+impl VirtioPciCap {
+    pub fn new(cfg_type: PciCapabilityType, bar: u8, offset: u32, length: u32) -> Self {
+        VirtioPciCap {
+            cap_len: VIRTIO_PCI_CAPABILITY_BYTES,
+            cfg_type: cfg_type as u8,
+            bar,
+            padding: [0; 3],
+            offset: Le32::from(offset),
+            length: Le32::from(length),
+        }
+    }
+}
+
+#[allow(dead_code)]
+#[repr(packed)]
+#[derive(Clone, Copy)]
+struct VirtioPciNotifyCap {
+    cap: VirtioPciCap,
+    notify_off_multiplier: Le32,
+}
+// It is safe to implement DataInit; all members are simple numbers and any value is valid.
+unsafe impl DataInit for VirtioPciNotifyCap {}
+
+impl PciCapability for VirtioPciNotifyCap {
+    fn bytes(&self) -> &[u8] {
+        self.as_slice()
+    }
+
+    fn id(&self) -> PciCapabilityID {
+        PciCapabilityID::VendorSpecific
+    }
+}
+
+impl VirtioPciNotifyCap {
+    pub fn new(
+        cfg_type: PciCapabilityType,
+        bar: u8,
+        offset: u32,
+        length: u32,
+        multiplier: Le32,
+    ) -> Self {
+        VirtioPciNotifyCap {
+            cap: VirtioPciCap {
+                cap_len: std::mem::size_of::<VirtioPciNotifyCap>() as u8,
+                cfg_type: cfg_type as u8,
+                bar,
+                padding: [0; 3],
+                offset: Le32::from(offset),
+                length: Le32::from(length),
+            },
+            notify_off_multiplier: multiplier,
+        }
+    }
+}
+
+/// Subclasses for virtio.
+#[allow(dead_code)]
+#[derive(Copy, Clone)]
+pub enum PciVirtioSubclass {
+    NonTransitionalBase = 0xff,
+}
+
+impl PciSubclass for PciVirtioSubclass {
+    fn get_register_value(&self) -> u8 {
+        *self as u8
+    }
+}
+
+// Allocate one bar for the structs pointed to by the capability structures.
+const COMMON_CONFIG_BAR_OFFSET: u64 = 0x0000;
+const COMMON_CONFIG_SIZE: u64 = 56;
+const ISR_CONFIG_BAR_OFFSET: u64 = 0x1000;
+const ISR_CONFIG_SIZE: u64 = 1;
+const DEVICE_CONFIG_BAR_OFFSET: u64 = 0x2000;
+const DEVICE_CONFIG_SIZE: u64 = 0x1000;
+const NOTIFICATION_BAR_OFFSET: u64 = 0x3000;
+const NOTIFICATION_SIZE: u64 = 0x1000;
+const CAPABILITY_BAR_SIZE: u64 = 0x4000;
+
+const NOTIFY_OFF_MULTIPLIER: u32 = 4; // A dword per notification address.
+
+const VIRTIO_PCI_VENDOR_ID: u16 = 0x1af4;
+const VIRTIO_PCI_DEVICE_ID_BASE: u16 = 0x1040; // Add to device type to get device ID.
+
+/// Implements the
+/// [PCI](http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-650001)
+/// transport for virtio devices.
+pub struct VirtioPciDevice {
+    config_regs: PciConfiguration,
+
+    device: Box<VirtioDevice>,
+    device_activated: bool,
+
+    interrupt_status: Arc<AtomicUsize>,
+    interrupt_evt: Option<EventFd>,
+    queues: Vec<Queue>,
+    queue_evts: Vec<EventFd>,
+    mem: Option<GuestMemory>,
+    settings_bar: u8,
+
+    common_config: VirtioPciCommonConfig,
+}
+
+impl VirtioPciDevice {
+    /// Constructs a new PCI transport for the given virtio device.
+    pub fn new(mem: GuestMemory, device: Box<VirtioDevice>) -> Result<Self> {
+        let mut queue_evts = Vec::new();
+        for _ in device.queue_max_sizes().iter() {
+            queue_evts.push(EventFd::new()?)
+        }
+        let queues = device
+            .queue_max_sizes()
+            .iter()
+            .map(|&s| Queue::new(s))
+            .collect();
+
+        let pci_device_id = VIRTIO_PCI_DEVICE_ID_BASE + device.device_type() as u16;
+
+        let config_regs = PciConfiguration::new(
+            VIRTIO_PCI_VENDOR_ID,
+            pci_device_id,
+            PciClassCode::Other,
+            &PciVirtioSubclass::NonTransitionalBase,
+            None,
+            PciHeaderType::Device,
+            VIRTIO_PCI_VENDOR_ID,
+            pci_device_id,
+        );
+
+        Ok(VirtioPciDevice {
+            config_regs,
+            device,
+            device_activated: false,
+            interrupt_status: Arc::new(AtomicUsize::new(0)),
+            interrupt_evt: None,
+            queues,
+            queue_evts,
+            mem: Some(mem),
+            settings_bar: 0,
+            common_config: VirtioPciCommonConfig {
+                driver_status: 0,
+                config_generation: 0,
+                device_feature_select: 0,
+                driver_feature_select: 0,
+                queue_select: 0,
+            },
+        })
+    }
+
+    /// Gets the list of queue events that must be triggered whenever the VM writes to
+    /// `virtio::NOTIFY_REG_OFFSET` past the MMIO base. Each event must be triggered when the
+    /// value being written equals the index of the event in this list.
+    pub fn queue_evts(&self) -> &[EventFd] {
+        self.queue_evts.as_slice()
+    }
+
+    /// Gets the event this device uses to interrupt the VM when the used queue is changed.
+    pub fn interrupt_evt(&self) -> Option<&EventFd> {
+        self.interrupt_evt.as_ref()
+    }
+
+    fn is_driver_ready(&self) -> bool {
+        let ready_bits =
+            (DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK) as u8;
+        self.common_config.driver_status == ready_bits
+            && self.common_config.driver_status & DEVICE_FAILED as u8 == 0
+    }
+
+    fn are_queues_valid(&self) -> bool {
+        if let Some(mem) = self.mem.as_ref() {
+            self.queues.iter().all(|q| q.is_valid(mem))
+        } else {
+            false
+        }
+    }
+
+    fn add_pci_capabilities(&mut self, settings_bar: u8) {
+        // Add pointers to the different configuration structures from the PCI capabilities.
+        let common_cap = VirtioPciCap::new(
+            PciCapabilityType::CommonConfig,
+            settings_bar,
+            COMMON_CONFIG_BAR_OFFSET as u32,
+            COMMON_CONFIG_SIZE as u32,
+        );
+        self.config_regs.add_capability(&common_cap);
+
+        let isr_cap = VirtioPciCap::new(
+            PciCapabilityType::IsrConfig,
+            settings_bar,
+            ISR_CONFIG_BAR_OFFSET as u32,
+            ISR_CONFIG_SIZE as u32,
+        );
+        self.config_regs.add_capability(&isr_cap);
+
+        // TODO(dgreid) - set based on device's configuration size?
+        let device_cap = VirtioPciCap::new(
+            PciCapabilityType::DeviceConfig,
+            settings_bar,
+            DEVICE_CONFIG_BAR_OFFSET as u32,
+            DEVICE_CONFIG_SIZE as u32,
+        );
+        self.config_regs.add_capability(&device_cap);
+
+        let notify_cap = VirtioPciNotifyCap::new(
+            PciCapabilityType::NotifyConfig,
+            settings_bar,
+            NOTIFICATION_BAR_OFFSET as u32,
+            NOTIFICATION_SIZE as u32,
+            Le32::from(NOTIFY_OFF_MULTIPLIER),
+        );
+        self.config_regs.add_capability(&notify_cap);
+
+        //TODO(dgreid) - How will the configuration_cap work?
+        let configuration_cap = VirtioPciCap::new(PciCapabilityType::PciConfig, 0, 0, 0);
+        self.config_regs
+            .add_capability(&configuration_cap);
+
+        self.settings_bar = settings_bar;
+    }
+}
+
+impl PciDevice for VirtioPciDevice {
+    fn keep_fds(&self) -> Vec<RawFd> {
+        let mut fds = self.device.keep_fds();
+        if let Some(ref interrupt_evt) = self.interrupt_evt {
+            fds.push(interrupt_evt.as_raw_fd());
+        }
+        fds
+    }
+
+    fn assign_irq(&mut self, irq_evt: EventFd, irq_num: u32, irq_pin: PciInterruptPin) {
+        self.config_regs.set_irq(irq_num as u8, irq_pin);
+        self.interrupt_evt = Some(irq_evt);
+    }
+
+    fn allocate_io_bars(
+        &mut self,
+        resources: &mut SystemAllocator,
+    ) -> std::result::Result<Vec<(u64, u64)>, PciDeviceError> {
+        // Allocate one bar for the structures pointed to by the capability structures.
+        let mut ranges = Vec::new();
+        let settings_config_addr = resources
+            .allocate_mmio_addresses(CAPABILITY_BAR_SIZE)
+            .ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?;
+        let settings_bar = self
+            .config_regs
+            .add_memory_region(settings_config_addr, CAPABILITY_BAR_SIZE)
+            .ok_or(PciDeviceError::IoRegistrationFailed(settings_config_addr))?
+            as u8;
+        ranges.push((settings_config_addr, CAPABILITY_BAR_SIZE));
+
+        // Once the BARs are allocated, the capabilities can be added to the PCI configuration.
+        self.add_pci_capabilities(settings_bar);
+
+        Ok(ranges)
+    }
+
+    fn ioeventfds(&self) -> Vec<(&EventFd, u64)> {
+        let bar0 = self.config_regs.get_bar_addr(self.settings_bar as usize) as u64;
+        let notify_base = bar0 + NOTIFICATION_BAR_OFFSET;
+        self.queue_evts()
+            .iter()
+            .enumerate()
+            .map(|(i, event)| (event, notify_base + i as u64 * NOTIFY_OFF_MULTIPLIER as u64))
+            .collect()
+    }
+
+    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]) {
+        // The driver is only allowed to do aligned, properly sized access.
+        let bar0 = self.config_regs.get_bar_addr(self.settings_bar as usize) as u64;
+        let offset = addr - bar0;
+        match offset {
+            o if COMMON_CONFIG_BAR_OFFSET <= o
+                && o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE =>
+            {
+                self.common_config
+                    .read(o - COMMON_CONFIG_BAR_OFFSET, data, &mut self.queues, &mut self.device)
+            }
+            o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
+                if let Some(v) = data.get_mut(0) {
+                    *v = self.interrupt_status.load(Ordering::SeqCst) as u8;
+                }
+            }
+            o if DEVICE_CONFIG_BAR_OFFSET <= o
+                && o < DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE =>
+            {
+                self.device.read_config(o - DEVICE_CONFIG_BAR_OFFSET, data);
+            }
+            o if NOTIFICATION_BAR_OFFSET <= o
+                && o < NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE =>
+            {
+                // Handled with ioeventfds.
+            }
+            _ => (),
+        }
+    }
+
+    fn write_bar(&mut self, addr: u64, data: &[u8]) {
+        let bar0 = self.config_regs.get_bar_addr(self.settings_bar as usize) as u64;
+        let offset = addr - bar0;
+        match offset {
+            o if COMMON_CONFIG_BAR_OFFSET <= o
+                && o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE =>
+            {
+                self.common_config
+                    .write(o - COMMON_CONFIG_BAR_OFFSET, data, &mut self.queues, &mut self.device)
+            }
+            o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => {
+                if let Some(v) = data.get(0) {
+                    self.interrupt_status
+                        .fetch_and(!(*v as usize), Ordering::SeqCst);
+                }
+            }
+            o if DEVICE_CONFIG_BAR_OFFSET <= o
+                && o < DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE =>
+            {
+                self.device.write_config(o - DEVICE_CONFIG_BAR_OFFSET, data);
+            }
+            o if NOTIFICATION_BAR_OFFSET <= o
+                && o < NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE =>
+            {
+                // Handled with ioeventfds.
+            }
+            _ => (),
+        };
+
+        if !self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
+            if let Some(interrupt_evt) = self.interrupt_evt.take() {
+                if let Some(mem) = self.mem.take() {
+                    self.device.activate(
+                        mem,
+                        interrupt_evt,
+                        self.interrupt_status.clone(),
+                        self.queues.clone(),
+                        self.queue_evts.split_off(0),
+                    );
+                    self.device_activated = true;
+                }
+            }
+        }
+    }
+}