use std::convert::TryInto; 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, } 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) -> 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 } } } pub struct CSR { base: *mut T, } impl CSR where T: std::convert::TryFrom + std::convert::TryInto + std::default::Default, { pub fn new(base: *mut T) -> Self { CSR { base } } pub fn r(&mut self, reg: usize, field: Field) -> T { let usize_base: *mut usize = unsafe { core::mem::transmute(&self.base) }; ((unsafe { usize_base.add(reg).read_volatile() } >> field.offset) & field.mask) .try_into() .unwrap_or_default() } pub fn rw(&mut self, reg: usize, 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(reg).read_volatile() } & ! field.mask; unsafe { usize_base.add(reg).write_volatile(previous | value_as_usize)}; } pub fn ow(&mut self, reg: usize, 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(reg).write_volatile(value_as_usize)}; } } #[cfg(test)] mod tests { pub mod pac { pub mod audio { pub const RX_CTL: usize = 0x0c; pub const RX_CTL_ENABLE: crate::Field = crate::Field::new(1, 0); pub const RX_CTL_RESET: crate::Field = crate::Field::new(1, 1); } pub mod uart { pub const RXTX: usize = 0x00; pub const RXTX_RXTX: crate::Field = crate::Field::new(8, 0); pub const TXFULL: usize = 0x04; pub const TXFULL_TXFULL: crate::Field = crate::Field::new(1, 0); } } #[test] fn compile_check() { use super::*; let mut audio = CSR::new(0x1000_0000 as *mut u32); audio.r(pac::audio::RX_CTL, pac::audio::RX_CTL_ENABLE); audio.rw(pac::audio::RX_CTL, pac::audio::RX_CTL_RESET, 1); let mut uart = CSR::new(0x1001_0000 as *mut u8); uart.ow(pac::uart::RXTX, pac::uart::RXTX_RXTX, b'a'); assert_ne!(uart.r(pac::uart::TXFULL, pac::uart::TXFULL_TXFULL), 1); // Note: These also still compile. uart.ow(pac::uart::RXTX, pac::uart::TXFULL_TXFULL, b'a'); // 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, pac::uart::RXTX_RXTX, b'a' as _); } }