pci: Detect BAR reprogramming

Based on the value being written to the BAR, the implementation can
now detect if the BAR is being moved to another address. If that is the
case, it invokes move_bar() function from the DeviceRelocation trait.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
Sebastien Boeuf 2019-10-28 10:50:32 -07:00 committed by Samuel Ortiz
parent 04a449d3f3
commit 149b61b213
7 changed files with 197 additions and 28 deletions

View File

@ -9,6 +9,7 @@ use crate::device::{DeviceRelocation, Error as PciDeviceError, PciDevice};
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice; use devices::BusDevice;
use std; use std;
use std::ops::DerefMut;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use vm_memory::{Address, GuestAddress, GuestUsize}; use vm_memory::{Address, GuestAddress, GuestUsize};
@ -75,7 +76,7 @@ pub struct PciBus {
/// Devices attached to this bus. /// Devices attached to this bus.
/// Device 0 is host bridge. /// Device 0 is host bridge.
devices: Vec<Arc<Mutex<dyn PciDevice>>>, devices: Vec<Arc<Mutex<dyn PciDevice>>>,
_device_reloc: Arc<dyn DeviceRelocation>, device_reloc: Arc<dyn DeviceRelocation>,
} }
impl PciBus { impl PciBus {
@ -86,7 +87,7 @@ impl PciBus {
PciBus { PciBus {
devices, devices,
_device_reloc: device_reloc, device_reloc,
} }
} }
@ -185,10 +186,26 @@ impl PciConfigIo {
return; return;
} }
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) { let pci_bus = self.pci_bus.lock().unwrap();
d.lock() if let Some(d) = pci_bus.devices.get(device) {
.unwrap() let mut device = d.lock().unwrap();
.write_config_register(register, offset, data);
// 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; return;
} }
if let Some(d) = self.pci_bus.lock().unwrap().devices.get(device) { let pci_bus = self.pci_bus.lock().unwrap();
d.lock() if let Some(d) = pci_bus.devices.get(device) {
.unwrap() let mut device = d.lock().unwrap();
.write_config_register(register, offset, data);
// 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,
);
}
} }
} }
} }

View File

@ -4,6 +4,7 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::device::BarReprogrammingParams;
use crate::{MsixConfig, PciInterruptPin}; use crate::{MsixConfig, PciInterruptPin};
use byteorder::{ByteOrder, LittleEndian}; use byteorder::{ByteOrder, LittleEndian};
use std::fmt::{self, Display}; use std::fmt::{self, Display};
@ -249,9 +250,11 @@ pub trait PciCapability {
pub struct PciConfiguration { pub struct PciConfiguration {
registers: [u32; NUM_CONFIGURATION_REGISTERS], registers: [u32; NUM_CONFIGURATION_REGISTERS],
writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register. writable_bits: [u32; NUM_CONFIGURATION_REGISTERS], // writable bits for each register.
bar_addr: [u32; NUM_BAR_REGS],
bar_size: [u32; NUM_BAR_REGS], bar_size: [u32; NUM_BAR_REGS],
bar_used: [bool; NUM_BAR_REGS], bar_used: [bool; NUM_BAR_REGS],
bar_type: [Option<PciBarRegionType>; NUM_BAR_REGS], bar_type: [Option<PciBarRegionType>; NUM_BAR_REGS],
rom_bar_addr: u32,
rom_bar_size: u32, rom_bar_size: u32,
rom_bar_used: bool, rom_bar_used: bool,
// Contains the byte offset and size of the last capability. // Contains the byte offset and size of the last capability.
@ -344,6 +347,7 @@ impl PciConfiguration {
) -> Self { ) -> Self {
let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS]; let mut registers = [0u32; NUM_CONFIGURATION_REGISTERS];
let mut writable_bits = [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]; let bar_size = [0u32; NUM_BAR_REGS];
registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id); registers[0] = u32::from(device_id) << 16 | u32::from(vendor_id);
// TODO(dverkamp): Status should be write-1-to-clear // TODO(dverkamp): Status should be write-1-to-clear
@ -373,9 +377,11 @@ impl PciConfiguration {
PciConfiguration { PciConfiguration {
registers, registers,
writable_bits, writable_bits,
bar_addr,
bar_size, bar_size,
bar_used: [false; NUM_BAR_REGS], bar_used: [false; NUM_BAR_REGS],
bar_type: [None; NUM_BAR_REGS], bar_type: [None; NUM_BAR_REGS],
rom_bar_addr: 0,
rom_bar_size: 0, rom_bar_size: 0,
rom_bar_used: false, rom_bar_used: false,
last_capability: None, last_capability: None,
@ -392,15 +398,15 @@ impl PciConfiguration {
/// Writes a 32bit register to `reg_idx` in the register map. /// Writes a 32bit register to `reg_idx` in the register map.
pub fn write_reg(&mut self, reg_idx: usize, value: u32) { pub fn write_reg(&mut self, reg_idx: usize, value: u32) {
let mut mask = self.writable_bits[reg_idx]; let mut mask = self.writable_bits[reg_idx];
if reg_idx >= BAR0_REG
&& reg_idx < BAR0_REG + NUM_BAR_REGS if value == 0xffff_ffff {
&& (value & BAR_MEM_ADDR_MASK) == BAR_MEM_ADDR_MASK if reg_idx >= BAR0_REG && reg_idx < BAR0_REG + NUM_BAR_REGS {
{ // Handle very specific case where the BAR is being written with
// Handle very specific case where the BAR is being written with // all 1's to retrieve the BAR size on next BAR reading.
// all 1's to retrieve the BAR size on next BAR reading. mask = self.bar_size[reg_idx - 4];
mask = self.bar_size[reg_idx - 4]; } else if reg_idx == ROM_BAR_REG {
} else if reg_idx == ROM_BAR_REG && (value & ROM_BAR_ADDR_MASK) == ROM_BAR_ADDR_MASK { mask = self.rom_bar_size;
mask = self.rom_bar_size; }
} }
if let Some(r) = self.registers.get_mut(reg_idx) { 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.registers[bar_idx + 1] = (config.addr >> 32) as u32;
self.writable_bits[bar_idx + 1] = 0xffff_ffff; 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_size[config.reg_idx + 1] = (config.size >> 32) as u32;
self.bar_used[config.reg_idx + 1] = true; 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.registers[bar_idx] = ((config.addr as u32) & mask) | lower_bits;
self.writable_bits[bar_idx] = mask; 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_size[config.reg_idx] = config.size as u32;
self.bar_used[config.reg_idx] = true; self.bar_used[config.reg_idx] = true;
self.bar_type[config.reg_idx] = Some(config.region_type); 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.registers[config.reg_idx] = (config.addr as u32) | active;
self.writable_bits[config.reg_idx] = ROM_BAR_ADDR_MASK; 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_size = config.size as u32;
self.rom_bar_used = true; self.rom_bar_used = true;
Ok(config.reg_idx) Ok(config.reg_idx)
@ -648,6 +657,101 @@ impl PciConfiguration {
pub fn read_config_register(&self, reg_idx: usize) -> u32 { pub fn read_config_register(&self, reg_idx: usize) -> u32 {
self.read_reg(reg_idx) self.read_reg(reg_idx)
} }
pub fn detect_bar_reprogramming(
&mut self,
reg_idx: usize,
data: &[u8],
) -> Option<BarReprogrammingParams> {
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 { impl Default for PciBarConfiguration {

View File

@ -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 { pub trait PciDevice: BusDevice {
/// Assign a legacy PCI IRQ to this device. /// Assign a legacy PCI IRQ to this device.
/// The device may write to `irq_evt` to trigger an interrupt. /// 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. /// Gets a register from the configuration space.
/// * `reg_idx` - The index of the config register to read. /// * `reg_idx` - The index of the config register to read.
fn read_config_register(&self, reg_idx: usize) -> u32; 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<BarReprogrammingParams> {
None
}
/// Reads from a BAR region mapped in to the device. /// Reads from a BAR region mapped in to the device.
/// * `addr` - The guest address inside the BAR. /// * `addr` - The guest address inside the BAR.
/// * `data` - Filled with the data from `addr`. /// * `data` - Filled with the data from `addr`.

View File

@ -23,7 +23,8 @@ pub use self::configuration::{
PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass, PciNetworkControllerSubclass, PciProgrammingInterface, PciSerialBusSubClass, PciSubclass,
}; };
pub use self::device::{ 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::msi::MsiCap;
pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE}; pub use self::msix::{MsixCap, MsixConfig, MsixTableEntry, MSIX_TABLE_ENTRY_SIZE};

View File

@ -16,9 +16,9 @@ use kvm_bindings::{
}; };
use kvm_ioctls::*; use kvm_ioctls::*;
use pci::{ use pci::{
MsiCap, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, PciCapabilityID, BarReprogrammingParams, MsiCap, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType,
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass, PciCapabilityID, PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType,
MSIX_TABLE_ENTRY_SIZE, PciSubclass, MSIX_TABLE_ENTRY_SIZE,
}; };
use std::os::unix::io::AsRawFd; use std::os::unix::io::AsRawFd;
use std::ptr::null_mut; use std::ptr::null_mut;
@ -967,6 +967,14 @@ impl PciDevice for VfioPciDevice {
& mask & mask
} }
fn detect_bar_reprogramming(
&mut self,
reg_idx: usize,
data: &[u8],
) -> Option<BarReprogrammingParams> {
self.configuration.detect_bar_reprogramming(reg_idx, data)
}
fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) { fn read_bar(&mut self, base: u64, offset: u64, data: &mut [u8]) {
let addr = base + offset; let addr = base + offset;
if let Some(region) = self.find_region(addr) { if let Some(region) = self.find_region(addr) {

View File

@ -19,10 +19,10 @@ use std::sync::{Arc, Mutex, RwLock};
use devices::BusDevice; use devices::BusDevice;
use pci::{ use pci::{
InterruptDelivery, InterruptParameters, MsixCap, MsixConfig, PciBarConfiguration, BarReprogrammingParams, InterruptDelivery, InterruptParameters, MsixCap, MsixConfig,
PciBarRegionType, PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciDevice, PciBarConfiguration, PciBarRegionType, PciCapability, PciCapabilityID, PciClassCode,
PciDeviceError, PciHeaderType, PciInterruptPin, PciMassStorageSubclass, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciInterruptPin,
PciNetworkControllerSubclass, PciSubclass, PciMassStorageSubclass, PciNetworkControllerSubclass, PciSubclass,
}; };
use vm_allocator::SystemAllocator; use vm_allocator::SystemAllocator;
use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap, GuestUsize, Le32}; use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap, GuestUsize, Le32};
@ -520,6 +520,14 @@ impl PciDevice for VirtioPciDevice {
self.configuration.read_reg(reg_idx) self.configuration.read_reg(reg_idx)
} }
fn detect_bar_reprogramming(
&mut self,
reg_idx: usize,
data: &[u8],
) -> Option<BarReprogrammingParams> {
self.configuration.detect_bar_reprogramming(reg_idx, data)
}
fn ioeventfds(&self) -> Vec<(&EventFd, u64, u64)> { fn ioeventfds(&self) -> Vec<(&EventFd, u64, u64)> {
let bar0 = self.configuration.get_bar_addr(self.settings_bar as usize); let bar0 = self.configuration.get_bar_addr(self.settings_bar as usize);
let notify_base = bar0 + NOTIFICATION_BAR_OFFSET; let notify_base = bar0 + NOTIFICATION_BAR_OFFSET;

View File

@ -38,7 +38,6 @@ use std::sync::{Arc, Mutex, RwLock};
#[cfg(feature = "pci_support")] #[cfg(feature = "pci_support")]
use vfio::{VfioDevice, VfioDmaMapping, VfioPciDevice, VfioPciError}; use vfio::{VfioDevice, VfioDmaMapping, VfioPciDevice, VfioPciError};
use vm_allocator::SystemAllocator; use vm_allocator::SystemAllocator;
#[cfg(feature = "mmio_support")]
use vm_memory::GuestAddress; use vm_memory::GuestAddress;
use vm_memory::{Address, GuestMemoryMmap, GuestUsize}; use vm_memory::{Address, GuestMemoryMmap, GuestUsize};
#[cfg(feature = "pci_support")] #[cfg(feature = "pci_support")]