blob: e8a69278aaf30de3511c43a7fd7462875d81da95 [file] [log] [blame] [edit]
// 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.
#![deny(missing_docs)]
//! Safe Memory Mapped I/O for Fuchsia drivers.
//!
//! MMIO allows a program to interact with devices through operations on its address space. While
//! the interface is similar to regular memory access, there are some key semantic differences.
//! This crate provides safe interfaces for interacting with MMIO that are intended to be sound.
//!
//! # Memory semantics
//!
//! ## Safe Rust
//! The semantics of a safe Rust program are determined by the Rust Abstract Machine. The Rust
//! Abstract Machine notably does not provide certain guarantees that are required when interacting
//! with devices:
//!
//! - Guaranteed execution: I/O operations may be elided if the elision would have no impact on the
//! observable behavior of the executing thread (they may also be repeated).
//! - Operation ordering: operations may be reordered if doing so does not impact the observable
//! behavior of the executing thread.
//!
//! For memory that is under the control of the Rust Abstract Machine, the semantics of a safe Rust
//! program can depend on memory not changing value without a corresponding write.
//!
//! ## Device Memory
//! Device memory is not under the control of the Rust Abstract Machine. The guarantees around the
//! state and behavior of device memory do not provide the guarantees required for safe Rust.
//! Similarly, safe Rust cannot encode the semantics required to interact correctly with device
//! memory.
//!
//! ### Side Effects
//! Reading from or writing to device memory may have observable side effects, such as clearing a
//! status flag, advancing a buffer, or changing device state in some other way.
//!
//! ### Value Volatility
//! The value that would be returned when loading from a device memory address may change
//! completely independently of the program's control. Two subsequent reads on the same thread with
//! no interleaved write may return different values. A read of a just-written value is not
//! guaranteed to return the just-written value.
//!
//! ### Atomicity
//! A memory instruction which may be atomic when operating on regular memory is not guaranteed to
//! be atomic when interfacing with a device. For example a memory operation with a width larger
//! than the bus size supported by the device *must* be broken down into multiple operations.
//!
//! ### Elision and Spurious Accesses
//! As mentioned already, memory operations may be ellided or duplicated when executing safe Rust
//! provided they don't change the single-threaded semantics of the program. If a shared or mutable
//! reference to an object is active, memory operations may be performed to the backing memory. For
//! this reason accesses to a memory location via pointers should only be performed when there are
//! no active references that may alias the same memory.
//!
//! ### Compiler Reordering and Volatile
//! The Rust Compiler has some freedom to reorder instructions. For regular memory accesses the
//! compiler may reorder instructions where a data dependency does not prevent the reordering.
//!
//! The compiler is not allowed to reorder `volatile` operations with respect to other `volatile`
//! operations in the same thread. It is free to reorder `volatile` operations with other kinds of
//! memory accesses.
//!
//! By themselves, `volatile` operations cannot provide any sequencing guarantees with respect to
//! non-volatile operations on the same thread, or *any* kind of operation on another thread.
//!
//! # User Guide
//!
//! Users of this crate interact with device memory through the [Mmio] trait. This trait exposes
//! the low-level load and store operations to safely interact with device memory. All stores
//! performed through an [Mmio] instance are issued through a mutable reference.
//!
//! If an [Mmio] implementation also implements [MmioSplit], it is possible to split off
//! independently owned sub-regions from it. This allows concurrent mutable access to disjoint
//! MMIO regions.
//!
//! ## Operand Types
//! The [Mmio] trait provides load and store operations for the following types:
//!
//! - `u8`
//! - `u16`
//! - `u32`
//! - `u64`
//!
//! Conforming implementations of [Mmio] must perform these operations in the code order relative
//! to each other on the same thread, and in 1:1 correspondence with calls to corresponding
//! function.
//!
//! Where the operand type is larger than the bus size, an implementation should perform the
//! sub-operations in increasing address order.
//!
//! ## Dyn Compatibility and Trait Extensions
//! The [Mmio] trait is dyn compatible. Callers may want to also import the [MmioExt] trait which
//! defines some useful utilities on top of [Mmio] that would otherwise break dyn compatibility.
//! This trait is implemented automatically for any [Mmio].
//!
//! ## Alignment
//! The [Mmio] trait exposes offsets instead of addresses. Operations must be performed at a
//! suitable offset for the operand type. Valid alignment is not an intrinsic property of an offset.
//! Callers can use [Mmio::align_offset] to determine the first offset within the MMIO region
//! suitable for a given alignment.
//!
//! ## Usage Examples
//! ### VmoMapping
//! ```
//! use mmio::{Mmio, MmioExt};
//! use mmio::vmo::VmoMapping;
//! const VMO_OFFSET: usize = 0;
//! const VMO_LEN: usize = 1024;
//! # let vmo = zx::Vmo::create(VMO_LEN as u64).unwrap();
//! // Map the Vmo memory, returning an `MmioRegion`. The returned region implements `Mmio`.
//! let mut mmio = VmoMapping::map(VMO_OFFSET, VMO_LEN, vmo).unwrap();
//! // Load 4 bytes starting at offset 0.
//! let _ = mmio.load32(0);
//! // Store 2 bytes starting at offset 32.
//! let _ = mmio.store16(32, 0x1234);
//! ```
//!
//! ### Splittable VmoMapping
//! ```
//! use mmio::{Mmio, MmioExt};
//! use mmio::vmo::VmoMapping;
//! const VMO_LEN: usize = 1024;
//! # let vmo = zx::Vmo::create(VMO_LEN as u64).unwrap();
//! // Map the Vmo memory and convert it into a splittable `MmioRegion`.
//! // The returned region implements `Mmio + MmioSplit + Send`. If you only need split and not
//! // you can use `into_split` instead.
//! let mut mmio = VmoMapping::map(0, VMO_LEN, vmo).unwrap().into_split_send();
//!
//! // Split off a number of regions which have exclusive ownership of their ranges.
//! // reg1 owns the first 8 bytes, which start at offset 0 in the original mapping.
//! let mut reg1 = mmio.split_off(8);
//! // reg2 owns the next 4 bytes, which start at offset 8 in the original mapping.
//! let mut reg2 = mmio.split_off(4);
//! // reg3 owns the next 4 bytes, which start at offset 12 in the original mapping.
//! let mut reg3 = mmio.split_off(4);
//! // The original mmio owns the rest of the region, starting at offset 16 in the original
//! // mapping.
//!
//! let _ = reg1.load64(0);
//! reg1.store16(2, 0x1234);
//!
//! let _ = reg2.load32(0);
//! reg2.store8(3, 0xff);
//!
//! let _ = reg3.load32(0);
//! reg3.store16(0, 0xff00);
//! ```
//!
//! ### Alignment and Capacity
//! ```
//! use mmio::{Mmio, MmioExt};
//! use mmio::vmo::VmoMapping;
//! const VMO_LEN: usize = 1024;
//! # let vmo = zx::Vmo::create(VMO_LEN as u64).unwrap();
//! // Map the Vmo memory. The returned mapping can be converted into an `MmioRegion`.
//! let mut mmio = VmoMapping::map(0, VMO_LEN, vmo).unwrap().into_split();
//!
//! // Split off a number of regions which have exclusive ownership of their ranges.
//! // reg1 owns the first 8 bytes, which start at offset 0 in the original mapping.
//! let mut reg1 = mmio.split_off(8);
//! // reg2 owns the next 4 bytes, which start at offset 8 in the original mapping.
//! let mut reg2 = mmio.split_off(4);
//! // reg3 owns the next 4 bytes, which start at offset 12 in the original mapping.
//! let mut reg3 = mmio.split_off(4);
//! // The original mmio owns the rest of the region, starting at offset 16 in the original
//! // mapping.
//!
//! // reg1 owns the region starting at offset 0 in the Vmo, which is mapped to a page aligned
//! // boundary. It is suitably aligned for any type with an alignment <= Fuchsia's page size. It
//! // covers an 8 byte range, to can hold any `MmioOperand` type.
//! reg1.check_suitable_for::<u64>().unwrap();
//!
//! // reg2 owns the region starting at offset 8. It is aligned for any `MmioOperand` type but
//! // covers a 4 byte range.
//! reg2.check_aligned_for::<u64>().unwrap();
//! reg2.check_capacity_for::<u64>().expect_err("expect MmioError::OutOfRange");
//!
//! reg2.check_suitable_for::<u32>().unwrap();
//!
//! // reg3 owns the region starting at offset 12. It is aligned for types with an alignment <= 4
//! // and covers a 4 byte range.
//! reg3.check_aligned_for::<u64>().expect_err("expect MmioError::Unaligned");
//! reg3.check_capacity_for::<u64>().expect_err("expect MmioError::OutOfRange");
//!
//! reg3.check_suitable_for::<u32>().unwrap();
//!
//! // The original mmio object owns the region starting at offset 16 and covering the rest of the
//! // mapping.
//! mmio.check_suitable_for::<u64>().unwrap();
//! mmio.check_suitable_for::<[u64; 8]>().unwrap();
//! ```
//!
//! ## Testing
//! ### Regular Memory Implementation
//! ```
//! use mmio::memory::Memory;
//! use std::mem::MaybeUninit;
//!
//! let mut mem = MaybeUninit<[u64; 8]>::uninit();
//! // A MaybeUninit's memory can be borrowed as an Mmio region.
//! let mmio = Memory::borrow_uninit(&mut mem);
//!
//! run_test(mmio);
//!
//! // Safety: any bit pattern is valid for [u64; 8].
//! let mem = unsafe { mem.assume_init() };
//!
//! // Check that the memory is in the expected state.
//! validate_mem(mem);
//! ```
//!
//! # Implementers Guide
//! Implementers of [Mmio] and [MmioSplit] are required to uphold some guarantees. These are
//! discussed more thoroughly in the corresponding trait's documentation. These requirements are
//! intended to guarantee the semantics required to interface with devices correctly, as discussed
//! earlier.
mod arch;
mod memory;
mod mmio;
pub mod region;
pub mod vmo;
pub use mmio::*;