[PATCH v7 10/10] RFC: rust: io: allow fixed register values directly in `write`

From: Alexandre Courbot

Date: Tue Feb 24 2026 - 09:29:48 EST


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@xxxxxxxxxx>
---
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