diff --git a/Cargo.toml b/Cargo.toml index a233b83..e8a9b05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "vm-device" version = "0.1.0" -authors = ["Samuel Ortiz ", "Liu Jiang ", "rust-vmm AWS maintainers "] +authors = ["Samuel Ortiz ", "Liu Jiang ", "rust-vmm AWS maintainers ", "Simon Lucido "] description = "management for virtual devices and resources" keywords = ["bus", "manager", "virtualization"] edition = "2018" @@ -9,3 +9,4 @@ repository = "https://github.com/rust-vmm/vm-device" license = "Apache-2.0 OR BSD-3-Clause" [dependencies] +vm-memory = { version = "0.13.1", features = ["backend-mmap"] } diff --git a/README.md b/README.md index 69c9889..ba82c49 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,11 @@ address range, IRQ number, etc) The virtual device model is built around four traits, `DevicePio` and `MutDevicePio` for [Programmed I/O](https://en.wikipedia.org/wiki/Programmed_input%E2%80%93output) -(PIO), and `DeviceMmio` and `MutDeviceMmio` for +(PIO), and `VirtioMmioDevice` and `MutVirtioMmioDevice` for [Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O) (MMIO). The traits define the same methods for handling read and write -operations. The difference is that `DevicePio` and `DeviceMmio` only require -immutable self borrows, whereas `MutDevicePio` and `MutDeviceMmio` require +operations. The difference is that `DevicePio` and `VirtioMmioDevice` only require +immutable self borrows, whereas `MutDevicePio` and `MutVirtioMmioDevice` require mutable borrows. The device manager abstraction is implemented by the `IoManager` struct. It @@ -56,9 +56,9 @@ with an `IoManager` instance within the specified address range on the bus. Creating a new `IoManager` is easy by calling `IoManager::new()` without any configuration. Internally the manager stores devices as trait objects wrapped in `Arc`’s, therefore if the device implements `MutDevicePio` or -`MutDeviceMmio`, then it must be wrapped in a `Mutex`. The crate contains +`MutVirtioMmioDevice`, then it must be wrapped in a `Mutex`. The crate contains automatic implementation of `DevicePio for Mutex where T: MutDevicePio` -and `DeviceMmio for Mutex where T: MutDeviceMmio` but only for the Mutex +and `VirtioMmioDevice for Mutex where T: MutVirtioMmioDevice` but only for the Mutex type in the standard library. For any other `Mutex` type from 3rd party crates the blanket implementation must be done by the user. diff --git a/src/bus/address.rs b/src/bus/address.rs deleted file mode 100644 index dfa41b7..0000000 --- a/src/bus/address.rs +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -use std::cmp::Ordering; -use std::convert::TryFrom; -use std::ops::{Add, Sub}; - -/// This trait defines the operations we expect to apply to bus address values. -pub trait BusAddress: - Add<::V, Output = Self> - + Copy - + Eq - + Ord - + Sub::V> -{ - /// Defines the underlying value type of the `BusAddress`. - type V: Add - + Copy - + From - + PartialEq - + Ord - + Sub - + TryFrom; - - /// Return the inner value. - fn value(&self) -> Self::V; - - /// Return the bus address computed by offsetting `self` by the specified value, if no - /// overflow occurs. - fn checked_add(&self, value: Self::V) -> Option; -} - -/// Represents a MMIO address offset. -pub type MmioAddressOffset = u64; - -/// Represents a MMIO address. -#[derive(Clone, Copy, Debug)] -pub struct MmioAddress(pub MmioAddressOffset); - -/// Represents a PIO address offset. -pub type PioAddressOffset = u16; - -/// Represents a PIO address. -#[derive(Clone, Copy, Debug)] -pub struct PioAddress(pub PioAddressOffset); - -// Implementing `BusAddress` and its prerequisites for `MmioAddress`. - -impl PartialEq for MmioAddress { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for MmioAddress {} - -impl PartialOrd for MmioAddress { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for MmioAddress { - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&other.0) - } -} - -impl Add for MmioAddress { - type Output = Self; - - fn add(self, rhs: MmioAddressOffset) -> Self::Output { - MmioAddress(self.0 + rhs) - } -} - -impl Sub for MmioAddress { - type Output = MmioAddressOffset; - - fn sub(self, rhs: Self) -> Self::Output { - self.0 - rhs.0 - } -} - -impl BusAddress for MmioAddress { - type V = MmioAddressOffset; - - fn value(&self) -> Self::V { - self.0 - } - - fn checked_add(&self, value: Self::V) -> Option { - self.0.checked_add(value).map(MmioAddress) - } -} - -// Implementing `BusAddress` and its prerequisites for `PioAddress`. - -impl PartialEq for PioAddress { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } -} - -impl Eq for PioAddress {} - -impl PartialOrd for PioAddress { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for PioAddress { - fn cmp(&self, other: &Self) -> Ordering { - self.0.cmp(&other.0) - } -} - -impl Add for PioAddress { - type Output = Self; - - fn add(self, rhs: PioAddressOffset) -> Self::Output { - PioAddress(self.0 + rhs) - } -} - -impl Sub for PioAddress { - type Output = PioAddressOffset; - - fn sub(self, rhs: Self) -> Self::Output { - self.0 - rhs.0 - } -} - -impl BusAddress for PioAddress { - type V = PioAddressOffset; - - fn value(&self) -> Self::V { - self.0 - } - - fn checked_add(&self, value: Self::V) -> Option { - self.0.checked_add(value).map(PioAddress) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use std::fmt::Debug; - - // `addr_zero` should be an address equivalent to 0, while `max_value` should contain the - // maximum possible address value. - fn check_bus_address_ops(addr_zero: A, max_value: A::V) - where - A: BusAddress + Debug, - A::V: Debug, - { - let value = A::V::from(5); - let addr = addr_zero + value; - - assert!(addr_zero < addr); - assert_eq!(addr - addr_zero, value); - - assert_eq!(addr.value(), value); - assert_eq!(addr_zero.checked_add(value).unwrap(), addr); - - let addr_max = addr_zero.checked_add(max_value).unwrap(); - assert!(addr_max.checked_add(A::V::from(1)).is_none()); - } - - #[test] - fn test_address_ops() { - check_bus_address_ops(MmioAddress(0), std::u64::MAX); - check_bus_address_ops(PioAddress(0), std::u16::MAX); - } -} diff --git a/src/bus/mod.rs b/src/bus/mod.rs index 8ab64ab..aa5b4c1 100644 --- a/src/bus/mod.rs +++ b/src/bus/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause +// SPDX-License-Identifier: GuestAddresspache-2.0 OR BSD-3-Clause //! Provides abstractions for modelling an I/O bus. //! @@ -8,18 +8,16 @@ //! A single device can be registered with multiple ranges, but no two ranges can overlap, //! regardless with their device associations. -mod address; mod range; use std::collections::BTreeMap; -use std::convert::TryFrom; use std::fmt::{Display, Formatter}; use std::result::Result; -use address::BusAddress; +pub use range::BusRange; +use vm_memory::GuestAddress; -pub use address::{MmioAddress, MmioAddressOffset, PioAddress, PioAddressOffset}; -pub use range::{BusRange, MmioRange, PioRange}; +use crate::VirtioMmioDevice; /// Errors encountered during bus operations. #[derive(Debug, Eq, PartialEq)] @@ -48,11 +46,11 @@ impl Display for Error { impl std::error::Error for Error {} /// A bus that's agnostic to the range address type and device type. -pub struct Bus { - devices: BTreeMap, D>, +pub struct Bus { + devices: BTreeMap, } -impl Default for Bus { +impl Default for Bus { fn default() -> Self { Bus { devices: BTreeMap::new(), @@ -60,14 +58,14 @@ impl Default for Bus { } } -impl Bus { +impl Bus { /// Create an empty bus. pub fn new() -> Self { Self::default() } /// Return the registered range and device associated with `addr`. - pub fn device(&self, addr: A) -> Option<(&BusRange, &D)> { + pub fn device(&self, addr: GuestAddress) -> Option<(&BusRange, &D)> { // The range is returned as an optimization because the caller // might need both the device and its associated bus range. // The same goes for the device_mut() method. @@ -79,7 +77,7 @@ impl Bus { /// Return the registered range and a mutable reference to the device /// associated with `addr`. - pub fn device_mut(&mut self, addr: A) -> Option<(&BusRange, &mut D)> { + pub fn device_mut(&mut self, addr: GuestAddress) -> Option<(&BusRange, &mut D)> { self.devices .range_mut(..=BusRange::unit(addr)) .nth_back(0) @@ -87,7 +85,7 @@ impl Bus { } /// Register a device with the provided range. - pub fn register(&mut self, range: BusRange, device: D) -> Result<(), Error> { + pub fn register(&mut self, range: BusRange, device: D) -> Result<(), Error> { for r in self.devices.keys() { if range.overlaps(r) { return Err(Error::DeviceOverlap); @@ -100,181 +98,29 @@ impl Bus { } /// Deregister the device associated with `addr`. - pub fn deregister(&mut self, addr: A) -> Option<(BusRange, D)> { + pub fn deregister(&mut self, addr: GuestAddress) -> Option<(BusRange, D)> { let range = self.device(addr).map(|(range, _)| *range)?; self.devices.remove(&range).map(|device| (range, device)) } /// Verify whether an access starting at `addr` with length `len` fits within any of /// the registered ranges. Return the range and a handle to the device when present. - pub fn check_access(&self, addr: A, len: usize) -> Result<(&BusRange, &D), Error> { - let access_range = BusRange::new( - addr, - A::V::try_from(len).map_err(|_| Error::InvalidAccessLength(len))?, - ) - .map_err(|_| Error::InvalidRange)?; + pub fn check_access(&self, addr: GuestAddress, len: usize) -> Result<(&BusRange, &D), Error> { + let access_range = BusRange::new(addr, len as u64).map_err(|_| Error::InvalidRange)?; self.device(addr) .filter(|(range, _)| range.last() >= access_range.last()) .ok_or(Error::DeviceNotFound) } } -/// Represents an MMIO bus. -pub type MmioBus = Bus; -/// Represents a PIO bus. -pub type PioBus = Bus; - /// Helper trait that can be implemented by types which hold one or more buses. -pub trait BusManager { +pub trait BusManager { /// Type of the objects held by the bus. type D; /// Return a reference to the bus. - fn bus(&self) -> &Bus; + fn bus(&self) -> &Bus; /// Return a mutable reference to the bus. - fn bus_mut(&mut self) -> &mut Bus; -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_bus() { - let base = MmioAddress(10); - let base_prev = MmioAddress(base.value().checked_sub(1).unwrap()); - let len = 10; - let range = MmioRange::new(base, len).unwrap(); - let range_next = range.last().checked_add(1).unwrap(); - - let mut bus = Bus::new(); - // The bus is agnostic to actual device types, so let's just use a numeric type here. - let device = 1u8; - - assert_eq!(bus.devices.len(), 0); - - bus.register(range, device).unwrap(); - assert_eq!(bus.devices.len(), 1); - - assert!(bus.device(base_prev).is_none()); - assert!(bus.device_mut(base_prev).is_none()); - assert!(bus.device(range_next).is_none()); - assert!(bus.device_mut(range_next).is_none()); - - for offset in 0..len { - let addr = base.checked_add(offset).unwrap(); - - { - let (r, d) = bus.device(addr).unwrap(); - assert_eq!(range, *r); - assert_eq!(device, *d); - } - - { - let (r, d) = bus.device_mut(addr).unwrap(); - assert_eq!(range, *r); - assert_eq!(device, *d); - } - - // Let's also check invocations of `Bus::check_access`. - for start_offset in 0..offset { - let start_addr = base.checked_add(start_offset).unwrap(); - - let (r, d) = bus - .check_access(start_addr, usize::try_from(offset - start_offset).unwrap()) - .unwrap(); - assert_eq!(range, *r); - assert_eq!(device, *d); - } - } - - // Detect double registration with the same range. - assert_eq!(bus.register(range, device), Err(Error::DeviceOverlap)); - - // We detect overlaps even if it's another range associated with the same device (we don't - // implicitly merge ranges). `check_access` fails if the specified range does not fully - // fit within a region associated with a particular device. - - { - let range2 = MmioRange::new(MmioAddress(1), 10).unwrap(); - assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap)); - assert_eq!( - bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()), - Err(Error::DeviceNotFound) - ); - } - - { - let range2 = MmioRange::new(range.last(), 10).unwrap(); - assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap)); - assert_eq!( - bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()), - Err(Error::DeviceNotFound) - ); - } - - { - let range2 = MmioRange::new(MmioAddress(1), range.last().value() + 100).unwrap(); - assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap)); - assert_eq!( - bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()), - Err(Error::DeviceNotFound) - ); - } - - { - // For a completely empty range, `check_access` should still fail, but `insert` - // will succeed. - - let range2 = MmioRange::new(range.last().checked_add(1).unwrap(), 5).unwrap(); - - assert_eq!( - bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()), - Err(Error::DeviceNotFound) - ); - - // Validate registration, and that `deregister` works for all addresses within a range. - for offset in 0..range2.size() { - let device2 = device + 1; - assert!(bus.register(range2, device2).is_ok()); - assert_eq!(bus.devices.len(), 2); - - let addr = range2.base().checked_add(offset).unwrap(); - let (r, d) = bus.deregister(addr).unwrap(); - assert_eq!(bus.devices.len(), 1); - assert_eq!(r, range2); - assert_eq!(d, device2); - - // A second deregister should fail. - assert!(bus.deregister(addr).is_none()); - assert_eq!(bus.devices.len(), 1); - } - - // Register the previous `device` for `range2`. - assert!(bus.register(range2, device).is_ok()); - assert_eq!(bus.devices.len(), 2); - - // Even though the new range is associated with the same device, and right after the - // previous one, accesses across multiple ranges are not allowed for now. - // TODO: Do we want to support this in the future? - assert_eq!( - bus.check_access(range.base(), usize::try_from(range.size() + 1).unwrap()), - Err(Error::DeviceNotFound) - ); - } - - // Ensure that bus::check_access() fails when the len argument - // cannot be safely converted to PioAddressOffset which is u16. - let pio_base = PioAddress(10); - let pio_len = 10; - let pio_range = PioRange::new(pio_base, pio_len).unwrap(); - let mut pio_bus = Bus::new(); - let pio_device = 1u8; - pio_bus.register(pio_range, pio_device).unwrap(); - assert_eq!( - pio_bus.check_access(pio_base, usize::MAX), - Err(Error::InvalidAccessLength(usize::MAX)) - ); - } + fn bus_mut(&mut self) -> &mut Bus; } diff --git a/src/bus/range.rs b/src/bus/range.rs index 6ac15f8..530fae2 100644 --- a/src/bus/range.rs +++ b/src/bus/range.rs @@ -3,56 +3,54 @@ use std::cmp::Ordering; -use crate::bus::{BusAddress, Error, MmioAddress, PioAddress}; +use vm_memory::GuestAddress; + +use crate::bus::Error; /// An interval in the address space of a bus. #[derive(Copy, Clone, Debug)] -pub struct BusRange { - base: A, - size: A::V, +pub struct BusRange { + base: GuestAddress, + size: u64, } -impl BusRange { +impl BusRange { /// Create a new range while checking for overflow. - pub fn new(base: A, size: A::V) -> Result { + pub fn new(base: GuestAddress, size: u64) -> Result { // A zero-length range is not valid. - if size == 0.into() { + if size == 0 { return Err(Error::InvalidRange); } // Subtracting one, because a range that ends at the very edge of the address space // is still valid. - base.checked_add(size - 1.into()) - .ok_or(Error::InvalidRange)?; + base.0.checked_add(size - 1).ok_or(Error::InvalidRange)?; Ok(BusRange { base, size }) } /// Create a new unit range (its size equals `1`). - pub fn unit(base: A) -> Self { - BusRange { - base, - size: 1.into(), - } + pub fn unit(base: GuestAddress) -> Self { + BusRange { base, size: 1 } } /// Return the base address of this range. - pub fn base(&self) -> A { + pub fn base(&self) -> GuestAddress { self.base } /// Return the size of the range. - pub fn size(&self) -> A::V { - self.size + pub fn size(&self) -> usize { + self.size as usize } /// Return the last bus address that's still part of the range. - pub fn last(&self) -> A { - self.base + (self.size - 1.into()) + pub fn last(&self) -> GuestAddress { + GuestAddress(self.base.0 + (self.size - 1)) } /// Check whether `self` and `other` overlap as intervals. - pub fn overlaps(&self, other: &BusRange) -> bool { + pub fn overlaps(&self, other: &BusRange) -> bool { !(self.base > other.last() || self.last() < other.base) } } @@ -62,117 +60,22 @@ impl BusRange { // not really possible with intervals, so we write the implementations as if `BusRange`s were // solely determined by their base addresses, and apply extra checks in the `Bus` logic. -impl PartialEq for BusRange { - fn eq(&self, other: &BusRange) -> bool { +impl PartialEq for BusRange { + fn eq(&self, other: &BusRange) -> bool { self.base == other.base } } -impl Eq for BusRange {} +impl Eq for BusRange {} -impl PartialOrd for BusRange { - fn partial_cmp(&self, other: &BusRange) -> Option { +impl PartialOrd for BusRange { + fn partial_cmp(&self, other: &BusRange) -> Option { self.base.partial_cmp(&other.base) } } -impl Ord for BusRange { - fn cmp(&self, other: &BusRange) -> Ordering { +impl Ord for BusRange { + fn cmp(&self, other: &BusRange) -> Ordering { self.base.cmp(&other.base) } } - -/// Represents an MMIO bus range. -pub type MmioRange = BusRange; -/// Represents a PIO bus range. -pub type PioRange = BusRange; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_bus_range() { - let base_zero = MmioAddress(0); - let value = 5; - - assert_eq!(BusRange::new(base_zero, 0), Err(Error::InvalidRange)); - - assert!(BusRange::new(base_zero, std::u64::MAX).is_ok()); - assert!(BusRange::new(MmioAddress(1), std::u64::MAX).is_ok()); - assert_eq!( - BusRange::new(MmioAddress(2), std::u64::MAX), - Err(Error::InvalidRange) - ); - - { - let range = BusRange::new(base_zero, value).unwrap(); - assert_eq!(range.base(), base_zero); - assert_eq!(range.size(), value); - assert_eq!(range.last(), MmioAddress(value - 1)); - assert!(range.base() < range.last()); - } - - { - let range = BusRange::unit(base_zero); - assert_eq!(range.base(), base_zero); - assert_eq!(range.last(), range.base()); - } - - // Let's test `BusRange::overlaps`. - { - let range = BusRange::new(MmioAddress(10), 10).unwrap(); - - let overlaps = |base_value, len_value| { - range.overlaps(&BusRange::new(MmioAddress(base_value), len_value).unwrap()) - }; - - assert!(!overlaps(0, 5)); - assert!(!overlaps(0, 10)); - assert!(!overlaps(5, 5)); - - assert!(overlaps(0, 11)); - assert!(overlaps(5, 6)); - assert!(overlaps(5, 10)); - assert!(overlaps(11, 15)); - assert!(overlaps(5, 35)); - assert!(overlaps(19, 1)); - assert!(overlaps(19, 10)); - - assert!(!overlaps(20, 1)); - assert!(!overlaps(30, 10)); - } - - // Finally, let's test the `BusRange` trait implementations that we added. - { - let base = MmioAddress(10); - let len = 10; - - let range = BusRange::new(base, len).unwrap(); - - assert_eq!(range.cmp(&range), range.partial_cmp(&range).unwrap()); - assert_eq!(range.cmp(&range), Ordering::Equal); - - { - let other = BusRange::new(base, len + 1).unwrap(); - - // Still equal becase we're only comparing `base` values as part - // of the `eq` implementation. - assert_eq!(range, other); - - assert_eq!(range.cmp(&other), range.partial_cmp(&other).unwrap()); - assert_eq!(range.cmp(&other), Ordering::Equal); - } - - { - let other = BusRange::unit(base.checked_add(1).unwrap()); - - // Different due to different base addresses. - assert_ne!(range, other); - - assert_eq!(range.cmp(&other), range.partial_cmp(&other).unwrap()); - assert_eq!(range.cmp(&other), Ordering::Less); - } - } - } -} diff --git a/src/device_manager.rs b/src/device_manager.rs index 86402b7..62a4098 100644 --- a/src/device_manager.rs +++ b/src/device_manager.rs @@ -17,13 +17,13 @@ //! //! Registering a new device can be done using the register methods of [`PioManager`] //! and [`MmioManager`] with an appropriate bus range -//! ([`PioRange`](../bus/type.PioRange.html) or [`MmioRange`](../bus/type.MmioRange.html)). +//! ([`PioRange`](../bus/type.PioRange.html) or [`BusRange`](../bus/type.BusRange.html)). //! ``` //! # use std::sync::Arc; //! # use vm_device::bus::{PioAddress, PioAddressOffset, PioRange}; -//! # use vm_device::bus::{MmioAddress, MmioAddressOffset, MmioRange}; +//! # use vm_device::bus::{GuestAddress, GuestAddressOffset, BusRange}; //! # use vm_device::device_manager::{IoManager, PioManager, MmioManager}; -//! # use vm_device::{DevicePio, DeviceMmio}; +//! # use vm_device::{DevicePio, VirtioMmioDevice}; //! struct NoopDevice {} //! //! impl DevicePio for NoopDevice { @@ -31,9 +31,9 @@ //! fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) {} //! } //! -//! impl DeviceMmio for NoopDevice { -//! fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) {} -//! fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) {} +//! impl VirtioMmioDevice for NoopDevice { +//! fn mmio_read(&self, base: GuestAddress, offset: GuestAddressOffset, data: &mut [u8]) {} +//! fn mmio_write(&self, base: GuestAddress, offset: GuestAddressOffset, data: &[u8]) {} //! } //! //! // IoManager implements both PioManager and MmioManager. @@ -46,7 +46,7 @@ //! .unwrap(); //! //! // Register the device on the MMIO bus. -//! let mmio_range = MmioRange::new(MmioAddress(0), 10).unwrap(); +//! let mmio_range = BusRange::new(GuestAddress(0), 10).unwrap(); //! manager //! .register_mmio(mmio_range, Arc::new(NoopDevice {})) //! .unwrap(); @@ -56,7 +56,7 @@ //! //! // Dispatch I/O on the MMIO bus. //! manager -//! .mmio_write(MmioAddress(0), &vec![b'o', b'k']) +//! .mmio_write(GuestAddress(0), &vec![b'o', b'k']) //! .unwrap(); //! ``` //! @@ -68,9 +68,9 @@ //! ``` //! # use std::sync::Arc; //! # use vm_device::bus::{PioAddress, PioAddressOffset, PioRange}; -//! # use vm_device::bus::{MmioAddress, MmioAddressOffset, MmioRange}; +//! # use vm_device::bus::{GuestAddress, GuestAddressOffset, BusRange}; //! # use vm_device::device_manager::{IoManager, PioManager, MmioManager}; -//! # use vm_device::{DevicePio, DeviceMmio}; +//! # use vm_device::{DevicePio, VirtioMmioDevice}; //! # use vm_device::resources::Resource; //! # struct NoopDevice {} //! # @@ -79,9 +79,9 @@ //! # fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) {} //! # } //! # -//! # impl DeviceMmio for NoopDevice { -//! # fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) {} -//! # fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) {} +//! # impl VirtioMmioDevice for NoopDevice { +//! # fn mmio_read(&self, base: GuestAddress, offset: GuestAddressOffset, data: &mut [u8]) {} +//! # fn mmio_write(&self, base: GuestAddress, offset: GuestAddressOffset, data: &[u8]) {} //! # } //! // Use the same NoopDevice defined above. //! @@ -94,7 +94,7 @@ //! }; //! //! // Define a MMIO address range resource. -//! let mmio = Resource::MmioAddressRange { +//! let mmio = Resource::GuestAddressRange { //! base: 0, //! size: 10, //! }; @@ -111,16 +111,18 @@ //! //! // Dispatching I/O is the same. //! manager.pio_write(PioAddress(0), &vec![b'o', b'k']).unwrap(); -//! manager.mmio_write(MmioAddress(0), &vec![b'o', b'k']).unwrap(); +//! manager.mmio_write(GuestAddress(0), &vec![b'o', b'k']).unwrap(); //! ``` use std::fmt::{Display, Formatter}; use std::result::Result; use std::sync::Arc; -use crate::bus::{self, BusManager, MmioAddress, MmioBus, MmioRange, PioAddress, PioBus, PioRange}; +use vm_memory::GuestAddress; + +use crate::bus::{self, Bus, BusManager, BusRange}; use crate::resources::Resource; -use crate::{DeviceMmio, DevicePio}; +use crate::{VirtioMmioDevice, VirtioMmioOffset}; /// Error type for [IoManager] usage. #[derive(Debug)] @@ -145,116 +147,71 @@ impl std::error::Error for Error { } } -/// Represents an object that provides PIO manager operations. -pub trait PioManager { - /// Type of the objects that can be registered with this `PioManager`. - type D: DevicePio; - - /// Return a reference to the device registered at `addr`, together with the associated - /// range, if available. - fn pio_device(&self, addr: PioAddress) -> Option<(&PioRange, &Self::D)>; - - /// Dispatch a read operation to the device registered at `addr`. - fn pio_read(&self, addr: PioAddress, data: &mut [u8]) -> Result<(), bus::Error>; - - /// Dispatch a write operation to the device registered at `addr`. - fn pio_write(&self, addr: PioAddress, data: &[u8]) -> Result<(), bus::Error>; - - /// Register the provided device with the specified range. - fn register_pio(&mut self, range: PioRange, device: Self::D) -> Result<(), bus::Error>; - - /// Deregister the device currently registered at `addr` together with the - /// associated range. - fn deregister_pio(&mut self, addr: PioAddress) -> Option<(PioRange, Self::D)>; -} - -// This automatically provides a `PioManager` implementation for types that already implement -// `BusManager` if their inner associated type implements `DevicePio` as well. -impl PioManager for T -where - T: BusManager, - T::D: DevicePio, -{ - type D = >::D; - - fn pio_device(&self, addr: PioAddress) -> Option<(&PioRange, &Self::D)> { - self.bus().device(addr) - } - - fn pio_read(&self, addr: PioAddress, data: &mut [u8]) -> Result<(), bus::Error> { - self.bus() - .check_access(addr, data.len()) - .map(|(range, device)| device.pio_read(range.base(), addr - range.base(), data)) - } - - fn pio_write(&self, addr: PioAddress, data: &[u8]) -> Result<(), bus::Error> { - self.bus() - .check_access(addr, data.len()) - .map(|(range, device)| device.pio_write(range.base(), addr - range.base(), data)) - } - - fn register_pio(&mut self, range: PioRange, device: Self::D) -> Result<(), bus::Error> { - self.bus_mut().register(range, device) - } - - fn deregister_pio(&mut self, addr: PioAddress) -> Option<(PioRange, Self::D)> { - self.bus_mut().deregister(addr) - } -} - /// Represents an object that provides MMIO manager operations. pub trait MmioManager { /// Type of the objects that can be registered with this `MmioManager`. - type D: DeviceMmio; + type D: VirtioMmioDevice; /// Return a reference to the device registered at `addr`, together with the associated /// range, if available. - fn mmio_device(&self, addr: MmioAddress) -> Option<(&MmioRange, &Self::D)>; + fn mmio_device(&self, addr: GuestAddress) -> Option<(&BusRange, &Self::D)>; /// Dispatch a read operation to the device registered at `addr`. - fn mmio_read(&self, addr: MmioAddress, data: &mut [u8]) -> Result<(), bus::Error>; + fn mmio_read(&self, addr: GuestAddress, data: &mut [u8]) -> Result<(), bus::Error>; /// Dispatch a write operation to the device registered at `addr`. - fn mmio_write(&self, addr: MmioAddress, data: &[u8]) -> Result<(), bus::Error>; + fn mmio_write(&self, addr: GuestAddress, data: &[u8]) -> Result<(), bus::Error>; /// Register the provided device with the specified range. - fn register_mmio(&mut self, range: MmioRange, device: Self::D) -> Result<(), bus::Error>; + fn register_mmio(&mut self, range: BusRange, device: Self::D) -> Result<(), bus::Error>; /// Deregister the device currently registered at `addr` together with the /// associated range. - fn deregister_mmio(&mut self, addr: MmioAddress) -> Option<(MmioRange, Self::D)>; + fn deregister_mmio(&mut self, addr: GuestAddress) -> Option<(BusRange, Self::D)>; } // This automatically provides a `MmioManager` implementation for types that already implement -// `BusManager` if their inner associated type implements `DeviceMmio` as well. +// `BusManager` if their inner associated type implements `VirtioMmioDevice` as well. impl MmioManager for T where - T: BusManager, - T::D: DeviceMmio, + T: BusManager, + T::D: VirtioMmioDevice, { - type D = >::D; + type D = ::D; - fn mmio_device(&self, addr: MmioAddress) -> Option<(&MmioRange, &Self::D)> { + fn mmio_device(&self, addr: GuestAddress) -> Option<(&BusRange, &Self::D)> { self.bus().device(addr) } - fn mmio_read(&self, addr: MmioAddress, data: &mut [u8]) -> Result<(), bus::Error> { + fn mmio_read(&self, addr: GuestAddress, data: &mut [u8]) -> Result<(), bus::Error> { self.bus() .check_access(addr, data.len()) - .map(|(range, device)| device.mmio_read(range.base(), addr - range.base(), data)) + .map(|(range, device)| { + device.virtio_mmio_read( + range.base(), + VirtioMmioOffset::from(addr.0 - range.base().0), + data, + ) + }) } - fn mmio_write(&self, addr: MmioAddress, data: &[u8]) -> Result<(), bus::Error> { + fn mmio_write(&self, addr: GuestAddress, data: &[u8]) -> Result<(), bus::Error> { self.bus() .check_access(addr, data.len()) - .map(|(range, device)| device.mmio_write(range.base(), addr - range.base(), data)) + .map(|(range, device)| { + device.virtio_mmio_write( + range.base(), + VirtioMmioOffset::from(addr.0 - range.base().0), + data, + ) + }) } - fn register_mmio(&mut self, range: MmioRange, device: Self::D) -> Result<(), bus::Error> { + fn register_mmio(&mut self, range: BusRange, device: Self::D) -> Result<(), bus::Error> { self.bus_mut().register(range, device) } - fn deregister_mmio(&mut self, addr: MmioAddress) -> Option<(MmioRange, Self::D)> { + fn deregister_mmio(&mut self, addr: GuestAddress) -> Option<(BusRange, Self::D)> { self.bus_mut().deregister(addr) } } @@ -262,34 +219,19 @@ where /// 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: PioBus>, // Range mapping for VM exit mmio operations. - mmio_bus: MmioBus>, -} - -// Enables the automatic implementation of `PioManager` for `IoManager`. -impl BusManager for IoManager { - type D = Arc; - - fn bus(&self) -> &PioBus> { - &self.pio_bus - } - - fn bus_mut(&mut self) -> &mut PioBus> { - &mut self.pio_bus - } + mmio_bus: Bus>, } // Enables the automatic implementation of `MmioManager` for `IoManager`. -impl BusManager for IoManager { - type D = Arc; +impl BusManager for IoManager { + type D = Arc; - fn bus(&self) -> &MmioBus> { + fn bus(&self) -> &Bus> { &self.mmio_bus } - fn bus_mut(&mut self) -> &mut MmioBus> { + fn bus_mut(&mut self) -> &mut Bus> { &mut self.mmio_bus } } @@ -310,46 +252,16 @@ impl IoManager { /// port I/O and memory-mapped I/O ranges, irq number, etc. pub fn register_mmio_resources( &mut self, - device: Arc, + device: Arc, resources: &[Resource], ) -> Result<(), Error> { // Register and mark device resources // The resources addresses being registered are sucessfully allocated before. for res in resources.iter() { match *res { - Resource::MmioAddressRange { base, size } => { + Resource::GuestAddressRange { base, size } => { self.register_mmio( - MmioRange::new(MmioAddress(base), size).unwrap(), - device.clone(), - ) - .map_err(Error::Bus)?; - } - _ => continue, - } - } - Ok(()) - } - - /// Register a new PIO device 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_pio_resources( - &mut self, - device: Arc, - resources: &[Resource], - ) -> Result<(), Error> { - // Register and mark device resources - // The resources addresses being registered are sucessfully allocated before. - for res in resources.iter() { - match *res { - Resource::PioAddressRange { base, size } => { - self.register_pio( - PioRange::new(PioAddress(base), size).unwrap(), + BusRange::new(GuestAddress(base), size).unwrap(), device.clone(), ) .map_err(Error::Bus)?; @@ -368,13 +280,12 @@ impl IoManager { /// * `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_resources( + pub fn register_resources( &mut self, device: Arc, resources: &[Resource], ) -> Result<(), Error> { - self.register_mmio_resources(device.clone(), resources)?; - self.register_pio_resources(device, resources) + self.register_mmio_resources(device.clone(), resources) } /// Deregister a device from `IoManager`, e.g. users specified removing. @@ -390,13 +301,8 @@ impl IoManager { let mut count = 0; for res in resources.iter() { match *res { - Resource::PioAddressRange { base, .. } => { - if self.deregister_pio(PioAddress(base)).is_some() { - count += 1; - } - } - Resource::MmioAddressRange { base, .. } => { - if self.deregister_mmio(MmioAddress(base)).is_some() { + Resource::GuestAddressRange { base, .. } => { + if self.deregister_mmio(GuestAddress(base)).is_some() { count += 1; } } @@ -406,177 +312,3 @@ impl IoManager { count } } - -#[cfg(test)] -mod tests { - use super::*; - - use std::error::Error; - use std::sync::Mutex; - - use bus::{MmioAddressOffset, PioAddressOffset}; - - 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 DevicePio for DummyDevice { - fn pio_read(&self, _base: PioAddress, _offset: PioAddressOffset, 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 pio_write(&self, _base: PioAddress, _offset: PioAddressOffset, data: &[u8]) { - let mut config = self.config.lock().expect("failed to acquire lock"); - *config = u32::from(data[0]) & 0xff; - } - } - - impl DeviceMmio for DummyDevice { - fn mmio_read(&self, _base: MmioAddress, _offset: MmioAddressOffset, 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 mmio_write(&self, _base: MmioAddress, _offset: MmioAddressOffset, data: &[u8]) { - let mut config = self.config.lock().expect("failed to acquire lock"); - *config = u32::from(data[0]) & 0xff; - } - } - - #[test] - fn test_register_deregister_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); - let pio = Resource::PioAddressRange { - base: PIO_ADDRESS_BASE, - size: PIO_ADDRESS_SIZE, - }; - - resource.push(mmio); - resource.push(irq); - resource.push(pio); - - assert!(io_mgr - .register_mmio_resources(dum.clone(), &resource) - .is_ok()); - assert!(io_mgr.register_pio_resources(dum, &resource).is_ok()); - assert_eq!(io_mgr.deregister_resources(&resource), 2); - } - - #[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_mmio_resources(dum.clone(), &resource) - .is_ok()); - - let mut data = [0; 4]; - assert!(io_mgr - .mmio_read(MmioAddress(MMIO_ADDRESS_BASE), &mut data) - .is_ok()); - assert_eq!(data, [0x34, 0x12, 0, 0]); - - assert!(io_mgr - .mmio_read( - MmioAddress(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE), - &mut data - ) - .is_err()); - - data = [0; 4]; - assert!(io_mgr - .mmio_write(MmioAddress(MMIO_ADDRESS_BASE), &data) - .is_ok()); - assert_eq!(*dum.config.lock().unwrap(), 0); - - assert!(io_mgr - .mmio_write(MmioAddress(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_pio_resources(dum.clone(), &resource) - .is_ok()); - - let mut data = [0; 4]; - assert!(io_mgr - .pio_read(PioAddress(PIO_ADDRESS_BASE), &mut data) - .is_ok()); - assert_eq!(data, [0x34, 0x12, 0, 0]); - - assert!(io_mgr - .pio_read(PioAddress(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE), &mut data) - .is_err()); - - data = [0; 4]; - assert!(io_mgr - .pio_write(PioAddress(PIO_ADDRESS_BASE), &data) - .is_ok()); - assert_eq!(*dum.config.lock().unwrap(), 0); - - assert!(io_mgr - .pio_write(PioAddress(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE), &data) - .is_err()); - } - - #[test] - fn test_error_code() { - let err = super::Error::Bus(bus::Error::DeviceOverlap); - - assert!(err.source().is_some()); - assert_eq!(format!("{}", err), "device_manager: bus error"); - } -} diff --git a/src/lib.rs b/src/lib.rs index 70e2451..b326379 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,287 +1,41 @@ -// Copyright © 2019 Intel Corporation. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause - -#![deny(missing_docs)] +use std::ops::Deref; +use std::sync::{Arc, Mutex}; -//! This crate provides: -//! * device traits defining read and write operations on specialized buses -//! * device manager (bus-specific traits and a concrete implementation) for -//! operating devices and dispatching I/O -//! * abstractions for defining resources and their constraints (e.g. a specific bus -//! address range, IRQ number, etc) -//! -//! [`MutDevicePio`] and [`MutDeviceMmio`] traits help with composite inner mutability -//! (i.e. if we have a `Mutex` that holds a `T` which implements [`MutDevicePio`], -//! then the `Mutex` can implement [`DevicePio`] based on its inner -//! mutability properties). -//! -//! # Example -//! -//! Implement a simple log PIO device, register it with -//! [`IoManager`](device_manager/struct.IoManager.html) -//! and dispatch a write operation to the device. -//!``` -//! use std::sync::{Arc, Mutex}; -//! use vm_device::bus::{PioAddress, PioAddressOffset, PioRange}; -//! use vm_device::device_manager::{IoManager, PioManager}; -//! use vm_device::MutDevicePio; -//! -//! struct LogDevice {} -//! -//! impl MutDevicePio for LogDevice { -//! fn pio_read(&mut self, base: PioAddress, offset: PioAddressOffset, _data: &mut [u8]) { -//! println!("mut pio_read: base {:?}, offset {}", base, offset); -//! } -//! fn pio_write(&mut self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) { -//! println!( -//! "mut pio_write: base {:?}, offset {}, data {:?}", -//! base, offset, data -//! ); -//! } -//! } -//! -//! // IoManager implements PioManager trait. -//! let mut manager = IoManager::new(); -//! let device = LogDevice {}; -//! let bus_range = PioRange::new(PioAddress(0), 10).unwrap(); -//! manager -//! .register_pio(bus_range, Arc::new(Mutex::new(device))) -//! .unwrap(); -//! manager.pio_write(PioAddress(0), &vec![b'o', b'k']).unwrap(); -//! ``` +use vm_memory::GuestAddress; pub mod bus; pub mod device_manager; pub mod resources; +pub mod virtio_mmio; -use std::ops::Deref; -use std::sync::{Arc, Mutex}; - -use bus::{MmioAddress, MmioAddressOffset, PioAddress, PioAddressOffset}; - -/// Allows a device to be attached to a -/// [PIO](https://en.wikipedia.org/wiki/Programmed_input%E2%80%93output) bus. -/// -/// # Example -/// ``` -/// # use std::sync::Mutex; -/// # use vm_device::{DevicePio, bus::{PioAddress, PioAddressOffset}}; -/// struct DummyDevice { -/// config: Mutex, -/// } -/// -/// impl DevicePio for DummyDevice { -/// fn pio_read(&self, _base: PioAddress, _offset: PioAddressOffset, 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 pio_write(&self, _base: PioAddress, _offset: PioAddressOffset, data: &[u8]) { -/// let mut config = self.config.lock().expect("failed to acquire lock"); -/// *config = u32::from(data[0]) & 0xff; -/// } -/// } -/// ``` -pub trait DevicePio { - /// Handle a read operation on the device. - /// - /// # Arguments - /// - /// * `base`: base address on a PIO bus - /// * `offset`: base address' offset - /// * `data`: a buffer provided by the caller to store the read data - fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]); - - /// Handle a write operation to the device. - /// - /// # Arguments - /// - /// * `base`: base address on a PIO bus - /// * `offset`: base address' offset - /// * `data`: a buffer provided by the caller holding the data to write - fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]); -} - -/// Allows a device to be attached to a -/// [MMIO](https://en.wikipedia.org/wiki/Memory-mapped_I/O) bus. -/// -/// # Example -/// ``` -/// # use std::sync::Mutex; -/// # use vm_device::{DeviceMmio, bus::{MmioAddress, MmioAddressOffset}}; -/// struct DummyDevice { -/// config: Mutex, -/// } -/// -/// impl DeviceMmio for DummyDevice { -/// fn mmio_read(&self, _base: MmioAddress, _offset: MmioAddressOffset, 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 mmio_write(&self, _base: MmioAddress, _offset: MmioAddressOffset, data: &[u8]) { -/// let mut config = self.config.lock().expect("failed to acquire lock"); -/// *config = u32::from(data[0]) & 0xff; -/// } -/// } -/// ``` -pub trait DeviceMmio { - /// Handle a read operation on the device. - /// - /// # Arguments - /// - /// * `base`: base address on a MMIO bus - /// * `offset`: base address' offset - /// * `data`: a buffer provided by the caller to store the read data - fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]); - - /// Handle a write operation to the device. - /// - /// # Arguments - /// - /// * `base`: base address on a MMIO bus - /// * `offset`: base address' offset - /// * `data`: a buffer provided by the caller holding the data to write - fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]); -} - -/// Same as [DevicePio] but the methods are invoked with a mutable self borrow. -/// -/// # Example -/// ``` -/// # use vm_device::{MutDevicePio, bus::{PioAddress, PioAddressOffset}}; -/// struct DummyDevice { -/// config: u32, -/// } -/// -/// impl MutDevicePio for DummyDevice { -/// fn pio_read(&mut self, _base: PioAddress, _offset: PioAddressOffset, data: &mut [u8]) { -/// if data.len() > 4 { -/// return; -/// } -/// for (idx, iter) in data.iter_mut().enumerate() { -/// *iter = (self.config >> (idx * 8) & 0xff) as u8; -/// } -/// } -/// -/// fn pio_write(&mut self, _base: PioAddress, _offset: PioAddressOffset, data: &[u8]) { -/// self.config = u32::from(data[0]) & 0xff; -/// } -/// } -/// ``` -pub trait MutDevicePio { - /// Handle a read operation on the device. - /// - /// # Arguments - /// - /// * `base`: base address on a PIO bus - /// * `offset`: base address' offset - /// * `data`: a buffer provided by the caller to store the read data - fn pio_read(&mut self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]); +pub use virtio_mmio::VirtioMmioOffset; - /// Handle a write operation to the device. - /// - /// # Arguments - /// - /// * `base`: base address on a PIO bus - /// * `offset`: base address' offset - /// * `data`: a buffer provided by the caller holding the data to write - fn pio_write(&mut self, base: PioAddress, offset: PioAddressOffset, data: &[u8]); +pub trait VirtioMmioDevice { + fn virtio_mmio_read(&self, base: GuestAddress, offset: VirtioMmioOffset, data: &mut [u8]); + fn virtio_mmio_write(&self, base: GuestAddress, offset: VirtioMmioOffset, data: &[u8]); } -/// Same as [DeviceMmio] but the methods are invoked with a mutable self borrow. -/// # Example -/// ``` -/// # use vm_device::{MutDeviceMmio, bus::{MmioAddress, MmioAddressOffset}}; -/// struct DummyDevice { -/// config: u32, -/// } -/// -/// impl MutDeviceMmio for DummyDevice { -/// fn mmio_read(&mut self, _base: MmioAddress, _offset: MmioAddressOffset, data: &mut [u8]) { -/// if data.len() > 4 { -/// return; -/// } -/// for (idx, iter) in data.iter_mut().enumerate() { -/// *iter = (self.config >> (idx * 8) & 0xff) as u8; -/// } -/// } -/// -/// fn mmio_write(&mut self, _base: MmioAddress, _offset: MmioAddressOffset, data: &[u8]) { -/// self.config = u32::from(data[0]) & 0xff; -/// } -/// } -/// ``` -pub trait MutDeviceMmio { - /// Handle a read operation on the device. - /// - /// # Arguments - /// - /// * `base`: base address on a MMIO bus - /// * `offset`: base address' offset - /// * `data`: a buffer provided by the caller to store the read data - fn mmio_read(&mut self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]); - - /// Handle a write operation to the device. - /// - /// # Arguments - /// - /// * `base`: base address on a MMIO bus - /// * `offset`: base address' offset - /// * `data`: a buffer provided by the caller holding the data to write - fn mmio_write(&mut self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]); -} - -// Blanket implementations for Arc. - -impl DeviceMmio for Arc { - fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) { - self.deref().mmio_read(base, offset, data); - } - - fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) { - self.deref().mmio_write(base, offset, data); - } +pub trait MutVirtioMmioDevice { + fn virtio_mmio_read(&mut self, base: GuestAddress, offset: VirtioMmioOffset, data: &mut [u8]); + fn virtio_mmio_write(&mut self, base: GuestAddress, offset: VirtioMmioOffset, data: &[u8]); } -impl DevicePio for Arc { - fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) { - self.deref().pio_read(base, offset, data); - } - - fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) { - self.deref().pio_write(base, offset, data); - } -} - -// Blanket implementations for Mutex. - -impl DeviceMmio for Mutex { - fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) { - self.lock().unwrap().mmio_read(base, offset, data) +impl VirtioMmioDevice for Arc { + fn virtio_mmio_read(&self, base: GuestAddress, offset: VirtioMmioOffset, data: &mut [u8]) { + self.deref().virtio_mmio_read(base, offset, data); } - fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) { - self.lock().unwrap().mmio_write(base, offset, data) + fn virtio_mmio_write(&self, base: GuestAddress, offset: VirtioMmioOffset, data: &[u8]) { + self.deref().virtio_mmio_write(base, offset, data); } } -impl DevicePio for Mutex { - fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) { - self.lock().unwrap().pio_read(base, offset, data) +impl VirtioMmioDevice for Mutex { + fn virtio_mmio_read(&self, base: GuestAddress, offset: VirtioMmioOffset, data: &mut [u8]) { + self.lock().unwrap().virtio_mmio_read(base, offset, data) } - fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) { - self.lock().unwrap().pio_write(base, offset, data) + fn virtio_mmio_write(&self, base: GuestAddress, offset: VirtioMmioOffset, data: &[u8]) { + self.lock().unwrap().virtio_mmio_write(base, offset, data) } } diff --git a/src/resources.rs b/src/resources.rs index 1a79d7b..9b550e7 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -122,10 +122,8 @@ pub enum MsiIrqType { #[allow(missing_docs)] #[derive(Clone)] pub enum Resource { - /// IO Port address range. - PioAddressRange { base: u16, size: u16 }, /// Memory Mapped IO address range. - MmioAddressRange { base: u64, size: u64 }, + GuestAddressRange { base: u64, size: u64 }, /// Legacy IRQ number. LegacyIrq(u32), /// Message Signaled Interrupt @@ -155,22 +153,11 @@ impl DeviceResources { self.0.push(entry); } - /// Get the IO port address resources. - pub fn get_pio_address_ranges(&self) -> Vec<(u16, u16)> { - let mut vec = Vec::new(); - for entry in self.0.iter().as_ref() { - if let Resource::PioAddressRange { base, size } = entry { - vec.push((*base, *size)); - } - } - vec - } - /// Get the Memory Mapped IO address resources. pub fn get_mmio_address_ranges(&self) -> Vec<(u64, u64)> { let mut vec = Vec::new(); for entry in self.0.iter().as_ref() { - if let Resource::MmioAddressRange { base, size } = entry { + if let Resource::GuestAddressRange { base, size } = entry { vec.push((*base, *size)); } } @@ -244,191 +231,3 @@ impl DeviceResources { &self.0 } } - -#[cfg(test)] -mod tests { - use super::*; - - const PIO_ADDRESS_SIZE: u16 = 5; - const PIO_ADDRESS_BASE: u16 = 0; - const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321; - const MMIO_ADDRESS_BASE: u64 = 0x1234_5678; - const LEGACY_IRQ: u32 = 0x168; - const PCI_MSI_IRQ_SIZE: u32 = 0x8888; - const PCI_MSI_IRQ_BASE: u32 = 0x6666; - const PCI_MSIX_IRQ_SIZE: u32 = 0x16666; - const PCI_MSIX_IRQ_BASE: u32 = 0x8888; - const GENERIC_MSI_IRQS_SIZE: u32 = 0x16888; - const GENERIC_MSI_IRQS_BASE: u32 = 0x16688; - const MAC_ADDRESS: &str = "00:08:63:66:86:88"; - const KVM_SLOT_ID: u32 = 0x0100; - - fn get_device_resource() -> DeviceResources { - let entry = Resource::PioAddressRange { - base: PIO_ADDRESS_BASE, - size: PIO_ADDRESS_SIZE, - }; - let mut resource = DeviceResources::new(); - resource.append(entry); - let entry = Resource::MmioAddressRange { - base: MMIO_ADDRESS_BASE, - size: MMIO_ADDRESS_SIZE, - }; - resource.append(entry); - let entry = Resource::LegacyIrq(LEGACY_IRQ); - resource.append(entry); - let entry = Resource::MsiIrq { - ty: MsiIrqType::PciMsi, - base: PCI_MSI_IRQ_BASE, - size: PCI_MSI_IRQ_SIZE, - }; - resource.append(entry); - let entry = Resource::MsiIrq { - ty: MsiIrqType::PciMsix, - base: PCI_MSIX_IRQ_BASE, - size: PCI_MSIX_IRQ_SIZE, - }; - resource.append(entry); - let entry = Resource::MsiIrq { - ty: MsiIrqType::GenericMsi, - base: GENERIC_MSI_IRQS_BASE, - size: GENERIC_MSI_IRQS_SIZE, - }; - resource.append(entry); - let entry = Resource::MacAddresss(MAC_ADDRESS.to_string()); - resource.append(entry); - - resource.append(Resource::KvmMemSlot(KVM_SLOT_ID)); - - resource - } - - #[test] - fn get_pio_address_ranges() { - let resources = get_device_resource(); - assert!( - resources.get_pio_address_ranges()[0].0 == PIO_ADDRESS_BASE - && resources.get_pio_address_ranges()[0].1 == PIO_ADDRESS_SIZE - ); - } - - #[test] - fn test_get_mmio_address_ranges() { - let resources = get_device_resource(); - assert!( - resources.get_mmio_address_ranges()[0].0 == MMIO_ADDRESS_BASE - && resources.get_mmio_address_ranges()[0].1 == MMIO_ADDRESS_SIZE - ); - } - - #[test] - fn test_get_legacy_irq() { - let resources = get_device_resource(); - assert!(resources.get_legacy_irq().unwrap() == LEGACY_IRQ); - } - - #[test] - fn test_get_pci_msi_irqs() { - let resources = get_device_resource(); - assert!( - resources.get_pci_msi_irqs().unwrap().0 == PCI_MSI_IRQ_BASE - && resources.get_pci_msi_irqs().unwrap().1 == PCI_MSI_IRQ_SIZE - ); - } - - #[test] - fn test_pci_msix_irqs() { - let resources = get_device_resource(); - assert!( - resources.get_pci_msix_irqs().unwrap().0 == PCI_MSIX_IRQ_BASE - && resources.get_pci_msix_irqs().unwrap().1 == PCI_MSIX_IRQ_SIZE - ); - } - - #[test] - fn test_get_generic_msi_irqs() { - let resources = get_device_resource(); - assert!( - resources.get_generic_msi_irqs().unwrap().0 == GENERIC_MSI_IRQS_BASE - && resources.get_generic_msi_irqs().unwrap().1 == GENERIC_MSI_IRQS_SIZE - ); - } - - #[test] - fn test_get_mac_address() { - let resources = get_device_resource(); - assert_eq!(resources.get_mac_address().unwrap(), MAC_ADDRESS); - } - - #[test] - fn test_get_kvm_slot() { - let resources = get_device_resource(); - assert_eq!(resources.get_kvm_mem_slots(), vec![KVM_SLOT_ID]); - } - - #[test] - fn test_get_all_resources() { - let resources = get_device_resource(); - assert_eq!(resources.get_all_resources().len(), 8); - } - - #[test] - fn test_resource_constraint() { - if let ResourceConstraint::PioAddress { range, align, size } = - ResourceConstraint::new_pio(2) - { - assert_eq!(range, None); - assert_eq!(align, 1); - assert_eq!(size, 2); - } else { - panic!("Pio resource constraint is invalid."); - } - - if let ResourceConstraint::PioAddress { range, align, size } = - ResourceConstraint::pio_with_constraints(2, Some((15, 16)), 2) - { - assert_eq!(range, Some((15, 16))); - assert_eq!(align, 2); - assert_eq!(size, 2); - } else { - panic!("Pio resource constraint is invalid."); - } - - if let ResourceConstraint::MmioAddress { range, align, size } = - ResourceConstraint::new_mmio(0x2000) - { - assert_eq!(range, None); - assert_eq!(align, 0x1000); - assert_eq!(size, 0x2000); - } else { - panic!("Mmio resource constraint is invalid."); - } - - if let ResourceConstraint::MmioAddress { range, align, size } = - ResourceConstraint::mmio_with_constraints(0x2000, Some((0x0, 0x2000)), 0x2000) - { - assert_eq!(range, Some((0x0, 0x2000))); - assert_eq!(align, 0x2000); - assert_eq!(size, 0x2000); - } else { - 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."); - } - } -} diff --git a/src/virtio_mmio.rs b/src/virtio_mmio.rs new file mode 100644 index 0000000..8fff16c --- /dev/null +++ b/src/virtio_mmio.rs @@ -0,0 +1,109 @@ +/// MMIO address for virtio 1.2 devices +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(usize)] +pub enum VirtioMmioOffset { + MagicValue(u64) = 0x0, + VirtioVersion(u64) = 0x4, + DeviceId(u64) = 0x8, + VendorId(u64) = 0xc, + HostFeatures(u64) = 0x10, + HostFeaturesSel(u64) = 0x14, + GuestFeatures(u64) = 0x20, + GuestFeaturesSel(u64) = 0x24, + QueueSel(u64) = 0x30, + QueueNumMax(u64) = 0x34, + QueueNum(u64) = 0x38, + QueueReady(u64) = 0x44, + QueueNotify(u64) = 0x50, + InterruptStatus(u64) = 0x60, + InterruptAck(u64) = 0x64, + Status(u64) = 0x70, + QueueDescLow(u64) = 0x80, + QueueDescHigh(u64) = 0x84, + QueueDriverLow(u64) = 0x90, + QueueDriverHigh(u64) = 0x94, + QueueDeviceLow(u64) = 0xa0, + QueueDeviceHigh(u64) = 0xa4, + SHMSel(u64) = 0xac, + SHMLenLow(u64) = 0xb0, + SHMLenHigh(u64) = 0xb4, + SHMBaseLow(u64) = 0xb8, + SHMBaseHigh(u64) = 0xbc, + QueueReset(u64) = 0xc0, + ConfigGeneration(u64) = 0x0fc, + DeviceSpecific(u64) = 0x100, +} + +impl From for u64 { + fn from(value: VirtioMmioOffset) -> Self { + match value { + VirtioMmioOffset::MagicValue(value) => value, + VirtioMmioOffset::VirtioVersion(value) => value + 0x4, + VirtioMmioOffset::DeviceId(value) => value + 0x8, + VirtioMmioOffset::VendorId(value) => value + 0xc, + VirtioMmioOffset::HostFeatures(value) => value + 0x10, + VirtioMmioOffset::HostFeaturesSel(value) => value + 0x14, + VirtioMmioOffset::GuestFeatures(value) => value + 0x20, + VirtioMmioOffset::GuestFeaturesSel(value) => value + 0x24, + VirtioMmioOffset::QueueSel(value) => value + 0x30, + VirtioMmioOffset::QueueNumMax(value) => value + 0x34, + VirtioMmioOffset::QueueNum(value) => value + 0x38, + VirtioMmioOffset::QueueReady(value) => value + 0x44, + VirtioMmioOffset::QueueNotify(value) => value + 0x50, + VirtioMmioOffset::InterruptStatus(value) => value + 0x60, + VirtioMmioOffset::InterruptAck(value) => value + 0x64, + VirtioMmioOffset::Status(value) => value + 0x70, + VirtioMmioOffset::QueueDescLow(value) => value + 0x80, + VirtioMmioOffset::QueueDescHigh(value) => value + 0x84, + VirtioMmioOffset::QueueDriverLow(value) => value + 0x90, + VirtioMmioOffset::QueueDriverHigh(value) => value + 0x94, + VirtioMmioOffset::QueueDeviceLow(value) => value + 0xa0, + VirtioMmioOffset::QueueDeviceHigh(value) => value + 0xa4, + VirtioMmioOffset::SHMSel(value) => value + 0xac, + VirtioMmioOffset::SHMLenLow(value) => value + 0xb0, + VirtioMmioOffset::SHMLenHigh(value) => value + 0xb4, + VirtioMmioOffset::SHMBaseLow(value) => value + 0xb8, + VirtioMmioOffset::SHMBaseHigh(value) => value + 0xbc, + VirtioMmioOffset::QueueReset(value) => value + 0xc0, + VirtioMmioOffset::ConfigGeneration(value) => value + 0x0fc, + VirtioMmioOffset::DeviceSpecific(value) => value + 0x100, + } + } +} + +impl From for VirtioMmioOffset { + fn from(value: u64) -> Self { + match value { + 0x0..=0x3 => VirtioMmioOffset::MagicValue(value), + 0x4..=0x7 => VirtioMmioOffset::VirtioVersion(value - 0x4), + 0x8..=0xb => VirtioMmioOffset::DeviceId(value - 0x8), + 0xc..=0xf => VirtioMmioOffset::VendorId(value - 0xc), + 0x10..=0x13 => VirtioMmioOffset::HostFeatures(value - 0x10), + 0x14..=0x1f => VirtioMmioOffset::HostFeaturesSel(value - 0x14), + 0x20..=0x23 => VirtioMmioOffset::GuestFeatures(value - 0x20), + 0x24..=0x2f => VirtioMmioOffset::GuestFeaturesSel(value - 0x24), + 0x30..=0x33 => VirtioMmioOffset::QueueSel(value - 0x30), + 0x34..=0x37 => VirtioMmioOffset::QueueNumMax(value - 0x34), + 0x38..=0x43 => VirtioMmioOffset::QueueNum(value - 0x38), + 0x44..=0x4f => VirtioMmioOffset::QueueReady(value - 0x44), + 0x50..=0x5f => VirtioMmioOffset::QueueNotify(value - 0x50), + 0x60..=0x63 => VirtioMmioOffset::InterruptStatus(value - 0x60), + 0x64..=0x6f => VirtioMmioOffset::InterruptAck(value - 0x64), + 0x70..=0x7f => VirtioMmioOffset::Status(value - 0x70), + 0x80..=0x83 => VirtioMmioOffset::QueueDescLow(value - 0x80), + 0x84..=0x8f => VirtioMmioOffset::QueueDescHigh(value - 0x84), + 0x90..=0x93 => VirtioMmioOffset::QueueDriverLow(value - 0x90), + 0x94..=0x9f => VirtioMmioOffset::QueueDriverHigh(value - 0x94), + 0xa0..=0xa3 => VirtioMmioOffset::QueueDeviceLow(value - 0xa0), + 0xa4..=0xab => VirtioMmioOffset::QueueDeviceHigh(value - 0xa4), + 0xac..=0xaf => VirtioMmioOffset::SHMSel(value - 0xac), + 0xb0..=0xb3 => VirtioMmioOffset::SHMLenLow(value - 0xb0), + 0xb4..=0xb7 => VirtioMmioOffset::SHMLenHigh(value - 0xb4), + 0xb8..=0xbb => VirtioMmioOffset::SHMBaseLow(value - 0xb8), + 0xbc..=0xbf => VirtioMmioOffset::SHMBaseHigh(value - 0xbc), + 0xc0..=0xfb => VirtioMmioOffset::QueueReset(value - 0xc0), + 0xfc..=0xff => VirtioMmioOffset::ConfigGeneration(value - 0x0fc), + _ => VirtioMmioOffset::DeviceSpecific(value - 0x100), + } + } +}