Add a macro for defining hardware register types with I/O accessors.
Each register field is represented as a `Bounded` of the appropriate bit
width, ensuring field values are never silently truncated.
Fields can optionally be converted to/from custom types, either fallibly
or infallibly.
The address of registers can be direct, relative, or indexed, supporting
most of the patterns in which registers are arranged.
Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/io.rs | 1 +
rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1288 insertions(+)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 056a3ec71647..112f43ecbf88 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -11,6 +11,7 @@
pub mod mem;
pub mod poll;
+pub mod register;
pub mod resource;
pub use resource::Resource;
diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
new file mode 100644
index 000000000000..fc85dcd1f09a
--- /dev/null
+++ b/rust/kernel/io/register.rs
@@ -0,0 +1,1287 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! A macro to define register layout and accessors.
+//!
+//! A single register typically includes several fields, which are accessed through a combination
+//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
+//! not all possible field values are necessarily valid.
+//!
+//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a
+//! dedicated type for each register. Each such type comes with its own field accessors that can
+//! return an error if a field's value is invalid.
+//!
+//! [`register!`]: kernel::register!
+
+use core::ops::Deref;
+
+use crate::io::{
+ IoCapable,
+ IoKnownSize, //
+};
+
+/// Trait providing a base address to be added to the offset of a relative register to obtain
+/// its actual offset.
+///
+/// The `T` generic argument is used to distinguish which base to use, in case a type provides
+/// several bases. It is given to the `register!` macro to restrict the use of the register to
+/// implementors of this particular variant.
+pub trait RegisterBase<T> {
+ /// Base address to which register offsets are added.
+ const BASE: usize;
+}
+
+/// Trait providing I/O read/write operations for register storage types.
+///
+/// This trait is implemented for all integer types on which I/O can be performed, allowing the
+/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage
+/// type.
+pub trait RegisterIo: Sized {
+ /// Read a value from the given offset in the I/O region.
+ fn read<T, I>(io: &T, offset: usize) -> Self
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>;
+
+ /// Write a value to the given offset in the I/O region.
+ fn write<T, I>(self, io: &T, offset: usize)
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>;
+}
+
+impl RegisterIo for u8 {
+ #[inline(always)]
+ fn read<T, I>(io: &T, offset: usize) -> Self
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>,
+ {
+ io.read8(offset)
+ }
+
+ #[inline(always)]
+ fn write<T, I>(self, io: &T, offset: usize)
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>,
+ {
+ io.write8(self, offset)
+ }
+}
+
+impl RegisterIo for u16 {
+ #[inline(always)]
+ fn read<T, I>(io: &T, offset: usize) -> Self
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>,
+ {
+ io.read16(offset)
+ }
+
+ #[inline(always)]
+ fn write<T, I>(self, io: &T, offset: usize)
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>,
+ {
+ io.write16(self, offset)
+ }
+}
+
+impl RegisterIo for u32 {
+ #[inline(always)]
+ fn read<T, I>(io: &T, offset: usize) -> Self
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>,
+ {
+ io.read32(offset)
+ }
+
+ #[inline(always)]
+ fn write<T, I>(self, io: &T, offset: usize)
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>,
+ {
+ io.write32(self, offset)
+ }
+}
+
+#[cfg(CONFIG_64BIT)]
+impl RegisterIo for u64 {
+ #[inline(always)]
+ fn read<T, I>(io: &T, offset: usize) -> Self
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>,
+ {
+ io.read64(offset)
+ }
+
+ #[inline(always)]
+ fn write<T, I>(self, io: &T, offset: usize)
+ where
+ T: Deref<Target = I>,
+ I: IoKnownSize + IoCapable<Self>,
+ {
+ io.write64(self, offset)
+ }
+}
+
+/// 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.
+///
+/// Example:
+///
+/// ```
+/// use kernel::register;
+///
+/// register! {
+/// /// Basic information about the chip.
+/// pub BOOT_0(u32) @ 0x00000100 {
+/// /// Vendor ID.
+/// 15:8 vendor_id;
+/// /// Major revision of the chip.
+/// 7:4 major_revision;
+/// /// Minor revision of the chip.
+/// 3:0 minor_revision;
+/// }
+/// }
+/// ```
+///
+/// This defines a `BOOT_0` type which can be read from or written to offset `0x100` of an `Io`
+/// region. For instance, `minor_revision` consists of the 4 least significant bits of the
+/// register.
+///
+/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their
+/// getter method, which is named after them. They also have setter methods prefixed with `set_`
+/// for runtime values and `with_` for constant values. All setters return the updated register
+/// value.
+///
+/// ```no_run
+/// use kernel::register;
+/// use kernel::num::Bounded;
+///
+/// # register! {
+/// # pub BOOT_0(u32) @ 0x00000100 {
+/// # 15:8 vendor_id;
+/// # 7:4 major_revision;
+/// # 3:0 minor_revision;
+/// # }
+/// # }
+/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) {
+/// # fn obtain_vendor_id() -> u8 { 0xff }
+/// // Read from the register's defined offset (0x100).
+/// let boot0 = BOOT_0::read(&bar);
+/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get());
+///
+/// // Update some fields and write the new value back.
+/// boot0
+/// // Constant values.
+/// .with_major_revision::<3>()
+/// .with_minor_revision::<10>()
+/// // Run-time value.
+/// .set_vendor_id(obtain_vendor_id())
+/// .write(&bar);
+///
+/// // Or, just read and update the register in a single step.
+/// BOOT_0::update(&bar, |r| r
+/// .with_major_revision::<3>()
+/// .with_minor_revision::<10>()
+/// .set_vendor_id(obtain_vendor_id())
+/// );
+///
+/// // Constant values can also be built using the const setters.
+/// const V: BOOT_0 = BOOT_0::zeroed()
+/// .with_major_revision::<3>()
+/// .with_minor_revision::<10>();
+/// # }
+/// ```
+///
+/// Fields can also be transparently converted from/to an arbitrary type by using the bitfield `=>`
+/// and `?=>` syntaxes.
+///
+/// If present, doccomments above register or fields definitions are added to the relevant item
+/// they document (the register type itself, or the field's setter and getter methods).
+///
+/// Note that multiple registers can be defined in a single `register!` invocation. This can be
+/// useful to group related registers together.
+///
+/// ```
+/// use kernel::register;
+///
+/// register! {
+/// pub BOOT_0(u8) @ 0x00000100 {
+/// 7:4 major_revision;
+/// 3:0 minor_revision;
+/// }
+///
+/// pub BOOT_1(u8) @ 0x00000101 {
+/// 7:5 num_threads;
+/// 4:0 num_cores;
+/// }
+/// };
+/// ```
+///
+/// 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::register;
+///
+/// register! {
+/// /// Scratch register.
+/// pub SCRATCH(u32) @ 0x00000200 {
+/// /// Raw value.
+/// 31:0 value;
+/// }
+///
+/// /// Boot status of the firmware.
+/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH {
+/// /// Whether the firmware has completed booting.
+/// 0:0 completed;
+/// }
+/// }
+/// ```
+///
+/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also
+/// providing its own `completed` field.
+///
+/// ## Relative registers
+///
+/// A register can be defined as being accessible from a fixed offset of a provided base. For
+/// instance, imagine the following I/O space:
+///
+/// ```text
+/// +-----------------------------+
+/// | ... |
+/// | |
+/// 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 using the `Base + Offset` syntax when specifying the register's address.
+///
+/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the
+/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for
+/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated
+/// into code:
+///
+/// ```no_run
+/// use kernel::register;
+/// use kernel::io::register::RegisterBase;
+///
+/// // Type used to identify the base.
+/// pub struct CpuCtlBase;
+///
+/// // ZST describing `CPU0`.
+/// struct Cpu0;
+/// impl RegisterBase<CpuCtlBase> for Cpu0 {
+/// const BASE: usize = 0x100;
+/// }
+/// // Singleton of `CPU0` used to identify it.
+/// const CPU0: Cpu0 = Cpu0;
+///
+/// // ZST describing `CPU1`.
+/// struct Cpu1;
+/// impl RegisterBase<CpuCtlBase> for Cpu1 {
+/// const BASE: usize = 0x200;
+/// }
+/// // Singleton of `CPU1` used to identify it.
+/// const CPU1: Cpu1 = Cpu1;
+///
+/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) {
+/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
+/// register! {
+/// /// CPU core control.
+/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 {
+/// /// Start the CPU core.
+/// 0:0 start;
+/// }
+/// }
+///
+/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument
+/// // that is used to resolve its final address by adding its `BASE` to the offset of the
+/// // register.
+///
+/// // Start `CPU0`.
+/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true));
+///
+/// // Start `CPU1`.
+/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true));
+///
+/// // Aliases can also be defined for relative register.
+/// register! {
+/// /// Alias to CPU core control.
+/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL {
+/// /// Start the aliased CPU core.
+/// 1:1 alias_start;
+/// }
+/// }
+///
+/// // Start the aliased `CPU0`.
+/// CPU_CTL_ALIAS::update(&bar, &CPU0, |r| r.set_alias_start(true));
+/// # }
+/// ```
+///
+/// ## Arrays of registers
+///
+/// Some I/O areas contain consecutive registers that can be interpreted in the same way. 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. Simply specify their size inside `[` and `]`
+/// brackets, and add an `idx` parameter to their `read`, `write` and `update` methods:
+///
+/// ```no_run
+/// use kernel::register;
+///
+/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T)
+/// # -> Result<(), Error>{
+/// # fn get_scratch_idx() -> usize {
+/// # 0x15
+/// # }
+/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
+/// register! {
+/// /// Scratch registers.
+/// pub SCRATCH(u32)[64] @ 0x00000080 {
+/// 31:0 value;
+/// }
+/// }
+///
+/// // Read scratch register 0, i.e. I/O address `0x80`.
+/// let scratch_0 = SCRATCH::read(&bar, 0).value();
+/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
+/// let scratch_15 = SCRATCH::read(&bar, 15).value();
+///
+/// // This is out of bounds and won't build.
+/// // let scratch_128 = SCRATCH::read(&bar, 128).value();
+///
+/// // Runtime-obtained array index.
+/// let scratch_idx = get_scratch_idx();
+/// // Access on a runtime index returns an error if it is out-of-bounds.
+/// let some_scratch = SCRATCH::try_read(&bar, scratch_idx)?.value();
+///
+/// // Alias to a particular register in an array.
+/// // Here `SCRATCH[8]` is used to convey the firmware exit code.
+/// register! {
+/// /// Firmware exit status code.
+/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] {
+/// 7:0 status;
+/// }
+/// }
+/// let status = FIRMWARE_STATUS::read(&bar).status();
+///
+/// // Non-contiguous register arrays can be defined by adding a stride parameter.
+/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
+/// // registers of the two declarations below are interleaved.
+/// register! {
+/// /// Scratch registers bank 0.
+/// pub SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ 0x000000c0 {
+/// 31:0 value;
+/// }
+///
+/// /// Scratch registers bank 1.
+/// pub SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ 0x000000c4 {
+/// 31:0 value;
+/// }
+/// }
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ## Relative arrays of registers
+///
+/// Combining the two features described in the sections above, arrays of registers accessible from
+/// a base can also be defined:
+///
+/// ```no_run
+/// use kernel::register;
+/// use kernel::io::register::RegisterBase;
+///
+/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T)
+/// # -> Result<(), Error>{
+/// # fn get_scratch_idx() -> usize {
+/// # 0x15
+/// # }
+/// // Type used as parameter of `RegisterBase` to specify the base.
+/// pub struct CpuCtlBase;
+///
+/// // ZST describing `CPU0`.
+/// struct Cpu0;
+/// impl RegisterBase<CpuCtlBase> for Cpu0 {
+/// const BASE: usize = 0x100;
+/// }
+/// // Singleton of `CPU0` used to identify it.
+/// const CPU0: Cpu0 = Cpu0;
+///
+/// // ZST describing `CPU1`.
+/// struct Cpu1;
+/// impl RegisterBase<CpuCtlBase> for Cpu1 {
+/// const BASE: usize = 0x200;
+/// }
+/// // Singleton of `CPU1` used to identify it.
+/// const CPU1: Cpu1 = Cpu1;
+///
+/// // 64 per-cpu scratch registers, arranged as a contiguous array.
+/// register! {
+/// /// Per-CPU scratch registers.
+/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 {
+/// 31:0 value;
+/// }
+/// }
+///
+/// let cpu0_scratch_0 = CPU_SCRATCH::read(&bar, &Cpu0, 0).value();
+/// let cpu1_scratch_15 = CPU_SCRATCH::read(&bar, &Cpu1, 15).value();
+///
+/// // This won't build.
+/// // let cpu0_scratch_128 = CPU_SCRATCH::read(&bar, &Cpu0, 128).value();
+///
+/// // Runtime-obtained array index.
+/// let scratch_idx = get_scratch_idx();
+/// // Access on a runtime value returns an error if it is out-of-bounds.
+/// let cpu0_some_scratch = CPU_SCRATCH::try_read(&bar, &Cpu0, scratch_idx)?.value();
+///
+/// // `SCRATCH[8]` is used to convey the firmware exit code.
+/// register! {
+/// /// Per-CPU firmware exit status code.
+/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] {
+/// 7:0 status;
+/// }
+/// }
+/// let cpu0_status = CPU_FIRMWARE_STATUS::read(&bar, &Cpu0).status();
+///
+/// // Non-contiguous register arrays can be defined by adding a stride parameter.
+/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
+/// // registers of the two declarations below are interleaved.
+/// register! {
+/// /// Scratch registers bank 0.
+/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ CpuCtlBase + 0x00000d00 {
+/// 31:0 value;
+/// }
+///
+/// /// Scratch registers bank 1.
+/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ CpuCtlBase + 0x00000d04 {
+/// 31:0 value;
+/// }
+/// }
+/// # Ok(())
+/// # }
+/// ```
+/// [`Io`]: kernel::io::Io
+#[macro_export]
+macro_rules! register {
+ // Entry point for the macro, allowing multiple registers to be defined in one call.
+ // It matches all possible register declaration patterns to dispatch them to corresponding
+ // `@reg` rule that defines a single register.
+ (
+ $(
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ $([ $size:expr $(; $stride:expr)? ])? $(@ $offset:literal)?
+ $(@ $base:ident + $base_offset:literal)?
+ $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )?
+ { $($fields:tt)* }
+ )*
+ ) => {
+ $(
+ ::kernel::register!(
+ @reg $(#[$attr])* $vis $name ($storage) $([$size $(; $stride)?])?
+ $(@ $offset)?
+ $(@ $base + $base_offset)?
+ $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )?
+ { $($fields)* }
+ );
+ )*
+ };
+
+ // All the rules below are private helpers.
+
+ // Creates a register at a fixed offset of the MMIO space.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal
+ { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ ::kernel::register!(@io_fixed $name($storage) @ $offset);
+ };
+
+ // Creates an alias register of fixed offset register `alias` with its own fields.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident
+ { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET);
+ };
+
+ // Creates a register at a relative offset from a base address provider.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal
+ { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ ::kernel::register!(@io_relative $name($storage) @ $base + $offset );
+ };
+
+ // Creates an alias register of relative offset register `alias` with its own fields.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident
+ { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ ::kernel::register!(@io_relative $name($storage) @ $base + $alias::OFFSET );
+ };
+
+ // Creates an array of registers at a fixed offset of the MMIO space.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ [ $size:expr ; $stride:expr ] @ $offset:literal { $($fields:tt)* }
+ ) => {
+ static_assert!(::core::mem::size_of::<$storage>() <= $stride);
+
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ ::kernel::register!(@io_array $name($storage) [ $size ; $stride ] @ $offset);
+ };
+
+ // Shortcut for contiguous array of registers (stride == size of element).
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal
+ { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ]
+ @ $offset { $($fields)* }
+ );
+ };
+
+ // Creates an alias of register `idx` of array of registers `alias` with its own fields.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ]
+ { $($fields:tt)* }
+ ) => {
+ static_assert!($idx < $alias::SIZE);
+
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE);
+ };
+
+ // Creates an array of registers at a relative offset from a base address provider.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ; $stride:expr ]
+ @ $base:ident + $offset:literal { $($fields:tt)* }
+ ) => {
+ static_assert!(::core::mem::size_of::<$storage>() <= $stride);
+
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ ::kernel::register!(
+ @io_relative_array $name($storage) [ $size ; $stride ] @ $base + $offset
+ );
+ };
+
+ // Shortcut for contiguous array of relative registers (stride == size of element).
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ]
+ @ $base:ident + $offset:literal { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ]
+ @ $base + $offset { $($fields)* }
+ );
+ };
+
+ // Creates an alias of register `idx` of relative array of registers `alias` with its own
+ // fields.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* }
+ ) => {
+ static_assert!($idx < $alias::SIZE);
+
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ ::kernel::register!(
+ @io_relative $name($storage) @ $base + $alias::OFFSET + $idx * $alias::STRIDE
+ );
+ };
+
+ // Generates the bitfield for the register.
+ //
+ // `#[allow(non_camel_case_types)]` is added since register names typically use SCREAMING_CASE.
+ (
+ @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(@bitfield_core
+ #[allow(non_camel_case_types)]
+ $(#[$attr])* $vis $name $storage
+ );
+ ::kernel::register!(@bitfield_fields $vis $name $storage { $($fields)* });
+ };
+
+ // Generates the IO accessors for a fixed offset register.
+ (@io_fixed $name:ident ($storage:ty) @ $offset:expr) => {
+ #[allow(dead_code)]
+ impl $name {
+ /// Absolute offset of the register.
+ pub const OFFSET: usize = $offset;
+
+ /// Read the register from its address in `io`.
+ #[inline(always)]
+ pub fn read<T, I>(io: &T) -> Self where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ {
+ Self(<$storage as $crate::io::register::RegisterIo>::read(io, Self::OFFSET))
+ }
+
+ /// Write the value contained in `self` to the register address in `io`.
+ #[inline(always)]
+ pub fn write<T, I>(self, io: &T) where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ {
+ <$storage as $crate::io::register::RegisterIo>::write(self.0, io, Self::OFFSET)
+ }
+
+ /// Read the register from its address in `io` and run `f` on its value to obtain a new
+ /// value to write back.
+ ///
+ /// Note that this operation is not atomic. In concurrent contexts, external
+ /// synchronization may be required to prevent race conditions.
+ #[inline(always)]
+ pub fn update<T, I, F>(
+ io: &T,
+ f: F,
+ ) where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ F: ::core::ops::FnOnce(Self) -> Self,
+ {
+ let reg = f(Self::read(io));
+ reg.write(io);
+ }
+ }
+ };
+
+ // Generates the IO accessors for a relative offset register.
+ (@io_relative $name:ident ($storage:ty) @ $base:ident + $offset:expr ) => {
+ #[allow(dead_code)]
+ impl $name {
+ /// Relative offset of the register.
+ pub const OFFSET: usize = $offset;
+
+ /// Read the register from `io`, using the base address provided by `base` and adding
+ /// the register's offset to it.
+ #[inline(always)]
+ pub fn read<T, I, B>(
+ io: &T,
+ #[allow(unused_variables)]
+ base: &B,
+ ) -> Self where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ {
+ let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET;
+
+ Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
+ }
+
+ /// Write the value contained in `self` to `io`, using the base address provided by
+ /// `base` and adding the register's offset to it.
+ #[inline(always)]
+ pub fn write<T, I, B>(
+ self,
+ io: &T,
+ #[allow(unused_variables)]
+ base: &B,
+ ) where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ {
+ let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET;
+
+ <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
+ }
+
+ /// Read the register from `io`, using the base address provided by `base` and adding
+ /// the register's offset to it, then run `f` on its value to obtain a new value to
+ /// write back.
+ ///
+ /// Note that this operation is not atomic. In concurrent contexts, external
+ /// synchronization may be required to prevent race conditions.
+ #[inline(always)]
+ pub fn update<T, I, B, F>(
+ io: &T,
+ base: &B,
+ f: F,
+ ) where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ F: ::core::ops::FnOnce(Self) -> Self,
+ {
+ let reg = f(Self::read(io, base));
+ reg.write(io, base);
+ }
+ }
+ };
+
+ // Generates the IO accessors for an array of registers.
+ (@io_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] @ $offset:literal) => {
+ #[allow(dead_code)]
+ impl $name {
+ /// Absolute offset of the register array.
+ pub const OFFSET: usize = $offset;
+ /// Number of elements in the array of registers.
+ pub const SIZE: usize = $size;
+ /// Number of bytes separating each element of the array of registers.
+ pub const STRIDE: usize = $stride;
+
+ /// Read the array register at index `idx` from its address in `io`.
+ #[inline(always)]
+ pub fn read<T, I>(
+ io: &T,
+ idx: usize,
+ ) -> Self where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ {
+ build_assert!(idx < Self::SIZE);
+
+ let offset = Self::OFFSET + (idx * Self::STRIDE);
+
+ Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
+ }
+
+ /// Write the value contained in `self` to the array register with index `idx` in `io`.
+ #[inline(always)]
+ pub fn write<T, I>(
+ self,
+ io: &T,
+ idx: usize
+ ) where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ {
+ build_assert!(idx < Self::SIZE);
+
+ let offset = Self::OFFSET + (idx * Self::STRIDE);
+
+ <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
+ }
+
+ /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
+ /// new value to write back.
+ ///
+ /// Note that this operation is not atomic. In concurrent contexts, external
+ /// synchronization may be required to prevent race conditions.
+ #[inline(always)]
+ pub fn update<T, I, F>(
+ io: &T,
+ idx: usize,
+ f: F,
+ ) where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ F: ::core::ops::FnOnce(Self) -> Self,
+ {
+ let reg = f(Self::read(io, idx));
+ reg.write(io, idx);
+ }
+
+ /// Read the array register at index `idx` from its address in `io`.
+ ///
+ /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
+ /// access was out-of-bounds.
+ #[inline(always)]
+ pub fn try_read<T, I>(
+ io: &T,
+ idx: usize,
+ ) -> ::kernel::error::Result<Self> where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ {
+ if idx < Self::SIZE {
+ Ok(Self::read(io, idx))
+ } else {
+ Err(::kernel::error::code::EINVAL)
+ }
+ }
+
+ /// Write the value contained in `self` to the array register with index `idx` in `io`.
+ ///
+ /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
+ /// access was out-of-bounds.
+ #[inline(always)]
+ pub fn try_write<T, I>(
+ self,
+ io: &T,
+ idx: usize,
+ ) -> ::kernel::error::Result where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ {
+ if idx < Self::SIZE {
+ Ok(self.write(io, idx))
+ } else {
+ Err(::kernel::error::code::EINVAL)
+ }
+ }
+
+ /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
+ /// new value to write back.
+ ///
+ /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
+ /// access was out-of-bounds.
+ ///
+ /// Note that this operation is not atomic. In concurrent contexts, external
+ /// synchronization may be required to prevent race conditions.
+ #[inline(always)]
+ pub fn try_update<T, I, F>(
+ io: &T,
+ idx: usize,
+ f: F,
+ ) -> ::kernel::error::Result where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ F: ::core::ops::FnOnce(Self) -> Self,
+ {
+ if idx < Self::SIZE {
+ Ok(Self::update(io, idx, f))
+ } else {
+ Err(::kernel::error::code::EINVAL)
+ }
+ }
+ }
+ };
+
+ // Generates the IO accessors for an array of relative registers.
+ (
+ @io_relative_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ]
+ @ $base:ident + $offset:literal
+ ) => {
+ #[allow(dead_code)]
+ impl $name {
+ /// Relative offset of the register array.
+ pub const OFFSET: usize = $offset;
+ /// Number of elements in the array of registers.
+ pub const SIZE: usize = $size;
+ /// Number of bytes separating each element of the array of registers.
+ pub const STRIDE: usize = $stride;
+
+ /// Read the array register at index `idx` from `io`, using the base address provided
+ /// by `base` and adding the register's offset to it.
+ #[inline(always)]
+ pub fn read<T, I, B>(
+ io: &T,
+ #[allow(unused_variables)]
+ base: &B,
+ idx: usize,
+ ) -> Self where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ {
+ build_assert!(idx < Self::SIZE);
+
+ let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE +
+ Self::OFFSET + (idx * Self::STRIDE);
+
+ Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
+ }
+
+ /// Write the value contained in `self` to `io`, using the base address provided by
+ /// `base` and adding the offset of array register `idx` to it.
+ #[inline(always)]
+ pub fn write<T, I, B>(
+ self,
+ io: &T,
+ #[allow(unused_variables)]
+ base: &B,
+ idx: usize
+ ) where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ {
+ build_assert!(idx < Self::SIZE);
+
+ let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE +
+ Self::OFFSET + (idx * Self::STRIDE);
+
+ <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
+ }
+
+ /// Read the array register at index `idx` from `io`, using the base address provided
+ /// by `base` and adding the register's offset to it, then run `f` on its value to
+ /// obtain a new value to write back.
+ ///
+ /// Note that this operation is not atomic. In concurrent contexts, external
+ /// synchronization may be required to prevent race conditions.
+ #[inline(always)]
+ pub fn update<T, I, B, F>(
+ io: &T,
+ base: &B,
+ idx: usize,
+ f: F,
+ ) where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ F: ::core::ops::FnOnce(Self) -> Self,
+ {
+ let reg = f(Self::read(io, base, idx));
+ reg.write(io, base, idx);
+ }
+
+ /// Read the array register at index `idx` from `io`, using the base address provided
+ /// by `base` and adding the register's offset to it.
+ ///
+ /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
+ /// access was out-of-bounds.
+ #[inline(always)]
+ pub fn try_read<T, I, B>(
+ io: &T,
+ base: &B,
+ idx: usize,
+ ) -> ::kernel::error::Result<Self> where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ {
+ if idx < Self::SIZE {
+ Ok(Self::read(io, base, idx))
+ } else {
+ Err(::kernel::error::code::EINVAL)
+ }
+ }
+
+ /// Write the value contained in `self` to `io`, using the base address provided by
+ /// `base` and adding the offset of array register `idx` to it.
+ ///
+ /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
+ /// access was out-of-bounds.
+ #[inline(always)]
+ pub fn try_write<T, I, B>(
+ self,
+ io: &T,
+ base: &B,
+ idx: usize,
+ ) -> ::kernel::error::Result where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ {
+ if idx < Self::SIZE {
+ Ok(self.write(io, base, idx))
+ } else {
+ Err(::kernel::error::code::EINVAL)
+ }
+ }
+
+ /// Read the array register at index `idx` from `io`, using the base address provided
+ /// by `base` and adding the register's offset to it, then run `f` on its value to
+ /// obtain a new value to write back.
+ ///
+ /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
+ /// access was out-of-bounds.
+ ///
+ /// Note that this operation is not atomic. In concurrent contexts, external
+ /// synchronization may be required to prevent race conditions.
+ #[inline(always)]
+ pub fn try_update<T, I, B, F>(
+ io: &T,
+ base: &B,
+ idx: usize,
+ f: F,
+ ) -> ::kernel::error::Result where
+ T: ::core::ops::Deref<Target = I>,
+ I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
+ B: $crate::io::register::RegisterBase<$base>,
+ F: ::core::ops::FnOnce(Self) -> Self,
+ {
+ if idx < Self::SIZE {
+ Ok(Self::update(io, base, idx, f))
+ } else {
+ Err(::kernel::error::code::EINVAL)
+ }
+ }
+ }
+ };
+
+ // Defines the wrapper `$name` type and its conversions from/to the storage type.
+ (@bitfield_core $(#[$attr:meta])* $vis:vis $name:ident $storage:ty) => {
+ $(#[$attr])*
+ #[repr(transparent)]
+ #[derive(Clone, Copy, PartialEq, Eq)]
+ $vis struct $name($storage);
+
+ #[allow(dead_code)]
+ impl $name {
+ /// Creates a bitfield from a raw value.
+ $vis const fn from_raw(value: $storage) -> Self {
+ Self(value)
+ }
+
+ /// Creates a zeroed bitfield value.
+ ///
+ /// This is a const alternative to the `Zeroable::zeroed()` trait method.
+ $vis const fn zeroed() -> Self {
+ Self(0)
+ }
+
+ /// Returns the raw value of this bitfield.
+ ///
+ /// This is similar to the [`From`] implementation, but is shorter to invoke in
+ /// most cases.
+ $vis const fn as_raw(self) -> $storage {
+ self.0
+ }
+ }
+
+ // SAFETY: `$storage` is `Zeroable` and `$name` is transparent.
+ unsafe impl ::pin_init::Zeroable for $name {}
+
+ impl ::core::convert::From<$name> for $storage {
+ fn from(val: $name) -> $storage {
+ val.as_raw()
+ }
+ }
+
+ impl ::core::convert::From<$storage> for $name {
+ fn from(val: $storage) -> $name {
+ Self::from_raw(val)
+ }
+ }
+ };
+
+ // Definitions requiring knowledge of individual fields: private and public field accessors,
+ // and `Debug` implementation.
+ (@bitfield_fields $vis:vis $name:ident $storage:ty {
+ $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident
+ $(?=> $try_into_type:ty)?
+ $(=> $into_type:ty)?
+ ;
+ )*
+ }
+ ) => {
+ #[allow(dead_code)]
+ impl $name {
+ $(
+ ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field);
+ ::kernel::register!(
+ @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field
+ $(?=> $try_into_type)?
+ $(=> $into_type)?
+ );
+ )*
+ }
+
+ ::kernel::register!(@debug $name { $($field;)* });
+ };
+
+ // Private field accessors working with the correct `Bounded` type for the field.
+ (
+ @private_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident
+ ) => {
+ ::kernel::macros::paste!(
+ $vis const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi;
+ $vis const [<$field:upper _MASK>]: $storage =
+ ((((1 << $hi) - 1) << 1) + 1) - ((1 << $lo) - 1);
+ $vis const [<$field:upper _SHIFT>]: u32 = $lo;
+ );
+
+ ::kernel::macros::paste!(
+ fn [<__ $field>](self) ->
+ ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> {
+ // Left shift to align the field's MSB with the storage MSB.
+ const ALIGN_TOP: u32 = $storage::BITS - ($hi + 1);
+ // Right shift to move the top-aligned field to bit 0 of the storage.
+ const ALIGN_BOTTOM: u32 = ALIGN_TOP + $lo;
+
+ // Extract the field using two shifts. `Bounded::shr` produces the correctly-sized
+ // output type.
+ let val = ::kernel::num::Bounded::<$storage, { $storage::BITS }>::from(
+ self.0 << ALIGN_TOP
+ );
+ val.shr::<ALIGN_BOTTOM, { $hi + 1 - $lo } >()
+ }
+
+ const fn [<__set_ $field>](
+ mut self,
+ value: ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>,
+ ) -> Self
+ {
+ const MASK: $storage = $name::[<$field:upper _MASK>];
+ const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
+
+ let value = value.into_inner() << SHIFT;
+ self.0 = (self.0 & !MASK) | value;
+
+ self
+ }
+ );
+ };
+
+ // Public accessors for fields infallibly (`=>`) converted to a type.
+ (
+ @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty :
+ $hi:literal:$lo:literal $field:ident => $into_type:ty
+ ) => {
+ ::kernel::macros::paste!(
+
+ $(#[doc = $doc])*
+ #[doc = "Returns the value of this field."]
+ #[inline(always)]
+ $vis fn $field(self) -> $into_type
+ {
+ self.[<__ $field>]().into()
+ }
+
+ $(#[doc = $doc])*
+ #[doc = "Sets this field to the given `value`."]
+ #[inline(always)]
+ $vis fn [<set_ $field>](self, value: $into_type) -> Self
+ {
+ self.[<__set_ $field>](value.into())
+ }
+
+ );
+ };
+
+ // Public accessors for fields fallibly (`?=>`) converted to a type.
+ (
+ @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty :
+ $hi:tt:$lo:tt $field:ident ?=> $try_into_type:ty
+ ) => {
+ ::kernel::macros::paste!(
+
+ $(#[doc = $doc])*
+ #[doc = "Returns the value of this field."]
+ #[inline(always)]
+ $vis fn $field(self) ->
+ Result<
+ $try_into_type,
+ <$try_into_type as ::core::convert::TryFrom<
+ ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>
+ >>::Error
+ >
+ {
+ self.[<__ $field>]().try_into()
+ }
+
+ $(#[doc = $doc])*
+ #[doc = "Sets this field to the given `value`."]
+ #[inline(always)]
+ $vis fn [<set_ $field>](self, value: $try_into_type) -> Self
+ {
+ self.[<__set_ $field>](value.into())
+ }
+
+ );
+ };
+
+ // Public accessors for fields not converted to a type.
+ (
+ @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty :
+ $hi:tt:$lo:tt $field:ident
+ ) => {
+ ::kernel::macros::paste!(
+
+ $(#[doc = $doc])*
+ #[doc = "Returns the value of this field."]
+ #[inline(always)]
+ $vis fn $field(self) ->
+ ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>
+ {
+ self.[<__ $field>]()
+ }
+
+ $(#[doc = $doc])*
+ #[doc = "Sets this field to the compile-time constant `VALUE`."]
+ #[inline(always)]
+ $vis const fn [<with_ $field>]<const VALUE: $storage>(self) -> Self {
+ self.[<__set_ $field>](
+ ::kernel::num::Bounded::<$storage, { $hi + 1 - $lo }>::new::<VALUE>()
+ )
+ }
+
+ $(#[doc = $doc])*
+ #[doc = "Sets this field to the given `value`."]
+ #[inline(always)]
+ $vis fn [<set_ $field>]<T>(
+ self,
+ value: T,
+ ) -> Self
+ where T: Into<::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>>,
+ {
+ self.[<__set_ $field>](value.into())
+ }
+
+ $(#[doc = $doc])*
+ #[doc = "Tries to set this field to `value`, returning an error if it is out of range."]
+ #[inline(always)]
+ $vis fn [<try_set_ $field>]<T>(
+ self,
+ value: T,
+ ) -> ::kernel::error::Result<Self>
+ where T: ::kernel::num::TryIntoBounded<$storage, { $hi + 1 - $lo }>,
+ {
+ Ok(
+ self.[<__set_ $field>](
+ value.try_into_bounded().ok_or(::kernel::error::code::EOVERFLOW)?
+ )
+ )
+ }
+
+ );
+ };
+
+ // `Debug` implementation.
+ (@debug $name:ident { $($field:ident;)* }) => {
+ impl ::kernel::fmt::Debug for $name {
+ fn fmt(&self, f: &mut ::kernel::fmt::Formatter<'_>) -> ::kernel::fmt::Result {
+ f.debug_struct(stringify!($name))
+ .field("<raw>", &::kernel::prelude::fmt!("{:#x}", self.0))
+ $(
+ .field(stringify!($field), &self.$field())
+ )*
+ .finish()
+ }
+ }
+ };
+}
--
2.52.0
On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote:
> Add a macro for defining hardware register types with I/O accessors.
>
> Each register field is represented as a `Bounded` of the appropriate bit
> width, ensuring field values are never silently truncated.
>
> Fields can optionally be converted to/from custom types, either fallibly
> or infallibly.
>
> The address of registers can be direct, relative, or indexed, supporting
> most of the patterns in which registers are arranged.
>
> Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> rust/kernel/io.rs | 1 +
> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 1288 insertions(+)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index 056a3ec71647..112f43ecbf88 100644
> --- a/rust/kernel/io.rs
> +++ b/rust/kernel/io.rs
> @@ -11,6 +11,7 @@
>
> pub mod mem;
> pub mod poll;
> +pub mod register;
> pub mod resource;
>
> pub use resource::Resource;
> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
> new file mode 100644
> index 000000000000..fc85dcd1f09a
> --- /dev/null
> +++ b/rust/kernel/io/register.rs
> @@ -0,0 +1,1287 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! A macro to define register layout and accessors.
> +//!
> +//! A single register typically includes several fields, which are accessed through a combination
> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
> +//! not all possible field values are necessarily valid.
> +//!
> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a
> +//! dedicated type for each register. Each such type comes with its own field accessors that can
> +//! return an error if a field's value is invalid.
> +//!
> +//! [`register!`]: kernel::register!
> +
> +use core::ops::Deref;
> +
> +use crate::io::{
> + IoCapable,
> + IoKnownSize, //
> +};
> +
> +/// Trait providing a base address to be added to the offset of a relative register to obtain
> +/// its actual offset.
> +///
> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides
> +/// several bases. It is given to the `register!` macro to restrict the use of the register to
> +/// implementors of this particular variant.
> +pub trait RegisterBase<T> {
> + /// Base address to which register offsets are added.
> + const BASE: usize;
> +}
> +
> +/// Trait providing I/O read/write operations for register storage types.
> +///
> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the
> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage
> +/// type.
> +pub trait RegisterIo: Sized {
Is this trait intended for public usage or just internal detail of `register!()`
macro?
If it's the former, then we should probably just put the method into `IoCapable`
and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so
it won't get abused.
> + /// Read a value from the given offset in the I/O region.
> + fn read<T, I>(io: &T, offset: usize) -> Self
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>;
I think generally `Deref` bound shouldn't be exposed to user. What smart
pointers are involved here, and can we implement forwarding impls of `Io`?
> +
> + /// Write a value to the given offset in the I/O region.
> + fn write<T, I>(self, io: &T, offset: usize)
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>;
> +}
> +
> +impl RegisterIo for u8 {
> + #[inline(always)]
> + fn read<T, I>(io: &T, offset: usize) -> Self
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>,
> + {
> + io.read8(offset)
> + }
> +
> + #[inline(always)]
> + fn write<T, I>(self, io: &T, offset: usize)
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>,
> + {
> + io.write8(self, offset)
> + }
> +}
> +
> +impl RegisterIo for u16 {
> + #[inline(always)]
> + fn read<T, I>(io: &T, offset: usize) -> Self
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>,
> + {
> + io.read16(offset)
> + }
> +
> + #[inline(always)]
> + fn write<T, I>(self, io: &T, offset: usize)
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>,
> + {
> + io.write16(self, offset)
> + }
> +}
> +
> +impl RegisterIo for u32 {
> + #[inline(always)]
> + fn read<T, I>(io: &T, offset: usize) -> Self
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>,
> + {
> + io.read32(offset)
> + }
> +
> + #[inline(always)]
> + fn write<T, I>(self, io: &T, offset: usize)
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>,
> + {
> + io.write32(self, offset)
> + }
> +}
> +
> +#[cfg(CONFIG_64BIT)]
This cfg should be removed.
> +impl RegisterIo for u64 {
> + #[inline(always)]
> + fn read<T, I>(io: &T, offset: usize) -> Self
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>,
> + {
> + io.read64(offset)
> + }
> +
> + #[inline(always)]
> + fn write<T, I>(self, io: &T, offset: usize)
> + where
> + T: Deref<Target = I>,
> + I: IoKnownSize + IoCapable<Self>,
> + {
> + io.write64(self, offset)
> + }
> +}
> +
> +/// 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.
> +///
> +/// Example:
> +///
> +/// ```
> +/// use kernel::register;
> +///
> +/// register! {
> +/// /// Basic information about the chip.
> +/// pub BOOT_0(u32) @ 0x00000100 {
> +/// /// Vendor ID.
> +/// 15:8 vendor_id;
> +/// /// Major revision of the chip.
> +/// 7:4 major_revision;
> +/// /// Minor revision of the chip.
> +/// 3:0 minor_revision;
> +/// }
> +/// }
> +/// ```
> +///
> +/// This defines a `BOOT_0` type which can be read from or written to offset `0x100` of an `Io`
> +/// region. For instance, `minor_revision` consists of the 4 least significant bits of the
> +/// register.
> +///
> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their
> +/// getter method, which is named after them. They also have setter methods prefixed with `set_`
> +/// for runtime values and `with_` for constant values. All setters return the updated register
> +/// value.
> +///
> +/// ```no_run
> +/// use kernel::register;
> +/// use kernel::num::Bounded;
> +///
> +/// # register! {
> +/// # pub BOOT_0(u32) @ 0x00000100 {
> +/// # 15:8 vendor_id;
> +/// # 7:4 major_revision;
> +/// # 3:0 minor_revision;
> +/// # }
> +/// # }
> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) {
> +/// # fn obtain_vendor_id() -> u8 { 0xff }
> +/// // Read from the register's defined offset (0x100).
> +/// let boot0 = BOOT_0::read(&bar);
> +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get());
> +///
> +/// // Update some fields and write the new value back.
> +/// boot0
> +/// // Constant values.
> +/// .with_major_revision::<3>()
> +/// .with_minor_revision::<10>()
> +/// // Run-time value.
> +/// .set_vendor_id(obtain_vendor_id())
> +/// .write(&bar);
> +///
> +/// // Or, just read and update the register in a single step.
> +/// BOOT_0::update(&bar, |r| r
> +/// .with_major_revision::<3>()
> +/// .with_minor_revision::<10>()
> +/// .set_vendor_id(obtain_vendor_id())
> +/// );
> +///
> +/// // Constant values can also be built using the const setters.
> +/// const V: BOOT_0 = BOOT_0::zeroed()
> +/// .with_major_revision::<3>()
> +/// .with_minor_revision::<10>();
> +/// # }
> +/// ```
> +///
> +/// Fields can also be transparently converted from/to an arbitrary type by using the bitfield `=>`
> +/// and `?=>` syntaxes.
> +///
> +/// If present, doccomments above register or fields definitions are added to the relevant item
> +/// they document (the register type itself, or the field's setter and getter methods).
> +///
> +/// Note that multiple registers can be defined in a single `register!` invocation. This can be
> +/// useful to group related registers together.
> +///
> +/// ```
> +/// use kernel::register;
> +///
> +/// register! {
> +/// pub BOOT_0(u8) @ 0x00000100 {
> +/// 7:4 major_revision;
> +/// 3:0 minor_revision;
> +/// }
> +///
> +/// pub BOOT_1(u8) @ 0x00000101 {
> +/// 7:5 num_threads;
> +/// 4:0 num_cores;
> +/// }
> +/// };
> +/// ```
> +///
> +/// 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::register;
> +///
> +/// register! {
> +/// /// Scratch register.
> +/// pub SCRATCH(u32) @ 0x00000200 {
> +/// /// Raw value.
> +/// 31:0 value;
> +/// }
> +///
> +/// /// Boot status of the firmware.
> +/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH {
> +/// /// Whether the firmware has completed booting.
> +/// 0:0 completed;
> +/// }
> +/// }
> +/// ```
> +///
> +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also
> +/// providing its own `completed` field.
> +///
> +/// ## Relative registers
> +///
> +/// A register can be defined as being accessible from a fixed offset of a provided base. For
> +/// instance, imagine the following I/O space:
> +///
> +/// ```text
> +/// +-----------------------------+
> +/// | ... |
> +/// | |
> +/// 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 using the `Base + Offset` syntax when specifying the register's address.
> +///
> +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the
> +/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for
> +/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated
> +/// into code:
> +///
> +/// ```no_run
> +/// use kernel::register;
> +/// use kernel::io::register::RegisterBase;
> +///
> +/// // Type used to identify the base.
> +/// pub struct CpuCtlBase;
> +///
> +/// // ZST describing `CPU0`.
> +/// struct Cpu0;
> +/// impl RegisterBase<CpuCtlBase> for Cpu0 {
> +/// const BASE: usize = 0x100;
> +/// }
> +/// // Singleton of `CPU0` used to identify it.
> +/// const CPU0: Cpu0 = Cpu0;
> +///
> +/// // ZST describing `CPU1`.
> +/// struct Cpu1;
> +/// impl RegisterBase<CpuCtlBase> for Cpu1 {
> +/// const BASE: usize = 0x200;
> +/// }
> +/// // Singleton of `CPU1` used to identify it.
> +/// const CPU1: Cpu1 = Cpu1;
> +///
> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) {
> +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
> +/// register! {
> +/// /// CPU core control.
> +/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 {
> +/// /// Start the CPU core.
> +/// 0:0 start;
> +/// }
> +/// }
> +///
> +/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument
> +/// // that is used to resolve its final address by adding its `BASE` to the offset of the
> +/// // register.
> +///
> +/// // Start `CPU0`.
> +/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true));
> +///
> +/// // Start `CPU1`.
> +/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true));
> +///
> +/// // Aliases can also be defined for relative register.
> +/// register! {
> +/// /// Alias to CPU core control.
> +/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL {
> +/// /// Start the aliased CPU core.
> +/// 1:1 alias_start;
> +/// }
> +/// }
> +///
> +/// // Start the aliased `CPU0`.
> +/// CPU_CTL_ALIAS::update(&bar, &CPU0, |r| r.set_alias_start(true));
> +/// # }
> +/// ```
> +///
> +/// ## Arrays of registers
> +///
> +/// Some I/O areas contain consecutive registers that can be interpreted in the same way. 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. Simply specify their size inside `[` and `]`
> +/// brackets, and add an `idx` parameter to their `read`, `write` and `update` methods:
> +///
> +/// ```no_run
> +/// use kernel::register;
> +///
> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T)
> +/// # -> Result<(), Error>{
> +/// # fn get_scratch_idx() -> usize {
> +/// # 0x15
> +/// # }
> +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
> +/// register! {
> +/// /// Scratch registers.
> +/// pub SCRATCH(u32)[64] @ 0x00000080 {
> +/// 31:0 value;
> +/// }
> +/// }
> +///
> +/// // Read scratch register 0, i.e. I/O address `0x80`.
> +/// let scratch_0 = SCRATCH::read(&bar, 0).value();
> +/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
> +/// let scratch_15 = SCRATCH::read(&bar, 15).value();
> +///
> +/// // This is out of bounds and won't build.
> +/// // let scratch_128 = SCRATCH::read(&bar, 128).value();
> +///
> +/// // Runtime-obtained array index.
> +/// let scratch_idx = get_scratch_idx();
> +/// // Access on a runtime index returns an error if it is out-of-bounds.
> +/// let some_scratch = SCRATCH::try_read(&bar, scratch_idx)?.value();
> +///
> +/// // Alias to a particular register in an array.
> +/// // Here `SCRATCH[8]` is used to convey the firmware exit code.
> +/// register! {
> +/// /// Firmware exit status code.
> +/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] {
> +/// 7:0 status;
> +/// }
> +/// }
> +/// let status = FIRMWARE_STATUS::read(&bar).status();
> +///
> +/// // Non-contiguous register arrays can be defined by adding a stride parameter.
> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
> +/// // registers of the two declarations below are interleaved.
> +/// register! {
> +/// /// Scratch registers bank 0.
> +/// pub SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ 0x000000c0 {
> +/// 31:0 value;
> +/// }
> +///
> +/// /// Scratch registers bank 1.
> +/// pub SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ 0x000000c4 {
> +/// 31:0 value;
> +/// }
> +/// }
> +/// # Ok(())
> +/// # }
> +/// ```
> +///
> +/// ## Relative arrays of registers
> +///
> +/// Combining the two features described in the sections above, arrays of registers accessible from
> +/// a base can also be defined:
> +///
> +/// ```no_run
> +/// use kernel::register;
> +/// use kernel::io::register::RegisterBase;
> +///
> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T)
> +/// # -> Result<(), Error>{
> +/// # fn get_scratch_idx() -> usize {
> +/// # 0x15
> +/// # }
> +/// // Type used as parameter of `RegisterBase` to specify the base.
> +/// pub struct CpuCtlBase;
> +///
> +/// // ZST describing `CPU0`.
> +/// struct Cpu0;
> +/// impl RegisterBase<CpuCtlBase> for Cpu0 {
> +/// const BASE: usize = 0x100;
> +/// }
> +/// // Singleton of `CPU0` used to identify it.
> +/// const CPU0: Cpu0 = Cpu0;
> +///
> +/// // ZST describing `CPU1`.
> +/// struct Cpu1;
> +/// impl RegisterBase<CpuCtlBase> for Cpu1 {
> +/// const BASE: usize = 0x200;
> +/// }
> +/// // Singleton of `CPU1` used to identify it.
> +/// const CPU1: Cpu1 = Cpu1;
> +///
> +/// // 64 per-cpu scratch registers, arranged as a contiguous array.
> +/// register! {
> +/// /// Per-CPU scratch registers.
> +/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 {
> +/// 31:0 value;
> +/// }
> +/// }
> +///
> +/// let cpu0_scratch_0 = CPU_SCRATCH::read(&bar, &Cpu0, 0).value();
> +/// let cpu1_scratch_15 = CPU_SCRATCH::read(&bar, &Cpu1, 15).value();
> +///
> +/// // This won't build.
> +/// // let cpu0_scratch_128 = CPU_SCRATCH::read(&bar, &Cpu0, 128).value();
> +///
> +/// // Runtime-obtained array index.
> +/// let scratch_idx = get_scratch_idx();
> +/// // Access on a runtime value returns an error if it is out-of-bounds.
> +/// let cpu0_some_scratch = CPU_SCRATCH::try_read(&bar, &Cpu0, scratch_idx)?.value();
> +///
> +/// // `SCRATCH[8]` is used to convey the firmware exit code.
> +/// register! {
> +/// /// Per-CPU firmware exit status code.
> +/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] {
> +/// 7:0 status;
> +/// }
> +/// }
> +/// let cpu0_status = CPU_FIRMWARE_STATUS::read(&bar, &Cpu0).status();
> +///
> +/// // Non-contiguous register arrays can be defined by adding a stride parameter.
> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
> +/// // registers of the two declarations below are interleaved.
> +/// register! {
> +/// /// Scratch registers bank 0.
> +/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ CpuCtlBase + 0x00000d00 {
I discussed this with Alex off-list and we agree that this'll better be
pub CPU_SCRATCH_INTERLEAVED_0(u32)[u16, stride = 8] @ CpuCtlBase + 0x00000d00
spelling the full intention out rather than have a special syntax, as this isn't
the common case.
> +/// 31:0 value;
> +/// }
> +///
> +/// /// Scratch registers bank 1.
> +/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ CpuCtlBase + 0x00000d04 {
> +/// 31:0 value;
> +/// }
> +/// }
> +/// # Ok(())
> +/// # }
> +/// ```
> +/// [`Io`]: kernel::io::Io
> +#[macro_export]
> +macro_rules! register {
> + // Entry point for the macro, allowing multiple registers to be defined in one call.
> + // It matches all possible register declaration patterns to dispatch them to corresponding
> + // `@reg` rule that defines a single register.
> + (
> + $(
> + $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
> + $([ $size:expr $(; $stride:expr)? ])? $(@ $offset:literal)?
> + $(@ $base:ident + $base_offset:literal)?
Does `$(@ $($base:ident +)? $offset:literal)?` not work?
> + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )?
> + { $($fields:tt)* }
> + )*
> + ) => {
> + $(
> + ::kernel::register!(
$crate::register!()
> + @reg $(#[$attr])* $vis $name ($storage) $([$size $(; $stride)?])?
> + $(@ $offset)?
> + $(@ $base + $base_offset)?
> + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )?
> + { $($fields)* }
> + );
> + )*
> + };
> +
> + // All the rules below are private helpers.
> +
> + // Creates a register at a fixed offset of the MMIO space.
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal
> + { $($fields:tt)* }
> + ) => {
> + ::kernel::register!(
> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
> + );
> + ::kernel::register!(@io_fixed $name($storage) @ $offset);
> + };
> +
> + // Creates an alias register of fixed offset register `alias` with its own fields.
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident
> + { $($fields:tt)* }
> + ) => {
> + ::kernel::register!(
> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
> + );
> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET);
> + };
> +
> + // Creates a register at a relative offset from a base address provider.
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal
> + { $($fields:tt)* }
> + ) => {
> + ::kernel::register!(
> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
> + );
> + ::kernel::register!(@io_relative $name($storage) @ $base + $offset );
> + };
> +
> + // Creates an alias register of relative offset register `alias` with its own fields.
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident
> + { $($fields:tt)* }
> + ) => {
> + ::kernel::register!(
> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
> + );
> + ::kernel::register!(@io_relative $name($storage) @ $base + $alias::OFFSET );
Would this generate error messages if $name and $alias are of different base, or
would such case be a bug but silently compiles?
> + };
> +
> + // Creates an array of registers at a fixed offset of the MMIO space.
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
> + [ $size:expr ; $stride:expr ] @ $offset:literal { $($fields:tt)* }
> + ) => {
> + static_assert!(::core::mem::size_of::<$storage>() <= $stride);
> +
> + ::kernel::register!(
> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
> + );
> + ::kernel::register!(@io_array $name($storage) [ $size ; $stride ] @ $offset);
> + };
> +
> + // Shortcut for contiguous array of registers (stride == size of element).
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal
> + { $($fields:tt)* }
> + ) => {
> + ::kernel::register!(
> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ]
> + @ $offset { $($fields)* }
> + );
> + };
> +
> + // Creates an alias of register `idx` of array of registers `alias` with its own fields.
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ]
> + { $($fields:tt)* }
> + ) => {
> + static_assert!($idx < $alias::SIZE);
> +
> + ::kernel::register!(
> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
> + );
> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE);
> + };
> +
> + // Creates an array of registers at a relative offset from a base address provider.
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ; $stride:expr ]
> + @ $base:ident + $offset:literal { $($fields:tt)* }
> + ) => {
> + static_assert!(::core::mem::size_of::<$storage>() <= $stride);
> +
> + ::kernel::register!(
> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
> + );
> + ::kernel::register!(
> + @io_relative_array $name($storage) [ $size ; $stride ] @ $base + $offset
> + );
> + };
> +
> + // Shortcut for contiguous array of relative registers (stride == size of element).
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ]
> + @ $base:ident + $offset:literal { $($fields:tt)* }
> + ) => {
> + ::kernel::register!(
> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ]
> + @ $base + $offset { $($fields)* }
> + );
> + };
> +
> + // Creates an alias of register `idx` of relative array of registers `alias` with its own
> + // fields.
> + (
> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
> + => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* }
> + ) => {
> + static_assert!($idx < $alias::SIZE);
> +
> + ::kernel::register!(
> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
> + );
> + ::kernel::register!(
> + @io_relative $name($storage) @ $base + $alias::OFFSET + $idx * $alias::STRIDE
> + );
> + };
> +
> + // Generates the bitfield for the register.
> + //
> + // `#[allow(non_camel_case_types)]` is added since register names typically use SCREAMING_CASE.
> + (
> + @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* }
> + ) => {
> + ::kernel::register!(@bitfield_core
> + #[allow(non_camel_case_types)]
> + $(#[$attr])* $vis $name $storage
> + );
> + ::kernel::register!(@bitfield_fields $vis $name $storage { $($fields)* });
> + };
> +
> + // Generates the IO accessors for a fixed offset register.
> + (@io_fixed $name:ident ($storage:ty) @ $offset:expr) => {
> + #[allow(dead_code)]
> + impl $name {
> + /// Absolute offset of the register.
> + pub const OFFSET: usize = $offset;
> +
> + /// Read the register from its address in `io`.
> + #[inline(always)]
> + pub fn read<T, I>(io: &T) -> Self where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + {
> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, Self::OFFSET))
> + }
> +
> + /// Write the value contained in `self` to the register address in `io`.
> + #[inline(always)]
> + pub fn write<T, I>(self, io: &T) where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + {
> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, Self::OFFSET)
> + }
> +
> + /// Read the register from its address in `io` and run `f` on its value to obtain a new
> + /// value to write back.
> + ///
> + /// Note that this operation is not atomic. In concurrent contexts, external
> + /// synchronization may be required to prevent race conditions.
Given the non-atomicity, how much value does it provide compared to having the
user write read and write themselves? I feel that people reading the code may
assume the atomicity without reading docs if they see `FOO::update`, while it's
less likely that they do so if they read
`FOO::read(io).with_bar(baz).write(io)`.
> + #[inline(always)]
> + pub fn update<T, I, F>(
> + io: &T,
> + f: F,
> + ) where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + F: ::core::ops::FnOnce(Self) -> Self,
> + {
> + let reg = f(Self::read(io));
> + reg.write(io);
> + }
> + }
> + };
> +
> + // Generates the IO accessors for a relative offset register.
> + (@io_relative $name:ident ($storage:ty) @ $base:ident + $offset:expr ) => {
> + #[allow(dead_code)]
> + impl $name {
> + /// Relative offset of the register.
> + pub const OFFSET: usize = $offset;
> +
> + /// Read the register from `io`, using the base address provided by `base` and adding
> + /// the register's offset to it.
> + #[inline(always)]
> + pub fn read<T, I, B>(
> + io: &T,
> + #[allow(unused_variables)]
> + base: &B,
> + ) -> Self where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + {
> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET;
> +
> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
> + }
> +
> + /// Write the value contained in `self` to `io`, using the base address provided by
> + /// `base` and adding the register's offset to it.
> + #[inline(always)]
> + pub fn write<T, I, B>(
> + self,
> + io: &T,
> + #[allow(unused_variables)]
> + base: &B,
> + ) where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + {
> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET;
> +
> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
> + }
> +
> + /// Read the register from `io`, using the base address provided by `base` and adding
> + /// the register's offset to it, then run `f` on its value to obtain a new value to
> + /// write back.
> + ///
> + /// Note that this operation is not atomic. In concurrent contexts, external
> + /// synchronization may be required to prevent race conditions.
> + #[inline(always)]
> + pub fn update<T, I, B, F>(
> + io: &T,
> + base: &B,
> + f: F,
> + ) where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + F: ::core::ops::FnOnce(Self) -> Self,
> + {
> + let reg = f(Self::read(io, base));
> + reg.write(io, base);
> + }
> + }
> + };
> +
> + // Generates the IO accessors for an array of registers.
> + (@io_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] @ $offset:literal) => {
> + #[allow(dead_code)]
> + impl $name {
> + /// Absolute offset of the register array.
> + pub const OFFSET: usize = $offset;
> + /// Number of elements in the array of registers.
> + pub const SIZE: usize = $size;
> + /// Number of bytes separating each element of the array of registers.
> + pub const STRIDE: usize = $stride;
> +
> + /// Read the array register at index `idx` from its address in `io`.
> + #[inline(always)]
> + pub fn read<T, I>(
> + io: &T,
> + idx: usize,
> + ) -> Self where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + {
> + build_assert!(idx < Self::SIZE);
> +
> + let offset = Self::OFFSET + (idx * Self::STRIDE);
> +
> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
> + }
> +
> + /// Write the value contained in `self` to the array register with index `idx` in `io`.
> + #[inline(always)]
> + pub fn write<T, I>(
> + self,
> + io: &T,
> + idx: usize
> + ) where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + {
> + build_assert!(idx < Self::SIZE);
> +
> + let offset = Self::OFFSET + (idx * Self::STRIDE);
> +
> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
> + }
> +
> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
> + /// new value to write back.
> + ///
> + /// Note that this operation is not atomic. In concurrent contexts, external
> + /// synchronization may be required to prevent race conditions.
> + #[inline(always)]
> + pub fn update<T, I, F>(
> + io: &T,
> + idx: usize,
> + f: F,
> + ) where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + F: ::core::ops::FnOnce(Self) -> Self,
> + {
> + let reg = f(Self::read(io, idx));
> + reg.write(io, idx);
> + }
> +
> + /// Read the array register at index `idx` from its address in `io`.
> + ///
> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
> + /// access was out-of-bounds.
> + #[inline(always)]
> + pub fn try_read<T, I>(
> + io: &T,
> + idx: usize,
> + ) -> ::kernel::error::Result<Self> where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + {
> + if idx < Self::SIZE {
> + Ok(Self::read(io, idx))
> + } else {
> + Err(::kernel::error::code::EINVAL)
> + }
> + }
> +
> + /// Write the value contained in `self` to the array register with index `idx` in `io`.
> + ///
> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
> + /// access was out-of-bounds.
> + #[inline(always)]
> + pub fn try_write<T, I>(
> + self,
> + io: &T,
> + idx: usize,
> + ) -> ::kernel::error::Result where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + {
> + if idx < Self::SIZE {
> + Ok(self.write(io, idx))
> + } else {
> + Err(::kernel::error::code::EINVAL)
> + }
> + }
> +
> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
> + /// new value to write back.
> + ///
> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
> + /// access was out-of-bounds.
> + ///
> + /// Note that this operation is not atomic. In concurrent contexts, external
> + /// synchronization may be required to prevent race conditions.
> + #[inline(always)]
> + pub fn try_update<T, I, F>(
> + io: &T,
> + idx: usize,
> + f: F,
> + ) -> ::kernel::error::Result where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + F: ::core::ops::FnOnce(Self) -> Self,
> + {
> + if idx < Self::SIZE {
> + Ok(Self::update(io, idx, f))
> + } else {
> + Err(::kernel::error::code::EINVAL)
> + }
> + }
> + }
> + };
> +
> + // Generates the IO accessors for an array of relative registers.
> + (
> + @io_relative_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ]
> + @ $base:ident + $offset:literal
> + ) => {
> + #[allow(dead_code)]
> + impl $name {
> + /// Relative offset of the register array.
> + pub const OFFSET: usize = $offset;
> + /// Number of elements in the array of registers.
> + pub const SIZE: usize = $size;
> + /// Number of bytes separating each element of the array of registers.
> + pub const STRIDE: usize = $stride;
> +
> + /// Read the array register at index `idx` from `io`, using the base address provided
> + /// by `base` and adding the register's offset to it.
> + #[inline(always)]
> + pub fn read<T, I, B>(
> + io: &T,
> + #[allow(unused_variables)]
> + base: &B,
> + idx: usize,
> + ) -> Self where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + {
> + build_assert!(idx < Self::SIZE);
> +
> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE +
> + Self::OFFSET + (idx * Self::STRIDE);
> +
> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
> + }
> +
> + /// Write the value contained in `self` to `io`, using the base address provided by
> + /// `base` and adding the offset of array register `idx` to it.
> + #[inline(always)]
> + pub fn write<T, I, B>(
> + self,
> + io: &T,
> + #[allow(unused_variables)]
> + base: &B,
> + idx: usize
> + ) where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + {
> + build_assert!(idx < Self::SIZE);
> +
> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE +
> + Self::OFFSET + (idx * Self::STRIDE);
> +
> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
> + }
> +
> + /// Read the array register at index `idx` from `io`, using the base address provided
> + /// by `base` and adding the register's offset to it, then run `f` on its value to
> + /// obtain a new value to write back.
> + ///
> + /// Note that this operation is not atomic. In concurrent contexts, external
> + /// synchronization may be required to prevent race conditions.
> + #[inline(always)]
> + pub fn update<T, I, B, F>(
> + io: &T,
> + base: &B,
> + idx: usize,
> + f: F,
> + ) where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + F: ::core::ops::FnOnce(Self) -> Self,
> + {
> + let reg = f(Self::read(io, base, idx));
> + reg.write(io, base, idx);
> + }
> +
> + /// Read the array register at index `idx` from `io`, using the base address provided
> + /// by `base` and adding the register's offset to it.
> + ///
> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
> + /// access was out-of-bounds.
> + #[inline(always)]
> + pub fn try_read<T, I, B>(
> + io: &T,
> + base: &B,
> + idx: usize,
> + ) -> ::kernel::error::Result<Self> where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + {
> + if idx < Self::SIZE {
> + Ok(Self::read(io, base, idx))
> + } else {
> + Err(::kernel::error::code::EINVAL)
> + }
> + }
> +
> + /// Write the value contained in `self` to `io`, using the base address provided by
> + /// `base` and adding the offset of array register `idx` to it.
> + ///
> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
> + /// access was out-of-bounds.
> + #[inline(always)]
> + pub fn try_write<T, I, B>(
> + self,
> + io: &T,
> + base: &B,
> + idx: usize,
> + ) -> ::kernel::error::Result where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + {
> + if idx < Self::SIZE {
> + Ok(self.write(io, base, idx))
> + } else {
> + Err(::kernel::error::code::EINVAL)
> + }
> + }
> +
> + /// Read the array register at index `idx` from `io`, using the base address provided
> + /// by `base` and adding the register's offset to it, then run `f` on its value to
> + /// obtain a new value to write back.
> + ///
> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
> + /// access was out-of-bounds.
> + ///
> + /// Note that this operation is not atomic. In concurrent contexts, external
> + /// synchronization may be required to prevent race conditions.
> + #[inline(always)]
> + pub fn try_update<T, I, B, F>(
> + io: &T,
> + base: &B,
> + idx: usize,
> + f: F,
> + ) -> ::kernel::error::Result where
> + T: ::core::ops::Deref<Target = I>,
> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
> + B: $crate::io::register::RegisterBase<$base>,
> + F: ::core::ops::FnOnce(Self) -> Self,
> + {
> + if idx < Self::SIZE {
> + Ok(Self::update(io, base, idx, f))
> + } else {
> + Err(::kernel::error::code::EINVAL)
> + }
> + }
> + }
> + };
> +
> + // Defines the wrapper `$name` type and its conversions from/to the storage type.
> + (@bitfield_core $(#[$attr:meta])* $vis:vis $name:ident $storage:ty) => {
> + $(#[$attr])*
> + #[repr(transparent)]
> + #[derive(Clone, Copy, PartialEq, Eq)]
You can derive `Zeroable` and save you an unsafe impl.
> + $vis struct $name($storage);
> +
> + #[allow(dead_code)]
> + impl $name {
> + /// Creates a bitfield from a raw value.
> + $vis const fn from_raw(value: $storage) -> Self {
> + Self(value)
> + }
> +
> + /// Creates a zeroed bitfield value.
> + ///
> + /// This is a const alternative to the `Zeroable::zeroed()` trait method.
> + $vis const fn zeroed() -> Self {
> + Self(0)
> + }
All types that impl `Zeroable` automatically have the `::zeroed()` fn provided
via the trait.
> +
> + /// Returns the raw value of this bitfield.
> + ///
> + /// This is similar to the [`From`] implementation, but is shorter to invoke in
> + /// most cases.
> + $vis const fn as_raw(self) -> $storage {
> + self.0
> + }
> + }
> +
> + // SAFETY: `$storage` is `Zeroable` and `$name` is transparent.
> + unsafe impl ::pin_init::Zeroable for $name {}
> +
> + impl ::core::convert::From<$name> for $storage {
> + fn from(val: $name) -> $storage {
> + val.as_raw()
> + }
> + }
> +
> + impl ::core::convert::From<$storage> for $name {
> + fn from(val: $storage) -> $name {
> + Self::from_raw(val)
> + }
> + }
> + };
> +
> + // Definitions requiring knowledge of individual fields: private and public field accessors,
> + // and `Debug` implementation.
> + (@bitfield_fields $vis:vis $name:ident $storage:ty {
> + $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident
> + $(?=> $try_into_type:ty)?
> + $(=> $into_type:ty)?
> + ;
> + )*
> + }
> + ) => {
> + #[allow(dead_code)]
> + impl $name {
> + $(
> + ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field);
> + ::kernel::register!(
> + @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field
> + $(?=> $try_into_type)?
> + $(=> $into_type)?
> + );
> + )*
> + }
> +
> + ::kernel::register!(@debug $name { $($field;)* });
> + };
> +
> + // Private field accessors working with the correct `Bounded` type for the field.
> + (
> + @private_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident
> + ) => {
> + ::kernel::macros::paste!(
> + $vis const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi;
Is this used by anything?
Best,
Gary
> + $vis const [<$field:upper _MASK>]: $storage =
> + ((((1 << $hi) - 1) << 1) + 1) - ((1 << $lo) - 1);
> + $vis const [<$field:upper _SHIFT>]: u32 = $lo;
> + );
> +
> + ::kernel::macros::paste!(
> + fn [<__ $field>](self) ->
> + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> {
> + // Left shift to align the field's MSB with the storage MSB.
> + const ALIGN_TOP: u32 = $storage::BITS - ($hi + 1);
> + // Right shift to move the top-aligned field to bit 0 of the storage.
> + const ALIGN_BOTTOM: u32 = ALIGN_TOP + $lo;
> +
> + // Extract the field using two shifts. `Bounded::shr` produces the correctly-sized
> + // output type.
> + let val = ::kernel::num::Bounded::<$storage, { $storage::BITS }>::from(
> + self.0 << ALIGN_TOP
> + );
> + val.shr::<ALIGN_BOTTOM, { $hi + 1 - $lo } >()
> + }
> +
> + const fn [<__set_ $field>](
> + mut self,
> + value: ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>,
> + ) -> Self
> + {
> + const MASK: $storage = $name::[<$field:upper _MASK>];
> + const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
> +
> + let value = value.into_inner() << SHIFT;
> + self.0 = (self.0 & !MASK) | value;
> +
> + self
> + }
> + );
> + };
> +
> + // Public accessors for fields infallibly (`=>`) converted to a type.
> + (
> + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty :
> + $hi:literal:$lo:literal $field:ident => $into_type:ty
> + ) => {
> + ::kernel::macros::paste!(
> +
> + $(#[doc = $doc])*
> + #[doc = "Returns the value of this field."]
> + #[inline(always)]
> + $vis fn $field(self) -> $into_type
> + {
> + self.[<__ $field>]().into()
> + }
> +
> + $(#[doc = $doc])*
> + #[doc = "Sets this field to the given `value`."]
> + #[inline(always)]
> + $vis fn [<set_ $field>](self, value: $into_type) -> Self
> + {
> + self.[<__set_ $field>](value.into())
> + }
> +
> + );
> + };
> +
> + // Public accessors for fields fallibly (`?=>`) converted to a type.
> + (
> + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty :
> + $hi:tt:$lo:tt $field:ident ?=> $try_into_type:ty
> + ) => {
> + ::kernel::macros::paste!(
> +
> + $(#[doc = $doc])*
> + #[doc = "Returns the value of this field."]
> + #[inline(always)]
> + $vis fn $field(self) ->
> + Result<
> + $try_into_type,
> + <$try_into_type as ::core::convert::TryFrom<
> + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>
> + >>::Error
> + >
> + {
> + self.[<__ $field>]().try_into()
> + }
> +
> + $(#[doc = $doc])*
> + #[doc = "Sets this field to the given `value`."]
> + #[inline(always)]
> + $vis fn [<set_ $field>](self, value: $try_into_type) -> Self
> + {
> + self.[<__set_ $field>](value.into())
> + }
> +
> + );
> + };
> +
> + // Public accessors for fields not converted to a type.
> + (
> + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty :
> + $hi:tt:$lo:tt $field:ident
> + ) => {
> + ::kernel::macros::paste!(
> +
> + $(#[doc = $doc])*
> + #[doc = "Returns the value of this field."]
> + #[inline(always)]
> + $vis fn $field(self) ->
> + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>
> + {
> + self.[<__ $field>]()
> + }
> +
> + $(#[doc = $doc])*
> + #[doc = "Sets this field to the compile-time constant `VALUE`."]
> + #[inline(always)]
> + $vis const fn [<with_ $field>]<const VALUE: $storage>(self) -> Self {
> + self.[<__set_ $field>](
> + ::kernel::num::Bounded::<$storage, { $hi + 1 - $lo }>::new::<VALUE>()
> + )
> + }
> +
> + $(#[doc = $doc])*
> + #[doc = "Sets this field to the given `value`."]
> + #[inline(always)]
> + $vis fn [<set_ $field>]<T>(
> + self,
> + value: T,
> + ) -> Self
> + where T: Into<::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>>,
> + {
> + self.[<__set_ $field>](value.into())
> + }
> +
> + $(#[doc = $doc])*
> + #[doc = "Tries to set this field to `value`, returning an error if it is out of range."]
> + #[inline(always)]
> + $vis fn [<try_set_ $field>]<T>(
> + self,
> + value: T,
> + ) -> ::kernel::error::Result<Self>
> + where T: ::kernel::num::TryIntoBounded<$storage, { $hi + 1 - $lo }>,
> + {
> + Ok(
> + self.[<__set_ $field>](
> + value.try_into_bounded().ok_or(::kernel::error::code::EOVERFLOW)?
> + )
> + )
> + }
> +
> + );
> + };
> +
> + // `Debug` implementation.
> + (@debug $name:ident { $($field:ident;)* }) => {
> + impl ::kernel::fmt::Debug for $name {
> + fn fmt(&self, f: &mut ::kernel::fmt::Formatter<'_>) -> ::kernel::fmt::Result {
> + f.debug_struct(stringify!($name))
> + .field("<raw>", &::kernel::prelude::fmt!("{:#x}", self.0))
> + $(
> + .field(stringify!($field), &self.$field())
> + )*
> + .finish()
> + }
> + }
> + };
> +}
On Wed Jan 28, 2026 at 5:16 PM CET, Gary Guo wrote:
> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote:
>> + /// Read the register from its address in `io` and run `f` on its value to obtain a new
>> + /// value to write back.
>> + ///
>> + /// Note that this operation is not atomic. In concurrent contexts, external
>> + /// synchronization may be required to prevent race conditions.
>
> Given the non-atomicity, how much value does it provide compared to having the
> user write read and write themselves? I feel that people reading the code may
> assume the atomicity without reading docs if they see `FOO::update`, while it's
> less likely that they do so if they read
> `FOO::read(io).with_bar(baz).write(io)`.
I think update() is fine, there are no promises about atomicity for any of the
I/O functions, also not for read() and write(). I.e. whether an operation is
atomic or not depends on the architecture, bus and device.
We should probably document this clearly to not raise wrong expectations.
>> + #[inline(always)]
>> + pub fn update<T, I, F>(
>> + io: &T,
>> + f: F,
>> + ) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + F: ::core::ops::FnOnce(Self) -> Self,
>> + {
>> + let reg = f(Self::read(io));
>> + reg.write(io);
>> + }
>> + }
>> + };
On Thu Jan 29, 2026 at 1:16 AM JST, Gary Guo wrote:
> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote:
>> Add a macro for defining hardware register types with I/O accessors.
>>
>> Each register field is represented as a `Bounded` of the appropriate bit
>> width, ensuring field values are never silently truncated.
>>
>> Fields can optionally be converted to/from custom types, either fallibly
>> or infallibly.
>>
>> The address of registers can be direct, relative, or indexed, supporting
>> most of the patterns in which registers are arranged.
>>
>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>> rust/kernel/io.rs | 1 +
>> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 1288 insertions(+)
>>
>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>> index 056a3ec71647..112f43ecbf88 100644
>> --- a/rust/kernel/io.rs
>> +++ b/rust/kernel/io.rs
>> @@ -11,6 +11,7 @@
>>
>> pub mod mem;
>> pub mod poll;
>> +pub mod register;
>> pub mod resource;
>>
>> pub use resource::Resource;
>> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
>> new file mode 100644
>> index 000000000000..fc85dcd1f09a
>> --- /dev/null
>> +++ b/rust/kernel/io/register.rs
>> @@ -0,0 +1,1287 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +//! A macro to define register layout and accessors.
>> +//!
>> +//! A single register typically includes several fields, which are accessed through a combination
>> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
>> +//! not all possible field values are necessarily valid.
>> +//!
>> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a
>> +//! dedicated type for each register. Each such type comes with its own field accessors that can
>> +//! return an error if a field's value is invalid.
>> +//!
>> +//! [`register!`]: kernel::register!
>> +
>> +use core::ops::Deref;
>> +
>> +use crate::io::{
>> + IoCapable,
>> + IoKnownSize, //
>> +};
>> +
>> +/// Trait providing a base address to be added to the offset of a relative register to obtain
>> +/// its actual offset.
>> +///
>> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides
>> +/// several bases. It is given to the `register!` macro to restrict the use of the register to
>> +/// implementors of this particular variant.
>> +pub trait RegisterBase<T> {
>> + /// Base address to which register offsets are added.
>> + const BASE: usize;
>> +}
>> +
>> +/// Trait providing I/O read/write operations for register storage types.
>> +///
>> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the
>> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage
>> +/// type.
>> +pub trait RegisterIo: Sized {
>
> Is this trait intended for public usage or just internal detail of `register!()`
> macro?
>
> If it's the former, then we should probably just put the method into `IoCapable`
> and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so
> it won't get abused.
It is an internal detail of `register!()` and not supposed to be used by anyone
else.
>
>> + /// Read a value from the given offset in the I/O region.
>> + fn read<T, I>(io: &T, offset: usize) -> Self
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>;
>
> I think generally `Deref` bound shouldn't be exposed to user. What smart
> pointers are involved here, and can we implement forwarding impls of `Io`?
Removing the requirement for `Deref` in the `RegisterIo` trait is simple - we
can just call `Deref` in the register IO accessors.
But I suspect you mean that we should also remove it from the register
accessors themselves? The problem if I remove it there is that things like
Nova's `Bar0` do not implement `IoKnownSize` and `IoCapable` (but rather deref
to something that does), which would require all callers to do the deref op
itself as deref coercion doesn't seem to be usable here.
>
>> +
>> + /// Write a value to the given offset in the I/O region.
>> + fn write<T, I>(self, io: &T, offset: usize)
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>;
>> +}
>> +
>> +impl RegisterIo for u8 {
>> + #[inline(always)]
>> + fn read<T, I>(io: &T, offset: usize) -> Self
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>,
>> + {
>> + io.read8(offset)
>> + }
>> +
>> + #[inline(always)]
>> + fn write<T, I>(self, io: &T, offset: usize)
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>,
>> + {
>> + io.write8(self, offset)
>> + }
>> +}
>> +
>> +impl RegisterIo for u16 {
>> + #[inline(always)]
>> + fn read<T, I>(io: &T, offset: usize) -> Self
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>,
>> + {
>> + io.read16(offset)
>> + }
>> +
>> + #[inline(always)]
>> + fn write<T, I>(self, io: &T, offset: usize)
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>,
>> + {
>> + io.write16(self, offset)
>> + }
>> +}
>> +
>> +impl RegisterIo for u32 {
>> + #[inline(always)]
>> + fn read<T, I>(io: &T, offset: usize) -> Self
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>,
>> + {
>> + io.read32(offset)
>> + }
>> +
>> + #[inline(always)]
>> + fn write<T, I>(self, io: &T, offset: usize)
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>,
>> + {
>> + io.write32(self, offset)
>> + }
>> +}
>> +
>> +#[cfg(CONFIG_64BIT)]
>
> This cfg should be removed.
Done.
>
>> +impl RegisterIo for u64 {
>> + #[inline(always)]
>> + fn read<T, I>(io: &T, offset: usize) -> Self
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>,
>> + {
>> + io.read64(offset)
>> + }
>> +
>> + #[inline(always)]
>> + fn write<T, I>(self, io: &T, offset: usize)
>> + where
>> + T: Deref<Target = I>,
>> + I: IoKnownSize + IoCapable<Self>,
>> + {
>> + io.write64(self, offset)
>> + }
>> +}
>> +
>> +/// 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.
>> +///
>> +/// Example:
>> +///
>> +/// ```
>> +/// use kernel::register;
>> +///
>> +/// register! {
>> +/// /// Basic information about the chip.
>> +/// pub BOOT_0(u32) @ 0x00000100 {
>> +/// /// Vendor ID.
>> +/// 15:8 vendor_id;
>> +/// /// Major revision of the chip.
>> +/// 7:4 major_revision;
>> +/// /// Minor revision of the chip.
>> +/// 3:0 minor_revision;
>> +/// }
>> +/// }
>> +/// ```
>> +///
>> +/// This defines a `BOOT_0` type which can be read from or written to offset `0x100` of an `Io`
>> +/// region. For instance, `minor_revision` consists of the 4 least significant bits of the
>> +/// register.
>> +///
>> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their
>> +/// getter method, which is named after them. They also have setter methods prefixed with `set_`
>> +/// for runtime values and `with_` for constant values. All setters return the updated register
>> +/// value.
>> +///
>> +/// ```no_run
>> +/// use kernel::register;
>> +/// use kernel::num::Bounded;
>> +///
>> +/// # register! {
>> +/// # pub BOOT_0(u32) @ 0x00000100 {
>> +/// # 15:8 vendor_id;
>> +/// # 7:4 major_revision;
>> +/// # 3:0 minor_revision;
>> +/// # }
>> +/// # }
>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) {
>> +/// # fn obtain_vendor_id() -> u8 { 0xff }
>> +/// // Read from the register's defined offset (0x100).
>> +/// let boot0 = BOOT_0::read(&bar);
>> +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get());
>> +///
>> +/// // Update some fields and write the new value back.
>> +/// boot0
>> +/// // Constant values.
>> +/// .with_major_revision::<3>()
>> +/// .with_minor_revision::<10>()
>> +/// // Run-time value.
>> +/// .set_vendor_id(obtain_vendor_id())
>> +/// .write(&bar);
>> +///
>> +/// // Or, just read and update the register in a single step.
>> +/// BOOT_0::update(&bar, |r| r
>> +/// .with_major_revision::<3>()
>> +/// .with_minor_revision::<10>()
>> +/// .set_vendor_id(obtain_vendor_id())
>> +/// );
>> +///
>> +/// // Constant values can also be built using the const setters.
>> +/// const V: BOOT_0 = BOOT_0::zeroed()
>> +/// .with_major_revision::<3>()
>> +/// .with_minor_revision::<10>();
>> +/// # }
>> +/// ```
>> +///
>> +/// Fields can also be transparently converted from/to an arbitrary type by using the bitfield `=>`
>> +/// and `?=>` syntaxes.
>> +///
>> +/// If present, doccomments above register or fields definitions are added to the relevant item
>> +/// they document (the register type itself, or the field's setter and getter methods).
>> +///
>> +/// Note that multiple registers can be defined in a single `register!` invocation. This can be
>> +/// useful to group related registers together.
>> +///
>> +/// ```
>> +/// use kernel::register;
>> +///
>> +/// register! {
>> +/// pub BOOT_0(u8) @ 0x00000100 {
>> +/// 7:4 major_revision;
>> +/// 3:0 minor_revision;
>> +/// }
>> +///
>> +/// pub BOOT_1(u8) @ 0x00000101 {
>> +/// 7:5 num_threads;
>> +/// 4:0 num_cores;
>> +/// }
>> +/// };
>> +/// ```
>> +///
>> +/// 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::register;
>> +///
>> +/// register! {
>> +/// /// Scratch register.
>> +/// pub SCRATCH(u32) @ 0x00000200 {
>> +/// /// Raw value.
>> +/// 31:0 value;
>> +/// }
>> +///
>> +/// /// Boot status of the firmware.
>> +/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH {
>> +/// /// Whether the firmware has completed booting.
>> +/// 0:0 completed;
>> +/// }
>> +/// }
>> +/// ```
>> +///
>> +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also
>> +/// providing its own `completed` field.
>> +///
>> +/// ## Relative registers
>> +///
>> +/// A register can be defined as being accessible from a fixed offset of a provided base. For
>> +/// instance, imagine the following I/O space:
>> +///
>> +/// ```text
>> +/// +-----------------------------+
>> +/// | ... |
>> +/// | |
>> +/// 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 using the `Base + Offset` syntax when specifying the register's address.
>> +///
>> +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the
>> +/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for
>> +/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated
>> +/// into code:
>> +///
>> +/// ```no_run
>> +/// use kernel::register;
>> +/// use kernel::io::register::RegisterBase;
>> +///
>> +/// // Type used to identify the base.
>> +/// pub struct CpuCtlBase;
>> +///
>> +/// // ZST describing `CPU0`.
>> +/// struct Cpu0;
>> +/// impl RegisterBase<CpuCtlBase> for Cpu0 {
>> +/// const BASE: usize = 0x100;
>> +/// }
>> +/// // Singleton of `CPU0` used to identify it.
>> +/// const CPU0: Cpu0 = Cpu0;
>> +///
>> +/// // ZST describing `CPU1`.
>> +/// struct Cpu1;
>> +/// impl RegisterBase<CpuCtlBase> for Cpu1 {
>> +/// const BASE: usize = 0x200;
>> +/// }
>> +/// // Singleton of `CPU1` used to identify it.
>> +/// const CPU1: Cpu1 = Cpu1;
>> +///
>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) {
>> +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
>> +/// register! {
>> +/// /// CPU core control.
>> +/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 {
>> +/// /// Start the CPU core.
>> +/// 0:0 start;
>> +/// }
>> +/// }
>> +///
>> +/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument
>> +/// // that is used to resolve its final address by adding its `BASE` to the offset of the
>> +/// // register.
>> +///
>> +/// // Start `CPU0`.
>> +/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true));
>> +///
>> +/// // Start `CPU1`.
>> +/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true));
>> +///
>> +/// // Aliases can also be defined for relative register.
>> +/// register! {
>> +/// /// Alias to CPU core control.
>> +/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL {
>> +/// /// Start the aliased CPU core.
>> +/// 1:1 alias_start;
>> +/// }
>> +/// }
>> +///
>> +/// // Start the aliased `CPU0`.
>> +/// CPU_CTL_ALIAS::update(&bar, &CPU0, |r| r.set_alias_start(true));
>> +/// # }
>> +/// ```
>> +///
>> +/// ## Arrays of registers
>> +///
>> +/// Some I/O areas contain consecutive registers that can be interpreted in the same way. 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. Simply specify their size inside `[` and `]`
>> +/// brackets, and add an `idx` parameter to their `read`, `write` and `update` methods:
>> +///
>> +/// ```no_run
>> +/// use kernel::register;
>> +///
>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T)
>> +/// # -> Result<(), Error>{
>> +/// # fn get_scratch_idx() -> usize {
>> +/// # 0x15
>> +/// # }
>> +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
>> +/// register! {
>> +/// /// Scratch registers.
>> +/// pub SCRATCH(u32)[64] @ 0x00000080 {
>> +/// 31:0 value;
>> +/// }
>> +/// }
>> +///
>> +/// // Read scratch register 0, i.e. I/O address `0x80`.
>> +/// let scratch_0 = SCRATCH::read(&bar, 0).value();
>> +/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
>> +/// let scratch_15 = SCRATCH::read(&bar, 15).value();
>> +///
>> +/// // This is out of bounds and won't build.
>> +/// // let scratch_128 = SCRATCH::read(&bar, 128).value();
>> +///
>> +/// // Runtime-obtained array index.
>> +/// let scratch_idx = get_scratch_idx();
>> +/// // Access on a runtime index returns an error if it is out-of-bounds.
>> +/// let some_scratch = SCRATCH::try_read(&bar, scratch_idx)?.value();
>> +///
>> +/// // Alias to a particular register in an array.
>> +/// // Here `SCRATCH[8]` is used to convey the firmware exit code.
>> +/// register! {
>> +/// /// Firmware exit status code.
>> +/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] {
>> +/// 7:0 status;
>> +/// }
>> +/// }
>> +/// let status = FIRMWARE_STATUS::read(&bar).status();
>> +///
>> +/// // Non-contiguous register arrays can be defined by adding a stride parameter.
>> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
>> +/// // registers of the two declarations below are interleaved.
>> +/// register! {
>> +/// /// Scratch registers bank 0.
>> +/// pub SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ 0x000000c0 {
>> +/// 31:0 value;
>> +/// }
>> +///
>> +/// /// Scratch registers bank 1.
>> +/// pub SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ 0x000000c4 {
>> +/// 31:0 value;
>> +/// }
>> +/// }
>> +/// # Ok(())
>> +/// # }
>> +/// ```
>> +///
>> +/// ## Relative arrays of registers
>> +///
>> +/// Combining the two features described in the sections above, arrays of registers accessible from
>> +/// a base can also be defined:
>> +///
>> +/// ```no_run
>> +/// use kernel::register;
>> +/// use kernel::io::register::RegisterBase;
>> +///
>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T)
>> +/// # -> Result<(), Error>{
>> +/// # fn get_scratch_idx() -> usize {
>> +/// # 0x15
>> +/// # }
>> +/// // Type used as parameter of `RegisterBase` to specify the base.
>> +/// pub struct CpuCtlBase;
>> +///
>> +/// // ZST describing `CPU0`.
>> +/// struct Cpu0;
>> +/// impl RegisterBase<CpuCtlBase> for Cpu0 {
>> +/// const BASE: usize = 0x100;
>> +/// }
>> +/// // Singleton of `CPU0` used to identify it.
>> +/// const CPU0: Cpu0 = Cpu0;
>> +///
>> +/// // ZST describing `CPU1`.
>> +/// struct Cpu1;
>> +/// impl RegisterBase<CpuCtlBase> for Cpu1 {
>> +/// const BASE: usize = 0x200;
>> +/// }
>> +/// // Singleton of `CPU1` used to identify it.
>> +/// const CPU1: Cpu1 = Cpu1;
>> +///
>> +/// // 64 per-cpu scratch registers, arranged as a contiguous array.
>> +/// register! {
>> +/// /// Per-CPU scratch registers.
>> +/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 {
>> +/// 31:0 value;
>> +/// }
>> +/// }
>> +///
>> +/// let cpu0_scratch_0 = CPU_SCRATCH::read(&bar, &Cpu0, 0).value();
>> +/// let cpu1_scratch_15 = CPU_SCRATCH::read(&bar, &Cpu1, 15).value();
>> +///
>> +/// // This won't build.
>> +/// // let cpu0_scratch_128 = CPU_SCRATCH::read(&bar, &Cpu0, 128).value();
>> +///
>> +/// // Runtime-obtained array index.
>> +/// let scratch_idx = get_scratch_idx();
>> +/// // Access on a runtime value returns an error if it is out-of-bounds.
>> +/// let cpu0_some_scratch = CPU_SCRATCH::try_read(&bar, &Cpu0, scratch_idx)?.value();
>> +///
>> +/// // `SCRATCH[8]` is used to convey the firmware exit code.
>> +/// register! {
>> +/// /// Per-CPU firmware exit status code.
>> +/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] {
>> +/// 7:0 status;
>> +/// }
>> +/// }
>> +/// let cpu0_status = CPU_FIRMWARE_STATUS::read(&bar, &Cpu0).status();
>> +///
>> +/// // Non-contiguous register arrays can be defined by adding a stride parameter.
>> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
>> +/// // registers of the two declarations below are interleaved.
>> +/// register! {
>> +/// /// Scratch registers bank 0.
>> +/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ CpuCtlBase + 0x00000d00 {
>
> I discussed this with Alex off-list and we agree that this'll better be
>
> pub CPU_SCRATCH_INTERLEAVED_0(u32)[u16, stride = 8] @ CpuCtlBase + 0x00000d00
>
> spelling the full intention out rather than have a special syntax, as this isn't
> the common case.
Implemented it and it indeed looks much better.
>
>> +/// 31:0 value;
>> +/// }
>> +///
>> +/// /// Scratch registers bank 1.
>> +/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ CpuCtlBase + 0x00000d04 {
>> +/// 31:0 value;
>> +/// }
>> +/// }
>> +/// # Ok(())
>> +/// # }
>> +/// ```
>> +/// [`Io`]: kernel::io::Io
>> +#[macro_export]
>> +macro_rules! register {
>> + // Entry point for the macro, allowing multiple registers to be defined in one call.
>> + // It matches all possible register declaration patterns to dispatch them to corresponding
>> + // `@reg` rule that defines a single register.
>> + (
>> + $(
>> + $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
>> + $([ $size:expr $(; $stride:expr)? ])? $(@ $offset:literal)?
>> + $(@ $base:ident + $base_offset:literal)?
>
> Does `$(@ $($base:ident +)? $offset:literal)?` not work?
It does! I was trying to match the `+` in the right-side expression and
obviously couldn't, but it didn't occur to me to do it the other way. :)
>
>> + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )?
>> + { $($fields:tt)* }
>> + )*
>> + ) => {
>> + $(
>> + ::kernel::register!(
>
> $crate::register!()
Updated throughout the file (I am not sure to understand the difference though?).
>
>> + @reg $(#[$attr])* $vis $name ($storage) $([$size $(; $stride)?])?
>> + $(@ $offset)?
>> + $(@ $base + $base_offset)?
>> + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )?
>> + { $($fields)* }
>> + );
>> + )*
>> + };
>> +
>> + // All the rules below are private helpers.
>> +
>> + // Creates a register at a fixed offset of the MMIO space.
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal
>> + { $($fields:tt)* }
>> + ) => {
>> + ::kernel::register!(
>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>> + );
>> + ::kernel::register!(@io_fixed $name($storage) @ $offset);
>> + };
>> +
>> + // Creates an alias register of fixed offset register `alias` with its own fields.
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident
>> + { $($fields:tt)* }
>> + ) => {
>> + ::kernel::register!(
>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>> + );
>> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET);
>> + };
>> +
>> + // Creates a register at a relative offset from a base address provider.
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal
>> + { $($fields:tt)* }
>> + ) => {
>> + ::kernel::register!(
>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>> + );
>> + ::kernel::register!(@io_relative $name($storage) @ $base + $offset );
>> + };
>> +
>> + // Creates an alias register of relative offset register `alias` with its own fields.
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident
>> + { $($fields:tt)* }
>> + ) => {
>> + ::kernel::register!(
>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>> + );
>> + ::kernel::register!(@io_relative $name($storage) @ $base + $alias::OFFSET );
>
> Would this generate error messages if $name and $alias are of different base, or
> would such case be a bug but silently compiles?
You would get an build error when trying to use the alias as the bases
are dedicated types and the types would be incompatible.
>
>> + };
>> +
>> + // Creates an array of registers at a fixed offset of the MMIO space.
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
>> + [ $size:expr ; $stride:expr ] @ $offset:literal { $($fields:tt)* }
>> + ) => {
>> + static_assert!(::core::mem::size_of::<$storage>() <= $stride);
>> +
>> + ::kernel::register!(
>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>> + );
>> + ::kernel::register!(@io_array $name($storage) [ $size ; $stride ] @ $offset);
>> + };
>> +
>> + // Shortcut for contiguous array of registers (stride == size of element).
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal
>> + { $($fields:tt)* }
>> + ) => {
>> + ::kernel::register!(
>> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ]
>> + @ $offset { $($fields)* }
>> + );
>> + };
>> +
>> + // Creates an alias of register `idx` of array of registers `alias` with its own fields.
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ]
>> + { $($fields:tt)* }
>> + ) => {
>> + static_assert!($idx < $alias::SIZE);
>> +
>> + ::kernel::register!(
>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>> + );
>> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE);
>> + };
>> +
>> + // Creates an array of registers at a relative offset from a base address provider.
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ; $stride:expr ]
>> + @ $base:ident + $offset:literal { $($fields:tt)* }
>> + ) => {
>> + static_assert!(::core::mem::size_of::<$storage>() <= $stride);
>> +
>> + ::kernel::register!(
>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>> + );
>> + ::kernel::register!(
>> + @io_relative_array $name($storage) [ $size ; $stride ] @ $base + $offset
>> + );
>> + };
>> +
>> + // Shortcut for contiguous array of relative registers (stride == size of element).
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ]
>> + @ $base:ident + $offset:literal { $($fields:tt)* }
>> + ) => {
>> + ::kernel::register!(
>> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ]
>> + @ $base + $offset { $($fields)* }
>> + );
>> + };
>> +
>> + // Creates an alias of register `idx` of relative array of registers `alias` with its own
>> + // fields.
>> + (
>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
>> + => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* }
>> + ) => {
>> + static_assert!($idx < $alias::SIZE);
>> +
>> + ::kernel::register!(
>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>> + );
>> + ::kernel::register!(
>> + @io_relative $name($storage) @ $base + $alias::OFFSET + $idx * $alias::STRIDE
>> + );
>> + };
>> +
>> + // Generates the bitfield for the register.
>> + //
>> + // `#[allow(non_camel_case_types)]` is added since register names typically use SCREAMING_CASE.
>> + (
>> + @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* }
>> + ) => {
>> + ::kernel::register!(@bitfield_core
>> + #[allow(non_camel_case_types)]
>> + $(#[$attr])* $vis $name $storage
>> + );
>> + ::kernel::register!(@bitfield_fields $vis $name $storage { $($fields)* });
>> + };
>> +
>> + // Generates the IO accessors for a fixed offset register.
>> + (@io_fixed $name:ident ($storage:ty) @ $offset:expr) => {
>> + #[allow(dead_code)]
>> + impl $name {
>> + /// Absolute offset of the register.
>> + pub const OFFSET: usize = $offset;
>> +
>> + /// Read the register from its address in `io`.
>> + #[inline(always)]
>> + pub fn read<T, I>(io: &T) -> Self where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + {
>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, Self::OFFSET))
>> + }
>> +
>> + /// Write the value contained in `self` to the register address in `io`.
>> + #[inline(always)]
>> + pub fn write<T, I>(self, io: &T) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + {
>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, Self::OFFSET)
>> + }
>> +
>> + /// Read the register from its address in `io` and run `f` on its value to obtain a new
>> + /// value to write back.
>> + ///
>> + /// Note that this operation is not atomic. In concurrent contexts, external
>> + /// synchronization may be required to prevent race conditions.
>
> Given the non-atomicity, how much value does it provide compared to having the
> user write read and write themselves? I feel that people reading the code may
> assume the atomicity without reading docs if they see `FOO::update`, while it's
> less likely that they do so if they read
> `FOO::read(io).with_bar(baz).write(io)`.
It is just a convenience method. I cannot pretend it is widely used, but
it allows to turn multiple-step ops into one-liners.
>
>> + #[inline(always)]
>> + pub fn update<T, I, F>(
>> + io: &T,
>> + f: F,
>> + ) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + F: ::core::ops::FnOnce(Self) -> Self,
>> + {
>> + let reg = f(Self::read(io));
>> + reg.write(io);
>> + }
>> + }
>> + };
>> +
>> + // Generates the IO accessors for a relative offset register.
>> + (@io_relative $name:ident ($storage:ty) @ $base:ident + $offset:expr ) => {
>> + #[allow(dead_code)]
>> + impl $name {
>> + /// Relative offset of the register.
>> + pub const OFFSET: usize = $offset;
>> +
>> + /// Read the register from `io`, using the base address provided by `base` and adding
>> + /// the register's offset to it.
>> + #[inline(always)]
>> + pub fn read<T, I, B>(
>> + io: &T,
>> + #[allow(unused_variables)]
>> + base: &B,
>> + ) -> Self where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + {
>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET;
>> +
>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
>> + }
>> +
>> + /// Write the value contained in `self` to `io`, using the base address provided by
>> + /// `base` and adding the register's offset to it.
>> + #[inline(always)]
>> + pub fn write<T, I, B>(
>> + self,
>> + io: &T,
>> + #[allow(unused_variables)]
>> + base: &B,
>> + ) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + {
>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET;
>> +
>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
>> + }
>> +
>> + /// Read the register from `io`, using the base address provided by `base` and adding
>> + /// the register's offset to it, then run `f` on its value to obtain a new value to
>> + /// write back.
>> + ///
>> + /// Note that this operation is not atomic. In concurrent contexts, external
>> + /// synchronization may be required to prevent race conditions.
>> + #[inline(always)]
>> + pub fn update<T, I, B, F>(
>> + io: &T,
>> + base: &B,
>> + f: F,
>> + ) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + F: ::core::ops::FnOnce(Self) -> Self,
>> + {
>> + let reg = f(Self::read(io, base));
>> + reg.write(io, base);
>> + }
>> + }
>> + };
>> +
>> + // Generates the IO accessors for an array of registers.
>> + (@io_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] @ $offset:literal) => {
>> + #[allow(dead_code)]
>> + impl $name {
>> + /// Absolute offset of the register array.
>> + pub const OFFSET: usize = $offset;
>> + /// Number of elements in the array of registers.
>> + pub const SIZE: usize = $size;
>> + /// Number of bytes separating each element of the array of registers.
>> + pub const STRIDE: usize = $stride;
>> +
>> + /// Read the array register at index `idx` from its address in `io`.
>> + #[inline(always)]
>> + pub fn read<T, I>(
>> + io: &T,
>> + idx: usize,
>> + ) -> Self where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + {
>> + build_assert!(idx < Self::SIZE);
>> +
>> + let offset = Self::OFFSET + (idx * Self::STRIDE);
>> +
>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
>> + }
>> +
>> + /// Write the value contained in `self` to the array register with index `idx` in `io`.
>> + #[inline(always)]
>> + pub fn write<T, I>(
>> + self,
>> + io: &T,
>> + idx: usize
>> + ) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + {
>> + build_assert!(idx < Self::SIZE);
>> +
>> + let offset = Self::OFFSET + (idx * Self::STRIDE);
>> +
>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
>> + }
>> +
>> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
>> + /// new value to write back.
>> + ///
>> + /// Note that this operation is not atomic. In concurrent contexts, external
>> + /// synchronization may be required to prevent race conditions.
>> + #[inline(always)]
>> + pub fn update<T, I, F>(
>> + io: &T,
>> + idx: usize,
>> + f: F,
>> + ) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + F: ::core::ops::FnOnce(Self) -> Self,
>> + {
>> + let reg = f(Self::read(io, idx));
>> + reg.write(io, idx);
>> + }
>> +
>> + /// Read the array register at index `idx` from its address in `io`.
>> + ///
>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>> + /// access was out-of-bounds.
>> + #[inline(always)]
>> + pub fn try_read<T, I>(
>> + io: &T,
>> + idx: usize,
>> + ) -> ::kernel::error::Result<Self> where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + {
>> + if idx < Self::SIZE {
>> + Ok(Self::read(io, idx))
>> + } else {
>> + Err(::kernel::error::code::EINVAL)
>> + }
>> + }
>> +
>> + /// Write the value contained in `self` to the array register with index `idx` in `io`.
>> + ///
>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>> + /// access was out-of-bounds.
>> + #[inline(always)]
>> + pub fn try_write<T, I>(
>> + self,
>> + io: &T,
>> + idx: usize,
>> + ) -> ::kernel::error::Result where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + {
>> + if idx < Self::SIZE {
>> + Ok(self.write(io, idx))
>> + } else {
>> + Err(::kernel::error::code::EINVAL)
>> + }
>> + }
>> +
>> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
>> + /// new value to write back.
>> + ///
>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>> + /// access was out-of-bounds.
>> + ///
>> + /// Note that this operation is not atomic. In concurrent contexts, external
>> + /// synchronization may be required to prevent race conditions.
>> + #[inline(always)]
>> + pub fn try_update<T, I, F>(
>> + io: &T,
>> + idx: usize,
>> + f: F,
>> + ) -> ::kernel::error::Result where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + F: ::core::ops::FnOnce(Self) -> Self,
>> + {
>> + if idx < Self::SIZE {
>> + Ok(Self::update(io, idx, f))
>> + } else {
>> + Err(::kernel::error::code::EINVAL)
>> + }
>> + }
>> + }
>> + };
>> +
>> + // Generates the IO accessors for an array of relative registers.
>> + (
>> + @io_relative_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ]
>> + @ $base:ident + $offset:literal
>> + ) => {
>> + #[allow(dead_code)]
>> + impl $name {
>> + /// Relative offset of the register array.
>> + pub const OFFSET: usize = $offset;
>> + /// Number of elements in the array of registers.
>> + pub const SIZE: usize = $size;
>> + /// Number of bytes separating each element of the array of registers.
>> + pub const STRIDE: usize = $stride;
>> +
>> + /// Read the array register at index `idx` from `io`, using the base address provided
>> + /// by `base` and adding the register's offset to it.
>> + #[inline(always)]
>> + pub fn read<T, I, B>(
>> + io: &T,
>> + #[allow(unused_variables)]
>> + base: &B,
>> + idx: usize,
>> + ) -> Self where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + {
>> + build_assert!(idx < Self::SIZE);
>> +
>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE +
>> + Self::OFFSET + (idx * Self::STRIDE);
>> +
>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
>> + }
>> +
>> + /// Write the value contained in `self` to `io`, using the base address provided by
>> + /// `base` and adding the offset of array register `idx` to it.
>> + #[inline(always)]
>> + pub fn write<T, I, B>(
>> + self,
>> + io: &T,
>> + #[allow(unused_variables)]
>> + base: &B,
>> + idx: usize
>> + ) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + {
>> + build_assert!(idx < Self::SIZE);
>> +
>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE +
>> + Self::OFFSET + (idx * Self::STRIDE);
>> +
>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
>> + }
>> +
>> + /// Read the array register at index `idx` from `io`, using the base address provided
>> + /// by `base` and adding the register's offset to it, then run `f` on its value to
>> + /// obtain a new value to write back.
>> + ///
>> + /// Note that this operation is not atomic. In concurrent contexts, external
>> + /// synchronization may be required to prevent race conditions.
>> + #[inline(always)]
>> + pub fn update<T, I, B, F>(
>> + io: &T,
>> + base: &B,
>> + idx: usize,
>> + f: F,
>> + ) where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + F: ::core::ops::FnOnce(Self) -> Self,
>> + {
>> + let reg = f(Self::read(io, base, idx));
>> + reg.write(io, base, idx);
>> + }
>> +
>> + /// Read the array register at index `idx` from `io`, using the base address provided
>> + /// by `base` and adding the register's offset to it.
>> + ///
>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>> + /// access was out-of-bounds.
>> + #[inline(always)]
>> + pub fn try_read<T, I, B>(
>> + io: &T,
>> + base: &B,
>> + idx: usize,
>> + ) -> ::kernel::error::Result<Self> where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + {
>> + if idx < Self::SIZE {
>> + Ok(Self::read(io, base, idx))
>> + } else {
>> + Err(::kernel::error::code::EINVAL)
>> + }
>> + }
>> +
>> + /// Write the value contained in `self` to `io`, using the base address provided by
>> + /// `base` and adding the offset of array register `idx` to it.
>> + ///
>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>> + /// access was out-of-bounds.
>> + #[inline(always)]
>> + pub fn try_write<T, I, B>(
>> + self,
>> + io: &T,
>> + base: &B,
>> + idx: usize,
>> + ) -> ::kernel::error::Result where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + {
>> + if idx < Self::SIZE {
>> + Ok(self.write(io, base, idx))
>> + } else {
>> + Err(::kernel::error::code::EINVAL)
>> + }
>> + }
>> +
>> + /// Read the array register at index `idx` from `io`, using the base address provided
>> + /// by `base` and adding the register's offset to it, then run `f` on its value to
>> + /// obtain a new value to write back.
>> + ///
>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>> + /// access was out-of-bounds.
>> + ///
>> + /// Note that this operation is not atomic. In concurrent contexts, external
>> + /// synchronization may be required to prevent race conditions.
>> + #[inline(always)]
>> + pub fn try_update<T, I, B, F>(
>> + io: &T,
>> + base: &B,
>> + idx: usize,
>> + f: F,
>> + ) -> ::kernel::error::Result where
>> + T: ::core::ops::Deref<Target = I>,
>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>> + B: $crate::io::register::RegisterBase<$base>,
>> + F: ::core::ops::FnOnce(Self) -> Self,
>> + {
>> + if idx < Self::SIZE {
>> + Ok(Self::update(io, base, idx, f))
>> + } else {
>> + Err(::kernel::error::code::EINVAL)
>> + }
>> + }
>> + }
>> + };
>> +
>> + // Defines the wrapper `$name` type and its conversions from/to the storage type.
>> + (@bitfield_core $(#[$attr:meta])* $vis:vis $name:ident $storage:ty) => {
>> + $(#[$attr])*
>> + #[repr(transparent)]
>> + #[derive(Clone, Copy, PartialEq, Eq)]
>
> You can derive `Zeroable` and save you an unsafe impl.
Unfortunately this happens if I try to do it:
error: no rules expected `(`
--> ../samples/rust/rust_driver_pci.rs:74:9
|
74 | / ::kernel::register! {
75 | | VENDOR_ID(u16) @ 0x0 {
76 | | 15:0 vendor_id;
... |
86 | | }
| |_________^ no rules expected this token in macro call
Not quite sure why to be honest.
>
>> + $vis struct $name($storage);
>> +
>> + #[allow(dead_code)]
>> + impl $name {
>> + /// Creates a bitfield from a raw value.
>> + $vis const fn from_raw(value: $storage) -> Self {
>> + Self(value)
>> + }
>> +
>> + /// Creates a zeroed bitfield value.
>> + ///
>> + /// This is a const alternative to the `Zeroable::zeroed()` trait method.
>> + $vis const fn zeroed() -> Self {
>> + Self(0)
>> + }
>
> All types that impl `Zeroable` automatically have the `::zeroed()` fn provided
> via the trait.
Yes, but that method from the trait cannot be used in const context, and
`zeroed` is the starting point for building register values from scratch (and
thus constant values).
>
>> +
>> + /// Returns the raw value of this bitfield.
>> + ///
>> + /// This is similar to the [`From`] implementation, but is shorter to invoke in
>> + /// most cases.
>> + $vis const fn as_raw(self) -> $storage {
>> + self.0
>> + }
>> + }
>> +
>> + // SAFETY: `$storage` is `Zeroable` and `$name` is transparent.
>> + unsafe impl ::pin_init::Zeroable for $name {}
>> +
>> + impl ::core::convert::From<$name> for $storage {
>> + fn from(val: $name) -> $storage {
>> + val.as_raw()
>> + }
>> + }
>> +
>> + impl ::core::convert::From<$storage> for $name {
>> + fn from(val: $storage) -> $name {
>> + Self::from_raw(val)
>> + }
>> + }
>> + };
>> +
>> + // Definitions requiring knowledge of individual fields: private and public field accessors,
>> + // and `Debug` implementation.
>> + (@bitfield_fields $vis:vis $name:ident $storage:ty {
>> + $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident
>> + $(?=> $try_into_type:ty)?
>> + $(=> $into_type:ty)?
>> + ;
>> + )*
>> + }
>> + ) => {
>> + #[allow(dead_code)]
>> + impl $name {
>> + $(
>> + ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field);
>> + ::kernel::register!(
>> + @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field
>> + $(?=> $try_into_type)?
>> + $(=> $into_type)?
>> + );
>> + )*
>> + }
>> +
>> + ::kernel::register!(@debug $name { $($field;)* });
>> + };
>> +
>> + // Private field accessors working with the correct `Bounded` type for the field.
>> + (
>> + @private_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident
>> + ) => {
>> + ::kernel::macros::paste!(
>> + $vis const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi;
>
> Is this used by anything?
Not yet. This is intended for user code that wants to know which bits are used
by a specific field. I don't mind removing it though.
Thanks for the review and ideas!
On Thu Jan 29, 2026 at 8:00 AM GMT, Alexandre Courbot wrote:
> On Thu Jan 29, 2026 at 1:16 AM JST, Gary Guo wrote:
>> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote:
>>> Add a macro for defining hardware register types with I/O accessors.
>>>
>>> Each register field is represented as a `Bounded` of the appropriate bit
>>> width, ensuring field values are never silently truncated.
>>>
>>> Fields can optionally be converted to/from custom types, either fallibly
>>> or infallibly.
>>>
>>> The address of registers can be direct, relative, or indexed, supporting
>>> most of the patterns in which registers are arranged.
>>>
>>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>>> ---
>>> rust/kernel/io.rs | 1 +
>>> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++
>>> 2 files changed, 1288 insertions(+)
>>>
>>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>>> index 056a3ec71647..112f43ecbf88 100644
>>> --- a/rust/kernel/io.rs
>>> +++ b/rust/kernel/io.rs
>>> @@ -11,6 +11,7 @@
>>>
>>> pub mod mem;
>>> pub mod poll;
>>> +pub mod register;
>>> pub mod resource;
>>>
>>> pub use resource::Resource;
>>> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
>>> new file mode 100644
>>> index 000000000000..fc85dcd1f09a
>>> --- /dev/null
>>> +++ b/rust/kernel/io/register.rs
>>> @@ -0,0 +1,1287 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +
>>> +//! A macro to define register layout and accessors.
>>> +//!
>>> +//! A single register typically includes several fields, which are accessed through a combination
>>> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
>>> +//! not all possible field values are necessarily valid.
>>> +//!
>>> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a
>>> +//! dedicated type for each register. Each such type comes with its own field accessors that can
>>> +//! return an error if a field's value is invalid.
>>> +//!
>>> +//! [`register!`]: kernel::register!
>>> +
>>> +use core::ops::Deref;
>>> +
>>> +use crate::io::{
>>> + IoCapable,
>>> + IoKnownSize, //
>>> +};
>>> +
>>> +/// Trait providing a base address to be added to the offset of a relative register to obtain
>>> +/// its actual offset.
>>> +///
>>> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides
>>> +/// several bases. It is given to the `register!` macro to restrict the use of the register to
>>> +/// implementors of this particular variant.
>>> +pub trait RegisterBase<T> {
>>> + /// Base address to which register offsets are added.
>>> + const BASE: usize;
>>> +}
>>> +
>>> +/// Trait providing I/O read/write operations for register storage types.
>>> +///
>>> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the
>>> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage
>>> +/// type.
>>> +pub trait RegisterIo: Sized {
>>
>> Is this trait intended for public usage or just internal detail of `register!()`
>> macro?
>>
>> If it's the former, then we should probably just put the method into `IoCapable`
>> and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so
>> it won't get abused.
>
> It is an internal detail of `register!()` and not supposed to be used by anyone
> else.
>
>>
>>> + /// Read a value from the given offset in the I/O region.
>>> + fn read<T, I>(io: &T, offset: usize) -> Self
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>;
>>
>> I think generally `Deref` bound shouldn't be exposed to user. What smart
>> pointers are involved here, and can we implement forwarding impls of `Io`?
>
> Removing the requirement for `Deref` in the `RegisterIo` trait is simple - we
> can just call `Deref` in the register IO accessors.
The issue with `Deref` bound here is that now you *require* a level of
indirection. If something implements `Io` directly, you cannot use it with the
method. While `&Bar` is accepted, `&Mmio` is not because `Mmio` is a direct
implementor of `Io` and not deref to it.
A `Deref` bound also does not help if, say, a type is `Arc<Bar>` which needs two
level of dereffing before it is Io. For consistency I think it's best to avoid
`Deref` call all together
>
> But I suspect you mean that we should also remove it from the register
> accessors themselves? The problem if I remove it there is that things like
> Nova's `Bar0` do not implement `IoKnownSize` and `IoCapable` (but rather deref
> to something that does), which would require all callers to do the deref op
> itself as deref coercion doesn't seem to be usable here.
>
>>
>>> +
>>> + /// Write a value to the given offset in the I/O region.
>>> + fn write<T, I>(self, io: &T, offset: usize)
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>;
>>> +}
>>> +
>>> +impl RegisterIo for u8 {
>>> + #[inline(always)]
>>> + fn read<T, I>(io: &T, offset: usize) -> Self
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>,
>>> + {
>>> + io.read8(offset)
>>> + }
>>> +
>>> + #[inline(always)]
>>> + fn write<T, I>(self, io: &T, offset: usize)
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>,
>>> + {
>>> + io.write8(self, offset)
>>> + }
>>> +}
>>> +
>>> +impl RegisterIo for u16 {
>>> + #[inline(always)]
>>> + fn read<T, I>(io: &T, offset: usize) -> Self
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>,
>>> + {
>>> + io.read16(offset)
>>> + }
>>> +
>>> + #[inline(always)]
>>> + fn write<T, I>(self, io: &T, offset: usize)
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>,
>>> + {
>>> + io.write16(self, offset)
>>> + }
>>> +}
>>> +
>>> +impl RegisterIo for u32 {
>>> + #[inline(always)]
>>> + fn read<T, I>(io: &T, offset: usize) -> Self
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>,
>>> + {
>>> + io.read32(offset)
>>> + }
>>> +
>>> + #[inline(always)]
>>> + fn write<T, I>(self, io: &T, offset: usize)
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>,
>>> + {
>>> + io.write32(self, offset)
>>> + }
>>> +}
>>> +
>>> +#[cfg(CONFIG_64BIT)]
>>
>> This cfg should be removed.
>
> Done.
>
>>
>>> +impl RegisterIo for u64 {
>>> + #[inline(always)]
>>> + fn read<T, I>(io: &T, offset: usize) -> Self
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>,
>>> + {
>>> + io.read64(offset)
>>> + }
>>> +
>>> + #[inline(always)]
>>> + fn write<T, I>(self, io: &T, offset: usize)
>>> + where
>>> + T: Deref<Target = I>,
>>> + I: IoKnownSize + IoCapable<Self>,
>>> + {
>>> + io.write64(self, offset)
>>> + }
>>> +}
>>> +
>>> +/// 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.
>>> +///
>>> +/// Example:
>>> +///
>>> +/// ```
>>> +/// use kernel::register;
>>> +///
>>> +/// register! {
>>> +/// /// Basic information about the chip.
>>> +/// pub BOOT_0(u32) @ 0x00000100 {
>>> +/// /// Vendor ID.
>>> +/// 15:8 vendor_id;
>>> +/// /// Major revision of the chip.
>>> +/// 7:4 major_revision;
>>> +/// /// Minor revision of the chip.
>>> +/// 3:0 minor_revision;
>>> +/// }
>>> +/// }
>>> +/// ```
>>> +///
>>> +/// This defines a `BOOT_0` type which can be read from or written to offset `0x100` of an `Io`
>>> +/// region. For instance, `minor_revision` consists of the 4 least significant bits of the
>>> +/// register.
>>> +///
>>> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their
>>> +/// getter method, which is named after them. They also have setter methods prefixed with `set_`
>>> +/// for runtime values and `with_` for constant values. All setters return the updated register
>>> +/// value.
>>> +///
>>> +/// ```no_run
>>> +/// use kernel::register;
>>> +/// use kernel::num::Bounded;
>>> +///
>>> +/// # register! {
>>> +/// # pub BOOT_0(u32) @ 0x00000100 {
>>> +/// # 15:8 vendor_id;
>>> +/// # 7:4 major_revision;
>>> +/// # 3:0 minor_revision;
>>> +/// # }
>>> +/// # }
>>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) {
>>> +/// # fn obtain_vendor_id() -> u8 { 0xff }
>>> +/// // Read from the register's defined offset (0x100).
>>> +/// let boot0 = BOOT_0::read(&bar);
>>> +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get());
>>> +///
>>> +/// // Update some fields and write the new value back.
>>> +/// boot0
>>> +/// // Constant values.
>>> +/// .with_major_revision::<3>()
>>> +/// .with_minor_revision::<10>()
>>> +/// // Run-time value.
>>> +/// .set_vendor_id(obtain_vendor_id())
>>> +/// .write(&bar);
>>> +///
>>> +/// // Or, just read and update the register in a single step.
>>> +/// BOOT_0::update(&bar, |r| r
>>> +/// .with_major_revision::<3>()
>>> +/// .with_minor_revision::<10>()
>>> +/// .set_vendor_id(obtain_vendor_id())
>>> +/// );
>>> +///
>>> +/// // Constant values can also be built using the const setters.
>>> +/// const V: BOOT_0 = BOOT_0::zeroed()
>>> +/// .with_major_revision::<3>()
>>> +/// .with_minor_revision::<10>();
>>> +/// # }
>>> +/// ```
>>> +///
>>> +/// Fields can also be transparently converted from/to an arbitrary type by using the bitfield `=>`
>>> +/// and `?=>` syntaxes.
>>> +///
>>> +/// If present, doccomments above register or fields definitions are added to the relevant item
>>> +/// they document (the register type itself, or the field's setter and getter methods).
>>> +///
>>> +/// Note that multiple registers can be defined in a single `register!` invocation. This can be
>>> +/// useful to group related registers together.
>>> +///
>>> +/// ```
>>> +/// use kernel::register;
>>> +///
>>> +/// register! {
>>> +/// pub BOOT_0(u8) @ 0x00000100 {
>>> +/// 7:4 major_revision;
>>> +/// 3:0 minor_revision;
>>> +/// }
>>> +///
>>> +/// pub BOOT_1(u8) @ 0x00000101 {
>>> +/// 7:5 num_threads;
>>> +/// 4:0 num_cores;
>>> +/// }
>>> +/// };
>>> +/// ```
>>> +///
>>> +/// 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::register;
>>> +///
>>> +/// register! {
>>> +/// /// Scratch register.
>>> +/// pub SCRATCH(u32) @ 0x00000200 {
>>> +/// /// Raw value.
>>> +/// 31:0 value;
>>> +/// }
>>> +///
>>> +/// /// Boot status of the firmware.
>>> +/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH {
>>> +/// /// Whether the firmware has completed booting.
>>> +/// 0:0 completed;
>>> +/// }
>>> +/// }
>>> +/// ```
>>> +///
>>> +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also
>>> +/// providing its own `completed` field.
>>> +///
>>> +/// ## Relative registers
>>> +///
>>> +/// A register can be defined as being accessible from a fixed offset of a provided base. For
>>> +/// instance, imagine the following I/O space:
>>> +///
>>> +/// ```text
>>> +/// +-----------------------------+
>>> +/// | ... |
>>> +/// | |
>>> +/// 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 using the `Base + Offset` syntax when specifying the register's address.
>>> +///
>>> +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the
>>> +/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for
>>> +/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated
>>> +/// into code:
>>> +///
>>> +/// ```no_run
>>> +/// use kernel::register;
>>> +/// use kernel::io::register::RegisterBase;
>>> +///
>>> +/// // Type used to identify the base.
>>> +/// pub struct CpuCtlBase;
>>> +///
>>> +/// // ZST describing `CPU0`.
>>> +/// struct Cpu0;
>>> +/// impl RegisterBase<CpuCtlBase> for Cpu0 {
>>> +/// const BASE: usize = 0x100;
>>> +/// }
>>> +/// // Singleton of `CPU0` used to identify it.
>>> +/// const CPU0: Cpu0 = Cpu0;
>>> +///
>>> +/// // ZST describing `CPU1`.
>>> +/// struct Cpu1;
>>> +/// impl RegisterBase<CpuCtlBase> for Cpu1 {
>>> +/// const BASE: usize = 0x200;
>>> +/// }
>>> +/// // Singleton of `CPU1` used to identify it.
>>> +/// const CPU1: Cpu1 = Cpu1;
>>> +///
>>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) {
>>> +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
>>> +/// register! {
>>> +/// /// CPU core control.
>>> +/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 {
>>> +/// /// Start the CPU core.
>>> +/// 0:0 start;
>>> +/// }
>>> +/// }
>>> +///
>>> +/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument
>>> +/// // that is used to resolve its final address by adding its `BASE` to the offset of the
>>> +/// // register.
>>> +///
>>> +/// // Start `CPU0`.
>>> +/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true));
>>> +///
>>> +/// // Start `CPU1`.
>>> +/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true));
>>> +///
>>> +/// // Aliases can also be defined for relative register.
>>> +/// register! {
>>> +/// /// Alias to CPU core control.
>>> +/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL {
>>> +/// /// Start the aliased CPU core.
>>> +/// 1:1 alias_start;
>>> +/// }
>>> +/// }
>>> +///
>>> +/// // Start the aliased `CPU0`.
>>> +/// CPU_CTL_ALIAS::update(&bar, &CPU0, |r| r.set_alias_start(true));
>>> +/// # }
>>> +/// ```
>>> +///
>>> +/// ## Arrays of registers
>>> +///
>>> +/// Some I/O areas contain consecutive registers that can be interpreted in the same way. 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. Simply specify their size inside `[` and `]`
>>> +/// brackets, and add an `idx` parameter to their `read`, `write` and `update` methods:
>>> +///
>>> +/// ```no_run
>>> +/// use kernel::register;
>>> +///
>>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T)
>>> +/// # -> Result<(), Error>{
>>> +/// # fn get_scratch_idx() -> usize {
>>> +/// # 0x15
>>> +/// # }
>>> +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
>>> +/// register! {
>>> +/// /// Scratch registers.
>>> +/// pub SCRATCH(u32)[64] @ 0x00000080 {
>>> +/// 31:0 value;
>>> +/// }
>>> +/// }
>>> +///
>>> +/// // Read scratch register 0, i.e. I/O address `0x80`.
>>> +/// let scratch_0 = SCRATCH::read(&bar, 0).value();
>>> +/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
>>> +/// let scratch_15 = SCRATCH::read(&bar, 15).value();
>>> +///
>>> +/// // This is out of bounds and won't build.
>>> +/// // let scratch_128 = SCRATCH::read(&bar, 128).value();
>>> +///
>>> +/// // Runtime-obtained array index.
>>> +/// let scratch_idx = get_scratch_idx();
>>> +/// // Access on a runtime index returns an error if it is out-of-bounds.
>>> +/// let some_scratch = SCRATCH::try_read(&bar, scratch_idx)?.value();
>>> +///
>>> +/// // Alias to a particular register in an array.
>>> +/// // Here `SCRATCH[8]` is used to convey the firmware exit code.
>>> +/// register! {
>>> +/// /// Firmware exit status code.
>>> +/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] {
>>> +/// 7:0 status;
>>> +/// }
>>> +/// }
>>> +/// let status = FIRMWARE_STATUS::read(&bar).status();
>>> +///
>>> +/// // Non-contiguous register arrays can be defined by adding a stride parameter.
>>> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
>>> +/// // registers of the two declarations below are interleaved.
>>> +/// register! {
>>> +/// /// Scratch registers bank 0.
>>> +/// pub SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ 0x000000c0 {
>>> +/// 31:0 value;
>>> +/// }
>>> +///
>>> +/// /// Scratch registers bank 1.
>>> +/// pub SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ 0x000000c4 {
>>> +/// 31:0 value;
>>> +/// }
>>> +/// }
>>> +/// # Ok(())
>>> +/// # }
>>> +/// ```
>>> +///
>>> +/// ## Relative arrays of registers
>>> +///
>>> +/// Combining the two features described in the sections above, arrays of registers accessible from
>>> +/// a base can also be defined:
>>> +///
>>> +/// ```no_run
>>> +/// use kernel::register;
>>> +/// use kernel::io::register::RegisterBase;
>>> +///
>>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T)
>>> +/// # -> Result<(), Error>{
>>> +/// # fn get_scratch_idx() -> usize {
>>> +/// # 0x15
>>> +/// # }
>>> +/// // Type used as parameter of `RegisterBase` to specify the base.
>>> +/// pub struct CpuCtlBase;
>>> +///
>>> +/// // ZST describing `CPU0`.
>>> +/// struct Cpu0;
>>> +/// impl RegisterBase<CpuCtlBase> for Cpu0 {
>>> +/// const BASE: usize = 0x100;
>>> +/// }
>>> +/// // Singleton of `CPU0` used to identify it.
>>> +/// const CPU0: Cpu0 = Cpu0;
>>> +///
>>> +/// // ZST describing `CPU1`.
>>> +/// struct Cpu1;
>>> +/// impl RegisterBase<CpuCtlBase> for Cpu1 {
>>> +/// const BASE: usize = 0x200;
>>> +/// }
>>> +/// // Singleton of `CPU1` used to identify it.
>>> +/// const CPU1: Cpu1 = Cpu1;
>>> +///
>>> +/// // 64 per-cpu scratch registers, arranged as a contiguous array.
>>> +/// register! {
>>> +/// /// Per-CPU scratch registers.
>>> +/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 {
>>> +/// 31:0 value;
>>> +/// }
>>> +/// }
>>> +///
>>> +/// let cpu0_scratch_0 = CPU_SCRATCH::read(&bar, &Cpu0, 0).value();
>>> +/// let cpu1_scratch_15 = CPU_SCRATCH::read(&bar, &Cpu1, 15).value();
>>> +///
>>> +/// // This won't build.
>>> +/// // let cpu0_scratch_128 = CPU_SCRATCH::read(&bar, &Cpu0, 128).value();
>>> +///
>>> +/// // Runtime-obtained array index.
>>> +/// let scratch_idx = get_scratch_idx();
>>> +/// // Access on a runtime value returns an error if it is out-of-bounds.
>>> +/// let cpu0_some_scratch = CPU_SCRATCH::try_read(&bar, &Cpu0, scratch_idx)?.value();
>>> +///
>>> +/// // `SCRATCH[8]` is used to convey the firmware exit code.
>>> +/// register! {
>>> +/// /// Per-CPU firmware exit status code.
>>> +/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] {
>>> +/// 7:0 status;
>>> +/// }
>>> +/// }
>>> +/// let cpu0_status = CPU_FIRMWARE_STATUS::read(&bar, &Cpu0).status();
>>> +///
>>> +/// // Non-contiguous register arrays can be defined by adding a stride parameter.
>>> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
>>> +/// // registers of the two declarations below are interleaved.
>>> +/// register! {
>>> +/// /// Scratch registers bank 0.
>>> +/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ CpuCtlBase + 0x00000d00 {
>>
>> I discussed this with Alex off-list and we agree that this'll better be
>>
>> pub CPU_SCRATCH_INTERLEAVED_0(u32)[u16, stride = 8] @ CpuCtlBase + 0x00000d00
>>
>> spelling the full intention out rather than have a special syntax, as this isn't
>> the common case.
>
> Implemented it and it indeed looks much better.
>
>>
>>> +/// 31:0 value;
>>> +/// }
>>> +///
>>> +/// /// Scratch registers bank 1.
>>> +/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ CpuCtlBase + 0x00000d04 {
>>> +/// 31:0 value;
>>> +/// }
>>> +/// }
>>> +/// # Ok(())
>>> +/// # }
>>> +/// ```
>>> +/// [`Io`]: kernel::io::Io
>>> +#[macro_export]
>>> +macro_rules! register {
>>> + // Entry point for the macro, allowing multiple registers to be defined in one call.
>>> + // It matches all possible register declaration patterns to dispatch them to corresponding
>>> + // `@reg` rule that defines a single register.
>>> + (
>>> + $(
>>> + $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
>>> + $([ $size:expr $(; $stride:expr)? ])? $(@ $offset:literal)?
>>> + $(@ $base:ident + $base_offset:literal)?
>>
>> Does `$(@ $($base:ident +)? $offset:literal)?` not work?
>
> It does! I was trying to match the `+` in the right-side expression and
> obviously couldn't, but it didn't occur to me to do it the other way. :)
>
>>
>>> + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )?
>>> + { $($fields:tt)* }
>>> + )*
>>> + ) => {
>>> + $(
>>> + ::kernel::register!(
>>
>> $crate::register!()
>
> Updated throughout the file (I am not sure to understand the difference though?).
It'll always resolve to the correct crate.
In theory, a crate can do
extern crate kernel as shell;
extern crate foobar as kernel;
and now ::kernel:: would resolve to the wrong crate. Of course, people doing
this are just shooting their own foot so it's not something to worry about.
Using `$crate::` also allow re-exporting of the macro without adding a direct
dependency (as indirect dependencies are not visible with ::crate_name).
Neither are issue of the `kernel` crate, but in general it's advisable to use
`$crate` where possible.
>
>>
>>> + @reg $(#[$attr])* $vis $name ($storage) $([$size $(; $stride)?])?
>>> + $(@ $offset)?
>>> + $(@ $base + $base_offset)?
>>> + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )?
>>> + { $($fields)* }
>>> + );
>>> + )*
>>> + };
>>> +
>>> + // All the rules below are private helpers.
>>> +
>>> + // Creates a register at a fixed offset of the MMIO space.
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal
>>> + { $($fields:tt)* }
>>> + ) => {
>>> + ::kernel::register!(
>>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>>> + );
>>> + ::kernel::register!(@io_fixed $name($storage) @ $offset);
>>> + };
>>> +
>>> + // Creates an alias register of fixed offset register `alias` with its own fields.
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident
>>> + { $($fields:tt)* }
>>> + ) => {
>>> + ::kernel::register!(
>>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>>> + );
>>> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET);
>>> + };
>>> +
>>> + // Creates a register at a relative offset from a base address provider.
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal
>>> + { $($fields:tt)* }
>>> + ) => {
>>> + ::kernel::register!(
>>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>>> + );
>>> + ::kernel::register!(@io_relative $name($storage) @ $base + $offset );
>>> + };
>>> +
>>> + // Creates an alias register of relative offset register `alias` with its own fields.
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident
>>> + { $($fields:tt)* }
>>> + ) => {
>>> + ::kernel::register!(
>>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>>> + );
>>> + ::kernel::register!(@io_relative $name($storage) @ $base + $alias::OFFSET );
>>
>> Would this generate error messages if $name and $alias are of different base, or
>> would such case be a bug but silently compiles?
>
> You would get an build error when trying to use the alias as the bases
> are dedicated types and the types would be incompatible.
>
>>
>>> + };
>>> +
>>> + // Creates an array of registers at a fixed offset of the MMIO space.
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
>>> + [ $size:expr ; $stride:expr ] @ $offset:literal { $($fields:tt)* }
>>> + ) => {
>>> + static_assert!(::core::mem::size_of::<$storage>() <= $stride);
>>> +
>>> + ::kernel::register!(
>>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>>> + );
>>> + ::kernel::register!(@io_array $name($storage) [ $size ; $stride ] @ $offset);
>>> + };
>>> +
>>> + // Shortcut for contiguous array of registers (stride == size of element).
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal
>>> + { $($fields:tt)* }
>>> + ) => {
>>> + ::kernel::register!(
>>> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ]
>>> + @ $offset { $($fields)* }
>>> + );
>>> + };
>>> +
>>> + // Creates an alias of register `idx` of array of registers `alias` with its own fields.
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ]
>>> + { $($fields:tt)* }
>>> + ) => {
>>> + static_assert!($idx < $alias::SIZE);
>>> +
>>> + ::kernel::register!(
>>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>>> + );
>>> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE);
>>> + };
>>> +
>>> + // Creates an array of registers at a relative offset from a base address provider.
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ; $stride:expr ]
>>> + @ $base:ident + $offset:literal { $($fields:tt)* }
>>> + ) => {
>>> + static_assert!(::core::mem::size_of::<$storage>() <= $stride);
>>> +
>>> + ::kernel::register!(
>>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>>> + );
>>> + ::kernel::register!(
>>> + @io_relative_array $name($storage) [ $size ; $stride ] @ $base + $offset
>>> + );
>>> + };
>>> +
>>> + // Shortcut for contiguous array of relative registers (stride == size of element).
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ]
>>> + @ $base:ident + $offset:literal { $($fields:tt)* }
>>> + ) => {
>>> + ::kernel::register!(
>>> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ]
>>> + @ $base + $offset { $($fields)* }
>>> + );
>>> + };
>>> +
>>> + // Creates an alias of register `idx` of relative array of registers `alias` with its own
>>> + // fields.
>>> + (
>>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
>>> + => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* }
>>> + ) => {
>>> + static_assert!($idx < $alias::SIZE);
>>> +
>>> + ::kernel::register!(
>>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
>>> + );
>>> + ::kernel::register!(
>>> + @io_relative $name($storage) @ $base + $alias::OFFSET + $idx * $alias::STRIDE
>>> + );
>>> + };
>>> +
>>> + // Generates the bitfield for the register.
>>> + //
>>> + // `#[allow(non_camel_case_types)]` is added since register names typically use SCREAMING_CASE.
>>> + (
>>> + @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* }
>>> + ) => {
>>> + ::kernel::register!(@bitfield_core
>>> + #[allow(non_camel_case_types)]
>>> + $(#[$attr])* $vis $name $storage
>>> + );
>>> + ::kernel::register!(@bitfield_fields $vis $name $storage { $($fields)* });
>>> + };
>>> +
>>> + // Generates the IO accessors for a fixed offset register.
>>> + (@io_fixed $name:ident ($storage:ty) @ $offset:expr) => {
>>> + #[allow(dead_code)]
>>> + impl $name {
>>> + /// Absolute offset of the register.
>>> + pub const OFFSET: usize = $offset;
>>> +
>>> + /// Read the register from its address in `io`.
>>> + #[inline(always)]
>>> + pub fn read<T, I>(io: &T) -> Self where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + {
>>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, Self::OFFSET))
>>> + }
>>> +
>>> + /// Write the value contained in `self` to the register address in `io`.
>>> + #[inline(always)]
>>> + pub fn write<T, I>(self, io: &T) where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + {
>>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, Self::OFFSET)
>>> + }
>>> +
>>> + /// Read the register from its address in `io` and run `f` on its value to obtain a new
>>> + /// value to write back.
>>> + ///
>>> + /// Note that this operation is not atomic. In concurrent contexts, external
>>> + /// synchronization may be required to prevent race conditions.
>>
>> Given the non-atomicity, how much value does it provide compared to having the
>> user write read and write themselves? I feel that people reading the code may
>> assume the atomicity without reading docs if they see `FOO::update`, while it's
>> less likely that they do so if they read
>> `FOO::read(io).with_bar(baz).write(io)`.
>
> It is just a convenience method. I cannot pretend it is widely used, but
> it allows to turn multiple-step ops into one-liners.
>
>>
>>> + #[inline(always)]
>>> + pub fn update<T, I, F>(
>>> + io: &T,
>>> + f: F,
>>> + ) where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + F: ::core::ops::FnOnce(Self) -> Self,
>>> + {
>>> + let reg = f(Self::read(io));
>>> + reg.write(io);
>>> + }
>>> + }
>>> + };
>>> +
>>> + // Generates the IO accessors for a relative offset register.
>>> + (@io_relative $name:ident ($storage:ty) @ $base:ident + $offset:expr ) => {
>>> + #[allow(dead_code)]
>>> + impl $name {
>>> + /// Relative offset of the register.
>>> + pub const OFFSET: usize = $offset;
>>> +
>>> + /// Read the register from `io`, using the base address provided by `base` and adding
>>> + /// the register's offset to it.
>>> + #[inline(always)]
>>> + pub fn read<T, I, B>(
>>> + io: &T,
>>> + #[allow(unused_variables)]
>>> + base: &B,
>>> + ) -> Self where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + {
>>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET;
>>> +
>>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
>>> + }
>>> +
>>> + /// Write the value contained in `self` to `io`, using the base address provided by
>>> + /// `base` and adding the register's offset to it.
>>> + #[inline(always)]
>>> + pub fn write<T, I, B>(
>>> + self,
>>> + io: &T,
>>> + #[allow(unused_variables)]
>>> + base: &B,
>>> + ) where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + {
>>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET;
>>> +
>>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
>>> + }
>>> +
>>> + /// Read the register from `io`, using the base address provided by `base` and adding
>>> + /// the register's offset to it, then run `f` on its value to obtain a new value to
>>> + /// write back.
>>> + ///
>>> + /// Note that this operation is not atomic. In concurrent contexts, external
>>> + /// synchronization may be required to prevent race conditions.
>>> + #[inline(always)]
>>> + pub fn update<T, I, B, F>(
>>> + io: &T,
>>> + base: &B,
>>> + f: F,
>>> + ) where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + F: ::core::ops::FnOnce(Self) -> Self,
>>> + {
>>> + let reg = f(Self::read(io, base));
>>> + reg.write(io, base);
>>> + }
>>> + }
>>> + };
>>> +
>>> + // Generates the IO accessors for an array of registers.
>>> + (@io_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] @ $offset:literal) => {
>>> + #[allow(dead_code)]
>>> + impl $name {
>>> + /// Absolute offset of the register array.
>>> + pub const OFFSET: usize = $offset;
>>> + /// Number of elements in the array of registers.
>>> + pub const SIZE: usize = $size;
>>> + /// Number of bytes separating each element of the array of registers.
>>> + pub const STRIDE: usize = $stride;
>>> +
>>> + /// Read the array register at index `idx` from its address in `io`.
>>> + #[inline(always)]
>>> + pub fn read<T, I>(
>>> + io: &T,
>>> + idx: usize,
>>> + ) -> Self where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + {
>>> + build_assert!(idx < Self::SIZE);
>>> +
>>> + let offset = Self::OFFSET + (idx * Self::STRIDE);
>>> +
>>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
>>> + }
>>> +
>>> + /// Write the value contained in `self` to the array register with index `idx` in `io`.
>>> + #[inline(always)]
>>> + pub fn write<T, I>(
>>> + self,
>>> + io: &T,
>>> + idx: usize
>>> + ) where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + {
>>> + build_assert!(idx < Self::SIZE);
>>> +
>>> + let offset = Self::OFFSET + (idx * Self::STRIDE);
>>> +
>>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
>>> + }
>>> +
>>> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
>>> + /// new value to write back.
>>> + ///
>>> + /// Note that this operation is not atomic. In concurrent contexts, external
>>> + /// synchronization may be required to prevent race conditions.
>>> + #[inline(always)]
>>> + pub fn update<T, I, F>(
>>> + io: &T,
>>> + idx: usize,
>>> + f: F,
>>> + ) where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + F: ::core::ops::FnOnce(Self) -> Self,
>>> + {
>>> + let reg = f(Self::read(io, idx));
>>> + reg.write(io, idx);
>>> + }
>>> +
>>> + /// Read the array register at index `idx` from its address in `io`.
>>> + ///
>>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>>> + /// access was out-of-bounds.
>>> + #[inline(always)]
>>> + pub fn try_read<T, I>(
>>> + io: &T,
>>> + idx: usize,
>>> + ) -> ::kernel::error::Result<Self> where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + {
>>> + if idx < Self::SIZE {
>>> + Ok(Self::read(io, idx))
>>> + } else {
>>> + Err(::kernel::error::code::EINVAL)
>>> + }
>>> + }
>>> +
>>> + /// Write the value contained in `self` to the array register with index `idx` in `io`.
>>> + ///
>>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>>> + /// access was out-of-bounds.
>>> + #[inline(always)]
>>> + pub fn try_write<T, I>(
>>> + self,
>>> + io: &T,
>>> + idx: usize,
>>> + ) -> ::kernel::error::Result where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + {
>>> + if idx < Self::SIZE {
>>> + Ok(self.write(io, idx))
>>> + } else {
>>> + Err(::kernel::error::code::EINVAL)
>>> + }
>>> + }
>>> +
>>> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
>>> + /// new value to write back.
>>> + ///
>>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>>> + /// access was out-of-bounds.
>>> + ///
>>> + /// Note that this operation is not atomic. In concurrent contexts, external
>>> + /// synchronization may be required to prevent race conditions.
>>> + #[inline(always)]
>>> + pub fn try_update<T, I, F>(
>>> + io: &T,
>>> + idx: usize,
>>> + f: F,
>>> + ) -> ::kernel::error::Result where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + F: ::core::ops::FnOnce(Self) -> Self,
>>> + {
>>> + if idx < Self::SIZE {
>>> + Ok(Self::update(io, idx, f))
>>> + } else {
>>> + Err(::kernel::error::code::EINVAL)
>>> + }
>>> + }
>>> + }
>>> + };
>>> +
>>> + // Generates the IO accessors for an array of relative registers.
>>> + (
>>> + @io_relative_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ]
>>> + @ $base:ident + $offset:literal
>>> + ) => {
>>> + #[allow(dead_code)]
>>> + impl $name {
>>> + /// Relative offset of the register array.
>>> + pub const OFFSET: usize = $offset;
>>> + /// Number of elements in the array of registers.
>>> + pub const SIZE: usize = $size;
>>> + /// Number of bytes separating each element of the array of registers.
>>> + pub const STRIDE: usize = $stride;
>>> +
>>> + /// Read the array register at index `idx` from `io`, using the base address provided
>>> + /// by `base` and adding the register's offset to it.
>>> + #[inline(always)]
>>> + pub fn read<T, I, B>(
>>> + io: &T,
>>> + #[allow(unused_variables)]
>>> + base: &B,
>>> + idx: usize,
>>> + ) -> Self where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + {
>>> + build_assert!(idx < Self::SIZE);
>>> +
>>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE +
>>> + Self::OFFSET + (idx * Self::STRIDE);
>>> +
>>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset))
>>> + }
>>> +
>>> + /// Write the value contained in `self` to `io`, using the base address provided by
>>> + /// `base` and adding the offset of array register `idx` to it.
>>> + #[inline(always)]
>>> + pub fn write<T, I, B>(
>>> + self,
>>> + io: &T,
>>> + #[allow(unused_variables)]
>>> + base: &B,
>>> + idx: usize
>>> + ) where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + {
>>> + build_assert!(idx < Self::SIZE);
>>> +
>>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE +
>>> + Self::OFFSET + (idx * Self::STRIDE);
>>> +
>>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset)
>>> + }
>>> +
>>> + /// Read the array register at index `idx` from `io`, using the base address provided
>>> + /// by `base` and adding the register's offset to it, then run `f` on its value to
>>> + /// obtain a new value to write back.
>>> + ///
>>> + /// Note that this operation is not atomic. In concurrent contexts, external
>>> + /// synchronization may be required to prevent race conditions.
>>> + #[inline(always)]
>>> + pub fn update<T, I, B, F>(
>>> + io: &T,
>>> + base: &B,
>>> + idx: usize,
>>> + f: F,
>>> + ) where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + F: ::core::ops::FnOnce(Self) -> Self,
>>> + {
>>> + let reg = f(Self::read(io, base, idx));
>>> + reg.write(io, base, idx);
>>> + }
>>> +
>>> + /// Read the array register at index `idx` from `io`, using the base address provided
>>> + /// by `base` and adding the register's offset to it.
>>> + ///
>>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>>> + /// access was out-of-bounds.
>>> + #[inline(always)]
>>> + pub fn try_read<T, I, B>(
>>> + io: &T,
>>> + base: &B,
>>> + idx: usize,
>>> + ) -> ::kernel::error::Result<Self> where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + {
>>> + if idx < Self::SIZE {
>>> + Ok(Self::read(io, base, idx))
>>> + } else {
>>> + Err(::kernel::error::code::EINVAL)
>>> + }
>>> + }
>>> +
>>> + /// Write the value contained in `self` to `io`, using the base address provided by
>>> + /// `base` and adding the offset of array register `idx` to it.
>>> + ///
>>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>>> + /// access was out-of-bounds.
>>> + #[inline(always)]
>>> + pub fn try_write<T, I, B>(
>>> + self,
>>> + io: &T,
>>> + base: &B,
>>> + idx: usize,
>>> + ) -> ::kernel::error::Result where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + {
>>> + if idx < Self::SIZE {
>>> + Ok(self.write(io, base, idx))
>>> + } else {
>>> + Err(::kernel::error::code::EINVAL)
>>> + }
>>> + }
>>> +
>>> + /// Read the array register at index `idx` from `io`, using the base address provided
>>> + /// by `base` and adding the register's offset to it, then run `f` on its value to
>>> + /// obtain a new value to write back.
>>> + ///
>>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the
>>> + /// access was out-of-bounds.
>>> + ///
>>> + /// Note that this operation is not atomic. In concurrent contexts, external
>>> + /// synchronization may be required to prevent race conditions.
>>> + #[inline(always)]
>>> + pub fn try_update<T, I, B, F>(
>>> + io: &T,
>>> + base: &B,
>>> + idx: usize,
>>> + f: F,
>>> + ) -> ::kernel::error::Result where
>>> + T: ::core::ops::Deref<Target = I>,
>>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>,
>>> + B: $crate::io::register::RegisterBase<$base>,
>>> + F: ::core::ops::FnOnce(Self) -> Self,
>>> + {
>>> + if idx < Self::SIZE {
>>> + Ok(Self::update(io, base, idx, f))
>>> + } else {
>>> + Err(::kernel::error::code::EINVAL)
>>> + }
>>> + }
>>> + }
>>> + };
>>> +
>>> + // Defines the wrapper `$name` type and its conversions from/to the storage type.
>>> + (@bitfield_core $(#[$attr:meta])* $vis:vis $name:ident $storage:ty) => {
>>> + $(#[$attr])*
>>> + #[repr(transparent)]
>>> + #[derive(Clone, Copy, PartialEq, Eq)]
>>
>> You can derive `Zeroable` and save you an unsafe impl.
>
> Unfortunately this happens if I try to do it:
>
> error: no rules expected `(`
> --> ../samples/rust/rust_driver_pci.rs:74:9
> |
> 74 | / ::kernel::register! {
> 75 | | VENDOR_ID(u16) @ 0x0 {
> 76 | | 15:0 vendor_id;
> ... |
> 86 | | }
> | |_________^ no rules expected this token in macro call
>
> Not quite sure why to be honest.
I'll look into this.
>
>>
>>> + $vis struct $name($storage);
>>> +
>>> + #[allow(dead_code)]
>>> + impl $name {
>>> + /// Creates a bitfield from a raw value.
>>> + $vis const fn from_raw(value: $storage) -> Self {
>>> + Self(value)
>>> + }
>>> +
>>> + /// Creates a zeroed bitfield value.
>>> + ///
>>> + /// This is a const alternative to the `Zeroable::zeroed()` trait method.
>>> + $vis const fn zeroed() -> Self {
>>> + Self(0)
>>> + }
>>
>> All types that impl `Zeroable` automatically have the `::zeroed()` fn provided
>> via the trait.
>
> Yes, but that method from the trait cannot be used in const context, and
> `zeroed` is the starting point for building register values from scratch (and
> thus constant values).
`pin_init:::zeroed()` is a const function.
Best,
Gary
>
>>
>>> +
>>> + /// Returns the raw value of this bitfield.
>>> + ///
>>> + /// This is similar to the [`From`] implementation, but is shorter to invoke in
>>> + /// most cases.
>>> + $vis const fn as_raw(self) -> $storage {
>>> + self.0
>>> + }
>>> + }
>>> +
>>> + // SAFETY: `$storage` is `Zeroable` and `$name` is transparent.
>>> + unsafe impl ::pin_init::Zeroable for $name {}
>>> +
>>> + impl ::core::convert::From<$name> for $storage {
>>> + fn from(val: $name) -> $storage {
>>> + val.as_raw()
>>> + }
>>> + }
>>> +
>>> + impl ::core::convert::From<$storage> for $name {
>>> + fn from(val: $storage) -> $name {
>>> + Self::from_raw(val)
>>> + }
>>> + }
>>> + };
>>> +
>>> + // Definitions requiring knowledge of individual fields: private and public field accessors,
>>> + // and `Debug` implementation.
>>> + (@bitfield_fields $vis:vis $name:ident $storage:ty {
>>> + $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident
>>> + $(?=> $try_into_type:ty)?
>>> + $(=> $into_type:ty)?
>>> + ;
>>> + )*
>>> + }
>>> + ) => {
>>> + #[allow(dead_code)]
>>> + impl $name {
>>> + $(
>>> + ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field);
>>> + ::kernel::register!(
>>> + @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field
>>> + $(?=> $try_into_type)?
>>> + $(=> $into_type)?
>>> + );
>>> + )*
>>> + }
>>> +
>>> + ::kernel::register!(@debug $name { $($field;)* });
>>> + };
>>> +
>>> + // Private field accessors working with the correct `Bounded` type for the field.
>>> + (
>>> + @private_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident
>>> + ) => {
>>> + ::kernel::macros::paste!(
>>> + $vis const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi;
>>
>> Is this used by anything?
>
> Not yet. This is intended for user code that wants to know which bits are used
> by a specific field. I don't mind removing it though.
>
> Thanks for the review and ideas!
On Thu Jan 29, 2026 at 11:10 PM JST, Gary Guo wrote:
> On Thu Jan 29, 2026 at 8:00 AM GMT, Alexandre Courbot wrote:
>> On Thu Jan 29, 2026 at 1:16 AM JST, Gary Guo wrote:
>>> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote:
>>>> Add a macro for defining hardware register types with I/O accessors.
>>>>
>>>> Each register field is represented as a `Bounded` of the appropriate bit
>>>> width, ensuring field values are never silently truncated.
>>>>
>>>> Fields can optionally be converted to/from custom types, either fallibly
>>>> or infallibly.
>>>>
>>>> The address of registers can be direct, relative, or indexed, supporting
>>>> most of the patterns in which registers are arranged.
>>>>
>>>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
>>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>>>> ---
>>>> rust/kernel/io.rs | 1 +
>>>> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++
>>>> 2 files changed, 1288 insertions(+)
>>>>
>>>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>>>> index 056a3ec71647..112f43ecbf88 100644
>>>> --- a/rust/kernel/io.rs
>>>> +++ b/rust/kernel/io.rs
>>>> @@ -11,6 +11,7 @@
>>>>
>>>> pub mod mem;
>>>> pub mod poll;
>>>> +pub mod register;
>>>> pub mod resource;
>>>>
>>>> pub use resource::Resource;
>>>> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
>>>> new file mode 100644
>>>> index 000000000000..fc85dcd1f09a
>>>> --- /dev/null
>>>> +++ b/rust/kernel/io/register.rs
>>>> @@ -0,0 +1,1287 @@
>>>> +// SPDX-License-Identifier: GPL-2.0
>>>> +
>>>> +//! A macro to define register layout and accessors.
>>>> +//!
>>>> +//! A single register typically includes several fields, which are accessed through a combination
>>>> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
>>>> +//! not all possible field values are necessarily valid.
>>>> +//!
>>>> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a
>>>> +//! dedicated type for each register. Each such type comes with its own field accessors that can
>>>> +//! return an error if a field's value is invalid.
>>>> +//!
>>>> +//! [`register!`]: kernel::register!
>>>> +
>>>> +use core::ops::Deref;
>>>> +
>>>> +use crate::io::{
>>>> + IoCapable,
>>>> + IoKnownSize, //
>>>> +};
>>>> +
>>>> +/// Trait providing a base address to be added to the offset of a relative register to obtain
>>>> +/// its actual offset.
>>>> +///
>>>> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides
>>>> +/// several bases. It is given to the `register!` macro to restrict the use of the register to
>>>> +/// implementors of this particular variant.
>>>> +pub trait RegisterBase<T> {
>>>> + /// Base address to which register offsets are added.
>>>> + const BASE: usize;
>>>> +}
>>>> +
>>>> +/// Trait providing I/O read/write operations for register storage types.
>>>> +///
>>>> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the
>>>> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage
>>>> +/// type.
>>>> +pub trait RegisterIo: Sized {
>>>
>>> Is this trait intended for public usage or just internal detail of `register!()`
>>> macro?
>>>
>>> If it's the former, then we should probably just put the method into `IoCapable`
>>> and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so
>>> it won't get abused.
>>
>> It is an internal detail of `register!()` and not supposed to be used by anyone
>> else.
>>
>>>
>>>> + /// Read a value from the given offset in the I/O region.
>>>> + fn read<T, I>(io: &T, offset: usize) -> Self
>>>> + where
>>>> + T: Deref<Target = I>,
>>>> + I: IoKnownSize + IoCapable<Self>;
>>>
>>> I think generally `Deref` bound shouldn't be exposed to user. What smart
>>> pointers are involved here, and can we implement forwarding impls of `Io`?
>>
>> Removing the requirement for `Deref` in the `RegisterIo` trait is simple - we
>> can just call `Deref` in the register IO accessors.
>
> The issue with `Deref` bound here is that now you *require* a level of
> indirection. If something implements `Io` directly, you cannot use it with the
> method. While `&Bar` is accepted, `&Mmio` is not because `Mmio` is a direct
> implementor of `Io` and not deref to it.
>
> A `Deref` bound also does not help if, say, a type is `Arc<Bar>` which needs two
> level of dereffing before it is Io. For consistency I think it's best to avoid
> `Deref` call all together
Do you mean that `Bar` should implement `Io`, `IoKnownSize`, and all the
required `IoCapable`s? That's a lot of boilerplate I'm afraid, and that
would need to be repeated for all other I/O proxy types.
No, I'm starting to believe that the fundamental issue is that the
register interface does its I/O backwards, and that design issue is only
exacerbated by the recent I/O redesign. I.e. instead of doing
regs::NV_PMC_BOOT_0::read(bar);
We should really do
bar.read_reg::<regs::NV_PMC_BOOT_0>();
Because that way we can use deref coercion.
That's quite a big redesign though, which means I don't believe
`register!` can make it this cycle... I'll give it a try though.
<snip>
>>>> + $vis struct $name($storage);
>>>> +
>>>> + #[allow(dead_code)]
>>>> + impl $name {
>>>> + /// Creates a bitfield from a raw value.
>>>> + $vis const fn from_raw(value: $storage) -> Self {
>>>> + Self(value)
>>>> + }
>>>> +
>>>> + /// Creates a zeroed bitfield value.
>>>> + ///
>>>> + /// This is a const alternative to the `Zeroable::zeroed()` trait method.
>>>> + $vis const fn zeroed() -> Self {
>>>> + Self(0)
>>>> + }
>>>
>>> All types that impl `Zeroable` automatically have the `::zeroed()` fn provided
>>> via the trait.
>>
>> Yes, but that method from the trait cannot be used in const context, and
>> `zeroed` is the starting point for building register values from scratch (and
>> thus constant values).
>
> `pin_init:::zeroed()` is a const function.
Ah, I thought about using `Zeroable::zeroed()` at first, but indeed we
can use the function instead.
Strangely though, I expected to be able to do
const V: BOOT_0 = pin_init::zeroed()
and have the compiler infer the generic type, but for some reason I have
to specify it explicitly:
const V: BOOT_0 = pin_init::zeroed::<BOOT_0>()
Not a big deal, but a const method would reduce this to
const V: BOOT_0 = BOOT_0::zeroed()
(for variables `let v: BOOT_0 = pin_init::zeroed()` works fine.)
On Fri Jan 30, 2026 at 6:55 AM GMT, Alexandre Courbot wrote:
> On Thu Jan 29, 2026 at 11:10 PM JST, Gary Guo wrote:
>> On Thu Jan 29, 2026 at 8:00 AM GMT, Alexandre Courbot wrote:
>>> On Thu Jan 29, 2026 at 1:16 AM JST, Gary Guo wrote:
>>>> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote:
>>>>> Add a macro for defining hardware register types with I/O accessors.
>>>>>
>>>>> Each register field is represented as a `Bounded` of the appropriate bit
>>>>> width, ensuring field values are never silently truncated.
>>>>>
>>>>> Fields can optionally be converted to/from custom types, either fallibly
>>>>> or infallibly.
>>>>>
>>>>> The address of registers can be direct, relative, or indexed, supporting
>>>>> most of the patterns in which registers are arranged.
>>>>>
>>>>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
>>>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>>>>> ---
>>>>> rust/kernel/io.rs | 1 +
>>>>> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++
>>>>> 2 files changed, 1288 insertions(+)
>>>>>
>>>>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>>>>> index 056a3ec71647..112f43ecbf88 100644
>>>>> --- a/rust/kernel/io.rs
>>>>> +++ b/rust/kernel/io.rs
>>>>> @@ -11,6 +11,7 @@
>>>>>
>>>>> pub mod mem;
>>>>> pub mod poll;
>>>>> +pub mod register;
>>>>> pub mod resource;
>>>>>
>>>>> pub use resource::Resource;
>>>>> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
>>>>> new file mode 100644
>>>>> index 000000000000..fc85dcd1f09a
>>>>> --- /dev/null
>>>>> +++ b/rust/kernel/io/register.rs
>>>>> @@ -0,0 +1,1287 @@
>>>>> +// SPDX-License-Identifier: GPL-2.0
>>>>> +
>>>>> +//! A macro to define register layout and accessors.
>>>>> +//!
>>>>> +//! A single register typically includes several fields, which are accessed through a combination
>>>>> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
>>>>> +//! not all possible field values are necessarily valid.
>>>>> +//!
>>>>> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a
>>>>> +//! dedicated type for each register. Each such type comes with its own field accessors that can
>>>>> +//! return an error if a field's value is invalid.
>>>>> +//!
>>>>> +//! [`register!`]: kernel::register!
>>>>> +
>>>>> +use core::ops::Deref;
>>>>> +
>>>>> +use crate::io::{
>>>>> + IoCapable,
>>>>> + IoKnownSize, //
>>>>> +};
>>>>> +
>>>>> +/// Trait providing a base address to be added to the offset of a relative register to obtain
>>>>> +/// its actual offset.
>>>>> +///
>>>>> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides
>>>>> +/// several bases. It is given to the `register!` macro to restrict the use of the register to
>>>>> +/// implementors of this particular variant.
>>>>> +pub trait RegisterBase<T> {
>>>>> + /// Base address to which register offsets are added.
>>>>> + const BASE: usize;
>>>>> +}
>>>>> +
>>>>> +/// Trait providing I/O read/write operations for register storage types.
>>>>> +///
>>>>> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the
>>>>> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage
>>>>> +/// type.
>>>>> +pub trait RegisterIo: Sized {
>>>>
>>>> Is this trait intended for public usage or just internal detail of `register!()`
>>>> macro?
>>>>
>>>> If it's the former, then we should probably just put the method into `IoCapable`
>>>> and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so
>>>> it won't get abused.
>>>
>>> It is an internal detail of `register!()` and not supposed to be used by anyone
>>> else.
>>>
>>>>
>>>>> + /// Read a value from the given offset in the I/O region.
>>>>> + fn read<T, I>(io: &T, offset: usize) -> Self
>>>>> + where
>>>>> + T: Deref<Target = I>,
>>>>> + I: IoKnownSize + IoCapable<Self>;
>>>>
>>>> I think generally `Deref` bound shouldn't be exposed to user. What smart
>>>> pointers are involved here, and can we implement forwarding impls of `Io`?
>>>
>>> Removing the requirement for `Deref` in the `RegisterIo` trait is simple - we
>>> can just call `Deref` in the register IO accessors.
>>
>> The issue with `Deref` bound here is that now you *require* a level of
>> indirection. If something implements `Io` directly, you cannot use it with the
>> method. While `&Bar` is accepted, `&Mmio` is not because `Mmio` is a direct
>> implementor of `Io` and not deref to it.
>>
>> A `Deref` bound also does not help if, say, a type is `Arc<Bar>` which needs two
>> level of dereffing before it is Io. For consistency I think it's best to avoid
>> `Deref` call all together
>
> Do you mean that `Bar` should implement `Io`, `IoKnownSize`, and all the
> required `IoCapable`s? That's a lot of boilerplate I'm afraid, and that
> would need to be repeated for all other I/O proxy types.
>
> No, I'm starting to believe that the fundamental issue is that the
> register interface does its I/O backwards, and that design issue is only
> exacerbated by the recent I/O redesign. I.e. instead of doing
>
> regs::NV_PMC_BOOT_0::read(bar);
>
> We should really do
>
> bar.read_reg::<regs::NV_PMC_BOOT_0>();
>
> Because that way we can use deref coercion.
>
> That's quite a big redesign though, which means I don't believe
> `register!` can make it this cycle... I'll give it a try though.
Hmm, that's unfortunate, but I think this is indeed a big design change that we
should iron out before merging...
I think you're right that if we put the methods on `Io` then all of the deref
issue would just went away.
BTW, there's a tricky that would allow you to avoid turbofish in this case, i.e.
you would be able to write
bar.read_reg(regs::NV_PMC_BOOT_0)
Or
bar.write_reg(regs::PV_PMC_BOOT_0, ...)
The trick is to note that the same name can present in both type and value
namesapce. For example, when you define
struct Foo;
you create `Foo` type in type namespace and a `const Foo: Foo` in value
namespace. Similarly
struct Foo(u32);
creates a `Foo` in type namespace and a `fn(u32) -> Foo` in value namespace.
This process doesn't need to be automatic. So if you create
struct Foo { value: u32 }
then you're only creating `Foo` in type namespace and not in value namespace.
This means that you could do something like:
// Use PhantomData as example, could be other token/marker types.
const Foo: PhantomData<Foo> = PhantomData;
trait Io {
fn read_reg<Reg>(&self, _: PhantomData<Reg>) -> Reg { ... }
}
// Now no turbofish!
bar.read_reg(NV_PMC_BOOT_0)
This trick is heavily used in `syn`, so you do `input.peek(type)` instead of
`input.peek::<Type>()`, which I find quite handy.
>
> <snip>
>>>>> + $vis struct $name($storage);
>>>>> +
>>>>> + #[allow(dead_code)]
>>>>> + impl $name {
>>>>> + /// Creates a bitfield from a raw value.
>>>>> + $vis const fn from_raw(value: $storage) -> Self {
>>>>> + Self(value)
>>>>> + }
>>>>> +
>>>>> + /// Creates a zeroed bitfield value.
>>>>> + ///
>>>>> + /// This is a const alternative to the `Zeroable::zeroed()` trait method.
>>>>> + $vis const fn zeroed() -> Self {
>>>>> + Self(0)
>>>>> + }
>>>>
>>>> All types that impl `Zeroable` automatically have the `::zeroed()` fn provided
>>>> via the trait.
>>>
>>> Yes, but that method from the trait cannot be used in const context, and
>>> `zeroed` is the starting point for building register values from scratch (and
>>> thus constant values).
>>
>> `pin_init:::zeroed()` is a const function.
>
> Ah, I thought about using `Zeroable::zeroed()` at first, but indeed we
> can use the function instead.
>
> Strangely though, I expected to be able to do
>
> const V: BOOT_0 = pin_init::zeroed()
>
> and have the compiler infer the generic type, but for some reason I have
> to specify it explicitly:
>
> const V: BOOT_0 = pin_init::zeroed::<BOOT_0>()
>
> Not a big deal, but a const method would reduce this to
>
> const V: BOOT_0 = BOOT_0::zeroed()
>
> (for variables `let v: BOOT_0 = pin_init::zeroed()` works fine.)
That's strange. Do you have a MCVE for this? I can't reproduce what you
described.
Best,
Gary
On Fri Jan 30, 2026 at 5:14 PM CET, Gary Guo wrote:
> On Fri Jan 30, 2026 at 6:55 AM GMT, Alexandre Courbot wrote:
>> No, I'm starting to believe that the fundamental issue is that the
>> register interface does its I/O backwards, and that design issue is only
>> exacerbated by the recent I/O redesign. I.e. instead of doing
>>
>> regs::NV_PMC_BOOT_0::read(bar);
>>
>> We should really do
>>
>> bar.read_reg::<regs::NV_PMC_BOOT_0>();
>>
>> Because that way we can use deref coercion.
>>
>> That's quite a big redesign though, which means I don't believe
>> `register!` can make it this cycle... I'll give it a try though.
>
> Hmm, that's unfortunate, but I think this is indeed a big design change that we
> should iron out before merging...
>
> I think you're right that if we put the methods on `Io` then all of the deref
> issue would just went away.
I already discussed this with Alex offline and I think we should not take this
direction just because of the Deref issue, as we can easily overcome this with
using AsRef (or a custom trait).
Whereas the downside of bar.read_reg() is that you end up with a more
inconsistent and complicated API for drivers.
For instance, with the API as is you can do things like:
register!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] {
0:0 reset as bool;
});
impl NV_PFALCON_FALCON_ENGINE {
pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) {
Self::update(bar, &E::ID, |r| r.set_reset(true));
// TIMEOUT: falcon engine should not take more than 10us to reset.
time::delay::fsleep(time::Delta::from_micros(10));
Self::update(bar, &E::ID, |r| r.set_reset(false));
}
}
and then use this from the driver code like this:
impl<E: FalconEngine> FalconHal<E> for Tu102<E> {
fn do_stuff(&self, bar: &Bar0) {
//...
regs::NV_PFALCON_FALCON_ENGINE::reset_engine::<E>(bar);
//...
}
}
Having to implement AsRef (or a custom trait) for the corresponding I/O backend
implementations is a pretty minor inconvinience compared to the simplicity the
current API provides to drivers.
On Fri Jan 30, 2026 at 4:44 PM GMT, Danilo Krummrich wrote:
> On Fri Jan 30, 2026 at 5:14 PM CET, Gary Guo wrote:
>> On Fri Jan 30, 2026 at 6:55 AM GMT, Alexandre Courbot wrote:
>>> No, I'm starting to believe that the fundamental issue is that the
>>> register interface does its I/O backwards, and that design issue is only
>>> exacerbated by the recent I/O redesign. I.e. instead of doing
>>>
>>> regs::NV_PMC_BOOT_0::read(bar);
>>>
>>> We should really do
>>>
>>> bar.read_reg::<regs::NV_PMC_BOOT_0>();
>>>
>>> Because that way we can use deref coercion.
>>>
>>> That's quite a big redesign though, which means I don't believe
>>> `register!` can make it this cycle... I'll give it a try though.
>>
>> Hmm, that's unfortunate, but I think this is indeed a big design change that we
>> should iron out before merging...
>>
>> I think you're right that if we put the methods on `Io` then all of the deref
>> issue would just went away.
>
> I already discussed this with Alex offline and I think we should not take this
> direction just because of the Deref issue, as we can easily overcome this with
> using AsRef (or a custom trait).
`AsRef` won't work for `Io` which is a trait, unlike `Device` which is a type. A
custom trait might work but that's more complex than what Alex purposed.
I'm quite fond of the `io.read_reg(REGISTER_REF)` API suggested. It looks quite
uniform with other I/O acceessor methods, except typed and with constant
offsets.
>
> Whereas the downside of bar.read_reg() is that you end up with a more
> inconsistent and complicated API for drivers.
>
> For instance, with the API as is you can do things like:
>
> register!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] {
> 0:0 reset as bool;
> });
>
> impl NV_PFALCON_FALCON_ENGINE {
> pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) {
> Self::update(bar, &E::ID, |r| r.set_reset(true));
>
> // TIMEOUT: falcon engine should not take more than 10us to reset.
> time::delay::fsleep(time::Delta::from_micros(10));
>
> Self::update(bar, &E::ID, |r| r.set_reset(false));
> }
> }
The only benefit of this compared to the newly proposed API is that you can use
`Self`. It's not too bad to have (hypothetical API)
pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) {
bar.update_reg(NV_PFALCON_FALCON_ENGINE(&E::ID), |r| r.set_reset(true));
// TIMEOUT: falcon engine should not take more than 10us to reset.
time::delay::fsleep(time::Delta::from_micros(10));
bar.update_reg(NV_PFALCON_FALCON_ENGINE(&E::ID), |r| r.set_reset(true));
}
I also think in most cases these helper function won't be implemented on the
register, so you're not saving much by having the ability to refer to `Self`.
For example, in this case you could implement it on `FalconEngine`.
Best,
Gary
>
> and then use this from the driver code like this:
>
> impl<E: FalconEngine> FalconHal<E> for Tu102<E> {
> fn do_stuff(&self, bar: &Bar0) {
> //...
>
> regs::NV_PFALCON_FALCON_ENGINE::reset_engine::<E>(bar);
>
> //...
> }
> }
>
> Having to implement AsRef (or a custom trait) for the corresponding I/O backend
> implementations is a pretty minor inconvinience compared to the simplicity the
> current API provides to drivers.
On Fri Jan 30, 2026 at 6:01 PM CET, Gary Guo wrote: > `AsRef` won't work for `Io` which is a trait, unlike `Device` which is a type. A > custom trait might work but that's more complex than what Alex purposed. Are we sure about that? For instance, how do we determine the size of the I/O operation in *_reg()? How can we trust that the type passed to *_reg() is valid for an arbitrary bit pattern? I mean, I'm not saying it's possible, but it seems that we end up with a couple of safety requirements for the base register traits. > I'm quite fond of the `io.read_reg(REGISTER_REF)` API suggested. It looks quite > uniform with other I/O acceessor methods, except typed and with constant > offsets. It's just a nit, but I find it a bit icky that we have to have something like the "_reg" suffix.
On Thu Jan 29, 2026 at 3:10 PM CET, Gary Guo wrote: > The issue with `Deref` bound here is that now you *require* a level of > indirection. If something implements `Io` directly, you cannot use it with the > method. While `&Bar` is accepted, `&Mmio` is not because `Mmio` is a direct > implementor of `Io` and not deref to it. > > A `Deref` bound also does not help if, say, a type is `Arc<Bar>` which needs two > level of dereffing before it is Io. For consistency I think it's best to avoid > `Deref` call all together I agree with that and we will probably run into this once we start implementing the I/O traits for things like DMA memory. We could use AsRef or a custom trait instead, but I'd rather leave this for a subsequent series, as it requires more changes. They should also be transparent to users.
On 1/27/26 6:37 PM, Alexandre Courbot wrote: > Add a macro for defining hardware register types with I/O accessors. ... > +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their > +/// getter method, which is named after them. They also have setter methods prefixed with `set_` > +/// for runtime values and `with_` for constant values. All setters return the updated register OK, this still looks like a naming problem that we don't need to create. Let's just pick either "set_" or "with_" as a prefix (it seems that "with_" has won out, in the v3 discussion thread), and then add "const", so that the name doesn't require explanation. ... > +/// // Update some fields and write the new value back. > +/// boot0 > +/// // Constant values. > +/// .with_major_revision::<3>() > +/// .with_minor_revision::<10>() > +/// // Run-time value. > +/// .set_vendor_id(obtain_vendor_id()) See, that is just not obvious at all, why it's different. That's why you had to write a couple of comments. .with_const_major_revision(), on the other hand, keeps the pattern clear and obvious. No comments necessary. nova-core doesn't even use the const_ settors yet at all, according to my quick read of the (very helpful) [PATCH FOR REFERENCE v4 7/7]. So clearly it is a bit rare, and the extra characters won't come into play in daily life. thanks, -- John Hubbard
On Jan 27, 2026, at 10:02 PM, John Hubbard <jhubbard@nvidia.com> wrote:
> On 1/27/26 6:37 PM, Alexandre Courbot wrote:
>> Add a macro for defining hardware register types with I/O accessors.
> ...
>> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their
>> +/// getter method, which is named after them. They also have setter methods prefixed with `set_`
>> +/// for runtime values and `with_` for constant values. All setters return the updated register
> OK, this still looks like a naming problem that we don't need to create.
> Let's just pick either "set_" or "with_" as a prefix (it seems that
> "with_" has won out, in the v3 discussion thread), and then add "const",
> so that the name doesn't require explanation.
>
> ...
>> +/// // Update some fields and write the new value back.
>> +/// boot0
>> +/// // Constant values.
>> +/// .with_major_revision::<3>()
>> +/// .with_minor_revision::<10>()
>> +/// // Run-time value.
>> +/// .set_vendor_id(obtain_vendor_id())
>
> See, that is just not obvious at all, why it's different. That's why
> you had to write a couple of comments.
>
> .with_const_major_revision(), on the other hand, keeps the pattern
> clear and obvious. No comments necessary.
>
> nova-core doesn't even use the const_ settors yet at all, according
> to my quick read of the (very helpful) [PATCH FOR REFERENCE v4 7/7].
> So clearly it is a bit rare, and the extra characters won't come into
> play in daily life.
I completely agree with John here, let us please not create a confusing API.
I would much rather use a bounded! macro to create bounded values and pass
those to a single unified setter, making it a unified API for both constants
and runtime values. Let us not use const generics just for the sake of it or
create special const APIs that no one but examples use!!!
For example, instead of having separate set_/with_ prefixes, we could have:
boot0
.with_major_revision(bounded!(u8, 3))
.with_minor_revision(bounded!(u8, 10))
.with_vendor_id(obtain_vendor_id())
This keeps the syntax clean while still providing compile-time bounds
checking for constants. A single unified setter API is way cleaner and less
confusing than having set_ for runtime and with_<N>() for compile-time
values.
Is there some reason why this will not work?
--
Joel Fernandes
On Wed Jan 28, 2026 at 12:47 PM JST, Joel Fernandes wrote: > On Jan 27, 2026, at 10:02 PM, John Hubbard <jhubbard@nvidia.com> wrote: >> On 1/27/26 6:37 PM, Alexandre Courbot wrote: >>> Add a macro for defining hardware register types with I/O accessors. >> ... >>> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their >>> +/// getter method, which is named after them. They also have setter methods prefixed with `set_` >>> +/// for runtime values and `with_` for constant values. All setters return the updated register >> OK, this still looks like a naming problem that we don't need to create. >> Let's just pick either "set_" or "with_" as a prefix (it seems that >> "with_" has won out, in the v3 discussion thread), and then add "const", >> so that the name doesn't require explanation. >> >> ... >>> +/// // Update some fields and write the new value back. >>> +/// boot0 >>> +/// // Constant values. >>> +/// .with_major_revision::<3>() >>> +/// .with_minor_revision::<10>() >>> +/// // Run-time value. >>> +/// .set_vendor_id(obtain_vendor_id()) >> >> See, that is just not obvious at all, why it's different. That's why >> you had to write a couple of comments. >> >> .with_const_major_revision(), on the other hand, keeps the pattern >> clear and obvious. No comments necessary. >> >> nova-core doesn't even use the const_ settors yet at all, according >> to my quick read of the (very helpful) [PATCH FOR REFERENCE v4 7/7]. >> So clearly it is a bit rare, and the extra characters won't come into >> play in daily life. Great, if everyone agrees on with/with_const then this is definitely idiomatic and clear (and not too verbose). I'll respin tomorrow unless there is new input. > > I completely agree with John here, let us please not create a confusing API. > I would much rather use a bounded! macro to create bounded values and pass > those to a single unified setter, making it a unified API for both constants > and runtime values. Let us not use const generics just for the sake of it or > create special const APIs that no one but examples use!!! > > For example, instead of having separate set_/with_ prefixes, we could have: > > boot0 > .with_major_revision(bounded!(u8, 3)) > .with_minor_revision(bounded!(u8, 10)) > .with_vendor_id(obtain_vendor_id()) > > This keeps the syntax clean while still providing compile-time bounds > checking for constants. A single unified setter API is way cleaner and less > confusing than having set_ for runtime and with_<N>() for compile-time > values. > > Is there some reason why this will not work? So these setters are really part of the `bitfield!` API. Bitfields are, at the end of the day, just integers which we will very likely want to declare constants of out of individual const field values. This can only be done by using const methods, and a const `Bounded` needs to validate its constraints at build time, which requires the `Bounded::new::<VALUE>()` constructor, and by transition similarly-shaped setters to set fields at const time.
On Wed, 28 Jan 2026, Alexandre Courbot wrote:
> So these setters are really part of the `bitfield!` API. Bitfields are,
> at the end of the day, just integers which we will very likely want to
> declare constants of out of individual const field values. This can only
> be done by using const methods, and a const `Bounded` needs to validate
> its constraints at build time, which requires the
> `Bounded::new::<VALUE>()` constructor, and by transition
> similarly-shaped setters to set fields at const time.
I think you run into that issue because the setter requires to do `.into()`
to do the bounded conversion.
If we accept Bounded type in the setter directly instead, it
should allow to keep the API simple. The advantage is it keeps the API naming
simple. with_<field>() which accepts Bounded type, that's the contract. The
trade off is the call-site can be a bit more icky but I wonder how much of an
issue that really is (it really depends on usage).
I wrote a simple program to confirm the same function will work for both const
and non-const parameters. Actually the caller does not even need to specific
the bounded int size at the call site, it gets inferred. So we don't even need
a bounded! macro.
struct Bounded<const BITS: u32>(u32);
impl<const BITS: u32> Bounded<BITS> {
pub const fn new<const VAL: u32>() -> Self { Self(VAL) }
pub fn try_new(val: u32) -> Result<Self, Error> { Ok(Self(val)) }
}
struct Reg(u32);
impl Reg {
pub const fn with_foo(self, b: Bounded<4>) -> Self { Self(b.0) }
}
fn bar() -> Result {
const C: Reg = Reg(0).with_foo(Bounded::new::<7>()); // const
let r = Reg(0).with_foo(Bounded::try_new(9)?); // runtime
println!("{} {}", C.0, r.0);
Ok(())
}
This is clean IMO. Additionally, bounded! and bounded_expr! could be provided
to build the bounded types.
Thoughts?
--
Joel Fernandes
On Thu Jan 29, 2026 at 2:56 AM JST, Joel Fernandes wrote:
> On Wed, 28 Jan 2026, Alexandre Courbot wrote:
>> So these setters are really part of the `bitfield!` API. Bitfields are,
>> at the end of the day, just integers which we will very likely want to
>> declare constants of out of individual const field values. This can only
>> be done by using const methods, and a const `Bounded` needs to validate
>> its constraints at build time, which requires the
>> `Bounded::new::<VALUE>()` constructor, and by transition
>> similarly-shaped setters to set fields at const time.
>
> I think you run into that issue because the setter requires to do `.into()`
> to do the bounded conversion.
Precisely. Without that conversion, the public field setter could just
forward to the private one, which is const.
>
> If we accept Bounded type in the setter directly instead, it
> should allow to keep the API simple. The advantage is it keeps the API naming
> simple. with_<field>() which accepts Bounded type, that's the contract. The
> trade off is the call-site can be a bit more icky but I wonder how much of an
> issue that really is (it really depends on usage).
I have tried converting the current macro to this idea, and this results
in quite a few `.into()` calls. Notably, I find the following a bit (pun
intended) annoying:
r.with_start(true.into())
`start` is a 1-bit bitfield, so it should accept a boolean, but here we
cannot pass it directly.
So I am not sure that I prefer this over the current generic argument.
In any case, this exercice has revealed that even if we adopt this idea,
the issue won't be entirely solved because of the `=>` and `?=>` field
conversions.
For such fields, performing an `into()` or `try_into()` is mandatory, as
that's how the argument is converted to the Bounded internally. So
pursuing const-purity at the moment is futile anyway, at least until we
get const trait methods.
Which leaves the original problem: how do we avoid invoking the awkward
`Bounded` constructor for const field values. And AFAICT, taking the
`Bounded` directly still requires us to build it, which requires us to
specify the backing type explicitly as that's the only way for the
compiler to know which constructor to call. So in terms of API
convenience I guess the `with_`/`with_const_` combo remains the best bet
for now.
>
> I wrote a simple program to confirm the same function will work for both const
> and non-const parameters. Actually the caller does not even need to specific
> the bounded int size at the call site, it gets inferred. So we don't even need
> a bounded! macro.
>
> struct Bounded<const BITS: u32>(u32);
For this example yes, because you have hardcoded the `Bounded` to be
32 bits, but I have tested this against the version in the kernel and it
still requires the fully-specified constructor.
© 2016 - 2026 Red Hat, Inc.