regio (“reh-jee-o”) is a general, extensible framework for register I/O. It is agnostic of the choice of layout type, and aims to support varied notions of ‘register’ across common CPU architectures and hardware.
RegisterRegister represents an abstract register tied to a specific layout, access permissions, and an ‘I/O backend’ defining how it is accessed. Its API admits the usual access methods when its permissions and backend permit them.
Conventions:
It is expected that hardware-specific notions of register define themselves as a type alias or wrapper of Register and implement their own specialized constructors.
When register instances are const-constructible, they ought to be consts and carry the same name as they are documented in architectural manuals. They are usually documented in screaming snake case already, so this makes for a nice, readable coincidence.
The [traits] module abstracts register access as traits (e.g., [ReadReg] and [SafeWriteReg]), permitting register-related logic to be written generically for testability. The [testing] module - available with the testing feature - provides implementations of these traits (e.g., [ExpectationReg]) that may be supplied instead of [Register] at test time.
For simple read-only cases, a closure can serve as a stand-in register:
# #[cfg(feature = "testing")] { use regio::traits::ReadReg; fn is_busy(reg: &impl ReadReg<u32>) -> bool { reg.read() & 1 != 0 } assert!(is_busy(&|| 0x3u32)); assert!(!is_busy(&|| 0x2u32)); # }
One can also reach for [ExpectationReg] to validates a full sequence of expected accesses:
# #[cfg(feature = "testing")] { use regio::testing::ExpectationReg; use regio::traits::RwSafeReg; fn set_enable_bit(reg: &impl RwSafeReg<u32>) { reg.modify(|value| *value |= 1); } let mut reg = ExpectationReg::<u32>::new(); reg.expect_read(0xf0).expect_write(0xf1); set_enable_bit(®); // On drop, `reg` asserts that no expected accesses remain. # }
The core MMIO types are Offset, Mmio, MmioPtr, and MmioBank. Mmio is an alias of Register representing an MMIO register. Often MMIO addresses are not statically known; in this case, we port the above const Register convention over to defining the offset descriptors - which are usually statically known - to be const instances bearing the documented register names.
The following is example logic for driving a SiFive UART.
use core::ptr; use bitrs::layout; use regio::{MmioBank, MmioPtr, Offset, RwSafe}; layout!({ struct TransmitDataRegister(u32); { let full @ 31; // Whether the TX FIFO is full. let __ @ 30..8; let data @ 7..0; } }); const TXDATA: Offset<TransmitDataRegister, RwSafe> = Offset::new(0); layout!({ struct ReceiveDataRegister(u32); { let empty @ 31; // Whether the RX FIFO is empty. let __ @ 30..8; let data @ 7..0; } }); const RXDATA: Offset<ReceiveDataRegister, RwSafe> = Offset::new(4); layout!({ struct InterruptEnableRegister(u32); { let _ @ 31..2; let rxwm @ 1; // Receive watermark interrupt enable let txwm @ 0; // Transmit watermark interrupt enable } }); const IE: Offset<InterruptEnableRegister, RwSafe> = Offset::new(0x10); // ... const BANK_SIZE = 0x1c; // The highest documented offset is 0x18. // Could be made const in this case, but in practice it may not be. let uart_base = unsafe { MmioPtr::<u32, RwSafe>::new(ptr::with_exposed_provenance_mut(0x1001_3000)) }; let uart = MmioBank::new(uart_base, BANK_SIZE); // Can access the MmioBank to get an Mmio. unsafe { uart.at(IE) }.write(0u32.into()); // Disable all interrupts. let data = TransmitDataRegister::from(0).set_data(0xab); unsafe { uart.at(TXDATA) }.write(data); // Simple reads can fluidly chain from MmioPtr -> Mmio -> Layout. let _: u8 = unsafe { uart.at(RXDATA) }.read().data();
The core CPUID type is Cpuid for defining the layouts of a particular (sub)leaf.
Example usage:
use regio::x86::Cpuid; const CPUID_MAX_LEAF_AND_VENDOR_STRING: Cpuid<0x0, 0x0, u32, u32, u32, u32> = Cpuid::new(); println!("Maximum CPUID leaf number: {:#x}", CPUID_MAX_LEAF_AND_VENDOR_STRING.read().eax);
Msr is an alias of Register used to model MSRs:
use regio::RwSafe; use regio::x86::Msr; const IA32_TIME_STAMP_COUNTER: Msr<0x10, u64, RwSafe> = Msr::new(); println!("Current timestamp: {:#x}", IA32_TIME_STAMP_COUNTER.read());