diff --git a/Cargo.toml b/Cargo.toml index 16e2bc5..42ef5bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,3 @@ repository = "https://github.com/rust-vmm/vm-device" license = "Apache-2.0" [dependencies] -vm-memory = { git = "https://github.com/rust-vmm/vm-memory" } diff --git a/coverage_config.json b/coverage_config.json index 03bbeba..a9e9a75 100644 --- a/coverage_config.json +++ b/coverage_config.json @@ -1,5 +1,5 @@ { - "coverage_score": 75.8, + "coverage_score": 79.9, "exclude_path": "", "crate_features": "" } diff --git a/rust-vmm-ci b/rust-vmm-ci index bb1cd14..c309d06 160000 --- a/rust-vmm-ci +++ b/rust-vmm-ci @@ -1 +1 @@ -Subproject commit bb1cd14d2c164b4f699b08c885c06a02fbe3f7b0 +Subproject commit c309d0627bde6b07db91201dd8b47007841c100a diff --git a/src/device_manager.rs b/src/device_manager.rs new file mode 100644 index 0000000..bbbb857 --- /dev/null +++ b/src/device_manager.rs @@ -0,0 +1,358 @@ +// Copyright © 2019 Intel Corporation. All Rights Reserved. +// SPDX-License-Identifier: (Apache-2.0 OR BSD-3-Clause) + +//! System level device management. +//! +//! [IoManager](struct.IoManager.html) is respondsible for managing +//! all devices of virtual machine, registering IO resources callback, +//! unregistering devices and helping VM IO exit handling. +//! +//!VMM would be responsible for getting device resource request, ask +//! vm_allocator to allocate the resources, ask vm_device to register the +//! devices IO ranges, and finally set resources to virtual device. + +use crate::resources::Resource; +use crate::{DeviceIo, IoAddress, IoSize}; + +use std::cmp::{Ord, Ordering, PartialEq, PartialOrd}; +use std::collections::btree_map::BTreeMap; +use std::result; +use std::sync::Arc; + +/// Error type for `IoManager` usage. +#[derive(Debug)] +pub enum Error { + /// The inserting device overlaps with a current device. + DeviceOverlap, + /// The device doesn't exist. + NoDevice, +} + +/// Simplify the `Result` type. +pub type Result = result::Result; + +// Structure describing an IO range. +#[derive(Debug, Copy, Clone)] +struct IoRange { + base: IoAddress, + size: IoSize, +} + +impl IoRange { + fn new_pio_range(base: u16, size: u16) -> Self { + IoRange { + base: IoAddress::Pio(base), + size: IoSize::Pio(size), + } + } + fn new_mmio_range(base: u64, size: u64) -> Self { + IoRange { + base: IoAddress::Mmio(base), + size: IoSize::Mmio(size), + } + } +} + +impl Eq for IoRange {} + +impl PartialEq for IoRange { + fn eq(&self, other: &IoRange) -> bool { + self.base == other.base + } +} + +impl Ord for IoRange { + fn cmp(&self, other: &IoRange) -> Ordering { + self.base.cmp(&other.base) + } +} + +impl PartialOrd for IoRange { + fn partial_cmp(&self, other: &IoRange) -> Option { + self.base.partial_cmp(&other.base) + } +} + +/// System IO manager serving for all devices management and VM exit handling. +#[derive(Default)] +pub struct IoManager { + /// Range mapping for VM exit pio operations. + pio_bus: BTreeMap>, + /// Range mapping for VM exit mmio operations. + mmio_bus: BTreeMap>, +} + +impl IoManager { + /// Create an default IoManager with empty IO member. + pub fn new() -> Self { + IoManager::default() + } + /// Register a new device IO with its allocated resources. + /// VMM is responsible for providing the allocated resources to virtual device. + /// + /// # Arguments + /// + /// * `device`: device instance object to be registered + /// * `resources`: resources that this device owns, might include + /// port I/O and memory-mapped I/O ranges, irq number, etc. + pub fn register_device_io( + &mut self, + device: Arc, + resources: &[Resource], + ) -> Result<()> { + // Register and mark device resources + // The resources addresses being registered are sucessfully allocated before. + for (idx, res) in resources.iter().enumerate() { + match *res { + Resource::PioAddressRange { base, size } => { + if self + .pio_bus + .insert(IoRange::new_pio_range(base, size), device.clone()) + .is_some() + { + // Unregister registered resources. + self.unregister_device_io(&resources[0..idx]) + .expect("failed to unregister devices"); + + return Err(Error::DeviceOverlap); + } + } + Resource::MmioAddressRange { base, size } => { + if self + .mmio_bus + .insert(IoRange::new_mmio_range(base, size), device.clone()) + .is_some() + { + // Unregister registered resources. + self.unregister_device_io(&resources[0..idx]) + .expect("failed to unregister devices"); + + return Err(Error::DeviceOverlap); + } + } + _ => continue, + } + } + Ok(()) + } + + /// Unregister a device from `IoManager`, e.g. users specified removing. + /// VMM pre-fetches the resources e.g. dev.get_assigned_resources() + /// VMM is responsible for freeing the resources. + /// + /// # Arguments + /// + /// * `resources`: resources that this device owns, might include + /// port I/O and memory-mapped I/O ranges, irq number, etc. + pub fn unregister_device_io(&mut self, resources: &[Resource]) -> Result<()> { + for res in resources.iter() { + match *res { + Resource::PioAddressRange { base, size } => { + self.pio_bus.remove(&IoRange::new_pio_range(base, size)); + } + Resource::MmioAddressRange { base, size } => { + self.mmio_bus.remove(&IoRange::new_mmio_range(base, size)); + } + _ => continue, + } + } + Ok(()) + } + + fn get_entry(&self, addr: IoAddress) -> Option<(&IoRange, &Arc)> { + match addr { + IoAddress::Pio(a) => self + .pio_bus + .range(..=&IoRange::new_pio_range(a, 0)) + .nth_back(0), + IoAddress::Mmio(a) => self + .mmio_bus + .range(..=&IoRange::new_mmio_range(a, 0)) + .nth_back(0), + } + } + + // Return the Device mapped `addr` and the base address. + fn get_device(&self, addr: IoAddress) -> Option<(&Arc, IoAddress)> { + if let Some((range, dev)) = self.get_entry(addr) { + if (addr.raw_value() - range.base.raw_value()) < range.size.raw_value() { + return Some((dev, range.base)); + } + } + None + } + + /// A helper function handling PIO read command during VM exit. + /// The virtual device itself provides mutable ability and thead-safe protection. + /// + /// Return error if failed to get the device. + pub fn pio_read(&self, addr: u16, data: &mut [u8]) -> Result<()> { + if let Some((device, base)) = self.get_device(IoAddress::Pio(addr)) { + device.read(base, IoAddress::Pio(addr - (base.raw_value() as u16)), data); + Ok(()) + } else { + Err(Error::NoDevice) + } + } + + /// A helper function handling PIO write command during VM exit. + /// The virtual device itself provides mutable ability and thead-safe protection. + /// + /// Return error if failed to get the device. + pub fn pio_write(&self, addr: u16, data: &[u8]) -> Result<()> { + if let Some((device, base)) = self.get_device(IoAddress::Pio(addr)) { + device.write(base, IoAddress::Pio(addr - (base.raw_value() as u16)), data); + Ok(()) + } else { + Err(Error::NoDevice) + } + } + + /// A helper function handling MMIO read command during VM exit. + /// The virtual device itself provides mutable ability and thead-safe protection. + /// + /// Return error if failed to get the device. + pub fn mmio_read(&self, addr: u64, data: &mut [u8]) -> Result<()> { + if let Some((device, base)) = self.get_device(IoAddress::Mmio(addr)) { + device.read(base, IoAddress::Mmio(addr - base.raw_value()), data); + Ok(()) + } else { + Err(Error::NoDevice) + } + } + + /// A helper function handling MMIO write command during VM exit. + /// The virtual device itself provides mutable ability and thead-safe protection. + /// + /// Return error if failed to get the device. + pub fn mmio_write(&self, addr: u64, data: &[u8]) -> Result<()> { + if let Some((device, base)) = self.get_device(IoAddress::Mmio(addr)) { + device.write(base, IoAddress::Mmio(addr - base.raw_value()), data); + Ok(()) + } else { + Err(Error::NoDevice) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + const PIO_ADDRESS_SIZE: u16 = 4; + const PIO_ADDRESS_BASE: u16 = 0x40; + const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321; + const MMIO_ADDRESS_BASE: u64 = 0x1234_5678; + const LEGACY_IRQ: u32 = 4; + const CONFIG_DATA: u32 = 0x1234; + + struct DummyDevice { + config: Mutex, + } + + impl DummyDevice { + fn new(config: u32) -> Self { + DummyDevice { + config: Mutex::new(config), + } + } + } + + impl DeviceIo for DummyDevice { + fn read(&self, _base: IoAddress, _offset: IoAddress, data: &mut [u8]) { + if data.len() > 4 { + return; + } + for (idx, iter) in data.iter_mut().enumerate() { + let config = self.config.lock().expect("failed to acquire lock"); + *iter = (*config >> (idx * 8) & 0xff) as u8; + } + } + + fn write(&self, _base: IoAddress, _offset: IoAddress, data: &[u8]) { + let mut config = self.config.lock().expect("failed to acquire lock"); + *config = u32::from(data[0]) & 0xff; + } + } + + #[test] + fn test_register_unregister_device_io() { + let mut io_mgr = IoManager::new(); + let dummy = DummyDevice::new(0); + let dum = Arc::new(dummy); + + let mut resource: Vec = Vec::new(); + let mmio = Resource::MmioAddressRange { + base: MMIO_ADDRESS_BASE, + size: MMIO_ADDRESS_SIZE, + }; + let irq = Resource::LegacyIrq(LEGACY_IRQ); + + resource.push(mmio); + resource.push(irq); + + assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok()); + assert!(io_mgr.unregister_device_io(&resource).is_ok()) + } + + #[test] + fn test_mmio_read_write() { + let mut io_mgr: IoManager = Default::default(); + let dum = Arc::new(DummyDevice::new(CONFIG_DATA)); + let mut resource: Vec = Vec::new(); + + let mmio = Resource::MmioAddressRange { + base: MMIO_ADDRESS_BASE, + size: MMIO_ADDRESS_SIZE, + }; + resource.push(mmio); + assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok()); + + let mut data = [0; 4]; + assert!(io_mgr.mmio_read(MMIO_ADDRESS_BASE, &mut data).is_ok()); + assert_eq!(data, [0x34, 0x12, 0, 0]); + + assert!(io_mgr + .mmio_read(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE, &mut data) + .is_err()); + + data = [0; 4]; + assert!(io_mgr.mmio_write(MMIO_ADDRESS_BASE, &data).is_ok()); + assert_eq!(*dum.config.lock().unwrap(), 0); + + assert!(io_mgr + .mmio_write(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE, &data) + .is_err()); + } + + #[test] + fn test_pio_read_write() { + let mut io_mgr: IoManager = Default::default(); + let dum = Arc::new(DummyDevice::new(CONFIG_DATA)); + let mut resource: Vec = Vec::new(); + + let pio = Resource::PioAddressRange { + base: PIO_ADDRESS_BASE, + size: PIO_ADDRESS_SIZE, + }; + resource.push(pio); + assert!(io_mgr.register_device_io(dum.clone(), &resource).is_ok()); + + let mut data = [0; 4]; + assert!(io_mgr.pio_read(PIO_ADDRESS_BASE, &mut data).is_ok()); + assert_eq!(data, [0x34, 0x12, 0, 0]); + + assert!(io_mgr + .pio_read(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE, &mut data) + .is_err()); + + data = [0; 4]; + assert!(io_mgr.pio_write(PIO_ADDRESS_BASE, &data).is_ok()); + assert_eq!(*dum.config.lock().unwrap(), 0); + + assert!(io_mgr + .pio_write(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE, &data) + .is_err()); + } +} diff --git a/src/interrupt/mod.rs b/src/interrupt/mod.rs new file mode 100644 index 0000000..d696224 --- /dev/null +++ b/src/interrupt/mod.rs @@ -0,0 +1,203 @@ +// Copyright (C) 2019 Alibaba Cloud. All rights reserved. +// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// Copyright © 2019 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause + +//! 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 +//! * The guest kernel loads drivers for virtual devices +//! * The 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::fs::File; +use std::sync::Arc; + +/// 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; + +/// Data type to store an interrupt source type. +#[derive(Copy, Clone, Debug)] +pub enum InterruptSourceType { + /// Legacy Pin-based Interrupt. + /// On x86 platforms, legacy interrupts are routed through 8259 PICs and/or IOAPICs. + LegacyIrq, + /// 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, +} + +/// 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 + /// * interrupt_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( + &mut self, + interrupt_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(&mut self, group: Arc>) -> Result<()>; +} + +/// Configuration data for legacy interrupts. +/// +/// On x86 platforms, legacy interrupts means those interrupts routed through PICs or IOAPICs. +#[derive(Copy, Clone, Debug)] +pub struct LegacyIrqSourceConfig {} + +/// Configuration data for MSI/MSI-X interrupts. +/// +/// On x86 platforms, these interrupts are vectors delivered directly to the LAPIC. +#[derive(Copy, Clone, Debug, Default)] +pub struct MsiIrqSourceConfig { + /// High address to delivery message signaled interrupt. + pub high_addr: u32, + /// Low address to delivery message signaled interrupt. + pub low_addr: u32, + /// Data to write to delivery message signaled interrupt. + pub data: u32, +} + +/// Configuration data for an interrupt source. +#[derive(Copy, Clone, Debug)] +pub enum InterruptSourceConfig { + /// Configuration data for Legacy interrupts. + LegacyIrq(LegacyIrqSourceConfig), + /// Configuration data for PciMsi, PciMsix and generic MSI interrupts. + MsiIrq(MsiIrqSourceConfig), +} + +pub trait InterruptSourceGroup: Send + Sync { + /// Enable the interrupt sources in the group to generate interrupts. + fn enable(&mut self) -> Result<()> { + // Not all interrupt sources can be enabled. + // To accommodate this, we can have a no-op here. + Ok(()) + } + + /// Disable the interrupt sources in the group to generate interrupts. + fn disable(&mut self) -> Result<()> { + // Not all interrupt sources can be disabled. + // To accommodate this, we can have a no-op here. + Ok(()) + } + + /// Inject an interrupt from this interrupt source into the guest. + /// + /// # Arguments + /// * index: sub-index into the group. + fn trigger(&self, index: InterruptIndex) -> Result<()>; + + /// Returns an interrupt notifier from this interrupt. + /// + /// An interrupt notifier allows for external components and processes + /// to inject interrupts into a guest, by writing to the file returned + /// by this method. + #[allow(unused_variables)] + fn notifier(&self, index: InterruptIndex) -> Option { + // One use case of the notifier is to implement vhost user backends. + // For all other implementations we can just return None here. + None + } + + /// Update the interrupt source group configuration. + /// + /// # Arguments + /// * index: sub-index into the group. + /// * config: configuration data for the interrupt source. + fn update(&self, _index: InterruptIndex, _config: InterruptSourceConfig) -> Result<()> { + // Interrupt source group update is a typical MSI + // related operation. Most legacy interrupts don't + // support that. + Ok(()) + } + + /// Mask an interrupt from this interrupt source group. + /// + /// # Arguments + /// * index: sub-index into the group. + fn mask(&self, _index: InterruptIndex) -> Result<()> { + // Not all interrupt sources can be disabled. + // To accommodate this, we can have a no-op here. + Ok(()) + } + + /// Unmask an interrupt from this interrupt source group. + /// + /// # Arguments + /// * index: sub-index into the group. + fn unmask(&self, _index: InterruptIndex) -> Result<()> { + // Not all interrupt sources can be disabled. + // To accommodate this, we can have a no-op here. + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9ef255d..235e4da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,12 +3,32 @@ //! rust-vmm device model. -extern crate vm_memory; - -use vm_memory::GuestAddress; +use std::cmp::{Ord, Ordering, PartialOrd}; +pub mod device_manager; +pub mod interrupt; pub mod resources; +// IO Size. +#[derive(Debug, Copy, Clone)] +enum IoSize { + // Port I/O size. + Pio(u16), + + // Memory mapped I/O size. + Mmio(u64), +} + +impl IoSize { + // Get the raw value as u64 to make operation simple. + fn raw_value(&self) -> u64 { + match *self { + IoSize::Pio(p) => u64::from(p), + IoSize::Mmio(m) => m, + } + } +} + /// IO Addresses. #[derive(Debug, Copy, Clone)] pub enum IoAddress { @@ -16,7 +36,37 @@ pub enum IoAddress { Pio(u16), /// Memory mapped I/O address. - Mmio(GuestAddress), + Mmio(u64), +} + +impl IoAddress { + // Get the raw value of IO Address to make operation simple. + fn raw_value(&self) -> u64 { + match *self { + IoAddress::Pio(p) => u64::from(p), + IoAddress::Mmio(m) => m, + } + } +} + +impl Eq for IoAddress {} + +impl PartialEq for IoAddress { + fn eq(&self, other: &IoAddress) -> bool { + self.raw_value() == other.raw_value() + } +} + +impl Ord for IoAddress { + fn cmp(&self, other: &IoAddress) -> Ordering { + self.raw_value().cmp(&other.raw_value()) + } +} + +impl PartialOrd for IoAddress { + fn partial_cmp(&self, other: &IoAddress) -> Option { + self.raw_value().partial_cmp(&other.raw_value()) + } } /// Device IO trait. @@ -24,10 +74,13 @@ pub enum IoAddress { /// register itself against the different IO type ranges it handles. /// The VMM will then dispatch IO (PIO or MMIO) VM exits by calling into the /// registered devices read or write method from this trait. +/// The DeviceIo trait adopts the interior mutability pattern +/// so we can get a real multiple threads handling. pub trait DeviceIo: Send { - /// Read from the guest physical address `addr` to `data`. - fn read(&mut self, addr: IoAddress, data: &mut [u8]); + /// Read from the guest physical address `base`, starting at `offset`. + /// Result is placed in `data`. + fn read(&self, base: IoAddress, offset: IoAddress, data: &mut [u8]); - /// Write `data` to the guest physical address `addr`. - fn write(&mut self, addr: IoAddress, data: &[u8]); + /// Write `data` to the guest physical address `base`, starting from `offset`. + fn write(&self, base: IoAddress, offset: IoAddress, data: &[u8]); } diff --git a/src/resources.rs b/src/resources.rs index 5ae37dd..8a74ea8 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -401,7 +401,7 @@ mod tests { assert_eq!(align, 0x1000); assert_eq!(size, 0x2000); } else { - panic!("Pio resource constraint is invalid."); + panic!("Mmio resource constraint is invalid."); } if let ResourceConstraint::MmioAddress { range, align, size } = @@ -411,7 +411,24 @@ mod tests { assert_eq!(align, 0x2000); assert_eq!(size, 0x2000); } else { - panic!("Pio resource constraint is invalid."); + panic!("Mmio resource constraint is invalid."); + } + + if let ResourceConstraint::LegacyIrq { irq } = + ResourceConstraint::new_legacy_irq(Some(0x123)) + { + assert_eq!(irq, Some(0x123)); + } else { + panic!("IRQ resource constraint is invalid."); + } + + if let ResourceConstraint::KvmMemSlot { slot, size } = + ResourceConstraint::new_kvm_mem_slot(0x1000, Some(0x2000)) + { + assert_eq!(slot, Some(0x2000)); + assert_eq!(size, 0x1000); + } else { + panic!("KVM slot resource constraint is invalid."); } } }