* [PATCH v8 00/10] rust: add `register!` macro
@ 2026-03-09 15:13 Alexandre Courbot
2026-03-09 15:13 ` [PATCH v8 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot
` (12 more replies)
0 siblings, 13 replies; 57+ messages in thread
From: Alexandre Courbot @ 2026-03-09 15:13 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Boqun Feng
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
Another revision, another redesign.
This one eschews closures completely for write operations, and defaults
to a two-argument `write` method backed by a single-argument `write_val`
one to avoid register name repetition. The two-argument version can now
be used in most cases thanks to a clever use of traits (thanks Gary!).
We went from this syntax:
bar.write_with(regs::NV_PFALCON_FALCON_DMATRFFBOFFS::of::<E>(), |r| {
r.with_offs(src_start + pos)
});
to this one:
bar.write(
WithBase::of::<E>(),
regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed().with_offs(src_start + pos),
);
While slightly more verbose, it is also much easier to read, as the
location and value are properly formatted into their own line, and the
confusing closure syntax is gone. Getting rid of closures also allows
any register value constructor to be used, not just a set of fixed ones.
The supporting code in the register module also doesn't spill on `io` at
all.
This design also trims more generated code from the register macro:
almost everything is provided through traits and generic code, and the
macro only implements the traits relevant to the register type (while
also embedding the bitfield feature which will eventually move to its
own macro).
The `register` macro documentation and top patch should give a good idea
of how this is used.
This revision is based on `driver-core-next` as of 2026-03-09. The top
patch also depends on `drm-rust-next`, but it is only here to illustrate
how client code looks like.
A tree with this series and its dependencies is available at [1].
[1] https://github.com/Gnurou/linux/tree/b4/register
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
Changes in v8:
- Use two-arguments `Io::write` with a convenience single-argument
`Io::write_val`.
- Remove the `IoWrite` type, all helper methods of `IoLoc`, and stop
relying on closures to write values.
- Add traits for specifying relative and array register locations.
- Improved documentation and examples.
- Link to v7: https://patch.msgid.link/20260224-register-v7-0-aad44f760f33@nvidia.com
Changes in v7:
- Use `RES + SHIFT >= N` instead of `RES >= N - SHIFT` in
`Bounded::shr`.
- Rename `IoRef` to `IoLoc` and all related types
accordingly.
- Use `Into` trait bounds in both directions on `IoLoc`.
- Add RFC patch allowing fixed register values to be used directly with
`write`.
- Link to v6: https://patch.msgid.link/20260216-register-v6-0-eec9a4de9e9e@nvidia.com
Changes in v6:
- Remove Tested-by tags as the code has considerably changed.
- Make `Bounded::get` const so it can be used with registers.
- Use the `pin_init::zeroed()` const function instead of defining our
own method.
- Generalize all `Io` around the new `IoRef` and `IoWrite` types, and
make registers use these as well.
- Use the more natural pattern of having the `Io` type perform the I/O
access instead of the register type.
- Convert the whole PCI driver example, and not only the PCI
configuration space.
- Rename `Bounded::as_bool` to `Bounded::into_bool`.
- Drop `Bounded::into_inner` in favor of making `Bounded::get` const.
- Link to v5: https://patch.msgid.link/20260129-register-v5-0-c4587c902514@nvidia.com
Changes in v5:
- Rename all setters to `with_*` and `with_const_*`.
- Use `, stride = ` to specify the stride of register arrays.
- Remove `Deref` requirement on the `RegisterIo` trait and make it
`#[doc(hidden)`.
- Simplify the top dispatch rule a bit.
- Link to v4: https://patch.msgid.link/20260128-register-v4-0-aee3a33d9649@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 (10):
rust: enable the `generic_arg_infer` feature
rust: num: add `shr` and `shl` methods to `Bounded`
rust: num: add `into_bool` method to `Bounded`
rust: num: make Bounded::get const
rust: io: add IoLoc type and generic I/O accessors
rust: io: use generic read/write accessors for primitive accesses
rust: io: introduce `IntoIoVal` trait and single-argument `write_val`
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 | 330 +++---
drivers/gpu/nova-core/falcon/gsp.rs | 25 +-
drivers/gpu/nova-core/falcon/hal/ga102.rs | 70 +-
drivers/gpu/nova-core/falcon/hal/tu102.rs | 12 +-
drivers/gpu/nova-core/falcon/sec2.rs | 17 +-
drivers/gpu/nova-core/fb.rs | 6 +-
drivers/gpu/nova-core/fb/hal/ga100.rs | 37 +-
drivers/gpu/nova-core/fb/hal/ga102.rs | 7 +-
drivers/gpu/nova-core/fb/hal/tu102.rs | 17 +-
drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 16 +-
drivers/gpu/nova-core/gfw.rs | 11 +-
drivers/gpu/nova-core/gpu.rs | 36 +-
drivers/gpu/nova-core/gsp/boot.rs | 11 +-
drivers/gpu/nova-core/gsp/cmdq.rs | 9 +-
drivers/gpu/nova-core/regs.rs | 612 +++++-----
drivers/gpu/nova-core/regs/macros.rs | 739 ------------
rust/kernel/io.rs | 391 +++++--
rust/kernel/io/register.rs | 1190 ++++++++++++++++++++
rust/kernel/lib.rs | 3 +
rust/kernel/num/bounded.rs | 70 +-
samples/rust/rust_driver_pci.rs | 84 +-
scripts/Makefile.build | 3 +-
22 files changed, 2319 insertions(+), 1377 deletions(-)
---
base-commit: a985880eb0f73b0d497d30dcce73e537f787b6c0
change-id: 20260117-register-ccaba1d21713
Best regards,
--
Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply [flat|nested] 57+ messages in thread* [PATCH v8 01/10] rust: enable the `generic_arg_infer` feature 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot @ 2026-03-09 15:13 ` Alexandre Courbot 2026-03-11 0:13 ` Yury Norov 2026-03-09 15:13 ` [PATCH v8 02/10] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot ` (11 subsequent siblings) 12 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:13 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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. 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/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 3da92f18f4ee..cddeae8b6cb2 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 32e209bc7985..923886735299 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -314,12 +314,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.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH v8 01/10] rust: enable the `generic_arg_infer` feature 2026-03-09 15:13 ` [PATCH v8 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot @ 2026-03-11 0:13 ` Yury Norov 2026-03-11 6:16 ` Miguel Ojeda 0 siblings, 1 reply; 57+ messages in thread From: Yury Norov @ 2026-03-11 0:13 UTC (permalink / raw) To: Alexandre Courbot Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue, Mar 10, 2026 at 12:13:58AM +0900, Alexandre Courbot wrote: > This feature is stable since 1.89, and used in subsequent patches. > > 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/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 3da92f18f4ee..cddeae8b6cb2 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 32e209bc7985..923886735299 100644 > --- a/scripts/Makefile.build > +++ b/scripts/Makefile.build > @@ -314,12 +314,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 Maybe split the line? It's already over 100 chars, and it doesn't seem to get shorter. > > # `--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.53.0 ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 01/10] rust: enable the `generic_arg_infer` feature 2026-03-11 0:13 ` Yury Norov @ 2026-03-11 6:16 ` Miguel Ojeda 0 siblings, 0 replies; 57+ messages in thread From: Miguel Ojeda @ 2026-03-11 6:16 UTC (permalink / raw) To: Yury Norov Cc: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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, Mar 11, 2026 at 1:13 AM Yury Norov <ynorov@nvidia.com> wrote: > > Maybe split the line? It's already over 100 chars, and it doesn't seem > to get shorter. We are about to bump the minimum to Debian Stable's, 1.85, so it will become shorter at just about the right time... :) Cheers, Miguel ^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 02/10] rust: num: add `shr` and `shl` methods to `Bounded` 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot 2026-03-09 15:13 ` [PATCH v8 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot @ 2026-03-09 15:13 ` Alexandre Courbot 2026-03-11 0:26 ` Yury Norov 2026-03-09 15:14 ` [PATCH v8 03/10] rust: num: add `into_bool` method " Alexandre Courbot ` (10 subsequent siblings) 12 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:13 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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> Reviewed-by: Gary Guo <gary@garyguo.net> Reviewed-by: Daniel Almeida <daniel.almeida@collabora.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 fa81acbdc8c2..2f5f13ecd3d6 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -473,6 +473,48 @@ pub fn cast<U>(self) -> Bounded<U, N> // `N` bits, and with the same signedness. unsafe { 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 + SHIFT >= N) } + + // 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.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH v8 02/10] rust: num: add `shr` and `shl` methods to `Bounded` 2026-03-09 15:13 ` [PATCH v8 02/10] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot @ 2026-03-11 0:26 ` Yury Norov 0 siblings, 0 replies; 57+ messages in thread From: Yury Norov @ 2026-03-11 0:26 UTC (permalink / raw) To: Alexandre Courbot Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue, Mar 10, 2026 at 12:13:59AM +0900, 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> > Reviewed-by: Gary Guo <gary@garyguo.net> > Reviewed-by: Daniel Almeida <daniel.almeida@collabora.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> Acked-by: Yury Norov <ynorov@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 fa81acbdc8c2..2f5f13ecd3d6 100644 > --- a/rust/kernel/num/bounded.rs > +++ b/rust/kernel/num/bounded.rs > @@ -473,6 +473,48 @@ pub fn cast<U>(self) -> Bounded<U, N> > // `N` bits, and with the same signedness. > unsafe { 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 + SHIFT >= N) } > + > + // 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.53.0 ^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 03/10] rust: num: add `into_bool` method to `Bounded` 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot 2026-03-09 15:13 ` [PATCH v8 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot 2026-03-09 15:13 ` [PATCH v8 02/10] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot @ 2026-03-09 15:14 ` Alexandre Courbot 2026-03-11 0:29 ` Yury Norov 2026-03-09 15:14 ` [PATCH v8 04/10] rust: num: make Bounded::get const Alexandre Courbot ` (9 subsequent siblings) 12 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:14 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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 `into_bool` method as a simpler shortcut. Reviewed-by: Alice Ryhl <aliceryhl@google.com> Reviewed-by: Gary Guo <gary@garyguo.net> Reviewed-by: Daniel Almeida <daniel.almeida@collabora.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 | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs index 2f5f13ecd3d6..d28d118abd8e 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -1101,3 +1101,24 @@ fn from(value: bool) -> Self { unsafe { Self::__new(T::from(value)) } } } + +impl<T> Bounded<T, 1> +where + T: Integer + Zeroable, +{ + /// Converts this [`Bounded`] into a [`bool`]. + /// + /// This is a shorter way of writing `bool::from(self)`. + /// + /// # Examples + /// + /// ``` + /// use kernel::num::Bounded; + /// + /// assert_eq!(Bounded::<u8, 1>::new::<0>().into_bool(), false); + /// assert_eq!(Bounded::<u8, 1>::new::<1>().into_bool(), true); + /// ``` + pub fn into_bool(self) -> bool { + self.into() + } +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH v8 03/10] rust: num: add `into_bool` method to `Bounded` 2026-03-09 15:14 ` [PATCH v8 03/10] rust: num: add `into_bool` method " Alexandre Courbot @ 2026-03-11 0:29 ` Yury Norov 0 siblings, 0 replies; 57+ messages in thread From: Yury Norov @ 2026-03-11 0:29 UTC (permalink / raw) To: Alexandre Courbot Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue, Mar 10, 2026 at 12:14:00AM +0900, Alexandre Courbot wrote: > Single-bit numbers are typically treated as booleans. There is an > `Into<bool>` implementation for those, but invoking it from contexts > that lack type expectations is not always convenient. > > Add an `into_bool` method as a simpler shortcut. > > Reviewed-by: Alice Ryhl <aliceryhl@google.com> > Reviewed-by: Gary Guo <gary@garyguo.net> > Reviewed-by: Daniel Almeida <daniel.almeida@collabora.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: Yury Norov <yury.norov@gmail.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 2f5f13ecd3d6..d28d118abd8e 100644 > --- a/rust/kernel/num/bounded.rs > +++ b/rust/kernel/num/bounded.rs > @@ -1101,3 +1101,24 @@ fn from(value: bool) -> Self { > unsafe { Self::__new(T::from(value)) } > } > } > + > +impl<T> Bounded<T, 1> > +where > + T: Integer + Zeroable, > +{ > + /// Converts this [`Bounded`] into a [`bool`]. > + /// > + /// This is a shorter way of writing `bool::from(self)`. > + /// > + /// # Examples > + /// > + /// ``` > + /// use kernel::num::Bounded; > + /// > + /// assert_eq!(Bounded::<u8, 1>::new::<0>().into_bool(), false); > + /// assert_eq!(Bounded::<u8, 1>::new::<1>().into_bool(), true); > + /// ``` > + pub fn into_bool(self) -> bool { > + self.into() > + } > +} > > -- > 2.53.0 ^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 04/10] rust: num: make Bounded::get const 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (2 preceding siblings ...) 2026-03-09 15:14 ` [PATCH v8 03/10] rust: num: add `into_bool` method " Alexandre Courbot @ 2026-03-09 15:14 ` Alexandre Courbot 2026-03-09 15:14 ` [PATCH v8 05/10] rust: io: add IoLoc type and generic I/O accessors Alexandre Courbot ` (8 subsequent siblings) 12 siblings, 0 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:14 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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 There is a need to access the inner value of a `Bounded` in const context, notably for bitfields and registers. Remove the invariant check of `Bounded::get`, which allows us to make it const. Reviewed-by: Gary Guo <gary@garyguo.net> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/num/bounded.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs index d28d118abd8e..bbab6bbcb315 100644 --- a/rust/kernel/num/bounded.rs +++ b/rust/kernel/num/bounded.rs @@ -379,6 +379,9 @@ pub fn from_expr(expr: T) -> Self { /// Returns the wrapped value as the backing type. /// + /// This is similar to the [`Deref`] implementation, but doesn't enforce the size invariant of + /// the [`Bounded`], which might produce slightly less optimal code. + /// /// # Examples /// /// ``` @@ -387,8 +390,8 @@ pub fn from_expr(expr: T) -> Self { /// let v = Bounded::<u32, 4>::new::<7>(); /// assert_eq!(v.get(), 7u32); /// ``` - pub fn get(self) -> T { - *self.deref() + pub const fn get(self) -> T { + self.0 } /// Increases the number of bits usable for `self`. -- 2.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH v8 05/10] rust: io: add IoLoc type and generic I/O accessors 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (3 preceding siblings ...) 2026-03-09 15:14 ` [PATCH v8 04/10] rust: num: make Bounded::get const Alexandre Courbot @ 2026-03-09 15:14 ` Alexandre Courbot 2026-03-10 15:15 ` Danilo Krummrich 2026-03-09 15:14 ` [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot ` (7 subsequent siblings) 12 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:14 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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 I/O accesses are defined by the following properties: - An I/O location, which consists of a start address, a width, and a type to interpret the read value as, - A value, which is returned for reads or provided for writes. Introduce the `IoLoc` trait, which allows implementing types to fully specify an I/O location. This allows I/O operations to be made generic through the new `read` and `write` methods. This design will allow us to factorize the I/O code working with primitives, and to introduce ways to perform I/O with a higher degree of control through register types. Co-developed-by: Gary Guo <gary@garyguo.net> Signed-off-by: Gary Guo <gary@garyguo.net> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/io.rs | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index b150743ffa4f..1db6572f4a42 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -173,6 +173,30 @@ pub trait IoCapable<T> { unsafe fn io_write(&self, value: T, address: usize); } +/// Describes a given I/O location: its offset, width, and type to convert the raw value from and +/// into. +/// +/// This trait is the key abstraction allowing [`Io::read`], [`Io::write`], and [`Io::update`] (and +/// their fallible [`try_read`](Io::try_read), [`try_write`](Io::try_write) and +/// [`try_update`](Io::try_update) counterparts) to work uniformly with both raw [`usize`] offsets +/// (for primitive types like [`u32`]) and typed ones. +/// +/// An `IoLoc<T>` carries three pieces of information: +/// +/// - The offset to access (returned by [`IoLoc::offset`]), +/// - The width of the access (determined by [`IoLoc::IoType`]), +/// - The type `T` in which the raw data is returned or provided. +/// +/// `T` and `IoLoc::IoType` may differ: for instance, a typed register has `T` = the register type +/// with its bitfields, and `IoType` = its backing primitive (e.g. `u32`). +pub trait IoLoc<T> { + /// Size ([`u8`], [`u16`], etc) of the I/O performed on the returned [`offset`](IoLoc::offset). + type IoType: Into<T> + From<T>; + + /// Returns the offset of this location. + fn offset(&self) -> usize; +} + /// Types implementing this trait (e.g. MMIO BARs or PCI config regions) /// can perform I/O operations on regions of memory. /// @@ -406,6 +430,106 @@ fn write64(&self, value: u64, offset: usize) // SAFETY: `address` has been validated by `io_addr_assert`. unsafe { self.io_write(value, address) } } + + /// Generic fallible read with runtime bounds check. + #[inline(always)] + fn try_read<T, L>(&self, location: L) -> Result<T> + where + L: IoLoc<T>, + Self: IoCapable<L::IoType>, + { + let address = self.io_addr::<L::IoType>(location.offset())?; + + // SAFETY: `address` has been validated by `io_addr`. + Ok(unsafe { self.io_read(address) }.into()) + } + + /// Generic fallible write with runtime bounds check. + #[inline(always)] + fn try_write<T, L>(&self, location: L, value: T) -> Result + where + L: IoLoc<T>, + Self: IoCapable<L::IoType>, + { + let address = self.io_addr::<L::IoType>(location.offset())?; + let io_value = value.into(); + + // SAFETY: `address` has been validated by `io_addr`. + unsafe { self.io_write(io_value, address) } + + Ok(()) + } + + /// Generic fallible update with runtime bounds check. + /// + /// Caution: this does not perform any synchronization. Race conditions can occur in case of + /// concurrent access. + #[inline(always)] + fn try_update<T, L, F>(&self, location: L, f: F) -> Result + where + L: IoLoc<T>, + Self: IoCapable<L::IoType>, + F: FnOnce(T) -> T, + { + let address = self.io_addr::<L::IoType>(location.offset())?; + + // SAFETY: `address` has been validated by `io_addr`. + let value: T = unsafe { self.io_read(address) }.into(); + let io_value = f(value).into(); + + // SAFETY: `address` has been validated by `io_addr_assert`. + unsafe { self.io_write(io_value, address) } + + Ok(()) + } + + /// Generic infallible read with compile-time bounds check. + #[inline(always)] + fn read<T, L>(&self, location: L) -> T + where + L: IoLoc<T>, + Self: IoKnownSize + IoCapable<L::IoType>, + { + let address = self.io_addr_assert::<L::IoType>(location.offset()); + + // SAFETY: `address` has been validated by `io_addr_assert`. + unsafe { self.io_read(address) }.into() + } + + /// Generic infallible write with compile-time bounds check. + #[inline(always)] + fn write<T, L>(&self, location: L, value: T) + where + L: IoLoc<T>, + Self: IoKnownSize + IoCapable<L::IoType>, + { + let address = self.io_addr_assert::<L::IoType>(location.offset()); + let io_value = value.into(); + + // SAFETY: `address` has been validated by `io_addr_assert`. + unsafe { self.io_write(io_value, address) } + } + + /// Generic infallible update with compile-time bounds check. + /// + /// Caution: this does not perform any synchronization. Race conditions can occur in case of + /// concurrent access. + #[inline(always)] + fn update<T, L, F>(&self, location: L, f: F) + where + L: IoLoc<T>, + Self: IoKnownSize + IoCapable<L::IoType> + Sized, + F: FnOnce(T) -> T, + { + let address = self.io_addr_assert::<L::IoType>(location.offset()); + + // SAFETY: `address` has been validated by `io_addr_assert`. + let value: T = unsafe { self.io_read(address) }.into(); + let io_value = f(value).into(); + + // SAFETY: `address` has been validated by `io_addr_assert`. + unsafe { self.io_write(io_value, address) } + } } /// Trait for types with a known size at compile time. -- 2.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH v8 05/10] rust: io: add IoLoc type and generic I/O accessors 2026-03-09 15:14 ` [PATCH v8 05/10] rust: io: add IoLoc type and generic I/O accessors Alexandre Courbot @ 2026-03-10 15:15 ` Danilo Krummrich 0 siblings, 0 replies; 57+ messages in thread From: Danilo Krummrich @ 2026-03-10 15:15 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 4:14 PM CET, Alexandre Courbot wrote: > + /// Generic fallible update with runtime bounds check. > + /// > + /// Caution: this does not perform any synchronization. Race conditions can occur in case of > + /// concurrent access. NIT: I don't think this really comes as a surprise, so I'd make it "Note:" instead. I'd also replace "Race conditions can occur in case of concurrent access." with something along the lines of "The caller is responsible to ensure exclusive access if required." > + #[inline(always)] > + fn try_update<T, L, F>(&self, location: L, f: F) -> Result ^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (4 preceding siblings ...) 2026-03-09 15:14 ` [PATCH v8 05/10] rust: io: add IoLoc type and generic I/O accessors Alexandre Courbot @ 2026-03-09 15:14 ` Alexandre Courbot 2026-03-09 15:29 ` Gary Guo 2026-03-10 15:17 ` Danilo Krummrich 2026-03-09 15:14 ` [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` Alexandre Courbot ` (6 subsequent siblings) 12 siblings, 2 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:14 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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 By providing the required `IoLoc` implementations on `usize`, we can leverage the generic accessors and reduce the number of unsafe blocks in the module. This also allows us to directly call the generic `read/write/update` methods with primitive types, so add examples illustrating this. Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/io.rs | 199 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 131 insertions(+), 68 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 1db6572f4a42..ed6fab001a39 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -197,6 +197,25 @@ pub trait IoLoc<T> { fn offset(&self) -> usize; } +/// Implements [`IoLoc<$ty>`] for [`usize`], allowing to use `usize` as a parameter of +/// [`Io::read`] and [`Io::write`]. +macro_rules! impl_usize_ioloc { + ($($ty:ty),*) => { + $( + impl IoLoc<$ty> for usize { + type IoType = $ty; + + fn offset(&self) -> usize { + *self + } + } + )* + } +} + +// Provide the ability to read any primitive type from a [`usize`]. +impl_usize_ioloc!(u8, u16, u32, u64); + /// Types implementing this trait (e.g. MMIO BARs or PCI config regions) /// can perform I/O operations on regions of memory. /// @@ -241,10 +260,7 @@ fn try_read8(&self, offset: usize) -> Result<u8> where Self: IoCapable<u8>, { - let address = self.io_addr::<u8>(offset)?; - - // SAFETY: `address` has been validated by `io_addr`. - Ok(unsafe { self.io_read(address) }) + self.try_read(offset) } /// Fallible 16-bit read with runtime bounds check. @@ -253,10 +269,7 @@ fn try_read16(&self, offset: usize) -> Result<u16> where Self: IoCapable<u16>, { - let address = self.io_addr::<u16>(offset)?; - - // SAFETY: `address` has been validated by `io_addr`. - Ok(unsafe { self.io_read(address) }) + self.try_read(offset) } /// Fallible 32-bit read with runtime bounds check. @@ -265,10 +278,7 @@ fn try_read32(&self, offset: usize) -> Result<u32> where Self: IoCapable<u32>, { - let address = self.io_addr::<u32>(offset)?; - - // SAFETY: `address` has been validated by `io_addr`. - Ok(unsafe { self.io_read(address) }) + self.try_read(offset) } /// Fallible 64-bit read with runtime bounds check. @@ -277,10 +287,7 @@ fn try_read64(&self, offset: usize) -> Result<u64> where Self: IoCapable<u64>, { - let address = self.io_addr::<u64>(offset)?; - - // SAFETY: `address` has been validated by `io_addr`. - Ok(unsafe { self.io_read(address) }) + self.try_read(offset) } /// Fallible 8-bit write with runtime bounds check. @@ -289,11 +296,7 @@ fn try_write8(&self, value: u8, offset: usize) -> Result where Self: IoCapable<u8>, { - let address = self.io_addr::<u8>(offset)?; - - // SAFETY: `address` has been validated by `io_addr`. - unsafe { self.io_write(value, address) }; - Ok(()) + self.try_write(offset, value) } /// Fallible 16-bit write with runtime bounds check. @@ -302,11 +305,7 @@ fn try_write16(&self, value: u16, offset: usize) -> Result where Self: IoCapable<u16>, { - let address = self.io_addr::<u16>(offset)?; - - // SAFETY: `address` has been validated by `io_addr`. - unsafe { self.io_write(value, address) }; - Ok(()) + self.try_write(offset, value) } /// Fallible 32-bit write with runtime bounds check. @@ -315,11 +314,7 @@ fn try_write32(&self, value: u32, offset: usize) -> Result where Self: IoCapable<u32>, { - let address = self.io_addr::<u32>(offset)?; - - // SAFETY: `address` has been validated by `io_addr`. - unsafe { self.io_write(value, address) }; - Ok(()) + self.try_write(offset, value) } /// Fallible 64-bit write with runtime bounds check. @@ -328,11 +323,7 @@ fn try_write64(&self, value: u64, offset: usize) -> Result where Self: IoCapable<u64>, { - let address = self.io_addr::<u64>(offset)?; - - // SAFETY: `address` has been validated by `io_addr`. - unsafe { self.io_write(value, address) }; - Ok(()) + self.try_write(offset, value) } /// Infallible 8-bit read with compile-time bounds check. @@ -341,10 +332,7 @@ fn read8(&self, offset: usize) -> u8 where Self: IoKnownSize + IoCapable<u8>, { - let address = self.io_addr_assert::<u8>(offset); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_read(address) } + self.read(offset) } /// Infallible 16-bit read with compile-time bounds check. @@ -353,10 +341,7 @@ fn read16(&self, offset: usize) -> u16 where Self: IoKnownSize + IoCapable<u16>, { - let address = self.io_addr_assert::<u16>(offset); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_read(address) } + self.read(offset) } /// Infallible 32-bit read with compile-time bounds check. @@ -365,10 +350,7 @@ fn read32(&self, offset: usize) -> u32 where Self: IoKnownSize + IoCapable<u32>, { - let address = self.io_addr_assert::<u32>(offset); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_read(address) } + self.read(offset) } /// Infallible 64-bit read with compile-time bounds check. @@ -377,10 +359,7 @@ fn read64(&self, offset: usize) -> u64 where Self: IoKnownSize + IoCapable<u64>, { - let address = self.io_addr_assert::<u64>(offset); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_read(address) } + self.read(offset) } /// Infallible 8-bit write with compile-time bounds check. @@ -389,10 +368,7 @@ fn write8(&self, value: u8, offset: usize) where Self: IoKnownSize + IoCapable<u8>, { - let address = self.io_addr_assert::<u8>(offset); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_write(value, address) } + self.write(offset, value) } /// Infallible 16-bit write with compile-time bounds check. @@ -401,10 +377,7 @@ fn write16(&self, value: u16, offset: usize) where Self: IoKnownSize + IoCapable<u16>, { - let address = self.io_addr_assert::<u16>(offset); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_write(value, address) } + self.write(offset, value) } /// Infallible 32-bit write with compile-time bounds check. @@ -413,10 +386,7 @@ fn write32(&self, value: u32, offset: usize) where Self: IoKnownSize + IoCapable<u32>, { - let address = self.io_addr_assert::<u32>(offset); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_write(value, address) } + self.write(offset, value) } /// Infallible 64-bit write with compile-time bounds check. @@ -425,13 +395,28 @@ fn write64(&self, value: u64, offset: usize) where Self: IoKnownSize + IoCapable<u64>, { - let address = self.io_addr_assert::<u64>(offset); - - // SAFETY: `address` has been validated by `io_addr_assert`. - unsafe { self.io_write(value, address) } + self.write(offset, value) } /// Generic fallible read with runtime bounds check. + /// + /// # Examples + /// + /// Read a primitive type from an I/O address: + /// + /// ```no_run + /// use kernel::io::{Io, Mmio}; + /// + /// fn do_reads(io: &Mmio) -> Result { + /// // 32-bit read from address `0x10`. + /// let v: u32 = io.try_read(0x10)?; + /// + /// // 8-bit read from address `0xfff`. + /// let v: u8 = io.try_read(0xfff)?; + /// + /// Ok(()) + /// } + /// ``` #[inline(always)] fn try_read<T, L>(&self, location: L) -> Result<T> where @@ -445,6 +430,24 @@ fn try_read<T, L>(&self, location: L) -> Result<T> } /// Generic fallible write with runtime bounds check. + /// + /// # Examples + /// + /// Write a primitive type to an I/O address: + /// + /// ```no_run + /// use kernel::io::{Io, Mmio}; + /// + /// fn do_writes(io: &Mmio) -> Result { + /// // 32-bit write of value `1` at address `0x10`. + /// io.try_write(0x10, 1u32)?; + /// + /// // 8-bit write of value `0xff` at address `0xfff`. + /// io.try_write(0xfff, 0xffu8)?; + /// + /// Ok(()) + /// } + /// ``` #[inline(always)] fn try_write<T, L>(&self, location: L, value: T) -> Result where @@ -464,6 +467,20 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result /// /// Caution: this does not perform any synchronization. Race conditions can occur in case of /// concurrent access. + /// + /// # Examples + /// + /// Read the u32 value at address `0x10`, increment it, and store the updated value back: + /// + /// ```no_run + /// use kernel::io::{Io, Mmio}; + /// + /// fn do_update(io: &Mmio<0x1000>) -> Result { + /// io.try_update(0x10, |v: u32| { + /// v + 1 + /// }) + /// } + /// ``` #[inline(always)] fn try_update<T, L, F>(&self, location: L, f: F) -> Result where @@ -484,6 +501,22 @@ fn try_update<T, L, F>(&self, location: L, f: F) -> Result } /// Generic infallible read with compile-time bounds check. + /// + /// # Examples + /// + /// Read a primitive type from an I/O address: + /// + /// ```no_run + /// use kernel::io::{Io, Mmio}; + /// + /// fn do_reads(io: &Mmio<0x1000>) { + /// // 32-bit read from address `0x10`. + /// let v: u32 = io.read(0x10); + /// + /// // 8-bit read from the top of the I/O space. + /// let v: u8 = io.read(0xfff); + /// } + /// ``` #[inline(always)] fn read<T, L>(&self, location: L) -> T where @@ -497,6 +530,22 @@ fn read<T, L>(&self, location: L) -> T } /// Generic infallible write with compile-time bounds check. + /// + /// # Examples + /// + /// Write a primitive type to an I/O address: + /// + /// ```no_run + /// use kernel::io::{Io, Mmio}; + /// + /// fn do_writes(io: &Mmio<0x1000>) { + /// // 32-bit write of value `1` at address `0x10`. + /// io.write(0x10, 1u32); + /// + /// // 8-bit write of value `0xff` at the top of the I/O space. + /// io.write(0xfff, 0xffu8); + /// } + /// ``` #[inline(always)] fn write<T, L>(&self, location: L, value: T) where @@ -514,6 +563,20 @@ fn write<T, L>(&self, location: L, value: T) /// /// Caution: this does not perform any synchronization. Race conditions can occur in case of /// concurrent access. + /// + /// # Examples + /// + /// Read the u32 value at address `0x10`, increment it, and store the updated value back: + /// + /// ```no_run + /// use kernel::io::{Io, Mmio}; + /// + /// fn do_update(io: &Mmio<0x1000>) { + /// io.update(0x10, |v: u32| { + /// v + 1 + /// }) + /// } + /// ``` #[inline(always)] fn update<T, L, F>(&self, location: L, f: F) where -- 2.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses 2026-03-09 15:14 ` [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot @ 2026-03-09 15:29 ` Gary Guo 2026-03-10 1:57 ` Alexandre Courbot 2026-03-10 15:17 ` Danilo Krummrich 1 sibling, 1 reply; 57+ messages in thread From: Gary Guo @ 2026-03-09 15:29 UTC (permalink / raw) To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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 Mon Mar 9, 2026 at 3:14 PM GMT, Alexandre Courbot wrote: > By providing the required `IoLoc` implementations on `usize`, we can > leverage the generic accessors and reduce the number of unsafe blocks in > the module. > > This also allows us to directly call the generic `read/write/update` > methods with primitive types, so add examples illustrating this. > > Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> > --- > rust/kernel/io.rs | 199 +++++++++++++++++++++++++++++++++++------------------- > 1 file changed, 131 insertions(+), 68 deletions(-) > > diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs > index 1db6572f4a42..ed6fab001a39 100644 > --- a/rust/kernel/io.rs > +++ b/rust/kernel/io.rs > @@ -197,6 +197,25 @@ pub trait IoLoc<T> { > fn offset(&self) -> usize; > } > > +/// Implements [`IoLoc<$ty>`] for [`usize`], allowing to use `usize` as a parameter of > +/// [`Io::read`] and [`Io::write`]. > +macro_rules! impl_usize_ioloc { > + ($($ty:ty),*) => { > + $( > + impl IoLoc<$ty> for usize { > + type IoType = $ty; > + #[inline(always)] the fact that this is a pointer is somewhat uneasy to me. I wonder if Clippy with its inlining tweak would cause optimisation failure here. Could this be just `fn offset(self)`? The rest LGTM. Best, Gary > + fn offset(&self) -> usize { > + *self > + } > + } > + )* > + } > +} > + > +// Provide the ability to read any primitive type from a [`usize`]. > +impl_usize_ioloc!(u8, u16, u32, u64); > + > /// Types implementing this trait (e.g. MMIO BARs or PCI config regions) > /// can perform I/O operations on regions of memory. > /// > @@ -241,10 +260,7 @@ fn try_read8(&self, offset: usize) -> Result<u8> > where > Self: IoCapable<u8>, > { > - let address = self.io_addr::<u8>(offset)?; > - > - // SAFETY: `address` has been validated by `io_addr`. > - Ok(unsafe { self.io_read(address) }) > + self.try_read(offset) > } > > /// Fallible 16-bit read with runtime bounds check. > @@ -253,10 +269,7 @@ fn try_read16(&self, offset: usize) -> Result<u16> > where > Self: IoCapable<u16>, > { > - let address = self.io_addr::<u16>(offset)?; > - > - // SAFETY: `address` has been validated by `io_addr`. > - Ok(unsafe { self.io_read(address) }) > + self.try_read(offset) > } > > /// Fallible 32-bit read with runtime bounds check. > @@ -265,10 +278,7 @@ fn try_read32(&self, offset: usize) -> Result<u32> > where > Self: IoCapable<u32>, > { > - let address = self.io_addr::<u32>(offset)?; > - > - // SAFETY: `address` has been validated by `io_addr`. > - Ok(unsafe { self.io_read(address) }) > + self.try_read(offset) > } > > /// Fallible 64-bit read with runtime bounds check. > @@ -277,10 +287,7 @@ fn try_read64(&self, offset: usize) -> Result<u64> > where > Self: IoCapable<u64>, > { > - let address = self.io_addr::<u64>(offset)?; > - > - // SAFETY: `address` has been validated by `io_addr`. > - Ok(unsafe { self.io_read(address) }) > + self.try_read(offset) > } > > /// Fallible 8-bit write with runtime bounds check. > @@ -289,11 +296,7 @@ fn try_write8(&self, value: u8, offset: usize) -> Result > where > Self: IoCapable<u8>, > { > - let address = self.io_addr::<u8>(offset)?; > - > - // SAFETY: `address` has been validated by `io_addr`. > - unsafe { self.io_write(value, address) }; > - Ok(()) > + self.try_write(offset, value) > } > > /// Fallible 16-bit write with runtime bounds check. > @@ -302,11 +305,7 @@ fn try_write16(&self, value: u16, offset: usize) -> Result > where > Self: IoCapable<u16>, > { > - let address = self.io_addr::<u16>(offset)?; > - > - // SAFETY: `address` has been validated by `io_addr`. > - unsafe { self.io_write(value, address) }; > - Ok(()) > + self.try_write(offset, value) > } > > /// Fallible 32-bit write with runtime bounds check. > @@ -315,11 +314,7 @@ fn try_write32(&self, value: u32, offset: usize) -> Result > where > Self: IoCapable<u32>, > { > - let address = self.io_addr::<u32>(offset)?; > - > - // SAFETY: `address` has been validated by `io_addr`. > - unsafe { self.io_write(value, address) }; > - Ok(()) > + self.try_write(offset, value) > } > > /// Fallible 64-bit write with runtime bounds check. > @@ -328,11 +323,7 @@ fn try_write64(&self, value: u64, offset: usize) -> Result > where > Self: IoCapable<u64>, > { > - let address = self.io_addr::<u64>(offset)?; > - > - // SAFETY: `address` has been validated by `io_addr`. > - unsafe { self.io_write(value, address) }; > - Ok(()) > + self.try_write(offset, value) > } > > /// Infallible 8-bit read with compile-time bounds check. > @@ -341,10 +332,7 @@ fn read8(&self, offset: usize) -> u8 > where > Self: IoKnownSize + IoCapable<u8>, > { > - let address = self.io_addr_assert::<u8>(offset); > - > - // SAFETY: `address` has been validated by `io_addr_assert`. > - unsafe { self.io_read(address) } > + self.read(offset) > } > > /// Infallible 16-bit read with compile-time bounds check. > @@ -353,10 +341,7 @@ fn read16(&self, offset: usize) -> u16 > where > Self: IoKnownSize + IoCapable<u16>, > { > - let address = self.io_addr_assert::<u16>(offset); > - > - // SAFETY: `address` has been validated by `io_addr_assert`. > - unsafe { self.io_read(address) } > + self.read(offset) > } > > /// Infallible 32-bit read with compile-time bounds check. > @@ -365,10 +350,7 @@ fn read32(&self, offset: usize) -> u32 > where > Self: IoKnownSize + IoCapable<u32>, > { > - let address = self.io_addr_assert::<u32>(offset); > - > - // SAFETY: `address` has been validated by `io_addr_assert`. > - unsafe { self.io_read(address) } > + self.read(offset) > } > > /// Infallible 64-bit read with compile-time bounds check. > @@ -377,10 +359,7 @@ fn read64(&self, offset: usize) -> u64 > where > Self: IoKnownSize + IoCapable<u64>, > { > - let address = self.io_addr_assert::<u64>(offset); > - > - // SAFETY: `address` has been validated by `io_addr_assert`. > - unsafe { self.io_read(address) } > + self.read(offset) > } > > /// Infallible 8-bit write with compile-time bounds check. > @@ -389,10 +368,7 @@ fn write8(&self, value: u8, offset: usize) > where > Self: IoKnownSize + IoCapable<u8>, > { > - let address = self.io_addr_assert::<u8>(offset); > - > - // SAFETY: `address` has been validated by `io_addr_assert`. > - unsafe { self.io_write(value, address) } > + self.write(offset, value) > } > > /// Infallible 16-bit write with compile-time bounds check. > @@ -401,10 +377,7 @@ fn write16(&self, value: u16, offset: usize) > where > Self: IoKnownSize + IoCapable<u16>, > { > - let address = self.io_addr_assert::<u16>(offset); > - > - // SAFETY: `address` has been validated by `io_addr_assert`. > - unsafe { self.io_write(value, address) } > + self.write(offset, value) > } > > /// Infallible 32-bit write with compile-time bounds check. > @@ -413,10 +386,7 @@ fn write32(&self, value: u32, offset: usize) > where > Self: IoKnownSize + IoCapable<u32>, > { > - let address = self.io_addr_assert::<u32>(offset); > - > - // SAFETY: `address` has been validated by `io_addr_assert`. > - unsafe { self.io_write(value, address) } > + self.write(offset, value) > } > > /// Infallible 64-bit write with compile-time bounds check. > @@ -425,13 +395,28 @@ fn write64(&self, value: u64, offset: usize) > where > Self: IoKnownSize + IoCapable<u64>, > { > - let address = self.io_addr_assert::<u64>(offset); > - > - // SAFETY: `address` has been validated by `io_addr_assert`. > - unsafe { self.io_write(value, address) } > + self.write(offset, value) > } > > /// Generic fallible read with runtime bounds check. > + /// > + /// # Examples > + /// > + /// Read a primitive type from an I/O address: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_reads(io: &Mmio) -> Result { > + /// // 32-bit read from address `0x10`. > + /// let v: u32 = io.try_read(0x10)?; > + /// > + /// // 8-bit read from address `0xfff`. > + /// let v: u8 = io.try_read(0xfff)?; > + /// > + /// Ok(()) > + /// } > + /// ``` > #[inline(always)] > fn try_read<T, L>(&self, location: L) -> Result<T> > where > @@ -445,6 +430,24 @@ fn try_read<T, L>(&self, location: L) -> Result<T> > } > > /// Generic fallible write with runtime bounds check. > + /// > + /// # Examples > + /// > + /// Write a primitive type to an I/O address: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_writes(io: &Mmio) -> Result { > + /// // 32-bit write of value `1` at address `0x10`. > + /// io.try_write(0x10, 1u32)?; > + /// > + /// // 8-bit write of value `0xff` at address `0xfff`. > + /// io.try_write(0xfff, 0xffu8)?; > + /// > + /// Ok(()) > + /// } > + /// ``` > #[inline(always)] > fn try_write<T, L>(&self, location: L, value: T) -> Result > where > @@ -464,6 +467,20 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result > /// > /// Caution: this does not perform any synchronization. Race conditions can occur in case of > /// concurrent access. > + /// > + /// # Examples > + /// > + /// Read the u32 value at address `0x10`, increment it, and store the updated value back: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_update(io: &Mmio<0x1000>) -> Result { > + /// io.try_update(0x10, |v: u32| { > + /// v + 1 > + /// }) > + /// } > + /// ``` > #[inline(always)] > fn try_update<T, L, F>(&self, location: L, f: F) -> Result > where > @@ -484,6 +501,22 @@ fn try_update<T, L, F>(&self, location: L, f: F) -> Result > } > > /// Generic infallible read with compile-time bounds check. > + /// > + /// # Examples > + /// > + /// Read a primitive type from an I/O address: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_reads(io: &Mmio<0x1000>) { > + /// // 32-bit read from address `0x10`. > + /// let v: u32 = io.read(0x10); > + /// > + /// // 8-bit read from the top of the I/O space. > + /// let v: u8 = io.read(0xfff); > + /// } > + /// ``` > #[inline(always)] > fn read<T, L>(&self, location: L) -> T > where > @@ -497,6 +530,22 @@ fn read<T, L>(&self, location: L) -> T > } > > /// Generic infallible write with compile-time bounds check. > + /// > + /// # Examples > + /// > + /// Write a primitive type to an I/O address: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_writes(io: &Mmio<0x1000>) { > + /// // 32-bit write of value `1` at address `0x10`. > + /// io.write(0x10, 1u32); > + /// > + /// // 8-bit write of value `0xff` at the top of the I/O space. > + /// io.write(0xfff, 0xffu8); > + /// } > + /// ``` > #[inline(always)] > fn write<T, L>(&self, location: L, value: T) > where > @@ -514,6 +563,20 @@ fn write<T, L>(&self, location: L, value: T) > /// > /// Caution: this does not perform any synchronization. Race conditions can occur in case of > /// concurrent access. > + /// > + /// # Examples > + /// > + /// Read the u32 value at address `0x10`, increment it, and store the updated value back: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_update(io: &Mmio<0x1000>) { > + /// io.update(0x10, |v: u32| { > + /// v + 1 > + /// }) > + /// } > + /// ``` > #[inline(always)] > fn update<T, L, F>(&self, location: L, f: F) > where ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses 2026-03-09 15:29 ` Gary Guo @ 2026-03-10 1:57 ` Alexandre Courbot 2026-03-10 1:59 ` Alexandre Courbot 0 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-10 1:57 UTC (permalink / raw) To: Gary Guo Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue Mar 10, 2026 at 12:29 AM JST, Gary Guo wrote: > On Mon Mar 9, 2026 at 3:14 PM GMT, Alexandre Courbot wrote: >> By providing the required `IoLoc` implementations on `usize`, we can >> leverage the generic accessors and reduce the number of unsafe blocks in >> the module. >> >> This also allows us to directly call the generic `read/write/update` >> methods with primitive types, so add examples illustrating this. >> >> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >> --- >> rust/kernel/io.rs | 199 +++++++++++++++++++++++++++++++++++------------------- >> 1 file changed, 131 insertions(+), 68 deletions(-) >> >> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs >> index 1db6572f4a42..ed6fab001a39 100644 >> --- a/rust/kernel/io.rs >> +++ b/rust/kernel/io.rs >> @@ -197,6 +197,25 @@ pub trait IoLoc<T> { >> fn offset(&self) -> usize; >> } >> >> +/// Implements [`IoLoc<$ty>`] for [`usize`], allowing to use `usize` as a parameter of >> +/// [`Io::read`] and [`Io::write`]. >> +macro_rules! impl_usize_ioloc { >> + ($($ty:ty),*) => { >> + $( >> + impl IoLoc<$ty> for usize { >> + type IoType = $ty; >> + > > #[inline(always)] > > the fact that this is a pointer is somewhat uneasy to me. I wonder if Clippy > with its inlining tweak would cause optimisation failure here. > > Could this be just `fn offset(self)`? Yes, this was a remnant from a previous design where I needed to either make `IoLoc` require `Copy` or use a reference here, so I opted for the latter. With the current code though we are using `IoLoc`s exactly one time, so we can consume `self` without requiring `Copy`. ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses 2026-03-10 1:57 ` Alexandre Courbot @ 2026-03-10 1:59 ` Alexandre Courbot 2026-03-10 2:04 ` Gary Guo 0 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-10 1:59 UTC (permalink / raw) To: Gary Guo Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue Mar 10, 2026 at 10:57 AM JST, Alexandre Courbot wrote: > On Tue Mar 10, 2026 at 12:29 AM JST, Gary Guo wrote: >> On Mon Mar 9, 2026 at 3:14 PM GMT, Alexandre Courbot wrote: >>> By providing the required `IoLoc` implementations on `usize`, we can >>> leverage the generic accessors and reduce the number of unsafe blocks in >>> the module. >>> >>> This also allows us to directly call the generic `read/write/update` >>> methods with primitive types, so add examples illustrating this. >>> >>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >>> --- >>> rust/kernel/io.rs | 199 +++++++++++++++++++++++++++++++++++------------------- >>> 1 file changed, 131 insertions(+), 68 deletions(-) >>> >>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs >>> index 1db6572f4a42..ed6fab001a39 100644 >>> --- a/rust/kernel/io.rs >>> +++ b/rust/kernel/io.rs >>> @@ -197,6 +197,25 @@ pub trait IoLoc<T> { >>> fn offset(&self) -> usize; >>> } >>> >>> +/// Implements [`IoLoc<$ty>`] for [`usize`], allowing to use `usize` as a parameter of >>> +/// [`Io::read`] and [`Io::write`]. >>> +macro_rules! impl_usize_ioloc { >>> + ($($ty:ty),*) => { >>> + $( >>> + impl IoLoc<$ty> for usize { >>> + type IoType = $ty; >>> + >> >> #[inline(always)] >> >> the fact that this is a pointer is somewhat uneasy to me. I wonder if Clippy >> with its inlining tweak would cause optimisation failure here. >> >> Could this be just `fn offset(self)`? > > Yes, this was a remnant from a previous design where I needed to either > make `IoLoc` require `Copy` or use a reference here, so I opted for the > latter. > > With the current code though we are using `IoLoc`s exactly one time, so > we can consume `self` without requiring `Copy`. I also guess this method should be renamed `into_offset` then? ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses 2026-03-10 1:59 ` Alexandre Courbot @ 2026-03-10 2:04 ` Gary Guo 0 siblings, 0 replies; 57+ messages in thread From: Gary Guo @ 2026-03-10 2:04 UTC (permalink / raw) To: Alexandre Courbot, Gary Guo Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue Mar 10, 2026 at 1:59 AM GMT, Alexandre Courbot wrote: > On Tue Mar 10, 2026 at 10:57 AM JST, Alexandre Courbot wrote: >> On Tue Mar 10, 2026 at 12:29 AM JST, Gary Guo wrote: >>> On Mon Mar 9, 2026 at 3:14 PM GMT, Alexandre Courbot wrote: >>>> By providing the required `IoLoc` implementations on `usize`, we can >>>> leverage the generic accessors and reduce the number of unsafe blocks in >>>> the module. >>>> >>>> This also allows us to directly call the generic `read/write/update` >>>> methods with primitive types, so add examples illustrating this. >>>> >>>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >>>> --- >>>> rust/kernel/io.rs | 199 +++++++++++++++++++++++++++++++++++------------------- >>>> 1 file changed, 131 insertions(+), 68 deletions(-) >>>> >>>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs >>>> index 1db6572f4a42..ed6fab001a39 100644 >>>> --- a/rust/kernel/io.rs >>>> +++ b/rust/kernel/io.rs >>>> @@ -197,6 +197,25 @@ pub trait IoLoc<T> { >>>> fn offset(&self) -> usize; >>>> } >>>> >>>> +/// Implements [`IoLoc<$ty>`] for [`usize`], allowing to use `usize` as a parameter of >>>> +/// [`Io::read`] and [`Io::write`]. >>>> +macro_rules! impl_usize_ioloc { >>>> + ($($ty:ty),*) => { >>>> + $( >>>> + impl IoLoc<$ty> for usize { >>>> + type IoType = $ty; >>>> + >>> >>> #[inline(always)] >>> >>> the fact that this is a pointer is somewhat uneasy to me. I wonder if Clippy >>> with its inlining tweak would cause optimisation failure here. >>> >>> Could this be just `fn offset(self)`? >> >> Yes, this was a remnant from a previous design where I needed to either >> make `IoLoc` require `Copy` or use a reference here, so I opted for the >> latter. >> >> With the current code though we are using `IoLoc`s exactly one time, so >> we can consume `self` without requiring `Copy`. > > I also guess this method should be renamed `into_offset` then? Calling this `offset` is fine, given that this is a trait method and is for I/O plumbing and not really conversion. But I'm fine with `into_offset` too. Also, most of the `IoLoc` types would be `Copy` anyway (even if it's not required as a bound), neither makes a difference. Best, Gary ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses 2026-03-09 15:14 ` [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot 2026-03-09 15:29 ` Gary Guo @ 2026-03-10 15:17 ` Danilo Krummrich 1 sibling, 0 replies; 57+ messages in thread From: Danilo Krummrich @ 2026-03-10 15:17 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 4:14 PM CET, Alexandre Courbot wrote: > /// Generic fallible read with runtime bounds check. > + /// > + /// # Examples > + /// > + /// Read a primitive type from an I/O address: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; NIT: Please use kernel import style, here and in various other places (including other patches). > @@ -514,6 +563,20 @@ fn write<T, L>(&self, location: L, value: T) > /// > /// Caution: this does not perform any synchronization. Race conditions can occur in case of > /// concurrent access. Same as in the previous patch. ^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (5 preceding siblings ...) 2026-03-09 15:14 ` [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot @ 2026-03-09 15:14 ` Alexandre Courbot 2026-03-09 15:30 ` Gary Guo 2026-03-10 16:34 ` Danilo Krummrich 2026-03-09 15:14 ` [PATCH v8 08/10] rust: io: add `register!` macro Alexandre Courbot ` (5 subsequent siblings) 12 siblings, 2 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:14 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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 Some I/O types, like fixed address registers, carry their location alongside their values. For these types, the regular `Io::write` method can lead into repeating the location information twice: once to provide the location itself, another time to build the value. Add a new `Io::write_val` convenience method that takes a single argument implementing `IntoIoVal`, a trait that decomposes implementors into a `(location, value)` tuple. This allows write operations on fixed offset registers to be done while specifying their name only once. Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/io.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index ed6fab001a39..09a0fe06f201 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -216,6 +216,22 @@ fn offset(&self) -> usize { // Provide the ability to read any primitive type from a [`usize`]. impl_usize_ioloc!(u8, u16, u32, u64); +/// Trait implemented by items that contain both an I/O location and a value to assign to it. +/// +/// Implementors can be used with [`Io::write_val`]. +pub trait IntoIoVal<T, L: IoLoc<T>> { + /// Consumes `self` and returns a `(location, value)` tuple describing a valid I/O write + /// operation. + fn into_io_val(self) -> (L, T); +} + +/// `(location, value)` tuples can be used as [`IntoIoVal`]s. +impl<T, L: IoLoc<T>> IntoIoVal<T, L> for (L, T) { + fn into_io_val(self) -> (L, T) { + self + } +} + /// Types implementing this trait (e.g. MMIO BARs or PCI config regions) /// can perform I/O operations on regions of memory. /// @@ -463,6 +479,32 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result Ok(()) } + /// Generic fallible write of value bearing its location, with runtime bounds check. + /// + /// # Examples + /// + /// Tuples carrying a location and a value can be used with this method: + /// + /// ```no_run + /// use kernel::io::{Io, Mmio}; + /// + /// fn do_writes(io: &Mmio) -> Result { + /// // 32-bit write of value `1` at address `0x10`. + /// io.try_write_val((0x10, 1u32)) + /// } + /// ``` + #[inline(always)] + fn try_write_val<T, L, V>(&self, value: V) -> Result + where + L: IoLoc<T>, + V: IntoIoVal<T, L>, + Self: IoCapable<L::IoType>, + { + let (location, value) = value.into_io_val(); + + self.try_write(location, value) + } + /// Generic fallible update with runtime bounds check. /// /// Caution: this does not perform any synchronization. Race conditions can occur in case of @@ -559,6 +601,32 @@ fn write<T, L>(&self, location: L, value: T) unsafe { self.io_write(io_value, address) } } + /// Generic infallible write of value bearing its location, with compile-time bounds check. + /// + /// # Examples + /// + /// Tuples carrying a location and a value can be used with this method: + /// + /// ```no_run + /// use kernel::io::{Io, Mmio}; + /// + /// fn do_writes(io: &Mmio) { + /// // 32-bit write of value `1` at address `0x10`. + /// io.write_val((0x10, 1u32)); + /// } + /// ``` + #[inline(always)] + fn write_val<T, L, V>(&self, value: V) + where + L: IoLoc<T>, + V: IntoIoVal<T, L>, + Self: IoKnownSize + IoCapable<L::IoType>, + { + let (location, value) = value.into_io_val(); + + self.write(location, value) + } + /// Generic infallible update with compile-time bounds check. /// /// Caution: this does not perform any synchronization. Race conditions can occur in case of -- 2.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-09 15:14 ` [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` Alexandre Courbot @ 2026-03-09 15:30 ` Gary Guo 2026-03-10 2:05 ` Alexandre Courbot 2026-03-10 16:34 ` Danilo Krummrich 1 sibling, 1 reply; 57+ messages in thread From: Gary Guo @ 2026-03-09 15:30 UTC (permalink / raw) To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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 Mon Mar 9, 2026 at 3:14 PM GMT, Alexandre Courbot wrote: > Some I/O types, like fixed address registers, carry their location > alongside their values. For these types, the regular `Io::write` method > can lead into repeating the location information twice: once to provide > the location itself, another time to build the value. > > Add a new `Io::write_val` convenience method that takes a single > argument implementing `IntoIoVal`, a trait that decomposes implementors > into a `(location, value)` tuple. This allows write operations on fixed > offset registers to be done while specifying their name only once. > > Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> > --- > rust/kernel/io.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 68 insertions(+) > > diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs > index ed6fab001a39..09a0fe06f201 100644 > --- a/rust/kernel/io.rs > +++ b/rust/kernel/io.rs > @@ -216,6 +216,22 @@ fn offset(&self) -> usize { > // Provide the ability to read any primitive type from a [`usize`]. > impl_usize_ioloc!(u8, u16, u32, u64); > > +/// Trait implemented by items that contain both an I/O location and a value to assign to it. > +/// > +/// Implementors can be used with [`Io::write_val`]. > +pub trait IntoIoVal<T, L: IoLoc<T>> { Is this generics instead of associative types for some reason? If so, please mention it in the commit mesasge. > + /// Consumes `self` and returns a `(location, value)` tuple describing a valid I/O write > + /// operation. > + fn into_io_val(self) -> (L, T); > +} > + > +/// `(location, value)` tuples can be used as [`IntoIoVal`]s. > +impl<T, L: IoLoc<T>> IntoIoVal<T, L> for (L, T) { Missing inline annotations. Best, Gary > + fn into_io_val(self) -> (L, T) { > + self > + } > +} > + > /// Types implementing this trait (e.g. MMIO BARs or PCI config regions) > /// can perform I/O operations on regions of memory. > /// > @@ -463,6 +479,32 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result > Ok(()) > } > > + /// Generic fallible write of value bearing its location, with runtime bounds check. > + /// > + /// # Examples > + /// > + /// Tuples carrying a location and a value can be used with this method: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_writes(io: &Mmio) -> Result { > + /// // 32-bit write of value `1` at address `0x10`. > + /// io.try_write_val((0x10, 1u32)) > + /// } > + /// ``` > + #[inline(always)] > + fn try_write_val<T, L, V>(&self, value: V) -> Result > + where > + L: IoLoc<T>, > + V: IntoIoVal<T, L>, > + Self: IoCapable<L::IoType>, > + { > + let (location, value) = value.into_io_val(); > + > + self.try_write(location, value) > + } > + > /// Generic fallible update with runtime bounds check. > /// > /// Caution: this does not perform any synchronization. Race conditions can occur in case of > @@ -559,6 +601,32 @@ fn write<T, L>(&self, location: L, value: T) > unsafe { self.io_write(io_value, address) } > } > > + /// Generic infallible write of value bearing its location, with compile-time bounds check. > + /// > + /// # Examples > + /// > + /// Tuples carrying a location and a value can be used with this method: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_writes(io: &Mmio) { > + /// // 32-bit write of value `1` at address `0x10`. > + /// io.write_val((0x10, 1u32)); > + /// } > + /// ``` > + #[inline(always)] > + fn write_val<T, L, V>(&self, value: V) > + where > + L: IoLoc<T>, > + V: IntoIoVal<T, L>, > + Self: IoKnownSize + IoCapable<L::IoType>, > + { > + let (location, value) = value.into_io_val(); > + > + self.write(location, value) > + } > + > /// Generic infallible update with compile-time bounds check. > /// > /// Caution: this does not perform any synchronization. Race conditions can occur in case of ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-09 15:30 ` Gary Guo @ 2026-03-10 2:05 ` Alexandre Courbot 0 siblings, 0 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-10 2:05 UTC (permalink / raw) To: Gary Guo Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue Mar 10, 2026 at 12:30 AM JST, Gary Guo wrote: > On Mon Mar 9, 2026 at 3:14 PM GMT, Alexandre Courbot wrote: >> Some I/O types, like fixed address registers, carry their location >> alongside their values. For these types, the regular `Io::write` method >> can lead into repeating the location information twice: once to provide >> the location itself, another time to build the value. >> >> Add a new `Io::write_val` convenience method that takes a single >> argument implementing `IntoIoVal`, a trait that decomposes implementors >> into a `(location, value)` tuple. This allows write operations on fixed >> offset registers to be done while specifying their name only once. >> >> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> >> --- >> rust/kernel/io.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ >> 1 file changed, 68 insertions(+) >> >> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs >> index ed6fab001a39..09a0fe06f201 100644 >> --- a/rust/kernel/io.rs >> +++ b/rust/kernel/io.rs >> @@ -216,6 +216,22 @@ fn offset(&self) -> usize { >> // Provide the ability to read any primitive type from a [`usize`]. >> impl_usize_ioloc!(u8, u16, u32, u64); >> >> +/// Trait implemented by items that contain both an I/O location and a value to assign to it. >> +/// >> +/// Implementors can be used with [`Io::write_val`]. >> +pub trait IntoIoVal<T, L: IoLoc<T>> { > > Is this generics instead of associative types for some reason? If so, please > mention it in the commit mesasge. Associated types sound better in that situation, I don't think we can have several implementors with different returned types for the same value anyway. > >> + /// Consumes `self` and returns a `(location, value)` tuple describing a valid I/O write >> + /// operation. >> + fn into_io_val(self) -> (L, T); >> +} >> + >> +/// `(location, value)` tuples can be used as [`IntoIoVal`]s. >> +impl<T, L: IoLoc<T>> IntoIoVal<T, L> for (L, T) { > > Missing inline annotations. Fixed, thanks! (and elsewhere in this series as well) ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-09 15:14 ` [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` Alexandre Courbot 2026-03-09 15:30 ` Gary Guo @ 2026-03-10 16:34 ` Danilo Krummrich 2026-03-10 22:33 ` Gary Guo 2026-03-11 13:28 ` Alexandre Courbot 1 sibling, 2 replies; 57+ messages in thread From: Danilo Krummrich @ 2026-03-10 16:34 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 4:14 PM CET, Alexandre Courbot wrote: > + /// Generic infallible write of value bearing its location, with compile-time bounds check. > + /// > + /// # Examples > + /// > + /// Tuples carrying a location and a value can be used with this method: > + /// > + /// ```no_run > + /// use kernel::io::{Io, Mmio}; > + /// > + /// fn do_writes(io: &Mmio) { > + /// // 32-bit write of value `1` at address `0x10`. > + /// io.write_val((0x10, 1u32)); > + /// } > + /// ``` > + #[inline(always)] > + fn write_val<T, L, V>(&self, value: V) Still not super satisfied with the name write_val() plus I have some more considerations, sorry. :) Let's look at this: let reg = bar.read(regs::NV_PMC_BOOT_0); // Pass around, do some modifications, etc. bar.write_val(reg); In this case read() returns an object that encodes the absolute location and value, i.e. read() pairs with write_val(), which is a bit odd. In this example: let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); // Pass around, do some modifications, etc. bar.write(WithBase::of::<E>(), reg); read() pairs with write(), since the object returned by read() does only encode a relative location, so we have to supply the base location through the first argument of write(). Well, technically it also pairs with write_val(), as we could also pass this as tuple to write_val(). bar.write_val((WithBase::of::<E>(), reg)); However, my point is that this is a bit inconsistent and I think a user would expect something more symmetric. For instance, let's say write() would become the single argument one, then read() could return an impl of IntoIoVal, so we would have let reg = bar.read(regs::NV_PMC_BOOT_0); bar.write(reg); let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); bar.write(reg); and additionally we can have let val = bar.read_val(regs::NV_PFALCON_FALCON_RM::of::<E>()); bar.write_val(WithBase::of::<E>(), val); The same goes for let val = bar.read_val(regs::NV_PMC_BOOT_0); bar.write_val(regs::NV_PMC_BOOT_0, val); which for complex values does not achieve anything, but makes sense for the FIFO register use-case, as val could also be a primitive, e.g. u32. With a dynamic base, and a single field we could just do the following to support such primitives: let val = bar.read_val(regs::NV_PFALCON_FALCON_RM::of::<E>()); bar.write_val(regs::NV_PFALCON_FALCON_RM::of::<E>(), val); I think adding this symmetry should be trivial, as most of this already compiles with the current code. Also, let's not get into a discussion whether we name on or the other write() vs. write_foo(). I just turned it around as I think with the specific name write_val() it makes more sense this way around. But I'm perfectly happy either way as long as we find descriptive names that actually make sense. Unfortunately, I can't really think of a good name for write_val() with its current semantics. If we flip it around as in my examples above, the _val() prefix seems fine. Maybe write_reg() and read_reg()? > + where > + L: IoLoc<T>, > + V: IntoIoVal<T, L>, > + Self: IoKnownSize + IoCapable<L::IoType>, > + { > + let (location, value) = value.into_io_val(); > + > + self.write(location, value) > + } ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-10 16:34 ` Danilo Krummrich @ 2026-03-10 22:33 ` Gary Guo 2026-03-11 13:25 ` Danilo Krummrich 2026-03-11 13:28 ` Alexandre Courbot 1 sibling, 1 reply; 57+ messages in thread From: Gary Guo @ 2026-03-10 22:33 UTC (permalink / raw) To: Danilo Krummrich, Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue Mar 10, 2026 at 4:34 PM GMT, Danilo Krummrich wrote: > On Mon Mar 9, 2026 at 4:14 PM CET, Alexandre Courbot wrote: >> + /// Generic infallible write of value bearing its location, with compile-time bounds check. >> + /// >> + /// # Examples >> + /// >> + /// Tuples carrying a location and a value can be used with this method: >> + /// >> + /// ```no_run >> + /// use kernel::io::{Io, Mmio}; >> + /// >> + /// fn do_writes(io: &Mmio) { >> + /// // 32-bit write of value `1` at address `0x10`. >> + /// io.write_val((0x10, 1u32)); >> + /// } >> + /// ``` >> + #[inline(always)] >> + fn write_val<T, L, V>(&self, value: V) > > Still not super satisfied with the name write_val() plus I have some more > considerations, sorry. :) > > Let's look at this: > > let reg = bar.read(regs::NV_PMC_BOOT_0); > > // Pass around, do some modifications, etc. > > bar.write_val(reg); > > In this case read() returns an object that encodes the absolute location and > value, i.e. read() pairs with write_val(), which is a bit odd. I mean, it also pairs with `bar.write(regs::NV_PMC_BOOT_0, reg)`, so it's not totally odd. We just need to teach that "write_val" infers the register location from value. > > In this example: > > let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); > > // Pass around, do some modifications, etc. > > bar.write(WithBase::of::<E>(), reg); > > read() pairs with write(), since the object returned by read() does only encode > a relative location, so we have to supply the base location through the first > argument of write(). > > Well, technically it also pairs with write_val(), as we could also pass this as > tuple to write_val(). > > bar.write_val((WithBase::of::<E>(), reg)); > > However, my point is that this is a bit inconsistent and I think a user would > expect something more symmetric. > > For instance, let's say write() would become the single argument one, then > read() could return an impl of IntoIoVal, so we would have > > let reg = bar.read(regs::NV_PMC_BOOT_0); > bar.write(reg); > > let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); > bar.write(reg); How would you modify the value then? `reg` becomes a combined pair with both location and value, and we would start need to use a closure to mutate inner values again, which in my opinion is bad. This would again become the `IoWrite` design of the last iteration which is the part that I disliked most. > > and additionally we can have > > let val = bar.read_val(regs::NV_PFALCON_FALCON_RM::of::<E>()); > bar.write_val(WithBase::of::<E>(), val); > > The same goes for > > let val = bar.read_val(regs::NV_PMC_BOOT_0); > bar.write_val(regs::NV_PMC_BOOT_0, val); > > which for complex values does not achieve anything, but makes sense for the FIFO > register use-case, as val could also be a primitive, e.g. u32. > > With a dynamic base, and a single field we could just do the following to > support such primitives: > > let val = bar.read_val(regs::NV_PFALCON_FALCON_RM::of::<E>()); > bar.write_val(regs::NV_PFALCON_FALCON_RM::of::<E>(), val); > > I think adding this symmetry should be trivial, as most of this already compiles > with the current code. > > Also, let's not get into a discussion whether we name on or the other write() > vs. write_foo(). I just turned it around as I think with the specific name > write_val() it makes more sense this way around. To me flipping this around is the odd one. If this is flipped around then this should be named `write_at`, but then it becomes odd as it is the dual of `read` and we won't have `read_at`. Best, Gary > > But I'm perfectly happy either way as long as we find descriptive names that > actually make sense. > > Unfortunately, I can't really think of a good name for write_val() with its > current semantics. If we flip it around as in my examples above, the _val() > prefix seems fine. Maybe write_reg() and read_reg()? > >> + where >> + L: IoLoc<T>, >> + V: IntoIoVal<T, L>, >> + Self: IoKnownSize + IoCapable<L::IoType>, >> + { >> + let (location, value) = value.into_io_val(); >> + >> + self.write(location, value) >> + } ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-10 22:33 ` Gary Guo @ 2026-03-11 13:25 ` Danilo Krummrich 2026-03-11 13:42 ` Alexandre Courbot 0 siblings, 1 reply; 57+ messages in thread From: Danilo Krummrich @ 2026-03-11 13:25 UTC (permalink / raw) To: Gary Guo Cc: Alexandre Courbot, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue Mar 10, 2026 at 11:33 PM CET, Gary Guo wrote: > On Tue Mar 10, 2026 at 4:34 PM GMT, Danilo Krummrich wrote: >> On Mon Mar 9, 2026 at 4:14 PM CET, Alexandre Courbot wrote: >>> + /// Generic infallible write of value bearing its location, with compile-time bounds check. >>> + /// >>> + /// # Examples >>> + /// >>> + /// Tuples carrying a location and a value can be used with this method: >>> + /// >>> + /// ```no_run >>> + /// use kernel::io::{Io, Mmio}; >>> + /// >>> + /// fn do_writes(io: &Mmio) { >>> + /// // 32-bit write of value `1` at address `0x10`. >>> + /// io.write_val((0x10, 1u32)); >>> + /// } >>> + /// ``` >>> + #[inline(always)] >>> + fn write_val<T, L, V>(&self, value: V) >> >> Still not super satisfied with the name write_val() plus I have some more >> considerations, sorry. :) >> >> Let's look at this: >> >> let reg = bar.read(regs::NV_PMC_BOOT_0); >> >> // Pass around, do some modifications, etc. >> >> bar.write_val(reg); >> >> In this case read() returns an object that encodes the absolute location and >> value, i.e. read() pairs with write_val(), which is a bit odd. > > I mean, it also pairs with `bar.write(regs::NV_PMC_BOOT_0, reg)`, so it's not s/also pairs with/also works with/ > totally odd. We just need to teach that "write_val" infers the register location > from value. > >> >> In this example: >> >> let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); >> >> // Pass around, do some modifications, etc. >> >> bar.write(WithBase::of::<E>(), reg); >> >> read() pairs with write(), since the object returned by read() does only encode >> a relative location, so we have to supply the base location through the first >> argument of write(). >> >> Well, technically it also pairs with write_val(), as we could also pass this as >> tuple to write_val(). >> >> bar.write_val((WithBase::of::<E>(), reg)); >> >> However, my point is that this is a bit inconsistent and I think a user would >> expect something more symmetric. >> >> For instance, let's say write() would become the single argument one, then >> read() could return an impl of IntoIoVal, so we would have >> >> let reg = bar.read(regs::NV_PMC_BOOT_0); >> bar.write(reg); >> >> let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); >> bar.write(reg); > > How would you modify the value then? `reg` becomes a combined pair with both > location and value, and we would start need to use a closure to mutate inner > values again, which in my opinion is bad. IIUC, the return value could be a generic tuple struct with <L: IoLoc<T>, V: IntoIoVal<T, L>> that dereferences to V? I.e. the current write_val() already allows this: bar.write_val((WithBase::of::<E>(), reg)) so, a corresponding read_val() - both write_val() and read_val() should really have different names - could return the same thing, just as a generic type that dereferences to self.1. With this you could do let reg = bar.read_reg(regs::NV_PFALCON_FALCON_RM::of::<E>()); reg.modify(); bar.write_reg(reg); without repeating the of::<E>() part while let val = regs::NV_PFALCON_FALCON_RM::zeroed(); // Set a couple of fields. bar.write(WithBase::of::<E>(), val); is still possible. > This would again become the `IoWrite` design of the last iteration which is the > part that I disliked most. > >> >> and additionally we can have >> >> let val = bar.read_val(regs::NV_PFALCON_FALCON_RM::of::<E>()); >> bar.write_val(WithBase::of::<E>(), val); >> >> The same goes for >> >> let val = bar.read_val(regs::NV_PMC_BOOT_0); >> bar.write_val(regs::NV_PMC_BOOT_0, val); >> >> which for complex values does not achieve anything, but makes sense for the FIFO >> register use-case, as val could also be a primitive, e.g. u32. >> >> With a dynamic base, and a single field we could just do the following to >> support such primitives: >> >> let val = bar.read_val(regs::NV_PFALCON_FALCON_RM::of::<E>()); >> bar.write_val(regs::NV_PFALCON_FALCON_RM::of::<E>(), val); >> >> I think adding this symmetry should be trivial, as most of this already compiles >> with the current code. >> >> Also, let's not get into a discussion whether we name on or the other write() >> vs. write_foo(). I just turned it around as I think with the specific name >> write_val() it makes more sense this way around. > > To me flipping this around is the odd one. If this is flipped around then this > should be named `write_at`, but then it becomes odd as it is the dual of `read` > and we won't have `read_at`. That's why I said I only flipped it because write_val() is an odd name for what it does currently. Please also note the below. >> But I'm perfectly happy either way as long as we find descriptive names that >> actually make sense. >> >> Unfortunately, I can't really think of a good name for write_val() with its >> current semantics. If we flip it around as in my examples above, the _val() >> prefix seems fine. Maybe write_reg() and read_reg()? >> >>> + where >>> + L: IoLoc<T>, >>> + V: IntoIoVal<T, L>, >>> + Self: IoKnownSize + IoCapable<L::IoType>, >>> + { >>> + let (location, value) = value.into_io_val(); >>> + >>> + self.write(location, value) >>> + } ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-11 13:25 ` Danilo Krummrich @ 2026-03-11 13:42 ` Alexandre Courbot 0 siblings, 0 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-11 13:42 UTC (permalink / raw) To: Danilo Krummrich Cc: Gary Guo, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 10:25 PM JST, Danilo Krummrich wrote: <snip> > IIUC, the return value could be a generic tuple struct with <L: IoLoc<T>, V: > IntoIoVal<T, L>> that dereferences to V? > > I.e. the current write_val() already allows this: > > bar.write_val((WithBase::of::<E>(), reg)) > > so, a corresponding read_val() - both write_val() and read_val() should really > have different names - could return the same thing, just as a generic type that > dereferences to self.1. > > With this you could do > > let reg = bar.read_reg(regs::NV_PFALCON_FALCON_RM::of::<E>()); > > reg.modify(); That's the tricky part - the register methods don't modify in-place, they return a new value. I don't see how we could avoid a closure (and mutable variables) here. ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-10 16:34 ` Danilo Krummrich 2026-03-10 22:33 ` Gary Guo @ 2026-03-11 13:28 ` Alexandre Courbot 2026-03-11 14:56 ` Danilo Krummrich 1 sibling, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-11 13:28 UTC (permalink / raw) To: Danilo Krummrich Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 1:34 AM JST, Danilo Krummrich wrote: > On Mon Mar 9, 2026 at 4:14 PM CET, Alexandre Courbot wrote: >> + /// Generic infallible write of value bearing its location, with compile-time bounds check. >> + /// >> + /// # Examples >> + /// >> + /// Tuples carrying a location and a value can be used with this method: >> + /// >> + /// ```no_run >> + /// use kernel::io::{Io, Mmio}; >> + /// >> + /// fn do_writes(io: &Mmio) { >> + /// // 32-bit write of value `1` at address `0x10`. >> + /// io.write_val((0x10, 1u32)); >> + /// } >> + /// ``` >> + #[inline(always)] >> + fn write_val<T, L, V>(&self, value: V) > > Still not super satisfied with the name write_val() plus I have some more > considerations, sorry. :) > > Let's look at this: > > let reg = bar.read(regs::NV_PMC_BOOT_0); > > // Pass around, do some modifications, etc. > > bar.write_val(reg); > > In this case read() returns an object that encodes the absolute location and > value, i.e. read() pairs with write_val(), which is a bit odd. > > In this example: > > let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); > > // Pass around, do some modifications, etc. > > bar.write(WithBase::of::<E>(), reg); > > read() pairs with write(), since the object returned by read() does only encode > a relative location, so we have to supply the base location through the first > argument of write(). > > Well, technically it also pairs with write_val(), as we could also pass this as > tuple to write_val(). > > bar.write_val((WithBase::of::<E>(), reg)); > > However, my point is that this is a bit inconsistent and I think a user would > expect something more symmetric. > > For instance, let's say write() would become the single argument one, then > read() could return an impl of IntoIoVal, so we would have > > let reg = bar.read(regs::NV_PMC_BOOT_0); > bar.write(reg); > > let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); > bar.write(reg); > > and additionally we can have > > let val = bar.read_val(regs::NV_PFALCON_FALCON_RM::of::<E>()); > bar.write_val(WithBase::of::<E>(), val); > > The same goes for > > let val = bar.read_val(regs::NV_PMC_BOOT_0); > bar.write_val(regs::NV_PMC_BOOT_0, val); > > which for complex values does not achieve anything, but makes sense for the FIFO > register use-case, as val could also be a primitive, e.g. u32. > > With a dynamic base, and a single field we could just do the following to > support such primitives: > > let val = bar.read_val(regs::NV_PFALCON_FALCON_RM::of::<E>()); > bar.write_val(regs::NV_PFALCON_FALCON_RM::of::<E>(), val); > > I think adding this symmetry should be trivial, as most of this already compiles > with the current code. Not quite, if I understood your idea correctly. The new `read` would need to return some new type (except for fixed offset registers) that contains the corresponding `IoLoc`, creating overhead for the default I/O methods (so at the very least, I think they should be the ones with the suffix). Accessing the value of that new type would require some indirection, and probably the return of closures. And having two pairs of read/write methods that work eerily similarly but are marginally distinct sounds to me like it could itself become a source of confusion. > > Also, let's not get into a discussion whether we name on or the other write() > vs. write_foo(). I just turned it around as I think with the specific name > write_val() it makes more sense this way around. > > But I'm perfectly happy either way as long as we find descriptive names that > actually make sense. > > Unfortunately, I can't really think of a good name for write_val() with its > current semantics. If we flip it around as in my examples above, the _val() > prefix seems fine. Maybe write_reg() and read_reg()? The fact is, there is a symmetry between `read` and `write`: - `read` takes a location and returns a value, - `write` takes a location and a value. `write_val` is really nothing but a convenience shortcut that has technically no strong reason to exist. As Gary pointed out, the counterpart of let reg = bar.read(regs::NV_PMC_BOOT_0); is bar.write(regs::NV_PMC_BOOT_0, reg); ... and we introduced `write_val` for those cases where the value in itself provides its location, as `NV_PMC_BOOT_0` is redundant. But the above statement could also be written: bar.write((), reg); which is exactly the same length as the `write_val` equivalent - it's just that you need to remember that `()` can be used in this case. But if you can remember that your register type can be used with `write_val`, then why not this? This actually makes me doubt that `write_val` is needed at all, and if we get rid of it, then we have a symmetric API. We were so focused on this single issue for the last few revisions that the single-argument write variant sounded like the only way to handle this properly, but the `()` use proposed by Gary actually fulfills the same role and doesn't introduce more burden when you think of it. So why not try without `write_val` at first? We can always add it later if we feel the need (and the same applies to a `(location, value)` symmetric read/write API). And most importantly, that way we also don't have to worry about its name. :) ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-11 13:28 ` Alexandre Courbot @ 2026-03-11 14:56 ` Danilo Krummrich 2026-03-11 15:42 ` Gary Guo 2026-03-12 13:53 ` Alexandre Courbot 0 siblings, 2 replies; 57+ messages in thread From: Danilo Krummrich @ 2026-03-11 14:56 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 2:28 PM CET, Alexandre Courbot wrote: > The fact is, there is a symmetry between `read` and `write`: > > - `read` takes a location and returns a value, > - `write` takes a location and a value. This is not entirely true. read() takes an absolute location and returns something that encodes the value and a relative location, where for fixed registers the relative location is equivalent to an absolute location. write() works with both an absolute location and something that encodes the value and a relative location, or a base location and something that encodes the value and a relative location. // `reg` encodes the value and an absolute location. let reg = bar.read(regs::NV_PMC_BOOT_0); // `reg` encodes the value and a relative location. let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); // First argument is an absolute location, second argument is a type // that encodes a value and a relative location. bar.write(regs::NV_PFALCON_FALCON_RM::of::<E>(), reg); // First argument is a base location that infers the relative location // from `reg`. bar.write(WithBase::of::<E>(), reg); And yes, I am aware that the above wording around the difference between regs::NV_PFALCON_FALCON_RM::of::<E>() and WithBase::of::<E>() is at least a bit vague technically, but my point is about how this appears to users. In any case, the fact that you can write WithBase::of::<E>() as a location argument in the first place proves that `reg` is not *only* a value. fn write<T, L>(&self, location: L, value: T) where L: IoLoc<T>, Self: IoKnownSize + IoCapable<L::IoType>, Which is the reason why L: IoLoc<T>, i.e. the relative location is on T, not on L. So, what you could say is - read() takes an absolute location and returns something that encodes a value and relative location - write() takes a base location (or absolute location) and something that encodes a value and relative location But then there is the case with fixed registers that simply do not have a base, but write() still asks for a base (or absolute) location. > `write_val` is really nothing but a convenience shortcut that has > technically no strong reason to exist. As Gary pointed out, the > counterpart of > > let reg = bar.read(regs::NV_PMC_BOOT_0); > > is > > bar.write(regs::NV_PMC_BOOT_0, reg); I do not agree for the reasons mentioned above. In this case read() returns (location + value), while write() asks for location *and* (location + value), i.e. there is no base location for fixed registers. > ... and we introduced `write_val` for those cases where the value > in itself provides its location, as `NV_PMC_BOOT_0` is redundant. But > the above statement could also be written: > > bar.write((), reg); This makes more sense, as one could argue that a fixed register still requires a base location, which is just always zero, but why would we bother users with this implementation detail? Is this really less confusing than an additional bar.write_reg(reg) that just works with any register? > which is exactly the same length as the `write_val` equivalent - it's > just that you need to remember that `()` can be used in this case. But > if you can remember that your register type can be used with > `write_val`, then why not this? This actually makes me doubt that > `write_val` is needed at all, and if we get rid of it, then we have a > symmetric API. Still not symmetric, and I also don't think we will have a lot of fun explaining people why they have to call it as bar.write((), reg). :( OOC, how would you explain it when the question is raised without arguing with implementation details? > We were so focused on this single issue for the last few revisions that > the single-argument write variant sounded like the only way to handle > this properly, but the `()` use proposed by Gary actually fulfills the > same role and doesn't introduce more burden when you think of it. > > So why not try without `write_val` at first? We can always add it later > if we feel the need (and the same applies to a `(location, value)` > symmetric read/write API). If you really think it's the best solution, I'm fine picking it up this way for now, but to me it still sounds like we have no solution for a very simple case that does not at least raise an eyebrow. > And most importantly, that way we also don't have to worry about its > name. :) ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-11 14:56 ` Danilo Krummrich @ 2026-03-11 15:42 ` Gary Guo 2026-03-11 16:22 ` Danilo Krummrich 2026-03-12 13:53 ` Alexandre Courbot 1 sibling, 1 reply; 57+ messages in thread From: Gary Guo @ 2026-03-11 15:42 UTC (permalink / raw) To: Danilo Krummrich, Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 2:56 PM GMT, Danilo Krummrich wrote: > On Wed Mar 11, 2026 at 2:28 PM CET, Alexandre Courbot wrote: >> The fact is, there is a symmetry between `read` and `write`: >> >> - `read` takes a location and returns a value, >> - `write` takes a location and a value. > > This is not entirely true. > > read() takes an absolute location and returns something that encodes the value > and a relative location, where for fixed registers the relative location is > equivalent to an absolute location. The value returned by `read` does not encode relative location. If a primitive is returned (of course, this is not currently supported by the `register!` macro) for the FIFO register case, the returned integer has no location informaiton whatsoever. What encodes the relative location is the type itself. If you just construct the value yourself without using `read`, you can also use `write_val` or `bar.write(WithBase...)`. > > write() works with both an absolute location and something that encodes the > value and a relative location, or a base location and something that encodes the > value and a relative location. > > // `reg` encodes the value and an absolute location. > let reg = bar.read(regs::NV_PMC_BOOT_0); > > // `reg` encodes the value and a relative location. > let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); > > // First argument is an absolute location, second argument is a type > // that encodes a value and a relative location. > bar.write(regs::NV_PFALCON_FALCON_RM::of::<E>(), reg); > > // First argument is a base location that infers the relative location > // from `reg`. > bar.write(WithBase::of::<E>(), reg); > > And yes, I am aware that the above wording around the difference between > regs::NV_PFALCON_FALCON_RM::of::<E>() and WithBase::of::<E>() is at least a bit > vague technically, but my point is about how this appears to users. The latter is just a type inferred version of the former, to avoid having you to write the name twice. They're exactly the same. > > In any case, the fact that you can write WithBase::of::<E>() as a location > argument in the first place proves that `reg` is not *only* a value. Not true as discussed above. > > fn write<T, L>(&self, location: L, value: T) > where > L: IoLoc<T>, > Self: IoKnownSize + IoCapable<L::IoType>, > > Which is the reason why L: IoLoc<T>, i.e. the relative location is on T, not on > L. The location has nothing to do with `value`, just the type `T`. > > So, what you could say is > > - read() takes an absolute location and returns something that encodes a value > and relative location > > - write() takes a base location (or absolute location) and something that > encodes a value and relative location Only `location` gives you the location. Value has nothing to do with locations at all. Type inference does the trick. Best, Gary > > But then there is the case with fixed registers that simply do not have a base, > but write() still asks for a base (or absolute) location. > >> `write_val` is really nothing but a convenience shortcut that has >> technically no strong reason to exist. As Gary pointed out, the >> counterpart of >> >> let reg = bar.read(regs::NV_PMC_BOOT_0); >> >> is >> >> bar.write(regs::NV_PMC_BOOT_0, reg); > > I do not agree for the reasons mentioned above. > > In this case read() returns (location + value), while write() asks for location > *and* (location + value), i.e. there is no base location for fixed registers. > >> ... and we introduced `write_val` for those cases where the value >> in itself provides its location, as `NV_PMC_BOOT_0` is redundant. But >> the above statement could also be written: >> >> bar.write((), reg); > > This makes more sense, as one could argue that a fixed register still requires a > base location, which is just always zero, but why would we bother users with > this implementation detail? > > Is this really less confusing than an additional bar.write_reg(reg) that just > works with any register? > >> which is exactly the same length as the `write_val` equivalent - it's >> just that you need to remember that `()` can be used in this case. But >> if you can remember that your register type can be used with >> `write_val`, then why not this? This actually makes me doubt that >> `write_val` is needed at all, and if we get rid of it, then we have a >> symmetric API. > > Still not symmetric, and I also don't think we will have a lot of fun explaining > people why they have to call it as bar.write((), reg). :( > > OOC, how would you explain it when the question is raised without arguing with > implementation details? > >> We were so focused on this single issue for the last few revisions that >> the single-argument write variant sounded like the only way to handle >> this properly, but the `()` use proposed by Gary actually fulfills the >> same role and doesn't introduce more burden when you think of it. >> >> So why not try without `write_val` at first? We can always add it later >> if we feel the need (and the same applies to a `(location, value)` >> symmetric read/write API). > > If you really think it's the best solution, I'm fine picking it up this way for > now, but to me it still sounds like we have no solution for a very simple case > that does not at least raise an eyebrow. > >> And most importantly, that way we also don't have to worry about its >> name. :) ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-11 15:42 ` Gary Guo @ 2026-03-11 16:22 ` Danilo Krummrich 2026-03-11 17:16 ` Gary Guo 0 siblings, 1 reply; 57+ messages in thread From: Danilo Krummrich @ 2026-03-11 16:22 UTC (permalink / raw) To: Gary Guo Cc: Alexandre Courbot, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 4:42 PM CET, Gary Guo wrote: > On Wed Mar 11, 2026 at 2:56 PM GMT, Danilo Krummrich wrote: >> >> fn write<T, L>(&self, location: L, value: T) >> where >> L: IoLoc<T>, >> Self: IoKnownSize + IoCapable<L::IoType>, >> >> Which is the reason why L: IoLoc<T>, i.e. the relative location is on T, not on >> L. > > The location has nothing to do with `value`, just the type `T`. (Cutting all the above, since it all boils down to the same thing.) So, that's what I mean, the relative location is on type T (the value type), not on L (the location type), hence T can't just be an arbitrary primitive, no? >> So, what you could say is >> >> - read() takes an absolute location and returns something that encodes a value >> and relative location >> >> - write() takes a base location (or absolute location) and something that >> encodes a value and relative location > > Only `location` gives you the location. Value has nothing to do with locations at all. Type > inference does the trick. The value does have something to do with the relative location, as it comes from the value type. You can't pass an "independent" value whose type does not carry location information, can you? ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-11 16:22 ` Danilo Krummrich @ 2026-03-11 17:16 ` Gary Guo 0 siblings, 0 replies; 57+ messages in thread From: Gary Guo @ 2026-03-11 17:16 UTC (permalink / raw) To: Danilo Krummrich, Gary Guo Cc: Alexandre Courbot, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 4:22 PM GMT, Danilo Krummrich wrote: > On Wed Mar 11, 2026 at 4:42 PM CET, Gary Guo wrote: >> On Wed Mar 11, 2026 at 2:56 PM GMT, Danilo Krummrich wrote: >>> >>> fn write<T, L>(&self, location: L, value: T) >>> where >>> L: IoLoc<T>, >>> Self: IoKnownSize + IoCapable<L::IoType>, >>> >>> Which is the reason why L: IoLoc<T>, i.e. the relative location is on T, not on >>> L. >> >> The location has nothing to do with `value`, just the type `T`. > > (Cutting all the above, since it all boils down to the same thing.) > > So, that's what I mean, the relative location is on type T (the value type), not > on L (the location type), hence T can't just be an arbitrary primitive, no? The location is on the trait impl of `L`, it just may depend on `T`. For a register location, say, `UART_TX`, it is itself contain full information on location; the `IoLoc<u8>` impl of it would be just used to constrain the type so that no other types than u8 gets used. Yes, for the case of `()` or `WithBase`, it is itself only partial information and the location is only complete with an associated type. But this is more just a type-system trick so that we can avoid spelling out the full register name. For the `write_val` case for a bitfield, I would basically consider the canonical way of doing a register write is bar.write(REGISTER_NAME, bitfield) value. Where bar.write((), bitfield) or (`write_val`) is a nice sugar to avoid writing the name multiple times. > >>> So, what you could say is >>> >>> - read() takes an absolute location and returns something that encodes a value >>> and relative location >>> >>> - write() takes a base location (or absolute location) and something that >>> encodes a value and relative location >> >> Only `location` gives you the location. Value has nothing to do with locations at all. Type >> inference does the trick. > > The value does have something to do with the relative location, as it comes from > the value type. You can't pass an "independent" value whose type does not carry > location information, can you? You can also not pass an "independent" value to register of a different type. With my FIFO example, you cannot write bar.write(UART_TX, 0u32) because `UART_TX: IoLoc<u8>` only. It doesn't mean that the `0u32` suddenly start carrying location information. I just view this as type system working as designed. Best, Gary ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-11 14:56 ` Danilo Krummrich 2026-03-11 15:42 ` Gary Guo @ 2026-03-12 13:53 ` Alexandre Courbot 2026-03-12 15:54 ` Danilo Krummrich 1 sibling, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-12 13:53 UTC (permalink / raw) To: Danilo Krummrich Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 11:56 PM JST, Danilo Krummrich wrote: > On Wed Mar 11, 2026 at 2:28 PM CET, Alexandre Courbot wrote: >> The fact is, there is a symmetry between `read` and `write`: >> >> - `read` takes a location and returns a value, >> - `write` takes a location and a value. > > This is not entirely true. > > read() takes an absolute location and returns something that encodes the value > and a relative location, where for fixed registers the relative location is > equivalent to an absolute location. Let's look at the prototype of `read` (inherited constraints in parentheses): fn read<T, L>(&self, location: L) -> T where L: IoLoc<T>, Self: IoKnownSize + IoCapable<L::IoType>, (L::IoType: Into<T> + From<T>,) It takes an absolute location and returns... something that can be converted to/from the I/O type. There is no mention of a relative location anywhere. The very concept of a relative location is local to the `register` module. `read` doesn't care about this at all. It just so happens that register do carry that information in their type - but that's not a requirement of the read/write I/O API. > > write() works with both an absolute location and something that encodes the > value and a relative location, or a base location and something that encodes the > value and a relative location. Again, looking at the prototype for `write`: fn write<T, L>(&self, location: L, value: T) where L: IoLoc<T>, Self: IoKnownSize + IoCapable<L::IoType>, (L::IoType: Into<T> + From<T>,) The first argument is an absolute location. The second argument is just a value - there is no requirement for any kind of partial location information. It is true that in the case of registers, we can use the second argument to infer a generic parameter of the first that is needed to build that absolute location, which makes the first argument look as if it contained partial location information, but that's just because the *register* module defines this relationship between its own location and value types (and the compiler is nice enough to connect the dots for us). This is again not the work of the read/write I/O API. So we really have this: - read(location) -> value - write(location, value) Otherwise we couldn't use primitive types with read/write, since they don't carry any location information by themselves. > > // `reg` encodes the value and an absolute location. > let reg = bar.read(regs::NV_PMC_BOOT_0); > > // `reg` encodes the value and a relative location. > let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); > > // First argument is an absolute location, second argument is a type > // that encodes a value and a relative location. > bar.write(regs::NV_PFALCON_FALCON_RM::of::<E>(), reg); > > // First argument is a base location that infers the relative location > // from `reg`. > bar.write(WithBase::of::<E>(), reg); > > And yes, I am aware that the above wording around the difference between > regs::NV_PFALCON_FALCON_RM::of::<E>() and WithBase::of::<E>() is at least a bit > vague technically, but my point is about how this appears to users. > > In any case, the fact that you can write WithBase::of::<E>() as a location > argument in the first place proves that `reg` is not *only* a value. But that is only true in the case of registers. The I/O module can and does cover things other than registers - currently primitive values, but we might want to extend that in future (don't ask me with what, but I don't see a reason to close the possibility :)). > > fn write<T, L>(&self, location: L, value: T) > where > L: IoLoc<T>, > Self: IoKnownSize + IoCapable<L::IoType>, > > Which is the reason why L: IoLoc<T>, i.e. the relative location is on T, not on > L. > > So, what you could say is > > - read() takes an absolute location and returns something that encodes a value > and relative location > > - write() takes a base location (or absolute location) and something that > encodes a value and relative location > > But then there is the case with fixed registers that simply do not have a base, > but write() still asks for a base (or absolute) location. > >> `write_val` is really nothing but a convenience shortcut that has >> technically no strong reason to exist. As Gary pointed out, the >> counterpart of >> >> let reg = bar.read(regs::NV_PMC_BOOT_0); >> >> is >> >> bar.write(regs::NV_PMC_BOOT_0, reg); > > I do not agree for the reasons mentioned above. > > In this case read() returns (location + value), while write() asks for location > *and* (location + value), i.e. there is no base location for fixed registers. > >> ... and we introduced `write_val` for those cases where the value >> in itself provides its location, as `NV_PMC_BOOT_0` is redundant. But >> the above statement could also be written: >> >> bar.write((), reg); > > This makes more sense, as one could argue that a fixed register still requires a > base location, which is just always zero, but why would we bother users with > this implementation detail? > > Is this really less confusing than an additional bar.write_reg(reg) that just > works with any register? I don't think it is less or more confusing, in the end they are really equivalent. What I would like to avoid is having register-specific functions spill into the `io` module. I mean, I am not strictly opposed to it if we reach the conclusion that it is more convenient to users - in this case we could add an `impl Io` block in `register.rs` and add a `write_reg` method (and possibly a `read` variant returning the full position information?). But if we can just use the same 2 methods everywhere, that's better IMHO. > >> which is exactly the same length as the `write_val` equivalent - it's >> just that you need to remember that `()` can be used in this case. But >> if you can remember that your register type can be used with >> `write_val`, then why not this? This actually makes me doubt that >> `write_val` is needed at all, and if we get rid of it, then we have a >> symmetric API. > > Still not symmetric, and I also don't think we will have a lot of fun explaining > people why they have to call it as bar.write((), reg). :( > > OOC, how would you explain it when the question is raised without arguing with > implementation details? This seems to indicate that instead of a `Io::write_val` method in `io.rs`, we might need a `Io::write_reg` method in `register.rs` that is dedicated to writing unambiguous registers exclusively. How does that sound to you? > >> We were so focused on this single issue for the last few revisions that >> the single-argument write variant sounded like the only way to handle >> this properly, but the `()` use proposed by Gary actually fulfills the >> same role and doesn't introduce more burden when you think of it. >> >> So why not try without `write_val` at first? We can always add it later >> if we feel the need (and the same applies to a `(location, value)` >> symmetric read/write API). > > If you really think it's the best solution, I'm fine picking it up this way for > now, but to me it still sounds like we have no solution for a very simple case > that does not at least raise an eyebrow. I think the current `read` and `write` methods are going to stay the basis of I/O, so I'm pretty confident we can commit to them. They also allow to do everything we want to do with registers. So my suggestion would be to see how it goes and add functionality as we get feedback and more visibility into how the API is used. After reading your reply I believe an `Io::write_reg` in `register.rs` could solve the problem of writing fixed offset registers without making a single-argument `write` part of the core I/O API (and save the trouble of finding a generic-enough name for it!). It's just not clear to me that we should add this right now - but I can of course do it in the next revision if you think the idea is good. ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-12 13:53 ` Alexandre Courbot @ 2026-03-12 15:54 ` Danilo Krummrich 2026-03-14 1:19 ` Alexandre Courbot 0 siblings, 1 reply; 57+ messages in thread From: Danilo Krummrich @ 2026-03-12 15:54 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 12, 2026 at 2:53 PM CET, Alexandre Courbot wrote: > On Wed Mar 11, 2026 at 11:56 PM JST, Danilo Krummrich wrote: >> And yes, I am aware that the above wording around the difference between >> regs::NV_PFALCON_FALCON_RM::of::<E>() and WithBase::of::<E>() is at least a bit >> vague technically, but my point is about how this appears to users. >> >> In any case, the fact that you can write WithBase::of::<E>() as a location >> argument in the first place proves that `reg` is not *only* a value. > > But that is only true in the case of registers. Agree with this and all the above. But - and this is why I wrote "my point is about how this appears to users" - users use this API with registers only and except for the FIFO use-case, which is not implemented yet, it is obvious to users that part of the location comes from the value type. I'm not saying that this is a problem. Actually, quite the opposite. When I proposed the register!() macro, one of the problems I wanted to solve was that people can't read from a certain location and accidentally treat it as something semantically different. Or in other words, if you want to read register A, you shouldn't be able to accidentally read from B and treat it as A. > The I/O module can and does cover things other than registers - currently > primitive values, but we might want to extend that in future (don't ask me > with what, but I don't see a reason to close the possibility :)). Neither do I. :) But, as mentioned above, we should be aware that people notice that for registers part of the location information comes from the value type (except for the FIFO case of course) and registers are the thing that people deal with most. Other use-cases such as I/O memcpy, etc. will be accssible through other APIs, such as IoSlice, or more generally, IoView. Having that said, my point is that considering the use-cases it also makes sense to think about how this API will be perceived. And from this context something like bar.write((), reg) looks pretty odd. >> Is this really less confusing than an additional bar.write_reg(reg) that just >> works with any register? > > I don't think it is less or more confusing, in the end they are really > equivalent. From your point of view being an expert on the I/O and register!() implementation details, but please also consider the above. Also, look at the Tyr patches for instance, they exclusively use write_val(). > What I would like to avoid is having register-specific functions spill > into the `io` module. I mean, I am not strictly opposed to it if we > reach the conclusion that it is more convenient to users - in this case > we could add an `impl Io` block in `register.rs` and add a `write_reg` > method (and possibly a `read` variant returning the full position > information?). But if we can just use the same 2 methods everywhere, > that's better IMHO. Just leave it in the io module I'd say, register access is probably the most essential part of I/O, I think there is no need to factor it out. >>> which is exactly the same length as the `write_val` equivalent - it's >>> just that you need to remember that `()` can be used in this case. But >>> if you can remember that your register type can be used with >>> `write_val`, then why not this? This actually makes me doubt that >>> `write_val` is needed at all, and if we get rid of it, then we have a >>> symmetric API. >> >> Still not symmetric, and I also don't think we will have a lot of fun explaining >> people why they have to call it as bar.write((), reg). :( >> >> OOC, how would you explain it when the question is raised without arguing with >> implementation details? > > This seems to indicate that instead of a `Io::write_val` method in `io.rs`, > we might need a `Io::write_reg` method in `register.rs` that is > dedicated to writing unambiguous registers exclusively. How does that > sound to you? I don't see it adds any value to factor it out with an extention trait, rename to write_reg() seems fine. Additionally, I'd like to leave it open for the future to add read_reg() method returning a generic (loc, val) tuple dereferencing to the value in case we see a repetition of the following pattern. let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); // modify reg bar.write(WithBase::of::<E>(), reg) The reason is the same as mentioned above. In most cases drivers don't want to switch the base location between a read and a write, i.e. write the value from A to B. The most common case is to read from A and write back to A. For instance, talking to the Tyr folks, they told me that for the array registers they will have, they will never have the case that they want to write a register value from A to B. So, I still think it would be good to provide an option for drivers to prevent any mistakes in the first place. Now, obviously this would require that we also provide register accessors that take a mutable reference for this to work, but that doesn't seem like a big deal. I also don't think we have to do this now, but I'd like to have something like this in the future. For now s/write_val/write_reg/ and we should be good to go. :) ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` 2026-03-12 15:54 ` Danilo Krummrich @ 2026-03-14 1:19 ` Alexandre Courbot 0 siblings, 0 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-14 1:19 UTC (permalink / raw) To: Danilo Krummrich Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 13, 2026 at 12:54 AM JST, Danilo Krummrich wrote: <snip> >>>> which is exactly the same length as the `write_val` equivalent - it's >>>> just that you need to remember that `()` can be used in this case. But >>>> if you can remember that your register type can be used with >>>> `write_val`, then why not this? This actually makes me doubt that >>>> `write_val` is needed at all, and if we get rid of it, then we have a >>>> symmetric API. >>> >>> Still not symmetric, and I also don't think we will have a lot of fun explaining >>> people why they have to call it as bar.write((), reg). :( >>> >>> OOC, how would you explain it when the question is raised without arguing with >>> implementation details? >> >> This seems to indicate that instead of a `Io::write_val` method in `io.rs`, >> we might need a `Io::write_reg` method in `register.rs` that is >> dedicated to writing unambiguous registers exclusively. How does that >> sound to you? > > I don't see it adds any value to factor it out with an extention trait, rename > to write_reg() seems fine. > > Additionally, I'd like to leave it open for the future to add read_reg() method > returning a generic (loc, val) tuple dereferencing to the value in case we see a > repetition of the following pattern. > > let reg = bar.read(regs::NV_PFALCON_FALCON_RM::of::<E>()); > > // modify reg > > bar.write(WithBase::of::<E>(), reg) > > The reason is the same as mentioned above. In most cases drivers don't want to > switch the base location between a read and a write, i.e. write the value from A > to B. That's definitely a pattern we are seeing a lot already. I agree there is a use-case for that. > > The most common case is to read from A and write back to A. For instance, > talking to the Tyr folks, they told me that for the array registers they will > have, they will never have the case that they want to write a register value > from A to B. > > So, I still think it would be good to provide an option for drivers to prevent > any mistakes in the first place. > > Now, obviously this would require that we also provide register accessors that > take a mutable reference for this to work, but that doesn't seem like a big > deal. > > I also don't think we have to do this now, but I'd like to have something like > this in the future. There are several ways we can achieve this. One would be to use the macro to forward the bitfield setter methods to the containing type and return an updated one. Or, we implement `Deref` on the containing type, and add a set of `set_<field>` methods that modify the value in-place. That way `read_reg` and `write_reg` should be symmetric and we remove another cause of typos and mistakes. I'l start thinking about it once I post the extracted `bitfield` patches, as it makes it easier to look at the problem space. ^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 08/10] rust: io: add `register!` macro 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (6 preceding siblings ...) 2026-03-09 15:14 ` [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` Alexandre Courbot @ 2026-03-09 15:14 ` Alexandre Courbot 2026-03-10 16:39 ` Danilo Krummrich 2026-03-09 15:14 ` [PATCH v8 09/10] sample: rust: pci: use " Alexandre Courbot ` (4 subsequent siblings) 12 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:14 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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. Suggested-by: Danilo Krummrich <dakr@kernel.org> Link: https://lore.kernel.org/all/20250306222336.23482-6-dakr@kernel.org/ Co-developed-by: Gary Guo <gary@garyguo.net> Signed-off-by: Gary Guo <gary@garyguo.net> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- rust/kernel/io.rs | 6 +- rust/kernel/io/register.rs | 1190 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1195 insertions(+), 1 deletion(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 09a0fe06f201..bb58a77fd055 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; @@ -179,7 +180,8 @@ pub trait IoCapable<T> { /// This trait is the key abstraction allowing [`Io::read`], [`Io::write`], and [`Io::update`] (and /// their fallible [`try_read`](Io::try_read), [`try_write`](Io::try_write) and /// [`try_update`](Io::try_update) counterparts) to work uniformly with both raw [`usize`] offsets -/// (for primitive types like [`u32`]) and typed ones. +/// (for primitive types like [`u32`]) and typed ones (like those generated by the [`register!`] +/// macro). /// /// An `IoLoc<T>` carries three pieces of information: /// @@ -189,6 +191,8 @@ pub trait IoCapable<T> { /// /// `T` and `IoLoc::IoType` may differ: for instance, a typed register has `T` = the register type /// with its bitfields, and `IoType` = its backing primitive (e.g. `u32`). +/// +/// [`register!`]: kernel::register! pub trait IoLoc<T> { /// Size ([`u8`], [`u16`], etc) of the I/O performed on the returned [`offset`](IoLoc::offset). type IoType: Into<T> + From<T>; diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs new file mode 100644 index 000000000000..153ea779b244 --- /dev/null +++ b/rust/kernel/io/register.rs @@ -0,0 +1,1190 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Macro to define register layout and accessors. +//! +//! The [`register!`](kernel::register) macro in this module provides an intuitive and readable +//! syntax for defining a dedicated type for each register and accessing it using +//! [`Io`](super::Io). Each such type comes with its own field accessors that can return an error +//! if a field's value is invalid. +//! +//! Most of the items in this module are public so they can be referenced by the macro, but most +//! are not to be used directly by users. Outside of the `register!` macro itself, the only items +//! you might want to import from this module are [`WithBase`] and [`Array`]. + +use core::marker::PhantomData; + +use crate::io::{ + IntoIoVal, + IoLoc, // +}; + +/// Trait implemented by all registers. +pub trait Register: Sized { + /// Backing primitive type of the register. + type Storage: Into<Self> + From<Self>; + + /// Start offset of the register. + /// + /// The interpretation of this offset depends on the type of the register. + const OFFSET: usize; +} + +/// Trait implemented by registers with a fixed offset. +pub trait FixedRegister: Register {} + +/// Allows `()` to be used as the `location` parameter of [`Io::write`](super::Io::write) when +/// passing a [`FixedRegister`] value. +impl<T> IoLoc<T> for () +where + T: FixedRegister, +{ + type IoType = T::Storage; + + fn offset(&self) -> usize { + T::OFFSET + } +} + +/// A [`FixedRegister`] carries its location in its type. Thus `FixedRegister` values can be used +/// as an [`IoLoc`]. +impl<T> IoLoc<T> for T +where + T: FixedRegister, +{ + type IoType = T::Storage; + + fn offset(&self) -> usize { + T::OFFSET + } +} + +impl<T> IntoIoVal<T, FixedRegisterLoc<T>> for T +where + T: FixedRegister, +{ + fn into_io_val(self) -> (FixedRegisterLoc<T>, T) { + (FixedRegisterLoc::new(), self) + } +} + +/// Location of a fixed register. +pub struct FixedRegisterLoc<T: FixedRegister>(PhantomData<T>); + +impl<T: FixedRegister> FixedRegisterLoc<T> { + /// Returns the location of `T`. + #[inline(always)] + // We do not implement `Default` so we can be const. + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Self(PhantomData) + } +} + +impl<T> IoLoc<T> for FixedRegisterLoc<T> +where + T: FixedRegister, +{ + type IoType = T::Storage; + + fn offset(&self) -> usize { + T::OFFSET + } +} + +/// Trait providing a base address to be added to the offset of a relative register to obtain +/// its actual offset. +/// +/// The `T` generic argument is used to distinguish which base to use, in case a type provides +/// several bases. It is given to the `register!` macro to restrict the use of the register to +/// implementors of this particular variant. +pub trait RegisterBase<T> { + /// Base address to which register offsets are added. + const BASE: usize; +} + +/// Trait implemented by all registers that are relative to a base. +pub trait WithBase { + /// Family of bases applicable to this register. + type BaseFamily; + + /// Returns the absolute location of this type when using `B` as its base. + fn of<B: RegisterBase<Self::BaseFamily>>() -> RelativeRegisterLoc<Self, B> + where + Self: Register, + { + RelativeRegisterLoc::new() + } +} + +/// Trait implemented by relative registers. +pub trait RelativeRegister: Register + WithBase {} + +/// Location of a relative register. +/// +/// This can either be an immediately accessible regular [`RelativeRegister`], or a +/// [`RelativeRegisterArray`] that needs one additional resolution through +/// [`RelativeRegisterLoc::at`]. +pub struct RelativeRegisterLoc<T: WithBase, B: ?Sized>(PhantomData<T>, PhantomData<B>); + +impl<T, B> RelativeRegisterLoc<T, B> +where + T: Register + WithBase, + B: RegisterBase<T::BaseFamily> + ?Sized, +{ + /// Returns the location of a relative register or register array. + #[inline(always)] + // We do not implement `Default` so we can be const. + #[allow(clippy::new_without_default)] + pub const fn new() -> Self { + Self(PhantomData, PhantomData) + } + + // Returns the absolute offset of the relative register using base `B`. + // + // This is implemented as a private const method so it can be reused by the [`IoLoc`] + // implementations of both [`RelativeRegisterLoc`] and [`RelativeRegisterArrayLoc`]. + const fn offset(&self) -> usize { + B::BASE + T::OFFSET + } +} + +impl<T, B> IoLoc<T> for RelativeRegisterLoc<T, B> +where + T: RelativeRegister, + B: RegisterBase<T::BaseFamily> + ?Sized, +{ + type IoType = T::Storage; + + fn offset(&self) -> usize { + self.offset() + } +} + +/// Trait implemented by arrays of registers. +pub trait RegisterArray: Register { + /// Number of elements in the registers array. + const SIZE: usize; + /// Number of bytes between the start of elements in the registers array. + const STRIDE: usize; +} + +/// Location of an array register. +pub struct RegisterArrayLoc<T: RegisterArray>(usize, PhantomData<T>); + +impl<T: RegisterArray> RegisterArrayLoc<T> { + /// Returns the location of register `T` at position `idx`, with build-time validation. + #[inline(always)] + pub fn new(idx: usize) -> Self { + ::kernel::build_assert!(idx < T::SIZE); + + Self(idx, PhantomData) + } + + /// Attempts to return the location of register `T` at position `idx`, with runtime validation. + #[inline(always)] + pub fn try_new(idx: usize) -> Option<Self> { + if idx < T::SIZE { + Some(Self(idx, PhantomData)) + } else { + None + } + } +} + +impl<T> IoLoc<T> for RegisterArrayLoc<T> +where + T: RegisterArray, +{ + type IoType = T::Storage; + + fn offset(&self) -> usize { + T::OFFSET + self.0 * T::STRIDE + } +} + +/// Trait providing location builders for [`RegisterArray`]s. +pub trait Array { + /// Returns the location of the register at position `idx`, with build-time validation. + fn at(idx: usize) -> RegisterArrayLoc<Self> + where + Self: RegisterArray, + { + RegisterArrayLoc::new(idx) + } + + /// Returns the location of the register at position `idx`, with runtime validation. + fn try_at(idx: usize) -> Option<RegisterArrayLoc<Self>> + where + Self: RegisterArray, + { + RegisterArrayLoc::try_new(idx) + } +} + +/// Trait implemented by arrays of relative registers. +pub trait RelativeRegisterArray: RegisterArray + WithBase {} + +/// Location to a relative array register. +pub struct RelativeRegisterArrayLoc< + T: RelativeRegisterArray, + B: RegisterBase<T::BaseFamily> + ?Sized, +>(RelativeRegisterLoc<T, B>, usize); + +impl<T, B> RelativeRegisterArrayLoc<T, B> +where + T: RelativeRegisterArray, + B: RegisterBase<T::BaseFamily> + ?Sized, +{ + /// Returns the location of register `T` from the base `B` at index `idx`, with build-time + /// validation. + #[inline(always)] + pub fn new(idx: usize) -> Self { + ::kernel::build_assert!(idx < T::SIZE); + + Self(RelativeRegisterLoc::new(), idx) + } + + /// Attempts to return the location of register `T` from the base `B` at index `idx`, with + /// runtime validation. + #[inline(always)] + pub fn try_new(idx: usize) -> Option<Self> { + if idx < T::SIZE { + Some(Self(RelativeRegisterLoc::new(), idx)) + } else { + None + } + } +} + +/// Methods exclusive to [`RelativeRegisterLoc`]s created with a [`RelativeRegisterArray`]. +impl<T, B> RelativeRegisterLoc<T, B> +where + T: RelativeRegisterArray, + B: RegisterBase<T::BaseFamily> + ?Sized, +{ + /// Returns the location of the register at position `idx`, with build-time validation. + #[inline(always)] + pub fn at(self, idx: usize) -> RelativeRegisterArrayLoc<T, B> { + RelativeRegisterArrayLoc::new(idx) + } + + /// Returns the location of the register at position `idx`, with runtime validation. + #[inline(always)] + pub fn try_at(self, idx: usize) -> Option<RelativeRegisterArrayLoc<T, B>> { + RelativeRegisterArrayLoc::try_new(idx) + } +} + +impl<T, B> IoLoc<T> for RelativeRegisterArrayLoc<T, B> +where + T: RelativeRegisterArray, + B: RegisterBase<T::BaseFamily> + ?Sized, +{ + type IoType = T::Storage; + + fn offset(&self) -> usize { + self.0.offset() + self.1 * T::STRIDE + } +} + +/// Defines a dedicated type for a register, including getter and setter methods for its fields and +/// methods to read and write it from an [`Io`](kernel::io::Io) region. +/// +/// # Simple example +/// +/// ```no_run +/// 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 32-bit `BOOT_0` type which can be read from or written to offset `0x100` of an +/// `Io` region, with the described bitfields. For instance, `minor_revision` consists of the 4 +/// least significant bits of the type. +/// +/// Fields are instances of [`Bounded`](kernel::num::Bounded) and can be read by calling their +/// getter method, which is named after them. They also have setter methods prefixed with `with_` +/// for runtime values and `with_const_` for constant values. All setters return the updated +/// register value. +/// +/// Fields can also be transparently converted from/to an arbitrary type by using the `=>` and +/// `?=>` syntaxes. +/// +/// If present, doc comments above register or fields definitions are added to the relevant item +/// they document (the register type itself, or the field's setter and getter methods). +/// +/// Note that multiple registers can be defined in a single `register!` invocation. This can be +/// useful to group related registers together. +/// +/// ```no_run +/// use kernel::register; +/// use kernel::io::IoLoc; +/// 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 = bar.read(BOOT_0); +/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get()); +/// +/// // Update some fields and write the new value back. +/// let new_boot0 = boot0 +/// // Constant values. +/// .with_const_major_revision::<3>() +/// .with_const_minor_revision::<10>() +/// // Run-time value. +/// .with_vendor_id(obtain_vendor_id()); +/// bar.write_val(new_boot0); +/// +/// // Or, build a new value from zero and write it: +/// bar.write_val(BOOT_0::zeroed() +/// .with_const_major_revision::<3>() +/// .with_const_minor_revision::<10>() +/// .with_vendor_id(obtain_vendor_id()) +/// ); +/// +/// // Or, read and update the register in a single step. +/// bar.update(BOOT_0, |r| r +/// .with_const_major_revision::<3>() +/// .with_const_minor_revision::<10>() +/// .with_vendor_id(obtain_vendor_id()) +/// ); +/// +/// // Constant values can also be built using the const setters. +/// const V: BOOT_0 = pin_init::zeroed::<BOOT_0>() +/// .with_const_major_revision::<3>() +/// .with_const_minor_revision::<10>(); +/// # } +/// ``` +/// +/// There are 4 possible kinds of registers: fixed offset registers, relative registers, arrays of +/// registers, and relative arrays of registers. +/// +/// ## Fixed offset registers +/// +/// These are the simplest kind of registers. Their location is simply an offset inside the I/O +/// region. For instance: +/// +/// ```ignore +/// register! { +/// pub FIXED_REG(u16) @ 0x80 { +/// ... +/// } +/// } +/// ``` +/// +/// This creates a 16-bit register named `FIXED_REG` located at offset `0x80` of an I/O region. +/// +/// These registers' location can be built for access by just referencing their name: +/// +/// ```no_run +/// use kernel::io::Io; +/// use kernel::register; +/// # use kernel::io::Mmio; +/// +/// register! { +/// FIXED_REG(u32) @ 0x100 { +/// 16:8 high_byte; +/// 7:0 low_byte; +/// } +/// } +/// +/// # fn test(io: &Mmio<0x1000>) { +/// let val = io.read(FIXED_REG); +/// +/// // Write from an already-existing value. +/// io.write(FIXED_REG, val.with_low_byte(0xff)); +/// +/// // Write a value created from scratch. +/// io.write_val(FIXED_REG::zeroed().with_high_byte(0x80)); +/// # } +/// +/// ``` +/// +/// It is possible to create an alias of an existing register with new field definitions by using +/// the `=> ALIAS` syntax. This is useful for cases where a register's interpretation depends on +/// the context: +/// +/// ```no_run +/// use kernel::register; +/// +/// register! { +/// /// Scratch register. +/// pub SCRATCH(u32) @ 0x00000200 { +/// 31:0 value; +/// } +/// +/// /// Boot status of the firmware. +/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH { +/// 0:0 completed; +/// } +/// } +/// ``` +/// +/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while providing +/// its own `completed` field. +/// +/// ## Relative registers +/// +/// Relative registers can be instantiated several times at a relative offset of a group of bases. +/// For instance, imagine the following I/O space: +/// +/// ```text +/// +-----------------------------+ +/// | ... | +/// | | +/// 0x100--->+------------CPU0-------------+ +/// | | +/// 0x110--->+-----------------------------+ +/// | CPU_CTL | +/// +-----------------------------+ +/// | ... | +/// | | +/// | | +/// 0x200--->+------------CPU1-------------+ +/// | | +/// 0x210--->+-----------------------------+ +/// | CPU_CTL | +/// +-----------------------------+ +/// | ... | +/// +-----------------------------+ +/// ``` +/// +/// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O +/// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define +/// them twice and would prefer a way to select which one to use from a single definition. +/// +/// This can be done using the `Base + Offset` syntax when specifying the register's address: +/// +/// ```ignore +/// register! { +/// pub RELATIVE_REG(u32) @ Base + 0x80 { +/// ... +/// } +/// } +/// ``` +/// +/// This creates a register with an offset of `0x80` from a given base. +/// +/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the +/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for +/// this register needs to implement `RegisterBase<Base>`. +/// +/// The location of relative registers can be built using the [`WithBase::of`] method to specify +/// its base. All relative registers implement [`WithBase`]. +/// +/// Here is the above layout translated into code: +/// +/// ```no_run +/// use kernel::io::Io; +/// use kernel::io::register::{RegisterBase, WithBase}; +/// use kernel::register; +/// # use kernel::io::Mmio; +/// +/// // Type used to identify the base. +/// pub struct CpuCtlBase; +/// +/// // ZST describing `CPU0`. +/// struct Cpu0; +/// impl RegisterBase<CpuCtlBase> for Cpu0 { +/// const BASE: usize = 0x100; +/// } +/// +/// // ZST describing `CPU1`. +/// struct Cpu1; +/// impl RegisterBase<CpuCtlBase> for Cpu1 { +/// const BASE: usize = 0x200; +/// } +/// +/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`. +/// register! { +/// /// CPU core control. +/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 { +/// 0:0 start; +/// } +/// } +/// +/// # fn test(io: Mmio<0x1000>) { +/// // Read the status of `Cpu0`. +/// let cpu0_started = io.read(CPU_CTL::of::<Cpu0>()); +/// +/// // Stop `Cpu0`. +/// io.write(WithBase::of::<Cpu0>(), CPU_CTL::zeroed()); +/// # } +/// +/// // Aliases can also be defined for relative register. +/// register! { +/// /// Alias to CPU core control. +/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL { +/// /// Start the aliased CPU core. +/// 1:1 alias_start; +/// } +/// } +/// +/// # fn test2(io: Mmio<0x1000>) { +/// // Start the aliased `CPU0`, leaving its other fields untouched. +/// io.update(CPU_CTL_ALIAS::of::<Cpu0>(), |r| r.with_alias_start(true)); +/// # } +/// ``` +/// +/// ## Arrays of registers +/// +/// Some I/O areas contain consecutive registers that share the same field layout. These areas can +/// be defined as an array of identical registers, allowing them to be accessed by index with +/// compile-time or runtime bound checking: +/// +/// +/// ```ignore +/// register! { +/// pub REGISTER_ARRAY(u8)[10, stride = 4] @ 0x100 { +/// ... +/// } +/// } +/// ``` +/// +/// This defines `REGISER_ARRAY`, an array of 10 byte registers starting at offset `0x100`. Each +/// register is separated from its neighbor by 4 bytes. +/// +/// The `stride` parameter is optional; if unspecified, the registers are placed consecutively from +/// each other. +/// +/// A location for a register in a register array is built using the [`Array::at`] trait method. +/// All arrays of registers implement [`Array`]. +/// +/// ```no_run +/// use kernel::register; +/// use kernel::io::Io; +/// use kernel::io::register::Array; +/// # use kernel::io::Mmio; +/// # fn get_scratch_idx() -> usize { +/// # 0x15 +/// # } +/// +/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`. +/// register! { +/// /// Scratch registers. +/// pub SCRATCH(u32)[64] @ 0x00000080 { +/// 31:0 value; +/// } +/// } +/// +/// # fn test(io: &Mmio<0x1000>) +/// # -> Result<(), Error>{ +/// // Read scratch register 0, i.e. I/O address `0x80`. +/// let scratch_0 = io.read(SCRATCH::at(0)).value(); +/// +/// // Write scratch register 15, i.e. I/O address `0x80 + (15 * 4)`. +/// io.write(Array::at(15), SCRATCH::from(0xffeeaabb)); +/// +/// // This is out of bounds and won't build. +/// // let scratch_128 = bar.read(SCRATCH::at(128)).value(); +/// +/// // Runtime-obtained array index. +/// let idx = get_scratch_idx(); +/// // Access on a runtime index returns an error if it is out-of-bounds. +/// let some_scratch = io.read(SCRATCH::try_at(idx).ok_or(EINVAL)?).value(); +/// +/// // Alias to a specific register in an array. +/// // Here `SCRATCH[8]` is used to convey the firmware exit code. +/// register! { +/// /// Firmware exit status code. +/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] { +/// 7:0 status; +/// } +/// } +/// +/// let status = io.read(FIRMWARE_STATUS).status(); +/// +/// // Non-contiguous register arrays can be defined by adding a stride parameter. +/// // Here, each of the 16 registers of the array 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, stride = 8] @ 0x000000c0 { +/// 31:0 value; +/// } +/// +/// /// Scratch registers bank 1. +/// pub SCRATCH_INTERLEAVED_1(u32)[16, stride = 8] @ 0x000000c4 { +/// 31:0 value; +/// } +/// } +/// # Ok(()) +/// # } +/// ``` +/// +/// ## Relative arrays of registers +/// +/// Combining the two features described in the sections above, arrays of registers accessible from +/// a base can also be defined: +/// +/// ```ignore +/// register! { +/// pub RELATIVE_REGISTER_ARRAY(u8)[10, stride = 4] @ Base + 0x100 { +/// ... +/// } +/// } +/// ``` +/// +/// Like relative registers, they implement the [`WithBase`] trait. However the return value of +/// [`WithBase::of`] cannot be used directly as a location and must be further specified using the +/// [`at`](RelativeRegisterLoc::at) method. +/// +/// ```no_run +/// use kernel::io::Io; +/// use kernel::io::register::{RegisterBase, WithBase}; +/// use kernel::register; +/// # use kernel::io::Mmio; +/// # fn get_scratch_idx() -> usize { +/// # 0x15 +/// # } +/// +/// // Type used as parameter of `RegisterBase` to specify the base. +/// pub struct CpuCtlBase; +/// +/// // ZST describing `CPU0`. +/// struct Cpu0; +/// impl RegisterBase<CpuCtlBase> for Cpu0 { +/// const BASE: usize = 0x100; +/// } +/// +/// // ZST describing `CPU1`. +/// struct Cpu1; +/// impl RegisterBase<CpuCtlBase> for Cpu1 { +/// const BASE: usize = 0x200; +/// } +/// +/// // 64 per-cpu scratch registers, arranged as a contiguous array. +/// register! { +/// /// Per-CPU scratch registers. +/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 { +/// 31:0 value; +/// } +/// } +/// +/// # fn test(io: &Mmio<0x1000>) -> Result<(), Error> { +/// // Read scratch register 0 of CPU0. +/// let scratch = io.read(CPU_SCRATCH::of::<Cpu0>().at(0)); +/// +/// // Write the retrieved value into scratch register 15 of CPU1. +/// io.write(WithBase::of::<Cpu1>().at(15), scratch); +/// +/// // This won't build. +/// // let cpu0_scratch_128 = bar.read(CPU_SCRATCH::of::<Cpu0>().at(128)).value(); +/// +/// // Runtime-obtained array index. +/// let scratch_idx = get_scratch_idx(); +/// // Access on a runtime index returns an error if it is out-of-bounds. +/// let cpu0_scratch = io.read( +/// CPU_SCRATCH::of::<Cpu0>().try_at(scratch_idx).ok_or(EINVAL)? +/// ).value(); +/// # Ok(()) +/// # } +/// +/// // Alias to `SCRATCH[8]` used to convey the firmware exit code. +/// register! { +/// /// Per-CPU firmware exit status code. +/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] { +/// 7:0 status; +/// } +/// } +/// +/// // Non-contiguous relative register arrays can be defined by adding a stride parameter. +/// // Here, each of the 16 registers of the array 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, stride = 8] @ CpuCtlBase + 0x00000d00 { +/// 31:0 value; +/// } +/// +/// /// Scratch registers bank 1. +/// pub CPU_SCRATCH_INTERLEAVED_1(u32)[16, stride = 8] @ CpuCtlBase + 0x00000d04 { +/// 31:0 value; +/// } +/// } +/// +/// # fn test2(io: &Mmio<0x1000>) -> Result<(), Error> { +/// let cpu0_status = io.read(CPU_FIRMWARE_STATUS::of::<Cpu0>()).status(); +/// # Ok(()) +/// # } +/// ``` +#[macro_export] +macro_rules! register { + // Entry point for the macro, allowing multiple registers to be defined in one call. + // It matches all possible register declaration patterns to dispatch them to corresponding + // `@reg` rule that defines a single register. + ( + $( + $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) + $([ $size:expr $(, stride = $stride:expr)? ])? + $(@ $($base:ident +)? $offset:literal)? + $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )? + { $($fields:tt)* } + )* + ) => { + $( + $crate::register!( + @reg $(#[$attr])* $vis $name ($storage) $([$size $(, stride = $stride)?])? + $(@ $($base +)? $offset)? + $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )? + { $($fields)* } + ); + )* + }; + + // All the rules below are private helpers. + + // Creates a register at a fixed offset of the MMIO space. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:literal + { $($fields:tt)* } + ) => { + $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); + $crate::register!(@io_base $name($storage) @ $offset); + $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); + }; + + // Creates an alias register of fixed offset register `alias` with its own fields. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident + { $($fields:tt)* } + ) => { + $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); + $crate::register!( + @io_base $name($storage) @ + <$alias as $crate::io::register::Register>::OFFSET + ); + $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); + }; + + // Creates a register at a relative offset from a base address provider. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal + { $($fields:tt)* } + ) => { + $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); + $crate::register!(@io_base $name($storage) @ $offset); + $crate::register!(@io_relative $vis $name($storage) @ $base); + }; + + // Creates an alias register of relative offset register `alias` with its own fields. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident + { $($fields:tt)* } + ) => { + $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); + $crate::register!( + @io_base $name($storage) @ <$alias as $crate::io::register::Register>::OFFSET + ); + $crate::register!(@io_relative $vis $name($storage) @ $base); + }; + + // Creates an array of registers at a fixed offset of the MMIO space. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) + [ $size:expr, stride = $stride:expr ] @ $offset:literal { $($fields:tt)* } + ) => { + static_assert!(::core::mem::size_of::<$storage>() <= $stride); + + $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); + $crate::register!(@io_base $name($storage) @ $offset); + $crate::register!(@io_array $vis $name($storage) [ $size, stride = $stride ]); + }; + + // Shortcut for contiguous array of registers (stride == size of element). + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal + { $($fields:tt)* } + ) => { + $crate::register!( + $(#[$attr])* $vis $name($storage) [ $size, stride = ::core::mem::size_of::<$storage>() ] + @ $offset { $($fields)* } + ); + }; + + // Creates an alias of register `idx` of array of registers `alias` with its own fields. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident [ $idx:expr ] + { $($fields:tt)* } + ) => { + static_assert!($idx < <$alias as $crate::io::register::RegisterArray>::SIZE); + + $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); + $crate::register!( + @io_base $name($storage) @ + <$alias as $crate::io::register::Register>::OFFSET + + $idx * <$alias as $crate::io::register::RegisterArray>::STRIDE + ); + $crate::register!(@io_fixed $(#[$attr])* $vis $name($storage)); + }; + + // Creates an array of registers at a relative offset from a base address provider. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) + [ $size:expr, stride = $stride:expr ] + @ $base:ident + $offset:literal { $($fields:tt)* } + ) => { + static_assert!(::core::mem::size_of::<$storage>() <= $stride); + + $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); + $crate::register!(@io_base $name($storage) @ $offset); + $crate::register!( + @io_relative_array $vis $name($storage) [ $size, stride = $stride ] @ $base + $offset + ); + }; + + // Shortcut for contiguous array of relative registers (stride == size of element). + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] + @ $base:ident + $offset:literal { $($fields:tt)* } + ) => { + $crate::register!( + $(#[$attr])* $vis $name($storage) [ $size, stride = ::core::mem::size_of::<$storage>() ] + @ $base + $offset { $($fields)* } + ); + }; + + // Creates an alias of register `idx` of relative array of registers `alias` with its own + // fields. + ( + @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) + => $base:ident + $alias:ident [ $idx:expr ] { $($fields:tt)* } + ) => { + static_assert!($idx < <$alias as $crate::io::register::RegisterArray>::SIZE); + + $crate::register!(@bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }); + $crate::register!( + @io_base $name($storage) @ + <$alias as $crate::io::register::Register>::OFFSET + + $idx * <$alias as $crate::io::register::RegisterArray>::STRIDE + ); + $crate::register!(@io_relative $vis $name($storage) @ $base); + }; + + // Generates the bitfield for the register. + // + // `#[allow(non_camel_case_types)]` is added since register names typically use + // `SCREAMING_CASE`. + ( + @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* } + ) => { + $crate::register!(@bitfield_core + #[allow(non_camel_case_types)] + $(#[$attr])* $vis $name $storage + ); + $crate::register!(@bitfield_fields $vis $name $storage { $($fields)* }); + }; + + // Implementations shared by all registers types. + (@io_base $name:ident($storage:ty) @ $offset:expr) => { + impl $crate::io::register::Register for $name { + type Storage = $storage; + + const OFFSET: usize = $offset; + } + }; + + // Implementations of fixed registers. + (@io_fixed $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)) => { + impl $crate::io::register::FixedRegister for $name {} + + $(#[$attr])* + $vis const $name: $crate::io::register::FixedRegisterLoc<$name> = + $crate::io::register::FixedRegisterLoc::<$name>::new(); + }; + + // Implementations of relative registers. + (@io_relative $vis:vis $name:ident ($storage:ty) @ $base:ident) => { + impl $crate::io::register::WithBase for $name { + type BaseFamily = $base; + } + + impl $crate::io::register::RelativeRegister for $name {} + }; + + // Implementations of register arrays. + (@io_array $vis:vis $name:ident ($storage:ty) [ $size:expr, stride = $stride:expr ]) => { + impl $crate::io::register::Array for $name {} + + impl $crate::io::register::RegisterArray for $name { + const SIZE: usize = $size; + const STRIDE: usize = $stride; + } + }; + + // Implementations of relative array registers. + ( + @io_relative_array $vis:vis $name:ident ($storage:ty) [ $size:expr, stride = $stride:expr ] + @ $base:ident + $offset:literal + ) => { + impl $crate::io::register::WithBase for $name { + type BaseFamily = $base; + } + + impl $crate::io::register::RegisterArray for $name { + const SIZE: usize = $size; + const STRIDE: usize = $stride; + } + + impl $crate::io::register::RelativeRegisterArray for $name {} + }; + + // 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 { + inner: $storage, + } + + #[allow(dead_code)] + impl $name { + /// Creates a bitfield from a raw value. + #[inline(always)] + $vis const fn from_raw(value: $storage) -> Self { + Self{ inner: value } + } + + /// Turns this bitfield into its raw value. + /// + /// This is similar to the [`From`] implementation, but is shorter to invoke in + /// most cases. + #[inline(always)] + $vis const fn into_raw(self) -> $storage { + self.inner + } + } + + // SAFETY: `$storage` is `Zeroable` and `$name` is transparent. + unsafe impl ::pin_init::Zeroable for $name {} + + impl ::core::convert::From<$name> for $storage { + #[inline(always)] + fn from(val: $name) -> $storage { + val.into_raw() + } + } + + impl ::core::convert::From<$storage> for $name { + #[inline(always)] + 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 { + $( + $crate::register!(@private_field_accessors $vis $name $storage : $hi:$lo $field); + $crate::register!( + @public_field_accessors $(#[doc = $doc])* $vis $name $storage : $hi:$lo $field + $(?=> $try_into_type)? + $(=> $into_type)? + ); + )* + } + + $crate::register!(@debug $name { $($field;)* }); + }; + + // Private field accessors working with the exact `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.inner << ALIGN_TOP + ); + val.shr::<ALIGN_BOTTOM, { $hi + 1 - $lo } >() + } + + const fn [<__with_ $field>]( + mut self, + value: ::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>, + ) -> Self + { + const MASK: $storage = <$name>::[<$field:upper _MASK>]; + const SHIFT: u32 = <$name>::[<$field:upper _SHIFT>]; + + let value = value.get() << SHIFT; + self.inner = (self.inner & !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 [<with_ $field>](self, value: $into_type) -> Self + { + self.[<__with_ $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 [<with_ $field>](self, value: $try_into_type) -> Self + { + self.[<__with_ $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_const_ $field>]<const VALUE: $storage>(self) -> Self { + self.[<__with_ $field>]( + ::kernel::num::Bounded::<$storage, { $hi + 1 - $lo }>::new::<VALUE>() + ) + } + + $(#[doc = $doc])* + #[doc = "Sets this field to the given `value`."] + #[inline(always)] + $vis fn [<with_ $field>]<T>( + self, + value: T, + ) -> Self + where T: Into<::kernel::num::Bounded<$storage, { $hi + 1 - $lo }>>, + { + self.[<__with_ $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_with_ $field>]<T>( + self, + value: T, + ) -> ::kernel::error::Result<Self> + where T: ::kernel::num::TryIntoBounded<$storage, { $hi + 1 - $lo }>, + { + Ok( + self.[<__with_ $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.inner)) + $( + .field(stringify!($field), &self.$field()) + )* + .finish() + } + } + }; +} -- 2.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH v8 08/10] rust: io: add `register!` macro 2026-03-09 15:14 ` [PATCH v8 08/10] rust: io: add `register!` macro Alexandre Courbot @ 2026-03-10 16:39 ` Danilo Krummrich 0 siblings, 0 replies; 57+ messages in thread From: Danilo Krummrich @ 2026-03-10 16:39 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 4:14 PM CET, Alexandre Courbot wrote: > +#[macro_export] > +macro_rules! register { As discussed, maybe move some of the documentation to the module and keep the documentation on register!() itself a bit smaller focusing on how to use the macro itself. Also, please add a pub use, re-export in rust/kernel/io.rs and refer to it as kernel::io::register. ^ permalink raw reply [flat|nested] 57+ messages in thread
* [PATCH v8 09/10] sample: rust: pci: use `register!` macro 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (7 preceding siblings ...) 2026-03-09 15:14 ` [PATCH v8 08/10] rust: io: add `register!` macro Alexandre Courbot @ 2026-03-09 15:14 ` Alexandre Courbot 2026-03-09 15:14 ` [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel " Alexandre Courbot ` (3 subsequent siblings) 12 siblings, 0 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:14 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng 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 | 84 +++++++++++++++++++++++++++++++---------- 1 file changed, 64 insertions(+), 20 deletions(-) diff --git a/samples/rust/rust_driver_pci.rs b/samples/rust/rust_driver_pci.rs index d3d4a7931deb..3f52cfcb7f4b 100644 --- a/samples/rust/rust_driver_pci.rs +++ b/samples/rust/rust_driver_pci.rs @@ -8,27 +8,58 @@ device::Bound, device::Core, devres::Devres, - io::Io, + io::{ + register::Array, + Io, // + }, + num::Bounded, pci, prelude::*, + register, sync::aref::ARef, // }; -struct Regs; +mod regs { + use super::*; -impl Regs { - const TEST: usize = 0x0; - const OFFSET: usize = 0x4; - const DATA: usize = 0x8; - const COUNT: usize = 0xC; - const END: usize = 0x10; + register! { + pub(super) TEST(u8) @ 0x0 { + 7:0 index => TestIndex; + } + + pub(super) OFFSET(u32) @ 0x4 { + 31:0 offset; + } + + pub(super) DATA(u8) @ 0x8 { + 7:0 data; + } + + pub(super) COUNT(u32) @ 0xC { + 31:0 count; + } + } + + pub(super) const END: usize = 0x10; } -type Bar0 = pci::Bar<{ Regs::END }>; +type Bar0 = pci::Bar<{ regs::END }>; #[derive(Copy, Clone, Debug)] struct TestIndex(u8); +impl From<Bounded<u8, 8>> for TestIndex { + fn from(value: Bounded<u8, 8>) -> Self { + Self(value.into()) + } +} + +impl From<TestIndex> for Bounded<u8, 8> { + fn from(value: TestIndex) -> Self { + value.0.into() + } +} + impl TestIndex { const NO_EVENTFD: Self = Self(0); } @@ -54,40 +85,53 @@ struct SampleDriver { impl SampleDriver { fn testdev(index: &TestIndex, bar: &Bar0) -> Result<u32> { // Select the test. - bar.write8(index.0, Regs::TEST); + bar.write_val(regs::TEST::zeroed().with_index(*index)); - let offset = bar.read32(Regs::OFFSET) as usize; - let data = bar.read8(Regs::DATA); + let offset = bar.read(regs::OFFSET).into_raw() as usize; + let data = bar.read(regs::DATA).into(); // Write `data` to `offset` to increase `count` by one. // // Note that we need `try_write8`, since `offset` can't be checked at compile-time. bar.try_write8(data, offset)?; - Ok(bar.read32(Regs::COUNT)) + Ok(bar.read(regs::COUNT).into()) } 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. + 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, "pci-testdev config space read8 rev ID: {:x}\n", - config.read8(0x8) + config.read(REVISION_ID).revision_id() ); dev_info!( pdev, "pci-testdev config space read16 vendor ID: {:x}\n", - config.read16(0) + config.read(VENDOR_ID).vendor_id() ); dev_info!( pdev, "pci-testdev config space read32 BAR 0: {:x}\n", - config.read32(0x10) + config.read(BAR::at(0)).value() ); } } @@ -111,7 +155,7 @@ fn probe(pdev: &pci::Device<Core>, info: &Self::IdInfo) -> impl PinInit<Self, Er pdev.set_master(); Ok(try_pin_init!(Self { - bar <- pdev.iomap_region_sized::<{ Regs::END }>(0, c"rust_driver_pci"), + bar <- pdev.iomap_region_sized::<{ regs::END }>(0, c"rust_driver_pci"), index: *info, _: { let bar = bar.access(pdev.as_ref())?; @@ -131,7 +175,7 @@ fn probe(pdev: &pci::Device<Core>, info: &Self::IdInfo) -> impl PinInit<Self, Er fn unbind(pdev: &pci::Device<Core>, this: Pin<&Self>) { if let Ok(bar) = this.bar.access(pdev.as_ref()) { // Reset pci-testdev by writing a new test index. - bar.write8(this.index.0, Regs::TEST); + bar.write_val(regs::TEST::zeroed().with_index(this.index)); } } } -- 2.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (8 preceding siblings ...) 2026-03-09 15:14 ` [PATCH v8 09/10] sample: rust: pci: use " Alexandre Courbot @ 2026-03-09 15:14 ` Alexandre Courbot 2026-03-09 15:43 ` Joel Fernandes 2026-03-09 18:05 ` [PATCH v8 00/10] rust: add " John Hubbard ` (2 subsequent siblings) 12 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-09 15:14 UTC (permalink / raw) To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel, Alexandre Courbot Replace the nova-core internal `register!` macro by the one defined in the `kernel` crate and remove our own private implementations. Signed-off-by: Alexandre Courbot <acourbot@nvidia.com> --- drivers/gpu/nova-core/falcon.rs | 330 ++++----- drivers/gpu/nova-core/falcon/gsp.rs | 25 +- drivers/gpu/nova-core/falcon/hal/ga102.rs | 70 +- drivers/gpu/nova-core/falcon/hal/tu102.rs | 12 +- drivers/gpu/nova-core/falcon/sec2.rs | 17 +- drivers/gpu/nova-core/fb.rs | 6 +- drivers/gpu/nova-core/fb/hal/ga100.rs | 37 +- drivers/gpu/nova-core/fb/hal/ga102.rs | 7 +- drivers/gpu/nova-core/fb/hal/tu102.rs | 17 +- drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 16 +- drivers/gpu/nova-core/gfw.rs | 11 +- drivers/gpu/nova-core/gpu.rs | 36 +- drivers/gpu/nova-core/gsp/boot.rs | 11 +- drivers/gpu/nova-core/gsp/cmdq.rs | 9 +- drivers/gpu/nova-core/regs.rs | 612 +++++++++-------- drivers/gpu/nova-core/regs/macros.rs | 739 --------------------- 16 files changed, 667 insertions(+), 1288 deletions(-) diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs index 7097a206ec3c..a2611e6bba90 100644 --- a/drivers/gpu/nova-core/falcon.rs +++ b/drivers/gpu/nova-core/falcon.rs @@ -13,7 +13,12 @@ DmaAddress, DmaMask, // }, - io::poll::read_poll_timeout, + io::{ + poll::read_poll_timeout, + register::{RegisterBase, WithBase}, + Io, + }, + num::Bounded, prelude::*, sync::aref::ARef, time::Delta, @@ -30,7 +35,6 @@ IntoSafeCast, // }, regs, - regs::macros::RegisterBase, // }; pub(crate) mod gsp; @@ -41,11 +45,11 @@ pub(crate) const MEM_BLOCK_ALIGNMENT: usize = 256; // 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) } } }; @@ -53,10 +57,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, @@ -65,16 +67,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, @@ -91,46 +93,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 @@ -144,16 +138,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, @@ -166,24 +160,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), } @@ -191,21 +183,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), } @@ -213,33 +203,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 { @@ -255,10 +236,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). @@ -266,14 +245,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, @@ -285,34 +264,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(()); @@ -321,13 +291,10 @@ fn from(value: FalconFbifMemType) -> Self { /// Trait defining the parameters of a given Falcon engine. /// -/// Each engine provides one base for `PFALCON` and `PFALCON2` registers. The `ID` constant is used -/// to identify a given Falcon instance with register I/O methods. +/// Each engine provides one base for `PFALCON` and `PFALCON2` registers. pub(crate) trait FalconEngine: Send + Sync + RegisterBase<PFalconBase> + RegisterBase<PFalcon2Base> + Sized { - /// Singleton of the engine, used to identify it with register I/O methods. - const ID: Self; } /// Represents a portion of the firmware to be loaded into a particular memory (e.g. IMEM or DMEM) @@ -521,8 +488,14 @@ 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); + bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| { + v.with_allow_phys_no_ctx(true) + }); + + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_DMACTL::zeroed(), + ); } /// Reset the controller, select the falcon core, and wait for memory scrubbing to complete. @@ -531,9 +504,10 @@ pub(crate) fn reset(&self, bar: &Bar0) -> Result { self.hal.select_core(self, bar)?; self.hal.reset_wait_mem_scrubbing(bar)?; - regs::NV_PFALCON_FALCON_RM::default() - .set_value(regs::NV_PMC_BOOT_0::read(bar).into()) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_RM::from(bar.read(regs::NV_PMC_BOOT_0).into_raw()), + ); Ok(()) } @@ -551,25 +525,27 @@ fn pio_wr_imem_slice(&self, bar: &Bar0, load_offsets: FalconPioImemLoadTarget<'_ return Err(EINVAL); } - regs::NV_PFALCON_FALCON_IMEMC::default() - .set_secure(load_offsets.secure) - .set_aincw(true) - .set_offs(load_offsets.dst_start) - .write(bar, &E::ID, Self::PIO_PORT); + bar.write( + WithBase::of::<E>().at(Self::PIO_PORT), + regs::NV_PFALCON_FALCON_IMEMC::zeroed() + .with_secure(load_offsets.secure) + .with_aincw(true) + .with_offs(load_offsets.dst_start), + ); for (n, block) in load_offsets.data.chunks(MEM_BLOCK_ALIGNMENT).enumerate() { let n = u16::try_from(n)?; let tag: u16 = load_offsets.start_tag.checked_add(n).ok_or(ERANGE)?; - regs::NV_PFALCON_FALCON_IMEMT::default().set_tag(tag).write( - bar, - &E::ID, - Self::PIO_PORT, + bar.write( + WithBase::of::<E>().at(Self::PIO_PORT), + regs::NV_PFALCON_FALCON_IMEMT::zeroed().with_tag(tag), ); for word in block.chunks_exact(4) { let w = [word[0], word[1], word[2], word[3]]; - regs::NV_PFALCON_FALCON_IMEMD::default() - .set_data(u32::from_le_bytes(w)) - .write(bar, &E::ID, Self::PIO_PORT); + bar.write( + WithBase::of::<E>().at(Self::PIO_PORT), + regs::NV_PFALCON_FALCON_IMEMD::zeroed().with_data(u32::from_le_bytes(w)), + ); } } @@ -586,16 +562,19 @@ fn pio_wr_dmem_slice(&self, bar: &Bar0, load_offsets: FalconPioDmemLoadTarget<'_ return Err(EINVAL); } - regs::NV_PFALCON_FALCON_DMEMC::default() - .set_aincw(true) - .set_offs(load_offsets.dst_start) - .write(bar, &E::ID, Self::PIO_PORT); + bar.write( + WithBase::of::<E>().at(Self::PIO_PORT), + regs::NV_PFALCON_FALCON_DMEMC::zeroed() + .with_aincw(true) + .with_offs(load_offsets.dst_start), + ); for word in load_offsets.data.chunks_exact(4) { let w = [word[0], word[1], word[2], word[3]]; - regs::NV_PFALCON_FALCON_DMEMD::default() - .set_data(u32::from_le_bytes(w)) - .write(bar, &E::ID, Self::PIO_PORT); + bar.write( + WithBase::of::<E>().at(Self::PIO_PORT), + regs::NV_PFALCON_FALCON_DMEMD::zeroed().with_data(u32::from_le_bytes(w)), + ); } Ok(()) @@ -607,11 +586,14 @@ pub(crate) fn pio_load<F: FalconFirmware<Target = E> + FalconPioLoadable>( bar: &Bar0, fw: &F, ) -> Result { - regs::NV_PFALCON_FBIF_CTL::read(bar, &E::ID) - .set_allow_phys_no_ctx(true) - .write(bar, &E::ID); + bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| { + v.with_allow_phys_no_ctx(true) + }); - regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_DMACTL::zeroed(), + ); if let Some(imem_ns) = fw.imem_ns_load_params() { self.pio_wr_imem_slice(bar, imem_ns)?; @@ -623,9 +605,10 @@ pub(crate) fn pio_load<F: FalconFirmware<Target = E> + FalconPioLoadable>( self.hal.program_brom(self, bar, &fw.brom_params())?; - regs::NV_PFALCON_FALCON_BOOTVEC::default() - .set_value(fw.boot_addr()) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_BOOTVEC::zeroed().with_value(fw.boot_addr()), + ); Ok(()) } @@ -694,36 +677,42 @@ fn dma_wr( // Set up the base source DMA address. - regs::NV_PFALCON_FALCON_DMATRFBASE::default() - // 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) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_DMATRFBASE::zeroed().with_base( + // 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`. + (dma_start >> 8) as u32, + ), + ); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_DMATRFBASE1::zeroed().try_with_base(dma_start >> 40)?, + ); - let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::default() - .set_size(DmaTrfCmdSize::Size256B) + let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::zeroed() + .with_size(DmaTrfCmdSize::Size256B) .with_falcon_mem(target_mem); 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) - .write(bar, &E::ID); - regs::NV_PFALCON_FALCON_DMATRFFBOFFS::default() - .set_offs(src_start + pos) - .write(bar, &E::ID); - cmd.write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed() + .try_with_offs(load_offsets.dst_start + pos)?, + ); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed().with_offs(src_start + pos), + ); + + bar.write(WithBase::of::<E>(), cmd); // Wait for the transfer to complete. // TIMEOUT: arbitrarily large value, no DMA transfer to the falcon's small memories // should ever take that long. read_poll_timeout( - || Ok(regs::NV_PFALCON_FALCON_DMATRFCMD::read(bar, &E::ID)), + || Ok(bar.read(regs::NV_PFALCON_FALCON_DMATRFCMD::of::<E>())), |r| r.idle(), Delta::ZERO, Delta::from_secs(2), @@ -744,9 +733,9 @@ fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>( let dma_obj = DmaObject::from_data(dev, fw.as_slice())?; self.dma_reset(bar); - regs::NV_PFALCON_FBIF_TRANSCFG::update(bar, &E::ID, 0, |v| { - v.set_target(FalconFbifTarget::CoherentSysmem) - .set_mem_type(FalconFbifMemType::Physical) + bar.update(regs::NV_PFALCON_FBIF_TRANSCFG::of::<E>().at(0), |v| { + v.with_target(FalconFbifTarget::CoherentSysmem) + .with_mem_type(FalconFbifMemType::Physical) }); self.dma_wr( @@ -760,9 +749,10 @@ fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>( self.hal.program_brom(self, bar, &fw.brom_params())?; // Set `BootVec` to start of non-secure code. - regs::NV_PFALCON_FALCON_BOOTVEC::default() - .set_value(fw.boot_addr()) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_BOOTVEC::zeroed().with_value(fw.boot_addr()), + ); Ok(()) } @@ -771,7 +761,7 @@ fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>( pub(crate) fn wait_till_halted(&self, bar: &Bar0) -> Result<()> { // TIMEOUT: arbitrarily large value, firmwares should complete in less than 2 seconds. read_poll_timeout( - || Ok(regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID)), + || Ok(bar.read(regs::NV_PFALCON_FALCON_CPUCTL::of::<E>())), |r| r.halted(), Delta::ZERO, Delta::from_secs(2), @@ -782,13 +772,18 @@ 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() - .set_startcpu(true) - .write(bar, &E::ID), - false => regs::NV_PFALCON_FALCON_CPUCTL::default() - .set_startcpu(true) - .write(bar, &E::ID), + match bar + .read(regs::NV_PFALCON_FALCON_CPUCTL::of::<E>()) + .alias_en() + { + true => bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::zeroed().with_startcpu(true), + ), + false => bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_CPUCTL::zeroed().with_startcpu(true), + ), } Ok(()) @@ -797,26 +792,30 @@ 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() - .set_value(mbox0) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_MAILBOX0::zeroed().with_value(mbox0), + ); } if let Some(mbox1) = mbox1 { - regs::NV_PFALCON_FALCON_MAILBOX1::default() - .set_value(mbox1) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1), + ); } } /// Reads the value from `mbox0` register. pub(crate) fn read_mailbox0(&self, bar: &Bar0) -> u32 { - regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, &E::ID).value() + bar.read(regs::NV_PFALCON_FALCON_MAILBOX0::of::<E>()) + .value() } /// Reads the value from `mbox1` register. pub(crate) fn read_mailbox1(&self, bar: &Bar0) -> u32 { - regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, &E::ID).value() + bar.read(regs::NV_PFALCON_FALCON_MAILBOX1::of::<E>()) + .value() } /// Reads values from both mailbox registers. @@ -881,8 +880,9 @@ pub(crate) fn load<F: FalconFirmware<Target = E> + FalconDmaLoadable>( /// 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() - .set_value(app_version) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON_FALCON_OS::zeroed().with_value(app_version), + ); } } diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs index 67edef3636c1..41b9fa49414c 100644 --- a/drivers/gpu/nova-core/falcon/gsp.rs +++ b/drivers/gpu/nova-core/falcon/gsp.rs @@ -1,7 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 use kernel::{ - io::poll::read_poll_timeout, + io::{ + poll::read_poll_timeout, + register::{RegisterBase, WithBase}, + Io, + }, prelude::*, time::Delta, // }; @@ -14,13 +18,9 @@ PFalcon2Base, PFalconBase, // }, - regs::{ - self, - macros::RegisterBase, // - }, + regs, }; -/// Type specifying the `Gsp` falcon engine. Cannot be instantiated. pub(crate) struct Gsp(()); impl RegisterBase<PFalconBase> for Gsp { @@ -31,23 +31,22 @@ impl RegisterBase<PFalcon2Base> for Gsp { const BASE: usize = 0x00111000; } -impl FalconEngine for Gsp { - const ID: Self = Gsp(()); -} +impl FalconEngine for Gsp {} 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() - .set_swgen0(true) - .write(bar, &Gsp::ID); + bar.write( + WithBase::of::<Gsp>(), + regs::NV_PFALCON_FALCON_IRQSCLR::zeroed().with_swgen0(true), + ); } /// Checks if GSP reload/resume has completed during the boot process. pub(crate) fn check_reload_completed(&self, bar: &Bar0, timeout: Delta) -> Result<bool> { read_poll_timeout( - || Ok(regs::NV_PGC6_BSI_SECURE_SCRATCH_14::read(bar)), + || Ok(bar.read(regs::NV_PGC6_BSI_SECURE_SCRATCH_14)), |val| val.boot_stage_3_handoff(), Delta::ZERO, timeout, diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs index 8f62df10da0a..e8c8414c90f2 100644 --- a/drivers/gpu/nova-core/falcon/hal/ga102.rs +++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs @@ -4,7 +4,11 @@ use kernel::{ device, - io::poll::read_poll_timeout, + io::{ + poll::read_poll_timeout, + register::{Array, WithBase}, + Io, + }, prelude::*, time::Delta, // }; @@ -25,15 +29,16 @@ use super::FalconHal; fn select_core_ga102<E: FalconEngine>(bar: &Bar0) -> Result { - let bcr_ctrl = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID); + let bcr_ctrl = bar.read(regs::NV_PRISCV_RISCV_BCR_CTRL::of::<E>()); if bcr_ctrl.core_select() != PeregrineCoreSelect::Falcon { - regs::NV_PRISCV_RISCV_BCR_CTRL::default() - .set_core_select(PeregrineCoreSelect::Falcon) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>(), + regs::NV_PRISCV_RISCV_BCR_CTRL::zeroed().with_core_select(PeregrineCoreSelect::Falcon), + ); // TIMEOUT: falcon core should take less than 10ms to report being enabled. read_poll_timeout( - || Ok(regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID)), + || Ok(bar.read(regs::NV_PRISCV_RISCV_BCR_CTRL::of::<E>())), |r| r.valid(), Delta::ZERO, Delta::from_millis(10), @@ -60,34 +65,43 @@ 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 { - regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::read(bar, ucode_idx).data() + let reg_fuse_version: u16 = if engine_id_mask & 0x0001 != 0 { + bar.read(regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::at(ucode_idx)) + .data() } else if engine_id_mask & 0x0004 != 0 { - regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::read(bar, ucode_idx).data() + bar.read(regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::at(ucode_idx)) + .data() } else if engine_id_mask & 0x0400 != 0 { - regs::NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION::read(bar, ucode_idx).data() + bar.read(regs::NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION::at(ucode_idx)) + .data() } else { dev_err!(dev, "unexpected engine_id_mask {:#x}\n", 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() - .set_value(params.pkc_data_offset) - .write(bar, &E::ID, 0); - regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::default() - .set_value(u32::from(params.engine_id_mask)) - .write(bar, &E::ID); - regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::default() - .set_ucode_id(params.ucode_id) - .write(bar, &E::ID); - regs::NV_PFALCON2_FALCON_MOD_SEL::default() - .set_algo(FalconModSelAlgo::Rsa3k) - .write(bar, &E::ID); + bar.write( + WithBase::of::<E>().at(0), + regs::NV_PFALCON2_FALCON_BROM_PARAADDR::zeroed().with_value(params.pkc_data_offset), + ); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::zeroed() + .with_value(u32::from(params.engine_id_mask)), + ); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::zeroed().with_ucode_id(params.ucode_id), + ); + bar.write( + WithBase::of::<E>(), + regs::NV_PFALCON2_FALCON_MOD_SEL::zeroed().with_algo(FalconModSelAlgo::Rsa3k), + ); Ok(()) } @@ -120,14 +134,14 @@ fn program_brom(&self, _falcon: &Falcon<E>, bar: &Bar0, params: &FalconBromParam } fn is_riscv_active(&self, bar: &Bar0) -> bool { - let cpuctl = regs::NV_PRISCV_RISCV_CPUCTL::read(bar, &E::ID); - cpuctl.active_stat() + bar.read(regs::NV_PRISCV_RISCV_CPUCTL::of::<E>()) + .active_stat() } fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result { // TIMEOUT: memory scrubbing should complete in less than 20ms. read_poll_timeout( - || Ok(regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID)), + || Ok(bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<E>())), |r| r.mem_scrubbing_done(), Delta::ZERO, Delta::from_millis(20), @@ -136,12 +150,12 @@ fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result { } fn reset_eng(&self, bar: &Bar0) -> Result { - let _ = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID); + let _ = bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<E>()); // According to OpenRM's `kflcnPreResetWait_GA102` documentation, HW sometimes does not set // RESET_READY so a non-failing timeout is used. let _ = read_poll_timeout( - || Ok(regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID)), + || Ok(bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<E>())), |r| r.reset_ready(), Delta::ZERO, Delta::from_micros(150), diff --git a/drivers/gpu/nova-core/falcon/hal/tu102.rs b/drivers/gpu/nova-core/falcon/hal/tu102.rs index 7de6f24cc0a0..c7a90266cb44 100644 --- a/drivers/gpu/nova-core/falcon/hal/tu102.rs +++ b/drivers/gpu/nova-core/falcon/hal/tu102.rs @@ -3,7 +3,11 @@ use core::marker::PhantomData; use kernel::{ - io::poll::read_poll_timeout, + io::{ + poll::read_poll_timeout, + register::WithBase, + Io, // + }, prelude::*, time::Delta, // }; @@ -49,14 +53,14 @@ fn program_brom(&self, _falcon: &Falcon<E>, _bar: &Bar0, _params: &FalconBromPar } fn is_riscv_active(&self, bar: &Bar0) -> bool { - let cpuctl = regs::NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS::read(bar, &E::ID); - cpuctl.active_stat() + bar.read(regs::NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS::of::<E>()) + .active_stat() } fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result { // TIMEOUT: memory scrubbing should complete in less than 10ms. read_poll_timeout( - || Ok(regs::NV_PFALCON_FALCON_DMACTL::read(bar, &E::ID)), + || Ok(bar.read(regs::NV_PFALCON_FALCON_DMACTL::of::<E>())), |r| r.mem_scrubbing_done(), Delta::ZERO, Delta::from_millis(10), diff --git a/drivers/gpu/nova-core/falcon/sec2.rs b/drivers/gpu/nova-core/falcon/sec2.rs index b57d362e576a..91ec7d49c1f5 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. @@ -20,6 +19,4 @@ impl RegisterBase<PFalcon2Base> for Sec2 { const BASE: usize = 0x00841000; } -impl FalconEngine for Sec2 { - const ID: Self = Sec2(()); -} +impl FalconEngine for Sec2 {} diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs index c62abcaed547..6b392588f766 100644 --- a/drivers/gpu/nova-core/fb.rs +++ b/drivers/gpu/nova-core/fb.rs @@ -4,6 +4,7 @@ use kernel::{ device, + io::Io, prelude::*, ptr::{ Alignable, @@ -134,7 +135,10 @@ pub(crate) fn new(chipset: Chipset, bar: &Bar0, gsp_fw: &GspFirmware) -> Result< let base = fb.end - NV_PRAMIN_SIZE; if hal.supports_display(bar) { - match regs::NV_PDISP_VGA_WORKSPACE_BASE::read(bar).vga_workspace_addr() { + match bar + .read(regs::NV_PDISP_VGA_WORKSPACE_BASE) + .vga_workspace_addr() + { Some(addr) => { if addr < base { const VBIOS_WORKSPACE_SIZE: u64 = usize_as_u64(SZ_128K); diff --git a/drivers/gpu/nova-core/fb/hal/ga100.rs b/drivers/gpu/nova-core/fb/hal/ga100.rs index e0acc41aa7cd..bda343e73399 100644 --- a/drivers/gpu/nova-core/fb/hal/ga100.rs +++ b/drivers/gpu/nova-core/fb/hal/ga100.rs @@ -1,6 +1,10 @@ // SPDX-License-Identifier: GPL-2.0 -use kernel::prelude::*; +use kernel::{ + io::Io, + num::Bounded, + prelude::*, // +}; use crate::{ driver::Bar0, @@ -13,26 +17,31 @@ struct Ga100; pub(super) fn read_sysmem_flush_page_ga100(bar: &Bar0) -> u64 { - u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::read(bar).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT - | u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::read(bar).adr_63_40()) + u64::from(bar.read(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT + | u64::from(bar.read(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI).adr_63_40()) << FLUSH_SYSMEM_ADDR_SHIFT_HI } 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) - .write(bar); - regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::default() - // CAST: `as u32` is used on purpose since we want to strip the upper bits that have been - // written to `NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI`. - .set_adr_39_08((addr >> FLUSH_SYSMEM_ADDR_SHIFT) as u32) - .write(bar); + bar.write_val( + regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::zeroed().with_adr_63_40( + Bounded::<u64, _>::from(addr) + .shr::<FLUSH_SYSMEM_ADDR_SHIFT_HI, _>() + .cast(), + ), + ); + + bar.write_val( + 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`. + .with_adr_39_08((addr >> FLUSH_SYSMEM_ADDR_SHIFT) as u32), + ); } pub(super) fn display_enabled_ga100(bar: &Bar0) -> bool { - !regs::ga100::NV_FUSE_STATUS_OPT_DISPLAY::read(bar).display_disabled() + !bar.read(regs::ga100::NV_FUSE_STATUS_OPT_DISPLAY) + .display_disabled() } /// Shift applied to the sysmem address before it is written into diff --git a/drivers/gpu/nova-core/fb/hal/ga102.rs b/drivers/gpu/nova-core/fb/hal/ga102.rs index 734605905031..4b9f0f74d0e7 100644 --- a/drivers/gpu/nova-core/fb/hal/ga102.rs +++ b/drivers/gpu/nova-core/fb/hal/ga102.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 -use kernel::prelude::*; +use kernel::{ + io::Io, + prelude::*, // +}; use crate::{ driver::Bar0, @@ -9,7 +12,7 @@ }; fn vidmem_size_ga102(bar: &Bar0) -> u64 { - regs::NV_USABLE_FB_SIZE_IN_MB::read(bar).usable_fb_size() + bar.read(regs::NV_USABLE_FB_SIZE_IN_MB).usable_fb_size() } struct Ga102; diff --git a/drivers/gpu/nova-core/fb/hal/tu102.rs b/drivers/gpu/nova-core/fb/hal/tu102.rs index eec984f4e816..2833c1c57ac9 100644 --- a/drivers/gpu/nova-core/fb/hal/tu102.rs +++ b/drivers/gpu/nova-core/fb/hal/tu102.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-2.0 -use kernel::prelude::*; +use kernel::{ + io::Io, + prelude::*, // +}; use crate::{ driver::Bar0, @@ -13,7 +16,7 @@ pub(super) const FLUSH_SYSMEM_ADDR_SHIFT: u32 = 8; pub(super) fn read_sysmem_flush_page_gm107(bar: &Bar0) -> u64 { - u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::read(bar).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT + u64::from(bar.read(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT } pub(super) fn write_sysmem_flush_page_gm107(bar: &Bar0, addr: u64) -> Result { @@ -21,18 +24,18 @@ 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() - .set_adr_39_08(addr) - .write(bar) + bar.write_val(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::zeroed().with_adr_39_08(addr)) }) } pub(super) fn display_enabled_gm107(bar: &Bar0) -> bool { - !regs::gm107::NV_FUSE_STATUS_OPT_DISPLAY::read(bar).display_disabled() + !bar.read(regs::gm107::NV_FUSE_STATUS_OPT_DISPLAY) + .display_disabled() } pub(super) fn vidmem_size_gp102(bar: &Bar0) -> u64 { - regs::NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE::read(bar).usable_fb_size() + bar.read(regs::NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE) + .usable_fb_size() } struct Tu102; diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs index 342dba59b2f9..9a16d328685b 100644 --- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs +++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs @@ -12,6 +12,10 @@ self, Device, // }, + io::{ + register::WithBase, // + Io, + }, prelude::*, ptr::{ Alignable, @@ -33,7 +37,6 @@ Falcon, FalconBromParams, FalconDmaLoadable, - FalconEngine, FalconFbifMemType, FalconFbifTarget, FalconFirmware, @@ -288,13 +291,12 @@ pub(crate) fn run( .inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?; // Configure DMA index for the bootloader to fetch the FWSEC firmware from system memory. - regs::NV_PFALCON_FBIF_TRANSCFG::try_update( - bar, - &Gsp::ID, - usize::from_safe_cast(self.dmem_desc.ctx_dma), + bar.try_update( + regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>() + .at(usize::from_safe_cast(self.dmem_desc.ctx_dma)), |v| { - v.set_target(FalconFbifTarget::CoherentSysmem) - .set_mem_type(FalconFbifMemType::Physical) + v.with_target(FalconFbifTarget::CoherentSysmem) + .with_mem_type(FalconFbifMemType::Physical) }, )?; diff --git a/drivers/gpu/nova-core/gfw.rs b/drivers/gpu/nova-core/gfw.rs index 9121f400046d..fb75dd10a172 100644 --- a/drivers/gpu/nova-core/gfw.rs +++ b/drivers/gpu/nova-core/gfw.rs @@ -19,7 +19,10 @@ //! Note that the devinit sequence also needs to run during suspend/resume. use kernel::{ - io::poll::read_poll_timeout, + io::{ + poll::read_poll_timeout, + Io, // + }, prelude::*, time::Delta, // }; @@ -58,9 +61,11 @@ pub(crate) fn wait_gfw_boot_completion(bar: &Bar0) -> Result { Ok( // Check that FWSEC has lowered its protection level before reading the GFW_BOOT // status. - regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK::read(bar) + bar.read(regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK) .read_protection_level0() - && regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT::read(bar).completed(), + && bar + .read(regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT) + .completed(), ) }, |&gfw_booted| gfw_booted, diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs index 8579d632e717..bb1c6bf88657 100644 --- a/drivers/gpu/nova-core/gpu.rs +++ b/drivers/gpu/nova-core/gpu.rs @@ -4,6 +4,8 @@ device, devres::Devres, fmt, + io::Io, + num::Bounded, pci, prelude::*, sync::Arc, // @@ -129,24 +131,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), @@ -155,23 +152,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(), } } } @@ -208,13 +208,13 @@ fn new(dev: &device::Device, bar: &Bar0) -> Result<Spec> { // from an earlier (pre-Fermi) era, and then using boot42 to precisely identify the GPU. // Somewhere in the Rubin timeframe, boot0 will no longer have space to add new GPU IDs. - let boot0 = regs::NV_PMC_BOOT_0::read(bar); + let boot0 = bar.read(regs::NV_PMC_BOOT_0); if boot0.is_older_than_fermi() { return Err(ENODEV); } - let boot42 = regs::NV_PMC_BOOT_42::read(bar); + let boot42 = bar.read(regs::NV_PMC_BOOT_42); Spec::try_from(boot42).inspect_err(|_| { dev_err!(dev, "Unsupported chipset: {}\n", boot42); }) diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs index 9a00ddb922ac..d9d999519183 100644 --- a/drivers/gpu/nova-core/gsp/boot.rs +++ b/drivers/gpu/nova-core/gsp/boot.rs @@ -5,6 +5,7 @@ dma::CoherentAllocation, dma_write, io::poll::read_poll_timeout, + io::Io, pci, prelude::*, time::Delta, // @@ -57,7 +58,7 @@ fn run_fwsec_frts( ) -> Result<()> { // Check that the WPR2 region does not already exists - if it does, we cannot run // FWSEC-FRTS until the GPU is reset. - if regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI::read(bar).higher_bound() != 0 { + if bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound() != 0 { dev_err!( dev, "WPR2 region already exists - GPU needs to be reset to proceed\n" @@ -87,7 +88,9 @@ fn run_fwsec_frts( } // SCRATCH_E contains the error code for FWSEC-FRTS. - let frts_status = regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR::read(bar).frts_err_code(); + let frts_status = bar + .read(regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR) + .frts_err_code(); if frts_status != 0 { dev_err!( dev, @@ -100,8 +103,8 @@ fn run_fwsec_frts( // Check that the WPR2 region has been created as we requested. let (wpr2_lo, wpr2_hi) = ( - regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO::read(bar).lower_bound(), - regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI::read(bar).higher_bound(), + bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO).lower_bound(), + bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound(), ); match (wpr2_lo, wpr2_hi) { diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs index 87dbbd6d1be9..04a3ba4b9e05 100644 --- a/drivers/gpu/nova-core/gsp/cmdq.rs +++ b/drivers/gpu/nova-core/gsp/cmdq.rs @@ -15,7 +15,10 @@ DmaAddress, // }, dma_write, - io::poll::read_poll_timeout, + io::{ + poll::read_poll_timeout, + Io, // + }, prelude::*, sync::aref::ARef, time::Delta, @@ -488,9 +491,7 @@ fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 { /// Notifies the GSP that we have updated the command queue pointers. fn notify_gsp(bar: &Bar0) { - regs::NV_PGSP_QUEUE_HEAD::default() - .set_address(0) - .write(bar); + bar.write_val(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(0u32)); } /// Sends `command` to the GSP. diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs index 53f412f0ca32..d65a7ceffd1b 100644 --- a/drivers/gpu/nova-core/regs.rs +++ b/drivers/gpu/nova-core/regs.rs @@ -1,13 +1,10 @@ // 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::{ + io::{ + register::WithBase, + Io, // + }, prelude::*, time, // }; @@ -35,20 +32,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 = $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 = $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". @@ -56,13 +97,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> { @@ -76,8 +110,8 @@ pub(crate) fn chipset(self) -> Result<Chipset> { /// Returns the raw architecture value from the register. fn architecture_raw(self) -> u8 { - ((self.0 >> Self::ARCHITECTURE_RANGE.start()) & ((1 << Self::ARCHITECTURE_RANGE.len()) - 1)) - as u8 + ((self.inner >> Self::ARCHITECTURE_RANGE.start()) + & ((1 << Self::ARCHITECTURE_RANGE.len()) - 1)) as u8 } } @@ -86,7 +120,7 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result { write!( f, "boot42 = 0x{:08x} (architecture 0x{:x}, implementation 0x{:x})", - self.0, + self.inner, self.architecture_raw(), self.implementation() ) @@ -95,35 +129,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. @@ -140,10 +189,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 { @@ -151,10 +196,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. /// @@ -173,29 +214,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. @@ -204,16 +246,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. @@ -224,10 +267,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. @@ -244,73 +291,162 @@ 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; -}); - -// PFALCON - -register!(NV_PFALCON_FALCON_IRQSCLR @ PFalconBase[0x00000004] { - 4:4 halt as bool; - 6:6 swgen0 as bool; -}); - -register!(NV_PFALCON_FALCON_MAILBOX0 @ PFalconBase[0x00000040] { - 31:0 value as u32; -}); - -register!(NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase[0x00000044] { - 31:0 value as 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; -}); - -register!(NV_PFALCON_FALCON_RM @ PFalconBase[0x00000084] { - 31:0 value as 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+)"; -}); - -impl NV_PFALCON_FALCON_HWCFG2 { - /// Returns `true` if memory scrubbing is completed. - pub(crate) fn mem_scrubbing_done(self) -> bool { - !self.mem_scrubbing() + NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION[NV_FUSE_OPT_FPF_SIZE] @ 0x008241c0 { + 15:0 data; } } -register!(NV_PFALCON_FALCON_CPUCTL @ PFalconBase[0x00000100] { - 1:1 startcpu as bool; - 4:4 halted as bool; - 6:6 alias_en as bool; -}); +// PFALCON -register!(NV_PFALCON_FALCON_BOOTVEC @ PFalconBase[0x00000104] { - 31:0 value as u32; -}); +nv_reg! { + NV_PFALCON_FALCON_IRQSCLR @ PFalconBase + 0x00000004 { + 4:4 halt => bool; + 6:6 swgen0 => bool; + } -register!(NV_PFALCON_FALCON_DMACTL @ PFalconBase[0x0000010c] { - 0:0 require_ctx as bool; - 1:1 dmem_scrubbing as bool; - 2:2 imem_scrubbing as bool; - 6:3 dmaq_num as u8; - 7:7 secure_stat as bool; -}); + NV_PFALCON_FALCON_MAILBOX0 @ PFalconBase + 0x00000040 { + 31:0 value => u32; + } + + NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase + 0x00000044 { + 31:0 value => u32; + } + + /// Used to store version information about the firmware running + /// on the Falcon processor. + NV_PFALCON_FALCON_OS @ PFalconBase + 0x00000080 { + 31:0 value => u32; + } + + NV_PFALCON_FALCON_RM @ PFalconBase + 0x00000084 { + 31:0 value => u32; + } + + 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; + } + + /// IMEM access control register. Up to 4 ports are available for IMEM access. + NV_PFALCON_FALCON_IMEMC[4, stride = 16] @ PFalconBase + 0x00000180 { + /// IMEM block and word offset. + 15:0 offs; + /// Auto-increment on write. + 24:24 aincw => bool; + /// Access secure IMEM. + 28:28 secure => bool; + } + + /// IMEM data register. Reading/writing this register accesses IMEM at the address + /// specified by the corresponding IMEMC register. + NV_PFALCON_FALCON_IMEMD[4, stride = 16] @ PFalconBase + 0x00000184 { + 31:0 data; + } + + /// IMEM tag register. Used to set the tag for the current IMEM block. + NV_PFALCON_FALCON_IMEMT[4, stride = 16] @ PFalconBase + 0x00000188 { + 15:0 tag; + } + + /// DMEM access control register. Up to 8 ports are available for DMEM access. + NV_PFALCON_FALCON_DMEMC[8, stride = 8] @ PFalconBase + 0x000001c0 { + /// DMEM block and word offset. + 15:0 offs; + /// Auto-increment on write. + 24:24 aincw => bool; + } + + /// DMEM data register. Reading/writing this register accesses DMEM at the address + /// specified by the corresponding DMEMC register. + NV_PFALCON_FALCON_DMEMD[8, stride = 8] @ PFalconBase + 0x000001c4 { + 31:0 data; + } + + /// 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_DMACTL { /// Returns `true` if memory scrubbing is completed. @@ -319,147 +455,81 @@ pub(crate) fn mem_scrubbing_done(self) -> 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; -}); - impl NV_PFALCON_FALCON_DMATRFCMD { /// Programs the `imem` and `sec` fields for the given FalconMem pub(crate) fn with_falcon_mem(self, mem: FalconMem) -> Self { - self.set_imem(mem != FalconMem::Dmem) - .set_sec(if mem == FalconMem::ImemSecure { 1 } else { 0 }) + let this = self.with_imem(mem != FalconMem::Dmem); + + match mem { + FalconMem::ImemSecure => this.with_const_sec::<1>(), + _ => this.with_const_sec::<0>(), + } } } -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; -}); - -// IMEM access control register. Up to 4 ports are available for IMEM access. -register!(NV_PFALCON_FALCON_IMEMC @ PFalconBase[0x00000180[4; 16]] { - 15:0 offs as u16, "IMEM block and word offset"; - 24:24 aincw as bool, "Auto-increment on write"; - 28:28 secure as bool, "Access secure IMEM"; -}); - -// IMEM data register. Reading/writing this register accesses IMEM at the address -// specified by the corresponding IMEMC register. -register!(NV_PFALCON_FALCON_IMEMD @ PFalconBase[0x00000184[4; 16]] { - 31:0 data as u32; -}); - -// IMEM tag register. Used to set the tag for the current IMEM block. -register!(NV_PFALCON_FALCON_IMEMT @ PFalconBase[0x00000188[4; 16]] { - 15:0 tag as u16; -}); - -// DMEM access control register. Up to 8 ports are available for DMEM access. -register!(NV_PFALCON_FALCON_DMEMC @ PFalconBase[0x000001c0[8; 8]] { - 15:0 offs as u16, "DMEM block and word offset"; - 24:24 aincw as bool, "Auto-increment on write"; -}); - -// DMEM data register. Reading/writing this register accesses DMEM at the address -// specified by the corresponding DMEMC register. -register!(NV_PFALCON_FALCON_DMEMD @ PFalconBase[0x000001c4[8; 8]] { - 31:0 data as u32; -}); - -// 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; -}); - impl NV_PFALCON_FALCON_ENGINE { /// Resets the falcon pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) { - Self::read(bar, &E::ID).set_reset(true).write(bar, &E::ID); + bar.update(Self::of::<E>(), |r| r.with_reset(true)); // TIMEOUT: falcon engine should not take more than 10us to reset. time::delay::fsleep(time::Delta::from_micros(10)); - Self::read(bar, &E::ID).set_reset(false).write(bar, &E::ID); + bar.update(Self::of::<E>(), |r| r.with_reset(false)); } } -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; -}); +impl NV_PFALCON_FALCON_HWCFG2 { + /// Returns `true` if memory scrubbing is completed. + pub(crate) fn mem_scrubbing_done(self) -> bool { + !self.mem_scrubbing() + } +} /* 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 -// RISC-V status register for debug (Turing and GA100 only). -// Reflects current RISC-V core status. -register!(NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS @ PFalcon2Base[0x00000240] { - 0:0 active_stat as bool, "RISC-V core active/inactive status"; -}); - // GA102 and later -register!(NV_PRISCV_RISCV_CPUCTL @ PFalcon2Base[0x00000388] { - 0:0 halted as bool; - 7:7 active_stat as bool; -}); +nv_reg! { + // RISC-V status register for debug (Turing and GA100 only). + // Reflects current RISC-V core status. + NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS @ PFalcon2Base + 0x00000240 { + // RISC-V core active/inactive status. + 0:0 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_CPUCTL @ PFalcon2Base + 0x00000388 { + 0:0 halted => bool; + 7:7 active_stat => 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. @@ -467,15 +537,19 @@ pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) { 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.53.0 ^ permalink raw reply related [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 15:14 ` [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel " Alexandre Courbot @ 2026-03-09 15:43 ` Joel Fernandes 2026-03-09 17:34 ` John Hubbard 2026-03-10 2:18 ` Alexandre Courbot 0 siblings, 2 replies; 57+ messages in thread From: Joel Fernandes @ 2026-03-09 15:43 UTC (permalink / raw) To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/2026 11:14 AM, Alexandre Courbot wrote: > > @@ -797,26 +792,30 @@ 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() > - .set_value(mbox0) > - .write(bar, &E::ID); > + bar.write( > + WithBase::of::<E>(), > + regs::NV_PFALCON_FALCON_MAILBOX0::zeroed().with_value(mbox0), > + ); > } > > if let Some(mbox1) = mbox1 { > - regs::NV_PFALCON_FALCON_MAILBOX1::default() > - .set_value(mbox1) > - .write(bar, &E::ID); > + bar.write( > + WithBase::of::<E>(), > + regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1), > + ); > } > } TBH, this is quite a readability hit, the previous was much more readable IMHO. Why doesn't the previous caller-side syntax still work? thanks, -- Joel Fernandes ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 15:43 ` Joel Fernandes @ 2026-03-09 17:34 ` John Hubbard 2026-03-09 17:51 ` Joel Fernandes 2026-03-09 18:04 ` Danilo Krummrich 2026-03-10 2:18 ` Alexandre Courbot 1 sibling, 2 replies; 57+ messages in thread From: John Hubbard @ 2026-03-09 17:34 UTC (permalink / raw) To: Joel Fernandes, Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/26 8:43 AM, Joel Fernandes wrote: > > On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >> >> @@ -797,26 +792,30 @@ 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() >> - .set_value(mbox0) >> - .write(bar, &E::ID); >> + bar.write( >> + WithBase::of::<E>(), >> + regs::NV_PFALCON_FALCON_MAILBOX0::zeroed().with_value(mbox0), >> + ); >> } >> >> if let Some(mbox1) = mbox1 { >> - regs::NV_PFALCON_FALCON_MAILBOX1::default() >> - .set_value(mbox1) >> - .write(bar, &E::ID); >> + bar.write( >> + WithBase::of::<E>(), >> + regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1), >> + ); >> } >> } > > TBH, this is quite a readability hit, the previous was much more readable IMHO. Yes, I think so too, except that "WithBase::of::<E>" makes a lot more sense on-screen, than "&E::ID". The latter just looks quite random, so this part is an improvement. Let's break down the remaining troublesome part a bit: regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1) * "regs::" can be omitted with a "use" statement, right? * ::zeroed() maybe should be the default behavior here, and then it could also be omitted? * .with_value(mbox1) I'm sure this is necessary, but the construction is unfortunately much less clear than .write(value)! Thoughts? thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 17:34 ` John Hubbard @ 2026-03-09 17:51 ` Joel Fernandes 2026-03-09 18:01 ` John Hubbard 2026-03-09 18:04 ` Danilo Krummrich 1 sibling, 1 reply; 57+ messages in thread From: Joel Fernandes @ 2026-03-09 17:51 UTC (permalink / raw) To: John Hubbard, Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/2026 1:34 PM, John Hubbard wrote: > On 3/9/26 8:43 AM, Joel Fernandes wrote: >> >> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >>> >>> @@ -797,26 +792,30 @@ 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() >>> - .set_value(mbox0) >>> - .write(bar, &E::ID); >>> + bar.write( >>> + WithBase::of::<E>(), >>> + regs::NV_PFALCON_FALCON_MAILBOX0::zeroed().with_value(mbox0), >>> + ); >>> } >>> >>> if let Some(mbox1) = mbox1 { >>> - regs::NV_PFALCON_FALCON_MAILBOX1::default() >>> - .set_value(mbox1) >>> - .write(bar, &E::ID); >>> + bar.write( >>> + WithBase::of::<E>(), >>> + regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1), >>> + ); >>> } >>> } >> >> TBH, this is quite a readability hit, the previous was much more readable IMHO. > > Yes, I think so too, except that "WithBase::of::<E>" makes a lot more > sense on-screen, than "&E::ID". The latter just looks quite random, > so this part is an improvement. > > Let's break down the remaining troublesome part a bit: > > regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1) > > * "regs::" can be omitted with a "use" statement, right? > > * ::zeroed() maybe should be the default behavior here, and then > it could also be omitted? The issue with omitting it is it still needs a constructor? so if not zeroed() it would still need ::default() I think. > > * .with_value(mbox1) I'm sure this is necessary, but the construction > is unfortunately much less clear than .write(value)! Thoughts? yeah, also perhaps the whole thing should have a macro! to de-sugar. The patterns seem somewhat repetitive. thanks, -- Joel Fernandes ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 17:51 ` Joel Fernandes @ 2026-03-09 18:01 ` John Hubbard 2026-03-09 18:28 ` Gary Guo 0 siblings, 1 reply; 57+ messages in thread From: John Hubbard @ 2026-03-09 18:01 UTC (permalink / raw) To: Joel Fernandes, Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/26 10:51 AM, Joel Fernandes wrote: > On 3/9/2026 1:34 PM, John Hubbard wrote: >> On 3/9/26 8:43 AM, Joel Fernandes wrote: >>> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: ... >>> TBH, this is quite a readability hit, the previous was much more readable IMHO. >> >> Yes, I think so too, except that "WithBase::of::<E>" makes a lot more >> sense on-screen, than "&E::ID". The latter just looks quite random, >> so this part is an improvement. >> >> Let's break down the remaining troublesome part a bit: >> >> regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1) >> >> * "regs::" can be omitted with a "use" statement, right? >> >> * ::zeroed() maybe should be the default behavior here, and then >> it could also be omitted? > > The issue with omitting it is it still needs a constructor? so if not zeroed() > it would still need ::default() I think. > >> >> * .with_value(mbox1) I'm sure this is necessary, but the construction >> is unfortunately much less clear than .write(value)! Thoughts? > > yeah, also perhaps the whole thing should have a macro! to de-sugar. The > patterns seem somewhat repetitive. Yes, and actually, I don't immediately see why we have to specify both a base value and a register name, because registers have a fixed base address. What about this instead (*very* approximately): // set up or construct NV_PFALCON_FALCON_MAILBOX1: let reg = NV_PFALCON_FALCON_MAILBOX1::zeroed(&bar); reg.write(NV_PFALCON_FALCON_MAILBOX1, new_mbox1_val); thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 18:01 ` John Hubbard @ 2026-03-09 18:28 ` Gary Guo 2026-03-09 18:34 ` John Hubbard 0 siblings, 1 reply; 57+ messages in thread From: Gary Guo @ 2026-03-09 18:28 UTC (permalink / raw) To: John Hubbard, Joel Fernandes, Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 6:01 PM GMT, John Hubbard wrote: > On 3/9/26 10:51 AM, Joel Fernandes wrote: >> On 3/9/2026 1:34 PM, John Hubbard wrote: >>> On 3/9/26 8:43 AM, Joel Fernandes wrote: >>>> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: > ... >>>> TBH, this is quite a readability hit, the previous was much more readable IMHO. >>> >>> Yes, I think so too, except that "WithBase::of::<E>" makes a lot more >>> sense on-screen, than "&E::ID". The latter just looks quite random, >>> so this part is an improvement. >>> >>> Let's break down the remaining troublesome part a bit: >>> >>> regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1) >>> >>> * "regs::" can be omitted with a "use" statement, right? >>> >>> * ::zeroed() maybe should be the default behavior here, and then >>> it could also be omitted? >> >> The issue with omitting it is it still needs a constructor? so if not zeroed() >> it would still need ::default() I think. >> >>> >>> * .with_value(mbox1) I'm sure this is necessary, but the construction >>> is unfortunately much less clear than .write(value)! Thoughts? >> >> yeah, also perhaps the whole thing should have a macro! to de-sugar. The >> patterns seem somewhat repetitive. Once we agreed on how bitfield macros should work, this could just be bar.write(WithBase::of::<E>(), bitfield!(NV_PFCALON_FALCON_MAILBOX1 { value: mbox1 })) or something similar. Or as Danilo pointed out, for the generic mailbox using bitfields isn't really meaningful, so the bitfield should eventually be removed and replaced with a direct write of primitive integer. > > Yes, and actually, I don't immediately see why we have to specify both > a base value and a register name, because registers have a fixed base > address. > > What about this instead (*very* approximately): > > // set up or construct NV_PFALCON_FALCON_MAILBOX1: > > let reg = NV_PFALCON_FALCON_MAILBOX1::zeroed(&bar); This doesn't really make sense. What does "zeroed(&bar)" even mean? You're neither zeroing the register on the bar, nor constructing a zeroed value? Best, Gary > > reg.write(NV_PFALCON_FALCON_MAILBOX1, new_mbox1_val); > > > > thanks, ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 18:28 ` Gary Guo @ 2026-03-09 18:34 ` John Hubbard 2026-03-09 18:42 ` Danilo Krummrich 2026-03-09 18:42 ` Gary Guo 0 siblings, 2 replies; 57+ messages in thread From: John Hubbard @ 2026-03-09 18:34 UTC (permalink / raw) To: Gary Guo, Joel Fernandes, Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/26 11:28 AM, Gary Guo wrote: > On Mon Mar 9, 2026 at 6:01 PM GMT, John Hubbard wrote: >> On 3/9/26 10:51 AM, Joel Fernandes wrote: >>> On 3/9/2026 1:34 PM, John Hubbard wrote: >>>> On 3/9/26 8:43 AM, Joel Fernandes wrote: >>>>> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >> ... >> What about this instead (*very* approximately): >> >> // set up or construct NV_PFALCON_FALCON_MAILBOX1: >> >> let reg = NV_PFALCON_FALCON_MAILBOX1::zeroed(&bar); > > This doesn't really make sense. What does "zeroed(&bar)" even mean? You're > neither zeroing the register on the bar, nor constructing a zeroed value? > The idea was to separate constructing with a zeroed value, from the actual register write. I am not surprised that the syntax is All Wrong at this point, I'm just hoping to spark more ideas about how to make this clear and nice to read. thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 18:34 ` John Hubbard @ 2026-03-09 18:42 ` Danilo Krummrich 2026-03-09 18:47 ` John Hubbard 2026-03-09 18:42 ` Gary Guo 1 sibling, 1 reply; 57+ messages in thread From: Danilo Krummrich @ 2026-03-09 18:42 UTC (permalink / raw) To: John Hubbard Cc: Gary Guo, Joel Fernandes, Alexandre Courbot, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 7:34 PM CET, John Hubbard wrote: > On 3/9/26 11:28 AM, Gary Guo wrote: >> On Mon Mar 9, 2026 at 6:01 PM GMT, John Hubbard wrote: >>> On 3/9/26 10:51 AM, Joel Fernandes wrote: >>>> On 3/9/2026 1:34 PM, John Hubbard wrote: >>>>> On 3/9/26 8:43 AM, Joel Fernandes wrote: >>>>>> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >>> ... >>> What about this instead (*very* approximately): >>> >>> // set up or construct NV_PFALCON_FALCON_MAILBOX1: >>> >>> let reg = NV_PFALCON_FALCON_MAILBOX1::zeroed(&bar); >> >> This doesn't really make sense. What does "zeroed(&bar)" even mean? You're >> neither zeroing the register on the bar, nor constructing a zeroed value? >> > > The idea was to separate constructing with a zeroed value, from > the actual register write. I am not surprised that the syntax > is All Wrong at this point, I'm just hoping to spark more ideas > about how to make this clear and nice to read. This is where you would use write_val(), i.e. // Construct with some custom constructor. let reg = regs::NV_PFALCON_FALCON_MAILBOX1::new(...); bar.write_val(reg); ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 18:42 ` Danilo Krummrich @ 2026-03-09 18:47 ` John Hubbard 0 siblings, 0 replies; 57+ messages in thread From: John Hubbard @ 2026-03-09 18:47 UTC (permalink / raw) To: Danilo Krummrich Cc: Gary Guo, Joel Fernandes, Alexandre Courbot, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/26 11:42 AM, Danilo Krummrich wrote: > On Mon Mar 9, 2026 at 7:34 PM CET, John Hubbard wrote: >> On 3/9/26 11:28 AM, Gary Guo wrote: >>> On Mon Mar 9, 2026 at 6:01 PM GMT, John Hubbard wrote: >>>> On 3/9/26 10:51 AM, Joel Fernandes wrote: >>>>> On 3/9/2026 1:34 PM, John Hubbard wrote: >>>>>> On 3/9/26 8:43 AM, Joel Fernandes wrote: >>>>>>> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >>>> ... >>>> What about this instead (*very* approximately): >>>> >>>> // set up or construct NV_PFALCON_FALCON_MAILBOX1: >>>> >>>> let reg = NV_PFALCON_FALCON_MAILBOX1::zeroed(&bar); >>> >>> This doesn't really make sense. What does "zeroed(&bar)" even mean? You're >>> neither zeroing the register on the bar, nor constructing a zeroed value? >>> >> >> The idea was to separate constructing with a zeroed value, from >> the actual register write. I am not surprised that the syntax >> is All Wrong at this point, I'm just hoping to spark more ideas >> about how to make this clear and nice to read. > > This is where you would use write_val(), i.e. > > // Construct with some custom constructor. > let reg = regs::NV_PFALCON_FALCON_MAILBOX1::new(...); > > bar.write_val(reg); Beautiful! That's what I wish I had written. :) thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 18:34 ` John Hubbard 2026-03-09 18:42 ` Danilo Krummrich @ 2026-03-09 18:42 ` Gary Guo 2026-03-09 18:50 ` John Hubbard 1 sibling, 1 reply; 57+ messages in thread From: Gary Guo @ 2026-03-09 18:42 UTC (permalink / raw) To: John Hubbard, Gary Guo, Joel Fernandes, Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 6:34 PM GMT, John Hubbard wrote: > On 3/9/26 11:28 AM, Gary Guo wrote: >> On Mon Mar 9, 2026 at 6:01 PM GMT, John Hubbard wrote: >>> On 3/9/26 10:51 AM, Joel Fernandes wrote: >>>> On 3/9/2026 1:34 PM, John Hubbard wrote: >>>>> On 3/9/26 8:43 AM, Joel Fernandes wrote: >>>>>> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >>> ... >>> What about this instead (*very* approximately): >>> >>> // set up or construct NV_PFALCON_FALCON_MAILBOX1: >>> >>> let reg = NV_PFALCON_FALCON_MAILBOX1::zeroed(&bar); >> >> This doesn't really make sense. What does "zeroed(&bar)" even mean? You're >> neither zeroing the register on the bar, nor constructing a zeroed value? >> > > The idea was to separate constructing with a zeroed value, from > the actual register write. I am not surprised that the syntax > is All Wrong at this point, I'm just hoping to spark more ideas > about how to make this clear and nice to read. Separated construct and write is just let reg = NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1); bar.write(WithBase::of::<E>(), reg); (or `bar.write_val(reg)` for non-relative registers) Best, Gary ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 18:42 ` Gary Guo @ 2026-03-09 18:50 ` John Hubbard 0 siblings, 0 replies; 57+ messages in thread From: John Hubbard @ 2026-03-09 18:50 UTC (permalink / raw) To: Gary Guo, Joel Fernandes, Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/26 11:42 AM, Gary Guo wrote: ... >> The idea was to separate constructing with a zeroed value, from >> the actual register write. I am not surprised that the syntax >> is All Wrong at this point, I'm just hoping to spark more ideas >> about how to make this clear and nice to read. > > Separated construct and write is just > > let reg = NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1); > bar.write(WithBase::of::<E>(), reg); > > (or `bar.write_val(reg)` for non-relative registers) OK, so I think that Alex's example was sort of a worst case, because if we wrote it as you do above, it would be clearer to read. And hopefully we can continue on to what Danilo wrote in the other thread, which makes it even simpler and clearer. thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 17:34 ` John Hubbard 2026-03-09 17:51 ` Joel Fernandes @ 2026-03-09 18:04 ` Danilo Krummrich 2026-03-09 18:06 ` John Hubbard 1 sibling, 1 reply; 57+ messages in thread From: Danilo Krummrich @ 2026-03-09 18:04 UTC (permalink / raw) To: John Hubbard Cc: Joel Fernandes, Alexandre Courbot, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 6:34 PM CET, John Hubbard wrote: > On 3/9/26 8:43 AM, Joel Fernandes wrote: >> >> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >>> >>> @@ -797,26 +792,30 @@ 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() >>> - .set_value(mbox0) >>> - .write(bar, &E::ID); >>> + bar.write( >>> + WithBase::of::<E>(), >>> + regs::NV_PFALCON_FALCON_MAILBOX0::zeroed().with_value(mbox0), >>> + ); >>> } >>> >>> if let Some(mbox1) = mbox1 { >>> - regs::NV_PFALCON_FALCON_MAILBOX1::default() >>> - .set_value(mbox1) >>> - .write(bar, &E::ID); >>> + bar.write( >>> + WithBase::of::<E>(), >>> + regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1), >>> + ); >>> } >>> } >> >> TBH, this is quite a readability hit, the previous was much more readable IMHO. > > Yes, I think so too, except that "WithBase::of::<E>" makes a lot more > sense on-screen, than "&E::ID". The latter just looks quite random, > so this part is an improvement. > > Let's break down the remaining troublesome part a bit: > > regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1) > > * "regs::" can be omitted with a "use" statement, right? > > * ::zeroed() maybe should be the default behavior here, and then > it could also be omitted? > > * .with_value(mbox1) I'm sure this is necessary, but the construction > is unfortunately much less clear than .write(value)! Thoughts? I think this could also just be bar.write(regs::NV_PFALCON_FALCON_MAILBOX1::of::<E>(), mbox1); as we would want it for FIFO-like registers. ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 18:04 ` Danilo Krummrich @ 2026-03-09 18:06 ` John Hubbard 0 siblings, 0 replies; 57+ messages in thread From: John Hubbard @ 2026-03-09 18:06 UTC (permalink / raw) To: Danilo Krummrich Cc: Joel Fernandes, Alexandre Courbot, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/26 11:04 AM, Danilo Krummrich wrote: > On Mon Mar 9, 2026 at 6:34 PM CET, John Hubbard wrote: >> On 3/9/26 8:43 AM, Joel Fernandes wrote: >>> On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >> Let's break down the remaining troublesome part a bit: >> >> regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1) >> >> * "regs::" can be omitted with a "use" statement, right? >> >> * ::zeroed() maybe should be the default behavior here, and then >> it could also be omitted? >> >> * .with_value(mbox1) I'm sure this is necessary, but the construction >> is unfortunately much less clear than .write(value)! Thoughts? > > I think this could also just be > > bar.write(regs::NV_PFALCON_FALCON_MAILBOX1::of::<E>(), mbox1); That would be quite nice. > > as we would want it for FIFO-like registers. thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel `register!` macro 2026-03-09 15:43 ` Joel Fernandes 2026-03-09 17:34 ` John Hubbard @ 2026-03-10 2:18 ` Alexandre Courbot 1 sibling, 0 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-10 2:18 UTC (permalink / raw) To: Joel Fernandes Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Tue Mar 10, 2026 at 12:43 AM JST, Joel Fernandes wrote: > > On 3/9/2026 11:14 AM, Alexandre Courbot wrote: >> >> @@ -797,26 +792,30 @@ 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() >> - .set_value(mbox0) >> - .write(bar, &E::ID); >> + bar.write( >> + WithBase::of::<E>(), >> + regs::NV_PFALCON_FALCON_MAILBOX0::zeroed().with_value(mbox0), >> + ); >> } >> >> if let Some(mbox1) = mbox1 { >> - regs::NV_PFALCON_FALCON_MAILBOX1::default() >> - .set_value(mbox1) >> - .write(bar, &E::ID); >> + bar.write( >> + WithBase::of::<E>(), >> + regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1), >> + ); >> } >> } > > TBH, this is quite a readability hit, the previous was much more readable IMHO. > > Why doesn't the previous caller-side syntax still work? Looking down the discussion is looks like almost everyone found a way to work with this API that they like, but here are the reasons for the record. The previous caller-side syntax had many problems: * Every register must have its own ~6 (try_)read/write/update methods generated by the macro, creating a rift in how I/O is used only for registers. * The number of arguments these methods take is dependent on the register type, which is awkward. * The I/O type is passed as an argument, meaning we cannot use Deref coercion on it, further complicating things when we have more than one layer of indirection. For instance, it didn't work well with both Nova and the Rust PCI driver sample (one of them had to `&` or `*` its I/O type with every call). With the current revision, there is only one site where I/O methods are defined (io.rs), to which registers cleanly integrate like any other I/O type (i.e. registers are not "special"). Deref coercion on the `Io` type can be used without limitation. There is much less code generated by the register macro, which has become much simpler as a result. This syntax might require one more line here and there, but after working with it a bit I think it is actually much more readable and well-formatted than before. It makes it clear from the get go what kind of register you are accessing, and we *do* want to be meticulous when working with registers. It looks better both on the inside and the outside, and I hold that view not as an opinion, but an objective fact. :) ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 00/10] rust: add `register!` macro 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (9 preceding siblings ...) 2026-03-09 15:14 ` [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel " Alexandre Courbot @ 2026-03-09 18:05 ` John Hubbard 2026-03-09 21:08 ` Daniel Almeida 2026-03-10 17:20 ` Danilo Krummrich 12 siblings, 0 replies; 57+ messages in thread From: John Hubbard @ 2026-03-09 18:05 UTC (permalink / raw) To: Alexandre Courbot, Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng Cc: Yury Norov, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On 3/9/26 8:13 AM, Alexandre Courbot wrote: > Another revision, another redesign. > > This one eschews closures completely for write operations, and defaults > to a two-argument `write` method backed by a single-argument `write_val` > one to avoid register name repetition. The two-argument version can now > be used in most cases thanks to a clever use of traits (thanks Gary!). > > We went from this syntax: > > bar.write_with(regs::NV_PFALCON_FALCON_DMATRFFBOFFS::of::<E>(), |r| { > r.with_offs(src_start + pos) > }); > > to this one: > > bar.write( > WithBase::of::<E>(), > regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed().with_offs(src_start + pos), > ); > > While slightly more verbose, it is also much easier to read, as the Yes, it is! Let's acknowledge the progress in readability here, even though we are still working on minor details in the other thread. :) thanks, -- John Hubbard ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 00/10] rust: add `register!` macro 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (10 preceding siblings ...) 2026-03-09 18:05 ` [PATCH v8 00/10] rust: add " John Hubbard @ 2026-03-09 21:08 ` Daniel Almeida 2026-03-10 17:20 ` Danilo Krummrich 12 siblings, 0 replies; 57+ messages in thread From: Daniel Almeida @ 2026-03-09 21:08 UTC (permalink / raw) To: Alexandre Courbot Cc: Danilo Krummrich, Alice Ryhl, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel Hi Alex, We will test that on Tyr shortly. There’s a patch on the list that targeted an earlier version of the macro. We will rebase it on the current one. At least you get one more user from the get-go. — Daniel ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 00/10] rust: add `register!` macro 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot ` (11 preceding siblings ...) 2026-03-09 21:08 ` Daniel Almeida @ 2026-03-10 17:20 ` Danilo Krummrich 2026-03-11 13:01 ` Alexandre Courbot 12 siblings, 1 reply; 57+ messages in thread From: Danilo Krummrich @ 2026-03-10 17:20 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov, John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux, linux-kernel On Mon Mar 9, 2026 at 4:13 PM CET, Alexandre Courbot wrote: > Alexandre Courbot (10): > rust: enable the `generic_arg_infer` feature > rust: num: add `shr` and `shl` methods to `Bounded` > rust: num: add `into_bool` method to `Bounded` > rust: num: make Bounded::get const > rust: io: add IoLoc type and generic I/O accessors > rust: io: use generic read/write accessors for primitive accesses > rust: io: introduce `IntoIoVal` trait and single-argument `write_val` > rust: io: add `register!` macro > sample: rust: pci: use `register!` macro > [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro I did not look into the root cause, but fetching this patch series my build fails due to a build_assert!(). Maybe you have CONFIG_RUST_BUILD_ASSERT_ALLOW enabled? ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 00/10] rust: add `register!` macro 2026-03-10 17:20 ` Danilo Krummrich @ 2026-03-11 13:01 ` Alexandre Courbot 2026-03-11 13:07 ` Danilo Krummrich 0 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-11 13:01 UTC (permalink / raw) To: Danilo Krummrich Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 2:20 AM JST, Danilo Krummrich wrote: > On Mon Mar 9, 2026 at 4:13 PM CET, Alexandre Courbot wrote: >> Alexandre Courbot (10): >> rust: enable the `generic_arg_infer` feature >> rust: num: add `shr` and `shl` methods to `Bounded` >> rust: num: add `into_bool` method to `Bounded` >> rust: num: make Bounded::get const >> rust: io: add IoLoc type and generic I/O accessors >> rust: io: use generic read/write accessors for primitive accesses >> rust: io: introduce `IntoIoVal` trait and single-argument `write_val` >> rust: io: add `register!` macro >> sample: rust: pci: use `register!` macro >> [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro > > I did not look into the root cause, but fetching this patch series my build > fails due to a build_assert!(). Maybe you have CONFIG_RUST_BUILD_ASSERT_ALLOW > enabled? Nope, it is disabled and I build with Clippy so I am surprised you are getting this. Do you know which module is failing? ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 00/10] rust: add `register!` macro 2026-03-11 13:01 ` Alexandre Courbot @ 2026-03-11 13:07 ` Danilo Krummrich 2026-03-11 13:35 ` Alexandre Courbot 0 siblings, 1 reply; 57+ messages in thread From: Danilo Krummrich @ 2026-03-11 13:07 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 2:01 PM CET, Alexandre Courbot wrote: > On Wed Mar 11, 2026 at 2:20 AM JST, Danilo Krummrich wrote: >> On Mon Mar 9, 2026 at 4:13 PM CET, Alexandre Courbot wrote: >>> Alexandre Courbot (10): >>> rust: enable the `generic_arg_infer` feature >>> rust: num: add `shr` and `shl` methods to `Bounded` >>> rust: num: add `into_bool` method to `Bounded` >>> rust: num: make Bounded::get const >>> rust: io: add IoLoc type and generic I/O accessors >>> rust: io: use generic read/write accessors for primitive accesses >>> rust: io: introduce `IntoIoVal` trait and single-argument `write_val` >>> rust: io: add `register!` macro >>> sample: rust: pci: use `register!` macro >>> [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro >> >> I did not look into the root cause, but fetching this patch series my build >> fails due to a build_assert!(). Maybe you have CONFIG_RUST_BUILD_ASSERT_ALLOW >> enabled? > > Nope, it is disabled and I build with Clippy so I am surprised you are > getting this. Do you know which module is failing? ERROR: modpost: "rust_build_error" [drivers/gpu/nova-core/nova_core.ko] undefined! ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 00/10] rust: add `register!` macro 2026-03-11 13:07 ` Danilo Krummrich @ 2026-03-11 13:35 ` Alexandre Courbot 2026-03-11 13:38 ` Danilo Krummrich 0 siblings, 1 reply; 57+ messages in thread From: Alexandre Courbot @ 2026-03-11 13:35 UTC (permalink / raw) To: Danilo Krummrich Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 10:07 PM JST, Danilo Krummrich wrote: > On Wed Mar 11, 2026 at 2:01 PM CET, Alexandre Courbot wrote: >> On Wed Mar 11, 2026 at 2:20 AM JST, Danilo Krummrich wrote: >>> On Mon Mar 9, 2026 at 4:13 PM CET, Alexandre Courbot wrote: >>>> Alexandre Courbot (10): >>>> rust: enable the `generic_arg_infer` feature >>>> rust: num: add `shr` and `shl` methods to `Bounded` >>>> rust: num: add `into_bool` method to `Bounded` >>>> rust: num: make Bounded::get const >>>> rust: io: add IoLoc type and generic I/O accessors >>>> rust: io: use generic read/write accessors for primitive accesses >>>> rust: io: introduce `IntoIoVal` trait and single-argument `write_val` >>>> rust: io: add `register!` macro >>>> sample: rust: pci: use `register!` macro >>>> [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro >>> >>> I did not look into the root cause, but fetching this patch series my build >>> fails due to a build_assert!(). Maybe you have CONFIG_RUST_BUILD_ASSERT_ALLOW >>> enabled? >> >> Nope, it is disabled and I build with Clippy so I am surprised you are >> getting this. Do you know which module is failing? > > ERROR: modpost: "rust_build_error" [drivers/gpu/nova-core/nova_core.ko] undefined! Does it happen with the exact branch I posted? https://github.com/Gnurou/linux/tree/b4/register This one builds fine for me. Gary also pointed out a few missing `inlines` that could potentially trigger that kind of error. But I'd be curious to try and reproduce myself if you can post your tree somewhere. ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 00/10] rust: add `register!` macro 2026-03-11 13:35 ` Alexandre Courbot @ 2026-03-11 13:38 ` Danilo Krummrich 2026-03-12 2:23 ` Alexandre Courbot 0 siblings, 1 reply; 57+ messages in thread From: Danilo Krummrich @ 2026-03-11 13:38 UTC (permalink / raw) To: Alexandre Courbot Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 2:35 PM CET, Alexandre Courbot wrote: > On Wed Mar 11, 2026 at 10:07 PM JST, Danilo Krummrich wrote: >> On Wed Mar 11, 2026 at 2:01 PM CET, Alexandre Courbot wrote: >>> On Wed Mar 11, 2026 at 2:20 AM JST, Danilo Krummrich wrote: >>>> On Mon Mar 9, 2026 at 4:13 PM CET, Alexandre Courbot wrote: >>>>> Alexandre Courbot (10): >>>>> rust: enable the `generic_arg_infer` feature >>>>> rust: num: add `shr` and `shl` methods to `Bounded` >>>>> rust: num: add `into_bool` method to `Bounded` >>>>> rust: num: make Bounded::get const >>>>> rust: io: add IoLoc type and generic I/O accessors >>>>> rust: io: use generic read/write accessors for primitive accesses >>>>> rust: io: introduce `IntoIoVal` trait and single-argument `write_val` >>>>> rust: io: add `register!` macro >>>>> sample: rust: pci: use `register!` macro >>>>> [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro >>>> >>>> I did not look into the root cause, but fetching this patch series my build >>>> fails due to a build_assert!(). Maybe you have CONFIG_RUST_BUILD_ASSERT_ALLOW >>>> enabled? >>> >>> Nope, it is disabled and I build with Clippy so I am surprised you are >>> getting this. Do you know which module is failing? >> >> ERROR: modpost: "rust_build_error" [drivers/gpu/nova-core/nova_core.ko] undefined! > > Does it happen with the exact branch I posted? Yup, my HEAD is at commit 5c558172827e ("[FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro"). ^ permalink raw reply [flat|nested] 57+ messages in thread
* Re: [PATCH v8 00/10] rust: add `register!` macro 2026-03-11 13:38 ` Danilo Krummrich @ 2026-03-12 2:23 ` Alexandre Courbot 0 siblings, 0 replies; 57+ messages in thread From: Alexandre Courbot @ 2026-03-12 2:23 UTC (permalink / raw) To: Danilo Krummrich Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng, 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 Mar 11, 2026 at 10:38 PM JST, Danilo Krummrich wrote: > On Wed Mar 11, 2026 at 2:35 PM CET, Alexandre Courbot wrote: >> On Wed Mar 11, 2026 at 10:07 PM JST, Danilo Krummrich wrote: >>> On Wed Mar 11, 2026 at 2:01 PM CET, Alexandre Courbot wrote: >>>> On Wed Mar 11, 2026 at 2:20 AM JST, Danilo Krummrich wrote: >>>>> On Mon Mar 9, 2026 at 4:13 PM CET, Alexandre Courbot wrote: >>>>>> Alexandre Courbot (10): >>>>>> rust: enable the `generic_arg_infer` feature >>>>>> rust: num: add `shr` and `shl` methods to `Bounded` >>>>>> rust: num: add `into_bool` method to `Bounded` >>>>>> rust: num: make Bounded::get const >>>>>> rust: io: add IoLoc type and generic I/O accessors >>>>>> rust: io: use generic read/write accessors for primitive accesses >>>>>> rust: io: introduce `IntoIoVal` trait and single-argument `write_val` >>>>>> rust: io: add `register!` macro >>>>>> sample: rust: pci: use `register!` macro >>>>>> [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro >>>>> >>>>> I did not look into the root cause, but fetching this patch series my build >>>>> fails due to a build_assert!(). Maybe you have CONFIG_RUST_BUILD_ASSERT_ALLOW >>>>> enabled? >>>> >>>> Nope, it is disabled and I build with Clippy so I am surprised you are >>>> getting this. Do you know which module is failing? >>> >>> ERROR: modpost: "rust_build_error" [drivers/gpu/nova-core/nova_core.ko] undefined! >> >> Does it happen with the exact branch I posted? > > Yup, my HEAD is at commit 5c558172827e ("[FOR REFERENCE] gpu: nova-core: use the > kernel `register!` macro"). I did a mistake when updating the nova-core code - changed a dynamically-checked index into a static one, and since it could not be proven at build-time it failed. Working as intended, apart from the too cryptic error message. For the record, here is the fix: --- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs +++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs @@ -291,14 +291,15 @@ pub(crate) fn run( .inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?; // Configure DMA index for the bootloader to fetch the FWSEC firmware from system memory. - bar.try_update( + bar.update( regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>() - .at(usize::from_safe_cast(self.dmem_desc.ctx_dma)), + .try_at(usize::from_safe_cast(self.dmem_desc.ctx_dma)) + .ok_or(EINVAL)?, |v| { v.with_target(FalconFbifTarget::CoherentSysmem) .with_mem_type(FalconFbifMemType::Physical) }, - )?; + ); let (mbox0, _) = falcon .boot(bar, Some(0), None) ^ permalink raw reply [flat|nested] 57+ messages in thread
end of thread, other threads:[~2026-03-14 1:19 UTC | newest] Thread overview: 57+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-03-09 15:13 [PATCH v8 00/10] rust: add `register!` macro Alexandre Courbot 2026-03-09 15:13 ` [PATCH v8 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot 2026-03-11 0:13 ` Yury Norov 2026-03-11 6:16 ` Miguel Ojeda 2026-03-09 15:13 ` [PATCH v8 02/10] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot 2026-03-11 0:26 ` Yury Norov 2026-03-09 15:14 ` [PATCH v8 03/10] rust: num: add `into_bool` method " Alexandre Courbot 2026-03-11 0:29 ` Yury Norov 2026-03-09 15:14 ` [PATCH v8 04/10] rust: num: make Bounded::get const Alexandre Courbot 2026-03-09 15:14 ` [PATCH v8 05/10] rust: io: add IoLoc type and generic I/O accessors Alexandre Courbot 2026-03-10 15:15 ` Danilo Krummrich 2026-03-09 15:14 ` [PATCH v8 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot 2026-03-09 15:29 ` Gary Guo 2026-03-10 1:57 ` Alexandre Courbot 2026-03-10 1:59 ` Alexandre Courbot 2026-03-10 2:04 ` Gary Guo 2026-03-10 15:17 ` Danilo Krummrich 2026-03-09 15:14 ` [PATCH v8 07/10] rust: io: introduce `IntoIoVal` trait and single-argument `write_val` Alexandre Courbot 2026-03-09 15:30 ` Gary Guo 2026-03-10 2:05 ` Alexandre Courbot 2026-03-10 16:34 ` Danilo Krummrich 2026-03-10 22:33 ` Gary Guo 2026-03-11 13:25 ` Danilo Krummrich 2026-03-11 13:42 ` Alexandre Courbot 2026-03-11 13:28 ` Alexandre Courbot 2026-03-11 14:56 ` Danilo Krummrich 2026-03-11 15:42 ` Gary Guo 2026-03-11 16:22 ` Danilo Krummrich 2026-03-11 17:16 ` Gary Guo 2026-03-12 13:53 ` Alexandre Courbot 2026-03-12 15:54 ` Danilo Krummrich 2026-03-14 1:19 ` Alexandre Courbot 2026-03-09 15:14 ` [PATCH v8 08/10] rust: io: add `register!` macro Alexandre Courbot 2026-03-10 16:39 ` Danilo Krummrich 2026-03-09 15:14 ` [PATCH v8 09/10] sample: rust: pci: use " Alexandre Courbot 2026-03-09 15:14 ` [PATCH FOR REFERENCE v8 10/10] gpu: nova-core: use the kernel " Alexandre Courbot 2026-03-09 15:43 ` Joel Fernandes 2026-03-09 17:34 ` John Hubbard 2026-03-09 17:51 ` Joel Fernandes 2026-03-09 18:01 ` John Hubbard 2026-03-09 18:28 ` Gary Guo 2026-03-09 18:34 ` John Hubbard 2026-03-09 18:42 ` Danilo Krummrich 2026-03-09 18:47 ` John Hubbard 2026-03-09 18:42 ` Gary Guo 2026-03-09 18:50 ` John Hubbard 2026-03-09 18:04 ` Danilo Krummrich 2026-03-09 18:06 ` John Hubbard 2026-03-10 2:18 ` Alexandre Courbot 2026-03-09 18:05 ` [PATCH v8 00/10] rust: add " John Hubbard 2026-03-09 21:08 ` Daniel Almeida 2026-03-10 17:20 ` Danilo Krummrich 2026-03-11 13:01 ` Alexandre Courbot 2026-03-11 13:07 ` Danilo Krummrich 2026-03-11 13:35 ` Alexandre Courbot 2026-03-11 13:38 ` Danilo Krummrich 2026-03-12 2:23 ` Alexandre Courbot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox