From c2c51dc9d18352b39cefb16db5e427a64960373f Mon Sep 17 00:00:00 2001 From: Samuel Ortiz Date: Thu, 18 Apr 2019 19:32:41 +0200 Subject: [PATCH] vm-virtio: Add PCI transport support Copied from crosvm 107edb3e with one main modification: VirtioPciDevice implements BusDevice. We need this modification because it is the only way for us to be able to add a VirtioPciDevice to the MMIO bus. Bus insertion takes a BusDevice. The fact that VirtioPciDevice implements PciDevice which itself implements BusDevice does not mean that Rust will automatically downcast a VirtioPciDevice into a BusDevice. crosvm works around that issue by having the PCI, virtio and BusDevice implementations in the same crate. Signed-off-by: Samuel Ortiz --- vm-virtio/src/lib.rs | 7 +- vm-virtio/src/transport/mod.rs | 7 + vm-virtio/src/transport/pci_common_config.rs | 335 ++++++++++++++ vm-virtio/src/transport/pci_device.rs | 461 +++++++++++++++++++ 4 files changed, 805 insertions(+), 5 deletions(-) create mode 100644 vm-virtio/src/transport/mod.rs create mode 100644 vm-virtio/src/transport/pci_common_config.rs create mode 100755 vm-virtio/src/transport/pci_device.rs diff --git a/vm-virtio/src/lib.rs b/vm-virtio/src/lib.rs index 7997bc1a2..b8b1f4ee2 100644 --- a/vm-virtio/src/lib.rs +++ b/vm-virtio/src/lib.rs @@ -20,20 +20,17 @@ use std::io; mod device; mod queue; +pub mod transport; + pub use self::device::*; pub use self::queue::*; #[allow(dead_code)] const DEVICE_INIT: u32 = 0x00; -#[allow(dead_code)] const DEVICE_ACKNOWLEDGE: u32 = 0x01; -#[allow(dead_code)] const DEVICE_DRIVER: u32 = 0x02; -#[allow(dead_code)] const DEVICE_DRIVER_OK: u32 = 0x04; -#[allow(dead_code)] const DEVICE_FEATURES_OK: u32 = 0x08; -#[allow(dead_code)] const DEVICE_FAILED: u32 = 0x80; #[allow(dead_code)] diff --git a/vm-virtio/src/transport/mod.rs b/vm-virtio/src/transport/mod.rs new file mode 100644 index 000000000..aa4aab243 --- /dev/null +++ b/vm-virtio/src/transport/mod.rs @@ -0,0 +1,7 @@ +// Copyright (c) 2019 Intel + +mod pci_common_config; +mod pci_device; + +pub use pci_common_config::VirtioPciCommonConfig; +pub use pci_device::VirtioPciDevice; diff --git a/vm-virtio/src/transport/pci_common_config.rs b/vm-virtio/src/transport/pci_common_config.rs new file mode 100644 index 000000000..8694a4bba --- /dev/null +++ b/vm-virtio/src/transport/pci_common_config.rs @@ -0,0 +1,335 @@ +// 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. +extern crate byteorder; + +use byteorder::{ByteOrder, LittleEndian}; +use vm_memory::GuestAddress; + +use crate::{Queue, VirtioDevice}; + +/// 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; // 0x00 // read-write +/// le32 device_feature; // 0x04 // read-only for driver +/// le32 driver_feature_select; // 0x08 // read-write +/// le32 driver_feature; // 0x0C // read-write +/// le16 msix_config; // 0x10 // read-write +/// le16 num_queues; // 0x12 // read-only for driver +/// u8 device_status; // 0x14 // read-write (driver_status) +/// u8 config_generation; // 0x15 // read-only for driver +/// ** About a specific virtqueue. +/// le16 queue_select; // 0x16 // read-write +/// le16 queue_size; // 0x18 // read-write, power of 2, or 0. +/// le16 queue_msix_vector; // 0x1A // read-write +/// le16 queue_enable; // 0x1C // read-write (Ready) +/// le16 queue_notify_off; // 0x1E // read-only for driver +/// le64 queue_desc; // 0x20 // read-write +/// le64 queue_avail; // 0x28 // read-write +/// le64 queue_used; // 0x30 // 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, + device: &mut dyn VirtioDevice, + ) { + assert!(data.len() <= 8); + + 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); + } + _ => error!("invalid data length for virtio read: len {}", data.len()), + } + } + + pub fn write( + &mut self, + offset: u64, + data: &[u8], + queues: &mut Vec, + device: &mut dyn VirtioDevice, + ) { + assert!(data.len() <= 8); + + 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), + _ => error!("invalid data length for virtio write: len {}", data.len()), + } + } + + fn read_common_config_byte(&self, offset: u64) -> u8 { + debug!("read_common_config_byte: offset 0x{:x}", offset); + // The driver is only allowed to do aligned, properly sized access. + match offset { + 0x14 => self.driver_status, + 0x15 => self.config_generation, + _ => { + warn!("invalid virtio config byte read: 0x{:x}", offset); + 0 + } + } + } + + fn write_common_config_byte(&mut self, offset: u64, value: u8) { + debug!("write_common_config_byte: offset 0x{:x}", offset); + match offset { + 0x14 => self.driver_status = value, + _ => { + warn!("invalid virtio config byte write: 0x{:x}", offset); + } + } + } + + fn read_common_config_word(&self, offset: u64, queues: &[Queue]) -> u16 { + debug!("read_common_config_word: offset 0x{:x}", offset); + 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 + _ => { + warn!("invalid virtio register word read: 0x{:x}", offset); + 0 + } + } + } + + fn write_common_config_word(&mut self, offset: u64, value: u16, queues: &mut Vec) { + debug!("write_common_config_word: offset 0x{:x}", offset); + 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: &dyn VirtioDevice) -> u32 { + debug!("read_common_config_dword: offset 0x{:x}", offset); + match offset { + 0x00 => self.device_feature_select, + 0x04 => { + // Only 64 bits of features (2 pages) are defined for now, so limit + // device_feature_select to avoid shifting by 64 or more bits. + if self.device_feature_select < 2 { + device.features(self.device_feature_select) + } else { + 0 + } + } + 0x08 => self.driver_feature_select, + _ => { + warn!("invalid virtio register dword read: 0x{:x}", offset); + 0 + } + } + } + + fn write_common_config_dword( + &mut self, + offset: u64, + value: u32, + queues: &mut Vec, + device: &mut dyn VirtioDevice, + ) { + debug!("write_common_config_dword: offset 0x{:x}", offset); + fn hi(v: &mut GuestAddress, x: u32) { + *v = (*v & 0xffff_ffff) | ((u64::from(x)) << 32) + } + + fn lo(v: &mut GuestAddress, x: u32) { + *v = (*v & !0xffff_ffff) | (u64::from(x)) + } + + match offset { + 0x00 => self.device_feature_select = value, + 0x08 => self.driver_feature_select = value, + 0x0c => { + if self.driver_feature_select < 2 { + device.ack_features(self.driver_feature_select, value); + } else { + warn!( + "invalid ack_features (page {}, value 0x{:x})", + 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 { + debug!("read_common_config_qword: offset 0x{:x}", _offset); + 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) { + debug!("write_common_config_qword: offset 0x{:x}", offset); + 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(&self, queues: &[Queue], f: F) -> Option + where + F: FnOnce(&Queue) -> U, + { + queues.get(self.queue_select as usize).map(f) + } + + fn with_queue_mut(&self, queues: &mut Vec, f: F) { + if let Some(queue) = queues.get_mut(self.queue_select as usize) { + f(queue); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ActivateResult; + + use std::sync::atomic::AtomicUsize; + use std::sync::Arc; + use vm_memory::GuestMemoryMmap; + use vmm_sys_util::EventFd; + + struct DummyDevice(u32); + const QUEUE_SIZE: u16 = 256; + const QUEUE_SIZES: &'static [u16] = &[QUEUE_SIZE]; + const DUMMY_FEATURES: u64 = 0x5555_aaaa; + impl VirtioDevice for DummyDevice { + fn device_type(&self) -> u32 { + return self.0; + } + fn queue_max_sizes(&self) -> &[u16] { + QUEUE_SIZES + } + fn activate( + &mut self, + _mem: GuestMemoryMmap, + _interrupt_evt: EventFd, + _status: Arc, + _queues: Vec, + _queue_evts: Vec, + ) -> ActivateResult { + Ok(()) + } + + fn features(&self, _page: u32) -> u32 { + DUMMY_FEATURES as u32 + } + + fn ack_features(&mut self, _page: u32, _value: u32) {} + + fn read_config(&self, _offset: u64, _data: &mut [u8]) {} + + fn write_config(&mut self, _offset: u64, _data: &[u8]) {} + } + + #[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 dev = &mut DummyDevice(0) as &mut dyn VirtioDevice; + let mut queues = Vec::new(); + + // Can set all bits of driver_status. + regs.write(0x14, &[0x55], &mut queues, dev); + let mut read_back = vec![0x00]; + regs.read(0x14, &mut read_back, &mut queues, dev); + assert_eq!(read_back[0], 0x55); + + // The config generation register is read only. + regs.write(0x15, &[0xaa], &mut queues, dev); + let mut read_back = vec![0x00]; + regs.read(0x15, &mut read_back, &mut queues, 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, dev); + let mut read_back = vec![0, 0, 0, 0]; + regs.read(0x04, &mut read_back, &mut queues, dev); + assert_eq!(LittleEndian::read_u32(&read_back), DUMMY_FEATURES as u32); + + // Feature select registers are read/write. + regs.write(0x00, &[1, 2, 3, 4], &mut queues, dev); + let mut read_back = vec![0, 0, 0, 0]; + regs.read(0x00, &mut read_back, &mut queues, dev); + assert_eq!(LittleEndian::read_u32(&read_back), 0x0403_0201); + regs.write(0x08, &[1, 2, 3, 4], &mut queues, dev); + let mut read_back = vec![0, 0, 0, 0]; + regs.read(0x08, &mut read_back, &mut queues, dev); + assert_eq!(LittleEndian::read_u32(&read_back), 0x0403_0201); + + // 'queue_select' can be read and written. + regs.write(0x16, &[0xaa, 0x55], &mut queues, dev); + let mut read_back = vec![0x00, 0x00]; + regs.read(0x16, &mut read_back, &mut queues, dev); + assert_eq!(read_back[0], 0xaa); + assert_eq!(read_back[1], 0x55); + } +} diff --git a/vm-virtio/src/transport/pci_device.rs b/vm-virtio/src/transport/pci_device.rs new file mode 100755 index 000000000..4f67a0cc4 --- /dev/null +++ b/vm-virtio/src/transport/pci_device.rs @@ -0,0 +1,461 @@ +// 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. + +extern crate devices; +extern crate pci; +extern crate vm_allocator; +extern crate vm_memory; +extern crate vmm_sys_util; + +use byteorder::{ByteOrder, LittleEndian}; +use libc::EFD_NONBLOCK; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use devices::BusDevice; +use pci::{ + PciBarConfiguration, PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciDevice, + PciDeviceError, PciHeaderType, PciInterruptPin, PciSubclass, +}; +use vm_allocator::SystemAllocator; +use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap, GuestUsize, Le32}; +use vmm_sys_util::{EventFd, Result}; + +use super::VirtioPciCommonConfig; +use crate::{ + Queue, VirtioDevice, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED, + DEVICE_FEATURES_OK, +}; + +#[allow(clippy::enum_variant_names)] +enum PciCapabilityType { + CommonConfig = 1, + NotifyConfig = 2, + IsrConfig = 3, + DeviceConfig = 4, + PciConfig = 5, +} + +#[allow(dead_code)] +#[repr(packed)] +#[derive(Clone, Copy, Default)] +struct VirtioPciCap { + cap_len: u8, // Generic PCI field: capability length + cfg_type: u8, // Identifies the structure. + pci_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 ByteValued. All members are simple numbers and any value is valid. +unsafe impl ByteValued 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, pci_bar: u8, offset: u32, length: u32) -> Self { + VirtioPciCap { + cap_len: VIRTIO_PCI_CAPABILITY_BYTES, + cfg_type: cfg_type as u8, + pci_bar, + padding: [0; 3], + offset: Le32::from(offset), + length: Le32::from(length), + } + } +} + +#[allow(dead_code)] +#[repr(packed)] +#[derive(Clone, Copy, Default)] +struct VirtioPciNotifyCap { + cap: VirtioPciCap, + notify_off_multiplier: Le32, +} +// It is safe to implement ByteValued. All members are simple numbers and any value is valid. +unsafe impl ByteValued 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, + pci_bar: u8, + offset: u32, + length: u32, + multiplier: Le32, + ) -> Self { + VirtioPciNotifyCap { + cap: VirtioPciCap { + cap_len: std::mem::size_of::() as u8, + cfg_type: cfg_type as u8, + pci_bar, + padding: [0; 3], + offset: Le32::from(offset), + length: Le32::from(length), + }, + notify_off_multiplier: multiplier, + } + } +} + +#[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. + +pub struct VirtioPciDevice { + // PCI configuration registers. + configuration: PciConfiguration, + + // virtio PCI common configuration + common_config: VirtioPciCommonConfig, + + // Virtio device reference and status + device: Box, + device_activated: bool, + + // PCI interrupts. + interrupt_status: Arc, + interrupt_evt: Option, + + // virtio queues + queues: Vec, + queue_evts: Vec, + + // Guest memory + memory: Option, + + // Setting PCI BAR + settings_bar: u8, +} + +impl VirtioPciDevice { + /// Constructs a new PCI transport for the given virtio device. + pub fn new(memory: GuestMemoryMmap, device: Box) -> Result { + let mut queue_evts = Vec::new(); + for _ in device.queue_max_sizes().iter() { + queue_evts.push(EventFd::new(EFD_NONBLOCK)?) + } + 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 configuration = 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 { + configuration, + common_config: VirtioPciCommonConfig { + driver_status: 0, + config_generation: 0, + device_feature_select: 0, + driver_feature_select: 0, + queue_select: 0, + }, + device, + device_activated: false, + interrupt_status: Arc::new(AtomicUsize::new(0)), + interrupt_evt: None, + queues, + queue_evts, + memory: Some(memory), + settings_bar: 0, + }) + } + + 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.memory.as_ref() { + self.queues.iter().all(|q| q.is_valid(mem)) + } else { + false + } + } + + fn add_pci_capabilities( + &mut self, + settings_bar: u8, + ) -> std::result::Result<(), PciDeviceError> { + // 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.configuration + .add_capability(&common_cap) + .map_err(PciDeviceError::CapabilitiesSetup)?; + + let isr_cap = VirtioPciCap::new( + PciCapabilityType::IsrConfig, + settings_bar, + ISR_CONFIG_BAR_OFFSET as u32, + ISR_CONFIG_SIZE as u32, + ); + self.configuration + .add_capability(&isr_cap) + .map_err(PciDeviceError::CapabilitiesSetup)?; + + // 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.configuration + .add_capability(&device_cap) + .map_err(PciDeviceError::CapabilitiesSetup)?; + + let notify_cap = VirtioPciNotifyCap::new( + PciCapabilityType::NotifyConfig, + settings_bar, + NOTIFICATION_BAR_OFFSET as u32, + NOTIFICATION_SIZE as u32, + Le32::from(NOTIFY_OFF_MULTIPLIER), + ); + self.configuration + .add_capability(¬ify_cap) + .map_err(PciDeviceError::CapabilitiesSetup)?; + + //TODO(dgreid) - How will the configuration_cap work? + let configuration_cap = VirtioPciCap::new(PciCapabilityType::PciConfig, 0, 0, 0); + self.configuration + .add_capability(&configuration_cap) + .map_err(PciDeviceError::CapabilitiesSetup)?; + + self.settings_bar = settings_bar; + Ok(()) + } +} + +impl PciDevice for VirtioPciDevice { + fn assign_irq(&mut self, irq_evt: EventFd, irq_num: u32, irq_pin: PciInterruptPin) { + self.configuration.set_irq(irq_num as u8, irq_pin); + self.interrupt_evt = Some(irq_evt); + } + + fn config_registers(&self) -> &PciConfiguration { + &self.configuration + } + + fn config_registers_mut(&mut self) -> &mut PciConfiguration { + &mut self.configuration + } + + fn allocate_bars( + &mut self, + allocator: &mut SystemAllocator, + ) -> std::result::Result, PciDeviceError> { + let mut ranges = Vec::new(); + + // Allocate the virtio-pci capability BAR. + // See http://docs.oasis-open.org/virtio/virtio/v1.0/cs04/virtio-v1.0-cs04.html#x1-740004 + let virtio_pci_bar_addr = allocator + .allocate_mmio_addresses(None, CAPABILITY_BAR_SIZE) + .ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?; + let config = PciBarConfiguration::default() + .set_register_index(0) + .set_address(virtio_pci_bar_addr.raw_value()) + .set_size(CAPABILITY_BAR_SIZE); + let virtio_pci_bar = + self.configuration.add_pci_bar(&config).map_err(|e| { + PciDeviceError::IoRegistrationFailed(virtio_pci_bar_addr.raw_value(), e) + })? as u8; + + println!( + "VIRTIO PCI BAR starts at 0x{:x}", + virtio_pci_bar_addr.raw_value() + ); + ranges.push((virtio_pci_bar_addr, CAPABILITY_BAR_SIZE)); + + // Once the BARs are allocated, the capabilities can be added to the PCI configuration. + self.add_pci_capabilities(virtio_pci_bar)?; + + // Allocate the device specific BARs. + for config in self.device.get_device_bars() { + let device_bar_addr = allocator + .allocate_mmio_addresses(None, config.get_size()) + .ok_or_else(|| PciDeviceError::IoAllocationFailed(config.get_size()))?; + config.set_address(device_bar_addr.raw_value()); + let _device_bar = self.configuration.add_pci_bar(&config).map_err(|e| { + PciDeviceError::IoRegistrationFailed(device_bar_addr.raw_value(), e) + })?; + ranges.push((device_bar_addr, config.get_size())); + } + + Ok(ranges) + } + + fn read_bar(&mut self, addr: u64, data: &mut [u8]) { + // The driver is only allowed to do aligned, properly sized access. + let bar0 = u64::from(self.configuration.get_bar_addr(self.settings_bar as usize)); + let offset = addr - bar0; + match offset { + o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.read( + o - COMMON_CONFIG_BAR_OFFSET, + data, + &mut self.queues, + self.device.as_mut(), + ), + o if ISR_CONFIG_BAR_OFFSET <= o && o < ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE => { + if let Some(v) = data.get_mut(0) { + // Reading this register resets it to 0. + *v = self.interrupt_status.swap(0, 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 = u64::from(self.configuration.get_bar_addr(self.settings_bar as usize)); + let offset = addr - bar0; + match offset { + o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.write( + o - COMMON_CONFIG_BAR_OFFSET, + data, + &mut self.queues, + self.device.as_mut(), + ), + 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.memory.take() { + self.device + .activate( + mem, + interrupt_evt, + self.interrupt_status.clone(), + self.queues.clone(), + self.queue_evts.split_off(0), + ) + .expect("Failed to activate device");; + self.device_activated = true; + } + } + } + } +} + +impl BusDevice for VirtioPciDevice { + fn read(&mut self, offset: u64, data: &mut [u8]) { + self.read_bar(offset, data) + } + + fn write(&mut self, offset: u64, data: &[u8]) { + self.write_bar(offset, data) + } + + fn write_config_register(&mut self, reg_idx: usize, offset: u64, data: &[u8]) { + if offset as usize + data.len() > 4 { + return; + } + + let regs = self.config_registers_mut(); + + match data.len() { + 1 => regs.write_byte(reg_idx * 4 + offset as usize, data[0]), + 2 => regs.write_word( + reg_idx * 4 + offset as usize, + u16::from(data[0]) | (u16::from(data[1]) << 8), + ), + 4 => regs.write_reg(reg_idx, LittleEndian::read_u32(data)), + _ => (), + } + } + + fn read_config_register(&self, reg_idx: usize) -> u32 { + self.config_registers().read_reg(reg_idx) + } +}