Skip to content

Commit 4e80190

Browse files
committed
Implement infrastructure to manage interrupts by KVM
Implement infrastructure to manage interrupt sources based on Linux KVM kernel module. Signed-off-by: Liu Jiang <gerry@linux.alibaba.com> Signed-off-by: Bin Zha <zhabin@linux.alibaba.com>
1 parent 43b8c0b commit 4e80190

File tree

3 files changed

+285
-2
lines changed

3 files changed

+285
-2
lines changed

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ edition = "2018"
88

99
[dependencies]
1010
libc = ">=0.2.39"
11+
kvm-bindings = { version = ">=0.1.1, <1.0", optional = true }
12+
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls.git", branch = "master", optional = true }
1113
vmm-sys-util = ">=0.2.0, <1.0"
1214

1315
vm-memory = { git = "https://github.com/rust-vmm/vm-memory" }
1416

1517
[features]
16-
legacy_irq = []
17-
msi_irq = []
18+
kvm_irq = ["kvm-ioctls", "kvm-bindings"]

src/interrupt/kvm/mod.rs

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
// Copyright (C) 2019 Alibaba Cloud. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! Manage virtual device's interrupts based on the Linux KVM framework.
5+
//!
6+
//! When updaing KVM IRQ routing by ioctl(KVM_SET_GSI_ROUTING), all interrupts of the virtual
7+
//! machine must be updated all together. The [KvmIrqRouting](struct.KvmIrqRouting.html)
8+
//! structure is to maintain the global interrupt routing table.
9+
10+
use std::collections::HashMap;
11+
use std::sync::{Arc, Mutex};
12+
13+
use kvm_bindings::{kvm_irq_routing, kvm_irq_routing_entry};
14+
#[cfg(all(
15+
feature = "legacy_irq",
16+
any(target_arch = "x86", target_arch = "x86_64")
17+
))]
18+
use kvm_bindings::{
19+
KVM_IRQCHIP_IOAPIC, KVM_IRQCHIP_PIC_MASTER, KVM_IRQCHIP_PIC_SLAVE, KVM_IRQ_ROUTING_IRQCHIP,
20+
};
21+
use kvm_ioctls::VmFd;
22+
23+
use super::*;
24+
25+
/// Structure to manage interrupt sources for a virtual machine based on the Linux KVM framework.
26+
///
27+
/// The KVM framework provides methods to inject interrupts into the target virtual machines,
28+
/// which uses irqfd to notity the KVM kernel module for injecting interrupts. When the interrupt
29+
/// source, usually a virtual device backend in userspace, writes to the irqfd file descriptor,
30+
/// the KVM kernel module will inject a corresponding interrupt into the target VM according to
31+
/// the IRQ routing configuration.
32+
pub struct KvmIrqManager {
33+
mgr: Mutex<KvmIrqManagerObj>,
34+
}
35+
36+
impl KvmIrqManager {
37+
/// Create a new interrupt manager based on the Linux KVM framework.
38+
///
39+
/// # Arguments
40+
/// * `vmfd`: The KVM VM file descriptor, which will be used to access the KVM subsystem.
41+
pub fn new(vmfd: Arc<VmFd>) -> Self {
42+
let vmfd2 = vmfd.clone();
43+
KvmIrqManager {
44+
mgr: Mutex::new(KvmIrqManagerObj {
45+
vmfd,
46+
groups: HashMap::new(),
47+
routes: Arc::new(KvmIrqRouting::new(vmfd2)),
48+
}),
49+
}
50+
}
51+
52+
/// Prepare the interrupt manager for generating interrupts into the target VM.
53+
///
54+
/// On x86 platforms, this will set up IRQ routings for legacy IRQs.
55+
pub fn initialize(&self) -> Result<()> {
56+
// Safe to unwrap because there's no legal way to break the mutex.
57+
let mgr = self.mgr.lock().unwrap();
58+
mgr.initialize()
59+
}
60+
}
61+
62+
impl InterruptManager for KvmIrqManager {
63+
fn create_group(
64+
&self,
65+
ty: InterruptSourceType,
66+
base: InterruptIndex,
67+
count: u32,
68+
) -> Result<Arc<Box<dyn InterruptSourceGroup>>> {
69+
// Safe to unwrap because there's no legal way to break the mutex.
70+
let mut mgr = self.mgr.lock().unwrap();
71+
mgr.create_group(ty, base, count)
72+
}
73+
74+
fn destroy_group(&self, group: Arc<Box<dyn InterruptSourceGroup>>) -> Result<()> {
75+
// Safe to unwrap because there's no legal way to break the mutex.
76+
let mut mgr = self.mgr.lock().unwrap();
77+
mgr.destroy_group(group)
78+
}
79+
}
80+
81+
struct KvmIrqManagerObj {
82+
vmfd: Arc<VmFd>,
83+
routes: Arc<KvmIrqRouting>,
84+
groups: HashMap<InterruptIndex, Arc<Box<dyn InterruptSourceGroup>>>,
85+
}
86+
87+
impl KvmIrqManagerObj {
88+
fn initialize(&self) -> Result<()> {
89+
self.routes.initialize()?;
90+
Ok(())
91+
}
92+
93+
fn create_group(
94+
&mut self,
95+
ty: InterruptSourceType,
96+
base: InterruptIndex,
97+
count: u32,
98+
) -> Result<Arc<Box<dyn InterruptSourceGroup>>> {
99+
let group = match ty {
100+
#[cfg(feature = "legacy_irq")]
101+
InterruptSourceType::LegacyIrq => {
102+
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
103+
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
104+
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
105+
LegacyIrq::new(base, count, self.vmfd.clone(), self.routes.clone())?
106+
}
107+
#[cfg(feature = "pci_msi_irq")]
108+
InterruptSourceType::MsiIrq => {
109+
PciMsiIrq::new(base, count, self.vmfd.clone(), self.routes.clone())?
110+
}
111+
};
112+
113+
self.groups.insert(base, group.clone());
114+
115+
Ok(group)
116+
}
117+
118+
fn destroy_group(&mut self, group: Arc<Box<dyn InterruptSourceGroup>>) -> Result<()> {
119+
self.groups.remove(&group.base());
120+
Ok(())
121+
}
122+
}
123+
124+
// Use (entry.type, entry.gsi) as the hash key because entry.gsi can't uniquely identify an
125+
// interrupt source on x86 platforms. The PIC and IOAPIC may share the same GSI on x86 platforms.
126+
fn hash_key(entry: &kvm_irq_routing_entry) -> u64 {
127+
let type1 = match entry.type_ {
128+
#[cfg(feature = "legacy_irq")]
129+
KVM_IRQ_ROUTING_IRQCHIP => unsafe { entry.u.irqchip.irqchip },
130+
_ => 0u32,
131+
};
132+
(u64::from(type1) << 48 | u64::from(entry.type_) << 32) | u64::from(entry.gsi)
133+
}
134+
135+
pub(super) struct KvmIrqRouting {
136+
vm_fd: Arc<VmFd>,
137+
routes: Mutex<HashMap<u64, kvm_irq_routing_entry>>,
138+
}
139+
140+
impl KvmIrqRouting {
141+
pub(super) fn new(vm_fd: Arc<VmFd>) -> Self {
142+
KvmIrqRouting {
143+
vm_fd,
144+
routes: Mutex::new(HashMap::new()),
145+
}
146+
}
147+
148+
pub(super) fn initialize(&self) -> Result<()> {
149+
// Safe to unwrap because there's no legal way to break the mutex.
150+
#[allow(unused_mut)]
151+
let mut routes = self.routes.lock().unwrap();
152+
153+
#[cfg(all(
154+
feature = "legacy_irq",
155+
any(target_arch = "x86", target_arch = "x86_64")
156+
))]
157+
self.initialize_legacy(&mut *routes)?;
158+
159+
self.set_routing(&*routes)
160+
}
161+
162+
fn set_routing(&self, routes: &HashMap<u64, kvm_irq_routing_entry>) -> Result<()> {
163+
// Allocate enough buffer memory.
164+
let elem_sz = std::mem::size_of::<kvm_irq_routing>();
165+
let total_sz = std::mem::size_of::<kvm_irq_routing_entry>() * routes.len() + elem_sz;
166+
let elem_cnt = (total_sz + elem_sz - 1) / elem_sz;
167+
let mut irq_routings = Vec::<kvm_irq_routing>::with_capacity(elem_cnt);
168+
irq_routings.resize_with(elem_cnt, Default::default);
169+
170+
// Prepare the irq_routing header.
171+
let mut irq_routing = &mut irq_routings[0];
172+
irq_routing.nr = routes.len() as u32;
173+
irq_routing.flags = 0;
174+
175+
// Safe because we have just allocated enough memory above.
176+
let irq_routing_entries = unsafe { irq_routing.entries.as_mut_slice(routes.len()) };
177+
for (idx, entry) in routes.values().enumerate() {
178+
irq_routing_entries[idx] = *entry;
179+
}
180+
181+
self.vm_fd.set_gsi_routing(irq_routing)?;
182+
183+
Ok(())
184+
}
185+
186+
#[cfg(feature = "msi_irq")]
187+
pub(super) fn add(&self, entries: &[kvm_irq_routing_entry]) -> Result<()> {
188+
// Safe to unwrap because there's no legal way to break the mutex.
189+
let mut routes = self.routes.lock().unwrap();
190+
for entry in entries {
191+
if entry.gsi >= MAX_IRQS {
192+
return Err(std::io::Error::from_raw_os_error(libc::EINVAL));
193+
} else if routes.contains_key(&hash_key(entry)) {
194+
return Err(std::io::Error::from_raw_os_error(libc::EEXIST));
195+
}
196+
}
197+
198+
for entry in entries {
199+
let _ = routes.insert(hash_key(entry), *entry);
200+
}
201+
self.set_routing(&routes)
202+
}
203+
204+
#[cfg(feature = "msi_irq")]
205+
pub(super) fn remove(&self, entries: &[kvm_irq_routing_entry]) -> Result<()> {
206+
// Safe to unwrap because there's no legal way to break the mutex.
207+
let mut routes = self.routes.lock().unwrap();
208+
for entry in entries {
209+
let _ = routes.remove(&hash_key(entry));
210+
}
211+
self.set_routing(&routes)
212+
}
213+
214+
#[cfg(feature = "msi_irq")]
215+
pub(super) fn modify(&self, entry: &kvm_irq_routing_entry) -> Result<()> {
216+
// Safe to unwrap because there's no legal way to break the mutex.
217+
let mut routes = self.routes.lock().unwrap();
218+
if !routes.contains_key(&hash_key(entry)) {
219+
return Err(std::io::Error::from_raw_os_error(libc::ENOENT));
220+
}
221+
222+
let _ = routes.insert(hash_key(entry), *entry);
223+
self.set_routing(&routes)
224+
}
225+
226+
#[cfg(feature = "legacy_irq")]
227+
pub(super) fn add_legacy_entry(
228+
gsi: u32,
229+
chip: u32,
230+
pin: u32,
231+
routes: &mut HashMap<u64, kvm_irq_routing_entry>,
232+
) -> Result<()> {
233+
let mut entry = kvm_irq_routing_entry {
234+
gsi,
235+
type_: KVM_IRQ_ROUTING_IRQCHIP,
236+
..Default::default()
237+
};
238+
// Safe because we are initializing all fields of the `irqchip` struct.
239+
unsafe {
240+
entry.u.irqchip.irqchip = chip;
241+
entry.u.irqchip.pin = pin;
242+
}
243+
routes.insert(hash_key(&entry), entry);
244+
245+
Ok(())
246+
}
247+
248+
#[cfg(all(
249+
feature = "legacy_irq",
250+
any(target_arch = "x86", target_arch = "x86_64")
251+
))]
252+
/// Build routings for IRQs connected to the master PIC, the slave PIC or the first IOAPIC.
253+
fn initialize_legacy(&self, routes: &mut HashMap<u64, kvm_irq_routing_entry>) -> Result<()> {
254+
// Build routings for the master PIC
255+
for i in 0..8 {
256+
if i != 2 {
257+
Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_MASTER, i, routes)?;
258+
}
259+
}
260+
261+
// Build routings for the slave PIC
262+
for i in 8..16 {
263+
Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_SLAVE, i - 8, routes)?;
264+
}
265+
266+
// Build routings for the first IOAPIC
267+
for i in 0..24 {
268+
if i == 0 {
269+
Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, 2, routes)?;
270+
} else if i != 2 {
271+
Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, i, routes)?;
272+
};
273+
}
274+
275+
Ok(())
276+
}
277+
}

src/interrupt/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,8 @@ pub trait InterruptSourceGroup: Send + Sync {
203203
/// will get atomically cleared from the `interrupt_status` register.
204204
fn ack(&self, index: InterruptIndex, flags: u32) -> Result<()>;
205205
}
206+
207+
#[cfg(feature = "kvm_irq")]
208+
mod kvm;
209+
#[cfg(feature = "kvm_irq")]
210+
pub use kvm::KvmIrqManager;

0 commit comments

Comments
 (0)