Memory-Mapped I/O (MMIO)
This page explains the soundness problems with naive MMIO access in Rust and why Patina uses the
safe-mmio crate.
The patina crate re-exports safe-mmio through a patina::mmio wrapper module. All Patina code should
import from patina::mmio rather than depending on safe-mmio directly. This:
- Allows consumers to use the crate without adding
safe-mmioas a direct dependency. - Reduces the likelihood of the same crate being compiled multiple times with different versions.
Crates that already depend on patina should not add safe-mmio to their own Cargo.toml.
The Problem with References to MMIO Space
In C, casting an address to a pointer and performing volatile reads/writes is the standard way to access device
registers. In Rust, it might seem natural to create a reference (&T or &mut T) to a #[repr(C)] struct
laid out over the register block. However, creating a Rust reference to MMIO space is unsound.
The Rust compiler is free to insert additional reads through any &T reference at any time. For normal memory this
is harmless, but for MMIO registers an extra read can clear an interrupt status, pop a byte from a FIFO, or trigger
other device side-effects. More details are documented
and tracked by the Rust Embedded Working Group.
Because this is part of Rust's reference semantics, MMIO must be accessed exclusively through raw pointers combined with volatile operations.
Why safe-mmio
Patina uses the safe-mmio crate because it directly addresses the following
concerns:
-
No references to MMIO space.
UniqueMmioPointer<T>is a unique owned pointer to the registers of some MMIO device. It never creates a&Tto device memory, eliminating potential for undefined behavior. -
Read side-effect distinction. The crate separates read-with-side-effects (
ReadOnlyandReadWriterequires&mut UniqueMmioPointer) from pure reads (ReadPureandReadPureWriteallows&UniqueMmioPointeror&SharedMmioPointer). This encodes at the type level whether a read is safe to perform. -
Correct codegen on AArch64. On AArch64,
read_volatile/write_volatilecan emit instructions that cannot be virtualized.safe-mmioworks around this by using inline assembly to emit the correct MMIO instructions on AArch64.
Field Wrapper Types
The below table is provided for quick Patina developer reference. See the safe-mmio documentation for full API details.
| Wrapper | Read | Write | Read Requires &mut | Notes |
|---|---|---|---|---|
ReadPure<T> | yes | no | no | Pure read, no device side-effects |
ReadOnly<T> | yes | no | yes | Read has device side-effects |
WriteOnly<T> | no | yes | n/a | Write-only register |
ReadWrite<T> | yes | yes | yes | Read has side-effects |
ReadPureWrite<T> | yes | yes | no (for read) | Read is pure, write allowed |
See safe-mmio usage instructions for examples of how to define register blocks and access registers.
Further Reading
safe-mmioREADME — full API overview and comparison with other MMIO cratessafe-mmiodocs.rs — API reference