From 902b9ae1f1b19fdf05d86f8e55d0da2240730406 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Mon, 5 Aug 2019 00:38:48 +0800 Subject: [PATCH 1/8] Switch to rust 2018 edition Switch to rust 2018 edition and turn on deny(missing_docs). Signed-off-by: Liu Jiang --- Cargo.toml | 1 + src/lib.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 16e2bc5..fda0615 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Samuel Ortiz "] repository = "https://github.com/rust-vmm/vm-device" license = "Apache-2.0" +edition = "2018" [dependencies] vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } diff --git a/src/lib.rs b/src/lib.rs index 9ef255d..1cca586 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ // Copyright © 2019 Intel Corporation. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +#![deny(missing_docs)] + //! rust-vmm device model. extern crate vm_memory; From 43b8c0b36347a8f3b4c18dcb8a2d09908fa5c490 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sun, 11 Aug 2019 18:25:06 +0800 Subject: [PATCH 2/8] interrupt: introduce traits to manage interrupt sources Introduce traits InterruptManager and InterruptSourceGroup to manage interrupt sources for virtual devices. Signed-off-by: Liu Jiang Signed-off-by: Bin Zha --- Cargo.toml | 7 ++ src/interrupt/mod.rs | 205 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 3 files changed, 213 insertions(+) create mode 100644 src/interrupt/mod.rs diff --git a/Cargo.toml b/Cargo.toml index fda0615..faf7321 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,11 @@ license = "Apache-2.0" edition = "2018" [dependencies] +libc = ">=0.2.39" +vmm-sys-util = ">=0.2.0, <1.0" + vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } + +[features] +legacy_irq = [] +msi_irq = [] diff --git a/src/interrupt/mod.rs b/src/interrupt/mod.rs new file mode 100644 index 0000000..3ced586 --- /dev/null +++ b/src/interrupt/mod.rs @@ -0,0 +1,205 @@ +// Copyright (C) 2019 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Traits and Structs to manage interrupt sources for devices. +//! +//! In system programming, an interrupt is a signal to the processor emitted by hardware or +//! software indicating an event that needs immediate attention. An interrupt alerts the processor +//! to a high-priority condition requiring the interruption of the current code the processor is +//! executing. The processor responds by suspending its current activities, saving its state, and +//! executing a function called an interrupt handler (or an interrupt service routine, ISR) to deal +//! with the event. This interruption is temporary, and, after the interrupt handler finishes, +//! unless handling the interrupt has emitted a fatal error, the processor resumes normal +//! activities. +//! +//! Hardware interrupts are used by devices to communicate that they require attention from the +//! operating system, or a bare-metal program running on the CPU if there are no OSes. The act of +//! initiating a hardware interrupt is referred to as an interrupt request (IRQ). Different devices +//! are usually associated with different interrupts using a unique value associated with each +//! interrupt. This makes it possible to know which hardware device caused which interrupts. +//! These interrupt values are often called IRQ lines, or just interrupt lines. +//! +//! Nowadays, IRQ lines is not the only mechanism to deliver device interrupts to processors. +//! MSI [(Message Signaled Interrupt)](https://en.wikipedia.org/wiki/Message_Signaled_Interrupts) +//! is another commonly used alternative in-band method of signaling an interrupt, using special +//! in-band messages to replace traditional out-of-band assertion of dedicated interrupt lines. +//! While more complex to implement in a device, message signaled interrupts have some significant +//! advantages over pin-based out-of-band interrupt signaling. Message signaled interrupts are +//! supported in PCI bus since its version 2.2, and in later available PCI Express bus. Some non-PCI +//! architectures also use message signaled interrupts. +//! +//! While IRQ is a term commonly used by Operating Systems when dealing with hardware +//! interrupts, the IRQ numbers managed by OSes are independent of the ones managed by VMM. +//! For simplicity sake, the term `Interrupt Source` is used instead of IRQ to represent both pin-based +//! interrupts and MSI interrupts. +//! +//! A device may support multiple types of interrupts, and each type of interrupt may support one +//! or multiple interrupt sources. For example, a PCI device may support: +//! * Legacy Irq: exactly one interrupt source. +//! * PCI MSI Irq: 1,2,4,8,16,32 interrupt sources. +//! * PCI MSIx Irq: 2^n(n=0-11) interrupt sources. +//! +//! A distinct Interrupt Source Identifier (ISID) will be assigned to each interrupt source. +//! An ID allocator will be used to allocate and free Interrupt Source Identifiers for devices. +//! To decouple the vm-device crate from the ID allocator, the vm-device crate doesn't take the +//! responsibility to allocate/free Interrupt Source IDs but only makes use of assigned IDs. +//! +//! The overall flow to deal with interrupts is: +//! * the VMM creates an interrupt manager +//! * the VMM creates a device manager, passing on an reference to the interrupt manager +//! * the device manager passes on an reference to the interrupt manager to all registered devices +//! * guest kernel loads drivers for virtual devices +//! * guest device driver determines the type and number of interrupts needed, and update the +//! device configuration +//! * the virtual device backend requests the interrupt manager to create an interrupt group +//! according to guest configuration information + +use std::sync::Arc; +use vmm_sys_util::eventfd::EventFd; + +/// Reuse std::io::Result to simplify interoperability among crates. +pub type Result = std::io::Result; + +/// Data type to store an interrupt source identifier. +pub type InterruptIndex = u32; + +/// Maximum number of global interrupt sources. +pub const MAX_IRQS: InterruptIndex = 1024; + +#[cfg(feature = "msi_irq")] +/// Maximum number of Message Signaled Interrupts per device. +pub const MAX_MSI_IRQS_PER_DEVICE: InterruptIndex = 128; + +/// Type of interrupt source. +#[derive(Copy, Clone, Debug)] +pub enum InterruptSourceType { + #[cfg(feature = "legacy_irq")] + /// Legacy Pin-based Interrupt. + /// On x86 platforms, legacy interrupts are routed through 8259 PICs and/or IOAPICs. + LegacyIrq, + #[cfg(feature = "pci_msi_irq")] + /// Message Signaled Interrupt (PCI MSI/PCI MSIx). + /// Some non-PCI devices (like HPET on x86) make use of generic MSI in platform specific ways. + PciMsiIrq, +} + +/// Configuration data for an interrupt source. +#[derive(Copy, Clone, Debug)] +pub enum InterruptSourceConfig { + #[cfg(feature = "legacy_irq")] + /// Configuration data for Legacy interrupts. + LegacyIrq(LegacyIrqSourceConfig), + #[cfg(feature = "msi_irq")] + /// Configuration data for PciMsi, PciMsix and generic MSI interrupts. + MsiIrq(MsiIrqSourceConfig), +} + +/// Configuration data for legacy interrupts. +/// +/// On x86 platforms, legacy interrupts means those interrupts routed through PICs or IOAPICs. +#[cfg(feature = "legacy_irq")] +#[derive(Copy, Clone, Debug)] +pub struct LegacyIrqSourceConfig {} + +/// Configuration data for GenericMsi, PciMsi, PciMsix interrupts. +#[cfg(feature = "msi_irq")] +#[derive(Copy, Clone, Debug, Default)] +pub struct MsiIrqSourceConfig { + /// High address to deliver message signaled interrupt. + pub high_addr: u32, + /// Low address to deliver message signaled interrupt. + pub low_addr: u32, + /// Data to write to deliver message signaled interrupt. + pub data: u32, +} + +/// Trait to manage interrupt sources for virtual device backends. +/// +/// The InterruptManager implementations should protect itself from concurrent accesses internally, +/// so it could be invoked from multi-threaded context. +pub trait InterruptManager { + /// Create an [InterruptSourceGroup](trait.InterruptSourceGroup.html) object to manage + /// interrupt sources for a virtual device + /// + /// An [InterruptSourceGroup](trait.InterruptSourceGroup.html) object manages all interrupt + /// sources of the same type for a virtual device. + /// + /// # Arguments + /// * type_: type of interrupt source. + /// * base: base Interrupt Source ID to be managed by the group object. + /// * count: number of Interrupt Sources to be managed by the group object. + fn create_group( + &self, + type_: InterruptSourceType, + base: InterruptIndex, + count: InterruptIndex, + ) -> Result>>; + + /// Destroy an [InterruptSourceGroup](trait.InterruptSourceGroup.html) object created by + /// [create_group()](trait.InterruptManager.html#tymethod.create_group). + /// + /// Assume the caller takes the responsibility to disable all interrupt sources of the group + /// before calling destroy_group(). This assumption helps to simplify InterruptSourceGroup + /// implementations. + fn destroy_group(&self, group: Arc>) -> Result<()>; +} + +/// Trait to manage a group of interrupt sources for a device. +/// +/// A device may support several types of interrupts, and each type of interrupt may contain one or +/// multiple continuous interrupt sources. For example, a PCI device may concurrently support: +/// * Legacy Irq: exactly one interrupt source. +/// * PCI MSI Irq: 1,2,4,8,16,32 interrupt sources. +/// * PCI MSIx Irq: 2^n(n=0-11) interrupt sources. +/// +/// PCI MSI interrupts of a device may not be configured individually, and must configured as a +/// whole block. So all interrupts of the same type of a device are abstracted as an +/// [InterruptSourceGroup](trait.InterruptSourceGroup.html) object, instead of abstracting each +/// interrupt source as a distinct InterruptSource. +#[allow(clippy::len_without_is_empty)] +#[allow(clippy::trivially_copy_pass_by_ref)] +pub trait InterruptSourceGroup: Send + Sync { + /// Get type of interrupt sources managed by the group. + fn interrupt_type(&self) -> InterruptSourceType; + + /// Get number of interrupt sources managed by the group. + fn len(&self) -> InterruptIndex; + + /// Get base of the assigned Interrupt Source Identifiers. + fn base(&self) -> InterruptIndex; + + /// Get the irqfd to inject interrupts into the guest by using Linux KVM module. + fn irqfd(&self, _index: InterruptIndex) -> Option<&EventFd> { + None + } + + /// Get interrupt flags. + fn flags(&self, _index: InterruptIndex) -> u32 { + 0 + } + + /// Enable the interrupt sources in the group to generate interrupts. + fn enable(&self, configs: &[InterruptSourceConfig]) -> Result<()>; + + /// Disable the interrupt sources in the group to generate interrupts. + fn disable(&self) -> Result<()>; + + /// Change the configuration to generate interrupts. + /// + /// # Arguments + /// * index: sub-index into the group. + /// * config: configuration data for the interrupt source. + fn update(&self, index: InterruptIndex, config: &InterruptSourceConfig) -> Result<()>; + + /// Inject an interrupt into the guest. + /// + /// If the interrupt has an associated `interrupt_status` register, all bits set in `flag` + /// will be atomically ORed into the `interrupt_status` register. + fn trigger(&self, index: InterruptIndex, flags: u32) -> Result<()>; + + /// Acknowledge that the guest has handled the interrupt. + /// + /// If the interrupt has an associated `interrupt_status` register, all bits set in `flag` + /// will get atomically cleared from the `interrupt_status` register. + fn ack(&self, index: InterruptIndex, flags: u32) -> Result<()>; +} diff --git a/src/lib.rs b/src/lib.rs index 1cca586..48306a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,6 +9,7 @@ extern crate vm_memory; use vm_memory::GuestAddress; +pub mod interrupt; pub mod resources; /// IO Addresses. From 4e80190f4a2fb896fba4cb8f65d034e1fa05c8e3 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sun, 11 Aug 2019 18:48:23 +0800 Subject: [PATCH 3/8] Implement infrastructure to manage interrupts by KVM Implement infrastructure to manage interrupt sources based on Linux KVM kernel module. Signed-off-by: Liu Jiang Signed-off-by: Bin Zha --- Cargo.toml | 5 +- src/interrupt/kvm/mod.rs | 277 +++++++++++++++++++++++++++++++++++++++ src/interrupt/mod.rs | 5 + 3 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 src/interrupt/kvm/mod.rs diff --git a/Cargo.toml b/Cargo.toml index faf7321..cd44a6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,11 @@ edition = "2018" [dependencies] libc = ">=0.2.39" +kvm-bindings = { version = ">=0.1.1, <1.0", optional = true } +kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls.git", branch = "master", optional = true } vmm-sys-util = ">=0.2.0, <1.0" vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } [features] -legacy_irq = [] -msi_irq = [] +kvm_irq = ["kvm-ioctls", "kvm-bindings"] diff --git a/src/interrupt/kvm/mod.rs b/src/interrupt/kvm/mod.rs new file mode 100644 index 0000000..302dd90 --- /dev/null +++ b/src/interrupt/kvm/mod.rs @@ -0,0 +1,277 @@ +// Copyright (C) 2019 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Manage virtual device's interrupts based on the Linux KVM framework. +//! +//! When updaing KVM IRQ routing by ioctl(KVM_SET_GSI_ROUTING), all interrupts of the virtual +//! machine must be updated all together. The [KvmIrqRouting](struct.KvmIrqRouting.html) +//! structure is to maintain the global interrupt routing table. + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +use kvm_bindings::{kvm_irq_routing, kvm_irq_routing_entry}; +#[cfg(all( + feature = "legacy_irq", + any(target_arch = "x86", target_arch = "x86_64") +))] +use kvm_bindings::{ + KVM_IRQCHIP_IOAPIC, KVM_IRQCHIP_PIC_MASTER, KVM_IRQCHIP_PIC_SLAVE, KVM_IRQ_ROUTING_IRQCHIP, +}; +use kvm_ioctls::VmFd; + +use super::*; + +/// Structure to manage interrupt sources for a virtual machine based on the Linux KVM framework. +/// +/// The KVM framework provides methods to inject interrupts into the target virtual machines, +/// which uses irqfd to notity the KVM kernel module for injecting interrupts. When the interrupt +/// source, usually a virtual device backend in userspace, writes to the irqfd file descriptor, +/// the KVM kernel module will inject a corresponding interrupt into the target VM according to +/// the IRQ routing configuration. +pub struct KvmIrqManager { + mgr: Mutex, +} + +impl KvmIrqManager { + /// Create a new interrupt manager based on the Linux KVM framework. + /// + /// # Arguments + /// * `vmfd`: The KVM VM file descriptor, which will be used to access the KVM subsystem. + pub fn new(vmfd: Arc) -> Self { + let vmfd2 = vmfd.clone(); + KvmIrqManager { + mgr: Mutex::new(KvmIrqManagerObj { + vmfd, + groups: HashMap::new(), + routes: Arc::new(KvmIrqRouting::new(vmfd2)), + }), + } + } + + /// Prepare the interrupt manager for generating interrupts into the target VM. + /// + /// On x86 platforms, this will set up IRQ routings for legacy IRQs. + pub fn initialize(&self) -> Result<()> { + // Safe to unwrap because there's no legal way to break the mutex. + let mgr = self.mgr.lock().unwrap(); + mgr.initialize() + } +} + +impl InterruptManager for KvmIrqManager { + fn create_group( + &self, + ty: InterruptSourceType, + base: InterruptIndex, + count: u32, + ) -> Result>> { + // Safe to unwrap because there's no legal way to break the mutex. + let mut mgr = self.mgr.lock().unwrap(); + mgr.create_group(ty, base, count) + } + + fn destroy_group(&self, group: Arc>) -> Result<()> { + // Safe to unwrap because there's no legal way to break the mutex. + let mut mgr = self.mgr.lock().unwrap(); + mgr.destroy_group(group) + } +} + +struct KvmIrqManagerObj { + vmfd: Arc, + routes: Arc, + groups: HashMap>>, +} + +impl KvmIrqManagerObj { + fn initialize(&self) -> Result<()> { + self.routes.initialize()?; + Ok(()) + } + + fn create_group( + &mut self, + ty: InterruptSourceType, + base: InterruptIndex, + count: u32, + ) -> Result>> { + let group = match ty { + #[cfg(feature = "legacy_irq")] + InterruptSourceType::LegacyIrq => { + #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + LegacyIrq::new(base, count, self.vmfd.clone(), self.routes.clone())? + } + #[cfg(feature = "pci_msi_irq")] + InterruptSourceType::MsiIrq => { + PciMsiIrq::new(base, count, self.vmfd.clone(), self.routes.clone())? + } + }; + + self.groups.insert(base, group.clone()); + + Ok(group) + } + + fn destroy_group(&mut self, group: Arc>) -> Result<()> { + self.groups.remove(&group.base()); + Ok(()) + } +} + +// Use (entry.type, entry.gsi) as the hash key because entry.gsi can't uniquely identify an +// interrupt source on x86 platforms. The PIC and IOAPIC may share the same GSI on x86 platforms. +fn hash_key(entry: &kvm_irq_routing_entry) -> u64 { + let type1 = match entry.type_ { + #[cfg(feature = "legacy_irq")] + KVM_IRQ_ROUTING_IRQCHIP => unsafe { entry.u.irqchip.irqchip }, + _ => 0u32, + }; + (u64::from(type1) << 48 | u64::from(entry.type_) << 32) | u64::from(entry.gsi) +} + +pub(super) struct KvmIrqRouting { + vm_fd: Arc, + routes: Mutex>, +} + +impl KvmIrqRouting { + pub(super) fn new(vm_fd: Arc) -> Self { + KvmIrqRouting { + vm_fd, + routes: Mutex::new(HashMap::new()), + } + } + + pub(super) fn initialize(&self) -> Result<()> { + // Safe to unwrap because there's no legal way to break the mutex. + #[allow(unused_mut)] + let mut routes = self.routes.lock().unwrap(); + + #[cfg(all( + feature = "legacy_irq", + any(target_arch = "x86", target_arch = "x86_64") + ))] + self.initialize_legacy(&mut *routes)?; + + self.set_routing(&*routes) + } + + fn set_routing(&self, routes: &HashMap) -> Result<()> { + // Allocate enough buffer memory. + let elem_sz = std::mem::size_of::(); + let total_sz = std::mem::size_of::() * routes.len() + elem_sz; + let elem_cnt = (total_sz + elem_sz - 1) / elem_sz; + let mut irq_routings = Vec::::with_capacity(elem_cnt); + irq_routings.resize_with(elem_cnt, Default::default); + + // Prepare the irq_routing header. + let mut irq_routing = &mut irq_routings[0]; + irq_routing.nr = routes.len() as u32; + irq_routing.flags = 0; + + // Safe because we have just allocated enough memory above. + let irq_routing_entries = unsafe { irq_routing.entries.as_mut_slice(routes.len()) }; + for (idx, entry) in routes.values().enumerate() { + irq_routing_entries[idx] = *entry; + } + + self.vm_fd.set_gsi_routing(irq_routing)?; + + Ok(()) + } + + #[cfg(feature = "msi_irq")] + pub(super) fn add(&self, entries: &[kvm_irq_routing_entry]) -> Result<()> { + // Safe to unwrap because there's no legal way to break the mutex. + let mut routes = self.routes.lock().unwrap(); + for entry in entries { + if entry.gsi >= MAX_IRQS { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } else if routes.contains_key(&hash_key(entry)) { + return Err(std::io::Error::from_raw_os_error(libc::EEXIST)); + } + } + + for entry in entries { + let _ = routes.insert(hash_key(entry), *entry); + } + self.set_routing(&routes) + } + + #[cfg(feature = "msi_irq")] + pub(super) fn remove(&self, entries: &[kvm_irq_routing_entry]) -> Result<()> { + // Safe to unwrap because there's no legal way to break the mutex. + let mut routes = self.routes.lock().unwrap(); + for entry in entries { + let _ = routes.remove(&hash_key(entry)); + } + self.set_routing(&routes) + } + + #[cfg(feature = "msi_irq")] + pub(super) fn modify(&self, entry: &kvm_irq_routing_entry) -> Result<()> { + // Safe to unwrap because there's no legal way to break the mutex. + let mut routes = self.routes.lock().unwrap(); + if !routes.contains_key(&hash_key(entry)) { + return Err(std::io::Error::from_raw_os_error(libc::ENOENT)); + } + + let _ = routes.insert(hash_key(entry), *entry); + self.set_routing(&routes) + } + + #[cfg(feature = "legacy_irq")] + pub(super) fn add_legacy_entry( + gsi: u32, + chip: u32, + pin: u32, + routes: &mut HashMap, + ) -> Result<()> { + let mut entry = kvm_irq_routing_entry { + gsi, + type_: KVM_IRQ_ROUTING_IRQCHIP, + ..Default::default() + }; + // Safe because we are initializing all fields of the `irqchip` struct. + unsafe { + entry.u.irqchip.irqchip = chip; + entry.u.irqchip.pin = pin; + } + routes.insert(hash_key(&entry), entry); + + Ok(()) + } + + #[cfg(all( + feature = "legacy_irq", + any(target_arch = "x86", target_arch = "x86_64") + ))] + /// Build routings for IRQs connected to the master PIC, the slave PIC or the first IOAPIC. + fn initialize_legacy(&self, routes: &mut HashMap) -> Result<()> { + // Build routings for the master PIC + for i in 0..8 { + if i != 2 { + Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_MASTER, i, routes)?; + } + } + + // Build routings for the slave PIC + for i in 8..16 { + Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_SLAVE, i - 8, routes)?; + } + + // Build routings for the first IOAPIC + for i in 0..24 { + if i == 0 { + Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, 2, routes)?; + } else if i != 2 { + Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, i, routes)?; + }; + } + + Ok(()) + } +} diff --git a/src/interrupt/mod.rs b/src/interrupt/mod.rs index 3ced586..c1a7365 100644 --- a/src/interrupt/mod.rs +++ b/src/interrupt/mod.rs @@ -203,3 +203,8 @@ pub trait InterruptSourceGroup: Send + Sync { /// will get atomically cleared from the `interrupt_status` register. fn ack(&self, index: InterruptIndex, flags: u32) -> Result<()>; } + +#[cfg(feature = "kvm_irq")] +mod kvm; +#[cfg(feature = "kvm_irq")] +pub use kvm::KvmIrqManager; From 807d639788fc942faf2efe8580e400a8ad1152c3 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sun, 11 Aug 2019 19:04:13 +0800 Subject: [PATCH 4/8] Manage x86 legacy interrupts based on KVM Implement InterruptSourceGroup trait to manage x86 legacy interruts. On x86 platforms, pin-based device interrupts connecting to the master PIC, the slave PIC and IOAPICs are named as legacy interrupts. For legacy interrupts, the interrupt routing logic are manged by the PICs/IOAPICs and the interrupt group logic only takes responsibility to enable/disable the interrupts. Signed-off-by: Liu Jiang Signed-off-by: Bin Zha --- Cargo.toml | 1 + src/interrupt/kvm/legacy_irq.rs | 168 ++++++++++++++++++++++++++++++++ src/interrupt/kvm/mod.rs | 89 +++-------------- 3 files changed, 184 insertions(+), 74 deletions(-) create mode 100644 src/interrupt/kvm/legacy_irq.rs diff --git a/Cargo.toml b/Cargo.toml index cd44a6c..c9cb7b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,4 @@ vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } [features] kvm_irq = ["kvm-ioctls", "kvm-bindings"] +legacy_irq = [] diff --git a/src/interrupt/kvm/legacy_irq.rs b/src/interrupt/kvm/legacy_irq.rs new file mode 100644 index 0000000..3769255 --- /dev/null +++ b/src/interrupt/kvm/legacy_irq.rs @@ -0,0 +1,168 @@ +// Copyright (C) 2019 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Manage virtual device's legacy interrupts based on Linux KVM framework. +//! +//! On x86 platforms, legacy interrupts are those managed by the Master PIC, the slave PIC and +//! IOAPICs. + +use super::*; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use kvm_bindings::{KVM_IRQCHIP_IOAPIC, KVM_IRQCHIP_PIC_MASTER, KVM_IRQCHIP_PIC_SLAVE}; +use std::sync::atomic::{AtomicUsize, Ordering}; + +pub(super) struct LegacyIrq { + base: u32, + vmfd: Arc, + irqfd: EventFd, + status: AtomicUsize, +} + +impl LegacyIrq { + #[allow(clippy::new_ret_no_self)] + pub(super) fn new( + base: InterruptIndex, + count: InterruptIndex, + vmfd: Arc, + _routes: Arc, + ) -> Result { + if count != 1 { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + Ok(LegacyIrq { + base, + vmfd, + irqfd: EventFd::new(0)?, + status: AtomicUsize::new(0), + }) + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + fn add_legacy_entry( + gsi: u32, + chip: u32, + pin: u32, + routes: &mut HashMap, + ) -> Result<()> { + let mut entry = kvm_irq_routing_entry { + gsi, + type_: KVM_IRQ_ROUTING_IRQCHIP, + ..Default::default() + }; + // Safe because we are initializing all fields of the `irqchip` struct. + unsafe { + entry.u.irqchip.irqchip = chip; + entry.u.irqchip.pin = pin; + } + routes.insert(hash_key(&entry), entry); + + Ok(()) + } + + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] + /// Build routings for IRQs connected to the master PIC, the slave PIC or the first IOAPIC. + pub(super) fn initialize_legacy( + routes: &mut HashMap, + ) -> Result<()> { + // Build routings for the master PIC + for i in 0..8 { + if i != 2 { + Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_MASTER, i, routes)?; + } + } + + // Build routings for the slave PIC + for i in 8..16 { + Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_SLAVE, i - 8, routes)?; + } + + // Build routings for the first IOAPIC + for i in 0..24 { + if i == 0 { + Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, 2, routes)?; + } else if i != 2 { + Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, i, routes)?; + }; + } + + Ok(()) + } + + #[cfg(any(target_arch = "aarch", target_arch = "aarch64"))] + pub(super) fn initialize_legacy( + _routes: &mut HashMap, + ) -> Result<()> { + //TODO + Ok(()) + } +} + +impl InterruptSourceGroup for LegacyIrq { + fn interrupt_type(&self) -> InterruptSourceType { + InterruptSourceType::LegacyIrq + } + + fn len(&self) -> u32 { + 1 + } + + fn base(&self) -> u32 { + self.base + } + + fn irqfd(&self, index: InterruptIndex) -> Option<&EventFd> { + if index != 0 { + None + } else { + Some(&self.irqfd) + } + } + + fn flags(&self, index: InterruptIndex) -> u32 { + if index == 0 { + self.status.load(Ordering::SeqCst) as u32 + } else { + 0 + } + } + + fn enable(&self, configs: &[InterruptSourceConfig]) -> Result<()> { + if configs.len() != 1 { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + // The IRQ routings for legacy IRQs have been configured during + // KvmIrqManager::initialize(), so only need to register irqfd to the KVM driver. + self.vmfd.register_irqfd(&self.irqfd, self.base) + } + + fn disable(&self) -> Result<()> { + self.vmfd.unregister_irqfd(&self.irqfd, self.base) + } + + fn update(&self, index: InterruptIndex, _config: &InterruptSourceConfig) -> Result<()> { + // For legacy interrupts, the routing configuration is managed by the PIC/IOAPIC interrupt + // controller drivers, so nothing to do here. + if index != 0 { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + Ok(()) + } + + fn trigger(&self, index: InterruptIndex, flags: u32) -> Result<()> { + if index != 0 { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + // Set interrupt status bits before writing to the irqfd. + self.status.fetch_or(flags as usize, Ordering::SeqCst); + self.irqfd.write(1) + } + + fn ack(&self, index: InterruptIndex, flags: u32) -> Result<()> { + if index != 0 { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + // Clear interrupt status bits. + self.status.fetch_and(!(flags as usize), Ordering::SeqCst); + Ok(()) + } +} diff --git a/src/interrupt/kvm/mod.rs b/src/interrupt/kvm/mod.rs index 302dd90..b6727ea 100644 --- a/src/interrupt/kvm/mod.rs +++ b/src/interrupt/kvm/mod.rs @@ -10,18 +10,16 @@ use std::collections::HashMap; use std::sync::{Arc, Mutex}; -use kvm_bindings::{kvm_irq_routing, kvm_irq_routing_entry}; -#[cfg(all( - feature = "legacy_irq", - any(target_arch = "x86", target_arch = "x86_64") -))] -use kvm_bindings::{ - KVM_IRQCHIP_IOAPIC, KVM_IRQCHIP_PIC_MASTER, KVM_IRQCHIP_PIC_SLAVE, KVM_IRQ_ROUTING_IRQCHIP, -}; +use kvm_bindings::{kvm_irq_routing, kvm_irq_routing_entry, KVM_IRQ_ROUTING_IRQCHIP}; use kvm_ioctls::VmFd; use super::*; +#[cfg(feature = "legacy_irq")] +mod legacy_irq; +#[cfg(feature = "legacy_irq")] +use self::legacy_irq::LegacyIrq; + /// Structure to manage interrupt sources for a virtual machine based on the Linux KVM framework. /// /// The KVM framework provides methods to inject interrupts into the target virtual machines, @@ -50,8 +48,6 @@ impl KvmIrqManager { } /// Prepare the interrupt manager for generating interrupts into the target VM. - /// - /// On x86 platforms, this will set up IRQ routings for legacy IRQs. pub fn initialize(&self) -> Result<()> { // Safe to unwrap because there's no legal way to break the mutex. let mgr = self.mgr.lock().unwrap(); @@ -96,14 +92,14 @@ impl KvmIrqManagerObj { base: InterruptIndex, count: u32, ) -> Result>> { - let group = match ty { + let group: Arc> = match ty { #[cfg(feature = "legacy_irq")] - InterruptSourceType::LegacyIrq => { - #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))] - return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - LegacyIrq::new(base, count, self.vmfd.clone(), self.routes.clone())? - } + InterruptSourceType::LegacyIrq => Arc::new(Box::new(LegacyIrq::new( + base, + count, + self.vmfd.clone(), + self.routes.clone(), + )?)), #[cfg(feature = "pci_msi_irq")] InterruptSourceType::MsiIrq => { PciMsiIrq::new(base, count, self.vmfd.clone(), self.routes.clone())? @@ -150,11 +146,8 @@ impl KvmIrqRouting { #[allow(unused_mut)] let mut routes = self.routes.lock().unwrap(); - #[cfg(all( - feature = "legacy_irq", - any(target_arch = "x86", target_arch = "x86_64") - ))] - self.initialize_legacy(&mut *routes)?; + #[cfg(feature = "legacy_irq")] + LegacyIrq::initialize_legacy(&mut *routes)?; self.set_routing(&*routes) } @@ -222,56 +215,4 @@ impl KvmIrqRouting { let _ = routes.insert(hash_key(entry), *entry); self.set_routing(&routes) } - - #[cfg(feature = "legacy_irq")] - pub(super) fn add_legacy_entry( - gsi: u32, - chip: u32, - pin: u32, - routes: &mut HashMap, - ) -> Result<()> { - let mut entry = kvm_irq_routing_entry { - gsi, - type_: KVM_IRQ_ROUTING_IRQCHIP, - ..Default::default() - }; - // Safe because we are initializing all fields of the `irqchip` struct. - unsafe { - entry.u.irqchip.irqchip = chip; - entry.u.irqchip.pin = pin; - } - routes.insert(hash_key(&entry), entry); - - Ok(()) - } - - #[cfg(all( - feature = "legacy_irq", - any(target_arch = "x86", target_arch = "x86_64") - ))] - /// Build routings for IRQs connected to the master PIC, the slave PIC or the first IOAPIC. - fn initialize_legacy(&self, routes: &mut HashMap) -> Result<()> { - // Build routings for the master PIC - for i in 0..8 { - if i != 2 { - Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_MASTER, i, routes)?; - } - } - - // Build routings for the slave PIC - for i in 8..16 { - Self::add_legacy_entry(i, KVM_IRQCHIP_PIC_SLAVE, i - 8, routes)?; - } - - // Build routings for the first IOAPIC - for i in 0..24 { - if i == 0 { - Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, 2, routes)?; - } else if i != 2 { - Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, i, routes)?; - }; - } - - Ok(()) - } } From 8854f47ce5b9cf35d3614cf3937190dbd59a233a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=88=E6=83=85?= Date: Fri, 16 Aug 2019 11:03:49 +0800 Subject: [PATCH 5/8] Limit number of legacy irqs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With some kvm version, setting irq_routing for non-existing legaccy IRQs may cause system crash. So limit the number to available legacy interrupts. Signed-off-by: 守情 --- src/interrupt/kvm/legacy_irq.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/interrupt/kvm/legacy_irq.rs b/src/interrupt/kvm/legacy_irq.rs index 3769255..11f85f7 100644 --- a/src/interrupt/kvm/legacy_irq.rs +++ b/src/interrupt/kvm/legacy_irq.rs @@ -11,6 +11,9 @@ use super::*; use kvm_bindings::{KVM_IRQCHIP_IOAPIC, KVM_IRQCHIP_PIC_MASTER, KVM_IRQCHIP_PIC_SLAVE}; use std::sync::atomic::{AtomicUsize, Ordering}; +/// Maximum number of legacy interrupts supported. +pub const MAX_LEGACY_IRQS: u32 = 24; + pub(super) struct LegacyIrq { base: u32, vmfd: Arc, @@ -29,6 +32,11 @@ impl LegacyIrq { if count != 1 { return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); } + + if base >= MAX_LEGACY_IRQS { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + Ok(LegacyIrq { base, vmfd, @@ -77,7 +85,7 @@ impl LegacyIrq { } // Build routings for the first IOAPIC - for i in 0..24 { + for i in 0..MAX_LEGACY_IRQS { if i == 0 { Self::add_legacy_entry(i, KVM_IRQCHIP_IOAPIC, 2, routes)?; } else if i != 2 { From 288f947dbe4c938fe31ea8aaf9297b4edbb9fd0f Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Sun, 27 Oct 2019 00:43:23 +0800 Subject: [PATCH 6/8] Add generic heplers to manage MSI interrupts Introduce generic mechanism to support message signalled interrupts based on KVM hypervisor. Signed-off-by: Liu Jiang Signed-off-by: Bin Zha --- Cargo.toml | 1 + src/interrupt/kvm/mod.rs | 3 ++ src/interrupt/kvm/msi_irq.rs | 59 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/interrupt/kvm/msi_irq.rs diff --git a/Cargo.toml b/Cargo.toml index c9cb7b0..ba3eda4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } [features] kvm_irq = ["kvm-ioctls", "kvm-bindings"] legacy_irq = [] +msi_irq = [] diff --git a/src/interrupt/kvm/mod.rs b/src/interrupt/kvm/mod.rs index b6727ea..0ef473d 100644 --- a/src/interrupt/kvm/mod.rs +++ b/src/interrupt/kvm/mod.rs @@ -20,6 +20,9 @@ mod legacy_irq; #[cfg(feature = "legacy_irq")] use self::legacy_irq::LegacyIrq; +#[cfg(feature = "msi_irq")] +mod msi_irq; + /// Structure to manage interrupt sources for a virtual machine based on the Linux KVM framework. /// /// The KVM framework provides methods to inject interrupts into the target virtual machines, diff --git a/src/interrupt/kvm/msi_irq.rs b/src/interrupt/kvm/msi_irq.rs new file mode 100644 index 0000000..2455c6e --- /dev/null +++ b/src/interrupt/kvm/msi_irq.rs @@ -0,0 +1,59 @@ +// Copyright (C) 2019 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Helper utilities for handling MSI interrupts. + +use super::*; +use kvm_bindings::{kvm_irq_routing_entry, KVM_IRQ_ROUTING_MSI}; + +pub(super) struct MsiConfig { + pub(super) irqfd: EventFd, + pub(super) config: Mutex, +} + +impl MsiConfig { + pub(super) fn new() -> Self { + MsiConfig { + irqfd: EventFd::new(0).unwrap(), + config: Mutex::new(Default::default()), + } + } +} + +pub(super) fn new_msi_routing_entry( + gsi: InterruptIndex, + msicfg: &MsiIrqSourceConfig, +) -> kvm_irq_routing_entry { + let mut entry = kvm_irq_routing_entry { + gsi, + type_: KVM_IRQ_ROUTING_MSI, + flags: 0, + ..Default::default() + }; + unsafe { + entry.u.msi.address_hi = msicfg.high_addr; + entry.u.msi.address_lo = msicfg.low_addr; + entry.u.msi.data = msicfg.data; + } + entry +} + +#[allow(irrefutable_let_patterns)] +pub(super) fn create_msi_routing_entries( + base: InterruptIndex, + configs: &[InterruptSourceConfig], +) -> Result> { + let _ = base + .checked_add(configs.len() as u32) + .ok_or_else(|| std::io::Error::from_raw_os_error(libc::EINVAL))?; + let mut entries = Vec::with_capacity(configs.len()); + for (i, ref val) in configs.iter().enumerate() { + if let InterruptSourceConfig::MsiIrq(msicfg) = val { + let entry = new_msi_routing_entry(base + i as u32, msicfg); + entries.push(entry); + } else { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + } + Ok(entries) +} From ed65d8a9942732756957a6916f1fb5f0ebec1f53 Mon Sep 17 00:00:00 2001 From: Liu Jiang Date: Mon, 5 Aug 2019 00:40:08 +0800 Subject: [PATCH 7/8] Manage PCI MSI/PCI MSI-x interrupts Implement interrupt source driver to manage PCI MSI/MSI-x interrupts. Signed-off-by: Liu Jiang Signed-off-by: Bin Zha --- Cargo.toml | 1 + src/interrupt/kvm/mod.rs | 22 ++++- src/interrupt/kvm/pci_msi_irq.rs | 148 +++++++++++++++++++++++++++++++ src/interrupt/mod.rs | 7 +- 4 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 src/interrupt/kvm/pci_msi_irq.rs diff --git a/Cargo.toml b/Cargo.toml index ba3eda4..08b932d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } kvm_irq = ["kvm-ioctls", "kvm-bindings"] legacy_irq = [] msi_irq = [] +pci_msi_irq = ["msi_irq"] diff --git a/src/interrupt/kvm/mod.rs b/src/interrupt/kvm/mod.rs index 0ef473d..d46ae25 100644 --- a/src/interrupt/kvm/mod.rs +++ b/src/interrupt/kvm/mod.rs @@ -23,6 +23,11 @@ use self::legacy_irq::LegacyIrq; #[cfg(feature = "msi_irq")] mod msi_irq; +#[cfg(feature = "pci_msi_irq")] +mod pci_msi_irq; +#[cfg(feature = "pci_msi_irq")] +use self::pci_msi_irq::PciMsiIrq; + /// Structure to manage interrupt sources for a virtual machine based on the Linux KVM framework. /// /// The KVM framework provides methods to inject interrupts into the target virtual machines, @@ -46,6 +51,7 @@ impl KvmIrqManager { vmfd, groups: HashMap::new(), routes: Arc::new(KvmIrqRouting::new(vmfd2)), + max_msi_irqs: DEFAULT_MAX_MSI_IRQS_PER_DEVICE, }), } } @@ -75,12 +81,18 @@ impl InterruptManager for KvmIrqManager { let mut mgr = self.mgr.lock().unwrap(); mgr.destroy_group(group) } + + fn set_max_msi_irqs(&self, max_msi_irqs: InterruptIndex) { + let mut mgr = self.mgr.lock().unwrap(); + mgr.max_msi_irqs = max_msi_irqs; + } } struct KvmIrqManagerObj { vmfd: Arc, routes: Arc, groups: HashMap>>, + max_msi_irqs: InterruptIndex, } impl KvmIrqManagerObj { @@ -104,9 +116,13 @@ impl KvmIrqManagerObj { self.routes.clone(), )?)), #[cfg(feature = "pci_msi_irq")] - InterruptSourceType::MsiIrq => { - PciMsiIrq::new(base, count, self.vmfd.clone(), self.routes.clone())? - } + InterruptSourceType::PciMsiIrq => Arc::new(Box::new(PciMsiIrq::new( + base, + count, + self.max_msi_irqs, + self.vmfd.clone(), + self.routes.clone(), + )?)), }; self.groups.insert(base, group.clone()); diff --git a/src/interrupt/kvm/pci_msi_irq.rs b/src/interrupt/kvm/pci_msi_irq.rs new file mode 100644 index 0000000..a3298bc --- /dev/null +++ b/src/interrupt/kvm/pci_msi_irq.rs @@ -0,0 +1,148 @@ +// Copyright (C) 2019 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Manage virtual device's PCI MSI/PCI MSIx interrupts based on Linux KVM framework. +//! +//! To optimize for performance by avoiding unnecessary locking and state checking, we assume that +//! the caller will take the responsibility to maintain the interrupt states and only issue valid +//! requests to this driver. If the caller doesn't obey the contract, only the current virtual +//! machine will be affected, it shouldn't break the host or other virtual machines. + +use super::msi_irq::{create_msi_routing_entries, new_msi_routing_entry, MsiConfig}; +use super::*; + +pub(super) struct PciMsiIrq { + base: InterruptIndex, + count: InterruptIndex, + vmfd: Arc, + irq_routing: Arc, + msi_configs: Vec, +} + +impl PciMsiIrq { + #[allow(clippy::new_ret_no_self)] + pub(super) fn new( + base: InterruptIndex, + count: InterruptIndex, + max_msi_irqs: InterruptIndex, + vmfd: Arc, + irq_routing: Arc, + ) -> Result { + if count > max_msi_irqs || base >= MAX_IRQS || base + count > MAX_IRQS { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + + let mut msi_configs = Vec::with_capacity(count as usize); + for _ in 0..count { + msi_configs.push(MsiConfig::new()); + } + + Ok(PciMsiIrq { + base, + count, + vmfd, + irq_routing, + msi_configs, + }) + } +} + +impl InterruptSourceGroup for PciMsiIrq { + fn interrupt_type(&self) -> InterruptSourceType { + InterruptSourceType::PciMsiIrq + } + + fn len(&self) -> u32 { + self.count + } + + fn base(&self) -> u32 { + self.base + } + + fn irqfd(&self, index: InterruptIndex) -> Option<&EventFd> { + if index >= self.count { + None + } else { + let msi_config = &self.msi_configs[index as usize]; + Some(&msi_config.irqfd) + } + } + + fn enable(&self, configs: &[InterruptSourceConfig]) -> Result<()> { + if configs.len() != self.count as usize { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + + // First add IRQ routings for all the MSI interrupts. + let entries = create_msi_routing_entries(self.base, configs)?; + self.irq_routing.add(&entries)?; + + // Then register irqfds to the KVM module. + for i in 0..self.count { + let irqfd = &self.msi_configs[i as usize].irqfd; + self.vmfd.register_irqfd(irqfd, self.base + i)?; + } + + Ok(()) + } + + fn disable(&self) -> Result<()> { + // First unregister all irqfds, so it won't trigger anymore. + for i in 0..self.count { + let irqfd = &self.msi_configs[i as usize].irqfd; + self.vmfd.unregister_irqfd(irqfd, self.base + i)?; + } + + // Then tear down the IRQ routings for all the MSI interrupts. + let mut entries = Vec::with_capacity(self.count as usize); + for i in 0..self.count { + // Safe to unwrap because there's no legal way to break the mutex. + let msicfg = self.msi_configs[i as usize].config.lock().unwrap(); + let entry = new_msi_routing_entry(self.base + i, &*msicfg); + entries.push(entry); + } + self.irq_routing.remove(&entries)?; + + Ok(()) + } + + #[allow(irrefutable_let_patterns)] + fn update(&self, index: InterruptIndex, config: &InterruptSourceConfig) -> Result<()> { + if index >= self.count { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + + if let InterruptSourceConfig::MsiIrq(ref cfg) = config { + // Safe to unwrap because there's no legal way to break the mutex. + let entry = { + let mut msicfg = self.msi_configs[index as usize].config.lock().unwrap(); + msicfg.high_addr = cfg.high_addr; + msicfg.low_addr = cfg.low_addr; + msicfg.data = cfg.data; + new_msi_routing_entry(self.base + index, &*msicfg) + }; + self.irq_routing.modify(&entry) + } else { + Err(std::io::Error::from_raw_os_error(libc::EINVAL)) + } + } + + fn trigger(&self, index: InterruptIndex, flags: u32) -> Result<()> { + // Assume that the caller will maintain the interrupt states and only call this function + // when suitable. + if index >= self.count || flags != 0 { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + let msi_config = &self.msi_configs[index as usize]; + msi_config.irqfd.write(1) + } + + fn ack(&self, index: InterruptIndex, flags: u32) -> Result<()> { + // It's a noop to acknowledge an edge triggered MSI interrupts. + if index >= self.count || flags != 0 { + return Err(std::io::Error::from_raw_os_error(libc::EINVAL)); + } + Ok(()) + } +} diff --git a/src/interrupt/mod.rs b/src/interrupt/mod.rs index c1a7365..1bec390 100644 --- a/src/interrupt/mod.rs +++ b/src/interrupt/mod.rs @@ -67,8 +67,8 @@ pub type InterruptIndex = u32; pub const MAX_IRQS: InterruptIndex = 1024; #[cfg(feature = "msi_irq")] -/// Maximum number of Message Signaled Interrupts per device. -pub const MAX_MSI_IRQS_PER_DEVICE: InterruptIndex = 128; +/// Default maximum number of Message Signaled Interrupts per device. +pub const DEFAULT_MAX_MSI_IRQS_PER_DEVICE: InterruptIndex = 128; /// Type of interrupt source. #[derive(Copy, Clone, Debug)] @@ -142,6 +142,9 @@ pub trait InterruptManager { /// before calling destroy_group(). This assumption helps to simplify InterruptSourceGroup /// implementations. fn destroy_group(&self, group: Arc>) -> Result<()>; + + /// Set maximum supported MSI interrupts per device. + fn set_max_msi_irqs(&self, max: InterruptIndex); } /// Trait to manage a group of interrupt sources for a device. From f3bd7b170e309727b3ba429f66bfa369f5c498c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=88=E6=83=85?= Date: Fri, 16 Aug 2019 10:34:59 +0800 Subject: [PATCH 8/8] Add unit tests for interrupt manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 守情 --- coverage_config.json | 2 +- src/interrupt/kvm/legacy_irq.rs | 50 ++++++++++ src/interrupt/kvm/mod.rs | 154 +++++++++++++++++++++++++++++++ src/interrupt/kvm/msi_irq.rs | 67 ++++++++++++++ src/interrupt/kvm/pci_msi_irq.rs | 90 ++++++++++++++++++ 5 files changed, 362 insertions(+), 1 deletion(-) diff --git a/coverage_config.json b/coverage_config.json index 03bbeba..6a1695b 100644 --- a/coverage_config.json +++ b/coverage_config.json @@ -1,5 +1,5 @@ { - "coverage_score": 75.8, + "coverage_score": 74.3, "exclude_path": "", "crate_features": "" } diff --git a/src/interrupt/kvm/legacy_irq.rs b/src/interrupt/kvm/legacy_irq.rs index 11f85f7..5704a74 100644 --- a/src/interrupt/kvm/legacy_irq.rs +++ b/src/interrupt/kvm/legacy_irq.rs @@ -174,3 +174,53 @@ impl InterruptSourceGroup for LegacyIrq { Ok(()) } } + +#[cfg(test)] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod test { + use super::*; + use kvm_ioctls::{Kvm, VmFd}; + + fn create_vm_fd() -> VmFd { + let kvm = Kvm::new().unwrap(); + kvm.create_vm().unwrap() + } + + #[test] + #[allow(unreachable_patterns)] + fn test_legacy_interrupt_group() { + let vmfd = Arc::new(create_vm_fd()); + let rounting = Arc::new(KvmIrqRouting::new(vmfd.clone())); + let base = 0; + let count = 1; + let group = LegacyIrq::new(base, count, vmfd.clone(), rounting.clone()).unwrap(); + + let mut legacy_fds = Vec::with_capacity(1); + legacy_fds.push(InterruptSourceConfig::LegacyIrq(LegacyIrqSourceConfig {})); + + match group.interrupt_type() { + InterruptSourceType::LegacyIrq => {} + _ => { + panic!(); + } + } + assert_eq!(group.len(), 1); + assert_eq!(group.base(), base); + assert!(group.enable(&legacy_fds).is_ok()); + assert!(group.irqfd(0).unwrap().write(1).is_ok()); + assert!(group.trigger(0, 0x168).is_ok()); + assert!(group.ack(0, 0x168).is_ok()); + assert!(group.trigger(1, 0x168).is_err()); + assert!(group.ack(1, 0x168).is_err()); + assert!(group + .update( + 0, + &InterruptSourceConfig::LegacyIrq(LegacyIrqSourceConfig {}) + ) + .is_ok()); + assert!(group.disable().is_ok()); + + assert!(LegacyIrq::new(base, 2, vmfd.clone(), rounting.clone()).is_err()); + assert!(LegacyIrq::new(110, 1, vmfd.clone(), rounting.clone()).is_err()); + } +} diff --git a/src/interrupt/kvm/mod.rs b/src/interrupt/kvm/mod.rs index d46ae25..61f8158 100644 --- a/src/interrupt/kvm/mod.rs +++ b/src/interrupt/kvm/mod.rs @@ -235,3 +235,157 @@ impl KvmIrqRouting { self.set_routing(&routes) } } + +#[cfg(any(target = "x86", target = "x86_64"))] +#[cfg(test)] +mod test { + use super::*; + use kvm_ioctls::{Kvm, VmFd}; + + //const VFIO_PCI_MSI_IRQ_INDEX: u32 = 1; + + fn create_vm_fd() -> VmFd { + let kvm = Kvm::new().unwrap(); + kvm.create_vm().unwrap() + } + + fn create_irq_group( + manager: Arc, + _vmfd: Arc, + ) -> Arc> { + let base = 0; + let count = 1; + + manager + .create_group(InterruptSourceType::LegacyIrq, base, count) + .unwrap() + } + + fn create_msi_group( + manager: Arc, + _vmfd: Arc, + ) -> Arc> { + let base = 168; + let count = 32; + + manager + .create_group(InterruptSourceType::MsiIrq, base, count) + .unwrap() + } + + const MASTER_PIC: usize = 7; + const SLAVE_PIC: usize = 8; + const IOAPIC: usize = 23; + + #[test] + fn test_create_kvmirqmanager() { + let vmfd = Arc::new(create_vm_fd()); + let manager = KvmIrqManager::new(vmfd.clone()); + assert!(vmfd.create_irq_chip().is_ok()); + assert!(manager.initialize().is_ok()); + } + + #[test] + fn test_kvmirqmanager_opt() { + let vmfd = Arc::new(create_vm_fd()); + assert!(vmfd.create_irq_chip().is_ok()); + let manager = Arc::new(KvmIrqManager::new(vmfd.clone())); + assert!(manager.initialize().is_ok()); + //irq + let group = create_irq_group(manager.clone(), vmfd.clone()); + let _ = group.clone(); + assert!(manager.destroy_group(group).is_ok()); + //msi + let group = create_msi_group(manager.clone(), vmfd.clone()); + let _ = group.clone(); + assert!(manager.destroy_group(group).is_ok()); + } + + #[test] + fn test_irqrouting_initialize_legacy() { + let vmfd = Arc::new(create_vm_fd()); + let routing = KvmIrqRouting::new(vmfd.clone()); + assert!(routing.initialize().is_err()); + assert!(vmfd.create_irq_chip().is_ok()); + assert!(routing.initialize().is_ok()); + let routes = &routing.routes.lock().unwrap(); + assert_eq!(routes.len(), MASTER_PIC + SLAVE_PIC + IOAPIC); + } + + #[test] + fn test_routing_opt() { + // pub(super) fn modify(&self, entry: &kvm_irq_routing_entry) -> Result<()> { + let vmfd = Arc::new(create_vm_fd()); + let routing = KvmIrqRouting::new(vmfd.clone()); + assert!(routing.initialize().is_err()); + assert!(vmfd.create_irq_chip().is_ok()); + assert!(routing.initialize().is_ok()); + + let mut entry = kvm_irq_routing_entry { + gsi: 8, + type_: KVM_IRQ_ROUTING_IRQCHIP, + ..Default::default() + }; + + // Safe because we are initializing all fields of the `irqchip` struct. + unsafe { + entry.u.irqchip.irqchip = 0; + entry.u.irqchip.pin = 3; + } + + let entrys = vec![entry.clone()]; + + assert!(routing.modify(&entry).is_err()); + assert!(routing.add(&entrys).is_ok()); + unsafe { + entry.u.irqchip.pin = 4; + } + assert!(routing.modify(&entry).is_ok()); + assert!(routing.remove(&entrys).is_ok()); + assert!(routing.modify(&entry).is_err()); + } + + #[test] + fn test_routing_commit() { + let vmfd = Arc::new(create_vm_fd()); + let routing = KvmIrqRouting::new(vmfd.clone()); + + assert!(routing.initialize().is_err()); + assert!(vmfd.create_irq_chip().is_ok()); + assert!(routing.initialize().is_ok()); + + let mut entry = kvm_irq_routing_entry { + gsi: 8, + type_: KVM_IRQ_ROUTING_IRQCHIP, + ..Default::default() + }; + unsafe { + entry.u.irqchip.irqchip = 0; + entry.u.irqchip.pin = 3; + } + + routing + .routes + .lock() + .unwrap() + .insert(hash_key(&entry), entry); + let routes = routing.routes.lock().unwrap(); + assert!(routing.commit(&routes).is_ok()); + } + + #[test] + fn test_has_key() { + let gsi = 4; + let mut entry = kvm_irq_routing_entry { + gsi, + type_: KVM_IRQ_ROUTING_IRQCHIP, + ..Default::default() + }; + // Safe because we are initializing all fields of the `irqchip` struct. + unsafe { + entry.u.irqchip.irqchip = KVM_IRQCHIP_PIC_MASTER; + entry.u.irqchip.pin = gsi; + } + assert_eq!(hash_key(&entry), 0x0001_0000_0004); + } +} diff --git a/src/interrupt/kvm/msi_irq.rs b/src/interrupt/kvm/msi_irq.rs index 2455c6e..b56df0f 100644 --- a/src/interrupt/kvm/msi_irq.rs +++ b/src/interrupt/kvm/msi_irq.rs @@ -57,3 +57,70 @@ pub(super) fn create_msi_routing_entries( } Ok(entries) } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_create_msiconfig() { + let config = MsiConfig::new(); + config.irqfd.write(1).unwrap(); + } + + #[test] + fn test_new_msi_routing_single() { + let test_gsi = 4; + let msi_source_config = MsiIrqSourceConfig { + high_addr: 0x1234, + low_addr: 0x5678, + data: 0x9876, + }; + let entry = new_msi_routing_entry(test_gsi, &msi_source_config); + assert_eq!(entry.gsi, test_gsi); + assert_eq!(entry.type_, KVM_IRQ_ROUTING_MSI); + unsafe { + assert_eq!(entry.u.msi.address_hi, msi_source_config.high_addr); + assert_eq!(entry.u.msi.address_lo, msi_source_config.low_addr); + assert_eq!(entry.u.msi.data, msi_source_config.data); + } + } + + #[cfg(all( + feature = "legacy_irq", + any(target_arch = "x86", target_arch = "x86_64") + ))] + #[test] + fn test_new_msi_routing_multi() { + let mut msi_fds = Vec::with_capacity(16); + for _ in 0..16 { + msi_fds.push(InterruptSourceConfig::MsiIrq(MsiIrqSourceConfig { + high_addr: 0x1234, + low_addr: 0x5678, + data: 0x9876, + })); + } + let mut legacy_fds = Vec::with_capacity(16); + for _ in 0..16 { + legacy_fds.push(InterruptSourceConfig::LegacyIrq(LegacyIrqSourceConfig {})); + } + + let base = 0; + let entrys = create_msi_routing_entries(0, &msi_fds).unwrap(); + + for (i, entry) in entrys.iter().enumerate() { + assert_eq!(entry.gsi, (base + i) as u32); + assert_eq!(entry.type_, KVM_IRQ_ROUTING_MSI); + if let InterruptSourceConfig::MsiIrq(config) = &msi_fds[i] { + unsafe { + assert_eq!(entry.u.msi.address_hi, config.high_addr); + assert_eq!(entry.u.msi.address_lo, config.low_addr); + assert_eq!(entry.u.msi.data, config.data); + } + } + } + + assert!(create_msi_routing_entries(0, &legacy_fds).is_err()); + assert!(create_msi_routing_entries(!0, &msi_fds).is_err()); + } +} diff --git a/src/interrupt/kvm/pci_msi_irq.rs b/src/interrupt/kvm/pci_msi_irq.rs index a3298bc..73c5b1d 100644 --- a/src/interrupt/kvm/pci_msi_irq.rs +++ b/src/interrupt/kvm/pci_msi_irq.rs @@ -146,3 +146,93 @@ impl InterruptSourceGroup for PciMsiIrq { Ok(()) } } + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +#[cfg(test)] +mod test { + use super::*; + use kvm_ioctls::{Kvm, VmFd}; + + fn create_vm_fd() -> VmFd { + let kvm = Kvm::new().unwrap(); + kvm.create_vm().unwrap() + } + + #[test] + #[allow(unreachable_patterns)] + fn test_msi_interrupt_group() { + let vmfd = Arc::new(create_vm_fd()); + assert!(vmfd.create_irq_chip().is_ok()); + + let rounting = Arc::new(KvmIrqRouting::new(vmfd.clone())); + assert!(rounting.initialize().is_ok()); + + let base = 168; + let count = 32; + let group = PciMsiIrq::new( + base, + count, + DEFAULT_MAX_MSI_IRQS_PER_DEVICE, + vmfd.clone(), + rounting.clone(), + ) + .unwrap(); + let mut msi_fds = Vec::with_capacity(count as usize); + + match group.interrupt_type() { + InterruptSourceType::PciMsiIrq => {} + _ => { + panic!(); + } + } + + for _ in 0..count { + let msi_source_config = MsiIrqSourceConfig { + high_addr: 0x1234, + low_addr: 0x5678, + data: 0x9876, + }; + msi_fds.push(InterruptSourceConfig::MsiIrq(msi_source_config)); + } + + assert!(group.enable(&msi_fds).is_ok()); + assert_eq!(group.len(), count); + assert_eq!(group.base(), base); + + for i in 0..count { + let msi_source_config = MsiIrqSourceConfig { + high_addr: i + 0x1234, + low_addr: i + 0x5678, + data: i + 0x9876, + }; + assert!(group.irqfd(i).unwrap().write(1).is_ok()); + assert!(group.trigger(i, 0x168).is_err()); + assert!(group.trigger(i, 0).is_ok()); + assert!(group.ack(i, 0x168).is_err()); + assert!(group.ack(i, 0).is_ok()); + assert!(group + .update(0, &InterruptSourceConfig::MsiIrq(msi_source_config)) + .is_ok()); + } + assert!(group.trigger(33, 0x168).is_err()); + assert!(group.ack(33, 0x168).is_err()); + assert!(group.disable().is_ok()); + + assert!(PciMsiIrq::new( + base, + DEFAULT_MAX_MSI_IRQS_PER_DEVICE + 1, + DEFAULT_MAX_MSI_IRQS_PER_DEVICE, + vmfd.clone(), + rounting.clone() + ) + .is_err()); + assert!(PciMsiIrq::new( + 1100, + 1, + DEFAULT_MAX_MSI_IRQS_PER_DEVICE, + vmfd.clone(), + rounting.clone() + ) + .is_err()); + } +}