All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/7] rust: io: add read-only and write-only views and registers
@ 2026-10-15 12:39 Daniel Almeida
  2026-10-15 12:39 ` [PATCH 1/7] rust: io: split IoCapable into IoReadable and IoWritable Daniel Almeida
                   ` (6 more replies)
  0 siblings, 7 replies; 15+ messages in thread
From: Daniel Almeida @ 2026-10-15 12:39 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Robin Murphy, Andreas Hindborg,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Bjorn Helgaas,
	Krzysztof Wilczyński, David Airlie, Simona Vetter
  Cc: driver-core, rust-for-linux, linux-kernel, linux-pci, dri-devel,
	Daniel Almeida

This series adds read-only and write-only I/O views, and the #[ro]
and #[wo] attributes to register!. An access in the wrong direction
then does not compile.

This is needed by Tyr, where the doorbell registers are write-only and
the CSF control and output blocks shared with the firmware must only be
read by the driver. Laura's CSF interface series [1] will be the second
user.

Patches 1 and 2 split IoCapable into IoReadable and IoWritable, and
IoCopyable into IoCopyReadable and IoCopyWritable.

Patch 3 adds the ReadOnly and WriteOnly views, returned by
Io::read_only() and Io::write_only(). They wrap any view, and the views
projected from them keep the restriction.

Patch 4 makes the primitive accessors (read32() and friends) project the
view directly instead of going through the generic read and write
methods.

Patch 5 lets an IoLoc carry an access mode, which is applied to the view
projected through it.

Patch 6 adds the #[ro] and #[wo] attributes to register!.

Patch 7 marks the read-only and write-only registers in Tyr. Where
regs.rs does not document a mode, it follows the register permissions
for v10 GPUs in Arm's kbase driver (r54p1).

This is based on drm-rust-next.

[1] https://lore.kernel.org/r/20260915-tyr-interfaces-v1-6-5d28f1f75aca@collabora.com

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
Daniel Almeida (7):
      rust: io: split IoCapable into IoReadable and IoWritable
      rust: io: split IoCopyable into IoCopyReadable and IoCopyWritable
      rust: io: add read-only and write-only views
      rust: io: project primitive accessors directly
      rust: io: let I/O locations carry an access mode
      rust: io: register: add `#[ro]` and `#[wo]` attributes
      drm/tyr: mark read-only and write-only registers

 drivers/gpu/drm/tyr/regs.rs |  77 ++++++++
 rust/kernel/dma.rs          |  29 +--
 rust/kernel/io.rs           | 416 +++++++++++++++++++++++++++++++++-----------
 rust/kernel/io/access.rs    |  76 ++++++++
 rust/kernel/io/register.rs  |  71 +++++++-
 rust/kernel/pci/io.rs       |  10 +-
 rust/macros/io/register.rs  |  34 +++-
 7 files changed, 595 insertions(+), 118 deletions(-)
---
base-commit: 5a1b7121a6c4db6c0239d3ac3fd1c2bb33a40025
change-id: 20261015-io-access-modes-19ac2b0aa238

Best regards,
--  
Daniel Almeida <daniel.almeida@collabora.com>


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH 1/7] rust: io: split IoCapable into IoReadable and IoWritable
  2026-10-15 12:39 [PATCH 0/7] rust: io: add read-only and write-only views and registers Daniel Almeida
@ 2026-10-15 12:39 ` Daniel Almeida
  2026-10-15 12:46   ` sashiko-bot
  2026-10-15 12:39 ` [PATCH 2/7] rust: io: split IoCopyable into IoCopyReadable and IoCopyWritable Daniel Almeida
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Daniel Almeida @ 2026-10-15 12:39 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Robin Murphy, Andreas Hindborg,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Bjorn Helgaas,
	Krzysztof Wilczyński, David Airlie, Simona Vetter
  Cc: driver-core, rust-for-linux, linux-kernel, linux-pci, dri-devel,
	Daniel Almeida

`IoCapable<T>` provides both `io_read` and `io_write`, so a backend that
supports accesses of type `T` supports them in both directions, and every
(non-copy) `Io` method that reads or writes a value requires both. There
is no way to express a backend, or a view, that can only be read or only
be written.

Thus, replace `IoCapable<T>` with an `IoReadable<T>` trait holding
`io_read` and an `IoWritable<T>` trait holding `io_write`, and implement
both for all existing backends. The non-copy `Io` methods that read a
value now require `IoReadable` and those that write one require
`IoWritable`. `update` and `try_update` read and then write, so they
require both.

The copy methods use `IoCopyable` and are not changed here.

No functional change intended. This prepares for read-only and write-only
views, added in a following patch.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/kernel/dma.rs    |  12 ++++--
 rust/kernel/io.rs     | 101 +++++++++++++++++++++++++++++---------------------
 rust/kernel/pci/io.rs |  10 +++--
 3 files changed, 75 insertions(+), 48 deletions(-)

diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs
index 4ce914b7d1da..7216a78d38c9 100644
--- a/rust/kernel/dma.rs
+++ b/rust/kernel/dma.rs
@@ -17,8 +17,9 @@
     io::{
         IoBackend,
         IoBase,
-        IoCapable,
         IoCopyable,
+        IoReadable,
+        IoWritable,
         SysMem,
         SysMemBackend, //
     },
@@ -1180,15 +1181,20 @@ unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
     }
 }
 
-impl<T> IoCapable<T> for CoherentIoBackend
+impl<T> IoReadable<T> for CoherentIoBackend
 where
-    SysMemBackend: IoCapable<T>,
+    SysMemBackend: IoReadable<T>,
 {
     #[inline]
     fn io_read<'a>(view: Self::View<'a, T>) -> T {
         SysMemBackend::io_read(view.cpu_addr)
     }
+}
 
+impl<T> IoWritable<T> for CoherentIoBackend
+where
+    SysMemBackend: IoWritable<T>,
+{
     #[inline]
     fn io_write<'a>(view: Self::View<'a, T>, value: T) {
         SysMemBackend::io_write(view.cpu_addr, value)
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index de8ef8e2aec4..fd5dd5f6bda4 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -247,9 +247,9 @@ fn io_view_as_repr<'a, IO: Io<'a, Target = T>, T: AsRepr>(
 /// backends (e.g. MMIO, PCI config space, etc.).
 ///
 /// The base trait only defines the projection operations; which I/O methods are available depends
-/// on which [`IoCapable<T>`] traits are implemented for the type. For example, for MMIO regions,
-/// all widths (u8, u16, u32, and u64 on 64-bit systems) are typically supported. For PCI
-/// configuration space, u8, u16, and u32 are supported but u64 is not.
+/// on which [`IoReadable<T>`] and [`IoWritable<T>`] traits are implemented for the type. For
+/// example, for MMIO regions, all widths (u8, u16, u32, and u64 on 64-bit systems) are typically
+/// supported. For PCI configuration space, u8, u16, and u32 are supported but u64 is not.
 ///
 /// This trait is separate from the `Io` trait as multiple different I/O types may share the same
 /// operation.
@@ -276,18 +276,24 @@ unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
     ) -> Self::View<'a, U>;
 }
 
-/// Trait indicating that an I/O backend supports operations of a certain type and providing an
-/// implementation for these operations.
+/// Trait indicating that an I/O backend supports reads of a certain type and providing an
+/// implementation for these reads.
 ///
 /// Different I/O backends can implement this trait to expose only the operations they support.
 ///
-/// For example, a PCI configuration space may implement `IoCapable<u8>`, `IoCapable<u16>`,
-/// and `IoCapable<u32>`, but not `IoCapable<u64>`, while an MMIO region on a 64-bit
+/// For example, a PCI configuration space may implement `IoReadable<u8>`, `IoReadable<u16>`,
+/// and `IoReadable<u32>`, but not `IoReadable<u64>`, while an MMIO region on a 64-bit
 /// system might implement all four.
-pub trait IoCapable<T>: IoBackend {
+pub trait IoReadable<T>: IoBackend {
     /// Performs an I/O read of type `T` at `view` and returns the result.
     fn io_read<'a>(view: Self::View<'a, T>) -> T;
+}
 
+/// Trait indicating that an I/O backend supports writes of a certain type and providing an
+/// implementation for these writes.
+///
+/// Like [`IoReadable`], it is implemented for each type the backend supports.
+pub trait IoWritable<T>: IoBackend {
     /// Performs an I/O write of `value` at `view`.
     fn io_write<'a>(view: Self::View<'a, T>, value: T);
 }
@@ -548,7 +554,7 @@ fn try_cast<U>(self) -> Result<<Self::Backend as IoBackend>::View<'a, U>>
     fn read_val(self) -> Self::Target
     where
         Self::Target: AsReprMut,
-        Self::Backend: IoCapable<<Self::Target as AsRepr>::Repr>,
+        Self::Backend: IoReadable<<Self::Target as AsRepr>::Repr>,
     {
         Self::Target::from_repr(Self::Backend::io_read(io_view_as_repr(self)))
     }
@@ -570,7 +576,7 @@ fn read_val(self) -> Self::Target
     fn write_val(self, value: Self::Target)
     where
         Self::Target: AsRepr,
-        Self::Backend: IoCapable<<Self::Target as AsRepr>::Repr>,
+        Self::Backend: IoWritable<<Self::Target as AsRepr>::Repr>,
     {
         Self::Backend::io_write(io_view_as_repr(self), Self::Target::into_repr(value))
     }
@@ -695,7 +701,7 @@ fn copy_to_slice(self, data: &mut [u8])
     fn try_read8(self, offset: usize) -> Result<u8>
     where
         usize: IoLoc<Self::Target, u8>,
-        Self::Backend: IoCapable<u8>,
+        Self::Backend: IoReadable<u8>,
     {
         self.try_read(offset)
     }
@@ -705,7 +711,7 @@ fn try_read8(self, offset: usize) -> Result<u8>
     fn try_read16(self, offset: usize) -> Result<u16>
     where
         usize: IoLoc<Self::Target, u16>,
-        Self::Backend: IoCapable<u16>,
+        Self::Backend: IoReadable<u16>,
     {
         self.try_read(offset)
     }
@@ -715,7 +721,7 @@ fn try_read16(self, offset: usize) -> Result<u16>
     fn try_read32(self, offset: usize) -> Result<u32>
     where
         usize: IoLoc<Self::Target, u32>,
-        Self::Backend: IoCapable<u32>,
+        Self::Backend: IoReadable<u32>,
     {
         self.try_read(offset)
     }
@@ -725,7 +731,7 @@ fn try_read32(self, offset: usize) -> Result<u32>
     fn try_read64(self, offset: usize) -> Result<u64>
     where
         usize: IoLoc<Self::Target, u64>,
-        Self::Backend: IoCapable<u64>,
+        Self::Backend: IoReadable<u64>,
     {
         self.try_read(offset)
     }
@@ -735,7 +741,7 @@ fn try_read64(self, offset: usize) -> Result<u64>
     fn try_write8(self, value: u8, offset: usize) -> Result
     where
         usize: IoLoc<Self::Target, u8>,
-        Self::Backend: IoCapable<u8>,
+        Self::Backend: IoWritable<u8>,
     {
         self.try_write(offset, value)
     }
@@ -745,7 +751,7 @@ fn try_write8(self, value: u8, offset: usize) -> Result
     fn try_write16(self, value: u16, offset: usize) -> Result
     where
         usize: IoLoc<Self::Target, u16>,
-        Self::Backend: IoCapable<u16>,
+        Self::Backend: IoWritable<u16>,
     {
         self.try_write(offset, value)
     }
@@ -755,7 +761,7 @@ fn try_write16(self, value: u16, offset: usize) -> Result
     fn try_write32(self, value: u32, offset: usize) -> Result
     where
         usize: IoLoc<Self::Target, u32>,
-        Self::Backend: IoCapable<u32>,
+        Self::Backend: IoWritable<u32>,
     {
         self.try_write(offset, value)
     }
@@ -765,7 +771,7 @@ fn try_write32(self, value: u32, offset: usize) -> Result
     fn try_write64(self, value: u64, offset: usize) -> Result
     where
         usize: IoLoc<Self::Target, u64>,
-        Self::Backend: IoCapable<u64>,
+        Self::Backend: IoWritable<u64>,
     {
         self.try_write(offset, value)
     }
@@ -777,7 +783,7 @@ fn try_write64(self, value: u64, offset: usize) -> Result
     fn read8(self, offset: usize) -> u8
     where
         usize: IoLoc<Self::Target, u8>,
-        Self::Backend: IoCapable<u8>,
+        Self::Backend: IoReadable<u8>,
     {
         self.read(offset)
     }
@@ -789,7 +795,7 @@ fn read8(self, offset: usize) -> u8
     fn read16(self, offset: usize) -> u16
     where
         usize: IoLoc<Self::Target, u16>,
-        Self::Backend: IoCapable<u16>,
+        Self::Backend: IoReadable<u16>,
     {
         self.read(offset)
     }
@@ -801,7 +807,7 @@ fn read16(self, offset: usize) -> u16
     fn read32(self, offset: usize) -> u32
     where
         usize: IoLoc<Self::Target, u32>,
-        Self::Backend: IoCapable<u32>,
+        Self::Backend: IoReadable<u32>,
     {
         self.read(offset)
     }
@@ -813,7 +819,7 @@ fn read32(self, offset: usize) -> u32
     fn read64(self, offset: usize) -> u64
     where
         usize: IoLoc<Self::Target, u64>,
-        Self::Backend: IoCapable<u64>,
+        Self::Backend: IoReadable<u64>,
     {
         self.read(offset)
     }
@@ -825,7 +831,7 @@ fn read64(self, offset: usize) -> u64
     fn write8(self, value: u8, offset: usize)
     where
         usize: IoLoc<Self::Target, u8>,
-        Self::Backend: IoCapable<u8>,
+        Self::Backend: IoWritable<u8>,
     {
         self.write(offset, value)
     }
@@ -837,7 +843,7 @@ fn write8(self, value: u8, offset: usize)
     fn write16(self, value: u16, offset: usize)
     where
         usize: IoLoc<Self::Target, u16>,
-        Self::Backend: IoCapable<u16>,
+        Self::Backend: IoWritable<u16>,
     {
         self.write(offset, value)
     }
@@ -849,7 +855,7 @@ fn write16(self, value: u16, offset: usize)
     fn write32(self, value: u32, offset: usize)
     where
         usize: IoLoc<Self::Target, u32>,
-        Self::Backend: IoCapable<u32>,
+        Self::Backend: IoWritable<u32>,
     {
         self.write(offset, value)
     }
@@ -861,7 +867,7 @@ fn write32(self, value: u32, offset: usize)
     fn write64(self, value: u64, offset: usize)
     where
         usize: IoLoc<Self::Target, u64>,
-        Self::Backend: IoCapable<u64>,
+        Self::Backend: IoWritable<u64>,
     {
         self.write(offset, value)
     }
@@ -894,7 +900,7 @@ fn try_read<T, L>(self, location: L) -> Result<T>
     where
         T: AsReprMut,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoCapable<<T as AsRepr>::Repr>,
+        Self::Backend: IoReadable<<T as AsRepr>::Repr>,
     {
         Ok(io_read!(self, try: location))
     }
@@ -927,7 +933,7 @@ fn try_write<T, L>(self, location: L, value: T) -> Result
     where
         T: AsRepr,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoCapable<<T as AsRepr>::Repr>,
+        Self::Backend: IoWritable<<T as AsRepr>::Repr>,
     {
         io_write!(self, try: location, value);
         Ok(())
@@ -973,7 +979,7 @@ fn try_write_reg<T, L, V>(self, value: V) -> Result
         T: AsRepr,
         L: IoLoc<Self::Target, T>,
         V: LocatedRegister<Self::Target, Location = L, Value = T>,
-        Self::Backend: IoCapable<<T as AsRepr>::Repr>,
+        Self::Backend: IoWritable<<T as AsRepr>::Repr>,
     {
         let (location, value) = value.into_io_op();
 
@@ -1007,7 +1013,7 @@ fn try_update<T, L, F>(self, location: L, f: F) -> Result
     where
         T: AsReprMut,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoCapable<<T as AsRepr>::Repr>,
+        Self::Backend: IoReadable<<T as AsRepr>::Repr> + IoWritable<<T as AsRepr>::Repr>,
         F: FnOnce(T) -> T,
     {
         let view = io_project!(self, try: location);
@@ -1041,7 +1047,7 @@ fn read<T, L>(self, location: L) -> T
     where
         T: AsReprMut,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoCapable<<T as AsRepr>::Repr>,
+        Self::Backend: IoReadable<<T as AsRepr>::Repr>,
     {
         io_read!(self, build: location)
     }
@@ -1072,7 +1078,7 @@ fn write<T, L>(self, location: L, value: T)
     where
         T: AsRepr,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoCapable<<T as AsRepr>::Repr>,
+        Self::Backend: IoWritable<<T as AsRepr>::Repr>,
     {
         io_write!(self, build: location, value);
     }
@@ -1116,7 +1122,7 @@ fn write_reg<T, L, V>(self, value: V)
         T: AsRepr,
         L: IoLoc<Self::Target, T>,
         V: LocatedRegister<Self::Target, Location = L, Value = T>,
-        Self::Backend: IoCapable<<T as AsRepr>::Repr>,
+        Self::Backend: IoWritable<<T as AsRepr>::Repr>,
     {
         let (location, value) = value.into_io_op();
 
@@ -1150,7 +1156,7 @@ fn update<T, L, F>(self, location: L, f: F)
     where
         T: AsReprMut,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoCapable<<T as AsRepr>::Repr>,
+        Self::Backend: IoReadable<<T as AsRepr>::Repr> + IoWritable<<T as AsRepr>::Repr>,
         F: FnOnce(T) -> T,
     {
         let view = io_project!(self, build: location);
@@ -1237,17 +1243,20 @@ unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
     }
 }
 
-/// Implements [`IoCapable`] on `$backend` for `$ty` using `$read_fn` and `$write_fn`.
+/// Implements [`IoReadable`] and [`IoWritable`] on `$backend` for `$ty` using `$read_fn` and
+/// `$write_fn`.
 macro_rules! impl_mmio_io_capable {
     ($backend: ident, $ty:ty, $read_fn:ident, $write_fn:ident) => {
-        impl IoCapable<$ty> for $backend {
+        impl IoReadable<$ty> for $backend {
             #[inline]
             fn io_read(view: <$backend as IoBackend>::View<'_, $ty>) -> $ty {
                 // SAFETY: `$backend::as_ptr(view)` is a valid pointer for MMIO operations for both
                 // `MmioBackend` and `RelaxedMmioBackend`.
                 unsafe { bindings::$read_fn($backend::as_ptr(view).cast_const().cast()) }
             }
+        }
 
+        impl IoWritable<$ty> for $backend {
             #[inline]
             fn io_write(view: <$backend as IoBackend>::View<'_, $ty>, value: $ty) {
                 // SAFETY: `$backend::as_ptr(view)` is a valid pointer for MMIO operations for both
@@ -1396,11 +1405,11 @@ unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
     }
 }
 
-/// Implements [`IoCapable`] on `SysMemBackend` for `$ty` using `read_volatile` and
-/// `write_volatile`.
+/// Implements [`IoReadable`] and [`IoWritable`] on `SysMemBackend` for `$ty` using
+/// `read_volatile` and `write_volatile`.
 macro_rules! impl_sysmem_io_capable {
     ($ty:ty) => {
-        impl IoCapable<$ty> for SysMemBackend {
+        impl IoReadable<$ty> for SysMemBackend {
             #[inline]
             fn io_read(view: SysMem<'_, $ty>) -> $ty {
                 // SAFETY:
@@ -1411,7 +1420,9 @@ fn io_read(view: SysMem<'_, $ty>) -> $ty {
                 // - The macro is only used on primitives so all bit patterns are valid.
                 unsafe { view.ptr.read_volatile() }
             }
+        }
 
+        impl IoWritable<$ty> for SysMemBackend {
             #[inline]
             fn io_write(view: SysMem<'_, $ty>, value: $ty) {
                 // SAFETY:
@@ -1601,10 +1612,10 @@ unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
     }
 }
 
-impl<T> IoCapable<T> for IoSysMapBackend
+impl<T> IoReadable<T> for IoSysMapBackend
 where
-    MmioBackend: IoCapable<T>,
-    SysMemBackend: IoCapable<T>,
+    MmioBackend: IoReadable<T>,
+    SysMemBackend: IoReadable<T>,
 {
     #[inline]
     fn io_read(view: Self::View<'_, T>) -> T {
@@ -1613,7 +1624,13 @@ fn io_read(view: Self::View<'_, T>) -> T {
             IoSysMap::Sys(r) => SysMemBackend::io_read(r),
         }
     }
+}
 
+impl<T> IoWritable<T> for IoSysMapBackend
+where
+    MmioBackend: IoWritable<T>,
+    SysMemBackend: IoWritable<T>,
+{
     #[inline]
     fn io_write<'a>(view: Self::View<'a, T>, value: T) {
         match view {
diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs
index 953e16735c6e..426e914e15c2 100644
--- a/rust/kernel/pci/io.rs
+++ b/rust/kernel/pci/io.rs
@@ -10,7 +10,8 @@
     io::{
         IoBackend,
         IoBase,
-        IoCapable,
+        IoReadable,
+        IoWritable,
         Mmio,
         MmioBackend,
         MmioRaw,
@@ -111,10 +112,11 @@ unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
     }
 }
 
-/// Implements [`IoCapable`] on [`ConfigSpace`] for `$ty` using `$read_fn` and `$write_fn`.
+/// Implements [`IoReadable`] and [`IoWritable`] on [`ConfigSpace`] for `$ty` using `$read_fn`
+/// and `$write_fn`.
 macro_rules! impl_config_space_io_capable {
     ($ty:ty, $read_fn:ident, $write_fn:ident) => {
-        impl IoCapable<$ty> for ConfigSpaceBackend {
+        impl IoReadable<$ty> for ConfigSpaceBackend {
             fn io_read(view: ConfigSpace<'_, $ty>) -> $ty {
                 // CAST: The offset is cast to `i32` because the C functions expect a 32-bit
                 // signed offset parameter. PCI configuration space size is at most 4096 bytes,
@@ -128,7 +130,9 @@ fn io_read(view: ConfigSpace<'_, $ty>) -> $ty {
                 let _ = unsafe { bindings::$read_fn(view.pdev.as_raw(), addr, &mut val) };
                 val
             }
+        }
 
+        impl IoWritable<$ty> for ConfigSpaceBackend {
             fn io_write(view: ConfigSpace<'_, $ty>, value: $ty) {
                 // CAST: The offset is cast to `i32` because the C functions expect a 32-bit
                 // signed offset parameter. PCI configuration space size is at most 4096 bytes,

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 2/7] rust: io: split IoCopyable into IoCopyReadable and IoCopyWritable
  2026-10-15 12:39 [PATCH 0/7] rust: io: add read-only and write-only views and registers Daniel Almeida
  2026-10-15 12:39 ` [PATCH 1/7] rust: io: split IoCapable into IoReadable and IoWritable Daniel Almeida
@ 2026-10-15 12:39 ` Daniel Almeida
  2026-10-15 12:44   ` sashiko-bot
  2026-10-15 12:39 ` [PATCH 3/7] rust: io: add read-only and write-only views Daniel Almeida
                   ` (4 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Daniel Almeida @ 2026-10-15 12:39 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Robin Murphy, Andreas Hindborg,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Bjorn Helgaas,
	Krzysztof Wilczyński, David Airlie, Simona Vetter
  Cc: driver-core, rust-for-linux, linux-kernel, linux-pci, dri-devel,
	Daniel Almeida

`IoCopyable` provides both `copy_from_io` and `copy_to_io`, so a backend
that supports copies supports them in both directions, and all four copy
methods of `Io` require both.

Thus, as done for `IoCapable` in the previous patch, replace `IoCopyable`
with an `IoCopyReadable` trait holding `copy_from_io` and `copy_read`, and
an `IoCopyWritable` trait holding `copy_to_io` and `copy_write`, and
implement both for every backend that implemented `IoCopyable`.
`copy_read` and `copy_to_slice`, which read from I/O memory, now require
`IoCopyReadable`, and `copy_write` and `copy_from_slice`, which write to
it, require `IoCopyWritable`.

No functional change intended.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/kernel/dma.rs | 17 +++++++-----
 rust/kernel/io.rs  | 77 ++++++++++++++++++++++++++++++------------------------
 2 files changed, 53 insertions(+), 41 deletions(-)

diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs
index 7216a78d38c9..c57026368ab8 100644
--- a/rust/kernel/dma.rs
+++ b/rust/kernel/dma.rs
@@ -17,7 +17,8 @@
     io::{
         IoBackend,
         IoBase,
-        IoCopyable,
+        IoCopyReadable,
+        IoCopyWritable,
         IoReadable,
         IoWritable,
         SysMem,
@@ -1201,7 +1202,7 @@ fn io_write<'a>(view: Self::View<'a, T>, value: T) {
     }
 }
 
-impl IoCopyable for CoherentIoBackend {
+impl IoCopyReadable for CoherentIoBackend {
     #[inline]
     unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
         // SAFETY: Per safety requirement.
@@ -1209,14 +1210,16 @@ unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
     }
 
     #[inline]
-    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
-        // SAFETY: Per safety requirement.
-        unsafe { SysMemBackend::copy_to_io(view.cpu_addr, buffer) }
+    fn copy_read<T: zerocopy::FromBytes>(view: Self::View<'_, T>) -> T {
+        SysMemBackend::copy_read(view.cpu_addr)
     }
+}
 
+impl IoCopyWritable for CoherentIoBackend {
     #[inline]
-    fn copy_read<T: zerocopy::FromBytes>(view: Self::View<'_, T>) -> T {
-        SysMemBackend::copy_read(view.cpu_addr)
+    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
+        // SAFETY: Per safety requirement.
+        unsafe { SysMemBackend::copy_to_io(view.cpu_addr, buffer) }
     }
 
     #[inline]
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index fd5dd5f6bda4..ae60099fd4c3 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -298,8 +298,8 @@ pub trait IoWritable<T>: IoBackend {
     fn io_write<'a>(view: Self::View<'a, T>, value: T);
 }
 
-/// Trait indicating that an I/O backend supports memory copy operations.
-pub trait IoCopyable: IoBackend {
+/// Trait indicating that an I/O backend supports memory copy operations from I/O.
+pub trait IoCopyReadable: IoBackend {
     /// Copy contents of `view` to `buffer`.
     ///
     /// # Safety
@@ -308,14 +308,6 @@ pub trait IoCopyable: IoBackend {
     /// - `buffer` should not overlap with `view`.
     unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8);
 
-    /// Copy contents from `buffer` to `view`.
-    ///
-    /// # Safety
-    ///
-    /// - `buffer` is valid for volatile read for `view.size()` bytes.
-    /// - `buffer` should not overlap with `view`.
-    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8);
-
     /// Copy from `view` and return the value.
     #[inline]
     fn copy_read<T: FromBytes>(view: Self::View<'_, T>) -> T {
@@ -337,6 +329,17 @@ fn copy_read<T: FromBytes>(view: Self::View<'_, T>) -> T {
         // SAFETY: `T: FromBytes` guarantee that all bit patterns are valid.
         unsafe { buf.assume_init() }
     }
+}
+
+/// Trait indicating that an I/O backend supports memory copy operations to I/O.
+pub trait IoCopyWritable: IoBackend {
+    /// Copy contents from `buffer` to `view`.
+    ///
+    /// # Safety
+    ///
+    /// - `buffer` is valid for volatile read for `view.size()` bytes.
+    /// - `buffer` should not overlap with `view`.
+    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8);
 
     /// Copy `value` to `view`.
     ///
@@ -602,7 +605,7 @@ fn write_val(self, value: Self::Target)
     #[inline]
     fn copy_read(self) -> Self::Target
     where
-        Self::Backend: IoCopyable,
+        Self::Backend: IoCopyReadable,
         Self::Target: Sized + FromBytes,
     {
         Self::Backend::copy_read(self.as_view())
@@ -629,7 +632,7 @@ fn copy_read(self) -> Self::Target
     #[inline]
     fn copy_write(self, value: Self::Target)
     where
-        Self::Backend: IoCopyable,
+        Self::Backend: IoCopyWritable,
         Self::Target: Sized + IntoBytes,
     {
         Self::Backend::copy_write(self.as_view(), value);
@@ -654,7 +657,7 @@ fn copy_write(self, value: Self::Target)
     #[inline]
     fn copy_from_slice(self, data: &[u8])
     where
-        Self::Backend: IoCopyable,
+        Self::Backend: IoCopyWritable,
         Self: Io<'a, Target = [u8]>,
     {
         assert_eq!(self.len(), data.len());
@@ -685,7 +688,7 @@ fn copy_from_slice(self, data: &[u8])
     #[inline]
     fn copy_to_slice(self, data: &mut [u8])
     where
-        Self::Backend: IoCopyable,
+        Self::Backend: IoCopyReadable,
         Self: Io<'a, Target = [u8]>,
     {
         assert_eq!(self.len(), data.len());
@@ -1275,7 +1278,7 @@ fn io_write(view: <$backend as IoBackend>::View<'_, $ty>, value: $ty) {
 #[cfg(CONFIG_64BIT)]
 impl_mmio_io_capable!(MmioBackend, u64, readq, writeq);
 
-impl IoCopyable for MmioBackend {
+impl IoCopyReadable for MmioBackend {
     #[inline]
     unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
         // SAFETY:
@@ -1285,7 +1288,9 @@ unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
             bindings::memcpy_fromio(buffer.cast(), view.ptr.cast(), view.size());
         }
     }
+}
 
+impl IoCopyWritable for MmioBackend {
     #[inline]
     unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
         // SAFETY:
@@ -1442,7 +1447,7 @@ fn io_write(view: SysMem<'_, $ty>, value: $ty) {
 #[cfg(CONFIG_64BIT)]
 impl_sysmem_io_capable!(u64);
 
-impl IoCopyable for SysMemBackend {
+impl IoCopyReadable for SysMemBackend {
     #[inline]
     unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
         // Use `bindings::memcpy` instead of `copy_nonoverlapping` for volatile.
@@ -1452,15 +1457,6 @@ unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
         unsafe { bindings::memcpy(buffer.cast(), view.ptr.cast(), view.ptr.len()) };
     }
 
-    #[inline]
-    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
-        // Use `bindings::memcpy` instead of `copy_nonoverlapping` for volatile.
-        // SAFETY:
-        // - `view.ptr` is in CPU address space and valid for write.
-        // - `buffer` is valid for read for `view.size()` bytes which is equal to `view.ptr.len()`.
-        unsafe { bindings::memcpy(view.ptr.cast(), buffer.cast(), view.ptr.len()) };
-    }
-
     #[inline]
     fn copy_read<T: FromBytes>(view: Self::View<'_, T>) -> T {
         // SAFETY:
@@ -1471,6 +1467,17 @@ fn copy_read<T: FromBytes>(view: Self::View<'_, T>) -> T {
         // - `T: FromBytes` so all bit patterns are valid.
         unsafe { view.ptr.read_volatile() }
     }
+}
+
+impl IoCopyWritable for SysMemBackend {
+    #[inline]
+    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
+        // Use `bindings::memcpy` instead of `copy_nonoverlapping` for volatile.
+        // SAFETY:
+        // - `view.ptr` is in CPU address space and valid for write.
+        // - `buffer` is valid for read for `view.size()` bytes which is equal to `view.ptr.len()`.
+        unsafe { bindings::memcpy(view.ptr.cast(), buffer.cast(), view.ptr.len()) };
+    }
 
     #[inline]
     fn copy_write<T: IntoBytes>(view: Self::View<'_, T>, value: T) {
@@ -1640,7 +1647,7 @@ fn io_write<'a>(view: Self::View<'a, T>, value: T) {
     }
 }
 
-impl IoCopyable for IoSysMapBackend {
+impl IoCopyReadable for IoSysMapBackend {
     #[inline]
     unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
         match view {
@@ -1652,20 +1659,22 @@ unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
     }
 
     #[inline]
-    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
+    fn copy_read<T: FromBytes>(view: Self::View<'_, T>) -> T {
         match view {
-            // SAFETY: Per safety requirement.
-            IoSysMap::Io(l) => unsafe { MmioBackend::copy_to_io(l, buffer) },
-            // SAFETY: Per safety requirement.
-            IoSysMap::Sys(r) => unsafe { SysMemBackend::copy_to_io(r, buffer) },
+            IoSysMap::Io(l) => MmioBackend::copy_read(l),
+            IoSysMap::Sys(r) => SysMemBackend::copy_read(r),
         }
     }
+}
 
+impl IoCopyWritable for IoSysMapBackend {
     #[inline]
-    fn copy_read<T: FromBytes>(view: Self::View<'_, T>) -> T {
+    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
         match view {
-            IoSysMap::Io(l) => MmioBackend::copy_read(l),
-            IoSysMap::Sys(r) => SysMemBackend::copy_read(r),
+            // SAFETY: Per safety requirement.
+            IoSysMap::Io(l) => unsafe { MmioBackend::copy_to_io(l, buffer) },
+            // SAFETY: Per safety requirement.
+            IoSysMap::Sys(r) => unsafe { SysMemBackend::copy_to_io(r, buffer) },
         }
     }
 

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 3/7] rust: io: add read-only and write-only views
  2026-10-15 12:39 [PATCH 0/7] rust: io: add read-only and write-only views and registers Daniel Almeida
  2026-10-15 12:39 ` [PATCH 1/7] rust: io: split IoCapable into IoReadable and IoWritable Daniel Almeida
  2026-10-15 12:39 ` [PATCH 2/7] rust: io: split IoCopyable into IoCopyReadable and IoCopyWritable Daniel Almeida
@ 2026-10-15 12:39 ` Daniel Almeida
  2026-10-15 12:44   ` sashiko-bot
  2026-10-15 12:39 ` [PATCH 4/7] rust: io: project primitive accessors directly Daniel Almeida
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Daniel Almeida @ 2026-10-15 12:39 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Robin Murphy, Andreas Hindborg,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Bjorn Helgaas,
	Krzysztof Wilczyński, David Airlie, Simona Vetter
  Cc: driver-core, rust-for-linux, linux-kernel, linux-pci, dri-devel,
	Daniel Almeida

An I/O view can always be both read and written, as long as its backend
supports both directions. The I/O API has no view that only allows the
accesses the hardware or firmware permits, such as a status register
that must not be written or a doorbell that must not be read.

Add `ReadOnly` and `WriteOnly`, which wrap a view of any backend, and
their backends `ReadOnlyBackend` and `WriteOnlyBackend`. A read-only
backend implements `IoReadable` and `IoCopyReadable` where the wrapped
backend does, by forwarding to it, and implements neither `IoWritable`
nor `IoCopyWritable`, so the write methods of `Io` do not compile on a
`ReadOnly` view. `WriteOnly` is the converse. Views projected from a
wrapped view stay wrapped, and the wrapped view cannot be retrieved.

Add `Io::read_only` and `Io::write_only` to create them from any view or
I/O handle.

There are no users yet. Register locations with an access mode will be
the first, in a following patch.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/kernel/io.rs | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 176 insertions(+)

diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index ae60099fd4c3..03af0590b0f4 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -540,6 +540,60 @@ fn try_cast<U>(self) -> Result<<Self::Backend as IoBackend>::View<'a, U>>
         Ok(unsafe { Self::Backend::project_view(view, ptr.cast()) })
     }
 
+    /// Returns a view of this I/O region that can only be read.
+    ///
+    /// The returned view, and every view projected from it, provides the read methods of [`Io`]
+    /// and none of the write methods.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use kernel::io::{
+    ///     Io,
+    ///     Mmio,
+    ///     ReadOnly,
+    ///     Region,
+    /// };
+    ///
+    /// fn do_reads(io: Mmio<'_, Region<0x100>>) -> u32 {
+    ///     let ro: ReadOnly<Mmio<'_, Region<0x100>>> = io.read_only();
+    ///
+    ///     // Reads work as on `io`. A write such as `ro.write32(0, 0x10)` does not compile.
+    ///     ro.read32(0x10)
+    /// }
+    /// ```
+    #[inline]
+    fn read_only(self) -> ReadOnly<<Self::Backend as IoBackend>::View<'a, Self::Target>> {
+        ReadOnly(self.as_view())
+    }
+
+    /// Returns a view of this I/O region that can only be written.
+    ///
+    /// The returned view, and every view projected from it, provides the write methods of [`Io`]
+    /// and none of the read methods.
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// use kernel::io::{
+    ///     Io,
+    ///     Mmio,
+    ///     Region,
+    ///     WriteOnly,
+    /// };
+    ///
+    /// fn do_writes(io: Mmio<'_, Region<0x100>>) {
+    ///     let wo: WriteOnly<Mmio<'_, Region<0x100>>> = io.write_only();
+    ///
+    ///     // Writes work as on `io`. A read such as `wo.read32(0x10)` does not compile.
+    ///     wo.write32(1, 0x10);
+    /// }
+    /// ```
+    #[inline]
+    fn write_only(self) -> WriteOnly<<Self::Backend as IoBackend>::View<'a, Self::Target>> {
+        WriteOnly(self.as_view())
+    }
+
     /// Read a value from I/O.
     ///
     /// This only works for primitives supported by the I/O backend.
@@ -1697,6 +1751,128 @@ fn as_view(self) -> IoSysMap<'a, T> {
     }
 }
 
+/// A view that can only be read.
+///
+/// It wraps a view `V` of another I/O backend and provides the read methods of [`Io`] for it, but
+/// none of the write methods. Views projected from it are read-only too, and the wrapped view
+/// cannot be retrieved.
+///
+/// See [`Io::read_only`] for a usage example.
+#[derive(Clone, Copy)]
+pub struct ReadOnly<V>(V);
+
+/// I/O Backend for [`ReadOnly`] views of backend `B`.
+pub struct ReadOnlyBackend<B>(PhantomData<B>);
+
+impl<B: IoBackend> IoBackend for ReadOnlyBackend<B> {
+    type View<'a, T: ?Sized + KnownSize> = ReadOnly<B::View<'a, T>>;
+
+    #[inline]
+    fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T {
+        B::as_ptr(view.0)
+    }
+
+    #[inline]
+    unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
+        view: Self::View<'a, T>,
+        ptr: *mut U,
+    ) -> Self::View<'a, U> {
+        // SAFETY: Per safety requirement.
+        ReadOnly(unsafe { B::project_view(view.0, ptr) })
+    }
+}
+
+impl<'a, V: IoBase<'a>> IoBase<'a> for ReadOnly<V> {
+    type Backend = ReadOnlyBackend<V::Backend>;
+    type Target = V::Target;
+
+    #[inline]
+    fn as_view(self) -> <Self::Backend as IoBackend>::View<'a, Self::Target> {
+        ReadOnly(self.0.as_view())
+    }
+}
+
+impl<B: IoReadable<T>, T> IoReadable<T> for ReadOnlyBackend<B> {
+    #[inline]
+    fn io_read(view: Self::View<'_, T>) -> T {
+        B::io_read(view.0)
+    }
+}
+
+impl<B: IoCopyReadable> IoCopyReadable for ReadOnlyBackend<B> {
+    #[inline]
+    unsafe fn copy_from_io(view: Self::View<'_, [u8]>, buffer: *mut u8) {
+        // SAFETY: Per safety requirement.
+        unsafe { B::copy_from_io(view.0, buffer) }
+    }
+
+    #[inline]
+    fn copy_read<T: FromBytes>(view: Self::View<'_, T>) -> T {
+        B::copy_read(view.0)
+    }
+}
+
+/// A view that can only be written.
+///
+/// It wraps a view `V` of another I/O backend and provides the write methods of [`Io`] for it, but
+/// none of the read methods. Views projected from it are write-only too, and the wrapped view
+/// cannot be retrieved.
+///
+/// See [`Io::write_only`] for a usage example.
+#[derive(Clone, Copy)]
+pub struct WriteOnly<V>(V);
+
+/// I/O Backend for [`WriteOnly`] views of backend `B`.
+pub struct WriteOnlyBackend<B>(PhantomData<B>);
+
+impl<B: IoBackend> IoBackend for WriteOnlyBackend<B> {
+    type View<'a, T: ?Sized + KnownSize> = WriteOnly<B::View<'a, T>>;
+
+    #[inline]
+    fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T {
+        B::as_ptr(view.0)
+    }
+
+    #[inline]
+    unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
+        view: Self::View<'a, T>,
+        ptr: *mut U,
+    ) -> Self::View<'a, U> {
+        // SAFETY: Per safety requirement.
+        WriteOnly(unsafe { B::project_view(view.0, ptr) })
+    }
+}
+
+impl<'a, V: IoBase<'a>> IoBase<'a> for WriteOnly<V> {
+    type Backend = WriteOnlyBackend<V::Backend>;
+    type Target = V::Target;
+
+    #[inline]
+    fn as_view(self) -> <Self::Backend as IoBackend>::View<'a, Self::Target> {
+        WriteOnly(self.0.as_view())
+    }
+}
+
+impl<B: IoWritable<T>, T> IoWritable<T> for WriteOnlyBackend<B> {
+    #[inline]
+    fn io_write(view: Self::View<'_, T>, value: T) {
+        B::io_write(view.0, value)
+    }
+}
+
+impl<B: IoCopyWritable> IoCopyWritable for WriteOnlyBackend<B> {
+    #[inline]
+    unsafe fn copy_to_io(view: Self::View<'_, [u8]>, buffer: *const u8) {
+        // SAFETY: Per safety requirement.
+        unsafe { B::copy_to_io(view.0, buffer) }
+    }
+
+    #[inline]
+    fn copy_write<T: IntoBytes>(view: Self::View<'_, T>, value: T) {
+        B::copy_write(view.0, value)
+    }
+}
+
 // This helper turns associated functions to methods so it can be invoked in macro.
 // Used by `io_project!()` only.
 #[doc(hidden)]

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 4/7] rust: io: project primitive accessors directly
  2026-10-15 12:39 [PATCH 0/7] rust: io: add read-only and write-only views and registers Daniel Almeida
                   ` (2 preceding siblings ...)
  2026-10-15 12:39 ` [PATCH 3/7] rust: io: add read-only and write-only views Daniel Almeida
@ 2026-10-15 12:39 ` Daniel Almeida
  2026-10-15 12:44   ` sashiko-bot
  2026-10-15 12:39 ` [PATCH 5/7] rust: io: let I/O locations carry an access mode Daniel Almeida
                   ` (2 subsequent siblings)
  6 siblings, 1 reply; 15+ messages in thread
From: Daniel Almeida @ 2026-10-15 12:39 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Robin Murphy, Andreas Hindborg,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Bjorn Helgaas,
	Krzysztof Wilczyński, David Airlie, Simona Vetter
  Cc: driver-core, rust-for-linux, linux-kernel, linux-pci, dri-devel,
	Daniel Almeida

The primitive accessors, `read8` to `write64` and their `try_` variants,
pass their offset as an `IoLoc` to the generic `read`, `write`,
`try_read` and `try_write`, which project the view to that location and
then call `read_val` or `write_val`. Commit 147b41ba23d6 ("rust: io: use
generic read/write accessors for primitive accesses") made them do so to
reduce the number of unsafe blocks.

Since commit e0454ec1220c ("rust: io: use view types instead of
addresses for `Io`"), projecting to an offset is safe. Thus, have the
primitive accessors project with `io_view` and `io_view_assert` and call
`read_val` or `write_val` themselves. The bounds checks are the same.

This prepares for access modes on I/O locations, added in a following
patch, which make the bounds of the generic methods depend on the mode
of the location. The primitive accessors keep their current bounds.

No functional change intended.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/kernel/io.rs | 36 ++++++++++++++++++++----------------
 1 file changed, 20 insertions(+), 16 deletions(-)

diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 03af0590b0f4..4be8c761c556 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -760,7 +760,7 @@ fn try_read8(self, offset: usize) -> Result<u8>
         usize: IoLoc<Self::Target, u8>,
         Self::Backend: IoReadable<u8>,
     {
-        self.try_read(offset)
+        Ok(io_view::<_, u8>(self, offset)?.read_val())
     }
 
     /// Fallible 16-bit read with runtime bounds check.
@@ -770,7 +770,7 @@ fn try_read16(self, offset: usize) -> Result<u16>
         usize: IoLoc<Self::Target, u16>,
         Self::Backend: IoReadable<u16>,
     {
-        self.try_read(offset)
+        Ok(io_view::<_, u16>(self, offset)?.read_val())
     }
 
     /// Fallible 32-bit read with runtime bounds check.
@@ -780,7 +780,7 @@ fn try_read32(self, offset: usize) -> Result<u32>
         usize: IoLoc<Self::Target, u32>,
         Self::Backend: IoReadable<u32>,
     {
-        self.try_read(offset)
+        Ok(io_view::<_, u32>(self, offset)?.read_val())
     }
 
     /// Fallible 64-bit read with runtime bounds check.
@@ -790,7 +790,7 @@ fn try_read64(self, offset: usize) -> Result<u64>
         usize: IoLoc<Self::Target, u64>,
         Self::Backend: IoReadable<u64>,
     {
-        self.try_read(offset)
+        Ok(io_view::<_, u64>(self, offset)?.read_val())
     }
 
     /// Fallible 8-bit write with runtime bounds check.
@@ -800,7 +800,8 @@ fn try_write8(self, value: u8, offset: usize) -> Result
         usize: IoLoc<Self::Target, u8>,
         Self::Backend: IoWritable<u8>,
     {
-        self.try_write(offset, value)
+        io_view::<_, u8>(self, offset)?.write_val(value);
+        Ok(())
     }
 
     /// Fallible 16-bit write with runtime bounds check.
@@ -810,7 +811,8 @@ fn try_write16(self, value: u16, offset: usize) -> Result
         usize: IoLoc<Self::Target, u16>,
         Self::Backend: IoWritable<u16>,
     {
-        self.try_write(offset, value)
+        io_view::<_, u16>(self, offset)?.write_val(value);
+        Ok(())
     }
 
     /// Fallible 32-bit write with runtime bounds check.
@@ -820,7 +822,8 @@ fn try_write32(self, value: u32, offset: usize) -> Result
         usize: IoLoc<Self::Target, u32>,
         Self::Backend: IoWritable<u32>,
     {
-        self.try_write(offset, value)
+        io_view::<_, u32>(self, offset)?.write_val(value);
+        Ok(())
     }
 
     /// Fallible 64-bit write with runtime bounds check.
@@ -830,7 +833,8 @@ fn try_write64(self, value: u64, offset: usize) -> Result
         usize: IoLoc<Self::Target, u64>,
         Self::Backend: IoWritable<u64>,
     {
-        self.try_write(offset, value)
+        io_view::<_, u64>(self, offset)?.write_val(value);
+        Ok(())
     }
 
     /// Infallible 8-bit read with compile-time bounds check.
@@ -842,7 +846,7 @@ fn read8(self, offset: usize) -> u8
         usize: IoLoc<Self::Target, u8>,
         Self::Backend: IoReadable<u8>,
     {
-        self.read(offset)
+        io_view_assert::<_, u8>(self, offset).read_val()
     }
 
     /// Infallible 16-bit read with compile-time bounds check.
@@ -854,7 +858,7 @@ fn read16(self, offset: usize) -> u16
         usize: IoLoc<Self::Target, u16>,
         Self::Backend: IoReadable<u16>,
     {
-        self.read(offset)
+        io_view_assert::<_, u16>(self, offset).read_val()
     }
 
     /// Infallible 32-bit read with compile-time bounds check.
@@ -866,7 +870,7 @@ fn read32(self, offset: usize) -> u32
         usize: IoLoc<Self::Target, u32>,
         Self::Backend: IoReadable<u32>,
     {
-        self.read(offset)
+        io_view_assert::<_, u32>(self, offset).read_val()
     }
 
     /// Infallible 64-bit read with compile-time bounds check.
@@ -878,7 +882,7 @@ fn read64(self, offset: usize) -> u64
         usize: IoLoc<Self::Target, u64>,
         Self::Backend: IoReadable<u64>,
     {
-        self.read(offset)
+        io_view_assert::<_, u64>(self, offset).read_val()
     }
 
     /// Infallible 8-bit write with compile-time bounds check.
@@ -890,7 +894,7 @@ fn write8(self, value: u8, offset: usize)
         usize: IoLoc<Self::Target, u8>,
         Self::Backend: IoWritable<u8>,
     {
-        self.write(offset, value)
+        io_view_assert::<_, u8>(self, offset).write_val(value)
     }
 
     /// Infallible 16-bit write with compile-time bounds check.
@@ -902,7 +906,7 @@ fn write16(self, value: u16, offset: usize)
         usize: IoLoc<Self::Target, u16>,
         Self::Backend: IoWritable<u16>,
     {
-        self.write(offset, value)
+        io_view_assert::<_, u16>(self, offset).write_val(value)
     }
 
     /// Infallible 32-bit write with compile-time bounds check.
@@ -914,7 +918,7 @@ fn write32(self, value: u32, offset: usize)
         usize: IoLoc<Self::Target, u32>,
         Self::Backend: IoWritable<u32>,
     {
-        self.write(offset, value)
+        io_view_assert::<_, u32>(self, offset).write_val(value)
     }
 
     /// Infallible 64-bit write with compile-time bounds check.
@@ -926,7 +930,7 @@ fn write64(self, value: u64, offset: usize)
         usize: IoLoc<Self::Target, u64>,
         Self::Backend: IoWritable<u64>,
     {
-        self.write(offset, value)
+        io_view_assert::<_, u64>(self, offset).write_val(value)
     }
 
     /// Generic fallible read with runtime bounds check.

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 5/7] rust: io: let I/O locations carry an access mode
  2026-10-15 12:39 [PATCH 0/7] rust: io: add read-only and write-only views and registers Daniel Almeida
                   ` (3 preceding siblings ...)
  2026-10-15 12:39 ` [PATCH 4/7] rust: io: project primitive accessors directly Daniel Almeida
@ 2026-10-15 12:39 ` Daniel Almeida
  2026-10-15 12:50   ` sashiko-bot
  2026-10-15 12:39 ` [PATCH 6/7] rust: io: register: add `#[ro]` and `#[wo]` attributes Daniel Almeida
  2026-10-15 12:39 ` [PATCH 7/7] drm/tyr: mark read-only and write-only registers Daniel Almeida
  6 siblings, 1 reply; 15+ messages in thread
From: Daniel Almeida @ 2026-10-15 12:39 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Robin Murphy, Andreas Hindborg,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Bjorn Helgaas,
	Krzysztof Wilczyński, David Airlie, Simona Vetter
  Cc: driver-core, rust-for-linux, linux-kernel, linux-pci, dri-devel,
	Daniel Almeida

`Io::read`, `Io::write`, `Io::update` and their fallible variants
project the view to the given `IoLoc` and access it there. A location
has no say in which accesses are allowed, so a register cannot be
declared read-only or write-only.

Add the `io::access` module with the `Mode` trait and the `ReadWrite`,
`ReadOnly` and `WriteOnly` modes, and add an `Access` associated type to
`IoLoc`. Projecting through a location applies its mode: `ReadWrite`
leaves the view as it is, while `ReadOnly` and `WriteOnly` wrap it in the
`ReadOnly` and `WriteOnly` views. The bounds of the generic accessors now
apply to the backend of the restricted view, so a write to a read-only
location, or a read from a write-only one, does not compile.

Raw offsets are `ReadWrite`. `OffsetLoc` takes the mode as a type
parameter that defaults to `ReadWrite`, and `RegisterArray` gets an
`Access` type, which `register!` sets to `ReadWrite` for now.

No existing location changes its mode, so there is no functional change.
A following patch lets `register!` declare read-only and write-only
registers.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/kernel/io.rs          | 42 +++++++++++++++++--------
 rust/kernel/io/access.rs   | 76 ++++++++++++++++++++++++++++++++++++++++++++++
 rust/kernel/io/register.rs | 18 +++++++++--
 rust/macros/io/register.rs |  1 +
 4 files changed, 121 insertions(+), 16 deletions(-)

diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 4be8c761c556..71bcd0d77ce9 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -22,6 +22,7 @@
     }, //
 };
 
+pub mod access;
 #[cfg(CONFIG_HAS_IOMEM)]
 pub mod mem;
 pub mod poll;
@@ -377,12 +378,16 @@ fn copy_write<T: IntoBytes>(view: Self::View<'_, T>, value: T) {
 ///
 /// - The valid `Base` to operate on. For most registers, this should be [`Region`].
 /// - The offset to access (returned by [`IoLoc::offset`]),
-/// - The type `T` in which the data is returned or provided.
+/// - The type `T` in which the data is returned or provided,
+/// - The access mode ([`IoLoc::Access`]) applied to the views projected through it.
 ///
 /// `T` is not necessarily the type for underlying I/O operation. Methods that take `IoLoc` have `T:
 /// AsRepr` bound and the `<T as AsRepr>::Repr` type would be used to perform I/O and converted to
 /// `T` instead.
 pub trait IoLoc<Base: ?Sized, T> {
+    /// Access mode applied to the views projected through this location.
+    type Access: access::Mode;
+
     /// Consumes `self` and returns the offset of this location.
     fn offset(self) -> usize;
 }
@@ -393,6 +398,8 @@ macro_rules! impl_usize_ioloc {
     ($($ty:ty),*) => {
         $(
             impl<const SIZE: usize> IoLoc<Region<SIZE>, $ty> for usize {
+                type Access = access::ReadWrite;
+
                 #[inline(always)]
                 fn offset(self) -> usize {
                     self
@@ -961,7 +968,7 @@ fn try_read<T, L>(self, location: L) -> Result<T>
     where
         T: AsReprMut,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoReadable<<T as AsRepr>::Repr>,
+        <L::Access as access::Mode>::Backend<Self::Backend>: IoReadable<<T as AsRepr>::Repr>,
     {
         Ok(io_read!(self, try: location))
     }
@@ -994,7 +1001,7 @@ fn try_write<T, L>(self, location: L, value: T) -> Result
     where
         T: AsRepr,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoWritable<<T as AsRepr>::Repr>,
+        <L::Access as access::Mode>::Backend<Self::Backend>: IoWritable<<T as AsRepr>::Repr>,
     {
         io_write!(self, try: location, value);
         Ok(())
@@ -1040,7 +1047,7 @@ fn try_write_reg<T, L, V>(self, value: V) -> Result
         T: AsRepr,
         L: IoLoc<Self::Target, T>,
         V: LocatedRegister<Self::Target, Location = L, Value = T>,
-        Self::Backend: IoWritable<<T as AsRepr>::Repr>,
+        <L::Access as access::Mode>::Backend<Self::Backend>: IoWritable<<T as AsRepr>::Repr>,
     {
         let (location, value) = value.into_io_op();
 
@@ -1074,7 +1081,8 @@ fn try_update<T, L, F>(self, location: L, f: F) -> Result
     where
         T: AsReprMut,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoReadable<<T as AsRepr>::Repr> + IoWritable<<T as AsRepr>::Repr>,
+        <L::Access as access::Mode>::Backend<Self::Backend>:
+            IoReadable<<T as AsRepr>::Repr> + IoWritable<<T as AsRepr>::Repr>,
         F: FnOnce(T) -> T,
     {
         let view = io_project!(self, try: location);
@@ -1108,7 +1116,7 @@ fn read<T, L>(self, location: L) -> T
     where
         T: AsReprMut,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoReadable<<T as AsRepr>::Repr>,
+        <L::Access as access::Mode>::Backend<Self::Backend>: IoReadable<<T as AsRepr>::Repr>,
     {
         io_read!(self, build: location)
     }
@@ -1139,7 +1147,7 @@ fn write<T, L>(self, location: L, value: T)
     where
         T: AsRepr,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoWritable<<T as AsRepr>::Repr>,
+        <L::Access as access::Mode>::Backend<Self::Backend>: IoWritable<<T as AsRepr>::Repr>,
     {
         io_write!(self, build: location, value);
     }
@@ -1183,7 +1191,7 @@ fn write_reg<T, L, V>(self, value: V)
         T: AsRepr,
         L: IoLoc<Self::Target, T>,
         V: LocatedRegister<Self::Target, Location = L, Value = T>,
-        Self::Backend: IoWritable<<T as AsRepr>::Repr>,
+        <L::Access as access::Mode>::Backend<Self::Backend>: IoWritable<<T as AsRepr>::Repr>,
     {
         let (location, value) = value.into_io_op();
 
@@ -1217,7 +1225,8 @@ fn update<T, L, F>(self, location: L, f: F)
     where
         T: AsReprMut,
         L: IoLoc<Self::Target, T>,
-        Self::Backend: IoReadable<<T as AsRepr>::Repr> + IoWritable<<T as AsRepr>::Repr>,
+        <L::Access as access::Mode>::Backend<Self::Backend>:
+            IoReadable<<T as AsRepr>::Repr> + IoWritable<<T as AsRepr>::Repr>,
         F: FnOnce(T) -> T,
     {
         let view = io_project!(self, build: location);
@@ -1909,19 +1918,26 @@ pub unsafe fn project_view<U: ?Sized + KnownSize>(
     pub fn try_project_loc<U, L>(
         self,
         location: L,
-    ) -> Result<<T::Backend as IoBackend>::View<'a, U>>
+    ) -> Result<access::RestrictedView<'a, L::Access, T::Backend, U>>
     where
         L: IoLoc<T::Target, U>,
     {
-        io_view::<_, U>(self.0, location.offset())
+        let view = io_view::<_, U>(self.0, location.offset())?;
+
+        Ok(<L::Access as access::Mode>::restrict::<T::Backend, U>(view))
     }
 
     #[inline(always)]
-    pub fn project_loc<U, L>(self, location: L) -> <T::Backend as IoBackend>::View<'a, U>
+    pub fn project_loc<U, L>(
+        self,
+        location: L,
+    ) -> access::RestrictedView<'a, L::Access, T::Backend, U>
     where
         L: IoLoc<T::Target, U>,
     {
-        io_view_assert::<_, U>(self.0, location.offset())
+        let view = io_view_assert::<_, U>(self.0, location.offset());
+
+        <L::Access as access::Mode>::restrict::<T::Backend, U>(view)
     }
 }
 
diff --git a/rust/kernel/io/access.rs b/rust/kernel/io/access.rs
new file mode 100644
index 000000000000..caec21dd943b
--- /dev/null
+++ b/rust/kernel/io/access.rs
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Access modes of I/O locations.
+//!
+//! An I/O location ([`IoLoc`](super::IoLoc)) has an access mode, which applies to the views
+//! projected through it: a [`ReadWrite`] location gives the view as it is, a [`ReadOnly`] location
+//! gives a [`ReadOnly`](super::ReadOnly) view, and a [`WriteOnly`] location gives a
+//! [`WriteOnly`](super::WriteOnly) view.
+
+use crate::ptr::KnownSize;
+
+use super::{
+    IoBackend,
+    ReadOnlyBackend,
+    WriteOnlyBackend, //
+};
+
+/// An access mode of an I/O location.
+pub trait Mode {
+    /// Backend of the views this mode gives for views of backend `B`.
+    type Backend<B: IoBackend>: IoBackend;
+
+    /// Applies this mode to `view`.
+    fn restrict<'a, B: IoBackend, T: ?Sized + KnownSize>(
+        view: B::View<'a, T>,
+    ) -> <Self::Backend<B> as IoBackend>::View<'a, T>;
+}
+
+/// View of `T` that access mode `M` gives for views of backend `B`.
+pub type RestrictedView<'a, M, B, T> = <<M as Mode>::Backend<B> as IoBackend>::View<'a, T>;
+
+/// Read-write access mode.
+///
+/// Views are given as they are.
+pub struct ReadWrite;
+
+impl Mode for ReadWrite {
+    type Backend<B: IoBackend> = B;
+
+    #[inline(always)]
+    fn restrict<'a, B: IoBackend, T: ?Sized + KnownSize>(view: B::View<'a, T>) -> B::View<'a, T> {
+        view
+    }
+}
+
+/// Read-only access mode.
+///
+/// Views are wrapped in [`ReadOnly`](super::ReadOnly).
+pub struct ReadOnly;
+
+impl Mode for ReadOnly {
+    type Backend<B: IoBackend> = ReadOnlyBackend<B>;
+
+    #[inline(always)]
+    fn restrict<'a, B: IoBackend, T: ?Sized + KnownSize>(
+        view: B::View<'a, T>,
+    ) -> super::ReadOnly<B::View<'a, T>> {
+        super::ReadOnly(view)
+    }
+}
+
+/// Write-only access mode.
+///
+/// Views are wrapped in [`WriteOnly`](super::WriteOnly).
+pub struct WriteOnly;
+
+impl Mode for WriteOnly {
+    type Backend<B: IoBackend> = WriteOnlyBackend<B>;
+
+    #[inline(always)]
+    fn restrict<'a, B: IoBackend, T: ?Sized + KnownSize>(
+        view: B::View<'a, T>,
+    ) -> super::WriteOnly<B::View<'a, T>> {
+        super::WriteOnly(view)
+    }
+}
diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
index b6513fa0f412..9218dc66017b 100644
--- a/rust/kernel/io/register.rs
+++ b/rust/kernel/io/register.rs
@@ -118,7 +118,10 @@
 
 use crate::{
     build_assert::build_assert,
-    io::IoLoc, //
+    io::{
+        access,
+        IoLoc, //
+    },
 };
 
 /// Allows `()` to be used as the `location` parameter of [`Io::write`](super::Io::write) when
@@ -127,6 +130,8 @@ impl<Base: ?Sized, T> IoLoc<Base, T> for ()
 where
     T: FixedIoLoc<Base>,
 {
+    type Access = <T::Location as IoLoc<Base, T>>::Access;
+
     #[inline(always)]
     fn offset(self) -> usize {
         T::LOCATION.offset()
@@ -135,7 +140,7 @@ fn offset(self) -> usize {
 
 // Provides a `IoLoc` impl that for a fixed offset.
 #[doc(hidden)]
-pub struct OffsetLoc<Base: ?Sized, T>(usize, PhantomData<(T, Base)>);
+pub struct OffsetLoc<Base: ?Sized, T, A = access::ReadWrite>(usize, PhantomData<(T, A, Base)>);
 
 impl<Base: ?Sized, T> OffsetLoc<Base, T> {
     #[inline]
@@ -149,7 +154,9 @@ pub const fn const_offset(self) -> usize {
     }
 }
 
-impl<Base: ?Sized, T> IoLoc<Base, T> for OffsetLoc<Base, T> {
+impl<Base: ?Sized, T, A: access::Mode> IoLoc<Base, T> for OffsetLoc<Base, T, A> {
+    type Access = A;
+
     #[inline(always)]
     fn offset(self) -> usize {
         self.0
@@ -161,6 +168,9 @@ pub trait RegisterArray: Sized {
     /// Base type for this register.
     type Base: ?Sized;
 
+    /// Access mode of the registers in this array.
+    type Access: access::Mode;
+
     /// Start offset of the register.
     ///
     /// The interpretation of this offset depends on the type of the register.
@@ -198,6 +208,8 @@ impl<Base: ?Sized, T> IoLoc<Base, T> for RegisterArrayLoc<T>
 where
     T: RegisterArray<Base = Base>,
 {
+    type Access = T::Access;
+
     #[inline(always)]
     fn offset(self) -> usize {
         T::OFFSET + self.0 * T::STRIDE
diff --git a/rust/macros/io/register.rs b/rust/macros/io/register.rs
index 420c3ad052b8..2d92c7f41447 100644
--- a/rust/macros/io/register.rs
+++ b/rust/macros/io/register.rs
@@ -283,6 +283,7 @@ impl ::kernel::io::register::Array for #name {}
 
                     impl ::kernel::io::register::RegisterArray for #name {
                         type Base = #base;
+                        type Access = ::kernel::io::access::ReadWrite;
                         const OFFSET: usize = #offset;
                         const SIZE: usize = #size;
                         const STRIDE: usize = #stride;

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 6/7] rust: io: register: add `#[ro]` and `#[wo]` attributes
  2026-10-15 12:39 [PATCH 0/7] rust: io: add read-only and write-only views and registers Daniel Almeida
                   ` (4 preceding siblings ...)
  2026-10-15 12:39 ` [PATCH 5/7] rust: io: let I/O locations carry an access mode Daniel Almeida
@ 2026-10-15 12:39 ` Daniel Almeida
  2026-10-15 12:47   ` sashiko-bot
  2026-10-15 12:39 ` [PATCH 7/7] drm/tyr: mark read-only and write-only registers Daniel Almeida
  6 siblings, 1 reply; 15+ messages in thread
From: Daniel Almeida @ 2026-10-15 12:39 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Robin Murphy, Andreas Hindborg,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Bjorn Helgaas,
	Krzysztof Wilczyński, David Airlie, Simona Vetter
  Cc: driver-core, rust-for-linux, linux-kernel, linux-pci, dri-devel,
	Daniel Almeida

`register!` gives every register a location with the `ReadWrite` access
mode, so any register can be read, written and updated, including the
ones the hardware defines as read-only or write-only.

Add the `#[ro]` and `#[wo]` attributes, which make the macro use the
`ReadOnly` or `WriteOnly` mode instead: in the register's `OffsetLoc`
for fixed registers, and as `RegisterArray::Access` for arrays. Reading
a write-only register, or writing or updating a read-only one, then does
not compile. An alias does not inherit the mode of the register it
aliases. Add an example to the `register!` documentation.

The tyr driver uses the attributes in a following patch.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/kernel/io/register.rs | 53 ++++++++++++++++++++++++++++++++++++++++++++++
 rust/macros/io/register.rs | 35 ++++++++++++++++++++++++------
 2 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
index 9218dc66017b..5c0188947879 100644
--- a/rust/kernel/io/register.rs
+++ b/rust/kernel/io/register.rs
@@ -147,6 +147,13 @@ impl<Base: ?Sized, T> OffsetLoc<Base, T> {
     pub const fn new(offset: usize) -> Self {
         Self(offset, PhantomData)
     }
+}
+
+impl<Base: ?Sized, T, A> OffsetLoc<Base, T, A> {
+    #[inline]
+    pub const fn with_access(offset: usize) -> Self {
+        Self(offset, PhantomData)
+    }
 
     #[inline]
     pub const fn const_offset(self) -> usize {
@@ -596,6 +603,52 @@ pub const fn element_alias_offset<Base: ?Sized, Alias: RegisterArray<Base = Base
 /// io_project!(io, build: CPU0).write_reg(CPU_CTL::zeroed());
 /// # }
 /// ```
+///
+/// ## Access modes
+///
+/// Registers are read-write unless declared with the `#[ro]` (read-only) or `#[wo]` (write-only)
+/// attribute. Reading a write-only register, or writing or updating a read-only one, does not
+/// compile:
+///
+/// ```no_run
+/// use kernel::io::{
+///     register,
+///     Io,
+///     Region,
+/// };
+/// # use kernel::io::Mmio;
+///
+/// register! {
+///     base: Region<0x1000>;
+///
+///     #[ro]
+///     pub R1(u32) @ 0x0 {
+///         31:0 value;
+///     }
+///
+///     #[wo]
+///     pub R2(u32) @ 0x4 {
+///         31:0 value;
+///     }
+///
+///     #[ro]
+///     pub R3: u8 @ 0x8;
+/// }
+///
+/// # fn test(io: Mmio<'_, Region<0x1000>>) {
+/// let r1 = io.read(R1);
+/// let r3 = io.read(R3);
+/// io.write_reg(R2::zeroed());
+///
+/// // Neither of these compiles.
+/// // io.write_reg(r1);
+/// // io.read(R2);
+/// # }
+/// ```
+///
+/// The mode applies to accesses through the register's location only. An alias is read-write
+/// unless declared `#[ro]` or `#[wo]` itself, and the register's offset can still be accessed
+/// directly through the I/O region.
 #[macro_export]
 macro_rules! register {
     ($($tt:tt)*) => {
diff --git a/rust/macros/io/register.rs b/rust/macros/io/register.rs
index 2d92c7f41447..171a4c1f96b4 100644
--- a/rust/macros/io/register.rs
+++ b/rust/macros/io/register.rs
@@ -63,6 +63,7 @@ struct Reg {
     attrs: Vec<Attribute>,
     vis: Visibility,
     name: Ident,
+    access: Type,
     unique: bool,
     ty: Type,
     array: Option<RegArrayDef>,
@@ -72,7 +73,26 @@ struct Reg {
 
 impl Parse for Reg {
     fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
-        let attrs = input.call(Attribute::parse_outer)?;
+        let mut attrs = input.call(Attribute::parse_outer)?;
+        let modes = attrs
+            .extract_if(.., |attr| {
+                attr.path().is_ident("ro") || attr.path().is_ident("wo")
+            })
+            .collect::<Vec<_>>();
+        if let Some(attr) = modes.get(1) {
+            Err(Error::new_spanned(
+                attr,
+                "only one of `#[ro]` and `#[wo]` can be specified",
+            ))?
+        }
+        let access = match modes.first() {
+            None => parse_quote!(::kernel::io::access::ReadWrite),
+            Some(attr) if attr.path().is_ident("ro") => {
+                parse_quote!(::kernel::io::access::ReadOnly)
+            }
+            Some(_) => parse_quote!(::kernel::io::access::WriteOnly),
+        };
+
         let vis = input.parse()?;
         let name = input.parse()?;
 
@@ -161,6 +181,7 @@ fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
             attrs,
             vis,
             name,
+            access,
             unique,
             ty,
             array,
@@ -205,6 +226,7 @@ pub(crate) fn register(def: RegDef) -> Result<TokenStream> {
             attrs,
             vis,
             name,
+            access,
             unique,
             ty,
             array,
@@ -220,7 +242,7 @@ pub(crate) fn register(def: RegDef) -> Result<TokenStream> {
             RegOffset::Fixed { offset } => quote!(#offset),
             RegOffset::Alias { alias } => {
                 quote_spanned!(alias.span().resolved_at(span) =>
-                    ::kernel::io::register::OffsetLoc::<#base, _>::const_offset(#alias)
+                    ::kernel::io::register::OffsetLoc::<#base, _, _>::const_offset(#alias)
                 )
             }
             RegOffset::ElementAlias { alias, idx } => {
@@ -246,15 +268,16 @@ pub(crate) fn register(def: RegDef) -> Result<TokenStream> {
                 if unique {
                     outputs.extend(quote!(
                         impl ::kernel::io::register::FixedIoLoc<#base> for #ty {
-                            type Location = ::kernel::io::register::OffsetLoc<#base, #ty>;
+                            type Location = ::kernel::io::register::OffsetLoc<#base, #ty, #access>;
                             const LOCATION: Self::Location = #name;
                         }
                     ))
                 }
 
                 outputs.extend(quote_spanned!(span =>
-                    #(#attrs)* #vis const #name: ::kernel::io::register::OffsetLoc<#base, #ty> =
-                        ::kernel::io::register::OffsetLoc::new(#offset);
+                    #(#attrs)* #vis const #name:
+                        ::kernel::io::register::OffsetLoc<#base, #ty, #access> =
+                        ::kernel::io::register::OffsetLoc::with_access(#offset);
                 ));
             }
 
@@ -283,7 +306,7 @@ impl ::kernel::io::register::Array for #name {}
 
                     impl ::kernel::io::register::RegisterArray for #name {
                         type Base = #base;
-                        type Access = ::kernel::io::access::ReadWrite;
+                        type Access = #access;
                         const OFFSET: usize = #offset;
                         const SIZE: usize = #size;
                         const STRIDE: usize = #stride;

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 7/7] drm/tyr: mark read-only and write-only registers
  2026-10-15 12:39 [PATCH 0/7] rust: io: add read-only and write-only views and registers Daniel Almeida
                   ` (5 preceding siblings ...)
  2026-10-15 12:39 ` [PATCH 6/7] rust: io: register: add `#[ro]` and `#[wo]` attributes Daniel Almeida
@ 2026-10-15 12:39 ` Daniel Almeida
  2026-10-15 12:43   ` sashiko-bot
  6 siblings, 1 reply; 15+ messages in thread
From: Daniel Almeida @ 2026-10-15 12:39 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Robin Murphy, Andreas Hindborg,
	Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Alice Ryhl, Trevor Gross, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Bjorn Helgaas,
	Krzysztof Wilczyński, David Airlie, Simona Vetter
  Cc: driver-core, rust-for-linux, linux-kernel, linux-pci, dri-devel,
	Daniel Almeida

The documentation in regs.rs describes some registers as read-only or
write-only, but nothing enforces it, and most registers do not document
a mode at all.

Declare every read-only and write-only register with the `#[ro]` and
`#[wo]` attributes of `register!`, so that writing a read-only register,
or reading a write-only one, does not compile. The modes follow the
documentation in regs.rs where there is one, and otherwise the register
permissions in Arm's kbase driver for v10 GPUs. Both halves of a 64-bit
register get the same mode, and so do the aliases of `GPU_COMMAND`.

No existing access goes against these modes, so the driver code does not
change.

Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 drivers/gpu/drm/tyr/regs.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/drivers/gpu/drm/tyr/regs.rs b/drivers/gpu/drm/tyr/regs.rs
index 69c901afb28f..6695a292c786 100644
--- a/drivers/gpu/drm/tyr/regs.rs
+++ b/drivers/gpu/drm/tyr/regs.rs
@@ -63,6 +63,7 @@ pub(crate) mod gpu_control {
         base: TyrRegisters;
 
         /// GPU identification register.
+        #[ro]
         pub(crate) GPU_ID(u32) @ 0x0 {
             /// Status of the GPU release.
             3:0     ver_status;
@@ -81,6 +82,7 @@ pub(crate) mod gpu_control {
         }
 
         /// Level 2 cache features register.
+        #[ro]
         pub(crate) L2_FEATURES(u32) @ 0x4 {
             /// Cache line size.
             7:0     line_size;
@@ -93,12 +95,14 @@ pub(crate) mod gpu_control {
         }
 
         /// Shader core features.
+        #[ro]
         pub(crate) CORE_FEATURES(u32) @ 0x8 {
             /// Shader core variant.
             7:0     core_variant;
         }
 
         /// Tiler features.
+        #[ro]
         pub(crate) TILER_FEATURES(u32) @ 0xc {
             /// Log of the tiler's bin size.
             5:0     bin_size;
@@ -107,6 +111,7 @@ pub(crate) mod gpu_control {
         }
 
         /// Memory system features.
+        #[ro]
         pub(crate) MEM_FEATURES(u32) @ 0x10 {
             0:0     coherent_core_group => bool;
             1:1     coherent_super_group => bool;
@@ -114,6 +119,7 @@ pub(crate) mod gpu_control {
         }
 
         /// Memory management unit features.
+        #[ro]
         pub(crate) MMU_FEATURES(u32) @ 0x14 {
             /// Number of bits supported in virtual addresses.
             7:0     va_bits;
@@ -122,11 +128,13 @@ pub(crate) mod gpu_control {
         }
 
         /// Address spaces present.
+        #[ro]
         pub(crate) AS_PRESENT(u32) @ 0x18 {
             31:0    present;
         }
 
         /// CSF version information.
+        #[ro]
         pub(crate) CSF_ID(u32) @ 0x1c {
             /// MCU revision ID.
             3:0     mcu_rev;
@@ -164,6 +172,7 @@ pub(crate) mod gpu_control {
         }
 
         /// IRQ sources to clear. Write only.
+        #[wo]
         pub(crate) GPU_IRQ_CLEAR(u32) @ 0x24 {
             /// Clear the GPU_FAULT interrupt, a 1-bit boolean flag.
             0:0     gpu_fault => bool;
@@ -202,6 +211,7 @@ pub(crate) mod gpu_control {
         }
 
         /// IRQ status for enabled sources. Read only.
+        #[ro]
         pub(crate) GPU_IRQ_STATUS(u32) @ 0x2c {
             /// GPU_FAULT interrupt status, a 1-bit boolean flag.
             0:0     gpu_fault => bool;
@@ -328,11 +338,13 @@ fn from(mode: FlushMode) -> Self {
         /// - [`GPU_COMMAND::reset()`]
         /// - [`GPU_COMMAND::flush_caches()`]
         /// - [`GPU_COMMAND::clear_fault()`]
+        #[wo]
         pub(crate) GPU_COMMAND (u32) @ 0x30 {
             7:0     command ?=> GpuCommand;
         }
         /// Internal alias for GPU_COMMAND in reset mode.
         /// Use [`GPU_COMMAND::reset()`] instead.
+        #[wo]
         GPU_COMMAND_RESET (u32) => GPU_COMMAND {
             7:0     command ?=> GpuCommand;
             11:8    reset_mode ?=> ResetMode;
@@ -340,6 +352,7 @@ fn from(mode: FlushMode) -> Self {
 
         /// Internal alias for GPU_COMMAND in cache flush mode.
         /// Use [`GPU_COMMAND::flush_caches()`] instead.
+        #[wo]
         GPU_COMMAND_FLUSH (u32) => GPU_COMMAND {
             7:0     command ?=> GpuCommand;
             /// L2 cache flush mode.
@@ -389,6 +402,7 @@ pub(crate) fn clear_fault() -> Self {
         base: TyrRegisters;
 
         /// GPU status register. Read only.
+        #[ro]
         pub(crate) GPU_STATUS(u32) @ 0x34 {
             /// GPU active, a 1-bit boolean flag.
             0:0     gpu_active => bool;
@@ -474,6 +488,7 @@ fn from(access: AccessType) -> Self {
         base: TyrRegisters;
 
         /// GPU fault status register. Read only.
+        #[ro]
         pub(crate) GPU_FAULTSTATUS(u32) @ 0x3c {
             /// Exception type.
             7:0     exception_type ?=> ExceptionType;
@@ -493,10 +508,12 @@ fn from(access: AccessType) -> Self {
         /// Once a fault is reported, it must be manually cleared by issuing a
         /// [`GPU_COMMAND::clear_fault()`] command to the [`GPU_COMMAND`] register. No further GPU
         /// faults will be reported until the previous fault has been cleared.
+        #[ro]
         pub(crate) GPU_FAULTADDRESS_LO(u32) @ 0x40 {
             31:0    pointer;
         }
 
+        #[ro]
         pub(crate) GPU_FAULTADDRESS_HI(u32) @ 0x44 {
             31:0    pointer;
         }
@@ -519,39 +536,47 @@ fn from(access: AccessType) -> Self {
         }
 
         /// GPU cycle counter. Read only.
+        #[ro]
         pub(crate) CYCLE_COUNT_LO(u32) @ 0x90 {
             31:0    count;
         }
 
+        #[ro]
         pub(crate) CYCLE_COUNT_HI(u32) @ 0x94 {
             31:0    count;
         }
 
         /// Global time stamp. Read only.
+        #[ro]
         pub(crate) TIMESTAMP_LO(u32) @ 0x98 {
             31:0    timestamp;
         }
 
+        #[ro]
         pub(crate) TIMESTAMP_HI(u32) @ 0x9c {
             31:0    timestamp;
         }
 
         /// Maximum number of threads per core. Read only constant.
+        #[ro]
         pub(crate) THREAD_MAX_THREADS(u32) @ 0xa0 {
             31:0    threads;
         }
 
         /// Maximum number of threads per workgroup. Read only constant.
+        #[ro]
         pub(crate) THREAD_MAX_WORKGROUP_SIZE(u32) @ 0xa4 {
             31:0    threads;
         }
 
         /// Maximum number of threads per barrier. Read only constant.
+        #[ro]
         pub(crate) THREAD_MAX_BARRIER_SIZE(u32) @ 0xa8 {
             31:0    threads;
         }
 
         /// Thread features. Read only constant.
+        #[ro]
         pub(crate) THREAD_FEATURES(u32) @ 0xac {
             /// Total number of registers per core.
             21:0    max_registers;
@@ -565,173 +590,211 @@ fn from(access: AccessType) -> Self {
         ///
         /// A bitmap where each bit indicates support for a specific compressed texture format.
         /// The bit position maps to an opaque format ID (`texture_features_key_t` in spec).
+        #[ro]
         pub(crate) TEXTURE_FEATURES(u32)[4] @ 0xb0 {
             31:0    supported_formats;
         }
 
         /// Shader core present bitmap. Read only constant.
+        #[ro]
         pub(crate) SHADER_PRESENT_LO(u32) @ 0x100 {
             31:0    value;
         }
 
+        #[ro]
         pub(crate) SHADER_PRESENT_HI(u32) @ 0x104 {
             31:0    value;
         }
 
         /// Tiler present bitmap. Read only constant.
+        #[ro]
         pub(crate) TILER_PRESENT_LO(u32) @ 0x110 {
             31:0    present;
         }
 
+        #[ro]
         pub(crate) TILER_PRESENT_HI(u32) @ 0x114 {
             31:0    present;
         }
 
         /// L2 cache present bitmap. Read only constant.
+        #[ro]
         pub(crate) L2_PRESENT_LO(u32) @ 0x120 {
             31:0    present;
         }
 
+        #[ro]
         pub(crate) L2_PRESENT_HI(u32) @ 0x124 {
             31:0    present;
         }
 
         /// Shader core ready bitmap. Read only.
+        #[ro]
         pub(crate) SHADER_READY_LO(u32) @ 0x140 {
             31:0    ready;
         }
 
+        #[ro]
         pub(crate) SHADER_READY_HI(u32) @ 0x144 {
             31:0    ready;
         }
 
         /// Tiler ready bitmap. Read only.
+        #[ro]
         pub(crate) TILER_READY_LO(u32) @ 0x150 {
             31:0    ready;
         }
 
+        #[ro]
         pub(crate) TILER_READY_HI(u32) @ 0x154 {
             31:0    ready;
         }
 
         /// L2 ready bitmap. Read only.
+        #[ro]
         pub(crate) L2_READY_LO(u32) @ 0x160 {
             31:0    ready;
         }
 
+        #[ro]
         pub(crate) L2_READY_HI(u32) @ 0x164 {
             31:0    ready;
         }
 
         /// Shader core power up bitmap.
+        #[wo]
         pub(crate) SHADER_PWRON_LO(u32) @ 0x180 {
             31:0    request;
         }
 
+        #[wo]
         pub(crate) SHADER_PWRON_HI(u32) @ 0x184 {
             31:0    request;
         }
 
         /// Tiler power up bitmap.
+        #[wo]
         pub(crate) TILER_PWRON_LO(u32) @ 0x190 {
             31:0    request;
         }
 
+        #[wo]
         pub(crate) TILER_PWRON_HI(u32) @ 0x194 {
             31:0    request;
         }
 
         /// L2 power up bitmap.
+        #[wo]
         pub(crate) L2_PWRON_LO(u32) @ 0x1a0 {
             31:0    request;
         }
 
+        #[wo]
         pub(crate) L2_PWRON_HI(u32) @ 0x1a4 {
             31:0    request;
         }
 
         /// Shader core power down bitmap.
+        #[wo]
         pub(crate) SHADER_PWROFF_LO(u32) @ 0x1c0 {
             31:0    request;
         }
 
+        #[wo]
         pub(crate) SHADER_PWROFF_HI(u32) @ 0x1c4 {
             31:0    request;
         }
 
         /// Tiler power down bitmap.
+        #[wo]
         pub(crate) TILER_PWROFF_LO(u32) @ 0x1d0 {
             31:0    request;
         }
 
+        #[wo]
         pub(crate) TILER_PWROFF_HI(u32) @ 0x1d4 {
             31:0    request;
         }
 
         /// L2 power down bitmap.
+        #[wo]
         pub(crate) L2_PWROFF_LO(u32) @ 0x1e0 {
             31:0    request;
         }
 
+        #[wo]
         pub(crate) L2_PWROFF_HI(u32) @ 0x1e4 {
             31:0    request;
         }
 
         /// Shader core power transition bitmap. Read-only.
+        #[ro]
         pub(crate) SHADER_PWRTRANS_LO(u32) @ 0x200 {
             31:0    changing;
         }
 
+        #[ro]
         pub(crate) SHADER_PWRTRANS_HI(u32) @ 0x204 {
             31:0    changing;
         }
 
         /// Tiler power transition bitmap. Read-only.
+        #[ro]
         pub(crate) TILER_PWRTRANS_LO(u32) @ 0x210 {
             31:0    changing;
         }
 
+        #[ro]
         pub(crate) TILER_PWRTRANS_HI(u32) @ 0x214 {
             31:0    changing;
         }
 
         /// L2 power transition bitmap. Read-only.
+        #[ro]
         pub(crate) L2_PWRTRANS_LO(u32) @ 0x220 {
             31:0    changing;
         }
 
+        #[ro]
         pub(crate) L2_PWRTRANS_HI(u32) @ 0x224 {
             31:0    changing;
         }
 
         /// Shader core active bitmap. Read-only.
+        #[ro]
         pub(crate) SHADER_PWRACTIVE_LO(u32) @ 0x240 {
             31:0    active;
         }
 
+        #[ro]
         pub(crate) SHADER_PWRACTIVE_HI(u32) @ 0x244 {
             31:0    active;
         }
 
         /// Tiler active bitmap. Read-only.
+        #[ro]
         pub(crate) TILER_PWRACTIVE_LO(u32) @ 0x250 {
             31:0    active;
         }
 
+        #[ro]
         pub(crate) TILER_PWRACTIVE_HI(u32) @ 0x254 {
             31:0    active;
         }
 
         /// L2 active bitmap.  Read-only.
+        #[ro]
         pub(crate) L2_PWRACTIVE_LO(u32) @ 0x260 {
             31:0    active;
         }
 
+        #[ro]
         pub(crate) L2_PWRACTIVE_HI(u32) @ 0x264 {
             31:0    active;
         }
 
         /// Revision ID. Read only constant.
+        #[ro]
         pub(crate) REVIDR(u32) @ 0x280 {
             31:0    revision;
         }
@@ -739,6 +802,7 @@ fn from(access: AccessType) -> Self {
         /// Coherency features present. Read only constant.
         /// Supported protocols on the interconnect between the GPU and the
         /// system into which it is integrated.
+        #[ro]
         pub(crate) COHERENCY_FEATURES(u32) @ 0x300 {
             /// ACE-Lite protocol supported, a 1-bit boolean flag.
             0:0     ace_lite => bool;
@@ -866,6 +930,7 @@ fn from(status: McuStatus) -> Self {
         base: TyrRegisters;
 
         /// MCU status. Read only.
+        #[ro]
         pub(crate) MCU_STATUS(u32) @ 0x704 {
             /// Read current state of MCU.
             1:0 value => McuStatus;
@@ -897,6 +962,7 @@ pub(crate) mod job_control {
         /// Clear job interrupts. Write only.
         ///
         /// Write a 1 to a bit to clear the corresponding bit in [`JOB_IRQ_RAWSTAT`].
+        #[wo]
         pub(crate) JOB_IRQ_CLEAR(u32) @ 0x1004 {
             /// Clear CSG request interrupts.
             30:0    csg;
@@ -918,6 +984,7 @@ pub(crate) mod job_control {
         ///
         /// This register contains the result of ANDing together [`JOB_IRQ_RAWSTAT`] and
         /// [`JOB_IRQ_MASK`].
+        #[ro]
         pub(crate) JOB_IRQ_STATUS(u32) @ 0x100c {
             /// CSG request interrupt status.
             30:0    csg;
@@ -953,6 +1020,7 @@ pub(crate) mod mmu_control {
 
         /// IRQ sources to clear.
         /// Write a 1 to a bit to clear the corresponding bit in [`IRQ_RAWSTAT`].
+        #[wo]
         pub(crate) IRQ_CLEAR(u32) @ 0x2004 {
             /// Clear the PAGE_FAULT interrupt.
             15:0    page_fault;
@@ -973,6 +1041,7 @@ pub(crate) mod mmu_control {
         /// IRQ status for enabled sources. Read only.
         ///
         /// This register contains the result of ANDing together [`IRQ_RAWSTAT`] and [`IRQ_MASK`].
+        #[ro]
         pub(crate) IRQ_STATUS(u32) @ 0x200c {
             /// PAGE_FAULT interrupt status.
             15:0    page_fault;
@@ -1388,6 +1457,7 @@ fn from(cmd: MmuCommand) -> Self {
             base: TyrRegisters;
 
             /// MMU command register for each address space. Write only.
+            #[wo]
             pub(crate) COMMAND(u32)[MAX_AS, stride = STRIDE] @ 0x2418 {
                 7:0     command ?=> MmuCommand;
             }
@@ -1517,6 +1587,7 @@ fn from(access: MmuAccessType) -> Self {
             base: TyrRegisters;
 
             /// Fault status register for each address space. Read only.
+            #[ro]
             pub(crate) FAULTSTATUS(u32)[MAX_AS, stride = STRIDE] @ 0x241c {
                 /// Exception type.
                 7:0     exception_type ?=> MmuExceptionType;
@@ -1527,15 +1598,18 @@ fn from(access: MmuAccessType) -> Self {
             }
 
             /// Fault address for each address space. Read only.
+            #[ro]
             pub(crate) FAULTADDRESS_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2420 {
                 31:0    pointer;
             }
 
+            #[ro]
             pub(crate) FAULTADDRESS_HI(u32)[MAX_AS, stride = STRIDE] @ 0x2424 {
                 31:0    pointer;
             }
 
             /// MMU status register for each address space. Read only.
+            #[ro]
             pub(crate) STATUS(u32)[MAX_AS, stride = STRIDE] @ 0x2428 {
                 /// External address space command is active, a 1-bit boolean flag.
                 0:0     active_ext => bool;
@@ -1783,10 +1857,12 @@ fn from(sh: PtwShareability) -> Self {
             }
 
             /// Extra fault information for each address space. Read only.
+            #[ro]
             pub(crate) FAULTEXTRA_LO(u32)[MAX_AS, stride = STRIDE] @ 0x2438 {
                 31:0    value;
             }
 
+            #[ro]
             pub(crate) FAULTEXTRA_HI(u32)[MAX_AS, stride = STRIDE] @ 0x243c {
                 31:0    value;
             }
@@ -1816,6 +1892,7 @@ pub(crate) mod doorbell_block {
         base: TyrRegisters;
 
         /// Doorbell request register. Write-only.
+        #[wo]
         pub(crate) DOORBELL(u32)[NUM_DOORBELLS, stride = STRIDE] @ 0x80000 {
             /// Doorbell set. Writing 1 triggers the doorbell.
             0:0    ring => bool;

-- 
2.56.0


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH 7/7] drm/tyr: mark read-only and write-only registers
  2026-10-15 12:39 ` [PATCH 7/7] drm/tyr: mark read-only and write-only registers Daniel Almeida
@ 2026-10-15 12:43   ` sashiko-bot
  0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-15 12:43 UTC (permalink / raw)
  To: Daniel Almeida; +Cc: linux-pci

> The documentation in regs.rs describes some registers as read-only or
> write-only, but nothing enforces it, and most registers do not document
> a mode at all.
> 
> Declare every read-only and write-only register with the `#[ro]` and
> `#[wo]` attributes of `register!`, so that writing a read-only register,
> or reading a write-only one, does not compile. The modes follow the
> documentation in regs.rs where there is one, and otherwise the register
> permissions in Arm's kbase driver for v10 GPUs. Both halves of a 64-bit
> register get the same mode, and so do the aliases of `GPU_COMMAND`.
> 
> No existing access goes against these modes, so the driver code does not
> change.
> 
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-io-access-modes-v1-0-93611b84b5e3@collabora.com?part=7


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/7] rust: io: split IoCopyable into IoCopyReadable and IoCopyWritable
  2026-10-15 12:39 ` [PATCH 2/7] rust: io: split IoCopyable into IoCopyReadable and IoCopyWritable Daniel Almeida
@ 2026-10-15 12:44   ` sashiko-bot
  0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-15 12:44 UTC (permalink / raw)
  To: Daniel Almeida; +Cc: linux-pci

> `IoCopyable` provides both `copy_from_io` and `copy_to_io`, so a backend
> that supports copies supports them in both directions, and all four copy
> methods of `Io` require both.
> 
> Thus, as done for `IoCapable` in the previous patch, replace `IoCopyable`
> with an `IoCopyReadable` trait holding `copy_from_io` and `copy_read`, and
> an `IoCopyWritable` trait holding `copy_to_io` and `copy_write`, and
> implement both for every backend that implemented `IoCopyable`.
> `copy_read` and `copy_to_slice`, which read from I/O memory, now require
> `IoCopyReadable`, and `copy_write` and `copy_from_slice`, which write to
> it, require `IoCopyWritable`.
> 
> No functional change intended.
> 
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-io-access-modes-v1-0-93611b84b5e3@collabora.com?part=2


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 3/7] rust: io: add read-only and write-only views
  2026-10-15 12:39 ` [PATCH 3/7] rust: io: add read-only and write-only views Daniel Almeida
@ 2026-10-15 12:44   ` sashiko-bot
  0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-15 12:44 UTC (permalink / raw)
  To: Daniel Almeida; +Cc: linux-pci

> An I/O view can always be both read and written, as long as its backend
> supports both directions. The I/O API has no view that only allows the
> accesses the hardware or firmware permits, such as a status register
> that must not be written or a doorbell that must not be read.
> 
> Add `ReadOnly` and `WriteOnly`, which wrap a view of any backend, and
> their backends `ReadOnlyBackend` and `WriteOnlyBackend`. A read-only
> backend implements `IoReadable` and `IoCopyReadable` where the wrapped
> backend does, by forwarding to it, and implements neither `IoWritable`
> nor `IoCopyWritable`, so the write methods of `Io` do not compile on a
> `ReadOnly` view. `WriteOnly` is the converse. Views projected from a
> wrapped view stay wrapped, and the wrapped view cannot be retrieved.
> 
> Add `Io::read_only` and `Io::write_only` to create them from any view or
> I/O handle.
> 
> There are no users yet. Register locations with an access mode will be
> the first, in a following patch.
> 
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-io-access-modes-v1-0-93611b84b5e3@collabora.com?part=3


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 4/7] rust: io: project primitive accessors directly
  2026-10-15 12:39 ` [PATCH 4/7] rust: io: project primitive accessors directly Daniel Almeida
@ 2026-10-15 12:44   ` sashiko-bot
  0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-15 12:44 UTC (permalink / raw)
  To: Daniel Almeida; +Cc: linux-pci

> The primitive accessors, `read8` to `write64` and their `try_` variants,
> pass their offset as an `IoLoc` to the generic `read`, `write`,
> `try_read` and `try_write`, which project the view to that location and
> then call `read_val` or `write_val`. Commit 147b41ba23d6 ("rust: io: use
> generic read/write accessors for primitive accesses") made them do so to
> reduce the number of unsafe blocks.
> 
> Since commit e0454ec1220c ("rust: io: use view types instead of
> addresses for `Io`"), projecting to an offset is safe. Thus, have the
> primitive accessors project with `io_view` and `io_view_assert` and call
> `read_val` or `write_val` themselves. The bounds checks are the same.
> 
> This prepares for access modes on I/O locations, added in a following
> patch, which make the bounds of the generic methods depend on the mode
> of the location. The primitive accessors keep their current bounds.
> 
> No functional change intended.
> 
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-io-access-modes-v1-0-93611b84b5e3@collabora.com?part=4


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 1/7] rust: io: split IoCapable into IoReadable and IoWritable
  2026-10-15 12:39 ` [PATCH 1/7] rust: io: split IoCapable into IoReadable and IoWritable Daniel Almeida
@ 2026-10-15 12:46   ` sashiko-bot
  0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-15 12:46 UTC (permalink / raw)
  To: Daniel Almeida; +Cc: linux-pci

> `IoCapable<T>` provides both `io_read` and `io_write`, so a backend that
> supports accesses of type `T` supports them in both directions, and every
> (non-copy) `Io` method that reads or writes a value requires both. There
> is no way to express a backend, or a view, that can only be read or only
> be written.
> 
> Thus, replace `IoCapable<T>` with an `IoReadable<T>` trait holding
> `io_read` and an `IoWritable<T>` trait holding `io_write`, and implement
> both for all existing backends. The non-copy `Io` methods that read a
> value now require `IoReadable` and those that write one require
> `IoWritable`. `update` and `try_update` read and then write, so they
> require both.
> 
> The copy methods use `IoCopyable` and are not changed here.
> 
> No functional change intended. This prepares for read-only and write-only
> views, added in a following patch.
> 
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-io-access-modes-v1-0-93611b84b5e3@collabora.com?part=1


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 6/7] rust: io: register: add `#[ro]` and `#[wo]` attributes
  2026-10-15 12:39 ` [PATCH 6/7] rust: io: register: add `#[ro]` and `#[wo]` attributes Daniel Almeida
@ 2026-10-15 12:47   ` sashiko-bot
  0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-15 12:47 UTC (permalink / raw)
  To: Daniel Almeida; +Cc: linux-pci

> `register!` gives every register a location with the `ReadWrite` access
> mode, so any register can be read, written and updated, including the
> ones the hardware defines as read-only or write-only.
> 
> Add the `#[ro]` and `#[wo]` attributes, which make the macro use the
> `ReadOnly` or `WriteOnly` mode instead: in the register's `OffsetLoc`
> for fixed registers, and as `RegisterArray::Access` for arrays. Reading
> a write-only register, or writing or updating a read-only one, then does
> not compile. An alias does not inherit the mode of the register it
> aliases. Add an example to the `register!` documentation.
> 
> The tyr driver uses the attributes in a following patch.
> 
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-io-access-modes-v1-0-93611b84b5e3@collabora.com?part=6


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 5/7] rust: io: let I/O locations carry an access mode
  2026-10-15 12:39 ` [PATCH 5/7] rust: io: let I/O locations carry an access mode Daniel Almeida
@ 2026-10-15 12:50   ` sashiko-bot
  0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-10-15 12:50 UTC (permalink / raw)
  To: Daniel Almeida; +Cc: linux-pci

> `Io::read`, `Io::write`, `Io::update` and their fallible variants
> project the view to the given `IoLoc` and access it there. A location
> has no say in which accesses are allowed, so a register cannot be
> declared read-only or write-only.
> 
> Add the `io::access` module with the `Mode` trait and the `ReadWrite`,
> `ReadOnly` and `WriteOnly` modes, and add an `Access` associated type to
> `IoLoc`. Projecting through a location applies its mode: `ReadWrite`
> leaves the view as it is, while `ReadOnly` and `WriteOnly` wrap it in the
> `ReadOnly` and `WriteOnly` views. The bounds of the generic accessors now
> apply to the backend of the restricted view, so a write to a read-only
> location, or a read from a write-only one, does not compile.
> 
> Raw offsets are `ReadWrite`. `OffsetLoc` takes the mode as a type
> parameter that defaults to `ReadWrite`, and `RegisterArray` gets an
> `Access` type, which `register!` sets to `ReadWrite` for now.
> 
> No existing location changes its mode, so there is no functional change.
> A following patch lets `register!` declare read-only and write-only
> registers.
> 
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261015-io-access-modes-v1-0-93611b84b5e3@collabora.com?part=5


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2026-10-15 12:50 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-15 12:39 [PATCH 0/7] rust: io: add read-only and write-only views and registers Daniel Almeida
2026-10-15 12:39 ` [PATCH 1/7] rust: io: split IoCapable into IoReadable and IoWritable Daniel Almeida
2026-10-15 12:46   ` sashiko-bot
2026-10-15 12:39 ` [PATCH 2/7] rust: io: split IoCopyable into IoCopyReadable and IoCopyWritable Daniel Almeida
2026-10-15 12:44   ` sashiko-bot
2026-10-15 12:39 ` [PATCH 3/7] rust: io: add read-only and write-only views Daniel Almeida
2026-10-15 12:44   ` sashiko-bot
2026-10-15 12:39 ` [PATCH 4/7] rust: io: project primitive accessors directly Daniel Almeida
2026-10-15 12:44   ` sashiko-bot
2026-10-15 12:39 ` [PATCH 5/7] rust: io: let I/O locations carry an access mode Daniel Almeida
2026-10-15 12:50   ` sashiko-bot
2026-10-15 12:39 ` [PATCH 6/7] rust: io: register: add `#[ro]` and `#[wo]` attributes Daniel Almeida
2026-10-15 12:47   ` sashiko-bot
2026-10-15 12:39 ` [PATCH 7/7] drm/tyr: mark read-only and write-only registers Daniel Almeida
2026-10-15 12:43   ` sashiko-bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.