├── .gitignore ├── CODEOWNERS ├── .gitmodules ├── coverage_config_aarch64.json ├── coverage_config_x86_64.json ├── .cargo └── config ├── .github └── dependabot.yml ├── CHANGELOG.md ├── Cargo.toml ├── src ├── bus │ ├── address.rs │ ├── range.rs │ └── mod.rs ├── lib.rs ├── resources.rs └── device_manager.rs ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | kcov_output/ 5 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Add the list of code owners here (using their GitHub username) 2 | * @alexandruag @gsserge @jiangliu 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rust-vmm-ci"] 2 | path = rust-vmm-ci 3 | url = https://github.com/rust-vmm/rust-vmm-ci.git 4 | -------------------------------------------------------------------------------- /coverage_config_aarch64.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage_score": 80.5, 3 | "exclude_path": "", 4 | "crate_features": "" 5 | } 6 | -------------------------------------------------------------------------------- /coverage_config_x86_64.json: -------------------------------------------------------------------------------- 1 | { 2 | "coverage_score": 92.2, 3 | "exclude_path": "", 4 | "crate_features": "" 5 | } 6 | -------------------------------------------------------------------------------- /.cargo/config: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-musl] 2 | rustflags = [ "-C", "target-feature=+crt-static", "-C", "link-arg=-lgcc" ] 3 | 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gitsubmodule 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.1.0 4 | 5 | This is the first `vm-device` release. 6 | The `vm-device` crate provides device traits defining read and write operations 7 | on specialized buses, a device manager for operating devices and dispatching 8 | I/O, and abstractions for defining resources and their constraints (e.g. a 9 | specific bus address range, IRQ number, etc.). 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vm-device" 3 | version = "0.1.0" 4 | authors = ["Samuel Ortiz ", "Liu Jiang ", "rust-vmm AWS maintainers "] 5 | description = "management for virtual devices and resources" 6 | keywords = ["bus", "manager", "virtualization"] 7 | edition = "2018" 8 | repository = "https://github.com/rust-vmm/vm-device" 9 | license = "Apache-2.0 OR BSD-3-Clause" 10 | 11 | [dependencies] 12 | -------------------------------------------------------------------------------- /src/bus/address.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::cmp::Ordering; 5 | use std::convert::TryFrom; 6 | use std::ops::{Add, Sub}; 7 | 8 | /// This trait defines the operations we expect to apply to bus address values. 9 | pub trait BusAddress: 10 | Add<::V, Output = Self> 11 | + Copy 12 | + Eq 13 | + Ord 14 | + Sub::V> 15 | { 16 | /// Defines the underlying value type of the `BusAddress`. 17 | type V: Add 18 | + Copy 19 | + From 20 | + PartialEq 21 | + Ord 22 | + Sub 23 | + TryFrom; 24 | 25 | /// Return the inner value. 26 | fn value(&self) -> Self::V; 27 | 28 | /// Return the bus address computed by offsetting `self` by the specified value, if no 29 | /// overflow occurs. 30 | fn checked_add(&self, value: Self::V) -> Option; 31 | } 32 | 33 | /// Represents a MMIO address offset. 34 | pub type MmioAddressOffset = u64; 35 | 36 | /// Represents a MMIO address. 37 | #[derive(Clone, Copy, Debug)] 38 | pub struct MmioAddress(pub MmioAddressOffset); 39 | 40 | /// Represents a PIO address offset. 41 | pub type PioAddressOffset = u16; 42 | 43 | /// Represents a PIO address. 44 | #[derive(Clone, Copy, Debug)] 45 | pub struct PioAddress(pub PioAddressOffset); 46 | 47 | // Implementing `BusAddress` and its prerequisites for `MmioAddress`. 48 | 49 | impl PartialEq for MmioAddress { 50 | fn eq(&self, other: &Self) -> bool { 51 | self.0 == other.0 52 | } 53 | } 54 | 55 | impl Eq for MmioAddress {} 56 | 57 | impl PartialOrd for MmioAddress { 58 | fn partial_cmp(&self, other: &Self) -> Option { 59 | self.0.partial_cmp(&other.0) 60 | } 61 | } 62 | 63 | impl Ord for MmioAddress { 64 | fn cmp(&self, other: &Self) -> Ordering { 65 | self.0.cmp(&other.0) 66 | } 67 | } 68 | 69 | impl Add for MmioAddress { 70 | type Output = Self; 71 | 72 | fn add(self, rhs: MmioAddressOffset) -> Self::Output { 73 | MmioAddress(self.0 + rhs) 74 | } 75 | } 76 | 77 | impl Sub for MmioAddress { 78 | type Output = MmioAddressOffset; 79 | 80 | fn sub(self, rhs: Self) -> Self::Output { 81 | self.0 - rhs.0 82 | } 83 | } 84 | 85 | impl BusAddress for MmioAddress { 86 | type V = MmioAddressOffset; 87 | 88 | fn value(&self) -> Self::V { 89 | self.0 90 | } 91 | 92 | fn checked_add(&self, value: Self::V) -> Option { 93 | self.0.checked_add(value).map(MmioAddress) 94 | } 95 | } 96 | 97 | // Implementing `BusAddress` and its prerequisites for `PioAddress`. 98 | 99 | impl PartialEq for PioAddress { 100 | fn eq(&self, other: &Self) -> bool { 101 | self.0 == other.0 102 | } 103 | } 104 | 105 | impl Eq for PioAddress {} 106 | 107 | impl PartialOrd for PioAddress { 108 | fn partial_cmp(&self, other: &Self) -> Option { 109 | self.0.partial_cmp(&other.0) 110 | } 111 | } 112 | 113 | impl Ord for PioAddress { 114 | fn cmp(&self, other: &Self) -> Ordering { 115 | self.0.cmp(&other.0) 116 | } 117 | } 118 | 119 | impl Add for PioAddress { 120 | type Output = Self; 121 | 122 | fn add(self, rhs: PioAddressOffset) -> Self::Output { 123 | PioAddress(self.0 + rhs) 124 | } 125 | } 126 | 127 | impl Sub for PioAddress { 128 | type Output = PioAddressOffset; 129 | 130 | fn sub(self, rhs: Self) -> Self::Output { 131 | self.0 - rhs.0 132 | } 133 | } 134 | 135 | impl BusAddress for PioAddress { 136 | type V = PioAddressOffset; 137 | 138 | fn value(&self) -> Self::V { 139 | self.0 140 | } 141 | 142 | fn checked_add(&self, value: Self::V) -> Option { 143 | self.0.checked_add(value).map(PioAddress) 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use super::*; 150 | 151 | use std::fmt::Debug; 152 | 153 | // `addr_zero` should be an address equivalent to 0, while `max_value` should contain the 154 | // maximum possible address value. 155 | fn check_bus_address_ops(addr_zero: A, max_value: A::V) 156 | where 157 | A: BusAddress + Debug, 158 | A::V: Debug, 159 | { 160 | let value = A::V::from(5); 161 | let addr = addr_zero + value; 162 | 163 | assert!(addr_zero < addr); 164 | assert_eq!(addr - addr_zero, value); 165 | 166 | assert_eq!(addr.value(), value); 167 | assert_eq!(addr_zero.checked_add(value).unwrap(), addr); 168 | 169 | let addr_max = addr_zero.checked_add(max_value).unwrap(); 170 | assert!(addr_max.checked_add(A::V::from(1)).is_none()); 171 | } 172 | 173 | #[test] 174 | fn test_address_ops() { 175 | check_bus_address_ops(MmioAddress(0), std::u64::MAX); 176 | check_bus_address_ops(PioAddress(0), std::u16::MAX); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vm-device 2 | 3 | The `vm-device` crate provides: 4 | * device traits defining read and write operations on specialized buses 5 | * device manager (bus-specific traits and a concrete implementation) for 6 | operating devices and dispatching I/O 7 | * abstractions for defining resources and their constraints (e.g. a specific bus 8 | address range, IRQ number, etc) 9 | 10 | ## Design 11 | 12 | The virtual device model is built around four traits, `DevicePio` and 13 | `MutDevicePio` for 14 | [Programmed I/O](https://en.wikipedia.org/wiki/Programmed_input%E2%80%93output) 15 | (PIO), and `DeviceMmio` and `MutDeviceMmio` for 16 | [Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O) 17 | (MMIO). The traits define the same methods for handling read and write 18 | operations. The difference is that `DevicePio` and `DeviceMmio` only require 19 | immutable self borrows, whereas `MutDevicePio` and `MutDeviceMmio` require 20 | mutable borrows. 21 | 22 | The device manager abstraction is implemented by the `IoManager` struct. It 23 | defines two buses, one for PIO and one for MMIO. For each bus, with the help of 24 | the `PioManager` and `MmioManager` traits, the manager provides methods for 25 | device registration, as well as for dispatching read and write requests. 26 | The manager will determine which device is responsible for handling the I/O 27 | request based on the accessed address range, and will route the request to that 28 | device. 29 | 30 | `PioManager` and `MmioManager` traits are useful as interfaces for a couple of 31 | reasons. First, to allow for alternative implementations when the provided 32 | `IoManager` is not sufficient. Second, to allow other crates depend on the 33 | traits without depending on the actual implementation. 34 | 35 | The interaction between a guest kernel, a host kernel and a VMM using 36 | `IoManager` is depicted at the diagram below. A driver in the guest kernel 37 | issues an I/O request. That request gets turned by the host kernel’s hypervisor 38 | (KVM) into a trigger for the VMM to handle. The trigger is either a vCPU exit or 39 | an eventfd notification. The VMM extracts address information and determines the 40 | target bus. Then it dispatches a new request to the `IoManager`, which checks 41 | that there’s a virtual device registered on the bus for the requested address, 42 | and finally sends the request to that device. 43 | ![vm-device](https://user-images.githubusercontent.com/241037/143853115-b1526028-6836-4845-a311-71cf989c60ef.png) 44 | 45 | ## Usage 46 | 47 | A device is usually attached to a particular bus and thus needs to implement a 48 | trait of only one type. For example, serial port on x86 is a PIO device, while 49 | VirtIO devices use MMIO. It’s also possible for a device to implement both. Once 50 | the type of I/O is determined, the next step is to choose between mutable and 51 | immutable trait variant. If read or write method needs to mutate the device’s 52 | internal state, then the mutable variant must be used. 53 | 54 | Before dispatching any I/O requests to the new device, it needs to be registered 55 | with an `IoManager` instance within the specified address range on the bus. 56 | Creating a new `IoManager` is easy by calling `IoManager::new()` without any 57 | configuration. Internally the manager stores devices as trait objects wrapped 58 | in `Arc`’s, therefore if the device implements `MutDevicePio` or 59 | `MutDeviceMmio`, then it must be wrapped in a `Mutex`. The crate contains 60 | automatic implementation of `DevicePio for Mutex where T: MutDevicePio` 61 | and `DeviceMmio for Mutex where T: MutDeviceMmio` but only for the Mutex 62 | type in the standard library. For any other `Mutex` type from 3rd party crates 63 | the blanket implementation must be done by the user. 64 | 65 | From now on the IoManager will be routing I/O requests for the registered 66 | address range to the device. The requests are dispatched by the client code, for 67 | example when handling VM exits, using `IoManager`'s methods `pio_read`, 68 | `pio_write`, `mmio_read` and `mmio_write`. 69 | 70 | ## Examples 71 | 72 | ### Implementing a simple log PIO device 73 | 74 | ```rust 75 | use std::sync::{Arc, Mutex}; 76 | use vm_device::bus::{PioAddress, PioAddressOffset, PioRange}; 77 | use vm_device::device_manager::{IoManager, PioManager}; 78 | use vm_device::MutDevicePio; 79 | 80 | struct LogDevice {} 81 | 82 | impl MutDevicePio for LogDevice { 83 | fn pio_read(&mut self, base: PioAddress, offset: PioAddressOffset, _data: &mut [u8]) { 84 | println!("mut pio_read: base {:?}, offset {}", base, offset); 85 | } 86 | 87 | fn pio_write(&mut self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) { 88 | println!( 89 | "mut pio_write: base {:?}, offset {}, data {:?}", 90 | base, offset, data 91 | ); 92 | } 93 | } 94 | ``` 95 | 96 | ### Registering the device with IoManager and performing I/O 97 | 98 | ```rust 99 | fn main() { 100 | let mut manager = IoManager::new(); 101 | let device = LogDevice {}; 102 | let bus_range = PioRange::new(PioAddress(0), 10).unwrap(); 103 | manager 104 | .register_pio(bus_range, Arc::new(Mutex::new(device))) 105 | .unwrap(); 106 | manager.pio_write(PioAddress(0), &vec![b'o', b'k']).unwrap(); 107 | } 108 | ``` 109 | 110 | ## Testing 111 | 112 | The `vm-device` is tested using unit tests and integration tests. 113 | It leverages [`rust-vmm-ci`](https://github.com/rust-vmm/rust-vmm-ci) 114 | for continuous testing. All tests are ran in the `rustvmm/dev` container. 115 | 116 | ## License 117 | 118 | This project is licensed under either of: 119 | 120 | - [Apache License](http://www.apache.org/licenses/LICENSE-2.0), Version 2.0 121 | - [BSD-3-Clause License](https://opensource.org/licenses/BSD-3-Clause) 122 | -------------------------------------------------------------------------------- /src/bus/range.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | use std::cmp::Ordering; 5 | 6 | use crate::bus::{BusAddress, Error, MmioAddress, PioAddress}; 7 | 8 | /// An interval in the address space of a bus. 9 | #[derive(Copy, Clone, Debug)] 10 | pub struct BusRange { 11 | base: A, 12 | size: A::V, 13 | } 14 | 15 | impl BusRange { 16 | /// Create a new range while checking for overflow. 17 | pub fn new(base: A, size: A::V) -> Result { 18 | // A zero-length range is not valid. 19 | if size == 0.into() { 20 | return Err(Error::InvalidRange); 21 | } 22 | 23 | // Subtracting one, because a range that ends at the very edge of the address space 24 | // is still valid. 25 | base.checked_add(size - 1.into()) 26 | .ok_or(Error::InvalidRange)?; 27 | 28 | Ok(BusRange { base, size }) 29 | } 30 | 31 | /// Create a new unit range (its size equals `1`). 32 | pub fn unit(base: A) -> Self { 33 | BusRange { 34 | base, 35 | size: 1.into(), 36 | } 37 | } 38 | 39 | /// Return the base address of this range. 40 | pub fn base(&self) -> A { 41 | self.base 42 | } 43 | 44 | /// Return the size of the range. 45 | pub fn size(&self) -> A::V { 46 | self.size 47 | } 48 | 49 | /// Return the last bus address that's still part of the range. 50 | pub fn last(&self) -> A { 51 | self.base + (self.size - 1.into()) 52 | } 53 | 54 | /// Check whether `self` and `other` overlap as intervals. 55 | pub fn overlaps(&self, other: &BusRange) -> bool { 56 | !(self.base > other.last() || self.last() < other.base) 57 | } 58 | } 59 | 60 | // We need to implement the following traits so we can use `BusRange` values with `BTreeMap`s. 61 | // This usage scenario requires treating ranges as if they supported a total order, but that's 62 | // not really possible with intervals, so we write the implementations as if `BusRange`s were 63 | // solely determined by their base addresses, and apply extra checks in the `Bus` logic. 64 | 65 | impl PartialEq for BusRange { 66 | fn eq(&self, other: &BusRange) -> bool { 67 | self.base == other.base 68 | } 69 | } 70 | 71 | impl Eq for BusRange {} 72 | 73 | impl PartialOrd for BusRange { 74 | fn partial_cmp(&self, other: &BusRange) -> Option { 75 | self.base.partial_cmp(&other.base) 76 | } 77 | } 78 | 79 | impl Ord for BusRange { 80 | fn cmp(&self, other: &BusRange) -> Ordering { 81 | self.base.cmp(&other.base) 82 | } 83 | } 84 | 85 | /// Represents an MMIO bus range. 86 | pub type MmioRange = BusRange; 87 | /// Represents a PIO bus range. 88 | pub type PioRange = BusRange; 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | #[test] 95 | fn test_bus_range() { 96 | let base_zero = MmioAddress(0); 97 | let value = 5; 98 | 99 | assert_eq!(BusRange::new(base_zero, 0), Err(Error::InvalidRange)); 100 | 101 | assert!(BusRange::new(base_zero, std::u64::MAX).is_ok()); 102 | assert!(BusRange::new(MmioAddress(1), std::u64::MAX).is_ok()); 103 | assert_eq!( 104 | BusRange::new(MmioAddress(2), std::u64::MAX), 105 | Err(Error::InvalidRange) 106 | ); 107 | 108 | { 109 | let range = BusRange::new(base_zero, value).unwrap(); 110 | assert_eq!(range.base(), base_zero); 111 | assert_eq!(range.size(), value); 112 | assert_eq!(range.last(), MmioAddress(value - 1)); 113 | assert!(range.base() < range.last()); 114 | } 115 | 116 | { 117 | let range = BusRange::unit(base_zero); 118 | assert_eq!(range.base(), base_zero); 119 | assert_eq!(range.last(), range.base()); 120 | } 121 | 122 | // Let's test `BusRange::overlaps`. 123 | { 124 | let range = BusRange::new(MmioAddress(10), 10).unwrap(); 125 | 126 | let overlaps = |base_value, len_value| { 127 | range.overlaps(&BusRange::new(MmioAddress(base_value), len_value).unwrap()) 128 | }; 129 | 130 | assert!(!overlaps(0, 5)); 131 | assert!(!overlaps(0, 10)); 132 | assert!(!overlaps(5, 5)); 133 | 134 | assert!(overlaps(0, 11)); 135 | assert!(overlaps(5, 6)); 136 | assert!(overlaps(5, 10)); 137 | assert!(overlaps(11, 15)); 138 | assert!(overlaps(5, 35)); 139 | assert!(overlaps(19, 1)); 140 | assert!(overlaps(19, 10)); 141 | 142 | assert!(!overlaps(20, 1)); 143 | assert!(!overlaps(30, 10)); 144 | } 145 | 146 | // Finally, let's test the `BusRange` trait implementations that we added. 147 | { 148 | let base = MmioAddress(10); 149 | let len = 10; 150 | 151 | let range = BusRange::new(base, len).unwrap(); 152 | 153 | assert_eq!(range.cmp(&range), range.partial_cmp(&range).unwrap()); 154 | assert_eq!(range.cmp(&range), Ordering::Equal); 155 | 156 | { 157 | let other = BusRange::new(base, len + 1).unwrap(); 158 | 159 | // Still equal becase we're only comparing `base` values as part 160 | // of the `eq` implementation. 161 | assert_eq!(range, other); 162 | 163 | assert_eq!(range.cmp(&other), range.partial_cmp(&other).unwrap()); 164 | assert_eq!(range.cmp(&other), Ordering::Equal); 165 | } 166 | 167 | { 168 | let other = BusRange::unit(base.checked_add(1).unwrap()); 169 | 170 | // Different due to different base addresses. 171 | assert_ne!(range, other); 172 | 173 | assert_eq!(range.cmp(&other), range.partial_cmp(&other).unwrap()); 174 | assert_eq!(range.cmp(&other), Ordering::Less); 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Intel Corporation. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | #![deny(missing_docs)] 5 | 6 | //! This crate provides: 7 | //! * device traits defining read and write operations on specialized buses 8 | //! * device manager (bus-specific traits and a concrete implementation) for 9 | //! operating devices and dispatching I/O 10 | //! * abstractions for defining resources and their constraints (e.g. a specific bus 11 | //! address range, IRQ number, etc) 12 | //! 13 | //! [`MutDevicePio`] and [`MutDeviceMmio`] traits help with composite inner mutability 14 | //! (i.e. if we have a `Mutex` that holds a `T` which implements [`MutDevicePio`], 15 | //! then the `Mutex` can implement [`DevicePio`] based on its inner 16 | //! mutability properties). 17 | //! 18 | //! # Example 19 | //! 20 | //! Implement a simple log PIO device, register it with 21 | //! [`IoManager`](device_manager/struct.IoManager.html) 22 | //! and dispatch a write operation to the device. 23 | //!``` 24 | //! use std::sync::{Arc, Mutex}; 25 | //! use vm_device::bus::{PioAddress, PioAddressOffset, PioRange}; 26 | //! use vm_device::device_manager::{IoManager, PioManager}; 27 | //! use vm_device::MutDevicePio; 28 | //! 29 | //! struct LogDevice {} 30 | //! 31 | //! impl MutDevicePio for LogDevice { 32 | //! fn pio_read(&mut self, base: PioAddress, offset: PioAddressOffset, _data: &mut [u8]) { 33 | //! println!("mut pio_read: base {:?}, offset {}", base, offset); 34 | //! } 35 | //! fn pio_write(&mut self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) { 36 | //! println!( 37 | //! "mut pio_write: base {:?}, offset {}, data {:?}", 38 | //! base, offset, data 39 | //! ); 40 | //! } 41 | //! } 42 | //! 43 | //! // IoManager implements PioManager trait. 44 | //! let mut manager = IoManager::new(); 45 | //! let device = LogDevice {}; 46 | //! let bus_range = PioRange::new(PioAddress(0), 10).unwrap(); 47 | //! manager 48 | //! .register_pio(bus_range, Arc::new(Mutex::new(device))) 49 | //! .unwrap(); 50 | //! manager.pio_write(PioAddress(0), &vec![b'o', b'k']).unwrap(); 51 | //! ``` 52 | 53 | pub mod bus; 54 | pub mod device_manager; 55 | pub mod resources; 56 | 57 | use std::ops::Deref; 58 | use std::sync::{Arc, Mutex}; 59 | 60 | use bus::{MmioAddress, MmioAddressOffset, PioAddress, PioAddressOffset}; 61 | 62 | /// Allows a device to be attached to a 63 | /// [PIO](https://en.wikipedia.org/wiki/Programmed_input%E2%80%93output) bus. 64 | /// 65 | /// # Example 66 | /// ``` 67 | /// # use std::sync::Mutex; 68 | /// # use vm_device::{DevicePio, bus::{PioAddress, PioAddressOffset}}; 69 | /// struct DummyDevice { 70 | /// config: Mutex, 71 | /// } 72 | /// 73 | /// impl DevicePio for DummyDevice { 74 | /// fn pio_read(&self, _base: PioAddress, _offset: PioAddressOffset, data: &mut [u8]) { 75 | /// if data.len() > 4 { 76 | /// return; 77 | /// } 78 | /// for (idx, iter) in data.iter_mut().enumerate() { 79 | /// let config = self.config.lock().expect("failed to acquire lock"); 80 | /// *iter = (*config >> (idx * 8) & 0xff) as u8; 81 | /// } 82 | /// } 83 | /// 84 | /// fn pio_write(&self, _base: PioAddress, _offset: PioAddressOffset, data: &[u8]) { 85 | /// let mut config = self.config.lock().expect("failed to acquire lock"); 86 | /// *config = u32::from(data[0]) & 0xff; 87 | /// } 88 | /// } 89 | /// ``` 90 | pub trait DevicePio { 91 | /// Handle a read operation on the device. 92 | /// 93 | /// # Arguments 94 | /// 95 | /// * `base`: base address on a PIO bus 96 | /// * `offset`: base address' offset 97 | /// * `data`: a buffer provided by the caller to store the read data 98 | fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]); 99 | 100 | /// Handle a write operation to the device. 101 | /// 102 | /// # Arguments 103 | /// 104 | /// * `base`: base address on a PIO bus 105 | /// * `offset`: base address' offset 106 | /// * `data`: a buffer provided by the caller holding the data to write 107 | fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]); 108 | } 109 | 110 | /// Allows a device to be attached to a 111 | /// [MMIO](https://en.wikipedia.org/wiki/Memory-mapped_I/O) bus. 112 | /// 113 | /// # Example 114 | /// ``` 115 | /// # use std::sync::Mutex; 116 | /// # use vm_device::{DeviceMmio, bus::{MmioAddress, MmioAddressOffset}}; 117 | /// struct DummyDevice { 118 | /// config: Mutex, 119 | /// } 120 | /// 121 | /// impl DeviceMmio for DummyDevice { 122 | /// fn mmio_read(&self, _base: MmioAddress, _offset: MmioAddressOffset, data: &mut [u8]) { 123 | /// if data.len() > 4 { 124 | /// return; 125 | /// } 126 | /// for (idx, iter) in data.iter_mut().enumerate() { 127 | /// let config = self.config.lock().expect("failed to acquire lock"); 128 | /// *iter = (*config >> (idx * 8) & 0xff) as u8; 129 | /// } 130 | /// } 131 | /// 132 | /// fn mmio_write(&self, _base: MmioAddress, _offset: MmioAddressOffset, data: &[u8]) { 133 | /// let mut config = self.config.lock().expect("failed to acquire lock"); 134 | /// *config = u32::from(data[0]) & 0xff; 135 | /// } 136 | /// } 137 | /// ``` 138 | pub trait DeviceMmio { 139 | /// Handle a read operation on the device. 140 | /// 141 | /// # Arguments 142 | /// 143 | /// * `base`: base address on a MMIO bus 144 | /// * `offset`: base address' offset 145 | /// * `data`: a buffer provided by the caller to store the read data 146 | fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]); 147 | 148 | /// Handle a write operation to the device. 149 | /// 150 | /// # Arguments 151 | /// 152 | /// * `base`: base address on a MMIO bus 153 | /// * `offset`: base address' offset 154 | /// * `data`: a buffer provided by the caller holding the data to write 155 | fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]); 156 | } 157 | 158 | /// Same as [DevicePio] but the methods are invoked with a mutable self borrow. 159 | /// 160 | /// # Example 161 | /// ``` 162 | /// # use vm_device::{MutDevicePio, bus::{PioAddress, PioAddressOffset}}; 163 | /// struct DummyDevice { 164 | /// config: u32, 165 | /// } 166 | /// 167 | /// impl MutDevicePio for DummyDevice { 168 | /// fn pio_read(&mut self, _base: PioAddress, _offset: PioAddressOffset, data: &mut [u8]) { 169 | /// if data.len() > 4 { 170 | /// return; 171 | /// } 172 | /// for (idx, iter) in data.iter_mut().enumerate() { 173 | /// *iter = (self.config >> (idx * 8) & 0xff) as u8; 174 | /// } 175 | /// } 176 | /// 177 | /// fn pio_write(&mut self, _base: PioAddress, _offset: PioAddressOffset, data: &[u8]) { 178 | /// self.config = u32::from(data[0]) & 0xff; 179 | /// } 180 | /// } 181 | /// ``` 182 | pub trait MutDevicePio { 183 | /// Handle a read operation on the device. 184 | /// 185 | /// # Arguments 186 | /// 187 | /// * `base`: base address on a PIO bus 188 | /// * `offset`: base address' offset 189 | /// * `data`: a buffer provided by the caller to store the read data 190 | fn pio_read(&mut self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]); 191 | 192 | /// Handle a write operation to the device. 193 | /// 194 | /// # Arguments 195 | /// 196 | /// * `base`: base address on a PIO bus 197 | /// * `offset`: base address' offset 198 | /// * `data`: a buffer provided by the caller holding the data to write 199 | fn pio_write(&mut self, base: PioAddress, offset: PioAddressOffset, data: &[u8]); 200 | } 201 | 202 | /// Same as [DeviceMmio] but the methods are invoked with a mutable self borrow. 203 | /// # Example 204 | /// ``` 205 | /// # use vm_device::{MutDeviceMmio, bus::{MmioAddress, MmioAddressOffset}}; 206 | /// struct DummyDevice { 207 | /// config: u32, 208 | /// } 209 | /// 210 | /// impl MutDeviceMmio for DummyDevice { 211 | /// fn mmio_read(&mut self, _base: MmioAddress, _offset: MmioAddressOffset, data: &mut [u8]) { 212 | /// if data.len() > 4 { 213 | /// return; 214 | /// } 215 | /// for (idx, iter) in data.iter_mut().enumerate() { 216 | /// *iter = (self.config >> (idx * 8) & 0xff) as u8; 217 | /// } 218 | /// } 219 | /// 220 | /// fn mmio_write(&mut self, _base: MmioAddress, _offset: MmioAddressOffset, data: &[u8]) { 221 | /// self.config = u32::from(data[0]) & 0xff; 222 | /// } 223 | /// } 224 | /// ``` 225 | pub trait MutDeviceMmio { 226 | /// Handle a read operation on the device. 227 | /// 228 | /// # Arguments 229 | /// 230 | /// * `base`: base address on a MMIO bus 231 | /// * `offset`: base address' offset 232 | /// * `data`: a buffer provided by the caller to store the read data 233 | fn mmio_read(&mut self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]); 234 | 235 | /// Handle a write operation to the device. 236 | /// 237 | /// # Arguments 238 | /// 239 | /// * `base`: base address on a MMIO bus 240 | /// * `offset`: base address' offset 241 | /// * `data`: a buffer provided by the caller holding the data to write 242 | fn mmio_write(&mut self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]); 243 | } 244 | 245 | // Blanket implementations for Arc. 246 | 247 | impl DeviceMmio for Arc { 248 | fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) { 249 | self.deref().mmio_read(base, offset, data); 250 | } 251 | 252 | fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) { 253 | self.deref().mmio_write(base, offset, data); 254 | } 255 | } 256 | 257 | impl DevicePio for Arc { 258 | fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) { 259 | self.deref().pio_read(base, offset, data); 260 | } 261 | 262 | fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) { 263 | self.deref().pio_write(base, offset, data); 264 | } 265 | } 266 | 267 | // Blanket implementations for Mutex. 268 | 269 | impl DeviceMmio for Mutex { 270 | fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) { 271 | self.lock().unwrap().mmio_read(base, offset, data) 272 | } 273 | 274 | fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) { 275 | self.lock().unwrap().mmio_write(base, offset, data) 276 | } 277 | } 278 | 279 | impl DevicePio for Mutex { 280 | fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) { 281 | self.lock().unwrap().pio_read(base, offset, data) 282 | } 283 | 284 | fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) { 285 | self.lock().unwrap().pio_write(base, offset, data) 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /src/bus/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause 3 | 4 | //! Provides abstractions for modelling an I/O bus. 5 | //! 6 | //! A bus is seen here as a mapping between 7 | //! disjoint intervals (ranges) from an address space and objects (devices) associated with them. 8 | //! A single device can be registered with multiple ranges, but no two ranges can overlap, 9 | //! regardless with their device associations. 10 | 11 | mod address; 12 | mod range; 13 | 14 | use std::collections::BTreeMap; 15 | use std::convert::TryFrom; 16 | use std::fmt::{Display, Formatter}; 17 | use std::result::Result; 18 | 19 | use address::BusAddress; 20 | 21 | pub use address::{MmioAddress, MmioAddressOffset, PioAddress, PioAddressOffset}; 22 | pub use range::{BusRange, MmioRange, PioRange}; 23 | 24 | /// Errors encountered during bus operations. 25 | #[derive(Debug, Eq, PartialEq)] 26 | pub enum Error { 27 | /// No device is associated with the specified address or range. 28 | DeviceNotFound, 29 | /// Specified range overlaps an already registered range. 30 | DeviceOverlap, 31 | /// Access with invalid length attempted. 32 | InvalidAccessLength(usize), 33 | /// Invalid range provided (either zero-sized, or last address overflows). 34 | InvalidRange, 35 | } 36 | 37 | impl Display for Error { 38 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 39 | match self { 40 | Error::DeviceNotFound => write!(f, "device not found"), 41 | Error::DeviceOverlap => write!(f, "range overlaps with existing device"), 42 | Error::InvalidAccessLength(len) => write!(f, "invalid access length ({})", len), 43 | Error::InvalidRange => write!(f, "invalid range provided"), 44 | } 45 | } 46 | } 47 | 48 | impl std::error::Error for Error {} 49 | 50 | /// A bus that's agnostic to the range address type and device type. 51 | pub struct Bus { 52 | devices: BTreeMap, D>, 53 | } 54 | 55 | impl Default for Bus { 56 | fn default() -> Self { 57 | Bus { 58 | devices: BTreeMap::new(), 59 | } 60 | } 61 | } 62 | 63 | impl Bus { 64 | /// Create an empty bus. 65 | pub fn new() -> Self { 66 | Self::default() 67 | } 68 | 69 | /// Return the registered range and device associated with `addr`. 70 | pub fn device(&self, addr: A) -> Option<(&BusRange, &D)> { 71 | // The range is returned as an optimization because the caller 72 | // might need both the device and its associated bus range. 73 | // The same goes for the device_mut() method. 74 | self.devices 75 | .range(..=BusRange::unit(addr)) 76 | .nth_back(0) 77 | .filter(|pair| pair.0.last() >= addr) 78 | } 79 | 80 | /// Return the registered range and a mutable reference to the device 81 | /// associated with `addr`. 82 | pub fn device_mut(&mut self, addr: A) -> Option<(&BusRange, &mut D)> { 83 | self.devices 84 | .range_mut(..=BusRange::unit(addr)) 85 | .nth_back(0) 86 | .filter(|pair| pair.0.last() >= addr) 87 | } 88 | 89 | /// Register a device with the provided range. 90 | pub fn register(&mut self, range: BusRange, device: D) -> Result<(), Error> { 91 | for r in self.devices.keys() { 92 | if range.overlaps(r) { 93 | return Err(Error::DeviceOverlap); 94 | } 95 | } 96 | 97 | self.devices.insert(range, device); 98 | 99 | Ok(()) 100 | } 101 | 102 | /// Deregister the device associated with `addr`. 103 | pub fn deregister(&mut self, addr: A) -> Option<(BusRange, D)> { 104 | let range = self.device(addr).map(|(range, _)| *range)?; 105 | self.devices.remove(&range).map(|device| (range, device)) 106 | } 107 | 108 | /// Verify whether an access starting at `addr` with length `len` fits within any of 109 | /// the registered ranges. Return the range and a handle to the device when present. 110 | pub fn check_access(&self, addr: A, len: usize) -> Result<(&BusRange, &D), Error> { 111 | let access_range = BusRange::new( 112 | addr, 113 | A::V::try_from(len).map_err(|_| Error::InvalidAccessLength(len))?, 114 | ) 115 | .map_err(|_| Error::InvalidRange)?; 116 | self.device(addr) 117 | .filter(|(range, _)| range.last() >= access_range.last()) 118 | .ok_or(Error::DeviceNotFound) 119 | } 120 | } 121 | 122 | /// Represents an MMIO bus. 123 | pub type MmioBus = Bus; 124 | /// Represents a PIO bus. 125 | pub type PioBus = Bus; 126 | 127 | /// Helper trait that can be implemented by types which hold one or more buses. 128 | pub trait BusManager { 129 | /// Type of the objects held by the bus. 130 | type D; 131 | 132 | /// Return a reference to the bus. 133 | fn bus(&self) -> &Bus; 134 | 135 | /// Return a mutable reference to the bus. 136 | fn bus_mut(&mut self) -> &mut Bus; 137 | } 138 | 139 | #[cfg(test)] 140 | mod test { 141 | use super::*; 142 | 143 | #[test] 144 | fn test_bus() { 145 | let base = MmioAddress(10); 146 | let base_prev = MmioAddress(base.value().checked_sub(1).unwrap()); 147 | let len = 10; 148 | let range = MmioRange::new(base, len).unwrap(); 149 | let range_next = range.last().checked_add(1).unwrap(); 150 | 151 | let mut bus = Bus::new(); 152 | // The bus is agnostic to actual device types, so let's just use a numeric type here. 153 | let device = 1u8; 154 | 155 | assert_eq!(bus.devices.len(), 0); 156 | 157 | bus.register(range, device).unwrap(); 158 | assert_eq!(bus.devices.len(), 1); 159 | 160 | assert!(bus.device(base_prev).is_none()); 161 | assert!(bus.device_mut(base_prev).is_none()); 162 | assert!(bus.device(range_next).is_none()); 163 | assert!(bus.device_mut(range_next).is_none()); 164 | 165 | for offset in 0..len { 166 | let addr = base.checked_add(offset).unwrap(); 167 | 168 | { 169 | let (r, d) = bus.device(addr).unwrap(); 170 | assert_eq!(range, *r); 171 | assert_eq!(device, *d); 172 | } 173 | 174 | { 175 | let (r, d) = bus.device_mut(addr).unwrap(); 176 | assert_eq!(range, *r); 177 | assert_eq!(device, *d); 178 | } 179 | 180 | // Let's also check invocations of `Bus::check_access`. 181 | for start_offset in 0..offset { 182 | let start_addr = base.checked_add(start_offset).unwrap(); 183 | 184 | let (r, d) = bus 185 | .check_access(start_addr, usize::try_from(offset - start_offset).unwrap()) 186 | .unwrap(); 187 | assert_eq!(range, *r); 188 | assert_eq!(device, *d); 189 | } 190 | } 191 | 192 | // Detect double registration with the same range. 193 | assert_eq!(bus.register(range, device), Err(Error::DeviceOverlap)); 194 | 195 | // We detect overlaps even if it's another range associated with the same device (we don't 196 | // implicitly merge ranges). `check_access` fails if the specified range does not fully 197 | // fit within a region associated with a particular device. 198 | 199 | { 200 | let range2 = MmioRange::new(MmioAddress(1), 10).unwrap(); 201 | assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap)); 202 | assert_eq!( 203 | bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()), 204 | Err(Error::DeviceNotFound) 205 | ); 206 | } 207 | 208 | { 209 | let range2 = MmioRange::new(range.last(), 10).unwrap(); 210 | assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap)); 211 | assert_eq!( 212 | bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()), 213 | Err(Error::DeviceNotFound) 214 | ); 215 | } 216 | 217 | { 218 | let range2 = MmioRange::new(MmioAddress(1), range.last().value() + 100).unwrap(); 219 | assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap)); 220 | assert_eq!( 221 | bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()), 222 | Err(Error::DeviceNotFound) 223 | ); 224 | } 225 | 226 | { 227 | // For a completely empty range, `check_access` should still fail, but `insert` 228 | // will succeed. 229 | 230 | let range2 = MmioRange::new(range.last().checked_add(1).unwrap(), 5).unwrap(); 231 | 232 | assert_eq!( 233 | bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()), 234 | Err(Error::DeviceNotFound) 235 | ); 236 | 237 | // Validate registration, and that `deregister` works for all addresses within a range. 238 | for offset in 0..range2.size() { 239 | let device2 = device + 1; 240 | assert!(bus.register(range2, device2).is_ok()); 241 | assert_eq!(bus.devices.len(), 2); 242 | 243 | let addr = range2.base().checked_add(offset).unwrap(); 244 | let (r, d) = bus.deregister(addr).unwrap(); 245 | assert_eq!(bus.devices.len(), 1); 246 | assert_eq!(r, range2); 247 | assert_eq!(d, device2); 248 | 249 | // A second deregister should fail. 250 | assert!(bus.deregister(addr).is_none()); 251 | assert_eq!(bus.devices.len(), 1); 252 | } 253 | 254 | // Register the previous `device` for `range2`. 255 | assert!(bus.register(range2, device).is_ok()); 256 | assert_eq!(bus.devices.len(), 2); 257 | 258 | // Even though the new range is associated with the same device, and right after the 259 | // previous one, accesses across multiple ranges are not allowed for now. 260 | // TODO: Do we want to support this in the future? 261 | assert_eq!( 262 | bus.check_access(range.base(), usize::try_from(range.size() + 1).unwrap()), 263 | Err(Error::DeviceNotFound) 264 | ); 265 | } 266 | 267 | // Ensure that bus::check_access() fails when the len argument 268 | // cannot be safely converted to PioAddressOffset which is u16. 269 | let pio_base = PioAddress(10); 270 | let pio_len = 10; 271 | let pio_range = PioRange::new(pio_base, pio_len).unwrap(); 272 | let mut pio_bus = Bus::new(); 273 | let pio_device = 1u8; 274 | pio_bus.register(pio_range, pio_device).unwrap(); 275 | assert_eq!( 276 | pio_bus.check_access(pio_base, usize::MAX), 277 | Err(Error::InvalidAccessLength(usize::MAX)) 278 | ); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/resources.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2019 Alibaba Cloud. All rights reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //! Structs to manage device resources. 5 | //! 6 | //! The high level flow of resource management among the VMM, the device manager, and the device 7 | //! is as below: 8 | //! 1) the VMM creates a new device object. 9 | //! 2) the VMM asks the new device object for its resource constraints. 10 | //! 3) the VMM allocates resources for the device object according to resource constraints. 11 | //! 4) the VMM passes the allocated resources to the device object. 12 | //! 5) the VMM registers the new device onto corresponding device managers according the allocated 13 | //! resources. 14 | 15 | use std::{u16, u32, u64}; 16 | 17 | /// Enumeration describing a device's resource constraints. 18 | pub enum ResourceConstraint { 19 | /// Constraint for an IO Port address range. 20 | PioAddress { 21 | /// Allocating resource within the range [`min`, `max`] if specified. 22 | range: Option<(u16, u16)>, 23 | /// Alignment for the allocated address. 24 | align: u16, 25 | /// Size for the allocated address range. 26 | size: u16, 27 | }, 28 | /// Constraint for a Memory Mapped IO address range. 29 | MmioAddress { 30 | /// Allocating resource within the range [`min`, `max`] if specified. 31 | range: Option<(u64, u64)>, 32 | /// Alignment for the allocated address. 33 | align: u64, 34 | /// Size for the allocated address range. 35 | size: u64, 36 | }, 37 | /// Constraint for a legacy IRQ. 38 | LegacyIrq { 39 | /// Reserving the pre-allocated IRQ if it's specified. 40 | irq: Option, 41 | }, 42 | /// Constraint for PCI MSI IRQs. 43 | PciMsiIrq { 44 | /// Number of Irqs to allocate. 45 | size: u32, 46 | }, 47 | /// Constraint for PCI MSIx IRQs. 48 | PciMsixIrq { 49 | /// Number of Irqs to allocate. 50 | size: u32, 51 | }, 52 | /// Constraint for generic IRQs. 53 | GenericIrq { 54 | /// Number of Irqs to allocate. 55 | size: u32, 56 | }, 57 | /// Constraint for KVM mem_slot indexes to map memory into the guest. 58 | KvmMemSlot { 59 | /// Allocating kvm memory slots starting from the index `slot` if specified. 60 | slot: Option, 61 | /// Number of slots to allocate. 62 | size: u32, 63 | }, 64 | } 65 | 66 | impl ResourceConstraint { 67 | /// Create a new PIO address constraint object with default configuration. 68 | pub fn new_pio(size: u16) -> Self { 69 | ResourceConstraint::PioAddress { 70 | range: None, 71 | align: 0x1, 72 | size, 73 | } 74 | } 75 | 76 | /// Create a new PIO address constraint object. 77 | pub fn pio_with_constraints(size: u16, range: Option<(u16, u16)>, align: u16) -> Self { 78 | ResourceConstraint::PioAddress { range, align, size } 79 | } 80 | 81 | /// Create a new MMIO address constraint object with default configuration. 82 | pub fn new_mmio(size: u64) -> Self { 83 | ResourceConstraint::MmioAddress { 84 | range: None, 85 | align: 0x1000, 86 | size, 87 | } 88 | } 89 | 90 | /// Create a new MMIO address constraint object. 91 | pub fn mmio_with_constraints(size: u64, range: Option<(u64, u64)>, align: u64) -> Self { 92 | ResourceConstraint::MmioAddress { range, align, size } 93 | } 94 | 95 | /// Create a new legacy IRQ constraint object. 96 | /// 97 | /// Allocating the pre-allocated legacy Irq `irq` if specified. 98 | pub fn new_legacy_irq(irq: Option) -> Self { 99 | ResourceConstraint::LegacyIrq { irq } 100 | } 101 | 102 | /// Create a new KVM memory slot constraint object. 103 | /// 104 | /// Allocating kvm memory slots starting from the index `slot` if specified. 105 | pub fn new_kvm_mem_slot(size: u32, slot: Option) -> Self { 106 | ResourceConstraint::KvmMemSlot { slot, size } 107 | } 108 | } 109 | 110 | /// Type of Message Signaled Interrupt 111 | #[derive(Clone, Copy, Eq, PartialEq)] 112 | pub enum MsiIrqType { 113 | /// PCI MSI IRQ numbers. 114 | PciMsi, 115 | /// PCI MSIx IRQ numbers. 116 | PciMsix, 117 | /// Generic MSI IRQ numbers. 118 | GenericMsi, 119 | } 120 | 121 | /// Enumeration for device resources. 122 | #[allow(missing_docs)] 123 | #[derive(Clone)] 124 | pub enum Resource { 125 | /// IO Port address range. 126 | PioAddressRange { base: u16, size: u16 }, 127 | /// Memory Mapped IO address range. 128 | MmioAddressRange { base: u64, size: u64 }, 129 | /// Legacy IRQ number. 130 | LegacyIrq(u32), 131 | /// Message Signaled Interrupt 132 | MsiIrq { 133 | ty: MsiIrqType, 134 | base: u32, 135 | size: u32, 136 | }, 137 | /// Network Interface Card MAC address. 138 | MacAddresss(String), 139 | /// KVM memslot index. 140 | KvmMemSlot(u32), 141 | } 142 | 143 | /// Newtype to store a set of device resources. 144 | #[derive(Default, Clone)] 145 | pub struct DeviceResources(Vec); 146 | 147 | impl DeviceResources { 148 | /// Create a container object to store device resources. 149 | pub fn new() -> Self { 150 | DeviceResources(Vec::new()) 151 | } 152 | 153 | /// Append a device resource to the container object. 154 | pub fn append(&mut self, entry: Resource) { 155 | self.0.push(entry); 156 | } 157 | 158 | /// Get the IO port address resources. 159 | pub fn get_pio_address_ranges(&self) -> Vec<(u16, u16)> { 160 | let mut vec = Vec::new(); 161 | for entry in self.0.iter().as_ref() { 162 | if let Resource::PioAddressRange { base, size } = entry { 163 | vec.push((*base, *size)); 164 | } 165 | } 166 | vec 167 | } 168 | 169 | /// Get the Memory Mapped IO address resources. 170 | pub fn get_mmio_address_ranges(&self) -> Vec<(u64, u64)> { 171 | let mut vec = Vec::new(); 172 | for entry in self.0.iter().as_ref() { 173 | if let Resource::MmioAddressRange { base, size } = entry { 174 | vec.push((*base, *size)); 175 | } 176 | } 177 | vec 178 | } 179 | 180 | /// Get the first legacy interrupt number(IRQ). 181 | pub fn get_legacy_irq(&self) -> Option { 182 | for entry in self.0.iter().as_ref() { 183 | if let Resource::LegacyIrq(base) = entry { 184 | return Some(*base); 185 | } 186 | } 187 | None 188 | } 189 | 190 | /// Get information about the first PCI MSI interrupt resource. 191 | pub fn get_pci_msi_irqs(&self) -> Option<(u32, u32)> { 192 | self.get_msi_irqs(MsiIrqType::PciMsi) 193 | } 194 | 195 | /// Get information about the first PCI MSIx interrupt resource. 196 | pub fn get_pci_msix_irqs(&self) -> Option<(u32, u32)> { 197 | self.get_msi_irqs(MsiIrqType::PciMsix) 198 | } 199 | 200 | /// Get information about the first Generic MSI interrupt resource. 201 | pub fn get_generic_msi_irqs(&self) -> Option<(u32, u32)> { 202 | self.get_msi_irqs(MsiIrqType::GenericMsi) 203 | } 204 | 205 | fn get_msi_irqs(&self, ty: MsiIrqType) -> Option<(u32, u32)> { 206 | for entry in self.0.iter().as_ref() { 207 | if let Resource::MsiIrq { 208 | ty: msi_type, 209 | base, 210 | size, 211 | } = entry 212 | { 213 | if ty == *msi_type { 214 | return Some((*base, *size)); 215 | } 216 | } 217 | } 218 | None 219 | } 220 | 221 | /// Get the KVM memory slots to map memory into the guest. 222 | pub fn get_kvm_mem_slots(&self) -> Vec { 223 | let mut vec = Vec::new(); 224 | for entry in self.0.iter().as_ref() { 225 | if let Resource::KvmMemSlot(index) = entry { 226 | vec.push(*index); 227 | } 228 | } 229 | vec 230 | } 231 | 232 | /// Get the first resource information for NIC MAC address. 233 | pub fn get_mac_address(&self) -> Option { 234 | for entry in self.0.iter().as_ref() { 235 | if let Resource::MacAddresss(addr) = entry { 236 | return Some(addr.clone()); 237 | } 238 | } 239 | None 240 | } 241 | 242 | /// Get immutable reference to all the resources. 243 | pub fn get_all_resources(&self) -> &[Resource] { 244 | &self.0 245 | } 246 | } 247 | 248 | #[cfg(test)] 249 | mod tests { 250 | use super::*; 251 | 252 | const PIO_ADDRESS_SIZE: u16 = 5; 253 | const PIO_ADDRESS_BASE: u16 = 0; 254 | const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321; 255 | const MMIO_ADDRESS_BASE: u64 = 0x1234_5678; 256 | const LEGACY_IRQ: u32 = 0x168; 257 | const PCI_MSI_IRQ_SIZE: u32 = 0x8888; 258 | const PCI_MSI_IRQ_BASE: u32 = 0x6666; 259 | const PCI_MSIX_IRQ_SIZE: u32 = 0x16666; 260 | const PCI_MSIX_IRQ_BASE: u32 = 0x8888; 261 | const GENERIC_MSI_IRQS_SIZE: u32 = 0x16888; 262 | const GENERIC_MSI_IRQS_BASE: u32 = 0x16688; 263 | const MAC_ADDRESS: &str = "00:08:63:66:86:88"; 264 | const KVM_SLOT_ID: u32 = 0x0100; 265 | 266 | fn get_device_resource() -> DeviceResources { 267 | let entry = Resource::PioAddressRange { 268 | base: PIO_ADDRESS_BASE, 269 | size: PIO_ADDRESS_SIZE, 270 | }; 271 | let mut resource = DeviceResources::new(); 272 | resource.append(entry); 273 | let entry = Resource::MmioAddressRange { 274 | base: MMIO_ADDRESS_BASE, 275 | size: MMIO_ADDRESS_SIZE, 276 | }; 277 | resource.append(entry); 278 | let entry = Resource::LegacyIrq(LEGACY_IRQ); 279 | resource.append(entry); 280 | let entry = Resource::MsiIrq { 281 | ty: MsiIrqType::PciMsi, 282 | base: PCI_MSI_IRQ_BASE, 283 | size: PCI_MSI_IRQ_SIZE, 284 | }; 285 | resource.append(entry); 286 | let entry = Resource::MsiIrq { 287 | ty: MsiIrqType::PciMsix, 288 | base: PCI_MSIX_IRQ_BASE, 289 | size: PCI_MSIX_IRQ_SIZE, 290 | }; 291 | resource.append(entry); 292 | let entry = Resource::MsiIrq { 293 | ty: MsiIrqType::GenericMsi, 294 | base: GENERIC_MSI_IRQS_BASE, 295 | size: GENERIC_MSI_IRQS_SIZE, 296 | }; 297 | resource.append(entry); 298 | let entry = Resource::MacAddresss(MAC_ADDRESS.to_string()); 299 | resource.append(entry); 300 | 301 | resource.append(Resource::KvmMemSlot(KVM_SLOT_ID)); 302 | 303 | resource 304 | } 305 | 306 | #[test] 307 | fn get_pio_address_ranges() { 308 | let resources = get_device_resource(); 309 | assert!( 310 | resources.get_pio_address_ranges()[0].0 == PIO_ADDRESS_BASE 311 | && resources.get_pio_address_ranges()[0].1 == PIO_ADDRESS_SIZE 312 | ); 313 | } 314 | 315 | #[test] 316 | fn test_get_mmio_address_ranges() { 317 | let resources = get_device_resource(); 318 | assert!( 319 | resources.get_mmio_address_ranges()[0].0 == MMIO_ADDRESS_BASE 320 | && resources.get_mmio_address_ranges()[0].1 == MMIO_ADDRESS_SIZE 321 | ); 322 | } 323 | 324 | #[test] 325 | fn test_get_legacy_irq() { 326 | let resources = get_device_resource(); 327 | assert!(resources.get_legacy_irq().unwrap() == LEGACY_IRQ); 328 | } 329 | 330 | #[test] 331 | fn test_get_pci_msi_irqs() { 332 | let resources = get_device_resource(); 333 | assert!( 334 | resources.get_pci_msi_irqs().unwrap().0 == PCI_MSI_IRQ_BASE 335 | && resources.get_pci_msi_irqs().unwrap().1 == PCI_MSI_IRQ_SIZE 336 | ); 337 | } 338 | 339 | #[test] 340 | fn test_pci_msix_irqs() { 341 | let resources = get_device_resource(); 342 | assert!( 343 | resources.get_pci_msix_irqs().unwrap().0 == PCI_MSIX_IRQ_BASE 344 | && resources.get_pci_msix_irqs().unwrap().1 == PCI_MSIX_IRQ_SIZE 345 | ); 346 | } 347 | 348 | #[test] 349 | fn test_get_generic_msi_irqs() { 350 | let resources = get_device_resource(); 351 | assert!( 352 | resources.get_generic_msi_irqs().unwrap().0 == GENERIC_MSI_IRQS_BASE 353 | && resources.get_generic_msi_irqs().unwrap().1 == GENERIC_MSI_IRQS_SIZE 354 | ); 355 | } 356 | 357 | #[test] 358 | fn test_get_mac_address() { 359 | let resources = get_device_resource(); 360 | assert_eq!(resources.get_mac_address().unwrap(), MAC_ADDRESS); 361 | } 362 | 363 | #[test] 364 | fn test_get_kvm_slot() { 365 | let resources = get_device_resource(); 366 | assert_eq!(resources.get_kvm_mem_slots(), vec![KVM_SLOT_ID]); 367 | } 368 | 369 | #[test] 370 | fn test_get_all_resources() { 371 | let resources = get_device_resource(); 372 | assert_eq!(resources.get_all_resources().len(), 8); 373 | } 374 | 375 | #[test] 376 | fn test_resource_constraint() { 377 | if let ResourceConstraint::PioAddress { range, align, size } = 378 | ResourceConstraint::new_pio(2) 379 | { 380 | assert_eq!(range, None); 381 | assert_eq!(align, 1); 382 | assert_eq!(size, 2); 383 | } else { 384 | panic!("Pio resource constraint is invalid."); 385 | } 386 | 387 | if let ResourceConstraint::PioAddress { range, align, size } = 388 | ResourceConstraint::pio_with_constraints(2, Some((15, 16)), 2) 389 | { 390 | assert_eq!(range, Some((15, 16))); 391 | assert_eq!(align, 2); 392 | assert_eq!(size, 2); 393 | } else { 394 | panic!("Pio resource constraint is invalid."); 395 | } 396 | 397 | if let ResourceConstraint::MmioAddress { range, align, size } = 398 | ResourceConstraint::new_mmio(0x2000) 399 | { 400 | assert_eq!(range, None); 401 | assert_eq!(align, 0x1000); 402 | assert_eq!(size, 0x2000); 403 | } else { 404 | panic!("Mmio resource constraint is invalid."); 405 | } 406 | 407 | if let ResourceConstraint::MmioAddress { range, align, size } = 408 | ResourceConstraint::mmio_with_constraints(0x2000, Some((0x0, 0x2000)), 0x2000) 409 | { 410 | assert_eq!(range, Some((0x0, 0x2000))); 411 | assert_eq!(align, 0x2000); 412 | assert_eq!(size, 0x2000); 413 | } else { 414 | panic!("Mmio resource constraint is invalid."); 415 | } 416 | 417 | if let ResourceConstraint::LegacyIrq { irq } = 418 | ResourceConstraint::new_legacy_irq(Some(0x123)) 419 | { 420 | assert_eq!(irq, Some(0x123)); 421 | } else { 422 | panic!("IRQ resource constraint is invalid."); 423 | } 424 | 425 | if let ResourceConstraint::KvmMemSlot { slot, size } = 426 | ResourceConstraint::new_kvm_mem_slot(0x1000, Some(0x2000)) 427 | { 428 | assert_eq!(slot, Some(0x2000)); 429 | assert_eq!(size, 0x1000); 430 | } else { 431 | panic!("KVM slot resource constraint is invalid."); 432 | } 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /src/device_manager.rs: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 Intel Corporation. All Rights Reserved. 2 | // SPDX-License-Identifier: (Apache-2.0 OR BSD-3-Clause) 3 | 4 | //! System level device management. 5 | //! 6 | //! [`IoManager`] is responsible for managing 7 | //! all devices of virtual machine, registering IO resources callback, 8 | //! deregistering devices and helping VM IO exit handling. 9 | //! It defines two buses, one for PIO and one for MMIO, and provides default 10 | //! implementations of [`PioManager`] and [`MmioManager`]. 11 | //! 12 | //! The VMM must first allocate unique resources (such as bus ranges), and then 13 | //! call into the vm-device interface to register the devices with their 14 | //! corresponding resources. 15 | //! 16 | //! # Examples 17 | //! 18 | //! Registering a new device can be done using the register methods of [`PioManager`] 19 | //! and [`MmioManager`] with an appropriate bus range 20 | //! ([`PioRange`](../bus/type.PioRange.html) or [`MmioRange`](../bus/type.MmioRange.html)). 21 | //! ``` 22 | //! # use std::sync::Arc; 23 | //! # use vm_device::bus::{PioAddress, PioAddressOffset, PioRange}; 24 | //! # use vm_device::bus::{MmioAddress, MmioAddressOffset, MmioRange}; 25 | //! # use vm_device::device_manager::{IoManager, PioManager, MmioManager}; 26 | //! # use vm_device::{DevicePio, DeviceMmio}; 27 | //! struct NoopDevice {} 28 | //! 29 | //! impl DevicePio for NoopDevice { 30 | //! fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) {} 31 | //! fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) {} 32 | //! } 33 | //! 34 | //! impl DeviceMmio for NoopDevice { 35 | //! fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) {} 36 | //! fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) {} 37 | //! } 38 | //! 39 | //! // IoManager implements both PioManager and MmioManager. 40 | //! let mut manager = IoManager::new(); 41 | //! 42 | //! // Register the device on the PIO bus. 43 | //! let pio_range = PioRange::new(PioAddress(0), 10).unwrap(); 44 | //! manager 45 | //! .register_pio(pio_range, Arc::new(NoopDevice {})) 46 | //! .unwrap(); 47 | //! 48 | //! // Register the device on the MMIO bus. 49 | //! let mmio_range = MmioRange::new(MmioAddress(0), 10).unwrap(); 50 | //! manager 51 | //! .register_mmio(mmio_range, Arc::new(NoopDevice {})) 52 | //! .unwrap(); 53 | //! 54 | //! // Dispatch I/O on the PIO bus. 55 | //! manager.pio_write(PioAddress(0), &vec![b'o', b'k']).unwrap(); 56 | //! 57 | //! // Dispatch I/O on the MMIO bus. 58 | //! manager 59 | //! .mmio_write(MmioAddress(0), &vec![b'o', b'k']) 60 | //! .unwrap(); 61 | //! ``` 62 | //! 63 | //! An alternative way would be to use [`resources`](../resources/index.html) and the 64 | //! resources registration methods of [`IoManager`]: 65 | //! * [`register_pio_resources`](struct.IoManager.html#method.register_pio_resources) 66 | //! * [`register_mmio_resources`](struct.IoManager.html#method.register_mmio_resources) 67 | //! * or generic [`register_resources`](struct.IoManager.html#method.register_resources) 68 | //! ``` 69 | //! # use std::sync::Arc; 70 | //! # use vm_device::bus::{PioAddress, PioAddressOffset, PioRange}; 71 | //! # use vm_device::bus::{MmioAddress, MmioAddressOffset, MmioRange}; 72 | //! # use vm_device::device_manager::{IoManager, PioManager, MmioManager}; 73 | //! # use vm_device::{DevicePio, DeviceMmio}; 74 | //! # use vm_device::resources::Resource; 75 | //! # struct NoopDevice {} 76 | //! # 77 | //! # impl DevicePio for NoopDevice { 78 | //! # fn pio_read(&self, base: PioAddress, offset: PioAddressOffset, data: &mut [u8]) {} 79 | //! # fn pio_write(&self, base: PioAddress, offset: PioAddressOffset, data: &[u8]) {} 80 | //! # } 81 | //! # 82 | //! # impl DeviceMmio for NoopDevice { 83 | //! # fn mmio_read(&self, base: MmioAddress, offset: MmioAddressOffset, data: &mut [u8]) {} 84 | //! # fn mmio_write(&self, base: MmioAddress, offset: MmioAddressOffset, data: &[u8]) {} 85 | //! # } 86 | //! // Use the same NoopDevice defined above. 87 | //! 88 | //! let mut manager = IoManager::new(); 89 | //! 90 | //! // Define a PIO address range resource. 91 | //! let pio = Resource::PioAddressRange { 92 | //! base: 0, 93 | //! size: 10, 94 | //! }; 95 | //! 96 | //! // Define a MMIO address range resource. 97 | //! let mmio = Resource::MmioAddressRange { 98 | //! base: 0, 99 | //! size: 10, 100 | //! }; 101 | //! 102 | //! // Register the PIO resource. 103 | //! manager 104 | //! .register_pio_resources(Arc::new(NoopDevice {}), &vec![pio]) 105 | //! .unwrap(); 106 | //! 107 | //! // Register the MMIO resource. 108 | //! manager 109 | //! .register_mmio_resources(Arc::new(NoopDevice {}), &vec![mmio]) 110 | //! .unwrap(); 111 | //! 112 | //! // Dispatching I/O is the same. 113 | //! manager.pio_write(PioAddress(0), &vec![b'o', b'k']).unwrap(); 114 | //! manager.mmio_write(MmioAddress(0), &vec![b'o', b'k']).unwrap(); 115 | //! ``` 116 | 117 | use std::fmt::{Display, Formatter}; 118 | use std::result::Result; 119 | use std::sync::Arc; 120 | 121 | use crate::bus::{self, BusManager, MmioAddress, MmioBus, MmioRange, PioAddress, PioBus, PioRange}; 122 | use crate::resources::Resource; 123 | use crate::{DeviceMmio, DevicePio}; 124 | 125 | /// Error type for [IoManager] usage. 126 | #[derive(Debug)] 127 | pub enum Error { 128 | /// Error during bus operation. 129 | Bus(bus::Error), 130 | } 131 | 132 | impl Display for Error { 133 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 134 | match self { 135 | Error::Bus(_) => write!(f, "device_manager: bus error"), 136 | } 137 | } 138 | } 139 | 140 | impl std::error::Error for Error { 141 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 142 | match self { 143 | Error::Bus(e) => Some(e), 144 | } 145 | } 146 | } 147 | 148 | /// Represents an object that provides PIO manager operations. 149 | pub trait PioManager { 150 | /// Type of the objects that can be registered with this `PioManager`. 151 | type D: DevicePio; 152 | 153 | /// Return a reference to the device registered at `addr`, together with the associated 154 | /// range, if available. 155 | fn pio_device(&self, addr: PioAddress) -> Option<(&PioRange, &Self::D)>; 156 | 157 | /// Dispatch a read operation to the device registered at `addr`. 158 | fn pio_read(&self, addr: PioAddress, data: &mut [u8]) -> Result<(), bus::Error>; 159 | 160 | /// Dispatch a write operation to the device registered at `addr`. 161 | fn pio_write(&self, addr: PioAddress, data: &[u8]) -> Result<(), bus::Error>; 162 | 163 | /// Register the provided device with the specified range. 164 | fn register_pio(&mut self, range: PioRange, device: Self::D) -> Result<(), bus::Error>; 165 | 166 | /// Deregister the device currently registered at `addr` together with the 167 | /// associated range. 168 | fn deregister_pio(&mut self, addr: PioAddress) -> Option<(PioRange, Self::D)>; 169 | } 170 | 171 | // This automatically provides a `PioManager` implementation for types that already implement 172 | // `BusManager` if their inner associated type implements `DevicePio` as well. 173 | impl PioManager for T 174 | where 175 | T: BusManager, 176 | T::D: DevicePio, 177 | { 178 | type D = >::D; 179 | 180 | fn pio_device(&self, addr: PioAddress) -> Option<(&PioRange, &Self::D)> { 181 | self.bus().device(addr) 182 | } 183 | 184 | fn pio_read(&self, addr: PioAddress, data: &mut [u8]) -> Result<(), bus::Error> { 185 | self.bus() 186 | .check_access(addr, data.len()) 187 | .map(|(range, device)| device.pio_read(range.base(), addr - range.base(), data)) 188 | } 189 | 190 | fn pio_write(&self, addr: PioAddress, data: &[u8]) -> Result<(), bus::Error> { 191 | self.bus() 192 | .check_access(addr, data.len()) 193 | .map(|(range, device)| device.pio_write(range.base(), addr - range.base(), data)) 194 | } 195 | 196 | fn register_pio(&mut self, range: PioRange, device: Self::D) -> Result<(), bus::Error> { 197 | self.bus_mut().register(range, device) 198 | } 199 | 200 | fn deregister_pio(&mut self, addr: PioAddress) -> Option<(PioRange, Self::D)> { 201 | self.bus_mut().deregister(addr) 202 | } 203 | } 204 | 205 | /// Represents an object that provides MMIO manager operations. 206 | pub trait MmioManager { 207 | /// Type of the objects that can be registered with this `MmioManager`. 208 | type D: DeviceMmio; 209 | 210 | /// Return a reference to the device registered at `addr`, together with the associated 211 | /// range, if available. 212 | fn mmio_device(&self, addr: MmioAddress) -> Option<(&MmioRange, &Self::D)>; 213 | 214 | /// Dispatch a read operation to the device registered at `addr`. 215 | fn mmio_read(&self, addr: MmioAddress, data: &mut [u8]) -> Result<(), bus::Error>; 216 | 217 | /// Dispatch a write operation to the device registered at `addr`. 218 | fn mmio_write(&self, addr: MmioAddress, data: &[u8]) -> Result<(), bus::Error>; 219 | 220 | /// Register the provided device with the specified range. 221 | fn register_mmio(&mut self, range: MmioRange, device: Self::D) -> Result<(), bus::Error>; 222 | 223 | /// Deregister the device currently registered at `addr` together with the 224 | /// associated range. 225 | fn deregister_mmio(&mut self, addr: MmioAddress) -> Option<(MmioRange, Self::D)>; 226 | } 227 | 228 | // This automatically provides a `MmioManager` implementation for types that already implement 229 | // `BusManager` if their inner associated type implements `DeviceMmio` as well. 230 | impl MmioManager for T 231 | where 232 | T: BusManager, 233 | T::D: DeviceMmio, 234 | { 235 | type D = >::D; 236 | 237 | fn mmio_device(&self, addr: MmioAddress) -> Option<(&MmioRange, &Self::D)> { 238 | self.bus().device(addr) 239 | } 240 | 241 | fn mmio_read(&self, addr: MmioAddress, data: &mut [u8]) -> Result<(), bus::Error> { 242 | self.bus() 243 | .check_access(addr, data.len()) 244 | .map(|(range, device)| device.mmio_read(range.base(), addr - range.base(), data)) 245 | } 246 | 247 | fn mmio_write(&self, addr: MmioAddress, data: &[u8]) -> Result<(), bus::Error> { 248 | self.bus() 249 | .check_access(addr, data.len()) 250 | .map(|(range, device)| device.mmio_write(range.base(), addr - range.base(), data)) 251 | } 252 | 253 | fn register_mmio(&mut self, range: MmioRange, device: Self::D) -> Result<(), bus::Error> { 254 | self.bus_mut().register(range, device) 255 | } 256 | 257 | fn deregister_mmio(&mut self, addr: MmioAddress) -> Option<(MmioRange, Self::D)> { 258 | self.bus_mut().deregister(addr) 259 | } 260 | } 261 | 262 | /// System IO manager serving for all devices management and VM exit handling. 263 | #[derive(Default)] 264 | pub struct IoManager { 265 | // Range mapping for VM exit pio operations. 266 | pio_bus: PioBus>, 267 | // Range mapping for VM exit mmio operations. 268 | mmio_bus: MmioBus>, 269 | } 270 | 271 | // Enables the automatic implementation of `PioManager` for `IoManager`. 272 | impl BusManager for IoManager { 273 | type D = Arc; 274 | 275 | fn bus(&self) -> &PioBus> { 276 | &self.pio_bus 277 | } 278 | 279 | fn bus_mut(&mut self) -> &mut PioBus> { 280 | &mut self.pio_bus 281 | } 282 | } 283 | 284 | // Enables the automatic implementation of `MmioManager` for `IoManager`. 285 | impl BusManager for IoManager { 286 | type D = Arc; 287 | 288 | fn bus(&self) -> &MmioBus> { 289 | &self.mmio_bus 290 | } 291 | 292 | fn bus_mut(&mut self) -> &mut MmioBus> { 293 | &mut self.mmio_bus 294 | } 295 | } 296 | 297 | impl IoManager { 298 | /// Create an default IoManager with empty IO member. 299 | pub fn new() -> Self { 300 | IoManager::default() 301 | } 302 | 303 | /// Register a new MMIO device with its allocated resources. 304 | /// VMM is responsible for providing the allocated resources to virtual device. 305 | /// 306 | /// # Arguments 307 | /// 308 | /// * `device`: device instance object to be registered 309 | /// * `resources`: resources that this device owns, might include 310 | /// port I/O and memory-mapped I/O ranges, irq number, etc. 311 | pub fn register_mmio_resources( 312 | &mut self, 313 | device: Arc, 314 | resources: &[Resource], 315 | ) -> Result<(), Error> { 316 | // Register and mark device resources 317 | // The resources addresses being registered are sucessfully allocated before. 318 | for res in resources.iter() { 319 | match *res { 320 | Resource::MmioAddressRange { base, size } => { 321 | self.register_mmio( 322 | MmioRange::new(MmioAddress(base), size).unwrap(), 323 | device.clone(), 324 | ) 325 | .map_err(Error::Bus)?; 326 | } 327 | _ => continue, 328 | } 329 | } 330 | Ok(()) 331 | } 332 | 333 | /// Register a new PIO device with its allocated resources. 334 | /// VMM is responsible for providing the allocated resources to virtual device. 335 | /// 336 | /// # Arguments 337 | /// 338 | /// * `device`: device instance object to be registered 339 | /// * `resources`: resources that this device owns, might include 340 | /// port I/O and memory-mapped I/O ranges, irq number, etc. 341 | pub fn register_pio_resources( 342 | &mut self, 343 | device: Arc, 344 | resources: &[Resource], 345 | ) -> Result<(), Error> { 346 | // Register and mark device resources 347 | // The resources addresses being registered are sucessfully allocated before. 348 | for res in resources.iter() { 349 | match *res { 350 | Resource::PioAddressRange { base, size } => { 351 | self.register_pio( 352 | PioRange::new(PioAddress(base), size).unwrap(), 353 | device.clone(), 354 | ) 355 | .map_err(Error::Bus)?; 356 | } 357 | _ => continue, 358 | } 359 | } 360 | Ok(()) 361 | } 362 | 363 | /// Register a new MMIO + PIO device with its allocated resources. 364 | /// VMM is responsible for providing the allocated resources to virtual device. 365 | /// 366 | /// # Arguments 367 | /// 368 | /// * `device`: device instance object to be registered 369 | /// * `resources`: resources that this device owns, might include 370 | /// port I/O and memory-mapped I/O ranges, irq number, etc. 371 | pub fn register_resources( 372 | &mut self, 373 | device: Arc, 374 | resources: &[Resource], 375 | ) -> Result<(), Error> { 376 | self.register_mmio_resources(device.clone(), resources)?; 377 | self.register_pio_resources(device, resources) 378 | } 379 | 380 | /// Deregister a device from `IoManager`, e.g. users specified removing. 381 | /// VMM pre-fetches the resources e.g. dev.get_assigned_resources() 382 | /// VMM is responsible for freeing the resources. Returns the number 383 | /// of deregistered devices. 384 | /// 385 | /// # Arguments 386 | /// 387 | /// * `resources`: resources that this device owns, might include 388 | /// port I/O and memory-mapped I/O ranges, irq number, etc. 389 | pub fn deregister_resources(&mut self, resources: &[Resource]) -> usize { 390 | let mut count = 0; 391 | for res in resources.iter() { 392 | match *res { 393 | Resource::PioAddressRange { base, .. } => { 394 | if self.deregister_pio(PioAddress(base)).is_some() { 395 | count += 1; 396 | } 397 | } 398 | Resource::MmioAddressRange { base, .. } => { 399 | if self.deregister_mmio(MmioAddress(base)).is_some() { 400 | count += 1; 401 | } 402 | } 403 | _ => continue, 404 | } 405 | } 406 | count 407 | } 408 | } 409 | 410 | #[cfg(test)] 411 | mod tests { 412 | use super::*; 413 | 414 | use std::error::Error; 415 | use std::sync::Mutex; 416 | 417 | use bus::{MmioAddressOffset, PioAddressOffset}; 418 | 419 | const PIO_ADDRESS_SIZE: u16 = 4; 420 | const PIO_ADDRESS_BASE: u16 = 0x40; 421 | const MMIO_ADDRESS_SIZE: u64 = 0x8765_4321; 422 | const MMIO_ADDRESS_BASE: u64 = 0x1234_5678; 423 | const LEGACY_IRQ: u32 = 4; 424 | const CONFIG_DATA: u32 = 0x1234; 425 | 426 | struct DummyDevice { 427 | config: Mutex, 428 | } 429 | 430 | impl DummyDevice { 431 | fn new(config: u32) -> Self { 432 | DummyDevice { 433 | config: Mutex::new(config), 434 | } 435 | } 436 | } 437 | 438 | impl DevicePio for DummyDevice { 439 | fn pio_read(&self, _base: PioAddress, _offset: PioAddressOffset, data: &mut [u8]) { 440 | if data.len() > 4 { 441 | return; 442 | } 443 | for (idx, iter) in data.iter_mut().enumerate() { 444 | let config = self.config.lock().expect("failed to acquire lock"); 445 | *iter = (*config >> (idx * 8) & 0xff) as u8; 446 | } 447 | } 448 | 449 | fn pio_write(&self, _base: PioAddress, _offset: PioAddressOffset, data: &[u8]) { 450 | let mut config = self.config.lock().expect("failed to acquire lock"); 451 | *config = u32::from(data[0]) & 0xff; 452 | } 453 | } 454 | 455 | impl DeviceMmio for DummyDevice { 456 | fn mmio_read(&self, _base: MmioAddress, _offset: MmioAddressOffset, data: &mut [u8]) { 457 | if data.len() > 4 { 458 | return; 459 | } 460 | for (idx, iter) in data.iter_mut().enumerate() { 461 | let config = self.config.lock().expect("failed to acquire lock"); 462 | *iter = (*config >> (idx * 8) & 0xff) as u8; 463 | } 464 | } 465 | 466 | fn mmio_write(&self, _base: MmioAddress, _offset: MmioAddressOffset, data: &[u8]) { 467 | let mut config = self.config.lock().expect("failed to acquire lock"); 468 | *config = u32::from(data[0]) & 0xff; 469 | } 470 | } 471 | 472 | #[test] 473 | fn test_register_deregister_device_io() { 474 | let mut io_mgr = IoManager::new(); 475 | let dummy = DummyDevice::new(0); 476 | let dum = Arc::new(dummy); 477 | 478 | let mut resource: Vec = Vec::new(); 479 | let mmio = Resource::MmioAddressRange { 480 | base: MMIO_ADDRESS_BASE, 481 | size: MMIO_ADDRESS_SIZE, 482 | }; 483 | let irq = Resource::LegacyIrq(LEGACY_IRQ); 484 | let pio = Resource::PioAddressRange { 485 | base: PIO_ADDRESS_BASE, 486 | size: PIO_ADDRESS_SIZE, 487 | }; 488 | 489 | resource.push(mmio); 490 | resource.push(irq); 491 | resource.push(pio); 492 | 493 | assert!(io_mgr 494 | .register_mmio_resources(dum.clone(), &resource) 495 | .is_ok()); 496 | assert!(io_mgr.register_pio_resources(dum, &resource).is_ok()); 497 | assert_eq!(io_mgr.deregister_resources(&resource), 2); 498 | } 499 | 500 | #[test] 501 | fn test_mmio_read_write() { 502 | let mut io_mgr: IoManager = Default::default(); 503 | let dum = Arc::new(DummyDevice::new(CONFIG_DATA)); 504 | let mut resource: Vec = Vec::new(); 505 | 506 | let mmio = Resource::MmioAddressRange { 507 | base: MMIO_ADDRESS_BASE, 508 | size: MMIO_ADDRESS_SIZE, 509 | }; 510 | resource.push(mmio); 511 | assert!(io_mgr 512 | .register_mmio_resources(dum.clone(), &resource) 513 | .is_ok()); 514 | 515 | let mut data = [0; 4]; 516 | assert!(io_mgr 517 | .mmio_read(MmioAddress(MMIO_ADDRESS_BASE), &mut data) 518 | .is_ok()); 519 | assert_eq!(data, [0x34, 0x12, 0, 0]); 520 | 521 | assert!(io_mgr 522 | .mmio_read( 523 | MmioAddress(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE), 524 | &mut data 525 | ) 526 | .is_err()); 527 | 528 | data = [0; 4]; 529 | assert!(io_mgr 530 | .mmio_write(MmioAddress(MMIO_ADDRESS_BASE), &data) 531 | .is_ok()); 532 | assert_eq!(*dum.config.lock().unwrap(), 0); 533 | 534 | assert!(io_mgr 535 | .mmio_write(MmioAddress(MMIO_ADDRESS_BASE + MMIO_ADDRESS_SIZE), &data) 536 | .is_err()); 537 | } 538 | 539 | #[test] 540 | fn test_pio_read_write() { 541 | let mut io_mgr: IoManager = Default::default(); 542 | let dum = Arc::new(DummyDevice::new(CONFIG_DATA)); 543 | let mut resource: Vec = Vec::new(); 544 | 545 | let pio = Resource::PioAddressRange { 546 | base: PIO_ADDRESS_BASE, 547 | size: PIO_ADDRESS_SIZE, 548 | }; 549 | resource.push(pio); 550 | assert!(io_mgr 551 | .register_pio_resources(dum.clone(), &resource) 552 | .is_ok()); 553 | 554 | let mut data = [0; 4]; 555 | assert!(io_mgr 556 | .pio_read(PioAddress(PIO_ADDRESS_BASE), &mut data) 557 | .is_ok()); 558 | assert_eq!(data, [0x34, 0x12, 0, 0]); 559 | 560 | assert!(io_mgr 561 | .pio_read(PioAddress(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE), &mut data) 562 | .is_err()); 563 | 564 | data = [0; 4]; 565 | assert!(io_mgr 566 | .pio_write(PioAddress(PIO_ADDRESS_BASE), &data) 567 | .is_ok()); 568 | assert_eq!(*dum.config.lock().unwrap(), 0); 569 | 570 | assert!(io_mgr 571 | .pio_write(PioAddress(PIO_ADDRESS_BASE + PIO_ADDRESS_SIZE), &data) 572 | .is_err()); 573 | } 574 | 575 | #[test] 576 | fn test_error_code() { 577 | let err = super::Error::Bus(bus::Error::DeviceOverlap); 578 | 579 | assert!(err.source().is_some()); 580 | assert_eq!(format!("{}", err), "device_manager: bus error"); 581 | } 582 | } 583 | --------------------------------------------------------------------------------