2 unstable releases
new 0.2.0 | Mar 12, 2025 |
---|---|
0.1.0 | Feb 24, 2025 |
#223 in Embedded development
171 downloads per month
Used in 2 crates
(via arm-pl011-uart)
39KB
545 lines
safe-mmio
This crate provides types for safe MMIO device access, especially in systems with an MMU.
This is not an officially supported Google product.
Usage
UniqueMmioPointer
The main type provided by this crate is UniqueMmioPointer
. A UniqueMmioPointer<T>
is roughly
equivalent to an &mut T
for a memory-mapped IO device. Suppose you want to construct a pointer to
the data register of some UART device, and write some character to it:
use core::ptr::NonNull;
use safe_mmio::UniqueMmioPointer;
let mut data_register =
unsafe { UniqueMmioPointer::<u8>::new(NonNull::new(0x900_0000 as _).unwrap()) };
unsafe {
data_register.write_unsafe(b'x');
}
Depending on your platform this will either use write_volatile
or some platform-dependent inline
assembly to perform the MMIO write.
Safe MMIO methods
If you know that a particular MMIO field is safe to access, you can use the appropriate wrapper type to mark that. In this case, suppose that the UART data register should only be written to:
use core::ptr::NonNull;
use safe_mmio::{fields::WriteOnly, UniqueMmioPointer};
let mut data_register: UniqueMmioPointer<WriteOnly<u8>> =
unsafe { UniqueMmioPointer::new(NonNull::new(0x900_0000 as _).unwrap()) };
data_register.write(b'x');
Grouping registers with a struct
In practice, most devices have more than one register. To model this, you can create a struct, and
then use the field!
macro to project from a UniqueMmioPointer
to the struct to a pointer to one
of its fields:
use core::ptr::NonNull;
use safe_mmio::{
field,
fields::{ReadOnly, ReadPure, ReadWrite, WriteOnly},
UniqueMmioPointer,
};
#[repr(C)]
struct UartRegisters {
data: ReadWrite<u8>,
status: ReadPure<u8>,
pending_interrupt: ReadOnly<u8>,
}
let mut uart_registers: UniqueMmioPointer<UartRegisters> =
unsafe { UniqueMmioPointer::new(NonNull::new(0x900_0000 as _).unwrap()) };
field!(uart_registers, data).write(b'x');
Methods are also provided to go from a UniqueMmioPointer
to an array or slice to its elements.
Pure reads vs. side-effects
We distinguish between fields which for which MMIO reads may have side effects (e.g. popping a byte
from the UART's receive FIFO or clearing an interrupt status) and those for which reads are 'pure'
with no side-effects. Reading from a ReadOnly
or ReadWrite
field is assumed to have
side-effects, whereas reading from a ReadPure
or ReadPureWrite
must not. Reading from a
ReadOnly
or ReadWrite
field requires an &mut UniqueMmioPointer
(the same as writing), whereas
reading from a ReadPure
or ReadPureWrite
field can be done with an &UniqueMmioPointer
or
&SharedMmioPointer
.
Physical addresses
UniqueMmioPointer
(and SharedMmioPointer
) is used for a pointer to a device which is mapped into
the page table and accessible, i.e. a virtual address. Sometimes you may want to deal with the
physical address of a device, which may or may not be mapped in. For this you can use the
PhysicalInstance
type. A PhysicalInstance
doesn't let you do anything other than get the
physical address and size of the device's MMIO region, but is intended to convey ownership. There
should never be more than one PhysicalInstance
pointer to the same device. This way your page
table management code can take a PhysicalInstance<T>
and return a UniqueMmioPointer<T>
when a
device is mapped into the page table.
Comparison with other MMIO crates
There are a number of things that distinguish this crate from other crates providing abstractions for MMIO in Rust.
- We avoid creating references to MMIO address space. The Rust compiler is free to dereference references whenever it likes, so constructing references to MMIO address space (even temporarily) can lead to undefined behaviour. See https://github.com/rust-embedded/volatile-register/issues/10 for more background on this.
- We distinguish between MMIO reads which have side-effects (e.g. clearing an interrupt status, or popping from a queue) and those which don't (e.g. just reading some status). A read which has side-effects should be treated like a write and only be allowed from a unique pointer (passed via &mut) whereas a read without side-effects can safely be done via a shared pointer (passed via '&'), e.g. simultaneously from multiple threads.
- On most platforms MMIO reads and writes can be done via
read_volatile
andwrite_volatile
, but on aarch64 this may generate instructions which can't be virtualised. This is arguably a bug in rustc, but in the meantime we work around this by using inline assembly to generate the correct instructions for MMIO reads and writes on aarch64.
Crate name | Last release | Version | Avoids references | Distinguishes reads with side-effects | Works around aarch64 volatile bug | Model | Field projection | Notes |
---|---|---|---|---|---|---|---|---|
safe-mmio | unreleased | 0.2.0 | ✅ | ✅ | ✅ | struct with field wrappers | macro | |
derive-mmio | February 2025 | 0.3.0 | ✅ | ❌ | ❌ | struct with derive macro | only one level, through derive macro | |
volatile | June 2024 | 0.6.1 | ✅ | ❌ | ❌ | struct with derive macro | macro or generated methods | |
volatile-register | October 2023 | 0.2.2 | ❌ | ❌ | ❌ | struct with field wrappers | manual (references) | |
tock-registers | September 2023 | 0.9.0 | ❌ | ❌ | ❌ | macros to define fields and structs | manual (references) | Also covers CPU registers, and bitfields |
mmio | May 2021 | 2.1.0 | ✅ | ❌ | ❌ | only deals with individual fields | ❌ | |
rumio | March 2021 | 0.2.0 | ✅ | ❌ | ❌ | macros to define fields and structs | generated methods | Also covers CPU registers, and bitfields |
vcell | January 2021 | 0.1.3 | ❌ | ❌ | ❌ | plain struct | manual (references) | |
register | January 2021 | 1.0.2 | ❌ | ❌ | ❌ | macros to define fields and structs | manual (references) | Deprecated in favour of tock-registers. Also covers CPU registers, and bitfields. |
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Contributing
If you want to contribute to the project, see details of how we accept contributions.
Dependencies
~0.8–1.4MB
~23K SLoC