Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add interrupt subsystem to vm-device #8

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "rust-vmm-ci"]
path = rust-vmm-ci
url = https://github.com/rust-vmm/rust-vmm-ci.git
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,16 @@ version = "0.1.0"
authors = ["Samuel Ortiz <[email protected]>"]
repository = "https://github.com/rust-vmm/vm-device"
license = "Apache-2.0"
edition = "2018"

[dependencies]
libc = ">=0.2.39"
kvm-bindings = { version = "0.1", optional = true }
kvm-ioctls = { git = "https://github.com/rust-vmm/kvm-ioctls.git", branch = "master", optional = true }
vmm-sys-util = { git = "https://github.com/rust-vmm/vmm-sys-util.git", branch = "master" }

[features]
kvm_irq = ["kvm-ioctls", "kvm-bindings"]
legacy_irq = []
generic_msi = []
msi_irq = ["generic_msi"]
5 changes: 5 additions & 0 deletions coverage_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"coverage_score": 60.3,
"exclude_path": "",
"crate_features": "kvm_irq,msi_irq,legacy_irq"
}
1 change: 1 addition & 0 deletions rust-vmm-ci
Submodule rust-vmm-ci added at bb1cd1
121 changes: 121 additions & 0 deletions src/interrupt/kvm_irq/generic_msi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 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<MsiIrqSourceConfig>,
}

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
}

pub(super) fn create_msi_routing_entries(
base: InterruptIndex,
configs: &[InterruptSourceConfig],
) -> Result<Vec<kvm_irq_routing_entry>> {
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)
}

#[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);
}
}

#[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());
}
}
165 changes: 165 additions & 0 deletions src/interrupt/kvm_irq/legacy_irq.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// 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::*;
use std::os::unix::io::AsRawFd;
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<VmFd>,
irqfd: EventFd,
status: AtomicUsize,
}

impl LegacyIrq {
#[allow(clippy::new_ret_no_self)]
pub(super) fn new(
base: InterruptIndex,
count: InterruptIndex,
vmfd: Arc<VmFd>,
_routes: Arc<KvmIrqRouting>,
) -> Result<Arc<dyn InterruptSourceGroup>> {
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(Arc::new(LegacyIrq {
base,
vmfd,
irqfd: EventFd::new(0)?,
status: AtomicUsize::new(0),
}))
}
}

impl InterruptSourceGroup for LegacyIrq {
fn get_type(&self) -> InterruptSourceType {
InterruptSourceType::LegacyIrq
}

fn len(&self) -> u32 {
1
}

fn get_base(&self) -> u32 {
self.base
}

fn get_irqfd(&self, index: InterruptIndex) -> Option<&EventFd> {
if index != 0 {
None
} else {
Some(&self.irqfd)
}
}

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.as_raw_fd(), self.base)
}

fn disable(&self) -> Result<()> {
self.vmfd
.unregister_irqfd(self.irqfd.as_raw_fd(), self.base)
}

fn modify(&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(())
}

fn get_flags(&self, index: InterruptIndex) -> u32 {
if index == 0 {
self.status.load(Ordering::SeqCst) as u32
} else {
0
}
}
}

#[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]
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.get_type() {
InterruptSourceType::LegacyIrq => {}
_ => {
panic!();
}
}
assert_eq!(group.len(), 1);
assert_eq!(group.get_base(), base);
assert!(group.enable(&legacy_fds).is_ok());
assert!(group.get_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
.modify(
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());
}
}
Loading