blob: 2817df163d9949bcb5dc2246ef41414b2b8bbc20 [file] [view]
# 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(&reg);
// 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());
```