2 unstable releases

0.2.0 Oct 24, 2022
0.1.0 Mar 7, 2022

#863 in Hardware support

Download history 58/week @ 2023-12-04 20/week @ 2023-12-11 35/week @ 2023-12-18 27/week @ 2023-12-25 31/week @ 2024-01-01 31/week @ 2024-01-08 23/week @ 2024-01-15 11/week @ 2024-01-22 34/week @ 2024-01-29 21/week @ 2024-02-05 33/week @ 2024-02-12 66/week @ 2024-02-19 74/week @ 2024-02-26 51/week @ 2024-03-04 57/week @ 2024-03-11 38/week @ 2024-03-18

223 downloads per month
Used in 5 crates (4 directly)

Apache-2.0

65KB
1K SLoC

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:

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 and 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.
  • 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<T: DeviceIoMut>

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<T>.

From now on, the IoManager will dispatch I/O requests for the registered address ranges to the device.

Examples

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.

Dependencies

~0.3–0.8MB
~19K SLoC