[][src]Crate voladdress

voladdress is a crate that makes it easy to work with volatile memory addresses (eg: memory mapped hardware).

When working with volatile memory, it's assumed that you'll generally be working with one or more of:

  • A single address (VolAddress)
  • A block of contiguous memory addresses (VolBlock)
  • A series of evenly strided memory addresses (VolSeries)

All the types have unsafe creation and then safe use, so that the actual usage is as ergonomic as possible. Obviously you tend to use an address far more often than you name an address, so that should be the best part of the experience. Iterators are also provided for the VolBlock and VolSeries types.

For example, on the GBA there's a palette of 256 color values (u16) for the background palette 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 benefit.

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 impl Deref/DerefMut and Index/IndexMut on these things?

No. Absolutely not. They all return &T or &mut T, which use normal reads and writes, so the accesses can be elided by the compiler. In fact references end up being more aggressive about access elision than happens raw pointers. 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.

Modules

read_only

This is like the top level module, but types here are read only.

write_only

This is like the top level module, but types here are write only.

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.