[PATCH v9 08/10] rust: io: introduce `write_reg` and `LocatedRegister`

Alexandre Courbot posted 10 patches 2 weeks, 5 days ago
[PATCH v9 08/10] rust: io: introduce `write_reg` and `LocatedRegister`
Posted by Alexandre Courbot 2 weeks, 5 days ago
Some I/O types, like fixed address registers, carry their location
alongside their values. For these types, the regular `Io::write` method
can lead into repeating the location information twice: once to provide
the location itself, another time to build the value.

We are also considering supporting making all register values carry
their full location information for convenience and safety.

Add a new `Io::write_reg` method that takes a single argument
implementing `LocatedRegister`, a trait that decomposes implementors
into a `(location, value)` tuple. This allows write operations on fixed
offset registers to be done while specifying their name only once.

Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
 rust/kernel/io.rs          | 70 ++++++++++++++++++++++++++++++++++++++++++++++
 rust/kernel/io/register.rs | 35 +++++++++++++++++++++--
 2 files changed, 103 insertions(+), 2 deletions(-)

diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index bfea30a9acdf..24e6b48b6582 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -17,6 +17,8 @@
 pub use crate::register;
 pub use resource::Resource;
 
+use register::LocatedRegister;
+
 /// Physical address type.
 ///
 /// This is a type alias to either `u32` or `u64` depending on the config option
@@ -473,6 +475,40 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result
         Ok(())
     }
 
+    /// Generic fallible write of a fully-located register value.
+    ///
+    /// # Examples
+    ///
+    /// Tuples carrying a location and a value can be used with this method:
+    ///
+    /// ```no_run
+    /// use kernel::io::{
+    ///     register,
+    ///     Io,
+    ///     Mmio,
+    /// };
+    ///
+    /// register! {
+    ///     FIFO_OUT(u32) @ 0x100 {}
+    /// }
+    ///
+    /// fn do_write_reg(io: &Mmio) -> Result {
+    ///     // `FIFO_OUT` provides us the location of the write operation.
+    ///     io.try_write_reg(FIFO_OUT::from(10))
+    /// }
+    /// ```
+    #[inline(always)]
+    fn try_write_reg<T, L, V>(&self, value: V) -> Result
+    where
+        L: IoLoc<T>,
+        V: LocatedRegister<Location = L, Value = T>,
+        Self: IoCapable<L::IoType>,
+    {
+        let (location, value) = value.into_io_op();
+
+        self.try_write(location, value)
+    }
+
     /// Generic fallible update with runtime bounds check.
     ///
     /// Note: this does not perform any synchronization. The caller is responsible for ensuring
@@ -578,6 +614,40 @@ fn write<T, L>(&self, location: L, value: T)
         unsafe { self.io_write(io_value, address) }
     }
 
+    /// Generic infallible write of a fully-located register value.
+    ///
+    /// # Examples
+    ///
+    /// Tuples carrying a location and a value can be used with this method:
+    ///
+    /// ```no_run
+    /// use kernel::io::{
+    ///     register,
+    ///     Io,
+    ///     Mmio,
+    /// };
+    ///
+    /// register! {
+    ///     FIFO_OUT(u32) @ 0x100 {}
+    /// }
+    ///
+    /// fn do_write_reg(io: &Mmio<0x1000>) {
+    ///     // `FIFO_OUT` provides us the location of the write operation.
+    ///     io.write_reg(FIFO_OUT::from(10));
+    /// }
+    /// ```
+    #[inline(always)]
+    fn write_reg<T, L, V>(&self, value: V)
+    where
+        L: IoLoc<T>,
+        V: LocatedRegister<Location = L, Value = T>,
+        Self: IoKnownSize + IoCapable<L::IoType>,
+    {
+        let (location, value) = value.into_io_op();
+
+        self.write(location, value)
+    }
+
     /// Generic infallible update with compile-time bounds check.
     ///
     /// Note: this does not perform any synchronization. The caller is responsible for ensuring
diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
index 40085953c831..b26dc2400009 100644
--- a/rust/kernel/io/register.rs
+++ b/rust/kernel/io/register.rs
@@ -80,10 +80,10 @@
 //!     .with_const_minor_revision::<10>()
 //!     // Run-time value.
 //!     .with_vendor_id(obtain_vendor_id());
-//! io.write((), new_boot0);
+//! io.write_reg(new_boot0);
 //!
 //! // Or, build a new value from zero and write it:
-//! io.write((), BOOT_0::zeroed()
+//! io.write_reg(BOOT_0::zeroed()
 //!     .with_const_major_revision::<3>()
 //!     .with_const_minor_revision::<10>()
 //!     .with_vendor_id(obtain_vendor_id())
@@ -379,6 +379,34 @@ fn offset(self) -> usize {
     }
 }
 
+/// Trait implemented by items that contain both a register value and the absolute I/O location at
+/// which to write it.
+///
+/// Implementors can be used with [`Io::write_reg`](super::Io::write_reg).
+pub trait LocatedRegister {
+    /// Register value to write.
+    type Value: Register;
+    /// Full location information at which to write the value.
+    type Location: IoLoc<Self::Value>;
+
+    /// Consumes `self` and returns a `(location, value)` tuple describing a valid I/O write
+    /// operation.
+    fn into_io_op(self) -> (Self::Location, Self::Value);
+}
+
+impl<T> LocatedRegister for T
+where
+    T: FixedRegister,
+{
+    type Location = FixedRegisterLoc<Self::Value>;
+    type Value = T;
+
+    #[inline(always)]
+    fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
+        (FixedRegisterLoc::new(), self)
+    }
+}
+
 /// Defines a dedicated type for a register, including getter and setter methods for its fields and
 /// methods to read and write it from an [`Io`](kernel::io::Io) region.
 ///
@@ -433,6 +461,9 @@ fn offset(self) -> usize {
 /// // The location of fixed offset registers is already contained in their type. Thus, the
 /// // `location` argument of `Io::write` is technically redundant and can be replaced by `()`.
 /// io.write((), val2);
+///
+/// // Or, the single-argument `Io::write_reg` can be used.
+/// io.write_reg(val2);
 /// # }
 ///
 /// ```

-- 
2.53.0
Re: [PATCH v9 08/10] rust: io: introduce `write_reg` and `LocatedRegister`
Posted by Gary Guo 2 weeks, 5 days ago
On Sat Mar 14, 2026 at 1:06 AM GMT, Alexandre Courbot wrote:
> Some I/O types, like fixed address registers, carry their location
> alongside their values. For these types, the regular `Io::write` method
> can lead into repeating the location information twice: once to provide
> the location itself, another time to build the value.
>
> We are also considering supporting making all register values carry
> their full location information for convenience and safety.
>
> Add a new `Io::write_reg` method that takes a single argument
> implementing `LocatedRegister`, a trait that decomposes implementors
> into a `(location, value)` tuple. This allows write operations on fixed
> offset registers to be done while specifying their name only once.
>
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---
>  rust/kernel/io.rs          | 70 ++++++++++++++++++++++++++++++++++++++++++++++
>  rust/kernel/io/register.rs | 35 +++++++++++++++++++++--
>  2 files changed, 103 insertions(+), 2 deletions(-)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index bfea30a9acdf..24e6b48b6582 100644
> --- a/rust/kernel/io.rs
> +++ b/rust/kernel/io.rs
> @@ -17,6 +17,8 @@
>  pub use crate::register;
>  pub use resource::Resource;
>  
> +use register::LocatedRegister;
> +
>  /// Physical address type.
>  ///
>  /// This is a type alias to either `u32` or `u64` depending on the config option
> @@ -473,6 +475,40 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result
>          Ok(())
>      }
>  
> +    /// Generic fallible write of a fully-located register value.
> +    ///
> +    /// # Examples
> +    ///
> +    /// Tuples carrying a location and a value can be used with this method:
> +    ///
> +    /// ```no_run
> +    /// use kernel::io::{
> +    ///     register,
> +    ///     Io,
> +    ///     Mmio,
> +    /// };
> +    ///
> +    /// register! {
> +    ///     FIFO_OUT(u32) @ 0x100 {}
> +    /// }
> +    ///
> +    /// fn do_write_reg(io: &Mmio) -> Result {
> +    ///     // `FIFO_OUT` provides us the location of the write operation.
> +    ///     io.try_write_reg(FIFO_OUT::from(10))

This is not a good example, as I want to make FIFO not generate bitfields in the
future and so people write

    io.write(FIFO_OUT, 10)

Perhaps replace with any other register example that has bitfields..

The version register in the updated doc comment below is a very good example...

Best,
Gary

> +    /// }
> +    /// ```
> +    #[inline(always)]
> +    fn try_write_reg<T, L, V>(&self, value: V) -> Result
> +    where
> +        L: IoLoc<T>,
> +        V: LocatedRegister<Location = L, Value = T>,
> +        Self: IoCapable<L::IoType>,
> +    {
> +        let (location, value) = value.into_io_op();
> +
> +        self.try_write(location, value)
> +    }
> +
>      /// Generic fallible update with runtime bounds check.
>      ///
>      /// Note: this does not perform any synchronization. The caller is responsible for ensuring
> @@ -578,6 +614,40 @@ fn write<T, L>(&self, location: L, value: T)
>          unsafe { self.io_write(io_value, address) }
>      }
>  
> +    /// Generic infallible write of a fully-located register value.
> +    ///
> +    /// # Examples
> +    ///
> +    /// Tuples carrying a location and a value can be used with this method:
> +    ///
> +    /// ```no_run
> +    /// use kernel::io::{
> +    ///     register,
> +    ///     Io,
> +    ///     Mmio,
> +    /// };
> +    ///
> +    /// register! {
> +    ///     FIFO_OUT(u32) @ 0x100 {}
> +    /// }
> +    ///
> +    /// fn do_write_reg(io: &Mmio<0x1000>) {
> +    ///     // `FIFO_OUT` provides us the location of the write operation.
> +    ///     io.write_reg(FIFO_OUT::from(10));
> +    /// }
> +    /// ```
> +    #[inline(always)]
> +    fn write_reg<T, L, V>(&self, value: V)
> +    where
> +        L: IoLoc<T>,
> +        V: LocatedRegister<Location = L, Value = T>,
> +        Self: IoKnownSize + IoCapable<L::IoType>,
> +    {
> +        let (location, value) = value.into_io_op();
> +
> +        self.write(location, value)
> +    }
> +
>      /// Generic infallible update with compile-time bounds check.
>      ///
>      /// Note: this does not perform any synchronization. The caller is responsible for ensuring
> diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
> index 40085953c831..b26dc2400009 100644
> --- a/rust/kernel/io/register.rs
> +++ b/rust/kernel/io/register.rs
> @@ -80,10 +80,10 @@
>  //!     .with_const_minor_revision::<10>()
>  //!     // Run-time value.
>  //!     .with_vendor_id(obtain_vendor_id());
> -//! io.write((), new_boot0);
> +//! io.write_reg(new_boot0);
>  //!
>  //! // Or, build a new value from zero and write it:
> -//! io.write((), BOOT_0::zeroed()
> +//! io.write_reg(BOOT_0::zeroed()
>  //!     .with_const_major_revision::<3>()
>  //!     .with_const_minor_revision::<10>()
>  //!     .with_vendor_id(obtain_vendor_id())
> @@ -379,6 +379,34 @@ fn offset(self) -> usize {
>      }
>  }
>  
> +/// Trait implemented by items that contain both a register value and the absolute I/O location at
> +/// which to write it.
> +///
> +/// Implementors can be used with [`Io::write_reg`](super::Io::write_reg).
> +pub trait LocatedRegister {
> +    /// Register value to write.
> +    type Value: Register;
> +    /// Full location information at which to write the value.
> +    type Location: IoLoc<Self::Value>;
> +
> +    /// Consumes `self` and returns a `(location, value)` tuple describing a valid I/O write
> +    /// operation.
> +    fn into_io_op(self) -> (Self::Location, Self::Value);
> +}
> +
> +impl<T> LocatedRegister for T
> +where
> +    T: FixedRegister,
> +{
> +    type Location = FixedRegisterLoc<Self::Value>;
> +    type Value = T;
> +
> +    #[inline(always)]
> +    fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
> +        (FixedRegisterLoc::new(), self)
> +    }
> +}
> +
>  /// Defines a dedicated type for a register, including getter and setter methods for its fields and
>  /// methods to read and write it from an [`Io`](kernel::io::Io) region.
>  ///
> @@ -433,6 +461,9 @@ fn offset(self) -> usize {
>  /// // The location of fixed offset registers is already contained in their type. Thus, the
>  /// // `location` argument of `Io::write` is technically redundant and can be replaced by `()`.
>  /// io.write((), val2);
> +///
> +/// // Or, the single-argument `Io::write_reg` can be used.
> +/// io.write_reg(val2);
>  /// # }
>  ///
>  /// ```
Re: [PATCH v9 08/10] rust: io: introduce `write_reg` and `LocatedRegister`
Posted by Alexandre Courbot 2 weeks, 4 days ago
On Sat Mar 14, 2026 at 10:56 PM JST, Gary Guo wrote:
> On Sat Mar 14, 2026 at 1:06 AM GMT, Alexandre Courbot wrote:
>> Some I/O types, like fixed address registers, carry their location
>> alongside their values. For these types, the regular `Io::write` method
>> can lead into repeating the location information twice: once to provide
>> the location itself, another time to build the value.
>>
>> We are also considering supporting making all register values carry
>> their full location information for convenience and safety.
>>
>> Add a new `Io::write_reg` method that takes a single argument
>> implementing `LocatedRegister`, a trait that decomposes implementors
>> into a `(location, value)` tuple. This allows write operations on fixed
>> offset registers to be done while specifying their name only once.
>>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>>  rust/kernel/io.rs          | 70 ++++++++++++++++++++++++++++++++++++++++++++++
>>  rust/kernel/io/register.rs | 35 +++++++++++++++++++++--
>>  2 files changed, 103 insertions(+), 2 deletions(-)
>>
>> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
>> index bfea30a9acdf..24e6b48b6582 100644
>> --- a/rust/kernel/io.rs
>> +++ b/rust/kernel/io.rs
>> @@ -17,6 +17,8 @@
>>  pub use crate::register;
>>  pub use resource::Resource;
>>  
>> +use register::LocatedRegister;
>> +
>>  /// Physical address type.
>>  ///
>>  /// This is a type alias to either `u32` or `u64` depending on the config option
>> @@ -473,6 +475,40 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result
>>          Ok(())
>>      }
>>  
>> +    /// Generic fallible write of a fully-located register value.
>> +    ///
>> +    /// # Examples
>> +    ///
>> +    /// Tuples carrying a location and a value can be used with this method:
>> +    ///
>> +    /// ```no_run
>> +    /// use kernel::io::{
>> +    ///     register,
>> +    ///     Io,
>> +    ///     Mmio,
>> +    /// };
>> +    ///
>> +    /// register! {
>> +    ///     FIFO_OUT(u32) @ 0x100 {}
>> +    /// }
>> +    ///
>> +    /// fn do_write_reg(io: &Mmio) -> Result {
>> +    ///     // `FIFO_OUT` provides us the location of the write operation.
>> +    ///     io.try_write_reg(FIFO_OUT::from(10))
>
> This is not a good example, as I want to make FIFO not generate bitfields in the
> future and so people write
>
>     io.write(FIFO_OUT, 10)
>
> Perhaps replace with any other register example that has bitfields..

What is wrong with the example? It demonstrates how we can do a FIFO
register with the current macro.

I was thinking that we can update this example once we have the right
support, but in the meantime this looks useful to me.
Re: [PATCH v9 08/10] rust: io: introduce `write_reg` and `LocatedRegister`
Posted by Danilo Krummrich 2 weeks, 4 days ago
On Sun Mar 15, 2026 at 6:10 AM CET, Alexandre Courbot wrote:
> What is wrong with the example? It demonstrates how we can do a FIFO
> register with the current macro.

FIFO registers are not a go-to example for write_reg() and try_write_reg(), as
the API will change. It is better to choose an example that remains valid.

> I was thinking that we can update this example once we have the right
> support, but in the meantime this looks useful to me.

That'd be fine to do somewhere else, but changing the example of write_reg() and
try_write_reg() to

	io.write(FIFO_OUT, 10)

later on would not make much sense.