vm-device 0.1.0

management for virtual devices and resources
Documentation
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause

//! Provides abstractions for modelling an I/O bus.
//!
//! A bus is seen here as a mapping between
//! disjoint intervals (ranges) from an address space and objects (devices) associated with them.
//! A single device can be registered with multiple ranges, but no two ranges can overlap,
//! regardless with their device associations.

mod address;
mod range;

use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::fmt::{Display, Formatter};
use std::result::Result;

use address::BusAddress;

pub use address::{MmioAddress, MmioAddressOffset, PioAddress, PioAddressOffset};
pub use range::{BusRange, MmioRange, PioRange};

/// Errors encountered during bus operations.
#[derive(Debug, PartialEq)]
pub enum Error {
    /// No device is associated with the specified address or range.
    DeviceNotFound,
    /// Specified range overlaps an already registered range.
    DeviceOverlap,
    /// Access with invalid length attempted.
    InvalidAccessLength(usize),
    /// Invalid range provided (either zero-sized, or last address overflows).
    InvalidRange,
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Error::DeviceNotFound => write!(f, "device not found"),
            Error::DeviceOverlap => write!(f, "range overlaps with existing device"),
            Error::InvalidAccessLength(len) => write!(f, "invalid access length ({})", len),
            Error::InvalidRange => write!(f, "invalid range provided"),
        }
    }
}

impl std::error::Error for Error {}

/// A bus that's agnostic to the range address type and device type.
pub struct Bus<A: BusAddress, D> {
    devices: BTreeMap<BusRange<A>, D>,
}

impl<A: BusAddress, D> Default for Bus<A, D> {
    fn default() -> Self {
        Bus {
            devices: BTreeMap::new(),
        }
    }
}

impl<A: BusAddress, D> Bus<A, D> {
    /// Create an empty bus.
    pub fn new() -> Self {
        Self::default()
    }

    /// Return the registered range and device associated with `addr`.
    pub fn device(&self, addr: A) -> Option<(&BusRange<A>, &D)> {
        // The range is returned as an optimization because the caller
        // might need both the device and its associated bus range.
        // The same goes for the device_mut() method.
        self.devices
            .range(..=BusRange::unit(addr))
            .nth_back(0)
            .filter(|pair| pair.0.last() >= addr)
    }

    /// Return the registered range and a mutable reference to the device
    /// associated with `addr`.
    pub fn device_mut(&mut self, addr: A) -> Option<(&BusRange<A>, &mut D)> {
        self.devices
            .range_mut(..=BusRange::unit(addr))
            .nth_back(0)
            .filter(|pair| pair.0.last() >= addr)
    }

    /// Register a device with the provided range.
    pub fn register(&mut self, range: BusRange<A>, device: D) -> Result<(), Error> {
        for r in self.devices.keys() {
            if range.overlaps(r) {
                return Err(Error::DeviceOverlap);
            }
        }

        self.devices.insert(range, device);

        Ok(())
    }

    /// Deregister the device associated with `addr`.
    pub fn deregister(&mut self, addr: A) -> Option<(BusRange<A>, D)> {
        let range = self.device(addr).map(|(range, _)| *range)?;
        self.devices.remove(&range).map(|device| (range, device))
    }

    /// Verify whether an access starting at `addr` with length `len` fits within any of
    /// the registered ranges. Return the range and a handle to the device when present.
    pub fn check_access(&self, addr: A, len: usize) -> Result<(&BusRange<A>, &D), Error> {
        let access_range = BusRange::new(
            addr,
            A::V::try_from(len).map_err(|_| Error::InvalidAccessLength(len))?,
        )
        .map_err(|_| Error::InvalidRange)?;
        self.device(addr)
            .filter(|(range, _)| range.last() >= access_range.last())
            .ok_or(Error::DeviceNotFound)
    }
}

/// Represents an MMIO bus.
pub type MmioBus<D> = Bus<MmioAddress, D>;
/// Represents a PIO bus.
pub type PioBus<D> = Bus<PioAddress, D>;

/// Helper trait that can be implemented by types which hold one or more buses.
pub trait BusManager<A: BusAddress> {
    /// Type of the objects held by the bus.
    type D;

    /// Return a reference to the bus.
    fn bus(&self) -> &Bus<A, Self::D>;

    /// Return a mutable reference to the bus.
    fn bus_mut(&mut self) -> &mut Bus<A, Self::D>;
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_bus() {
        let base = MmioAddress(10);
        let base_prev = MmioAddress(base.value().checked_sub(1).unwrap());
        let len = 10;
        let range = MmioRange::new(base, len).unwrap();
        let range_next = range.last().checked_add(1).unwrap();

        let mut bus = Bus::new();
        // The bus is agnostic to actual device types, so let's just use a numeric type here.
        let device = 1u8;

        assert_eq!(bus.devices.len(), 0);

        bus.register(range, device).unwrap();
        assert_eq!(bus.devices.len(), 1);

        assert!(bus.device(base_prev).is_none());
        assert!(bus.device_mut(base_prev).is_none());
        assert!(bus.device(range_next).is_none());
        assert!(bus.device_mut(range_next).is_none());

        for offset in 0..len {
            let addr = base.checked_add(offset).unwrap();

            {
                let (r, d) = bus.device(addr).unwrap();
                assert_eq!(range, *r);
                assert_eq!(device, *d);
            }

            {
                let (r, d) = bus.device_mut(addr).unwrap();
                assert_eq!(range, *r);
                assert_eq!(device, *d);
            }

            // Let's also check invocations of `Bus::check_access`.
            for start_offset in 0..offset {
                let start_addr = base.checked_add(start_offset).unwrap();

                let (r, d) = bus
                    .check_access(start_addr, usize::try_from(offset - start_offset).unwrap())
                    .unwrap();
                assert_eq!(range, *r);
                assert_eq!(device, *d);
            }
        }

        // Detect double registration with the same range.
        assert_eq!(bus.register(range, device), Err(Error::DeviceOverlap));

        // We detect overlaps even if it's another range associated with the same device (we don't
        // implicitly merge ranges). `check_access` fails if the specified range does not fully
        // fit within a region associated with a particular device.

        {
            let range2 = MmioRange::new(MmioAddress(1), 10).unwrap();
            assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap));
            assert_eq!(
                bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()),
                Err(Error::DeviceNotFound)
            );
        }

        {
            let range2 = MmioRange::new(range.last(), 10).unwrap();
            assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap));
            assert_eq!(
                bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()),
                Err(Error::DeviceNotFound)
            );
        }

        {
            let range2 = MmioRange::new(MmioAddress(1), range.last().value() + 100).unwrap();
            assert_eq!(bus.register(range2, device), Err(Error::DeviceOverlap));
            assert_eq!(
                bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()),
                Err(Error::DeviceNotFound)
            );
        }

        {
            // For a completely empty range, `check_access` should still fail, but `insert`
            // will succeed.

            let range2 = MmioRange::new(range.last().checked_add(1).unwrap(), 5).unwrap();

            assert_eq!(
                bus.check_access(range2.base(), usize::try_from(range2.size()).unwrap()),
                Err(Error::DeviceNotFound)
            );

            // Validate registration, and that `deregister` works for all addresses within a range.
            for offset in 0..range2.size() {
                let device2 = device + 1;
                assert!(bus.register(range2, device2).is_ok());
                assert_eq!(bus.devices.len(), 2);

                let addr = range2.base().checked_add(offset).unwrap();
                let (r, d) = bus.deregister(addr).unwrap();
                assert_eq!(bus.devices.len(), 1);
                assert_eq!(r, range2);
                assert_eq!(d, device2);

                // A second deregister should fail.
                assert!(bus.deregister(addr).is_none());
                assert_eq!(bus.devices.len(), 1);
            }

            // Register the previous `device` for `range2`.
            assert!(bus.register(range2, device).is_ok());
            assert_eq!(bus.devices.len(), 2);

            // Even though the new range is associated with the same device, and right after the
            // previous one, accesses across multiple ranges are not allowed for now.
            // TODO: Do we want to support this in the future?
            assert_eq!(
                bus.check_access(range.base(), usize::try_from(range.size() + 1).unwrap()),
                Err(Error::DeviceNotFound)
            );
        }

        // Ensure that bus::check_access() fails when the len argument
        // cannot be safely converted to PioAddressOffset which is u16.
        let pio_base = PioAddress(10);
        let pio_len = 10;
        let pio_range = PioRange::new(pio_base, pio_len).unwrap();
        let mut pio_bus = Bus::new();
        let pio_device = 1u8;
        pio_bus.register(pio_range, pio_device).unwrap();
        assert_eq!(
            pio_bus.check_access(pio_base, usize::MAX),
            Err(Error::InvalidAccessLength(usize::MAX))
        );
    }
}