Expand description
§Description
This library provides a convenient attribute macro for working with segmented registers in Rust.
Registers often have multiple distinct fields of varying widths and varying types. The typical way to assign and retrieve the values in these fields have been with bit shifting operations. This logic is error prone, and the code surface area that needs to be carefully examined is not insignificant.
The macro provided by this library generates this bit shifting code for you, wrapping it up into an idiomatic struct pattern that Rust users will be familiar with.
§Features
- Custom bit precise field sizes
- 2s complement signed fields
- Support for 8, 16, 32, 64, and 128 bit registers
- Toggleable MMIO read and write methods
- Toggleable field get and set methods
- Zero cost abstraction
§Example
For example, take a PCI memory space BAR:
| Bits 31-4 | Bit 3 | Bits 2-1 | Bit 0 |
|---|---|---|---|
| Base Address | Prefetchable | Type | Always 0 |
This register could be supported like so:
#[register(size = 32, write = false)]
struct PCI_BAR {
#[field(lsb = 4, msb = 31, write = false)]
base_addr: u,
#[field(lsb = 3, msb = 3, write = false)]
prefetch: u,
#[field(lsb = 1, msb = 2, write = false)]
ty: u,
#[field(lsb = 0, msb = 0, write = false)]
reserved: u,
}
// Address Meta
let mock_bar: u32 = 0xDEADBEE0 + 0b1100;
let mut bar = PCI_BAR::new();
let bar_addr = &mock_bar as *const u32;
unsafe { bar.read(bar_addr) }
assert_eq!(bar.get_base_addr(), 0xDEADBEE);
assert_eq!(bar.get_prefetch(), 1);
assert_eq!(bar.get_ty(), 0b10);
assert_eq!(bar.get_reserved(), 0);§Note
Reserved bits are not required to be specified. They may be omitted if desired.
For a control register (writable) let’s make up a register definition:
| Bits 15-1 | Bit 0 |
|---|---|
| Coefficient (signed) | Enable |
Support for this register might look like:
#[register(size = 16, read = false)]
struct CSR {
#[field(msb = 15, lsb = 1)]
coeff: i,
#[field(msb = 0, lsb = 0)]
enable: u,
}
let mut csr = CSR::new();
csr.set_coeff(-14)?;
csr.set_enable(1)?;
// Set will also bit bounds check
assert!(csr.set_enable(0b11).is_err());
assert_eq!(csr.get_coeff(), -14);
assert_eq!(csr.get_enable(), 1);This library also supports flags in registers. Take for example the x86_64 register cr3:
| Bits 63-12 | Bit 4 | Bit 3 |
|---|---|---|
| Pgdir Base | Page Cache Disable | Page Write Through |
#[register(size = 64)]
struct CR3 {
#[field(msb = 63, lsb = 12)]
pgdir_base: u,
#[field(msb = 4, lsb = 4)]
pg_cache_disable: b,
#[field(msb = 3, lsb = 3)]
pg_write_thru: b,
}
let mut cr3 = CR3::new();
cr3.set_pg_cache_disable(true);
cr3.set_pg_write_thru(false);
cr3.set_pgdir_base(0xDEADBEEF)?;
assert!(cr3.get_pg_cache_disable());
assert!(!cr3.get_pg_write_thru());
assert_eq!(cr3.get_pgdir_base(), 0xDEADBEEF);You could, of course, still use unsigned type for flags. However, using the dedicated boolean
flag type means that the APIs get ergonomic bool parameters and return types, and since it is
impossible for an out-of-bounds write with a bool, the set methods do not fail.
§Knobs
Fields must be specified as one of the following types:
| Type | Description |
|---|---|
| u | Unsigned |
| i | Signed (2s complement) |
| b | Flag (boolean) |
The register attribute supports the following metadata:
| Name | Required | Description |
|---|---|---|
| size | True | Size of register in bits |
| read | False | Whether this register is readable from memory, controls the .read() method, defaults to true |
| write | False | Whether this register is writeable from memory, controls the .write() method, defaults to true |
Each field attribute supports the following metadata:
| Name | Required | Description |
|---|---|---|
| lsb | True | Index of the least significant bit |
| msb | True | Index of the most significant bit |
| read | False | Whether this field should be readable, defaults to true |
| write | False | Whether this field should be writable, defaults to true |
§Overhead
§Size
The actual struct representation contains just a single internal unsigned integer of the same size as the register. This means that an instance of this struct is the same size as the u32 you would have used to hold the value from that memory address.
§Performance
The compiler will inline the implementations, meaning that the bitwise shifting and masking will compile to essentially the same machine instructions as a manual implementation would have.
Enums§
Type Aliases§
Attribute Macros§
- register
- Generate register handling code for a register definition.