| # regio |
| |
| `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. |
| |
| ## `Register` |
| |
| `Register` 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 `const`s |
| 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. |
| |
| ## Traits and testing |
| |
| 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: |
| |
| ```rust |
| # #[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: |
| |
| ```rust |
| # #[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. |
| # } |
| ``` |
| |
| ## MMIO |
| |
| 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. |
| |
| ```rust |
| 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(); |
| ``` |
| |
| ## x86 CPUID |
| |
| The core CPUID type is `Cpuid` for defining the layouts of a particular |
| (sub)leaf. |
| |
| Example usage: |
| |
| ```rust |
| 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); |
| ``` |
| |
| ## x86 MSRs |
| |
| `Msr` is an alias of `Register` used to model MSRs: |
| |
| ```rust |
| 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()); |
| ``` |