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/lib.rs b/src/lib.rs index b60ba5d..dc09ddb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,31 @@ //! rust-vmm device model. +use std::cmp::{Ord, Ordering, PartialOrd}; + +pub mod device_manager; 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 { @@ -15,16 +38,48 @@ pub enum IoAddress { 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. /// A device supporting memory based I/O should implement this trait, then /// 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 `base`, starting at `offset`. /// Result is placed in `data`. - fn read(&mut self, base: IoAddress, offset: IoAddress, data: &mut [u8]); + fn read(&self, base: IoAddress, offset: IoAddress, data: &mut [u8]); /// Write `data` to the guest physical address `base`, starting from `offset`. - fn write(&mut self, base: IoAddress, offset: IoAddress, data: &[u8]); + 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."); } } }