[−][src]Crate voladdress
voladdress
is a crate that makes it easy to work with volatile memory
addresses (eg: memory mapped hardware).
Specific memory mapped hardware addresses may have particular read and write rules, and we generally want to use those addresses more often than we name them. This crate provides the utilities for safely accessing memory mapped hardware, while preventing the compiler from optimizing our memory accesses away.
For example, on the GBA there's a palette of 256 background color values
(u16
) starting at 0x500_0000
, so you might write something like.
use typenum::consts::U256; use voladdress::{VolBlock, VolAddress}; pub type Color = u16; pub const PALRAM_BG: VolBlock<Color,U256> = unsafe { VolBlock::new(0x500_0000) };
And then in your actual program you might do something like this
fn main() { let i = 5; // the palette is all 0 (black) at startup. assert_eq!(PALRAM_BG.index(i).read(), 0); // we can make that index blue instead. const BLUE: u16 = 0b11111; PALRAM_BG.index(i).write(BLUE); assert_eq!(PALRAM_BG.index(i).read(), BLUE); }
You could use an address of any *mut T
that you have (which is how the
tests and doctests work), but the intent is that you use this crate with
memory mapped hardware. Exactly what hardware is memory mapped where depends
on your target device. Please read your target device's documentation.
Why Use This?
It may seem rather silly to have special types for what is basically a *mut T
. However, when reading and writing with a normal pointer (eg: *ptr
or
*ptr = x;
) Rust will desugar that to the
read and
write functions. The
compiler is allowed to elide these accesses if it "knows" what the value is
already going to be, or if it "knows" that the read will never be seen.
However, when working with memory mapped hardware the read and write
operations have various side effects that the compiler isn't aware of, so
the access must not be elided. You have to use
read_volatile
and
write_volatile,
which are immune to being elided by the compiler. The rust standard library
doesn't have a way to "tag" a pointer as being volatile to force that
volatile access always be used, and so we have this crate.
There are other crates that address the general issue of volatile memory,
but none that I've seen are as easy to use as this one. They generally
expect you to cast the target address (a usize
that you get out of your
hardware manual) into their crate's volatile type (eg: let p = 1234 as *mut VolatileCell<u16>
), but then you have to dereference that raw pointer each
time you call read or write, and it always requires parenthesis too,
because of prescience rules (eg: let a = (*p).read();
). You end up with
unsafe
blocks and parens and asterisks all over the code for no reason.
This crate is much better than any of that. Once you've decided that the
initial unsafety is alright, and you've created a VolAddress
value for
your target type at the target address, the read
and write
methods are
entirely safe to use and don't require the manual de-reference.
Can't you just impl Deref
and DerefMut
on these things?
No. Absolutely not. Both &T
and &mut T
use normal reads and writes, so
they'll elide the access just like a raw pointer would. In fact they're
more aggressive about it than raw pointers are because they assume that
either the target value never changes (&T
) so you don't ever need to read
twice, or the target value is exclusively controlled by the local scope
(&mut T
) so you never need to do intermediate writes. For standard code
this is exactly what we want (it makes the code faster to skip reads and
writes we don't need), but with memory mapped hardware this is the opposite
of a good time.
Structs
VolAddress | Abstracts the use of a volatile memory address. |
VolBlock | A block of addresses all in a row. |
VolIter | An iterator that produces consecutive |
VolSeries | A series of evenly strided addresses. |
VolStridingIter | An iterator that produces strided |