Contributors: 5
Author Tokens Token Proportion Commits Commit Proportion
Alexandre Courbot 5344 96.88% 18 78.26%
Danilo Krummrich 144 2.61% 1 4.35%
Tamir Duberstein 18 0.33% 2 8.70%
John Hubbard 7 0.13% 1 4.35%
Linus Torvalds 3 0.05% 1 4.35%
Total 5516 23


// SPDX-License-Identifier: GPL-2.0

//! `register!` 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.

/// 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(crate) trait RegisterBase<T> {
    const BASE: usize;
}

/// Defines a dedicated type for a register with an absolute offset, including getter and setter
/// methods for its fields and methods to read and write it from an `Io` region.
///
/// Example:
///
/// ```no_run
/// register!(BOOT_0 @ 0x00000100, "Basic revision information about the GPU" {
///    3:0     minor_revision as u8, "Minor revision of the chip";
///    7:4     major_revision as u8, "Major revision of the chip";
///    28:20   chipset as u32 ?=> Chipset, "Chipset model";
/// });
/// ```
///
/// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io`
/// region. It is composed of 3 fields, for instance `minor_revision` is made of the 4 least
/// significant bits of the register. Each field can be accessed and modified using accessor
/// methods:
///
/// ```no_run
/// // Read from the register's defined offset (0x100).
/// let boot0 = BOOT_0::read(&bar);
/// pr_info!("chip revision: {}.{}", boot0.major_revision(), boot0.minor_revision());
///
/// // `Chipset::try_from` is called with the value of the `chipset` field and returns an
/// // error if it is invalid.
/// let chipset = boot0.chipset()?;
///
/// // Update some fields and write the value back.
/// boot0.set_major_revision(3).set_minor_revision(10).write(&bar);
///
/// // Or, just read and update the register in a single step:
/// BOOT_0::alter(&bar, |r| r.set_major_revision(3).set_minor_revision(10));
/// ```
///
/// Fields are defined as follows:
///
/// - `as <type>` simply returns the field value casted to <type>, typically `u32`, `u16`, `u8` or
///   `bool`. Note that `bool` fields must have a range of 1 bit.
/// - `as <type> => <into_type>` calls `<into_type>`'s `From::<<type>>` implementation and returns
///   the result.
/// - `as <type> ?=> <try_into_type>` calls `<try_into_type>`'s `TryFrom::<<type>>` implementation
///   and returns the result. This is useful with fields for which not all values are valid.
///
/// The documentation strings are optional. If present, they will be added to the type's
/// definition, or the field getter and setter methods they are attached to.
///
/// It is also possible to create a alias register by using the `=> ALIAS` syntax. This is useful
/// for cases where a register's interpretation depends on the context:
///
/// ```no_run
/// register!(SCRATCH @ 0x00000200, "Scratch register" {
///    31:0     value as u32, "Raw value";
/// });
///
/// register!(SCRATCH_BOOT_STATUS => SCRATCH, "Boot status of the firmware" {
///     0:0     completed as bool, "Whether the firmware has completed booting";
/// });
/// ```
///
/// In this example, `SCRATCH_0_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
/// // Type used to identify the base.
/// pub(crate) 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;
///
/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
/// register!(CPU_CTL @ CpuCtlBase[0x10], "CPU core control" {
///     0:0     start as bool, "Start the CPU core";
/// });
///
/// // The `read`, `write` and `alter` 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::alter(bar, &CPU0, |r| r.set_start(true));
///
/// // Start `CPU1`.
/// CPU_CTL::alter(bar, &CPU1, |r| r.set_start(true));
///
/// // Aliases can also be defined for relative register.
/// register!(CPU_CTL_ALIAS => CpuCtlBase[CPU_CTL], "Alias to CPU core control" {
///     1:1     alias_start as bool, "Start the aliased CPU core";
/// });
///
/// // Start the aliased `CPU0`.
/// CPU_CTL_ALIAS::alter(bar, &CPU0, |r| r.set_alias_start(true));
/// ```
///
/// ## Arrays of registers
///
/// Some I/O areas contain consecutive values 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 define their address as `Address[Size]`, and add
/// an `idx` parameter to their `read`, `write` and `alter` methods:
///
/// ```no_run
/// # fn no_run() -> Result<(), Error> {
/// # fn get_scratch_idx() -> usize {
/// #   0x15
/// # }
/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
/// register!(SCRATCH @ 0x00000080[64], "Scratch registers" {
///     31:0    value as u32;
/// });
///
/// // 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_STATUS => SCRATCH[8], "Firmware exit status code" {
///     7:0     status as u8;
/// });
///
/// 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_INTERLEAVED_0 @ 0x000000c0[16 ; 8], "Scratch registers bank 0" {
///     31:0    value as u32;
/// });
/// register!(SCRATCH_INTERLEAVED_1 @ 0x000000c4[16 ; 8], "Scratch registers bank 1" {
///     31:0    value as u32;
/// });
/// # 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
/// # fn no_run() -> Result<(), Error> {
/// # fn get_scratch_idx() -> usize {
/// #   0x15
/// # }
/// // Type used as parameter of `RegisterBase` to specify the base.
/// pub(crate) 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 an contiguous array.
/// register!(CPU_SCRATCH @ CpuCtlBase[0x00000080[64]], "Per-CPU scratch registers" {
///     31:0    value as u32;
/// });
///
/// 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!(CPU_FIRMWARE_STATUS => CpuCtlBase[CPU_SCRATCH[8]],
///     "Per-CPU firmware exit status code" {
///     7:0     status as u8;
/// });
///
/// 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!(CPU_SCRATCH_INTERLEAVED_0 @ CpuCtlBase[0x00000d00[16 ; 8]],
///           "Scratch registers bank 0" {
///     31:0    value as u32;
/// });
/// register!(CPU_SCRATCH_INTERLEAVED_1 @ CpuCtlBase[0x00000d04[16 ; 8]],
///           "Scratch registers bank 1" {
///     31:0    value as u32;
/// });
/// # Ok(())
/// # }
/// ```
macro_rules! register {
    // Creates a register at a fixed offset of the MMIO space.
    ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => {
        register!(@core $name $(, $comment)? { $($fields)* } );
        register!(@io_fixed $name @ $offset);
    };

    // Creates an alias register of fixed offset register `alias` with its own fields.
    ($name:ident => $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => {
        register!(@core $name $(, $comment)? { $($fields)* } );
        register!(@io_fixed $name @ $alias::OFFSET);
    };

    // Creates a register at a relative offset from a base address provider.
    ($name:ident @ $base:ty [ $offset:literal ] $(, $comment:literal)? { $($fields:tt)* } ) => {
        register!(@core $name $(, $comment)? { $($fields)* } );
        register!(@io_relative $name @ $base [ $offset ]);
    };

    // Creates an alias register of relative offset register `alias` with its own fields.
    ($name:ident => $base:ty [ $alias:ident ] $(, $comment:literal)? { $($fields:tt)* }) => {
        register!(@core $name $(, $comment)? { $($fields)* } );
        register!(@io_relative $name @ $base [ $alias::OFFSET ]);
    };

    // Creates an array of registers at a fixed offset of the MMIO space.
    (
        $name:ident @ $offset:literal [ $size:expr ; $stride:expr ] $(, $comment:literal)? {
            $($fields:tt)*
        }
    ) => {
        static_assert!(::core::mem::size_of::<u32>() <= $stride);
        register!(@core $name $(, $comment)? { $($fields)* } );
        register!(@io_array $name @ $offset [ $size ; $stride ]);
    };

    // Shortcut for contiguous array of registers (stride == size of element).
    (
        $name:ident @ $offset:literal [ $size:expr ] $(, $comment:literal)? {
            $($fields:tt)*
        }
    ) => {
        register!($name @ $offset [ $size ; ::core::mem::size_of::<u32>() ] $(, $comment)? {
            $($fields)*
        } );
    };

    // Creates an array of registers at a relative offset from a base address provider.
    (
        $name:ident @ $base:ty [ $offset:literal [ $size:expr ; $stride:expr ] ]
            $(, $comment:literal)? { $($fields:tt)* }
    ) => {
        static_assert!(::core::mem::size_of::<u32>() <= $stride);
        register!(@core $name $(, $comment)? { $($fields)* } );
        register!(@io_relative_array $name @ $base [ $offset [ $size ; $stride ] ]);
    };

    // Shortcut for contiguous array of relative registers (stride == size of element).
    (
        $name:ident @ $base:ty [ $offset:literal [ $size:expr ] ] $(, $comment:literal)? {
            $($fields:tt)*
        }
    ) => {
        register!($name @ $base [ $offset [ $size ; ::core::mem::size_of::<u32>() ] ]
            $(, $comment)? { $($fields)* } );
    };

    // Creates an alias of register `idx` of relative array of registers `alias` with its own
    // fields.
    (
        $name:ident => $base:ty [ $alias:ident [ $idx:expr ] ] $(, $comment:literal)? {
            $($fields:tt)*
        }
    ) => {
        static_assert!($idx < $alias::SIZE);
        register!(@core $name $(, $comment)? { $($fields)* } );
        register!(@io_relative $name @ $base [ $alias::OFFSET + $idx * $alias::STRIDE ] );
    };

    // Creates an alias of register `idx` of array of registers `alias` with its own fields.
    // This rule belongs to the (non-relative) register arrays set, but needs to be put last
    // to avoid it being interpreted in place of the relative register array alias rule.
    ($name:ident => $alias:ident [ $idx:expr ] $(, $comment:literal)? { $($fields:tt)* }) => {
        static_assert!($idx < $alias::SIZE);
        register!(@core $name $(, $comment)? { $($fields)* } );
        register!(@io_fixed $name @ $alias::OFFSET + $idx * $alias::STRIDE );
    };

    // All rules below are helpers.

    // Defines the wrapper `$name` type, as well as its relevant implementations (`Debug`,
    // `Default`, `BitOr`, and conversion to the value type) and field accessor methods.
    (@core $name:ident $(, $comment:literal)? { $($fields:tt)* }) => {
        $(
        #[doc=$comment]
        )?
        #[repr(transparent)]
        #[derive(Clone, Copy)]
        pub(crate) struct $name(u32);

        impl ::core::ops::BitOr for $name {
            type Output = Self;

            fn bitor(self, rhs: Self) -> Self::Output {
                Self(self.0 | rhs.0)
            }
        }

        impl ::core::convert::From<$name> for u32 {
            fn from(reg: $name) -> u32 {
                reg.0
            }
        }

        register!(@fields_dispatcher $name { $($fields)* });
    };

    // Captures the fields and passes them to all the implementers that require field information.
    //
    // Used to simplify the matching rules for implementers, so they don't need to match the entire
    // complex fields rule even though they only make use of part of it.
    (@fields_dispatcher $name:ident {
        $($hi:tt:$lo:tt $field:ident as $type:tt
            $(?=> $try_into_type:ty)?
            $(=> $into_type:ty)?
            $(, $comment:literal)?
        ;
        )*
    }
    ) => {
        register!(@field_accessors $name {
            $(
                $hi:$lo $field as $type
                $(?=> $try_into_type)?
                $(=> $into_type)?
                $(, $comment)?
            ;
            )*
        });
        register!(@debug $name { $($field;)* });
        register!(@default $name { $($field;)* });
    };

    // Defines all the field getter/methods methods for `$name`.
    (
        @field_accessors $name:ident {
        $($hi:tt:$lo:tt $field:ident as $type:tt
            $(?=> $try_into_type:ty)?
            $(=> $into_type:ty)?
            $(, $comment:literal)?
        ;
        )*
        }
    ) => {
        $(
            register!(@check_field_bounds $hi:$lo $field as $type);
        )*

        #[allow(dead_code)]
        impl $name {
            $(
            register!(@field_accessor $name $hi:$lo $field as $type
                $(?=> $try_into_type)?
                $(=> $into_type)?
                $(, $comment)?
                ;
            );
            )*
        }
    };

    // Boolean fields must have `$hi == $lo`.
    (@check_field_bounds $hi:tt:$lo:tt $field:ident as bool) => {
        #[allow(clippy::eq_op)]
        const _: () = {
            ::kernel::build_assert!(
                $hi == $lo,
                concat!("boolean field `", stringify!($field), "` covers more than one bit")
            );
        };
    };

    // Non-boolean fields must have `$hi >= $lo`.
    (@check_field_bounds $hi:tt:$lo:tt $field:ident as $type:tt) => {
        #[allow(clippy::eq_op)]
        const _: () = {
            ::kernel::build_assert!(
                $hi >= $lo,
                concat!("field `", stringify!($field), "`'s MSB is smaller than its LSB")
            );
        };
    };

    // Catches fields defined as `bool` and convert them into a boolean value.
    (
        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as bool => $into_type:ty
            $(, $comment:literal)?;
    ) => {
        register!(
            @leaf_accessor $name $hi:$lo $field
            { |f| <$into_type>::from(if f != 0 { true } else { false }) }
            $into_type => $into_type $(, $comment)?;
        );
    };

    // Shortcut for fields defined as `bool` without the `=>` syntax.
    (
        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as bool $(, $comment:literal)?;
    ) => {
        register!(@field_accessor $name $hi:$lo $field as bool => bool $(, $comment)?;);
    };

    // Catches the `?=>` syntax for non-boolean fields.
    (
        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt ?=> $try_into_type:ty
            $(, $comment:literal)?;
    ) => {
        register!(@leaf_accessor $name $hi:$lo $field
            { |f| <$try_into_type>::try_from(f as $type) } $try_into_type =>
            ::core::result::Result<
                $try_into_type,
                <$try_into_type as ::core::convert::TryFrom<$type>>::Error
            >
            $(, $comment)?;);
    };

    // Catches the `=>` syntax for non-boolean fields.
    (
        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt => $into_type:ty
            $(, $comment:literal)?;
    ) => {
        register!(@leaf_accessor $name $hi:$lo $field
            { |f| <$into_type>::from(f as $type) } $into_type => $into_type $(, $comment)?;);
    };

    // Shortcut for non-boolean fields defined without the `=>` or `?=>` syntax.
    (
        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt
            $(, $comment:literal)?;
    ) => {
        register!(@field_accessor $name $hi:$lo $field as $type => $type $(, $comment)?;);
    };

    // Generates the accessor methods for a single field.
    (
        @leaf_accessor $name:ident $hi:tt:$lo:tt $field:ident
            { $process:expr } $to_type:ty => $res_type:ty $(, $comment:literal)?;
    ) => {
        ::kernel::macros::paste!(
        const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi;
        const [<$field:upper _MASK>]: u32 = ((((1 << $hi) - 1) << 1) + 1) - ((1 << $lo) - 1);
        const [<$field:upper _SHIFT>]: u32 = Self::[<$field:upper _MASK>].trailing_zeros();
        );

        $(
        #[doc="Returns the value of this field:"]
        #[doc=$comment]
        )?
        #[inline(always)]
        pub(crate) fn $field(self) -> $res_type {
            ::kernel::macros::paste!(
            const MASK: u32 = $name::[<$field:upper _MASK>];
            const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
            );
            let field = ((self.0 & MASK) >> SHIFT);

            $process(field)
        }

        ::kernel::macros::paste!(
        $(
        #[doc="Sets the value of this field:"]
        #[doc=$comment]
        )?
        #[inline(always)]
        pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self {
            const MASK: u32 = $name::[<$field:upper _MASK>];
            const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
            let value = (u32::from(value) << SHIFT) & MASK;
            self.0 = (self.0 & !MASK) | value;

            self
        }
        );
    };

    // Generates the `Debug` implementation for `$name`.
    (@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()
            }
        }
    };

    // Generates the `Default` implementation for `$name`.
    (@default $name:ident { $($field:ident;)* }) => {
        /// Returns a value for the register where all fields are set to their default value.
        impl ::core::default::Default for $name {
            fn default() -> Self {
                #[allow(unused_mut)]
                let mut value = Self(Default::default());

                ::kernel::macros::paste!(
                $(
                value.[<set_ $field>](Default::default());
                )*
                );

                value
            }
        }
    };

    // Generates the IO accessors for a fixed offset register.
    (@io_fixed $name:ident @ $offset:expr) => {
        #[allow(dead_code)]
        impl $name {
            pub(crate) const OFFSET: usize = $offset;

            /// Read the register from its address in `io`.
            #[inline(always)]
            pub(crate) fn read<const SIZE: usize, T>(io: &T) -> Self where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
            {
                Self(io.read32($offset))
            }

            /// Write the value contained in `self` to the register address in `io`.
            #[inline(always)]
            pub(crate) fn write<const SIZE: usize, T>(self, io: &T) where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
            {
                io.write32(self.0, $offset)
            }

            /// Read the register from its address in `io` and run `f` on its value to obtain a new
            /// value to write back.
            #[inline(always)]
            pub(crate) fn alter<const SIZE: usize, T, F>(
                io: &T,
                f: F,
            ) where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                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 @ $base:ty [ $offset:expr ]) => {
        #[allow(dead_code)]
        impl $name {
            pub(crate) 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(crate) fn read<const SIZE: usize, T, B>(
                io: &T,
                #[allow(unused_variables)]
                base: &B,
            ) -> Self where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::RegisterBase<$base>,
            {
                const OFFSET: usize = $name::OFFSET;

                let value = io.read32(
                    <B as crate::regs::macros::RegisterBase<$base>>::BASE + OFFSET
                );

                Self(value)
            }

            /// 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(crate) fn write<const SIZE: usize, T, B>(
                self,
                io: &T,
                #[allow(unused_variables)]
                base: &B,
            ) where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::RegisterBase<$base>,
            {
                const OFFSET: usize = $name::OFFSET;

                io.write32(
                    self.0,
                    <B as crate::regs::macros::RegisterBase<$base>>::BASE + 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.
            #[inline(always)]
            pub(crate) fn alter<const SIZE: usize, T, B, F>(
                io: &T,
                base: &B,
                f: F,
            ) where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::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 @ $offset:literal [ $size:expr ; $stride:expr ]) => {
        #[allow(dead_code)]
        impl $name {
            pub(crate) const OFFSET: usize = $offset;
            pub(crate) const SIZE: usize = $size;
            pub(crate) const STRIDE: usize = $stride;

            /// Read the array register at index `idx` from its address in `io`.
            #[inline(always)]
            pub(crate) fn read<const SIZE: usize, T>(
                io: &T,
                idx: usize,
            ) -> Self where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
            {
                build_assert!(idx < Self::SIZE);

                let offset = Self::OFFSET + (idx * Self::STRIDE);
                let value = io.read32(offset);

                Self(value)
            }

            /// Write the value contained in `self` to the array register with index `idx` in `io`.
            #[inline(always)]
            pub(crate) fn write<const SIZE: usize, T>(
                self,
                io: &T,
                idx: usize
            ) where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
            {
                build_assert!(idx < Self::SIZE);

                let offset = Self::OFFSET + (idx * Self::STRIDE);

                io.write32(self.0, offset);
            }

            /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
            /// new value to write back.
            #[inline(always)]
            pub(crate) fn alter<const SIZE: usize, T, F>(
                io: &T,
                idx: usize,
                f: F,
            ) where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                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 is the
            /// access was out-of-bounds.
            #[inline(always)]
            pub(crate) fn try_read<const SIZE: usize, T>(
                io: &T,
                idx: usize,
            ) -> ::kernel::error::Result<Self> where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
            {
                if idx < Self::SIZE {
                    Ok(Self::read(io, idx))
                } else {
                    Err(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 is the
            /// access was out-of-bounds.
            #[inline(always)]
            pub(crate) fn try_write<const SIZE: usize, T>(
                self,
                io: &T,
                idx: usize,
            ) -> ::kernel::error::Result where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
            {
                if idx < Self::SIZE {
                    Ok(self.write(io, idx))
                } else {
                    Err(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 is the
            /// access was out-of-bounds.
            #[inline(always)]
            pub(crate) fn try_alter<const SIZE: usize, T, F>(
                io: &T,
                idx: usize,
                f: F,
            ) -> ::kernel::error::Result where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                F: ::core::ops::FnOnce(Self) -> Self,
            {
                if idx < Self::SIZE {
                    Ok(Self::alter(io, idx, f))
                } else {
                    Err(EINVAL)
                }
            }
        }
    };

    // Generates the IO accessors for an array of relative registers.
    (
        @io_relative_array $name:ident @ $base:ty
            [ $offset:literal [ $size:expr ; $stride:expr ] ]
    ) => {
        #[allow(dead_code)]
        impl $name {
            pub(crate) const OFFSET: usize = $offset;
            pub(crate) const SIZE: usize = $size;
            pub(crate) 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(crate) fn read<const SIZE: usize, T, B>(
                io: &T,
                #[allow(unused_variables)]
                base: &B,
                idx: usize,
            ) -> Self where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::RegisterBase<$base>,
            {
                build_assert!(idx < Self::SIZE);

                let offset = <B as crate::regs::macros::RegisterBase<$base>>::BASE +
                    Self::OFFSET + (idx * Self::STRIDE);
                let value = io.read32(offset);

                Self(value)
            }

            /// 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(crate) fn write<const SIZE: usize, T, B>(
                self,
                io: &T,
                #[allow(unused_variables)]
                base: &B,
                idx: usize
            ) where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::RegisterBase<$base>,
            {
                build_assert!(idx < Self::SIZE);

                let offset = <B as crate::regs::macros::RegisterBase<$base>>::BASE +
                    Self::OFFSET + (idx * Self::STRIDE);

                io.write32(self.0, 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.
            #[inline(always)]
            pub(crate) fn alter<const SIZE: usize, T, B, F>(
                io: &T,
                base: &B,
                idx: usize,
                f: F,
            ) where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::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 is the
            /// access was out-of-bounds.
            #[inline(always)]
            pub(crate) fn try_read<const SIZE: usize, T, B>(
                io: &T,
                base: &B,
                idx: usize,
            ) -> ::kernel::error::Result<Self> where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::RegisterBase<$base>,
            {
                if idx < Self::SIZE {
                    Ok(Self::read(io, base, idx))
                } else {
                    Err(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 is the
            /// access was out-of-bounds.
            #[inline(always)]
            pub(crate) fn try_write<const SIZE: usize, T, B>(
                self,
                io: &T,
                base: &B,
                idx: usize,
            ) -> ::kernel::error::Result where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::RegisterBase<$base>,
            {
                if idx < Self::SIZE {
                    Ok(self.write(io, base, idx))
                } else {
                    Err(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 is the
            /// access was out-of-bounds.
            #[inline(always)]
            pub(crate) fn try_alter<const SIZE: usize, T, B, F>(
                io: &T,
                base: &B,
                idx: usize,
                f: F,
            ) -> ::kernel::error::Result where
                T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
                B: crate::regs::macros::RegisterBase<$base>,
                F: ::core::ops::FnOnce(Self) -> Self,
            {
                if idx < Self::SIZE {
                    Ok(Self::alter(io, base, idx, f))
                } else {
                    Err(EINVAL)
                }
            }
        }
    };
}