* [PATCH v2 0/5] rust: add `register!` macro
@ 2026-01-21 7:23 Alexandre Courbot
2026-01-21 7:23 ` [PATCH v2 1/5] rust: enable the `generic_arg_infer` feature Alexandre Courbot
` (5 more replies)
0 siblings, 6 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-21 7:23 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel, Alexandre Courbot
Add an improved version of nova-core's `register!` macro to the `kernel`
crate for all drivers to use.
This is not a direct move from `nova-core`, but rather a new
introduction to facilitate code review and introduce features that are
missing in the nova-core versions. Differences notably include:
- Use of `Bounded` to prevent any data truncation when manipulating
bitfields,
- Extended documentation,
- Doccomments now build and run,
- Supports visibility and different storage sizes.
The `bitfield!` macro of nova-core has for the moment been wrapped into
`register!`, as a set of private rules, to allow `register!` to be
merged first while `bitfield!` undergoes review during the next cycle.
Thus, some of the code from v1 (including `bitfield!`'s doccomments and
Kunit tests) are kept for later.
The first patch enables the `generic_arg_infer` feature, which is
required for generic type inference and used in subsequent patches. This
feature is stable since rustc 1.89.
The second patch adds `shr` and `shl` methods to `Bounded`. These were
suggested by Alice during LPC as a way to avoid the use of the
controversial `Bounded::from_expr` in both the bitfield macro and the
Nova code. Third patch adds another convenience method to obtain a
`bool` from single-bit `Bounded`s.
Patch 4 adds the `register!` macro. Since it falls under
`rust/kernel/io` it is covered by the corresponding MAINTAINERS entry so
I refrained from adding one just for this file, especially since the
bitfield-related parts will eventually move and what remains is very
tightly related to I/O.
The last patch illustrates how this macro is used by converting
nova-core to use it, and removing the local implementation. This patch
is to be merged one cycle after the other patches.
Previous work to extract the macros was done in the partially-merged
[1]. The current series can be considered a reboot with more features
and the `bitfield!` macro being postponed.
This patchset is based on `driver-core-next`.
Note that it also need `rust-fixes` to avoid an `unused_unsafe` warning.
[1] https://lore.kernel.org/all/20251003154748.1687160-1-joelagnelf@nvidia.com/
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
Changes in v2:
- Remove `bitfield!` and put its rules into `register!` to give it more
time to get reviewed.
- Allow output type larger than strictly required for `shr` and `shl` on
`Bounded`.
- Enable the `generic_arg_infer` feature, required for rustc < 1.89.
- Link to v1: https://patch.msgid.link/20260120-register-v1-0-723a1743b557@nvidia.com
---
Alexandre Courbot (5):
rust: enable the `generic_arg_infer` feature
rust: num: add `shr` and `shl` methods to `Bounded`
rust: num: add `as_bool` method to `Bounded<_, 1>`
rust: io: add `register!` macro
[FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro
drivers/gpu/nova-core/falcon.rs | 127 ++-
drivers/gpu/nova-core/falcon/gsp.rs | 10 +-
drivers/gpu/nova-core/falcon/hal/ga102.rs | 5 +-
drivers/gpu/nova-core/falcon/sec2.rs | 13 +-
drivers/gpu/nova-core/fb/hal/ga100.rs | 9 +-
drivers/gpu/nova-core/gpu.rs | 24 +-
drivers/gpu/nova-core/gsp/cmdq.rs | 2 +-
drivers/gpu/nova-core/regs.rs | 265 +++----
drivers/gpu/nova-core/regs/macros.rs | 721 -----------------
rust/kernel/io.rs | 1 +
rust/kernel/io/register.rs | 1198 +++++++++++++++++++++++++++++
rust/kernel/lib.rs | 3 +
rust/kernel/num/bounded.rs | 61 ++
scripts/Makefile.build | 3 +-
14 files changed, 1485 insertions(+), 957 deletions(-)
---
base-commit: c259cd7ea3c9ad369c473ba2385d82e3432088b1
change-id: 20260117-register-ccaba1d21713
Best regards,
--
Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply [flat|nested] 25+ messages in thread
* [PATCH v2 1/5] rust: enable the `generic_arg_infer` feature
2026-01-21 7:23 [PATCH v2 0/5] rust: add `register!` macro Alexandre Courbot
@ 2026-01-21 7:23 ` Alexandre Courbot
2026-01-21 11:48 ` Gary Guo
2026-01-21 7:23 ` [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
` (4 subsequent siblings)
5 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-21 7:23 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel, Alexandre Courbot
This feature is stable since 1.89, and used in subsequent patches.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/lib.rs | 3 +++
scripts/Makefile.build | 3 ++-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 6d637e2fed1b..122ad64880cd 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -37,6 +37,9 @@
#![feature(const_ptr_write)]
#![feature(const_refs_to_cell)]
//
+// Stable since Rust 1.89.0.
+#![feature(generic_arg_infer)]
+//
// Expected to become stable.
#![feature(arbitrary_self_types)]
//
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 5037f4715d74..8fd0e7096bd1 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -312,12 +312,13 @@ $(obj)/%.lst: $(obj)/%.c FORCE
# - Stable since Rust 1.82.0: `feature(asm_const)`,
# `feature(offset_of_nested)`, `feature(raw_ref_op)`.
# - Stable since Rust 1.87.0: `feature(asm_goto)`.
+# - Stable since Rust 1.89.0: `feature(generic_arg_infer)`.
# - Expected to become stable: `feature(arbitrary_self_types)`.
# - To be determined: `feature(used_with_arg)`.
#
# Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on
# the unstable features in use.
-rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,used_with_arg
+rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,used_with_arg,generic_arg_infer
# `--out-dir` is required to avoid temporaries being created by `rustc` in the
# current working directory, which may be not accessible in the out-of-tree
--
2.52.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded`
2026-01-21 7:23 [PATCH v2 0/5] rust: add `register!` macro Alexandre Courbot
2026-01-21 7:23 ` [PATCH v2 1/5] rust: enable the `generic_arg_infer` feature Alexandre Courbot
@ 2026-01-21 7:23 ` Alexandre Courbot
2026-01-21 14:12 ` Gary Guo
2026-01-21 17:49 ` kernel test robot
2026-01-21 7:23 ` [PATCH v2 3/5] rust: num: add `as_bool` method to `Bounded<_, 1>` Alexandre Courbot
` (3 subsequent siblings)
5 siblings, 2 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-21 7:23 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel, Alexandre Courbot
Shifting a `Bounded` left or right changes the number of bits required
to represent the value. Add methods that perform the shift and return a
`Bounded` with the appropriately adjusted bit width.
These methods are particularly useful for bitfield extraction.
Suggested-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/num/bounded.rs | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs
index f870080af8ac..8782535770f1 100644
--- a/rust/kernel/num/bounded.rs
+++ b/rust/kernel/num/bounded.rs
@@ -470,6 +470,46 @@ pub fn cast<U>(self) -> Bounded<U, N>
// `N` bits, and with the same signedness.
Bounded::__new(value)
}
+
+ /// Right-shifts `self` by `SHIFT` and returns the result as a `Bounded<_, { N - SHIFT }>`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::num::Bounded;
+ ///
+ /// let v = Bounded::<u32, 16>::new::<0xff00>();
+ /// let v_shifted: Bounded::<u32, 8> = v.shr::<8, _>();
+ ///
+ /// assert_eq!(v_shifted.get(), 0xff);
+ /// ```
+ pub fn shr<const SHIFT: u32, const RES: u32>(self) -> Bounded<T, RES> {
+ const { assert!(RES >= N - SHIFT) }
+
+ // SAFETY: we shift the value right by `SHIFT`, reducing the number of bits needed to
+ // represent the shifted value by as much, and just asserted that `RES == N - SHIFT`.
+ unsafe { Bounded::__new(self.0 >> SHIFT) }
+ }
+
+ /// Left-shifts `self` by `SHIFT` and returns the result as a `Bounded<_, { N + SHIFT }>`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::num::Bounded;
+ ///
+ /// let v = Bounded::<u32, 8>::new::<0xff>();
+ /// let v_shifted: Bounded::<u32, 16> = v.shl::<8, _>();
+ ///
+ /// assert_eq!(v_shifted.get(), 0xff00);
+ /// ```
+ pub fn shl<const SHIFT: u32, const RES: u32>(self) -> Bounded<T, RES> {
+ const { assert!(RES >= N + SHIFT) }
+
+ // SAFETY: we shift the value left by `SHIFT`, augmenting the number of bits needed to
+ // represent the shifted value by as much, and just asserted that `RES == N + SHIFT`.
+ unsafe { Bounded::__new(self.0 << SHIFT) }
+ }
}
impl<T, const N: u32> Deref for Bounded<T, N>
--
2.52.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 3/5] rust: num: add `as_bool` method to `Bounded<_, 1>`
2026-01-21 7:23 [PATCH v2 0/5] rust: add `register!` macro Alexandre Courbot
2026-01-21 7:23 ` [PATCH v2 1/5] rust: enable the `generic_arg_infer` feature Alexandre Courbot
2026-01-21 7:23 ` [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
@ 2026-01-21 7:23 ` Alexandre Courbot
2026-01-21 14:13 ` Gary Guo
2026-01-21 7:23 ` [PATCH v2 4/5] rust: io: add `register!` macro Alexandre Courbot
` (2 subsequent siblings)
5 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-21 7:23 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel, Alexandre Courbot
Single-bit numbers are typically treated as booleans. There is an
`Into<bool>` implementation for those, but invoking it from contexts
that lack type expectations is not always convenient.
Add an `as_bool` method as a simpler shortcut.
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/num/bounded.rs | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs
index 8782535770f1..8407606f2fa7 100644
--- a/rust/kernel/num/bounded.rs
+++ b/rust/kernel/num/bounded.rs
@@ -1096,3 +1096,24 @@ fn from(value: bool) -> Self {
Self::__new(T::from(value))
}
}
+
+impl<T> Bounded<T, 1>
+where
+ T: Integer + Zeroable,
+{
+ /// Returns the value of this `Bounded` as a `bool`.
+ ///
+ /// This is a shorter way of writing `bool::from(self)`.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::num::Bounded;
+ ///
+ /// assert_eq!(Bounded::<u8, 1>::new::<0>().as_bool(), false);
+ /// assert_eq!(Bounded::<u8, 1>::new::<1>().as_bool(), true);
+ /// ```
+ pub fn as_bool(self) -> bool {
+ self.into()
+ }
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 7:23 [PATCH v2 0/5] rust: add `register!` macro Alexandre Courbot
` (2 preceding siblings ...)
2026-01-21 7:23 ` [PATCH v2 3/5] rust: num: add `as_bool` method to `Bounded<_, 1>` Alexandre Courbot
@ 2026-01-21 7:23 ` Alexandre Courbot
2026-01-21 13:13 ` Alexandre Courbot
` (3 more replies)
2026-01-21 7:23 ` [PATCH FOR REFERENCE v2 5/5] gpu: nova-core: use the kernel " Alexandre Courbot
2026-01-21 9:16 ` [PATCH v2 0/5] rust: add " Dirk Behme
5 siblings, 4 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-21 7:23 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel, Alexandre Courbot
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.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/io.rs | 1 +
rust/kernel/io/register.rs | 1198 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1199 insertions(+)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index a97eb44a9a87..eccaa176b6b9 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..e414aebe4c86
--- /dev/null
+++ b/rust/kernel/io/register.rs
@@ -0,0 +1,1198 @@
+// 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. Please look at the [`bitfield!`] macro for the
+//! complete syntax of fields definitions.
+//!
+//! [`register!`]: kernel::register!
+//! [`bitfield!`]: crate::bitfield!
+
+/// 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<const SIZE: usize, T>(io: &T, offset: usize) -> Self
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>;
+
+ /// Write a value to the given offset in the I/O region.
+ fn write<const SIZE: usize, T>(self, io: &T, offset: usize)
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>;
+}
+
+impl RegisterIo for u8 {
+ #[inline(always)]
+ fn read<const SIZE: usize, T>(io: &T, offset: usize) -> Self
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>,
+ {
+ io.read8(offset)
+ }
+
+ #[inline(always)]
+ fn write<const SIZE: usize, T>(self, io: &T, offset: usize)
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>,
+ {
+ io.write8(self, offset)
+ }
+}
+
+impl RegisterIo for u16 {
+ #[inline(always)]
+ fn read<const SIZE: usize, T>(io: &T, offset: usize) -> Self
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>,
+ {
+ io.read16(offset)
+ }
+
+ #[inline(always)]
+ fn write<const SIZE: usize, T>(self, io: &T, offset: usize)
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>,
+ {
+ io.write16(self, offset)
+ }
+}
+
+impl RegisterIo for u32 {
+ #[inline(always)]
+ fn read<const SIZE: usize, T>(io: &T, offset: usize) -> Self
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>,
+ {
+ io.read32(offset)
+ }
+
+ #[inline(always)]
+ fn write<const SIZE: usize, T>(self, io: &T, offset: usize)
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>,
+ {
+ io.write32(self, offset)
+ }
+}
+
+#[cfg(CONFIG_64BIT)]
+impl RegisterIo for u64 {
+ #[inline(always)]
+ fn read<const SIZE: usize, T>(io: &T, offset: usize) -> Self
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>,
+ {
+ io.read64(offset)
+ }
+
+ #[inline(always)]
+ fn write<const SIZE: usize, T>(self, io: &T, offset: usize)
+ where
+ T: core::ops::Deref<Target = crate::io::Io<SIZE>>,
+ {
+ io.write64(self, offset)
+ }
+}
+
+/// 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.
+///
+/// A register is essentially a [`bitfield!`] with I/O capabilities. The syntax of the `register!`
+/// macro reflects that fact, being essentially identical to that of [`bitfield!`] with the
+/// addition of addressing information after the `@` token.
+///
+/// Example:
+///
+/// ```
+/// use kernel::register;
+///
+/// register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
+/// 7:4 major_revision, "Major revision of the chip";
+/// 3:0 minor_revision, "Minor revision of the chip";
+/// });
+/// ```
+///
+/// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io`
+/// region. 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
+/// use kernel::register;
+/// use kernel::num::Bounded;
+///
+/// # register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
+/// # 7:4 major_revision, "Major revision of the chip";
+/// # 3:0 minor_revision, "Minor revision of the chip";
+/// # });
+/// # fn test(bar: &kernel::io::Io) {
+/// // 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 value back.
+/// boot0
+/// .set_major_revision(Bounded::<u32, _>::new::<3>())
+/// .set_minor_revision(Bounded::<u32, _>::new::<10>())
+/// .write(&bar);
+///
+/// // Or, just read and update the register in a single step:
+/// BOOT_0::update(&bar, |r| r
+/// .set_major_revision(Bounded::<u32, _>::new::<3>())
+/// .set_minor_revision(Bounded::<u32, _>::new::<10>())
+/// );
+/// # }
+/// ```
+///
+/// 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.
+///
+/// Attributes can be applied to the generated struct. The `#[allow(non_camel_case_types)]`
+/// attribute is automatically added since register names typically use SCREAMING_CASE:
+///
+/// ```
+/// use kernel::register;
+///
+/// register! {
+/// pub STATUS(u32) @ 0x00000000, "Status register" {
+/// 0:0 ready, "Device ready flag";
+/// }
+/// }
+/// ```
+///
+/// It is also possible to create an alias register by using the `=> ALIAS` syntax. This is useful
+/// for cases where a register's interpretation depends on the context:
+///
+/// ```
+/// use kernel::register;
+///
+/// register!(pub SCRATCH(u32) @ 0x00000200, "Scratch register" {
+/// 31:0 value, "Raw value";
+/// });
+///
+/// register!(pub SCRATCH_BOOT_STATUS(u32) => SCRATCH, "Boot status of the firmware" {
+/// 0:0 completed, "Whether the firmware has completed booting";
+/// });
+/// ```
+///
+/// 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(bar: &kernel::io::Io) {
+/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
+/// register!(pub CPU_CTL(u32) @ CpuCtlBase[0x10], "CPU core control" {
+/// 0:0 start, "Start the CPU core";
+/// });
+///
+/// // 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!(pub CPU_CTL_ALIAS(u32) => CpuCtlBase[CPU_CTL], "Alias to CPU core control" {
+/// 1:1 alias_start, "Start the aliased CPU core";
+/// });
+///
+/// // 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 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 `update` methods:
+///
+/// ```no_run
+/// use kernel::register;
+///
+/// # fn no_run(bar: &kernel::io::Io) -> Result<(), Error> {
+/// # fn get_scratch_idx() -> usize {
+/// # 0x15
+/// # }
+/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
+/// register!(pub SCRATCH(u32) @ 0x00000080[64], "Scratch registers" {
+/// 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!(pub FIRMWARE_STATUS(u32) => SCRATCH[8], "Firmware exit status code" {
+/// 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!(pub SCRATCH_INTERLEAVED_0(u32) @ 0x000000c0[16 ; 8], "Scratch registers bank 0" {
+/// 31:0 value;
+/// });
+/// register!(pub SCRATCH_INTERLEAVED_1(u32) @ 0x000000c4[16 ; 8], "Scratch registers bank 1" {
+/// 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 no_run(bar: &kernel::io::Io) -> 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!(pub CPU_SCRATCH(u32) @ CpuCtlBase[0x00000080[64]], "Per-CPU scratch registers" {
+/// 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!(pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase[CPU_SCRATCH[8]],
+/// "Per-CPU firmware exit status code" {
+/// 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!(pub CPU_SCRATCH_INTERLEAVED_0(u32) @ CpuCtlBase[0x00000d00[16 ; 8]],
+/// "Scratch registers bank 0" {
+/// 31:0 value;
+/// });
+/// register!(pub CPU_SCRATCH_INTERLEAVED_1(u32) @ CpuCtlBase[0x00000d04[16 ; 8]],
+/// "Scratch registers bank 1" {
+/// 31:0 value;
+/// });
+/// # Ok(())
+/// # }
+/// ```
+/// [`bitfield!`]: crate::bitfield!
+#[macro_export]
+macro_rules! register {
+ // Creates a register at a fixed offset of the MMIO space.
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) $(, $comment)? { $($fields)* }
+ );
+ ::kernel::register!(@io_fixed $name($storage) @ $offset);
+ };
+
+ // Creates an alias register of fixed offset register `alias` with its own fields.
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) $(, $comment)? { $($fields)* }
+ );
+ ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET);
+ };
+
+ // Creates a register at a relative offset from a base address provider.
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ty [ $offset:literal ]
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) $(, $comment)? { $($fields)* }
+ );
+ ::kernel::register!(@io_relative $name($storage) @ $base [ $offset ]);
+ };
+
+ // Creates an alias register of relative offset register `alias` with its own fields.
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ty [ $alias:ident ]
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) $(, $comment)? { $($fields)* }
+ );
+ ::kernel::register!(@io_relative $name($storage) @ $base [ $alias::OFFSET ]);
+ };
+
+ // Creates an array of registers at a fixed offset of the MMIO space.
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ @ $offset:literal [ $size:expr ; $stride:expr ]
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ static_assert!(::core::mem::size_of::<$storage>() <= $stride);
+
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) $(, $comment)? { $($fields)* }
+ );
+ ::kernel::register!(@io_array $name($storage) @ $offset [ $size ; $stride ]);
+ };
+
+ // Shortcut for contiguous array of registers (stride == size of element).
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal [ $size:expr ]
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ $(#[$attr])* $vis $name($storage)
+ @ $offset [ $size ; ::core::mem::size_of::<$storage>() ]
+ $(, $comment)? { $($fields)* }
+ );
+ };
+
+ // Creates an array of registers at a relative offset from a base address provider.
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ @ $base:ty [ $offset:literal [ $size:expr ; $stride:expr ] ]
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ static_assert!(::core::mem::size_of::<$storage>() <= $stride);
+
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) $(, $comment)? { $($fields)* }
+ );
+ ::kernel::register!(
+ @io_relative_array $name($storage) @ $base [ $offset [ $size ; $stride ] ]
+ );
+ };
+
+ // Shortcut for contiguous array of relative registers (stride == size of element).
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ @ $base:ty [ $offset:literal [ $size:expr ] ]
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(
+ $(#[$attr])* $vis $name($storage)
+ @ $base [ $offset [ $size ; ::core::mem::size_of::<$storage>() ] ]
+ $(, $comment)? { $($fields)* }
+ );
+ };
+
+ // Creates an alias of register `idx` of relative array of registers `alias` with its own
+ // fields.
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ => $base:ty [ $alias:ident [ $idx:expr ] ]
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ static_assert!($idx < $alias::SIZE);
+
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) $(, $comment)? { $($fields)* }
+ );
+ ::kernel::register!(
+ @io_relative $name($storage) @ $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.
+ (
+ $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ]
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ static_assert!($idx < $alias::SIZE);
+
+ ::kernel::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) $(, $comment)? { $($fields)* }
+ );
+ ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE);
+ };
+
+ // All rules below are helpers.
+
+ // 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)
+ $(, $comment:literal)? { $($fields:tt)* }
+ ) => {
+ ::kernel::register!(@bitfield_core
+ #[allow(non_camel_case_types)]
+ $(#[$attr])* $vis $name $storage $(, $comment)?
+ );
+ ::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 {
+ pub const OFFSET: usize = $offset;
+
+ /// Read the register from its address in `io`.
+ #[inline(always)]
+ pub fn read<const SIZE: usize, T>(io: &T) -> Self where
+ T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
+ {
+ Self(<$storage as $crate::io::register::RegisterIo>::read(io, $offset))
+ }
+
+ /// Write the value contained in `self` to the register address in `io`.
+ #[inline(always)]
+ pub fn write<const SIZE: usize, T>(self, io: &T) where
+ T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
+ {
+ <$storage as $crate::io::register::RegisterIo>::write(self.0, io, $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<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 ($storage:ty) @ $base:ty [ $offset:expr ]) => {
+ #[allow(dead_code)]
+ impl $name {
+ 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<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::io::register::RegisterBase<$base>,
+ {
+ let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + $name::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<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::io::register::RegisterBase<$base>,
+ {
+ let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + $name::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<const SIZE: usize, T, B, F>(
+ io: &T,
+ base: &B,
+ f: F,
+ ) where
+ T: ::core::ops::Deref<Target = ::kernel::io::Io<SIZE>>,
+ 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) @ $offset:literal [ $size:expr ; $stride:expr ]) => {
+ #[allow(dead_code)]
+ impl $name {
+ pub const OFFSET: usize = $offset;
+ pub const SIZE: usize = $size;
+ pub const STRIDE: usize = $stride;
+
+ /// Read the array register at index `idx` from its address in `io`.
+ #[inline(always)]
+ pub 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);
+
+ 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<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);
+
+ <$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<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 if the
+ /// access was out-of-bounds.
+ #[inline(always)]
+ pub 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(::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<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(::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<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::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) @ $base:ty
+ [ $offset:literal [ $size:expr ; $stride:expr ] ]
+ ) => {
+ #[allow(dead_code)]
+ impl $name {
+ pub const OFFSET: usize = $offset;
+ pub const SIZE: usize = $size;
+ 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<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::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<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::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<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::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<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::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<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::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<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::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 $(, $comment:literal)?) => {
+ $(
+ #[doc=$comment]
+ )?
+ $(#[$attr])*
+ #[repr(transparent)]
+ #[derive(Clone, Copy, PartialEq, Eq)]
+ $vis struct $name($storage);
+
+ #[allow(dead_code)]
+ impl $name {
+ /// Returns the raw value of this bitfield.
+ ///
+ /// This is similar to the [`From`] implementation, but is shorter to invoke in
+ /// most cases.
+ $vis fn as_raw(self) -> $storage {
+ self.0
+ }
+ }
+
+ impl ::core::convert::From<$name> for $storage {
+ fn from(val: $name) -> $storage {
+ val.0
+ }
+ }
+
+ impl ::core::convert::From<$storage> for $name {
+ fn from(val: $storage) -> $name {
+ Self(val)
+ }
+ }
+ };
+
+ // Definitions requiring knowledge of individual fields: private and public field accessors,
+ // and `Debug` and `Default` implementations.
+ (@bitfield_fields $vis:vis $name:ident $storage:ty {
+ $($hi:tt:$lo:tt $field:ident
+ $(?=> $try_into_type:ty)?
+ $(=> $into_type:ty)?
+ $(, $comment:literal)?
+ ;
+ )*
+ }
+ ) => {
+ #[allow(dead_code)]
+ impl $name {
+ $(
+ ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field);
+ ::kernel::register!(@public_field_accessors $vis $name $storage : $hi:$lo $field
+ $(?=> $try_into_type)?
+ $(=> $into_type)?
+ $(, $comment)?
+ );
+ )*
+ }
+
+ ::kernel::register!(@debug $name { $($field;)* });
+ ::kernel::register!(@default $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 } >()
+ }
+
+ 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.get() << SHIFT;
+ self.0 = (self.0 & !MASK) | value;
+
+ self
+ }
+ );
+ };
+
+ // Public accessors for fields infallibly (`=>`) converted to a type.
+ (
+ @public_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident
+ => $into_type:ty $(, $comment:literal)?
+ ) => {
+ ::kernel::macros::paste!(
+
+ $(
+ #[doc="Returns the value of this field:"]
+ #[doc=$comment]
+ )?
+ #[inline(always)]
+ $vis fn $field(self) -> $into_type
+ {
+ self.[<__ $field>]().into()
+ }
+
+ $(
+ #[doc="Sets the value of this field:"]
+ #[doc=$comment]
+ )?
+ #[inline(always)]
+ $vis fn [<set_ $field>](self, value: $into_type) -> Self
+ {
+ self.[<__set_ $field>](value.into())
+ }
+
+ /// Private method, for use in the [`Default`] implementation.
+ fn [<$field _default>]() -> $into_type {
+ Default::default()
+ }
+
+ );
+ };
+
+ // Public accessors for fields fallibly (`?=>`) converted to a type.
+ (
+ @public_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident
+ ?=> $try_into_type:ty $(, $comment:literal)?
+ ) => {
+ ::kernel::macros::paste!(
+
+ $(
+ #[doc="Returns the value of this field:"]
+ #[doc=$comment]
+ )?
+ #[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="Sets the value of this field:"]
+ #[doc=$comment]
+ )?
+ #[inline(always)]
+ $vis fn [<set_ $field>](self, value: $try_into_type) -> Self
+ {
+ self.[<__set_ $field>](value.into())
+ }
+
+ /// Private method, for use in the [`Default`] implementation.
+ fn [<$field _default>]() -> $try_into_type {
+ Default::default()
+ }
+
+ );
+ };
+
+ // Public accessors for fields not converted to a type.
+ (
+ @public_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident
+ $(, $comment:literal)?
+ ) => {
+ ::kernel::macros::paste!(
+
+ $(
+ #[doc="Returns the value of this field:"]
+ #[doc=$comment]
+ )?
+ #[inline(always)]
+ $vis fn $field(self) ->
+ ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>
+ {
+ self.[<__ $field>]()
+ }
+
+ $(
+ #[doc="Sets the value of this field:"]
+ #[doc=$comment]
+ )?
+ #[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="Attempts to set the value of this field:"]
+ #[doc=$comment]
+ )?
+ #[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)?
+ )
+ )
+ }
+
+ /// Private method, for use in the [`Default`] implementation.
+ fn [<$field _default>]() -> ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> {
+ Default::default()
+ }
+
+ );
+ };
+
+ // `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()
+ }
+ }
+ };
+
+ // `Default` implementation.
+ (@default $name:ident { $($field:ident;)* }) => {
+ /// Returns a value for the bitfield 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 = value.[<set_ $field>](Self::[<$field _default>]());
+ )*
+ );
+
+ value
+ }
+ }
+ };
+}
--
2.52.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [PATCH FOR REFERENCE v2 5/5] gpu: nova-core: use the kernel `register!` macro
2026-01-21 7:23 [PATCH v2 0/5] rust: add `register!` macro Alexandre Courbot
` (3 preceding siblings ...)
2026-01-21 7:23 ` [PATCH v2 4/5] rust: io: add `register!` macro Alexandre Courbot
@ 2026-01-21 7:23 ` Alexandre Courbot
2026-01-21 9:16 ` [PATCH v2 0/5] rust: add " Dirk Behme
5 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-21 7:23 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel, Alexandre Courbot
Replace the nova-core internal `register!` macro by the one defined in
the `kernel` crate and remove our own private implementations.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 127 +++---
drivers/gpu/nova-core/falcon/gsp.rs | 10 +-
drivers/gpu/nova-core/falcon/hal/ga102.rs | 5 +-
drivers/gpu/nova-core/falcon/sec2.rs | 13 +-
drivers/gpu/nova-core/fb/hal/ga100.rs | 9 +-
drivers/gpu/nova-core/gpu.rs | 24 +-
drivers/gpu/nova-core/gsp/cmdq.rs | 2 +-
drivers/gpu/nova-core/regs.rs | 265 +++++------
drivers/gpu/nova-core/regs/macros.rs | 721 ------------------------------
9 files changed, 220 insertions(+), 956 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 82c661aef594..2d0c4cbd9e67 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -9,12 +9,16 @@
use kernel::{
device,
dma::DmaAddress,
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ register::RegisterBase, //
+ },
+ num::Bounded,
prelude::*,
sync::aref::ARef,
time::{
- delay::fsleep,
- Delta, //
+ delay::fsleep, //
+ Delta,
},
};
@@ -27,7 +31,6 @@
IntoSafeCast, //
},
regs,
- regs::macros::RegisterBase, //
};
pub(crate) mod gsp;
@@ -35,11 +38,12 @@
pub(crate) mod sec2;
// TODO[FPRI]: Replace with `ToPrimitive`.
-macro_rules! impl_from_enum_to_u8 {
- ($enum_type:ty) => {
- impl From<$enum_type> for u8 {
+// TODO: macro that defines the struct and impls, like for Chipset.
+macro_rules! impl_from_enum_to_bounded {
+ ($enum_type:ty, $length:literal) => {
+ impl From<$enum_type> for Bounded<u32, $length> {
fn from(value: $enum_type) -> Self {
- value as u8
+ Bounded::from_expr(value as u32)
}
}
};
@@ -47,7 +51,6 @@ fn from(value: $enum_type) -> Self {
/// Revision number of a falcon core, used in the [`crate::regs::NV_PFALCON_FALCON_HWCFG1`]
/// register.
-#[repr(u8)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum FalconCoreRev {
#[default]
@@ -59,16 +62,16 @@ pub(crate) enum FalconCoreRev {
Rev6 = 6,
Rev7 = 7,
}
-impl_from_enum_to_u8!(FalconCoreRev);
+impl_from_enum_to_bounded!(FalconCoreRev, 4);
// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconCoreRev {
+impl TryFrom<Bounded<u32, 4>> for FalconCoreRev {
type Error = Error;
- fn try_from(value: u8) -> Result<Self> {
+ fn try_from(value: Bounded<u32, 4>) -> Result<Self> {
use FalconCoreRev::*;
- let rev = match value {
+ let rev = match value.get() {
1 => Rev1,
2 => Rev2,
3 => Rev3,
@@ -85,7 +88,6 @@ fn try_from(value: u8) -> Result<Self> {
/// Revision subversion number of a falcon core, used in the
/// [`crate::regs::NV_PFALCON_FALCON_HWCFG1`] register.
-#[repr(u8)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum FalconCoreRevSubversion {
#[default]
@@ -94,30 +96,27 @@ pub(crate) enum FalconCoreRevSubversion {
Subversion2 = 2,
Subversion3 = 3,
}
-impl_from_enum_to_u8!(FalconCoreRevSubversion);
+impl_from_enum_to_bounded!(FalconCoreRevSubversion, 2);
// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconCoreRevSubversion {
- type Error = Error;
-
- fn try_from(value: u8) -> Result<Self> {
+impl From<Bounded<u32, 2>> for FalconCoreRevSubversion {
+ fn from(value: Bounded<u32, 2>) -> Self {
use FalconCoreRevSubversion::*;
- let sub_version = match value & 0b11 {
+ match value.get() {
0 => Subversion0,
1 => Subversion1,
2 => Subversion2,
3 => Subversion3,
- _ => return Err(EINVAL),
- };
-
- Ok(sub_version)
+ // SAFETY: `value` comes from a 2-bit `Bounded`, and we just checked all possible
+ // values.
+ _ => unsafe { core::hint::unreachable_unchecked() },
+ }
}
}
/// Security model of a falcon core, used in the [`crate::regs::NV_PFALCON_FALCON_HWCFG1`]
/// register.
-#[repr(u8)]
#[derive(Debug, Default, Copy, Clone)]
/// Security mode of the Falcon microprocessor.
///
@@ -138,16 +137,16 @@ pub(crate) enum FalconSecurityModel {
/// Also known as High-Secure, Privilege Level 3 or PL3.
Heavy = 3,
}
-impl_from_enum_to_u8!(FalconSecurityModel);
+impl_from_enum_to_bounded!(FalconSecurityModel, 2);
// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconSecurityModel {
+impl TryFrom<Bounded<u32, 2>> for FalconSecurityModel {
type Error = Error;
- fn try_from(value: u8) -> Result<Self> {
+ fn try_from(value: Bounded<u32, 2>) -> Result<Self> {
use FalconSecurityModel::*;
- let sec_model = match value {
+ let sec_model = match value.get() {
0 => None,
2 => Light,
3 => Heavy,
@@ -160,24 +159,23 @@ fn try_from(value: u8) -> Result<Self> {
/// Signing algorithm for a given firmware, used in the [`crate::regs::NV_PFALCON2_FALCON_MOD_SEL`]
/// register. It is passed to the Falcon Boot ROM (BROM) as a parameter.
-#[repr(u8)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub(crate) enum FalconModSelAlgo {
/// AES.
- #[expect(dead_code)]
Aes = 0,
/// RSA3K.
#[default]
Rsa3k = 1,
}
-impl_from_enum_to_u8!(FalconModSelAlgo);
+impl_from_enum_to_bounded!(FalconModSelAlgo, 8);
// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconModSelAlgo {
+impl TryFrom<Bounded<u32, 8>> for FalconModSelAlgo {
type Error = Error;
- fn try_from(value: u8) -> Result<Self> {
- match value {
+ fn try_from(value: Bounded<u32, 8>) -> Result<Self> {
+ match value.get() {
+ 0 => Ok(FalconModSelAlgo::Aes),
1 => Ok(FalconModSelAlgo::Rsa3k),
_ => Err(EINVAL),
}
@@ -185,21 +183,20 @@ fn try_from(value: u8) -> Result<Self> {
}
/// Valid values for the `size` field of the [`crate::regs::NV_PFALCON_FALCON_DMATRFCMD`] register.
-#[repr(u8)]
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
pub(crate) enum DmaTrfCmdSize {
/// 256 bytes transfer.
#[default]
Size256B = 0x6,
}
-impl_from_enum_to_u8!(DmaTrfCmdSize);
+impl_from_enum_to_bounded!(DmaTrfCmdSize, 3);
// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for DmaTrfCmdSize {
+impl TryFrom<Bounded<u32, 3>> for DmaTrfCmdSize {
type Error = Error;
- fn try_from(value: u8) -> Result<Self> {
- match value {
+ fn try_from(value: Bounded<u32, 3>) -> Result<Self> {
+ match value.get() {
0x6 => Ok(Self::Size256B),
_ => Err(EINVAL),
}
@@ -215,25 +212,17 @@ pub(crate) enum PeregrineCoreSelect {
/// RISC-V core is active.
Riscv = 1,
}
+impl_from_enum_to_bounded!(PeregrineCoreSelect, 1);
-impl From<bool> for PeregrineCoreSelect {
- fn from(value: bool) -> Self {
- match value {
+impl From<Bounded<u32, 1>> for PeregrineCoreSelect {
+ fn from(value: Bounded<u32, 1>) -> Self {
+ match bool::from(value) {
false => PeregrineCoreSelect::Falcon,
true => PeregrineCoreSelect::Riscv,
}
}
}
-impl From<PeregrineCoreSelect> for bool {
- fn from(value: PeregrineCoreSelect) -> Self {
- match value {
- PeregrineCoreSelect::Falcon => false,
- PeregrineCoreSelect::Riscv => true,
- }
- }
-}
-
/// Different types of memory present in a falcon core.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum FalconMem {
@@ -257,14 +246,14 @@ pub(crate) enum FalconFbifTarget {
/// Non-coherent system memory (System DRAM).
NoncoherentSysmem = 2,
}
-impl_from_enum_to_u8!(FalconFbifTarget);
+impl_from_enum_to_bounded!(FalconFbifTarget, 2);
// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconFbifTarget {
+impl TryFrom<Bounded<u32, 2>> for FalconFbifTarget {
type Error = Error;
- fn try_from(value: u8) -> Result<Self> {
- let res = match value {
+ fn try_from(value: Bounded<u32, 2>) -> Result<Self> {
+ let res = match value.get() {
0 => Self::LocalFb,
1 => Self::CoherentSysmem,
2 => Self::NoncoherentSysmem,
@@ -284,26 +273,18 @@ pub(crate) enum FalconFbifMemType {
/// Physical memory addresses.
Physical = 1,
}
+impl_from_enum_to_bounded!(FalconFbifMemType, 1);
/// Conversion from a single-bit register field.
-impl From<bool> for FalconFbifMemType {
- fn from(value: bool) -> Self {
- match value {
+impl From<Bounded<u32, 1>> for FalconFbifMemType {
+ fn from(value: Bounded<u32, 1>) -> Self {
+ match bool::from(value) {
false => Self::Virtual,
true => Self::Physical,
}
}
}
-impl From<FalconFbifMemType> for bool {
- fn from(value: FalconFbifMemType) -> Self {
- match value {
- FalconFbifMemType::Virtual => false,
- FalconFbifMemType::Physical => true,
- }
- }
-}
-
/// Type used to represent the `PFALCON` registers address base for a given falcon engine.
pub(crate) struct PFalconBase(());
@@ -432,7 +413,7 @@ pub(crate) fn reset(&self, bar: &Bar0) -> Result {
self.reset_wait_mem_scrubbing(bar)?;
regs::NV_PFALCON_FALCON_RM::default()
- .set_value(regs::NV_PMC_BOOT_0::read(bar).into())
+ .set_value(regs::NV_PMC_BOOT_0::read(bar).as_raw())
.write(bar, &E::ID);
Ok(())
@@ -501,20 +482,18 @@ fn dma_wr<F: FalconFirmware<Target = E>>(
.set_base((dma_start >> 8) as u32)
.write(bar, &E::ID);
regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
- // CAST: `as u16` is used on purpose since the remaining bits are guaranteed to fit
- // within a `u16`.
- .set_base((dma_start >> 40) as u16)
+ .try_set_base(dma_start >> 40)?
.write(bar, &E::ID);
let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::default()
.set_size(DmaTrfCmdSize::Size256B)
.set_imem(target_mem == FalconMem::Imem)
- .set_sec(if sec { 1 } else { 0 });
+ .set_sec(sec);
for pos in (0..num_transfers).map(|i| i * DMA_LEN) {
// Perform a transfer of size `DMA_LEN`.
regs::NV_PFALCON_FALCON_DMATRFMOFFS::default()
- .set_offs(load_offsets.dst_start + pos)
+ .try_set_offs(load_offsets.dst_start + pos)?
.write(bar, &E::ID);
regs::NV_PFALCON_FALCON_DMATRFFBOFFS::default()
.set_offs(src_start + pos)
diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
index 67edef3636c1..dcdf3962ab0d 100644
--- a/drivers/gpu/nova-core/falcon/gsp.rs
+++ b/drivers/gpu/nova-core/falcon/gsp.rs
@@ -1,7 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
use kernel::{
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ register::RegisterBase, //
+ },
prelude::*,
time::Delta, //
};
@@ -14,10 +17,7 @@
PFalcon2Base,
PFalconBase, //
},
- regs::{
- self,
- macros::RegisterBase, //
- },
+ regs,
};
/// Type specifying the `Gsp` falcon engine. Cannot be instantiated.
diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs
index 69a7a95cac16..72afbd9101cf 100644
--- a/drivers/gpu/nova-core/falcon/hal/ga102.rs
+++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs
@@ -59,7 +59,7 @@ fn signature_reg_fuse_version_ga102(
// `ucode_idx` is guaranteed to be in the range [0..15], making the `read` calls provable valid
// at build-time.
- let reg_fuse_version = if engine_id_mask & 0x0001 != 0 {
+ let reg_fuse_version: u16 = if engine_id_mask & 0x0001 != 0 {
regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::read(bar, ucode_idx).data()
} else if engine_id_mask & 0x0004 != 0 {
regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::read(bar, ucode_idx).data()
@@ -68,7 +68,8 @@ fn signature_reg_fuse_version_ga102(
} else {
dev_err!(dev, "unexpected engine_id_mask {:#x}", engine_id_mask);
return Err(EINVAL);
- };
+ }
+ .into();
// TODO[NUMM]: replace with `last_set_bit` once it lands.
Ok(u16::BITS - reg_fuse_version.leading_zeros())
diff --git a/drivers/gpu/nova-core/falcon/sec2.rs b/drivers/gpu/nova-core/falcon/sec2.rs
index b57d362e576a..5d836e2d17dd 100644
--- a/drivers/gpu/nova-core/falcon/sec2.rs
+++ b/drivers/gpu/nova-core/falcon/sec2.rs
@@ -1,12 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
-use crate::{
- falcon::{
- FalconEngine,
- PFalcon2Base,
- PFalconBase, //
- },
- regs::macros::RegisterBase,
+use kernel::io::register::RegisterBase;
+
+use crate::falcon::{
+ FalconEngine,
+ PFalcon2Base,
+ PFalconBase, //
};
/// Type specifying the `Sec2` falcon engine. Cannot be instantiated.
diff --git a/drivers/gpu/nova-core/fb/hal/ga100.rs b/drivers/gpu/nova-core/fb/hal/ga100.rs
index e0acc41aa7cd..acf46ad0dba1 100644
--- a/drivers/gpu/nova-core/fb/hal/ga100.rs
+++ b/drivers/gpu/nova-core/fb/hal/ga100.rs
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
-use kernel::prelude::*;
+use kernel::{
+ num::Bounded,
+ prelude::*, //
+};
use crate::{
driver::Bar0,
@@ -20,9 +23,7 @@ pub(super) fn read_sysmem_flush_page_ga100(bar: &Bar0) -> u64 {
pub(super) fn write_sysmem_flush_page_ga100(bar: &Bar0, addr: u64) {
regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::default()
- // CAST: `as u32` is used on purpose since the remaining bits are guaranteed to fit within
- // a `u32`.
- .set_adr_63_40((addr >> FLUSH_SYSMEM_ADDR_SHIFT_HI) as u32)
+ .set_adr_63_40(Bounded::from_expr(addr >> FLUSH_SYSMEM_ADDR_SHIFT_HI).cast())
.write(bar);
regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::default()
// CAST: `as u32` is used on purpose since we want to strip the upper bits that have been
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 629c9d2dc994..556b2454b5a6 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -4,6 +4,7 @@
device,
devres::Devres,
fmt,
+ num::Bounded,
pci,
prelude::*,
sync::Arc, //
@@ -135,11 +136,11 @@ pub(crate) enum Architecture {
Ada = 0x19,
}
-impl TryFrom<u8> for Architecture {
+impl TryFrom<Bounded<u32, 6>> for Architecture {
type Error = Error;
- fn try_from(value: u8) -> Result<Self> {
- match value {
+ fn try_from(value: Bounded<u32, 6>) -> Result<Self> {
+ match u8::from(value) {
0x16 => Ok(Self::Turing),
0x17 => Ok(Self::Ampere),
0x19 => Ok(Self::Ada),
@@ -148,23 +149,26 @@ fn try_from(value: u8) -> Result<Self> {
}
}
-impl From<Architecture> for u8 {
+impl From<Architecture> for Bounded<u32, 6> {
fn from(value: Architecture) -> Self {
- // CAST: `Architecture` is `repr(u8)`, so this cast is always lossless.
- value as u8
+ match value {
+ Architecture::Turing => Bounded::<u8, 6>::new::<0x16>().cast(),
+ Architecture::Ampere => Bounded::<u8, 6>::new::<0x17>().cast(),
+ Architecture::Ada => Bounded::<u8, 6>::new::<0x19>().cast(),
+ }
}
}
pub(crate) struct Revision {
- major: u8,
- minor: u8,
+ major: Bounded<u8, 4>,
+ minor: Bounded<u8, 4>,
}
impl From<regs::NV_PMC_BOOT_42> for Revision {
fn from(boot0: regs::NV_PMC_BOOT_42) -> Self {
Self {
- major: boot0.major_revision(),
- minor: boot0.minor_revision(),
+ major: boot0.major_revision().cast(),
+ minor: boot0.minor_revision().cast(),
}
}
}
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 3991ccc0c10f..60a1b0fecf62 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -476,7 +476,7 @@ fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
/// Notifies the GSP that we have updated the command queue pointers.
fn notify_gsp(bar: &Bar0) {
regs::NV_PGSP_QUEUE_HEAD::default()
- .set_address(0)
+ .set_address(0u32)
.write(bar);
}
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 82cc6c0790e5..794401122f06 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -1,12 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
-// Required to retain the original register names used by OpenRM, which are all capital snake case
-// but are mapped to types.
-#![allow(non_camel_case_types)]
-
-#[macro_use]
-pub(crate) mod macros;
-
use kernel::prelude::*;
use crate::{
@@ -29,20 +22,28 @@
num::FromSafeCast,
};
+// All nova-core registers are 32-bit and `pub(crate)`. Wrap the `register!` macro to avoid
+// repeating this information for every register.
+macro_rules! nv_reg {
+ ($(#[$attr:meta])* $name:ident $($tail:tt)*) => {
+ ::kernel::register!($(#[$attr])* pub(crate) $name(u32) $($tail)*);
+ };
+}
+
// PMC
-register!(NV_PMC_BOOT_0 @ 0x00000000, "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";
- 8:8 architecture_1 as u8, "MSB of the architecture";
- 23:20 implementation as u8, "Implementation version of the architecture";
- 28:24 architecture_0 as u8, "Lower bits of the architecture";
+nv_reg!(NV_PMC_BOOT_0 @ 0x00000000, "Basic revision information about the GPU" {
+ 3:0 minor_revision, "Minor revision of the chip";
+ 7:4 major_revision, "Major revision of the chip";
+ 8:8 architecture_1, "MSB of the architecture";
+ 23:20 implementation, "Implementation version of the architecture";
+ 28:24 architecture_0, "Lower bits of the architecture";
});
impl NV_PMC_BOOT_0 {
pub(crate) fn is_older_than_fermi(self) -> bool {
// From https://github.com/NVIDIA/open-gpu-doc/tree/master/manuals :
- const NV_PMC_BOOT_0_ARCHITECTURE_GF100: u8 = 0xc;
+ const NV_PMC_BOOT_0_ARCHITECTURE_GF100: u32 = 0xc;
// Older chips left arch1 zeroed out. That, combined with an arch0 value that is less than
// GF100, means "older than Fermi".
@@ -50,11 +51,11 @@ pub(crate) fn is_older_than_fermi(self) -> bool {
}
}
-register!(NV_PMC_BOOT_42 @ 0x00000a00, "Extended architecture information" {
- 15:12 minor_revision as u8, "Minor revision of the chip";
- 19:16 major_revision as u8, "Major revision of the chip";
- 23:20 implementation as u8, "Implementation version of the architecture";
- 29:24 architecture as u8 ?=> Architecture, "Architecture value";
+nv_reg!(NV_PMC_BOOT_42 @ 0x00000a00, "Extended architecture information" {
+ 15:12 minor_revision, "Minor revision of the chip";
+ 19:16 major_revision, "Major revision of the chip";
+ 23:20 implementation, "Implementation version of the architecture";
+ 29:24 architecture ?=> Architecture, "Architecture value";
});
impl NV_PMC_BOOT_42 {
@@ -89,11 +90,11 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
// PBUS
-register!(NV_PBUS_SW_SCRATCH @ 0x00001400[64] {});
+nv_reg!(NV_PBUS_SW_SCRATCH @ 0x00001400[64] {});
-register!(NV_PBUS_SW_SCRATCH_0E_FRTS_ERR => NV_PBUS_SW_SCRATCH[0xe],
+nv_reg!(NV_PBUS_SW_SCRATCH_0E_FRTS_ERR => NV_PBUS_SW_SCRATCH[0xe],
"scratch register 0xe used as FRTS firmware error code" {
- 31:16 frts_err_code as u16;
+ 31:16 frts_err_code;
});
// PFB
@@ -101,22 +102,22 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
// The following two registers together hold the physical system memory address that is used by the
// GPU to perform sysmembar operations (see `fb::SysmemFlush`).
-register!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR @ 0x00100c10 {
- 31:0 adr_39_08 as u32;
+nv_reg!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR @ 0x00100c10 {
+ 31:0 adr_39_08;
});
-register!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI @ 0x00100c40 {
- 23:0 adr_63_40 as u32;
+nv_reg!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI @ 0x00100c40 {
+ 23:0 adr_63_40;
});
-register!(NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE @ 0x00100ce0 {
- 3:0 lower_scale as u8;
- 9:4 lower_mag as u8;
- 30:30 ecc_mode_enabled as bool;
+nv_reg!(NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE @ 0x00100ce0 {
+ 3:0 lower_scale;
+ 9:4 lower_mag;
+ 30:30 ecc_mode_enabled => bool;
});
-register!(NV_PGSP_QUEUE_HEAD @ 0x00110c00 {
- 31:0 address as u32;
+nv_reg!(NV_PGSP_QUEUE_HEAD @ 0x00110c00 {
+ 31:0 address;
});
impl NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE {
@@ -134,8 +135,8 @@ pub(crate) fn usable_fb_size(self) -> u64 {
}
}
-register!(NV_PFB_PRI_MMU_WPR2_ADDR_LO@0x001fa824 {
- 31:4 lo_val as u32, "Bits 12..40 of the lower (inclusive) bound of the WPR2 region";
+nv_reg!(NV_PFB_PRI_MMU_WPR2_ADDR_LO@0x001fa824 {
+ 31:4 lo_val, "Bits 12..40 of the lower (inclusive) bound of the WPR2 region";
});
impl NV_PFB_PRI_MMU_WPR2_ADDR_LO {
@@ -145,8 +146,8 @@ pub(crate) fn lower_bound(self) -> u64 {
}
}
-register!(NV_PFB_PRI_MMU_WPR2_ADDR_HI@0x001fa828 {
- 31:4 hi_val as u32, "Bits 12..40 of the higher (exclusive) bound of the WPR2 region";
+nv_reg!(NV_PFB_PRI_MMU_WPR2_ADDR_HI@0x001fa828 {
+ 31:4 hi_val, "Bits 12..40 of the higher (exclusive) bound of the WPR2 region";
});
impl NV_PFB_PRI_MMU_WPR2_ADDR_HI {
@@ -169,25 +170,25 @@ pub(crate) fn higher_bound(self) -> u64 {
// Boot Sequence Interface (BSI) register used to determine
// if GSP reload/resume has completed during the boot process.
-register!(NV_PGC6_BSI_SECURE_SCRATCH_14 @ 0x001180f8 {
- 26:26 boot_stage_3_handoff as bool;
+nv_reg!(NV_PGC6_BSI_SECURE_SCRATCH_14 @ 0x001180f8 {
+ 26:26 boot_stage_3_handoff => bool;
});
// Privilege level mask register. It dictates whether the host CPU has privilege to access the
// `PGC6_AON_SECURE_SCRATCH_GROUP_05` register (which it needs to read GFW_BOOT).
-register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK @ 0x00118128,
+nv_reg!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK @ 0x00118128,
"Privilege level mask register" {
- 0:0 read_protection_level0 as bool, "Set after FWSEC lowers its protection level";
+ 0:0 read_protection_level0 => bool, "Set after FWSEC lowers its protection level";
});
// OpenRM defines this as a register array, but doesn't specify its size and only uses its first
// element. Be conservative until we know the actual size or need to use more registers.
-register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05 @ 0x00118234[1] {});
+nv_reg!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05 @ 0x00118234[1] {});
-register!(
+nv_reg!(
NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT => NV_PGC6_AON_SECURE_SCRATCH_GROUP_05[0],
"Scratch group 05 register 0 used as GFW boot progress indicator" {
- 7:0 progress as u8, "Progress of GFW boot (0xff means completed)";
+ 7:0 progress, "Progress of GFW boot (0xff means completed)";
}
);
@@ -198,14 +199,14 @@ pub(crate) fn completed(self) -> bool {
}
}
-register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_42 @ 0x001183a4 {
- 31:0 value as u32;
+nv_reg!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_42 @ 0x001183a4 {
+ 31:0 value;
});
-register!(
+nv_reg!(
NV_USABLE_FB_SIZE_IN_MB => NV_PGC6_AON_SECURE_SCRATCH_GROUP_42,
"Scratch group 42 register used as framebuffer size" {
- 31:0 value as u32, "Usable framebuffer size, in megabytes";
+ 31:0 value, "Usable framebuffer size, in megabytes";
}
);
@@ -218,9 +219,9 @@ pub(crate) fn usable_fb_size(self) -> u64 {
// PDISP
-register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
- 3:3 status_valid as bool, "Set if the `addr` field is valid";
- 31:8 addr as u32, "VGA workspace base address divided by 0x10000";
+nv_reg!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
+ 3:3 status_valid => bool, "Set if the `addr` field is valid";
+ 31:8 addr, "VGA workspace base address divided by 0x10000";
});
impl NV_PDISP_VGA_WORKSPACE_BASE {
@@ -238,47 +239,47 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
pub(crate) const NV_FUSE_OPT_FPF_SIZE: usize = 16;
-register!(NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION @ 0x00824100[NV_FUSE_OPT_FPF_SIZE] {
- 15:0 data as u16;
+nv_reg!(NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION @ 0x00824100[NV_FUSE_OPT_FPF_SIZE] {
+ 15:0 data;
});
-register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140[NV_FUSE_OPT_FPF_SIZE] {
- 15:0 data as u16;
+nv_reg!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140[NV_FUSE_OPT_FPF_SIZE] {
+ 15:0 data;
});
-register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0[NV_FUSE_OPT_FPF_SIZE] {
- 15:0 data as u16;
+nv_reg!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0[NV_FUSE_OPT_FPF_SIZE] {
+ 15:0 data;
});
// PFALCON
-register!(NV_PFALCON_FALCON_IRQSCLR @ PFalconBase[0x00000004] {
- 4:4 halt as bool;
- 6:6 swgen0 as bool;
+nv_reg!(NV_PFALCON_FALCON_IRQSCLR @ PFalconBase[0x00000004] {
+ 4:4 halt => bool;
+ 6:6 swgen0 => bool;
});
-register!(NV_PFALCON_FALCON_MAILBOX0 @ PFalconBase[0x00000040] {
- 31:0 value as u32;
+nv_reg!(NV_PFALCON_FALCON_MAILBOX0 @ PFalconBase[0x00000040] {
+ 31:0 value => u32;
});
-register!(NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase[0x00000044] {
- 31:0 value as u32;
+nv_reg!(NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase[0x00000044] {
+ 31:0 value => u32;
});
// Used to store version information about the firmware running
// on the Falcon processor.
-register!(NV_PFALCON_FALCON_OS @ PFalconBase[0x00000080] {
- 31:0 value as u32;
+nv_reg!(NV_PFALCON_FALCON_OS @ PFalconBase[0x00000080] {
+ 31:0 value => u32;
});
-register!(NV_PFALCON_FALCON_RM @ PFalconBase[0x00000084] {
- 31:0 value as u32;
+nv_reg!(NV_PFALCON_FALCON_RM @ PFalconBase[0x00000084] {
+ 31:0 value => u32;
});
-register!(NV_PFALCON_FALCON_HWCFG2 @ PFalconBase[0x000000f4] {
- 10:10 riscv as bool;
- 12:12 mem_scrubbing as bool, "Set to 0 after memory scrubbing is completed";
- 31:31 reset_ready as bool, "Signal indicating that reset is completed (GA102+)";
+nv_reg!(NV_PFALCON_FALCON_HWCFG2 @ PFalconBase[0x000000f4] {
+ 10:10 riscv => bool;
+ 12:12 mem_scrubbing => bool, "Set to 0 after memory scrubbing is completed";
+ 31:31 reset_ready => bool, "Signal indicating that reset is completed (GA102+)";
});
impl NV_PFALCON_FALCON_HWCFG2 {
@@ -288,107 +289,107 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
}
}
-register!(NV_PFALCON_FALCON_CPUCTL @ PFalconBase[0x00000100] {
- 1:1 startcpu as bool;
- 4:4 halted as bool;
- 6:6 alias_en as bool;
+nv_reg!(NV_PFALCON_FALCON_CPUCTL @ PFalconBase[0x00000100] {
+ 1:1 startcpu => bool;
+ 4:4 halted => bool;
+ 6:6 alias_en => bool;
});
-register!(NV_PFALCON_FALCON_BOOTVEC @ PFalconBase[0x00000104] {
- 31:0 value as u32;
+nv_reg!(NV_PFALCON_FALCON_BOOTVEC @ PFalconBase[0x00000104] {
+ 31:0 value => u32;
});
-register!(NV_PFALCON_FALCON_DMACTL @ PFalconBase[0x0000010c] {
- 0:0 require_ctx as bool;
- 1:1 dmem_scrubbing as bool;
- 2:2 imem_scrubbing as bool;
- 6:3 dmaq_num as u8;
- 7:7 secure_stat as bool;
+nv_reg!(NV_PFALCON_FALCON_DMACTL @ PFalconBase[0x0000010c] {
+ 0:0 require_ctx => bool;
+ 1:1 dmem_scrubbing => bool;
+ 2:2 imem_scrubbing => bool;
+ 6:3 dmaq_num;
+ 7:7 secure_stat => bool;
});
-register!(NV_PFALCON_FALCON_DMATRFBASE @ PFalconBase[0x00000110] {
- 31:0 base as u32;
+nv_reg!(NV_PFALCON_FALCON_DMATRFBASE @ PFalconBase[0x00000110] {
+ 31:0 base => u32;
});
-register!(NV_PFALCON_FALCON_DMATRFMOFFS @ PFalconBase[0x00000114] {
- 23:0 offs as u32;
+nv_reg!(NV_PFALCON_FALCON_DMATRFMOFFS @ PFalconBase[0x00000114] {
+ 23:0 offs;
});
-register!(NV_PFALCON_FALCON_DMATRFCMD @ PFalconBase[0x00000118] {
- 0:0 full as bool;
- 1:1 idle as bool;
- 3:2 sec as u8;
- 4:4 imem as bool;
- 5:5 is_write as bool;
- 10:8 size as u8 ?=> DmaTrfCmdSize;
- 14:12 ctxdma as u8;
- 16:16 set_dmtag as u8;
+nv_reg!(NV_PFALCON_FALCON_DMATRFCMD @ PFalconBase[0x00000118] {
+ 0:0 full => bool;
+ 1:1 idle => bool;
+ 3:2 sec;
+ 4:4 imem => bool;
+ 5:5 is_write => bool;
+ 10:8 size ?=> DmaTrfCmdSize;
+ 14:12 ctxdma;
+ 16:16 set_dmtag;
});
-register!(NV_PFALCON_FALCON_DMATRFFBOFFS @ PFalconBase[0x0000011c] {
- 31:0 offs as u32;
+nv_reg!(NV_PFALCON_FALCON_DMATRFFBOFFS @ PFalconBase[0x0000011c] {
+ 31:0 offs => u32;
});
-register!(NV_PFALCON_FALCON_DMATRFBASE1 @ PFalconBase[0x00000128] {
- 8:0 base as u16;
+nv_reg!(NV_PFALCON_FALCON_DMATRFBASE1 @ PFalconBase[0x00000128] {
+ 8:0 base;
});
-register!(NV_PFALCON_FALCON_HWCFG1 @ PFalconBase[0x0000012c] {
- 3:0 core_rev as u8 ?=> FalconCoreRev, "Core revision";
- 5:4 security_model as u8 ?=> FalconSecurityModel, "Security model";
- 7:6 core_rev_subversion as u8 ?=> FalconCoreRevSubversion, "Core revision subversion";
+nv_reg!(NV_PFALCON_FALCON_HWCFG1 @ PFalconBase[0x0000012c] {
+ 3:0 core_rev ?=> FalconCoreRev, "Core revision";
+ 5:4 security_model ?=> FalconSecurityModel, "Security model";
+ 7:6 core_rev_subversion => FalconCoreRevSubversion, "Core revision subversion";
});
-register!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ PFalconBase[0x00000130] {
- 1:1 startcpu as bool;
+nv_reg!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ PFalconBase[0x00000130] {
+ 1:1 startcpu => bool;
});
// Actually known as `NV_PSEC_FALCON_ENGINE` and `NV_PGSP_FALCON_ENGINE` depending on the falcon
// instance.
-register!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] {
- 0:0 reset as bool;
+nv_reg!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] {
+ 0:0 reset => bool;
});
-register!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600[8]] {
- 1:0 target as u8 ?=> FalconFbifTarget;
- 2:2 mem_type as bool => FalconFbifMemType;
+nv_reg!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600[8]] {
+ 1:0 target ?=> FalconFbifTarget;
+ 2:2 mem_type => FalconFbifMemType;
});
-register!(NV_PFALCON_FBIF_CTL @ PFalconBase[0x00000624] {
- 7:7 allow_phys_no_ctx as bool;
+nv_reg!(NV_PFALCON_FBIF_CTL @ PFalconBase[0x00000624] {
+ 7:7 allow_phys_no_ctx => bool;
});
/* PFALCON2 */
-register!(NV_PFALCON2_FALCON_MOD_SEL @ PFalcon2Base[0x00000180] {
- 7:0 algo as u8 ?=> FalconModSelAlgo;
+nv_reg!(NV_PFALCON2_FALCON_MOD_SEL @ PFalcon2Base[0x00000180] {
+ 7:0 algo ?=> FalconModSelAlgo;
});
-register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ PFalcon2Base[0x00000198] {
- 7:0 ucode_id as u8;
+nv_reg!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ PFalcon2Base[0x00000198] {
+ 7:0 ucode_id => u8;
});
-register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ PFalcon2Base[0x0000019c] {
- 31:0 value as u32;
+nv_reg!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ PFalcon2Base[0x0000019c] {
+ 31:0 value => u32;
});
// OpenRM defines this as a register array, but doesn't specify its size and only uses its first
// element. Be conservative until we know the actual size or need to use more registers.
-register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalcon2Base[0x00000210[1]] {
- 31:0 value as u32;
+nv_reg!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalcon2Base[0x00000210[1]] {
+ 31:0 value => u32;
});
// PRISCV
-register!(NV_PRISCV_RISCV_CPUCTL @ PFalcon2Base[0x00000388] {
- 0:0 halted as bool;
- 7:7 active_stat as bool;
+nv_reg!(NV_PRISCV_RISCV_CPUCTL @ PFalcon2Base[0x00000388] {
+ 0:0 halted => bool;
+ 7:7 active_stat => bool;
});
-register!(NV_PRISCV_RISCV_BCR_CTRL @ PFalcon2Base[0x00000668] {
- 0:0 valid as bool;
- 4:4 core_select as bool => PeregrineCoreSelect;
- 8:8 br_fetch as bool;
+nv_reg!(NV_PRISCV_RISCV_BCR_CTRL @ PFalconBase[0x00001668] {
+ 0:0 valid => bool;
+ 4:4 core_select => PeregrineCoreSelect;
+ 8:8 br_fetch => bool;
});
// The modules below provide registers that are not identical on all supported chips. They should
@@ -397,15 +398,15 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
pub(crate) mod gm107 {
// FUSE
- register!(NV_FUSE_STATUS_OPT_DISPLAY @ 0x00021c04 {
- 0:0 display_disabled as bool;
+ nv_reg!(NV_FUSE_STATUS_OPT_DISPLAY @ 0x00021c04 {
+ 0:0 display_disabled => bool;
});
}
pub(crate) mod ga100 {
// FUSE
- register!(NV_FUSE_STATUS_OPT_DISPLAY @ 0x00820c04 {
- 0:0 display_disabled as bool;
+ nv_reg!(NV_FUSE_STATUS_OPT_DISPLAY @ 0x00820c04 {
+ 0:0 display_disabled => bool;
});
}
diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs
deleted file mode 100644
index fd1a815fa57d..000000000000
--- a/drivers/gpu/nova-core/regs/macros.rs
+++ /dev/null
@@ -1,721 +0,0 @@
-// 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. Please look at the [`bitfield`] macro for the
-//! complete syntax of fields definitions.
-
-/// 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::update(&bar, |r| r.set_major_revision(3).set_minor_revision(10));
-/// ```
-///
-/// 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 `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!(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::update(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 `update` 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)* } ) => {
- bitfield!(pub(crate) struct $name(u32) $(, $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)* } ) => {
- bitfield!(pub(crate) struct $name(u32) $(, $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)* } ) => {
- bitfield!(pub(crate) struct $name(u32) $(, $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)* }) => {
- bitfield!(pub(crate) struct $name(u32) $(, $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);
- bitfield!(pub(crate) struct $name(u32) $(, $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);
- bitfield!(pub(crate) struct $name(u32) $(, $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);
- bitfield!(pub(crate) struct $name(u32) $(, $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);
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_fixed $name @ $alias::OFFSET + $idx * $alias::STRIDE );
- };
-
- // 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 update<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 update<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 update<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_update<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::update(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 update<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_update<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::update(io, base, idx, f))
- } else {
- Err(EINVAL)
- }
- }
- }
- };
-}
--
2.52.0
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [PATCH v2 0/5] rust: add `register!` macro
2026-01-21 7:23 [PATCH v2 0/5] rust: add `register!` macro Alexandre Courbot
` (4 preceding siblings ...)
2026-01-21 7:23 ` [PATCH FOR REFERENCE v2 5/5] gpu: nova-core: use the kernel " Alexandre Courbot
@ 2026-01-21 9:16 ` Dirk Behme
5 siblings, 0 replies; 25+ messages in thread
From: Dirk Behme @ 2026-01-21 9:16 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Steven Price,
rust-for-linux, linux-kernel
On 21/01/2026 08:23, Alexandre Courbot wrote:
> Add an improved version of nova-core's `register!` macro to the `kernel`
> crate for all drivers to use.
>
> This is not a direct move from `nova-core`, but rather a new
> introduction to facilitate code review and introduce features that are
> missing in the nova-core versions. Differences notably include:
>
> - Use of `Bounded` to prevent any data truncation when manipulating
> bitfields,
> - Extended documentation,
> - Doccomments now build and run,
> - Supports visibility and different storage sizes.
>
> The `bitfield!` macro of nova-core has for the moment been wrapped into
> `register!`, as a set of private rules, to allow `register!` to be
> merged first while `bitfield!` undergoes review during the next cycle.
> Thus, some of the code from v1 (including `bitfield!`'s doccomments and
> Kunit tests) are kept for later.
>
> The first patch enables the `generic_arg_infer` feature, which is
> required for generic type inference and used in subsequent patches. This
> feature is stable since rustc 1.89.
>
> The second patch adds `shr` and `shl` methods to `Bounded`. These were
> suggested by Alice during LPC as a way to avoid the use of the
> controversial `Bounded::from_expr` in both the bitfield macro and the
> Nova code. Third patch adds another convenience method to obtain a
> `bool` from single-bit `Bounded`s.
>
> Patch 4 adds the `register!` macro. Since it falls under
> `rust/kernel/io` it is covered by the corresponding MAINTAINERS entry so
> I refrained from adding one just for this file, especially since the
> bitfield-related parts will eventually move and what remains is very
> tightly related to I/O.
>
> The last patch illustrates how this macro is used by converting
> nova-core to use it, and removing the local implementation. This patch
> is to be merged one cycle after the other patches.
>
> Previous work to extract the macros was done in the partially-merged
> [1]. The current series can be considered a reboot with more features
> and the `bitfield!` macro being postponed.
>
> This patchset is based on `driver-core-next`.
>
> Note that it also need `rust-fixes` to avoid an `unused_unsafe` warning.
>
> [1] https://lore.kernel.org/all/20251003154748.1687160-1-joelagnelf@nvidia.com/
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
I gave this v2 a try on top of my 6.19.0-rc3 and it compiles now using
rustc 1.81.0. I adapted my aarch64 timer TMU test using register!() and
it works as expected. With this:
Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
Many thanks!
Dirk
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 1/5] rust: enable the `generic_arg_infer` feature
2026-01-21 7:23 ` [PATCH v2 1/5] rust: enable the `generic_arg_infer` feature Alexandre Courbot
@ 2026-01-21 11:48 ` Gary Guo
0 siblings, 0 replies; 25+ messages in thread
From: Gary Guo @ 2026-01-21 11:48 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel
On Wed Jan 21, 2026 at 7:23 AM GMT, Alexandre Courbot wrote:
> This feature is stable since 1.89, and used in subsequent patches.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> rust/kernel/lib.rs | 3 +++
> scripts/Makefile.build | 3 ++-
> 2 files changed, 5 insertions(+), 1 deletion(-)
>
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index 6d637e2fed1b..122ad64880cd 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -37,6 +37,9 @@
> #![feature(const_ptr_write)]
> #![feature(const_refs_to_cell)]
> //
> +// Stable since Rust 1.89.0.
> +#![feature(generic_arg_infer)]
> +//
> // Expected to become stable.
> #![feature(arbitrary_self_types)]
> //
> diff --git a/scripts/Makefile.build b/scripts/Makefile.build
> index 5037f4715d74..8fd0e7096bd1 100644
> --- a/scripts/Makefile.build
> +++ b/scripts/Makefile.build
> @@ -312,12 +312,13 @@ $(obj)/%.lst: $(obj)/%.c FORCE
> # - Stable since Rust 1.82.0: `feature(asm_const)`,
> # `feature(offset_of_nested)`, `feature(raw_ref_op)`.
> # - Stable since Rust 1.87.0: `feature(asm_goto)`.
> +# - Stable since Rust 1.89.0: `feature(generic_arg_infer)`.
> # - Expected to become stable: `feature(arbitrary_self_types)`.
> # - To be determined: `feature(used_with_arg)`.
> #
> # Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on
> # the unstable features in use.
> -rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,used_with_arg
> +rust_allowed_features := asm_const,asm_goto,arbitrary_self_types,lint_reasons,offset_of_nested,raw_ref_op,used_with_arg,generic_arg_infer
This is an alphabetically sorted list.
Best,
Gary
>
> # `--out-dir` is required to avoid temporaries being created by `rustc` in the
> # current working directory, which may be not accessible in the out-of-tree
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 7:23 ` [PATCH v2 4/5] rust: io: add `register!` macro Alexandre Courbot
@ 2026-01-21 13:13 ` Alexandre Courbot
2026-01-21 14:15 ` Gary Guo
` (2 subsequent siblings)
3 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-21 13:13 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel, Alexandre Courbot
On Wed Jan 21, 2026 at 4:23 PM JST, 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.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> rust/kernel/io.rs | 1 +
> rust/kernel/io/register.rs | 1198 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 1199 insertions(+)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index a97eb44a9a87..eccaa176b6b9 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..e414aebe4c86
> --- /dev/null
> +++ b/rust/kernel/io/register.rs
> @@ -0,0 +1,1198 @@
> +// 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. Please look at the [`bitfield!`] macro for the
> +//! complete syntax of fields definitions.
> +//!
> +//! [`register!`]: kernel::register!
> +//! [`bitfield!`]: crate::bitfield!
Oops, I should remove these references to `bitfield!`...
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded`
2026-01-21 7:23 ` [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
@ 2026-01-21 14:12 ` Gary Guo
2026-01-26 3:23 ` Alexandre Courbot
2026-01-21 17:49 ` kernel test robot
1 sibling, 1 reply; 25+ messages in thread
From: Gary Guo @ 2026-01-21 14:12 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel
On Wed Jan 21, 2026 at 7:23 AM GMT, Alexandre Courbot wrote:
> Shifting a `Bounded` left or right changes the number of bits required
> to represent the value. Add methods that perform the shift and return a
> `Bounded` with the appropriately adjusted bit width.
>
> These methods are particularly useful for bitfield extraction.
>
> Suggested-by: Alice Ryhl <aliceryhl@google.com>
> Reviewed-by: Alice Ryhl <aliceryhl@google.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> rust/kernel/num/bounded.rs | 40 ++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 40 insertions(+)
>
> diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs
> index f870080af8ac..8782535770f1 100644
> --- a/rust/kernel/num/bounded.rs
> +++ b/rust/kernel/num/bounded.rs
> @@ -470,6 +470,46 @@ pub fn cast<U>(self) -> Bounded<U, N>
> // `N` bits, and with the same signedness.
> Bounded::__new(value)
This patch doesn't apply cleanly. Looks like you send it from the wrong base
commit.
The __new call here is still safe while your code has `unsafe {}` in it.
> }
> +
> + /// Right-shifts `self` by `SHIFT` and returns the result as a `Bounded<_, { N - SHIFT }>`.
The returned bound can be larger given the assert below?
> + ///
> + /// # Examples
> + ///
> + /// ```
> + /// use kernel::num::Bounded;
> + ///
> + /// let v = Bounded::<u32, 16>::new::<0xff00>();
> + /// let v_shifted: Bounded::<u32, 8> = v.shr::<8, _>();
> + ///
> + /// assert_eq!(v_shifted.get(), 0xff);
> + /// ```
> + pub fn shr<const SHIFT: u32, const RES: u32>(self) -> Bounded<T, RES> {
> + const { assert!(RES >= N - SHIFT) }
Quite surprised that rustfmt didn't ask for the block to be expanded into
multiple lines.
I think we probably want to create a new assert macro for this pattern
(obviously this doesn't block this patch).
> +
> + // SAFETY: we shift the value right by `SHIFT`, reducing the number of bits needed to
> + // represent the shifted value by as much, and just asserted that `RES == N - SHIFT`.
> + unsafe { Bounded::__new(self.0 >> SHIFT) }
> + }
> +
> + /// Left-shifts `self` by `SHIFT` and returns the result as a `Bounded<_, { N + SHIFT }>`.
Same, the bound can be actually larger.
Best,
Gary
> + ///
> + /// # Examples
> + ///
> + /// ```
> + /// use kernel::num::Bounded;
> + ///
> + /// let v = Bounded::<u32, 8>::new::<0xff>();
> + /// let v_shifted: Bounded::<u32, 16> = v.shl::<8, _>();
> + ///
> + /// assert_eq!(v_shifted.get(), 0xff00);
> + /// ```
> + pub fn shl<const SHIFT: u32, const RES: u32>(self) -> Bounded<T, RES> {
> + const { assert!(RES >= N + SHIFT) }
> +
> + // SAFETY: we shift the value left by `SHIFT`, augmenting the number of bits needed to
> + // represent the shifted value by as much, and just asserted that `RES == N + SHIFT`.
> + unsafe { Bounded::__new(self.0 << SHIFT) }
> + }
> }
>
> impl<T, const N: u32> Deref for Bounded<T, N>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 3/5] rust: num: add `as_bool` method to `Bounded<_, 1>`
2026-01-21 7:23 ` [PATCH v2 3/5] rust: num: add `as_bool` method to `Bounded<_, 1>` Alexandre Courbot
@ 2026-01-21 14:13 ` Gary Guo
0 siblings, 0 replies; 25+ messages in thread
From: Gary Guo @ 2026-01-21 14:13 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel
On Wed Jan 21, 2026 at 7:23 AM GMT, Alexandre Courbot wrote:
> Single-bit numbers are typically treated as booleans. There is an
> `Into<bool>` implementation for those, but invoking it from contexts
> that lack type expectations is not always convenient.
>
> Add an `as_bool` method as a simpler shortcut.
>
> Reviewed-by: Alice Ryhl <aliceryhl@google.com>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
> ---
> rust/kernel/num/bounded.rs | 21 +++++++++++++++++++++
> 1 file changed, 21 insertions(+)
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 7:23 ` [PATCH v2 4/5] rust: io: add `register!` macro Alexandre Courbot
2026-01-21 13:13 ` Alexandre Courbot
@ 2026-01-21 14:15 ` Gary Guo
2026-01-26 3:23 ` Alexandre Courbot
2026-01-21 14:50 ` Gary Guo
2026-01-21 21:39 ` kernel test robot
3 siblings, 1 reply; 25+ messages in thread
From: Gary Guo @ 2026-01-21 14:15 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel
On Wed Jan 21, 2026 at 7:23 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.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
It looks like this is still based on the current Io impl. I think with Zhi's new
Io trait this will look quite different.
Best,
Gary
> ---
> rust/kernel/io.rs | 1 +
> rust/kernel/io/register.rs | 1198 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 1199 insertions(+)
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 7:23 ` [PATCH v2 4/5] rust: io: add `register!` macro Alexandre Courbot
2026-01-21 13:13 ` Alexandre Courbot
2026-01-21 14:15 ` Gary Guo
@ 2026-01-21 14:50 ` Gary Guo
2026-01-21 16:15 ` Miguel Ojeda
2026-01-26 3:24 ` Alexandre Courbot
2026-01-21 21:39 ` kernel test robot
3 siblings, 2 replies; 25+ messages in thread
From: Gary Guo @ 2026-01-21 14:50 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross
Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel
On Wed Jan 21, 2026 at 7:23 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.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> rust/kernel/io.rs | 1 +
> rust/kernel/io/register.rs | 1198 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 1199 insertions(+)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index a97eb44a9a87..eccaa176b6b9 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..e414aebe4c86
> --- /dev/null
> +++ b/rust/kernel/io/register.rs
> @@ -0,0 +1,1198 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/// 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.
> +///
> +/// A register is essentially a [`bitfield!`] with I/O capabilities. The syntax of the `register!`
> +/// macro reflects that fact, being essentially identical to that of [`bitfield!`] with the
> +/// addition of addressing information after the `@` token.
> +///
> +/// Example:
> +///
> +/// ```
> +/// use kernel::register;
> +///
> +/// register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
> +/// 7:4 major_revision, "Major revision of the chip";
> +/// 3:0 minor_revision, "Minor revision of the chip";
> +/// });
The comment is inserted as doc comment, but it uses the string syntax.
I guess the idea is that you want write everything in a single line so you can
visually align the fields? I think it
looks fine on the fields, but the same-line documentation of the type itself
looks a bit off.
Something like this will definitely feel much more Rusty:
register!(
/// Basic revision information about the chip.
pub struct BOOT_0(u32) @ 0x00000100 {
/// Major revision of the chip.
major_version: [7:4],
/// Minor revision of the chip.
///
/// This would also allow you easily expand the documentation into
/// multiple lines!
///
/// Perhaps useful to document some quirks about the register!
/// I know currently registers and their fields are very underdocumented
/// and they probably don't need multiple lines, but I hope that'll not
/// true in the future and we would have detailed docs in the driver --
/// in which case visually aligning becomes impossible anyway.
minor_version: [3:0],
// ^~ closer to the variable syntax in Rust
// ^~ I keep the hi:lo syntax which I suppose is to reflect Verilog.
}
)
Another top-level question I have is whether we should define multiple registers
(perhaps all) in one macro invocation. That's the strategy of `tock-registers`
crate which is widely used in embedded Rust.
(Although, they use a completely different strategy in generating register
mapping; all registers are becoming fields in the same struct).
> +/// ```
> +///
> +/// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io`
> +/// region. 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
> +/// use kernel::register;
> +/// use kernel::num::Bounded;
> +///
> +/// # register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
> +/// # 7:4 major_revision, "Major revision of the chip";
> +/// # 3:0 minor_revision, "Minor revision of the chip";
> +/// # });
> +/// # fn test(bar: &kernel::io::Io) {
> +/// // 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 value back.
> +/// boot0
> +/// .set_major_revision(Bounded::<u32, _>::new::<3>())
> +/// .set_minor_revision(Bounded::<u32, _>::new::<10>())
This looks very verbose...
> +/// .write(&bar);
> +///
> +/// // Or, just read and update the register in a single step:
> +/// BOOT_0::update(&bar, |r| r
> +/// .set_major_revision(Bounded::<u32, _>::new::<3>())
> +/// .set_minor_revision(Bounded::<u32, _>::new::<10>())
> +/// );
> +/// # }
> +/// ```
> +///
> +/// 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.
> +///
> +/// Attributes can be applied to the generated struct. The `#[allow(non_camel_case_types)]`
> +/// attribute is automatically added since register names typically use SCREAMING_CASE:
> +///
> +/// ```
> +/// use kernel::register;
> +///
> +/// register! {
> +/// pub STATUS(u32) @ 0x00000000, "Status register" {
> +/// 0:0 ready, "Device ready flag";
> +/// }
> +/// }
> +/// ```
> +///
> +/// It is also possible to create an alias register by using the `=> ALIAS` syntax. This is useful
> +/// for cases where a register's interpretation depends on the context:
> +///
> +/// ```
> +/// use kernel::register;
> +///
> +/// register!(pub SCRATCH(u32) @ 0x00000200, "Scratch register" {
> +/// 31:0 value, "Raw value";
> +/// });
> +///
> +/// register!(pub SCRATCH_BOOT_STATUS(u32) => SCRATCH, "Boot status of the firmware" {
How about "as"?
register!(pub SCRATCH_BOOT_STATUS(u32) as SCRATCH);
> +/// 0:0 completed, "Whether the firmware has completed booting";
> +/// });
> +/// ```
> +///
> +/// 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(bar: &kernel::io::Io) {
> +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
> +/// register!(pub CPU_CTL(u32) @ CpuCtlBase[0x10], "CPU core control" {
Maybe `CpuCtlBase + 0x10`? I think this means the offset is going to be differ
by 0x10, not that it's going to be the 17th register?
> +/// 0:0 start, "Start the CPU core";
> +/// });
> +///
> +/// // 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!(pub CPU_CTL_ALIAS(u32) => CpuCtlBase[CPU_CTL], "Alias to CPU core control" {
> +/// 1:1 alias_start, "Start the aliased CPU core";
> +/// });
> +///
> +/// // 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 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 `update` methods:
> +///
> +/// ```no_run
> +/// use kernel::register;
> +///
> +/// # fn no_run(bar: &kernel::io::Io) -> Result<(), Error> {
> +/// # fn get_scratch_idx() -> usize {
> +/// # 0x15
> +/// # }
> +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
> +/// register!(pub SCRATCH(u32) @ 0x00000080[64], "Scratch registers" {
This syntax is way to close to the base syntax above.
Perhaps `pub SCRATCH([u32; 64]) @ 0x00000080` would make more sense?
> +/// 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!(pub FIRMWARE_STATUS(u32) => SCRATCH[8], "Firmware exit status code" {
> +/// 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!(pub SCRATCH_INTERLEAVED_0(u32) @ 0x000000c0[16 ; 8], "Scratch registers bank 0" {
> +/// 31:0 value;
> +/// });
> +/// register!(pub SCRATCH_INTERLEAVED_1(u32) @ 0x000000c4[16 ; 8], "Scratch registers bank 1" {
> +/// 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 no_run(bar: &kernel::io::Io) -> 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!(pub CPU_SCRATCH(u32) @ CpuCtlBase[0x00000080[64]], "Per-CPU scratch registers" {
Ah... I don't like this.
> +/// 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!(pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase[CPU_SCRATCH[8]],
> +/// "Per-CPU firmware exit status code" {
> +/// 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!(pub CPU_SCRATCH_INTERLEAVED_0(u32) @ CpuCtlBase[0x00000d00[16 ; 8]],
For striding, SystemRDL uses syntax like this:
type name[len] @ addr += stride;
which I think is better as this won't be confused with array length syntax in
Rust.
Crazy idea: is it possible to just use SystemRDL (maybe via a proc macro).
Best,
Gary
> +/// "Scratch registers bank 0" {
> +/// 31:0 value;
> +/// });
> +/// register!(pub CPU_SCRATCH_INTERLEAVED_1(u32) @ CpuCtlBase[0x00000d04[16 ; 8]],
> +/// "Scratch registers bank 1" {
> +/// 31:0 value;
> +/// });
> +/// # Ok(())
> +/// # }
> +/// ```
> +/// [`bitfield!`]: crate::bitfield!
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 14:50 ` Gary Guo
@ 2026-01-21 16:15 ` Miguel Ojeda
2026-01-26 4:31 ` John Hubbard
2026-01-26 3:24 ` Alexandre Courbot
1 sibling, 1 reply; 25+ messages in thread
From: Miguel Ojeda @ 2026-01-21 16:15 UTC (permalink / raw)
To: Gary Guo
Cc: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Yury Norov, John Hubbard,
Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer,
Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux,
linux-kernel
On Wed, Jan 21, 2026 at 3:58 PM Gary Guo <gary@garyguo.net> wrote:
>
> Another top-level question I have is whether we should define multiple registers
> (perhaps all) in one macro invocation. That's the strategy of `tock-registers`
> crate which is widely used in embedded Rust.
Yeah, I suggested a multi-register one for something similar in 2021
and even tackling extra automatic codegen there, like e.g. adjusting
the value as needed to get to the proper unit from a sensor.
Not sure how useful the multi-register part in practice it would be,
but it would allow to relate several registers together if needed (is
that even common nowadays?). Or perhaps things like the
interleaving/striding example in the patch could be done "at once".
https://lore.kernel.org/all/CANiq72mAYE6Wh8AikfuuNm8Asr4+c90_bYbj8XdBGJ1Pb4kzvQ@mail.gmail.com/
Cheers,
Miguel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded`
2026-01-21 7:23 ` [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
2026-01-21 14:12 ` Gary Guo
@ 2026-01-21 17:49 ` kernel test robot
1 sibling, 0 replies; 25+ messages in thread
From: kernel test robot @ 2026-01-21 17:49 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross
Cc: llvm, oe-kbuild-all, Yury Norov, John Hubbard, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel,
Alexandre Courbot
Hi Alexandre,
kernel test robot noticed the following build warnings:
[auto build test WARNING on c259cd7ea3c9ad369c473ba2385d82e3432088b1]
url: https://github.com/intel-lab-lkp/linux/commits/Alexandre-Courbot/rust-enable-the-generic_arg_infer-feature/20260121-152936
base: c259cd7ea3c9ad369c473ba2385d82e3432088b1
patch link: https://lore.kernel.org/r/20260121-register-v2-2-79d9b8d5e36a%40nvidia.com
patch subject: [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded`
config: x86_64-randconfig-071-20260121 (https://download.01.org/0day-ci/archive/20260122/202601220119.LQY1LLC8-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
rustc: rustc 1.88.0 (6b00bc388 2025-06-23)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260122/202601220119.LQY1LLC8-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601220119.LQY1LLC8-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> warning: unnecessary `unsafe` block
--> rust/kernel/num/bounded.rs:491:9
|
491 | unsafe { Bounded::__new(self.0 >> SHIFT) }
| ^^^^^^ unnecessary `unsafe` block
|
= note: `#[warn(unused_unsafe)]` on by default
--
>> warning: unnecessary `unsafe` block
--> rust/kernel/num/bounded.rs:511:9
|
511 | unsafe { Bounded::__new(self.0 << SHIFT) }
| ^^^^^^ unnecessary `unsafe` block
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 7:23 ` [PATCH v2 4/5] rust: io: add `register!` macro Alexandre Courbot
` (2 preceding siblings ...)
2026-01-21 14:50 ` Gary Guo
@ 2026-01-21 21:39 ` kernel test robot
3 siblings, 0 replies; 25+ messages in thread
From: kernel test robot @ 2026-01-21 21:39 UTC (permalink / raw)
To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross
Cc: oe-kbuild-all, Yury Norov, John Hubbard, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel,
Alexandre Courbot
Hi Alexandre,
kernel test robot noticed the following build warnings:
[auto build test WARNING on c259cd7ea3c9ad369c473ba2385d82e3432088b1]
url: https://github.com/intel-lab-lkp/linux/commits/Alexandre-Courbot/rust-enable-the-generic_arg_infer-feature/20260121-152936
base: c259cd7ea3c9ad369c473ba2385d82e3432088b1
patch link: https://lore.kernel.org/r/20260121-register-v2-4-79d9b8d5e36a%40nvidia.com
patch subject: [PATCH v2 4/5] rust: io: add `register!` macro
config: x86_64-rhel-9.4-rust (https://download.01.org/0day-ci/archive/20260121/202601212253.xJmIu2oa-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
rustc: rustc 1.88.0 (6b00bc388 2025-06-23)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260121/202601212253.xJmIu2oa-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601212253.xJmIu2oa-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> warning: unresolved link to `crate::bitfield`
--> rust/kernel/io/register.rs:15:20
|
15 | //! [`bitfield!`]: crate::bitfield!
| ^^^^^^^^^^^^^^^^ no item named `bitfield` in module `kernel`
|
= note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default
--
>> warning: unresolved link to `crate::bitfield`
--> rust/kernel/io/register.rs:411:20
|
411 | /// [`bitfield!`]: crate::bitfield!
| ^^^^^^^^^^^^^^^^ no item named `bitfield` in module `kernel`
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded`
2026-01-21 14:12 ` Gary Guo
@ 2026-01-26 3:23 ` Alexandre Courbot
2026-01-26 3:28 ` Miguel Ojeda
0 siblings, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-26 3:23 UTC (permalink / raw)
To: Gary Guo
Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Yury Norov, John Hubbard, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel
On Wed Jan 21, 2026 at 11:12 PM JST, Gary Guo wrote:
> On Wed Jan 21, 2026 at 7:23 AM GMT, Alexandre Courbot wrote:
>> Shifting a `Bounded` left or right changes the number of bits required
>> to represent the value. Add methods that perform the shift and return a
>> `Bounded` with the appropriately adjusted bit width.
>>
>> These methods are particularly useful for bitfield extraction.
>>
>> Suggested-by: Alice Ryhl <aliceryhl@google.com>
>> Reviewed-by: Alice Ryhl <aliceryhl@google.com>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>> rust/kernel/num/bounded.rs | 40 ++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 40 insertions(+)
>>
>> diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs
>> index f870080af8ac..8782535770f1 100644
>> --- a/rust/kernel/num/bounded.rs
>> +++ b/rust/kernel/num/bounded.rs
>> @@ -470,6 +470,46 @@ pub fn cast<U>(self) -> Bounded<U, N>
>> // `N` bits, and with the same signedness.
>> Bounded::__new(value)
>
> This patch doesn't apply cleanly. Looks like you send it from the wrong base
> commit.
>
> The __new call here is still safe while your code has `unsafe {}` in it.
The cover letter mentions that the base for this patchset is
`driver-core-next`, since `register!` lands in `kernel/rust/io` and we
need to rebase on top of Zhi's Io patchset, which will land there as
well.
This indeed creates a minor conflict with `rust-next` as it includes a
couple of patches for `bounded.rs`.
>
>> }
>> +
>> + /// Right-shifts `self` by `SHIFT` and returns the result as a `Bounded<_, { N - SHIFT }>`.
>
> The returned bound can be larger given the assert below?
Indeed, I forgot to update this comment - thanks!
>
>> + ///
>> + /// # Examples
>> + ///
>> + /// ```
>> + /// use kernel::num::Bounded;
>> + ///
>> + /// let v = Bounded::<u32, 16>::new::<0xff00>();
>> + /// let v_shifted: Bounded::<u32, 8> = v.shr::<8, _>();
>> + ///
>> + /// assert_eq!(v_shifted.get(), 0xff);
>> + /// ```
>> + pub fn shr<const SHIFT: u32, const RES: u32>(self) -> Bounded<T, RES> {
>> + const { assert!(RES >= N - SHIFT) }
>
> Quite surprised that rustfmt didn't ask for the block to be expanded into
> multiple lines.
>
> I think we probably want to create a new assert macro for this pattern
> (obviously this doesn't block this patch).
>
>> +
>> + // SAFETY: we shift the value right by `SHIFT`, reducing the number of bits needed to
>> + // represent the shifted value by as much, and just asserted that `RES == N - SHIFT`.
>> + unsafe { Bounded::__new(self.0 >> SHIFT) }
>> + }
>> +
>> + /// Left-shifts `self` by `SHIFT` and returns the result as a `Bounded<_, { N + SHIFT }>`.
>
> Same, the bound can be actually larger.
Fixed, thanks.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 14:15 ` Gary Guo
@ 2026-01-26 3:23 ` Alexandre Courbot
0 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-26 3:23 UTC (permalink / raw)
To: Gary Guo
Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Yury Norov, John Hubbard, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel
On Wed Jan 21, 2026 at 11:15 PM JST, Gary Guo wrote:
> On Wed Jan 21, 2026 at 7:23 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.
>>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>
> It looks like this is still based on the current Io impl. I think with Zhi's new
> Io trait this will look quite different.
Correct. The next version will be rebased on top of it now that it is
merged into `driver-core-next`.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 14:50 ` Gary Guo
2026-01-21 16:15 ` Miguel Ojeda
@ 2026-01-26 3:24 ` Alexandre Courbot
2026-01-26 6:57 ` Alexandre Courbot
2026-01-26 7:45 ` Alexandre Courbot
1 sibling, 2 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-26 3:24 UTC (permalink / raw)
To: Gary Guo
Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Yury Norov, John Hubbard, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel
On Wed Jan 21, 2026 at 11:50 PM JST, Gary Guo wrote:
> On Wed Jan 21, 2026 at 7:23 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.
>>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>> rust/kernel/io.rs | 1 +
>> rust/kernel/io/register.rs | 1198 ++++++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 1199 insertions(+)
>>
>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>> index a97eb44a9a87..eccaa176b6b9 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..e414aebe4c86
>> --- /dev/null
>> +++ b/rust/kernel/io/register.rs
>> @@ -0,0 +1,1198 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +/// 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.
>> +///
>> +/// A register is essentially a [`bitfield!`] with I/O capabilities. The syntax of the `register!`
>> +/// macro reflects that fact, being essentially identical to that of [`bitfield!`] with the
>> +/// addition of addressing information after the `@` token.
>> +///
>> +/// Example:
>> +///
>> +/// ```
>> +/// use kernel::register;
>> +///
>> +/// register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
>> +/// 7:4 major_revision, "Major revision of the chip";
>> +/// 3:0 minor_revision, "Minor revision of the chip";
>> +/// });
>
> The comment is inserted as doc comment, but it uses the string syntax.
>
> I guess the idea is that you want write everything in a single line so you can
> visually align the fields? I think it
> looks fine on the fields, but the same-line documentation of the type itself
> looks a bit off.
>
> Something like this will definitely feel much more Rusty:
>
> register!(
> /// Basic revision information about the chip.
> pub struct BOOT_0(u32) @ 0x00000100 {
> /// Major revision of the chip.
> major_version: [7:4],
> /// Minor revision of the chip.
> ///
> /// This would also allow you easily expand the documentation into
> /// multiple lines!
> ///
> /// Perhaps useful to document some quirks about the register!
> /// I know currently registers and their fields are very underdocumented
> /// and they probably don't need multiple lines, but I hope that'll not
> /// true in the future and we would have detailed docs in the driver --
> /// in which case visually aligning becomes impossible anyway.
> minor_version: [3:0],
> // ^~ closer to the variable syntax in Rust
> // ^~ I keep the hi:lo syntax which I suppose is to reflect Verilog.
> }
> )
That would definitely be better, unfortunately since this is a
declarative macro it cannot match against comments, hence the current
syntax.
>
> Another top-level question I have is whether we should define multiple registers
> (perhaps all) in one macro invocation. That's the strategy of `tock-registers`
> crate which is widely used in embedded Rust.
I agree that would be nice, especially as it opens the way for better
syntax for e.g. register blocks with a base.
I am having trouble adding that to the current design though. Some rules
use the tt muncher pattern and these will greedily consume all tokens,
including those for the next register. I am a bit at my wit's end here,
but if you have an idea for how to do it nicely then by all means. :)
>
> (Although, they use a completely different strategy in generating register
> mapping; all registers are becoming fields in the same struct).
>
>> +/// ```
>> +///
>> +/// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io`
>> +/// region. 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
>> +/// use kernel::register;
>> +/// use kernel::num::Bounded;
>> +///
>> +/// # register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
>> +/// # 7:4 major_revision, "Major revision of the chip";
>> +/// # 3:0 minor_revision, "Minor revision of the chip";
>> +/// # });
>> +/// # fn test(bar: &kernel::io::Io) {
>> +/// // 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 value back.
>> +/// boot0
>> +/// .set_major_revision(Bounded::<u32, _>::new::<3>())
>> +/// .set_minor_revision(Bounded::<u32, _>::new::<10>())
>
> This looks very verbose...
Yes, it is a bit unfortunate (although real-life code is unlikely to be
that verbose if you look at the top patch).
And a bit frustrating as one could expect the first generic parameter to
be inferred, so we could write `Bounded::new<3>()`. But due to how the
`new` constructor of `Bounded` is declared (using one dedicated
impl block per dedicated type as we cannot use generics with const
code), the compiler requires the backing type to be explicitly
specified. :/
Once we have the ability to use const trait methods this can be improved
though.
>
>> +/// .write(&bar);
>> +///
>> +/// // Or, just read and update the register in a single step:
>> +/// BOOT_0::update(&bar, |r| r
>> +/// .set_major_revision(Bounded::<u32, _>::new::<3>())
>> +/// .set_minor_revision(Bounded::<u32, _>::new::<10>())
>> +/// );
>> +/// # }
>> +/// ```
>> +///
>> +/// 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.
>> +///
>> +/// Attributes can be applied to the generated struct. The `#[allow(non_camel_case_types)]`
>> +/// attribute is automatically added since register names typically use SCREAMING_CASE:
>> +///
>> +/// ```
>> +/// use kernel::register;
>> +///
>> +/// register! {
>> +/// pub STATUS(u32) @ 0x00000000, "Status register" {
>> +/// 0:0 ready, "Device ready flag";
>> +/// }
>> +/// }
>> +/// ```
>> +///
>> +/// It is also possible to create an alias register by using the `=> ALIAS` syntax. This is useful
>> +/// for cases where a register's interpretation depends on the context:
>> +///
>> +/// ```
>> +/// use kernel::register;
>> +///
>> +/// register!(pub SCRATCH(u32) @ 0x00000200, "Scratch register" {
>> +/// 31:0 value, "Raw value";
>> +/// });
>> +///
>> +/// register!(pub SCRATCH_BOOT_STATUS(u32) => SCRATCH, "Boot status of the firmware" {
>
> How about "as"?
>
> register!(pub SCRATCH_BOOT_STATUS(u32) as SCRATCH);
I'd rather not, this could be interpreted as the conversion being done
using the `as` keyword while we are using `Into`.
>
>> +/// 0:0 completed, "Whether the firmware has completed booting";
>> +/// });
>> +/// ```
>> +///
>> +/// 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(bar: &kernel::io::Io) {
>> +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
>> +/// register!(pub CPU_CTL(u32) @ CpuCtlBase[0x10], "CPU core control" {
>
> Maybe `CpuCtlBase + 0x10`? I think this means the offset is going to be differ
> by 0x10, not that it's going to be the 17th register?
Yup, I tend to agree with you on the readability side. This requires
changing some captures from `ty` to `ident` as `+` is not allowed after
`ty` though, which will forbid namespaces from being specified in the
type's path. Do you think this is an acceptable compromise?
>
>> +/// 0:0 start, "Start the CPU core";
>> +/// });
>> +///
>> +/// // 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!(pub CPU_CTL_ALIAS(u32) => CpuCtlBase[CPU_CTL], "Alias to CPU core control" {
>> +/// 1:1 alias_start, "Start the aliased CPU core";
>> +/// });
>> +///
>> +/// // 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 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 `update` methods:
>> +///
>> +/// ```no_run
>> +/// use kernel::register;
>> +///
>> +/// # fn no_run(bar: &kernel::io::Io) -> Result<(), Error> {
>> +/// # fn get_scratch_idx() -> usize {
>> +/// # 0x15
>> +/// # }
>> +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
>> +/// register!(pub SCRATCH(u32) @ 0x00000080[64], "Scratch registers" {
>
> This syntax is way to close to the base syntax above.
>
> Perhaps `pub SCRATCH([u32; 64]) @ 0x00000080` would make more sense?
So we also need to specify the optional stride of the array, which adds
one more parameter. We can go with the following though:
pub SCRATCH(u32)[64] @ 0x00000080
or with the stride:
pub SCRATCH(u32)[64; 16] @ 0x00000080
How does it look?
>
>> +/// 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!(pub FIRMWARE_STATUS(u32) => SCRATCH[8], "Firmware exit status code" {
>> +/// 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!(pub SCRATCH_INTERLEAVED_0(u32) @ 0x000000c0[16 ; 8], "Scratch registers bank 0" {
>> +/// 31:0 value;
>> +/// });
>> +/// register!(pub SCRATCH_INTERLEAVED_1(u32) @ 0x000000c4[16 ; 8], "Scratch registers bank 1" {
>> +/// 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 no_run(bar: &kernel::io::Io) -> 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!(pub CPU_SCRATCH(u32) @ CpuCtlBase[0x00000080[64]], "Per-CPU scratch registers" {
>
> Ah... I don't like this.
If we adopt your syntax suggestions this should thankfully look much
better.
>
>> +/// 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!(pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase[CPU_SCRATCH[8]],
>> +/// "Per-CPU firmware exit status code" {
>> +/// 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!(pub CPU_SCRATCH_INTERLEAVED_0(u32) @ CpuCtlBase[0x00000d00[16 ; 8]],
>
> For striding, SystemRDL uses syntax like this:
>
> type name[len] @ addr += stride;
>
> which I think is better as this won't be confused with array length syntax in
> Rust.
>
> Crazy idea: is it possible to just use SystemRDL (maybe via a proc macro).
I wasn't familiar with SystemRDL, your comment made me take a look.
Interestingly some of the syntax is already close to that of this macro
(notably the use of `@`). That makes me think that we should try to
align when possible/relevant.
There are also lots of ideas that we could probably integrate (like
register blocks and address maps), but parsing the syntax as-is seems
overkill to me, and would delay this work quite a bit as we would
probably reach the limits of what we can do with declarative macros.
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded`
2026-01-26 3:23 ` Alexandre Courbot
@ 2026-01-26 3:28 ` Miguel Ojeda
0 siblings, 0 replies; 25+ messages in thread
From: Miguel Ojeda @ 2026-01-26 3:28 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Gary Guo, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Yury Norov, John Hubbard,
Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer,
Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux,
linux-kernel
On Mon, Jan 26, 2026 at 4:24 AM Alexandre Courbot <acourbot@nvidia.com> wrote:
>
> This indeed creates a minor conflict with `rust-next` as it includes a
> couple of patches for `bounded.rs`.
`rust-fixes`, i.e. it will be in mainline soon, but yeah :)
Cheers,
Miguel
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-21 16:15 ` Miguel Ojeda
@ 2026-01-26 4:31 ` John Hubbard
2026-01-26 4:33 ` John Hubbard
0 siblings, 1 reply; 25+ messages in thread
From: John Hubbard @ 2026-01-26 4:31 UTC (permalink / raw)
To: Miguel Ojeda, Gary Guo
Cc: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Yury Norov, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel
On 1/21/26 8:15 AM, Miguel Ojeda wrote:
> On Wed, Jan 21, 2026 at 3:58 PM Gary Guo <gary@garyguo.net> wrote:
>>
>> Another top-level question I have is whether we should define multiple registers
>> (perhaps all) in one macro invocation. That's the strategy of `tock-registers`
>> crate which is widely used in embedded Rust.
>
> Yeah, I suggested a multi-register one for something similar in 2021
> and even tackling extra automatic codegen there, like e.g. adjusting
> the value as needed to get to the proper unit from a sensor.
>
> Not sure how useful the multi-register part in practice it would be,
> but it would allow to relate several registers together if needed (is
> that even common nowadays?). Or perhaps things like the
For the NVIDIA GPU drivers, maybe not. Just a data point.
thanks,
--
John Hubbard
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-26 4:31 ` John Hubbard
@ 2026-01-26 4:33 ` John Hubbard
0 siblings, 0 replies; 25+ messages in thread
From: John Hubbard @ 2026-01-26 4:33 UTC (permalink / raw)
To: Miguel Ojeda, Gary Guo
Cc: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Yury Norov, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel
On 1/25/26 8:31 PM, John Hubbard wrote:
> On 1/21/26 8:15 AM, Miguel Ojeda wrote:
>> On Wed, Jan 21, 2026 at 3:58 PM Gary Guo <gary@garyguo.net> wrote:
>>>
>>> Another top-level question I have is whether we should define multiple registers
>>> (perhaps all) in one macro invocation. That's the strategy of `tock-registers`
>>> crate which is widely used in embedded Rust.
>>
>> Yeah, I suggested a multi-register one for something similar in 2021
>> and even tackling extra automatic codegen there, like e.g. adjusting
>> the value as needed to get to the proper unit from a sensor.
>>
>> Not sure how useful the multi-register part in practice it would be,
>> but it would allow to relate several registers together if needed (is
>> that even common nowadays?). Or perhaps things like the
>
> For the NVIDIA GPU drivers, maybe not. Just a data point.
...although it depends on what you mean. We do have lots of registers
with a common base MMIO address. It's just that they are not all
invoked in one shot, is what I'm trying (struggling) to say.
thanks,
--
John Hubbard
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-26 3:24 ` Alexandre Courbot
@ 2026-01-26 6:57 ` Alexandre Courbot
2026-01-26 7:45 ` Alexandre Courbot
1 sibling, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-26 6:57 UTC (permalink / raw)
To: Gary Guo
Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Yury Norov, John Hubbard, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel
On Mon Jan 26, 2026 at 12:24 PM JST, Alexandre Courbot wrote:
>> Another top-level question I have is whether we should define multiple registers
>> (perhaps all) in one macro invocation. That's the strategy of `tock-registers`
>> crate which is widely used in embedded Rust.
>
> I agree that would be nice, especially as it opens the way for better
> syntax for e.g. register blocks with a base.
>
> I am having trouble adding that to the current design though. Some rules
> use the tt muncher pattern and these will greedily consume all tokens,
> including those for the next register. I am a bit at my wit's end here,
> but if you have an idea for how to do it nicely then by all means. :)
Actually I found a way to do it. V3 will support multiple register
definitions per invocation, thanks for the suggestion!
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-26 3:24 ` Alexandre Courbot
2026-01-26 6:57 ` Alexandre Courbot
@ 2026-01-26 7:45 ` Alexandre Courbot
2026-01-26 11:46 ` Alexandre Courbot
1 sibling, 1 reply; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-26 7:45 UTC (permalink / raw)
To: Gary Guo
Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Yury Norov, John Hubbard, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel
On Mon Jan 26, 2026 at 12:24 PM JST, Alexandre Courbot wrote:
> On Wed Jan 21, 2026 at 11:50 PM JST, Gary Guo wrote:
>> On Wed Jan 21, 2026 at 7:23 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.
>>>
>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>>> ---
>>> rust/kernel/io.rs | 1 +
>>> rust/kernel/io/register.rs | 1198 ++++++++++++++++++++++++++++++++++++++++++++
>>> 2 files changed, 1199 insertions(+)
>>>
>>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>>> index a97eb44a9a87..eccaa176b6b9 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..e414aebe4c86
>>> --- /dev/null
>>> +++ b/rust/kernel/io/register.rs
>>> @@ -0,0 +1,1198 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +
>>> +/// 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.
>>> +///
>>> +/// A register is essentially a [`bitfield!`] with I/O capabilities. The syntax of the `register!`
>>> +/// macro reflects that fact, being essentially identical to that of [`bitfield!`] with the
>>> +/// addition of addressing information after the `@` token.
>>> +///
>>> +/// Example:
>>> +///
>>> +/// ```
>>> +/// use kernel::register;
>>> +///
>>> +/// register!(pub BOOT_0(u32) @ 0x00000100, "Basic revision information about the chip" {
>>> +/// 7:4 major_revision, "Major revision of the chip";
>>> +/// 3:0 minor_revision, "Minor revision of the chip";
>>> +/// });
>>
>> The comment is inserted as doc comment, but it uses the string syntax.
>>
>> I guess the idea is that you want write everything in a single line so you can
>> visually align the fields? I think it
>> looks fine on the fields, but the same-line documentation of the type itself
>> looks a bit off.
>>
>> Something like this will definitely feel much more Rusty:
>>
>> register!(
>> /// Basic revision information about the chip.
>> pub struct BOOT_0(u32) @ 0x00000100 {
>> /// Major revision of the chip.
>> major_version: [7:4],
>> /// Minor revision of the chip.
>> ///
>> /// This would also allow you easily expand the documentation into
>> /// multiple lines!
>> ///
>> /// Perhaps useful to document some quirks about the register!
>> /// I know currently registers and their fields are very underdocumented
>> /// and they probably don't need multiple lines, but I hope that'll not
>> /// true in the future and we would have detailed docs in the driver --
>> /// in which case visually aligning becomes impossible anyway.
>> minor_version: [3:0],
>> // ^~ closer to the variable syntax in Rust
>> // ^~ I keep the hi:lo syntax which I suppose is to reflect Verilog.
>> }
>> )
>
> That would definitely be better, unfortunately since this is a
> declarative macro it cannot match against comments, hence the current
> syntax.
Looks like I might be wrong here as well. Doccomments can apparently be
matched as meta items, which would allow them to be re-emitted inside
the rule. If that works (LSP unfortunately doesn't let me check that at
the moment), then we should be able to do as you suggested, which would
be great.
I also only noticed now the proposed reordering:
minor_version: [3:0],
I don't have a strong opinion on that, but fwiw SystemRDL seems to do
something like
field minor_version[3:0];
If we agree to align on it the latter might be preferable?
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [PATCH v2 4/5] rust: io: add `register!` macro
2026-01-26 7:45 ` Alexandre Courbot
@ 2026-01-26 11:46 ` Alexandre Courbot
0 siblings, 0 replies; 25+ messages in thread
From: Alexandre Courbot @ 2026-01-26 11:46 UTC (permalink / raw)
To: Gary Guo
Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Yury Norov, John Hubbard, Alistair Popple,
Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney,
Dirk Behme, Steven Price, rust-for-linux, linux-kernel
On Mon Jan 26, 2026 at 4:45 PM JST, Alexandre Courbot wrote:
> I also only noticed now the proposed reordering:
>
> minor_version: [3:0],
>
> I don't have a strong opinion on that, but fwiw SystemRDL seems to do
> something like
>
> field minor_version[3:0];
>
> If we agree to align on it the latter might be preferable?
I have tried implementing the alternative syntaxes, and I think this is
a case where the original one was more readable. The bit fields are
better aligned and easier to skim when they are put before the field
name.
I'll keep it for v3, but of course remain open to alternatives.
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2026-01-26 11:46 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-21 7:23 [PATCH v2 0/5] rust: add `register!` macro Alexandre Courbot
2026-01-21 7:23 ` [PATCH v2 1/5] rust: enable the `generic_arg_infer` feature Alexandre Courbot
2026-01-21 11:48 ` Gary Guo
2026-01-21 7:23 ` [PATCH v2 2/5] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
2026-01-21 14:12 ` Gary Guo
2026-01-26 3:23 ` Alexandre Courbot
2026-01-26 3:28 ` Miguel Ojeda
2026-01-21 17:49 ` kernel test robot
2026-01-21 7:23 ` [PATCH v2 3/5] rust: num: add `as_bool` method to `Bounded<_, 1>` Alexandre Courbot
2026-01-21 14:13 ` Gary Guo
2026-01-21 7:23 ` [PATCH v2 4/5] rust: io: add `register!` macro Alexandre Courbot
2026-01-21 13:13 ` Alexandre Courbot
2026-01-21 14:15 ` Gary Guo
2026-01-26 3:23 ` Alexandre Courbot
2026-01-21 14:50 ` Gary Guo
2026-01-21 16:15 ` Miguel Ojeda
2026-01-26 4:31 ` John Hubbard
2026-01-26 4:33 ` John Hubbard
2026-01-26 3:24 ` Alexandre Courbot
2026-01-26 6:57 ` Alexandre Courbot
2026-01-26 7:45 ` Alexandre Courbot
2026-01-26 11:46 ` Alexandre Courbot
2026-01-21 21:39 ` kernel test robot
2026-01-21 7:23 ` [PATCH FOR REFERENCE v2 5/5] gpu: nova-core: use the kernel " Alexandre Courbot
2026-01-21 9:16 ` [PATCH v2 0/5] rust: add " Dirk Behme
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox