11 releases (4 stable)
1.4.0 | May 15, 2024 |
---|---|
1.3.0 | Mar 5, 2023 |
1.2.3 | Feb 20, 2023 |
1.2.2 | Nov 8, 2022 |
0.2.3 | Mar 16, 2019 |
#101 in Embedded development
370 downloads per month
Used in 5 crates
(4 directly)
60KB
1K
SLoC
voladdress
A crate to make volatile memory operations easy to work with.
This is primarily used for Memory Mapped IO (MMIO).
lib.rs
:
A crate for working with volatile locations, particularly Memory Mapped IO (MMIO).
Types
The crate's core type is [VolAddress<T, R, W>].
T
is the element type stored at the address. It is expected that your element type will be something that the CPU can read and write with a single instruction. Generally this will be a single integer, float, data pointer, function pointer, or arepr(transparent)
wrapper around one of the other types just listed.R
should be [Safe], [Unsafe], or()
. WhenR
isSafe
then you can safely read from the address. WhenR
isUnsafe
then you can unsafely read from the address. IfR
is any other type then you cannot read from the address at all. While any possible type can be used here, if reading isn't intended you should use()
as the canonical null type.W
works likeR
in terms of what types you should use with it, but it controls writing instead of reading.
The VolAddress
type uses the "unsafe creation, then safe use" style. This
allows us to use the fewest unsafe
blocks overall. Once a VolAddress
has
been unsafely declared, each individual operation using them is generally
going to be safe. Some addresses might be unsafe to use even after creation,
but this is relatively rare.
Here are some example declarations. Note that the address values used are for illustation purposes only, and will vary for each device.
// read-only
pub const VCOUNT: VolAddress<u16, Safe, ()> =
unsafe { VolAddress::new(0x0400_0006) };
// write-only
pub const BG0_XOFFSET: VolAddress<u16, (), Safe> =
unsafe { VolAddress::new(0x0400_0010) };
// read-write
pub const BLDALPHA_A: VolAddress<u8, Safe, Safe> =
unsafe { VolAddress::new(0x0400_0052) };
// this location has some illegal bit patterns, so it's unsafe
// to write to with any random `u16` you might have.
pub const RAW_DISPLAY_CONTROL: VolAddress<u16, Safe, Unsafe> =
unsafe { VolAddress::new(0x0400_0000) };
// If we use a transparent wrapper and getter/setters, we can
// prevent the illegal bit patterns, and now it's safe to write.
#[repr(transparent)]
pub struct DisplayCtrl(u16);
pub const DISPLAY_CONTROL: VolAddress<DisplayCtrl, Safe, Safe> =
unsafe { VolAddress::new(0x0400_0000) };
Multiple Locations
Often we have many identically typed values at a regular pattern in memory. These are handled with two very similar types.
[VolBlock<T, R, W, const C: usize>] is for when there's many values tightly packed, with no space in between. Use this type when you want to emulate how an array works.
[VolSeries<T, R, W, const C: usize, const S: usize>] is for when you have many values strided out at regular intervals, but they have extra space in between each element.
In both cases, there's two basic ways to work with the data:
- Using
len
,index
, andget
, you can produce individualVolAddress
values similar to how a slice can produce references into the slice's data range. - Using
iter
oriter_range
you can produce an in iterator that will go over the variousVolAddress
values during the iteration.
pub const BG_PALETTE: VolBlock<u16, Safe, Safe, 256> =
unsafe { VolBlock::new(0x0500_0000) };
pub const COLOR_RED: u16 = 0b11111;
BG_PALETTE.index(0).write(COLOR_RED);
pub const COLOR_GREEN: u16 = 0b11111_00000;
BG_PALETTE.iter_range(1..).for_each(|a| a.write(COLOR_GREEN));
pub const MY_ROM_PALETTE_DATA: [u16; 256] = [0xAB; 256];
BG_PALETTE
.iter()
.zip(MY_ROM_PALETTE_DATA.iter().copied())
.for_each(|(a, c)| a.write(c));
No Lifetimes
Note that VolAddress
, VolBlock
, and VolSeries
are all Copy
data
types, without any lifetime parameter. It is assumed that the MMIO memory
map of your device is a fixed part of the device, and that the types from
this crate will be used to create const
declarations that describe that
single memory map which is unchanging during the entire program. If the
memory mapping of your device can change then you must account for this in
your declarations.