Skip to content

Commit

Permalink
examples: add example for interrupt usage
Browse files Browse the repository at this point in the history
Add a simple example showing how the interrupt traits can be used.

Signed-off-by: Alexandru-Cezar Sardan <[email protected]>
  • Loading branch information
Alexandru-Cezar Sardan committed Jan 25, 2022
1 parent b2df144 commit fb4696d
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ license = "Apache-2.0 OR BSD-3-Clause"

[dependencies]
vmm-sys-util = { version = "0.8.0" }

[dev-dependencies]
libc = {version = "0.2.100" }
52 changes: 52 additions & 0 deletions examples/interrupts/device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::sync::{Arc, Mutex};
use vm_device::interrupt::{Interrupt, InterruptSourceGroup};

type IntGroup<I> = dyn InterruptSourceGroup<InterruptType = I, InterruptWrapper = Arc<I>>;

/// Example of the internal state of a device
pub struct Device<I: Interrupt> {
/// A device should hold a reference to the interrupt group
_interrupt_group: Arc<Mutex<IntGroup<I>>>,
/// Optionally, a device may store references to the interrupts for easy access
irq0: Arc<I>,
}

impl<I: Interrupt> Device<I> {
/// Creates a new device.
/// The interrupt group that the device will use is passed as an argument by the caller.
pub fn new(interrupt_group: Arc<Mutex<IntGroup<I>>>) -> Result<Self, &'static str> {
let mut irq_group = interrupt_group.lock().expect("Poisoned Lock");
// Allocate an interrupt. In this case the device uses a single interrupt.
irq_group
.allocate_interrupts(1)
.map_err(|_| "Cannot allocate interrupts.")?;
// Save the reference to the interrupt for easy access later.
let irq = irq_group.get(0).ok_or("Can't find interrupt with id 0.")?;
std::mem::drop(irq_group);

Ok(Device {
_interrupt_group: interrupt_group,
irq0: irq,
})
}

/// Start the device
pub fn start(&self) -> Result<(), &'static str> {
// Enable interrupt generation for this device
self.irq0
.enable()
.map_err(|_| "Cannot enable interrupts.")?;
Ok(())
}

/// Do some device speciffic work
pub fn run(&self) -> Result<(), &'static str> {
// Do some work...

// Trigger an interrupt when work is finished
Ok(self
.irq0
.trigger()
.map_err(|_| "Cannot trigger the interrupt.")?)
}
}
115 changes: 115 additions & 0 deletions examples/interrupts/interrupt_manager.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use std::sync::{atomic::AtomicBool, atomic::Ordering, Arc};
use vm_device::interrupt::trigger::{eventfd::EventFdTrigger, Trigger};
use vm_device::interrupt::{Interrupt, InterruptSourceGroup, Result};
use vmm_sys_util::eventfd::EventFd;
extern crate libc;

/// Defines an implementation for a VMM speciffic interrupt type
pub struct CustomInterrupt {
trigger: EventFdTrigger,
enabled: AtomicBool,
}

impl Interrupt for CustomInterrupt {
type TriggerType = EventFdTrigger;

fn trigger(&self) -> std::result::Result<(), <Self::TriggerType as Trigger>::E> {
if self.enabled.load(Ordering::Acquire) {
self.trigger.trigger()
} else {
Err(std::io::Error::from_raw_os_error(libc::EINVAL))
}
}

/// Return the underlying trigger
fn notifier(&self) -> Option<Self::TriggerType> {
Some(self.trigger.try_clone().unwrap())
}

/// Enable generation of interrupts from this interrupt source.
fn enable(&self) -> Result<()> {
// Change the state of the interrupt to enabled.
// In other implementation, here is the place that the event
// may be registered with listeners (e.g. register irqfd with KVM)
// For interrupt tha should be enabled as a group and not idividually
// e.g. MSI interrupts, this method should return OperationNotSupported.
self.enabled.store(true, Ordering::Release);
Ok(())
}

/// Disable generation of interrupts from this interrupt source.
fn disable(&self) -> Result<()> {
// Change the state of the interrupt to disabled.
self.enabled.store(false, Ordering::Release);
Ok(())
}
}

/// The custom interrupt group that all the interrupts for the device
pub struct CustomInterruptGroup {
interrupts: Vec<Arc<CustomInterrupt>>,
}

impl CustomInterruptGroup {
pub fn new() -> Self {
CustomInterruptGroup {
interrupts: Vec::new(),
}
}
}

impl InterruptSourceGroup for CustomInterruptGroup {
/// Type of the interrupts contained in this group.
type InterruptType = CustomInterrupt;
/// Interrupts are wrapped in an Arc to allow for easy access when triggering.
type InterruptWrapper = Arc<CustomInterrupt>;

fn is_empty(&self) -> bool {
self.interrupts.is_empty()
}

fn len(&self) -> usize {
self.interrupts.len()
}

fn enable(&self) -> Result<()> {
// For interrupts that should be enabled as a group (e.g. MSI), this method
// should coordinate enabling of all interrupts using methods from
// CustomInterrupt instead of the Interrupt trait.
Ok(for i in &self.interrupts {
i.enable().unwrap()
})
}

fn disable(&self) -> Result<()> {
Ok(for i in &self.interrupts {
i.disable().unwrap()
})
}

fn get(&self, index: usize) -> Option<Self::InterruptWrapper> {
match self.interrupts.get(index) {
Some(interrupt) => Some(interrupt.clone()),
None => None,
}
}

/// Request new interrupts within this group.
fn allocate_interrupts(&mut self, size: usize) -> Result<()> {
for _ in 0..size {
let irq_fd = EventFd::new(libc::EFD_NONBLOCK)?;
let interrupt = CustomInterrupt {
trigger: EventFdTrigger::new(irq_fd),
enabled: AtomicBool::new(false),
};
self.interrupts.push(Arc::new(interrupt));
}
Ok(())
}

/// Release all interrupts within this group.
fn free_interrupts(&mut self) -> Result<()> {
self.interrupts.clear();
Ok(())
}
}
33 changes: 33 additions & 0 deletions examples/interrupts/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
mod device;
mod interrupt_manager;

use device::Device;
use interrupt_manager::CustomInterruptGroup;
use std::error::Error;
use std::sync::{Arc, Mutex};
use vm_device::interrupt::{Interrupt, InterruptSourceGroup};

fn main() -> Result<(), Box<dyn Error>> {
// Create an interrupt group for the device
let interrupt_group = Arc::new(Mutex::new(CustomInterruptGroup::new()));
// Pass the interrupt group to the device
let device = Device::new(interrupt_group.clone())?;

device.start()?;
device.run()?; // The device will do some work and send an interrupt

let locked_group = interrupt_group.lock().expect("Poisoned Lock");

// This interrupt is using an eventfd
// so we can read from it to see if the device triggered an interrupt
let irq = locked_group.get(0).ok_or("Cannot get irq.")?;
let trigger = irq.notifier().ok_or("Cannot get trigger.")?;
let evt = trigger
.get_event()
.map_err(|_| "Cannot get eventfd from trigger.")?;
match evt.read() {
Ok(data) => println!("Received interrupt with data: {}", data),
Err(_) => println!("No interrupt!"),
};
Ok(())
}

0 comments on commit fb4696d

Please sign in to comment.