[][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 VolAddress values.

VolSeries

A series of evenly strided addresses.

VolStridingIter

An iterator that produces strided VolAddress values.