blob: cfe39bae359590bfedbcf349fec3a35168a1faf4 [file] [log] [blame]
// 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))
);
}
}