* [PATCH v7 00/10] rust: add `register!` macro
@ 2026-02-24 14:21 Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot
` (10 more replies)
0 siblings, 11 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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
New revision addressing the v6 feedback - the use of "location" instead
of "reference" in particular reads well both in the code and the
comments.
As a reminder, since the previous revision we use the I/O type to
perform the actual I/O instead of the register type, which moves us from
this access pattern:
let boot0 = regs::NV_PMC_BOOT_0::read(bar);
to this arguably more natural one:
let boot0 = bar.read(regs::NV_PMC_BOOT_0);
This revision is based on `driver-core-testing` as of 2026-02-24 with
[1] applied. A tree with this series and its dependencies is available
at [2].
[1] https://lore.kernel.org/all/20260206-io-v2-0-71dea20a06e6@nvidia.com/
[2] https://github.com/Gnurou/linux/tree/b4/register
The first patch enables the `generic_arg_infer` feature, which is
required for generic type inference and used in subsequent patches. This
feature is stable since rustc 1.89.
The second patch adds `shr` and `shl` methods to `Bounded`. These were
suggested by Alice during LPC as a way to avoid the use of the
controversial `Bounded::from_expr` in both the bitfield macro and the
Nova code. The third patch adds another convenience method to obtain a
`bool` from single-bit `Bounded`s, while the fourth patch turns
`Bounded::get` into a const method in order to make register setter
methods const.
Patches 5 and 6 introduce the `IoLoc` and `IoWrite` types around which
I/O accesses are centered. This allows registers to be accessed using
the same `read` and `write` methods as primitive types.
Patch 7 adds the `register!` macro and the types it requires.
Patch 8 updates the Rust PCI sample driver to use `register!`, as per
its TODO item.
Patch 9 illustrates more extensively how this macro is used by
converting nova-core to use it, and removing the local implementation.
This patch is to be merged one cycle after the other patches.
Patch 10 is an RFC allowing a shorter write syntax to be used in the
case of fixed or relative registers. It doesn't need to be merged
immediately, but I think it is a good time to discuss it.
I have also removed Gary's signoff on patches 5 and 7 to make sure it
doesn't get merged before he gives it explicitly.
Signed-off-by: Alexandre Courbot <acourbot@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 and IoWrite types
rust: io: use generic read/write accessors for primitive accesses
rust: io: add `register!` macro
sample: rust: pci: use `register!` macro
[FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro
RFC: rust: io: allow fixed register values directly in `write`
drivers/gpu/nova-core/falcon.rs | 249 +++----
drivers/gpu/nova-core/falcon/gsp.rs | 23 +-
drivers/gpu/nova-core/falcon/hal/ga102.rs | 65 +-
drivers/gpu/nova-core/falcon/hal/tu102.rs | 11 +-
drivers/gpu/nova-core/falcon/sec2.rs | 17 +-
drivers/gpu/nova-core/fb.rs | 6 +-
drivers/gpu/nova-core/fb/hal/ga100.rs | 40 +-
drivers/gpu/nova-core/fb/hal/ga102.rs | 7 +-
drivers/gpu/nova-core/fb/hal/tu102.rs | 22 +-
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 | 10 +-
drivers/gpu/nova-core/regs.rs | 544 ++++++++------
drivers/gpu/nova-core/regs/macros.rs | 739 -------------------
rust/kernel/io.rs | 347 +++++++--
rust/kernel/io/register.rs | 1135 +++++++++++++++++++++++++++++
rust/kernel/lib.rs | 3 +
rust/kernel/num/bounded.rs | 70 +-
samples/rust/rust_driver_pci.rs | 84 ++-
scripts/Makefile.build | 3 +-
21 files changed, 2118 insertions(+), 1315 deletions(-)
---
base-commit: 545225ee0b0b6998c7b6ff888f8497681b8b35c4
change-id: 20260117-register-ccaba1d21713
Best regards,
--
Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply [flat|nested] 64+ messages in thread
* [PATCH v7 01/10] rust: enable the `generic_arg_infer` feature
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 02/10] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
` (9 subsequent siblings)
10 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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] 64+ messages in thread
* [PATCH v7 02/10] rust: num: add `shr` and `shl` methods to `Bounded`
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 03/10] rust: num: add `into_bool` method " Alexandre Courbot
` (8 subsequent siblings)
10 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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] 64+ messages in thread
* [PATCH v7 03/10] rust: num: add `into_bool` method to `Bounded`
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 02/10] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 04/10] rust: num: make Bounded::get const Alexandre Courbot
` (7 subsequent siblings)
10 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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] 64+ messages in thread
* [PATCH v7 04/10] rust: num: make Bounded::get const
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
` (2 preceding siblings ...)
2026-02-24 14:21 ` [PATCH v7 03/10] rust: num: add `into_bool` method " Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-27 12:33 ` Gary Guo
2026-02-24 14:21 ` [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types Alexandre Courbot
` (6 subsequent siblings)
10 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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.
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] 64+ messages in thread
* [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
` (3 preceding siblings ...)
2026-02-24 14:21 ` [PATCH v7 04/10] rust: num: make Bounded::get const Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-27 18:02 ` Gary Guo
2026-03-04 22:19 ` Gary Guo
2026-02-24 14:21 ` [PATCH v7 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot
` (5 subsequent siblings)
10 siblings, 2 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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:
- For reads, a start address, a width, and a type to interpret the read
value as,
- For writes, the same as above, and a value to write.
Introduce the `IoLoc` trait, which allows implementing types to specify
the address a type expects to be accessed at, as well as the width of
the access, and the user-facing type used to perform the access.
This allows read operations to be made generic with the `read` method
over an `IoLoc` argument.
Write operations need a value to write on top of the `IoLoc`: fulfill
that purpose with the `IoWrite` type, which is the combination of an
`IoLoc` and a value of the type it expects. This allows write operations
to be made generic with the `write` method over a single `IoWrite`
argument.
The main purpose of these new entities is to allow register types to be
written using these generic `read` and `write` methods of `Io`.
Co-developed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/io.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 241 insertions(+)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index b150743ffa4f..fdd2549d8e13 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -173,6 +173,158 @@ pub trait IoCapable<T> {
unsafe fn io_write(&self, value: T, address: usize);
}
+/// Describes a given I/O location: its offset, width, and return type.
+///
+/// This trait is the key abstraction allowing [`Io::read`], [`Io::write`], and [`Io::update`]
+/// 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 data is returned or provided.
+///
+/// `T` and `IoType` may differ: for instance, a typed register has `T` = the register type with
+/// its bitfields, and `IoType` = its backing primitive (e.g. `u32`), with `Into` conversions
+/// between them.
+///
+/// An `IoLoc` can be passed directly to [`Io::read`] or [`Io::try_read`] to obtain a value, or
+/// turned into an [`IoWrite`] via [`IoLoc::set`] to be passed to [`Io::write`] or
+/// [`Io::try_write`].
+pub trait IoLoc<T>: Copy
+where
+ T: Into<Self::IoType>,
+ Self::IoType: Into<T>,
+{
+ /// Size (`u8`, `u16`, etc) of the I/O performed on the returned [`offset`](IoLoc::offset).
+ type IoType;
+
+ /// Returns the offset of this location.
+ fn offset(self) -> usize;
+
+ /// Turns this location into an [`IoWrite`] with the initial `value`.
+ fn set(self, value: T) -> IoWrite<T, Self> {
+ IoWrite { value, loc: self }
+ }
+
+ /// Turns this location into an [`IoWrite`] with the initial value `0`.
+ fn zeroed(self) -> IoWrite<T, Self>
+ where
+ T: Zeroable,
+ {
+ self.set(pin_init::zeroed())
+ }
+
+ /// Turns this location into an [`IoWrite`] with the initial [`Default`] value of `T`.
+ fn default(self) -> IoWrite<T, Self>
+ where
+ T: Default,
+ {
+ self.set(Default::default())
+ }
+
+ /// Turns this location into an [`IoWrite`] initialized from `0` and transformed by `f`.
+ ///
+ /// This is a shortcut for `self.zeroed().update(f)`.
+ fn init<F>(self, f: F) -> IoWrite<T, Self>
+ where
+ T: Zeroable,
+ F: FnOnce(T) -> T,
+ {
+ self.zeroed().update(f)
+ }
+
+ /// Turns this location into an [`IoWrite`] initialized from `0` and transformed by `f`.
+ ///
+ /// `f` is expected to return a [`Result<T>`].
+ ///
+ /// This is a shortcut for `self.zeroed().try_update(f)`.
+ fn try_init<F, E>(self, f: F) -> Result<IoWrite<T, Self>, E>
+ where
+ T: Zeroable,
+ F: FnOnce(T) -> Result<T, E>,
+ {
+ self.zeroed().try_update(f)
+ }
+
+ /// Turns this location into an [`IoWrite`] initialized from [`Default`] and transformed
+ /// by `f`.
+ ///
+ /// This is a shortcut for `self.default().update(f)`.
+ fn init_default<F>(self, f: F) -> IoWrite<T, Self>
+ where
+ T: Default,
+ F: FnOnce(T) -> T,
+ {
+ self.default().update(f)
+ }
+
+ /// Turns this location into an [`IoWrite`] initialized from [`Default`] and transformed by
+ /// `f`.
+ ///
+ /// `f` is expected to return a [`Result<T>`].
+ ///
+ /// This is a shortcut for `self.default().try_update(f)`.
+ fn try_init_default<F, E>(self, f: F) -> Result<IoWrite<T, Self>, E>
+ where
+ T: Default,
+ F: FnOnce(T) -> Result<T, E>,
+ {
+ self.default().try_update(f)
+ }
+}
+
+/// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
+///
+/// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
+/// [`IoLoc::init_default`], and consumed by [`Io::write`] or [`Io::try_write`] to perform the
+/// actual write.
+///
+/// The value can be modified before writing using [`IoWrite::update`] or [`IoWrite::try_update`],
+/// enabling a builder pattern:
+///
+/// ```ignore
+/// io.write(REGISTER.init(|v| v.with_field(x)));
+/// ```
+pub struct IoWrite<T, R>
+where
+ R: IoLoc<T>,
+ T: Into<R::IoType>,
+{
+ value: T,
+ loc: R,
+}
+
+impl<R, T> IoWrite<T, R>
+where
+ R: IoLoc<T>,
+ T: Into<R::IoType>,
+{
+ /// Transforms the value to be written by applying `f`, returning the modified [`IoWrite`].
+ #[inline(always)]
+ pub fn update<F>(mut self, f: F) -> Self
+ where
+ F: FnOnce(T) -> T,
+ {
+ self.value = f(self.value);
+
+ self
+ }
+
+ /// Transforms the value to be written by applying `f`, returning the modified [`IoWrite`] on
+ /// success.
+ #[inline(always)]
+ pub fn try_update<F, E>(mut self, f: F) -> Result<Self, E>
+ where
+ F: FnOnce(T) -> Result<T, E>,
+ {
+ self.value = f(self.value)?;
+
+ Ok(self)
+ }
+}
+
/// Types implementing this trait (e.g. MMIO BARs or PCI config regions)
/// can perform I/O operations on regions of memory.
///
@@ -406,6 +558,95 @@ 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, R>(&self, r: R) -> Result<T>
+ where
+ R: IoLoc<T>,
+ T: Into<R::IoType>,
+ Self: IoCapable<R::IoType>,
+ {
+ let address = self.io_addr::<R::IoType>(r.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, R>(&self, op: IoWrite<T, R>) -> Result
+ where
+ R: IoLoc<T>,
+ T: Into<R::IoType>,
+ Self: IoCapable<R::IoType>,
+ {
+ let address = self.io_addr::<R::IoType>(op.loc.offset())?;
+
+ // SAFETY: `address` has been validated by `io_addr`.
+ unsafe { self.io_write(op.value.into(), 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, R, F>(&self, r: R, f: F) -> Result
+ where
+ R: IoLoc<T>,
+ T: Into<R::IoType>,
+ Self: IoCapable<R::IoType>,
+ F: FnOnce(T) -> T,
+ {
+ let v = self.try_read(r)?;
+ self.try_write(r.set(f(v)))
+ }
+
+ /// Generic infallible read with compile-time bounds check.
+ #[inline(always)]
+ fn read<T, R>(&self, r: R) -> T
+ where
+ R: IoLoc<T>,
+ T: Into<R::IoType>,
+ Self: IoKnownSize + IoCapable<R::IoType>,
+ {
+ let address = self.io_addr_assert::<R::IoType>(r.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, R>(&self, op: IoWrite<T, R>)
+ where
+ R: IoLoc<T>,
+ T: Into<R::IoType>,
+ Self: IoKnownSize + IoCapable<R::IoType>,
+ {
+ let address = self.io_addr_assert::<R::IoType>(op.loc.offset());
+
+ // SAFETY: `address` has been validated by `io_addr_assert`.
+ unsafe { self.io_write(op.value.into(), 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, R, F>(&self, r: R, f: F)
+ where
+ R: IoLoc<T>,
+ T: Into<R::IoType>,
+ Self: IoKnownSize + IoCapable<R::IoType> + Sized,
+ F: FnOnce(T) -> T,
+ {
+ let v = self.read(r);
+ self.write(r.set(f(v)));
+ }
}
/// Trait for types with a known size at compile time.
--
2.53.0
^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH v7 06/10] rust: io: use generic read/write accessors for primitive accesses
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
` (4 preceding siblings ...)
2026-02-24 14:21 ` [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-27 18:04 ` Gary Guo
2026-02-24 14:21 ` [PATCH v7 07/10] rust: io: add `register!` macro Alexandre Courbot
` (4 subsequent siblings)
10 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/io.rs | 103 +++++++++++++++++++-----------------------------------
1 file changed, 35 insertions(+), 68 deletions(-)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index fdd2549d8e13..256eba16ccc8 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -275,6 +275,25 @@ fn try_init_default<F, E>(self, f: F) -> Result<IoWrite<T, Self>, E>
}
}
+/// 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);
+
/// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
///
/// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
@@ -369,10 +388,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.
@@ -381,10 +397,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.
@@ -393,10 +406,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.
@@ -405,10 +415,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.
@@ -417,11 +424,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.set(value))
}
/// Fallible 16-bit write with runtime bounds check.
@@ -430,11 +433,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.set(value))
}
/// Fallible 32-bit write with runtime bounds check.
@@ -443,11 +442,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.set(value))
}
/// Fallible 64-bit write with runtime bounds check.
@@ -456,11 +451,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.set(value))
}
/// Infallible 8-bit read with compile-time bounds check.
@@ -469,10 +460,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.
@@ -481,10 +469,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.
@@ -493,10 +478,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.
@@ -505,10 +487,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.
@@ -517,10 +496,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.set(value))
}
/// Infallible 16-bit write with compile-time bounds check.
@@ -529,10 +505,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.set(value))
}
/// Infallible 32-bit write with compile-time bounds check.
@@ -541,10 +514,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.set(value))
}
/// Infallible 64-bit write with compile-time bounds check.
@@ -553,10 +523,7 @@ 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.set(value))
}
/// Generic fallible read with runtime bounds check.
--
2.53.0
^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH v7 07/10] rust: io: add `register!` macro
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
` (5 preceding siblings ...)
2026-02-24 14:21 ` [PATCH v7 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 08/10] sample: rust: pci: use " Alexandre Courbot
` (3 subsequent siblings)
10 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/io.rs | 5 +-
rust/kernel/io/register.rs | 1125 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1129 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 256eba16ccc8..690c25de979d 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;
@@ -177,7 +178,7 @@ pub trait IoCapable<T> {
///
/// This trait is the key abstraction allowing [`Io::read`], [`Io::write`], and [`Io::update`]
/// to work uniformly with both raw `usize` offsets (for primitive types like `u32`) and typed
-/// ones.
+/// ones (like those generated by the [`register!`] macro).
///
/// An `IoLoc<T>` carries three pieces of information:
///
@@ -192,6 +193,8 @@ pub trait IoCapable<T> {
/// An `IoLoc` can be passed directly to [`Io::read`] or [`Io::try_read`] to obtain a value, or
/// turned into an [`IoWrite`] via [`IoLoc::set`] to be passed to [`Io::write`] or
/// [`Io::try_write`].
+///
+/// [`register!`]: kernel::register!
pub trait IoLoc<T>: Copy
where
T: Into<Self::IoType>,
diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
new file mode 100644
index 000000000000..498cb3b9dfb5
--- /dev/null
+++ b/rust/kernel/io/register.rs
@@ -0,0 +1,1125 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! A macro to define register layout and accessors.
+//!
+//! A single register typically includes several fields, which are accessed through a combination
+//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
+//! not all possible field values are necessarily valid.
+//!
+//! The [`register!`] macro in this module provides an intuitive and readable syntax for defining a
+//! dedicated type for each register. Each such type comes with its own field accessors that can
+//! return an error if a field's value is invalid.
+//!
+//! [`register!`]: kernel::register!
+
+use core::marker::PhantomData;
+
+use crate::io::IoLoc;
+
+/// 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.
+pub trait Register: Copy {
+ /// Backing primitive type of the register.
+ type Storage;
+}
+
+/// Trait implemented by registers with a fixed offset.
+pub trait FixedRegister: Register {
+ /// Offset of the register.
+ const OFFSET: usize;
+}
+
+/// Location of a fixed register.
+#[derive(Clone, Copy)]
+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 + From<T::Storage> + Into<T::Storage>,
+{
+ type IoType = T::Storage;
+
+ fn offset(self) -> usize {
+ T::OFFSET
+ }
+}
+
+/// Trait implemented by relative registers.
+pub trait RelativeRegister: Register {
+ /// Family of bases applicable to this register.
+ type BaseFamily;
+
+ /// Offset of the register relative to its base.
+ const OFFSET: usize;
+}
+
+/// 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, B: ?Sized>(PhantomData<T>, PhantomData<B>);
+
+// `Clone` and `Copy` unfortunately cannot be derived without requiring `B` to also implement them.
+impl<T, B> Clone for RelativeRegisterLoc<T, B>
+where
+ B: ?Sized,
+{
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<T, B> Copy for RelativeRegisterLoc<T, B> where B: ?Sized {}
+
+impl<T, B> RelativeRegisterLoc<T, B>
+where
+ B: ?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)
+ }
+}
+
+impl<T, B> IoLoc<T> for RelativeRegisterLoc<T, B>
+where
+ T: RelativeRegister + From<T::Storage> + Into<T::Storage>,
+ B: RegisterBase<T::BaseFamily> + ?Sized,
+{
+ type IoType = T::Storage;
+
+ fn offset(self) -> usize {
+ B::BASE + T::OFFSET
+ }
+}
+
+/// Trait implemented by arrays of registers.
+pub trait RegisterArray: Register {
+ /// Start offset of the registers array.
+ const OFFSET: usize;
+ /// 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.
+#[derive(Clone, Copy)]
+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 + From<T::Storage> + Into<T::Storage>,
+{
+ type IoType = T::Storage;
+
+ fn offset(self) -> usize {
+ T::OFFSET + self.0 * T::STRIDE
+ }
+}
+
+/// Trait implemented by arrays of relative registers.
+pub trait RelativeRegisterArray: Register {
+ /// Family of bases applicable to this register array.
+ type BaseFamily;
+
+ /// Offset of the registers array relative to its base.
+ const OFFSET: usize;
+ /// Number of elements in the registers array.
+ const SIZE: usize;
+ /// Number of bytes between each element in the registers array.
+ const STRIDE: usize;
+}
+
+/// Location to a relative array register.
+pub struct RelativeRegisterArrayLoc<
+ T: RelativeRegisterArray,
+ B: RegisterBase<T::BaseFamily> + ?Sized,
+>(usize, PhantomData<T>, PhantomData<B>);
+
+// `Clone` and `Copy` unfortunately cannot be derived without requiring `B` to also implement them.
+impl<T, B> Clone for RelativeRegisterArrayLoc<T, B>
+where
+ T: RelativeRegisterArray,
+ B: RegisterBase<T::BaseFamily> + ?Sized,
+{
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl<T, B> Copy for RelativeRegisterArrayLoc<T, B>
+where
+ T: RelativeRegisterArray,
+ B: RegisterBase<T::BaseFamily> + ?Sized,
+{
+}
+
+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(idx, PhantomData, PhantomData)
+ }
+
+ /// 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(idx, PhantomData, PhantomData))
+ } 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)
+ }
+
+ /// Attempts to return 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 + From<T::Storage> + Into<T::Storage>,
+ B: RegisterBase<T::BaseFamily> + ?Sized,
+{
+ type IoType = T::Storage;
+
+ fn offset(self) -> usize {
+ B::BASE + T::OFFSET + self.0 * 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.
+///
+/// # Example
+///
+/// ```
+/// use kernel::register;
+///
+/// register! {
+/// /// Basic information about the chip.
+/// pub BOOT_0(u32) @ 0x00000100 {
+/// /// Vendor ID.
+/// 15:8 vendor_id;
+/// /// Major revision of the chip.
+/// 7:4 major_revision;
+/// /// Minor revision of the chip.
+/// 3:0 minor_revision;
+/// }
+/// }
+/// ```
+///
+/// This defines a 32-bit `BOOT_0` type which can be read from or written to offset `0x100` of an
+/// `Io` region, with the described fields. 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.
+///
+/// ```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.
+/// bar.write(BOOT_0.set(boot0
+/// // Constant values.
+/// .with_const_major_revision::<3>()
+/// .with_const_minor_revision::<10>()
+/// // Run-time value.
+/// .with_vendor_id(obtain_vendor_id())
+/// ));
+///
+/// // Or, build a new value from zero and write it:
+/// bar.write(BOOT_0.init(|r| r
+/// .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>();
+/// # }
+/// ```
+///
+/// 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.
+///
+/// ```
+/// use kernel::register;
+///
+/// register! {
+/// pub BOOT_0(u8) @ 0x00000100 {
+/// 7:4 major_revision;
+/// 3:0 minor_revision;
+/// }
+///
+/// pub BOOT_1(u8) @ 0x00000101 {
+/// 7:5 num_threads;
+/// 4:0 num_cores;
+/// }
+/// };
+/// ```
+///
+/// It is possible to create an alias of an existing register with new field definitions by using
+/// the `=> ALIAS` syntax. This is useful for cases where a register's interpretation depends on
+/// the context:
+///
+/// ```
+/// use kernel::register;
+///
+/// register! {
+/// /// Scratch register.
+/// pub SCRATCH(u32) @ 0x00000200 {
+/// /// Raw value.
+/// 31:0 value;
+/// }
+///
+/// /// Boot status of the firmware.
+/// pub SCRATCH_BOOT_STATUS(u32) => SCRATCH {
+/// /// Whether the firmware has completed booting.
+/// 0:0 completed;
+/// }
+/// }
+/// ```
+///
+/// In this example, `SCRATCH_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also
+/// providing its own `completed` field.
+///
+/// ## Relative registers
+///
+/// A register can be defined as being accessible from a fixed offset of a provided base. For
+/// instance, imagine the following I/O space:
+///
+/// ```text
+/// +-----------------------------+
+/// | ... |
+/// | |
+/// 0x100--->+------------CPU0-------------+
+/// | |
+/// 0x110--->+-----------------------------+
+/// | CPU_CTL |
+/// +-----------------------------+
+/// | ... |
+/// | |
+/// | |
+/// 0x200--->+------------CPU1-------------+
+/// | |
+/// 0x210--->+-----------------------------+
+/// | CPU_CTL |
+/// +-----------------------------+
+/// | ... |
+/// +-----------------------------+
+/// ```
+///
+/// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O
+/// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define
+/// them twice and would prefer a way to select which one to use from a single definition.
+///
+/// This can be done using the `Base + Offset` syntax when specifying the register's address.
+///
+/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the
+/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for
+/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated
+/// into code:
+///
+/// ```no_run
+/// use kernel::register;
+/// use kernel::io::register::RegisterBase;
+///
+/// // Type used to identify the base.
+/// pub struct CpuCtlBase;
+///
+/// // ZST describing `CPU0`.
+/// struct Cpu0;
+/// impl RegisterBase<CpuCtlBase> for Cpu0 {
+/// const BASE: usize = 0x100;
+/// }
+/// // Singleton of `CPU0` used to identify it.
+/// const CPU0: Cpu0 = Cpu0;
+///
+/// // ZST describing `CPU1`.
+/// struct Cpu1;
+/// impl RegisterBase<CpuCtlBase> for Cpu1 {
+/// const BASE: usize = 0x200;
+/// }
+/// // Singleton of `CPU1` used to identify it.
+/// const CPU1: Cpu1 = Cpu1;
+///
+/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: T) {
+/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
+/// register! {
+/// /// CPU core control.
+/// pub CPU_CTL(u32) @ CpuCtlBase + 0x10 {
+/// /// Start the CPU core.
+/// 0:0 start;
+/// }
+/// }
+///
+/// // Start `Cpu0`.
+/// bar.update(CPU_CTL::of::<Cpu0>(), |r| r.with_start(true));
+///
+/// // Start `Cpu1`.
+/// bar.update(CPU_CTL::of::<Cpu1>(), |r| r.with_start(true));
+///
+/// // Aliases can also be defined for relative register.
+/// register! {
+/// /// Alias to CPU core control.
+/// pub CPU_CTL_ALIAS(u32) => CpuCtlBase + CPU_CTL {
+/// /// Start the aliased CPU core.
+/// 1:1 alias_start;
+/// }
+/// }
+///
+/// // Start the aliased `CPU0`.
+/// bar.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. Simply specify their size inside `[` and `]` brackets,
+/// and use the `at` method to obtain the correct location:
+///
+/// ```no_run
+/// use kernel::register;
+///
+/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: T)
+/// # -> Result<(), Error>{
+/// # fn get_scratch_idx() -> usize {
+/// # 0x15
+/// # }
+/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
+/// register! {
+/// /// Scratch registers.
+/// pub SCRATCH(u32)[64] @ 0x00000080 {
+/// 31:0 value;
+/// }
+/// }
+///
+/// // Read scratch register 0, i.e. I/O address `0x80`.
+/// let scratch_0 = bar.read(SCRATCH::at(0)).value();
+/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
+/// let scratch_15 = bar.read(SCRATCH::at(15)).value();
+///
+/// // 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 = bar.read(SCRATCH::try_at(idx).ok_or(EINVAL)?).value();
+///
+/// // Alias to a particular register in an array.
+/// // Here `SCRATCH[8]` is used to convey the firmware exit code.
+/// register! {
+/// /// Firmware exit status code.
+/// pub FIRMWARE_STATUS(u32) => SCRATCH[8] {
+/// 7:0 status;
+/// }
+/// }
+/// let status = bar.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:
+///
+/// ```no_run
+/// use kernel::register;
+/// use kernel::io::register::RegisterBase;
+///
+/// # fn test<T: kernel::io::IoKnownSize + kernel::io::IoCapable<u32>>(bar: T)
+/// # -> Result<(), Error>{
+/// # fn get_scratch_idx() -> usize {
+/// # 0x15
+/// # }
+/// // Type used as parameter of `RegisterBase` to specify the base.
+/// pub struct CpuCtlBase;
+///
+/// // ZST describing `CPU0`.
+/// struct Cpu0;
+/// impl RegisterBase<CpuCtlBase> for Cpu0 {
+/// const BASE: usize = 0x100;
+/// }
+/// // Singleton of `CPU0` used to identify it.
+/// const CPU0: Cpu0 = Cpu0;
+///
+/// // ZST describing `CPU1`.
+/// struct Cpu1;
+/// impl RegisterBase<CpuCtlBase> for Cpu1 {
+/// const BASE: usize = 0x200;
+/// }
+/// // Singleton of `CPU1` used to identify it.
+/// const CPU1: Cpu1 = Cpu1;
+///
+/// // 64 per-cpu scratch registers, arranged as a contiguous array.
+/// register! {
+/// /// Per-CPU scratch registers.
+/// pub CPU_SCRATCH(u32)[64] @ CpuCtlBase + 0x00000080 {
+/// 31:0 value;
+/// }
+/// }
+///
+/// // Read scratch register 0 of CPU0.
+/// let cpu0_scratch_0 = bar.read(CPU_SCRATCH::of::<Cpu0>().at(0)).value();
+/// // Read scratch register 15 of CPU1.
+/// let cpu1_scratch_15 = bar.read(CPU_SCRATCH::of::<Cpu1>().at(15)).value();
+///
+/// // 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 = bar.read(
+/// CPU_SCRATCH::of::<Cpu0>().try_at(scratch_idx).ok_or(EINVAL)?
+/// ).value();
+///
+/// // `SCRATCH[8]` is used to convey the firmware exit code.
+/// register! {
+/// /// Per-CPU firmware exit status code.
+/// pub CPU_FIRMWARE_STATUS(u32) => CpuCtlBase + CPU_SCRATCH[8] {
+/// 7:0 status;
+/// }
+/// }
+///
+/// let cpu0_status = bar.read(CPU_FIRMWARE_STATUS::of::<Cpu0>()).status();
+///
+/// // Non-contiguous register arrays can be defined by adding a stride parameter.
+/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
+/// // registers of the two declarations below are interleaved.
+/// register! {
+/// /// Scratch registers bank 0.
+/// pub CPU_SCRATCH_INTERLEAVED_0(u32)[16, 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;
+/// }
+/// }
+/// # 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_fixed $(#[$attr])* $vis $name($storage) @ $offset);
+ };
+
+ // Creates an alias register of fixed offset register `alias` with its own fields.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $alias:ident
+ { $($fields:tt)* }
+ ) => {
+ $crate::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ $crate::register!(
+ @io_fixed $(#[$attr])* $vis $name($storage) @
+ <$alias as $crate::io::register::FixedRegister>::OFFSET
+ );
+ };
+
+ // Creates a register at a relative offset from a base address provider.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $base:ident + $offset:literal
+ { $($fields:tt)* }
+ ) => {
+ $crate::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ $crate::register!(@io_relative $vis $name($storage) @ $base + $offset );
+ };
+
+ // Creates an alias register of relative offset register `alias` with its own fields.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) => $base:ident + $alias:ident
+ { $($fields:tt)* }
+ ) => {
+ $crate::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ $crate::register!(
+ @io_relative $vis $name($storage) @
+ $base + <$alias as $crate::io::register::RelativeRegister>::OFFSET
+ );
+ };
+
+ // Creates an array of registers at a fixed offset of the MMIO space.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ [ $size:expr, stride = $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_array $vis $name($storage) [ $size, stride = $stride ] @ $offset);
+ };
+
+ // Shortcut for contiguous array of registers (stride == size of element).
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) [ $size:expr ] @ $offset:literal
+ { $($fields:tt)* }
+ ) => {
+ $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_fixed $(#[$attr])* $vis $name($storage)
+ @ <$alias as $crate::io::register::RegisterArray>::OFFSET
+ + $idx * <$alias as $crate::io::register::RegisterArray>::STRIDE
+ );
+ };
+
+ // Creates an array of registers at a relative offset from a base address provider.
+ (
+ @reg $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty)
+ [ $size:expr, stride = $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_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::RelativeRegisterArray>::SIZE);
+
+ $crate::register!(
+ @bitfield $(#[$attr])* $vis struct $name($storage) { $($fields)* }
+ );
+ $crate::register!(
+ @io_relative $vis $name($storage) @ $base
+ + <$alias as $crate::io::register::RelativeRegisterArray>::OFFSET
+ + $idx * <$alias as $crate::io::register::RelativeRegisterArray>::STRIDE
+ );
+ };
+
+ // Generates the bitfield for the register.
+ //
+ // `#[allow(non_camel_case_types)]` is added since register names typically use
+ // `SCREAMING_CASE`.
+ (
+ @bitfield $(#[$attr:meta])* $vis:vis struct $name:ident($storage:ty) { $($fields:tt)* }
+ ) => {
+ $crate::register!(@bitfield_core
+ #[allow(non_camel_case_types)]
+ $(#[$attr])* $vis $name $storage
+ );
+ $crate::register!(@bitfield_fields $vis $name $storage { $($fields)* });
+
+ impl $crate::io::register::Register for $name {
+ type Storage = $storage;
+ }
+ };
+
+ // Implementations of fixed registers.
+ (@io_fixed $(#[$attr:meta])* $vis:vis $name:ident ($storage:ty) @ $offset:expr) => {
+ impl $crate::io::register::FixedRegister for $name {
+ const OFFSET: usize = $offset;
+ }
+
+ $(#[$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 + $offset:expr ) => {
+ impl $crate::io::register::RelativeRegister for $name
+ {
+ type BaseFamily = $base;
+ const OFFSET: usize = $offset;
+ }
+
+ #[allow(dead_code)]
+ impl $name {
+ /// Returns the location of the register with base `B`.
+ #[inline(always)]
+ $vis const fn of<B: $crate::io::register::RegisterBase<$base>>()
+ -> $crate::io::register::RelativeRegisterLoc<$name, B> {
+ $crate::io::register::RelativeRegisterLoc::new()
+ }
+ }
+ };
+
+ // Implementations of array registers.
+ (@io_array $vis:vis $name:ident ($storage:ty) [ $size:expr, stride = $stride:expr ]
+ @ $offset:literal) => {
+ impl $crate::io::register::RegisterArray for $name {
+ const OFFSET: usize = $offset;
+ const SIZE: usize = $size;
+ const STRIDE: usize = $stride;
+ }
+
+ #[allow(dead_code)]
+ impl $name {
+ /// Returns the location of the register at index `idx`, with build-time validation.
+ #[inline(always)]
+ $vis fn at(idx: usize) -> $crate::io::register::RegisterArrayLoc<$name> {
+ $crate::io::register::RegisterArrayLoc::new(idx)
+ }
+
+ /// Attempts to return the location of the register at index `idx`, with runtime
+ /// validation.
+ #[inline(always)]
+ $vis fn try_at(idx: usize) -> Option<$crate::io::register::RegisterArrayLoc<$name>> {
+ $crate::io::register::RegisterArrayLoc::try_new(idx)
+ }
+ }
+ };
+
+ // 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::RelativeRegisterArray for $name {
+ type BaseFamily = $base;
+ const OFFSET: usize = $offset;
+ const SIZE: usize = $size;
+ const STRIDE: usize = $stride;
+ }
+
+ #[allow(dead_code)]
+ impl $name {
+ /// Returns the location of the register array with base `B`.
+ ///
+ /// An individual register from the array still needs to be addressed using
+ /// [`RelativeRegisterLoc::at`] or [`RelativeRegisterLoc::try_at`].
+ #[inline(always)]
+ $vis const fn of<B: $crate::io::register::RegisterBase<$base>>()
+ -> $crate::io::register::RelativeRegisterLoc<$name, B> {
+ $crate::io::register::RelativeRegisterLoc::new()
+ }
+ }
+ };
+
+ // 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] 64+ messages in thread
* [PATCH v7 08/10] sample: rust: pci: use `register!` macro
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
` (6 preceding siblings ...)
2026-02-24 14:21 ` [PATCH v7 07/10] rust: io: add `register!` macro Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-24 14:21 ` [PATCH FOR REFERENCE v7 09/10] gpu: nova-core: use the kernel " Alexandre Courbot
` (2 subsequent siblings)
10 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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..0a5284d2f5e9 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::{
+ Io,
+ IoLoc, //
+ },
+ 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(regs::TEST.init(|r| r.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(regs::TEST.init(|r| r.with_index(this.index)));
}
}
}
--
2.53.0
^ permalink raw reply related [flat|nested] 64+ messages in thread
* [PATCH FOR REFERENCE v7 09/10] gpu: nova-core: use the kernel `register!` macro
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
` (7 preceding siblings ...)
2026-02-24 14:21 ` [PATCH v7 08/10] sample: rust: pci: use " Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 10/10] RFC: rust: io: allow fixed register values directly in `write` Alexandre Courbot
2026-02-25 11:58 ` [PATCH v7 00/10] rust: add `register!` macro Dirk Behme
10 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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 | 249 +++++-----
drivers/gpu/nova-core/falcon/gsp.rs | 23 +-
drivers/gpu/nova-core/falcon/hal/ga102.rs | 65 +--
drivers/gpu/nova-core/falcon/hal/tu102.rs | 11 +-
drivers/gpu/nova-core/falcon/sec2.rs | 17 +-
drivers/gpu/nova-core/fb.rs | 6 +-
drivers/gpu/nova-core/fb/hal/ga100.rs | 40 +-
drivers/gpu/nova-core/fb/hal/ga102.rs | 7 +-
drivers/gpu/nova-core/fb/hal/tu102.rs | 22 +-
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 | 10 +-
drivers/gpu/nova-core/regs.rs | 544 ++++++++++++----------
drivers/gpu/nova-core/regs/macros.rs | 739 ------------------------------
15 files changed, 565 insertions(+), 1226 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 37bfee1d0949..140f00185115 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -12,7 +12,13 @@
DmaAddress,
DmaMask, //
},
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ register::RegisterBase,
+ Io,
+ IoLoc, //
+ },
+ num::Bounded,
prelude::*,
sync::aref::ARef,
time::{
@@ -30,7 +36,6 @@
IntoSafeCast, //
},
regs,
- regs::macros::RegisterBase, //
};
pub(crate) mod gsp;
@@ -38,11 +43,11 @@
pub(crate) mod sec2;
// TODO[FPRI]: Replace with `ToPrimitive`.
-macro_rules! impl_from_enum_to_u8 {
- ($enum_type:ty) => {
- impl From<$enum_type> for u8 {
+macro_rules! impl_from_enum_to_bounded {
+ ($enum_type:ty, $length:literal) => {
+ impl From<$enum_type> for Bounded<u32, $length> {
fn from(value: $enum_type) -> Self {
- value as u8
+ Bounded::from_expr(value as u32)
}
}
};
@@ -50,10 +55,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,
@@ -62,16 +65,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,
@@ -88,46 +91,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
@@ -141,16 +136,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,
@@ -163,24 +158,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),
}
@@ -188,21 +181,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),
}
@@ -210,33 +201,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 {
@@ -252,10 +234,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).
@@ -263,14 +243,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,
@@ -282,34 +262,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(());
@@ -318,13 +289,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).
@@ -394,8 +362,10 @@ 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(regs::NV_PFALCON_FALCON_DMACTL::of::<E>().zeroed());
}
/// Reset the controller, select the falcon core, and wait for memory scrubbing to complete.
@@ -404,9 +374,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(
+ regs::NV_PFALCON_FALCON_RM::of::<E>()
+ .init(|r| r.with_value(bar.read(regs::NV_PMC_BOOT_0).into_raw())),
+ );
Ok(())
}
@@ -475,36 +446,37 @@ fn dma_wr<F: FalconFirmware<Target = E>>(
// 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(regs::NV_PFALCON_FALCON_DMATRFBASE::of::<E>().init(|r|
+ // 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`.
+ r.with_base((dma_start >> 8) as u32)));
+ bar.write(
+ regs::NV_PFALCON_FALCON_DMATRFBASE1::of::<E>()
+ .try_init(|r| r.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(
+ regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
+ .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?,
+ );
+ bar.write(
+ regs::NV_PFALCON_FALCON_DMATRFFBOFFS::of::<E>()
+ .init(|r| r.with_offs(src_start + pos)),
+ );
+
+ bar.write(regs::NV_PFALCON_FALCON_DMATRFCMD::of::<E>().set(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),
@@ -524,9 +496,9 @@ fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) -> Result
}
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(bar, fw, FalconMem::ImemSecure, fw.imem_sec_load_params())?;
@@ -535,9 +507,9 @@ fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) -> Result
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(
+ regs::NV_PFALCON_FALCON_BOOTVEC::of::<E>().init(|r| r.with_value(fw.boot_addr())),
+ );
Ok(())
}
@@ -546,7 +518,7 @@ fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) -> Result
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),
@@ -557,13 +529,16 @@ 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(
+ regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::of::<E>().init(|r| r.with_startcpu(true)),
+ ),
+ false => {
+ bar.write(regs::NV_PFALCON_FALCON_CPUCTL::of::<E>().init(|r| r.with_startcpu(true)))
+ }
}
Ok(())
@@ -572,26 +547,24 @@ 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(regs::NV_PFALCON_FALCON_MAILBOX0::of::<E>().init(|r| r.with_value(mbox0)));
}
if let Some(mbox1) = mbox1 {
- regs::NV_PFALCON_FALCON_MAILBOX1::default()
- .set_value(mbox1)
- .write(bar, &E::ID);
+ bar.write(regs::NV_PFALCON_FALCON_MAILBOX1::of::<E>().init(|r| r.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.
@@ -650,8 +623,6 @@ pub(crate) fn load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) ->
/// 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(regs::NV_PFALCON_FALCON_OS::of::<E>().init(|r| r.with_value(app_version)));
}
}
diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
index 67edef3636c1..40330ce3a617 100644
--- a/drivers/gpu/nova-core/falcon/gsp.rs
+++ b/drivers/gpu/nova-core/falcon/gsp.rs
@@ -1,7 +1,12 @@
// SPDX-License-Identifier: GPL-2.0
use kernel::{
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ register::RegisterBase,
+ Io,
+ IoLoc, //
+ },
prelude::*,
time::Delta, //
};
@@ -14,13 +19,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 +32,19 @@ 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(regs::NV_PFALCON_FALCON_IRQSCLR::of::<Gsp>().init(|r| r.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..ec15c096d62e 100644
--- a/drivers/gpu/nova-core/falcon/hal/ga102.rs
+++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs
@@ -5,6 +5,8 @@
use kernel::{
device,
io::poll::read_poll_timeout,
+ io::Io,
+ io::IoLoc,
prelude::*,
time::Delta, //
};
@@ -25,15 +27,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(
+ regs::NV_PRISCV_RISCV_BCR_CTRL::of::<E>()
+ .init(|r| r.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 +63,42 @@ 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(
+ regs::NV_PFALCON2_FALCON_BROM_PARAADDR::of::<E>()
+ .at(0)
+ .init(|r| r.with_value(params.pkc_data_offset)),
+ );
+ bar.write(
+ regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::of::<E>()
+ .init(|r| r.with_value(u32::from(params.engine_id_mask))),
+ );
+ bar.write(
+ regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::of::<E>()
+ .init(|r| r.with_ucode_id(params.ucode_id)),
+ );
+ bar.write(
+ regs::NV_PFALCON2_FALCON_MOD_SEL::of::<E>().init(|r| r.with_algo(FalconModSelAlgo::Rsa3k)),
+ );
Ok(())
}
@@ -120,14 +131,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 +147,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..cd4ac53e32af 100644
--- a/drivers/gpu/nova-core/falcon/hal/tu102.rs
+++ b/drivers/gpu/nova-core/falcon/hal/tu102.rs
@@ -3,7 +3,10 @@
use core::marker::PhantomData;
use kernel::{
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ Io, //
+ },
prelude::*,
time::Delta, //
};
@@ -49,14 +52,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..9deb0a32b7b9 100644
--- a/drivers/gpu/nova-core/fb/hal/ga100.rs
+++ b/drivers/gpu/nova-core/fb/hal/ga100.rs
@@ -1,6 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
-use kernel::prelude::*;
+use kernel::{
+ io::{
+ Io,
+ IoLoc, //
+ },
+ num::Bounded,
+ prelude::*, //
+};
use crate::{
driver::Bar0,
@@ -13,26 +20,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(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI.init(|r| {
+ r.with_adr_63_40(
+ Bounded::<u64, _>::from(addr)
+ .shr::<FLUSH_SYSMEM_ADDR_SHIFT_HI, _>()
+ .cast(),
+ )
+ }));
+
+ bar.write(
+ regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR
+ // 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`.
+ .init(|r| r.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..67327adaa04f 100644
--- a/drivers/gpu/nova-core/fb/hal/tu102.rs
+++ b/drivers/gpu/nova-core/fb/hal/tu102.rs
@@ -1,6 +1,12 @@
// SPDX-License-Identifier: GPL-2.0
-use kernel::prelude::*;
+use kernel::{
+ io::{
+ Io,
+ IoLoc, //
+ },
+ prelude::*, //
+};
use crate::{
driver::Bar0,
@@ -13,26 +19,24 @@
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 {
// Check that the address doesn't overflow the receiving 32-bit register.
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)
- })
+ .map(|addr| bar.write(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR.init(|r| r.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/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 9b042ef1a308..ca2c153cf87f 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, //
@@ -122,24 +124,19 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
}
/// Enum representation of the GPU generation.
-///
-/// TODO: remove the `Default` trait implementation, and the `#[default]`
-/// attribute, once the register!() macro (which creates Architecture items) no
-/// longer requires it for read-only fields.
-#[derive(fmt::Debug, Default, Copy, Clone)]
+#[derive(fmt::Debug, Copy, Clone)]
#[repr(u8)]
pub(crate) enum Architecture {
- #[default]
Turing = 0x16,
Ampere = 0x17,
Ada = 0x19,
}
-impl TryFrom<u8> for Architecture {
+impl TryFrom<Bounded<u32, 6>> for Architecture {
type Error = Error;
- fn try_from(value: u8) -> Result<Self> {
- match value {
+ fn try_from(value: Bounded<u32, 6>) -> Result<Self> {
+ match u8::from(value) {
0x16 => Ok(Self::Turing),
0x17 => Ok(Self::Ampere),
0x19 => Ok(Self::Ada),
@@ -148,23 +145,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(),
}
}
}
@@ -201,13 +201,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 be427fe26a58..154a2bc4655e 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, //
@@ -55,7 +56,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"
@@ -78,7 +79,9 @@ fn run_fwsec_frts(
fwsec_frts.run(dev, falcon, bar)?;
// 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,
@@ -91,8 +94,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 46819a82a51a..c23aa822cdb6 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -16,7 +16,11 @@
DmaAddress, //
},
dma_write,
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ Io,
+ IoLoc, //
+ },
prelude::*,
sync::aref::ARef,
time::Delta,
@@ -475,9 +479,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(regs::NV_PGSP_QUEUE_HEAD.init(|r| r.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 ea0d32f5396c..63d448e90406 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -1,13 +1,7 @@
// 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::Io,
prelude::*,
time, //
};
@@ -35,20 +29,64 @@
num::FromSafeCast,
};
+// All nova-core registers are 32-bit and `pub(crate)`. Wrap the `register!` macro to avoid
+// repeating this information for every register.
+macro_rules! nv_reg {
+ (
+ $(
+ $(#[$attr:meta])* $name:ident $([ $size:expr $(; $stride:expr)? ])?
+ $(@ $offset:literal)?
+ $(@ $base:ident + $base_offset:literal)?
+ $(=> $alias:ident $(+ $alias_offset:ident)? $([$alias_idx:expr])? )?
+ $(, $comment:literal)? { $($fields:tt)* }
+ )*
+ )=> {
+ $(
+ ::kernel::register!(
+ @reg $(#[$attr])* pub(crate) $name(u32) $([$size $(; $stride)?])?
+ $(@ $offset)?
+ $(@ $base + $base_offset)?
+ $(=> $alias $(+ $alias_offset)? $([$alias_idx])? )?
+ $(, $comment)? { $($fields)* }
+ );
+ )*
+ };
+}
+
// PMC
-register!(NV_PMC_BOOT_0 @ 0x00000000, "Basic revision information about the GPU" {
- 3:0 minor_revision as u8, "Minor revision of the chip";
- 7:4 major_revision as u8, "Major revision of the chip";
- 8:8 architecture_1 as u8, "MSB of the architecture";
- 23:20 implementation as u8, "Implementation version of the architecture";
- 28:24 architecture_0 as u8, "Lower bits of the architecture";
-});
+nv_reg! {
+ /// Basic revision information about the GPU.
+ NV_PMC_BOOT_0 @ 0x00000000 {
+ /// Minor revision of the chip.
+ 3:0 minor_revision;
+ /// Major revision of the chip.
+ 7:4 major_revision;
+ /// Meta-variable `newbase` repeats 0 times, but `offset` repeats 1 time.
+ 8:8 architecture_1;
+ /// Implementation version of the architecture.
+ 23:20 implementation;
+ /// Lower bits of the architecture.
+ 28:24 architecture_0;
+ }
+
+ /// Extended architecture information.
+ NV_PMC_BOOT_42 @ 0x00000a00 {
+ /// Minor revision of the chip.
+ 15:12 minor_revision;
+ /// Major revision of the chip.
+ 19:16 major_revision;
+ /// Implementation version of the architecture.
+ 23:20 implementation;
+ /// Architecture value.
+ 29:24 architecture ?=> Architecture;
+ }
+}
impl NV_PMC_BOOT_0 {
pub(crate) fn is_older_than_fermi(self) -> bool {
// From https://github.com/NVIDIA/open-gpu-doc/tree/master/manuals :
- const NV_PMC_BOOT_0_ARCHITECTURE_GF100: u8 = 0xc;
+ const NV_PMC_BOOT_0_ARCHITECTURE_GF100: u32 = 0xc;
// Older chips left arch1 zeroed out. That, combined with an arch0 value that is less than
// GF100, means "older than Fermi".
@@ -56,13 +94,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 +107,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 +117,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 +126,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 +186,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 +193,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 +211,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 +243,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 +264,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 +288,127 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
pub(crate) const NV_FUSE_OPT_FPF_SIZE: usize = 16;
-register!(NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION @ 0x00824100[NV_FUSE_OPT_FPF_SIZE] {
- 15:0 data as u16;
-});
+nv_reg! {
+ NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION[NV_FUSE_OPT_FPF_SIZE] @ 0x00824100 {
+ 15:0 data;
+ }
-register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140[NV_FUSE_OPT_FPF_SIZE] {
- 15:0 data as u16;
-});
+ NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION[NV_FUSE_OPT_FPF_SIZE] @ 0x00824140 {
+ 15:0 data;
+ }
-register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0[NV_FUSE_OPT_FPF_SIZE] {
- 15:0 data as u16;
-});
-
-// 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;
+ }
+
+ /// 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,117 +417,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;
-});
-
-// 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.
@@ -437,15 +499,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] 64+ messages in thread
* [PATCH v7 10/10] RFC: rust: io: allow fixed register values directly in `write`
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
` (8 preceding siblings ...)
2026-02-24 14:21 ` [PATCH FOR REFERENCE v7 09/10] gpu: nova-core: use the kernel " Alexandre Courbot
@ 2026-02-24 14:21 ` Alexandre Courbot
2026-02-25 11:58 ` [PATCH v7 00/10] rust: add `register!` macro Dirk Behme
10 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-24 14:21 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
When reading and writing a fixed register, we end up specifying the
destination register in the `write` operation even though it is already
carried in the type of its value. I.e. we need to write
let tcr = io.read(TCR);
io.write(TCR.set(tcr.with_unf(false)));
when the following should be enough:
let tcr = io.read(TCR);
// `tcr` is of type `TCR`, which carries the offset we need to write
// to.
io.write(tcr.with_unf(false));
This patch allows the second syntax to be used, by making `write` accept
any type that can be `Into`'ed an `IoWrite`, and providing the required
`Into` implementation for fixed registers.
A similar trick could be used for relative registers if we include their
base into their type (not done in this patch).
However, array registers won't be able to use this shortcut (at least
not without increasing their size to carry their originating index),
which introduces a slight inconsistency about the capabilities of each
register value. Hence this RFC to discuss whether the syntactical
benefit outweighs the consistency cost.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
rust/kernel/io.rs | 8 ++++++--
rust/kernel/io/register.rs | 14 ++++++++++++--
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 690c25de979d..b83472846d3c 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -545,12 +545,14 @@ fn try_read<T, R>(&self, r: R) -> Result<T>
/// Generic fallible write with runtime bounds check.
#[inline(always)]
- fn try_write<T, R>(&self, op: IoWrite<T, R>) -> Result
+ fn try_write<T, R, I>(&self, op: I) -> Result
where
R: IoLoc<T>,
T: Into<R::IoType>,
+ I: Into<IoWrite<T, R>>,
Self: IoCapable<R::IoType>,
{
+ let op = op.into();
let address = self.io_addr::<R::IoType>(op.loc.offset())?;
// SAFETY: `address` has been validated by `io_addr`.
@@ -590,12 +592,14 @@ fn read<T, R>(&self, r: R) -> T
/// Generic infallible write with compile-time bounds check.
#[inline(always)]
- fn write<T, R>(&self, op: IoWrite<T, R>)
+ fn write<T, R, I>(&self, op: I)
where
R: IoLoc<T>,
T: Into<R::IoType>,
+ I: Into<IoWrite<T, R>>,
Self: IoKnownSize + IoCapable<R::IoType>,
{
+ let op = op.into();
let address = self.io_addr_assert::<R::IoType>(op.loc.offset());
// SAFETY: `address` has been validated by `io_addr_assert`.
diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
index 498cb3b9dfb5..68e6ff1a2f89 100644
--- a/rust/kernel/io/register.rs
+++ b/rust/kernel/io/register.rs
@@ -305,12 +305,12 @@ fn offset(self) -> usize {
/// pr_info!("chip revision: {}.{}", boot0.major_revision().get(), boot0.minor_revision().get());
///
/// // Update some fields and write the new value back.
-/// bar.write(BOOT_0.set(boot0
+/// bar.write(boot0
/// // Constant values.
/// .with_const_major_revision::<3>()
/// .with_const_minor_revision::<10>()
/// // Run-time value.
-/// .with_vendor_id(obtain_vendor_id())
+/// .with_vendor_id(obtain_vendor_id()
/// ));
///
/// // Or, build a new value from zero and write it:
@@ -806,6 +806,16 @@ impl $crate::io::register::FixedRegister for $name {
const OFFSET: usize = $offset;
}
+ impl From<$name> for $crate::io::IoWrite<
+ $name, $crate::io::register::FixedRegisterLoc<$name>
+ > {
+ fn from(value: $name) -> Self {
+ use $crate::io::IoLoc;
+
+ $crate::io::register::FixedRegisterLoc::<$name>::new().set(value)
+ }
+ }
+
$(#[$attr])*
$vis const $name: $crate::io::register::FixedRegisterLoc<$name> =
$crate::io::register::FixedRegisterLoc::<$name>::new();
--
2.53.0
^ permalink raw reply related [flat|nested] 64+ messages in thread
* Re: [PATCH v7 00/10] rust: add `register!` macro
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
` (9 preceding siblings ...)
2026-02-24 14:21 ` [PATCH v7 10/10] RFC: rust: io: allow fixed register values directly in `write` Alexandre Courbot
@ 2026-02-25 11:58 ` Dirk Behme
2026-02-25 13:50 ` Alexandre Courbot
10 siblings, 1 reply; 64+ messages in thread
From: Dirk Behme @ 2026-02-25 11:58 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, Steven Price,
rust-for-linux, linux-kernel
On 24.02.2026 15:21, Alexandre Courbot wrote:
> New revision addressing the v6 feedback - the use of "location" instead
> of "reference" in particular reads well both in the code and the
> comments.
>
> As a reminder, since the previous revision we use the I/O type to
> perform the actual I/O instead of the register type, which moves us from
> this access pattern:
>
> let boot0 = regs::NV_PMC_BOOT_0::read(bar);
>
> to this arguably more natural one:
>
> let boot0 = bar.read(regs::NV_PMC_BOOT_0);
>
> This revision is based on `driver-core-testing` as of 2026-02-24 with
> [1] applied. A tree with this series and its dependencies is available
> at [2].
>
> [1] https://lore.kernel.org/all/20260206-io-v2-0-71dea20a06e6@nvidia.com/
> [2] https://github.com/Gnurou/linux/tree/b4/register
>
> The first patch enables the `generic_arg_infer` feature, which is
> required for generic type inference and used in subsequent patches. This
> feature is stable since rustc 1.89.
>
> The second patch adds `shr` and `shl` methods to `Bounded`. These were
> suggested by Alice during LPC as a way to avoid the use of the
> controversial `Bounded::from_expr` in both the bitfield macro and the
> Nova code. The third patch adds another convenience method to obtain a
> `bool` from single-bit `Bounded`s, while the fourth patch turns
> `Bounded::get` into a const method in order to make register setter
> methods const.
>
> Patches 5 and 6 introduce the `IoLoc` and `IoWrite` types around which
> I/O accesses are centered. This allows registers to be accessed using
> the same `read` and `write` methods as primitive types.
>
> Patch 7 adds the `register!` macro and the types it requires.
>
> Patch 8 updates the Rust PCI sample driver to use `register!`, as per
> its TODO item.
>
> Patch 9 illustrates more extensively how this macro is used by
> converting nova-core to use it, and removing the local implementation.
> This patch is to be merged one cycle after the other patches.
>
> Patch 10 is an RFC allowing a shorter write syntax to be used in the
> case of fixed or relative registers. It doesn't need to be merged
> immediately, but I think it is a good time to discuss it.
>
> I have also removed Gary's signoff on patches 5 and 7 to make sure it
> doesn't get merged before he gives it explicitly.
>
> Signed-off-by: Alexandre Courbot <acourbot@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 and IoWrite types
> rust: io: use generic read/write accessors for primitive accesses
> rust: io: add `register!` macro
> sample: rust: pci: use `register!` macro
> [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro
> RFC: rust: io: allow fixed register values directly in `write`
>
> drivers/gpu/nova-core/falcon.rs | 249 +++----
> drivers/gpu/nova-core/falcon/gsp.rs | 23 +-
> drivers/gpu/nova-core/falcon/hal/ga102.rs | 65 +-
> drivers/gpu/nova-core/falcon/hal/tu102.rs | 11 +-
> drivers/gpu/nova-core/falcon/sec2.rs | 17 +-
> drivers/gpu/nova-core/fb.rs | 6 +-
> drivers/gpu/nova-core/fb/hal/ga100.rs | 40 +-
> drivers/gpu/nova-core/fb/hal/ga102.rs | 7 +-
> drivers/gpu/nova-core/fb/hal/tu102.rs | 22 +-
> 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 | 10 +-
> drivers/gpu/nova-core/regs.rs | 544 ++++++++------
> drivers/gpu/nova-core/regs/macros.rs | 739 -------------------
> rust/kernel/io.rs | 347 +++++++--
> rust/kernel/io/register.rs | 1135 +++++++++++++++++++++++++++++
> rust/kernel/lib.rs | 3 +
> rust/kernel/num/bounded.rs | 70 +-
> samples/rust/rust_driver_pci.rs | 84 ++-
> scripts/Makefile.build | 3 +-
> 21 files changed, 2118 insertions(+), 1315 deletions(-)
> ---
> base-commit: 545225ee0b0b6998c7b6ff888f8497681b8b35c4
> change-id: 20260117-register-ccaba1d21713
I converted my simple aarch64 TMU timer example [1] over to v7 (many
thanks helping with that!) and gave it a try: It works like with v2.
With that:
Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
Thanks
Dirk
[1]
// SPDX-License-Identifier: GPL-2.0
//! Rust Renesas TMU driver.
use kernel::{
c_str,
device::{Core, Device, Bound},
devres::Devres,
io::{Io, IoCapable, IoKnownSize, mem::IoMem},
irq::{self, Flags, IrqReturn, Registration},
of, platform,
prelude::*,
register,
sync::Arc,
types::ARef,
};
use core::ops::Deref;
use core::sync::atomic::{AtomicU32, Ordering};
struct RenesasTMUDriver {
pdev: ARef<platform::Device>,
_registration: Arc<Registration<Data>>,
_iomem: Arc<Devres<IoMem<TMU_MMIO_SIZE>>>,
}
struct Info;
kernel::of_device_table!(
OF_TABLE,
MODULE_OF_TABLE,
<RenesasTMUDriver as platform::Driver>::IdInfo,
[(of::DeviceId::new(c_str!("renesas,tmu")), Info)]
);
register! {
TSTR(u8) @ 0x04 {
2:2 str2;
1:1 str1;
0:0 str0;
}
TSTRRAW(u8) => TSTR {
7:0 value;
}
TCOR(u32) @ 0x08 {
31:0 tcor;
}
TCNT(u32) @ 0x0C {
31:0 tcnt;
}
TCR(u16) @ 0x10 {
9:9 icpf;
8:8 unf;
7:6 icpe;
5:5 unie;
4:3 ckeg;
2:0 tpsc;
}
TCRRAW(u16) => TCR {
15:0 value;
}
}
impl TCR {
fn handle_underflow<T>(io: &T)
where
T: Io + IoKnownSize + IoCapable<u16>,
{
let tcr: TCR = io.read(TCR);
if tcr.unf().into_bool() {
io.write(tcr.with_unf(false));
}
}
}
const TMU_MMIO_SIZE: usize = 0x14;
// Data shared between process and IRQ context.
#[pin_data]
struct Data {
#[pin]
iomem: Arc<Devres<IoMem<TMU_MMIO_SIZE>>>,
#[pin]
irq_count: AtomicU32,
}
impl irq::Handler for Data {
// Executed in IRQ context.
fn handle(&self, dev: &Device<Bound>) -> IrqReturn {
let count = self.irq_count.fetch_add(1, Ordering::Relaxed);
dev_info!(dev, "Renesas TMU IRQ handler called {} time(s).\n",
count);
// Reset the underflow flag
let io = self.iomem.access(dev).unwrap();
TCR::handle_underflow(io.deref());
IrqReturn::Handled
}
}
impl platform::Driver for RenesasTMUDriver {
type IdInfo = Info;
const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
fn probe(
pdev: &platform::Device<Core>,
_info: Option<&Self::IdInfo>,
) -> impl PinInit<Self, Error> {
let dev = pdev.as_ref();
dev_dbg!(dev, "Probe Rust Renesas TMU driver.\n");
let request = pdev.io_request_by_index(0).ok_or(EINVAL)?;
let iomem =
Arc::pin_init(request.iomap_sized::<TMU_MMIO_SIZE>(), GFP_KERNEL)?;
let io = iomem.access(dev)?;
// Set count to 1 minute. Clock is 16.66MHz / 4 = 4.165MHz
let timeout: u32 = 4165000 * 60; // 1 minute in clock ticks
io.update(TCOR, |r| r.with_tcor(timeout));
io.update(TCNT, |r| r.with_tcnt(timeout));
// Enable underflow interrupt (UNIE, Underflow Interrupt Control)
io.update(TCR, |r| r.with_unie(true));
let request = pdev.irq_by_index(0)?;
dev_info!(dev, "IRQ: {}\n", request.irq());
let clone = iomem.clone();
let handler = try_pin_init!(Data{iomem: clone, irq_count:
AtomicU32::new(1)});
let registration = irq::Registration::new(request,
Flags::SHARED, c_str!("tmu"), handler);
let registration = Arc::pin_init(registration, GFP_KERNEL)?;
// Enable TMU
io.update(TSTR, |r| r.with_str0(true));
// Read back registers to verify
dev_info!(dev, "TSTR: 0x{:x}\n", io.read(TSTRRAW).value());
dev_info!(dev, "TCOR: 0x{:x}\n", io.read(TCOR).tcor());
dev_info!(dev, "TCNT: 0x{:x}\n", io.read(TCNT).tcnt());
dev_info!(dev, "TCR: 0x{:x}\n", io.read(TCRRAW).value());
dev_info!(dev, "probe done\n");
Ok(Self {
pdev: pdev.into(),
_registration: registration,
_iomem: iomem.clone(),
})
}
}
impl Drop for RenesasTMUDriver {
fn drop(&mut self) {
dev_dbg!(self.pdev.as_ref(), "Remove Rust Renesas TMU driver.\n");
}
}
kernel::module_platform_driver! {
type: RenesasTMUDriver,
name: "rust_tmu",
authors: ["Dirk Behme"],
description: "Rust Renesas TMU driver",
license: "GPL v2",
}
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 00/10] rust: add `register!` macro
2026-02-25 11:58 ` [PATCH v7 00/10] rust: add `register!` macro Dirk Behme
@ 2026-02-25 13:50 ` Alexandre Courbot
2026-02-26 12:01 ` Dirk Behme
0 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-25 13:50 UTC (permalink / raw)
To: Dirk Behme
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, Steven Price, rust-for-linux, linux-kernel
On Wed Feb 25, 2026 at 8:58 PM JST, Dirk Behme wrote:
> On 24.02.2026 15:21, Alexandre Courbot wrote:
>> New revision addressing the v6 feedback - the use of "location" instead
>> of "reference" in particular reads well both in the code and the
>> comments.
>>
>> As a reminder, since the previous revision we use the I/O type to
>> perform the actual I/O instead of the register type, which moves us from
>> this access pattern:
>>
>> let boot0 = regs::NV_PMC_BOOT_0::read(bar);
>>
>> to this arguably more natural one:
>>
>> let boot0 = bar.read(regs::NV_PMC_BOOT_0);
>>
>> This revision is based on `driver-core-testing` as of 2026-02-24 with
>> [1] applied. A tree with this series and its dependencies is available
>> at [2].
>>
>> [1] https://lore.kernel.org/all/20260206-io-v2-0-71dea20a06e6@nvidia.com/
>> [2] https://github.com/Gnurou/linux/tree/b4/register
>>
>> The first patch enables the `generic_arg_infer` feature, which is
>> required for generic type inference and used in subsequent patches. This
>> feature is stable since rustc 1.89.
>>
>> The second patch adds `shr` and `shl` methods to `Bounded`. These were
>> suggested by Alice during LPC as a way to avoid the use of the
>> controversial `Bounded::from_expr` in both the bitfield macro and the
>> Nova code. The third patch adds another convenience method to obtain a
>> `bool` from single-bit `Bounded`s, while the fourth patch turns
>> `Bounded::get` into a const method in order to make register setter
>> methods const.
>>
>> Patches 5 and 6 introduce the `IoLoc` and `IoWrite` types around which
>> I/O accesses are centered. This allows registers to be accessed using
>> the same `read` and `write` methods as primitive types.
>>
>> Patch 7 adds the `register!` macro and the types it requires.
>>
>> Patch 8 updates the Rust PCI sample driver to use `register!`, as per
>> its TODO item.
>>
>> Patch 9 illustrates more extensively how this macro is used by
>> converting nova-core to use it, and removing the local implementation.
>> This patch is to be merged one cycle after the other patches.
>>
>> Patch 10 is an RFC allowing a shorter write syntax to be used in the
>> case of fixed or relative registers. It doesn't need to be merged
>> immediately, but I think it is a good time to discuss it.
>>
>> I have also removed Gary's signoff on patches 5 and 7 to make sure it
>> doesn't get merged before he gives it explicitly.
>>
>> Signed-off-by: Alexandre Courbot <acourbot@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 and IoWrite types
>> rust: io: use generic read/write accessors for primitive accesses
>> rust: io: add `register!` macro
>> sample: rust: pci: use `register!` macro
>> [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro
>> RFC: rust: io: allow fixed register values directly in `write`
>>
>> drivers/gpu/nova-core/falcon.rs | 249 +++----
>> drivers/gpu/nova-core/falcon/gsp.rs | 23 +-
>> drivers/gpu/nova-core/falcon/hal/ga102.rs | 65 +-
>> drivers/gpu/nova-core/falcon/hal/tu102.rs | 11 +-
>> drivers/gpu/nova-core/falcon/sec2.rs | 17 +-
>> drivers/gpu/nova-core/fb.rs | 6 +-
>> drivers/gpu/nova-core/fb/hal/ga100.rs | 40 +-
>> drivers/gpu/nova-core/fb/hal/ga102.rs | 7 +-
>> drivers/gpu/nova-core/fb/hal/tu102.rs | 22 +-
>> 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 | 10 +-
>> drivers/gpu/nova-core/regs.rs | 544 ++++++++------
>> drivers/gpu/nova-core/regs/macros.rs | 739 -------------------
>> rust/kernel/io.rs | 347 +++++++--
>> rust/kernel/io/register.rs | 1135 +++++++++++++++++++++++++++++
>> rust/kernel/lib.rs | 3 +
>> rust/kernel/num/bounded.rs | 70 +-
>> samples/rust/rust_driver_pci.rs | 84 ++-
>> scripts/Makefile.build | 3 +-
>> 21 files changed, 2118 insertions(+), 1315 deletions(-)
>> ---
>> base-commit: 545225ee0b0b6998c7b6ff888f8497681b8b35c4
>> change-id: 20260117-register-ccaba1d21713
>
>
> I converted my simple aarch64 TMU timer example [1] over to v7 (many
> thanks helping with that!) and gave it a try: It works like with v2.
> With that:
>
> Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
Really appreciated! And I think we are finally converging, so hopefully
no more big refactoring ahead. :) Still, please let me know if you saw
anything that looked off during the conversion.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 00/10] rust: add `register!` macro
2026-02-25 13:50 ` Alexandre Courbot
@ 2026-02-26 12:01 ` Dirk Behme
2026-02-27 23:30 ` Alexandre Courbot
0 siblings, 1 reply; 64+ messages in thread
From: Dirk Behme @ 2026-02-26 12:01 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, Steven Price, rust-for-linux, linux-kernel
On 25.02.2026 14:50, Alexandre Courbot wrote:
> On Wed Feb 25, 2026 at 8:58 PM JST, Dirk Behme wrote:
>> On 24.02.2026 15:21, Alexandre Courbot wrote:
>>> New revision addressing the v6 feedback - the use of "location" instead
>>> of "reference" in particular reads well both in the code and the
>>> comments.
>>>
>>> As a reminder, since the previous revision we use the I/O type to
>>> perform the actual I/O instead of the register type, which moves us from
>>> this access pattern:
>>>
>>> let boot0 = regs::NV_PMC_BOOT_0::read(bar);
>>>
>>> to this arguably more natural one:
>>>
>>> let boot0 = bar.read(regs::NV_PMC_BOOT_0);
>>>
>>> This revision is based on `driver-core-testing` as of 2026-02-24 with
>>> [1] applied. A tree with this series and its dependencies is available
>>> at [2].
>>>
>>> [1] https://lore.kernel.org/all/20260206-io-v2-0-71dea20a06e6@nvidia.com/
>>> [2] https://github.com/Gnurou/linux/tree/b4/register
>>>
>>> The first patch enables the `generic_arg_infer` feature, which is
>>> required for generic type inference and used in subsequent patches. This
>>> feature is stable since rustc 1.89.
>>>
>>> The second patch adds `shr` and `shl` methods to `Bounded`. These were
>>> suggested by Alice during LPC as a way to avoid the use of the
>>> controversial `Bounded::from_expr` in both the bitfield macro and the
>>> Nova code. The third patch adds another convenience method to obtain a
>>> `bool` from single-bit `Bounded`s, while the fourth patch turns
>>> `Bounded::get` into a const method in order to make register setter
>>> methods const.
>>>
>>> Patches 5 and 6 introduce the `IoLoc` and `IoWrite` types around which
>>> I/O accesses are centered. This allows registers to be accessed using
>>> the same `read` and `write` methods as primitive types.
>>>
>>> Patch 7 adds the `register!` macro and the types it requires.
>>>
>>> Patch 8 updates the Rust PCI sample driver to use `register!`, as per
>>> its TODO item.
>>>
>>> Patch 9 illustrates more extensively how this macro is used by
>>> converting nova-core to use it, and removing the local implementation.
>>> This patch is to be merged one cycle after the other patches.
>>>
>>> Patch 10 is an RFC allowing a shorter write syntax to be used in the
>>> case of fixed or relative registers. It doesn't need to be merged
>>> immediately, but I think it is a good time to discuss it.
>>>
>>> I have also removed Gary's signoff on patches 5 and 7 to make sure it
>>> doesn't get merged before he gives it explicitly.
>>>
>>> Signed-off-by: Alexandre Courbot <acourbot@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 and IoWrite types
>>> rust: io: use generic read/write accessors for primitive accesses
>>> rust: io: add `register!` macro
>>> sample: rust: pci: use `register!` macro
>>> [FOR REFERENCE] gpu: nova-core: use the kernel `register!` macro
>>> RFC: rust: io: allow fixed register values directly in `write`
>>>
>>> drivers/gpu/nova-core/falcon.rs | 249 +++----
>>> drivers/gpu/nova-core/falcon/gsp.rs | 23 +-
>>> drivers/gpu/nova-core/falcon/hal/ga102.rs | 65 +-
>>> drivers/gpu/nova-core/falcon/hal/tu102.rs | 11 +-
>>> drivers/gpu/nova-core/falcon/sec2.rs | 17 +-
>>> drivers/gpu/nova-core/fb.rs | 6 +-
>>> drivers/gpu/nova-core/fb/hal/ga100.rs | 40 +-
>>> drivers/gpu/nova-core/fb/hal/ga102.rs | 7 +-
>>> drivers/gpu/nova-core/fb/hal/tu102.rs | 22 +-
>>> 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 | 10 +-
>>> drivers/gpu/nova-core/regs.rs | 544 ++++++++------
>>> drivers/gpu/nova-core/regs/macros.rs | 739 -------------------
>>> rust/kernel/io.rs | 347 +++++++--
>>> rust/kernel/io/register.rs | 1135 +++++++++++++++++++++++++++++
>>> rust/kernel/lib.rs | 3 +
>>> rust/kernel/num/bounded.rs | 70 +-
>>> samples/rust/rust_driver_pci.rs | 84 ++-
>>> scripts/Makefile.build | 3 +-
>>> 21 files changed, 2118 insertions(+), 1315 deletions(-)
>>> ---
>>> base-commit: 545225ee0b0b6998c7b6ff888f8497681b8b35c4
>>> change-id: 20260117-register-ccaba1d21713
>>
>>
>> I converted my simple aarch64 TMU timer example [1] over to v7 (many
>> thanks helping with that!) and gave it a try: It works like with v2.
>> With that:
>>
>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
>
> Really appreciated! And I think we are finally converging, so hopefully
> no more big refactoring ahead. :) Still, please let me know if you saw
> anything that looked off during the conversion.
I was slightly surprised about the `set_` to `with_` change. But
thinking about it I think it heavily depends on the point of view:
If you look at it from a physical register point of view I'm used to
"set" a register / bit. So looking from physical register point of view
I find using `set_` natural.
On the other hand, if we look at the Rust "syntax" you create here like
foo.with_a(4).with_b(9).write(io);
we write to register foo with bits a being 4 and bits b being 9. So we
are doing a write *with* some bits set. From this point of view `with_`
fits as well.
I think you mentioned that this is discussable. And I think it heavily
depends on the point of view you take. So I'm fine either.
Thanks
Dirk
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 04/10] rust: num: make Bounded::get const
2026-02-24 14:21 ` [PATCH v7 04/10] rust: num: make Bounded::get const Alexandre Courbot
@ 2026-02-27 12:33 ` Gary Guo
0 siblings, 0 replies; 64+ messages in thread
From: Gary Guo @ 2026-02-27 12:33 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 Tue Feb 24, 2026 at 2:21 PM GMT, Alexandre Courbot wrote:
> 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.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
> ---
> rust/kernel/num/bounded.rs | 7 +++++--
> 1 file changed, 5 insertions(+), 2 deletions(-)
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-02-24 14:21 ` [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types Alexandre Courbot
@ 2026-02-27 18:02 ` Gary Guo
2026-02-27 18:16 ` Danilo Krummrich
2026-02-28 0:33 ` Alexandre Courbot
2026-03-04 22:19 ` Gary Guo
1 sibling, 2 replies; 64+ messages in thread
From: Gary Guo @ 2026-02-27 18:02 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 Tue Feb 24, 2026 at 2:21 PM GMT, Alexandre Courbot wrote:
> I/O accesses are defined by the following properties:
>
> - For reads, a start address, a width, and a type to interpret the read
> value as,
> - For writes, the same as above, and a value to write.
>
> Introduce the `IoLoc` trait, which allows implementing types to specify
> the address a type expects to be accessed at, as well as the width of
> the access, and the user-facing type used to perform the access.
>
> This allows read operations to be made generic with the `read` method
> over an `IoLoc` argument.
>
> Write operations need a value to write on top of the `IoLoc`: fulfill
> that purpose with the `IoWrite` type, which is the combination of an
> `IoLoc` and a value of the type it expects. This allows write operations
> to be made generic with the `write` method over a single `IoWrite`
> argument.
>
> The main purpose of these new entities is to allow register types to be
> written using these generic `read` and `write` methods of `Io`.
>
> Co-developed-by: Gary Guo <gary@garyguo.net>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> rust/kernel/io.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 241 insertions(+)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index b150743ffa4f..fdd2549d8e13 100644
> --- a/rust/kernel/io.rs
> +++ b/rust/kernel/io.rs
> @@ -173,6 +173,158 @@ pub trait IoCapable<T> {
> unsafe fn io_write(&self, value: T, address: usize);
> }
>
> +/// Describes a given I/O location: its offset, width, and return type.
> +///
> +/// This trait is the key abstraction allowing [`Io::read`], [`Io::write`], and [`Io::update`]
> +/// 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 data is returned or provided.
> +///
> +/// `T` and `IoType` may differ: for instance, a typed register has `T` = the register type with
> +/// its bitfields, and `IoType` = its backing primitive (e.g. `u32`), with `Into` conversions
> +/// between them.
> +///
> +/// An `IoLoc` can be passed directly to [`Io::read`] or [`Io::try_read`] to obtain a value, or
> +/// turned into an [`IoWrite`] via [`IoLoc::set`] to be passed to [`Io::write`] or
> +/// [`Io::try_write`].
> +pub trait IoLoc<T>: Copy
> +where
> + T: Into<Self::IoType>,
> + Self::IoType: Into<T>,
> +{
> + /// Size (`u8`, `u16`, etc) of the I/O performed on the returned [`offset`](IoLoc::offset).
> + type IoType;
> +
> + /// Returns the offset of this location.
> + fn offset(self) -> usize;
> +
> + /// Turns this location into an [`IoWrite`] with the initial `value`.
> + fn set(self, value: T) -> IoWrite<T, Self> {
> + IoWrite { value, loc: self }
> + }
> +
> + /// Turns this location into an [`IoWrite`] with the initial value `0`.
> + fn zeroed(self) -> IoWrite<T, Self>
> + where
> + T: Zeroable,
> + {
> + self.set(pin_init::zeroed())
> + }
> +
> + /// Turns this location into an [`IoWrite`] with the initial [`Default`] value of `T`.
> + fn default(self) -> IoWrite<T, Self>
> + where
> + T: Default,
> + {
> + self.set(Default::default())
> + }
> +
> + /// Turns this location into an [`IoWrite`] initialized from `0` and transformed by `f`.
> + ///
> + /// This is a shortcut for `self.zeroed().update(f)`.
> + fn init<F>(self, f: F) -> IoWrite<T, Self>
> + where
> + T: Zeroable,
> + F: FnOnce(T) -> T,
> + {
> + self.zeroed().update(f)
> + }
> +
> + /// Turns this location into an [`IoWrite`] initialized from `0` and transformed by `f`.
> + ///
> + /// `f` is expected to return a [`Result<T>`].
> + ///
> + /// This is a shortcut for `self.zeroed().try_update(f)`.
> + fn try_init<F, E>(self, f: F) -> Result<IoWrite<T, Self>, E>
> + where
> + T: Zeroable,
> + F: FnOnce(T) -> Result<T, E>,
> + {
> + self.zeroed().try_update(f)
> + }
> +
> + /// Turns this location into an [`IoWrite`] initialized from [`Default`] and transformed
> + /// by `f`.
> + ///
> + /// This is a shortcut for `self.default().update(f)`.
> + fn init_default<F>(self, f: F) -> IoWrite<T, Self>
> + where
> + T: Default,
> + F: FnOnce(T) -> T,
> + {
> + self.default().update(f)
> + }
> +
> + /// Turns this location into an [`IoWrite`] initialized from [`Default`] and transformed by
> + /// `f`.
> + ///
> + /// `f` is expected to return a [`Result<T>`].
> + ///
> + /// This is a shortcut for `self.default().try_update(f)`.
> + fn try_init_default<F, E>(self, f: F) -> Result<IoWrite<T, Self>, E>
> + where
> + T: Default,
> + F: FnOnce(T) -> Result<T, E>,
> + {
> + self.default().try_update(f)
> + }
> +}
> +
> +/// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
> +///
> +/// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
> +/// [`IoLoc::init_default`], and consumed by [`Io::write`] or [`Io::try_write`] to perform the
> +/// actual write.
> +///
> +/// The value can be modified before writing using [`IoWrite::update`] or [`IoWrite::try_update`],
> +/// enabling a builder pattern:
> +///
> +/// ```ignore
> +/// io.write(REGISTER.init(|v| v.with_field(x)));
> +/// ```
Thinking about this again, I think we might still want to write
io.write(REGISTER, value)
Granted, this does mean that we will write `REGISTER` twice in some cases:
io.write(REGISTER, REGISTER::default().with_field(foo));
But, we have no redundancy for the update case:
io.update(REGISTER, |v| v.with_field(foo));
The reason for this thought is that conceptually, the type of a register is not
necessarily coupled with the register itself. This is the case currently for
register arrays, but also the case when we think in the long term where
bitfields become decoupled from `register!`. People also might just want to
define a register of u32 without any bitfields at all, and in this case writing
io.write(REGISTER, u32_value)
looks nicer than
io.write(REGISTER.set(u32_value))
Spelling out the "offset" explictly, arguably, is also more natural for a C
programmer, and also simpler on the implementation side (all the helper methods
on `IoLoc` would go away).
Best,
Gary
> +pub struct IoWrite<T, R>
> +where
> + R: IoLoc<T>,
> + T: Into<R::IoType>,
> +{
> + value: T,
> + loc: R,
> +}
> +
> +impl<R, T> IoWrite<T, R>
> +where
> + R: IoLoc<T>,
> + T: Into<R::IoType>,
> +{
> + /// Transforms the value to be written by applying `f`, returning the modified [`IoWrite`].
> + #[inline(always)]
> + pub fn update<F>(mut self, f: F) -> Self
> + where
> + F: FnOnce(T) -> T,
> + {
> + self.value = f(self.value);
> +
> + self
> + }
> +
> + /// Transforms the value to be written by applying `f`, returning the modified [`IoWrite`] on
> + /// success.
> + #[inline(always)]
> + pub fn try_update<F, E>(mut self, f: F) -> Result<Self, E>
> + where
> + F: FnOnce(T) -> Result<T, E>,
> + {
> + self.value = f(self.value)?;
> +
> + Ok(self)
> + }
> +}
> +
> /// Types implementing this trait (e.g. MMIO BARs or PCI config regions)
> /// can perform I/O operations on regions of memory.
> ///
> @@ -406,6 +558,95 @@ 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, R>(&self, r: R) -> Result<T>
> + where
> + R: IoLoc<T>,
> + T: Into<R::IoType>,
> + Self: IoCapable<R::IoType>,
> + {
> + let address = self.io_addr::<R::IoType>(r.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, R>(&self, op: IoWrite<T, R>) -> Result
> + where
> + R: IoLoc<T>,
> + T: Into<R::IoType>,
> + Self: IoCapable<R::IoType>,
> + {
> + let address = self.io_addr::<R::IoType>(op.loc.offset())?;
> +
> + // SAFETY: `address` has been validated by `io_addr`.
> + unsafe { self.io_write(op.value.into(), 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, R, F>(&self, r: R, f: F) -> Result
> + where
> + R: IoLoc<T>,
> + T: Into<R::IoType>,
> + Self: IoCapable<R::IoType>,
> + F: FnOnce(T) -> T,
> + {
> + let v = self.try_read(r)?;
> + self.try_write(r.set(f(v)))
> + }
> +
> + /// Generic infallible read with compile-time bounds check.
> + #[inline(always)]
> + fn read<T, R>(&self, r: R) -> T
> + where
> + R: IoLoc<T>,
> + T: Into<R::IoType>,
> + Self: IoKnownSize + IoCapable<R::IoType>,
> + {
> + let address = self.io_addr_assert::<R::IoType>(r.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, R>(&self, op: IoWrite<T, R>)
> + where
> + R: IoLoc<T>,
> + T: Into<R::IoType>,
> + Self: IoKnownSize + IoCapable<R::IoType>,
> + {
> + let address = self.io_addr_assert::<R::IoType>(op.loc.offset());
> +
> + // SAFETY: `address` has been validated by `io_addr_assert`.
> + unsafe { self.io_write(op.value.into(), 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, R, F>(&self, r: R, f: F)
> + where
> + R: IoLoc<T>,
> + T: Into<R::IoType>,
> + Self: IoKnownSize + IoCapable<R::IoType> + Sized,
> + F: FnOnce(T) -> T,
> + {
> + let v = self.read(r);
> + self.write(r.set(f(v)));
> + }
> }
>
> /// Trait for types with a known size at compile time.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 06/10] rust: io: use generic read/write accessors for primitive accesses
2026-02-24 14:21 ` [PATCH v7 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot
@ 2026-02-27 18:04 ` Gary Guo
0 siblings, 0 replies; 64+ messages in thread
From: Gary Guo @ 2026-02-27 18:04 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 Tue Feb 24, 2026 at 2:21 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.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
To expand my comment on patch 5, if we have
io.write(loc, value)
and
io.try_write(loc, value)
then we would naturally have
io.write::<u32>(loc, value)
becoming the generic equivalent of
io.write32(loc, value)
for free.
Best,
Gary
> ---
> rust/kernel/io.rs | 103 +++++++++++++++++++-----------------------------------
> 1 file changed, 35 insertions(+), 68 deletions(-)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index fdd2549d8e13..256eba16ccc8 100644
> --- a/rust/kernel/io.rs
> +++ b/rust/kernel/io.rs
> @@ -275,6 +275,25 @@ fn try_init_default<F, E>(self, f: F) -> Result<IoWrite<T, Self>, E>
> }
> }
>
> +/// 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);
> +
> /// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
> ///
> /// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
> @@ -369,10 +388,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.
> @@ -381,10 +397,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.
> @@ -393,10 +406,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.
> @@ -405,10 +415,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.
> @@ -417,11 +424,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.set(value))
> }
>
> /// Fallible 16-bit write with runtime bounds check.
> @@ -430,11 +433,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.set(value))
> }
>
> /// Fallible 32-bit write with runtime bounds check.
> @@ -443,11 +442,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.set(value))
> }
>
> /// Fallible 64-bit write with runtime bounds check.
> @@ -456,11 +451,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.set(value))
> }
>
> /// Infallible 8-bit read with compile-time bounds check.
> @@ -469,10 +460,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.
> @@ -481,10 +469,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.
> @@ -493,10 +478,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.
> @@ -505,10 +487,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.
> @@ -517,10 +496,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.set(value))
> }
>
> /// Infallible 16-bit write with compile-time bounds check.
> @@ -529,10 +505,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.set(value))
> }
>
> /// Infallible 32-bit write with compile-time bounds check.
> @@ -541,10 +514,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.set(value))
> }
>
> /// Infallible 64-bit write with compile-time bounds check.
> @@ -553,10 +523,7 @@ 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.set(value))
> }
>
> /// Generic fallible read with runtime bounds check.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-02-27 18:02 ` Gary Guo
@ 2026-02-27 18:16 ` Danilo Krummrich
2026-02-28 0:33 ` Alexandre Courbot
1 sibling, 0 replies; 64+ messages in thread
From: Danilo Krummrich @ 2026-02-27 18:16 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 Fri Feb 27, 2026 at 7:02 PM CET, Gary Guo wrote:
> Thinking about this again, I think we might still want to write
>
> io.write(REGISTER, value)
I'm not convinced about this; it feels like optimizing for the less common case.
> Granted, this does mean that we will write `REGISTER` twice in some cases:
I'd rather say in most cases, i.e. most registers do consist out of multiple
fields.
> io.write(REGISTER, REGISTER::default().with_field(foo));
>
> But, we have no redundancy for the update case:
>
> io.update(REGISTER, |v| v.with_field(foo));
>
> The reason for this thought is that conceptually, the type of a register is not
> necessarily coupled with the register itself. This is the case currently for
> register arrays, but also the case when we think in the long term where
> bitfields become decoupled from `register!`. People also might just want to
> define a register of u32 without any bitfields at all, and in this case writing
>
> io.write(REGISTER, u32_value)
It could also be written as
io.write(Register(u32_value))
but I also think that the below is acceptable.
> looks nicer than
>
> io.write(REGISTER.set(u32_value))
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 00/10] rust: add `register!` macro
2026-02-26 12:01 ` Dirk Behme
@ 2026-02-27 23:30 ` Alexandre Courbot
0 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-27 23:30 UTC (permalink / raw)
To: Dirk Behme
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, Steven Price, rust-for-linux, linux-kernel
On Thu Feb 26, 2026 at 9:01 PM JST, Dirk Behme wrote:
<snip>
>>> I converted my simple aarch64 TMU timer example [1] over to v7 (many
>>> thanks helping with that!) and gave it a try: It works like with v2.
>>> With that:
>>>
>>> Tested-by: Dirk Behme <dirk.behme@de.bosch.com>
>>
>> Really appreciated! And I think we are finally converging, so hopefully
>> no more big refactoring ahead. :) Still, please let me know if you saw
>> anything that looked off during the conversion.
>
>
> I was slightly surprised about the `set_` to `with_` change. But
> thinking about it I think it heavily depends on the point of view:
>
> If you look at it from a physical register point of view I'm used to
> "set" a register / bit. So looking from physical register point of view
> I find using `set_` natural.
>
> On the other hand, if we look at the Rust "syntax" you create here like
>
> foo.with_a(4).with_b(9).write(io);
>
> we write to register foo with bits a being 4 and bits b being 9. So we
> are doing a write *with* some bits set. From this point of view `with_`
> fits as well.
>
> I think you mentioned that this is discussable. And I think it heavily
> depends on the point of view you take. So I'm fine either.
This has to do with Rust conventions: we are using a builder pattern,
where fields are set by taking the register by value, and returning the
updated value. Such methods tend to be prefixed with `with_`, whereas
`set_` is typically used for in-plane modification using `&mut self`.
Now AFAICT this is not a *codified* convention, just a rather strong
unwritten one. But it seems adequate to follow it, so users are not
mistaken into thinking the setters do something different than they do.
As to why the builder pattern, this is because it is shorter to write
io.write(reg.with_foo(true));
than
mut foo = ...;
reg.set_foo(true);
io.write(reg);
and also cleaner as it removes the need for the register variable to be
mutable.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-02-27 18:02 ` Gary Guo
2026-02-27 18:16 ` Danilo Krummrich
@ 2026-02-28 0:33 ` Alexandre Courbot
2026-03-01 15:11 ` Gary Guo
1 sibling, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-02-28 0:33 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 Sat Feb 28, 2026 at 3:02 AM JST, Gary Guo wrote:
<snip>
>> +/// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
>> +///
>> +/// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
>> +/// [`IoLoc::init_default`], and consumed by [`Io::write`] or [`Io::try_write`] to perform the
>> +/// actual write.
>> +///
>> +/// The value can be modified before writing using [`IoWrite::update`] or [`IoWrite::try_update`],
>> +/// enabling a builder pattern:
>> +///
>> +/// ```ignore
>> +/// io.write(REGISTER.init(|v| v.with_field(x)));
>> +/// ```
>
> Thinking about this again, I think we might still want to write
>
> io.write(REGISTER, value)
This was the original design, but as you point out below this makes the
very common case of writing a register value built from scratch more
verbose than it needs. Real-world examples are significantly worse, e.g:
bar.write(
regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
.try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?,
);
turns into
bar.write(
regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(),
regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed()
.try_with_offs(load_offsets.dst_start + pos)?,
);
>
> Granted, this does mean that we will write `REGISTER` twice in some cases:
>
> io.write(REGISTER, REGISTER::default().with_field(foo));
>
> But, we have no redundancy for the update case:
>
> io.update(REGISTER, |v| v.with_field(foo));
In nova-core, `update` is less common than writing a register value
built from scratch. That's really the only thing that holds us from
two arguments in `write`.
>
> The reason for this thought is that conceptually, the type of a register is not
> necessarily coupled with the register itself. This is the case currently for
> register arrays, but also the case when we think in the long term where
For these cases I was thinking we could rely on From/Into
implementations, as it gives us the option to make the conversion
explicit and thus less error-prone.
> bitfields become decoupled from `register!`. People also might just want to
> define a register of u32 without any bitfields at all, and in this case writing
>
> io.write(REGISTER, u32_value)
>
> looks nicer than
>
> io.write(REGISTER.set(u32_value))
>
> Spelling out the "offset" explictly, arguably, is also more natural for a C
> programmer, and also simpler on the implementation side (all the helper methods
> on `IoLoc` would go away).
That's true that it would be less `(try_)init` and friends helpers
(`IoWrite` would also go away I guess?). I'm not a huge fan of these
because they only cover specific initial values (zeroed() and
default()).
Maybe we can macro our way out of it?
It would look a little bit weird to resort to a macro just for the write
case, but I don't think we will be able to work around this without any
kind of compromise. FWIW, I think the single-argument `write` is a
reasonable one that might look a bit surprising at first encounter, but
has the benefit of reading naturally.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-02-28 0:33 ` Alexandre Courbot
@ 2026-03-01 15:11 ` Gary Guo
2026-03-02 1:44 ` Alexandre Courbot
0 siblings, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-01 15:11 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 Sat Feb 28, 2026 at 12:33 AM GMT, Alexandre Courbot wrote:
> On Sat Feb 28, 2026 at 3:02 AM JST, Gary Guo wrote:
> <snip>
>>> +/// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
>>> +///
>>> +/// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
>>> +/// [`IoLoc::init_default`], and consumed by [`Io::write`] or [`Io::try_write`] to perform the
>>> +/// actual write.
>>> +///
>>> +/// The value can be modified before writing using [`IoWrite::update`] or [`IoWrite::try_update`],
>>> +/// enabling a builder pattern:
>>> +///
>>> +/// ```ignore
>>> +/// io.write(REGISTER.init(|v| v.with_field(x)));
>>> +/// ```
>>
>> Thinking about this again, I think we might still want to write
>>
>> io.write(REGISTER, value)
>
> This was the original design, but as you point out below this makes the
> very common case of writing a register value built from scratch more
> verbose than it needs. Real-world examples are significantly worse, e.g:
>
> bar.write(
> regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
> .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?,
> );
My main dissatisfaction with this is with the function call
I wonder if we can just have
io.write(loc, value)
io.write_with(loc, updater)
where the latter simply is a function that does
io.write(loc, updater(T::zeroed()))
then the example would be
bar.write_with(
regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(),
|r| r.try_with_offs(load_offsets.dst_start + pos)
)
[ Note: it's possible to have write_with that works with both fallible and
non-fallible callbacks. You can define a trait like a monad (well, not fully
a monad, because this just has a map, not a return and a bind):
trait Map<T> {
type Mapped<U>;
fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U>;
}
impl<T> Map<T> for T {
type Mapped<U> = U;
fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U> {
f(self)
}
}
impl<T, E> Map<T> for Result<T, E> {
type Mapped<U> = Result<U, E>;
fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U> {
Ok(f(self?))
}
}
impl Io {
fn write_with<R: IoLoc<T>, U: Map<T>, T: Zeroable, F: FnOnce(T) -> U>(&self, loc: R, f: F) -> U::Mapped<()> {
f(T::zeroed).map(|x| self.write(loc, x))
}
}
then this returns `()` if closure cannot fail, and returns `Result<()>` if it
fails).
end of note ]
BTW, I am also not very happy with the `::<E>()` syntax. I think with the I/O
projection upcoming, we might be able to get rid of relative offseting
completely by requiring user to first project into `View<'_, .., FalconBase>` and then have
some registers that can only be used on `Io<Type = FalconBase>` but nothing else.
Best,
Gary
>
> turns into
>
> bar.write(
> regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(),
> regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed()
> .try_with_offs(load_offsets.dst_start + pos)?,
> );
>
>>
>> Granted, this does mean that we will write `REGISTER` twice in some cases:
>>
>> io.write(REGISTER, REGISTER::default().with_field(foo));
>>
>> But, we have no redundancy for the update case:
>>
>> io.update(REGISTER, |v| v.with_field(foo));
>
> In nova-core, `update` is less common than writing a register value
> built from scratch. That's really the only thing that holds us from
> two arguments in `write`.
>
>>
>> The reason for this thought is that conceptually, the type of a register is not
>> necessarily coupled with the register itself. This is the case currently for
>> register arrays, but also the case when we think in the long term where
>
> For these cases I was thinking we could rely on From/Into
> implementations, as it gives us the option to make the conversion
> explicit and thus less error-prone.
>
>> bitfields become decoupled from `register!`. People also might just want to
>> define a register of u32 without any bitfields at all, and in this case writing
>>
>> io.write(REGISTER, u32_value)
>>
>> looks nicer than
>>
>> io.write(REGISTER.set(u32_value))
>>
>> Spelling out the "offset" explictly, arguably, is also more natural for a C
>> programmer, and also simpler on the implementation side (all the helper methods
>> on `IoLoc` would go away).
>
> That's true that it would be less `(try_)init` and friends helpers
> (`IoWrite` would also go away I guess?). I'm not a huge fan of these
> because they only cover specific initial values (zeroed() and
> default()).
>
> Maybe we can macro our way out of it?
>
> It would look a little bit weird to resort to a macro just for the write
> case, but I don't think we will be able to work around this without any
> kind of compromise. FWIW, I think the single-argument `write` is a
> reasonable one that might look a bit surprising at first encounter, but
> has the benefit of reading naturally.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-01 15:11 ` Gary Guo
@ 2026-03-02 1:44 ` Alexandre Courbot
2026-03-02 12:53 ` Gary Guo
0 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-02 1:44 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 Mon Mar 2, 2026 at 12:11 AM JST, Gary Guo wrote:
> On Sat Feb 28, 2026 at 12:33 AM GMT, Alexandre Courbot wrote:
>> On Sat Feb 28, 2026 at 3:02 AM JST, Gary Guo wrote:
>> <snip>
>>>> +/// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
>>>> +///
>>>> +/// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
>>>> +/// [`IoLoc::init_default`], and consumed by [`Io::write`] or [`Io::try_write`] to perform the
>>>> +/// actual write.
>>>> +///
>>>> +/// The value can be modified before writing using [`IoWrite::update`] or [`IoWrite::try_update`],
>>>> +/// enabling a builder pattern:
>>>> +///
>>>> +/// ```ignore
>>>> +/// io.write(REGISTER.init(|v| v.with_field(x)));
>>>> +/// ```
>>>
>>> Thinking about this again, I think we might still want to write
>>>
>>> io.write(REGISTER, value)
>>
>> This was the original design, but as you point out below this makes the
>> very common case of writing a register value built from scratch more
>> verbose than it needs. Real-world examples are significantly worse, e.g:
>>
>> bar.write(
>> regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
>> .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?,
>> );
>
> My main dissatisfaction with this is with the function call
>
> I wonder if we can just have
>
> io.write(loc, value)
> io.write_with(loc, updater)
>
> where the latter simply is a function that does
>
> io.write(loc, updater(T::zeroed()))
>
> then the example would be
>
> bar.write_with(
> regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(),
> |r| r.try_with_offs(load_offsets.dst_start + pos)
> )
That should be doable. Note that we currently support `zeroed` and
`default` as initializers, so having the same level of coverage would
require two `write` variants. I'd like to hear what Danilo thinks.
>
> [ Note: it's possible to have write_with that works with both fallible and
> non-fallible callbacks. You can define a trait like a monad (well, not fully
> a monad, because this just has a map, not a return and a bind):
>
> trait Map<T> {
> type Mapped<U>;
>
> fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U>;
> }
>
> impl<T> Map<T> for T {
> type Mapped<U> = U;
>
> fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U> {
> f(self)
> }
> }
>
> impl<T, E> Map<T> for Result<T, E> {
> type Mapped<U> = Result<U, E>;
>
> fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U> {
> Ok(f(self?))
> }
> }
>
> impl Io {
> fn write_with<R: IoLoc<T>, U: Map<T>, T: Zeroable, F: FnOnce(T) -> U>(&self, loc: R, f: F) -> U::Mapped<()> {
> f(T::zeroed).map(|x| self.write(loc, x))
> }
> }
>
> then this returns `()` if closure cannot fail, and returns `Result<()>` if it
> fails).
This approach look like it could also be used for I/O in general - right
now we do not handle bus errors, but we definitely should.
>
> end of note ]
>
> BTW, I am also not very happy with the `::<E>()` syntax. I think with the I/O
> projection upcoming, we might be able to get rid of relative offseting
> completely by requiring user to first project into `View<'_, .., FalconBase>` and then have
> some registers that can only be used on `Io<Type = FalconBase>` but nothing else.
That's something we can always update once I/O projection is available
if it can indeed be applied (using a two-steps approach if necessary). I
can already hear the pitchforks if we don't deliver some basic register
support for 7.1. :)
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-02 1:44 ` Alexandre Courbot
@ 2026-03-02 12:53 ` Gary Guo
2026-03-02 13:12 ` Danilo Krummrich
0 siblings, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-02 12:53 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 Mon Mar 2, 2026 at 1:44 AM GMT, Alexandre Courbot wrote:
> On Mon Mar 2, 2026 at 12:11 AM JST, Gary Guo wrote:
>> On Sat Feb 28, 2026 at 12:33 AM GMT, Alexandre Courbot wrote:
>>> On Sat Feb 28, 2026 at 3:02 AM JST, Gary Guo wrote:
>>> <snip>
>>>>> +/// A pending I/O write operation, bundling a value with the [`IoLoc`] it should be written to.
>>>>> +///
>>>>> +/// Created by [`IoLoc::set`], [`IoLoc::zeroed`], [`IoLoc::default`], [`IoLoc::init`], or
>>>>> +/// [`IoLoc::init_default`], and consumed by [`Io::write`] or [`Io::try_write`] to perform the
>>>>> +/// actual write.
>>>>> +///
>>>>> +/// The value can be modified before writing using [`IoWrite::update`] or [`IoWrite::try_update`],
>>>>> +/// enabling a builder pattern:
>>>>> +///
>>>>> +/// ```ignore
>>>>> +/// io.write(REGISTER.init(|v| v.with_field(x)));
>>>>> +/// ```
>>>>
>>>> Thinking about this again, I think we might still want to write
>>>>
>>>> io.write(REGISTER, value)
>>>
>>> This was the original design, but as you point out below this makes the
>>> very common case of writing a register value built from scratch more
>>> verbose than it needs. Real-world examples are significantly worse, e.g:
>>>
>>> bar.write(
>>> regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
>>> .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?,
>>> );
>>
>> My main dissatisfaction with this is with the function call
>>
>> I wonder if we can just have
>>
>> io.write(loc, value)
>> io.write_with(loc, updater)
>>
>> where the latter simply is a function that does
>>
>> io.write(loc, updater(T::zeroed()))
>>
>> then the example would be
>>
>> bar.write_with(
>> regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(),
>> |r| r.try_with_offs(load_offsets.dst_start + pos)
>> )
>
> That should be doable. Note that we currently support `zeroed` and
> `default` as initializers, so having the same level of coverage would
> require two `write` variants. I'd like to hear what Danilo thinks.
I looked at current Nova changes, it looks like they all just use the zeroed
version.
I wonder if just providing a single version that starts with
`Default::default()` should be sufficient? For most users, zeroed version is the
default version anyway. For those where default is not zero, it perhaps makes
more sense to start with default anyway; if explicitly zeroing is needed they
can always do an explicit `::zeroed()`.
I think with this we can make the API look nice for the common case, removing
most of API complexity that the current design have, and still preserve the
ability do full custom things, with perhaps just a little more verbosity in
code.
>
>>
>> [ Note: it's possible to have write_with that works with both fallible and
>> non-fallible callbacks. You can define a trait like a monad (well, not fully
>> a monad, because this just has a map, not a return and a bind):
>>
>> trait Map<T> {
>> type Mapped<U>;
>>
>> fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U>;
>> }
>>
>> impl<T> Map<T> for T {
>> type Mapped<U> = U;
>>
>> fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U> {
>> f(self)
>> }
>> }
>>
>> impl<T, E> Map<T> for Result<T, E> {
>> type Mapped<U> = Result<U, E>;
>>
>> fn map<U>(self, f: impl FnOnce(T) -> U) -> Self::Mapped<U> {
>> Ok(f(self?))
>> }
>> }
>>
>> impl Io {
>> fn write_with<R: IoLoc<T>, U: Map<T>, T: Zeroable, F: FnOnce(T) -> U>(&self, loc: R, f: F) -> U::Mapped<()> {
>> f(T::zeroed).map(|x| self.write(loc, x))
>> }
>> }
>>
>> then this returns `()` if closure cannot fail, and returns `Result<()>` if it
>> fails).
>
> This approach look like it could also be used for I/O in general - right
> now we do not handle bus errors, but we definitely should.
>
>>
>> end of note ]
>>
>> BTW, I am also not very happy with the `::<E>()` syntax. I think with the I/O
>> projection upcoming, we might be able to get rid of relative offseting
>> completely by requiring user to first project into `View<'_, .., FalconBase>` and then have
>> some registers that can only be used on `Io<Type = FalconBase>` but nothing else.
>
> That's something we can always update once I/O projection is available
> if it can indeed be applied (using a two-steps approach if necessary). I
> can already hear the pitchforks if we don't deliver some basic register
> support for 7.1. :)
Yeah, don't treat the above as "I don't like this so please fix before merge". I
am just pointing out things that can be in general improvements and there's no
need to make everything perfect from the get go.
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-02 12:53 ` Gary Guo
@ 2026-03-02 13:12 ` Danilo Krummrich
2026-03-02 13:39 ` Gary Guo
0 siblings, 1 reply; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-02 13:12 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 Mon Mar 2, 2026 at 1:53 PM CET, Gary Guo wrote:
> On Mon Mar 2, 2026 at 1:44 AM GMT, Alexandre Courbot wrote:
>> That should be doable. Note that we currently support `zeroed` and
>> `default` as initializers, so having the same level of coverage would
>> require two `write` variants. I'd like to hear what Danilo thinks.
>
> I wonder if just providing a single version that starts with
> `Default::default()` should be sufficient? For most users, zeroed version is the
> default version anyway. For those where default is not zero, it perhaps makes
> more sense to start with default anyway; if explicitly zeroing is needed they
> can always do an explicit `::zeroed()`.
I was thinking about this for a while and also thought that we probably only
ever need a version that starts with Default::default().
What I still dislike is that the common case becomes write_with() instead of
just write(). (Just to clarify, the name write_with() is perfectly fine for what
the function does, it's more that we need it in the first place.)
Also, IIUC, if the value is not created within the closure, we'd still have the
following redundancy, right?
let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
.try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?;
bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
It's just that this case nicely converts to write_with().
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-02 13:12 ` Danilo Krummrich
@ 2026-03-02 13:39 ` Gary Guo
2026-03-03 8:14 ` Alexandre Courbot
0 siblings, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-02 13:39 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 Mon Mar 2, 2026 at 1:12 PM GMT, Danilo Krummrich wrote:
> On Mon Mar 2, 2026 at 1:53 PM CET, Gary Guo wrote:
>> On Mon Mar 2, 2026 at 1:44 AM GMT, Alexandre Courbot wrote:
>>> That should be doable. Note that we currently support `zeroed` and
>>> `default` as initializers, so having the same level of coverage would
>>> require two `write` variants. I'd like to hear what Danilo thinks.
>>
>> I wonder if just providing a single version that starts with
>> `Default::default()` should be sufficient? For most users, zeroed version is the
>> default version anyway. For those where default is not zero, it perhaps makes
>> more sense to start with default anyway; if explicitly zeroing is needed they
>> can always do an explicit `::zeroed()`.
>
> I was thinking about this for a while and also thought that we probably only
> ever need a version that starts with Default::default().
>
> What I still dislike is that the common case becomes write_with() instead of
> just write(). (Just to clarify, the name write_with() is perfectly fine for what
> the function does, it's more that we need it in the first place.)
>
> Also, IIUC, if the value is not created within the closure, we'd still have the
> following redundancy, right?
>
> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
> .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?;
>
> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>
> It's just that this case nicely converts to write_with().
You would have
let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::default()
.try_with_offs(load_offsets.dst_start + pos))?;
bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
Note that the `default()` invocation doesn't mention relative base, as it's just
plain bitfields without offset at that point. [ I like the fact that this
doesn't need to use closure, as I generally prefer code without them, perhaps I
am not "rusty" enough :) ]
In my view, if the code is complex enough that you have
let reg = ...;
<some code>
bar.write(reg)
then it probably makes sense to have register name mentioned again (this is
typed checked anyway so you don't need to worry about misnaming it). Otherwise,
one might read the code and be confused about what register is being written to
at all.
I think for explicit location parameter makes much more sense for relative
addressed registers and register arrays.
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-02 13:39 ` Gary Guo
@ 2026-03-03 8:14 ` Alexandre Courbot
2026-03-03 8:31 ` Alexandre Courbot
0 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-03 8:14 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 Mon Mar 2, 2026 at 10:39 PM JST, Gary Guo wrote:
> On Mon Mar 2, 2026 at 1:12 PM GMT, Danilo Krummrich wrote:
>> On Mon Mar 2, 2026 at 1:53 PM CET, Gary Guo wrote:
>>> On Mon Mar 2, 2026 at 1:44 AM GMT, Alexandre Courbot wrote:
>>>> That should be doable. Note that we currently support `zeroed` and
>>>> `default` as initializers, so having the same level of coverage would
>>>> require two `write` variants. I'd like to hear what Danilo thinks.
>>>
>>> I wonder if just providing a single version that starts with
>>> `Default::default()` should be sufficient? For most users, zeroed version is the
>>> default version anyway. For those where default is not zero, it perhaps makes
>>> more sense to start with default anyway; if explicitly zeroing is needed they
>>> can always do an explicit `::zeroed()`.
>>
>> I was thinking about this for a while and also thought that we probably only
>> ever need a version that starts with Default::default().
>>
>> What I still dislike is that the common case becomes write_with() instead of
>> just write(). (Just to clarify, the name write_with() is perfectly fine for what
>> the function does, it's more that we need it in the first place.)
>>
>> Also, IIUC, if the value is not created within the closure, we'd still have the
>> following redundancy, right?
>>
>> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
>> .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?;
>>
>> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>>
>> It's just that this case nicely converts to write_with().
>
> You would have
>
> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::default()
> .try_with_offs(load_offsets.dst_start + pos))?;
>
> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>
> Note that the `default()` invocation doesn't mention relative base, as it's just
> plain bitfields without offset at that point. [ I like the fact that this
> doesn't need to use closure, as I generally prefer code without them, perhaps I
> am not "rusty" enough :) ]
>
> In my view, if the code is complex enough that you have
>
> let reg = ...;
> <some code>
> bar.write(reg)
>
> then it probably makes sense to have register name mentioned again (this is
> typed checked anyway so you don't need to worry about misnaming it). Otherwise,
> one might read the code and be confused about what register is being written to
> at all.
>
> I think for explicit location parameter makes much more sense for relative
> addressed registers and register arrays.
I am not too worried either about having to repeat the location in a
write if we needed to store the register value somewhere first. That
case should be covered by `update`/`try_update` anyway. What is less
acceptable imo is having to type the location twice in the same `write`
statement.
I spent the day testing different strategies to support the
two-arguments write with both explicit values and closures to create a
value from scratch. That included adding a trait to produce the value
and making `write` generic against it: if both immediate values and
closures implement the trait, that should work I thought. Except that in
the call site the compiler is unable to infer the closure's argument and
requires us to explicitly specify it - sending us back to square 1.
again.
Another strategy is to make `write` accept only closures, and implement
`FnOnce` on immediate values... but that requires the `fn_traits`
unstable feature.
So that really leaves us with two options:
- The current one-argument design based on `IoWrite`, which carries a
location and its value,
- Or a pair of `write`/`write_with` methods for immediate values and
closures, respectively.
I'm ok with either, but the first one looks more composable to me. I can
send a version implementing the second one if people want to see what it
would look like.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-03 8:14 ` Alexandre Courbot
@ 2026-03-03 8:31 ` Alexandre Courbot
2026-03-03 14:55 ` Alexandre Courbot
0 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-03 8:31 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 3, 2026 at 5:14 PM JST, Alexandre Courbot wrote:
> On Mon Mar 2, 2026 at 10:39 PM JST, Gary Guo wrote:
>> On Mon Mar 2, 2026 at 1:12 PM GMT, Danilo Krummrich wrote:
>>> On Mon Mar 2, 2026 at 1:53 PM CET, Gary Guo wrote:
>>>> On Mon Mar 2, 2026 at 1:44 AM GMT, Alexandre Courbot wrote:
>>>>> That should be doable. Note that we currently support `zeroed` and
>>>>> `default` as initializers, so having the same level of coverage would
>>>>> require two `write` variants. I'd like to hear what Danilo thinks.
>>>>
>>>> I wonder if just providing a single version that starts with
>>>> `Default::default()` should be sufficient? For most users, zeroed version is the
>>>> default version anyway. For those where default is not zero, it perhaps makes
>>>> more sense to start with default anyway; if explicitly zeroing is needed they
>>>> can always do an explicit `::zeroed()`.
>>>
>>> I was thinking about this for a while and also thought that we probably only
>>> ever need a version that starts with Default::default().
>>>
>>> What I still dislike is that the common case becomes write_with() instead of
>>> just write(). (Just to clarify, the name write_with() is perfectly fine for what
>>> the function does, it's more that we need it in the first place.)
>>>
>>> Also, IIUC, if the value is not created within the closure, we'd still have the
>>> following redundancy, right?
>>>
>>> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
>>> .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?;
>>>
>>> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>>>
>>> It's just that this case nicely converts to write_with().
>>
>> You would have
>>
>> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::default()
>> .try_with_offs(load_offsets.dst_start + pos))?;
>>
>> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>>
>> Note that the `default()` invocation doesn't mention relative base, as it's just
>> plain bitfields without offset at that point. [ I like the fact that this
>> doesn't need to use closure, as I generally prefer code without them, perhaps I
>> am not "rusty" enough :) ]
>>
>> In my view, if the code is complex enough that you have
>>
>> let reg = ...;
>> <some code>
>> bar.write(reg)
>>
>> then it probably makes sense to have register name mentioned again (this is
>> typed checked anyway so you don't need to worry about misnaming it). Otherwise,
>> one might read the code and be confused about what register is being written to
>> at all.
>>
>> I think for explicit location parameter makes much more sense for relative
>> addressed registers and register arrays.
>
> I am not too worried either about having to repeat the location in a
> write if we needed to store the register value somewhere first. That
> case should be covered by `update`/`try_update` anyway. What is less
> acceptable imo is having to type the location twice in the same `write`
> statement.
>
> I spent the day testing different strategies to support the
> two-arguments write with both explicit values and closures to create a
> value from scratch. That included adding a trait to produce the value
> and making `write` generic against it: if both immediate values and
> closures implement the trait, that should work I thought. Except that in
> the call site the compiler is unable to infer the closure's argument and
> requires us to explicitly specify it - sending us back to square 1.
> again.
>
> Another strategy is to make `write` accept only closures, and implement
> `FnOnce` on immediate values... but that requires the `fn_traits`
> unstable feature.
>
> So that really leaves us with two options:
>
> - The current one-argument design based on `IoWrite`, which carries a
> location and its value,
> - Or a pair of `write`/`write_with` methods for immediate values and
> closures, respectively.
>
> I'm ok with either, but the first one looks more composable to me. I can
> send a version implementing the second one if people want to see what it
> would look like.
Mmm looking closer at the two-methods alternative, it does look more
familiar in terms of Rust patterns and less hacky in the end (i.e. no
need for `IoWrite`). The drawbacks are also manageable. I'm torn.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-03 8:31 ` Alexandre Courbot
@ 2026-03-03 14:55 ` Alexandre Courbot
2026-03-03 15:05 ` Gary Guo
2026-03-04 16:18 ` Danilo Krummrich
0 siblings, 2 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-03 14:55 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 3, 2026 at 5:31 PM JST, Alexandre Courbot wrote:
> On Tue Mar 3, 2026 at 5:14 PM JST, Alexandre Courbot wrote:
>> On Mon Mar 2, 2026 at 10:39 PM JST, Gary Guo wrote:
>>> On Mon Mar 2, 2026 at 1:12 PM GMT, Danilo Krummrich wrote:
>>>> On Mon Mar 2, 2026 at 1:53 PM CET, Gary Guo wrote:
>>>>> On Mon Mar 2, 2026 at 1:44 AM GMT, Alexandre Courbot wrote:
>>>>>> That should be doable. Note that we currently support `zeroed` and
>>>>>> `default` as initializers, so having the same level of coverage would
>>>>>> require two `write` variants. I'd like to hear what Danilo thinks.
>>>>>
>>>>> I wonder if just providing a single version that starts with
>>>>> `Default::default()` should be sufficient? For most users, zeroed version is the
>>>>> default version anyway. For those where default is not zero, it perhaps makes
>>>>> more sense to start with default anyway; if explicitly zeroing is needed they
>>>>> can always do an explicit `::zeroed()`.
>>>>
>>>> I was thinking about this for a while and also thought that we probably only
>>>> ever need a version that starts with Default::default().
>>>>
>>>> What I still dislike is that the common case becomes write_with() instead of
>>>> just write(). (Just to clarify, the name write_with() is perfectly fine for what
>>>> the function does, it's more that we need it in the first place.)
>>>>
>>>> Also, IIUC, if the value is not created within the closure, we'd still have the
>>>> following redundancy, right?
>>>>
>>>> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
>>>> .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?;
>>>>
>>>> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>>>>
>>>> It's just that this case nicely converts to write_with().
>>>
>>> You would have
>>>
>>> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::default()
>>> .try_with_offs(load_offsets.dst_start + pos))?;
>>>
>>> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>>>
>>> Note that the `default()` invocation doesn't mention relative base, as it's just
>>> plain bitfields without offset at that point. [ I like the fact that this
>>> doesn't need to use closure, as I generally prefer code without them, perhaps I
>>> am not "rusty" enough :) ]
>>>
>>> In my view, if the code is complex enough that you have
>>>
>>> let reg = ...;
>>> <some code>
>>> bar.write(reg)
>>>
>>> then it probably makes sense to have register name mentioned again (this is
>>> typed checked anyway so you don't need to worry about misnaming it). Otherwise,
>>> one might read the code and be confused about what register is being written to
>>> at all.
>>>
>>> I think for explicit location parameter makes much more sense for relative
>>> addressed registers and register arrays.
>>
>> I am not too worried either about having to repeat the location in a
>> write if we needed to store the register value somewhere first. That
>> case should be covered by `update`/`try_update` anyway. What is less
>> acceptable imo is having to type the location twice in the same `write`
>> statement.
>>
>> I spent the day testing different strategies to support the
>> two-arguments write with both explicit values and closures to create a
>> value from scratch. That included adding a trait to produce the value
>> and making `write` generic against it: if both immediate values and
>> closures implement the trait, that should work I thought. Except that in
>> the call site the compiler is unable to infer the closure's argument and
>> requires us to explicitly specify it - sending us back to square 1.
>> again.
>>
>> Another strategy is to make `write` accept only closures, and implement
>> `FnOnce` on immediate values... but that requires the `fn_traits`
>> unstable feature.
>>
>> So that really leaves us with two options:
>>
>> - The current one-argument design based on `IoWrite`, which carries a
>> location and its value,
>> - Or a pair of `write`/`write_with` methods for immediate values and
>> closures, respectively.
>>
>> I'm ok with either, but the first one looks more composable to me. I can
>> send a version implementing the second one if people want to see what it
>> would look like.
>
> Mmm looking closer at the two-methods alternative, it does look more
> familiar in terms of Rust patterns and less hacky in the end (i.e. no
> need for `IoWrite`). The drawbacks are also manageable. I'm torn.
So, to get a better idea of these two options I have converted this
patchset to use the 2-arguments `write_with` method. Here is the
difference between the two - it is particularly interesting to see how
nova-core changes:
https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
The two-arguments version often results in *shorter* write statements
for multi-line statements. One-liners are interestingly the same length.
I haven't found any instance where I had to write the register location
an extra time.
Note that the `Map` trick to allow closures to return a `Result` is not
implemented yet, so there are a couple of `unwrap`s to allow the code to
build.
One detail: when using `write_with`, it is more natural to specify the
location first, and closure second, since the closure's argument is
derived from the location. However, all the `write*` methods of `Io` use
the `(value, location)` order. This introduces some dissonance in the
API, unless we convert everyone to the `(location, value)` order.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-03 14:55 ` Alexandre Courbot
@ 2026-03-03 15:05 ` Gary Guo
2026-03-04 16:18 ` Danilo Krummrich
1 sibling, 0 replies; 64+ messages in thread
From: Gary Guo @ 2026-03-03 15:05 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 3, 2026 at 2:55 PM GMT, Alexandre Courbot wrote:
> On Tue Mar 3, 2026 at 5:31 PM JST, Alexandre Courbot wrote:
>> On Tue Mar 3, 2026 at 5:14 PM JST, Alexandre Courbot wrote:
>>> On Mon Mar 2, 2026 at 10:39 PM JST, Gary Guo wrote:
>>>> On Mon Mar 2, 2026 at 1:12 PM GMT, Danilo Krummrich wrote:
>>>>> On Mon Mar 2, 2026 at 1:53 PM CET, Gary Guo wrote:
>>>>>> On Mon Mar 2, 2026 at 1:44 AM GMT, Alexandre Courbot wrote:
>>>>>>> That should be doable. Note that we currently support `zeroed` and
>>>>>>> `default` as initializers, so having the same level of coverage would
>>>>>>> require two `write` variants. I'd like to hear what Danilo thinks.
>>>>>>
>>>>>> I wonder if just providing a single version that starts with
>>>>>> `Default::default()` should be sufficient? For most users, zeroed version is the
>>>>>> default version anyway. For those where default is not zero, it perhaps makes
>>>>>> more sense to start with default anyway; if explicitly zeroing is needed they
>>>>>> can always do an explicit `::zeroed()`.
>>>>>
>>>>> I was thinking about this for a while and also thought that we probably only
>>>>> ever need a version that starts with Default::default().
>>>>>
>>>>> What I still dislike is that the common case becomes write_with() instead of
>>>>> just write(). (Just to clarify, the name write_with() is perfectly fine for what
>>>>> the function does, it's more that we need it in the first place.)
>>>>>
>>>>> Also, IIUC, if the value is not created within the closure, we'd still have the
>>>>> following redundancy, right?
>>>>>
>>>>> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>()
>>>>> .try_init(|r| r.try_with_offs(load_offsets.dst_start + pos))?;
>>>>>
>>>>> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>>>>>
>>>>> It's just that this case nicely converts to write_with().
>>>>
>>>> You would have
>>>>
>>>> let reg = regs::NV_PFALCON_FALCON_DMATRFMOFFS::default()
>>>> .try_with_offs(load_offsets.dst_start + pos))?;
>>>>
>>>> bar.write(regs::NV_PFALCON_FALCON_DMATRFMOFFS::of::<E>(), reg);
>>>>
>>>> Note that the `default()` invocation doesn't mention relative base, as it's just
>>>> plain bitfields without offset at that point. [ I like the fact that this
>>>> doesn't need to use closure, as I generally prefer code without them, perhaps I
>>>> am not "rusty" enough :) ]
>>>>
>>>> In my view, if the code is complex enough that you have
>>>>
>>>> let reg = ...;
>>>> <some code>
>>>> bar.write(reg)
>>>>
>>>> then it probably makes sense to have register name mentioned again (this is
>>>> typed checked anyway so you don't need to worry about misnaming it). Otherwise,
>>>> one might read the code and be confused about what register is being written to
>>>> at all.
>>>>
>>>> I think for explicit location parameter makes much more sense for relative
>>>> addressed registers and register arrays.
>>>
>>> I am not too worried either about having to repeat the location in a
>>> write if we needed to store the register value somewhere first. That
>>> case should be covered by `update`/`try_update` anyway. What is less
>>> acceptable imo is having to type the location twice in the same `write`
>>> statement.
>>>
>>> I spent the day testing different strategies to support the
>>> two-arguments write with both explicit values and closures to create a
>>> value from scratch. That included adding a trait to produce the value
>>> and making `write` generic against it: if both immediate values and
>>> closures implement the trait, that should work I thought. Except that in
>>> the call site the compiler is unable to infer the closure's argument and
>>> requires us to explicitly specify it - sending us back to square 1.
>>> again.
>>>
>>> Another strategy is to make `write` accept only closures, and implement
>>> `FnOnce` on immediate values... but that requires the `fn_traits`
>>> unstable feature.
>>>
>>> So that really leaves us with two options:
>>>
>>> - The current one-argument design based on `IoWrite`, which carries a
>>> location and its value,
>>> - Or a pair of `write`/`write_with` methods for immediate values and
>>> closures, respectively.
>>>
>>> I'm ok with either, but the first one looks more composable to me. I can
>>> send a version implementing the second one if people want to see what it
>>> would look like.
>>
>> Mmm looking closer at the two-methods alternative, it does look more
>> familiar in terms of Rust patterns and less hacky in the end (i.e. no
>> need for `IoWrite`). The drawbacks are also manageable. I'm torn.
>
> So, to get a better idea of these two options I have converted this
> patchset to use the 2-arguments `write_with` method. Here is the
> difference between the two - it is particularly interesting to see how
> nova-core changes:
>
> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>
> The two-arguments version often results in *shorter* write statements
> for multi-line statements. One-liners are interestingly the same length.
> I haven't found any instance where I had to write the register location
> an extra time.
Looks pretty good. Thanks for trying out.
>
> Note that the `Map` trick to allow closures to return a `Result` is not
> implemented yet, so there are a couple of `unwrap`s to allow the code to
> build.
Alternatively we can in the initial version just add a `write_try_with` method
and add the `Map` trick later.
>
> One detail: when using `write_with`, it is more natural to specify the
> location first, and closure second, since the closure's argument is
> derived from the location. However, all the `write*` methods of `Io` use
> the `(value, location)` order. This introduces some dissonance in the
> API, unless we convert everyone to the `(location, value)` order.
Personally I was actually quite surprised that we use `(value, location)` in our
I/O trait, when implementing I/O projections. I would actually be in favour if
the write API always use `(location, value)`.
I think the current order is just due to the fact that `writel` and friends take
value first and address second. Which itself I think probably is just a
historical choice and is not consistent with APIs like `WRITE_ONCE` and
`atomic_set`.
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-03 14:55 ` Alexandre Courbot
2026-03-03 15:05 ` Gary Guo
@ 2026-03-04 16:18 ` Danilo Krummrich
2026-03-04 18:39 ` Gary Guo
2026-03-04 18:53 ` Gary Guo
1 sibling, 2 replies; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-04 16:18 UTC (permalink / raw)
To: Alexandre Courbot
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 Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
> So, to get a better idea of these two options I have converted this
> patchset to use the 2-arguments `write_with` method. Here is the
> difference between the two - it is particularly interesting to see how
> nova-core changes:
>
> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
This looks good to me, but the fact that this turns out nicely has nothing to do
with write() now taking two arguments. I.e. there is no reason why we couldn't
have the exact same write_with() method together with the single argument
write() method.
The contention point for me with a two arguments write() method still remains
that the arguments are redundant.
I.e. you first have the location in form of an object instance of a ZST (which
in the end is just a "trick" to pass in the type itself) and then we have the
object that actually represents the entire register, describing both the
location *and* the value.
So, let's say a driver creates a register object with a custom constructor
let reset = regs::MyReg::reset();
then the two argument approach would be
(1) bar.write(regs::MyReg, regs::MyReg::reset());
whereas the single argument approach would just be
(2) bar.write(regs::MyReg::reset());
So, if I would have to write (1), I'd probably be tempted to implement a reset()
function that takes the bar as argument to hide this, i.e.
regs::MyReg::reset(bar);
I also can't agree with the argument that the notation of write(loc, val) - or
write(val, loc) as the C side does it - is common and we should stick to it.
This notation is only common because it is necessary when operating on
primitives or when the two representing types are discrete.
But this isn't the case here, a register object is already distinct in terms of
its location and value.
So, unless there is a strong argument why this improves anything on the user
side of the API, I still feel like we should keep write() as is with a single
argument; having an additional write_with() method seems fine though.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 16:18 ` Danilo Krummrich
@ 2026-03-04 18:39 ` Gary Guo
2026-03-04 18:58 ` Gary Guo
2026-03-04 18:53 ` Gary Guo
1 sibling, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-04 18:39 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
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 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>> So, to get a better idea of these two options I have converted this
>> patchset to use the 2-arguments `write_with` method. Here is the
>> difference between the two - it is particularly interesting to see how
>> nova-core changes:
>>
>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>
> This looks good to me, but the fact that this turns out nicely has nothing to do
> with write() now taking two arguments. I.e. there is no reason why we couldn't
> have the exact same write_with() method together with the single argument
> write() method.
>
> The contention point for me with a two arguments write() method still remains
> that the arguments are redundant.
>
> I.e. you first have the location in form of an object instance of a ZST (which
> in the end is just a "trick" to pass in the type itself) and then we have the
> object that actually represents the entire register, describing both the
> location *and* the value.
>
> So, let's say a driver creates a register object with a custom constructor
>
> let reset = regs::MyReg::reset();
>
> then the two argument approach would be
>
> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>
> whereas the single argument approach would just be
>
> (2) bar.write(regs::MyReg::reset());
That's only for bit field registers that has unique types. I still believe types
of registers should not be tightly coupled with name of registeres.
Allowing a value of register to be directly used for `write` is also confusing
if a value is not created immediately before written to.
>
> So, if I would have to write (1), I'd probably be tempted to implement a reset()
> function that takes the bar as argument to hide this, i.e.
>
> regs::MyReg::reset(bar);
>
> I also can't agree with the argument that the notation of write(loc, val) - or
> write(val, loc) as the C side does it - is common and we should stick to it.
>
> This notation is only common because it is necessary when operating on
> primitives or when the two representing types are discrete.
>
> But this isn't the case here, a register object is already distinct in terms of
> its location and value.
I see no reason why register values for different locations have to be distinct
in terms of value types.
Even Nova today has quite a few registers that are just bitfields of a single
field that spans all bits. I think many simple driver would probably want to
just operate on primitives for these.
Another example is that if there're multiple registers where fields have the
same meaning (e.g. a device address), then a user might want to just have the
same type for all these values. We've already have an example of register arrays
that share types, and I see no reason to forbid non-array registers from also
sharing types.
Forcing all registers to have different types is a design direction that I don't
want to take.
Best,
Gary
>
> So, unless there is a strong argument why this improves anything on the user
> side of the API, I still feel like we should keep write() as is with a single
> argument; having an additional write_with() method seems fine though.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 16:18 ` Danilo Krummrich
2026-03-04 18:39 ` Gary Guo
@ 2026-03-04 18:53 ` Gary Guo
1 sibling, 0 replies; 64+ messages in thread
From: Gary Guo @ 2026-03-04 18:53 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot
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 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>> So, to get a better idea of these two options I have converted this
>> patchset to use the 2-arguments `write_with` method. Here is the
>> difference between the two - it is particularly interesting to see how
>> nova-core changes:
>>
>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>
> This looks good to me, but the fact that this turns out nicely has nothing to do
> with write() now taking two arguments. I.e. there is no reason why we couldn't
> have the exact same write_with() method together with the single argument
> write() method.
>
> The contention point for me with a two arguments write() method still remains
> that the arguments are redundant.
>
> I.e. you first have the location in form of an object instance of a ZST (which
> in the end is just a "trick" to pass in the type itself) and then we have the
> object that actually represents the entire register, describing both the
> location *and* the value.
>
> So, let's say a driver creates a register object with a custom constructor
>
> let reset = regs::MyReg::reset();
>
> then the two argument approach would be
>
> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>
> whereas the single argument approach would just be
>
> (2) bar.write(regs::MyReg::reset());
>
> So, if I would have to write (1), I'd probably be tempted to implement a reset()
> function that takes the bar as argument to hide this, i.e.
>
> regs::MyReg::reset(bar);
>
> I also can't agree with the argument that the notation of write(loc, val) - or
> write(val, loc) as the C side does it - is common and we should stick to it.
>
> This notation is only common because it is necessary when operating on
> primitives or when the two representing types are discrete.
>
> But this isn't the case here, a register object is already distinct in terms of
> its location and value.
>
> So, unless there is a strong argument why this improves anything on the user
> side of the API, I still feel like we should keep write() as is with a single
> argument; having an additional write_with() method seems fine though.
Another thing is that `.write(loc, value)` can also replace the all
non-`register!` APIs.
With
impl IoLoc<u32> for usize {
type IoType = u32;
fn offset(self) -> usize { self }
}
then you can just use `.write(offset, primitive)`. Meaning this is a more
universal API design.
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 18:39 ` Gary Guo
@ 2026-03-04 18:58 ` Gary Guo
2026-03-04 19:19 ` John Hubbard
2026-03-04 19:38 ` Danilo Krummrich
0 siblings, 2 replies; 64+ messages in thread
From: Gary Guo @ 2026-03-04 18:58 UTC (permalink / raw)
To: Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: 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 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>> So, to get a better idea of these two options I have converted this
>>> patchset to use the 2-arguments `write_with` method. Here is the
>>> difference between the two - it is particularly interesting to see how
>>> nova-core changes:
>>>
>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>
>> This looks good to me, but the fact that this turns out nicely has nothing to do
>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>> have the exact same write_with() method together with the single argument
>> write() method.
>>
>> The contention point for me with a two arguments write() method still remains
>> that the arguments are redundant.
>>
>> I.e. you first have the location in form of an object instance of a ZST (which
>> in the end is just a "trick" to pass in the type itself) and then we have the
>> object that actually represents the entire register, describing both the
>> location *and* the value.
>>
>> So, let's say a driver creates a register object with a custom constructor
>>
>> let reset = regs::MyReg::reset();
>>
>> then the two argument approach would be
>>
>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>
>> whereas the single argument approach would just be
>>
>> (2) bar.write(regs::MyReg::reset());
>
> That's only for bit field registers that has unique types. I still believe types
> of registers should not be tightly coupled with name of registeres.
>
> Allowing a value of register to be directly used for `write` is also confusing
> if a value is not created immediately before written to.
>
>>
>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>> function that takes the bar as argument to hide this, i.e.
>>
>> regs::MyReg::reset(bar);
>>
>> I also can't agree with the argument that the notation of write(loc, val) - or
>> write(val, loc) as the C side does it - is common and we should stick to it.
>>
>> This notation is only common because it is necessary when operating on
>> primitives or when the two representing types are discrete.
>>
>> But this isn't the case here, a register object is already distinct in terms of
>> its location and value.
>
> I see no reason why register values for different locations have to be distinct
> in terms of value types.
>
> Even Nova today has quite a few registers that are just bitfields of a single
> field that spans all bits. I think many simple driver would probably want to
> just operate on primitives for these.
I shall add that I think the fact that the registers that are *not* fields still
gain their dedicated type in Nova driver is due to the limitation of the initial
`register!` API design that *requires* unique types due to the `value.op(io)`
design as opposed to `io.op(value)`.
I think even these ones should eventually be replaced by just primitives
eventually. I see no benefit of
bar.write(REG.init(|x| x.with_value(value)))
as opposed to just
bar.write(REG, value)
Best,
Gary
>
> Another example is that if there're multiple registers where fields have the
> same meaning (e.g. a device address), then a user might want to just have the
> same type for all these values. We've already have an example of register arrays
> that share types, and I see no reason to forbid non-array registers from also
> sharing types.
>
> Forcing all registers to have different types is a design direction that I don't
> want to take.
>
> Best,
> Gary
>
>>
>> So, unless there is a strong argument why this improves anything on the user
>> side of the API, I still feel like we should keep write() as is with a single
>> argument; having an additional write_with() method seems fine though.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 18:58 ` Gary Guo
@ 2026-03-04 19:19 ` John Hubbard
2026-03-04 19:53 ` Danilo Krummrich
2026-03-04 19:38 ` Danilo Krummrich
1 sibling, 1 reply; 64+ messages in thread
From: John Hubbard @ 2026-03-04 19:19 UTC (permalink / raw)
To: Gary Guo, Danilo Krummrich, Alexandre Courbot
Cc: Alice Ryhl, Daniel Almeida, Miguel Ojeda, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Trevor Gross, Boqun Feng,
Yury Norov, Alistair Popple, Joel Fernandes, Timur Tabi,
Edwin Peer, Eliot Courtney, Dirk Behme, Steven Price,
rust-for-linux, linux-kernel
On 3/4/26 10:58 AM, Gary Guo wrote:
...
>> I see no reason why register values for different locations have to be distinct
>> in terms of value types.
>>
>> Even Nova today has quite a few registers that are just bitfields of a single
>> field that spans all bits. I think many simple driver would probably want to
>> just operate on primitives for these.
>
> I shall add that I think the fact that the registers that are *not* fields still
> gain their dedicated type in Nova driver is due to the limitation of the initial
> `register!` API design that *requires* unique types due to the `value.op(io)`
> design as opposed to `io.op(value)`.
>
> I think even these ones should eventually be replaced by just primitives
> eventually. I see no benefit of
>
> bar.write(REG.init(|x| x.with_value(value)))
>
> as opposed to just
>
> bar.write(REG, value)
All other things being equal, the second form is clearly preferable, yes.
>
> Best,
> Gary
>
>>
>> Another example is that if there're multiple registers where fields have the
>> same meaning (e.g. a device address), then a user might want to just have the
>> same type for all these values. We've already have an example of register arrays
>> that share types, and I see no reason to forbid non-array registers from also
>> sharing types.
>>
>> Forcing all registers to have different types is a design direction that I don't
>> want to take.
I also want to register (haha) a strong yet unexplainable concern about
this. Registers as types is something I've never seen attempted, it feels
strange and unnecessary, and just worries me.
thanks,
--
John Hubbard
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 18:58 ` Gary Guo
2026-03-04 19:19 ` John Hubbard
@ 2026-03-04 19:38 ` Danilo Krummrich
2026-03-04 19:48 ` Gary Guo
1 sibling, 1 reply; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-04 19:38 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 4, 2026 at 7:58 PM CET, Gary Guo wrote:
> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>> So, to get a better idea of these two options I have converted this
>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>> difference between the two - it is particularly interesting to see how
>>>> nova-core changes:
>>>>
>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>
>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>> have the exact same write_with() method together with the single argument
>>> write() method.
>>>
>>> The contention point for me with a two arguments write() method still remains
>>> that the arguments are redundant.
>>>
>>> I.e. you first have the location in form of an object instance of a ZST (which
>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>> object that actually represents the entire register, describing both the
>>> location *and* the value.
>>>
>>> So, let's say a driver creates a register object with a custom constructor
>>>
>>> let reset = regs::MyReg::reset();
>>>
>>> then the two argument approach would be
>>>
>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>
>>> whereas the single argument approach would just be
>>>
>>> (2) bar.write(regs::MyReg::reset());
>>
>> That's only for bit field registers that has unique types. I still believe types
>> of registers should not be tightly coupled with name of registeres.
>>
>> Allowing a value of register to be directly used for `write` is also confusing
>> if a value is not created immediately before written to.
>>
>>>
>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>> function that takes the bar as argument to hide this, i.e.
>>>
>>> regs::MyReg::reset(bar);
>>>
>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>
>>> This notation is only common because it is necessary when operating on
>>> primitives or when the two representing types are discrete.
>>>
>>> But this isn't the case here, a register object is already distinct in terms of
>>> its location and value.
>>
>> I see no reason why register values for different locations have to be distinct
>> in terms of value types.
That's not what the register!() macro currently does, a register type always has
a unique location, or is an array register, etc. In any case a register type is
assoiciated with a location.
If the proposal is to disconnect location and register type entirely, that would
be a change to the current design.
If we'd have this clear separation, I would obviously not object to this change,
but currently it's just unnecessary redundancy.
>> Even Nova today has quite a few registers that are just bitfields of a single
>> field that spans all bits. I think many simple driver would probably want to
>> just operate on primitives for these.
>
> I shall add that I think the fact that the registers that are *not* fields still
> gain their dedicated type in Nova driver is due to the limitation of the initial
> `register!` API design that *requires* unique types due to the `value.op(io)`
> design as opposed to `io.op(value)`.
>
> I think even these ones should eventually be replaced by just primitives
> eventually. I see no benefit of
>
> bar.write(REG.init(|x| x.with_value(value)))
>
> as opposed to just
>
> bar.write(REG, value)
Well, you don't have to make that we have to use init() with a closure for such
cases. We can also do something like:
bar.write(Reg::from(value))
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 19:38 ` Danilo Krummrich
@ 2026-03-04 19:48 ` Gary Guo
2026-03-04 20:37 ` Danilo Krummrich
0 siblings, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-04 19:48 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 4, 2026 at 7:38 PM GMT, Danilo Krummrich wrote:
> On Wed Mar 4, 2026 at 7:58 PM CET, Gary Guo wrote:
>> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>>> So, to get a better idea of these two options I have converted this
>>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>>> difference between the two - it is particularly interesting to see how
>>>>> nova-core changes:
>>>>>
>>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>>
>>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>>> have the exact same write_with() method together with the single argument
>>>> write() method.
>>>>
>>>> The contention point for me with a two arguments write() method still remains
>>>> that the arguments are redundant.
>>>>
>>>> I.e. you first have the location in form of an object instance of a ZST (which
>>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>>> object that actually represents the entire register, describing both the
>>>> location *and* the value.
>>>>
>>>> So, let's say a driver creates a register object with a custom constructor
>>>>
>>>> let reset = regs::MyReg::reset();
>>>>
>>>> then the two argument approach would be
>>>>
>>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>>
>>>> whereas the single argument approach would just be
>>>>
>>>> (2) bar.write(regs::MyReg::reset());
>>>
>>> That's only for bit field registers that has unique types. I still believe types
>>> of registers should not be tightly coupled with name of registeres.
>>>
>>> Allowing a value of register to be directly used for `write` is also confusing
>>> if a value is not created immediately before written to.
>>>
>>>>
>>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>>> function that takes the bar as argument to hide this, i.e.
>>>>
>>>> regs::MyReg::reset(bar);
>>>>
>>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>>
>>>> This notation is only common because it is necessary when operating on
>>>> primitives or when the two representing types are discrete.
>>>>
>>>> But this isn't the case here, a register object is already distinct in terms of
>>>> its location and value.
>>>
>>> I see no reason why register values for different locations have to be distinct
>>> in terms of value types.
>
> That's not what the register!() macro currently does, a register type always has
> a unique location, or is an array register, etc. In any case a register type is
> assoiciated with a location.
>
> If the proposal is to disconnect location and register type entirely, that would
> be a change to the current design.
It's not what the macro do today, but I don't want to ask Alex to change it
further before landing the series. I do think it's a worthy follow-up to add the
ability to decouple the location and type. It's not incompatible with current
design anyway.
>
> If we'd have this clear separation, I would obviously not object to this change,
> but currently it's just unnecessary redundancy.
>
>>> Even Nova today has quite a few registers that are just bitfields of a single
>>> field that spans all bits. I think many simple driver would probably want to
>>> just operate on primitives for these.
>>
>> I shall add that I think the fact that the registers that are *not* fields still
>> gain their dedicated type in Nova driver is due to the limitation of the initial
>> `register!` API design that *requires* unique types due to the `value.op(io)`
>> design as opposed to `io.op(value)`.
>>
>> I think even these ones should eventually be replaced by just primitives
>> eventually. I see no benefit of
>>
>> bar.write(REG.init(|x| x.with_value(value)))
>>
>> as opposed to just
>>
>> bar.write(REG, value)
>
> Well, you don't have to make that we have to use init() with a closure for such
> cases. We can also do something like:
>
> bar.write(Reg::from(value))
This won't work for the array case, right? For array you'd have
bar.write(ARRAY.try_at(idx).ok_or(EINVAL)?.set(Reg::from(value)))
and now the register name is repeating twice rather than just
bar.write(ARRAY.try_at(idx).ok_or(EINVAL)?, value)
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 19:19 ` John Hubbard
@ 2026-03-04 19:53 ` Danilo Krummrich
2026-03-04 19:57 ` John Hubbard
2026-03-04 20:05 ` Gary Guo
0 siblings, 2 replies; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-04 19:53 UTC (permalink / raw)
To: John Hubbard
Cc: Gary Guo, Alexandre Courbot, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov,
Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer,
Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux,
linux-kernel
On Wed Mar 4, 2026 at 8:19 PM CET, John Hubbard wrote:
> I also want to register (haha) a strong yet unexplainable concern about
> this. Registers as types is something I've never seen attempted, it feels
> strange and unnecessary, and just worries me.
Do you want to go back to lots of defines, manual masking and shifts rather than
having a single obvious definition of a register that generates a type with
getters and setters? Needless to say that the former is much more error prone
and less readable. :)
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 19:53 ` Danilo Krummrich
@ 2026-03-04 19:57 ` John Hubbard
2026-03-04 20:05 ` Gary Guo
1 sibling, 0 replies; 64+ messages in thread
From: John Hubbard @ 2026-03-04 19:57 UTC (permalink / raw)
To: Danilo Krummrich
Cc: Gary Guo, Alexandre Courbot, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov,
Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer,
Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux,
linux-kernel
On 3/4/26 11:53 AM, Danilo Krummrich wrote:
> On Wed Mar 4, 2026 at 8:19 PM CET, John Hubbard wrote:
>> I also want to register (haha) a strong yet unexplainable concern about
>> this. Registers as types is something I've never seen attempted, it feels
>> strange and unnecessary, and just worries me.
>
> Do you want to go back to lots of defines, manual masking and shifts rather than
> having a single obvious definition of a register that generates a type with
> getters and setters? Needless to say that the former is much more error prone
> and less readable. :)
OK OK, it's a one-way portal into a brave new world, I will
try to accept that. hahaha :)
thanks,
--
John Hubbard
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 19:53 ` Danilo Krummrich
2026-03-04 19:57 ` John Hubbard
@ 2026-03-04 20:05 ` Gary Guo
1 sibling, 0 replies; 64+ messages in thread
From: Gary Guo @ 2026-03-04 20:05 UTC (permalink / raw)
To: Danilo Krummrich, John Hubbard
Cc: Gary Guo, Alexandre Courbot, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Boqun Feng, Yury Norov,
Alistair Popple, Joel Fernandes, Timur Tabi, Edwin Peer,
Eliot Courtney, Dirk Behme, Steven Price, rust-for-linux,
linux-kernel
On Wed Mar 4, 2026 at 7:53 PM GMT, Danilo Krummrich wrote:
> On Wed Mar 4, 2026 at 8:19 PM CET, John Hubbard wrote:
>> I also want to register (haha) a strong yet unexplainable concern about
>> this. Registers as types is something I've never seen attempted, it feels
>> strange and unnecessary, and just worries me.
>
> Do you want to go back to lots of defines, manual masking and shifts rather than
> having a single obvious definition of a register that generates a type with
> getters and setters? Needless to say that the former is much more error prone
> and less readable. :)
Elimination of manual masking and shifts don't require a nominal type system :)
I can imagine use cases where a interrupt-enabled register and interrupt-pending
register has identical bitfields, and you might want to use the same type for
interrupt enabled and interrupt pending registers and might even want to use
bitwise operations between them :)
Ultimately bitfields types and register-as-types are all or nothing.
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 19:48 ` Gary Guo
@ 2026-03-04 20:37 ` Danilo Krummrich
2026-03-04 21:13 ` Gary Guo
0 siblings, 1 reply; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-04 20:37 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 4, 2026 at 8:48 PM CET, Gary Guo wrote:
> On Wed Mar 4, 2026 at 7:38 PM GMT, Danilo Krummrich wrote:
>> On Wed Mar 4, 2026 at 7:58 PM CET, Gary Guo wrote:
>>> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>>>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>>>> So, to get a better idea of these two options I have converted this
>>>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>>>> difference between the two - it is particularly interesting to see how
>>>>>> nova-core changes:
>>>>>>
>>>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>>>
>>>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>>>> have the exact same write_with() method together with the single argument
>>>>> write() method.
>>>>>
>>>>> The contention point for me with a two arguments write() method still remains
>>>>> that the arguments are redundant.
>>>>>
>>>>> I.e. you first have the location in form of an object instance of a ZST (which
>>>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>>>> object that actually represents the entire register, describing both the
>>>>> location *and* the value.
>>>>>
>>>>> So, let's say a driver creates a register object with a custom constructor
>>>>>
>>>>> let reset = regs::MyReg::reset();
>>>>>
>>>>> then the two argument approach would be
>>>>>
>>>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>>>
>>>>> whereas the single argument approach would just be
>>>>>
>>>>> (2) bar.write(regs::MyReg::reset());
>>>>
>>>> That's only for bit field registers that has unique types. I still believe types
>>>> of registers should not be tightly coupled with name of registeres.
>>>>
>>>> Allowing a value of register to be directly used for `write` is also confusing
>>>> if a value is not created immediately before written to.
>>>>
>>>>>
>>>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>>>> function that takes the bar as argument to hide this, i.e.
>>>>>
>>>>> regs::MyReg::reset(bar);
>>>>>
>>>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>>>
>>>>> This notation is only common because it is necessary when operating on
>>>>> primitives or when the two representing types are discrete.
>>>>>
>>>>> But this isn't the case here, a register object is already distinct in terms of
>>>>> its location and value.
>>>>
>>>> I see no reason why register values for different locations have to be distinct
>>>> in terms of value types.
>>
>> That's not what the register!() macro currently does, a register type always has
>> a unique location, or is an array register, etc. In any case a register type is
>> assoiciated with a location.
>>
>> If the proposal is to disconnect location and register type entirely, that would
>> be a change to the current design.
>
> It's not what the macro do today, but I don't want to ask Alex to change it
> further before landing the series. I do think it's a worthy follow-up to add the
> ability to decouple the location and type. It's not incompatible with current
> design anyway.
I'm not sure there are any relevant use-cases for this. Do you have real
examples that would not be represented with array registers?
Otherwise, I think this disconnect has no advantages. Actually, I think it may
even be the opposite, as it would allow for confusing the location.
>> If we'd have this clear separation, I would obviously not object to this change,
>> but currently it's just unnecessary redundancy.
>>
>>>> Even Nova today has quite a few registers that are just bitfields of a single
>>>> field that spans all bits. I think many simple driver would probably want to
>>>> just operate on primitives for these.
>>>
>>> I shall add that I think the fact that the registers that are *not* fields still
>>> gain their dedicated type in Nova driver is due to the limitation of the initial
>>> `register!` API design that *requires* unique types due to the `value.op(io)`
>>> design as opposed to `io.op(value)`.
>>>
>>> I think even these ones should eventually be replaced by just primitives
>>> eventually. I see no benefit of
>>>
>>> bar.write(REG.init(|x| x.with_value(value)))
>>>
>>> as opposed to just
>>>
>>> bar.write(REG, value)
>>
>> Well, you don't have to make that we have to use init() with a closure for such
>> cases. We can also do something like:
>>
>> bar.write(Reg::from(value))
>
> This won't work for the array case, right? For array you'd have
>
> bar.write(ARRAY.try_at(idx).ok_or(EINVAL)?.set(Reg::from(value)))
>
> and now the register name is repeating twice rather than just
>
> bar.write(ARRAY.try_at(idx).ok_or(EINVAL)?, value)
We should be able to just do
bar.write(ARRAY.try_at(idx).ok_or(EINVAL)?.set(value.into()))
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 20:37 ` Danilo Krummrich
@ 2026-03-04 21:13 ` Gary Guo
2026-03-04 21:38 ` Danilo Krummrich
0 siblings, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-04 21:13 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 4, 2026 at 8:37 PM GMT, Danilo Krummrich wrote:
> On Wed Mar 4, 2026 at 8:48 PM CET, Gary Guo wrote:
>> On Wed Mar 4, 2026 at 7:38 PM GMT, Danilo Krummrich wrote:
>>> On Wed Mar 4, 2026 at 7:58 PM CET, Gary Guo wrote:
>>>> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>>>>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>>>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>>>>> So, to get a better idea of these two options I have converted this
>>>>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>>>>> difference between the two - it is particularly interesting to see how
>>>>>>> nova-core changes:
>>>>>>>
>>>>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>>>>
>>>>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>>>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>>>>> have the exact same write_with() method together with the single argument
>>>>>> write() method.
>>>>>>
>>>>>> The contention point for me with a two arguments write() method still remains
>>>>>> that the arguments are redundant.
>>>>>>
>>>>>> I.e. you first have the location in form of an object instance of a ZST (which
>>>>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>>>>> object that actually represents the entire register, describing both the
>>>>>> location *and* the value.
>>>>>>
>>>>>> So, let's say a driver creates a register object with a custom constructor
>>>>>>
>>>>>> let reset = regs::MyReg::reset();
>>>>>>
>>>>>> then the two argument approach would be
>>>>>>
>>>>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>>>>
>>>>>> whereas the single argument approach would just be
>>>>>>
>>>>>> (2) bar.write(regs::MyReg::reset());
>>>>>
>>>>> That's only for bit field registers that has unique types. I still believe types
>>>>> of registers should not be tightly coupled with name of registeres.
>>>>>
>>>>> Allowing a value of register to be directly used for `write` is also confusing
>>>>> if a value is not created immediately before written to.
>>>>>
>>>>>>
>>>>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>>>>> function that takes the bar as argument to hide this, i.e.
>>>>>>
>>>>>> regs::MyReg::reset(bar);
>>>>>>
>>>>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>>>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>>>>
>>>>>> This notation is only common because it is necessary when operating on
>>>>>> primitives or when the two representing types are discrete.
>>>>>>
>>>>>> But this isn't the case here, a register object is already distinct in terms of
>>>>>> its location and value.
>>>>>
>>>>> I see no reason why register values for different locations have to be distinct
>>>>> in terms of value types.
>>>
>>> That's not what the register!() macro currently does, a register type always has
>>> a unique location, or is an array register, etc. In any case a register type is
>>> assoiciated with a location.
>>>
>>> If the proposal is to disconnect location and register type entirely, that would
>>> be a change to the current design.
>>
>> It's not what the macro do today, but I don't want to ask Alex to change it
>> further before landing the series. I do think it's a worthy follow-up to add the
>> ability to decouple the location and type. It's not incompatible with current
>> design anyway.
>
> I'm not sure there are any relevant use-cases for this. Do you have real
> examples that would not be represented with array registers?
Even for the cases where there's a PIO register, I think it's beneficial to just
get a value without a type.
I don't see why we want people to write
self.io.read(UART_RX).value()
vs
self.io.read(UART_RX)
or
self.io.write(UART_TX::from(byte))
vs
self.io.write(UART_TX, byte)
what benefit does additional type provide?
>
> Otherwise, I think this disconnect has no advantages. Actually, I think it may
> even be the opposite, as it would allow for confusing the location.
The two ways of writing has the exact same possibility of location confusion.
It's just one uses comma and another uses `::from()`. Arguably the comma form is
better because you always spell out the location, *and* it's still type checked.
The comma one is more C like, less typing, and is more clear visually. The one
operand approach is just aesthetically bad.
I also have to say that I am not a big fan of adding additional types when
things can be just done with constant values. That's more work for compiler and
code monomorphization in general, without significant added value.
>
>>> If we'd have this clear separation, I would obviously not object to this change,
>>> but currently it's just unnecessary redundancy.
>>>
>>>>> Even Nova today has quite a few registers that are just bitfields of a single
>>>>> field that spans all bits. I think many simple driver would probably want to
>>>>> just operate on primitives for these.
>>>>
>>>> I shall add that I think the fact that the registers that are *not* fields still
>>>> gain their dedicated type in Nova driver is due to the limitation of the initial
>>>> `register!` API design that *requires* unique types due to the `value.op(io)`
>>>> design as opposed to `io.op(value)`.
>>>>
>>>> I think even these ones should eventually be replaced by just primitives
>>>> eventually. I see no benefit of
>>>>
>>>> bar.write(REG.init(|x| x.with_value(value)))
>>>>
>>>> as opposed to just
>>>>
>>>> bar.write(REG, value)
>>>
>>> Well, you don't have to make that we have to use init() with a closure for such
>>> cases. We can also do something like:
>>>
>>> bar.write(Reg::from(value))
>>
>> This won't work for the array case, right? For array you'd have
>>
>> bar.write(ARRAY.try_at(idx).ok_or(EINVAL)?.set(Reg::from(value)))
>>
>> and now the register name is repeating twice rather than just
>>
>> bar.write(ARRAY.try_at(idx).ok_or(EINVAL)?, value)
>
> We should be able to just do
>
> bar.write(ARRAY.try_at(idx).ok_or(EINVAL)?.set(value.into()))
Sure, more typing, more parenthesis nesting, and more cluttered visually. I
don't understand why is this preferrable.
The `io.write(loc, value)` approach is very simple to understand also very
straightforward to implement. `loc` is just an offset that also restricts the
type, `value` is a typed value, that can be bitfields or some other used-defined
types.
Want things to be just integers? Sure, you can have it. Want everything to be
fully typed? Sure, you can have it with bitfields. Custom types with custom
conversion logic? Implement your own conversion with a `IoCapable` primitive and
you can have it too.
I don't see a need to have a more restrictive API, and also have something that
is less clear visually, and, most important to me, ugly.
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 21:13 ` Gary Guo
@ 2026-03-04 21:38 ` Danilo Krummrich
2026-03-04 21:42 ` Danilo Krummrich
2026-03-04 22:15 ` Gary Guo
0 siblings, 2 replies; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-04 21:38 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 4, 2026 at 10:13 PM CET, Gary Guo wrote:
> On Wed Mar 4, 2026 at 8:37 PM GMT, Danilo Krummrich wrote:
>> On Wed Mar 4, 2026 at 8:48 PM CET, Gary Guo wrote:
>>> On Wed Mar 4, 2026 at 7:38 PM GMT, Danilo Krummrich wrote:
>>>> On Wed Mar 4, 2026 at 7:58 PM CET, Gary Guo wrote:
>>>>> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>>>>>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>>>>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>>>>>> So, to get a better idea of these two options I have converted this
>>>>>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>>>>>> difference between the two - it is particularly interesting to see how
>>>>>>>> nova-core changes:
>>>>>>>>
>>>>>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>>>>>
>>>>>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>>>>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>>>>>> have the exact same write_with() method together with the single argument
>>>>>>> write() method.
>>>>>>>
>>>>>>> The contention point for me with a two arguments write() method still remains
>>>>>>> that the arguments are redundant.
>>>>>>>
>>>>>>> I.e. you first have the location in form of an object instance of a ZST (which
>>>>>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>>>>>> object that actually represents the entire register, describing both the
>>>>>>> location *and* the value.
>>>>>>>
>>>>>>> So, let's say a driver creates a register object with a custom constructor
>>>>>>>
>>>>>>> let reset = regs::MyReg::reset();
>>>>>>>
>>>>>>> then the two argument approach would be
>>>>>>>
>>>>>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>>>>>
>>>>>>> whereas the single argument approach would just be
>>>>>>>
>>>>>>> (2) bar.write(regs::MyReg::reset());
>>>>>>
>>>>>> That's only for bit field registers that has unique types. I still believe types
>>>>>> of registers should not be tightly coupled with name of registeres.
>>>>>>
>>>>>> Allowing a value of register to be directly used for `write` is also confusing
>>>>>> if a value is not created immediately before written to.
>>>>>>
>>>>>>>
>>>>>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>>>>>> function that takes the bar as argument to hide this, i.e.
>>>>>>>
>>>>>>> regs::MyReg::reset(bar);
>>>>>>>
>>>>>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>>>>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>>>>>
>>>>>>> This notation is only common because it is necessary when operating on
>>>>>>> primitives or when the two representing types are discrete.
>>>>>>>
>>>>>>> But this isn't the case here, a register object is already distinct in terms of
>>>>>>> its location and value.
>>>>>>
>>>>>> I see no reason why register values for different locations have to be distinct
>>>>>> in terms of value types.
>>>>
>>>> That's not what the register!() macro currently does, a register type always has
>>>> a unique location, or is an array register, etc. In any case a register type is
>>>> assoiciated with a location.
>>>>
>>>> If the proposal is to disconnect location and register type entirely, that would
>>>> be a change to the current design.
>>>
>>> It's not what the macro do today, but I don't want to ask Alex to change it
>>> further before landing the series. I do think it's a worthy follow-up to add the
>>> ability to decouple the location and type. It's not incompatible with current
>>> design anyway.
>>
>> I'm not sure there are any relevant use-cases for this. Do you have real
>> examples that would not be represented with array registers?
>
> Even for the cases where there's a PIO register, I think it's beneficial to just
> get a value without a type.
>
> I don't see why we want people to write
>
> self.io.read(UART_RX).value()
>
> vs
>
> self.io.read(UART_RX)
>
> or
>
> self.io.write(UART_TX::from(byte))
>
> vs
>
> self.io.write(UART_TX, byte)
>
> what benefit does additional type provide?
Well, for FIFO registers this is indeed better. However, my main concern was
this
bar.write(regs::MyReg, regs::MyReg::foo())
for the reasons explained above.
As for FIFO registers, another option could be to leverage the raw accessors and
make a location type dereferece to the offset, e.g.
bar.write8(regs::TX_FIFO, byte)
The same goes for the array case of course.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 21:38 ` Danilo Krummrich
@ 2026-03-04 21:42 ` Danilo Krummrich
2026-03-04 22:15 ` Gary Guo
1 sibling, 0 replies; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-04 21:42 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 4, 2026 at 10:38 PM CET, Danilo Krummrich wrote:
> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>> On Wed Mar 4, 2026 at 8:37 PM GMT, Danilo Krummrich wrote:
>>> On Wed Mar 4, 2026 at 8:48 PM CET, Gary Guo wrote:
>>>> On Wed Mar 4, 2026 at 7:38 PM GMT, Danilo Krummrich wrote:
>>>>> On Wed Mar 4, 2026 at 7:58 PM CET, Gary Guo wrote:
>>>>>> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>>>>>>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>>>>>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>>>>>>> So, to get a better idea of these two options I have converted this
>>>>>>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>>>>>>> difference between the two - it is particularly interesting to see how
>>>>>>>>> nova-core changes:
>>>>>>>>>
>>>>>>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>>>>>>
>>>>>>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>>>>>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>>>>>>> have the exact same write_with() method together with the single argument
>>>>>>>> write() method.
>>>>>>>>
>>>>>>>> The contention point for me with a two arguments write() method still remains
>>>>>>>> that the arguments are redundant.
>>>>>>>>
>>>>>>>> I.e. you first have the location in form of an object instance of a ZST (which
>>>>>>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>>>>>>> object that actually represents the entire register, describing both the
>>>>>>>> location *and* the value.
>>>>>>>>
>>>>>>>> So, let's say a driver creates a register object with a custom constructor
>>>>>>>>
>>>>>>>> let reset = regs::MyReg::reset();
>>>>>>>>
>>>>>>>> then the two argument approach would be
>>>>>>>>
>>>>>>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>>>>>>
>>>>>>>> whereas the single argument approach would just be
>>>>>>>>
>>>>>>>> (2) bar.write(regs::MyReg::reset());
>>>>>>>
>>>>>>> That's only for bit field registers that has unique types. I still believe types
>>>>>>> of registers should not be tightly coupled with name of registeres.
>>>>>>>
>>>>>>> Allowing a value of register to be directly used for `write` is also confusing
>>>>>>> if a value is not created immediately before written to.
>>>>>>>
>>>>>>>>
>>>>>>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>>>>>>> function that takes the bar as argument to hide this, i.e.
>>>>>>>>
>>>>>>>> regs::MyReg::reset(bar);
>>>>>>>>
>>>>>>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>>>>>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>>>>>>
>>>>>>>> This notation is only common because it is necessary when operating on
>>>>>>>> primitives or when the two representing types are discrete.
>>>>>>>>
>>>>>>>> But this isn't the case here, a register object is already distinct in terms of
>>>>>>>> its location and value.
>>>>>>>
>>>>>>> I see no reason why register values for different locations have to be distinct
>>>>>>> in terms of value types.
>>>>>
>>>>> That's not what the register!() macro currently does, a register type always has
>>>>> a unique location, or is an array register, etc. In any case a register type is
>>>>> assoiciated with a location.
>>>>>
>>>>> If the proposal is to disconnect location and register type entirely, that would
>>>>> be a change to the current design.
>>>>
>>>> It's not what the macro do today, but I don't want to ask Alex to change it
>>>> further before landing the series. I do think it's a worthy follow-up to add the
>>>> ability to decouple the location and type. It's not incompatible with current
>>>> design anyway.
>>>
>>> I'm not sure there are any relevant use-cases for this. Do you have real
>>> examples that would not be represented with array registers?
>>
>> Even for the cases where there's a PIO register, I think it's beneficial to just
>> get a value without a type.
>>
>> I don't see why we want people to write
>>
>> self.io.read(UART_RX).value()
>>
>> vs
>>
>> self.io.read(UART_RX)
>>
>> or
>>
>> self.io.write(UART_TX::from(byte))
>>
>> vs
>>
>> self.io.write(UART_TX, byte)
>>
>> what benefit does additional type provide?
>
> Well, for FIFO registers this is indeed better. However, my main concern was
> this
>
> bar.write(regs::MyReg, regs::MyReg::foo())
>
> for the reasons explained above.
>
> As for FIFO registers, another option could be to leverage the raw accessors and
> make a location type dereferece to the offset, e.g.
>
> bar.write8(regs::TX_FIFO, byte)
>
> The same goes for the array case of course.
Actually, this wouldn't prevent
bar.write32(regs::TX_FIFO, byte)
for instance, so it's not an option.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 21:38 ` Danilo Krummrich
2026-03-04 21:42 ` Danilo Krummrich
@ 2026-03-04 22:15 ` Gary Guo
2026-03-04 22:22 ` Danilo Krummrich
2026-03-06 5:37 ` Alexandre Courbot
1 sibling, 2 replies; 64+ messages in thread
From: Gary Guo @ 2026-03-04 22:15 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 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>> On Wed Mar 4, 2026 at 8:37 PM GMT, Danilo Krummrich wrote:
>>> On Wed Mar 4, 2026 at 8:48 PM CET, Gary Guo wrote:
>>>> On Wed Mar 4, 2026 at 7:38 PM GMT, Danilo Krummrich wrote:
>>>>> On Wed Mar 4, 2026 at 7:58 PM CET, Gary Guo wrote:
>>>>>> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>>>>>>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>>>>>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>>>>>>> So, to get a better idea of these two options I have converted this
>>>>>>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>>>>>>> difference between the two - it is particularly interesting to see how
>>>>>>>>> nova-core changes:
>>>>>>>>>
>>>>>>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>>>>>>
>>>>>>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>>>>>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>>>>>>> have the exact same write_with() method together with the single argument
>>>>>>>> write() method.
>>>>>>>>
>>>>>>>> The contention point for me with a two arguments write() method still remains
>>>>>>>> that the arguments are redundant.
>>>>>>>>
>>>>>>>> I.e. you first have the location in form of an object instance of a ZST (which
>>>>>>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>>>>>>> object that actually represents the entire register, describing both the
>>>>>>>> location *and* the value.
>>>>>>>>
>>>>>>>> So, let's say a driver creates a register object with a custom constructor
>>>>>>>>
>>>>>>>> let reset = regs::MyReg::reset();
>>>>>>>>
>>>>>>>> then the two argument approach would be
>>>>>>>>
>>>>>>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>>>>>>
>>>>>>>> whereas the single argument approach would just be
>>>>>>>>
>>>>>>>> (2) bar.write(regs::MyReg::reset());
>>>>>>>
>>>>>>> That's only for bit field registers that has unique types. I still believe types
>>>>>>> of registers should not be tightly coupled with name of registeres.
>>>>>>>
>>>>>>> Allowing a value of register to be directly used for `write` is also confusing
>>>>>>> if a value is not created immediately before written to.
>>>>>>>
>>>>>>>>
>>>>>>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>>>>>>> function that takes the bar as argument to hide this, i.e.
>>>>>>>>
>>>>>>>> regs::MyReg::reset(bar);
>>>>>>>>
>>>>>>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>>>>>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>>>>>>
>>>>>>>> This notation is only common because it is necessary when operating on
>>>>>>>> primitives or when the two representing types are discrete.
>>>>>>>>
>>>>>>>> But this isn't the case here, a register object is already distinct in terms of
>>>>>>>> its location and value.
>>>>>>>
>>>>>>> I see no reason why register values for different locations have to be distinct
>>>>>>> in terms of value types.
>>>>>
>>>>> That's not what the register!() macro currently does, a register type always has
>>>>> a unique location, or is an array register, etc. In any case a register type is
>>>>> assoiciated with a location.
>>>>>
>>>>> If the proposal is to disconnect location and register type entirely, that would
>>>>> be a change to the current design.
>>>>
>>>> It's not what the macro do today, but I don't want to ask Alex to change it
>>>> further before landing the series. I do think it's a worthy follow-up to add the
>>>> ability to decouple the location and type. It's not incompatible with current
>>>> design anyway.
>>>
>>> I'm not sure there are any relevant use-cases for this. Do you have real
>>> examples that would not be represented with array registers?
>>
>> Even for the cases where there's a PIO register, I think it's beneficial to just
>> get a value without a type.
>>
>> I don't see why we want people to write
>>
>> self.io.read(UART_RX).value()
>>
>> vs
>>
>> self.io.read(UART_RX)
>>
>> or
>>
>> self.io.write(UART_TX::from(byte))
>>
>> vs
>>
>> self.io.write(UART_TX, byte)
>>
>> what benefit does additional type provide?
>
> Well, for FIFO registers this is indeed better. However, my main concern was
> this
>
> bar.write(regs::MyReg, regs::MyReg::foo())
This specific case is indeed more cumbersome with the two argument approach,
although given Alex's nova diff I think the occurance shouldn't be that
frequent.
It's also not that the two argument approach would preclude us from having a
single argument option. In fact, with the two-argument design as the basis, we
can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
`Into<IoWrite>`:
/// Indicates that this type is always associated with a specific fixed I/O
/// location.
///
/// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
/// the register name explicitly `io.write(REG, value)`.
trait FixedIoLocation {
type IoLocType: IoLoc<Self>;
const IO_LOCATION: Self::IoLocType;
}
trait Io {
fn bikeshed_shorthand_name<T>(&self, value: T)
where T: FixedIoLocation +
Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
{
self.write(T::IO_LOCATION, value)
}
}
No need for a `IoWrite` type, everything is done via traits.
Anyhow I don't think we should close door for a more universal approach because
a specific use case is shorter to write.
Best,
Gary
>
> for the reasons explained above.
>
> As for FIFO registers, another option could be to leverage the raw accessors and
> make a location type dereferece to the offset, e.g.
>
> bar.write8(regs::TX_FIFO, byte)
>
> The same goes for the array case of course.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-02-24 14:21 ` [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types Alexandre Courbot
2026-02-27 18:02 ` Gary Guo
@ 2026-03-04 22:19 ` Gary Guo
2026-03-05 11:02 ` Alexandre Courbot
1 sibling, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-04 22:19 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 Tue Feb 24, 2026 at 2:21 PM GMT, Alexandre Courbot wrote:
> I/O accesses are defined by the following properties:
>
> - For reads, a start address, a width, and a type to interpret the read
> value as,
> - For writes, the same as above, and a value to write.
>
> Introduce the `IoLoc` trait, which allows implementing types to specify
> the address a type expects to be accessed at, as well as the width of
> the access, and the user-facing type used to perform the access.
>
> This allows read operations to be made generic with the `read` method
> over an `IoLoc` argument.
>
> Write operations need a value to write on top of the `IoLoc`: fulfill
> that purpose with the `IoWrite` type, which is the combination of an
> `IoLoc` and a value of the type it expects. This allows write operations
> to be made generic with the `write` method over a single `IoWrite`
> argument.
>
> The main purpose of these new entities is to allow register types to be
> written using these generic `read` and `write` methods of `Io`.
>
> Co-developed-by: Gary Guo <gary@garyguo.net>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
> rust/kernel/io.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 241 insertions(+)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index b150743ffa4f..fdd2549d8e13 100644
> --- a/rust/kernel/io.rs
> +++ b/rust/kernel/io.rs
> @@ -173,6 +173,158 @@ pub trait IoCapable<T> {
> unsafe fn io_write(&self, value: T, address: usize);
> }
>
> +/// Describes a given I/O location: its offset, width, and return type.
> +///
> +/// This trait is the key abstraction allowing [`Io::read`], [`Io::write`], and [`Io::update`]
> +/// 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 data is returned or provided.
> +///
> +/// `T` and `IoType` may differ: for instance, a typed register has `T` = the register type with
> +/// its bitfields, and `IoType` = its backing primitive (e.g. `u32`), with `Into` conversions
> +/// between them.
> +///
> +/// An `IoLoc` can be passed directly to [`Io::read`] or [`Io::try_read`] to obtain a value, or
> +/// turned into an [`IoWrite`] via [`IoLoc::set`] to be passed to [`Io::write`] or
> +/// [`Io::try_write`].
> +pub trait IoLoc<T>: Copy
> +where
> + T: Into<Self::IoType>,
> + Self::IoType: Into<T>,
> +{
> + /// Size (`u8`, `u16`, etc) of the I/O performed on the returned [`offset`](IoLoc::offset).
> + type IoType;
You should move the where bound on the trait type to here.
A where bound on the trait is something that needs to be proved when referencing
the trait, rather than something that can be assumed (which is the case for
bounds in assoc types), which is why you need the extra `Into` bounds on `Io`
functions.
If you use
type IoType: Into<T> + From<T>;
then you can remove the `Into` bound from all `Io` methods.
(You would need to update the impl side though, with something like
impl<T> IoLoc<T> for FixedRegisterLoc<T>
where
- T: FixedRegister + From<T::Storage> + Into<T::Storage>,
+ T: FixedRegister + From<T::Storage>,
+ T::Storage: From<T>,
{
type IoType = T::Storage;
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 22:15 ` Gary Guo
@ 2026-03-04 22:22 ` Danilo Krummrich
2026-03-06 5:37 ` Alexandre Courbot
1 sibling, 0 replies; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-04 22:22 UTC (permalink / raw)
To: Gary Guo, Alexandre Courbot
Cc: 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 4, 2026 at 11:15 PM CET, Gary Guo wrote:
> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>> bar.write(regs::MyReg, regs::MyReg::foo())
>
> This specific case is indeed more cumbersome with the two argument approach,
> although given Alex's nova diff I think the occurance shouldn't be that
> frequent.
>
> It's also not that the two argument approach would preclude us from having a
> single argument option. In fact, with the two-argument design as the basis, we
> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
> `Into<IoWrite>`:
>
> /// Indicates that this type is always associated with a specific fixed I/O
> /// location.
> ///
> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
> /// the register name explicitly `io.write(REG, value)`.
> trait FixedIoLocation {
> type IoLocType: IoLoc<Self>;
> const IO_LOCATION: Self::IoLocType;
> }
>
> trait Io {
> fn bikeshed_shorthand_name<T>(&self, value: T)
What about write()? :P
> where T: FixedIoLocation +
> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
> {
> self.write(T::IO_LOCATION, value)
> }
> }
Ok, please go ahead with this then.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 22:19 ` Gary Guo
@ 2026-03-05 11:02 ` Alexandre Courbot
0 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-05 11:02 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 Thu Mar 5, 2026 at 7:19 AM JST, Gary Guo wrote:
> On Tue Feb 24, 2026 at 2:21 PM GMT, Alexandre Courbot wrote:
>> I/O accesses are defined by the following properties:
>>
>> - For reads, a start address, a width, and a type to interpret the read
>> value as,
>> - For writes, the same as above, and a value to write.
>>
>> Introduce the `IoLoc` trait, which allows implementing types to specify
>> the address a type expects to be accessed at, as well as the width of
>> the access, and the user-facing type used to perform the access.
>>
>> This allows read operations to be made generic with the `read` method
>> over an `IoLoc` argument.
>>
>> Write operations need a value to write on top of the `IoLoc`: fulfill
>> that purpose with the `IoWrite` type, which is the combination of an
>> `IoLoc` and a value of the type it expects. This allows write operations
>> to be made generic with the `write` method over a single `IoWrite`
>> argument.
>>
>> The main purpose of these new entities is to allow register types to be
>> written using these generic `read` and `write` methods of `Io`.
>>
>> Co-developed-by: Gary Guo <gary@garyguo.net>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>> rust/kernel/io.rs | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 241 insertions(+)
>>
>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>> index b150743ffa4f..fdd2549d8e13 100644
>> --- a/rust/kernel/io.rs
>> +++ b/rust/kernel/io.rs
>> @@ -173,6 +173,158 @@ pub trait IoCapable<T> {
>> unsafe fn io_write(&self, value: T, address: usize);
>> }
>>
>> +/// Describes a given I/O location: its offset, width, and return type.
>> +///
>> +/// This trait is the key abstraction allowing [`Io::read`], [`Io::write`], and [`Io::update`]
>> +/// 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 data is returned or provided.
>> +///
>> +/// `T` and `IoType` may differ: for instance, a typed register has `T` = the register type with
>> +/// its bitfields, and `IoType` = its backing primitive (e.g. `u32`), with `Into` conversions
>> +/// between them.
>> +///
>> +/// An `IoLoc` can be passed directly to [`Io::read`] or [`Io::try_read`] to obtain a value, or
>> +/// turned into an [`IoWrite`] via [`IoLoc::set`] to be passed to [`Io::write`] or
>> +/// [`Io::try_write`].
>> +pub trait IoLoc<T>: Copy
>> +where
>> + T: Into<Self::IoType>,
>> + Self::IoType: Into<T>,
>> +{
>> + /// Size (`u8`, `u16`, etc) of the I/O performed on the returned [`offset`](IoLoc::offset).
>> + type IoType;
>
> You should move the where bound on the trait type to here.
>
> A where bound on the trait is something that needs to be proved when referencing
> the trait, rather than something that can be assumed (which is the case for
> bounds in assoc types), which is why you need the extra `Into` bounds on `Io`
> functions.
Thanks, this indeed looks better. I remember Alice mentioned that we
should prefer `Into` for trait bounds [1] so I don't think we can port
her comment here, but I agree these constraints are things that we should
be able to assume.
[1] https://lore.kernel.org/all/aZlwG3wK5w-IV18x@google.com/
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-04 22:15 ` Gary Guo
2026-03-04 22:22 ` Danilo Krummrich
@ 2026-03-06 5:37 ` Alexandre Courbot
2026-03-06 7:47 ` Alexandre Courbot
2026-03-06 10:42 ` Gary Guo
1 sibling, 2 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-06 5:37 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 Thu Mar 5, 2026 at 7:15 AM JST, Gary Guo wrote:
> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>>> On Wed Mar 4, 2026 at 8:37 PM GMT, Danilo Krummrich wrote:
>>>> On Wed Mar 4, 2026 at 8:48 PM CET, Gary Guo wrote:
>>>>> On Wed Mar 4, 2026 at 7:38 PM GMT, Danilo Krummrich wrote:
>>>>>> On Wed Mar 4, 2026 at 7:58 PM CET, Gary Guo wrote:
>>>>>>> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>>>>>>>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>>>>>>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>>>>>>>> So, to get a better idea of these two options I have converted this
>>>>>>>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>>>>>>>> difference between the two - it is particularly interesting to see how
>>>>>>>>>> nova-core changes:
>>>>>>>>>>
>>>>>>>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>>>>>>>
>>>>>>>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>>>>>>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>>>>>>>> have the exact same write_with() method together with the single argument
>>>>>>>>> write() method.
>>>>>>>>>
>>>>>>>>> The contention point for me with a two arguments write() method still remains
>>>>>>>>> that the arguments are redundant.
>>>>>>>>>
>>>>>>>>> I.e. you first have the location in form of an object instance of a ZST (which
>>>>>>>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>>>>>>>> object that actually represents the entire register, describing both the
>>>>>>>>> location *and* the value.
>>>>>>>>>
>>>>>>>>> So, let's say a driver creates a register object with a custom constructor
>>>>>>>>>
>>>>>>>>> let reset = regs::MyReg::reset();
>>>>>>>>>
>>>>>>>>> then the two argument approach would be
>>>>>>>>>
>>>>>>>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>>>>>>>
>>>>>>>>> whereas the single argument approach would just be
>>>>>>>>>
>>>>>>>>> (2) bar.write(regs::MyReg::reset());
>>>>>>>>
>>>>>>>> That's only for bit field registers that has unique types. I still believe types
>>>>>>>> of registers should not be tightly coupled with name of registeres.
>>>>>>>>
>>>>>>>> Allowing a value of register to be directly used for `write` is also confusing
>>>>>>>> if a value is not created immediately before written to.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>>>>>>>> function that takes the bar as argument to hide this, i.e.
>>>>>>>>>
>>>>>>>>> regs::MyReg::reset(bar);
>>>>>>>>>
>>>>>>>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>>>>>>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>>>>>>>
>>>>>>>>> This notation is only common because it is necessary when operating on
>>>>>>>>> primitives or when the two representing types are discrete.
>>>>>>>>>
>>>>>>>>> But this isn't the case here, a register object is already distinct in terms of
>>>>>>>>> its location and value.
>>>>>>>>
>>>>>>>> I see no reason why register values for different locations have to be distinct
>>>>>>>> in terms of value types.
>>>>>>
>>>>>> That's not what the register!() macro currently does, a register type always has
>>>>>> a unique location, or is an array register, etc. In any case a register type is
>>>>>> assoiciated with a location.
>>>>>>
>>>>>> If the proposal is to disconnect location and register type entirely, that would
>>>>>> be a change to the current design.
>>>>>
>>>>> It's not what the macro do today, but I don't want to ask Alex to change it
>>>>> further before landing the series. I do think it's a worthy follow-up to add the
>>>>> ability to decouple the location and type. It's not incompatible with current
>>>>> design anyway.
>>>>
>>>> I'm not sure there are any relevant use-cases for this. Do you have real
>>>> examples that would not be represented with array registers?
>>>
>>> Even for the cases where there's a PIO register, I think it's beneficial to just
>>> get a value without a type.
>>>
>>> I don't see why we want people to write
>>>
>>> self.io.read(UART_RX).value()
>>>
>>> vs
>>>
>>> self.io.read(UART_RX)
>>>
>>> or
>>>
>>> self.io.write(UART_TX::from(byte))
>>>
>>> vs
>>>
>>> self.io.write(UART_TX, byte)
>>>
>>> what benefit does additional type provide?
>>
>> Well, for FIFO registers this is indeed better. However, my main concern was
>> this
>>
>> bar.write(regs::MyReg, regs::MyReg::foo())
>
> This specific case is indeed more cumbersome with the two argument approach,
> although given Alex's nova diff I think the occurance shouldn't be that
> frequent.
>
> It's also not that the two argument approach would preclude us from having a
> single argument option. In fact, with the two-argument design as the basis, we
> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
> `Into<IoWrite>`:
>
> /// Indicates that this type is always associated with a specific fixed I/O
> /// location.
> ///
> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
> /// the register name explicitly `io.write(REG, value)`.
> trait FixedIoLocation {
> type IoLocType: IoLoc<Self>;
> const IO_LOCATION: Self::IoLocType;
> }
>
> trait Io {
> fn bikeshed_shorthand_name<T>(&self, value: T)
> where T: FixedIoLocation +
> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
> {
> self.write(T::IO_LOCATION, value)
> }
> }
>
> No need for a `IoWrite` type, everything is done via traits.
That's cool but will only work for fixed registers. If you work with, say, an
array of registers, cannot implement this trait on a value as the value
doesn't have an index assigned - meaning you would have to build a
location in addition of it.
So it only solves the problem partially.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 5:37 ` Alexandre Courbot
@ 2026-03-06 7:47 ` Alexandre Courbot
2026-03-06 10:42 ` Gary Guo
1 sibling, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-06 7:47 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 Fri Mar 6, 2026 at 2:37 PM JST, Alexandre Courbot wrote:
> On Thu Mar 5, 2026 at 7:15 AM JST, Gary Guo wrote:
>> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>>> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>>>> On Wed Mar 4, 2026 at 8:37 PM GMT, Danilo Krummrich wrote:
>>>>> On Wed Mar 4, 2026 at 8:48 PM CET, Gary Guo wrote:
>>>>>> On Wed Mar 4, 2026 at 7:38 PM GMT, Danilo Krummrich wrote:
>>>>>>> On Wed Mar 4, 2026 at 7:58 PM CET, Gary Guo wrote:
>>>>>>>> On Wed Mar 4, 2026 at 6:39 PM GMT, Gary Guo wrote:
>>>>>>>>> On Wed Mar 4, 2026 at 4:18 PM GMT, Danilo Krummrich wrote:
>>>>>>>>>> On Tue Mar 3, 2026 at 3:55 PM CET, Alexandre Courbot wrote:
>>>>>>>>>>> So, to get a better idea of these two options I have converted this
>>>>>>>>>>> patchset to use the 2-arguments `write_with` method. Here is the
>>>>>>>>>>> difference between the two - it is particularly interesting to see how
>>>>>>>>>>> nova-core changes:
>>>>>>>>>>>
>>>>>>>>>>> https://github.com/Gnurou/linux/compare/register_1arg..Gnurou:linux:register_2args
>>>>>>>>>>
>>>>>>>>>> This looks good to me, but the fact that this turns out nicely has nothing to do
>>>>>>>>>> with write() now taking two arguments. I.e. there is no reason why we couldn't
>>>>>>>>>> have the exact same write_with() method together with the single argument
>>>>>>>>>> write() method.
>>>>>>>>>>
>>>>>>>>>> The contention point for me with a two arguments write() method still remains
>>>>>>>>>> that the arguments are redundant.
>>>>>>>>>>
>>>>>>>>>> I.e. you first have the location in form of an object instance of a ZST (which
>>>>>>>>>> in the end is just a "trick" to pass in the type itself) and then we have the
>>>>>>>>>> object that actually represents the entire register, describing both the
>>>>>>>>>> location *and* the value.
>>>>>>>>>>
>>>>>>>>>> So, let's say a driver creates a register object with a custom constructor
>>>>>>>>>>
>>>>>>>>>> let reset = regs::MyReg::reset();
>>>>>>>>>>
>>>>>>>>>> then the two argument approach would be
>>>>>>>>>>
>>>>>>>>>> (1) bar.write(regs::MyReg, regs::MyReg::reset());
>>>>>>>>>>
>>>>>>>>>> whereas the single argument approach would just be
>>>>>>>>>>
>>>>>>>>>> (2) bar.write(regs::MyReg::reset());
>>>>>>>>>
>>>>>>>>> That's only for bit field registers that has unique types. I still believe types
>>>>>>>>> of registers should not be tightly coupled with name of registeres.
>>>>>>>>>
>>>>>>>>> Allowing a value of register to be directly used for `write` is also confusing
>>>>>>>>> if a value is not created immediately before written to.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> So, if I would have to write (1), I'd probably be tempted to implement a reset()
>>>>>>>>>> function that takes the bar as argument to hide this, i.e.
>>>>>>>>>>
>>>>>>>>>> regs::MyReg::reset(bar);
>>>>>>>>>>
>>>>>>>>>> I also can't agree with the argument that the notation of write(loc, val) - or
>>>>>>>>>> write(val, loc) as the C side does it - is common and we should stick to it.
>>>>>>>>>>
>>>>>>>>>> This notation is only common because it is necessary when operating on
>>>>>>>>>> primitives or when the two representing types are discrete.
>>>>>>>>>>
>>>>>>>>>> But this isn't the case here, a register object is already distinct in terms of
>>>>>>>>>> its location and value.
>>>>>>>>>
>>>>>>>>> I see no reason why register values for different locations have to be distinct
>>>>>>>>> in terms of value types.
>>>>>>>
>>>>>>> That's not what the register!() macro currently does, a register type always has
>>>>>>> a unique location, or is an array register, etc. In any case a register type is
>>>>>>> assoiciated with a location.
>>>>>>>
>>>>>>> If the proposal is to disconnect location and register type entirely, that would
>>>>>>> be a change to the current design.
>>>>>>
>>>>>> It's not what the macro do today, but I don't want to ask Alex to change it
>>>>>> further before landing the series. I do think it's a worthy follow-up to add the
>>>>>> ability to decouple the location and type. It's not incompatible with current
>>>>>> design anyway.
>>>>>
>>>>> I'm not sure there are any relevant use-cases for this. Do you have real
>>>>> examples that would not be represented with array registers?
>>>>
>>>> Even for the cases where there's a PIO register, I think it's beneficial to just
>>>> get a value without a type.
>>>>
>>>> I don't see why we want people to write
>>>>
>>>> self.io.read(UART_RX).value()
>>>>
>>>> vs
>>>>
>>>> self.io.read(UART_RX)
>>>>
>>>> or
>>>>
>>>> self.io.write(UART_TX::from(byte))
>>>>
>>>> vs
>>>>
>>>> self.io.write(UART_TX, byte)
>>>>
>>>> what benefit does additional type provide?
>>>
>>> Well, for FIFO registers this is indeed better. However, my main concern was
>>> this
>>>
>>> bar.write(regs::MyReg, regs::MyReg::foo())
>>
>> This specific case is indeed more cumbersome with the two argument approach,
>> although given Alex's nova diff I think the occurance shouldn't be that
>> frequent.
>>
>> It's also not that the two argument approach would preclude us from having a
>> single argument option. In fact, with the two-argument design as the basis, we
>> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
>> `Into<IoWrite>`:
>>
>> /// Indicates that this type is always associated with a specific fixed I/O
>> /// location.
>> ///
>> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
>> /// the register name explicitly `io.write(REG, value)`.
>> trait FixedIoLocation {
>> type IoLocType: IoLoc<Self>;
>> const IO_LOCATION: Self::IoLocType;
>> }
>>
>> trait Io {
>> fn bikeshed_shorthand_name<T>(&self, value: T)
>> where T: FixedIoLocation +
>> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
>> {
>> self.write(T::IO_LOCATION, value)
>> }
>> }
>>
>> No need for a `IoWrite` type, everything is done via traits.
>
> That's cool but will only work for fixed registers. If you work with, say, an
> array of registers, cannot implement this trait on a value as the value
> doesn't have an index assigned - meaning you would have to build a
> location in addition of it.
>
> So it only solves the problem partially.
... but the remainder of the solution can be implemented to look
something like this:
// Write a fixed register.
bar.write(regs::MyReg::foo());
// Write into a registers array at index 10.
bar.write(Reg::at(10, regs::MyRegArray::foo()));
// Write a relative register at the `Gsp` instance.
bar.write(Reg::of::<Gsp>(regs::MyRelativeReg::foo()));
Amongst other benefits (no closures and the complications they bring),
it also seems to be the shortest syntax so far.
This could coexist with a 2-arguments write method, or completely
supplant it.
Before doing and sharing a full implementation, I'd like to check from
this syntax that people don't hate it. WDYT?
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 5:37 ` Alexandre Courbot
2026-03-06 7:47 ` Alexandre Courbot
@ 2026-03-06 10:42 ` Gary Guo
2026-03-06 11:10 ` Alexandre Courbot
1 sibling, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-06 10:42 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 Fri Mar 6, 2026 at 5:37 AM GMT, Alexandre Courbot wrote:
> On Thu Mar 5, 2026 at 7:15 AM JST, Gary Guo wrote:
>> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>>> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>>>> Even for the cases where there's a PIO register, I think it's beneficial to just
>>>> get a value without a type.
>>>>
>>>> I don't see why we want people to write
>>>>
>>>> self.io.read(UART_RX).value()
>>>>
>>>> vs
>>>>
>>>> self.io.read(UART_RX)
>>>>
>>>> or
>>>>
>>>> self.io.write(UART_TX::from(byte))
>>>>
>>>> vs
>>>>
>>>> self.io.write(UART_TX, byte)
>>>>
>>>> what benefit does additional type provide?
>>>
>>> Well, for FIFO registers this is indeed better. However, my main concern was
>>> this
>>>
>>> bar.write(regs::MyReg, regs::MyReg::foo())
>>
>> This specific case is indeed more cumbersome with the two argument approach,
>> although given Alex's nova diff I think the occurance shouldn't be that
>> frequent.
>>
>> It's also not that the two argument approach would preclude us from having a
>> single argument option. In fact, with the two-argument design as the basis, we
>> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
>> `Into<IoWrite>`:
>>
>> /// Indicates that this type is always associated with a specific fixed I/O
>> /// location.
>> ///
>> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
>> /// the register name explicitly `io.write(REG, value)`.
>> trait FixedIoLocation {
>> type IoLocType: IoLoc<Self>;
>> const IO_LOCATION: Self::IoLocType;
>> }
>>
>> trait Io {
>> fn bikeshed_shorthand_name<T>(&self, value: T)
>> where T: FixedIoLocation +
>> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
>> {
>> self.write(T::IO_LOCATION, value)
>> }
>> }
>>
>> No need for a `IoWrite` type, everything is done via traits.
>
> That's cool but will only work for fixed registers. If you work with, say, an
> array of registers, cannot implement this trait on a value as the value
> doesn't have an index assigned - meaning you would have to build a
> location in addition of it.
For array registers I think it makes more sense to use the two-argument version,
no?
The example here is to demonstrate that we can add a shorthand version for the
fixed register version that can write a value to register without mentioning its
name (as a supplemental helper), and the basic write method is the two-argument
one.
For cases where the type doesn't guarantee a fixed location like FIFO register
or an array register, mentioning the name twice is fine.
[
For array case, you *could* also do
impl IoLoc<RegisterName> for usize {
fn offset(self) -> usize {
self * stride + fixed_base
}
}
and now you can do `self.write(index, reg_value)`, although I think this
might confuse some people.
For the fixed case you could do
impl IoLoc<RegisterName> for () {
fn offset(self) -> usize {
fixed_loc
}
}
which means you can do `self.write((), value)`. I think this looks a bit
uglier compared to a dedicated method, but TBH it isn't too terrible.
]
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 10:42 ` Gary Guo
@ 2026-03-06 11:10 ` Alexandre Courbot
2026-03-06 11:35 ` Gary Guo
0 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-06 11:10 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 Fri Mar 6, 2026 at 7:42 PM JST, Gary Guo wrote:
> On Fri Mar 6, 2026 at 5:37 AM GMT, Alexandre Courbot wrote:
>> On Thu Mar 5, 2026 at 7:15 AM JST, Gary Guo wrote:
>>> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>>>> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>>>>> Even for the cases where there's a PIO register, I think it's beneficial to just
>>>>> get a value without a type.
>>>>>
>>>>> I don't see why we want people to write
>>>>>
>>>>> self.io.read(UART_RX).value()
>>>>>
>>>>> vs
>>>>>
>>>>> self.io.read(UART_RX)
>>>>>
>>>>> or
>>>>>
>>>>> self.io.write(UART_TX::from(byte))
>>>>>
>>>>> vs
>>>>>
>>>>> self.io.write(UART_TX, byte)
>>>>>
>>>>> what benefit does additional type provide?
>>>>
>>>> Well, for FIFO registers this is indeed better. However, my main concern was
>>>> this
>>>>
>>>> bar.write(regs::MyReg, regs::MyReg::foo())
>>>
>>> This specific case is indeed more cumbersome with the two argument approach,
>>> although given Alex's nova diff I think the occurance shouldn't be that
>>> frequent.
>>>
>>> It's also not that the two argument approach would preclude us from having a
>>> single argument option. In fact, with the two-argument design as the basis, we
>>> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
>>> `Into<IoWrite>`:
>>>
>>> /// Indicates that this type is always associated with a specific fixed I/O
>>> /// location.
>>> ///
>>> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
>>> /// the register name explicitly `io.write(REG, value)`.
>>> trait FixedIoLocation {
>>> type IoLocType: IoLoc<Self>;
>>> const IO_LOCATION: Self::IoLocType;
>>> }
>>>
>>> trait Io {
>>> fn bikeshed_shorthand_name<T>(&self, value: T)
>>> where T: FixedIoLocation +
>>> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
>>> {
>>> self.write(T::IO_LOCATION, value)
>>> }
>>> }
>>>
>>> No need for a `IoWrite` type, everything is done via traits.
>>
>> That's cool but will only work for fixed registers. If you work with, say, an
>> array of registers, cannot implement this trait on a value as the value
>> doesn't have an index assigned - meaning you would have to build a
>> location in addition of it.
>
> For array registers I think it makes more sense to use the two-argument version,
> no?
>
> The example here is to demonstrate that we can add a shorthand version for the
> fixed register version that can write a value to register without mentioning its
> name (as a supplemental helper), and the basic write method is the two-argument
> one.
>
> For cases where the type doesn't guarantee a fixed location like FIFO register
> or an array register, mentioning the name twice is fine.
It's still tedious, and a step back compared to the one-argument version
imho.
>
> [
>
> For array case, you *could* also do
>
> impl IoLoc<RegisterName> for usize {
> fn offset(self) -> usize {
> self * stride + fixed_base
> }
> }
>
>
> and now you can do `self.write(index, reg_value)`, although I think this
> might confuse some people.
Yes, in this case the semantics of write's first argument would be
dependent on the second argument... I think that's a potential footgun.
>
> For the fixed case you could do
>
> impl IoLoc<RegisterName> for () {
> fn offset(self) -> usize {
> fixed_loc
> }
> }
>
> which means you can do `self.write((), value)`. I think this looks a bit
> uglier compared to a dedicated method, but TBH it isn't too terrible.
There is another potential solution I have played with:
https://lore.kernel.org/all/DGVJ7VQX3TD5.2UYW004QJPI6N@nvidia.com/
It would let us keep the 2-arguments as a base, while letting us take
advantage of the one-argument syntax you proposed in [1] and extending
it to support other register types.
[1] https://lore.kernel.org/all/DGU9AZ43QK6Y.115RDSK0M9JY5@garyguo.net/
I think it turns out pretty nice and the supporting code is also not too
ugly. I'll try to send a diff a bit later so you can see how it behaves
in practice.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 11:10 ` Alexandre Courbot
@ 2026-03-06 11:35 ` Gary Guo
2026-03-06 12:50 ` Alexandre Courbot
0 siblings, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-06 11:35 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 Fri Mar 6, 2026 at 11:10 AM GMT, Alexandre Courbot wrote:
> On Fri Mar 6, 2026 at 7:42 PM JST, Gary Guo wrote:
>> On Fri Mar 6, 2026 at 5:37 AM GMT, Alexandre Courbot wrote:
>>> On Thu Mar 5, 2026 at 7:15 AM JST, Gary Guo wrote:
>>>> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>>>>> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>>>>>> Even for the cases where there's a PIO register, I think it's beneficial to just
>>>>>> get a value without a type.
>>>>>>
>>>>>> I don't see why we want people to write
>>>>>>
>>>>>> self.io.read(UART_RX).value()
>>>>>>
>>>>>> vs
>>>>>>
>>>>>> self.io.read(UART_RX)
>>>>>>
>>>>>> or
>>>>>>
>>>>>> self.io.write(UART_TX::from(byte))
>>>>>>
>>>>>> vs
>>>>>>
>>>>>> self.io.write(UART_TX, byte)
>>>>>>
>>>>>> what benefit does additional type provide?
>>>>>
>>>>> Well, for FIFO registers this is indeed better. However, my main concern was
>>>>> this
>>>>>
>>>>> bar.write(regs::MyReg, regs::MyReg::foo())
>>>>
>>>> This specific case is indeed more cumbersome with the two argument approach,
>>>> although given Alex's nova diff I think the occurance shouldn't be that
>>>> frequent.
>>>>
>>>> It's also not that the two argument approach would preclude us from having a
>>>> single argument option. In fact, with the two-argument design as the basis, we
>>>> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
>>>> `Into<IoWrite>`:
>>>>
>>>> /// Indicates that this type is always associated with a specific fixed I/O
>>>> /// location.
>>>> ///
>>>> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
>>>> /// the register name explicitly `io.write(REG, value)`.
>>>> trait FixedIoLocation {
>>>> type IoLocType: IoLoc<Self>;
>>>> const IO_LOCATION: Self::IoLocType;
>>>> }
>>>>
>>>> trait Io {
>>>> fn bikeshed_shorthand_name<T>(&self, value: T)
>>>> where T: FixedIoLocation +
>>>> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
>>>> {
>>>> self.write(T::IO_LOCATION, value)
>>>> }
>>>> }
>>>>
>>>> No need for a `IoWrite` type, everything is done via traits.
>>>
>>> That's cool but will only work for fixed registers. If you work with, say, an
>>> array of registers, cannot implement this trait on a value as the value
>>> doesn't have an index assigned - meaning you would have to build a
>>> location in addition of it.
>>
>> For array registers I think it makes more sense to use the two-argument version,
>> no?
>>
>> The example here is to demonstrate that we can add a shorthand version for the
>> fixed register version that can write a value to register without mentioning its
>> name (as a supplemental helper), and the basic write method is the two-argument
>> one.
>>
>> For cases where the type doesn't guarantee a fixed location like FIFO register
>> or an array register, mentioning the name twice is fine.
>
> It's still tedious, and a step back compared to the one-argument version
> imho.
>
>>
>> [
>>
>> For array case, you *could* also do
>>
>> impl IoLoc<RegisterName> for usize {
>> fn offset(self) -> usize {
>> self * stride + fixed_base
>> }
>> }
>>
>>
>> and now you can do `self.write(index, reg_value)`, although I think this
>> might confuse some people.
>
> Yes, in this case the semantics of write's first argument would be
> dependent on the second argument... I think that's a potential footgun.
I mean, `bar.write(Reg::at(10, regs::MyRegArray::foo()))` in your example is
also kind of "first argument depends on the second argument" situation, just
with a bit more boilerplate.
If you want to make things more explicit you could also have
`bar.write(at_array(10), ...)` or something similar.
For the array case I really think trying to shove everything into a single
argument is a footgun. The type of value in this case *doesn't* tell us the
location, and the location needs to be explicit.
Best,
Gary
>
>>
>> For the fixed case you could do
>>
>> impl IoLoc<RegisterName> for () {
>> fn offset(self) -> usize {
>> fixed_loc
>> }
>> }
>>
>> which means you can do `self.write((), value)`. I think this looks a bit
>> uglier compared to a dedicated method, but TBH it isn't too terrible.
>
> There is another potential solution I have played with:
>
> https://lore.kernel.org/all/DGVJ7VQX3TD5.2UYW004QJPI6N@nvidia.com/
>
> It would let us keep the 2-arguments as a base, while letting us take
> advantage of the one-argument syntax you proposed in [1] and extending
> it to support other register types.
>
> [1] https://lore.kernel.org/all/DGU9AZ43QK6Y.115RDSK0M9JY5@garyguo.net/
>
> I think it turns out pretty nice and the supporting code is also not too
> ugly. I'll try to send a diff a bit later so you can see how it behaves
> in practice.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 11:35 ` Gary Guo
@ 2026-03-06 12:50 ` Alexandre Courbot
2026-03-06 13:20 ` Gary Guo
0 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-06 12:50 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 Fri Mar 6, 2026 at 8:35 PM JST, Gary Guo wrote:
> On Fri Mar 6, 2026 at 11:10 AM GMT, Alexandre Courbot wrote:
>> On Fri Mar 6, 2026 at 7:42 PM JST, Gary Guo wrote:
>>> On Fri Mar 6, 2026 at 5:37 AM GMT, Alexandre Courbot wrote:
>>>> On Thu Mar 5, 2026 at 7:15 AM JST, Gary Guo wrote:
>>>>> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>>>>>> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>>>>>>> Even for the cases where there's a PIO register, I think it's beneficial to just
>>>>>>> get a value without a type.
>>>>>>>
>>>>>>> I don't see why we want people to write
>>>>>>>
>>>>>>> self.io.read(UART_RX).value()
>>>>>>>
>>>>>>> vs
>>>>>>>
>>>>>>> self.io.read(UART_RX)
>>>>>>>
>>>>>>> or
>>>>>>>
>>>>>>> self.io.write(UART_TX::from(byte))
>>>>>>>
>>>>>>> vs
>>>>>>>
>>>>>>> self.io.write(UART_TX, byte)
>>>>>>>
>>>>>>> what benefit does additional type provide?
>>>>>>
>>>>>> Well, for FIFO registers this is indeed better. However, my main concern was
>>>>>> this
>>>>>>
>>>>>> bar.write(regs::MyReg, regs::MyReg::foo())
>>>>>
>>>>> This specific case is indeed more cumbersome with the two argument approach,
>>>>> although given Alex's nova diff I think the occurance shouldn't be that
>>>>> frequent.
>>>>>
>>>>> It's also not that the two argument approach would preclude us from having a
>>>>> single argument option. In fact, with the two-argument design as the basis, we
>>>>> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
>>>>> `Into<IoWrite>`:
>>>>>
>>>>> /// Indicates that this type is always associated with a specific fixed I/O
>>>>> /// location.
>>>>> ///
>>>>> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
>>>>> /// the register name explicitly `io.write(REG, value)`.
>>>>> trait FixedIoLocation {
>>>>> type IoLocType: IoLoc<Self>;
>>>>> const IO_LOCATION: Self::IoLocType;
>>>>> }
>>>>>
>>>>> trait Io {
>>>>> fn bikeshed_shorthand_name<T>(&self, value: T)
>>>>> where T: FixedIoLocation +
>>>>> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
>>>>> {
>>>>> self.write(T::IO_LOCATION, value)
>>>>> }
>>>>> }
>>>>>
>>>>> No need for a `IoWrite` type, everything is done via traits.
>>>>
>>>> That's cool but will only work for fixed registers. If you work with, say, an
>>>> array of registers, cannot implement this trait on a value as the value
>>>> doesn't have an index assigned - meaning you would have to build a
>>>> location in addition of it.
>>>
>>> For array registers I think it makes more sense to use the two-argument version,
>>> no?
>>>
>>> The example here is to demonstrate that we can add a shorthand version for the
>>> fixed register version that can write a value to register without mentioning its
>>> name (as a supplemental helper), and the basic write method is the two-argument
>>> one.
>>>
>>> For cases where the type doesn't guarantee a fixed location like FIFO register
>>> or an array register, mentioning the name twice is fine.
>>
>> It's still tedious, and a step back compared to the one-argument version
>> imho.
>>
>>>
>>> [
>>>
>>> For array case, you *could* also do
>>>
>>> impl IoLoc<RegisterName> for usize {
>>> fn offset(self) -> usize {
>>> self * stride + fixed_base
>>> }
>>> }
>>>
>>>
>>> and now you can do `self.write(index, reg_value)`, although I think this
>>> might confuse some people.
>>
>> Yes, in this case the semantics of write's first argument would be
>> dependent on the second argument... I think that's a potential footgun.
>
> I mean, `bar.write(Reg::at(10, regs::MyRegArray::foo()))` in your example is
> also kind of "first argument depends on the second argument" situation, just
> with a bit more boilerplate.
Not really, `at` is enough to know that you are accessing an array.
Whereas `write(index, reg_value)` doesn't give us any indication of what
type of indirection (if any) we have.
>
> If you want to make things more explicit you could also have
> `bar.write(at_array(10), ...)` or something similar.
Is it possible to generate an `IoLoc<T>` without having `T` mentioned
anywhere in the call to `at_array`?
>
> For the array case I really think trying to shove everything into a single
> argument is a footgun. The type of value in this case *doesn't* tell us the
> location, and the location needs to be explicit.
bar.write(Reg::at(10, regs::MyRegArray::foo()))
"write the constructed value at the 10th position of the `MyRegArray`
register array"
What is missing here?
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 12:50 ` Alexandre Courbot
@ 2026-03-06 13:20 ` Gary Guo
2026-03-06 14:32 ` Alexandre Courbot
0 siblings, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-06 13:20 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 Fri Mar 6, 2026 at 12:50 PM GMT, Alexandre Courbot wrote:
> On Fri Mar 6, 2026 at 8:35 PM JST, Gary Guo wrote:
>> On Fri Mar 6, 2026 at 11:10 AM GMT, Alexandre Courbot wrote:
>>> On Fri Mar 6, 2026 at 7:42 PM JST, Gary Guo wrote:
>>>> On Fri Mar 6, 2026 at 5:37 AM GMT, Alexandre Courbot wrote:
>>>>> On Thu Mar 5, 2026 at 7:15 AM JST, Gary Guo wrote:
>>>>>> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>>>>>>> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>>>>>>>> Even for the cases where there's a PIO register, I think it's beneficial to just
>>>>>>>> get a value without a type.
>>>>>>>>
>>>>>>>> I don't see why we want people to write
>>>>>>>>
>>>>>>>> self.io.read(UART_RX).value()
>>>>>>>>
>>>>>>>> vs
>>>>>>>>
>>>>>>>> self.io.read(UART_RX)
>>>>>>>>
>>>>>>>> or
>>>>>>>>
>>>>>>>> self.io.write(UART_TX::from(byte))
>>>>>>>>
>>>>>>>> vs
>>>>>>>>
>>>>>>>> self.io.write(UART_TX, byte)
>>>>>>>>
>>>>>>>> what benefit does additional type provide?
>>>>>>>
>>>>>>> Well, for FIFO registers this is indeed better. However, my main concern was
>>>>>>> this
>>>>>>>
>>>>>>> bar.write(regs::MyReg, regs::MyReg::foo())
>>>>>>
>>>>>> This specific case is indeed more cumbersome with the two argument approach,
>>>>>> although given Alex's nova diff I think the occurance shouldn't be that
>>>>>> frequent.
>>>>>>
>>>>>> It's also not that the two argument approach would preclude us from having a
>>>>>> single argument option. In fact, with the two-argument design as the basis, we
>>>>>> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
>>>>>> `Into<IoWrite>`:
>>>>>>
>>>>>> /// Indicates that this type is always associated with a specific fixed I/O
>>>>>> /// location.
>>>>>> ///
>>>>>> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
>>>>>> /// the register name explicitly `io.write(REG, value)`.
>>>>>> trait FixedIoLocation {
>>>>>> type IoLocType: IoLoc<Self>;
>>>>>> const IO_LOCATION: Self::IoLocType;
>>>>>> }
>>>>>>
>>>>>> trait Io {
>>>>>> fn bikeshed_shorthand_name<T>(&self, value: T)
>>>>>> where T: FixedIoLocation +
>>>>>> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
>>>>>> {
>>>>>> self.write(T::IO_LOCATION, value)
>>>>>> }
>>>>>> }
>>>>>>
>>>>>> No need for a `IoWrite` type, everything is done via traits.
>>>>>
>>>>> That's cool but will only work for fixed registers. If you work with, say, an
>>>>> array of registers, cannot implement this trait on a value as the value
>>>>> doesn't have an index assigned - meaning you would have to build a
>>>>> location in addition of it.
>>>>
>>>> For array registers I think it makes more sense to use the two-argument version,
>>>> no?
>>>>
>>>> The example here is to demonstrate that we can add a shorthand version for the
>>>> fixed register version that can write a value to register without mentioning its
>>>> name (as a supplemental helper), and the basic write method is the two-argument
>>>> one.
>>>>
>>>> For cases where the type doesn't guarantee a fixed location like FIFO register
>>>> or an array register, mentioning the name twice is fine.
>>>
>>> It's still tedious, and a step back compared to the one-argument version
>>> imho.
>>>
>>>>
>>>> [
>>>>
>>>> For array case, you *could* also do
>>>>
>>>> impl IoLoc<RegisterName> for usize {
>>>> fn offset(self) -> usize {
>>>> self * stride + fixed_base
>>>> }
>>>> }
>>>>
>>>>
>>>> and now you can do `self.write(index, reg_value)`, although I think this
>>>> might confuse some people.
>>>
>>> Yes, in this case the semantics of write's first argument would be
>>> dependent on the second argument... I think that's a potential footgun.
>>
>> I mean, `bar.write(Reg::at(10, regs::MyRegArray::foo()))` in your example is
>> also kind of "first argument depends on the second argument" situation, just
>> with a bit more boilerplate.
>
> Not really, `at` is enough to know that you are accessing an array.
>
> Whereas `write(index, reg_value)` doesn't give us any indication of what
> type of indirection (if any) we have.
I mean not sure `at` gives me that impression at all. It would just let me know
that I am accessing it at a different location. If you omit the `MyRegArray`
part then there's no real indication that this is an array to me.
If `at` is only for array, how would you represent the case where the same type
is being used in multiple registers?
>
>>
>> If you want to make things more explicit you could also have
>> `bar.write(at_array(10), ...)` or something similar.
>
> Is it possible to generate an `IoLoc<T>` without having `T` mentioned
> anywhere in the call to `at_array`?
Exactly same as the `impl IoLoc<REG> for usize`:
struct AtArray(usize);
impl IoLoc<REG> for AtArray {
...
}
>
>>
>> For the array case I really think trying to shove everything into a single
>> argument is a footgun. The type of value in this case *doesn't* tell us the
>> location, and the location needs to be explicit.
>
> bar.write(Reg::at(10, regs::MyRegArray::foo()))
>
> "write the constructed value at the 10th position of the `MyRegArray`
> register array"
>
> What is missing here?
This is completely un-natural if I try to read it with fresh mind (try to forget
about implementation details for a second).
`MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
single value and not an array. "at" here is saying that the register is at a
specific location and doesn't really indicate the array nature.
This is why I insist that I would prefer an explicit location
bar.write(REG_ARRAY.at(10), Reg::foo())
would have no ambiguity whatsoever about user's intent.
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 13:20 ` Gary Guo
@ 2026-03-06 14:32 ` Alexandre Courbot
2026-03-06 14:52 ` Alexandre Courbot
` (2 more replies)
0 siblings, 3 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-06 14:32 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 Fri Mar 6, 2026 at 10:20 PM JST, Gary Guo wrote:
> On Fri Mar 6, 2026 at 12:50 PM GMT, Alexandre Courbot wrote:
>> On Fri Mar 6, 2026 at 8:35 PM JST, Gary Guo wrote:
>>> On Fri Mar 6, 2026 at 11:10 AM GMT, Alexandre Courbot wrote:
>>>> On Fri Mar 6, 2026 at 7:42 PM JST, Gary Guo wrote:
>>>>> On Fri Mar 6, 2026 at 5:37 AM GMT, Alexandre Courbot wrote:
>>>>>> On Thu Mar 5, 2026 at 7:15 AM JST, Gary Guo wrote:
>>>>>>> On Wed Mar 4, 2026 at 9:38 PM GMT, Danilo Krummrich wrote:
>>>>>>>> On Wed Mar 4, 2026 at 10:13 PM CET, Gary Guo wrote:
>>>>>>>>> Even for the cases where there's a PIO register, I think it's beneficial to just
>>>>>>>>> get a value without a type.
>>>>>>>>>
>>>>>>>>> I don't see why we want people to write
>>>>>>>>>
>>>>>>>>> self.io.read(UART_RX).value()
>>>>>>>>>
>>>>>>>>> vs
>>>>>>>>>
>>>>>>>>> self.io.read(UART_RX)
>>>>>>>>>
>>>>>>>>> or
>>>>>>>>>
>>>>>>>>> self.io.write(UART_TX::from(byte))
>>>>>>>>>
>>>>>>>>> vs
>>>>>>>>>
>>>>>>>>> self.io.write(UART_TX, byte)
>>>>>>>>>
>>>>>>>>> what benefit does additional type provide?
>>>>>>>>
>>>>>>>> Well, for FIFO registers this is indeed better. However, my main concern was
>>>>>>>> this
>>>>>>>>
>>>>>>>> bar.write(regs::MyReg, regs::MyReg::foo())
>>>>>>>
>>>>>>> This specific case is indeed more cumbersome with the two argument approach,
>>>>>>> although given Alex's nova diff I think the occurance shouldn't be that
>>>>>>> frequent.
>>>>>>>
>>>>>>> It's also not that the two argument approach would preclude us from having a
>>>>>>> single argument option. In fact, with the two-argument design as the basis, we
>>>>>>> can implement such a helper function cleaner than Alex's PATCH 10/10 (which uses
>>>>>>> `Into<IoWrite>`:
>>>>>>>
>>>>>>> /// Indicates that this type is always associated with a specific fixed I/O
>>>>>>> /// location.
>>>>>>> ///
>>>>>>> /// This allows use of `io.bikeshed_shorthand_name(value)` instead of specifying
>>>>>>> /// the register name explicitly `io.write(REG, value)`.
>>>>>>> trait FixedIoLocation {
>>>>>>> type IoLocType: IoLoc<Self>;
>>>>>>> const IO_LOCATION: Self::IoLocType;
>>>>>>> }
>>>>>>>
>>>>>>> trait Io {
>>>>>>> fn bikeshed_shorthand_name<T>(&self, value: T)
>>>>>>> where T: FixedIoLocation +
>>>>>>> Self: IoCapable<<T::IoLocType as IoLoc<T>>::IoType>,
>>>>>>> {
>>>>>>> self.write(T::IO_LOCATION, value)
>>>>>>> }
>>>>>>> }
>>>>>>>
>>>>>>> No need for a `IoWrite` type, everything is done via traits.
>>>>>>
>>>>>> That's cool but will only work for fixed registers. If you work with, say, an
>>>>>> array of registers, cannot implement this trait on a value as the value
>>>>>> doesn't have an index assigned - meaning you would have to build a
>>>>>> location in addition of it.
>>>>>
>>>>> For array registers I think it makes more sense to use the two-argument version,
>>>>> no?
>>>>>
>>>>> The example here is to demonstrate that we can add a shorthand version for the
>>>>> fixed register version that can write a value to register without mentioning its
>>>>> name (as a supplemental helper), and the basic write method is the two-argument
>>>>> one.
>>>>>
>>>>> For cases where the type doesn't guarantee a fixed location like FIFO register
>>>>> or an array register, mentioning the name twice is fine.
>>>>
>>>> It's still tedious, and a step back compared to the one-argument version
>>>> imho.
>>>>
>>>>>
>>>>> [
>>>>>
>>>>> For array case, you *could* also do
>>>>>
>>>>> impl IoLoc<RegisterName> for usize {
>>>>> fn offset(self) -> usize {
>>>>> self * stride + fixed_base
>>>>> }
>>>>> }
>>>>>
>>>>>
>>>>> and now you can do `self.write(index, reg_value)`, although I think this
>>>>> might confuse some people.
>>>>
>>>> Yes, in this case the semantics of write's first argument would be
>>>> dependent on the second argument... I think that's a potential footgun.
>>>
>>> I mean, `bar.write(Reg::at(10, regs::MyRegArray::foo()))` in your example is
>>> also kind of "first argument depends on the second argument" situation, just
>>> with a bit more boilerplate.
>>
>> Not really, `at` is enough to know that you are accessing an array.
>>
>> Whereas `write(index, reg_value)` doesn't give us any indication of what
>> type of indirection (if any) we have.
>
> I mean not sure `at` gives me that impression at all. It would just let me know
> that I am accessing it at a different location. If you omit the `MyRegArray`
> part then there's no real indication that this is an array to me.
`at` is a function name, we can change it - I picked it because it is
short and reasonably descriptive. The point being: we have a unique
function that indicates unambigously that we are using a location for
an array of registers.
You seem to reject this design because the syntax isn't obvious and
natural to you at first read. We are trying to build something new, so
of course if will look a bit alien at the first encounter. That's why we
have documentation, and I think it is not very difficult to wrap your
head around it after seeing a few examples.
>
> If `at` is only for array, how would you represent the case where the same type
> is being used in multiple registers?
That's not something that is supported by the register macro currently
(probably not a big change, but not something I will do in this series).
But to try and answer your question, such register types would not have
an `IoLoc` implementation of their own and would need to have their
location constructed explicitly. In accordance, the construction of
their value would not bear any location information; thus there would be
no redundancy.
We do have a case that is pretty close to this with relative registers.
These are accessed like this (real examples):
bar.write(Reg::of::<Gsp>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
and
bar.write(Reg::of::<Sec2>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
But for register types that can be used at several arbitrary locations,
I think this would be even simpler. The different locations would just
need to implement `IoLoc<T>`, where `T` is the shared register type.
Then, considering that the two-arguments version is called `write_at`,
you can simply do:
bar.write_at(REG_LOCATION, reg_value);
... but this design also makes this possible:
bar.write((REG_LOCATION, reg_value));
Tuples of (location, value) do implement `IoLoc` themselves, so we can
use this little trick to support a 2-arguments syntax with a single
method.
>
>>
>>>
>>> If you want to make things more explicit you could also have
>>> `bar.write(at_array(10), ...)` or something similar.
>>
>> Is it possible to generate an `IoLoc<T>` without having `T` mentioned
>> anywhere in the call to `at_array`?
>
> Exactly same as the `impl IoLoc<REG> for usize`:
>
> struct AtArray(usize);
>
> impl IoLoc<REG> for AtArray {
> ...
> }
Right, but can the correct `REG` be inferred when the call to `at_array`
doesn't bear that information? The type inferred by the second argument
would have to be propagated to the first. Guess I'll try and see.
>
>>
>>>
>>> For the array case I really think trying to shove everything into a single
>>> argument is a footgun. The type of value in this case *doesn't* tell us the
>>> location, and the location needs to be explicit.
>>
>> bar.write(Reg::at(10, regs::MyRegArray::foo()))
>>
>> "write the constructed value at the 10th position of the `MyRegArray`
>> register array"
>>
>> What is missing here?
>
> This is completely un-natural if I try to read it with fresh mind (try to forget
> about implementation details for a second).
That's what documentation is for. Please give it a fair chance and ask
yourself: would it still look unnatural after working with it for
20 minutes?
>
> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
> single value and not an array. "at" here is saying that the register is at a
> specific location and doesn't really indicate the array nature.
>
> This is why I insist that I would prefer an explicit location
>
> bar.write(REG_ARRAY.at(10), Reg::foo())
>
> would have no ambiguity whatsoever about user's intent.
IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
`AtArray(usize)`? I still have doubts that its generic type could be
inferred automatically but it's worth giving it a go.
If that works, then I assume fixed register writes would look like
bar.write(FIXED, Reg::foo());
Unless we have a specialized `write` variant for them.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 14:32 ` Alexandre Courbot
@ 2026-03-06 14:52 ` Alexandre Courbot
2026-03-06 15:10 ` Alexandre Courbot
2026-03-06 15:35 ` Gary Guo
2 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-06 14:52 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 Fri Mar 6, 2026 at 11:32 PM JST, Alexandre Courbot wrote:
<snip>
>> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
>> single value and not an array. "at" here is saying that the register is at a
>> specific location and doesn't really indicate the array nature.
>>
>> This is why I insist that I would prefer an explicit location
>>
>> bar.write(REG_ARRAY.at(10), Reg::foo())
>>
>> would have no ambiguity whatsoever about user's intent.
>
> IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
> `AtArray(usize)`? I still have doubts that its generic type could be
> inferred automatically but it's worth giving it a go.
I don't know why I assumed that `AtArray` had a generic - it has none of
course. So there is no problem building it in this context.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 14:32 ` Alexandre Courbot
2026-03-06 14:52 ` Alexandre Courbot
@ 2026-03-06 15:10 ` Alexandre Courbot
2026-03-06 15:35 ` Alexandre Courbot
2026-03-06 15:35 ` Gary Guo
2 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-06 15:10 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 Fri Mar 6, 2026 at 11:32 PM JST, Alexandre Courbot wrote:
>> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
>> single value and not an array. "at" here is saying that the register is at a
>> specific location and doesn't really indicate the array nature.
>>
>> This is why I insist that I would prefer an explicit location
>>
>> bar.write(REG_ARRAY.at(10), Reg::foo())
>>
>> would have no ambiguity whatsoever about user's intent.
>
> IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
> `AtArray(usize)`? I still have doubts that its generic type could be
> inferred automatically but it's worth giving it a go.
>
> If that works, then I assume fixed register writes would look like
>
> bar.write(FIXED, Reg::foo());
>
> Unless we have a specialized `write` variant for them.
So, I tried this. It seemed to work really well - but then I realized
there is a problem:
The constructor of the `AtArray` needs to check that the index is in
bounds for the register array. And to do that, it needs... the type of
the register as a generic parameter. So we would be back to repeating
the register's type in both arguments, unfortunately. :/
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 14:32 ` Alexandre Courbot
2026-03-06 14:52 ` Alexandre Courbot
2026-03-06 15:10 ` Alexandre Courbot
@ 2026-03-06 15:35 ` Gary Guo
2026-03-07 0:05 ` Alexandre Courbot
2 siblings, 1 reply; 64+ messages in thread
From: Gary Guo @ 2026-03-06 15:35 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 Fri Mar 6, 2026 at 2:32 PM GMT, Alexandre Courbot wrote:
> On Fri Mar 6, 2026 at 10:20 PM JST, Gary Guo wrote:
>> I mean not sure `at` gives me that impression at all. It would just let me know
>> that I am accessing it at a different location. If you omit the `MyRegArray`
>> part then there's no real indication that this is an array to me.
>
> `at` is a function name, we can change it - I picked it because it is
> short and reasonably descriptive. The point being: we have a unique
> function that indicates unambigously that we are using a location for
> an array of registers.
Okay, maybe `at` is not the biggest issue. I just instinctively feel the usage
example being awkward.
Perhaps the fact that `Reg` exist itself is awkward to me. It looks like a type
that exists only for things to typecheck, and not itself represent a meaningful
concept.
>
> You seem to reject this design because the syntax isn't obvious and
> natural to you at first read. We are trying to build something new, so
> of course if will look a bit alien at the first encounter. That's why we
> have documentation, and I think it is not very difficult to wrap your
> head around it after seeing a few examples.
>
>>
>> If `at` is only for array, how would you represent the case where the same type
>> is being used in multiple registers?
>
> That's not something that is supported by the register macro currently
> (probably not a big change, but not something I will do in this series).
>
> But to try and answer your question, such register types would not have
> an `IoLoc` implementation of their own and would need to have their
> location constructed explicitly. In accordance, the construction of
> their value would not bear any location information; thus there would be
> no redundancy.
>
> We do have a case that is pretty close to this with relative registers.
> These are accessed like this (real examples):
>
> bar.write(Reg::of::<Gsp>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>
> and
>
> bar.write(Reg::of::<Sec2>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
Relative registers are something that on my list to eliminate and replace with
projections, so I don't particular care about how it looks like.
>
> But for register types that can be used at several arbitrary locations,
> I think this would be even simpler. The different locations would just
> need to implement `IoLoc<T>`, where `T` is the shared register type.
> Then, considering that the two-arguments version is called `write_at`,
> you can simply do:
>
> bar.write_at(REG_LOCATION, reg_value);
Having `write_at` as the name of the two-argument version is okay to me.
>
> ... but this design also makes this possible:
>
> bar.write((REG_LOCATION, reg_value));
I considered about this, but IMO this looks awkward.
>
> Tuples of (location, value) do implement `IoLoc` themselves, so we can
> use this little trick to support a 2-arguments syntax with a single
> method.
>
>>
>>>
>>>>
>>>> If you want to make things more explicit you could also have
>>>> `bar.write(at_array(10), ...)` or something similar.
>>>
>>> Is it possible to generate an `IoLoc<T>` without having `T` mentioned
>>> anywhere in the call to `at_array`?
>>
>> Exactly same as the `impl IoLoc<REG> for usize`:
>>
>> struct AtArray(usize);
>>
>> impl IoLoc<REG> for AtArray {
>> ...
>> }
>
> Right, but can the correct `REG` be inferred when the call to `at_array`
> doesn't bear that information? The type inferred by the second argument
> would have to be propagated to the first. Guess I'll try and see.
RE: inference and bounds checking issue that you mentioned in another email, I
think you can have
fn at_array(i: usize) -> Result<AtArray<T>> { .. }
and
impl IoReg<REG> for AtArray<REG> {}
The type inference here is no different to `Reg::at`.
>
>>
>>>
>>>>
>>>> For the array case I really think trying to shove everything into a single
>>>> argument is a footgun. The type of value in this case *doesn't* tell us the
>>>> location, and the location needs to be explicit.
>>>
>>> bar.write(Reg::at(10, regs::MyRegArray::foo()))
>>>
>>> "write the constructed value at the 10th position of the `MyRegArray`
>>> register array"
>>>
>>> What is missing here?
>>
>> This is completely un-natural if I try to read it with fresh mind (try to forget
>> about implementation details for a second).
>
> That's what documentation is for. Please give it a fair chance and ask
> yourself: would it still look unnatural after working with it for
> 20 minutes?
>
>>
>> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
>> single value and not an array. "at" here is saying that the register is at a
>> specific location and doesn't really indicate the array nature.
>>
>> This is why I insist that I would prefer an explicit location
>>
>> bar.write(REG_ARRAY.at(10), Reg::foo())
>>
>> would have no ambiguity whatsoever about user's intent.
>
> IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
> `AtArray(usize)`? I still have doubts that its generic type could be
> inferred automatically but it's worth giving it a go.
>
> If that works, then I assume fixed register writes would look like
>
> bar.write(FIXED, Reg::foo());
>
> Unless we have a specialized `write` variant for them.
That, or `()` as I mentioned.
Best,
Gary
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 15:10 ` Alexandre Courbot
@ 2026-03-06 15:35 ` Alexandre Courbot
0 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-06 15:35 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 Sat Mar 7, 2026 at 12:10 AM JST, Alexandre Courbot wrote:
> On Fri Mar 6, 2026 at 11:32 PM JST, Alexandre Courbot wrote:
>>> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
>>> single value and not an array. "at" here is saying that the register is at a
>>> specific location and doesn't really indicate the array nature.
>>>
>>> This is why I insist that I would prefer an explicit location
>>>
>>> bar.write(REG_ARRAY.at(10), Reg::foo())
>>>
>>> would have no ambiguity whatsoever about user's intent.
>>
>> IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
>> `AtArray(usize)`? I still have doubts that its generic type could be
>> inferred automatically but it's worth giving it a go.
>>
>> If that works, then I assume fixed register writes would look like
>>
>> bar.write(FIXED, Reg::foo());
>>
>> Unless we have a specialized `write` variant for them.
>
> So, I tried this. It seemed to work really well - but then I realized
> there is a problem:
>
> The constructor of the `AtArray` needs to check that the index is in
> bounds for the register array. And to do that, it needs... the type of
> the register as a generic parameter. So we would be back to repeating
> the register's type in both arguments, unfortunately. :/
If we add the register type as a generic argument of `AtArray`, we end
up with exactly the already-existing `ArrayRegisterLoc` type, which
performs the bounds-checking and also implements `IoLoc`.
The last remaining question is whether it can be instanciated from a
2-arguments write without repeating the type, and yes it does! So that
part does work quite nicely at least.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-06 15:35 ` Gary Guo
@ 2026-03-07 0:05 ` Alexandre Courbot
2026-03-07 21:10 ` Gary Guo
0 siblings, 1 reply; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-07 0: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 Sat Mar 7, 2026 at 12:35 AM JST, Gary Guo wrote:
> On Fri Mar 6, 2026 at 2:32 PM GMT, Alexandre Courbot wrote:
>> On Fri Mar 6, 2026 at 10:20 PM JST, Gary Guo wrote:
>>> I mean not sure `at` gives me that impression at all. It would just let me know
>>> that I am accessing it at a different location. If you omit the `MyRegArray`
>>> part then there's no real indication that this is an array to me.
>>
>> `at` is a function name, we can change it - I picked it because it is
>> short and reasonably descriptive. The point being: we have a unique
>> function that indicates unambigously that we are using a location for
>> an array of registers.
>
> Okay, maybe `at` is not the biggest issue. I just instinctively feel the usage
> example being awkward.
>
> Perhaps the fact that `Reg` exist itself is awkward to me. It looks like a type
> that exists only for things to typecheck, and not itself represent a meaningful
> concept.
After partially implementing your two-args proposal it really turns out
the two alternatives are very similar in essence, with most of the
differences being details like naming and scope.
The one fundamental difference is that it didn't occur to me that we
could resolve a generic argument for a function in first position of
write using another argument, hence I went for:
bar.write(location_builder(location_info, value));
but it is also possible to do
bar.write(location_builder(location_info), value);
And that's really the main difference. All the rest is mostly details
about whether `location_builder` is an associated function of a type, a
method of a ZST, or just a module-level function. From the point of view
of `Io`, none of this matters as long as it gets an `IoLoc` that is
compatible with the value. IOW, each Io submodule can provide the
location builders that make sense for it.
The `Reg` thing in my previous email was just a way to provide a scope
for these location builders, but in retrospect a module-level function
seems better to me if we want to keep things concise. So if we turn the
location builders into module-level methods we could have:
use kernel::io::register as reg;
bar.write(reg::at(10), SOME_ARRAY_SCRATCH::foo());
Or does
bar.write(SOME_ARRAY_SCRATCH::foo(), reg::at(10));
Flow better now?
>
>>
>> You seem to reject this design because the syntax isn't obvious and
>> natural to you at first read. We are trying to build something new, so
>> of course if will look a bit alien at the first encounter. That's why we
>> have documentation, and I think it is not very difficult to wrap your
>> head around it after seeing a few examples.
>>
>>>
>>> If `at` is only for array, how would you represent the case where the same type
>>> is being used in multiple registers?
>>
>> That's not something that is supported by the register macro currently
>> (probably not a big change, but not something I will do in this series).
>>
>> But to try and answer your question, such register types would not have
>> an `IoLoc` implementation of their own and would need to have their
>> location constructed explicitly. In accordance, the construction of
>> their value would not bear any location information; thus there would be
>> no redundancy.
>>
>> We do have a case that is pretty close to this with relative registers.
>> These are accessed like this (real examples):
>>
>> bar.write(Reg::of::<Gsp>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>>
>> and
>>
>> bar.write(Reg::of::<Sec2>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>
> Relative registers are something that on my list to eliminate and replace with
> projections, so I don't particular care about how it looks like.
That's interesting, I thought register arrays would be easier to replace
than relative registers, I really need to take a closer look.
>
>>
>> But for register types that can be used at several arbitrary locations,
>> I think this would be even simpler. The different locations would just
>> need to implement `IoLoc<T>`, where `T` is the shared register type.
>> Then, considering that the two-arguments version is called `write_at`,
>> you can simply do:
>>
>> bar.write_at(REG_LOCATION, reg_value);
>
> Having `write_at` as the name of the two-argument version is okay to me.
I picked that for illustrative purposes, but `write` should be the
version that is going to be the most used. And if we remove the value
from the location builder, then the most used version is clearly going
to be the two arguments one.
The one argument variant would then become a shortcut - `put` sounds
adequate to me, but `write_val` also works.
>
>>
>> ... but this design also makes this possible:
>>
>> bar.write((REG_LOCATION, reg_value));
>
> I considered about this, but IMO this looks awkward.
Funny how you spell "elegant". :)
>
>>
>> Tuples of (location, value) do implement `IoLoc` themselves, so we can
>> use this little trick to support a 2-arguments syntax with a single
>> method.
>>
>>>
>>>>
>>>>>
>>>>> If you want to make things more explicit you could also have
>>>>> `bar.write(at_array(10), ...)` or something similar.
>>>>
>>>> Is it possible to generate an `IoLoc<T>` without having `T` mentioned
>>>> anywhere in the call to `at_array`?
>>>
>>> Exactly same as the `impl IoLoc<REG> for usize`:
>>>
>>> struct AtArray(usize);
>>>
>>> impl IoLoc<REG> for AtArray {
>>> ...
>>> }
>>
>> Right, but can the correct `REG` be inferred when the call to `at_array`
>> doesn't bear that information? The type inferred by the second argument
>> would have to be propagated to the first. Guess I'll try and see.
>
> RE: inference and bounds checking issue that you mentioned in another email, I
> think you can have
>
> fn at_array(i: usize) -> Result<AtArray<T>> { .. }
>
> and
>
> impl IoReg<REG> for AtArray<REG> {}
>
> The type inference here is no different to `Reg::at`.
Yes, this clearly works and then it actually becomes `RegisterArrayLoc`,
which already exists and also implements `IoLoc`. This convergence looks
like positive sign to me.
>
>>
>>>
>>>>
>>>>>
>>>>> For the array case I really think trying to shove everything into a single
>>>>> argument is a footgun. The type of value in this case *doesn't* tell us the
>>>>> location, and the location needs to be explicit.
>>>>
>>>> bar.write(Reg::at(10, regs::MyRegArray::foo()))
>>>>
>>>> "write the constructed value at the 10th position of the `MyRegArray`
>>>> register array"
>>>>
>>>> What is missing here?
>>>
>>> This is completely un-natural if I try to read it with fresh mind (try to forget
>>> about implementation details for a second).
>>
>> That's what documentation is for. Please give it a fair chance and ask
>> yourself: would it still look unnatural after working with it for
>> 20 minutes?
>>
>>>
>>> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
>>> single value and not an array. "at" here is saying that the register is at a
>>> specific location and doesn't really indicate the array nature.
>>>
>>> This is why I insist that I would prefer an explicit location
>>>
>>> bar.write(REG_ARRAY.at(10), Reg::foo())
>>>
>>> would have no ambiguity whatsoever about user's intent.
>>
>> IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
>> `AtArray(usize)`? I still have doubts that its generic type could be
>> inferred automatically but it's worth giving it a go.
>>
>> If that works, then I assume fixed register writes would look like
>>
>> bar.write(FIXED, Reg::foo());
>>
>> Unless we have a specialized `write` variant for them.
>
> That, or `()` as I mentioned.
Wouldn't it look... awkward? :)
But if we can agree on
bar.put(Reg::foo());
or even
bar.write_val(Reg::foo());
Then I believe we are getting close to something that can make everyone
happy (pending Danilo's blessing). The `()` syntax can also be supported
for generic code.
I'll try to speedrun the remainder of the implementation and send a new
revision for review, but AFAICT this ticks all the boxes.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-07 0:05 ` Alexandre Courbot
@ 2026-03-07 21:10 ` Gary Guo
2026-03-07 21:40 ` Danilo Krummrich
2026-03-08 11:35 ` Alexandre Courbot
0 siblings, 2 replies; 64+ messages in thread
From: Gary Guo @ 2026-03-07 21:10 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 Sat Mar 7, 2026 at 12:05 AM GMT, Alexandre Courbot wrote:
> On Sat Mar 7, 2026 at 12:35 AM JST, Gary Guo wrote:
>> On Fri Mar 6, 2026 at 2:32 PM GMT, Alexandre Courbot wrote:
>>> On Fri Mar 6, 2026 at 10:20 PM JST, Gary Guo wrote:
>>>> I mean not sure `at` gives me that impression at all. It would just let me know
>>>> that I am accessing it at a different location. If you omit the `MyRegArray`
>>>> part then there's no real indication that this is an array to me.
>>>
>>> `at` is a function name, we can change it - I picked it because it is
>>> short and reasonably descriptive. The point being: we have a unique
>>> function that indicates unambigously that we are using a location for
>>> an array of registers.
>>
>> Okay, maybe `at` is not the biggest issue. I just instinctively feel the usage
>> example being awkward.
>>
>> Perhaps the fact that `Reg` exist itself is awkward to me. It looks like a type
>> that exists only for things to typecheck, and not itself represent a meaningful
>> concept.
>
> After partially implementing your two-args proposal it really turns out
> the two alternatives are very similar in essence, with most of the
> differences being details like naming and scope.
>
> The one fundamental difference is that it didn't occur to me that we
> could resolve a generic argument for a function in first position of
> write using another argument, hence I went for:
>
> bar.write(location_builder(location_info, value));
>
> but it is also possible to do
>
> bar.write(location_builder(location_info), value);
>
> And that's really the main difference. All the rest is mostly details
> about whether `location_builder` is an associated function of a type, a
> method of a ZST, or just a module-level function. From the point of view
> of `Io`, none of this matters as long as it gets an `IoLoc` that is
> compatible with the value. IOW, each Io submodule can provide the
> location builders that make sense for it.
>
> The `Reg` thing in my previous email was just a way to provide a scope
> for these location builders, but in retrospect a module-level function
> seems better to me if we want to keep things concise. So if we turn the
> location builders into module-level methods we could have:
>
> use kernel::io::register as reg;
>
> bar.write(reg::at(10), SOME_ARRAY_SCRATCH::foo());
>
> Or does
>
> bar.write(SOME_ARRAY_SCRATCH::foo(), reg::at(10));
>
> Flow better now?
I'm still generally inclined to have location at front, as it's also going to be
how projection syntax will work for structs:
io_write!(bar, .scratch[10], foo);
or just assignment in general
my.scratch[10] = foo;
I should also say that, my desire of having the explicit locaction is also
partly driven by the desire to eventually unify projections with the register
macro. If we would generate a struct where fields inside are registers at there
correct offset, then `io_write!(bar, .register_name, foo)` would indeed be a
canonical way of accessing such registers using I/O projection. There's no way
to simplify `my_struct.foo = Foo {}` without repeating `foo` twice :)
For context, Benno suggested in rust-lang zulip in a discussion about how it
might be possible to use field projections to replace register macro in the
future:
https://rust-lang.zulipchat.com/#narrow/channel/522311-t-lang.2Fcustom-refs/topic/custom.20virtual.20fields/near/573703808
Don't worry, the language features don't exist yet, there's no change needed to
your series :)
Probably I should mention this earlier so you see the aspect I'm coming from,
but none of this is directly related to the `register!` work you're having
today, so I was avoiding to argue with unrelated features.
>
>>
>>>
>>> You seem to reject this design because the syntax isn't obvious and
>>> natural to you at first read. We are trying to build something new, so
>>> of course if will look a bit alien at the first encounter. That's why we
>>> have documentation, and I think it is not very difficult to wrap your
>>> head around it after seeing a few examples.
>>>
>>>>
>>>> If `at` is only for array, how would you represent the case where the same type
>>>> is being used in multiple registers?
>>>
>>> That's not something that is supported by the register macro currently
>>> (probably not a big change, but not something I will do in this series).
>>>
>>> But to try and answer your question, such register types would not have
>>> an `IoLoc` implementation of their own and would need to have their
>>> location constructed explicitly. In accordance, the construction of
>>> their value would not bear any location information; thus there would be
>>> no redundancy.
>>>
>>> We do have a case that is pretty close to this with relative registers.
>>> These are accessed like this (real examples):
>>>
>>> bar.write(Reg::of::<Gsp>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>>>
>>> and
>>>
>>> bar.write(Reg::of::<Sec2>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>>
>> Relative registers are something that on my list to eliminate and replace with
>> projections, so I don't particular care about how it looks like.
>
> That's interesting, I thought register arrays would be easier to replace
> than relative registers, I really need to take a closer look.
Register array with stride might be tricky, although I have to say I haven't put
much thought into this yet.
>
>>
>>>
>>> But for register types that can be used at several arbitrary locations,
>>> I think this would be even simpler. The different locations would just
>>> need to implement `IoLoc<T>`, where `T` is the shared register type.
>>> Then, considering that the two-arguments version is called `write_at`,
>>> you can simply do:
>>>
>>> bar.write_at(REG_LOCATION, reg_value);
>>
>> Having `write_at` as the name of the two-argument version is okay to me.
>
> I picked that for illustrative purposes, but `write` should be the
> version that is going to be the most used. And if we remove the value
> from the location builder, then the most used version is clearly going
> to be the two arguments one.
>
> The one argument variant would then become a shortcut - `put` sounds
> adequate to me, but `write_val` also works.
`put` sounds good. It was also the name that Gemini suggests to me when I asked
it to come up with a short and concise name for this :)
>
>>
>>>
>>> ... but this design also makes this possible:
>>>
>>> bar.write((REG_LOCATION, reg_value));
>>
>> I considered about this, but IMO this looks awkward.
>
> Funny how you spell "elegant". :)
It's indeed quite elegant from a FP and type theorist lens. I'm not personally
objecting this, although I feel that this may looks strange to people with C
background (not sure if that's actually true, though).
>
>>
>>>
>>> Tuples of (location, value) do implement `IoLoc` themselves, so we can
>>> use this little trick to support a 2-arguments syntax with a single
>>> method.
>>>
>>>>
>>>>>
>>>>>>
>>>>>> If you want to make things more explicit you could also have
>>>>>> `bar.write(at_array(10), ...)` or something similar.
>>>>>
>>>>> Is it possible to generate an `IoLoc<T>` without having `T` mentioned
>>>>> anywhere in the call to `at_array`?
>>>>
>>>> Exactly same as the `impl IoLoc<REG> for usize`:
>>>>
>>>> struct AtArray(usize);
>>>>
>>>> impl IoLoc<REG> for AtArray {
>>>> ...
>>>> }
>>>
>>> Right, but can the correct `REG` be inferred when the call to `at_array`
>>> doesn't bear that information? The type inferred by the second argument
>>> would have to be propagated to the first. Guess I'll try and see.
>>
>> RE: inference and bounds checking issue that you mentioned in another email, I
>> think you can have
>>
>> fn at_array(i: usize) -> Result<AtArray<T>> { .. }
>>
>> and
>>
>> impl IoReg<REG> for AtArray<REG> {}
>>
>> The type inference here is no different to `Reg::at`.
>
> Yes, this clearly works and then it actually becomes `RegisterArrayLoc`,
> which already exists and also implements `IoLoc`. This convergence looks
> like positive sign to me.
>
>>
>>>
>>>>
>>>>>
>>>>>>
>>>>>> For the array case I really think trying to shove everything into a single
>>>>>> argument is a footgun. The type of value in this case *doesn't* tell us the
>>>>>> location, and the location needs to be explicit.
>>>>>
>>>>> bar.write(Reg::at(10, regs::MyRegArray::foo()))
>>>>>
>>>>> "write the constructed value at the 10th position of the `MyRegArray`
>>>>> register array"
>>>>>
>>>>> What is missing here?
>>>>
>>>> This is completely un-natural if I try to read it with fresh mind (try to forget
>>>> about implementation details for a second).
>>>
>>> That's what documentation is for. Please give it a fair chance and ask
>>> yourself: would it still look unnatural after working with it for
>>> 20 minutes?
>>>
>>>>
>>>> `MyRegArray` here is a type name that is a bitfield and not an array. `foo` returns a
>>>> single value and not an array. "at" here is saying that the register is at a
>>>> specific location and doesn't really indicate the array nature.
>>>>
>>>> This is why I insist that I would prefer an explicit location
>>>>
>>>> bar.write(REG_ARRAY.at(10), Reg::foo())
>>>>
>>>> would have no ambiguity whatsoever about user's intent.
>>>
>>> IIUC `REG_ARRAY` would be a const ZST and `at` a method returning an
>>> `AtArray(usize)`? I still have doubts that its generic type could be
>>> inferred automatically but it's worth giving it a go.
>>>
>>> If that works, then I assume fixed register writes would look like
>>>
>>> bar.write(FIXED, Reg::foo());
>>>
>>> Unless we have a specialized `write` variant for them.
>>
>> That, or `()` as I mentioned.
>
> Wouldn't it look... awkward? :)
>
> But if we can agree on
>
> bar.put(Reg::foo());
>
> or even
>
> bar.write_val(Reg::foo());
I'm okay with either.
>
> Then I believe we are getting close to something that can make everyone
> happy (pending Danilo's blessing). The `()` syntax can also be supported
> for generic code.
Yep, if we want to support both the `put` can just be a sugar
fn put<T>(&self, value: T) where (): IoLoc<T> + ... {
self.write((), value)
}
Best,
Gary
>
> I'll try to speedrun the remainder of the implementation and send a new
> revision for review, but AFAICT this ticks all the boxes.
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-07 21:10 ` Gary Guo
@ 2026-03-07 21:40 ` Danilo Krummrich
2026-03-08 11:43 ` Alexandre Courbot
2026-03-08 11:35 ` Alexandre Courbot
1 sibling, 1 reply; 64+ messages in thread
From: Danilo Krummrich @ 2026-03-07 21:40 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 Sat Mar 7, 2026 at 10:10 PM CET, Gary Guo wrote:
>> But if we can agree on
>>
>> bar.put(Reg::foo());
>>
>> or even
>>
>> bar.write_val(Reg::foo());
>
> I'm okay with either.
write_val() or something similar please; put() is not a good choice for
something that does not release a reference count in the kernel.
(As for write_val(), I'm generally fine with the name, but I want to note that
it also may be slightly confusing as it is the two-argument write() method which
distinguishes between location and value, whereas this one combines the two.)
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-07 21:10 ` Gary Guo
2026-03-07 21:40 ` Danilo Krummrich
@ 2026-03-08 11:35 ` Alexandre Courbot
1 sibling, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-08 11:35 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 Sun Mar 8, 2026 at 6:10 AM JST, Gary Guo wrote:
> On Sat Mar 7, 2026 at 12:05 AM GMT, Alexandre Courbot wrote:
>> On Sat Mar 7, 2026 at 12:35 AM JST, Gary Guo wrote:
>>> On Fri Mar 6, 2026 at 2:32 PM GMT, Alexandre Courbot wrote:
>>>> On Fri Mar 6, 2026 at 10:20 PM JST, Gary Guo wrote:
>>>>> I mean not sure `at` gives me that impression at all. It would just let me know
>>>>> that I am accessing it at a different location. If you omit the `MyRegArray`
>>>>> part then there's no real indication that this is an array to me.
>>>>
>>>> `at` is a function name, we can change it - I picked it because it is
>>>> short and reasonably descriptive. The point being: we have a unique
>>>> function that indicates unambigously that we are using a location for
>>>> an array of registers.
>>>
>>> Okay, maybe `at` is not the biggest issue. I just instinctively feel the usage
>>> example being awkward.
>>>
>>> Perhaps the fact that `Reg` exist itself is awkward to me. It looks like a type
>>> that exists only for things to typecheck, and not itself represent a meaningful
>>> concept.
>>
>> After partially implementing your two-args proposal it really turns out
>> the two alternatives are very similar in essence, with most of the
>> differences being details like naming and scope.
>>
>> The one fundamental difference is that it didn't occur to me that we
>> could resolve a generic argument for a function in first position of
>> write using another argument, hence I went for:
>>
>> bar.write(location_builder(location_info, value));
>>
>> but it is also possible to do
>>
>> bar.write(location_builder(location_info), value);
>>
>> And that's really the main difference. All the rest is mostly details
>> about whether `location_builder` is an associated function of a type, a
>> method of a ZST, or just a module-level function. From the point of view
>> of `Io`, none of this matters as long as it gets an `IoLoc` that is
>> compatible with the value. IOW, each Io submodule can provide the
>> location builders that make sense for it.
>>
>> The `Reg` thing in my previous email was just a way to provide a scope
>> for these location builders, but in retrospect a module-level function
>> seems better to me if we want to keep things concise. So if we turn the
>> location builders into module-level methods we could have:
>>
>> use kernel::io::register as reg;
>>
>> bar.write(reg::at(10), SOME_ARRAY_SCRATCH::foo());
>>
>> Or does
>>
>> bar.write(SOME_ARRAY_SCRATCH::foo(), reg::at(10));
>>
>> Flow better now?
>
> I'm still generally inclined to have location at front, as it's also going to be
> how projection syntax will work for structs:
>
> io_write!(bar, .scratch[10], foo);
>
> or just assignment in general
>
> my.scratch[10] = foo;
Yes, after working on the implementation I came to a similar conclusion,
mostly because the new construct reads fine in that order.
>
> I should also say that, my desire of having the explicit locaction is also
> partly driven by the desire to eventually unify projections with the register
> macro. If we would generate a struct where fields inside are registers at there
> correct offset, then `io_write!(bar, .register_name, foo)` would indeed be a
> canonical way of accessing such registers using I/O projection. There's no way
> to simplify `my_struct.foo = Foo {}` without repeating `foo` twice :)
>
> For context, Benno suggested in rust-lang zulip in a discussion about how it
> might be possible to use field projections to replace register macro in the
> future:
> https://rust-lang.zulipchat.com/#narrow/channel/522311-t-lang.2Fcustom-refs/topic/custom.20virtual.20fields/near/573703808
> Don't worry, the language features don't exist yet, there's no change needed to
> your series :)
>
> Probably I should mention this earlier so you see the aspect I'm coming from,
> but none of this is directly related to the `register!` work you're having
> today, so I was avoiding to argue with unrelated features.
That's useful context to have indeed. To be fair I also haven't done my
homework with I/O projections - I took a peek at your pointer projection
series, but not quite enough to do a proper review.
Being able to define the register space as a regular Rust construct does
look very appealing. Actually that what the register macro tries to
mimic, albeit clumsily.
Name repetition might become a concern though, for the same reason it is
one now, but if we start to use macros like `io_write!` then maybe we
can macro our way around this as well. Or, have a way to produce
`IoLoc`s from struct fields and use the current I/O methods. Anyway it's
a concern for later.
>
>>
>>>
>>>>
>>>> You seem to reject this design because the syntax isn't obvious and
>>>> natural to you at first read. We are trying to build something new, so
>>>> of course if will look a bit alien at the first encounter. That's why we
>>>> have documentation, and I think it is not very difficult to wrap your
>>>> head around it after seeing a few examples.
>>>>
>>>>>
>>>>> If `at` is only for array, how would you represent the case where the same type
>>>>> is being used in multiple registers?
>>>>
>>>> That's not something that is supported by the register macro currently
>>>> (probably not a big change, but not something I will do in this series).
>>>>
>>>> But to try and answer your question, such register types would not have
>>>> an `IoLoc` implementation of their own and would need to have their
>>>> location constructed explicitly. In accordance, the construction of
>>>> their value would not bear any location information; thus there would be
>>>> no redundancy.
>>>>
>>>> We do have a case that is pretty close to this with relative registers.
>>>> These are accessed like this (real examples):
>>>>
>>>> bar.write(Reg::of::<Gsp>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>>>>
>>>> and
>>>>
>>>> bar.write(Reg::of::<Sec2>(regs::NV_PFALCON_FALCON_DMACTL::zeroed()));
>>>
>>> Relative registers are something that on my list to eliminate and replace with
>>> projections, so I don't particular care about how it looks like.
>>
>> That's interesting, I thought register arrays would be easier to replace
>> than relative registers, I really need to take a closer look.
>
> Register array with stride might be tricky, although I have to say I haven't put
> much thought into this yet.
Looking at the Zulip discussion, and lacking most of the context, it
seems we can either put intertwined register arrays into a struct and
make an array of that, or convert them to relative registers (or
possibly add some inaccessible padding). Register arrays that are not
contiguous tend to fall into one of these categories. IOW, proper
structure definitions we might very well be able to get rid of that
stride parameter that looks a bit out of place.
>
>>
>>>
>>>>
>>>> But for register types that can be used at several arbitrary locations,
>>>> I think this would be even simpler. The different locations would just
>>>> need to implement `IoLoc<T>`, where `T` is the shared register type.
>>>> Then, considering that the two-arguments version is called `write_at`,
>>>> you can simply do:
>>>>
>>>> bar.write_at(REG_LOCATION, reg_value);
>>>
>>> Having `write_at` as the name of the two-argument version is okay to me.
>>
>> I picked that for illustrative purposes, but `write` should be the
>> version that is going to be the most used. And if we remove the value
>> from the location builder, then the most used version is clearly going
>> to be the two arguments one.
>>
>> The one argument variant would then become a shortcut - `put` sounds
>> adequate to me, but `write_val` also works.
>
> `put` sounds good. It was also the name that Gemini suggests to me when I asked
> it to come up with a short and concise name for this :)
>
>>
>>>
>>>>
>>>> ... but this design also makes this possible:
>>>>
>>>> bar.write((REG_LOCATION, reg_value));
>>>
>>> I considered about this, but IMO this looks awkward.
>>
>> Funny how you spell "elegant". :)
>
> It's indeed quite elegant from a FP and type theorist lens. I'm not personally
> objecting this, although I feel that this may looks strange to people with C
> background (not sure if that's actually true, though).
Fair point, but the goal is also to allow people with a C background to
build their Rust knowledge, so I think such accomodations should be done
using thoughtful documentation rather than limiting our API choices.
Not saying this is what is happening here, I haven't seen such use of
tuples be deemed as idiomatic so I was just suggesting a way to preserve
the API purity. But I'd like to keep the `IoLoc` implementation on
tuples as it can be useful for generic code (just like `impl IoLoc<T>
for ()`).
^ permalink raw reply [flat|nested] 64+ messages in thread
* Re: [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types
2026-03-07 21:40 ` Danilo Krummrich
@ 2026-03-08 11:43 ` Alexandre Courbot
0 siblings, 0 replies; 64+ messages in thread
From: Alexandre Courbot @ 2026-03-08 11:43 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 Sun Mar 8, 2026 at 6:40 AM JST, Danilo Krummrich wrote:
> On Sat Mar 7, 2026 at 10:10 PM CET, Gary Guo wrote:
>>> But if we can agree on
>>>
>>> bar.put(Reg::foo());
>>>
>>> or even
>>>
>>> bar.write_val(Reg::foo());
>>
>> I'm okay with either.
>
> write_val() or something similar please; put() is not a good choice for
> something that does not release a reference count in the kernel.
Right, that could end up being confusing.
>
> (As for write_val(), I'm generally fine with the name, but I want to note that
> it also may be slightly confusing as it is the two-argument write() method which
> distinguishes between location and value, whereas this one combines the two.)
Would `write_to` be better?
We can also rename the two arguments variant `write_at`, and call the
single argument one `write`, but since the two arguments method will
likely be the most used with the current design it makes sense to me
that it inherits the default name as well.
^ permalink raw reply [flat|nested] 64+ messages in thread
end of thread, other threads:[~2026-03-08 11:43 UTC | newest]
Thread overview: 64+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-02-24 14:21 [PATCH v7 00/10] rust: add `register!` macro Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 01/10] rust: enable the `generic_arg_infer` feature Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 02/10] rust: num: add `shr` and `shl` methods to `Bounded` Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 03/10] rust: num: add `into_bool` method " Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 04/10] rust: num: make Bounded::get const Alexandre Courbot
2026-02-27 12:33 ` Gary Guo
2026-02-24 14:21 ` [PATCH v7 05/10] rust: io: add IoLoc and IoWrite types Alexandre Courbot
2026-02-27 18:02 ` Gary Guo
2026-02-27 18:16 ` Danilo Krummrich
2026-02-28 0:33 ` Alexandre Courbot
2026-03-01 15:11 ` Gary Guo
2026-03-02 1:44 ` Alexandre Courbot
2026-03-02 12:53 ` Gary Guo
2026-03-02 13:12 ` Danilo Krummrich
2026-03-02 13:39 ` Gary Guo
2026-03-03 8:14 ` Alexandre Courbot
2026-03-03 8:31 ` Alexandre Courbot
2026-03-03 14:55 ` Alexandre Courbot
2026-03-03 15:05 ` Gary Guo
2026-03-04 16:18 ` Danilo Krummrich
2026-03-04 18:39 ` Gary Guo
2026-03-04 18:58 ` Gary Guo
2026-03-04 19:19 ` John Hubbard
2026-03-04 19:53 ` Danilo Krummrich
2026-03-04 19:57 ` John Hubbard
2026-03-04 20:05 ` Gary Guo
2026-03-04 19:38 ` Danilo Krummrich
2026-03-04 19:48 ` Gary Guo
2026-03-04 20:37 ` Danilo Krummrich
2026-03-04 21:13 ` Gary Guo
2026-03-04 21:38 ` Danilo Krummrich
2026-03-04 21:42 ` Danilo Krummrich
2026-03-04 22:15 ` Gary Guo
2026-03-04 22:22 ` Danilo Krummrich
2026-03-06 5:37 ` Alexandre Courbot
2026-03-06 7:47 ` Alexandre Courbot
2026-03-06 10:42 ` Gary Guo
2026-03-06 11:10 ` Alexandre Courbot
2026-03-06 11:35 ` Gary Guo
2026-03-06 12:50 ` Alexandre Courbot
2026-03-06 13:20 ` Gary Guo
2026-03-06 14:32 ` Alexandre Courbot
2026-03-06 14:52 ` Alexandre Courbot
2026-03-06 15:10 ` Alexandre Courbot
2026-03-06 15:35 ` Alexandre Courbot
2026-03-06 15:35 ` Gary Guo
2026-03-07 0:05 ` Alexandre Courbot
2026-03-07 21:10 ` Gary Guo
2026-03-07 21:40 ` Danilo Krummrich
2026-03-08 11:43 ` Alexandre Courbot
2026-03-08 11:35 ` Alexandre Courbot
2026-03-04 18:53 ` Gary Guo
2026-03-04 22:19 ` Gary Guo
2026-03-05 11:02 ` Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 06/10] rust: io: use generic read/write accessors for primitive accesses Alexandre Courbot
2026-02-27 18:04 ` Gary Guo
2026-02-24 14:21 ` [PATCH v7 07/10] rust: io: add `register!` macro Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 08/10] sample: rust: pci: use " Alexandre Courbot
2026-02-24 14:21 ` [PATCH FOR REFERENCE v7 09/10] gpu: nova-core: use the kernel " Alexandre Courbot
2026-02-24 14:21 ` [PATCH v7 10/10] RFC: rust: io: allow fixed register values directly in `write` Alexandre Courbot
2026-02-25 11:58 ` [PATCH v7 00/10] rust: add `register!` macro Dirk Behme
2026-02-25 13:50 ` Alexandre Courbot
2026-02-26 12:01 ` Dirk Behme
2026-02-27 23:30 ` Alexandre Courbot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox