#macro #embedded #no-std

nightly no-std bitaccess

Macro for efficient and type-checked access to individual bits of a variable

4 releases (breaking)

0.4.0 Oct 13, 2021
0.3.0 Sep 26, 2021
0.2.0 Sep 23, 2021
0.1.0 Sep 23, 2021

#2194 in Data structures

MIT/Apache

12KB
186 lines

Bitaccess

Small crate that substitutes for lack of bitfield accessors in rust language.

Example usage:

#![feature(asm)]

use bitaccess::{bitaccess, FieldAccess};

#[derive(FieldAccess, Debug)]
#[field_access(u64)]
pub enum Mask {
    Unmasked = 0,
    Masked = 1,
}

#[bitaccess(
    base_type = u64,
    kind = read_write,
    read_via = r#"unsafe { asm!("mrs {}, daif", out(reg) value, options(nostack, nomem)); }"#,
    write_via = r#"unsafe { asm!("msr daif, {}", in(reg) value, options(nostack, nomem)); }"#
)]
pub enum Daif {
    #[bit(9)]
    #[variants(Mask)] D,
    #[bit(8)]
    #[variants(Mask)] A,
    #[bit(7)]
    #[variants(Mask)] I,
    #[bit(6)]
    #[variants(Mask)] F,
}

/// DAIF is an ARM register, so this example is not really suited for running on all machines.
/// It's here just to show power of the macro.
fn alter_daif() {
    println!("Daif IRQ: {:?}", Daif.read(Daif::I).variant());
    Daif.write(Daif::I, Mask::Unmasked);
    println!("Daif IRQ: {:?}", Daif.read(Daif::I).variant());
}

Features

Inline register

Allows you to create an object stored in normal memory, be it stack or heap (likely via some dereference wrapper).

To create such structure, you have to create an enum, like so:

use bitaccess::{bitaccess, FieldAccess};

#[derive(FieldAccess)]
#[field_access(u64)]
enum VariantThird {
    Val1 = 0x1,
    Val2 = 0x3,
    Val3 = 0xf,
}

#[bitaccess(
    base_type = u64,
    kind = read_write
)]
#[repr(C)]
enum MyRegister {
    #[bits(0..=3)]
    FirstDescriptor,
    #[bit(4)]
    #[variants(On => 1, Off => 0)]
    SecondDescriptor,
    #[bitaccess(5..9)]
    #[variants(VariantThird)]
    ThirdDescriptor,
}

Base_type

Any integer type.

putting any other type than basic integer types is unstable, however may work

Kind

Allowed options:

  • read_only
  • write_only
  • read_write | write_read | default

Depending on the chosen option resulting code may provide ReadBits, WriteBits or both implementations. Field can be skipped, which will result in read_write register.

Additional attributes on main enum

All attributes past bitaccess will be copied to resulting struct (yeah, this enum transforms into struct under the hood)

Bits / Bit / Bitaccess

Field attribute that declares which bits are part of given field.

Accepts 3 forms of declaration:

explicit

#[bits(offset = N, size = S)] where both N and S are expressions evaluable to base_type

range

Both Range and RangeInclusive are acceptable: 0..4 & 0..=3

single

For single bit accessors #[bit(N)] is allowed.

Variants

Fields may come in automatically cast variants (like VariantThird above). Bitaccess supports two ways of declaring such access:

inline

Comma separated list of Identifier => Value pairs. Variants will be accessible from enum with field identifier for a name, eg. in case from above, we'd call SecondDescriptor::On.

external

Specifying just type in #[variants(Type)] will use that type for field access. Type has to derive FieldAccess trait and specify #[field_access(N)] attribute, where N has to match base_type on main enum.

Global register

#[derive(FieldAccess)]
#[field_access(u64)]
pub enum ExceptionLevel {
    EL0 = 0b00,
    EL1 = 0b01,
    EL2 = 0b10,
    EL3 = 0b11,
}

#[bitaccess(
    base_type = u64,
    kind = read_only,
    read_via = r#"unsafe { asm!("mrs {}, currentel", out(reg) value, options(nomem, nostack)) }"#
)]
pub enum CurrentEl {
    #[bits(2..4)]
    #[variants(ExceptionLevel)]
    Value,
}

Global registers are created when read_via or write_via attributes are provided to bitaccess macro. All other attributes behave as in Inline register.

Read_via | write_via

Rust instructions provided within string. For some other use examples, you may check tests.

Dependencies

~1.5MB
~39K SLoC