A device is usually attached to a particular bus and thus needs to implement a trait of only one type. For example, serial port on x86 is a PIO device, while VirtIO devices use MMIO. It’s also possible for a device to implement both. Once the type of I/O is determined, the next step is to choose between mutable and immutable trait variant. If read or write method needs to mutate the device’s internal state, then the mutable variant must be used.
Before dispatching any I/O requests to the new device, it needs to be registered 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 automatic implementation of DevicePio for Mutex<T> where T: MutDevicePio
and DeviceMmio for Mutex<T> where T: MutDeviceMmio
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.
From now on the IoManager will be routing I/O requests for the registered address range to the device. The requests are dispatched by the client code, for example when handling VM exits, using IoManager
's methods pio_read
, pio_write
, mmio_read
and mmio_write
.
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 ); } }
fn main() { 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(); }
The vm-device
is tested using unit tests and integration tests. It leverages rust-vmm-ci
for continuous testing. All tests are ran in the rustvmm/dev
container.
This project is licensed under either of: