Skip to content

Commit

Permalink
vfio: pci: Track MSI and MSI-X capabilities
Browse files Browse the repository at this point in the history
In order to properly manage the VFIO device interrupt settings, we need
to keep track of both MSI and MSI-X PCI config capabilities changes.

When the guest programs the device for interrupt delivery, it writes to
the MSI and MSI-X capabilities. This information must be trapped and
cached in order to map the physical device interrupt delivery path to
the guest one. In other words, tracking MSI and MSI-X capabilites will
allow us to accurately build the KVM interrupt routes.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
  • Loading branch information
Sebastien Boeuf authored and Samuel Ortiz committed Jul 24, 2019
1 parent db5b476 commit 20f0116
Showing 1 changed file with 263 additions and 2 deletions.
265 changes: 263 additions & 2 deletions vfio/src/vfio_pci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ extern crate pci;
extern crate vm_allocator;

use crate::vfio_device::VfioDevice;
use byteorder::{ByteOrder, LittleEndian};
use devices::BusDevice;
use kvm_ioctls::*;
use pci::{
PciBarConfiguration, PciBarRegionType, PciClassCode, PciConfiguration, PciDevice,
PciDeviceError, PciHeaderType, PciSubclass,
MsiCap, MsixCap, MsixConfig, PciBarConfiguration, PciBarRegionType, PciCapabilityID,
PciClassCode, PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciSubclass,
MSIX_TABLE_ENTRY_SIZE,
};
use std::sync::Arc;
use vfio_bindings::bindings::vfio::*;
Expand All @@ -30,6 +32,158 @@ impl PciSubclass for PciVfioSubclass {
}
}

enum InterruptUpdateAction {
EnableMsi,
DisableMsi,
EnableMsix,
DisableMsix,
}

#[derive(Copy, Clone)]
struct VfioMsi {
cap: MsiCap,
cap_offset: u32,
}

impl VfioMsi {
fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
let old_enabled = self.cap.enabled();

self.cap.update(offset, data);

let new_enabled = self.cap.enabled();

if !old_enabled && new_enabled {
return Some(InterruptUpdateAction::EnableMsi);
}

if old_enabled && !new_enabled {
return Some(InterruptUpdateAction::DisableMsi);
}

None
}
}

struct VfioMsix {
bar: MsixConfig,
cap: MsixCap,
cap_offset: u32,
}

impl VfioMsix {
fn update(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
let old_enabled = self.cap.enabled();

// Update "Message Control" word
if offset == 2 && data.len() == 2 {
self.cap.set_msg_ctl(LittleEndian::read_u16(data));
}

let new_enabled = self.cap.enabled();

if !old_enabled && new_enabled {
return Some(InterruptUpdateAction::EnableMsix);
}

if old_enabled && !new_enabled {
return Some(InterruptUpdateAction::DisableMsix);
}

None
}

fn table_accessed(&self, bar_index: u32, offset: u64) -> bool {
let table_offset: u64 = u64::from(self.cap.table_offset());
let table_size: u64 = u64::from(self.cap.table_size()) * (MSIX_TABLE_ENTRY_SIZE as u64);
let table_bir: u32 = self.cap.table_bir();

bar_index == table_bir && offset >= table_offset && offset < table_offset + table_size
}
}

struct Interrupt {
msi: Option<VfioMsi>,
msix: Option<VfioMsix>,
}

impl Interrupt {
fn update_msi(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
if let Some(ref mut msi) = &mut self.msi {
let action = msi.update(offset, data);
return action;
}

None
}

fn update_msix(&mut self, offset: u64, data: &[u8]) -> Option<InterruptUpdateAction> {
if let Some(ref mut msix) = &mut self.msix {
let action = msix.update(offset, data);
return action;
}

None
}

fn accessed(&self, offset: u64) -> Option<(PciCapabilityID, u64)> {
if let Some(msi) = &self.msi {
if offset >= u64::from(msi.cap_offset)
&& offset < u64::from(msi.cap_offset) + msi.cap.size()
{
return Some((
PciCapabilityID::MessageSignalledInterrupts,
u64::from(msi.cap_offset),
));
}
}

if let Some(msix) = &self.msix {
if offset == u64::from(msix.cap_offset) {
return Some((PciCapabilityID::MSIX, u64::from(msix.cap_offset)));
}
}

None
}

fn msix_enabled(&self) -> bool {
if let Some(msix) = &self.msix {
return msix.cap.enabled();
}

false
}

fn msix_function_masked(&self) -> bool {
if let Some(msix) = &self.msix {
return msix.cap.masked();
}

false
}

fn msix_table_accessed(&self, bar_index: u32, offset: u64) -> bool {
if let Some(msix) = &self.msix {
return msix.table_accessed(bar_index, offset);
}

false
}

fn msix_write_table(&mut self, offset: u64, data: &[u8]) {
if let Some(ref mut msix) = &mut self.msix {
msix.bar.write_table(offset, data)
}
}

fn msix_read_table(&self, offset: u64, data: &mut [u8]) {
if let Some(msix) = &self.msix {
msix.bar.read_table(offset, data)
}
}
}

#[derive(Copy, Clone)]
struct MmioRegion {
start: GuestAddress,
Expand Down Expand Up @@ -89,6 +243,7 @@ pub struct VfioPciDevice {
vfio_pci_configuration: VfioPciConfig,
configuration: PciConfiguration,
mmio_regions: Vec<MmioRegion>,
interrupt: Interrupt,
}

impl VfioPciDevice {
Expand Down Expand Up @@ -121,11 +276,92 @@ impl VfioPciDevice {
configuration,
vfio_pci_configuration,
mmio_regions: Vec::new(),
interrupt: Interrupt {
msi: None,
msix: None,
},
};

vfio_pci_device.parse_capabilities();

Ok(vfio_pci_device)
}

fn parse_msix_capabilities(&mut self, cap: u8) {
let msg_ctl = self
.vfio_pci_configuration
.read_config_word((cap + 2).into());

let table = self
.vfio_pci_configuration
.read_config_dword((cap + 4).into());

let pba = self
.vfio_pci_configuration
.read_config_dword((cap + 8).into());

let msix_cap = MsixCap {
msg_ctl,
table,
pba,
};
let msix_config = MsixConfig::new(msix_cap.table_size());

self.interrupt.msix = Some(VfioMsix {
bar: msix_config,
cap: msix_cap,
cap_offset: cap.into(),
});
}

fn parse_msi_capabilities(&mut self, cap: u8) {
let msg_ctl = self
.vfio_pci_configuration
.read_config_word((cap + 2).into());

self.interrupt.msi = Some(VfioMsi {
cap: MsiCap {
msg_ctl,
..Default::default()
},
cap_offset: cap.into(),
});
}

fn parse_capabilities(&mut self) {
let mut cap_next = self
.vfio_pci_configuration
.read_config_byte(PCI_CONFIG_CAPABILITY_OFFSET);

while cap_next != 0 {
let cap_id = self
.vfio_pci_configuration
.read_config_byte(cap_next.into());

match PciCapabilityID::from(cap_id) {
PciCapabilityID::MessageSignalledInterrupts => {
self.parse_msi_capabilities(cap_next);
}
PciCapabilityID::MSIX => {
self.parse_msix_capabilities(cap_next);
}
_ => {}
};

cap_next = self
.vfio_pci_configuration
.read_config_byte((cap_next + 1).into());
}
}

fn update_msi_capabilities(&mut self, offset: u64, data: &[u8]) {
self.interrupt.update_msix(offset, data);
}

fn update_msix_capabilities(&mut self, offset: u64, data: &[u8]) {
self.interrupt.update_msix(offset, data);
}

fn find_region(&self, addr: u64) -> Option<MmioRegion> {
for region in self.mmio_regions.iter() {
if addr >= region.start.raw_value()
Expand All @@ -140,6 +376,14 @@ impl VfioPciDevice {

impl Drop for VfioPciDevice {
fn drop(&mut self) {
if self.interrupt.msi.is_some() && self.device.disable_msi().is_err() {
error!("Could not disable MSI");
}

if self.interrupt.msix.is_some() && self.device.disable_msix().is_err() {
error!("Could not disable MSI-X");
}

if self.device.unset_dma_map().is_err() {
error!("failed to remove all guest memory regions from iommu table");
}
Expand Down Expand Up @@ -322,6 +566,23 @@ impl PciDevice for VfioPciDevice {
let reg = (reg_idx * PCI_CONFIG_REGISTER_SIZE) as u64;
self.device
.region_write(VFIO_PCI_CONFIG_REGION_INDEX, data, reg + offset);

// If the MSI or MSI-X capabilities are accessed, we need to
// update our local cache accordingly.
// Depending on how the capabilities are modified, this could
// trigger a VFIO MSI or MSI-X toggle.
if let Some((cap_id, cap_base)) = self.interrupt.accessed(reg) {
let cap_offset: u64 = reg - cap_base + offset;
match cap_id {
PciCapabilityID::MessageSignalledInterrupts => {
self.update_msi_capabilities(cap_offset, data);
}
PciCapabilityID::MSIX => {
self.update_msix_capabilities(cap_offset, data);
}
_ => {}
}
}
}

fn read_config_register(&self, reg_idx: usize) -> u32 {
Expand Down

0 comments on commit 20f0116

Please sign in to comment.