| // SPDX-License-Identifier: GPL-2.0 | |
| //! Macro to define register layout and accessors. | |
| //! | |
| //! The [`register!`](kernel::io::register!) macro provides an intuitive and readable syntax for | |
| //! defining a dedicated type for each register and accessing it using [`Io`](super::Io). Each such | |
| //! type comes with its own field accessors that can return an error if a field's value is invalid. | |
| //! | |
| //! Note: most of the items in this module are public so they can be referenced by the macro, but | |
| //! most are not to be used directly by users. Outside of the `register!` macro itself, the only | |
| //! items you might want to import from this module are [`WithBase`] and [`Array`]. | |
| //! | |
| //! # Simple example | |
| //! | |
| //! ```no_run | |
| //! use kernel::io::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 32-bit `BOOT_0` type which can be read from or written to offset `0x100` of an | |
| //! `Io` region, with the described bitfields. For instance, `minor_revision` consists of the 4 | |
| //! least significant bits of the type. | |
| //! | |
| //! 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 `with_` | |
| //! for runtime values and `with_const_` for constant values. All setters return the updated | |
| //! register value. | |
| //! | |
| //! Fields can also be transparently converted from/to an arbitrary type by using the `=>` and | |
| //! `?=>` syntaxes. | |
| //! | |
| //! If present, doc comments 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. | |
| //! | |
| //! Here is how the register defined above can be used in code: | |
| //! | |
| //! | |
| //! ```no_run | |
| //! use kernel::{ | |
| //! io::{ | |
| //! register, | |
| //! Io, | |
| //! IoLoc, | |
| //! }, | |
| //! num::Bounded, | |
| //! }; | |
| //! # use kernel::io::Mmio; | |
| //! # register! { | |
| //! # pub BOOT_0(u32) @ 0x00000100 { | |
| //! # 15:8 vendor_id; | |
| //! # 7:4 major_revision; | |
| //! # 3:0 minor_revision; | |
| //! # } | |
| //! # } | |
| //! # fn test(io: &Mmio<0x1000>) { | |
| //! # fn obtain_vendor_id() -> u8 { 0xff } | |
| //! | |
| //! // Read from the register's defined offset (0x100). | |
| //! let boot0 = io.read(BOOT_0); | |
| //! pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get()); | |
| //! | |
| //! // Update some fields and write the new value back. | |
| //! let new_boot0 = boot0 | |
| //! // Constant values. | |
| //! .with_const_major_revision::<3>() | |
| //! .with_const_minor_revision::<10>() | |
| //! // Runtime value. | |
| //! .with_vendor_id(obtain_vendor_id()); | |
| //! io.write_reg(new_boot0); | |
| //! | |
| //! // Or, build a new value from zero and write it: | |
| //! io.write_reg(BOOT_0::zeroed() | |
| //! .with_const_major_revision::<3>() | |
| //! .with_const_minor_revision::<10>() | |
| //! .with_vendor_id(obtain_vendor_id()) | |
| //! ); | |
| //! | |
| //! // Or, read and update the register in a single step. | |
| //! io.update(BOOT_0, |r| r | |
| //! .with_const_major_revision::<3>() | |
| //! .with_const_minor_revision::<10>() | |
| //! .with_vendor_id(obtain_vendor_id()) | |
| //! ); | |
| //! | |
| //! // Constant values can also be built using the const setters. | |
| //! const V: BOOT_0 = pin_init::zeroed::<BOOT_0>() | |
| //! .with_const_major_revision::<3>() | |
| //! .with_const_minor_revision::<10>(); | |
| //! # } | |
| //! ``` | |
| //! | |
| //! For more extensive documentation about how to define registers, see the | |
| //! [`register!`](kernel::io::register!) macro. | |
| use core::marker::PhantomData; | |
| use crate::{ | |
| build_assert::build_assert, | |
| io::IoLoc, // | |
| }; | |
| /// Trait implemented by all registers. | |
| pub trait Register: Sized { | |
| /// Backing primitive type of the register. | |
| type Storage: Into<Self> + From<Self>; | |
| /// Start offset of the register. | |
| /// | |
| /// The interpretation of this offset depends on the type of the register. | |
| const OFFSET: usize; | |
| } | |
| /// Trait implemented by registers with a fixed offset. | |
| pub trait FixedRegister: Register {} | |
| /// Allows `()` to be used as the `location` parameter of [`Io::write`](super::Io::write) when | |
| /// passing a [`FixedRegister`] value. | |
| impl<T> IoLoc<T> for () | |
| where | |
| T: FixedRegister, | |
| { | |
| type IoType = T::Storage; | |
| #[inline(always)] | |
| fn offset(self) -> usize { | |
| T::OFFSET | |
| } | |
| } | |
| /// A [`FixedRegister`] carries its location in its type. Thus `FixedRegister` values can be used | |
| /// as an [`IoLoc`]. | |
| impl<T> IoLoc<T> for T | |
| where | |
| T: FixedRegister, | |
| { | |
| type IoType = T::Storage; | |
| #[inline(always)] | |
| fn offset(self) -> usize { | |
| T::OFFSET | |
| } | |
| } | |
| /// Location of a fixed register. | |
| pub struct FixedRegisterLoc<T: FixedRegister>(PhantomData<T>); | |
| impl<T: FixedRegister> FixedRegisterLoc<T> { | |
| /// Returns the location of `T`. | |
| #[inline(always)] | |
| // We do not implement `Default` so we can be const. | |
| #[expect(clippy::new_without_default)] | |
| pub const fn new() -> Self { | |
| Self(PhantomData) | |
| } | |
| } | |
| impl<T> IoLoc<T> for FixedRegisterLoc<T> | |
| where | |
| T: FixedRegister, | |
| { | |
| type IoType = T::Storage; | |
| #[inline(always)] | |
| fn offset(self) -> usize { | |
| T::OFFSET | |
| } | |
| } | |
| /// 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 implemented by all registers that are relative to a base. | |
| pub trait WithBase { | |
| /// Family of bases applicable to this register. | |
| type BaseFamily; | |
| /// Returns the absolute location of this type when using `B` as its base. | |
| #[inline(always)] | |
| fn of<B: RegisterBase<Self::BaseFamily>>() -> RelativeRegisterLoc<Self, B> | |
| where | |
| Self: Register, | |
| { | |
| RelativeRegisterLoc::new() | |
| } | |
| } | |
| /// Trait implemented by relative registers. | |
| pub trait RelativeRegister: Register + WithBase {} | |
| /// Location of a relative register. | |
| /// | |
| /// This can either be an immediately accessible regular [`RelativeRegister`], or a | |
| /// [`RelativeRegisterArray`] that needs one additional resolution through | |
| /// [`RelativeRegisterLoc::at`]. | |
| pub struct RelativeRegisterLoc<T: WithBase, B: ?Sized>(PhantomData<T>, PhantomData<B>); | |
| impl<T, B> RelativeRegisterLoc<T, B> | |
| where | |
| T: Register + WithBase, | |
| B: RegisterBase<T::BaseFamily> + ?Sized, | |
| { | |
| /// Returns the location of a relative register or register array. | |
| #[inline(always)] | |
| // We do not implement `Default` so we can be const. | |
| #[expect(clippy::new_without_default)] | |
| pub const fn new() -> Self { | |
| Self(PhantomData, PhantomData) | |
| } | |
| // Returns the absolute offset of the relative register using base `B`. | |
| // | |
| // This is implemented as a private const method so it can be reused by the [`IoLoc`] | |
| // implementations of both [`RelativeRegisterLoc`] and [`RelativeRegisterArrayLoc`]. | |
| #[inline] | |
| const fn offset(self) -> usize { | |
| B::BASE + T::OFFSET | |
| } | |
| } | |
| impl<T, B> IoLoc<T> for RelativeRegisterLoc<T, B> | |
| where | |
| T: RelativeRegister, | |
| B: RegisterBase<T::BaseFamily> + ?Sized, | |
| { | |
| type IoType = T::Storage; | |
| #[inline(always)] | |
| fn offset(self) -> usize { | |
| RelativeRegisterLoc::offset(self) | |
| } | |
| } | |
| /// Trait implemented by arrays of registers. | |
| pub trait RegisterArray: Register { | |
| /// Number of elements in the registers array. | |
| const SIZE: usize; | |
| /// Number of bytes between the start of elements in the registers array. | |
| const STRIDE: usize; | |
| } | |
| /// Location of an array register. | |
| pub struct RegisterArrayLoc<T: RegisterArray>(usize, PhantomData<T>); | |
| impl<T: RegisterArray> RegisterArrayLoc<T> { | |
| /// Returns the location of register `T` at position `idx`, with build-time validation. | |
| #[inline(always)] | |
| pub fn new(idx: usize) -> Self { | |
| build_assert!(idx < T::SIZE); | |
| Self(idx, PhantomData) | |
| } | |
| /// Attempts to return the location of register `T` at position `idx`, with runtime validation. | |
| #[inline(always)] | |
| pub fn try_new(idx: usize) -> Option<Self> { | |
| if idx < T::SIZE { | |
| Some(Self(idx, PhantomData)) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| impl<T> IoLoc<T> for RegisterArrayLoc<T> | |
| where | |
| T: RegisterArray, | |
| { | |
| type IoType = T::Storage; | |
| #[inline(always)] | |
| fn offset(self) -> usize { | |
| T::OFFSET + self.0 * T::STRIDE | |
| } | |
| } | |
| /// Trait providing location builders for [`RegisterArray`]s. | |
| pub trait Array { | |
| /// Returns the location of the register at position `idx`, with build-time validation. | |
| #[inline(always)] | |
| fn at(idx: usize) -> RegisterArrayLoc<Self> | |
| where | |
| Self: RegisterArray, | |
| { | |
| RegisterArrayLoc::new(idx) | |
| } | |
| /// Returns the location of the register at position `idx`, with runtime validation. | |
| #[inline(always)] | |
| fn try_at(idx: usize) -> Option<RegisterArrayLoc<Self>> | |
| where | |
| Self: RegisterArray, | |
| { | |
| RegisterArrayLoc::try_new(idx) | |
| } | |
| } | |
| /// Trait implemented by arrays of relative registers. | |
| pub trait RelativeRegisterArray: RegisterArray + WithBase {} | |
| /// Location of a relative array register. | |
| pub struct RelativeRegisterArrayLoc< | |
| T: RelativeRegisterArray, | |
| B: RegisterBase<T::BaseFamily> + ?Sized, | |
| >(RelativeRegisterLoc<T, B>, usize); | |
| impl<T, B> RelativeRegisterArrayLoc<T, B> | |
| where | |
| T: RelativeRegisterArray, | |
| B: RegisterBase<T::BaseFamily> + ?Sized, | |
| { | |
| /// Returns the location of register `T` from the base `B` at index `idx`, with build-time | |
| /// validation. | |
| #[inline(always)] | |
| pub fn new(idx: usize) -> Self { | |
| build_assert!(idx < T::SIZE); | |
| Self(RelativeRegisterLoc::new(), idx) | |
| } | |
| /// Attempts to return the location of register `T` from the base `B` at index `idx`, with | |
| /// runtime validation. | |
| #[inline(always)] | |
| pub fn try_new(idx: usize) -> Option<Self> { | |
| if idx < T::SIZE { | |
| Some(Self(RelativeRegisterLoc::new(), idx)) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| /// Methods exclusive to [`RelativeRegisterLoc`]s created with a [`RelativeRegisterArray`]. | |
| impl<T, B> RelativeRegisterLoc<T, B> | |
| where | |
| T: RelativeRegisterArray, | |
| B: RegisterBase<T::BaseFamily> + ?Sized, | |
| { | |
| /// Returns the location of the register at position `idx`, with build-time validation. | |
| #[inline(always)] | |
| pub fn at(self, idx: usize) -> RelativeRegisterArrayLoc<T, B> { | |
| RelativeRegisterArrayLoc::new(idx) | |
| } | |
| /// Returns the location of the register at position `idx`, with runtime validation. | |
| #[inline(always)] | |
| pub fn try_at(self, idx: usize) -> Option<RelativeRegisterArrayLoc<T, B>> { | |
| RelativeRegisterArrayLoc::try_new(idx) | |
| } | |
| } | |
| impl<T, B> IoLoc<T> for RelativeRegisterArrayLoc<T, B> | |
| where | |
| T: RelativeRegisterArray, | |
| B: RegisterBase<T::BaseFamily> + ?Sized, | |
| { | |
| type IoType = T::Storage; | |
| #[inline(always)] | |
| fn offset(self) -> usize { | |
| self.0.offset() + self.1 * T::STRIDE | |
| } | |
| } | |
| /// Trait implemented by items that contain both a register value and the absolute I/O location at | |
| /// which to write it. | |
| /// | |
| /// Implementors can be used with [`Io::write_reg`](super::Io::write_reg). | |
| pub trait LocatedRegister { | |
| /// Register value to write. | |
| type Value: Register; | |
| /// Full location information at which to write the value. | |
| type Location: IoLoc<Self::Value>; | |
| /// Consumes `self` and returns a `(location, value)` tuple describing a valid I/O write | |
| /// operation. | |
| fn into_io_op(self) -> (Self::Location, Self::Value); | |
| } | |
| impl<T> LocatedRegister for T | |
| where | |
| T: FixedRegister, | |
| { | |
| type Location = FixedRegisterLoc<Self::Value>; | |
| type Value = T; | |
| #[inline(always)] | |
| fn into_io_op(self) -> (FixedRegisterLoc<T>, T) { | |
| (FixedRegisterLoc::new(), self) | |
| } | |
| } | |
| /// 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`](kernel::io::Io) region. | |
| /// | |
| /// This documentation focuses on how to declare registers. See the [module-level | |
| /// documentation](mod@kernel::io::register) for examples of how to access them. | |
| /// | |
| /// There are 4 possible kinds of registers: fixed offset registers, relative registers, arrays of | |
| /// registers, and relative arrays of registers. | |
| /// | |
| /// ## Fixed offset registers | |
| /// | |
| /// These are the simplest kind of registers. Their location is simply an offset inside the I/O | |
| /// region. For instance: | |
| /// | |
| /// ```ignore | |
| /// register! { | |
| /// pub FIXED_REG(u16) @ 0x80 { | |
| /// ... | |
| /// } | |
| /// } | |
| /// ``` | |
| /// | |
| /// This creates a 16-bit register named `FIXED_REG` located at offset `0x80` of an I/O region. | |
| /// | |
| /// These registers' location can be built simply by referencing their name: | |
| /// | |
| /// ```no_run | |
| /// use kernel::{ | |
| /// io::{ | |
| /// register, | |
| /// Io, | |
| /// }, | |
| /// }; | |
| /// # use kernel::io::Mmio; | |
| /// | |
| /// register! { | |
| /// FIXED_REG(u32) @ 0x100 { | |
| /// 16:8 high_byte; | |
| /// 7:0 low_byte; | |
| /// } | |
| /// } | |
| /// | |
| /// # fn test(io: &Mmio<0x1000>) { | |
| /// let val = io.read(FIXED_REG); | |
| /// | |
| /// // Write from an already-existing value. | |
| /// io.write(FIXED_REG, val.with_low_byte(0xff)); | |
| /// | |
| /// // Create a register value from scratch. | |
| /// let val2 = FIXED_REG::zeroed().with_high_byte(0x80); | |
| /// | |
| /// // The location of fixed offset registers is already contained in their type. Thus, the | |
| /// // `location` argument of `Io::write` is technically redundant and can be replaced by `()`. | |
| /// io.write((), val2); | |
| /// | |
| /// // Or, the single-argument `Io::write_reg` can be used. | |
| /// io.write_reg(val2); | |
| /// # } | |
| /// | |
| /// ``` | |
| /// | |
| /// It is possible to create an alias of an existing register with new field definitions by using | |
| /// the `=> ALIAS` syntax. This is useful for cases where a register's interpretation depends on | |
| /// the context: | |
| /// | |
| /// ```no_run | |
| /// use kernel::io::register; | |
| /// | |
| /// register! { | |
| /// /// Scratch register. | |
| /// pub SCRATCH(u32) @ 0x00000200 { | |
| /// 31:0 value; | |
| /// } | |
| /// | |
| /// /// Boot status of the firmware. | |
| /// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH { | |
| /// 0:0 completed; | |
| /// } | |
| /// } | |
| /// ``` | |
| /// | |
| /// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while providing | |
| /// its own `completed` field. | |
| /// | |
| /// ## Relative registers | |
| /// | |
| /// Relative registers can be instantiated several times at a relative offset of a group of bases. | |
| /// 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: | |
| /// | |
| /// ```ignore | |
| /// register! { | |
| /// pub RELATIVE_REG(u32) @ Base + 0x80 { | |
| /// ... | |
| /// } | |
| /// } | |
| /// ``` | |
| /// | |
| /// This creates a register with an offset of `0x80` from a given base. | |
| /// | |
| /// `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>`. | |
| /// | |
| /// The location of relative registers can be built using the [`WithBase::of`] method to specify | |
| /// its base. All relative registers implement [`WithBase`]. | |
| /// | |
| /// Here is the above layout translated into code: | |
| /// | |
| /// ```no_run | |
| /// use kernel::{ | |
| /// io::{ | |
| /// register, | |
| /// register::{ | |
| /// RegisterBase, | |
| /// WithBase, | |
| /// }, | |
| /// Io, | |
| /// }, | |
| /// }; | |
| /// # use kernel::io::Mmio; | |
| /// | |
| /// // Type used to identify the base. | |
| /// pub struct CpuCtlBase; | |
| /// | |
| /// // ZST describing `CPU0`. | |
| /// struct Cpu0; | |
| /// impl RegisterBase<CpuCtlBase> for Cpu0 { | |
| /// const BASE: usize = 0x100; | |
| /// } | |
| /// | |
| /// // ZST describing `CPU1`. | |
| /// struct Cpu1; | |
| /// impl RegisterBase<CpuCtlBase> for Cpu1 { | |
| /// const BASE: usize = 0x200; | |
| /// } | |
| /// | |
| /// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`. | |
| /// register! { | |
| /// /// CPU core control. | |
| /// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 { | |
| /// 0:0 start; | |
| /// } | |
| /// } | |
| /// | |
| /// # fn test(io: Mmio<0x1000>) { | |
| /// // Read the status of `Cpu0`. | |
| /// let cpu0_started = io.read(CPU_CTL::of::<Cpu0>()); | |
| /// | |
| /// // Stop `Cpu0`. | |
| /// io.write(WithBase::of::<Cpu0>(), CPU_CTL::zeroed()); | |
| /// # } | |
| /// | |
| /// // 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; | |
| /// } | |
| /// } | |
| /// | |
| /// # fn test2(io: Mmio<0x1000>) { | |
| /// // Start the aliased `CPU0`, leaving its other fields untouched. | |
| /// io.update(CPU_CTL_ALIAS::of::<Cpu0>(), |r| r.with_alias_start(true)); | |
| /// # } | |
| /// ``` | |
| /// | |
| /// ## Arrays of registers | |
| /// | |
| /// Some I/O areas contain consecutive registers that share the same field layout. These areas can | |
| /// be defined as an array of identical registers, allowing them to be accessed by index with | |
| /// compile-time or runtime bound checking: | |
| /// | |
| /// ```ignore | |
| /// register! { | |
| /// pub REGISTER_ARRAY(u8)[10, stride = 4] @ 0x100 { | |
| /// ... | |
| /// } | |
| /// } | |
| /// ``` | |
| /// | |
| /// This defines `REGISTER_ARRAY`, an array of 10 byte registers starting at offset `0x100`. Each | |
| /// register is separated from its neighbor by 4 bytes. | |
| /// | |
| /// The `stride` parameter is optional; if unspecified, the registers are placed consecutively from | |
| /// each other. | |
| /// | |
| /// A location for a register in a register array is built using the [`Array::at`] trait method. | |
| /// All arrays of registers implement [`Array`]. | |
| /// | |
| /// ```no_run | |
| /// use kernel::{ | |
| /// io::{ | |
| /// register, | |
| /// register::Array, | |
| /// Io, | |
| /// }, | |
| /// }; | |
| /// # use kernel::io::Mmio; | |
| /// # 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; | |
| /// } | |
| /// } | |
| /// | |
| /// # fn test(io: &Mmio<0x1000>) | |
| /// # -> Result<(), Error>{ | |
| /// // Read scratch register 0, i.e. I/O address `0x80`. | |
| /// let scratch_0 = io.read(SCRATCH::at(0)).value(); | |
| /// | |
| /// // Write scratch register 15, i.e. I/O address `0x80 + (15 * 4)`. | |
| /// io.write(Array::at(15), SCRATCH::from(0xffeeaabb)); | |
| /// | |
| /// // This is out of bounds and won't build. | |
| /// // let scratch_128 = io.read(SCRATCH::at(128)).value(); | |
| /// | |
| /// // Runtime-obtained array index. | |
| /// let idx = get_scratch_idx(); | |
| /// // Access on a runtime index returns an error if it is out-of-bounds. | |
| /// let some_scratch = io.read(SCRATCH::try_at(idx).ok_or(EINVAL)?).value(); | |
| /// | |
| /// // Alias to a specific register in an array. | |
| /// // Here `SCRATCH[8]` is used to convey the firmware exit code. | |
| /// register! { | |
| /// /// Firmware exit status code. | |
| /// pub FIRMWARE_STATUS(u32) => SCRATCH[8] { | |
| /// 7:0 status; | |
| /// } | |
| /// } | |
| /// | |
| /// let status = io.read(FIRMWARE_STATUS).status(); | |
| /// | |
| /// // Non-contiguous register arrays can be defined by adding a stride parameter. | |
| /// // Here, each of the 16 registers of the array is separated by 8 bytes, meaning that the | |
| /// // registers of the two declarations below are interleaved. | |
| /// register! { | |
| /// /// Scratch registers bank 0. | |
| /// pub SCRATCH_INTERLEAVED_0(u32)[16, stride = 8] @ 0x000000c0 { | |
| /// 31:0 value; | |
| /// } | |
| /// | |
| /// /// Scratch registers bank 1. | |
| /// pub SCRATCH_INTERLEAVED_1(u32)[16, stride = 8] @ 0x000000c4 { | |
| /// 31:0 value; | |
| /// } | |
| /// } | |
| /// # 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: | |
| /// | |
| /// ```ignore | |
| /// register! { | |
| /// pub RELATIVE_REGISTER_ARRAY(u8)[10, stride = 4] @ Base + 0x100 { | |
| /// ... | |
| /// } | |
| /// } | |
| /// ``` | |
| /// | |
| /// Like relative registers, they implement the [`WithBase`] trait. However the return value of | |
| /// [`WithBase::of`] cannot be used directly as a location and must be further specified using the | |
| /// [`at`](RelativeRegisterLoc::at) method. | |
| /// | |
| /// ```no_run | |
| /// use kernel::{ | |
| /// io::{ | |
| /// register, | |
| /// register::{ | |
| /// RegisterBase, | |
| /// WithBase, | |
| /// }, | |
| /// Io, | |
| /// }, | |
| /// }; | |
| /// # use kernel::io::Mmio; | |
| /// # 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; | |
| /// } | |
| /// | |
| /// // ZST describing `CPU1`. | |
| /// struct Cpu1; | |
| /// impl RegisterBase<CpuCtlBase> for Cpu1 { | |
| /// const BASE: usize = 0x200; | |
| /// } | |
| /// | |
| /// // 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; | |
| /// } | |
| /// } | |
| /// | |
| /// # fn test(io: &Mmio<0x1000>) -> Result<(), Error> { | |
| /// // Read scratch register 0 of CPU0. | |
| /// let scratch = io.read(CPU_SCRATCH::of::<Cpu0>().at(0)); | |
| /// | |
| /// // Write the retrieved value into scratch register 15 of CPU1. | |
| /// io.write(WithBase::of::<Cpu1>().at(15), scratch); | |
| /// | |
| /// // This won't build. | |
| /// // let cpu0_scratch_128 = io.read(CPU_SCRATCH::of::<Cpu0>().at(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 cpu0_scratch = io.read( | |
| /// CPU_SCRATCH::of::<Cpu0>().try_at(scratch_idx).ok_or(EINVAL)? | |
| /// ).value(); | |
| /// # Ok(()) | |
| /// # } | |
| /// | |
| /// // Alias to `SCRATCH[8]` 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; | |
| /// } | |
| /// } | |
| /// | |
| /// // Non-contiguous relative register arrays can be defined by adding a stride parameter. | |
| /// // Here, each of the 16 registers of the array is separated by 8 bytes, meaning that the | |
| /// // registers of the two declarations below are interleaved. | |
| /// register! { | |
| /// /// Scratch registers bank 0. | |
| /// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16, stride = 8] @ CpuCtlBase + 0x00000d00 { | |
| /// 31:0 value; | |
| /// } | |
| /// | |
| /// /// Scratch registers bank 1. | |
| /// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16, stride = 8] @ CpuCtlBase + 0x00000d04 { | |
| /// 31:0 value; | |
| /// } | |
| /// } | |
| /// | |
| /// # fn test2(io: &Mmio<0x1000>) -> Result<(), Error> { | |
| /// let cpu0_status = io.read(CPU_FIRMWARE_STATUS::of::<Cpu0>()).status(); | |
| /// # Ok(()) | |
| /// # } | |
| /// ``` | |
| #[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 = $stride:expr)? ])? | |
| $(@ $($base:ident +)? $offset:literal)? | |
| $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )? | |
| { $($fields:tt)* } | |
| )* | |
| ) => { | |
| $( | |
| $crate::register!( | |
| @reg $(#[$attr])* $vis $name ($storage) $([$size $(, stride = $stride)?])? | |
| $(@ $($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)* } | |
| ) => { | |
| $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); | |
| $crate::register!(@io_base $name($storage) @ $offset); | |
| $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); | |
| }; | |
| // 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)* } | |
| ) => { | |
| $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); | |
| $crate::register!( | |
| @io_base $name($storage) @ | |
| <$alias as $crate::io::register::Register>::OFFSET | |
| ); | |
| $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); | |
| }; | |
| // 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)* } | |
| ) => { | |
| $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); | |
| $crate::register!(@io_base $name($storage) @ $offset); | |
| $crate::register!(@io_relative $vis $name($storage) @ $base); | |
| }; | |
| // 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)* } | |
| ) => { | |
| $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); | |
| $crate::register!( | |
| @io_base $name($storage) @ <$alias as $crate::io::register::Register>::OFFSET | |
| ); | |
| $crate::register!(@io_relative $vis $name($storage) @ $base); | |
| }; | |
| // 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 = $stride:expr ] @ $offset:literal { $($fields:tt)* } | |
| ) => { | |
| $crate::build_assert::static_assert!(::core::mem::size_of::<$storage>() <= $stride); | |
| $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); | |
| $crate::register!(@io_base $name($storage) @ $offset); | |
| $crate::register!(@io_array $vis $name($storage) [ $size, stride = $stride ]); | |
| }; | |
| // 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)* } | |
| ) => { | |
| $crate::register!( | |
| $(#[$attr])* $vis $name($storage) [ $size, stride = ::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)* } | |
| ) => { | |
| $crate::build_assert::static_assert!( | |
| $idx < <$alias as $crate::io::register::RegisterArray>::SIZE | |
| ); | |
| $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); | |
| $crate::register!( | |
| @io_base $name($storage) @ | |
| <$alias as $crate::io::register::Register>::OFFSET | |
| + $idx * <$alias as $crate::io::register::RegisterArray>::STRIDE | |
| ); | |
| $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); | |
| }; | |
| // 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 = $stride:expr ] | |
| @ $base:ident + $offset:literal { $($fields:tt)* } | |
| ) => { | |
| $crate::build_assert::static_assert!(::core::mem::size_of::<$storage>() <= $stride); | |
| $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); | |
| $crate::register!(@io_base $name($storage) @ $offset); | |
| $crate::register!( | |
| @io_relative_array $vis $name($storage) [ $size, stride = $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)* } | |
| ) => { | |
| $crate::register!( | |
| $(#[$attr])* $vis $name($storage) [ $size, stride = ::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)* } | |
| ) => { | |
| $crate::build_assert::static_assert!( | |
| $idx < <$alias as $crate::io::register::RegisterArray>::SIZE | |
| ); | |
| $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); | |
| $crate::register!( | |
| @io_base $name($storage) @ | |
| <$alias as $crate::io::register::Register>::OFFSET + | |
| $idx * <$alias as $crate::io::register::RegisterArray>::STRIDE | |
| ); | |
| $crate::register!(@io_relative $vis $name($storage) @ $base); | |
| }; | |
| // 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)* } | |
| ) => { | |
| $crate::bitfield!( | |
| #[allow(non_camel_case_types)] | |
| $(#[$attr])* $vis struct $name($storage) { $($fields)* } | |
| ); | |
| }; | |
| // Implementations shared by all registers types. | |
| (@io_base $name:ident($storage:ty) @ $offset:expr) => { | |
| impl $crate::io::register::Register for $name { | |
| type Storage = $storage; | |
| const OFFSET: usize = $offset; | |
| } | |
| }; | |
| // Implementations of fixed registers. | |
| (@io_fixed $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)) => { | |
| impl $crate::io::register::FixedRegister for $name {} | |
| $(#[$attr])* | |
| $vis const $name: $crate::io::register::FixedRegisterLoc<$name> = | |
| $crate::io::register::FixedRegisterLoc::<$name>::new(); | |
| }; | |
| // Implementations of relative registers. | |
| (@io_relative $vis:vis $name:ident ($storage:ty) @ $base:ident) => { | |
| impl $crate::io::register::WithBase for $name { | |
| type BaseFamily = $base; | |
| } | |
| impl $crate::io::register::RelativeRegister for $name {} | |
| }; | |
| // Implementations of register arrays. | |
| (@io_array $vis:vis $name:ident ($storage:ty) [ $size:expr, stride = $stride:expr ]) => { | |
| impl $crate::io::register::Array for $name {} | |
| impl $crate::io::register::RegisterArray for $name { | |
| const SIZE: usize = $size; | |
| const STRIDE: usize = $stride; | |
| } | |
| }; | |
| // Implementations of relative array registers. | |
| ( | |
| @io_relative_array $vis:vis $name:ident ($storage:ty) [ $size:expr, stride = $stride:expr ] | |
| @ $base:ident + $offset:literal | |
| ) => { | |
| impl $crate::io::register::WithBase for $name { | |
| type BaseFamily = $base; | |
| } | |
| impl $crate::io::register::RegisterArray for $name { | |
| const SIZE: usize = $size; | |
| const STRIDE: usize = $stride; | |
| } | |
| impl $crate::io::register::RelativeRegisterArray for $name {} | |
| }; | |
| } |