Skip to main content

register

Macro register 

Source
macro_rules! register {
    ($($tt:tt)*) => { ... };
}
Expand description

Defines a dedicated type for a register, including getter and setter methods for its fields and methods to read and write it from an Io region.

This documentation focuses on how to declare registers. See the module-level documentation for examples of how to access them.

Registers can either be fixed offset registers or arrays of registers.

§Fixed offset registers

These are the simplest kind of registers. Their location is simply an offset inside the I/O region. For instance:

ⓘ
register! {
    pub FIXED_REG(u16) @ 0x80 {
        ...
    }
}

This creates a 16-bit register named FIXED_REG located at offset 0x80 of an I/O region.

These registers’ location can be built simply by referencing their name:

use kernel::{
    io::{
        register,
        Io,
        Region,
    },
};

register! {
    base: Region<0x1000>;

    FIXED_REG(u32) @ 0x100 {
        15:8 high_byte;
        7:0  low_byte;
    }
}

let val = io.read(FIXED_REG);

// Write from an already-existing value.
io.write(FIXED_REG, val.with_low_byte(0xff));

// Create a register value from scratch.
let val2 = FIXED_REG::zeroed().with_high_byte(0x80);

// The location of fixed offset registers is already contained in their type. Thus, the
// `location` argument of `Io::write` is technically redundant and can be replaced by `()`.
io.write((), val2);

// Or, the single-argument `Io::write_reg` can be used.
io.write_reg(val2);

It is possible to create an alias of an existing register with new field definitions by using the => ALIAS syntax. This is useful for cases where a register’s interpretation depends on the context:

use kernel::io::{
    register,
    Region,
};

register! {
    base: Region<0x1000>;

    /// Scratch register.
    pub SCRATCH(u32) @ 0x00000200 {
        31:0 value;
    }

    /// Boot status of the firmware.
    pub SCRATCH_BOOT_STATUS(u32) => SCRATCH {
        0:0 completed;
    }
}

In this example, SCRATCH_BOOT_STATUS uses the same I/O address as SCRATCH, while providing its own completed field.

If you do not wish to have a bitfield defined, you can also create a register using an existing type.

register! {
    base: Region<0x1000>;

    /// UART RX register.
    pub UART_RX: u8 @ 0x100;
}

In case there is a fixed register associated with a specific type in the base, you can apply #[unique] attribute which enables write_reg shorthand. This is automatically applied to bitfields instantiated via the register! macro.

This should only be used when types meaningfully represent a register. For example, in the previous UART_RX example, even if only a single register is defined with u8 type, it is a bad idea to annotate it with #[unique].


bitfield! {
    pub struct Reset(u32) {
        0:0 reset;
    }
}

register! {
    base: Region<0x1000>;

    pub RESET: #[unique] Reset @ 0x100;
}

// let mmio: Mmio<'_, Region<0x1000>>;
mmio.write_reg(Reset::zeroed().with_const_reset::<1>());

§Arrays of registers

Some I/O areas contain consecutive registers that share the same field layout. These areas can be defined as an array of identical registers, allowing them to be accessed by index with compile-time or runtime bound checking:

ⓘ
register! {
    pub REGISTER_ARRAY(u8)[10, stride = 4] @ 0x100 {
        ...
    }
}

This defines REGISTER_ARRAY, an array of 10 byte registers starting at offset 0x100. Each register is separated from its neighbor by 4 bytes.

The stride parameter is optional; if unspecified, the registers are placed consecutively from each other.

A location for a register in a register array is built using the Array::at trait method. All arrays of registers implement Array.

use kernel::{
    io::{
        register,
        register::Array,
        Io,
        Region,
    },
};

// Array of 64 consecutive registers with the same layout starting at offset `0x80`.
register! {
    base: Region<0x1000>;

    /// Scratch registers.
    pub SCRATCH(u32)[64] @ 0x00000080 {
        31:0 value;
    }
}

// Read scratch register 0, i.e. I/O address `0x80`.
let scratch_0 = io.read(SCRATCH::at(0)).value();

// Write scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
io.write(Array::at(15), SCRATCH::from(0xffeeaabb));

// This is out of bounds and won't build.
// let scratch_128 = io.read(SCRATCH::at(128)).value();

// Runtime-obtained array index.
let idx = get_scratch_idx();
// Access on a runtime index returns an error if it is out-of-bounds.
let some_scratch = io.read(SCRATCH::try_at(idx).ok_or(EINVAL)?).value();

// Alias to a specific register in an array.
// Here `SCRATCH[8]` is used to convey the firmware exit code.
register! {
    base: Region<0x1000>;

    /// Firmware exit status code.
    pub FIRMWARE_STATUS(u32) => SCRATCH[8] {
        7:0 status;
    }
}

let status = io.read(FIRMWARE_STATUS).status();

// Non-contiguous register arrays can be defined by adding a stride parameter.
// Here, each of the 16 registers of the array is separated by 8 bytes, meaning that the
// registers of the two declarations below are interleaved.
register! {
    base: Region<0x1000>;

    /// Scratch registers bank 0.
    pub SCRATCH_INTERLEAVED_0(u32)[16, stride = 8] @ 0x000000c0 {
        31:0 value;
    }

    /// Scratch registers bank 1.
    pub SCRATCH_INTERLEAVED_1(u32)[16, stride = 8] @ 0x000000c4 {
        31:0 value;
    }
}

§Relative registers

There are cases where a register region is subdivided into small subregions, and you may wish to have your register definition be relative to these subregions. This may be needed, for example, if these subregions are instantiated several times, or you just want it for encapsulation purpose.

For instance, imagine the following I/O space:

          +-----------------------------+
          |             ...             |
          |                             |
 0x100--->+------------CPU0-------------+
          |                             |
 0x110--->+-----------------------------+
          |           CPU_CTL           |
          +-----------------------------+
          |             ...             |
          |                             |
          |                             |
 0x200--->+------------CPU1-------------+
          |                             |
 0x210--->+-----------------------------+
          |           CPU_CTL           |
          +-----------------------------+
          |             ...             |
          +-----------------------------+

CPU0 and CPU1 both have a CPU_CTL register that starts at offset 0x10 of their I/O space segment. Since both instances of CPU_CTL share the same layout, we don’t want to define them twice and would prefer a way to select which one to use from a single definition.

This can be done by defining a new type for the subregion, and then defining registers that use the new type as the base:

use kernel::{
    io::{
        io_project,
        register,
        Io,
        Region,
    },
};

// Subregion type. Make sure it has adequate size and alignment.
#[repr(align(4))]
#[derive(FromBytes, IntoBytes)]
pub struct CpuCtl([u8; 0x100]);

register! {
    base: Region<0x1000>;

    // Subregions can just be defined like normal registers.
    CPU0: CpuCtl @ 0x100;
    CPU1: CpuCtl @ 0x200;
}

// Then you can define new registers on the subregion.
register! {
    base: CpuCtl;

    /// CPU core control.
    pub CPU_CTL(u32) @ 0x10 {
        0:0 start;
    }
}

// Read the status of `Cpu0`.
let cpu0_started = io_project!(io, build: CPU0).read(CPU_CTL);

// Stop `Cpu0`.
io_project!(io, build: CPU0).write_reg(CPU_CTL::zeroed());