hardware-register/src/lib.rs

151 lines
4.6 KiB
Rust

use std::convert::TryInto;
pub struct Register {
/// Offset of this register within this CSR
offset: usize,
}
impl Register {
pub const fn new(offset: usize) -> Register {
Register { offset }
}
}
pub struct Field {
/// A bitmask we use to AND to the value, unshifted.
/// E.g. for a width of `3` bits, this mask would be 0b111.
mask: usize,
/// Offset of the first bit in this field
offset: usize,
register: Register,
}
impl Field {
/// Define a new CSR field with the given width at a specified
/// offset from the start of the register.
pub const fn new(width: usize, offset: usize, register: Register) -> Field {
// assert!(width != 0, "field width cannot be 0");
// assert!((width + offset) < 32, "field with and offset must fit within a 32-bit value");
// It would be lovely if we could call `usize::pow()` in a const fn.
let mask = match width {
0 => 0,
1 => 1,
2 => 3,
3 => 7,
4 => 15,
5 => 31,
6 => 63,
7 => 127,
8 => 255,
9 => 511,
10 => 1023,
11 => 2047,
12 => 4095,
13 => 8191,
14 => 16383,
15 => 32767,
16 => 65535,
17 => 131071,
18 => 262143,
19 => 524287,
20 => 1048575,
21 => 2097151,
22 => 4194303,
23 => 8388607,
24 => 16777215,
25 => 33554431,
26 => 67108863,
27 => 134217727,
28 => 268435455,
29 => 536870911,
30 => 1073741823,
31 => 2147483647,
_ => 0,
};
Field {
mask,
offset,
register,
}
}
}
pub struct CSR<T> {
base: *mut T,
}
impl<T> CSR<T>
where
T: std::convert::TryFrom<usize> + std::convert::TryInto<usize> + std::default::Default,
{
pub fn new(base: *mut T) -> Self {
CSR { base }
}
pub fn r(&mut self, field: Field) -> T {
let usize_base: *mut usize = unsafe { core::mem::transmute(&self.base) };
((unsafe { usize_base.add(field.register.offset).read_volatile() } >> field.offset)
& field.mask)
.try_into()
.unwrap_or_default()
}
pub fn rw(&mut self, field: Field, value: T) {
let usize_base: *mut usize = unsafe { core::mem::transmute(&self.base) };
let value_as_usize: usize = value.try_into().unwrap_or_default() << field.offset;
let previous =
unsafe { usize_base.add(field.register.offset).read_volatile() } & !field.mask;
unsafe {
usize_base
.add(field.register.offset)
.write_volatile(previous | value_as_usize)
};
}
pub fn ow(&mut self, field: Field, value: T) {
let usize_base: *mut usize = unsafe { core::mem::transmute(&self.base) };
let value_as_usize: usize = value.try_into().unwrap_or_default() << field.offset;
unsafe {
usize_base
.add(field.register.offset)
.write_volatile(value_as_usize)
};
}
}
#[cfg(test)]
mod tests {
pub mod pac {
pub mod audio {
pub const RX_CTL: crate::Register = crate::Register::new(0x0c);
pub const RX_CTL_ENABLE: crate::Field = crate::Field::new(1, 0, RX_CTL);
pub const RX_CTL_RESET: crate::Field = crate::Field::new(1, 1, RX_CTL);
}
pub mod uart {
pub const RXTX: crate::Register = crate::Register::new(0x00);
pub const RXTX_RXTX: crate::Field = crate::Field::new(8, 0, RXTX);
pub const TXFULL: crate::Register = crate::Register::new(0x04);
pub const TXFULL_TXFULL: crate::Field = crate::Field::new(1, 0, TXFULL);
}
}
#[test]
fn compile_check() {
use super::*;
let mut audio = CSR::new(0x0000_0000 as *mut u32);
audio.r(pac::audio::RX_CTL_ENABLE);
audio.rw(pac::audio::RX_CTL_RESET, 1);
let mut uart = CSR::new(0x0001_0000 as *mut u8);
uart.ow(pac::uart::RXTX_RXTX, b'a');
assert_ne!(uart.r(pac::uart::TXFULL_TXFULL), 1);
// This compiles but requires a cast since `audio` is a pointer to
// u32, whereas `uart` is a pointer to u8.
audio.ow(pac::uart::RXTX_RXTX, b'a' as _);
// This also compiles, despite the fact that the register offset is
// mismatched and nonsensical
audio.ow(pac::uart::TXFULL_TXFULL, 1);
}
}