Skip to main content

Crate registers

Crate registers 

Source
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-4Bit 3Bits 2-1Bit 0
Base AddressPrefetchableTypeAlways 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-1Bit 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-12Bit 4Bit 3
Pgdir BasePage Cache DisablePage 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:

TypeDescription
uUnsigned
iSigned (2s complement)
bFlag (boolean)

The register attribute supports the following metadata:

NameRequiredDescription
sizeTrueSize of register in bits
readFalseWhether this register is readable from memory, controls the .read() method, defaults to true
writeFalseWhether this register is writeable from memory, controls the .write() method, defaults to true

Each field attribute supports the following metadata:

NameRequiredDescription
lsbTrueIndex of the least significant bit
msbTrueIndex of the most significant bit
readFalseWhether this field should be readable, defaults to true
writeFalseWhether 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§

Error

Type Aliases§

Result

Attribute Macros§

register
Generate register handling code for a register definition.