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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
#![no_std]
#![deny(missing_docs)]
#![allow(clippy::iter_nth_zero)]
#![cfg_attr(test, allow(clippy::redundant_clone))]
#![cfg_attr(test, allow(bad_style))]
//! 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 voladdress_;
pub use voladdress_::*;
mod volblock;
pub use volblock::*;
mod volseries;
pub use volseries::*;
mod volgrid2d;
pub use volgrid2d::*;
mod volgrid2d_strided;
pub use volgrid2d_strided::*;
mod volregion;
pub use volregion::*;
/// Lets you put "Safe" into a generic type parameter.
///
/// This type affects the read and write methods of the volatile address types,
/// but has no effect on its own.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Safe;
/// Lets you put "Unsafe" into a generic type parameter.
///
/// This type affects the read and write methods of the volatile address types,
/// but has no effect on its own.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Unsafe;