Skip to content

Commit c7846d3

Browse files
committed
interrupts: add support for MSI/MSI-X interrupts
Enable Vm to vend and manage MSI/MSI-X interrupts. This adds the logic to create a set of MSI vectors and then handle their lifetime. Signed-off-by: Babis Chalios <[email protected]>
1 parent 87410a1 commit c7846d3

File tree

1 file changed

+234
-3
lines changed

1 file changed

+234
-3
lines changed

src/vmm/src/vstate/vm.rs

Lines changed: 234 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,23 @@ use std::collections::HashMap;
99
use std::fs::OpenOptions;
1010
use std::io::Write;
1111
use std::path::Path;
12+
use std::sync::atomic::{AtomicBool, Ordering};
1213
use std::sync::{Arc, Mutex};
1314

1415
#[cfg(target_arch = "x86_64")]
1516
use kvm_bindings::KVM_IRQCHIP_IOAPIC;
1617
use kvm_bindings::{
17-
KVM_IRQ_ROUTING_IRQCHIP, KVM_MEM_LOG_DIRTY_PAGES, kvm_irq_routing_entry,
18-
kvm_userspace_memory_region,
18+
KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MSI_VALID_DEVID,
19+
KvmIrqRouting, kvm_irq_routing_entry, kvm_userspace_memory_region,
1920
};
2021
use kvm_ioctls::VmFd;
22+
use log::debug;
23+
use vm_device::interrupt::{InterruptSourceGroup, MsiIrqSourceConfig};
2124
use vmm_sys_util::errno;
2225
use vmm_sys_util::eventfd::EventFd;
2326

2427
pub use crate::arch::{ArchVm as Vm, ArchVmError, VmState};
28+
use crate::device_manager::resources::ResourceAllocator;
2529
use crate::logger::info;
2630
use crate::persist::CreateSnapshotError;
2731
use crate::utils::u64_to_usize;
@@ -52,6 +56,148 @@ pub struct RoutingEntry {
5256
masked: bool,
5357
}
5458

59+
/// Type that describes an allocated interrupt
60+
#[derive(Debug)]
61+
pub struct MsiVector {
62+
/// GSI used for this vector
63+
pub gsi: u32,
64+
/// EventFd used for this vector
65+
pub event_fd: EventFd,
66+
/// Flag determining whether the vector is enabled
67+
pub enabled: AtomicBool,
68+
}
69+
70+
impl MsiVector {
71+
/// Create a new [`MsiVector`] of a particular type
72+
pub fn new(gsi: u32, enabled: bool) -> Result<MsiVector, InterruptError> {
73+
Ok(MsiVector {
74+
gsi,
75+
event_fd: EventFd::new(libc::EFD_NONBLOCK).map_err(InterruptError::EventFd)?,
76+
enabled: AtomicBool::new(enabled),
77+
})
78+
}
79+
}
80+
81+
impl MsiVector {
82+
/// Enable vector
83+
fn enable(&self, vmfd: &VmFd) -> Result<(), errno::Error> {
84+
if !self.enabled.load(Ordering::Acquire) {
85+
vmfd.register_irqfd(&self.event_fd, self.gsi)?;
86+
self.enabled.store(true, Ordering::Release);
87+
}
88+
89+
Ok(())
90+
}
91+
92+
/// Disable vector
93+
fn disable(&self, vmfd: &VmFd) -> Result<(), errno::Error> {
94+
if self.enabled.load(Ordering::Acquire) {
95+
vmfd.unregister_irqfd(&self.event_fd, self.gsi)?;
96+
self.enabled.store(false, Ordering::Release);
97+
}
98+
99+
Ok(())
100+
}
101+
}
102+
103+
#[derive(Debug)]
104+
/// MSI interrupts created for a VirtIO device
105+
pub struct MsiVectorGroup {
106+
vm: Arc<Vm>,
107+
irq_routes: HashMap<u32, MsiVector>,
108+
}
109+
110+
impl MsiVectorGroup {
111+
/// Returns the number of vectors in this group
112+
pub fn num_vectors(&self) -> u16 {
113+
u16::try_from(self.irq_routes.len()).unwrap()
114+
}
115+
}
116+
117+
impl InterruptSourceGroup for MsiVectorGroup {
118+
fn enable(&self) -> vm_device::interrupt::Result<()> {
119+
for (_, route) in self.irq_routes.iter() {
120+
route.enable(&self.vm.common.fd)?;
121+
}
122+
123+
Ok(())
124+
}
125+
126+
fn disable(&self) -> vm_device::interrupt::Result<()> {
127+
for (_, route) in self.irq_routes.iter() {
128+
route.disable(&self.vm.common.fd)?;
129+
}
130+
131+
Ok(())
132+
}
133+
134+
fn trigger(
135+
&self,
136+
index: vm_device::interrupt::InterruptIndex,
137+
) -> vm_device::interrupt::Result<()> {
138+
if let Some(route) = self.irq_routes.get(&index) {
139+
return route.event_fd.write(1);
140+
}
141+
142+
Err(std::io::Error::other(format!(
143+
"trigger: invalid interrupt index {index}"
144+
)))
145+
}
146+
147+
fn notifier(&self, index: vm_device::interrupt::InterruptIndex) -> Option<&EventFd> {
148+
self.irq_routes.get(&index).map(|route| &route.event_fd)
149+
}
150+
151+
fn update(
152+
&self,
153+
index: vm_device::interrupt::InterruptIndex,
154+
config: vm_device::interrupt::InterruptSourceConfig,
155+
masked: bool,
156+
set_gsi: bool,
157+
) -> vm_device::interrupt::Result<()> {
158+
let msi_config = match config {
159+
vm_device::interrupt::InterruptSourceConfig::LegacyIrq(_) => {
160+
return Err(std::io::Error::other(
161+
"MSI-x update: invalid configuration type",
162+
));
163+
}
164+
vm_device::interrupt::InterruptSourceConfig::MsiIrq(config) => config,
165+
};
166+
167+
if let Some(route) = self.irq_routes.get(&index) {
168+
// When an interrupt is masked the GSI will not be passed to KVM through
169+
// KVM_SET_GSI_ROUTING. So, call [`disable()`] to unregister the interrupt file
170+
// descriptor before passing the interrupt routes to KVM
171+
if masked {
172+
route.disable(&self.vm.common.fd)?;
173+
}
174+
175+
self.vm.register_msi(route, masked, msi_config)?;
176+
if set_gsi {
177+
self.vm.set_gsi_routes().unwrap();
178+
}
179+
180+
// Assign KVM_IRQFD after KVM_SET_GSI_ROUTING to avoid
181+
// panic on kernel which does not have commit a80ced6ea514
182+
// (KVM: SVM: fix panic on out-of-bounds guest IRQ).
183+
if !masked {
184+
route.enable(&self.vm.common.fd)?;
185+
}
186+
187+
return Ok(());
188+
}
189+
190+
Err(std::io::Error::other(format!(
191+
"MSI-X update: invalid vector index {index}"
192+
)))
193+
}
194+
195+
fn set_gsi(&self) -> vm_device::interrupt::Result<()> {
196+
self.vm.set_gsi_routes().unwrap();
197+
Ok(())
198+
}
199+
}
200+
55201
/// Architecture independent parts of a VM.
56202
#[derive(Debug)]
57203
pub struct VmCommon {
@@ -323,7 +469,6 @@ impl Vm {
323469
{
324470
entry.u.irqchip.irqchip = 0;
325471
}
326-
327472
entry.u.irqchip.pin = gsi;
328473

329474
self.common
@@ -339,6 +484,92 @@ impl Vm {
339484
);
340485
Ok(())
341486
}
487+
488+
/// Register an MSI device interrupt
489+
pub fn register_msi(
490+
&self,
491+
route: &MsiVector,
492+
masked: bool,
493+
config: MsiIrqSourceConfig,
494+
) -> Result<(), errno::Error> {
495+
let mut entry = kvm_irq_routing_entry {
496+
gsi: route.gsi,
497+
type_: KVM_IRQ_ROUTING_MSI,
498+
..Default::default()
499+
};
500+
entry.u.msi.address_lo = config.low_addr;
501+
entry.u.msi.address_hi = config.high_addr;
502+
entry.u.msi.data = config.data;
503+
504+
if self.common.fd.check_extension(kvm_ioctls::Cap::MsiDevid) {
505+
// On AArch64, there is limitation on the range of the 'devid',
506+
// it cannot be greater than 65536 (the max of u16).
507+
//
508+
// BDF cannot be used directly, because 'segment' is in high
509+
// 16 bits. The layout of the u32 BDF is:
510+
// |---- 16 bits ----|-- 8 bits --|-- 5 bits --|-- 3 bits --|
511+
// | segment | bus | device | function |
512+
//
513+
// Now that we support 1 bus only in a segment, we can build a
514+
// 'devid' by replacing the 'bus' bits with the low 8 bits of
515+
// 'segment' data.
516+
// This way we can resolve the range checking problem and give
517+
// different `devid` to all the devices. Limitation is that at
518+
// most 256 segments can be supported.
519+
//
520+
let modified_devid = ((config.devid & 0x00ff_0000) >> 8) | config.devid & 0xff;
521+
522+
entry.flags = KVM_MSI_VALID_DEVID;
523+
entry.u.msi.__bindgen_anon_1.devid = modified_devid;
524+
}
525+
526+
self.common
527+
.interrupts
528+
.lock()
529+
.expect("Poisoned lock")
530+
.insert(route.gsi, RoutingEntry { entry, masked });
531+
532+
Ok(())
533+
}
534+
535+
/// Create a group of MSI-X interrupts
536+
pub fn create_msix_group(
537+
vm: Arc<Vm>,
538+
resource_allocator: &ResourceAllocator,
539+
base: u32,
540+
count: u16,
541+
) -> Result<MsiVectorGroup, InterruptError> {
542+
debug!("Creating new MSI group with {count} vectors");
543+
let mut irq_routes = HashMap::with_capacity(count as usize);
544+
for (i, gsi) in resource_allocator
545+
.allocate_gsi(count as u32)?
546+
.iter()
547+
.enumerate()
548+
{
549+
irq_routes.insert(
550+
u32::try_from(i).unwrap() + base,
551+
MsiVector::new(*gsi, false)?,
552+
);
553+
}
554+
555+
Ok(MsiVectorGroup { vm, irq_routes })
556+
}
557+
558+
/// Set GSI routes to KVM
559+
pub fn set_gsi_routes(&self) -> Result<(), InterruptError> {
560+
let entries = self.common.interrupts.lock().expect("Poisoned lock");
561+
let mut routes = KvmIrqRouting::new(0)?;
562+
563+
for (_, entry) in entries.iter() {
564+
if entry.masked {
565+
continue;
566+
}
567+
routes.push(entry.entry)?;
568+
}
569+
570+
self.common.fd.set_gsi_routing(&routes)?;
571+
Ok(())
572+
}
342573
}
343574

344575
#[cfg(test)]

0 commit comments

Comments
 (0)