# dbs-device The `dbs-device` crate, as a counterpart of [vm-device], defines device model for the Dragonball Secure Sandbox. The `dbs-device` crate shares some common concepts and data structures with [vm-device], but it also diverges from [vm-device] due to different VMM designs. The dbs-device crate provides: - [DeviceIo] and [DeviceIoMut]: trait for device to handle trapped MMIO/PIO access requests. - [IoManager]: IO manager to handle trapped MMIO/PIO access requests. - [IoManagerContext]: trait for IO manager context object to support device hotplug at runtime. - [ResourceConstraint], [Resource] and [DeviceResources]: resource allocation requirements and constraints. ## Design The dbs-device crate is designed to support the virtual machine's device model. The core concepts of device model are [Port I/O](https://wiki.osdev.org/I/O_Ports) and [Memory-mapped I/O](https://en.wikipedia.org/wiki/Memory-mapped_I/O), which are two main methods of performing I/O between CPU and devices. The device model provided by the dbs-device crate works as below: - The VMM creates a global resource manager, device manager and IO manager. - The device manager creates virtual devices configured by the VMM - create device object - query device allocation requirements and constraints, the device returns an array of [ResourceConstraint]. - allocate resources for device from the resource manager, resource manager returns a [DeviceResources] object. - assign the allocated resources to the device. - The device manager register devices to the IO manager. - query trapped address ranges by calling [DeviceIo::get_trapped_io_resources()] - register the device to the IO manager with trapped address range - The guest access those trapped MMIO/PIO address ranges, and triggers VM IO Exit events to trap into the VMM. - The VMM parses the VM exit events and dispatch those events to the IO manager. - The IO manager looks up device by searching trapped address ranges, and call the device's [DeviceIO] handler to process those trapped MMIO/PIO access requests. ## Usage First, a vm needs to create an [IoManager] to help it dispatch I/O events to devices. And an [IoManager] has two types of bus, the PIO bus and the MMIO bus, to handle different types of IO. Then, when creating a device, it needs to implement the [DeviceIo] or [DeviceIoMut] trait to receive read or write events send by driver in guest OS: - `read()` and `write()` methods is used to deal with MMIO events - `pio_read()` and `pio_write()` methods is used to deal with PIO events - `get_assigned_resources()` method is used to get all resources assigned to the device - `get_trapped_io_resources()` method is used to get only MMIO/PIO resources assigned to the device The difference of [DeviceIo] and [DeviceIoMut] is the reference type of `self` passed to method: - [DeviceIo] trait would pass a immutable reference `&self` to method, so the implementation of device would provide interior mutability and thread-safe protection itself - [DeviceIoMut] trait would pass a mutable reference `&mut self` to method, and it can give mutability to device which is wrapped by `Mutex` directly to simplify the difficulty of achieving interior mutability. Additionally, the [DeviceIo] trait has an auto implement for `Mutex` Last, the device needs to be added to [IoManager] by using `register_device_io()`, and the function would add device to PIO bus and/or MMIO bus by the resources it have. If a device has not only MMIO resource but PIO resource, it would be added to both pio bus and mmio bus. So the device would wrapped by `Arc`. From now on, the [IoManager] will dispatch I/O requests for the registered address ranges to the device. ## Examples ```rust use std::sync::Arc; use dbs_device::device_manager::IoManager; use dbs_device::resources::{DeviceResources, Resource}; use dbs_device::{DeviceIo, IoAddress, PioAddress}; struct DummyDevice {} impl DeviceIo for DummyDevice { fn read(&self, base: IoAddress, offset: IoAddress, data: &mut [u8]) { println!( "mmio read, base: 0x{:x}, offset: 0x{:x}", base.raw_value(), offset.raw_value() ); } fn write(&self, base: IoAddress, offset: IoAddress, data: &[u8]) { println!( "mmio write, base: 0x{:x}, offset: 0x{:x}", base.raw_value(), offset.raw_value() ); } fn pio_read(&self, base: PioAddress, offset: PioAddress, data: &mut [u8]) { println!( "pio read, base: 0x{:x}, offset: 0x{:x}", base.raw_value(), offset.raw_value() ); } fn pio_write(&self, base: PioAddress, offset: PioAddress, data: &[u8]) { println!( "pio write, base: 0x{:x}, offset: 0x{:x}", base.raw_value(), offset.raw_value() ); } } // Allocate resources for device let mut resources = DeviceResources::new(); resources.append(Resource::MmioAddressRange { base: 0, size: 4096, }); resources.append(Resource::PioAddressRange { base: 0, size: 32 }); // Register device to `IoManager` with resources let device = Arc::new(DummyDevice {}); let mut manager = IoManager::new(); manager.register_device_io(device, &resources).unwrap(); // Dispatch I/O event from `IoManager` to device manager.mmio_write(0, &vec![0, 1]).unwrap(); let mut buffer = vec![0; 4]; manager.pio_read(0, &mut buffer); ``` ## License This project is licensed under [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). [DeviceIo::get_trapped_io_resources()]: https://docs.rs/dbs-device/0.1.0/dbs_device/trait.DeviceIo.html#method.get_trapped_io_resources [DeviceIo]: src/lib.rs [DeviceIoMut]: src/lib.rs [IoManager]: src/device_manager.rs [IoManagerContext]: src/device_manager.rs [ResourceConstraint]: src/resources.rs [Resource]: src/resources.rs [DeviceResources]: src/resources.rs [vm-device]: https://github.com/rust-vmm/vm-device