* [PATCH v4 0/7] rust: add `register!` macro
@ 2026-01-28 2:37 Alexandre Courbot
2026-01-28 2:37 ` [PATCH v4 1/7] rust: enable the `generic_arg_infer` feature Alexandre Courbot
` (6 more replies)
0 siblings, 7 replies; 34+ messages in thread
From: Alexandre Courbot @ 2026-01-28 2:37 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,
- Much better syntax (thanks to Gary for all the suggestions!)
- 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.
Another notable difference is that I have removed the `Default`
implementation for registers and instead implement `Zeroable` to provide
a start value. I have deemed this preferable because implementing
`Default` for a register requires that each of its field types also
implements it, which is quite constraining and doesn't always makes
sense (for instance, what is the default architecture for a family of
chips?). Fields for which zero is not a valid value must implement the
fallible getter anyway, so we are not at risk of undefined behavior, and
`Default` can always be implemented on a per-case basis.
In order to avoid the ugly `Bounded::<type, _>::new()` syntax when
setting constant field values, this revision adds an alternative const
field setter named `with_`. Setters to not follow the most idiomatic
Rust naming conventions, so their names are open to discussion.
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, fourth patch a const accessor to the
interval value of a `Bounded`, that is useful for making register
setter methods const.
Patch 5 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.
Patch 6 updates the Rust PCI sample driver to use the `register!`, as
per its TODO item.
The last patch illustrates more largely 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 v4:
- Add `with_` const field setter methods (removing the need to call
`Bounded::new` for constant field values).
- Add `into_inner` const method for `Bounded`.
- Add `from_raw` and const `zeroed` method to create initial register
values.
- More documentation improvements.
- Link to v3: https://patch.msgid.link/20260126-register-v3-0-2328a59d7312@nvidia.com
Changes in v3:
- Sort the Rust features list alphabetically.
- Rebase on top of latest `driver-core-next` including the new Io trait.
- Allow several registers to be defined from the same macro invocation.
- Remove references to `bitfield!` macro.
- Fix doccomment of `shr` and `shl`.
- Use `+` syntax for relative register offsets.
- Move register arrays size and stride to after the backing type declaration.
- Use regular doccomments to document registers and fields (thanks Gary!).
- Remove `Default` implementation and implement the more predictable
`Zeroable` instead.
- Improve doccomments a bit.
- Link to v2: https://patch.msgid.link/20260121-register-v2-0-79d9b8d5e36a@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 (7):
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: num: add `into_inner` method to `Bounded`
rust: io: add `register!` macro
sample: rust: pci: use `register!` macro
[FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro
drivers/gpu/nova-core/falcon.rs | 175 ++--
drivers/gpu/nova-core/falcon/gsp.rs | 12 +-
drivers/gpu/nova-core/falcon/hal/ga102.rs | 15 +-
drivers/gpu/nova-core/falcon/sec2.rs | 13 +-
drivers/gpu/nova-core/fb/hal/ga100.rs | 17 +-
drivers/gpu/nova-core/fb/hal/tu102.rs | 2 +-
drivers/gpu/nova-core/gpu.rs | 31 +-
drivers/gpu/nova-core/gsp/cmdq.rs | 4 +-
drivers/gpu/nova-core/regs.rs | 494 ++++++-----
drivers/gpu/nova-core/regs/macros.rs | 739 -----------------
rust/kernel/io.rs | 1 +
rust/kernel/io/register.rs | 1287 +++++++++++++++++++++++++++++
rust/kernel/lib.rs | 3 +
rust/kernel/num/bounded.rs | 79 ++
samples/rust/rust_driver_pci.rs | 23 +-
scripts/Makefile.build | 3 +-
16 files changed, 1787 insertions(+), 1111 deletions(-)
---
base-commit: 7c60d964fbb100103730db9560f5594c1c4fc844
change-id: 20260117-register-ccaba1d21713
Best regards,
--
Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply [flat|nested] 34+ messages in thread* [PATCH v4 1/7] rust: enable the `generic_arg_infer` feature 2026-01-28 2:37 [PATCH v4 0/7] rust: add `register!` macro Alexandre Courbot @ 2026-01-28 2:37 ` Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 2/7] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot ` (5 subsequent siblings) 6 siblings, 0 replies; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 2:37 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. Tested-by: Dirk Behme <dirk.behme@de.bosch.com> Reviewed-by: Gary Guo <gary@garyguo.net> Acked-by: Miguel Ojeda <ojeda@kernel.org> 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..4637aa11e870 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,generic_arg_infer,lint_reasons,offset_of_nested,raw_ref_op,used_with_arg # `--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] 34+ messages in thread
* [PATCH v4 2/7] rust: num: add `shr` and `shl` methods to `Bounded` 2026-01-28 2:37 [PATCH v4 0/7] rust: add `register!` macro Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 1/7] rust: enable the `generic_arg_infer` feature Alexandre Courbot @ 2026-01-28 2:37 ` Alexandre Courbot 2026-01-28 15:38 ` Gary Guo 2026-01-28 2:37 ` [PATCH v4 3/7] rust: num: add `as_bool` method to `Bounded<_, 1>` Alexandre Courbot ` (4 subsequent siblings) 6 siblings, 1 reply; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 2:37 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> Tested-by: Dirk Behme <dirk.behme@de.bosch.com> Acked-by: Miguel Ojeda <ojeda@kernel.org> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/num/bounded.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs index f870080af8ac..4b929762d5c2 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -470,6 +470,48 @@ 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<_, RES>`, where `RES >= + /// 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<_, RES>`, where `RES >= + /// 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] 34+ messages in thread
* Re: [PATCH v4 2/7] rust: num: add `shr` and `shl` methods to `Bounded` 2026-01-28 2:37 ` [PATCH v4 2/7] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot @ 2026-01-28 15:38 ` Gary Guo 0 siblings, 0 replies; 34+ messages in thread From: Gary Guo @ 2026-01-28 15:38 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 28, 2026 at 2:37 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> > Tested-by: Dirk Behme <dirk.behme@de.bosch.com> > Acked-by: Miguel Ojeda <ojeda@kernel.org> > Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> Reviewed-by: Gary Guo <gary@garyguo.net> > --- > rust/kernel/num/bounded.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 42 insertions(+) ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 3/7] rust: num: add `as_bool` method to `Bounded<_, 1>` 2026-01-28 2:37 [PATCH v4 0/7] rust: add `register!` macro Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 1/7] rust: enable the `generic_arg_infer` feature Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 2/7] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot @ 2026-01-28 2:37 ` Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` Alexandre Courbot ` (3 subsequent siblings) 6 siblings, 0 replies; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 2:37 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> Reviewed-by: Gary Guo <gary@garyguo.net> Tested-by: Dirk Behme <dirk.behme@de.bosch.com> Acked-by: Miguel Ojeda <ojeda@kernel.org> 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 4b929762d5c2..b41ca6df1525 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -1098,3 +1098,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] 34+ messages in thread
* [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` 2026-01-28 2:37 [PATCH v4 0/7] rust: add `register!` macro Alexandre Courbot ` (2 preceding siblings ...) 2026-01-28 2:37 ` [PATCH v4 3/7] rust: num: add `as_bool` method to `Bounded<_, 1>` Alexandre Courbot @ 2026-01-28 2:37 ` Alexandre Courbot 2026-01-28 15:43 ` Gary Guo 2026-01-28 2:37 ` [PATCH v4 5/7] rust: io: add `register!` macro Alexandre Courbot ` (2 subsequent siblings) 6 siblings, 1 reply; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 2:37 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 is useful to access the inner value in const contexts. Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/num/bounded.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs index b41ca6df1525..850827033f67 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -388,6 +388,22 @@ pub fn get(self) -> T { *self.deref() } + /// Returns the wrapped value as the backing type. + /// + /// This is a const-friendly variant of [`Self::get`] that can be used in const contexts. + /// + /// # Examples + /// + /// ``` + /// use kernel::num::Bounded; + /// + /// const V: u32 = Bounded::<u32, 4>::new::<7>().into_inner(); + /// assert_eq!(V, 7u32); + /// ``` + pub const fn into_inner(self) -> T { + self.0 + } + /// Increases the number of bits usable for `self`. /// /// This operation cannot fail. -- 2.52.0 ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` 2026-01-28 2:37 ` [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` Alexandre Courbot @ 2026-01-28 15:43 ` Gary Guo 2026-01-29 8:05 ` Alexandre Courbot 0 siblings, 1 reply; 34+ messages in thread From: Gary Guo @ 2026-01-28 15:43 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 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: > This is useful to access the inner value in const contexts. > > Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> > --- > rust/kernel/num/bounded.rs | 16 ++++++++++++++++ > 1 file changed, 16 insertions(+) > > diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs > index b41ca6df1525..850827033f67 100644 > --- a/rust/kernel/num/bounded.rs > +++ b/rust/kernel/num/bounded.rs > @@ -388,6 +388,22 @@ pub fn get(self) -> T { > *self.deref() > } > > + /// Returns the wrapped value as the backing type. > + /// > + /// This is a const-friendly variant of [`Self::get`] that can be used in const contexts. > + /// > + /// # Examples > + /// > + /// ``` > + /// use kernel::num::Bounded; > + /// > + /// const V: u32 = Bounded::<u32, 4>::new::<7>().into_inner(); > + /// assert_eq!(V, 7u32); > + /// ``` > + pub const fn into_inner(self) -> T { > + self.0 > + } This is... just `get`. You can implement it (with the `fits_with_in` check) in the same manner as the const new. Best, Gary > + > /// Increases the number of bits usable for `self`. > /// > /// This operation cannot fail. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` 2026-01-28 15:43 ` Gary Guo @ 2026-01-29 8:05 ` Alexandre Courbot 2026-01-29 20:23 ` Gary Guo 0 siblings, 1 reply; 34+ messages in thread From: Alexandre Courbot @ 2026-01-29 8:05 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 Thu Jan 29, 2026 at 12:43 AM JST, Gary Guo wrote: > On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: >> This is useful to access the inner value in const contexts. >> >> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >> --- >> rust/kernel/num/bounded.rs | 16 ++++++++++++++++ >> 1 file changed, 16 insertions(+) >> >> diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs >> index b41ca6df1525..850827033f67 100644 >> --- a/rust/kernel/num/bounded.rs >> +++ b/rust/kernel/num/bounded.rs >> @@ -388,6 +388,22 @@ pub fn get(self) -> T { >> *self.deref() >> } >> >> + /// Returns the wrapped value as the backing type. >> + /// >> + /// This is a const-friendly variant of [`Self::get`] that can be used in const contexts. >> + /// >> + /// # Examples >> + /// >> + /// ``` >> + /// use kernel::num::Bounded; >> + /// >> + /// const V: u32 = Bounded::<u32, 4>::new::<7>().into_inner(); >> + /// assert_eq!(V, 7u32); >> + /// ``` >> + pub const fn into_inner(self) -> T { >> + self.0 >> + } > > This is... just `get`. > > You can implement it (with the `fits_with_in` check) in the same manner as the > const new. But unfortunately doing so also prevents `get` from being called from generic code, which is a must-have. Or am I misunderstanding your suggestion? ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` 2026-01-29 8:05 ` Alexandre Courbot @ 2026-01-29 20:23 ` Gary Guo 2026-01-30 0:58 ` Alexandre Courbot 0 siblings, 1 reply; 34+ messages in thread From: Gary Guo @ 2026-01-29 20:23 UTC (permalink / raw) To: Alexandre Courbot, 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 Thu Jan 29, 2026 at 8:05 AM GMT, Alexandre Courbot wrote: > On Thu Jan 29, 2026 at 12:43 AM JST, Gary Guo wrote: >> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: >>> This is useful to access the inner value in const contexts. >>> >>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >>> --- >>> rust/kernel/num/bounded.rs | 16 ++++++++++++++++ >>> 1 file changed, 16 insertions(+) >>> >>> diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs >>> index b41ca6df1525..850827033f67 100644 >>> --- a/rust/kernel/num/bounded.rs >>> +++ b/rust/kernel/num/bounded.rs >>> @@ -388,6 +388,22 @@ pub fn get(self) -> T { >>> *self.deref() >>> } >>> >>> + /// Returns the wrapped value as the backing type. >>> + /// >>> + /// This is a const-friendly variant of [`Self::get`] that can be used in const contexts. >>> + /// >>> + /// # Examples >>> + /// >>> + /// ``` >>> + /// use kernel::num::Bounded; >>> + /// >>> + /// const V: u32 = Bounded::<u32, 4>::new::<7>().into_inner(); >>> + /// assert_eq!(V, 7u32); >>> + /// ``` >>> + pub const fn into_inner(self) -> T { >>> + self.0 >>> + } >> >> This is... just `get`. >> >> You can implement it (with the `fits_with_in` check) in the same manner as the >> const new. > > But unfortunately doing so also prevents `get` from being called from > generic code, which is a must-have. Or am I misunderstanding your > suggestion? Can you remove the `fits_with_in` check then and just provide `self.0` in `get` then? If the assume is really important, then we should at least name this `get_const` (or `const_get`) to indicate that this is a limitation caused by const eval, not a completely different method as the name would otherwise suggest. Best, Gary ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` 2026-01-29 20:23 ` Gary Guo @ 2026-01-30 0:58 ` Alexandre Courbot 0 siblings, 0 replies; 34+ messages in thread From: Alexandre Courbot @ 2026-01-30 0:58 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 Fri Jan 30, 2026 at 5:23 AM JST, Gary Guo wrote: > On Thu Jan 29, 2026 at 8:05 AM GMT, Alexandre Courbot wrote: >> On Thu Jan 29, 2026 at 12:43 AM JST, Gary Guo wrote: >>> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: >>>> This is useful to access the inner value in const contexts. >>>> >>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >>>> --- >>>> rust/kernel/num/bounded.rs | 16 ++++++++++++++++ >>>> 1 file changed, 16 insertions(+) >>>> >>>> diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs >>>> index b41ca6df1525..850827033f67 100644 >>>> --- a/rust/kernel/num/bounded.rs >>>> +++ b/rust/kernel/num/bounded.rs >>>> @@ -388,6 +388,22 @@ pub fn get(self) -> T { >>>> *self.deref() >>>> } >>>> >>>> + /// Returns the wrapped value as the backing type. >>>> + /// >>>> + /// This is a const-friendly variant of [`Self::get`] that can be used in const contexts. >>>> + /// >>>> + /// # Examples >>>> + /// >>>> + /// ``` >>>> + /// use kernel::num::Bounded; >>>> + /// >>>> + /// const V: u32 = Bounded::<u32, 4>::new::<7>().into_inner(); >>>> + /// assert_eq!(V, 7u32); >>>> + /// ``` >>>> + pub const fn into_inner(self) -> T { >>>> + self.0 >>>> + } >>> >>> This is... just `get`. >>> >>> You can implement it (with the `fits_with_in` check) in the same manner as the >>> const new. >> >> But unfortunately doing so also prevents `get` from being called from >> generic code, which is a must-have. Or am I misunderstanding your >> suggestion? > > Can you remove the `fits_with_in` check then and just provide `self.0` in `get` > then? > > If the assume is really important, then we should at least name this `get_const` > (or `const_get`) to indicate that this is a limitation caused by const eval, not > a completely different method as the name would otherwise suggest. You're right, we should just do that. The benefits of the invariant check are negligible at best, and being able to access the value in const context is more important. Furthermore, one can always use the `Deref` implementation if they need the potential optimization brought by checking the invariant. ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 2:37 [PATCH v4 0/7] rust: add `register!` macro Alexandre Courbot ` (3 preceding siblings ...) 2026-01-28 2:37 ` [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` Alexandre Courbot @ 2026-01-28 2:37 ` Alexandre Courbot 2026-01-28 3:02 ` John Hubbard 2026-01-28 16:16 ` Gary Guo 2026-01-28 2:37 ` [PATCH v4 6/7] sample: rust: pci: use " Alexandre Courbot 2026-01-28 2:37 ` [PATCH FOR REFERENCE v4 7/7] gpu: nova-core: use the kernel " Alexandre Courbot 6 siblings, 2 replies; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 2:37 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. Tested-by: Dirk Behme <dirk.behme@de.bosch.com> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/io.rs | 1 + rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1288 insertions(+) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 056a3ec71647..112f43ecbf88 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -11,6 +11,7 @@ pub mod mem; pub mod poll; +pub mod register; pub mod resource; pub use resource::Resource; diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs new file mode 100644 index 000000000000..fc85dcd1f09a --- /dev/null +++ b/rust/kernel/io/register.rs @@ -0,0 +1,1287 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! A macro to define register layout and accessors. +//! +//! A single register typically includes several fields, which are accessed through a combination +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because +//! not all possible field values are necessarily valid. +//! +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a +//! dedicated type for each register. Each such type comes with its own field accessors that can +//! return an error if a field's value is invalid. +//! +//! [`register!`]: kernel::register! + +use core::ops::Deref; + +use crate::io::{ + IoCapable, + IoKnownSize, // +}; + +/// Trait providing a base address to be added to the offset of a relative register to obtain +/// its actual offset. +/// +/// The `T` generic argument is used to distinguish which base to use, in case a type provides +/// several bases. It is given to the `register!` macro to restrict the use of the register to +/// implementors of this particular variant. +pub trait RegisterBase<T> { + /// Base address to which register offsets are added. + const BASE: usize; +} + +/// Trait providing I/O read/write operations for register storage types. +/// +/// This trait is implemented for all integer types on which I/O can be performed, allowing the +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage +/// type. +pub trait RegisterIo: Sized { + /// Read a value from the given offset in the I/O region. + fn read<T, I>(io: &T, offset: usize) -> Self + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>; + + /// Write a value to the given offset in the I/O region. + fn write<T, I>(self, io: &T, offset: usize) + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>; +} + +impl RegisterIo for u8 { + #[inline(always)] + fn read<T, I>(io: &T, offset: usize) -> Self + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>, + { + io.read8(offset) + } + + #[inline(always)] + fn write<T, I>(self, io: &T, offset: usize) + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>, + { + io.write8(self, offset) + } +} + +impl RegisterIo for u16 { + #[inline(always)] + fn read<T, I>(io: &T, offset: usize) -> Self + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>, + { + io.read16(offset) + } + + #[inline(always)] + fn write<T, I>(self, io: &T, offset: usize) + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>, + { + io.write16(self, offset) + } +} + +impl RegisterIo for u32 { + #[inline(always)] + fn read<T, I>(io: &T, offset: usize) -> Self + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>, + { + io.read32(offset) + } + + #[inline(always)] + fn write<T, I>(self, io: &T, offset: usize) + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>, + { + io.write32(self, offset) + } +} + +#[cfg(CONFIG_64BIT)] +impl RegisterIo for u64 { + #[inline(always)] + fn read<T, I>(io: &T, offset: usize) -> Self + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>, + { + io.read64(offset) + } + + #[inline(always)] + fn write<T, I>(self, io: &T, offset: usize) + where + T: Deref<Target = I>, + I: IoKnownSize + IoCapable<Self>, + { + io.write64(self, offset) + } +} + +/// Defines a dedicated type for a register, including getter and setter methods for its fields and +/// methods to read and write it from an [`Io`] region. +/// +/// Example: +/// +/// ``` +/// use kernel::register; +/// +/// register! { +/// /// Basic information about the chip. +/// pub BOOT_0(u32) @ 0x00000100 { +/// /// Vendor ID. +/// 15:8 vendor_id; +/// /// Major revision of the chip. +/// 7:4 major_revision; +/// /// Minor revision of the chip. +/// 3:0 minor_revision; +/// } +/// } +/// ``` +/// +/// This defines a `BOOT_0` type which can be read from or written to offset `0x100` of an `Io` +/// region. For instance, `minor_revision` consists of the 4 least significant bits of the +/// register. +/// +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their +/// getter method, which is named after them. They also have setter methods prefixed with `set_` +/// for runtime values and `with_` for constant values. All setters return the updated register +/// value. +/// +/// ```no_run +/// use kernel::register; +/// use kernel::num::Bounded; +/// +/// # register! { +/// # pub BOOT_0(u32) @ 0x00000100 { +/// # 15:8 vendor_id; +/// # 7:4 major_revision; +/// # 3:0 minor_revision; +/// # } +/// # } +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) { +/// # fn obtain_vendor_id() -> u8 { 0xff } +/// // Read from the register's defined offset (0x100). +/// let boot0 = BOOT_0::read(&bar); +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get()); +/// +/// // Update some fields and write the new value back. +/// boot0 +/// // Constant values. +/// .with_major_revision::<3>() +/// .with_minor_revision::<10>() +/// // Run-time value. +/// .set_vendor_id(obtain_vendor_id()) +/// .write(&bar); +/// +/// // Or, just read and update the register in a single step. +/// BOOT_0::update(&bar, |r| r +/// .with_major_revision::<3>() +/// .with_minor_revision::<10>() +/// .set_vendor_id(obtain_vendor_id()) +/// ); +/// +/// // Constant values can also be built using the const setters. +/// const V: BOOT_0 = BOOT_0::zeroed() +/// .with_major_revision::<3>() +/// .with_minor_revision::<10>(); +/// # } +/// ``` +/// +/// Fields can also be transparently converted from/to an arbitrary type by using the bitfield `=>` +/// and `?=>` syntaxes. +/// +/// If present, doccomments above register or fields definitions are added to the relevant item +/// they document (the register type itself, or the field's setter and getter methods). +/// +/// Note that multiple registers can be defined in a single `register!` invocation. This can be +/// useful to group related registers together. +/// +/// ``` +/// use kernel::register; +/// +/// register! { +/// pub BOOT_0(u8) @ 0x00000100 { +/// 7:4 major_revision; +/// 3:0 minor_revision; +/// } +/// +/// pub BOOT_1(u8) @ 0x00000101 { +/// 7:5 num_threads; +/// 4:0 num_cores; +/// } +/// }; +/// ``` +/// +/// It is possible to create an alias of an existing register with new field definitions by using +/// the `=> ALIAS` syntax. This is useful for cases where a register's interpretation depends on +/// the context: +/// +/// ``` +/// use kernel::register; +/// +/// register! { +/// /// Scratch register. +/// pub SCRATCH(u32) @ 0x00000200 { +/// /// Raw value. +/// 31:0 value; +/// } +/// +/// /// Boot status of the firmware. +/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH { +/// /// Whether the firmware has completed booting. +/// 0:0 completed; +/// } +/// } +/// ``` +/// +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also +/// providing its own `completed` field. +/// +/// ## Relative registers +/// +/// A register can be defined as being accessible from a fixed offset of a provided base. For +/// instance, imagine the following I/O space: +/// +/// ```text +/// +-----------------------------+ +/// | ... | +/// | | +/// 0x100--->+------------CPU0-------------+ +/// | | +/// 0x110--->+-----------------------------+ +/// | CPU_CTL | +/// +-----------------------------+ +/// | ... | +/// | | +/// | | +/// 0x200--->+------------CPU1-------------+ +/// | | +/// 0x210--->+-----------------------------+ +/// | CPU_CTL | +/// +-----------------------------+ +/// | ... | +/// +-----------------------------+ +/// ``` +/// +/// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O +/// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define +/// them twice and would prefer a way to select which one to use from a single definition +/// +/// This can be done using the `Base + Offset` syntax when specifying the register's address. +/// +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the +/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for +/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated +/// into code: +/// +/// ```no_run +/// use kernel::register; +/// use kernel::io::register::RegisterBase; +/// +/// // Type used to identify the base. +/// pub struct CpuCtlBase; +/// +/// // ZST describing `CPU0`. +/// struct Cpu0; +/// impl RegisterBase<CpuCtlBase> for Cpu0 { +/// const BASE: usize = 0x100; +/// } +/// // Singleton of `CPU0` used to identify it. +/// const CPU0: Cpu0 = Cpu0; +/// +/// // ZST describing `CPU1`. +/// struct Cpu1; +/// impl RegisterBase<CpuCtlBase> for Cpu1 { +/// const BASE: usize = 0x200; +/// } +/// // Singleton of `CPU1` used to identify it. +/// const CPU1: Cpu1 = Cpu1; +/// +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) { +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`. +/// register! { +/// /// CPU core control. +/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 { +/// /// Start the CPU core. +/// 0:0 start; +/// } +/// } +/// +/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument +/// // that is used to resolve its final address by adding its `BASE` to the offset of the +/// // register. +/// +/// // Start `CPU0`. +/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true)); +/// +/// // Start `CPU1`. +/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true)); +/// +/// // Aliases can also be defined for relative register. +/// register! { +/// /// Alias to CPU core control. +/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL { +/// /// Start the aliased CPU core. +/// 1:1 alias_start; +/// } +/// } +/// +/// // Start the aliased `CPU0`. +/// CPU_CTL_ALIAS::update(&bar, &CPU0, |r| r.set_alias_start(true)); +/// # } +/// ``` +/// +/// ## Arrays of registers +/// +/// Some I/O areas contain consecutive registers that can be interpreted in the same way. These +/// areas can be defined as an array of identical registers, allowing them to be accessed by index +/// with compile-time or runtime bound checking. Simply specify their size inside `[` and `]` +/// brackets, and add an `idx` parameter to their `read`, `write` and `update` methods: +/// +/// ```no_run +/// use kernel::register; +/// +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) +/// # -> Result<(), Error>{ +/// # fn get_scratch_idx() -> usize { +/// # 0x15 +/// # } +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`. +/// register! { +/// /// Scratch registers. +/// pub SCRATCH(u32)[64] @ 0x00000080 { +/// 31:0 value; +/// } +/// } +/// +/// // Read scratch register 0, i.e. I/O address `0x80`. +/// let scratch_0 = SCRATCH::read(&bar, 0).value(); +/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`. +/// let scratch_15 = SCRATCH::read(&bar, 15).value(); +/// +/// // This is out of bounds and won't build. +/// // let scratch_128 = SCRATCH::read(&bar, 128).value(); +/// +/// // Runtime-obtained array index. +/// let scratch_idx = get_scratch_idx(); +/// // Access on a runtime index returns an error if it is out-of-bounds. +/// let some_scratch = SCRATCH::try_read(&bar, scratch_idx)?.value(); +/// +/// // Alias to a particular register in an array. +/// // Here `SCRATCH[8]` is used to convey the firmware exit code. +/// register! { +/// /// Firmware exit status code. +/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] { +/// 7:0 status; +/// } +/// } +/// let status = FIRMWARE_STATUS::read(&bar).status(); +/// +/// // Non-contiguous register arrays can be defined by adding a stride parameter. +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the +/// // registers of the two declarations below are interleaved. +/// register! { +/// /// Scratch registers bank 0. +/// pub SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ 0x000000c0 { +/// 31:0 value; +/// } +/// +/// /// Scratch registers bank 1. +/// pub SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ 0x000000c4 { +/// 31:0 value; +/// } +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// ## Relative arrays of registers +/// +/// Combining the two features described in the sections above, arrays of registers accessible from +/// a base can also be defined: +/// +/// ```no_run +/// use kernel::register; +/// use kernel::io::register::RegisterBase; +/// +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) +/// # -> Result<(), Error>{ +/// # fn get_scratch_idx() -> usize { +/// # 0x15 +/// # } +/// // Type used as parameter of `RegisterBase` to specify the base. +/// pub struct CpuCtlBase; +/// +/// // ZST describing `CPU0`. +/// struct Cpu0; +/// impl RegisterBase<CpuCtlBase> for Cpu0 { +/// const BASE: usize = 0x100; +/// } +/// // Singleton of `CPU0` used to identify it. +/// const CPU0: Cpu0 = Cpu0; +/// +/// // ZST describing `CPU1`. +/// struct Cpu1; +/// impl RegisterBase<CpuCtlBase> for Cpu1 { +/// const BASE: usize = 0x200; +/// } +/// // Singleton of `CPU1` used to identify it. +/// const CPU1: Cpu1 = Cpu1; +/// +/// // 64 per-cpu scratch registers, arranged as a contiguous array. +/// register! { +/// /// Per-CPU scratch registers. +/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 { +/// 31:0 value; +/// } +/// } +/// +/// let cpu0_scratch_0 = CPU_SCRATCH::read(&bar, &Cpu0, 0).value(); +/// let cpu1_scratch_15 = CPU_SCRATCH::read(&bar, &Cpu1, 15).value(); +/// +/// // This won't build. +/// // let cpu0_scratch_128 = CPU_SCRATCH::read(&bar, &Cpu0, 128).value(); +/// +/// // Runtime-obtained array index. +/// let scratch_idx = get_scratch_idx(); +/// // Access on a runtime value returns an error if it is out-of-bounds. +/// let cpu0_some_scratch = CPU_SCRATCH::try_read(&bar, &Cpu0, scratch_idx)?.value(); +/// +/// // `SCRATCH[8]` is used to convey the firmware exit code. +/// register! { +/// /// Per-CPU firmware exit status code. +/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] { +/// 7:0 status; +/// } +/// } +/// let cpu0_status = CPU_FIRMWARE_STATUS::read(&bar, &Cpu0).status(); +/// +/// // Non-contiguous register arrays can be defined by adding a stride parameter. +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the +/// // registers of the two declarations below are interleaved. +/// register! { +/// /// Scratch registers bank 0. +/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ CpuCtlBase + 0x00000d00 { +/// 31:0 value; +/// } +/// +/// /// Scratch registers bank 1. +/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ CpuCtlBase + 0x00000d04 { +/// 31:0 value; +/// } +/// } +/// # Ok(()) +/// # } +/// ``` +/// [`Io`]: kernel::io::Io +#[macro_export] +macro_rules! register { + // Entry point for the macro, allowing multiple registers to be defined in one call. + // It matches all possible register declaration patterns to dispatch them to corresponding + // `@reg` rule that defines a single register. + ( + $( + $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) + $([ $size:expr $(; $stride:expr)? ])? $(@ $offset:literal)? + $(@ $base:ident + $base_offset:literal)? + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )? + { $($fields:tt)* } + )* + ) => { + $( + ::kernel::register!( + @reg $(#[$attr])* $vis $name ($storage) $([$size $(; $stride)?])? + $(@ $offset)? + $(@ $base + $base_offset)? + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )? + { $($fields)* } + ); + )* + }; + + // All the rules below are private helpers. + + // Creates a register at a fixed offset of the MMIO space. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal + { $($fields:tt)* } + ) => { + ::kernel::register!( + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } + ); + ::kernel::register!(@io_fixed $name($storage) @ $offset); + }; + + // Creates an alias register of fixed offset register `alias` with its own fields. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident + { $($fields:tt)* } + ) => { + ::kernel::register!( + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } + ); + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET); + }; + + // Creates a register at a relative offset from a base address provider. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal + { $($fields:tt)* } + ) => { + ::kernel::register!( + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } + ); + ::kernel::register!(@io_relative $name($storage) @ $base + $offset ); + }; + + // Creates an alias register of relative offset register `alias` with its own fields. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident + { $($fields:tt)* } + ) => { + ::kernel::register!( + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } + ); + ::kernel::register!(@io_relative $name($storage) @ $base + $alias::OFFSET ); + }; + + // Creates an array of registers at a fixed offset of the MMIO space. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) + [ $size:expr ; $stride:expr ] @ $offset:literal { $($fields:tt)* } + ) => { + static_assert!(::core::mem::size_of::<$storage>() <= $stride); + + ::kernel::register!( + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } + ); + ::kernel::register!(@io_array $name($storage) [ $size ; $stride ] @ $offset); + }; + + // Shortcut for contiguous array of registers (stride == size of element). + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal + { $($fields:tt)* } + ) => { + ::kernel::register!( + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ] + @ $offset { $($fields)* } + ); + }; + + // Creates an alias of register `idx` of array of registers `alias` with its own fields. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ] + { $($fields:tt)* } + ) => { + static_assert!($idx < $alias::SIZE); + + ::kernel::register!( + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } + ); + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE); + }; + + // Creates an array of registers at a relative offset from a base address provider. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] + @ $base:ident + $offset:literal { $($fields:tt)* } + ) => { + static_assert!(::core::mem::size_of::<$storage>() <= $stride); + + ::kernel::register!( + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } + ); + ::kernel::register!( + @io_relative_array $name($storage) [ $size ; $stride ] @ $base + $offset + ); + }; + + // Shortcut for contiguous array of relative registers (stride == size of element). + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] + @ $base:ident + $offset:literal { $($fields:tt)* } + ) => { + ::kernel::register!( + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ] + @ $base + $offset { $($fields)* } + ); + }; + + // Creates an alias of register `idx` of relative array of registers `alias` with its own + // fields. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) + => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* } + ) => { + static_assert!($idx < $alias::SIZE); + + ::kernel::register!( + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } + ); + ::kernel::register!( + @io_relative $name($storage) @ $base + $alias::OFFSET + $idx * $alias::STRIDE + ); + }; + + // Generates the bitfield for the register. + // + // `#[allow(non_camel_case_types)]` is added since register names typically use SCREAMING_CASE. + ( + @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* } + ) => { + ::kernel::register!(@bitfield_core + #[allow(non_camel_case_types)] + $(#[$attr])* $vis $name $storage + ); + ::kernel::register!(@bitfield_fields $vis $name $storage { $($fields)* }); + }; + + // Generates the IO accessors for a fixed offset register. + (@io_fixed $name:ident ($storage:ty) @ $offset:expr) => { + #[allow(dead_code)] + impl $name { + /// Absolute offset of the register. + pub const OFFSET: usize = $offset; + + /// Read the register from its address in `io`. + #[inline(always)] + pub fn read<T, I>(io: &T) -> Self where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + { + Self(<$storage as $crate::io::register::RegisterIo>::read(io, Self::OFFSET)) + } + + /// Write the value contained in `self` to the register address in `io`. + #[inline(always)] + pub fn write<T, I>(self, io: &T) where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + { + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, Self::OFFSET) + } + + /// Read the register from its address in `io` and run `f` on its value to obtain a new + /// value to write back. + /// + /// Note that this operation is not atomic. In concurrent contexts, external + /// synchronization may be required to prevent race conditions. + #[inline(always)] + pub fn update<T, I, F>( + io: &T, + f: F, + ) where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + F: ::core::ops::FnOnce(Self) -> Self, + { + let reg = f(Self::read(io)); + reg.write(io); + } + } + }; + + // Generates the IO accessors for a relative offset register. + (@io_relative $name:ident ($storage:ty) @ $base:ident + $offset:expr ) => { + #[allow(dead_code)] + impl $name { + /// Relative offset of the register. + pub const OFFSET: usize = $offset; + + /// Read the register from `io`, using the base address provided by `base` and adding + /// the register's offset to it. + #[inline(always)] + pub fn read<T, I, B>( + io: &T, + #[allow(unused_variables)] + base: &B, + ) -> Self where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + { + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET; + + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) + } + + /// Write the value contained in `self` to `io`, using the base address provided by + /// `base` and adding the register's offset to it. + #[inline(always)] + pub fn write<T, I, B>( + self, + io: &T, + #[allow(unused_variables)] + base: &B, + ) where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + { + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET; + + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) + } + + /// Read the register from `io`, using the base address provided by `base` and adding + /// the register's offset to it, then run `f` on its value to obtain a new value to + /// write back. + /// + /// Note that this operation is not atomic. In concurrent contexts, external + /// synchronization may be required to prevent race conditions. + #[inline(always)] + pub fn update<T, I, B, F>( + io: &T, + base: &B, + f: F, + ) where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + F: ::core::ops::FnOnce(Self) -> Self, + { + let reg = f(Self::read(io, base)); + reg.write(io, base); + } + } + }; + + // Generates the IO accessors for an array of registers. + (@io_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] @ $offset:literal) => { + #[allow(dead_code)] + impl $name { + /// Absolute offset of the register array. + pub const OFFSET: usize = $offset; + /// Number of elements in the array of registers. + pub const SIZE: usize = $size; + /// Number of bytes separating each element of the array of registers. + pub const STRIDE: usize = $stride; + + /// Read the array register at index `idx` from its address in `io`. + #[inline(always)] + pub fn read<T, I>( + io: &T, + idx: usize, + ) -> Self where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + { + build_assert!(idx < Self::SIZE); + + let offset = Self::OFFSET + (idx * Self::STRIDE); + + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) + } + + /// Write the value contained in `self` to the array register with index `idx` in `io`. + #[inline(always)] + pub fn write<T, I>( + self, + io: &T, + idx: usize + ) where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + { + build_assert!(idx < Self::SIZE); + + let offset = Self::OFFSET + (idx * Self::STRIDE); + + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) + } + + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a + /// new value to write back. + /// + /// Note that this operation is not atomic. In concurrent contexts, external + /// synchronization may be required to prevent race conditions. + #[inline(always)] + pub fn update<T, I, F>( + io: &T, + idx: usize, + f: F, + ) where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + F: ::core::ops::FnOnce(Self) -> Self, + { + let reg = f(Self::read(io, idx)); + reg.write(io, idx); + } + + /// Read the array register at index `idx` from its address in `io`. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the + /// access was out-of-bounds. + #[inline(always)] + pub fn try_read<T, I>( + io: &T, + idx: usize, + ) -> ::kernel::error::Result<Self> where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + { + if idx < Self::SIZE { + Ok(Self::read(io, idx)) + } else { + Err(::kernel::error::code::EINVAL) + } + } + + /// Write the value contained in `self` to the array register with index `idx` in `io`. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the + /// access was out-of-bounds. + #[inline(always)] + pub fn try_write<T, I>( + self, + io: &T, + idx: usize, + ) -> ::kernel::error::Result where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + { + if idx < Self::SIZE { + Ok(self.write(io, idx)) + } else { + Err(::kernel::error::code::EINVAL) + } + } + + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a + /// new value to write back. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the + /// access was out-of-bounds. + /// + /// Note that this operation is not atomic. In concurrent contexts, external + /// synchronization may be required to prevent race conditions. + #[inline(always)] + pub fn try_update<T, I, F>( + io: &T, + idx: usize, + f: F, + ) -> ::kernel::error::Result where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + F: ::core::ops::FnOnce(Self) -> Self, + { + if idx < Self::SIZE { + Ok(Self::update(io, idx, f)) + } else { + Err(::kernel::error::code::EINVAL) + } + } + } + }; + + // Generates the IO accessors for an array of relative registers. + ( + @io_relative_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] + @ $base:ident + $offset:literal + ) => { + #[allow(dead_code)] + impl $name { + /// Relative offset of the register array. + pub const OFFSET: usize = $offset; + /// Number of elements in the array of registers. + pub const SIZE: usize = $size; + /// Number of bytes separating each element of the array of registers. + pub const STRIDE: usize = $stride; + + /// Read the array register at index `idx` from `io`, using the base address provided + /// by `base` and adding the register's offset to it. + #[inline(always)] + pub fn read<T, I, B>( + io: &T, + #[allow(unused_variables)] + base: &B, + idx: usize, + ) -> Self where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + { + build_assert!(idx < Self::SIZE); + + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + + Self::OFFSET + (idx * Self::STRIDE); + + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) + } + + /// Write the value contained in `self` to `io`, using the base address provided by + /// `base` and adding the offset of array register `idx` to it. + #[inline(always)] + pub fn write<T, I, B>( + self, + io: &T, + #[allow(unused_variables)] + base: &B, + idx: usize + ) where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + { + build_assert!(idx < Self::SIZE); + + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + + Self::OFFSET + (idx * Self::STRIDE); + + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) + } + + /// Read the array register at index `idx` from `io`, using the base address provided + /// by `base` and adding the register's offset to it, then run `f` on its value to + /// obtain a new value to write back. + /// + /// Note that this operation is not atomic. In concurrent contexts, external + /// synchronization may be required to prevent race conditions. + #[inline(always)] + pub fn update<T, I, B, F>( + io: &T, + base: &B, + idx: usize, + f: F, + ) where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + F: ::core::ops::FnOnce(Self) -> Self, + { + let reg = f(Self::read(io, base, idx)); + reg.write(io, base, idx); + } + + /// Read the array register at index `idx` from `io`, using the base address provided + /// by `base` and adding the register's offset to it. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the + /// access was out-of-bounds. + #[inline(always)] + pub fn try_read<T, I, B>( + io: &T, + base: &B, + idx: usize, + ) -> ::kernel::error::Result<Self> where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + { + if idx < Self::SIZE { + Ok(Self::read(io, base, idx)) + } else { + Err(::kernel::error::code::EINVAL) + } + } + + /// Write the value contained in `self` to `io`, using the base address provided by + /// `base` and adding the offset of array register `idx` to it. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the + /// access was out-of-bounds. + #[inline(always)] + pub fn try_write<T, I, B>( + self, + io: &T, + base: &B, + idx: usize, + ) -> ::kernel::error::Result where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + { + if idx < Self::SIZE { + Ok(self.write(io, base, idx)) + } else { + Err(::kernel::error::code::EINVAL) + } + } + + /// Read the array register at index `idx` from `io`, using the base address provided + /// by `base` and adding the register's offset to it, then run `f` on its value to + /// obtain a new value to write back. + /// + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the + /// access was out-of-bounds. + /// + /// Note that this operation is not atomic. In concurrent contexts, external + /// synchronization may be required to prevent race conditions. + #[inline(always)] + pub fn try_update<T, I, B, F>( + io: &T, + base: &B, + idx: usize, + f: F, + ) -> ::kernel::error::Result where + T: ::core::ops::Deref<Target = I>, + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, + B: $crate::io::register::RegisterBase<$base>, + F: ::core::ops::FnOnce(Self) -> Self, + { + if idx < Self::SIZE { + Ok(Self::update(io, base, idx, f)) + } else { + Err(::kernel::error::code::EINVAL) + } + } + } + }; + + // Defines the wrapper `$name` type and its conversions from/to the storage type. + (@bitfield_core $(#[$attr:meta])* $vis:vis $name:ident $storage:ty) => { + $(#[$attr])* + #[repr(transparent)] + #[derive(Clone, Copy, PartialEq, Eq)] + $vis struct $name($storage); + + #[allow(dead_code)] + impl $name { + /// Creates a bitfield from a raw value. + $vis const fn from_raw(value: $storage) -> Self { + Self(value) + } + + /// Creates a zeroed bitfield value. + /// + /// This is a const alternative to the `Zeroable::zeroed()` trait method. + $vis const fn zeroed() -> Self { + Self(0) + } + + /// Returns the raw value of this bitfield. + /// + /// This is similar to the [`From`] implementation, but is shorter to invoke in + /// most cases. + $vis const fn as_raw(self) -> $storage { + self.0 + } + } + + // SAFETY: `$storage` is `Zeroable` and `$name` is transparent. + unsafe impl ::pin_init::Zeroable for $name {} + + impl ::core::convert::From<$name> for $storage { + fn from(val: $name) -> $storage { + val.as_raw() + } + } + + impl ::core::convert::From<$storage> for $name { + fn from(val: $storage) -> $name { + Self::from_raw(val) + } + } + }; + + // Definitions requiring knowledge of individual fields: private and public field accessors, + // and `Debug` implementation. + (@bitfield_fields $vis:vis $name:ident $storage:ty { + $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident + $(?=> $try_into_type:ty)? + $(=> $into_type:ty)? + ; + )* + } + ) => { + #[allow(dead_code)] + impl $name { + $( + ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field); + ::kernel::register!( + @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field + $(?=> $try_into_type)? + $(=> $into_type)? + ); + )* + } + + ::kernel::register!(@debug $name { $($field;)* }); + }; + + // Private field accessors working with the correct `Bounded` type for the field. + ( + @private_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident + ) => { + ::kernel::macros::paste!( + $vis const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi; + $vis const [<$field:upper _MASK>]: $storage = + ((((1 << $hi) - 1) << 1) + 1) - ((1 << $lo) - 1); + $vis const [<$field:upper _SHIFT>]: u32 = $lo; + ); + + ::kernel::macros::paste!( + fn [<__ $field>](self) -> + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> { + // Left shift to align the field's MSB with the storage MSB. + const ALIGN_TOP: u32 = $storage::BITS - ($hi + 1); + // Right shift to move the top-aligned field to bit 0 of the storage. + const ALIGN_BOTTOM: u32 = ALIGN_TOP + $lo; + + // Extract the field using two shifts. `Bounded::shr` produces the correctly-sized + // output type. + let val = ::kernel::num::Bounded::<$storage, { $storage::BITS }>::from( + self.0 << ALIGN_TOP + ); + val.shr::<ALIGN_BOTTOM, { $hi + 1 - $lo } >() + } + + const fn [<__set_ $field>]( + mut self, + value: ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>, + ) -> Self + { + const MASK: $storage = $name::[<$field:upper _MASK>]; + const SHIFT: u32 = $name::[<$field:upper _SHIFT>]; + + let value = value.into_inner() << SHIFT; + self.0 = (self.0 & !MASK) | value; + + self + } + ); + }; + + // Public accessors for fields infallibly (`=>`) converted to a type. + ( + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty : + $hi:literal:$lo:literal $field:ident => $into_type:ty + ) => { + ::kernel::macros::paste!( + + $(#[doc = $doc])* + #[doc = "Returns the value of this field."] + #[inline(always)] + $vis fn $field(self) -> $into_type + { + self.[<__ $field>]().into() + } + + $(#[doc = $doc])* + #[doc = "Sets this field to the given `value`."] + #[inline(always)] + $vis fn [<set_ $field>](self, value: $into_type) -> Self + { + self.[<__set_ $field>](value.into()) + } + + ); + }; + + // Public accessors for fields fallibly (`?=>`) converted to a type. + ( + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty : + $hi:tt:$lo:tt $field:ident ?=> $try_into_type:ty + ) => { + ::kernel::macros::paste!( + + $(#[doc = $doc])* + #[doc = "Returns the value of this field."] + #[inline(always)] + $vis fn $field(self) -> + Result< + $try_into_type, + <$try_into_type as ::core::convert::TryFrom< + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> + >>::Error + > + { + self.[<__ $field>]().try_into() + } + + $(#[doc = $doc])* + #[doc = "Sets this field to the given `value`."] + #[inline(always)] + $vis fn [<set_ $field>](self, value: $try_into_type) -> Self + { + self.[<__set_ $field>](value.into()) + } + + ); + }; + + // Public accessors for fields not converted to a type. + ( + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty : + $hi:tt:$lo:tt $field:ident + ) => { + ::kernel::macros::paste!( + + $(#[doc = $doc])* + #[doc = "Returns the value of this field."] + #[inline(always)] + $vis fn $field(self) -> + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> + { + self.[<__ $field>]() + } + + $(#[doc = $doc])* + #[doc = "Sets this field to the compile-time constant `VALUE`."] + #[inline(always)] + $vis const fn [<with_ $field>]<const VALUE: $storage>(self) -> Self { + self.[<__set_ $field>]( + ::kernel::num::Bounded::<$storage, { $hi + 1 - $lo }>::new::<VALUE>() + ) + } + + $(#[doc = $doc])* + #[doc = "Sets this field to the given `value`."] + #[inline(always)] + $vis fn [<set_ $field>]<T>( + self, + value: T, + ) -> Self + where T: Into<::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>>, + { + self.[<__set_ $field>](value.into()) + } + + $(#[doc = $doc])* + #[doc = "Tries to set this field to `value`, returning an error if it is out of range."] + #[inline(always)] + $vis fn [<try_set_ $field>]<T>( + self, + value: T, + ) -> ::kernel::error::Result<Self> + where T: ::kernel::num::TryIntoBounded<$storage, { $hi + 1 - $lo }>, + { + Ok( + self.[<__set_ $field>]( + value.try_into_bounded().ok_or(::kernel::error::code::EOVERFLOW)? + ) + ) + } + + ); + }; + + // `Debug` implementation. + (@debug $name:ident { $($field:ident;)* }) => { + impl ::kernel::fmt::Debug for $name { + fn fmt(&self, f: &mut ::kernel::fmt::Formatter<'_>) -> ::kernel::fmt::Result { + f.debug_struct(stringify!($name)) + .field("<raw>", &::kernel::prelude::fmt!("{:#x}", self.0)) + $( + .field(stringify!($field), &self.$field()) + )* + .finish() + } + } + }; +} -- 2.52.0 ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 2:37 ` [PATCH v4 5/7] rust: io: add `register!` macro Alexandre Courbot @ 2026-01-28 3:02 ` John Hubbard 2026-01-28 3:47 ` Joel Fernandes 2026-01-28 16:16 ` Gary Guo 1 sibling, 1 reply; 34+ messages in thread From: John Hubbard @ 2026-01-28 3:02 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, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 1/27/26 6:37 PM, Alexandre Courbot wrote: > Add a macro for defining hardware register types with I/O accessors. ... > +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their > +/// getter method, which is named after them. They also have setter methods prefixed with `set_` > +/// for runtime values and `with_` for constant values. All setters return the updated register OK, this still looks like a naming problem that we don't need to create. Let's just pick either "set_" or "with_" as a prefix (it seems that "with_" has won out, in the v3 discussion thread), and then add "const", so that the name doesn't require explanation. ... > +/// // Update some fields and write the new value back. > +/// boot0 > +/// // Constant values. > +/// .with_major_revision::<3>() > +/// .with_minor_revision::<10>() > +/// // Run-time value. > +/// .set_vendor_id(obtain_vendor_id()) See, that is just not obvious at all, why it's different. That's why you had to write a couple of comments. .with_const_major_revision(), on the other hand, keeps the pattern clear and obvious. No comments necessary. nova-core doesn't even use the const_ settors yet at all, according to my quick read of the (very helpful) [PATCH FOR REFERENCE v4 7/7]. So clearly it is a bit rare, and the extra characters won't come into play in daily life. thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 3:02 ` John Hubbard @ 2026-01-28 3:47 ` Joel Fernandes 2026-01-28 7:42 ` Alexandre Courbot 0 siblings, 1 reply; 34+ messages in thread From: Joel Fernandes @ 2026-01-28 3:47 UTC (permalink / raw) To: John Hubbard Cc: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Jan 27, 2026, at 10:02 PM, John Hubbard <jhubbard@nvidia.com> wrote: > On 1/27/26 6:37 PM, Alexandre Courbot wrote: >> Add a macro for defining hardware register types with I/O accessors. > ... >> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their >> +/// getter method, which is named after them. They also have setter methods prefixed with `set_` >> +/// for runtime values and `with_` for constant values. All setters return the updated register > OK, this still looks like a naming problem that we don't need to create. > Let's just pick either "set_" or "with_" as a prefix (it seems that > "with_" has won out, in the v3 discussion thread), and then add "const", > so that the name doesn't require explanation. > > ... >> +/// // Update some fields and write the new value back. >> +/// boot0 >> +/// // Constant values. >> +/// .with_major_revision::<3>() >> +/// .with_minor_revision::<10>() >> +/// // Run-time value. >> +/// .set_vendor_id(obtain_vendor_id()) > > See, that is just not obvious at all, why it's different. That's why > you had to write a couple of comments. > > .with_const_major_revision(), on the other hand, keeps the pattern > clear and obvious. No comments necessary. > > nova-core doesn't even use the const_ settors yet at all, according > to my quick read of the (very helpful) [PATCH FOR REFERENCE v4 7/7]. > So clearly it is a bit rare, and the extra characters won't come into > play in daily life. I completely agree with John here, let us please not create a confusing API. I would much rather use a bounded! macro to create bounded values and pass those to a single unified setter, making it a unified API for both constants and runtime values. Let us not use const generics just for the sake of it or create special const APIs that no one but examples use!!! For example, instead of having separate set_/with_ prefixes, we could have: boot0 .with_major_revision(bounded!(u8, 3)) .with_minor_revision(bounded!(u8, 10)) .with_vendor_id(obtain_vendor_id()) This keeps the syntax clean while still providing compile-time bounds checking for constants. A single unified setter API is way cleaner and less confusing than having set_ for runtime and with_<N>() for compile-time values. Is there some reason why this will not work? -- Joel Fernandes ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 3:47 ` Joel Fernandes @ 2026-01-28 7:42 ` Alexandre Courbot 2026-01-28 17:56 ` Joel Fernandes 0 siblings, 1 reply; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 7:42 UTC (permalink / raw) To: Joel Fernandes Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Wed Jan 28, 2026 at 12:47 PM JST, Joel Fernandes wrote: > On Jan 27, 2026, at 10:02 PM, John Hubbard <jhubbard@nvidia.com> wrote: >> On 1/27/26 6:37 PM, Alexandre Courbot wrote: >>> Add a macro for defining hardware register types with I/O accessors. >> ... >>> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their >>> +/// getter method, which is named after them. They also have setter methods prefixed with `set_` >>> +/// for runtime values and `with_` for constant values. All setters return the updated register >> OK, this still looks like a naming problem that we don't need to create. >> Let's just pick either "set_" or "with_" as a prefix (it seems that >> "with_" has won out, in the v3 discussion thread), and then add "const", >> so that the name doesn't require explanation. >> >> ... >>> +/// // Update some fields and write the new value back. >>> +/// boot0 >>> +/// // Constant values. >>> +/// .with_major_revision::<3>() >>> +/// .with_minor_revision::<10>() >>> +/// // Run-time value. >>> +/// .set_vendor_id(obtain_vendor_id()) >> >> See, that is just not obvious at all, why it's different. That's why >> you had to write a couple of comments. >> >> .with_const_major_revision(), on the other hand, keeps the pattern >> clear and obvious. No comments necessary. >> >> nova-core doesn't even use the const_ settors yet at all, according >> to my quick read of the (very helpful) [PATCH FOR REFERENCE v4 7/7]. >> So clearly it is a bit rare, and the extra characters won't come into >> play in daily life. Great, if everyone agrees on with/with_const then this is definitely idiomatic and clear (and not too verbose). I'll respin tomorrow unless there is new input. > > I completely agree with John here, let us please not create a confusing API. > I would much rather use a bounded! macro to create bounded values and pass > those to a single unified setter, making it a unified API for both constants > and runtime values. Let us not use const generics just for the sake of it or > create special const APIs that no one but examples use!!! > > For example, instead of having separate set_/with_ prefixes, we could have: > > boot0 > .with_major_revision(bounded!(u8, 3)) > .with_minor_revision(bounded!(u8, 10)) > .with_vendor_id(obtain_vendor_id()) > > This keeps the syntax clean while still providing compile-time bounds > checking for constants. A single unified setter API is way cleaner and less > confusing than having set_ for runtime and with_<N>() for compile-time > values. > > Is there some reason why this will not work? So these setters are really part of the `bitfield!` API. Bitfields are, at the end of the day, just integers which we will very likely want to declare constants of out of individual const field values. This can only be done by using const methods, and a const `Bounded` needs to validate its constraints at build time, which requires the `Bounded::new::<VALUE>()` constructor, and by transition similarly-shaped setters to set fields at const time. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 7:42 ` Alexandre Courbot @ 2026-01-28 17:56 ` Joel Fernandes 2026-01-29 11:59 ` Alexandre Courbot 0 siblings, 1 reply; 34+ messages in thread From: Joel Fernandes @ 2026-01-28 17:56 UTC (permalink / raw) To: Alexandre Courbot Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Wed, 28 Jan 2026, Alexandre Courbot wrote: > So these setters are really part of the `bitfield!` API. Bitfields are, > at the end of the day, just integers which we will very likely want to > declare constants of out of individual const field values. This can only > be done by using const methods, and a const `Bounded` needs to validate > its constraints at build time, which requires the > `Bounded::new::<VALUE>()` constructor, and by transition > similarly-shaped setters to set fields at const time. I think you run into that issue because the setter requires to do `.into()` to do the bounded conversion. If we accept Bounded type in the setter directly instead, it should allow to keep the API simple. The advantage is it keeps the API naming simple. with_<field>() which accepts Bounded type, that's the contract. The trade off is the call-site can be a bit more icky but I wonder how much of an issue that really is (it really depends on usage). I wrote a simple program to confirm the same function will work for both const and non-const parameters. Actually the caller does not even need to specific the bounded int size at the call site, it gets inferred. So we don't even need a bounded! macro. struct Bounded<const BITS: u32>(u32); impl<const BITS: u32> Bounded<BITS> { pub const fn new<const VAL: u32>() -> Self { Self(VAL) } pub fn try_new(val: u32) -> Result<Self, Error> { Ok(Self(val)) } } struct Reg(u32); impl Reg { pub const fn with_foo(self, b: Bounded<4>) -> Self { Self(b.0) } } fn bar() -> Result { const C: Reg = Reg(0).with_foo(Bounded::new::<7>()); // const let r = Reg(0).with_foo(Bounded::try_new(9)?); // runtime println!("{} {}", C.0, r.0); Ok(()) } This is clean IMO. Additionally, bounded! and bounded_expr! could be provided to build the bounded types. Thoughts? -- Joel Fernandes ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 17:56 ` Joel Fernandes @ 2026-01-29 11:59 ` Alexandre Courbot 0 siblings, 0 replies; 34+ messages in thread From: Alexandre Courbot @ 2026-01-29 11:59 UTC (permalink / raw) To: Joel Fernandes Cc: John Hubbard, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Thu Jan 29, 2026 at 2:56 AM JST, Joel Fernandes wrote: > On Wed, 28 Jan 2026, Alexandre Courbot wrote: >> So these setters are really part of the `bitfield!` API. Bitfields are, >> at the end of the day, just integers which we will very likely want to >> declare constants of out of individual const field values. This can only >> be done by using const methods, and a const `Bounded` needs to validate >> its constraints at build time, which requires the >> `Bounded::new::<VALUE>()` constructor, and by transition >> similarly-shaped setters to set fields at const time. > > I think you run into that issue because the setter requires to do `.into()` > to do the bounded conversion. Precisely. Without that conversion, the public field setter could just forward to the private one, which is const. > > If we accept Bounded type in the setter directly instead, it > should allow to keep the API simple. The advantage is it keeps the API naming > simple. with_<field>() which accepts Bounded type, that's the contract. The > trade off is the call-site can be a bit more icky but I wonder how much of an > issue that really is (it really depends on usage). I have tried converting the current macro to this idea, and this results in quite a few `.into()` calls. Notably, I find the following a bit (pun intended) annoying: r.with_start(true.into()) `start` is a 1-bit bitfield, so it should accept a boolean, but here we cannot pass it directly. So I am not sure that I prefer this over the current generic argument. In any case, this exercice has revealed that even if we adopt this idea, the issue won't be entirely solved because of the `=>` and `?=>` field conversions. For such fields, performing an `into()` or `try_into()` is mandatory, as that's how the argument is converted to the Bounded internally. So pursuing const-purity at the moment is futile anyway, at least until we get const trait methods. Which leaves the original problem: how do we avoid invoking the awkward `Bounded` constructor for const field values. And AFAICT, taking the `Bounded` directly still requires us to build it, which requires us to specify the backing type explicitly as that's the only way for the compiler to know which constructor to call. So in terms of API convenience I guess the `with_`/`with_const_` combo remains the best bet for now. > > I wrote a simple program to confirm the same function will work for both const > and non-const parameters. Actually the caller does not even need to specific > the bounded int size at the call site, it gets inferred. So we don't even need > a bounded! macro. > > struct Bounded<const BITS: u32>(u32); For this example yes, because you have hardcoded the `Bounded` to be 32 bits, but I have tested this against the version in the kernel and it still requires the fully-specified constructor. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 2:37 ` [PATCH v4 5/7] rust: io: add `register!` macro Alexandre Courbot 2026-01-28 3:02 ` John Hubbard @ 2026-01-28 16:16 ` Gary Guo 2026-01-29 8:00 ` Alexandre Courbot 2026-01-29 12:48 ` Danilo Krummrich 1 sibling, 2 replies; 34+ messages in thread From: Gary Guo @ 2026-01-28 16: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, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: > Add a macro for defining hardware register types with I/O accessors. > > Each register field is represented as a `Bounded` of the appropriate bit > width, ensuring field values are never silently truncated. > > Fields can optionally be converted to/from custom types, either fallibly > or infallibly. > > The address of registers can be direct, relative, or indexed, supporting > most of the patterns in which registers are arranged. > > Tested-by: Dirk Behme <dirk.behme@de.bosch.com> > Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> > --- > rust/kernel/io.rs | 1 + > rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 1288 insertions(+) > > diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs > index 056a3ec71647..112f43ecbf88 100644 > --- a/rust/kernel/io.rs > +++ b/rust/kernel/io.rs > @@ -11,6 +11,7 @@ > > pub mod mem; > pub mod poll; > +pub mod register; > pub mod resource; > > pub use resource::Resource; > diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs > new file mode 100644 > index 000000000000..fc85dcd1f09a > --- /dev/null > +++ b/rust/kernel/io/register.rs > @@ -0,0 +1,1287 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +//! A macro to define register layout and accessors. > +//! > +//! A single register typically includes several fields, which are accessed through a combination > +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because > +//! not all possible field values are necessarily valid. > +//! > +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a > +//! dedicated type for each register. Each such type comes with its own field accessors that can > +//! return an error if a field's value is invalid. > +//! > +//! [`register!`]: kernel::register! > + > +use core::ops::Deref; > + > +use crate::io::{ > + IoCapable, > + IoKnownSize, // > +}; > + > +/// Trait providing a base address to be added to the offset of a relative register to obtain > +/// its actual offset. > +/// > +/// The `T` generic argument is used to distinguish which base to use, in case a type provides > +/// several bases. It is given to the `register!` macro to restrict the use of the register to > +/// implementors of this particular variant. > +pub trait RegisterBase<T> { > + /// Base address to which register offsets are added. > + const BASE: usize; > +} > + > +/// Trait providing I/O read/write operations for register storage types. > +/// > +/// This trait is implemented for all integer types on which I/O can be performed, allowing the > +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage > +/// type. > +pub trait RegisterIo: Sized { Is this trait intended for public usage or just internal detail of `register!()` macro? If it's the former, then we should probably just put the method into `IoCapable` and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so it won't get abused. > + /// Read a value from the given offset in the I/O region. > + fn read<T, I>(io: &T, offset: usize) -> Self > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>; I think generally `Deref` bound shouldn't be exposed to user. What smart pointers are involved here, and can we implement forwarding impls of `Io`? > + > + /// Write a value to the given offset in the I/O region. > + fn write<T, I>(self, io: &T, offset: usize) > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>; > +} > + > +impl RegisterIo for u8 { > + #[inline(always)] > + fn read<T, I>(io: &T, offset: usize) -> Self > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>, > + { > + io.read8(offset) > + } > + > + #[inline(always)] > + fn write<T, I>(self, io: &T, offset: usize) > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>, > + { > + io.write8(self, offset) > + } > +} > + > +impl RegisterIo for u16 { > + #[inline(always)] > + fn read<T, I>(io: &T, offset: usize) -> Self > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>, > + { > + io.read16(offset) > + } > + > + #[inline(always)] > + fn write<T, I>(self, io: &T, offset: usize) > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>, > + { > + io.write16(self, offset) > + } > +} > + > +impl RegisterIo for u32 { > + #[inline(always)] > + fn read<T, I>(io: &T, offset: usize) -> Self > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>, > + { > + io.read32(offset) > + } > + > + #[inline(always)] > + fn write<T, I>(self, io: &T, offset: usize) > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>, > + { > + io.write32(self, offset) > + } > +} > + > +#[cfg(CONFIG_64BIT)] This cfg should be removed. > +impl RegisterIo for u64 { > + #[inline(always)] > + fn read<T, I>(io: &T, offset: usize) -> Self > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>, > + { > + io.read64(offset) > + } > + > + #[inline(always)] > + fn write<T, I>(self, io: &T, offset: usize) > + where > + T: Deref<Target = I>, > + I: IoKnownSize + IoCapable<Self>, > + { > + io.write64(self, offset) > + } > +} > + > +/// Defines a dedicated type for a register, including getter and setter methods for its fields and > +/// methods to read and write it from an [`Io`] region. > +/// > +/// Example: > +/// > +/// ``` > +/// use kernel::register; > +/// > +/// register! { > +/// /// Basic information about the chip. > +/// pub BOOT_0(u32) @ 0x00000100 { > +/// /// Vendor ID. > +/// 15:8 vendor_id; > +/// /// Major revision of the chip. > +/// 7:4 major_revision; > +/// /// Minor revision of the chip. > +/// 3:0 minor_revision; > +/// } > +/// } > +/// ``` > +/// > +/// This defines a `BOOT_0` type which can be read from or written to offset `0x100` of an `Io` > +/// region. For instance, `minor_revision` consists of the 4 least significant bits of the > +/// register. > +/// > +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their > +/// getter method, which is named after them. They also have setter methods prefixed with `set_` > +/// for runtime values and `with_` for constant values. All setters return the updated register > +/// value. > +/// > +/// ```no_run > +/// use kernel::register; > +/// use kernel::num::Bounded; > +/// > +/// # register! { > +/// # pub BOOT_0(u32) @ 0x00000100 { > +/// # 15:8 vendor_id; > +/// # 7:4 major_revision; > +/// # 3:0 minor_revision; > +/// # } > +/// # } > +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) { > +/// # fn obtain_vendor_id() -> u8 { 0xff } > +/// // Read from the register's defined offset (0x100). > +/// let boot0 = BOOT_0::read(&bar); > +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get()); > +/// > +/// // Update some fields and write the new value back. > +/// boot0 > +/// // Constant values. > +/// .with_major_revision::<3>() > +/// .with_minor_revision::<10>() > +/// // Run-time value. > +/// .set_vendor_id(obtain_vendor_id()) > +/// .write(&bar); > +/// > +/// // Or, just read and update the register in a single step. > +/// BOOT_0::update(&bar, |r| r > +/// .with_major_revision::<3>() > +/// .with_minor_revision::<10>() > +/// .set_vendor_id(obtain_vendor_id()) > +/// ); > +/// > +/// // Constant values can also be built using the const setters. > +/// const V: BOOT_0 = BOOT_0::zeroed() > +/// .with_major_revision::<3>() > +/// .with_minor_revision::<10>(); > +/// # } > +/// ``` > +/// > +/// Fields can also be transparently converted from/to an arbitrary type by using the bitfield `=>` > +/// and `?=>` syntaxes. > +/// > +/// If present, doccomments above register or fields definitions are added to the relevant item > +/// they document (the register type itself, or the field's setter and getter methods). > +/// > +/// Note that multiple registers can be defined in a single `register!` invocation. This can be > +/// useful to group related registers together. > +/// > +/// ``` > +/// use kernel::register; > +/// > +/// register! { > +/// pub BOOT_0(u8) @ 0x00000100 { > +/// 7:4 major_revision; > +/// 3:0 minor_revision; > +/// } > +/// > +/// pub BOOT_1(u8) @ 0x00000101 { > +/// 7:5 num_threads; > +/// 4:0 num_cores; > +/// } > +/// }; > +/// ``` > +/// > +/// It is possible to create an alias of an existing register with new field definitions by using > +/// the `=> ALIAS` syntax. This is useful for cases where a register's interpretation depends on > +/// the context: > +/// > +/// ``` > +/// use kernel::register; > +/// > +/// register! { > +/// /// Scratch register. > +/// pub SCRATCH(u32) @ 0x00000200 { > +/// /// Raw value. > +/// 31:0 value; > +/// } > +/// > +/// /// Boot status of the firmware. > +/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH { > +/// /// Whether the firmware has completed booting. > +/// 0:0 completed; > +/// } > +/// } > +/// ``` > +/// > +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also > +/// providing its own `completed` field. > +/// > +/// ## Relative registers > +/// > +/// A register can be defined as being accessible from a fixed offset of a provided base. For > +/// instance, imagine the following I/O space: > +/// > +/// ```text > +/// +-----------------------------+ > +/// | ... | > +/// | | > +/// 0x100--->+------------CPU0-------------+ > +/// | | > +/// 0x110--->+-----------------------------+ > +/// | CPU_CTL | > +/// +-----------------------------+ > +/// | ... | > +/// | | > +/// | | > +/// 0x200--->+------------CPU1-------------+ > +/// | | > +/// 0x210--->+-----------------------------+ > +/// | CPU_CTL | > +/// +-----------------------------+ > +/// | ... | > +/// +-----------------------------+ > +/// ``` > +/// > +/// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O > +/// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define > +/// them twice and would prefer a way to select which one to use from a single definition > +/// > +/// This can be done using the `Base + Offset` syntax when specifying the register's address. > +/// > +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the > +/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for > +/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated > +/// into code: > +/// > +/// ```no_run > +/// use kernel::register; > +/// use kernel::io::register::RegisterBase; > +/// > +/// // Type used to identify the base. > +/// pub struct CpuCtlBase; > +/// > +/// // ZST describing `CPU0`. > +/// struct Cpu0; > +/// impl RegisterBase<CpuCtlBase> for Cpu0 { > +/// const BASE: usize = 0x100; > +/// } > +/// // Singleton of `CPU0` used to identify it. > +/// const CPU0: Cpu0 = Cpu0; > +/// > +/// // ZST describing `CPU1`. > +/// struct Cpu1; > +/// impl RegisterBase<CpuCtlBase> for Cpu1 { > +/// const BASE: usize = 0x200; > +/// } > +/// // Singleton of `CPU1` used to identify it. > +/// const CPU1: Cpu1 = Cpu1; > +/// > +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) { > +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`. > +/// register! { > +/// /// CPU core control. > +/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 { > +/// /// Start the CPU core. > +/// 0:0 start; > +/// } > +/// } > +/// > +/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument > +/// // that is used to resolve its final address by adding its `BASE` to the offset of the > +/// // register. > +/// > +/// // Start `CPU0`. > +/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true)); > +/// > +/// // Start `CPU1`. > +/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true)); > +/// > +/// // Aliases can also be defined for relative register. > +/// register! { > +/// /// Alias to CPU core control. > +/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL { > +/// /// Start the aliased CPU core. > +/// 1:1 alias_start; > +/// } > +/// } > +/// > +/// // Start the aliased `CPU0`. > +/// CPU_CTL_ALIAS::update(&bar, &CPU0, |r| r.set_alias_start(true)); > +/// # } > +/// ``` > +/// > +/// ## Arrays of registers > +/// > +/// Some I/O areas contain consecutive registers that can be interpreted in the same way. These > +/// areas can be defined as an array of identical registers, allowing them to be accessed by index > +/// with compile-time or runtime bound checking. Simply specify their size inside `[` and `]` > +/// brackets, and add an `idx` parameter to their `read`, `write` and `update` methods: > +/// > +/// ```no_run > +/// use kernel::register; > +/// > +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) > +/// # -> Result<(), Error>{ > +/// # fn get_scratch_idx() -> usize { > +/// # 0x15 > +/// # } > +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`. > +/// register! { > +/// /// Scratch registers. > +/// pub SCRATCH(u32)[64] @ 0x00000080 { > +/// 31:0 value; > +/// } > +/// } > +/// > +/// // Read scratch register 0, i.e. I/O address `0x80`. > +/// let scratch_0 = SCRATCH::read(&bar, 0).value(); > +/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`. > +/// let scratch_15 = SCRATCH::read(&bar, 15).value(); > +/// > +/// // This is out of bounds and won't build. > +/// // let scratch_128 = SCRATCH::read(&bar, 128).value(); > +/// > +/// // Runtime-obtained array index. > +/// let scratch_idx = get_scratch_idx(); > +/// // Access on a runtime index returns an error if it is out-of-bounds. > +/// let some_scratch = SCRATCH::try_read(&bar, scratch_idx)?.value(); > +/// > +/// // Alias to a particular register in an array. > +/// // Here `SCRATCH[8]` is used to convey the firmware exit code. > +/// register! { > +/// /// Firmware exit status code. > +/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] { > +/// 7:0 status; > +/// } > +/// } > +/// let status = FIRMWARE_STATUS::read(&bar).status(); > +/// > +/// // Non-contiguous register arrays can be defined by adding a stride parameter. > +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the > +/// // registers of the two declarations below are interleaved. > +/// register! { > +/// /// Scratch registers bank 0. > +/// pub SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ 0x000000c0 { > +/// 31:0 value; > +/// } > +/// > +/// /// Scratch registers bank 1. > +/// pub SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ 0x000000c4 { > +/// 31:0 value; > +/// } > +/// } > +/// # Ok(()) > +/// # } > +/// ``` > +/// > +/// ## Relative arrays of registers > +/// > +/// Combining the two features described in the sections above, arrays of registers accessible from > +/// a base can also be defined: > +/// > +/// ```no_run > +/// use kernel::register; > +/// use kernel::io::register::RegisterBase; > +/// > +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) > +/// # -> Result<(), Error>{ > +/// # fn get_scratch_idx() -> usize { > +/// # 0x15 > +/// # } > +/// // Type used as parameter of `RegisterBase` to specify the base. > +/// pub struct CpuCtlBase; > +/// > +/// // ZST describing `CPU0`. > +/// struct Cpu0; > +/// impl RegisterBase<CpuCtlBase> for Cpu0 { > +/// const BASE: usize = 0x100; > +/// } > +/// // Singleton of `CPU0` used to identify it. > +/// const CPU0: Cpu0 = Cpu0; > +/// > +/// // ZST describing `CPU1`. > +/// struct Cpu1; > +/// impl RegisterBase<CpuCtlBase> for Cpu1 { > +/// const BASE: usize = 0x200; > +/// } > +/// // Singleton of `CPU1` used to identify it. > +/// const CPU1: Cpu1 = Cpu1; > +/// > +/// // 64 per-cpu scratch registers, arranged as a contiguous array. > +/// register! { > +/// /// Per-CPU scratch registers. > +/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 { > +/// 31:0 value; > +/// } > +/// } > +/// > +/// let cpu0_scratch_0 = CPU_SCRATCH::read(&bar, &Cpu0, 0).value(); > +/// let cpu1_scratch_15 = CPU_SCRATCH::read(&bar, &Cpu1, 15).value(); > +/// > +/// // This won't build. > +/// // let cpu0_scratch_128 = CPU_SCRATCH::read(&bar, &Cpu0, 128).value(); > +/// > +/// // Runtime-obtained array index. > +/// let scratch_idx = get_scratch_idx(); > +/// // Access on a runtime value returns an error if it is out-of-bounds. > +/// let cpu0_some_scratch = CPU_SCRATCH::try_read(&bar, &Cpu0, scratch_idx)?.value(); > +/// > +/// // `SCRATCH[8]` is used to convey the firmware exit code. > +/// register! { > +/// /// Per-CPU firmware exit status code. > +/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] { > +/// 7:0 status; > +/// } > +/// } > +/// let cpu0_status = CPU_FIRMWARE_STATUS::read(&bar, &Cpu0).status(); > +/// > +/// // Non-contiguous register arrays can be defined by adding a stride parameter. > +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the > +/// // registers of the two declarations below are interleaved. > +/// register! { > +/// /// Scratch registers bank 0. > +/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ CpuCtlBase + 0x00000d00 { I discussed this with Alex off-list and we agree that this'll better be pub CPU_SCRATCH_INTERLEAVED_0(u32)[u16, stride = 8] @ CpuCtlBase + 0x00000d00 spelling the full intention out rather than have a special syntax, as this isn't the common case. > +/// 31:0 value; > +/// } > +/// > +/// /// Scratch registers bank 1. > +/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ CpuCtlBase + 0x00000d04 { > +/// 31:0 value; > +/// } > +/// } > +/// # Ok(()) > +/// # } > +/// ``` > +/// [`Io`]: kernel::io::Io > +#[macro_export] > +macro_rules! register { > + // Entry point for the macro, allowing multiple registers to be defined in one call. > + // It matches all possible register declaration patterns to dispatch them to corresponding > + // `@reg` rule that defines a single register. > + ( > + $( > + $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) > + $([ $size:expr $(; $stride:expr)? ])? $(@ $offset:literal)? > + $(@ $base:ident + $base_offset:literal)? Does `$(@ $($base:ident +)? $offset:literal)?` not work? > + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )? > + { $($fields:tt)* } > + )* > + ) => { > + $( > + ::kernel::register!( $crate::register!() > + @reg $(#[$attr])* $vis $name ($storage) $([$size $(; $stride)?])? > + $(@ $offset)? > + $(@ $base + $base_offset)? > + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )? > + { $($fields)* } > + ); > + )* > + }; > + > + // All the rules below are private helpers. > + > + // Creates a register at a fixed offset of the MMIO space. > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal > + { $($fields:tt)* } > + ) => { > + ::kernel::register!( > + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } > + ); > + ::kernel::register!(@io_fixed $name($storage) @ $offset); > + }; > + > + // Creates an alias register of fixed offset register `alias` with its own fields. > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident > + { $($fields:tt)* } > + ) => { > + ::kernel::register!( > + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } > + ); > + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET); > + }; > + > + // Creates a register at a relative offset from a base address provider. > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal > + { $($fields:tt)* } > + ) => { > + ::kernel::register!( > + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } > + ); > + ::kernel::register!(@io_relative $name($storage) @ $base + $offset ); > + }; > + > + // Creates an alias register of relative offset register `alias` with its own fields. > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident > + { $($fields:tt)* } > + ) => { > + ::kernel::register!( > + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } > + ); > + ::kernel::register!(@io_relative $name($storage) @ $base + $alias::OFFSET ); Would this generate error messages if $name and $alias are of different base, or would such case be a bug but silently compiles? > + }; > + > + // Creates an array of registers at a fixed offset of the MMIO space. > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) > + [ $size:expr ; $stride:expr ] @ $offset:literal { $($fields:tt)* } > + ) => { > + static_assert!(::core::mem::size_of::<$storage>() <= $stride); > + > + ::kernel::register!( > + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } > + ); > + ::kernel::register!(@io_array $name($storage) [ $size ; $stride ] @ $offset); > + }; > + > + // Shortcut for contiguous array of registers (stride == size of element). > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal > + { $($fields:tt)* } > + ) => { > + ::kernel::register!( > + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ] > + @ $offset { $($fields)* } > + ); > + }; > + > + // Creates an alias of register `idx` of array of registers `alias` with its own fields. > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ] > + { $($fields:tt)* } > + ) => { > + static_assert!($idx < $alias::SIZE); > + > + ::kernel::register!( > + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } > + ); > + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE); > + }; > + > + // Creates an array of registers at a relative offset from a base address provider. > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] > + @ $base:ident + $offset:literal { $($fields:tt)* } > + ) => { > + static_assert!(::core::mem::size_of::<$storage>() <= $stride); > + > + ::kernel::register!( > + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } > + ); > + ::kernel::register!( > + @io_relative_array $name($storage) [ $size ; $stride ] @ $base + $offset > + ); > + }; > + > + // Shortcut for contiguous array of relative registers (stride == size of element). > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] > + @ $base:ident + $offset:literal { $($fields:tt)* } > + ) => { > + ::kernel::register!( > + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ] > + @ $base + $offset { $($fields)* } > + ); > + }; > + > + // Creates an alias of register `idx` of relative array of registers `alias` with its own > + // fields. > + ( > + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) > + => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* } > + ) => { > + static_assert!($idx < $alias::SIZE); > + > + ::kernel::register!( > + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } > + ); > + ::kernel::register!( > + @io_relative $name($storage) @ $base + $alias::OFFSET + $idx * $alias::STRIDE > + ); > + }; > + > + // Generates the bitfield for the register. > + // > + // `#[allow(non_camel_case_types)]` is added since register names typically use SCREAMING_CASE. > + ( > + @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* } > + ) => { > + ::kernel::register!(@bitfield_core > + #[allow(non_camel_case_types)] > + $(#[$attr])* $vis $name $storage > + ); > + ::kernel::register!(@bitfield_fields $vis $name $storage { $($fields)* }); > + }; > + > + // Generates the IO accessors for a fixed offset register. > + (@io_fixed $name:ident ($storage:ty) @ $offset:expr) => { > + #[allow(dead_code)] > + impl $name { > + /// Absolute offset of the register. > + pub const OFFSET: usize = $offset; > + > + /// Read the register from its address in `io`. > + #[inline(always)] > + pub fn read<T, I>(io: &T) -> Self where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + { > + Self(<$storage as $crate::io::register::RegisterIo>::read(io, Self::OFFSET)) > + } > + > + /// Write the value contained in `self` to the register address in `io`. > + #[inline(always)] > + pub fn write<T, I>(self, io: &T) where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + { > + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, Self::OFFSET) > + } > + > + /// Read the register from its address in `io` and run `f` on its value to obtain a new > + /// value to write back. > + /// > + /// Note that this operation is not atomic. In concurrent contexts, external > + /// synchronization may be required to prevent race conditions. Given the non-atomicity, how much value does it provide compared to having the user write read and write themselves? I feel that people reading the code may assume the atomicity without reading docs if they see `FOO::update`, while it's less likely that they do so if they read `FOO::read(io).with_bar(baz).write(io)`. > + #[inline(always)] > + pub fn update<T, I, F>( > + io: &T, > + f: F, > + ) where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + F: ::core::ops::FnOnce(Self) -> Self, > + { > + let reg = f(Self::read(io)); > + reg.write(io); > + } > + } > + }; > + > + // Generates the IO accessors for a relative offset register. > + (@io_relative $name:ident ($storage:ty) @ $base:ident + $offset:expr ) => { > + #[allow(dead_code)] > + impl $name { > + /// Relative offset of the register. > + pub const OFFSET: usize = $offset; > + > + /// Read the register from `io`, using the base address provided by `base` and adding > + /// the register's offset to it. > + #[inline(always)] > + pub fn read<T, I, B>( > + io: &T, > + #[allow(unused_variables)] > + base: &B, > + ) -> Self where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + { > + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET; > + > + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) > + } > + > + /// Write the value contained in `self` to `io`, using the base address provided by > + /// `base` and adding the register's offset to it. > + #[inline(always)] > + pub fn write<T, I, B>( > + self, > + io: &T, > + #[allow(unused_variables)] > + base: &B, > + ) where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + { > + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET; > + > + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) > + } > + > + /// Read the register from `io`, using the base address provided by `base` and adding > + /// the register's offset to it, then run `f` on its value to obtain a new value to > + /// write back. > + /// > + /// Note that this operation is not atomic. In concurrent contexts, external > + /// synchronization may be required to prevent race conditions. > + #[inline(always)] > + pub fn update<T, I, B, F>( > + io: &T, > + base: &B, > + f: F, > + ) where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + F: ::core::ops::FnOnce(Self) -> Self, > + { > + let reg = f(Self::read(io, base)); > + reg.write(io, base); > + } > + } > + }; > + > + // Generates the IO accessors for an array of registers. > + (@io_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] @ $offset:literal) => { > + #[allow(dead_code)] > + impl $name { > + /// Absolute offset of the register array. > + pub const OFFSET: usize = $offset; > + /// Number of elements in the array of registers. > + pub const SIZE: usize = $size; > + /// Number of bytes separating each element of the array of registers. > + pub const STRIDE: usize = $stride; > + > + /// Read the array register at index `idx` from its address in `io`. > + #[inline(always)] > + pub fn read<T, I>( > + io: &T, > + idx: usize, > + ) -> Self where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + { > + build_assert!(idx < Self::SIZE); > + > + let offset = Self::OFFSET + (idx * Self::STRIDE); > + > + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) > + } > + > + /// Write the value contained in `self` to the array register with index `idx` in `io`. > + #[inline(always)] > + pub fn write<T, I>( > + self, > + io: &T, > + idx: usize > + ) where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + { > + build_assert!(idx < Self::SIZE); > + > + let offset = Self::OFFSET + (idx * Self::STRIDE); > + > + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) > + } > + > + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a > + /// new value to write back. > + /// > + /// Note that this operation is not atomic. In concurrent contexts, external > + /// synchronization may be required to prevent race conditions. > + #[inline(always)] > + pub fn update<T, I, F>( > + io: &T, > + idx: usize, > + f: F, > + ) where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + F: ::core::ops::FnOnce(Self) -> Self, > + { > + let reg = f(Self::read(io, idx)); > + reg.write(io, idx); > + } > + > + /// Read the array register at index `idx` from its address in `io`. > + /// > + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the > + /// access was out-of-bounds. > + #[inline(always)] > + pub fn try_read<T, I>( > + io: &T, > + idx: usize, > + ) -> ::kernel::error::Result<Self> where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + { > + if idx < Self::SIZE { > + Ok(Self::read(io, idx)) > + } else { > + Err(::kernel::error::code::EINVAL) > + } > + } > + > + /// Write the value contained in `self` to the array register with index `idx` in `io`. > + /// > + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the > + /// access was out-of-bounds. > + #[inline(always)] > + pub fn try_write<T, I>( > + self, > + io: &T, > + idx: usize, > + ) -> ::kernel::error::Result where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + { > + if idx < Self::SIZE { > + Ok(self.write(io, idx)) > + } else { > + Err(::kernel::error::code::EINVAL) > + } > + } > + > + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a > + /// new value to write back. > + /// > + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the > + /// access was out-of-bounds. > + /// > + /// Note that this operation is not atomic. In concurrent contexts, external > + /// synchronization may be required to prevent race conditions. > + #[inline(always)] > + pub fn try_update<T, I, F>( > + io: &T, > + idx: usize, > + f: F, > + ) -> ::kernel::error::Result where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + F: ::core::ops::FnOnce(Self) -> Self, > + { > + if idx < Self::SIZE { > + Ok(Self::update(io, idx, f)) > + } else { > + Err(::kernel::error::code::EINVAL) > + } > + } > + } > + }; > + > + // Generates the IO accessors for an array of relative registers. > + ( > + @io_relative_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] > + @ $base:ident + $offset:literal > + ) => { > + #[allow(dead_code)] > + impl $name { > + /// Relative offset of the register array. > + pub const OFFSET: usize = $offset; > + /// Number of elements in the array of registers. > + pub const SIZE: usize = $size; > + /// Number of bytes separating each element of the array of registers. > + pub const STRIDE: usize = $stride; > + > + /// Read the array register at index `idx` from `io`, using the base address provided > + /// by `base` and adding the register's offset to it. > + #[inline(always)] > + pub fn read<T, I, B>( > + io: &T, > + #[allow(unused_variables)] > + base: &B, > + idx: usize, > + ) -> Self where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + { > + build_assert!(idx < Self::SIZE); > + > + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + > + Self::OFFSET + (idx * Self::STRIDE); > + > + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) > + } > + > + /// Write the value contained in `self` to `io`, using the base address provided by > + /// `base` and adding the offset of array register `idx` to it. > + #[inline(always)] > + pub fn write<T, I, B>( > + self, > + io: &T, > + #[allow(unused_variables)] > + base: &B, > + idx: usize > + ) where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + { > + build_assert!(idx < Self::SIZE); > + > + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + > + Self::OFFSET + (idx * Self::STRIDE); > + > + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) > + } > + > + /// Read the array register at index `idx` from `io`, using the base address provided > + /// by `base` and adding the register's offset to it, then run `f` on its value to > + /// obtain a new value to write back. > + /// > + /// Note that this operation is not atomic. In concurrent contexts, external > + /// synchronization may be required to prevent race conditions. > + #[inline(always)] > + pub fn update<T, I, B, F>( > + io: &T, > + base: &B, > + idx: usize, > + f: F, > + ) where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + F: ::core::ops::FnOnce(Self) -> Self, > + { > + let reg = f(Self::read(io, base, idx)); > + reg.write(io, base, idx); > + } > + > + /// Read the array register at index `idx` from `io`, using the base address provided > + /// by `base` and adding the register's offset to it. > + /// > + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the > + /// access was out-of-bounds. > + #[inline(always)] > + pub fn try_read<T, I, B>( > + io: &T, > + base: &B, > + idx: usize, > + ) -> ::kernel::error::Result<Self> where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + { > + if idx < Self::SIZE { > + Ok(Self::read(io, base, idx)) > + } else { > + Err(::kernel::error::code::EINVAL) > + } > + } > + > + /// Write the value contained in `self` to `io`, using the base address provided by > + /// `base` and adding the offset of array register `idx` to it. > + /// > + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the > + /// access was out-of-bounds. > + #[inline(always)] > + pub fn try_write<T, I, B>( > + self, > + io: &T, > + base: &B, > + idx: usize, > + ) -> ::kernel::error::Result where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + { > + if idx < Self::SIZE { > + Ok(self.write(io, base, idx)) > + } else { > + Err(::kernel::error::code::EINVAL) > + } > + } > + > + /// Read the array register at index `idx` from `io`, using the base address provided > + /// by `base` and adding the register's offset to it, then run `f` on its value to > + /// obtain a new value to write back. > + /// > + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the > + /// access was out-of-bounds. > + /// > + /// Note that this operation is not atomic. In concurrent contexts, external > + /// synchronization may be required to prevent race conditions. > + #[inline(always)] > + pub fn try_update<T, I, B, F>( > + io: &T, > + base: &B, > + idx: usize, > + f: F, > + ) -> ::kernel::error::Result where > + T: ::core::ops::Deref<Target = I>, > + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, > + B: $crate::io::register::RegisterBase<$base>, > + F: ::core::ops::FnOnce(Self) -> Self, > + { > + if idx < Self::SIZE { > + Ok(Self::update(io, base, idx, f)) > + } else { > + Err(::kernel::error::code::EINVAL) > + } > + } > + } > + }; > + > + // Defines the wrapper `$name` type and its conversions from/to the storage type. > + (@bitfield_core $(#[$attr:meta])* $vis:vis $name:ident $storage:ty) => { > + $(#[$attr])* > + #[repr(transparent)] > + #[derive(Clone, Copy, PartialEq, Eq)] You can derive `Zeroable` and save you an unsafe impl. > + $vis struct $name($storage); > + > + #[allow(dead_code)] > + impl $name { > + /// Creates a bitfield from a raw value. > + $vis const fn from_raw(value: $storage) -> Self { > + Self(value) > + } > + > + /// Creates a zeroed bitfield value. > + /// > + /// This is a const alternative to the `Zeroable::zeroed()` trait method. > + $vis const fn zeroed() -> Self { > + Self(0) > + } All types that impl `Zeroable` automatically have the `::zeroed()` fn provided via the trait. > + > + /// Returns the raw value of this bitfield. > + /// > + /// This is similar to the [`From`] implementation, but is shorter to invoke in > + /// most cases. > + $vis const fn as_raw(self) -> $storage { > + self.0 > + } > + } > + > + // SAFETY: `$storage` is `Zeroable` and `$name` is transparent. > + unsafe impl ::pin_init::Zeroable for $name {} > + > + impl ::core::convert::From<$name> for $storage { > + fn from(val: $name) -> $storage { > + val.as_raw() > + } > + } > + > + impl ::core::convert::From<$storage> for $name { > + fn from(val: $storage) -> $name { > + Self::from_raw(val) > + } > + } > + }; > + > + // Definitions requiring knowledge of individual fields: private and public field accessors, > + // and `Debug` implementation. > + (@bitfield_fields $vis:vis $name:ident $storage:ty { > + $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident > + $(?=> $try_into_type:ty)? > + $(=> $into_type:ty)? > + ; > + )* > + } > + ) => { > + #[allow(dead_code)] > + impl $name { > + $( > + ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field); > + ::kernel::register!( > + @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field > + $(?=> $try_into_type)? > + $(=> $into_type)? > + ); > + )* > + } > + > + ::kernel::register!(@debug $name { $($field;)* }); > + }; > + > + // Private field accessors working with the correct `Bounded` type for the field. > + ( > + @private_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident > + ) => { > + ::kernel::macros::paste!( > + $vis const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi; Is this used by anything? Best, Gary > + $vis const [<$field:upper _MASK>]: $storage = > + ((((1 << $hi) - 1) << 1) + 1) - ((1 << $lo) - 1); > + $vis const [<$field:upper _SHIFT>]: u32 = $lo; > + ); > + > + ::kernel::macros::paste!( > + fn [<__ $field>](self) -> > + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> { > + // Left shift to align the field's MSB with the storage MSB. > + const ALIGN_TOP: u32 = $storage::BITS - ($hi + 1); > + // Right shift to move the top-aligned field to bit 0 of the storage. > + const ALIGN_BOTTOM: u32 = ALIGN_TOP + $lo; > + > + // Extract the field using two shifts. `Bounded::shr` produces the correctly-sized > + // output type. > + let val = ::kernel::num::Bounded::<$storage, { $storage::BITS }>::from( > + self.0 << ALIGN_TOP > + ); > + val.shr::<ALIGN_BOTTOM, { $hi + 1 - $lo } >() > + } > + > + const fn [<__set_ $field>]( > + mut self, > + value: ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>, > + ) -> Self > + { > + const MASK: $storage = $name::[<$field:upper _MASK>]; > + const SHIFT: u32 = $name::[<$field:upper _SHIFT>]; > + > + let value = value.into_inner() << SHIFT; > + self.0 = (self.0 & !MASK) | value; > + > + self > + } > + ); > + }; > + > + // Public accessors for fields infallibly (`=>`) converted to a type. > + ( > + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty : > + $hi:literal:$lo:literal $field:ident => $into_type:ty > + ) => { > + ::kernel::macros::paste!( > + > + $(#[doc = $doc])* > + #[doc = "Returns the value of this field."] > + #[inline(always)] > + $vis fn $field(self) -> $into_type > + { > + self.[<__ $field>]().into() > + } > + > + $(#[doc = $doc])* > + #[doc = "Sets this field to the given `value`."] > + #[inline(always)] > + $vis fn [<set_ $field>](self, value: $into_type) -> Self > + { > + self.[<__set_ $field>](value.into()) > + } > + > + ); > + }; > + > + // Public accessors for fields fallibly (`?=>`) converted to a type. > + ( > + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty : > + $hi:tt:$lo:tt $field:ident ?=> $try_into_type:ty > + ) => { > + ::kernel::macros::paste!( > + > + $(#[doc = $doc])* > + #[doc = "Returns the value of this field."] > + #[inline(always)] > + $vis fn $field(self) -> > + Result< > + $try_into_type, > + <$try_into_type as ::core::convert::TryFrom< > + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> > + >>::Error > + > > + { > + self.[<__ $field>]().try_into() > + } > + > + $(#[doc = $doc])* > + #[doc = "Sets this field to the given `value`."] > + #[inline(always)] > + $vis fn [<set_ $field>](self, value: $try_into_type) -> Self > + { > + self.[<__set_ $field>](value.into()) > + } > + > + ); > + }; > + > + // Public accessors for fields not converted to a type. > + ( > + @public_field_accessors $(#[doc = $doc:expr])* $vis:vis $name:ident $storage:ty : > + $hi:tt:$lo:tt $field:ident > + ) => { > + ::kernel::macros::paste!( > + > + $(#[doc = $doc])* > + #[doc = "Returns the value of this field."] > + #[inline(always)] > + $vis fn $field(self) -> > + ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }> > + { > + self.[<__ $field>]() > + } > + > + $(#[doc = $doc])* > + #[doc = "Sets this field to the compile-time constant `VALUE`."] > + #[inline(always)] > + $vis const fn [<with_ $field>]<const VALUE: $storage>(self) -> Self { > + self.[<__set_ $field>]( > + ::kernel::num::Bounded::<$storage, { $hi + 1 - $lo }>::new::<VALUE>() > + ) > + } > + > + $(#[doc = $doc])* > + #[doc = "Sets this field to the given `value`."] > + #[inline(always)] > + $vis fn [<set_ $field>]<T>( > + self, > + value: T, > + ) -> Self > + where T: Into<::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>>, > + { > + self.[<__set_ $field>](value.into()) > + } > + > + $(#[doc = $doc])* > + #[doc = "Tries to set this field to `value`, returning an error if it is out of range."] > + #[inline(always)] > + $vis fn [<try_set_ $field>]<T>( > + self, > + value: T, > + ) -> ::kernel::error::Result<Self> > + where T: ::kernel::num::TryIntoBounded<$storage, { $hi + 1 - $lo }>, > + { > + Ok( > + self.[<__set_ $field>]( > + value.try_into_bounded().ok_or(::kernel::error::code::EOVERFLOW)? > + ) > + ) > + } > + > + ); > + }; > + > + // `Debug` implementation. > + (@debug $name:ident { $($field:ident;)* }) => { > + impl ::kernel::fmt::Debug for $name { > + fn fmt(&self, f: &mut ::kernel::fmt::Formatter<'_>) -> ::kernel::fmt::Result { > + f.debug_struct(stringify!($name)) > + .field("<raw>", &::kernel::prelude::fmt!("{:#x}", self.0)) > + $( > + .field(stringify!($field), &self.$field()) > + )* > + .finish() > + } > + } > + }; > +} ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 16:16 ` Gary Guo @ 2026-01-29 8:00 ` Alexandre Courbot 2026-01-29 14:10 ` Gary Guo 2026-01-29 12:48 ` Danilo Krummrich 1 sibling, 1 reply; 34+ messages in thread From: Alexandre Courbot @ 2026-01-29 8:00 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 Thu Jan 29, 2026 at 1:16 AM JST, Gary Guo wrote: > On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: >> Add a macro for defining hardware register types with I/O accessors. >> >> Each register field is represented as a `Bounded` of the appropriate bit >> width, ensuring field values are never silently truncated. >> >> Fields can optionally be converted to/from custom types, either fallibly >> or infallibly. >> >> The address of registers can be direct, relative, or indexed, supporting >> most of the patterns in which registers are arranged. >> >> Tested-by: Dirk Behme <dirk.behme@de.bosch.com> >> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >> --- >> rust/kernel/io.rs | 1 + >> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++ >> 2 files changed, 1288 insertions(+) >> >> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs >> index 056a3ec71647..112f43ecbf88 100644 >> --- a/rust/kernel/io.rs >> +++ b/rust/kernel/io.rs >> @@ -11,6 +11,7 @@ >> >> pub mod mem; >> pub mod poll; >> +pub mod register; >> pub mod resource; >> >> pub use resource::Resource; >> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs >> new file mode 100644 >> index 000000000000..fc85dcd1f09a >> --- /dev/null >> +++ b/rust/kernel/io/register.rs >> @@ -0,0 +1,1287 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +//! A macro to define register layout and accessors. >> +//! >> +//! A single register typically includes several fields, which are accessed through a combination >> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because >> +//! not all possible field values are necessarily valid. >> +//! >> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a >> +//! dedicated type for each register. Each such type comes with its own field accessors that can >> +//! return an error if a field's value is invalid. >> +//! >> +//! [`register!`]: kernel::register! >> + >> +use core::ops::Deref; >> + >> +use crate::io::{ >> + IoCapable, >> + IoKnownSize, // >> +}; >> + >> +/// Trait providing a base address to be added to the offset of a relative register to obtain >> +/// its actual offset. >> +/// >> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides >> +/// several bases. It is given to the `register!` macro to restrict the use of the register to >> +/// implementors of this particular variant. >> +pub trait RegisterBase<T> { >> + /// Base address to which register offsets are added. >> + const BASE: usize; >> +} >> + >> +/// Trait providing I/O read/write operations for register storage types. >> +/// >> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the >> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage >> +/// type. >> +pub trait RegisterIo: Sized { > > Is this trait intended for public usage or just internal detail of `register!()` > macro? > > If it's the former, then we should probably just put the method into `IoCapable` > and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so > it won't get abused. It is an internal detail of `register!()` and not supposed to be used by anyone else. > >> + /// Read a value from the given offset in the I/O region. >> + fn read<T, I>(io: &T, offset: usize) -> Self >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>; > > I think generally `Deref` bound shouldn't be exposed to user. What smart > pointers are involved here, and can we implement forwarding impls of `Io`? Removing the requirement for `Deref` in the `RegisterIo` trait is simple - we can just call `Deref` in the register IO accessors. But I suspect you mean that we should also remove it from the register accessors themselves? The problem if I remove it there is that things like Nova's `Bar0` do not implement `IoKnownSize` and `IoCapable` (but rather deref to something that does), which would require all callers to do the deref op itself as deref coercion doesn't seem to be usable here. > >> + >> + /// Write a value to the given offset in the I/O region. >> + fn write<T, I>(self, io: &T, offset: usize) >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>; >> +} >> + >> +impl RegisterIo for u8 { >> + #[inline(always)] >> + fn read<T, I>(io: &T, offset: usize) -> Self >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>, >> + { >> + io.read8(offset) >> + } >> + >> + #[inline(always)] >> + fn write<T, I>(self, io: &T, offset: usize) >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>, >> + { >> + io.write8(self, offset) >> + } >> +} >> + >> +impl RegisterIo for u16 { >> + #[inline(always)] >> + fn read<T, I>(io: &T, offset: usize) -> Self >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>, >> + { >> + io.read16(offset) >> + } >> + >> + #[inline(always)] >> + fn write<T, I>(self, io: &T, offset: usize) >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>, >> + { >> + io.write16(self, offset) >> + } >> +} >> + >> +impl RegisterIo for u32 { >> + #[inline(always)] >> + fn read<T, I>(io: &T, offset: usize) -> Self >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>, >> + { >> + io.read32(offset) >> + } >> + >> + #[inline(always)] >> + fn write<T, I>(self, io: &T, offset: usize) >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>, >> + { >> + io.write32(self, offset) >> + } >> +} >> + >> +#[cfg(CONFIG_64BIT)] > > This cfg should be removed. Done. > >> +impl RegisterIo for u64 { >> + #[inline(always)] >> + fn read<T, I>(io: &T, offset: usize) -> Self >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>, >> + { >> + io.read64(offset) >> + } >> + >> + #[inline(always)] >> + fn write<T, I>(self, io: &T, offset: usize) >> + where >> + T: Deref<Target = I>, >> + I: IoKnownSize + IoCapable<Self>, >> + { >> + io.write64(self, offset) >> + } >> +} >> + >> +/// Defines a dedicated type for a register, including getter and setter methods for its fields and >> +/// methods to read and write it from an [`Io`] region. >> +/// >> +/// Example: >> +/// >> +/// ``` >> +/// use kernel::register; >> +/// >> +/// register! { >> +/// /// Basic information about the chip. >> +/// pub BOOT_0(u32) @ 0x00000100 { >> +/// /// Vendor ID. >> +/// 15:8 vendor_id; >> +/// /// Major revision of the chip. >> +/// 7:4 major_revision; >> +/// /// Minor revision of the chip. >> +/// 3:0 minor_revision; >> +/// } >> +/// } >> +/// ``` >> +/// >> +/// This defines a `BOOT_0` type which can be read from or written to offset `0x100` of an `Io` >> +/// region. For instance, `minor_revision` consists of the 4 least significant bits of the >> +/// register. >> +/// >> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their >> +/// getter method, which is named after them. They also have setter methods prefixed with `set_` >> +/// for runtime values and `with_` for constant values. All setters return the updated register >> +/// value. >> +/// >> +/// ```no_run >> +/// use kernel::register; >> +/// use kernel::num::Bounded; >> +/// >> +/// # register! { >> +/// # pub BOOT_0(u32) @ 0x00000100 { >> +/// # 15:8 vendor_id; >> +/// # 7:4 major_revision; >> +/// # 3:0 minor_revision; >> +/// # } >> +/// # } >> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) { >> +/// # fn obtain_vendor_id() -> u8 { 0xff } >> +/// // Read from the register's defined offset (0x100). >> +/// let boot0 = BOOT_0::read(&bar); >> +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get()); >> +/// >> +/// // Update some fields and write the new value back. >> +/// boot0 >> +/// // Constant values. >> +/// .with_major_revision::<3>() >> +/// .with_minor_revision::<10>() >> +/// // Run-time value. >> +/// .set_vendor_id(obtain_vendor_id()) >> +/// .write(&bar); >> +/// >> +/// // Or, just read and update the register in a single step. >> +/// BOOT_0::update(&bar, |r| r >> +/// .with_major_revision::<3>() >> +/// .with_minor_revision::<10>() >> +/// .set_vendor_id(obtain_vendor_id()) >> +/// ); >> +/// >> +/// // Constant values can also be built using the const setters. >> +/// const V: BOOT_0 = BOOT_0::zeroed() >> +/// .with_major_revision::<3>() >> +/// .with_minor_revision::<10>(); >> +/// # } >> +/// ``` >> +/// >> +/// Fields can also be transparently converted from/to an arbitrary type by using the bitfield `=>` >> +/// and `?=>` syntaxes. >> +/// >> +/// If present, doccomments above register or fields definitions are added to the relevant item >> +/// they document (the register type itself, or the field's setter and getter methods). >> +/// >> +/// Note that multiple registers can be defined in a single `register!` invocation. This can be >> +/// useful to group related registers together. >> +/// >> +/// ``` >> +/// use kernel::register; >> +/// >> +/// register! { >> +/// pub BOOT_0(u8) @ 0x00000100 { >> +/// 7:4 major_revision; >> +/// 3:0 minor_revision; >> +/// } >> +/// >> +/// pub BOOT_1(u8) @ 0x00000101 { >> +/// 7:5 num_threads; >> +/// 4:0 num_cores; >> +/// } >> +/// }; >> +/// ``` >> +/// >> +/// It is possible to create an alias of an existing register with new field definitions by using >> +/// the `=> ALIAS` syntax. This is useful for cases where a register's interpretation depends on >> +/// the context: >> +/// >> +/// ``` >> +/// use kernel::register; >> +/// >> +/// register! { >> +/// /// Scratch register. >> +/// pub SCRATCH(u32) @ 0x00000200 { >> +/// /// Raw value. >> +/// 31:0 value; >> +/// } >> +/// >> +/// /// Boot status of the firmware. >> +/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH { >> +/// /// Whether the firmware has completed booting. >> +/// 0:0 completed; >> +/// } >> +/// } >> +/// ``` >> +/// >> +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also >> +/// providing its own `completed` field. >> +/// >> +/// ## Relative registers >> +/// >> +/// A register can be defined as being accessible from a fixed offset of a provided base. For >> +/// instance, imagine the following I/O space: >> +/// >> +/// ```text >> +/// +-----------------------------+ >> +/// | ... | >> +/// | | >> +/// 0x100--->+------------CPU0-------------+ >> +/// | | >> +/// 0x110--->+-----------------------------+ >> +/// | CPU_CTL | >> +/// +-----------------------------+ >> +/// | ... | >> +/// | | >> +/// | | >> +/// 0x200--->+------------CPU1-------------+ >> +/// | | >> +/// 0x210--->+-----------------------------+ >> +/// | CPU_CTL | >> +/// +-----------------------------+ >> +/// | ... | >> +/// +-----------------------------+ >> +/// ``` >> +/// >> +/// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O >> +/// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define >> +/// them twice and would prefer a way to select which one to use from a single definition >> +/// >> +/// This can be done using the `Base + Offset` syntax when specifying the register's address. >> +/// >> +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the >> +/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for >> +/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated >> +/// into code: >> +/// >> +/// ```no_run >> +/// use kernel::register; >> +/// use kernel::io::register::RegisterBase; >> +/// >> +/// // Type used to identify the base. >> +/// pub struct CpuCtlBase; >> +/// >> +/// // ZST describing `CPU0`. >> +/// struct Cpu0; >> +/// impl RegisterBase<CpuCtlBase> for Cpu0 { >> +/// const BASE: usize = 0x100; >> +/// } >> +/// // Singleton of `CPU0` used to identify it. >> +/// const CPU0: Cpu0 = Cpu0; >> +/// >> +/// // ZST describing `CPU1`. >> +/// struct Cpu1; >> +/// impl RegisterBase<CpuCtlBase> for Cpu1 { >> +/// const BASE: usize = 0x200; >> +/// } >> +/// // Singleton of `CPU1` used to identify it. >> +/// const CPU1: Cpu1 = Cpu1; >> +/// >> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) { >> +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`. >> +/// register! { >> +/// /// CPU core control. >> +/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 { >> +/// /// Start the CPU core. >> +/// 0:0 start; >> +/// } >> +/// } >> +/// >> +/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument >> +/// // that is used to resolve its final address by adding its `BASE` to the offset of the >> +/// // register. >> +/// >> +/// // Start `CPU0`. >> +/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true)); >> +/// >> +/// // Start `CPU1`. >> +/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true)); >> +/// >> +/// // Aliases can also be defined for relative register. >> +/// register! { >> +/// /// Alias to CPU core control. >> +/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL { >> +/// /// Start the aliased CPU core. >> +/// 1:1 alias_start; >> +/// } >> +/// } >> +/// >> +/// // Start the aliased `CPU0`. >> +/// CPU_CTL_ALIAS::update(&bar, &CPU0, |r| r.set_alias_start(true)); >> +/// # } >> +/// ``` >> +/// >> +/// ## Arrays of registers >> +/// >> +/// Some I/O areas contain consecutive registers that can be interpreted in the same way. These >> +/// areas can be defined as an array of identical registers, allowing them to be accessed by index >> +/// with compile-time or runtime bound checking. Simply specify their size inside `[` and `]` >> +/// brackets, and add an `idx` parameter to their `read`, `write` and `update` methods: >> +/// >> +/// ```no_run >> +/// use kernel::register; >> +/// >> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) >> +/// # -> Result<(), Error>{ >> +/// # fn get_scratch_idx() -> usize { >> +/// # 0x15 >> +/// # } >> +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`. >> +/// register! { >> +/// /// Scratch registers. >> +/// pub SCRATCH(u32)[64] @ 0x00000080 { >> +/// 31:0 value; >> +/// } >> +/// } >> +/// >> +/// // Read scratch register 0, i.e. I/O address `0x80`. >> +/// let scratch_0 = SCRATCH::read(&bar, 0).value(); >> +/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`. >> +/// let scratch_15 = SCRATCH::read(&bar, 15).value(); >> +/// >> +/// // This is out of bounds and won't build. >> +/// // let scratch_128 = SCRATCH::read(&bar, 128).value(); >> +/// >> +/// // Runtime-obtained array index. >> +/// let scratch_idx = get_scratch_idx(); >> +/// // Access on a runtime index returns an error if it is out-of-bounds. >> +/// let some_scratch = SCRATCH::try_read(&bar, scratch_idx)?.value(); >> +/// >> +/// // Alias to a particular register in an array. >> +/// // Here `SCRATCH[8]` is used to convey the firmware exit code. >> +/// register! { >> +/// /// Firmware exit status code. >> +/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] { >> +/// 7:0 status; >> +/// } >> +/// } >> +/// let status = FIRMWARE_STATUS::read(&bar).status(); >> +/// >> +/// // Non-contiguous register arrays can be defined by adding a stride parameter. >> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the >> +/// // registers of the two declarations below are interleaved. >> +/// register! { >> +/// /// Scratch registers bank 0. >> +/// pub SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ 0x000000c0 { >> +/// 31:0 value; >> +/// } >> +/// >> +/// /// Scratch registers bank 1. >> +/// pub SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ 0x000000c4 { >> +/// 31:0 value; >> +/// } >> +/// } >> +/// # Ok(()) >> +/// # } >> +/// ``` >> +/// >> +/// ## Relative arrays of registers >> +/// >> +/// Combining the two features described in the sections above, arrays of registers accessible from >> +/// a base can also be defined: >> +/// >> +/// ```no_run >> +/// use kernel::register; >> +/// use kernel::io::register::RegisterBase; >> +/// >> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) >> +/// # -> Result<(), Error>{ >> +/// # fn get_scratch_idx() -> usize { >> +/// # 0x15 >> +/// # } >> +/// // Type used as parameter of `RegisterBase` to specify the base. >> +/// pub struct CpuCtlBase; >> +/// >> +/// // ZST describing `CPU0`. >> +/// struct Cpu0; >> +/// impl RegisterBase<CpuCtlBase> for Cpu0 { >> +/// const BASE: usize = 0x100; >> +/// } >> +/// // Singleton of `CPU0` used to identify it. >> +/// const CPU0: Cpu0 = Cpu0; >> +/// >> +/// // ZST describing `CPU1`. >> +/// struct Cpu1; >> +/// impl RegisterBase<CpuCtlBase> for Cpu1 { >> +/// const BASE: usize = 0x200; >> +/// } >> +/// // Singleton of `CPU1` used to identify it. >> +/// const CPU1: Cpu1 = Cpu1; >> +/// >> +/// // 64 per-cpu scratch registers, arranged as a contiguous array. >> +/// register! { >> +/// /// Per-CPU scratch registers. >> +/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 { >> +/// 31:0 value; >> +/// } >> +/// } >> +/// >> +/// let cpu0_scratch_0 = CPU_SCRATCH::read(&bar, &Cpu0, 0).value(); >> +/// let cpu1_scratch_15 = CPU_SCRATCH::read(&bar, &Cpu1, 15).value(); >> +/// >> +/// // This won't build. >> +/// // let cpu0_scratch_128 = CPU_SCRATCH::read(&bar, &Cpu0, 128).value(); >> +/// >> +/// // Runtime-obtained array index. >> +/// let scratch_idx = get_scratch_idx(); >> +/// // Access on a runtime value returns an error if it is out-of-bounds. >> +/// let cpu0_some_scratch = CPU_SCRATCH::try_read(&bar, &Cpu0, scratch_idx)?.value(); >> +/// >> +/// // `SCRATCH[8]` is used to convey the firmware exit code. >> +/// register! { >> +/// /// Per-CPU firmware exit status code. >> +/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] { >> +/// 7:0 status; >> +/// } >> +/// } >> +/// let cpu0_status = CPU_FIRMWARE_STATUS::read(&bar, &Cpu0).status(); >> +/// >> +/// // Non-contiguous register arrays can be defined by adding a stride parameter. >> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the >> +/// // registers of the two declarations below are interleaved. >> +/// register! { >> +/// /// Scratch registers bank 0. >> +/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ CpuCtlBase + 0x00000d00 { > > I discussed this with Alex off-list and we agree that this'll better be > > pub CPU_SCRATCH_INTERLEAVED_0(u32)[u16, stride = 8] @ CpuCtlBase + 0x00000d00 > > spelling the full intention out rather than have a special syntax, as this isn't > the common case. Implemented it and it indeed looks much better. > >> +/// 31:0 value; >> +/// } >> +/// >> +/// /// Scratch registers bank 1. >> +/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ CpuCtlBase + 0x00000d04 { >> +/// 31:0 value; >> +/// } >> +/// } >> +/// # Ok(()) >> +/// # } >> +/// ``` >> +/// [`Io`]: kernel::io::Io >> +#[macro_export] >> +macro_rules! register { >> + // Entry point for the macro, allowing multiple registers to be defined in one call. >> + // It matches all possible register declaration patterns to dispatch them to corresponding >> + // `@reg` rule that defines a single register. >> + ( >> + $( >> + $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) >> + $([ $size:expr $(; $stride:expr)? ])? $(@ $offset:literal)? >> + $(@ $base:ident + $base_offset:literal)? > > Does `$(@ $($base:ident +)? $offset:literal)?` not work? It does! I was trying to match the `+` in the right-side expression and obviously couldn't, but it didn't occur to me to do it the other way. :) > >> + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )? >> + { $($fields:tt)* } >> + )* >> + ) => { >> + $( >> + ::kernel::register!( > > $crate::register!() Updated throughout the file (I am not sure to understand the difference though?). > >> + @reg $(#[$attr])* $vis $name ($storage) $([$size $(; $stride)?])? >> + $(@ $offset)? >> + $(@ $base + $base_offset)? >> + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )? >> + { $($fields)* } >> + ); >> + )* >> + }; >> + >> + // All the rules below are private helpers. >> + >> + // Creates a register at a fixed offset of the MMIO space. >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal >> + { $($fields:tt)* } >> + ) => { >> + ::kernel::register!( >> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >> + ); >> + ::kernel::register!(@io_fixed $name($storage) @ $offset); >> + }; >> + >> + // Creates an alias register of fixed offset register `alias` with its own fields. >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident >> + { $($fields:tt)* } >> + ) => { >> + ::kernel::register!( >> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >> + ); >> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET); >> + }; >> + >> + // Creates a register at a relative offset from a base address provider. >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal >> + { $($fields:tt)* } >> + ) => { >> + ::kernel::register!( >> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >> + ); >> + ::kernel::register!(@io_relative $name($storage) @ $base + $offset ); >> + }; >> + >> + // Creates an alias register of relative offset register `alias` with its own fields. >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident >> + { $($fields:tt)* } >> + ) => { >> + ::kernel::register!( >> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >> + ); >> + ::kernel::register!(@io_relative $name($storage) @ $base + $alias::OFFSET ); > > Would this generate error messages if $name and $alias are of different base, or > would such case be a bug but silently compiles? You would get an build error when trying to use the alias as the bases are dedicated types and the types would be incompatible. > >> + }; >> + >> + // Creates an array of registers at a fixed offset of the MMIO space. >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) >> + [ $size:expr ; $stride:expr ] @ $offset:literal { $($fields:tt)* } >> + ) => { >> + static_assert!(::core::mem::size_of::<$storage>() <= $stride); >> + >> + ::kernel::register!( >> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >> + ); >> + ::kernel::register!(@io_array $name($storage) [ $size ; $stride ] @ $offset); >> + }; >> + >> + // Shortcut for contiguous array of registers (stride == size of element). >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal >> + { $($fields:tt)* } >> + ) => { >> + ::kernel::register!( >> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ] >> + @ $offset { $($fields)* } >> + ); >> + }; >> + >> + // Creates an alias of register `idx` of array of registers `alias` with its own fields. >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ] >> + { $($fields:tt)* } >> + ) => { >> + static_assert!($idx < $alias::SIZE); >> + >> + ::kernel::register!( >> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >> + ); >> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE); >> + }; >> + >> + // Creates an array of registers at a relative offset from a base address provider. >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] >> + @ $base:ident + $offset:literal { $($fields:tt)* } >> + ) => { >> + static_assert!(::core::mem::size_of::<$storage>() <= $stride); >> + >> + ::kernel::register!( >> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >> + ); >> + ::kernel::register!( >> + @io_relative_array $name($storage) [ $size ; $stride ] @ $base + $offset >> + ); >> + }; >> + >> + // Shortcut for contiguous array of relative registers (stride == size of element). >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] >> + @ $base:ident + $offset:literal { $($fields:tt)* } >> + ) => { >> + ::kernel::register!( >> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ] >> + @ $base + $offset { $($fields)* } >> + ); >> + }; >> + >> + // Creates an alias of register `idx` of relative array of registers `alias` with its own >> + // fields. >> + ( >> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) >> + => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* } >> + ) => { >> + static_assert!($idx < $alias::SIZE); >> + >> + ::kernel::register!( >> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >> + ); >> + ::kernel::register!( >> + @io_relative $name($storage) @ $base + $alias::OFFSET + $idx * $alias::STRIDE >> + ); >> + }; >> + >> + // Generates the bitfield for the register. >> + // >> + // `#[allow(non_camel_case_types)]` is added since register names typically use SCREAMING_CASE. >> + ( >> + @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* } >> + ) => { >> + ::kernel::register!(@bitfield_core >> + #[allow(non_camel_case_types)] >> + $(#[$attr])* $vis $name $storage >> + ); >> + ::kernel::register!(@bitfield_fields $vis $name $storage { $($fields)* }); >> + }; >> + >> + // Generates the IO accessors for a fixed offset register. >> + (@io_fixed $name:ident ($storage:ty) @ $offset:expr) => { >> + #[allow(dead_code)] >> + impl $name { >> + /// Absolute offset of the register. >> + pub const OFFSET: usize = $offset; >> + >> + /// Read the register from its address in `io`. >> + #[inline(always)] >> + pub fn read<T, I>(io: &T) -> Self where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + { >> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, Self::OFFSET)) >> + } >> + >> + /// Write the value contained in `self` to the register address in `io`. >> + #[inline(always)] >> + pub fn write<T, I>(self, io: &T) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + { >> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, Self::OFFSET) >> + } >> + >> + /// Read the register from its address in `io` and run `f` on its value to obtain a new >> + /// value to write back. >> + /// >> + /// Note that this operation is not atomic. In concurrent contexts, external >> + /// synchronization may be required to prevent race conditions. > > Given the non-atomicity, how much value does it provide compared to having the > user write read and write themselves? I feel that people reading the code may > assume the atomicity without reading docs if they see `FOO::update`, while it's > less likely that they do so if they read > `FOO::read(io).with_bar(baz).write(io)`. It is just a convenience method. I cannot pretend it is widely used, but it allows to turn multiple-step ops into one-liners. > >> + #[inline(always)] >> + pub fn update<T, I, F>( >> + io: &T, >> + f: F, >> + ) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + F: ::core::ops::FnOnce(Self) -> Self, >> + { >> + let reg = f(Self::read(io)); >> + reg.write(io); >> + } >> + } >> + }; >> + >> + // Generates the IO accessors for a relative offset register. >> + (@io_relative $name:ident ($storage:ty) @ $base:ident + $offset:expr ) => { >> + #[allow(dead_code)] >> + impl $name { >> + /// Relative offset of the register. >> + pub const OFFSET: usize = $offset; >> + >> + /// Read the register from `io`, using the base address provided by `base` and adding >> + /// the register's offset to it. >> + #[inline(always)] >> + pub fn read<T, I, B>( >> + io: &T, >> + #[allow(unused_variables)] >> + base: &B, >> + ) -> Self where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + { >> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET; >> + >> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) >> + } >> + >> + /// Write the value contained in `self` to `io`, using the base address provided by >> + /// `base` and adding the register's offset to it. >> + #[inline(always)] >> + pub fn write<T, I, B>( >> + self, >> + io: &T, >> + #[allow(unused_variables)] >> + base: &B, >> + ) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + { >> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET; >> + >> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) >> + } >> + >> + /// Read the register from `io`, using the base address provided by `base` and adding >> + /// the register's offset to it, then run `f` on its value to obtain a new value to >> + /// write back. >> + /// >> + /// Note that this operation is not atomic. In concurrent contexts, external >> + /// synchronization may be required to prevent race conditions. >> + #[inline(always)] >> + pub fn update<T, I, B, F>( >> + io: &T, >> + base: &B, >> + f: F, >> + ) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + F: ::core::ops::FnOnce(Self) -> Self, >> + { >> + let reg = f(Self::read(io, base)); >> + reg.write(io, base); >> + } >> + } >> + }; >> + >> + // Generates the IO accessors for an array of registers. >> + (@io_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] @ $offset:literal) => { >> + #[allow(dead_code)] >> + impl $name { >> + /// Absolute offset of the register array. >> + pub const OFFSET: usize = $offset; >> + /// Number of elements in the array of registers. >> + pub const SIZE: usize = $size; >> + /// Number of bytes separating each element of the array of registers. >> + pub const STRIDE: usize = $stride; >> + >> + /// Read the array register at index `idx` from its address in `io`. >> + #[inline(always)] >> + pub fn read<T, I>( >> + io: &T, >> + idx: usize, >> + ) -> Self where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + { >> + build_assert!(idx < Self::SIZE); >> + >> + let offset = Self::OFFSET + (idx * Self::STRIDE); >> + >> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) >> + } >> + >> + /// Write the value contained in `self` to the array register with index `idx` in `io`. >> + #[inline(always)] >> + pub fn write<T, I>( >> + self, >> + io: &T, >> + idx: usize >> + ) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + { >> + build_assert!(idx < Self::SIZE); >> + >> + let offset = Self::OFFSET + (idx * Self::STRIDE); >> + >> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) >> + } >> + >> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a >> + /// new value to write back. >> + /// >> + /// Note that this operation is not atomic. In concurrent contexts, external >> + /// synchronization may be required to prevent race conditions. >> + #[inline(always)] >> + pub fn update<T, I, F>( >> + io: &T, >> + idx: usize, >> + f: F, >> + ) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + F: ::core::ops::FnOnce(Self) -> Self, >> + { >> + let reg = f(Self::read(io, idx)); >> + reg.write(io, idx); >> + } >> + >> + /// Read the array register at index `idx` from its address in `io`. >> + /// >> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >> + /// access was out-of-bounds. >> + #[inline(always)] >> + pub fn try_read<T, I>( >> + io: &T, >> + idx: usize, >> + ) -> ::kernel::error::Result<Self> where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + { >> + if idx < Self::SIZE { >> + Ok(Self::read(io, idx)) >> + } else { >> + Err(::kernel::error::code::EINVAL) >> + } >> + } >> + >> + /// Write the value contained in `self` to the array register with index `idx` in `io`. >> + /// >> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >> + /// access was out-of-bounds. >> + #[inline(always)] >> + pub fn try_write<T, I>( >> + self, >> + io: &T, >> + idx: usize, >> + ) -> ::kernel::error::Result where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + { >> + if idx < Self::SIZE { >> + Ok(self.write(io, idx)) >> + } else { >> + Err(::kernel::error::code::EINVAL) >> + } >> + } >> + >> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a >> + /// new value to write back. >> + /// >> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >> + /// access was out-of-bounds. >> + /// >> + /// Note that this operation is not atomic. In concurrent contexts, external >> + /// synchronization may be required to prevent race conditions. >> + #[inline(always)] >> + pub fn try_update<T, I, F>( >> + io: &T, >> + idx: usize, >> + f: F, >> + ) -> ::kernel::error::Result where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + F: ::core::ops::FnOnce(Self) -> Self, >> + { >> + if idx < Self::SIZE { >> + Ok(Self::update(io, idx, f)) >> + } else { >> + Err(::kernel::error::code::EINVAL) >> + } >> + } >> + } >> + }; >> + >> + // Generates the IO accessors for an array of relative registers. >> + ( >> + @io_relative_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] >> + @ $base:ident + $offset:literal >> + ) => { >> + #[allow(dead_code)] >> + impl $name { >> + /// Relative offset of the register array. >> + pub const OFFSET: usize = $offset; >> + /// Number of elements in the array of registers. >> + pub const SIZE: usize = $size; >> + /// Number of bytes separating each element of the array of registers. >> + pub const STRIDE: usize = $stride; >> + >> + /// Read the array register at index `idx` from `io`, using the base address provided >> + /// by `base` and adding the register's offset to it. >> + #[inline(always)] >> + pub fn read<T, I, B>( >> + io: &T, >> + #[allow(unused_variables)] >> + base: &B, >> + idx: usize, >> + ) -> Self where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + { >> + build_assert!(idx < Self::SIZE); >> + >> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + >> + Self::OFFSET + (idx * Self::STRIDE); >> + >> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) >> + } >> + >> + /// Write the value contained in `self` to `io`, using the base address provided by >> + /// `base` and adding the offset of array register `idx` to it. >> + #[inline(always)] >> + pub fn write<T, I, B>( >> + self, >> + io: &T, >> + #[allow(unused_variables)] >> + base: &B, >> + idx: usize >> + ) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + { >> + build_assert!(idx < Self::SIZE); >> + >> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + >> + Self::OFFSET + (idx * Self::STRIDE); >> + >> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) >> + } >> + >> + /// Read the array register at index `idx` from `io`, using the base address provided >> + /// by `base` and adding the register's offset to it, then run `f` on its value to >> + /// obtain a new value to write back. >> + /// >> + /// Note that this operation is not atomic. In concurrent contexts, external >> + /// synchronization may be required to prevent race conditions. >> + #[inline(always)] >> + pub fn update<T, I, B, F>( >> + io: &T, >> + base: &B, >> + idx: usize, >> + f: F, >> + ) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + F: ::core::ops::FnOnce(Self) -> Self, >> + { >> + let reg = f(Self::read(io, base, idx)); >> + reg.write(io, base, idx); >> + } >> + >> + /// Read the array register at index `idx` from `io`, using the base address provided >> + /// by `base` and adding the register's offset to it. >> + /// >> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >> + /// access was out-of-bounds. >> + #[inline(always)] >> + pub fn try_read<T, I, B>( >> + io: &T, >> + base: &B, >> + idx: usize, >> + ) -> ::kernel::error::Result<Self> where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + { >> + if idx < Self::SIZE { >> + Ok(Self::read(io, base, idx)) >> + } else { >> + Err(::kernel::error::code::EINVAL) >> + } >> + } >> + >> + /// Write the value contained in `self` to `io`, using the base address provided by >> + /// `base` and adding the offset of array register `idx` to it. >> + /// >> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >> + /// access was out-of-bounds. >> + #[inline(always)] >> + pub fn try_write<T, I, B>( >> + self, >> + io: &T, >> + base: &B, >> + idx: usize, >> + ) -> ::kernel::error::Result where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + { >> + if idx < Self::SIZE { >> + Ok(self.write(io, base, idx)) >> + } else { >> + Err(::kernel::error::code::EINVAL) >> + } >> + } >> + >> + /// Read the array register at index `idx` from `io`, using the base address provided >> + /// by `base` and adding the register's offset to it, then run `f` on its value to >> + /// obtain a new value to write back. >> + /// >> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >> + /// access was out-of-bounds. >> + /// >> + /// Note that this operation is not atomic. In concurrent contexts, external >> + /// synchronization may be required to prevent race conditions. >> + #[inline(always)] >> + pub fn try_update<T, I, B, F>( >> + io: &T, >> + base: &B, >> + idx: usize, >> + f: F, >> + ) -> ::kernel::error::Result where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + B: $crate::io::register::RegisterBase<$base>, >> + F: ::core::ops::FnOnce(Self) -> Self, >> + { >> + if idx < Self::SIZE { >> + Ok(Self::update(io, base, idx, f)) >> + } else { >> + Err(::kernel::error::code::EINVAL) >> + } >> + } >> + } >> + }; >> + >> + // Defines the wrapper `$name` type and its conversions from/to the storage type. >> + (@bitfield_core $(#[$attr:meta])* $vis:vis $name:ident $storage:ty) => { >> + $(#[$attr])* >> + #[repr(transparent)] >> + #[derive(Clone, Copy, PartialEq, Eq)] > > You can derive `Zeroable` and save you an unsafe impl. Unfortunately this happens if I try to do it: error: no rules expected `(` --> ../samples/rust/rust_driver_pci.rs:74:9 | 74 | / ::kernel::register! { 75 | | VENDOR_ID(u16) @ 0x0 { 76 | | 15:0 vendor_id; ... | 86 | | } | |_________^ no rules expected this token in macro call Not quite sure why to be honest. > >> + $vis struct $name($storage); >> + >> + #[allow(dead_code)] >> + impl $name { >> + /// Creates a bitfield from a raw value. >> + $vis const fn from_raw(value: $storage) -> Self { >> + Self(value) >> + } >> + >> + /// Creates a zeroed bitfield value. >> + /// >> + /// This is a const alternative to the `Zeroable::zeroed()` trait method. >> + $vis const fn zeroed() -> Self { >> + Self(0) >> + } > > All types that impl `Zeroable` automatically have the `::zeroed()` fn provided > via the trait. Yes, but that method from the trait cannot be used in const context, and `zeroed` is the starting point for building register values from scratch (and thus constant values). > >> + >> + /// Returns the raw value of this bitfield. >> + /// >> + /// This is similar to the [`From`] implementation, but is shorter to invoke in >> + /// most cases. >> + $vis const fn as_raw(self) -> $storage { >> + self.0 >> + } >> + } >> + >> + // SAFETY: `$storage` is `Zeroable` and `$name` is transparent. >> + unsafe impl ::pin_init::Zeroable for $name {} >> + >> + impl ::core::convert::From<$name> for $storage { >> + fn from(val: $name) -> $storage { >> + val.as_raw() >> + } >> + } >> + >> + impl ::core::convert::From<$storage> for $name { >> + fn from(val: $storage) -> $name { >> + Self::from_raw(val) >> + } >> + } >> + }; >> + >> + // Definitions requiring knowledge of individual fields: private and public field accessors, >> + // and `Debug` implementation. >> + (@bitfield_fields $vis:vis $name:ident $storage:ty { >> + $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident >> + $(?=> $try_into_type:ty)? >> + $(=> $into_type:ty)? >> + ; >> + )* >> + } >> + ) => { >> + #[allow(dead_code)] >> + impl $name { >> + $( >> + ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field); >> + ::kernel::register!( >> + @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field >> + $(?=> $try_into_type)? >> + $(=> $into_type)? >> + ); >> + )* >> + } >> + >> + ::kernel::register!(@debug $name { $($field;)* }); >> + }; >> + >> + // Private field accessors working with the correct `Bounded` type for the field. >> + ( >> + @private_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident >> + ) => { >> + ::kernel::macros::paste!( >> + $vis const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi; > > Is this used by anything? Not yet. This is intended for user code that wants to know which bits are used by a specific field. I don't mind removing it though. Thanks for the review and ideas! ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-29 8:00 ` Alexandre Courbot @ 2026-01-29 14:10 ` Gary Guo 2026-01-29 21:08 ` Danilo Krummrich 2026-01-30 6:55 ` Alexandre Courbot 0 siblings, 2 replies; 34+ messages in thread From: Gary Guo @ 2026-01-29 14:10 UTC (permalink / raw) To: Alexandre Courbot, 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 Thu Jan 29, 2026 at 8:00 AM GMT, Alexandre Courbot wrote: > On Thu Jan 29, 2026 at 1:16 AM JST, Gary Guo wrote: >> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: >>> Add a macro for defining hardware register types with I/O accessors. >>> >>> Each register field is represented as a `Bounded` of the appropriate bit >>> width, ensuring field values are never silently truncated. >>> >>> Fields can optionally be converted to/from custom types, either fallibly >>> or infallibly. >>> >>> The address of registers can be direct, relative, or indexed, supporting >>> most of the patterns in which registers are arranged. >>> >>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com> >>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >>> --- >>> rust/kernel/io.rs | 1 + >>> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++ >>> 2 files changed, 1288 insertions(+) >>> >>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs >>> index 056a3ec71647..112f43ecbf88 100644 >>> --- a/rust/kernel/io.rs >>> +++ b/rust/kernel/io.rs >>> @@ -11,6 +11,7 @@ >>> >>> pub mod mem; >>> pub mod poll; >>> +pub mod register; >>> pub mod resource; >>> >>> pub use resource::Resource; >>> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs >>> new file mode 100644 >>> index 000000000000..fc85dcd1f09a >>> --- /dev/null >>> +++ b/rust/kernel/io/register.rs >>> @@ -0,0 +1,1287 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +//! A macro to define register layout and accessors. >>> +//! >>> +//! A single register typically includes several fields, which are accessed through a combination >>> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because >>> +//! not all possible field values are necessarily valid. >>> +//! >>> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a >>> +//! dedicated type for each register. Each such type comes with its own field accessors that can >>> +//! return an error if a field's value is invalid. >>> +//! >>> +//! [`register!`]: kernel::register! >>> + >>> +use core::ops::Deref; >>> + >>> +use crate::io::{ >>> + IoCapable, >>> + IoKnownSize, // >>> +}; >>> + >>> +/// Trait providing a base address to be added to the offset of a relative register to obtain >>> +/// its actual offset. >>> +/// >>> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides >>> +/// several bases. It is given to the `register!` macro to restrict the use of the register to >>> +/// implementors of this particular variant. >>> +pub trait RegisterBase<T> { >>> + /// Base address to which register offsets are added. >>> + const BASE: usize; >>> +} >>> + >>> +/// Trait providing I/O read/write operations for register storage types. >>> +/// >>> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the >>> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage >>> +/// type. >>> +pub trait RegisterIo: Sized { >> >> Is this trait intended for public usage or just internal detail of `register!()` >> macro? >> >> If it's the former, then we should probably just put the method into `IoCapable` >> and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so >> it won't get abused. > > It is an internal detail of `register!()` and not supposed to be used by anyone > else. > >> >>> + /// Read a value from the given offset in the I/O region. >>> + fn read<T, I>(io: &T, offset: usize) -> Self >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>; >> >> I think generally `Deref` bound shouldn't be exposed to user. What smart >> pointers are involved here, and can we implement forwarding impls of `Io`? > > Removing the requirement for `Deref` in the `RegisterIo` trait is simple - we > can just call `Deref` in the register IO accessors. The issue with `Deref` bound here is that now you *require* a level of indirection. If something implements `Io` directly, you cannot use it with the method. While `&Bar` is accepted, `&Mmio` is not because `Mmio` is a direct implementor of `Io` and not deref to it. A `Deref` bound also does not help if, say, a type is `Arc<Bar>` which needs two level of dereffing before it is Io. For consistency I think it's best to avoid `Deref` call all together > > But I suspect you mean that we should also remove it from the register > accessors themselves? The problem if I remove it there is that things like > Nova's `Bar0` do not implement `IoKnownSize` and `IoCapable` (but rather deref > to something that does), which would require all callers to do the deref op > itself as deref coercion doesn't seem to be usable here. > >> >>> + >>> + /// Write a value to the given offset in the I/O region. >>> + fn write<T, I>(self, io: &T, offset: usize) >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>; >>> +} >>> + >>> +impl RegisterIo for u8 { >>> + #[inline(always)] >>> + fn read<T, I>(io: &T, offset: usize) -> Self >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>, >>> + { >>> + io.read8(offset) >>> + } >>> + >>> + #[inline(always)] >>> + fn write<T, I>(self, io: &T, offset: usize) >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>, >>> + { >>> + io.write8(self, offset) >>> + } >>> +} >>> + >>> +impl RegisterIo for u16 { >>> + #[inline(always)] >>> + fn read<T, I>(io: &T, offset: usize) -> Self >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>, >>> + { >>> + io.read16(offset) >>> + } >>> + >>> + #[inline(always)] >>> + fn write<T, I>(self, io: &T, offset: usize) >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>, >>> + { >>> + io.write16(self, offset) >>> + } >>> +} >>> + >>> +impl RegisterIo for u32 { >>> + #[inline(always)] >>> + fn read<T, I>(io: &T, offset: usize) -> Self >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>, >>> + { >>> + io.read32(offset) >>> + } >>> + >>> + #[inline(always)] >>> + fn write<T, I>(self, io: &T, offset: usize) >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>, >>> + { >>> + io.write32(self, offset) >>> + } >>> +} >>> + >>> +#[cfg(CONFIG_64BIT)] >> >> This cfg should be removed. > > Done. > >> >>> +impl RegisterIo for u64 { >>> + #[inline(always)] >>> + fn read<T, I>(io: &T, offset: usize) -> Self >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>, >>> + { >>> + io.read64(offset) >>> + } >>> + >>> + #[inline(always)] >>> + fn write<T, I>(self, io: &T, offset: usize) >>> + where >>> + T: Deref<Target = I>, >>> + I: IoKnownSize + IoCapable<Self>, >>> + { >>> + io.write64(self, offset) >>> + } >>> +} >>> + >>> +/// Defines a dedicated type for a register, including getter and setter methods for its fields and >>> +/// methods to read and write it from an [`Io`] region. >>> +/// >>> +/// Example: >>> +/// >>> +/// ``` >>> +/// use kernel::register; >>> +/// >>> +/// register! { >>> +/// /// Basic information about the chip. >>> +/// pub BOOT_0(u32) @ 0x00000100 { >>> +/// /// Vendor ID. >>> +/// 15:8 vendor_id; >>> +/// /// Major revision of the chip. >>> +/// 7:4 major_revision; >>> +/// /// Minor revision of the chip. >>> +/// 3:0 minor_revision; >>> +/// } >>> +/// } >>> +/// ``` >>> +/// >>> +/// This defines a `BOOT_0` type which can be read from or written to offset `0x100` of an `Io` >>> +/// region. For instance, `minor_revision` consists of the 4 least significant bits of the >>> +/// register. >>> +/// >>> +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their >>> +/// getter method, which is named after them. They also have setter methods prefixed with `set_` >>> +/// for runtime values and `with_` for constant values. All setters return the updated register >>> +/// value. >>> +/// >>> +/// ```no_run >>> +/// use kernel::register; >>> +/// use kernel::num::Bounded; >>> +/// >>> +/// # register! { >>> +/// # pub BOOT_0(u32) @ 0x00000100 { >>> +/// # 15:8 vendor_id; >>> +/// # 7:4 major_revision; >>> +/// # 3:0 minor_revision; >>> +/// # } >>> +/// # } >>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) { >>> +/// # fn obtain_vendor_id() -> u8 { 0xff } >>> +/// // Read from the register's defined offset (0x100). >>> +/// let boot0 = BOOT_0::read(&bar); >>> +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get()); >>> +/// >>> +/// // Update some fields and write the new value back. >>> +/// boot0 >>> +/// // Constant values. >>> +/// .with_major_revision::<3>() >>> +/// .with_minor_revision::<10>() >>> +/// // Run-time value. >>> +/// .set_vendor_id(obtain_vendor_id()) >>> +/// .write(&bar); >>> +/// >>> +/// // Or, just read and update the register in a single step. >>> +/// BOOT_0::update(&bar, |r| r >>> +/// .with_major_revision::<3>() >>> +/// .with_minor_revision::<10>() >>> +/// .set_vendor_id(obtain_vendor_id()) >>> +/// ); >>> +/// >>> +/// // Constant values can also be built using the const setters. >>> +/// const V: BOOT_0 = BOOT_0::zeroed() >>> +/// .with_major_revision::<3>() >>> +/// .with_minor_revision::<10>(); >>> +/// # } >>> +/// ``` >>> +/// >>> +/// Fields can also be transparently converted from/to an arbitrary type by using the bitfield `=>` >>> +/// and `?=>` syntaxes. >>> +/// >>> +/// If present, doccomments above register or fields definitions are added to the relevant item >>> +/// they document (the register type itself, or the field's setter and getter methods). >>> +/// >>> +/// Note that multiple registers can be defined in a single `register!` invocation. This can be >>> +/// useful to group related registers together. >>> +/// >>> +/// ``` >>> +/// use kernel::register; >>> +/// >>> +/// register! { >>> +/// pub BOOT_0(u8) @ 0x00000100 { >>> +/// 7:4 major_revision; >>> +/// 3:0 minor_revision; >>> +/// } >>> +/// >>> +/// pub BOOT_1(u8) @ 0x00000101 { >>> +/// 7:5 num_threads; >>> +/// 4:0 num_cores; >>> +/// } >>> +/// }; >>> +/// ``` >>> +/// >>> +/// It is possible to create an alias of an existing register with new field definitions by using >>> +/// the `=> ALIAS` syntax. This is useful for cases where a register's interpretation depends on >>> +/// the context: >>> +/// >>> +/// ``` >>> +/// use kernel::register; >>> +/// >>> +/// register! { >>> +/// /// Scratch register. >>> +/// pub SCRATCH(u32) @ 0x00000200 { >>> +/// /// Raw value. >>> +/// 31:0 value; >>> +/// } >>> +/// >>> +/// /// Boot status of the firmware. >>> +/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH { >>> +/// /// Whether the firmware has completed booting. >>> +/// 0:0 completed; >>> +/// } >>> +/// } >>> +/// ``` >>> +/// >>> +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also >>> +/// providing its own `completed` field. >>> +/// >>> +/// ## Relative registers >>> +/// >>> +/// A register can be defined as being accessible from a fixed offset of a provided base. For >>> +/// instance, imagine the following I/O space: >>> +/// >>> +/// ```text >>> +/// +-----------------------------+ >>> +/// | ... | >>> +/// | | >>> +/// 0x100--->+------------CPU0-------------+ >>> +/// | | >>> +/// 0x110--->+-----------------------------+ >>> +/// | CPU_CTL | >>> +/// +-----------------------------+ >>> +/// | ... | >>> +/// | | >>> +/// | | >>> +/// 0x200--->+------------CPU1-------------+ >>> +/// | | >>> +/// 0x210--->+-----------------------------+ >>> +/// | CPU_CTL | >>> +/// +-----------------------------+ >>> +/// | ... | >>> +/// +-----------------------------+ >>> +/// ``` >>> +/// >>> +/// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O >>> +/// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define >>> +/// them twice and would prefer a way to select which one to use from a single definition >>> +/// >>> +/// This can be done using the `Base + Offset` syntax when specifying the register's address. >>> +/// >>> +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the >>> +/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for >>> +/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated >>> +/// into code: >>> +/// >>> +/// ```no_run >>> +/// use kernel::register; >>> +/// use kernel::io::register::RegisterBase; >>> +/// >>> +/// // Type used to identify the base. >>> +/// pub struct CpuCtlBase; >>> +/// >>> +/// // ZST describing `CPU0`. >>> +/// struct Cpu0; >>> +/// impl RegisterBase<CpuCtlBase> for Cpu0 { >>> +/// const BASE: usize = 0x100; >>> +/// } >>> +/// // Singleton of `CPU0` used to identify it. >>> +/// const CPU0: Cpu0 = Cpu0; >>> +/// >>> +/// // ZST describing `CPU1`. >>> +/// struct Cpu1; >>> +/// impl RegisterBase<CpuCtlBase> for Cpu1 { >>> +/// const BASE: usize = 0x200; >>> +/// } >>> +/// // Singleton of `CPU1` used to identify it. >>> +/// const CPU1: Cpu1 = Cpu1; >>> +/// >>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) { >>> +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`. >>> +/// register! { >>> +/// /// CPU core control. >>> +/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 { >>> +/// /// Start the CPU core. >>> +/// 0:0 start; >>> +/// } >>> +/// } >>> +/// >>> +/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument >>> +/// // that is used to resolve its final address by adding its `BASE` to the offset of the >>> +/// // register. >>> +/// >>> +/// // Start `CPU0`. >>> +/// CPU_CTL::update(&bar, &CPU0, |r| r.set_start(true)); >>> +/// >>> +/// // Start `CPU1`. >>> +/// CPU_CTL::update(&bar, &CPU1, |r| r.set_start(true)); >>> +/// >>> +/// // Aliases can also be defined for relative register. >>> +/// register! { >>> +/// /// Alias to CPU core control. >>> +/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL { >>> +/// /// Start the aliased CPU core. >>> +/// 1:1 alias_start; >>> +/// } >>> +/// } >>> +/// >>> +/// // Start the aliased `CPU0`. >>> +/// CPU_CTL_ALIAS::update(&bar, &CPU0, |r| r.set_alias_start(true)); >>> +/// # } >>> +/// ``` >>> +/// >>> +/// ## Arrays of registers >>> +/// >>> +/// Some I/O areas contain consecutive registers that can be interpreted in the same way. These >>> +/// areas can be defined as an array of identical registers, allowing them to be accessed by index >>> +/// with compile-time or runtime bound checking. Simply specify their size inside `[` and `]` >>> +/// brackets, and add an `idx` parameter to their `read`, `write` and `update` methods: >>> +/// >>> +/// ```no_run >>> +/// use kernel::register; >>> +/// >>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) >>> +/// # -> Result<(), Error>{ >>> +/// # fn get_scratch_idx() -> usize { >>> +/// # 0x15 >>> +/// # } >>> +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`. >>> +/// register! { >>> +/// /// Scratch registers. >>> +/// pub SCRATCH(u32)[64] @ 0x00000080 { >>> +/// 31:0 value; >>> +/// } >>> +/// } >>> +/// >>> +/// // Read scratch register 0, i.e. I/O address `0x80`. >>> +/// let scratch_0 = SCRATCH::read(&bar, 0).value(); >>> +/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`. >>> +/// let scratch_15 = SCRATCH::read(&bar, 15).value(); >>> +/// >>> +/// // This is out of bounds and won't build. >>> +/// // let scratch_128 = SCRATCH::read(&bar, 128).value(); >>> +/// >>> +/// // Runtime-obtained array index. >>> +/// let scratch_idx = get_scratch_idx(); >>> +/// // Access on a runtime index returns an error if it is out-of-bounds. >>> +/// let some_scratch = SCRATCH::try_read(&bar, scratch_idx)?.value(); >>> +/// >>> +/// // Alias to a particular register in an array. >>> +/// // Here `SCRATCH[8]` is used to convey the firmware exit code. >>> +/// register! { >>> +/// /// Firmware exit status code. >>> +/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] { >>> +/// 7:0 status; >>> +/// } >>> +/// } >>> +/// let status = FIRMWARE_STATUS::read(&bar).status(); >>> +/// >>> +/// // Non-contiguous register arrays can be defined by adding a stride parameter. >>> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the >>> +/// // registers of the two declarations below are interleaved. >>> +/// register! { >>> +/// /// Scratch registers bank 0. >>> +/// pub SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ 0x000000c0 { >>> +/// 31:0 value; >>> +/// } >>> +/// >>> +/// /// Scratch registers bank 1. >>> +/// pub SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ 0x000000c4 { >>> +/// 31:0 value; >>> +/// } >>> +/// } >>> +/// # Ok(()) >>> +/// # } >>> +/// ``` >>> +/// >>> +/// ## Relative arrays of registers >>> +/// >>> +/// Combining the two features described in the sections above, arrays of registers accessible from >>> +/// a base can also be defined: >>> +/// >>> +/// ```no_run >>> +/// use kernel::register; >>> +/// use kernel::io::register::RegisterBase; >>> +/// >>> +/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: &T) >>> +/// # -> Result<(), Error>{ >>> +/// # fn get_scratch_idx() -> usize { >>> +/// # 0x15 >>> +/// # } >>> +/// // Type used as parameter of `RegisterBase` to specify the base. >>> +/// pub struct CpuCtlBase; >>> +/// >>> +/// // ZST describing `CPU0`. >>> +/// struct Cpu0; >>> +/// impl RegisterBase<CpuCtlBase> for Cpu0 { >>> +/// const BASE: usize = 0x100; >>> +/// } >>> +/// // Singleton of `CPU0` used to identify it. >>> +/// const CPU0: Cpu0 = Cpu0; >>> +/// >>> +/// // ZST describing `CPU1`. >>> +/// struct Cpu1; >>> +/// impl RegisterBase<CpuCtlBase> for Cpu1 { >>> +/// const BASE: usize = 0x200; >>> +/// } >>> +/// // Singleton of `CPU1` used to identify it. >>> +/// const CPU1: Cpu1 = Cpu1; >>> +/// >>> +/// // 64 per-cpu scratch registers, arranged as a contiguous array. >>> +/// register! { >>> +/// /// Per-CPU scratch registers. >>> +/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 { >>> +/// 31:0 value; >>> +/// } >>> +/// } >>> +/// >>> +/// let cpu0_scratch_0 = CPU_SCRATCH::read(&bar, &Cpu0, 0).value(); >>> +/// let cpu1_scratch_15 = CPU_SCRATCH::read(&bar, &Cpu1, 15).value(); >>> +/// >>> +/// // This won't build. >>> +/// // let cpu0_scratch_128 = CPU_SCRATCH::read(&bar, &Cpu0, 128).value(); >>> +/// >>> +/// // Runtime-obtained array index. >>> +/// let scratch_idx = get_scratch_idx(); >>> +/// // Access on a runtime value returns an error if it is out-of-bounds. >>> +/// let cpu0_some_scratch = CPU_SCRATCH::try_read(&bar, &Cpu0, scratch_idx)?.value(); >>> +/// >>> +/// // `SCRATCH[8]` is used to convey the firmware exit code. >>> +/// register! { >>> +/// /// Per-CPU firmware exit status code. >>> +/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] { >>> +/// 7:0 status; >>> +/// } >>> +/// } >>> +/// let cpu0_status = CPU_FIRMWARE_STATUS::read(&bar, &Cpu0).status(); >>> +/// >>> +/// // Non-contiguous register arrays can be defined by adding a stride parameter. >>> +/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the >>> +/// // registers of the two declarations below are interleaved. >>> +/// register! { >>> +/// /// Scratch registers bank 0. >>> +/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16 ; 8] @ CpuCtlBase + 0x00000d00 { >> >> I discussed this with Alex off-list and we agree that this'll better be >> >> pub CPU_SCRATCH_INTERLEAVED_0(u32)[u16, stride = 8] @ CpuCtlBase + 0x00000d00 >> >> spelling the full intention out rather than have a special syntax, as this isn't >> the common case. > > Implemented it and it indeed looks much better. > >> >>> +/// 31:0 value; >>> +/// } >>> +/// >>> +/// /// Scratch registers bank 1. >>> +/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16 ; 8] @ CpuCtlBase + 0x00000d04 { >>> +/// 31:0 value; >>> +/// } >>> +/// } >>> +/// # Ok(()) >>> +/// # } >>> +/// ``` >>> +/// [`Io`]: kernel::io::Io >>> +#[macro_export] >>> +macro_rules! register { >>> + // Entry point for the macro, allowing multiple registers to be defined in one call. >>> + // It matches all possible register declaration patterns to dispatch them to corresponding >>> + // `@reg` rule that defines a single register. >>> + ( >>> + $( >>> + $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) >>> + $([ $size:expr $(; $stride:expr)? ])? $(@ $offset:literal)? >>> + $(@ $base:ident + $base_offset:literal)? >> >> Does `$(@ $($base:ident +)? $offset:literal)?` not work? > > It does! I was trying to match the `+` in the right-side expression and > obviously couldn't, but it didn't occur to me to do it the other way. :) > >> >>> + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )? >>> + { $($fields:tt)* } >>> + )* >>> + ) => { >>> + $( >>> + ::kernel::register!( >> >> $crate::register!() > > Updated throughout the file (I am not sure to understand the difference though?). It'll always resolve to the correct crate. In theory, a crate can do extern crate kernel as shell; extern crate foobar as kernel; and now ::kernel:: would resolve to the wrong crate. Of course, people doing this are just shooting their own foot so it's not something to worry about. Using `$crate::` also allow re-exporting of the macro without adding a direct dependency (as indirect dependencies are not visible with ::crate_name). Neither are issue of the `kernel` crate, but in general it's advisable to use `$crate` where possible. > >> >>> + @reg $(#[$attr])* $vis $name ($storage) $([$size $(; $stride)?])? >>> + $(@ $offset)? >>> + $(@ $base + $base_offset)? >>> + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )? >>> + { $($fields)* } >>> + ); >>> + )* >>> + }; >>> + >>> + // All the rules below are private helpers. >>> + >>> + // Creates a register at a fixed offset of the MMIO space. >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal >>> + { $($fields:tt)* } >>> + ) => { >>> + ::kernel::register!( >>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >>> + ); >>> + ::kernel::register!(@io_fixed $name($storage) @ $offset); >>> + }; >>> + >>> + // Creates an alias register of fixed offset register `alias` with its own fields. >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident >>> + { $($fields:tt)* } >>> + ) => { >>> + ::kernel::register!( >>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >>> + ); >>> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET); >>> + }; >>> + >>> + // Creates a register at a relative offset from a base address provider. >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal >>> + { $($fields:tt)* } >>> + ) => { >>> + ::kernel::register!( >>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >>> + ); >>> + ::kernel::register!(@io_relative $name($storage) @ $base + $offset ); >>> + }; >>> + >>> + // Creates an alias register of relative offset register `alias` with its own fields. >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident >>> + { $($fields:tt)* } >>> + ) => { >>> + ::kernel::register!( >>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >>> + ); >>> + ::kernel::register!(@io_relative $name($storage) @ $base + $alias::OFFSET ); >> >> Would this generate error messages if $name and $alias are of different base, or >> would such case be a bug but silently compiles? > > You would get an build error when trying to use the alias as the bases > are dedicated types and the types would be incompatible. > >> >>> + }; >>> + >>> + // Creates an array of registers at a fixed offset of the MMIO space. >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) >>> + [ $size:expr ; $stride:expr ] @ $offset:literal { $($fields:tt)* } >>> + ) => { >>> + static_assert!(::core::mem::size_of::<$storage>() <= $stride); >>> + >>> + ::kernel::register!( >>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >>> + ); >>> + ::kernel::register!(@io_array $name($storage) [ $size ; $stride ] @ $offset); >>> + }; >>> + >>> + // Shortcut for contiguous array of registers (stride == size of element). >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal >>> + { $($fields:tt)* } >>> + ) => { >>> + ::kernel::register!( >>> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ] >>> + @ $offset { $($fields)* } >>> + ); >>> + }; >>> + >>> + // Creates an alias of register `idx` of array of registers `alias` with its own fields. >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ] >>> + { $($fields:tt)* } >>> + ) => { >>> + static_assert!($idx < $alias::SIZE); >>> + >>> + ::kernel::register!( >>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >>> + ); >>> + ::kernel::register!(@io_fixed $name($storage) @ $alias::OFFSET + $idx * $alias::STRIDE); >>> + }; >>> + >>> + // Creates an array of registers at a relative offset from a base address provider. >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] >>> + @ $base:ident + $offset:literal { $($fields:tt)* } >>> + ) => { >>> + static_assert!(::core::mem::size_of::<$storage>() <= $stride); >>> + >>> + ::kernel::register!( >>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >>> + ); >>> + ::kernel::register!( >>> + @io_relative_array $name($storage) [ $size ; $stride ] @ $base + $offset >>> + ); >>> + }; >>> + >>> + // Shortcut for contiguous array of relative registers (stride == size of element). >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] >>> + @ $base:ident + $offset:literal { $($fields:tt)* } >>> + ) => { >>> + ::kernel::register!( >>> + $(#[$attr])* $vis $name($storage) [ $size ; ::core::mem::size_of::<$storage>() ] >>> + @ $base + $offset { $($fields)* } >>> + ); >>> + }; >>> + >>> + // Creates an alias of register `idx` of relative array of registers `alias` with its own >>> + // fields. >>> + ( >>> + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) >>> + => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* } >>> + ) => { >>> + static_assert!($idx < $alias::SIZE); >>> + >>> + ::kernel::register!( >>> + @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* } >>> + ); >>> + ::kernel::register!( >>> + @io_relative $name($storage) @ $base + $alias::OFFSET + $idx * $alias::STRIDE >>> + ); >>> + }; >>> + >>> + // Generates the bitfield for the register. >>> + // >>> + // `#[allow(non_camel_case_types)]` is added since register names typically use SCREAMING_CASE. >>> + ( >>> + @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* } >>> + ) => { >>> + ::kernel::register!(@bitfield_core >>> + #[allow(non_camel_case_types)] >>> + $(#[$attr])* $vis $name $storage >>> + ); >>> + ::kernel::register!(@bitfield_fields $vis $name $storage { $($fields)* }); >>> + }; >>> + >>> + // Generates the IO accessors for a fixed offset register. >>> + (@io_fixed $name:ident ($storage:ty) @ $offset:expr) => { >>> + #[allow(dead_code)] >>> + impl $name { >>> + /// Absolute offset of the register. >>> + pub const OFFSET: usize = $offset; >>> + >>> + /// Read the register from its address in `io`. >>> + #[inline(always)] >>> + pub fn read<T, I>(io: &T) -> Self where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + { >>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, Self::OFFSET)) >>> + } >>> + >>> + /// Write the value contained in `self` to the register address in `io`. >>> + #[inline(always)] >>> + pub fn write<T, I>(self, io: &T) where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + { >>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, Self::OFFSET) >>> + } >>> + >>> + /// Read the register from its address in `io` and run `f` on its value to obtain a new >>> + /// value to write back. >>> + /// >>> + /// Note that this operation is not atomic. In concurrent contexts, external >>> + /// synchronization may be required to prevent race conditions. >> >> Given the non-atomicity, how much value does it provide compared to having the >> user write read and write themselves? I feel that people reading the code may >> assume the atomicity without reading docs if they see `FOO::update`, while it's >> less likely that they do so if they read >> `FOO::read(io).with_bar(baz).write(io)`. > > It is just a convenience method. I cannot pretend it is widely used, but > it allows to turn multiple-step ops into one-liners. > >> >>> + #[inline(always)] >>> + pub fn update<T, I, F>( >>> + io: &T, >>> + f: F, >>> + ) where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + F: ::core::ops::FnOnce(Self) -> Self, >>> + { >>> + let reg = f(Self::read(io)); >>> + reg.write(io); >>> + } >>> + } >>> + }; >>> + >>> + // Generates the IO accessors for a relative offset register. >>> + (@io_relative $name:ident ($storage:ty) @ $base:ident + $offset:expr ) => { >>> + #[allow(dead_code)] >>> + impl $name { >>> + /// Relative offset of the register. >>> + pub const OFFSET: usize = $offset; >>> + >>> + /// Read the register from `io`, using the base address provided by `base` and adding >>> + /// the register's offset to it. >>> + #[inline(always)] >>> + pub fn read<T, I, B>( >>> + io: &T, >>> + #[allow(unused_variables)] >>> + base: &B, >>> + ) -> Self where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + { >>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET; >>> + >>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) >>> + } >>> + >>> + /// Write the value contained in `self` to `io`, using the base address provided by >>> + /// `base` and adding the register's offset to it. >>> + #[inline(always)] >>> + pub fn write<T, I, B>( >>> + self, >>> + io: &T, >>> + #[allow(unused_variables)] >>> + base: &B, >>> + ) where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + { >>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + Self::OFFSET; >>> + >>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) >>> + } >>> + >>> + /// Read the register from `io`, using the base address provided by `base` and adding >>> + /// the register's offset to it, then run `f` on its value to obtain a new value to >>> + /// write back. >>> + /// >>> + /// Note that this operation is not atomic. In concurrent contexts, external >>> + /// synchronization may be required to prevent race conditions. >>> + #[inline(always)] >>> + pub fn update<T, I, B, F>( >>> + io: &T, >>> + base: &B, >>> + f: F, >>> + ) where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + F: ::core::ops::FnOnce(Self) -> Self, >>> + { >>> + let reg = f(Self::read(io, base)); >>> + reg.write(io, base); >>> + } >>> + } >>> + }; >>> + >>> + // Generates the IO accessors for an array of registers. >>> + (@io_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] @ $offset:literal) => { >>> + #[allow(dead_code)] >>> + impl $name { >>> + /// Absolute offset of the register array. >>> + pub const OFFSET: usize = $offset; >>> + /// Number of elements in the array of registers. >>> + pub const SIZE: usize = $size; >>> + /// Number of bytes separating each element of the array of registers. >>> + pub const STRIDE: usize = $stride; >>> + >>> + /// Read the array register at index `idx` from its address in `io`. >>> + #[inline(always)] >>> + pub fn read<T, I>( >>> + io: &T, >>> + idx: usize, >>> + ) -> Self where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + { >>> + build_assert!(idx < Self::SIZE); >>> + >>> + let offset = Self::OFFSET + (idx * Self::STRIDE); >>> + >>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) >>> + } >>> + >>> + /// Write the value contained in `self` to the array register with index `idx` in `io`. >>> + #[inline(always)] >>> + pub fn write<T, I>( >>> + self, >>> + io: &T, >>> + idx: usize >>> + ) where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + { >>> + build_assert!(idx < Self::SIZE); >>> + >>> + let offset = Self::OFFSET + (idx * Self::STRIDE); >>> + >>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) >>> + } >>> + >>> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a >>> + /// new value to write back. >>> + /// >>> + /// Note that this operation is not atomic. In concurrent contexts, external >>> + /// synchronization may be required to prevent race conditions. >>> + #[inline(always)] >>> + pub fn update<T, I, F>( >>> + io: &T, >>> + idx: usize, >>> + f: F, >>> + ) where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + F: ::core::ops::FnOnce(Self) -> Self, >>> + { >>> + let reg = f(Self::read(io, idx)); >>> + reg.write(io, idx); >>> + } >>> + >>> + /// Read the array register at index `idx` from its address in `io`. >>> + /// >>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >>> + /// access was out-of-bounds. >>> + #[inline(always)] >>> + pub fn try_read<T, I>( >>> + io: &T, >>> + idx: usize, >>> + ) -> ::kernel::error::Result<Self> where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + { >>> + if idx < Self::SIZE { >>> + Ok(Self::read(io, idx)) >>> + } else { >>> + Err(::kernel::error::code::EINVAL) >>> + } >>> + } >>> + >>> + /// Write the value contained in `self` to the array register with index `idx` in `io`. >>> + /// >>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >>> + /// access was out-of-bounds. >>> + #[inline(always)] >>> + pub fn try_write<T, I>( >>> + self, >>> + io: &T, >>> + idx: usize, >>> + ) -> ::kernel::error::Result where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + { >>> + if idx < Self::SIZE { >>> + Ok(self.write(io, idx)) >>> + } else { >>> + Err(::kernel::error::code::EINVAL) >>> + } >>> + } >>> + >>> + /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a >>> + /// new value to write back. >>> + /// >>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >>> + /// access was out-of-bounds. >>> + /// >>> + /// Note that this operation is not atomic. In concurrent contexts, external >>> + /// synchronization may be required to prevent race conditions. >>> + #[inline(always)] >>> + pub fn try_update<T, I, F>( >>> + io: &T, >>> + idx: usize, >>> + f: F, >>> + ) -> ::kernel::error::Result where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + F: ::core::ops::FnOnce(Self) -> Self, >>> + { >>> + if idx < Self::SIZE { >>> + Ok(Self::update(io, idx, f)) >>> + } else { >>> + Err(::kernel::error::code::EINVAL) >>> + } >>> + } >>> + } >>> + }; >>> + >>> + // Generates the IO accessors for an array of relative registers. >>> + ( >>> + @io_relative_array $name:ident ($storage:ty) [ $size:expr ; $stride:expr ] >>> + @ $base:ident + $offset:literal >>> + ) => { >>> + #[allow(dead_code)] >>> + impl $name { >>> + /// Relative offset of the register array. >>> + pub const OFFSET: usize = $offset; >>> + /// Number of elements in the array of registers. >>> + pub const SIZE: usize = $size; >>> + /// Number of bytes separating each element of the array of registers. >>> + pub const STRIDE: usize = $stride; >>> + >>> + /// Read the array register at index `idx` from `io`, using the base address provided >>> + /// by `base` and adding the register's offset to it. >>> + #[inline(always)] >>> + pub fn read<T, I, B>( >>> + io: &T, >>> + #[allow(unused_variables)] >>> + base: &B, >>> + idx: usize, >>> + ) -> Self where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + { >>> + build_assert!(idx < Self::SIZE); >>> + >>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + >>> + Self::OFFSET + (idx * Self::STRIDE); >>> + >>> + Self(<$storage as $crate::io::register::RegisterIo>::read(io, offset)) >>> + } >>> + >>> + /// Write the value contained in `self` to `io`, using the base address provided by >>> + /// `base` and adding the offset of array register `idx` to it. >>> + #[inline(always)] >>> + pub fn write<T, I, B>( >>> + self, >>> + io: &T, >>> + #[allow(unused_variables)] >>> + base: &B, >>> + idx: usize >>> + ) where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + { >>> + build_assert!(idx < Self::SIZE); >>> + >>> + let offset = <B as $crate::io::register::RegisterBase<$base>>::BASE + >>> + Self::OFFSET + (idx * Self::STRIDE); >>> + >>> + <$storage as $crate::io::register::RegisterIo>::write(self.0, io, offset) >>> + } >>> + >>> + /// Read the array register at index `idx` from `io`, using the base address provided >>> + /// by `base` and adding the register's offset to it, then run `f` on its value to >>> + /// obtain a new value to write back. >>> + /// >>> + /// Note that this operation is not atomic. In concurrent contexts, external >>> + /// synchronization may be required to prevent race conditions. >>> + #[inline(always)] >>> + pub fn update<T, I, B, F>( >>> + io: &T, >>> + base: &B, >>> + idx: usize, >>> + f: F, >>> + ) where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + F: ::core::ops::FnOnce(Self) -> Self, >>> + { >>> + let reg = f(Self::read(io, base, idx)); >>> + reg.write(io, base, idx); >>> + } >>> + >>> + /// Read the array register at index `idx` from `io`, using the base address provided >>> + /// by `base` and adding the register's offset to it. >>> + /// >>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >>> + /// access was out-of-bounds. >>> + #[inline(always)] >>> + pub fn try_read<T, I, B>( >>> + io: &T, >>> + base: &B, >>> + idx: usize, >>> + ) -> ::kernel::error::Result<Self> where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + { >>> + if idx < Self::SIZE { >>> + Ok(Self::read(io, base, idx)) >>> + } else { >>> + Err(::kernel::error::code::EINVAL) >>> + } >>> + } >>> + >>> + /// Write the value contained in `self` to `io`, using the base address provided by >>> + /// `base` and adding the offset of array register `idx` to it. >>> + /// >>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >>> + /// access was out-of-bounds. >>> + #[inline(always)] >>> + pub fn try_write<T, I, B>( >>> + self, >>> + io: &T, >>> + base: &B, >>> + idx: usize, >>> + ) -> ::kernel::error::Result where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + { >>> + if idx < Self::SIZE { >>> + Ok(self.write(io, base, idx)) >>> + } else { >>> + Err(::kernel::error::code::EINVAL) >>> + } >>> + } >>> + >>> + /// Read the array register at index `idx` from `io`, using the base address provided >>> + /// by `base` and adding the register's offset to it, then run `f` on its value to >>> + /// obtain a new value to write back. >>> + /// >>> + /// The validity of `idx` is checked at run-time, and `EINVAL` is returned if the >>> + /// access was out-of-bounds. >>> + /// >>> + /// Note that this operation is not atomic. In concurrent contexts, external >>> + /// synchronization may be required to prevent race conditions. >>> + #[inline(always)] >>> + pub fn try_update<T, I, B, F>( >>> + io: &T, >>> + base: &B, >>> + idx: usize, >>> + f: F, >>> + ) -> ::kernel::error::Result where >>> + T: ::core::ops::Deref<Target = I>, >>> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >>> + B: $crate::io::register::RegisterBase<$base>, >>> + F: ::core::ops::FnOnce(Self) -> Self, >>> + { >>> + if idx < Self::SIZE { >>> + Ok(Self::update(io, base, idx, f)) >>> + } else { >>> + Err(::kernel::error::code::EINVAL) >>> + } >>> + } >>> + } >>> + }; >>> + >>> + // Defines the wrapper `$name` type and its conversions from/to the storage type. >>> + (@bitfield_core $(#[$attr:meta])* $vis:vis $name:ident $storage:ty) => { >>> + $(#[$attr])* >>> + #[repr(transparent)] >>> + #[derive(Clone, Copy, PartialEq, Eq)] >> >> You can derive `Zeroable` and save you an unsafe impl. > > Unfortunately this happens if I try to do it: > > error: no rules expected `(` > --> ../samples/rust/rust_driver_pci.rs:74:9 > | > 74 | / ::kernel::register! { > 75 | | VENDOR_ID(u16) @ 0x0 { > 76 | | 15:0 vendor_id; > ... | > 86 | | } > | |_________^ no rules expected this token in macro call > > Not quite sure why to be honest. I'll look into this. > >> >>> + $vis struct $name($storage); >>> + >>> + #[allow(dead_code)] >>> + impl $name { >>> + /// Creates a bitfield from a raw value. >>> + $vis const fn from_raw(value: $storage) -> Self { >>> + Self(value) >>> + } >>> + >>> + /// Creates a zeroed bitfield value. >>> + /// >>> + /// This is a const alternative to the `Zeroable::zeroed()` trait method. >>> + $vis const fn zeroed() -> Self { >>> + Self(0) >>> + } >> >> All types that impl `Zeroable` automatically have the `::zeroed()` fn provided >> via the trait. > > Yes, but that method from the trait cannot be used in const context, and > `zeroed` is the starting point for building register values from scratch (and > thus constant values). `pin_init:::zeroed()` is a const function. Best, Gary > >> >>> + >>> + /// Returns the raw value of this bitfield. >>> + /// >>> + /// This is similar to the [`From`] implementation, but is shorter to invoke in >>> + /// most cases. >>> + $vis const fn as_raw(self) -> $storage { >>> + self.0 >>> + } >>> + } >>> + >>> + // SAFETY: `$storage` is `Zeroable` and `$name` is transparent. >>> + unsafe impl ::pin_init::Zeroable for $name {} >>> + >>> + impl ::core::convert::From<$name> for $storage { >>> + fn from(val: $name) -> $storage { >>> + val.as_raw() >>> + } >>> + } >>> + >>> + impl ::core::convert::From<$storage> for $name { >>> + fn from(val: $storage) -> $name { >>> + Self::from_raw(val) >>> + } >>> + } >>> + }; >>> + >>> + // Definitions requiring knowledge of individual fields: private and public field accessors, >>> + // and `Debug` implementation. >>> + (@bitfield_fields $vis:vis $name:ident $storage:ty { >>> + $($(#[doc = $doc:expr])* $hi:literal:$lo:literal $field:ident >>> + $(?=> $try_into_type:ty)? >>> + $(=> $into_type:ty)? >>> + ; >>> + )* >>> + } >>> + ) => { >>> + #[allow(dead_code)] >>> + impl $name { >>> + $( >>> + ::kernel::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field); >>> + ::kernel::register!( >>> + @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field >>> + $(?=> $try_into_type)? >>> + $(=> $into_type)? >>> + ); >>> + )* >>> + } >>> + >>> + ::kernel::register!(@debug $name { $($field;)* }); >>> + }; >>> + >>> + // Private field accessors working with the correct `Bounded` type for the field. >>> + ( >>> + @private_field_accessors $vis:vis $name:ident $storage:ty : $hi:tt:$lo:tt $field:ident >>> + ) => { >>> + ::kernel::macros::paste!( >>> + $vis const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi; >> >> Is this used by anything? > > Not yet. This is intended for user code that wants to know which bits are used > by a specific field. I don't mind removing it though. > > Thanks for the review and ideas! ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-29 14:10 ` Gary Guo @ 2026-01-29 21:08 ` Danilo Krummrich 2026-01-30 6:55 ` Alexandre Courbot 1 sibling, 0 replies; 34+ messages in thread From: Danilo Krummrich @ 2026-01-29 21:08 UTC (permalink / raw) To: Gary Guo Cc: Alexandre Courbot, 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 Thu Jan 29, 2026 at 3:10 PM CET, Gary Guo wrote: > The issue with `Deref` bound here is that now you *require* a level of > indirection. If something implements `Io` directly, you cannot use it with the > method. While `&Bar` is accepted, `&Mmio` is not because `Mmio` is a direct > implementor of `Io` and not deref to it. > > A `Deref` bound also does not help if, say, a type is `Arc<Bar>` which needs two > level of dereffing before it is Io. For consistency I think it's best to avoid > `Deref` call all together I agree with that and we will probably run into this once we start implementing the I/O traits for things like DMA memory. We could use AsRef or a custom trait instead, but I'd rather leave this for a subsequent series, as it requires more changes. They should also be transparent to users. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-29 14:10 ` Gary Guo 2026-01-29 21:08 ` Danilo Krummrich @ 2026-01-30 6:55 ` Alexandre Courbot 2026-01-30 16:14 ` Gary Guo 1 sibling, 1 reply; 34+ messages in thread From: Alexandre Courbot @ 2026-01-30 6:55 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 Thu Jan 29, 2026 at 11:10 PM JST, Gary Guo wrote: > On Thu Jan 29, 2026 at 8:00 AM GMT, Alexandre Courbot wrote: >> On Thu Jan 29, 2026 at 1:16 AM JST, Gary Guo wrote: >>> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: >>>> Add a macro for defining hardware register types with I/O accessors. >>>> >>>> Each register field is represented as a `Bounded` of the appropriate bit >>>> width, ensuring field values are never silently truncated. >>>> >>>> Fields can optionally be converted to/from custom types, either fallibly >>>> or infallibly. >>>> >>>> The address of registers can be direct, relative, or indexed, supporting >>>> most of the patterns in which registers are arranged. >>>> >>>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com> >>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >>>> --- >>>> rust/kernel/io.rs | 1 + >>>> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++ >>>> 2 files changed, 1288 insertions(+) >>>> >>>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs >>>> index 056a3ec71647..112f43ecbf88 100644 >>>> --- a/rust/kernel/io.rs >>>> +++ b/rust/kernel/io.rs >>>> @@ -11,6 +11,7 @@ >>>> >>>> pub mod mem; >>>> pub mod poll; >>>> +pub mod register; >>>> pub mod resource; >>>> >>>> pub use resource::Resource; >>>> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs >>>> new file mode 100644 >>>> index 000000000000..fc85dcd1f09a >>>> --- /dev/null >>>> +++ b/rust/kernel/io/register.rs >>>> @@ -0,0 +1,1287 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +//! A macro to define register layout and accessors. >>>> +//! >>>> +//! A single register typically includes several fields, which are accessed through a combination >>>> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because >>>> +//! not all possible field values are necessarily valid. >>>> +//! >>>> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a >>>> +//! dedicated type for each register. Each such type comes with its own field accessors that can >>>> +//! return an error if a field's value is invalid. >>>> +//! >>>> +//! [`register!`]: kernel::register! >>>> + >>>> +use core::ops::Deref; >>>> + >>>> +use crate::io::{ >>>> + IoCapable, >>>> + IoKnownSize, // >>>> +}; >>>> + >>>> +/// Trait providing a base address to be added to the offset of a relative register to obtain >>>> +/// its actual offset. >>>> +/// >>>> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides >>>> +/// several bases. It is given to the `register!` macro to restrict the use of the register to >>>> +/// implementors of this particular variant. >>>> +pub trait RegisterBase<T> { >>>> + /// Base address to which register offsets are added. >>>> + const BASE: usize; >>>> +} >>>> + >>>> +/// Trait providing I/O read/write operations for register storage types. >>>> +/// >>>> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the >>>> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage >>>> +/// type. >>>> +pub trait RegisterIo: Sized { >>> >>> Is this trait intended for public usage or just internal detail of `register!()` >>> macro? >>> >>> If it's the former, then we should probably just put the method into `IoCapable` >>> and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so >>> it won't get abused. >> >> It is an internal detail of `register!()` and not supposed to be used by anyone >> else. >> >>> >>>> + /// Read a value from the given offset in the I/O region. >>>> + fn read<T, I>(io: &T, offset: usize) -> Self >>>> + where >>>> + T: Deref<Target = I>, >>>> + I: IoKnownSize + IoCapable<Self>; >>> >>> I think generally `Deref` bound shouldn't be exposed to user. What smart >>> pointers are involved here, and can we implement forwarding impls of `Io`? >> >> Removing the requirement for `Deref` in the `RegisterIo` trait is simple - we >> can just call `Deref` in the register IO accessors. > > The issue with `Deref` bound here is that now you *require* a level of > indirection. If something implements `Io` directly, you cannot use it with the > method. While `&Bar` is accepted, `&Mmio` is not because `Mmio` is a direct > implementor of `Io` and not deref to it. > > A `Deref` bound also does not help if, say, a type is `Arc<Bar>` which needs two > level of dereffing before it is Io. For consistency I think it's best to avoid > `Deref` call all together Do you mean that `Bar` should implement `Io`, `IoKnownSize`, and all the required `IoCapable`s? That's a lot of boilerplate I'm afraid, and that would need to be repeated for all other I/O proxy types. No, I'm starting to believe that the fundamental issue is that the register interface does its I/O backwards, and that design issue is only exacerbated by the recent I/O redesign. I.e. instead of doing regs::NV_PMC_BOOT_0::read(bar); We should really do bar.read_reg::<regs::NV_PMC_BOOT_0>(); Because that way we can use deref coercion. That's quite a big redesign though, which means I don't believe `register!` can make it this cycle... I'll give it a try though. <snip> >>>> + $vis struct $name($storage); >>>> + >>>> + #[allow(dead_code)] >>>> + impl $name { >>>> + /// Creates a bitfield from a raw value. >>>> + $vis const fn from_raw(value: $storage) -> Self { >>>> + Self(value) >>>> + } >>>> + >>>> + /// Creates a zeroed bitfield value. >>>> + /// >>>> + /// This is a const alternative to the `Zeroable::zeroed()` trait method. >>>> + $vis const fn zeroed() -> Self { >>>> + Self(0) >>>> + } >>> >>> All types that impl `Zeroable` automatically have the `::zeroed()` fn provided >>> via the trait. >> >> Yes, but that method from the trait cannot be used in const context, and >> `zeroed` is the starting point for building register values from scratch (and >> thus constant values). > > `pin_init:::zeroed()` is a const function. Ah, I thought about using `Zeroable::zeroed()` at first, but indeed we can use the function instead. Strangely though, I expected to be able to do const V: BOOT_0 = pin_init::zeroed() and have the compiler infer the generic type, but for some reason I have to specify it explicitly: const V: BOOT_0 = pin_init::zeroed::<BOOT_0>() Not a big deal, but a const method would reduce this to const V: BOOT_0 = BOOT_0::zeroed() (for variables `let v: BOOT_0 = pin_init::zeroed()` works fine.) ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-30 6:55 ` Alexandre Courbot @ 2026-01-30 16:14 ` Gary Guo 2026-01-30 16:44 ` Danilo Krummrich 0 siblings, 1 reply; 34+ messages in thread From: Gary Guo @ 2026-01-30 16:14 UTC (permalink / raw) To: Alexandre Courbot, 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 Fri Jan 30, 2026 at 6:55 AM GMT, Alexandre Courbot wrote: > On Thu Jan 29, 2026 at 11:10 PM JST, Gary Guo wrote: >> On Thu Jan 29, 2026 at 8:00 AM GMT, Alexandre Courbot wrote: >>> On Thu Jan 29, 2026 at 1:16 AM JST, Gary Guo wrote: >>>> On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: >>>>> Add a macro for defining hardware register types with I/O accessors. >>>>> >>>>> Each register field is represented as a `Bounded` of the appropriate bit >>>>> width, ensuring field values are never silently truncated. >>>>> >>>>> Fields can optionally be converted to/from custom types, either fallibly >>>>> or infallibly. >>>>> >>>>> The address of registers can be direct, relative, or indexed, supporting >>>>> most of the patterns in which registers are arranged. >>>>> >>>>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com> >>>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >>>>> --- >>>>> rust/kernel/io.rs | 1 + >>>>> rust/kernel/io/register.rs | 1287 ++++++++++++++++++++++++++++++++++++++++++++ >>>>> 2 files changed, 1288 insertions(+) >>>>> >>>>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs >>>>> index 056a3ec71647..112f43ecbf88 100644 >>>>> --- a/rust/kernel/io.rs >>>>> +++ b/rust/kernel/io.rs >>>>> @@ -11,6 +11,7 @@ >>>>> >>>>> pub mod mem; >>>>> pub mod poll; >>>>> +pub mod register; >>>>> pub mod resource; >>>>> >>>>> pub use resource::Resource; >>>>> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs >>>>> new file mode 100644 >>>>> index 000000000000..fc85dcd1f09a >>>>> --- /dev/null >>>>> +++ b/rust/kernel/io/register.rs >>>>> @@ -0,0 +1,1287 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +//! A macro to define register layout and accessors. >>>>> +//! >>>>> +//! A single register typically includes several fields, which are accessed through a combination >>>>> +//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because >>>>> +//! not all possible field values are necessarily valid. >>>>> +//! >>>>> +//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a >>>>> +//! dedicated type for each register. Each such type comes with its own field accessors that can >>>>> +//! return an error if a field's value is invalid. >>>>> +//! >>>>> +//! [`register!`]: kernel::register! >>>>> + >>>>> +use core::ops::Deref; >>>>> + >>>>> +use crate::io::{ >>>>> + IoCapable, >>>>> + IoKnownSize, // >>>>> +}; >>>>> + >>>>> +/// Trait providing a base address to be added to the offset of a relative register to obtain >>>>> +/// its actual offset. >>>>> +/// >>>>> +/// The `T` generic argument is used to distinguish which base to use, in case a type provides >>>>> +/// several bases. It is given to the `register!` macro to restrict the use of the register to >>>>> +/// implementors of this particular variant. >>>>> +pub trait RegisterBase<T> { >>>>> + /// Base address to which register offsets are added. >>>>> + const BASE: usize; >>>>> +} >>>>> + >>>>> +/// Trait providing I/O read/write operations for register storage types. >>>>> +/// >>>>> +/// This trait is implemented for all integer types on which I/O can be performed, allowing the >>>>> +/// `register!` macro to generate appropriate I/O accessor methods based on the register's storage >>>>> +/// type. >>>>> +pub trait RegisterIo: Sized { >>>> >>>> Is this trait intended for public usage or just internal detail of `register!()` >>>> macro? >>>> >>>> If it's the former, then we should probably just put the method into `IoCapable` >>>> and allow generic-read in Io. If it's the latter, let's `#[doc(hidden)]` this so >>>> it won't get abused. >>> >>> It is an internal detail of `register!()` and not supposed to be used by anyone >>> else. >>> >>>> >>>>> + /// Read a value from the given offset in the I/O region. >>>>> + fn read<T, I>(io: &T, offset: usize) -> Self >>>>> + where >>>>> + T: Deref<Target = I>, >>>>> + I: IoKnownSize + IoCapable<Self>; >>>> >>>> I think generally `Deref` bound shouldn't be exposed to user. What smart >>>> pointers are involved here, and can we implement forwarding impls of `Io`? >>> >>> Removing the requirement for `Deref` in the `RegisterIo` trait is simple - we >>> can just call `Deref` in the register IO accessors. >> >> The issue with `Deref` bound here is that now you *require* a level of >> indirection. If something implements `Io` directly, you cannot use it with the >> method. While `&Bar` is accepted, `&Mmio` is not because `Mmio` is a direct >> implementor of `Io` and not deref to it. >> >> A `Deref` bound also does not help if, say, a type is `Arc<Bar>` which needs two >> level of dereffing before it is Io. For consistency I think it's best to avoid >> `Deref` call all together > > Do you mean that `Bar` should implement `Io`, `IoKnownSize`, and all the > required `IoCapable`s? That's a lot of boilerplate I'm afraid, and that > would need to be repeated for all other I/O proxy types. > > No, I'm starting to believe that the fundamental issue is that the > register interface does its I/O backwards, and that design issue is only > exacerbated by the recent I/O redesign. I.e. instead of doing > > regs::NV_PMC_BOOT_0::read(bar); > > We should really do > > bar.read_reg::<regs::NV_PMC_BOOT_0>(); > > Because that way we can use deref coercion. > > That's quite a big redesign though, which means I don't believe > `register!` can make it this cycle... I'll give it a try though. Hmm, that's unfortunate, but I think this is indeed a big design change that we should iron out before merging... I think you're right that if we put the methods on `Io` then all of the deref issue would just went away. BTW, there's a tricky that would allow you to avoid turbofish in this case, i.e. you would be able to write bar.read_reg(regs::NV_PMC_BOOT_0) Or bar.write_reg(regs::PV_PMC_BOOT_0, ...) The trick is to note that the same name can present in both type and value namesapce. For example, when you define struct Foo; you create `Foo` type in type namespace and a `const Foo: Foo` in value namespace. Similarly struct Foo(u32); creates a `Foo` in type namespace and a `fn(u32) -> Foo` in value namespace. This process doesn't need to be automatic. So if you create struct Foo { value: u32 } then you're only creating `Foo` in type namespace and not in value namespace. This means that you could do something like: // Use PhantomData as example, could be other token/marker types. const Foo: PhantomData<Foo> = PhantomData; trait Io { fn read_reg<Reg>(&self, _: PhantomData<Reg>) -> Reg { ... } } // Now no turbofish! bar.read_reg(NV_PMC_BOOT_0) This trick is heavily used in `syn`, so you do `input.peek(type)` instead of `input.peek::<Type>()`, which I find quite handy. > > <snip> >>>>> + $vis struct $name($storage); >>>>> + >>>>> + #[allow(dead_code)] >>>>> + impl $name { >>>>> + /// Creates a bitfield from a raw value. >>>>> + $vis const fn from_raw(value: $storage) -> Self { >>>>> + Self(value) >>>>> + } >>>>> + >>>>> + /// Creates a zeroed bitfield value. >>>>> + /// >>>>> + /// This is a const alternative to the `Zeroable::zeroed()` trait method. >>>>> + $vis const fn zeroed() -> Self { >>>>> + Self(0) >>>>> + } >>>> >>>> All types that impl `Zeroable` automatically have the `::zeroed()` fn provided >>>> via the trait. >>> >>> Yes, but that method from the trait cannot be used in const context, and >>> `zeroed` is the starting point for building register values from scratch (and >>> thus constant values). >> >> `pin_init:::zeroed()` is a const function. > > Ah, I thought about using `Zeroable::zeroed()` at first, but indeed we > can use the function instead. > > Strangely though, I expected to be able to do > > const V: BOOT_0 = pin_init::zeroed() > > and have the compiler infer the generic type, but for some reason I have > to specify it explicitly: > > const V: BOOT_0 = pin_init::zeroed::<BOOT_0>() > > Not a big deal, but a const method would reduce this to > > const V: BOOT_0 = BOOT_0::zeroed() > > (for variables `let v: BOOT_0 = pin_init::zeroed()` works fine.) That's strange. Do you have a MCVE for this? I can't reproduce what you described. Best, Gary ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-30 16:14 ` Gary Guo @ 2026-01-30 16:44 ` Danilo Krummrich 2026-01-30 17:01 ` Gary Guo 0 siblings, 1 reply; 34+ messages in thread From: Danilo Krummrich @ 2026-01-30 16:44 UTC (permalink / raw) To: Gary Guo Cc: Alexandre Courbot, 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 Fri Jan 30, 2026 at 5:14 PM CET, Gary Guo wrote: > On Fri Jan 30, 2026 at 6:55 AM GMT, Alexandre Courbot wrote: >> No, I'm starting to believe that the fundamental issue is that the >> register interface does its I/O backwards, and that design issue is only >> exacerbated by the recent I/O redesign. I.e. instead of doing >> >> regs::NV_PMC_BOOT_0::read(bar); >> >> We should really do >> >> bar.read_reg::<regs::NV_PMC_BOOT_0>(); >> >> Because that way we can use deref coercion. >> >> That's quite a big redesign though, which means I don't believe >> `register!` can make it this cycle... I'll give it a try though. > > Hmm, that's unfortunate, but I think this is indeed a big design change that we > should iron out before merging... > > I think you're right that if we put the methods on `Io` then all of the deref > issue would just went away. I already discussed this with Alex offline and I think we should not take this direction just because of the Deref issue, as we can easily overcome this with using AsRef (or a custom trait). Whereas the downside of bar.read_reg() is that you end up with a more inconsistent and complicated API for drivers. For instance, with the API as is you can do things like: register!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] { 0:0 reset as bool; }); impl NV_PFALCON_FALCON_ENGINE { pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) { Self::update(bar, &E::ID, |r| r.set_reset(true)); // TIMEOUT: falcon engine should not take more than 10us to reset. time::delay::fsleep(time::Delta::from_micros(10)); Self::update(bar, &E::ID, |r| r.set_reset(false)); } } and then use this from the driver code like this: impl<E: FalconEngine> FalconHal<E> for Tu102<E> { fn do_stuff(&self, bar: &Bar0) { //... regs::NV_PFALCON_FALCON_ENGINE::reset_engine::<E>(bar); //... } } Having to implement AsRef (or a custom trait) for the corresponding I/O backend implementations is a pretty minor inconvinience compared to the simplicity the current API provides to drivers. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-30 16:44 ` Danilo Krummrich @ 2026-01-30 17:01 ` Gary Guo 2026-01-30 17:18 ` Danilo Krummrich 0 siblings, 1 reply; 34+ messages in thread From: Gary Guo @ 2026-01-30 17:01 UTC (permalink / raw) To: Danilo Krummrich, Gary Guo Cc: Alexandre Courbot, 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 Fri Jan 30, 2026 at 4:44 PM GMT, Danilo Krummrich wrote: > On Fri Jan 30, 2026 at 5:14 PM CET, Gary Guo wrote: >> On Fri Jan 30, 2026 at 6:55 AM GMT, Alexandre Courbot wrote: >>> No, I'm starting to believe that the fundamental issue is that the >>> register interface does its I/O backwards, and that design issue is only >>> exacerbated by the recent I/O redesign. I.e. instead of doing >>> >>> regs::NV_PMC_BOOT_0::read(bar); >>> >>> We should really do >>> >>> bar.read_reg::<regs::NV_PMC_BOOT_0>(); >>> >>> Because that way we can use deref coercion. >>> >>> That's quite a big redesign though, which means I don't believe >>> `register!` can make it this cycle... I'll give it a try though. >> >> Hmm, that's unfortunate, but I think this is indeed a big design change that we >> should iron out before merging... >> >> I think you're right that if we put the methods on `Io` then all of the deref >> issue would just went away. > > I already discussed this with Alex offline and I think we should not take this > direction just because of the Deref issue, as we can easily overcome this with > using AsRef (or a custom trait). `AsRef` won't work for `Io` which is a trait, unlike `Device` which is a type. A custom trait might work but that's more complex than what Alex purposed. I'm quite fond of the `io.read_reg(REGISTER_REF)` API suggested. It looks quite uniform with other I/O acceessor methods, except typed and with constant offsets. > > Whereas the downside of bar.read_reg() is that you end up with a more > inconsistent and complicated API for drivers. > > For instance, with the API as is you can do things like: > > register!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] { > 0:0 reset as bool; > }); > > impl NV_PFALCON_FALCON_ENGINE { > pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) { > Self::update(bar, &E::ID, |r| r.set_reset(true)); > > // TIMEOUT: falcon engine should not take more than 10us to reset. > time::delay::fsleep(time::Delta::from_micros(10)); > > Self::update(bar, &E::ID, |r| r.set_reset(false)); > } > } The only benefit of this compared to the newly proposed API is that you can use `Self`. It's not too bad to have (hypothetical API) pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) { bar.update_reg(NV_PFALCON_FALCON_ENGINE(&E::ID), |r| r.set_reset(true)); // TIMEOUT: falcon engine should not take more than 10us to reset. time::delay::fsleep(time::Delta::from_micros(10)); bar.update_reg(NV_PFALCON_FALCON_ENGINE(&E::ID), |r| r.set_reset(true)); } I also think in most cases these helper function won't be implemented on the register, so you're not saving much by having the ability to refer to `Self`. For example, in this case you could implement it on `FalconEngine`. Best, Gary > > and then use this from the driver code like this: > > impl<E: FalconEngine> FalconHal<E> for Tu102<E> { > fn do_stuff(&self, bar: &Bar0) { > //... > > regs::NV_PFALCON_FALCON_ENGINE::reset_engine::<E>(bar); > > //... > } > } > > Having to implement AsRef (or a custom trait) for the corresponding I/O backend > implementations is a pretty minor inconvinience compared to the simplicity the > current API provides to drivers. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-30 17:01 ` Gary Guo @ 2026-01-30 17:18 ` Danilo Krummrich 0 siblings, 0 replies; 34+ messages in thread From: Danilo Krummrich @ 2026-01-30 17:18 UTC (permalink / raw) To: Gary Guo Cc: Alexandre Courbot, 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 Fri Jan 30, 2026 at 6:01 PM CET, Gary Guo wrote: > `AsRef` won't work for `Io` which is a trait, unlike `Device` which is a type. A > custom trait might work but that's more complex than what Alex purposed. Are we sure about that? For instance, how do we determine the size of the I/O operation in *_reg()? How can we trust that the type passed to *_reg() is valid for an arbitrary bit pattern? I mean, I'm not saying it's possible, but it seems that we end up with a couple of safety requirements for the base register traits. > I'm quite fond of the `io.read_reg(REGISTER_REF)` API suggested. It looks quite > uniform with other I/O acceessor methods, except typed and with constant > offsets. It's just a nit, but I find it a bit icky that we have to have something like the "_reg" suffix. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 5/7] rust: io: add `register!` macro 2026-01-28 16:16 ` Gary Guo 2026-01-29 8:00 ` Alexandre Courbot @ 2026-01-29 12:48 ` Danilo Krummrich 1 sibling, 0 replies; 34+ messages in thread From: Danilo Krummrich @ 2026-01-29 12:48 UTC (permalink / raw) To: Gary Guo Cc: Alexandre Courbot, 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 28, 2026 at 5:16 PM CET, Gary Guo wrote: > On Wed Jan 28, 2026 at 2:37 AM GMT, Alexandre Courbot wrote: >> + /// Read the register from its address in `io` and run `f` on its value to obtain a new >> + /// value to write back. >> + /// >> + /// Note that this operation is not atomic. In concurrent contexts, external >> + /// synchronization may be required to prevent race conditions. > > Given the non-atomicity, how much value does it provide compared to having the > user write read and write themselves? I feel that people reading the code may > assume the atomicity without reading docs if they see `FOO::update`, while it's > less likely that they do so if they read > `FOO::read(io).with_bar(baz).write(io)`. I think update() is fine, there are no promises about atomicity for any of the I/O functions, also not for read() and write(). I.e. whether an operation is atomic or not depends on the architecture, bus and device. We should probably document this clearly to not raise wrong expectations. >> + #[inline(always)] >> + pub fn update<T, I, F>( >> + io: &T, >> + f: F, >> + ) where >> + T: ::core::ops::Deref<Target = I>, >> + I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<$storage>, >> + F: ::core::ops::FnOnce(Self) -> Self, >> + { >> + let reg = f(Self::read(io)); >> + reg.write(io); >> + } >> + } >> + }; ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH v4 6/7] sample: rust: pci: use `register!` macro 2026-01-28 2:37 [PATCH v4 0/7] rust: add `register!` macro Alexandre Courbot ` (4 preceding siblings ...) 2026-01-28 2:37 ` [PATCH v4 5/7] rust: io: add `register!` macro Alexandre Courbot @ 2026-01-28 2:37 ` Alexandre Courbot 2026-01-28 12:35 ` Zhi Wang 2026-01-31 1:06 ` Danilo Krummrich 2026-01-28 2:37 ` [PATCH FOR REFERENCE v4 7/7] gpu: nova-core: use the kernel " Alexandre Courbot 6 siblings, 2 replies; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 2:37 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 Convert the direct IO accesses to properly defined registers. Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- samples/rust/rust_driver_pci.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index 4dfb8a6a4707..df2232d6b71f 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -70,24 +70,37 @@ fn testdev(index: &TestIndex, bar: &Bar0) -> Result<u32> { fn config_space(pdev: &pci::Device<Bound>) { let config = pdev.config_space(); - // TODO: use the register!() macro for defining PCI configuration space registers once it - // has been move out of nova-core. + // Some PCI configuration space registers. + ::kernel::register! { + VENDOR_ID(u16) @ 0x0 { + 15:0 vendor_id; + } + + REVISION_ID(u8) @ 0x8 { + 7:0 revision_id; + } + + BAR(u32)[6] @ 0x10 { + 31:0 value; + } + } + dev_info!( pdev.as_ref(), "pci-testdev config space read8 rev ID: {:x}\n", - config.read8(0x8) + REVISION_ID::read(&&config).revision_id() ); dev_info!( pdev.as_ref(), "pci-testdev config space read16 vendor ID: {:x}\n", - config.read16(0) + VENDOR_ID::read(&&config).vendor_id() ); dev_info!( pdev.as_ref(), "pci-testdev config space read32 BAR 0: {:x}\n", - config.read32(0x10) + BAR::read(&&config, 0).value() ); } } -- 2.52.0 ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v4 6/7] sample: rust: pci: use `register!` macro 2026-01-28 2:37 ` [PATCH v4 6/7] sample: rust: pci: use " Alexandre Courbot @ 2026-01-28 12:35 ` Zhi Wang 2026-01-28 13:27 ` Alexandre Courbot 2026-01-31 1:06 ` Danilo Krummrich 1 sibling, 1 reply; 34+ messages in thread From: Zhi Wang @ 2026-01-28 12:35 UTC (permalink / raw) To: Alexandre Courbot Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Boqun Feng, Gary Guo, 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, 28 Jan 2026 11:37:32 +0900 Alexandre Courbot <acourbot@nvidia.com> wrote: > Convert the direct IO accesses to properly defined registers. > Tested-by: Zhi Wang <zhiw@nvidia.com> I can see the correct value of the registers read by the sample driver from the demesg. Additionally, Clippy was complaining about unnecssary unsafe blocks and can be fixed by: (I haven't fully gone through the code, just applied this for getting rid of the warnnings.) diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs index 850827033f67..efabdd31ec37 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -503,9 +503,7 @@ pub fn cast<U>(self) -> Bounded<U, N> 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) } + Bounded::__new(self.0 >> SHIFT) } /// Left-shifts `self` by `SHIFT` and returns the result as a `Bounded<_, RES>`, where `RES >= @@ -524,9 +522,7 @@ pub fn shr<const SHIFT: u32, const RES: u32>(self) -> Bounded<T, RES> { 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) } + Bounded::__new(self.0 << SHIFT) } } Z. > Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> > --- > samples/rust/rust_driver_pci.rs | 23 ++++++++++++++++++----- > 1 file changed, 18 insertions(+), 5 deletions(-) > > diff --git a/samples/rust/rust_driver_pci.rs > b/samples/rust/rust_driver_pci.rs index 4dfb8a6a4707..df2232d6b71f 100644 > --- a/samples/rust/rust_driver_pci.rs > +++ b/samples/rust/rust_driver_pci.rs > @@ -70,24 +70,37 @@ fn testdev(index: &TestIndex, bar: &Bar0) -> > Result<u32> { fn config_space(pdev: &pci::Device<Bound>) { > let config = pdev.config_space(); > > - // TODO: use the register!() macro for defining PCI > configuration space registers once it > - // has been move out of nova-core. > + // Some PCI configuration space registers. > + ::kernel::register! { > + VENDOR_ID(u16) @ 0x0 { > + 15:0 vendor_id; > + } > + > + REVISION_ID(u8) @ 0x8 { > + 7:0 revision_id; > + } > + > + BAR(u32)[6] @ 0x10 { > + 31:0 value; > + } > + } > + > dev_info!( > pdev.as_ref(), > "pci-testdev config space read8 rev ID: {:x}\n", > - config.read8(0x8) > + REVISION_ID::read(&&config).revision_id() > ); > > dev_info!( > pdev.as_ref(), > "pci-testdev config space read16 vendor ID: {:x}\n", > - config.read16(0) > + VENDOR_ID::read(&&config).vendor_id() > ); > > dev_info!( > pdev.as_ref(), > "pci-testdev config space read32 BAR 0: {:x}\n", > - config.read32(0x10) > + BAR::read(&&config, 0).value() > ); > } > } > ^ permalink raw reply related [flat|nested] 34+ messages in thread
* Re: [PATCH v4 6/7] sample: rust: pci: use `register!` macro 2026-01-28 12:35 ` Zhi Wang @ 2026-01-28 13:27 ` Alexandre Courbot 2026-01-28 15:46 ` Gary Guo 0 siblings, 1 reply; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 13:27 UTC (permalink / raw) To: Zhi Wang Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Boqun Feng, Gary Guo, 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 28, 2026 at 9:35 PM JST, Zhi Wang wrote: > On Wed, 28 Jan 2026 11:37:32 +0900 > Alexandre Courbot <acourbot@nvidia.com> wrote: > >> Convert the direct IO accesses to properly defined registers. >> > > Tested-by: Zhi Wang <zhiw@nvidia.com> > > I can see the correct value of the registers read by the sample driver > from the demesg. Thanks! > > Additionally, Clippy was complaining about unnecssary unsafe blocks and > can be fixed by: (I haven't fully gone through the code, just applied this > for getting rid of the warnnings.) This is because `Bounded::__new` has become unsafe in `rust-next` ; this is not reflected in `driver-core-next` yet, but won't appear in -rc1. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 6/7] sample: rust: pci: use `register!` macro 2026-01-28 13:27 ` Alexandre Courbot @ 2026-01-28 15:46 ` Gary Guo 2026-01-29 8:01 ` Alexandre Courbot 0 siblings, 1 reply; 34+ messages in thread From: Gary Guo @ 2026-01-28 15:46 UTC (permalink / raw) To: Alexandre Courbot, Zhi Wang Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Boqun Feng, Gary Guo, 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 28, 2026 at 1:27 PM GMT, Alexandre Courbot wrote: > On Wed Jan 28, 2026 at 9:35 PM JST, Zhi Wang wrote: >> On Wed, 28 Jan 2026 11:37:32 +0900 >> Alexandre Courbot <acourbot@nvidia.com> wrote: >> >>> Convert the direct IO accesses to properly defined registers. >>> >> >> Tested-by: Zhi Wang <zhiw@nvidia.com> >> >> I can see the correct value of the registers read by the sample driver >> from the demesg. > > Thanks! > >> >> Additionally, Clippy was complaining about unnecssary unsafe blocks and >> can be fixed by: (I haven't fully gone through the code, just applied this >> for getting rid of the warnnings.) > > This is because `Bounded::__new` has become unsafe in `rust-next` ; this > is not reflected in `driver-core-next` yet, but won't appear in -rc1. Any reason that you send the patch based on driver-core-next, instead of linux-next? You won't have the issue if this is based on linux-next. Best, Gary ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 6/7] sample: rust: pci: use `register!` macro 2026-01-28 15:46 ` Gary Guo @ 2026-01-29 8:01 ` Alexandre Courbot 0 siblings, 0 replies; 34+ messages in thread From: Alexandre Courbot @ 2026-01-29 8:01 UTC (permalink / raw) To: Gary Guo Cc: Zhi Wang, 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 Thu Jan 29, 2026 at 12:46 AM JST, Gary Guo wrote: > On Wed Jan 28, 2026 at 1:27 PM GMT, Alexandre Courbot wrote: >> On Wed Jan 28, 2026 at 9:35 PM JST, Zhi Wang wrote: >>> On Wed, 28 Jan 2026 11:37:32 +0900 >>> Alexandre Courbot <acourbot@nvidia.com> wrote: >>> >>>> Convert the direct IO accesses to properly defined registers. >>>> >>> >>> Tested-by: Zhi Wang <zhiw@nvidia.com> >>> >>> I can see the correct value of the registers read by the sample driver >>> from the demesg. >> >> Thanks! >> >>> >>> Additionally, Clippy was complaining about unnecssary unsafe blocks and >>> can be fixed by: (I haven't fully gone through the code, just applied this >>> for getting rid of the warnnings.) >> >> This is because `Bounded::__new` has become unsafe in `rust-next` ; this >> is not reflected in `driver-core-next` yet, but won't appear in -rc1. > > Any reason that you send the patch based on driver-core-next, instead of > linux-next? You won't have the issue if this is based on linux-next. driver-core-next is where the patch is going to be merged, so I use that as the base for the patchset. ^ permalink raw reply [flat|nested] 34+ messages in thread
* Re: [PATCH v4 6/7] sample: rust: pci: use `register!` macro 2026-01-28 2:37 ` [PATCH v4 6/7] sample: rust: pci: use " Alexandre Courbot 2026-01-28 12:35 ` Zhi Wang @ 2026-01-31 1:06 ` Danilo Krummrich 1 sibling, 0 replies; 34+ messages in thread From: Danilo Krummrich @ 2026-01-31 1:06 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Boqun Feng, Gary Guo, 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 28, 2026 at 3:37 AM CET, Alexandre Courbot wrote: > Convert the direct IO accesses to properly defined registers. > > Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> > --- > samples/rust/rust_driver_pci.rs | 23 ++++++++++++++++++----- > 1 file changed, 18 insertions(+), 5 deletions(-) > > diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs > index 4dfb8a6a4707..df2232d6b71f 100644 > --- a/samples/rust/rust_driver_pci.rs > +++ b/samples/rust/rust_driver_pci.rs > @@ -70,24 +70,37 @@ fn testdev(index: &TestIndex, bar: &Bar0) -> Result<u32> { > fn config_space(pdev: &pci::Device<Bound>) { > let config = pdev.config_space(); > > - // TODO: use the register!() macro for defining PCI configuration space registers once it > - // has been move out of nova-core. > + // Some PCI configuration space registers. > + ::kernel::register! { > + VENDOR_ID(u16) @ 0x0 { > + 15:0 vendor_id; > + } > + > + REVISION_ID(u8) @ 0x8 { > + 7:0 revision_id; > + } > + > + BAR(u32)[6] @ 0x10 { > + 31:0 value; > + } > + } > + > dev_info!( > pdev.as_ref(), > "pci-testdev config space read8 rev ID: {:x}\n", > - config.read8(0x8) > + REVISION_ID::read(&&config).revision_id() > ); > > dev_info!( > pdev.as_ref(), > "pci-testdev config space read16 vendor ID: {:x}\n", > - config.read16(0) > + VENDOR_ID::read(&&config).vendor_id() > ); > > dev_info!( > pdev.as_ref(), > "pci-testdev config space read32 BAR 0: {:x}\n", > - config.read32(0x10) > + BAR::read(&&config, 0).value() > ); > } > } That's only the config space registers, can you please also convert the MMIO ones? ^ permalink raw reply [flat|nested] 34+ messages in thread
* [PATCH FOR REFERENCE v4 7/7] gpu: nova-core: use the kernel `register!` macro 2026-01-28 2:37 [PATCH v4 0/7] rust: add `register!` macro Alexandre Courbot ` (5 preceding siblings ...) 2026-01-28 2:37 ` [PATCH v4 6/7] sample: rust: pci: use " Alexandre Courbot @ 2026-01-28 2:37 ` Alexandre Courbot 2026-01-28 2:57 ` John Hubbard 6 siblings, 1 reply; 34+ messages in thread From: Alexandre Courbot @ 2026-01-28 2:37 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. Tested-by: Dirk Behme <dirk.behme@de.bosch.com> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- drivers/gpu/nova-core/falcon.rs | 175 +++---- drivers/gpu/nova-core/falcon/gsp.rs | 12 +- drivers/gpu/nova-core/falcon/hal/ga102.rs | 15 +- drivers/gpu/nova-core/falcon/sec2.rs | 13 +- drivers/gpu/nova-core/fb/hal/ga100.rs | 17 +- drivers/gpu/nova-core/fb/hal/tu102.rs | 2 +- drivers/gpu/nova-core/gpu.rs | 31 +- drivers/gpu/nova-core/gsp/cmdq.rs | 4 +- drivers/gpu/nova-core/regs.rs | 494 +++++++++++--------- drivers/gpu/nova-core/regs/macros.rs | 739 ------------------------------ 10 files changed, 397 insertions(+), 1105 deletions(-) diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs index 82c661aef594..1b9283018c13 100644 --- a/drivers/gpu/nova-core/falcon.rs +++ b/drivers/gpu/nova-core/falcon.rs @@ -9,7 +9,11 @@ use kernel::{ device, dma::DmaAddress, - io::poll::read_poll_timeout, + io::{ + poll::read_poll_timeout, + register::RegisterBase, // + }, + num::Bounded, prelude::*, sync::aref::ARef, time::{ @@ -27,7 +31,6 @@ IntoSafeCast, // }, regs, - regs::macros::RegisterBase, // }; pub(crate) mod gsp; @@ -35,11 +38,11 @@ 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 { +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,10 +50,8 @@ 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)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum FalconCoreRev { - #[default] Rev1 = 1, Rev2 = 2, Rev3 = 3, @@ -59,16 +60,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,46 +86,38 @@ 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)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum FalconCoreRevSubversion { - #[default] Subversion0 = 0, Subversion1 = 1, 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. /// /// See `falcon.rst` for more details. +#[derive(Debug, Copy, Clone)] pub(crate) enum FalconSecurityModel { /// Non-Secure: runs unsigned code without privileges. - #[default] None = 0, /// Light-Secured (LS): Runs signed code with some privileges. /// Entry into this mode is only possible from 'Heavy-secure' mode, which verifies the code's @@ -138,16 +131,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 +153,22 @@ 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)] +#[derive(Debug, 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 +176,19 @@ 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)] +#[derive(Debug, 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), } @@ -207,33 +196,24 @@ fn try_from(value: u8) -> Result<Self> { } /// Currently active core on a dual falcon/riscv (Peregrine) controller. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum PeregrineCoreSelect { /// Falcon core is active. - #[default] Falcon = 0, /// 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 { @@ -246,10 +226,8 @@ pub(crate) enum FalconMem { /// Defines the Framebuffer Interface (FBIF) aperture type. /// This determines the memory type for external memory access during a DMA transfer, which is /// performed by the Falcon's Framebuffer DMA (FBDMA) engine. See falcon.rst for more details. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub(crate) enum FalconFbifTarget { - /// VRAM. - #[default] /// Local Framebuffer (GPU's VRAM memory). LocalFb = 0, /// Coherent system memory (System DRAM). @@ -257,14 +235,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, @@ -276,34 +254,25 @@ fn try_from(value: u8) -> Result<Self> { } /// Type of memory addresses to use. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub(crate) enum FalconFbifMemType { /// Virtual memory addresses. - #[default] Virtual = 0, /// 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(()); @@ -385,7 +354,7 @@ pub(crate) fn new(dev: &device::Device, chipset: Chipset) -> Result<Self> { /// Resets DMA-related registers. pub(crate) fn dma_reset(&self, bar: &Bar0) { regs::NV_PFALCON_FBIF_CTL::update(bar, &E::ID, |v| v.set_allow_phys_no_ctx(true)); - regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID); + regs::NV_PFALCON_FALCON_DMACTL::zeroed().write(bar, &E::ID); } /// Wait for memory scrubbing to complete. @@ -431,8 +400,8 @@ pub(crate) fn reset(&self, bar: &Bar0) -> Result { self.hal.select_core(self, bar)?; self.reset_wait_mem_scrubbing(bar)?; - regs::NV_PFALCON_FALCON_RM::default() - .set_value(regs::NV_PMC_BOOT_0::read(bar).into()) + regs::NV_PFALCON_FALCON_RM::zeroed() + .set_value(regs::NV_PMC_BOOT_0::read(bar).as_raw()) .write(bar, &E::ID); Ok(()) @@ -495,28 +464,26 @@ fn dma_wr<F: FalconFirmware<Target = E>>( // Set up the base source DMA address. - regs::NV_PFALCON_FALCON_DMATRFBASE::default() + regs::NV_PFALCON_FALCON_DMATRFBASE::zeroed() // CAST: `as u32` is used on purpose since we do want to strip the upper bits, which // will be written to `NV_PFALCON_FALCON_DMATRFBASE1`. .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) + regs::NV_PFALCON_FALCON_DMATRFBASE1::zeroed() + .try_set_base(dma_start >> 40)? .write(bar, &E::ID); - let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::default() + let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::zeroed() .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) + regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed() + .try_set_offs(load_offsets.dst_start + pos)? .write(bar, &E::ID); - regs::NV_PFALCON_FALCON_DMATRFFBOFFS::default() + regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed() .set_offs(src_start + pos) .write(bar, &E::ID); cmd.write(bar, &E::ID); @@ -549,7 +516,7 @@ pub(crate) fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) self.hal.program_brom(self, bar, &fw.brom_params())?; // Set `BootVec` to start of non-secure code. - regs::NV_PFALCON_FALCON_BOOTVEC::default() + regs::NV_PFALCON_FALCON_BOOTVEC::zeroed() .set_value(fw.boot_addr()) .write(bar, &E::ID); @@ -572,10 +539,10 @@ pub(crate) fn wait_till_halted(&self, bar: &Bar0) -> Result<()> { /// Start the falcon CPU. pub(crate) fn start(&self, bar: &Bar0) -> Result<()> { match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID).alias_en() { - true => regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::default() + true => regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::zeroed() .set_startcpu(true) .write(bar, &E::ID), - false => regs::NV_PFALCON_FALCON_CPUCTL::default() + false => regs::NV_PFALCON_FALCON_CPUCTL::zeroed() .set_startcpu(true) .write(bar, &E::ID), } @@ -586,13 +553,13 @@ pub(crate) fn start(&self, bar: &Bar0) -> Result<()> { /// Writes values to the mailbox registers if provided. pub(crate) fn write_mailboxes(&self, bar: &Bar0, mbox0: Option<u32>, mbox1: Option<u32>) { if let Some(mbox0) = mbox0 { - regs::NV_PFALCON_FALCON_MAILBOX0::default() + regs::NV_PFALCON_FALCON_MAILBOX0::zeroed() .set_value(mbox0) .write(bar, &E::ID); } if let Some(mbox1) = mbox1 { - regs::NV_PFALCON_FALCON_MAILBOX1::default() + regs::NV_PFALCON_FALCON_MAILBOX1::zeroed() .set_value(mbox1) .write(bar, &E::ID); } @@ -657,7 +624,7 @@ pub(crate) fn is_riscv_active(&self, bar: &Bar0) -> bool { /// Write the application version to the OS register. pub(crate) fn write_os_version(&self, bar: &Bar0, app_version: u32) { - regs::NV_PFALCON_FALCON_OS::default() + regs::NV_PFALCON_FALCON_OS::zeroed() .set_value(app_version) .write(bar, &E::ID); } diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs index 67edef3636c1..32b28e22c741 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. @@ -39,7 +39,7 @@ impl Falcon<Gsp> { /// Clears the SWGEN0 bit in the Falcon's IRQ status clear register to /// allow GSP to signal CPU for processing new messages in message queue. pub(crate) fn clear_swgen0_intr(&self, bar: &Bar0) { - regs::NV_PFALCON_FALCON_IRQSCLR::default() + regs::NV_PFALCON_FALCON_IRQSCLR::zeroed() .set_swgen0(true) .write(bar, &Gsp::ID); } diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs index 69a7a95cac16..c567d7d81f2a 100644 --- a/drivers/gpu/nova-core/falcon/hal/ga102.rs +++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs @@ -26,7 +26,7 @@ fn select_core_ga102<E: FalconEngine>(bar: &Bar0) -> Result { let bcr_ctrl = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID); if bcr_ctrl.core_select() != PeregrineCoreSelect::Falcon { - regs::NV_PRISCV_RISCV_BCR_CTRL::default() + regs::NV_PRISCV_RISCV_BCR_CTRL::zeroed() .set_core_select(PeregrineCoreSelect::Falcon) .write(bar, &E::ID); @@ -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,23 +68,24 @@ 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()) } fn program_brom_ga102<E: FalconEngine>(bar: &Bar0, params: &FalconBromParams) -> Result { - regs::NV_PFALCON2_FALCON_BROM_PARAADDR::default() + regs::NV_PFALCON2_FALCON_BROM_PARAADDR::zeroed() .set_value(params.pkc_data_offset) .write(bar, &E::ID, 0); - regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::default() + regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::zeroed() .set_value(u32::from(params.engine_id_mask)) .write(bar, &E::ID); - regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::default() + regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::zeroed() .set_ucode_id(params.ucode_id) .write(bar, &E::ID); - regs::NV_PFALCON2_FALCON_MOD_SEL::default() + regs::NV_PFALCON2_FALCON_MOD_SEL::zeroed() .set_algo(FalconModSelAlgo::Rsa3k) .write(bar, &E::ID); 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..efdb602d949d 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, @@ -19,12 +22,14 @@ 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) + regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::zeroed() + .set_adr_63_40( + Bounded::<u64, _>::from(addr) + .shr::<FLUSH_SYSMEM_ADDR_SHIFT_HI, _>() + .cast(), + ) .write(bar); - regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::default() + regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::zeroed() // CAST: `as u32` is used on purpose since we want to strip the upper bits that have been // written to `NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI`. .set_adr_39_08((addr >> FLUSH_SYSMEM_ADDR_SHIFT) as u32) diff --git a/drivers/gpu/nova-core/fb/hal/tu102.rs b/drivers/gpu/nova-core/fb/hal/tu102.rs index eec984f4e816..37da571e9962 100644 --- a/drivers/gpu/nova-core/fb/hal/tu102.rs +++ b/drivers/gpu/nova-core/fb/hal/tu102.rs @@ -21,7 +21,7 @@ pub(super) fn write_sysmem_flush_page_gm107(bar: &Bar0, addr: u64) -> Result { u32::try_from(addr >> FLUSH_SYSMEM_ADDR_SHIFT) .map_err(|_| EINVAL) .map(|addr| { - regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::default() + regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::zeroed() .set_adr_39_08(addr) .write(bar) }) diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs index 629c9d2dc994..e42b1aec95b4 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, // @@ -122,24 +123,19 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { } /// Enum representation of the GPU generation. -/// -/// TODO: remove the `Default` trait implementation, and the `#[default]` -/// attribute, once the register!() macro (which creates Architecture items) no -/// longer requires it for read-only fields. -#[derive(fmt::Debug, Default, Copy, Clone)] +#[derive(fmt::Debug, Copy, Clone)] #[repr(u8)] pub(crate) enum Architecture { - #[default] Turing = 0x16, Ampere = 0x17, 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 +144,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..0a7b00fc399e 100644 --- a/drivers/gpu/nova-core/gsp/cmdq.rs +++ b/drivers/gpu/nova-core/gsp/cmdq.rs @@ -475,8 +475,8 @@ 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) + regs::NV_PGSP_QUEUE_HEAD::zeroed() + .set_address(0u32) .write(bar); } diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index 82cc6c0790e5..dbc6fa121ef9 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,64 @@ 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 $([ $size:expr $(; $stride:expr)? ])? + $(@ $offset:literal)? + $(@ $base:ident + $base_offset:literal)? + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )? + $(, $comment:literal)? { $($fields:tt)* } + )* + )=> { + $( + ::kernel::register!( + @reg $(#[$attr])* pub(crate) $name(u32) $([$size $(; $stride)?])? + $(@ $offset)? + $(@ $base + $base_offset)? + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )? + $(, $comment)? { $($fields)* } + ); + )* + }; +} + // 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! { + /// Basic revision information about the GPU. + NV_PMC_BOOT_0 @ 0x00000000 { + /// Minor revision of the chip. + 3:0 minor_revision; + /// Major revision of the chip. + 7:4 major_revision; + /// Meta-variable `newbase` repeats 0 times, but `offset` repeats 1 time. + 8:8 architecture_1; + /// Implementation version of the architecture. + 23:20 implementation; + /// Lower bits of the architecture. + 28:24 architecture_0; + } + + /// Extended architecture information. + NV_PMC_BOOT_42 @ 0x00000a00 { + /// Minor revision of the chip. + 15:12 minor_revision; + /// Major revision of the chip. + 19:16 major_revision; + /// Implementation version of the architecture. + 23:20 implementation; + /// Architecture value. + 29:24 architecture ?=> 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,13 +87,6 @@ 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"; -}); - impl NV_PMC_BOOT_42 { /// Combines `architecture` and `implementation` to obtain a code unique to the chipset. pub(crate) fn chipset(self) -> Result<Chipset> { @@ -89,35 +119,50 @@ 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[64] @ 0x00001400 {} -register!(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; -}); + /// Scratch register 0xe used as FRTS firmware error code. + NV_PBUS_SW_SCRATCH_0E_FRTS_ERR => NV_PBUS_SW_SCRATCH[0xe] { + 31:16 frts_err_code; + } +} // PFB -// The following two registers together hold the physical system memory address that is used by the -// GPU to perform sysmembar operations (see `fb::SysmemFlush`). +nv_reg! { + /// Low bits of the physical system memory address used by the GPU to perform sysmembar + /// operations (see [`crate::fb::SysmemFlush`]). + NV_PFB_NISO_FLUSH_SYSMEM_ADDR @ 0x00100c10 { + 31:0 adr_39_08; + } -register!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR @ 0x00100c10 { - 31:0 adr_39_08 as u32; -}); + /// High bits of the physical system memory address used by the GPU to perform sysmembar + /// operations (see [`crate::fb::SysmemFlush`]). + NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI @ 0x00100c40 { + 23:0 adr_63_40; + } -register!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI @ 0x00100c40 { - 23:0 adr_63_40 as u32; -}); + NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE @ 0x00100ce0 { + 3:0 lower_scale; + 9:4 lower_mag; + 30:30 ecc_mode_enabled => bool; + } -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_PGSP_QUEUE_HEAD @ 0x00110c00 { + 31:0 address; + } -register!(NV_PGSP_QUEUE_HEAD @ 0x00110c00 { - 31:0 address as u32; -}); + NV_PFB_PRI_MMU_WPR2_ADDR_LO @ 0x001fa824 { + /// Bits 12..40 of the lower (inclusive) bound of the WPR2 region. + 31:4 lo_val; + } + + NV_PFB_PRI_MMU_WPR2_ADDR_HI @ 0x001fa828 { + /// Bits 12..40 of the higher (exclusive) bound of the WPR2 region. + 31:4 hi_val; + } +} impl NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE { /// Returns the usable framebuffer size, in bytes. @@ -134,10 +179,6 @@ 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"; -}); - impl NV_PFB_PRI_MMU_WPR2_ADDR_LO { /// Returns the lower (inclusive) bound of the WPR2 region. pub(crate) fn lower_bound(self) -> u64 { @@ -145,10 +186,6 @@ 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"; -}); - impl NV_PFB_PRI_MMU_WPR2_ADDR_HI { /// Returns the higher (exclusive) bound of the WPR2 region. /// @@ -167,29 +204,30 @@ pub(crate) fn higher_bound(self) -> u64 { // These scratch registers remain powered on even in a low-power state and have a designated group // number. -// 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; -}); - -// 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, - "Privilege level mask register" { - 0:0 read_protection_level0 as 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] {}); - -register!( - 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)"; +nv_reg! { + /// Boot Sequence Interface (BSI) register used to determine + /// if GSP reload/resume has completed during the boot process. + 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). + NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK @ 0x00118128 { + /// Set after FWSEC lowers its protection level. + 0:0 read_protection_level0 => bool; + } + + /// 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. + NV_PGC6_AON_SECURE_SCRATCH_GROUP_05[1] @ 0x00118234 {} + + /// Scratch group 05 register 0 used as GFW boot progress indicator. + NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT => NV_PGC6_AON_SECURE_SCRATCH_GROUP_05[0] { + /// Progress of GFW boot (0xff means completed). + 7:0 progress; + } +} impl NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT { /// Returns `true` if GFW boot is completed. @@ -198,16 +236,17 @@ pub(crate) fn completed(self) -> bool { } } -register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_42 @ 0x001183a4 { - 31:0 value as u32; -}); - -register!( - 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"; +nv_reg! { + NV_PGC6_AON_SECURE_SCRATCH_GROUP_42 @ 0x001183a4 { + 31:0 value; } -); + + /// Scratch group 42 register used as framebuffer size. + NV_USABLE_FB_SIZE_IN_MB => NV_PGC6_AON_SECURE_SCRATCH_GROUP_42 { + /// Usable framebuffer size, in megabytes. + 31:0 value; + } +} impl NV_USABLE_FB_SIZE_IN_MB { /// Returns the usable framebuffer size, in bytes. @@ -218,10 +257,14 @@ 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 { + /// Set if the `addr` field is valid. + 3:3 status_valid => bool; + /// VGA workspace base address divided by 0x10000. + 31:8 addr; + } +} impl NV_PDISP_VGA_WORKSPACE_BASE { /// Returns the base address of the VGA workspace, or `None` if none exists. @@ -238,48 +281,127 @@ 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[NV_FUSE_OPT_FPF_SIZE] @ 0x00824100 { + 15:0 data; + } -register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140[NV_FUSE_OPT_FPF_SIZE] { - 15:0 data as u16; -}); + NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION[NV_FUSE_OPT_FPF_SIZE] @ 0x00824140 { + 15:0 data; + } -register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0[NV_FUSE_OPT_FPF_SIZE] { - 15:0 data as u16; -}); + NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION[NV_FUSE_OPT_FPF_SIZE] @ 0x008241c0 { + 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_PFALCON_FALCON_MAILBOX0 @ PFalconBase + 0x00000040 { + 31:0 value => u32; + } -register!(NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase[0x00000044] { - 31:0 value as u32; -}); + 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; -}); + /// Used to store version information about the firmware running + /// on the Falcon processor. + NV_PFALCON_FALCON_OS @ PFalconBase + 0x00000080 { + 31:0 value => u32; + } -register!(NV_PFALCON_FALCON_RM @ PFalconBase[0x00000084] { - 31:0 value as u32; -}); + 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_PFALCON_FALCON_HWCFG2 @ PFalconBase + 0x000000f4 { + 10:10 riscv => bool; + /// Set to 0 after memory scrubbing is completed. + 12:12 mem_scrubbing => bool; + /// Signal indicating that reset is completed (GA102+). + 31:31 reset_ready => bool; + } + + NV_PFALCON_FALCON_CPUCTL @ PFalconBase + 0x00000100 { + 1:1 startcpu => bool; + 4:4 halted => bool; + 6:6 alias_en => bool; + } + + NV_PFALCON_FALCON_BOOTVEC @ PFalconBase + 0x00000104 { + 31:0 value => u32; + } + + 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; + } + + NV_PFALCON_FALCON_DMATRFBASE @ PFalconBase + 0x00000110 { + 31:0 base => u32; + } + + NV_PFALCON_FALCON_DMATRFMOFFS @ PFalconBase + 0x00000114 { + 23:0 offs; + } + + 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; + } + + NV_PFALCON_FALCON_DMATRFFBOFFS @ PFalconBase + 0x0000011c { + 31:0 offs => u32; + } + + NV_PFALCON_FALCON_DMATRFBASE1 @ PFalconBase + 0x00000128 { + 8:0 base; + } + + NV_PFALCON_FALCON_HWCFG1 @ PFalconBase + 0x0000012c { + /// Core revision. + 3:0 core_rev ?=> FalconCoreRev; + /// Security model. + 5:4 security_model ?=> FalconSecurityModel; + /// Core revision subversion. + 7:6 core_rev_subversion => FalconCoreRevSubversion; + } + + 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. + NV_PFALCON_FALCON_ENGINE @ PFalconBase + 0x000003c0 { + 0:0 reset => bool; + } + + NV_PFALCON_FBIF_TRANSCFG[8] @ PFalconBase + 0x00000600 { + 1:0 target ?=> FalconFbifTarget; + 2:2 mem_type => FalconFbifMemType; + } + + NV_PFALCON_FBIF_CTL @ PFalconBase + 0x00000624 { + 7:7 allow_phys_no_ctx => bool; + } +} impl NV_PFALCON_FALCON_HWCFG2 { /// Returns `true` if memory scrubbing is completed. @@ -288,108 +410,42 @@ 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; -}); - -register!(NV_PFALCON_FALCON_BOOTVEC @ PFalconBase[0x00000104] { - 31:0 value as 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; -}); - -register!(NV_PFALCON_FALCON_DMATRFBASE @ PFalconBase[0x00000110] { - 31:0 base as u32; -}); - -register!(NV_PFALCON_FALCON_DMATRFMOFFS @ PFalconBase[0x00000114] { - 23:0 offs as u32; -}); - -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; -}); - -register!(NV_PFALCON_FALCON_DMATRFFBOFFS @ PFalconBase[0x0000011c] { - 31:0 offs as u32; -}); - -register!(NV_PFALCON_FALCON_DMATRFBASE1 @ PFalconBase[0x00000128] { - 8:0 base as u16; -}); - -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"; -}); - -register!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ PFalconBase[0x00000130] { - 1:1 startcpu as 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; -}); - -register!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600[8]] { - 1:0 target as u8 ?=> FalconFbifTarget; - 2:2 mem_type as bool => FalconFbifMemType; -}); - -register!(NV_PFALCON_FBIF_CTL @ PFalconBase[0x00000624] { - 7:7 allow_phys_no_ctx as 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_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_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; -}); + /// 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. + NV_PFALCON2_FALCON_BROM_PARAADDR[1] @ PFalcon2Base + 0x00000210 { + 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_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 // only be used in HAL modules. @@ -397,15 +453,19 @@ 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 ed624be1f39b..000000000000 --- a/drivers/gpu/nova-core/regs/macros.rs +++ /dev/null @@ -1,739 +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<T, I>(io: &T) -> Self where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - { - Self(io.read32($offset)) - } - - /// Write the value contained in `self` to the register address in `io`. - #[inline(always)] - pub(crate) fn write<T, I>(self, io: &T) where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - { - 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<T, I, F>( - io: &T, - f: F, - ) where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B>( - io: &T, - #[allow(unused_variables)] - base: &B, - ) -> Self where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B>( - self, - io: &T, - #[allow(unused_variables)] - base: &B, - ) where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B, F>( - io: &T, - base: &B, - f: F, - ) where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I>( - io: &T, - idx: usize, - ) -> Self where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - { - 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<T, I>( - self, - io: &T, - idx: usize - ) where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - { - 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<T, I, F>( - io: &T, - idx: usize, - f: F, - ) where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I>( - io: &T, - idx: usize, - ) -> ::kernel::error::Result<Self> where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - { - 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<T, I>( - self, - io: &T, - idx: usize, - ) -> ::kernel::error::Result where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - { - 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<T, I, F>( - io: &T, - idx: usize, - f: F, - ) -> ::kernel::error::Result where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B>( - io: &T, - #[allow(unused_variables)] - base: &B, - idx: usize, - ) -> Self where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B>( - self, - io: &T, - #[allow(unused_variables)] - base: &B, - idx: usize - ) where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B, F>( - io: &T, - base: &B, - idx: usize, - f: F, - ) where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B>( - io: &T, - base: &B, - idx: usize, - ) -> ::kernel::error::Result<Self> where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B>( - self, - io: &T, - base: &B, - idx: usize, - ) -> ::kernel::error::Result where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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<T, I, B, F>( - io: &T, - base: &B, - idx: usize, - f: F, - ) -> ::kernel::error::Result where - T: ::core::ops::Deref<Target = I>, - I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>, - 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] 34+ messages in thread
* Re: [PATCH FOR REFERENCE v4 7/7] gpu: nova-core: use the kernel `register!` macro 2026-01-28 2:37 ` [PATCH FOR REFERENCE v4 7/7] gpu: nova-core: use the kernel " Alexandre Courbot @ 2026-01-28 2:57 ` John Hubbard 0 siblings, 0 replies; 34+ messages in thread From: John Hubbard @ 2026-01-28 2:57 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, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 1/27/26 6:37 PM, Alexandre Courbot wrote: > Replace the nova-core internal `register!` macro by the one defined in > the `kernel` crate and remove our own private implementations. > > Tested-by: Dirk Behme <dirk.behme@de.bosch.com> > Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> > --- > drivers/gpu/nova-core/falcon.rs | 175 +++---- > drivers/gpu/nova-core/falcon/gsp.rs | 12 +- > drivers/gpu/nova-core/falcon/hal/ga102.rs | 15 +- > drivers/gpu/nova-core/falcon/sec2.rs | 13 +- > drivers/gpu/nova-core/fb/hal/ga100.rs | 17 +- > drivers/gpu/nova-core/fb/hal/tu102.rs | 2 +- > drivers/gpu/nova-core/gpu.rs | 31 +- > drivers/gpu/nova-core/gsp/cmdq.rs | 4 +- > drivers/gpu/nova-core/regs.rs | 494 +++++++++++--------- > drivers/gpu/nova-core/regs/macros.rs | 739 ------------------------------ > 10 files changed, 397 insertions(+), 1105 deletions(-) Reading through this, it does look like an across the board improvement in readability in nova-core. Great! thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 34+ messages in thread
end of thread, other threads:[~2026-01-31 1:07 UTC | newest] Thread overview: 34+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-01-28 2:37 [PATCH v4 0/7] rust: add `register!` macro Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 1/7] rust: enable the `generic_arg_infer` feature Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 2/7] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot 2026-01-28 15:38 ` Gary Guo 2026-01-28 2:37 ` [PATCH v4 3/7] rust: num: add `as_bool` method to `Bounded<_, 1>` Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 4/7] rust: num: add `into_inner` method to `Bounded` Alexandre Courbot 2026-01-28 15:43 ` Gary Guo 2026-01-29 8:05 ` Alexandre Courbot 2026-01-29 20:23 ` Gary Guo 2026-01-30 0:58 ` Alexandre Courbot 2026-01-28 2:37 ` [PATCH v4 5/7] rust: io: add `register!` macro Alexandre Courbot 2026-01-28 3:02 ` John Hubbard 2026-01-28 3:47 ` Joel Fernandes 2026-01-28 7:42 ` Alexandre Courbot 2026-01-28 17:56 ` Joel Fernandes 2026-01-29 11:59 ` Alexandre Courbot 2026-01-28 16:16 ` Gary Guo 2026-01-29 8:00 ` Alexandre Courbot 2026-01-29 14:10 ` Gary Guo 2026-01-29 21:08 ` Danilo Krummrich 2026-01-30 6:55 ` Alexandre Courbot 2026-01-30 16:14 ` Gary Guo 2026-01-30 16:44 ` Danilo Krummrich 2026-01-30 17:01 ` Gary Guo 2026-01-30 17:18 ` Danilo Krummrich 2026-01-29 12:48 ` Danilo Krummrich 2026-01-28 2:37 ` [PATCH v4 6/7] sample: rust: pci: use " Alexandre Courbot 2026-01-28 12:35 ` Zhi Wang 2026-01-28 13:27 ` Alexandre Courbot 2026-01-28 15:46 ` Gary Guo 2026-01-29 8:01 ` Alexandre Courbot 2026-01-31 1:06 ` Danilo Krummrich 2026-01-28 2:37 ` [PATCH FOR REFERENCE v4 7/7] gpu: nova-core: use the kernel " Alexandre Courbot 2026-01-28 2:57 ` John Hubbard
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox