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:

  1. Allows consumers to use the crate without adding safe-mmio as a direct dependency.
  2. 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:

  1. No references to MMIO space. UniqueMmioPointer<T> is a unique owned pointer to the registers of some MMIO device. It never creates a &T to device memory, eliminating potential for undefined behavior.

  2. Read side-effect distinction. The crate separates read-with-side-effects (ReadOnly and ReadWrite requires &mut UniqueMmioPointer) from pure reads (ReadPure and ReadPureWrite allows &UniqueMmioPointer or &SharedMmioPointer). This encodes at the type level whether a read is safe to perform.

  3. Correct codegen on AArch64. On AArch64, read_volatile/write_volatile can emit instructions that cannot be virtualized. safe-mmio works 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.

WrapperReadWriteRead Requires &mutNotes
ReadPure<T>yesnonoPure read, no device side-effects
ReadOnly<T>yesnoyesRead has device side-effects
WriteOnly<T>noyesn/aWrite-only register
ReadWrite<T>yesyesyesRead has side-effects
ReadPureWrite<T>yesyesno (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