blob: 4aaf957527ce430292c129a94ba6b4de5f2c599b [file] [log] [blame]
// Copyright 2025 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use core::fmt::{Debug, Display};
use core::ops::{BitAnd, BitOr, Not};
/// An error which may be encountered when performing operations on an MMIO region.
#[derive(Copy, Clone, PartialEq, Eq, Debug, thiserror::Error)]
pub enum MmioError {
/// An operation was attempted outside the bounds of an MMIO region.
#[error("An MMIO operation was out of range")]
OutOfRange,
/// An operation would be unaligned for the operand type at the given offset into the MMIO
/// region.
#[error("An MMIO operation was unaligned")]
Unaligned,
}
/// An `Mmio` implementation provides access to an MMIO region.
///
/// # Device memory
/// For implementations that provide access to device memory, all memory must be accessed with
/// volatile semantics. I/O instructions should not be coalesced or cached and should occur in
/// program order.
///
/// Implementations are not required to provide any ordering guarantees between device memory
/// access and other instructions. Furthermore there is no guarantee that any side-effects are
/// visible to subsequent operations.
///
/// Callers who need guarantees around instruction ordering and side-effect visibility should
/// insert the appropriate compiler fences and/or memory barriers.
///
/// # Safety
/// Implementations of `Mmio` are required to be safe in a Rust sense, however loads may have
/// side-effects and device memory may change outside of the control of the host.
///
/// # Offsets, Alignment and Capacity
/// Mmio implementations provide access to device memory via offsets, not addresses. There are no
/// requirements on the alignment of offset `0`.
///
/// When using the load and store operations, callers are required to provide offsets representing
/// locations that are suitably aligned and fully within bounds of the location. Failure to do so
/// does not cause any safety issues, but may result in errors for the checked loads and stores, or
/// panics for the non-checked variants.
///
/// If these guarantees can't be provided statically for a given use case, the following functions
/// can be used to ensure these requirements:
///
/// - [MmioExt::check_aligned_for<T>]: returns an [MmioError::Unaligned] error if the given offset is
/// not suitably aligned.
/// - [MmioExt::check_capacity_for<T>]: returns an [MmioError::OutOfRange] error if there is not
/// sufficient capacity at the given offset.
/// - [MmioExt::check_suitable_for<T>]: returns an [MmioError] if there is not sufficient capacity at
/// the given offset or it is not suitably aligned.
///
/// # Dyn Compatibility
/// This trait is dyn compatible. See the [MmioExt] trait for useful utilities that extend this
/// trait.
pub trait Mmio {
/// Returns the size in bytes of this MMIO region.
fn len(&self) -> usize;
/// Returns true if the MMIO region has a length of 0.
fn is_empty(&self) -> bool {
self.len() == 0
}
/// Computes the first offset within this region that is aligned to `align`.
///
/// See the trait-level documentation for more information on offsets and alignment.
///
/// # Panics
/// If `align` is not a power of 2.
fn align_offset(&self, align: usize) -> usize;
/// Loads one byte from this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
fn try_load8(&self, offset: usize) -> Result<u8, MmioError>;
/// Loads one byte from this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Panics
/// - If the load would exceed the bounds of this MMIO region.
///
/// See [Mmio::try_load8] for a non-panicking version.
fn load8(&self, offset: usize) -> u8 {
self.try_load8(offset).unwrap()
}
/// Loads two bytes from this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_load16(&self, offset: usize) -> Result<u16, MmioError>;
/// Loads two bytes from this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Panics
/// - If the load would exceed the bounds of this MMIO region.
/// - If the offset is not suitably aligned.
///
/// See [Mmio::try_load16] for a non-panicking version.
fn load16(&self, offset: usize) -> u16 {
self.try_load16(offset).unwrap()
}
/// Loads four bytes from this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_load32(&self, offset: usize) -> Result<u32, MmioError>;
/// Loads four bytes from this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Panics
/// - If the load would exceed the bounds of this MMIO region.
/// - If the offset is not suitably aligned.
///
/// See [Mmio::try_load32] for a non-panicking version.
fn load32(&self, offset: usize) -> u32 {
self.try_load32(offset).unwrap()
}
/// Loads eight bytes from this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_load64(&self, offset: usize) -> Result<u64, MmioError>;
/// Loads eight bytes from this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Panics
/// - If the load would exceed the bounds of this MMIO region.
/// - If the offset is not suitably aligned.
///
/// See [Mmio::try_load64] for a non-panicking version.
fn load64(&self, offset: usize) -> u64 {
self.try_load64(offset).unwrap()
}
/// Stores one byte to this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
fn try_store8(&mut self, offset: usize, value: u8) -> Result<(), MmioError>;
/// Stores one byte to this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Panics
/// - If the store would exceed the bounds of this MMIO region.
///
/// See [Mmio::try_store8] for a non-panicking version.
fn store8(&mut self, offset: usize, value: u8) {
self.try_store8(offset, value).unwrap();
}
/// Stores two bytes to this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_store16(&mut self, offset: usize, value: u16) -> Result<(), MmioError>;
/// Stores two bytes to this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Panics
/// - If the store would exceed the bounds of this MMIO region.
/// - If the offset is not suitably aligned.
///
/// See [Mmio::try_store16] for a non-panicking version.
fn store16(&mut self, offset: usize, value: u16) {
self.try_store16(offset, value).unwrap();
}
/// Stores four bytes to this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_store32(&mut self, offset: usize, value: u32) -> Result<(), MmioError>;
/// Stores four bytes to this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Panics
/// - If the store would exceed the bounds of this MMIO region.
/// - If the offset is not suitably aligned.
///
/// See [Mmio::try_store32] for a non-panicking version.
fn store32(&mut self, offset: usize, value: u32) {
self.try_store32(offset, value).unwrap();
}
/// Stores eight bytes to this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_store64(&mut self, offset: usize, value: u64) -> Result<(), MmioError>;
/// Stores eight bytes to this MMIO region at the given offset.
///
/// See the trait-level documentation for information about offset requirements.
///
/// # Panics
/// - If the store would exceed the bounds of this MMIO region.
/// - If the offset is not suitably aligned.
///
/// See [Mmio::try_store64] for a non-panicking version.
fn store64(&mut self, offset: usize, value: u64) {
self.try_store64(offset, value).unwrap();
}
}
/// An MMIO region from which ownership of disjoint sub-regions can be split off.
pub trait MmioSplit: Mmio + Sized {
/// Splits this Mmio region into two at the given mid-point and returns the left-hand-side.
/// This MMIO region's bounds are updated to contain only the right-hand-side.
///
/// # Errors
/// - [MmioError::OutOfRange]: if `mid > self.len()`.
fn try_split_off(&mut self, mid: usize) -> Result<Self, MmioError>;
/// Splits this MMIO region into two at the given mid-point and returns the left-hand-side.
/// This MMIO region's bounds are updated to contain only the right-hand-side.
///
/// # Panics
/// - If `mid > self.len()`.
fn split_off(&mut self, mid: usize) -> Self {
self.try_split_off(mid).unwrap()
}
}
/// Implemented for the fundamental types supported by all [Mmio] implementations.
///
/// This is a sealed trait, implemented for the types: u8, u16, u32, u64.
pub trait MmioOperand:
BitAnd<Output = Self>
+ BitOr<Output = Self>
+ Not<Output = Self>
+ sealed::MmioOperand
+ PartialEq
+ Copy
+ Debug
+ Display
+ Sized
{
/// Loads a value of the this type from the MMIO region at the given offset.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError>;
/// Stores a value of this type to the MMIO region at the given offset.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned: if the offset is not suitably aligned.
fn try_store<M: Mmio + ?Sized>(
mmio: &mut M,
offset: usize,
value: Self,
) -> Result<(), MmioError>;
}
mod sealed {
/// Internal sealed MmioOperand trait as the types implementing this must match the types in
/// the loadn/storen trait methods.
pub trait MmioOperand {}
impl MmioOperand for u8 {}
impl MmioOperand for u16 {}
impl MmioOperand for u32 {}
impl MmioOperand for u64 {}
}
impl MmioOperand for u8 {
fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
mmio.try_load8(offset)
}
fn try_store<M: Mmio + ?Sized>(
mmio: &mut M,
offset: usize,
value: Self,
) -> Result<(), MmioError> {
mmio.try_store8(offset, value)
}
}
impl MmioOperand for u16 {
fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
mmio.try_load16(offset)
}
fn try_store<M: Mmio + ?Sized>(
mmio: &mut M,
offset: usize,
value: Self,
) -> Result<(), MmioError> {
mmio.try_store16(offset, value)
}
}
impl MmioOperand for u32 {
fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
mmio.try_load32(offset)
}
fn try_store<M: Mmio + ?Sized>(
mmio: &mut M,
offset: usize,
value: Self,
) -> Result<(), MmioError> {
mmio.try_store32(offset, value)
}
}
impl MmioOperand for u64 {
fn try_load<M: Mmio + ?Sized>(mmio: &M, offset: usize) -> Result<Self, MmioError> {
mmio.try_load64(offset)
}
fn try_store<M: Mmio + ?Sized>(
mmio: &mut M,
offset: usize,
value: Self,
) -> Result<(), MmioError> {
mmio.try_store64(offset, value)
}
}
/// This trait extends [Mmio] with some useful utilities. There is a blanket implementation for all
/// types implementing [Mmio].
///
/// Functions may go into this trait instead of [Mmio] if any of the following is true:
/// - their behavior shouldn't differ across Mmio implementations
/// - they would make Mmio not dyn compatible
/// - they would introduce an unnecessary burden on [Mmio] implementers
pub trait MmioExt: Mmio {
/// Check that the given offset into this MMIO region would be suitable aligned for type `T`.
/// There is no guarantee that this offset is within the bounds of the MMIO region or that
/// there would be sufficient capacity to hold a `T` at that offset. See
/// [MmioExt::check_suitable_for].
///
/// Returns [MmioError::Unaligned] if the offset os not suitably aligned.
fn check_aligned_for<T>(&self, offset: usize) -> Result<(), MmioError> {
let align = align_of::<T>();
let align_offset = self.align_offset(align);
// An offset is aligned if (offset = align_offset + i * align) for some i.
if offset.wrapping_sub(align_offset).is_multiple_of(align) {
Ok(())
} else {
Err(MmioError::Unaligned)
}
}
/// Checks that the given offset into this MMIO region has sufficient capacity to hold a value
/// of type `T`. There is no guarantee that the offset is suitably aligned. See
/// [MmioExt::check_capacity_for].
fn check_capacity_for<T>(&self, offset: usize) -> Result<(), MmioError> {
let capacity_at_offset = self.len().checked_sub(offset).ok_or(MmioError::OutOfRange)?;
if capacity_at_offset >= size_of::<T>() { Ok(()) } else { Err(MmioError::OutOfRange) }
}
/// Checks that the given offset into this MMIO rgion is suitably aligned and has sufficient
/// capacity for a value of type `T`.
fn check_suitable_for<T>(&self, offset: usize) -> Result<(), MmioError> {
self.check_aligned_for::<T>(offset)?;
self.check_capacity_for::<T>(offset)?;
Ok(())
}
/// Loads an [MmioOperand] from the MMIO region at the given offset.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_load<T: MmioOperand>(&self, offset: usize) -> Result<T, MmioError> {
T::try_load(self, offset)
}
/// Loads an [MmioOperand] from the MMIO region at the given offset.
///
/// # Panics
/// If `self.check_suitable_for::<T>(offset)` would fail.
///
/// See [MmioExt::try_load] for a non-panicking version.
fn load<T: MmioOperand>(&self, offset: usize) -> T {
self.try_load(offset).unwrap()
}
/// Stores an [MmioOperand] to the MMIO region at the given offset.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the store would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_store<T: MmioOperand>(&mut self, offset: usize, value: T) -> Result<(), MmioError> {
T::try_store(self, offset, value)
}
/// Stores an [MmioOperand] to the MMIO region at the given offset.
///
/// # Panics
/// If `self.check_suitable_for::<T>(offset)` would fail.
///
/// See [MmioExt::try_store] for a non-panicking version.
fn store<T: MmioOperand>(&mut self, offset: usize, value: T) {
self.try_store(offset, value).unwrap()
}
/// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
/// set in the given mask.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> Result<T, MmioError> {
self.try_load::<T>(offset).map(|v| v & mask)
}
/// Loads an [MmioOperand] value from an MMIO region at the given offset, returning only the bits
/// set in the given mask.
///
/// # Panics
/// If `self.check_suitable_for::<T>(offset)` would fail.
///
/// See [MmioExt::try_masked_load] for a non-panicking version.
fn masked_load<T: MmioOperand>(&self, offset: usize, mask: T) -> T {
self.try_masked_load::<T>(offset, mask).unwrap()
}
/// Updates the value in the MMIO region at the given offset, only modifying the bits set in
/// the given mask.
///
/// This operation performs a read-modify-write in order to only modify the bits matching the
/// mask. As this is performed as a load from device memory followed by a store to device
/// memory, the device may change state in between these operations.
///
/// Callers must ensure that the this sequence of operations is valid for the device they're
/// accessing and their use case.
///
/// # Errors
/// - [MmioError::OutOfRange]: if the load would exceed the bounds of this MMIO region.
/// - [MmioError::Unaligned]: if the offset is not suitably aligned.
fn try_masked_modify<T: MmioOperand>(
&mut self,
offset: usize,
mask: T,
value: T,
) -> Result<(), MmioError> {
let current = self.try_load::<T>(offset)?;
let unchanged_bits = current & !mask;
let changed_bits = value & mask;
self.try_store(offset, unchanged_bits | changed_bits)
}
/// Updates the value in the MMIO region at the given offset, only modifying the bits set in
/// the given mask.
///
/// This operation performs a read-modify-write in order to only modify the bits matching the
/// mask. As this is performed as a load from device memory followed by a store to device
/// memory, the device may change state in between these operations.
///
/// Callers must ensure that the this sequence of operations is valid for the device they're
/// accessing and their use case.
///
/// # Panics
///
/// If `self.check_suitable_for::<T>(offset)` would fail.
///
/// See [MmioExt::try_masked_modify] for a non-panicking version.
fn masked_modify<T: MmioOperand>(&mut self, offset: usize, mask: T, value: T) {
self.try_masked_modify(offset, mask, value).unwrap()
}
}
impl<M: Mmio> MmioExt for M {}