1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#![no_std]
#![deny(missing_docs)]

//! 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 a `repr(transparent)` wrapper around one of
//!   the other types just listed.
//! * `R` should be [Safe], [Unsafe], or `()`. When `R` is `Safe` then you can
//!   *safely* read from the address. When `R` is `Unsafe` then you can
//!   *unsafely* read from the address. If `R` 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 like `R` 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.
//! ```
//! # use voladdress::*;
//! // 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`, and `get`, you can produce individual `VolAddress`
//!   values similar to how a slice can produce references into the slice's data
//!   range.
//! * Using `iter` or `iter_range` you can produce an in iterator that will go
//!   over the various `VolAddress` values during the iteration.
//!
//! ```no_run
//! # use voladdress::*;
//! 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.

use core::{
  marker::PhantomData,
  num::NonZeroUsize,
  ptr::{read_volatile, write_volatile},
};

mod plain_voladdress;
pub use plain_voladdress::*;

mod volblock;
pub use volblock::*;

mod volseries;
pub use volseries::*;

/// Lets you put "Safe" into a generic type parameter.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Safe;

/// Lets you put "Unsafe" into a generic type parameter.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Unsafe;