// 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-BSD-3-Clause file. // // Copyright © 2019 Intel Corporation // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause use std::any::Any; use std::cmp; use std::io::Write; use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU16, AtomicUsize, Ordering}; use std::sync::{Arc, Barrier, Mutex}; use anyhow::anyhow; use libc::EFD_NONBLOCK; use log::{error, info}; use pci::{ BarReprogrammingParams, MaybeMutInterruptSourceGroup, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, PciCapability, PciCapabilityId, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciMassStorageSubclass, PciNetworkControllerSubclass, PciSubclass, }; use serde::{Deserialize, Serialize}; use thiserror::Error; use virtio_queue::{Queue, QueueT}; use vm_allocator::{AddressAllocator, SystemAllocator}; use vm_device::dma_mapping::ExternalDmaMapping; use vm_device::interrupt::{ InterruptIndex, InterruptManager, InterruptSourceGroup, MsiIrqGroupConfig, }; use vm_device::{BusDevice, PciBarType, Resource}; use vm_memory::{Address, ByteValued, GuestAddress, GuestAddressSpace, GuestMemoryAtomic, Le32}; use vm_migration::{Migratable, MigratableError, Pausable, Snapshot, Snapshottable, Transportable}; use vm_virtio::AccessPlatform; use vmm_sys_util::eventfd::EventFd; use super::pci_common_config::VirtioPciCommonConfigState; use crate::transport::{VIRTIO_PCI_COMMON_CONFIG_ID, VirtioPciCommonConfig, VirtioTransport}; use crate::{ ActivateResult, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT, GuestMemoryMmap, VirtioDevice, VirtioDeviceType, VirtioInterrupt, VirtioInterruptType, }; /// Vector value used to disable MSI for a queue. pub(super) const VIRTQ_MSI_NO_VECTOR: u16 = 0xffff; enum PciCapabilityType { Common = 1, Notify = 2, Isr = 3, Device = 4, Pci = 5, SharedMemory = 8, } // This offset represents the 2 bytes omitted from the VirtioPciCap structure // as they are already handled through add_capability(). These 2 bytes are the // fields cap_vndr (1 byte) and cap_next (1 byte) defined in the virtio spec. const VIRTIO_PCI_CAP_OFFSET: usize = 2; #[allow(dead_code)] #[repr(C, 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. id: u8, // Multiple capabilities of the same type padding: [u8; 2], // Pad to full dword. offset: Le32, // Offset within bar. length: Le32, // Length of the structure, in bytes. } // SAFETY: 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_CAP_LEN_OFFSET: u8 = 2; impl VirtioPciCap { pub fn new(cfg_type: PciCapabilityType, pci_bar: u8, offset: u32, length: u32) -> Self { VirtioPciCap { cap_len: (std::mem::size_of::() as u8) + VIRTIO_PCI_CAP_LEN_OFFSET, cfg_type: cfg_type as u8, pci_bar, id: 0, padding: [0; 2], offset: Le32::from(offset), length: Le32::from(length), } } } #[allow(dead_code)] #[repr(C, packed)] #[derive(Clone, Copy, Default)] struct VirtioPciNotifyCap { cap: VirtioPciCap, notify_off_multiplier: Le32, } // SAFETY: 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) + VIRTIO_PCI_CAP_LEN_OFFSET, cfg_type: cfg_type as u8, pci_bar, id: 0, padding: [0; 2], offset: Le32::from(offset), length: Le32::from(length), }, notify_off_multiplier: multiplier, } } } #[allow(dead_code)] #[repr(C, packed)] #[derive(Clone, Copy, Default)] struct VirtioPciCap64 { cap: VirtioPciCap, offset_hi: Le32, length_hi: Le32, } // SAFETY: All members are simple numbers and any value is valid. unsafe impl ByteValued for VirtioPciCap64 {} impl PciCapability for VirtioPciCap64 { fn bytes(&self) -> &[u8] { self.as_slice() } fn id(&self) -> PciCapabilityId { PciCapabilityId::VendorSpecific } } impl VirtioPciCap64 { pub fn new(cfg_type: PciCapabilityType, pci_bar: u8, id: u8, offset: u64, length: u64) -> Self { VirtioPciCap64 { cap: VirtioPciCap { cap_len: (std::mem::size_of::() as u8) + VIRTIO_PCI_CAP_LEN_OFFSET, cfg_type: cfg_type as u8, pci_bar, id, padding: [0; 2], offset: Le32::from(offset as u32), length: Le32::from(length as u32), }, offset_hi: Le32::from((offset >> 32) as u32), length_hi: Le32::from((length >> 32) as u32), } } } #[allow(dead_code)] #[repr(C, packed)] #[derive(Clone, Copy, Default)] struct VirtioPciCfgCap { cap: VirtioPciCap, pci_cfg_data: [u8; 4], } // SAFETY: All members are simple numbers and any value is valid. unsafe impl ByteValued for VirtioPciCfgCap {} impl PciCapability for VirtioPciCfgCap { fn bytes(&self) -> &[u8] { self.as_slice() } fn id(&self) -> PciCapabilityId { PciCapabilityId::VendorSpecific } } impl VirtioPciCfgCap { fn new() -> Self { VirtioPciCfgCap { cap: VirtioPciCap::new(PciCapabilityType::Pci, 0, 0, 0), ..Default::default() } } } #[derive(Clone, Copy, Default)] struct VirtioPciCfgCapInfo { offset: usize, cap: VirtioPciCfgCap, } #[allow(dead_code)] #[derive(Copy, Clone)] pub enum PciVirtioSubclass { NonTransitionalBase = 0xff, } impl PciSubclass for PciVirtioSubclass { fn get_register_value(&self) -> u8 { *self as u8 } } /// Max number of virtio queues Cloud Hypervisor supports. /// This is set by the current size of the notification BAR. const MAX_QUEUES: u64 = 0x400; // Automatically compute the position of the next entry in the BAR. // This handles alignment properly and is much less error-prone than // manual calculation. const fn next_bar_addr_align(offset: u64, size: u64, align: u64) -> u64 { assert!(align >= 0x2000, "too small alignment for structure in BAR"); assert!(align.is_power_of_two(), "alignment must be a power of 2"); (offset + size).next_multiple_of(align) } // Same as next_bar_addr_align(), but with the default alignment (8K). const fn next_bar_addr(offset: u64, size: u64) -> u64 { next_bar_addr_align(offset, size, 0x2000) } // Allocate one bar for the structs pointed to by the capability structures. // As per the PCI specification, because the same BAR shares MSI-X and non // MSI-X structures, it is recommended to use 8KiB alignment for all those // structures. const COMMON_CONFIG_BAR_OFFSET: u64 = 0x0000; const COMMON_CONFIG_SIZE: u64 = 56; const ISR_CONFIG_BAR_OFFSET: u64 = next_bar_addr(COMMON_CONFIG_BAR_OFFSET, COMMON_CONFIG_SIZE); const ISR_CONFIG_SIZE: u64 = 1; const DEVICE_CONFIG_BAR_OFFSET: u64 = next_bar_addr(ISR_CONFIG_BAR_OFFSET, ISR_CONFIG_SIZE); const DEVICE_CONFIG_SIZE: u64 = 0x1000; const NOTIFICATION_BAR_OFFSET: u64 = next_bar_addr(DEVICE_CONFIG_BAR_OFFSET, DEVICE_CONFIG_SIZE); const NOTIFICATION_SIZE: u64 = MAX_QUEUES * NOTIFY_OFF_MULTIPLIER as u64; const MSIX_TABLE_BAR_OFFSET: u64 = next_bar_addr(NOTIFICATION_BAR_OFFSET, NOTIFICATION_SIZE); // The size is 256KiB because the table can hold up to 2048 entries, with each // entry being 128 bits (4 DWORDS). const MSIX_TABLE_SIZE: u64 = 0x40000; const MSIX_PBA_BAR_OFFSET: u64 = next_bar_addr(MSIX_TABLE_BAR_OFFSET, MSIX_TABLE_SIZE); // The size is 2KiB because the Pending Bit Array has one bit per vector and it // can support up to 2048 vectors. const MSIX_PBA_SIZE: u64 = 0x800; // The BAR size must be a power of 2. const CAPABILITY_BAR_SIZE: u64 = (MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE).next_power_of_two(); const VIRTIO_COMMON_BAR_INDEX: usize = 0; const VIRTIO_SHM_BAR_INDEX: usize = 2; 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. #[derive(Serialize, Deserialize)] struct QueueState { max_size: u16, size: u16, ready: bool, desc_table: u64, avail_ring: u64, used_ring: u64, } #[derive(Serialize, Deserialize)] pub struct VirtioPciDeviceState { device_activated: bool, queues: Vec, interrupt_status: usize, cap_pci_cfg_offset: usize, cap_pci_cfg: Vec, } pub struct VirtioPciDeviceActivator { interrupt: Option>, memory: Option>, device: Arc>, device_activated: Arc, queues: Option>, barrier: Option>, id: String, status: Arc, } impl VirtioPciDeviceActivator { pub fn activate(mut self) -> ActivateResult { let mut locked_device = self.device.lock().unwrap(); locked_device.activate(crate::device::ActivationContext { mem: self.memory.take().unwrap(), interrupt_cb: self.interrupt.take().unwrap(), queues: self.queues.take().unwrap(), device_status: self.status, })?; self.device_activated.store(true, Ordering::SeqCst); if let Some(barrier) = self.barrier.take() { info!("{}: Waiting for barrier", self.id); barrier.wait(); info!("{}: Barrier released", self.id); } Ok(()) } } #[derive(Error, Debug)] pub enum VirtioPciDeviceError { #[error("Failed creating VirtioPciDevice")] CreateVirtioPciDevice(#[source] anyhow::Error), } pub type Result = std::result::Result; pub struct VirtioPciDevice { id: String, // PCI configuration registers. configuration: PciConfiguration, // virtio PCI common configuration common_config: VirtioPciCommonConfig, // MSI-X config msix_config: Option>>, // Number of MSI-X vectors msix_num: u16, // Virtio device reference and status device: Arc>, device_activated: Arc, // PCI interrupts. interrupt_status: Arc, virtio_interrupt: Option>, interrupt_source_group: MaybeMutInterruptSourceGroup, // virtio queues queues: Vec, queue_evts: Vec, // Guest memory memory: GuestMemoryAtomic, // Settings PCI BAR settings_bar: u8, // Whether to use 64-bit bar location or 32-bit use_64bit_bar: bool, // Add a dedicated structure to hold information about the very specific // virtio-pci capability VIRTIO_PCI_CAP_PCI_CFG. This is needed to support // the legacy/backward compatible mechanism of letting the guest access the // other virtio capabilities without mapping the PCI BARs. This can be // needed when the guest tries to early access the virtio configuration of // a device. cap_pci_cfg_info: VirtioPciCfgCapInfo, // Details of bar regions to free bar_regions: Vec, // EventFd to signal on to request activation activate_evt: EventFd, // Optional DMA handler dma_handler: Option>, // Pending activations pending_activations: Arc>>, } impl VirtioPciDevice { /// Constructs a new PCI transport for the given virtio device. #[allow(clippy::too_many_arguments)] pub fn new( id: String, memory: GuestMemoryAtomic, device: Arc>, msix_num: u16, access_platform: Option>, interrupt_manager: &dyn InterruptManager, pci_device_bdf: u32, activate_evt: EventFd, use_64bit_bar: bool, dma_handler: Option>, pending_activations: Arc>>, snapshot: Option<&Snapshot>, ) -> Result { let mut locked_device = device.lock().unwrap(); let mut queue_evts = Vec::new(); for _ in locked_device.queue_max_sizes().iter() { queue_evts.push(EventFd::new(EFD_NONBLOCK).map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!("Failed creating eventfd: {e}")) })?); } let num_queues = locked_device.queue_max_sizes().len(); if let Some(access_platform) = &access_platform { locked_device.set_access_platform(access_platform.clone()); } let mut queues: Vec = locked_device .queue_max_sizes() .iter() .map(|&s| Queue::new(s).unwrap()) .collect(); let pci_device_id = VIRTIO_PCI_DEVICE_ID_BASE + locked_device.device_type() as u16; let interrupt_source_group: MaybeMutInterruptSourceGroup = { let config = MsiIrqGroupConfig { base: 0, count: msix_num as InterruptIndex, }; (if locked_device.interrupt_source_mutable() { interrupt_manager .create_group_mut(config) .map(MaybeMutInterruptSourceGroup::Mutable) } else { interrupt_manager .create_group(config) .map(MaybeMutInterruptSourceGroup::Immutable) }) .map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( "Failed creating MSI interrupt group: {e}" )) })? }; let msix_state = vm_migration::state_from_id(snapshot, pci::MSIX_CONFIG_ID).map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( "Failed to get MsixConfigState from Snapshot: {e}" )) })?; let (msix_config, msix_config_clone) = if msix_num > 0 { let interrupt_source_group: MaybeMutInterruptSourceGroup = interrupt_source_group.clone(); let msix_config = Arc::new(Mutex::new( MsixConfig::new(msix_num, interrupt_source_group, pci_device_bdf, msix_state) .unwrap(), )); let msix_config_clone = msix_config.clone(); (Some(msix_config), Some(msix_config_clone)) } else { (None, None) }; let (class, subclass) = match VirtioDeviceType::from(locked_device.device_type()) { VirtioDeviceType::Net => ( PciClassCode::NetworkController, &PciNetworkControllerSubclass::EthernetController as &dyn PciSubclass, ), VirtioDeviceType::Block => ( PciClassCode::MassStorage, &PciMassStorageSubclass::MassStorage as &dyn PciSubclass, ), _ => ( PciClassCode::Other, &PciVirtioSubclass::NonTransitionalBase as &dyn PciSubclass, ), }; let pci_configuration_state = vm_migration::state_from_id(snapshot, pci::PCI_CONFIGURATION_ID).map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( "Failed to get PciConfigurationState from Snapshot: {e}" )) })?; let configuration = PciConfiguration::new( VIRTIO_PCI_VENDOR_ID, pci_device_id, 0x1, // For modern virtio-PCI devices class, subclass, None, PciHeaderType::Device, VIRTIO_PCI_VENDOR_ID, pci_device_id, msix_config_clone, pci_configuration_state, ); let common_config_state = vm_migration::state_from_id(snapshot, VIRTIO_PCI_COMMON_CONFIG_ID).map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( "Failed to get VirtioPciCommonConfigState from Snapshot: {e}" )) })?; let common_config = if let Some(common_config_state) = common_config_state { VirtioPciCommonConfig::new(common_config_state, access_platform) } else { VirtioPciCommonConfig::new( VirtioPciCommonConfigState { driver_status: 0, config_generation: 0, device_feature_select: 0, driver_feature_select: 0, queue_select: 0, msix_config: VIRTQ_MSI_NO_VECTOR, msix_queues: vec![VIRTQ_MSI_NO_VECTOR; num_queues], }, access_platform, ) }; let state: Option = snapshot .as_ref() .map(|s| s.to_state()) .transpose() .map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( "Failed to get VirtioPciDeviceState from Snapshot: {e}" )) })?; let (device_activated, interrupt_status, cap_pci_cfg_info) = if let Some(state) = state { // Update virtqueues indexes for both available and used rings. for (i, queue) in queues.iter_mut().enumerate() { queue.set_size(state.queues[i].size); queue.set_ready(state.queues[i].ready); queue .try_set_desc_table_address(GuestAddress(state.queues[i].desc_table)) .unwrap(); queue .try_set_avail_ring_address(GuestAddress(state.queues[i].avail_ring)) .unwrap(); queue .try_set_used_ring_address(GuestAddress(state.queues[i].used_ring)) .unwrap(); queue.set_next_avail( queue .used_idx(memory.memory().deref(), Ordering::Acquire) .unwrap() .0, ); queue.set_next_used( queue .used_idx(memory.memory().deref(), Ordering::Acquire) .unwrap() .0, ); } ( state.device_activated, state.interrupt_status, VirtioPciCfgCapInfo { offset: state.cap_pci_cfg_offset, cap: *VirtioPciCfgCap::from_slice(&state.cap_pci_cfg).unwrap(), }, ) } else { (false, 0, VirtioPciCfgCapInfo::default()) }; // Dropping the MutexGuard to unlock the VirtioDevice. This is required // in the context of a restore given the device might require some // activation, meaning it will require locking. Dropping the lock // prevents from a subtle deadlock. std::mem::drop(locked_device); let mut virtio_pci_device = VirtioPciDevice { id, configuration, common_config, msix_config, msix_num, device, device_activated: Arc::new(AtomicBool::new(device_activated)), interrupt_status: Arc::new(AtomicUsize::new(interrupt_status)), virtio_interrupt: None, queues, queue_evts, memory, settings_bar: 0, use_64bit_bar, interrupt_source_group: interrupt_source_group.clone(), cap_pci_cfg_info, bar_regions: vec![], activate_evt, dma_handler, pending_activations, }; if let Some(msix_config) = &virtio_pci_device.msix_config { virtio_pci_device.virtio_interrupt = Some(Arc::new(VirtioInterruptMsix::new( msix_config.clone(), virtio_pci_device.common_config.msix_config.clone(), virtio_pci_device.common_config.msix_queues.clone(), virtio_pci_device.interrupt_source_group.clone(), ))); } // In case of a restore, we can activate the device, as we know at // this point the virtqueues are in the right state and the device is // ready to be activated, which will spawn each virtio worker thread. if virtio_pci_device.device_activated.load(Ordering::SeqCst) && virtio_pci_device.is_driver_ready() { virtio_pci_device.activate().map_err(|e| { VirtioPciDeviceError::CreateVirtioPciDevice(anyhow!( "Failed activating the device: {e}" )) })?; } Ok(virtio_pci_device) } fn state(&self) -> VirtioPciDeviceState { VirtioPciDeviceState { device_activated: self.device_activated.load(Ordering::Acquire), interrupt_status: self.interrupt_status.load(Ordering::Acquire), queues: self .queues .iter() .map(|q| QueueState { max_size: q.max_size(), size: q.size(), ready: q.ready(), desc_table: q.desc_table(), avail_ring: q.avail_ring(), used_ring: q.used_ring(), }) .collect(), cap_pci_cfg_offset: self.cap_pci_cfg_info.offset, cap_pci_cfg: self.cap_pci_cfg_info.cap.bytes().to_vec(), } } /// 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. fn queue_evts(&self) -> &[EventFd] { self.queue_evts.as_slice() } fn is_driver_ready(&self) -> bool { let ready_bits = (DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK) as u8; let driver_status = self.common_config.driver_status.load(Ordering::SeqCst); driver_status == ready_bits && (driver_status & DEVICE_FAILED as u8) == 0 } /// Determines if the driver has requested the device (re)init / reset itself fn is_driver_init(&self) -> bool { self.common_config.driver_status.load(Ordering::SeqCst) == DEVICE_INIT as u8 } pub fn config_bar_addr(&self) -> u64 { self.configuration.get_bar_addr(self.settings_bar as usize) } 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::Common, 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::Isr, 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::Device, 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::Notify, 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)?; let configuration_cap = VirtioPciCfgCap::new(); self.cap_pci_cfg_info.offset = self .configuration .add_capability(&configuration_cap) .map_err(PciDeviceError::CapabilitiesSetup)? + VIRTIO_PCI_CAP_OFFSET; self.cap_pci_cfg_info.cap = configuration_cap; if self.msix_config.is_some() { let msix_cap = MsixCap::new( settings_bar, self.msix_num, MSIX_TABLE_BAR_OFFSET as u32, settings_bar, MSIX_PBA_BAR_OFFSET as u32, ); self.configuration .add_capability(&msix_cap) .map_err(PciDeviceError::CapabilitiesSetup)?; } self.settings_bar = settings_bar; Ok(()) } fn read_cap_pci_cfg(&mut self, offset: usize, mut data: &mut [u8]) { let cap_slice = self.cap_pci_cfg_info.cap.as_slice(); let data_len = data.len(); let cap_len = cap_slice.len(); if offset + data_len > cap_len { error!("Failed to read cap_pci_cfg from config space"); return; } if offset < std::mem::size_of::() { if let Some(end) = offset.checked_add(data_len) { // This write can't fail, offset and end are checked against config_len. data.write_all(&cap_slice[offset..cmp::min(end, cap_len)]) .unwrap(); } } else { let bar_offset: u32 = // SAFETY: we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long. unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) }; self.read_bar(0, bar_offset as u64, data); } } fn write_cap_pci_cfg(&mut self, offset: usize, data: &[u8]) -> Option> { let cap_slice = self.cap_pci_cfg_info.cap.as_mut_slice(); let data_len = data.len(); let cap_len = cap_slice.len(); if offset + data_len > cap_len { error!("Failed to write cap_pci_cfg to config space"); return None; } if offset < std::mem::size_of::() { let (_, right) = cap_slice.split_at_mut(offset); right[..data_len].copy_from_slice(data); None } else { let bar_offset: u32 = // SAFETY: we know self.cap_pci_cfg_info.cap.cap.offset is 32bits long. unsafe { std::mem::transmute(self.cap_pci_cfg_info.cap.cap.offset) }; self.write_bar(0, bar_offset as u64, data) } } pub fn virtio_device(&self) -> Arc> { self.device.clone() } fn prepare_activator(&mut self, barrier: Option>) -> VirtioPciDeviceActivator { let mut queues = Vec::new(); for (queue_index, queue) in self.queues.iter().enumerate() { if !queue.ready() { continue; } if !queue.is_valid(self.memory.memory().deref()) { error!("Queue {queue_index} is not valid"); } queues.push(( queue_index, vm_virtio::clone_queue(queue), self.queue_evts[queue_index].try_clone().unwrap(), )); } VirtioPciDeviceActivator { interrupt: self.virtio_interrupt.take(), memory: Some(self.memory.clone()), device: self.device.clone(), queues: Some(queues), device_activated: self.device_activated.clone(), barrier, id: self.id.clone(), status: self.common_config.driver_status.clone(), } } fn activate(&mut self) -> ActivateResult { self.prepare_activator(None).activate() } fn needs_activation(&self) -> bool { !self.device_activated.load(Ordering::SeqCst) && self.is_driver_ready() } pub fn dma_handler(&self) -> Option<&dyn ExternalDmaMapping> { self.dma_handler.as_deref() } } impl VirtioTransport for VirtioPciDevice { fn ioeventfds(&self, base_addr: u64) -> impl Iterator { let notify_base = base_addr + NOTIFICATION_BAR_OFFSET; self.queue_evts().iter().enumerate().map(move |(i, event)| { ( event, notify_base + i as u64 * u64::from(NOTIFY_OFF_MULTIPLIER), ) }) } } pub struct VirtioInterruptMsix { msix_config: Arc>, config_vector: Arc, queues_vectors: Arc>>, interrupt_source_group: MaybeMutInterruptSourceGroup, } impl VirtioInterruptMsix { pub fn new( msix_config: Arc>, config_vector: Arc, queues_vectors: Arc>>, interrupt_source_group: MaybeMutInterruptSourceGroup, ) -> Self { VirtioInterruptMsix { msix_config, config_vector, queues_vectors, interrupt_source_group, } } } impl VirtioInterrupt for VirtioInterruptMsix { fn trigger(&self, int_type: VirtioInterruptType) -> std::result::Result<(), std::io::Error> { let vector = match int_type { VirtioInterruptType::Config => self.config_vector.load(Ordering::Acquire), VirtioInterruptType::Queue(queue_index) => { self.queues_vectors.lock().unwrap()[queue_index as usize] } }; if vector == VIRTQ_MSI_NO_VECTOR { return Ok(()); } let config = &mut self.msix_config.lock().unwrap(); let entry = &config.table_entries[vector as usize]; // In case the vector control register associated with the entry // has its first bit set, this means the vector is masked and the // device should not inject the interrupt. // Instead, the Pending Bit Array table is updated to reflect there // is a pending interrupt for this specific vector. if config.masked() || entry.masked() { config.set_pba_bit(vector, false); return Ok(()); } self.interrupt_source_group .trigger(vector as InterruptIndex) } fn notifier(&self, int_type: VirtioInterruptType) -> Option { let vector = match int_type { VirtioInterruptType::Config => self.config_vector.load(Ordering::Acquire), VirtioInterruptType::Queue(queue_index) => { self.queues_vectors.lock().unwrap()[queue_index as usize] } }; self.interrupt_source_group .notifier(vector as InterruptIndex) } fn set_notifier( &self, interrupt: u32, eventfd: Option, vm: &dyn hypervisor::Vm, ) -> std::io::Result<()> { self.interrupt_source_group .set_notifier(interrupt, eventfd, vm) } } impl PciDevice for VirtioPciDevice { fn write_config_register( &mut self, reg_idx: usize, offset: u64, data: &[u8], ) -> (Vec, Option>) { // Handle the special case where the capability VIRTIO_PCI_CAP_PCI_CFG // is accessed. This capability has a special meaning as it allows the // guest to access other capabilities without mapping the PCI BAR. let base = reg_idx * 4; if base + offset as usize >= self.cap_pci_cfg_info.offset && base + offset as usize + data.len() <= self.cap_pci_cfg_info.offset + self.cap_pci_cfg_info.cap.bytes().len() { let offset = base + offset as usize - self.cap_pci_cfg_info.offset; (Vec::new(), self.write_cap_pci_cfg(offset, data)) } else { ( self.configuration .write_config_register(reg_idx, offset, data), None, ) } } fn read_config_register(&mut self, reg_idx: usize) -> u32 { // Handle the special case where the capability VIRTIO_PCI_CAP_PCI_CFG // is accessed. This capability has a special meaning as it allows the // guest to access other capabilities without mapping the PCI BAR. let base = reg_idx * 4; if base >= self.cap_pci_cfg_info.offset && base + 4 <= self.cap_pci_cfg_info.offset + self.cap_pci_cfg_info.cap.bytes().len() { let offset = base - self.cap_pci_cfg_info.offset; let mut data = [0u8; 4]; self.read_cap_pci_cfg(offset, &mut data); u32::from_le_bytes(data) } else { self.configuration.read_reg(reg_idx) } } fn allocate_bars( &mut self, _allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, mmio64_allocator: &mut AddressAllocator, resources: Option>, ) -> std::result::Result, PciDeviceError> { let mut bars = Vec::new(); let device_clone = self.device.clone(); let device = device_clone.lock().unwrap(); let mut settings_bar_addr = None; let mut use_64bit_bar = self.use_64bit_bar; let restoring = resources.is_some(); if let Some(resources) = resources { for resource in resources { if let Resource::PciBar { index, base, type_, .. } = resource && index == VIRTIO_COMMON_BAR_INDEX { settings_bar_addr = Some(GuestAddress(base)); use_64bit_bar = match type_ { PciBarType::Io => { return Err(PciDeviceError::InvalidResource(resource)); } PciBarType::Mmio32 => false, PciBarType::Mmio64 => true, }; break; } } // Error out if no resource was matching the BAR id. if settings_bar_addr.is_none() { return Err(PciDeviceError::MissingResource); } } // 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, region_type) = if use_64bit_bar { let region_type = PciBarRegionType::Memory64BitRegion; let addr = mmio64_allocator .allocate( settings_bar_addr, CAPABILITY_BAR_SIZE, Some(CAPABILITY_BAR_SIZE), ) .ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?; (addr, region_type) } else { let region_type = PciBarRegionType::Memory32BitRegion; let addr = mmio32_allocator .allocate( settings_bar_addr, CAPABILITY_BAR_SIZE, Some(CAPABILITY_BAR_SIZE), ) .ok_or(PciDeviceError::IoAllocationFailed(CAPABILITY_BAR_SIZE))?; (addr, region_type) }; let bar = PciBarConfiguration::default() .set_index(VIRTIO_COMMON_BAR_INDEX) .set_address(virtio_pci_bar_addr.raw_value()) .set_size(CAPABILITY_BAR_SIZE) .set_region_type(region_type); // The creation of the PCI BAR and its associated capabilities must // happen only during the creation of a brand new VM. When a VM is // restored from a known state, the BARs are already created with the // right content, therefore we don't need to go through this codepath. if !restoring { self.configuration.add_pci_bar(&bar).map_err(|e| { PciDeviceError::IoRegistrationFailed(virtio_pci_bar_addr.raw_value(), e) })?; // Once the BARs are allocated, the capabilities can be added to the PCI configuration. self.add_pci_capabilities(VIRTIO_COMMON_BAR_INDEX as u8)?; } bars.push(bar); // Allocate a dedicated BAR if there are some shared memory regions. if let Some(shm_list) = device.get_shm_regions() { let bar = PciBarConfiguration::default() .set_index(VIRTIO_SHM_BAR_INDEX) .set_address(shm_list.addr.raw_value()) .set_size(shm_list.mapping.size() as _); // The creation of the PCI BAR and its associated capabilities must // happen only during the creation of a brand new VM. When a VM is // restored from a known state, the BARs are already created with the // right content, therefore we don't need to go through this codepath. if !restoring { self.configuration.add_pci_bar(&bar).map_err(|e| { PciDeviceError::IoRegistrationFailed(shm_list.addr.raw_value(), e) })?; for (idx, shm) in shm_list.region_list.iter().enumerate() { let shm_cap = VirtioPciCap64::new( PciCapabilityType::SharedMemory, VIRTIO_SHM_BAR_INDEX as u8, idx as u8, shm.offset, shm.len, ); self.configuration .add_capability(&shm_cap) .map_err(PciDeviceError::CapabilitiesSetup)?; } } bars.push(bar); } self.bar_regions.clone_from(&bars); Ok(bars) } fn free_bars( &mut self, _allocator: &mut SystemAllocator, mmio32_allocator: &mut AddressAllocator, mmio64_allocator: &mut AddressAllocator, ) -> std::result::Result<(), PciDeviceError> { for bar in self.bar_regions.drain(..) { match bar.region_type() { PciBarRegionType::Memory32BitRegion => { mmio32_allocator.free(GuestAddress(bar.addr()), bar.size()); } PciBarRegionType::Memory64BitRegion => { mmio64_allocator.free(GuestAddress(bar.addr()), bar.size()); } _ => error!("Unexpected PCI bar type"), } } Ok(()) } fn move_bar( &mut self, old_base: u64, new_base: u64, ) -> std::result::Result<(), std::io::Error> { // We only update our idea of the bar in order to support free_bars() above. // The majority of the reallocation is done inside DeviceManager. for bar in self.bar_regions.iter_mut() { if bar.addr() == old_base { *bar = bar.set_address(new_base); } } Ok(()) } fn restore_bar_addr(&mut self, params: &BarReprogrammingParams) { self.configuration.restore_bar_addr(params); } fn read_bar(&mut self, _base: u64, offset: u64, data: &mut [u8]) { match offset { o if o < COMMON_CONFIG_BAR_OFFSET + COMMON_CONFIG_SIZE => self.common_config.read( o - COMMON_CONFIG_BAR_OFFSET, data, &self.queues, self.device.clone(), ), o if (ISR_CONFIG_BAR_OFFSET..ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE).contains(&o) => { if let Some(v) = data.get_mut(0) { // Reading this register resets it to 0. *v = self.interrupt_status.swap(0, Ordering::AcqRel) as u8; } } o if (DEVICE_CONFIG_BAR_OFFSET..DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE) .contains(&o) => { let device = self.device.lock().unwrap(); device.read_config(o - DEVICE_CONFIG_BAR_OFFSET, data); } o if (NOTIFICATION_BAR_OFFSET..NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE) .contains(&o) => { // Handled with ioeventfds. } o if (MSIX_TABLE_BAR_OFFSET..MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE).contains(&o) => { if let Some(msix_config) = &self.msix_config { msix_config .lock() .unwrap() .read_table(o - MSIX_TABLE_BAR_OFFSET, data); } } o if (MSIX_PBA_BAR_OFFSET..MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE).contains(&o) => { if let Some(msix_config) = &self.msix_config { msix_config .lock() .unwrap() .read_pba(o - MSIX_PBA_BAR_OFFSET, data); } } _ => (), } } fn write_bar(&mut self, _base: u64, offset: u64, data: &[u8]) -> Option> { let initial_ready = self.is_driver_ready(); 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.clone(), ), o if (ISR_CONFIG_BAR_OFFSET..ISR_CONFIG_BAR_OFFSET + ISR_CONFIG_SIZE).contains(&o) => { if let Some(v) = data.first() { self.interrupt_status .fetch_and(!(*v as usize), Ordering::AcqRel); } } o if (DEVICE_CONFIG_BAR_OFFSET..DEVICE_CONFIG_BAR_OFFSET + DEVICE_CONFIG_SIZE) .contains(&o) => { let mut device = self.device.lock().unwrap(); device.write_config(o - DEVICE_CONFIG_BAR_OFFSET, data); } o if (NOTIFICATION_BAR_OFFSET..NOTIFICATION_BAR_OFFSET + NOTIFICATION_SIZE) .contains(&o) => { #[cfg(feature = "sev_snp")] for (event, addr) in self.ioeventfds(_base) { if addr == _base + offset { event.write(1).unwrap(); } } // Handled with ioeventfds. #[cfg(not(feature = "sev_snp"))] error!("Unexpected write to notification BAR: offset = 0x{o:x}"); } o if (MSIX_TABLE_BAR_OFFSET..MSIX_TABLE_BAR_OFFSET + MSIX_TABLE_SIZE).contains(&o) => { if let Some(msix_config) = &self.msix_config { msix_config .lock() .unwrap() .write_table(o - MSIX_TABLE_BAR_OFFSET, data); } } o if (MSIX_PBA_BAR_OFFSET..MSIX_PBA_BAR_OFFSET + MSIX_PBA_SIZE).contains(&o) => { if let Some(msix_config) = &self.msix_config { msix_config .lock() .unwrap() .write_pba(o - MSIX_PBA_BAR_OFFSET, data); } } _ => (), } // Try and activate the device if the driver status has changed (from unready to ready) if !initial_ready && self.needs_activation() { let barrier = Arc::new(Barrier::new(2)); let activator = self.prepare_activator(Some(barrier.clone())); self.pending_activations.lock().unwrap().push(activator); info!( "{}: Needs activation; writing to activate event fd", self.id ); self.activate_evt.write(1).ok(); info!("{}: Needs activation; returning barrier", self.id); return Some(barrier); } // Device has been reset by the driver if self.device_activated.load(Ordering::SeqCst) && self.is_driver_init() { let mut device = self.device.lock().unwrap(); if let Some(virtio_interrupt) = device.reset() { // Upon reset the device returns its interrupt EventFD self.virtio_interrupt = Some(virtio_interrupt); self.device_activated.store(false, Ordering::SeqCst); // Reset queue readiness (changes queue_enable), queue sizes // and selected_queue as per spec for reset self.queues.iter_mut().for_each(Queue::reset); self.common_config.queue_select = 0; } else { error!("Attempt to reset device when not implemented in underlying device"); self.common_config .driver_status .store(crate::DEVICE_FAILED as u8, Ordering::SeqCst); } } None } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn id(&self) -> Option { Some(self.id.clone()) } } impl BusDevice for VirtioPciDevice { fn read(&mut self, base: u64, offset: u64, data: &mut [u8]) { self.read_bar(base, offset, data); } fn write(&mut self, base: u64, offset: u64, data: &[u8]) -> Option> { self.write_bar(base, offset, data) } } impl Pausable for VirtioPciDevice { fn pause(&mut self) -> std::result::Result<(), MigratableError> { Ok(()) } fn resume(&mut self) -> std::result::Result<(), MigratableError> { Ok(()) } } impl Snapshottable for VirtioPciDevice { fn id(&self) -> String { self.id.clone() } fn snapshot(&mut self) -> std::result::Result { let mut virtio_pci_dev_snapshot = Snapshot::new_from_state(&self.state())?; // Snapshot PciConfiguration virtio_pci_dev_snapshot .add_snapshot(self.configuration.id(), self.configuration.snapshot()?); // Snapshot VirtioPciCommonConfig virtio_pci_dev_snapshot .add_snapshot(self.common_config.id(), self.common_config.snapshot()?); // Snapshot MSI-X if let Some(msix_config) = &self.msix_config { let mut msix_config = msix_config.lock().unwrap(); virtio_pci_dev_snapshot.add_snapshot(msix_config.id(), msix_config.snapshot()?); } Ok(virtio_pci_dev_snapshot) } } impl Transportable for VirtioPciDevice {} impl Migratable for VirtioPciDevice {}