interrupt: Make IRQ delivery generic

Because we cannot always assume the irq fd will be the way to send
an IRQ to the guest, this means we cannot make the assumption that
every virtio device implementation should expect an EventFd to
trigger an IRQ.

This commit organizes the code related to virtio devices so that it
now expects a Rust closure instead of a known EventFd. This lets the
caller decide what should be done whenever a device needs to trigger
an interrupt to the guest.

The closure will allow for other type of interrupt mechanism such as
MSI to be implemented. From the device perspective, it could be a
pin based interrupt or an MSI, it does not matter since the device
will simply call into the provided callback, passing the appropriate
Queue as a reference. This design keeps the device model generic.

Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
This commit is contained in:
Sebastien Boeuf 2019-06-03 13:57:26 -07:00 committed by Rob Bradford
parent 1f53488003
commit d3c7b45542
8 changed files with 58 additions and 57 deletions

View File

@ -7,10 +7,13 @@ use crate::PciInterruptPin;
use devices::BusDevice;
use std;
use std::fmt::{self, Display};
use std::sync::Arc;
use vm_allocator::SystemAllocator;
use vm_memory::{GuestAddress, GuestUsize};
use vmm_sys_util::EventFd;
pub type IrqClosure = Box<Fn() -> std::result::Result<(), std::io::Error> + Send + Sync>;
#[derive(Debug)]
pub enum Error {
/// Setup of the device capabilities failed.
@ -41,7 +44,7 @@ impl Display for Error {
pub trait PciDevice: BusDevice {
/// Assign a legacy PCI IRQ to this device.
/// The device may write to `irq_evt` to trigger an interrupt.
fn assign_irq(&mut self, _irq_evt: EventFd, _irq_num: u32, _irq_pin: PciInterruptPin) {}
fn assign_irq(&mut self, _irq_cb: Arc<IrqClosure>, _irq_num: u32, _irq_pin: PciInterruptPin) {}
/// Allocates the needed PCI BARs space using the `allocate` function which takes a size and
/// returns an address. Returns a Vec of (GuestAddress, GuestUsize) tuples.

View File

@ -20,7 +20,7 @@ pub use self::configuration::{
PciSubclass,
};
pub use self::device::Error as PciDeviceError;
pub use self::device::PciDevice;
pub use self::device::{IrqClosure, PciDevice};
pub use self::root::{PciConfigIo, PciConfigMmio, PciRoot, PciRootError};
/// PCI has four interrupt pins A->D.

View File

@ -26,6 +26,7 @@ use super::{
ActivateError, ActivateResult, DescriptorChain, DeviceEventT, Queue, VirtioDevice,
VirtioDeviceType, INTERRUPT_STATUS_USED_RING,
};
use crate::VirtioInterrupt;
use virtio_bindings::virtio_blk::*;
use vm_memory::{Bytes, GuestAddress, GuestMemory, GuestMemoryError, GuestMemoryMmap};
use vmm_sys_util::EventFd;
@ -325,7 +326,7 @@ struct BlockEpollHandler<T: DiskFile> {
disk_image: T,
disk_nsectors: u64,
interrupt_status: Arc<AtomicUsize>,
interrupt_evt: EventFd,
interrupt_cb: Arc<VirtioInterrupt>,
disk_image_id: Vec<u8>,
}
@ -374,10 +375,10 @@ impl<T: DiskFile> BlockEpollHandler<T> {
used_count > 0
}
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
fn signal_used_queue(&self, queue_index: usize) -> result::Result<(), DeviceError> {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
self.interrupt_evt.write(1).map_err(|e| {
(self.interrupt_cb)(&self.queues[queue_index]).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
@ -435,7 +436,7 @@ impl<T: DiskFile> BlockEpollHandler<T> {
error!("Failed to get queue event: {:?}", e);
break 'epoll;
} else if self.process_queue(0) {
if let Err(e) = self.signal_used_queue() {
if let Err(e) = self.signal_used_queue(0) {
error!("Failed to signal used queue: {:?}", e);
break 'epoll;
}
@ -466,7 +467,7 @@ pub struct Block<T: DiskFile> {
acked_features: u64,
config_space: Vec<u8>,
queue_evt: Option<EventFd>,
interrupt_evt: Option<EventFd>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
}
pub fn build_config_space(disk_size: u64) -> Vec<u8> {
@ -514,7 +515,7 @@ impl<T: DiskFile> Block<T> {
acked_features: 0u64,
config_space: build_config_space(disk_size),
queue_evt: None,
interrupt_evt: None,
interrupt_cb: None,
})
}
}
@ -598,7 +599,7 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
fn activate(
&mut self,
mem: GuestMemoryMmap,
interrupt_evt: EventFd,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
@ -627,16 +628,8 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
// Save the interrupt EventFD as we need to return it on reset
// but clone it to pass into the thread.
self.interrupt_evt = Some(interrupt_evt);
let interrupt_evt = self
.interrupt_evt
.as_ref()
.unwrap()
.try_clone()
.map_err(|e| {
error!("failed to clone interrupt EventFd: {}", e);
ActivateError::BadActivate
})?;
self.interrupt_cb = Some(interrupt_cb);
let interrupt_cb = self.interrupt_cb.as_ref().unwrap().clone();
// Save the queue EventFD as we need to return it on reset
// but clone it to pass into the thread.
@ -652,7 +645,7 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
disk_image,
disk_nsectors: self.disk_nsectors,
interrupt_status: status,
interrupt_evt,
interrupt_cb,
disk_image_id,
};
@ -670,7 +663,7 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
Err(ActivateError::BadActivate)
}
fn reset(&mut self) -> Option<(EventFd, Vec<EventFd>)> {
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
if let Some(kill_evt) = self.kill_evt.take() {
// Ignore the result because there is nothing we can do about it.
let _ = kill_evt.write(1);
@ -678,7 +671,7 @@ impl<T: 'static + DiskFile + Send> VirtioDevice for Block<T> {
// Return the interrupt and queue EventFDs
Some((
self.interrupt_evt.take().unwrap(),
self.interrupt_cb.take().unwrap(),
vec![self.queue_evt.take().unwrap()],
))
}

View File

@ -13,6 +13,8 @@ use std::sync::Arc;
use vm_memory::GuestMemoryMmap;
use vmm_sys_util::EventFd;
pub type VirtioInterrupt = Box<Fn(&Queue) -> std::result::Result<(), std::io::Error> + Send + Sync>;
/// Trait for virtio devices to be driven by a virtio transport.
///
/// The lifecycle of a virtio device is to be moved to a virtio transport, which will then query the
@ -46,7 +48,7 @@ pub trait VirtioDevice: Send {
fn activate(
&mut self,
mem: GuestMemoryMmap,
interrupt_evt: EventFd,
interrupt_evt: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
queue_evts: Vec<EventFd>,
@ -54,7 +56,7 @@ pub trait VirtioDevice: Send {
/// Optionally deactivates this device and returns ownership of the guest memory map, interrupt
/// event, and queue events.
fn reset(&mut self) -> Option<(EventFd, Vec<EventFd>)> {
fn reset(&mut self) -> Option<(Arc<VirtioInterrupt>, Vec<EventFd>)> {
None
}

View File

@ -28,6 +28,7 @@ use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
INTERRUPT_STATUS_USED_RING,
};
use crate::VirtioInterrupt;
use net_util::{MacAddr, Tap, TapError, MAC_ADDR_LEN};
use virtio_bindings::virtio_net::*;
use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap};
@ -121,15 +122,15 @@ struct NetEpollHandler {
rx: RxVirtio,
tx: TxVirtio,
interrupt_status: Arc<AtomicUsize>,
interrupt_evt: EventFd,
interrupt_cb: Arc<VirtioInterrupt>,
kill_evt: EventFd,
}
impl NetEpollHandler {
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
fn signal_used_queue(&self, queue: &Queue) -> result::Result<(), DeviceError> {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
self.interrupt_evt.write(1).map_err(|e| {
(self.interrupt_cb)(queue).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
@ -219,7 +220,7 @@ impl NetEpollHandler {
}
if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
self.signal_used_queue()
self.signal_used_queue(&self.rx.queue)
} else {
Ok(())
}
@ -234,7 +235,7 @@ impl NetEpollHandler {
self.process_rx()
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
self.signal_used_queue()
self.signal_used_queue(&self.rx.queue)
} else {
Ok(())
}
@ -374,7 +375,7 @@ impl NetEpollHandler {
self.process_rx().unwrap();
} else if self.rx.deferred_irqs {
self.rx.deferred_irqs = false;
self.signal_used_queue().unwrap();
self.signal_used_queue(&self.rx.queue).unwrap();
}
} else {
self.process_rx().unwrap();
@ -538,7 +539,7 @@ impl VirtioDevice for Net {
fn activate(
&mut self,
mem: GuestMemoryMmap,
interrupt_evt: EventFd,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
mut queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
@ -573,7 +574,7 @@ impl VirtioDevice for Net {
rx: RxVirtio::new(rx_queue, rx_queue_evt),
tx: TxVirtio::new(tx_queue, tx_queue_evt),
interrupt_status: status,
interrupt_evt,
interrupt_cb,
kill_evt,
};

View File

@ -18,7 +18,7 @@ use super::{
ActivateError, ActivateResult, DeviceEventT, Queue, VirtioDevice, VirtioDeviceType,
INTERRUPT_STATUS_USED_RING, VIRTIO_F_VERSION_1,
};
use crate::VirtioInterrupt;
use vm_memory::{Bytes, GuestMemoryMmap};
use vmm_sys_util::EventFd;
@ -36,7 +36,7 @@ struct RngEpollHandler {
mem: GuestMemoryMmap,
random_file: File,
interrupt_status: Arc<AtomicUsize>,
interrupt_evt: EventFd,
interrupt_cb: Arc<VirtioInterrupt>,
queue_evt: EventFd,
kill_evt: EventFd,
}
@ -79,7 +79,7 @@ impl RngEpollHandler {
fn signal_used_queue(&self) -> result::Result<(), DeviceError> {
self.interrupt_status
.fetch_or(INTERRUPT_STATUS_USED_RING as usize, Ordering::SeqCst);
self.interrupt_evt.write(1).map_err(|e| {
(self.interrupt_cb)(&self.queues[0]).map_err(|e| {
error!("Failed to signal used queue: {:?}", e);
DeviceError::FailedSignalingUsedQueue(e)
})
@ -228,7 +228,7 @@ impl VirtioDevice for Rng {
fn activate(
&mut self,
mem: GuestMemoryMmap,
interrupt_evt: EventFd,
interrupt_cb: Arc<VirtioInterrupt>,
status: Arc<AtomicUsize>,
queues: Vec<Queue>,
mut queue_evts: Vec<EventFd>,
@ -258,7 +258,7 @@ impl VirtioDevice for Rng {
mem,
random_file,
interrupt_status: status,
interrupt_evt,
interrupt_cb,
queue_evt: queue_evts.remove(0),
kill_evt,
};

View File

@ -19,8 +19,8 @@ use std::sync::Arc;
use devices::BusDevice;
use pci::{
PciBarConfiguration, PciCapability, PciCapabilityID, PciClassCode, PciConfiguration, PciDevice,
PciDeviceError, PciHeaderType, PciInterruptPin, PciSubclass,
IrqClosure, PciBarConfiguration, PciCapability, PciCapabilityID, PciClassCode,
PciConfiguration, PciDevice, PciDeviceError, PciHeaderType, PciInterruptPin, PciSubclass,
};
use vm_allocator::SystemAllocator;
use vm_memory::{Address, ByteValued, GuestAddress, GuestMemoryMmap, GuestUsize, Le32};
@ -28,8 +28,8 @@ use vmm_sys_util::{EventFd, Result};
use super::VirtioPciCommonConfig;
use crate::{
Queue, VirtioDevice, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK, DEVICE_FAILED,
DEVICE_FEATURES_OK, DEVICE_INIT,
Queue, VirtioDevice, VirtioInterrupt, DEVICE_ACKNOWLEDGE, DEVICE_DRIVER, DEVICE_DRIVER_OK,
DEVICE_FAILED, DEVICE_FEATURES_OK, DEVICE_INIT,
};
#[allow(clippy::enum_variant_names)]
@ -163,7 +163,7 @@ pub struct VirtioPciDevice {
// PCI interrupts.
interrupt_status: Arc<AtomicUsize>,
interrupt_evt: Option<EventFd>,
interrupt_cb: Option<Arc<VirtioInterrupt>>,
// virtio queues
queues: Vec<Queue>,
@ -214,7 +214,7 @@ impl VirtioPciDevice {
device,
device_activated: false,
interrupt_status: Arc::new(AtomicUsize::new(0)),
interrupt_evt: None,
interrupt_cb: None,
queues,
queue_evts,
memory: Some(memory),
@ -229,11 +229,6 @@ impl VirtioPciDevice {
self.queue_evts.as_slice()
}
/// Gets the event this device uses to interrupt the VM when the used queue is changed.
pub fn interrupt_evt(&self) -> Option<&EventFd> {
self.interrupt_evt.as_ref()
}
fn is_driver_ready(&self) -> bool {
let ready_bits =
(DEVICE_ACKNOWLEDGE | DEVICE_DRIVER | DEVICE_DRIVER_OK | DEVICE_FEATURES_OK) as u8;
@ -313,9 +308,12 @@ impl VirtioPciDevice {
}
impl PciDevice for VirtioPciDevice {
fn assign_irq(&mut self, irq_evt: EventFd, irq_num: u32, irq_pin: PciInterruptPin) {
fn assign_irq(&mut self, irq_cb: Arc<IrqClosure>, irq_num: u32, irq_pin: PciInterruptPin) {
self.configuration.set_irq(irq_num as u8, irq_pin);
self.interrupt_evt = Some(irq_evt);
let cb = Arc::new(Box::new(move |_queue: &Queue| (irq_cb)()) as VirtioInterrupt);
self.interrupt_cb = Some(cb);
}
fn config_registers(&self) -> &PciConfiguration {
@ -440,18 +438,18 @@ impl PciDevice for VirtioPciDevice {
};
if !self.device_activated && self.is_driver_ready() && self.are_queues_valid() {
if let Some(interrupt_evt) = self.interrupt_evt.take() {
if let Some(interrupt_cb) = self.interrupt_cb.take() {
if self.memory.is_some() {
let mem = self.memory.as_ref().unwrap().clone();
self.device
.activate(
mem,
interrupt_evt,
interrupt_cb,
self.interrupt_status.clone(),
self.queues.clone(),
self.queue_evts.split_off(0),
)
.expect("Failed to activate device");;
.expect("Failed to activate device");
self.device_activated = true;
}
}
@ -459,9 +457,9 @@ impl PciDevice for VirtioPciDevice {
// Device has been reset by the driver
if self.device_activated && self.is_driver_init() {
if let Some((interrupt_evt, mut queue_evts)) = self.device.reset() {
if let Some((interrupt_cb, mut queue_evts)) = self.device.reset() {
// Upon reset the device returns its interrupt EventFD and it's queue EventFDs
self.interrupt_evt = Some(interrupt_evt);
self.interrupt_cb = Some(interrupt_cb);
self.queue_evts.append(&mut queue_evts);
self.device_activated = false;

View File

@ -27,7 +27,7 @@ use kvm_ioctls::*;
use libc::{c_void, siginfo_t, EFD_NONBLOCK};
use linux_loader::loader::KernelLoader;
use net_util::Tap;
use pci::{PciConfigIo, PciDevice, PciInterruptPin, PciRoot};
use pci::{IrqClosure, PciConfigIo, PciDevice, PciInterruptPin, PciRoot};
use qcow::{self, ImageType, QcowFile};
use std::ffi::CString;
use std::fs::{File, OpenOptions};
@ -423,6 +423,7 @@ impl DeviceManager {
) -> DeviceManagerResult<()> {
let mut virtio_pci_device = VirtioPciDevice::new(memory, virtio_device)
.map_err(DeviceManagerError::VirtioDevice)?;
let bars = virtio_pci_device
.allocate_bars(allocator)
.map_err(DeviceManagerError::AllocateBars)?;
@ -442,8 +443,11 @@ impl DeviceManager {
vm_fd
.register_irqfd(irqfd.as_raw_fd(), irq_num)
.map_err(DeviceManagerError::Irq)?;
let irq_cb = Arc::new(Box::new(move || irqfd.write(1)) as IrqClosure);
// Let's use irq line INTA for now.
virtio_pci_device.assign_irq(irqfd, irq_num as u32, PciInterruptPin::IntA);
virtio_pci_device.assign_irq(irq_cb, irq_num as u32, PciInterruptPin::IntA);
let virtio_pci_device = Arc::new(Mutex::new(virtio_pci_device));