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 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<Arc<Mutex<dyn PciDevice>>>,
_device_reloc: Arc<dyn DeviceRelocation>,
device_reloc: Arc<dyn DeviceRelocation>,
}
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,
);
}
}
}
}

View File

@ -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<PciBarRegionType>; 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<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 {

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 {
/// 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<BarReprogrammingParams> {
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`.

View File

@ -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};

View File

@ -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<BarReprogrammingParams> {
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) {

View File

@ -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<BarReprogrammingParams> {
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;

View File

@ -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")]