Skip to content

Commit

Permalink
Manage PCI MSI/PCI MSI-x interrupts
Browse files Browse the repository at this point in the history
Implement interrupt source driver to manage PCI MSI/MSI-x interrupts.

Signed-off-by: Liu Jiang <[email protected]>
Signed-off-by: Bin Zha <[email protected]>
  • Loading branch information
jiangliu committed Nov 7, 2019
1 parent 17ffe44 commit 32cf4a5
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 1 deletion.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
kvm_irq = ["kvm-ioctls", "kvm-bindings"]
legacy_irq = []
msi_irq = []
pci_msi_irq = ["msi_irq"]
7 changes: 6 additions & 1 deletion src/interrupt/kvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ use self::legacy_irq::LegacyIrq;
#[cfg(feature = "msi_irq")]
mod msi_irq;

#[cfg(feature = "pci_msi_irq")]
mod pci_msi_irq;
#[cfg(feature = "pci_msi_irq")]
use self::pci_msi_irq::PciMsiIrq;

/// Structure to manage interrupt sources for a virtual machine based on the Linux KVM framework.
///
/// The KVM framework provides methods to inject interrupts into the target virtual machines,
Expand Down Expand Up @@ -119,7 +124,7 @@ impl KvmIrqManagerObj {
LegacyIrq::new(base, count, self.vmfd.clone(), self.routes.clone())?
}
#[cfg(feature = "pci_msi_irq")]
InterruptSourceType::MsiIrq => {
InterruptSourceType::PciMsiIrq => {
PciMsiIrq::new(base, count, self.vmfd.clone(), self.routes.clone())?
}
};
Expand Down
147 changes: 147 additions & 0 deletions src/interrupt/kvm/pci_msi_irq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

//! Manage virtual device's PCI MSI/PCI MSIx interrupts based on Linux KVM framework.
//!
//! To optimize for performance by avoiding unnecessary locking and state checking, we assume that
//! the caller will take the responsibility to maintain the interrupt states and only issue valid
//! requests to this driver. If the caller doesn't obey the contract, only the current virtual
//! machine will be affected, it shouldn't break the host or other virtual machines.
use super::msi_irq::{create_msi_routing_entries, new_msi_routing_entry, MsiConfig};
use super::*;

pub(super) struct PciMsiIrq {
base: InterruptIndex,
count: InterruptIndex,
vmfd: Arc<VmFd>,
irq_routing: Arc<KvmIrqRouting>,
msi_configs: Vec<MsiConfig>,
}

impl PciMsiIrq {
#[allow(clippy::new_ret_no_self)]
pub(super) fn new(
base: InterruptIndex,
count: InterruptIndex,
vmfd: Arc<VmFd>,
irq_routing: Arc<KvmIrqRouting>,
) -> Result<Arc<Box<dyn InterruptSourceGroup>>> {
if count > MAX_MSI_IRQS_PER_DEVICE || base >= MAX_IRQS || base + count > MAX_IRQS {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
}

let mut msi_configs = Vec::with_capacity(count as usize);
for _ in 0..count {
msi_configs.push(MsiConfig::new());
}

Ok(Arc::new(Box::new(PciMsiIrq {
base,
count,
vmfd,
irq_routing,
msi_configs,
})))
}
}

impl InterruptSourceGroup for PciMsiIrq {
fn interrupt_type(&self) -> InterruptSourceType {
InterruptSourceType::PciMsiIrq
}

fn len(&self) -> u32 {
self.count
}

fn base(&self) -> u32 {
self.base
}

fn irqfd(&self, index: InterruptIndex) -> Option<&EventFd> {
if index >= self.count {
None
} else {
let msi_config = &self.msi_configs[index as usize];
Some(&msi_config.irqfd)
}
}

fn enable(&self, configs: &[InterruptSourceConfig]) -> Result<()> {
if configs.len() != self.count as usize {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
}

// First add IRQ routings for all the MSI interrupts.
let entries = create_msi_routing_entries(self.base, configs)?;
self.irq_routing.add(&entries)?;

// Then register irqfds to the KVM module.
for i in 0..self.count {
let irqfd = &self.msi_configs[i as usize].irqfd;
self.vmfd.register_irqfd(irqfd, self.base + i)?;
}

Ok(())
}

fn disable(&self) -> Result<()> {
// First unregister all irqfds, so it won't trigger anymore.
for i in 0..self.count {
let irqfd = &self.msi_configs[i as usize].irqfd;
self.vmfd.unregister_irqfd(irqfd, self.base + i)?;
}

// Then tear down the IRQ routings for all the MSI interrupts.
let mut entries = Vec::with_capacity(self.count as usize);
for i in 0..self.count {
// Safe to unwrap because there's no legal way to break the mutex.
let msicfg = self.msi_configs[i as usize].config.lock().unwrap();
let entry = new_msi_routing_entry(self.base + i, &*msicfg);
entries.push(entry);
}
self.irq_routing.remove(&entries)?;

Ok(())
}

#[allow(irrefutable_let_patterns)]
fn update(&self, index: InterruptIndex, config: &InterruptSourceConfig) -> Result<()> {
if index >= self.count {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
}

if let InterruptSourceConfig::MsiIrq(ref cfg) = config {
// Safe to unwrap because there's no legal way to break the mutex.
let entry = {
let mut msicfg = self.msi_configs[index as usize].config.lock().unwrap();
msicfg.high_addr = cfg.high_addr;
msicfg.low_addr = cfg.low_addr;
msicfg.data = cfg.data;
new_msi_routing_entry(self.base + index, &*msicfg)
};
self.irq_routing.modify(&entry)
} else {
Err(std::io::Error::from_raw_os_error(libc::EINVAL))
}
}

fn trigger(&self, index: InterruptIndex, flags: u32) -> Result<()> {
// Assume that the caller will maintain the interrupt states and only call this function
// when suitable.
if index >= self.count || flags != 0 {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
}
let msi_config = &self.msi_configs[index as usize];
msi_config.irqfd.write(1)
}

fn ack(&self, index: InterruptIndex, flags: u32) -> Result<()> {
// It's a noop to acknowledge an edge triggered MSI interrupts.
if index >= self.count || flags != 0 {
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
}
Ok(())
}
}

0 comments on commit 32cf4a5

Please sign in to comment.