diff --git a/pci/src/bus.rs b/pci/src/bus.rs index 4c5c0235f..1519474d5 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -9,6 +9,7 @@ use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice}; use byteorder::{ByteOrder, LittleEndian}; use devices::BusDevice; use std; +use std::ops::DerefMut; use std::sync::Arc; use std::sync::Mutex; use vm_memory::{Address, GuestAddress, GuestUsize}; @@ -75,7 +76,7 @@ pub struct PciBus { /// Devices attached to this bus. /// Device 0 is host bridge. devices: Vec>>, - _device_reloc: Arc, + device_reloc: Arc, } impl PciBus { @@ -86,7 +87,7 @@ impl PciBus { PciBus { devices, - _device_reloc: device_reloc, + device_reloc, } } @@ -185,10 +186,26 @@ impl PciConfigIo { return; } - if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) { - d.lock() - .unwrap() - .write_config_register(register, offset, data); + let pci_bus = self.pci_bus.lock().unwrap(); + if let Some(d) = pci_bus.devices.get(device) { + let mut device = d.lock().unwrap(); + + // Find out if one of the device's BAR is being reprogrammed + let bar_reprog_params = device.detect_bar_reprogramming(register, data); + + // Update the register value + device.write_config_register(register, offset, data); + + // Reprogram the BAR if needed + if let Some(params) = bar_reprog_params { + pci_bus.device_reloc.move_bar( + params.old_base, + params.new_base, + params.len, + device.deref_mut(), + params.region_type, + ); + } } } @@ -285,10 +302,26 @@ impl PciConfigMmio { return; } - if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) { - d.lock() - .unwrap() - .write_config_register(register, offset, data); + let pci_bus = self.pci_bus.lock().unwrap(); + if let Some(d) = pci_bus.devices.get(device) { + let mut device = d.lock().unwrap(); + + // Find out if one of the device's BAR is being reprogrammed + let bar_reprog_params = device.detect_bar_reprogramming(register, data); + + // Update the register value + device.write_config_register(register, offset, data); + + // Reprogram the BAR if needed + if let Some(params) = bar_reprog_params { + pci_bus.device_reloc.move_bar( + params.old_base, + params.new_base, + params.len, + device.deref_mut(), + params.region_type, + ); + } } } } diff --git a/pci/src/configuration.rs b/pci/src/configuration.rs index d7d13e978..f18d8b655 100755 --- a/pci/src/configuration.rs +++ b/pci/src/configuration.rs @@ -4,6 +4,7 @@ use std::sync::{Arc, Mutex}; +use crate::device::BarReprogrammingParams; use crate::{MsixConfig, PciInterruptPin}; use byteorder::{ByteOrder, LittleEndian}; use std::fmt::{self, Display}; @@ -249,9 +250,11 @@ pub trait PciCapability { pub struct PciConfiguration { registers: [u32; NUM_CONFIGURATION_REGISTERS], writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register. + bar_addr: [u32; NUM_BAR_REGS], bar_size: [u32; NUM_BAR_REGS], bar_used: [bool; NUM_BAR_REGS], bar_type: [Option; NUM_BAR_REGS], + rom_bar_addr: u32, rom_bar_size: u32, rom_bar_used: bool, // Contains the byte offset and size of the last capability. @@ -344,6 +347,7 @@ impl PciConfiguration { ) -> Self { let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS]; let mut writable_bits = [0u32; NUM_CONFIGURATION_REGISTERS]; + let bar_addr = [0u32; NUM_BAR_REGS]; let bar_size = [0u32; NUM_BAR_REGS]; registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id); // TODO(dverkamp): Status should be write-1-to-clear @@ -373,9 +377,11 @@ impl PciConfiguration { PciConfiguration { registers, writable_bits, + bar_addr, bar_size, bar_used: [false; NUM_BAR_REGS], bar_type: [None; NUM_BAR_REGS], + rom_bar_addr: 0, rom_bar_size: 0, rom_bar_used: false, last_capability: None, @@ -392,15 +398,15 @@ impl PciConfiguration { /// Writes a 32bit register to `reg_idx` in the register map. pub fn write_reg(&mut self, reg_idx: usize, value: u32) { let mut mask = self.writable_bits[reg_idx]; - if reg_idx >= BAR0_REG - && reg_idx < BAR0_REG + NUM_BAR_REGS - && (value & BAR_MEM_ADDR_MASK) == BAR_MEM_ADDR_MASK - { - // Handle very specific case where the BAR is being written with - // all 1's to retrieve the BAR size on next BAR reading. - mask = self.bar_size[reg_idx - 4]; - } else if reg_idx == ROM_BAR_REG && (value & ROM_BAR_ADDR_MASK) == ROM_BAR_ADDR_MASK { - mask = self.rom_bar_size; + + if value == 0xffff_ffff { + if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS { + // Handle very specific case where the BAR is being written with + // all 1's to retrieve the BAR size on next BAR reading. + mask = self.bar_size[reg_idx - 4]; + } else if reg_idx == ROM_BAR_REG { + mask = self.rom_bar_size; + } } if let Some(r) = self.registers.get_mut(reg_idx) { @@ -499,6 +505,7 @@ impl PciConfiguration { self.registers[bar_idx + 1] = (config.addr >> 32) as u32; self.writable_bits[bar_idx + 1] = 0xffff_ffff; + self.bar_addr[config.reg_idx + 1] = self.registers[bar_idx + 1]; self.bar_size[config.reg_idx + 1] = (config.size >> 32) as u32; self.bar_used[config.reg_idx + 1] = true; } @@ -514,6 +521,7 @@ impl PciConfiguration { self.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits; self.writable_bits[bar_idx] = mask; + self.bar_addr[config.reg_idx] = self.registers[bar_idx]; self.bar_size[config.reg_idx] = config.size as u32; self.bar_used[config.reg_idx] = true; self.bar_type[config.reg_idx] = Some(config.region_type); @@ -545,6 +553,7 @@ impl PciConfiguration { self.registers[config.reg_idx] = (config.addr as u32) | active; self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK; + self.rom_bar_addr = self.registers[config.reg_idx]; self.rom_bar_size = config.size as u32; self.rom_bar_used = true; Ok(config.reg_idx) @@ -648,6 +657,101 @@ impl PciConfiguration { pub fn read_config_register(&self, reg_idx: usize) -> u32 { self.read_reg(reg_idx) } + + pub fn detect_bar_reprogramming( + &mut self, + reg_idx: usize, + data: &[u8], + ) -> Option { + if data.len() != 4 { + return None; + } + + let value = LittleEndian::read_u32(data); + + if value == 0xffff_ffff { + return None; + } + + let mask = self.writable_bits[reg_idx]; + if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS { + let bar_idx = reg_idx - 4; + if (value & mask) != (self.bar_addr[bar_idx] & mask) { + // Handle special case where the address being written is + // different from the address initially provided. This is a + // BAR reprogramming case which needs to be properly caught. + if let Some(bar_type) = self.bar_type[bar_idx] { + match bar_type { + PciBarRegionType::Memory64BitRegion => {} + _ => { + debug!( + "DETECT BAR REPROG: current 0x{:x}, new 0x{:x}", + self.registers[reg_idx], value + ); + let old_base = u64::from(self.bar_addr[bar_idx] & mask); + let new_base = u64::from(value & mask); + let len = u64::from(self.bar_size[bar_idx]); + let region_type = bar_type; + + self.bar_addr[bar_idx] = value; + + return Some(BarReprogrammingParams { + old_base, + new_base, + len, + region_type, + }); + } + } + } else if (reg_idx > BAR0_REG) + && (self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]) + != (self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]) + { + debug!( + "DETECT BAR REPROG: current 0x{:x}, new 0x{:x}", + self.registers[reg_idx], value + ); + let old_base = u64::from(self.bar_addr[bar_idx] & mask) << 32 + | u64::from(self.bar_addr[bar_idx - 1] & self.writable_bits[reg_idx - 1]); + let new_base = u64::from(value & mask) << 32 + | u64::from(self.registers[reg_idx - 1] & self.writable_bits[reg_idx - 1]); + let len = u64::from(self.bar_size[bar_idx]) << 32 + | u64::from(self.bar_size[bar_idx - 1]); + let region_type = PciBarRegionType::Memory64BitRegion; + + self.bar_addr[bar_idx] = value; + self.bar_addr[bar_idx - 1] = self.registers[reg_idx - 1]; + + return Some(BarReprogrammingParams { + old_base, + new_base, + len, + region_type, + }); + } + } + } else if reg_idx == ROM_BAR_REG && (value & mask) != (self.rom_bar_addr & mask) { + debug!( + "DETECT ROM BAR REPROG: current 0x{:x}, new 0x{:x}", + self.registers[reg_idx], value + ); + let old_base = u64::from(self.rom_bar_addr & mask); + let new_base = u64::from(value & mask); + let len = u64::from(self.rom_bar_size); + let region_type = PciBarRegionType::Memory32BitRegion; + + self.rom_bar_addr = value; + + return Some(BarReprogrammingParams { + old_base, + new_base, + len, + region_type, + }); + } + + None + } } impl Default for PciBarConfiguration { diff --git a/pci/src/device.rs b/pci/src/device.rs index eb9599d22..e41b8273e 100755 --- a/pci/src/device.rs +++ b/pci/src/device.rs @@ -47,6 +47,14 @@ impl Display for Error { } } +#[derive(Clone, Copy)] +pub struct BarReprogrammingParams { + pub old_base: u64, + pub new_base: u64, + pub len: u64, + pub region_type: PciBarRegionType, +} + pub trait PciDevice: BusDevice { /// Assign a legacy PCI IRQ to this device. /// The device may write to `irq_evt` to trigger an interrupt. @@ -82,6 +90,14 @@ pub trait PciDevice: BusDevice { /// Gets a register from the configuration space. /// * `reg_idx` - The index of the config register to read. fn read_config_register(&self, reg_idx: usize) -> u32; + /// Detects if a BAR is being reprogrammed. + fn detect_bar_reprogramming( + &mut self, + _reg_idx: usize, + _data: &[u8], + ) -> Option { + None + } /// Reads from a BAR region mapped in to the device. /// * `addr` - The guest address inside the BAR. /// * `data` - Filled with the data from `addr`. diff --git a/pci/src/lib.rs b/pci/src/lib.rs index 4432bb3f1..eb7921787 100644 --- a/pci/src/lib.rs +++ b/pci/src/lib.rs @@ -23,7 +23,8 @@ pub use self::configuration::{ PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass, }; pub use self::device::{ - DeviceRelocation, Error as PciDeviceError, InterruptDelivery, InterruptParameters, PciDevice, + BarReprogrammingParams, DeviceRelocation, Error as PciDeviceError, InterruptDelivery, + InterruptParameters, PciDevice, }; pub use self::msi::MsiCap; pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE}; diff --git a/vfio/src/vfio_pci.rs b/vfio/src/vfio_pci.rs index e65251ea9..d497040d1 100644 --- a/vfio/src/vfio_pci.rs +++ b/vfio/src/vfio_pci.rs @@ -16,9 +16,9 @@ use kvm_bindings::{ }; use kvm_ioctls::*; use pci::{ - MsiCap, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, PciCapabilityID, - PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass, - MSIX_TABLE_ENTRY_SIZE, + BarReprogrammingParams, MsiCap, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, + PciCapabilityID, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, + PciSubclass, MSIX_TABLE_ENTRY_SIZE, }; use std::os::unix::io::AsRawFd; use std::ptr::null_mut; @@ -967,6 +967,14 @@ impl PciDevice for VfioPciDevice { & mask } + fn detect_bar_reprogramming( + &mut self, + reg_idx: usize, + data: &[u8], + ) -> Option { + self.configuration.detect_bar_reprogramming(reg_idx, data) + } + fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) { let addr = base + offset; if let Some(region) = self.find_region(addr) { diff --git a/vm-virtio/src/transport/pci_device.rs b/vm-virtio/src/transport/pci_device.rs index 845f1cc7c..16d31fd5d 100755 --- a/vm-virtio/src/transport/pci_device.rs +++ b/vm-virtio/src/transport/pci_device.rs @@ -19,10 +19,10 @@ use std::sync::{Arc, Mutex, RwLock}; use devices::BusDevice; use pci::{ - InterruptDelivery, InterruptParameters, MsixCap, MsixConfig, PciBarConfiguration, - PciBarRegionType, PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciDevice, - PciDeviceError, PciHeaderType, PciInterruptPin, PciMassStorageSubclass, - PciNetworkControllerSubclass, PciSubclass, + BarReprogrammingParams, InterruptDelivery, InterruptParameters, MsixCap, MsixConfig, + PciBarConfiguration, PciBarRegionType, PciCapability, PciCapabilityID, PciClassCode, + PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciInterruptPin, + PciMassStorageSubclass, PciNetworkControllerSubclass, PciSubclass, }; use vm_allocator::SystemAllocator; use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap, GuestUsize, Le32}; @@ -520,6 +520,14 @@ impl PciDevice for VirtioPciDevice { self.configuration.read_reg(reg_idx) } + fn detect_bar_reprogramming( + &mut self, + reg_idx: usize, + data: &[u8], + ) -> Option { + self.configuration.detect_bar_reprogramming(reg_idx, data) + } + fn ioeventfds(&self) -> Vec<(&EventFd, u64, u64)> { let bar0 = self.configuration.get_bar_addr(self.settings_bar as usize); let notify_base = bar0 + NOTIFICATION_BAR_OFFSET; diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 381728fa5..2926c4d1b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -38,7 +38,6 @@ use std::sync::{Arc, Mutex, RwLock}; #[cfg(feature = "pci_support")] use vfio::{VfioDevice, VfioDmaMapping, VfioPciDevice, VfioPciError}; use vm_allocator::SystemAllocator; -#[cfg(feature = "mmio_support")] use vm_memory::GuestAddress; use vm_memory::{Address, GuestMemoryMmap, GuestUsize}; #[cfg(feature = "pci_support")]