Skip to content

Commit c49c75c

Browse files
committed
Manage PCI MSI/PCI MSI-x interrupts
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]>
1 parent 988419f commit c49c75c

File tree

4 files changed

+177
-5
lines changed

4 files changed

+177
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ vmm-sys-util = "~0"
1616
kvm_irq = ["kvm-ioctls", "kvm-bindings"]
1717
legacy_irq = []
1818
msi_irq = []
19+
pci_msi_irq = ["msi_irq"]

src/interrupt/kvm/mod.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ use self::legacy_irq::LegacyIrq;
2323
#[cfg(feature = "msi_irq")]
2424
mod msi_irq;
2525

26+
#[cfg(feature = "pci_msi_irq")]
27+
mod pci_msi_irq;
28+
#[cfg(feature = "pci_msi_irq")]
29+
use self::pci_msi_irq::PciMsiIrq;
30+
2631
/// Structure to manage interrupt sources for a virtual machine based on the Linux KVM framework.
2732
///
2833
/// The KVM framework provides methods to inject interrupts into the target virtual machines,
@@ -46,6 +51,7 @@ impl KvmIrqManager {
4651
vmfd,
4752
groups: HashMap::new(),
4853
routes: Arc::new(KvmIrqRouting::new(vmfd2)),
54+
max_msi_irqs: DEFAULT_MAX_MSI_IRQS_PER_DEVICE,
4955
}),
5056
}
5157
}
@@ -75,12 +81,18 @@ impl InterruptManager for KvmIrqManager {
7581
let mut mgr = self.mgr.lock().unwrap();
7682
mgr.destroy_group(group)
7783
}
84+
85+
fn set_max_msi_irqs(&self, max_msi_irqs: InterruptIndex) {
86+
let mut mgr = self.mgr.lock().unwrap();
87+
mgr.max_msi_irqs = max_msi_irqs;
88+
}
7889
}
7990

8091
struct KvmIrqManagerObj {
8192
vmfd: Arc<VmFd>,
8293
routes: Arc<KvmIrqRouting>,
8394
groups: HashMap<InterruptIndex, Arc<Box<dyn InterruptSourceGroup>>>,
95+
max_msi_irqs: InterruptIndex,
8496
}
8597

8698
impl KvmIrqManagerObj {
@@ -104,9 +116,13 @@ impl KvmIrqManagerObj {
104116
self.routes.clone(),
105117
)?)),
106118
#[cfg(feature = "pci_msi_irq")]
107-
InterruptSourceType::MsiIrq => {
108-
PciMsiIrq::new(base, count, self.vmfd.clone(), self.routes.clone())?
109-
}
119+
InterruptSourceType::PciMsiIrq => Arc::new(Box::new(PciMsiIrq::new(
120+
base,
121+
count,
122+
self.max_msi_irqs,
123+
self.vmfd.clone(),
124+
self.routes.clone(),
125+
)?)),
110126
};
111127

112128
self.groups.insert(base, group.clone());

src/interrupt/kvm/pci_msi_irq.rs

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Manage virtual device's PCI MSI/PCI MSIx interrupts based on Linux KVM framework.
5+
//!
6+
//! To optimize for performance by avoiding unnecessary locking and state checking, we assume that
7+
//! the caller will take the responsibility to maintain the interrupt states and only issue valid
8+
//! requests to this driver. If the caller doesn't obey the contract, only the current virtual
9+
//! machine will be affected, it shouldn't break the host or other virtual machines.
10+
11+
use super::msi_irq::{create_msi_routing_entries, new_msi_routing_entry, MsiConfig};
12+
use super::*;
13+
14+
pub(super) struct PciMsiIrq {
15+
base: InterruptIndex,
16+
count: InterruptIndex,
17+
vmfd: Arc<VmFd>,
18+
irq_routing: Arc<KvmIrqRouting>,
19+
msi_configs: Vec<MsiConfig>,
20+
}
21+
22+
impl PciMsiIrq {
23+
#[allow(clippy::new_ret_no_self)]
24+
pub(super) fn new(
25+
base: InterruptIndex,
26+
count: InterruptIndex,
27+
max_msi_irqs: InterruptIndex,
28+
vmfd: Arc<VmFd>,
29+
irq_routing: Arc<KvmIrqRouting>,
30+
) -> Result<Self> {
31+
if count > max_msi_irqs || base >= MAX_IRQS || base + count > MAX_IRQS {
32+
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
33+
}
34+
35+
let mut msi_configs = Vec::with_capacity(count as usize);
36+
for _ in 0..count {
37+
msi_configs.push(MsiConfig::new());
38+
}
39+
40+
Ok(PciMsiIrq {
41+
base,
42+
count,
43+
vmfd,
44+
irq_routing,
45+
msi_configs,
46+
})
47+
}
48+
}
49+
50+
impl InterruptSourceGroup for PciMsiIrq {
51+
fn interrupt_type(&self) -> InterruptSourceType {
52+
InterruptSourceType::PciMsiIrq
53+
}
54+
55+
fn len(&self) -> u32 {
56+
self.count
57+
}
58+
59+
fn base(&self) -> u32 {
60+
self.base
61+
}
62+
63+
fn irqfd(&self, index: InterruptIndex) -> Option<&EventFd> {
64+
if index >= self.count {
65+
None
66+
} else {
67+
let msi_config = &self.msi_configs[index as usize];
68+
Some(&msi_config.irqfd)
69+
}
70+
}
71+
72+
fn enable(&self, configs: &[InterruptSourceConfig]) -> Result<()> {
73+
if configs.len() != self.count as usize {
74+
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
75+
}
76+
77+
// First add IRQ routings for all the MSI interrupts.
78+
let entries = create_msi_routing_entries(self.base, configs)?;
79+
self.irq_routing.add(&entries)?;
80+
81+
// Then register irqfds to the KVM module.
82+
for i in 0..self.count {
83+
let irqfd = &self.msi_configs[i as usize].irqfd;
84+
self.vmfd
85+
.register_irqfd(irqfd, self.base + i)
86+
.map_err(|e| std::io::Error::from_raw_os_error(e.errno()))?;
87+
}
88+
89+
Ok(())
90+
}
91+
92+
fn disable(&self) -> Result<()> {
93+
// First unregister all irqfds, so it won't trigger anymore.
94+
for i in 0..self.count {
95+
let irqfd = &self.msi_configs[i as usize].irqfd;
96+
self.vmfd
97+
.unregister_irqfd(irqfd, self.base + i)
98+
.map_err(|e| std::io::Error::from_raw_os_error(e.errno()))?;
99+
}
100+
101+
// Then tear down the IRQ routings for all the MSI interrupts.
102+
let mut entries = Vec::with_capacity(self.count as usize);
103+
for i in 0..self.count {
104+
// Safe to unwrap because there's no legal way to break the mutex.
105+
let msicfg = self.msi_configs[i as usize].config.lock().unwrap();
106+
let entry = new_msi_routing_entry(self.base + i, &*msicfg);
107+
entries.push(entry);
108+
}
109+
self.irq_routing.remove(&entries)?;
110+
111+
Ok(())
112+
}
113+
114+
#[allow(irrefutable_let_patterns)]
115+
fn update(&self, index: InterruptIndex, config: &InterruptSourceConfig) -> Result<()> {
116+
if index >= self.count {
117+
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
118+
}
119+
120+
if let InterruptSourceConfig::MsiIrq(ref cfg) = config {
121+
// Safe to unwrap because there's no legal way to break the mutex.
122+
let entry = {
123+
let mut msicfg = self.msi_configs[index as usize].config.lock().unwrap();
124+
msicfg.high_addr = cfg.high_addr;
125+
msicfg.low_addr = cfg.low_addr;
126+
msicfg.data = cfg.data;
127+
new_msi_routing_entry(self.base + index, &*msicfg)
128+
};
129+
self.irq_routing.modify(&entry)
130+
} else {
131+
Err(std::io::Error::from_raw_os_error(libc::EINVAL))
132+
}
133+
}
134+
135+
fn trigger(&self, index: InterruptIndex, flags: u32) -> Result<()> {
136+
// Assume that the caller will maintain the interrupt states and only call this function
137+
// when suitable.
138+
if index >= self.count || flags != 0 {
139+
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
140+
}
141+
let msi_config = &self.msi_configs[index as usize];
142+
msi_config.irqfd.write(1)
143+
}
144+
145+
fn ack(&self, index: InterruptIndex, flags: u32) -> Result<()> {
146+
// It's a noop to acknowledge an edge triggered MSI interrupts.
147+
if index >= self.count || flags != 0 {
148+
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
149+
}
150+
Ok(())
151+
}
152+
}

src/interrupt/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ pub type InterruptIndex = u32;
6767
pub const MAX_IRQS: InterruptIndex = 1024;
6868

6969
#[cfg(feature = "msi_irq")]
70-
/// Maximum number of Message Signaled Interrupts per device.
71-
pub const MAX_MSI_IRQS_PER_DEVICE: InterruptIndex = 128;
70+
/// Default maximum number of Message Signaled Interrupts per device.
71+
pub const DEFAULT_MAX_MSI_IRQS_PER_DEVICE: InterruptIndex = 128;
7272

7373
/// Type of interrupt source.
7474
#[derive(Clone, Debug)]
@@ -142,6 +142,9 @@ pub trait InterruptManager {
142142
/// before calling destroy_group(). This assumption helps to simplify InterruptSourceGroup
143143
/// implementations.
144144
fn destroy_group(&self, group: Arc<Box<dyn InterruptSourceGroup>>) -> Result<()>;
145+
146+
/// Set maximum supported MSI interrupts per device.
147+
fn set_max_msi_irqs(&self, max: InterruptIndex);
145148
}
146149

147150
/// Trait to manage a group of interrupt sources for a device.

0 commit comments

Comments
 (0)