From: Daniel Almeida <daniel.almeida@collabora.com>
To: Gary Guo <gary@garyguo.net>
Cc: "Alice Ryhl" <aliceryhl@google.com>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Rafael J. Wysocki" <rafael@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Trevor Gross" <tmgross@umich.edu>,
"Tamir Duberstein" <tamird@kernel.org>,
"Onur Özkan" <work@onurozkan.dev>,
"Bjorn Helgaas" <bhelgaas@google.com>,
"Krzysztof Wilczyński" <kwilczynski@kernel.org>,
"Abdiel Janulgue" <abdiel.janulgue@gmail.com>,
"Robin Murphy" <robin.murphy@arm.com>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Michal Wilczynski" <m.wilczynski@samsung.com>,
"Uwe Kleine-König" <ukleinek@kernel.org>,
"Danilo Krummrich" <dakr@kernel.org>,
driver-core@lists.linux.dev, rust-for-linux@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pci@vger.kernel.org,
nova-gpu@lists.linux.dev, dri-devel@lists.freedesktop.org,
linux-pwm@vger.kernel.org
Subject: Re: [PATCH v6 09/20] rust: io: use view types instead of addresses for `Io`
Date: Wed, 8 Jul 2026 11:36:12 -0300 [thread overview]
Message-ID: <280FBE45-5653-499B-9BB0-A2C77030A826@collabora.com> (raw)
In-Reply-To: <20260706-io_projection-v6-9-72cd5d055d54@garyguo.net>
> On 6 Jul 2026, at 09:44, Gary Guo <gary@garyguo.net> wrote:
>
> Currently, `io_read` and `io_write` methods require the exact type of `Io`
> plus an address. This means that they need to be monomorphized for each
> different `Io` instance. This also means that multiple I/O implementors for
> the same I/O kind needs to duplicate implementation (e.g. `Mmio` and
> `MmioOwned`).
>
> Create a new `IoBackend` trait and define these operations on it instead.
> The operations are just going to receive a view type and operate on them.
> This has the additional advantage that the invariants can be moved from the
> trait (and guaranteed via `unsafe`) to type invariants on the canonical
> view types of the backends, so `io_read` and `io_write` can be safe.
>
> Note that view type is needed; addresses are insufficient in this
nit: missing article: “a view type is needed” or “view types are needed”.
> design, as they do not carry sufficient information. For example,
> `ConfigSpace` needs `&pci::Device` in addition to the address.
>
> `io_addr_assert` and `io_addr` are renamed to `io_view*` to reflect
> that they operate on views now, and make them standalone functions so
> they cannot be used by users to cast types outside io.rs.
>
> Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
> Signed-off-by: Gary Guo <gary@garyguo.net>
> ---
> rust/kernel/io.rs | 382 ++++++++++++++++++++++++++------------------------
> rust/kernel/pci/io.rs | 70 +++++----
> 2 files changed, 246 insertions(+), 206 deletions(-)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index 8110b49aa430..89039b3defd5 100644
> --- a/rust/kernel/io.rs
> +++ b/rust/kernel/io.rs
> @@ -243,6 +243,81 @@ const fn offset_valid<U>(base: usize, offset: usize, size: usize) -> bool {
> }
> }
>
> +/// Returns a view for a given `offset`, performing compile-time bound checks.
> +// Always inline to optimize out error path of `build_assert`.
You mean to avoid that cryptic “rust_build_error” when the assertion fails?
> +#[inline(always)]
> +fn io_view_assert<'a, IO: Io<'a>, U>(
> + this: IO,
> + offset: usize,
> +) -> <IO::Backend as IoBackend>::View<'a, U> {
> + // We cannot check alignment with `offset_valid` using `ptr.addr()`. So set 0 for it and
> + // ensure alignment by checking that the alignment of `U` is smaller or equal to the
> + // alignment of `IO::Target`.
> + const_assert!(Alignment::of::<U>().as_usize() <= IO::Target::MIN_ALIGN.as_usize());
> + build_assert!(offset_valid::<U>(0, offset, IO::Target::MIN_SIZE));
> +
> + let view = this.as_view();
> + let ptr = IO::Backend::as_ptr(view);
> + let projected_ptr = ptr.cast::<U>().wrapping_byte_add(offset);
> + // SAFETY: `offset_valid` checks for size and alignment and therefore `projected_ptr` is a
> + // valid projection.
> + unsafe { IO::Backend::project_view(view, projected_ptr) }
> +}
> +
> +/// Returns a view for a given `offset`, performing runtime bound checks.
> +#[inline]
> +fn io_view<'a, IO: Io<'a>, U>(
> + this: IO,
> + offset: usize,
> +) -> Result<<IO::Backend as IoBackend>::View<'a, U>> {
> + let view = this.as_view();
> + let ptr = IO::Backend::as_ptr(view);
> +
> + if !offset_valid::<U>(ptr.addr(), offset, KnownSize::size(ptr)) {
> + return Err(EINVAL);
> + }
> +
> + let projected_ptr = ptr.cast::<U>().wrapping_byte_add(offset);
> + // SAFETY: `offset_valid` checks for size and alignment and therefore `projected_ptr` is a
> + // valid projection.
> + Ok(unsafe { IO::Backend::project_view(view, projected_ptr) })
> +}
> +
> +/// I/O backends.
> +///
> +/// This is an abstract representation to be implemented by arbitrary I/O
> +/// 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.
> +///
> +/// This trait is separate from the `Io` trait as multiple different I/O types may share the same
> +/// operation.
> +pub trait IoBackend {
> + /// View type for this I/O backend.
> + type View<'a, T: ?Sized + KnownSize>: Io<'a, Backend = Self, Target = T>;
> +
> + /// Convert a `view` to a raw pointer for projection.
> + ///
> + /// The returned pointer is private implementation detail of the backend; it is likely not
> + /// valid. It should not be dereferenced.
> + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T;
> +
> + /// Project `view` to its subregion indicated by `ptr`.
> + ///
> + /// If input `view` is valid, returned view must also be valid.
> + ///
> + /// # Safety
> + ///
> + /// `ptr` must be a projection of `Self::as_ptr(view)`.
> + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
> + view: Self::View<'a, T>,
> + ptr: *mut U,
> + ) -> Self::View<'a, U>;
> +}
> +
> /// Trait indicating that an I/O backend supports operations of a certain type and providing an
> /// implementation for these operations.
> ///
> @@ -251,22 +326,12 @@ const fn offset_valid<U>(base: usize, offset: usize, size: usize) -> bool {
> /// 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
> /// system might implement all four.
> -pub trait IoCapable<T> {
> - /// Performs an I/O read of type `T` at `address` and returns the result.
> - ///
> - /// # Safety
> - ///
> - /// - The range `[address..address + size_of::<T>()]` must be within the bounds of `Self`.
> - /// - `address` must be aligned.
> - unsafe fn io_read(self, address: usize) -> T;
> +pub trait IoCapable<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;
>
> - /// Performs an I/O write of `value` at `address`.
> - ///
> - /// # Safety
> - ///
> - /// - The range `[address..address + size_of::<T>()]` must be within the bounds of `Self`.
> - /// - `address` must be aligned.
> - unsafe fn io_write(self, value: T, address: usize);
> + /// Performs an I/O write of `value` at `view`.
> + fn io_write<'a>(view: Self::View<'a, T>, value: T);
> }
>
> /// Describes a given I/O location: its offset, width, and type to convert the raw value from and
> @@ -318,66 +383,30 @@ fn offset(self) -> usize {
> /// Types implementing this trait (e.g. MMIO BARs or PCI config regions)
> /// can perform I/O operations on regions of memory.
> ///
> -/// This is an abstract representation to be implemented by arbitrary I/O
> -/// backends (e.g. MMIO, PCI config space, etc.).
> -///
> /// The [`Io`] trait provides:
> -/// - Base address and size information
> +/// - Method to convert into [`IoBackend::View`].
> /// - Helper methods for offset validation and address calculation
> /// - Fallible (runtime checked) accessors for different data widths
> ///
> -/// Which I/O methods are available depends on which [`IoCapable<T>`] traits
> -/// are implemented for the type.
> +/// Which I/O methods are available depends on the associated [`IoBackend`] implementation.
> ///
> /// This should be implemented on cheaply copyable handles, such as references or view types.
> -///
> -/// # Examples
> -///
> -/// 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.
> -pub trait Io: Copy {
> +pub trait Io<'a>: Copy {
> + /// Type that defines all I/O operations.
> + type Backend: IoBackend;
> +
> /// Type of this I/O region. For untyped regions, [`Region`] can be used.
> type Target: ?Sized + KnownSize;
>
> - /// Returns the base address of this mapping.
> - fn addr(self) -> usize;
> -
> - /// Returns the maximum size of this mapping.
> - fn maxsize(self) -> usize;
> -
> - /// Returns the absolute I/O address for a given `offset`,
> - /// performing compile-time bound checks.
> - // Always inline to optimize out error path of `build_assert`.
> - #[inline(always)]
> - fn io_addr_assert<U>(self, offset: usize) -> usize {
> - // We cannot check alignment with `offset_valid` using `self.addr()`. So set 0 for it and
> - // ensure alignment by checking that the alignment of `U` is smaller or equal to the
> - // alignment of `Self::Target`.
> - const_assert!(Alignment::of::<U>().as_usize() <= Self::Target::MIN_ALIGN.as_usize());
> - build_assert!(offset_valid::<U>(0, offset, Self::Target::MIN_SIZE));
> -
> - self.addr() + offset
> - }
> -
> - /// Returns the absolute I/O address for a given `offset`,
> - /// performing runtime bound checks.
> - #[inline]
> - fn io_addr<U>(self, offset: usize) -> Result<usize> {
> - if !offset_valid::<U>(self.addr(), offset, self.maxsize()) {
> - return Err(EINVAL);
> - }
> -
> - // Probably no need to check, since the safety requirements of `Self::new` guarantee that
> - // this can't overflow.
> - self.addr().checked_add(offset).ok_or(EINVAL)
> - }
> + /// Return a view that covers the full region.
> + fn as_view(self) -> <Self::Backend as IoBackend>::View<'a, Self::Target>;
>
> /// Fallible 8-bit read with runtime bounds check.
> #[inline(always)]
> fn try_read8(self, offset: usize) -> Result<u8>
> where
> usize: IoLoc<Self::Target, u8, IoType = u8>,
> - Self: IoCapable<u8>,
> + Self::Backend: IoCapable<u8>,
> {
> self.try_read(offset)
> }
> @@ -387,7 +416,7 @@ fn try_read8(self, offset: usize) -> Result<u8>
> fn try_read16(self, offset: usize) -> Result<u16>
> where
> usize: IoLoc<Self::Target, u16, IoType = u16>,
> - Self: IoCapable<u16>,
> + Self::Backend: IoCapable<u16>,
> {
> self.try_read(offset)
> }
> @@ -397,7 +426,7 @@ fn try_read16(self, offset: usize) -> Result<u16>
> fn try_read32(self, offset: usize) -> Result<u32>
> where
> usize: IoLoc<Self::Target, u32, IoType = u32>,
> - Self: IoCapable<u32>,
> + Self::Backend: IoCapable<u32>,
> {
> self.try_read(offset)
> }
> @@ -407,7 +436,7 @@ fn try_read32(self, offset: usize) -> Result<u32>
> fn try_read64(self, offset: usize) -> Result<u64>
> where
> usize: IoLoc<Self::Target, u64, IoType = u64>,
> - Self: IoCapable<u64>,
> + Self::Backend: IoCapable<u64>,
> {
> self.try_read(offset)
> }
> @@ -417,7 +446,7 @@ fn try_read64(self, offset: usize) -> Result<u64>
> fn try_write8(self, value: u8, offset: usize) -> Result
> where
> usize: IoLoc<Self::Target, u8, IoType = u8>,
> - Self: IoCapable<u8>,
> + Self::Backend: IoCapable<u8>,
> {
> self.try_write(offset, value)
> }
> @@ -427,7 +456,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, IoType = u16>,
> - Self: IoCapable<u16>,
> + Self::Backend: IoCapable<u16>,
> {
> self.try_write(offset, value)
> }
> @@ -437,7 +466,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, IoType = u32>,
> - Self: IoCapable<u32>,
> + Self::Backend: IoCapable<u32>,
> {
> self.try_write(offset, value)
> }
> @@ -447,7 +476,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, IoType = u64>,
> - Self: IoCapable<u64>,
> + Self::Backend: IoCapable<u64>,
> {
> self.try_write(offset, value)
> }
> @@ -457,7 +486,7 @@ fn try_write64(self, value: u64, offset: usize) -> Result
> fn read8(self, offset: usize) -> u8
> where
> usize: IoLoc<Self::Target, u8, IoType = u8>,
> - Self: IoCapable<u8>,
> + Self::Backend: IoCapable<u8>,
> {
> self.read(offset)
> }
> @@ -467,7 +496,7 @@ fn read8(self, offset: usize) -> u8
> fn read16(self, offset: usize) -> u16
> where
> usize: IoLoc<Self::Target, u16, IoType = u16>,
> - Self: IoCapable<u16>,
> + Self::Backend: IoCapable<u16>,
> {
> self.read(offset)
> }
> @@ -477,7 +506,7 @@ fn read16(self, offset: usize) -> u16
> fn read32(self, offset: usize) -> u32
> where
> usize: IoLoc<Self::Target, u32, IoType = u32>,
> - Self: IoCapable<u32>,
> + Self::Backend: IoCapable<u32>,
> {
> self.read(offset)
> }
> @@ -487,7 +516,7 @@ fn read32(self, offset: usize) -> u32
> fn read64(self, offset: usize) -> u64
> where
> usize: IoLoc<Self::Target, u64, IoType = u64>,
> - Self: IoCapable<u64>,
> + Self::Backend: IoCapable<u64>,
> {
> self.read(offset)
> }
> @@ -497,7 +526,7 @@ fn read64(self, offset: usize) -> u64
> fn write8(self, value: u8, offset: usize)
> where
> usize: IoLoc<Self::Target, u8, IoType = u8>,
> - Self: IoCapable<u8>,
> + Self::Backend: IoCapable<u8>,
> {
> self.write(offset, value)
> }
> @@ -507,7 +536,7 @@ fn write8(self, value: u8, offset: usize)
> fn write16(self, value: u16, offset: usize)
> where
> usize: IoLoc<Self::Target, u16, IoType = u16>,
> - Self: IoCapable<u16>,
> + Self::Backend: IoCapable<u16>,
> {
> self.write(offset, value)
> }
> @@ -517,7 +546,7 @@ fn write16(self, value: u16, offset: usize)
> fn write32(self, value: u32, offset: usize)
> where
> usize: IoLoc<Self::Target, u32, IoType = u32>,
> - Self: IoCapable<u32>,
> + Self::Backend: IoCapable<u32>,
> {
> self.write(offset, value)
> }
> @@ -527,7 +556,7 @@ fn write32(self, value: u32, offset: usize)
> fn write64(self, value: u64, offset: usize)
> where
> usize: IoLoc<Self::Target, u64, IoType = u64>,
> - Self: IoCapable<u64>,
> + Self::Backend: IoCapable<u64>,
> {
> self.write(offset, value)
> }
> @@ -559,12 +588,10 @@ fn write64(self, value: u64, offset: usize)
> fn try_read<T, L>(self, location: L) -> Result<T>
> where
> L: IoLoc<Self::Target, T>,
> - Self: IoCapable<L::IoType>,
> + Self::Backend: IoCapable<L::IoType>,
> {
> - let address = self.io_addr::<L::IoType>(location.offset())?;
> -
> - // SAFETY: `address` has been validated by `io_addr`.
> - Ok(unsafe { self.io_read(address) }.into())
> + let view = io_view::<Self, L::IoType>(self, location.offset())?;
> + Ok(Self::Backend::io_read(view).into())
> }
>
> /// Generic fallible write with runtime bounds check.
> @@ -594,14 +621,11 @@ fn try_read<T, L>(self, location: L) -> Result<T>
> fn try_write<T, L>(self, location: L, value: T) -> Result
> where
> L: IoLoc<Self::Target, T>,
> - Self: IoCapable<L::IoType>,
> + Self::Backend: IoCapable<L::IoType>,
> {
> - let address = self.io_addr::<L::IoType>(location.offset())?;
> + let view = io_view::<Self, L::IoType>(self, location.offset())?;
> let io_value = value.into();
> -
> - // SAFETY: `address` has been validated by `io_addr`.
> - unsafe { self.io_write(io_value, address) }
> -
> + Self::Backend::io_write(view, io_value);
> Ok(())
> }
>
> @@ -642,7 +666,7 @@ fn try_write_reg<T, L, V>(self, value: V) -> Result
> where
> L: IoLoc<Self::Target, T>,
> V: LocatedRegister<Self::Target, Location = L, Value = T>,
> - Self: IoCapable<L::IoType>,
> + Self::Backend: IoCapable<L::IoType>,
> {
> let (location, value) = value.into_io_op();
>
> @@ -675,17 +699,14 @@ fn try_write_reg<T, L, V>(self, value: V) -> Result
> fn try_update<T, L, F>(self, location: L, f: F) -> Result
> where
> L: IoLoc<Self::Target, T>,
> - Self: IoCapable<L::IoType>,
> + Self::Backend: IoCapable<L::IoType>,
> F: FnOnce(T) -> T,
> {
> - let address = self.io_addr::<L::IoType>(location.offset())?;
> + let view = io_view::<Self, L::IoType>(self, location.offset())?;
>
> - // SAFETY: `address` has been validated by `io_addr`.
> - let value: T = unsafe { self.io_read(address) }.into();
> + let value: T = Self::Backend::io_read(view).into();
> let io_value = f(value).into();
> -
> - // SAFETY: `address` has been validated by `io_addr`.
> - unsafe { self.io_write(io_value, address) }
> + Self::Backend::io_write(view, io_value);
>
> Ok(())
> }
> @@ -715,12 +736,10 @@ fn try_update<T, L, F>(self, location: L, f: F) -> Result
> fn read<T, L>(self, location: L) -> T
> where
> L: IoLoc<Self::Target, T>,
> - Self: IoCapable<L::IoType>,
> + Self::Backend: IoCapable<L::IoType>,
> {
> - let address = self.io_addr_assert::<L::IoType>(location.offset());
> -
> - // SAFETY: `address` has been validated by `io_addr_assert`.
> - unsafe { self.io_read(address) }.into()
> + let view = io_view_assert::<Self, L::IoType>(self, location.offset());
> + Self::Backend::io_read(view).into()
> }
>
> /// Generic infallible write with compile-time bounds check.
> @@ -748,13 +767,11 @@ fn read<T, L>(self, location: L) -> T
> fn write<T, L>(self, location: L, value: T)
> where
> L: IoLoc<Self::Target, T>,
> - Self: IoCapable<L::IoType>,
> + Self::Backend: IoCapable<L::IoType>,
> {
> - let address = self.io_addr_assert::<L::IoType>(location.offset());
> + let view = io_view_assert::<Self, L::IoType>(self, location.offset());
> let io_value = value.into();
> -
> - // SAFETY: `address` has been validated by `io_addr_assert`.
> - unsafe { self.io_write(io_value, address) }
> + Self::Backend::io_write(view, io_value);
> }
>
> /// Generic infallible write of a fully-located register value.
> @@ -793,7 +810,7 @@ fn write_reg<T, L, V>(self, value: V)
> where
> L: IoLoc<Self::Target, T>,
> V: LocatedRegister<Self::Target, Location = L, Value = T>,
> - Self: IoCapable<L::IoType>,
> + Self::Backend: IoCapable<L::IoType>,
> {
> let (location, value) = value.into_io_op();
>
> @@ -826,17 +843,13 @@ fn write_reg<T, L, V>(self, value: V)
> fn update<T, L, F>(self, location: L, f: F)
> where
> L: IoLoc<Self::Target, T>,
> - Self: IoCapable<L::IoType>,
> + Self::Backend: IoCapable<L::IoType>,
> F: FnOnce(T) -> T,
> {
> - let address = self.io_addr_assert::<L::IoType>(location.offset());
> -
> - // SAFETY: `address` has been validated by `io_addr_assert`.
> - let value: T = unsafe { self.io_read(address) }.into();
> + let view = io_view_assert::<Self, L::IoType>(self, location.offset());
> + let value: T = Self::Backend::io_read(view).into();
> let io_value = f(value).into();
> -
> - // SAFETY: `address` has been validated by `io_addr_assert`.
> - unsafe { self.io_write(io_value, address) }
> + Self::Backend::io_write(view, io_value);
> }
> }
>
> @@ -880,78 +893,78 @@ unsafe impl<T: ?Sized + Sync> Send for Mmio<'_, T> {}
> // SAFETY: `Mmio<'_, T>` is conceptually `&T` but in I/O memory.
> unsafe impl<T: ?Sized + Sync> Sync for Mmio<'_, T> {}
>
> -impl<T: ?Sized + KnownSize> Io for Mmio<'_, T> {
> +impl<'a, T: ?Sized + KnownSize> Io<'a> for Mmio<'a, T> {
> + type Backend = MmioBackend;
> type Target = T;
>
> #[inline]
> - fn addr(self) -> usize {
> - self.ptr.addr()
> + fn as_view(self) -> Mmio<'a, T> {
> + self
> }
> +}
> +
> +/// I/O Backend for memory-mapped I/O.
> +pub struct MmioBackend;
> +
> +impl IoBackend for MmioBackend {
> + type View<'a, T: ?Sized + KnownSize> = Mmio<'a, T>;
>
> #[inline]
> - fn maxsize(self) -> usize {
> - KnownSize::size(self.ptr)
> + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T {
> + view.ptr
> + }
> +
> + #[inline]
> + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
> + _view: Self::View<'a, T>,
> + ptr: *mut U,
> + ) -> Self::View<'a, U> {
> + // INVARIANT: Per safety requirement, `ptr` is projection from `view`, so it is also a valid
> + // memory-mapped I/O region.
> + Mmio {
> + ptr,
> + phantom: PhantomData,
> + }
> }
> }
>
> -/// Implements [`IoCapable`] on `$mmio` for `$ty` using `$read_fn` and `$write_fn`.
> +/// Implements [`IoCapable`] on `$backend` for `$ty` using `$read_fn` and `$write_fn`.
> macro_rules! impl_mmio_io_capable {
> - ($mmio:ident, $(#[$attr:meta])* $ty:ty, $read_fn:ident, $write_fn:ident) => {
> - $(#[$attr])*
> - impl<T: ?Sized> IoCapable<$ty> for $mmio<'_, T> {
> + ($backend: ident, $ty:ty, $read_fn:ident, $write_fn:ident) => {
> + impl IoCapable<$ty> for $backend {
> #[inline]
> - unsafe fn io_read(self, address: usize) -> $ty {
> - // SAFETY: By the trait invariant `address` is a valid address for MMIO operations.
> - unsafe { bindings::$read_fn(address as *const c_void) }
> + 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()) }
> }
>
> #[inline]
> - unsafe fn io_write(self, value: $ty, address: usize) {
> - // SAFETY: By the trait invariant `address` is a valid address for MMIO operations.
> - unsafe { bindings::$write_fn(value, address as *mut c_void) }
> + 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
> + // `MmioBackend` and `RelaxedMmioBackend`.
> + unsafe { bindings::$write_fn(value, $backend::as_ptr(view).cast()) }
> }
> }
> };
> }
>
> // MMIO regions support 8, 16, and 32-bit accesses.
> -impl_mmio_io_capable!(Mmio, u8, readb, writeb);
> -impl_mmio_io_capable!(Mmio, u16, readw, writew);
> -impl_mmio_io_capable!(Mmio, u32, readl, writel);
> +impl_mmio_io_capable!(MmioBackend, u8, readb, writeb);
> +impl_mmio_io_capable!(MmioBackend, u16, readw, writew);
> +impl_mmio_io_capable!(MmioBackend, u32, readl, writel);
> // MMIO regions on 64-bit systems also support 64-bit accesses.
> #[cfg(CONFIG_64BIT)]
> -impl_mmio_io_capable!(Mmio, u64, readq, writeq);
> +impl_mmio_io_capable!(MmioBackend, u64, readq, writeq);
>
> -impl<'a, const SIZE: usize> Io for &'a MmioOwned<SIZE> {
> +impl<'a, const SIZE: usize> Io<'a> for &'a MmioOwned<SIZE> {
> + type Backend = MmioBackend;
> type Target = Region<SIZE>;
>
> - /// Returns the base address of this mapping.
> #[inline]
> - fn addr(self) -> usize {
> - self.0.addr()
> - }
> -
> - /// Returns the maximum size of this mapping.
> - #[inline]
> - fn maxsize(self) -> usize {
> - self.0.size()
> - }
> -}
> -
> -impl<'a, const SIZE: usize, T> IoCapable<T> for &'a MmioOwned<SIZE>
> -where
> - Mmio<'a, Region<SIZE>>: IoCapable<T>,
> -{
> - #[inline]
> - unsafe fn io_read(self, address: usize) -> T {
> - // SAFETY: Per safety requirement.
> - unsafe { self.as_view().io_read(address) }
> - }
> -
> - #[inline]
> - unsafe fn io_write(self, value: T, address: usize) {
> - // SAFETY: Per safety requirement.
> - unsafe { self.as_view().io_write(value, address) }
> + fn as_view(self) -> Mmio<'a, Self::Target> {
> + // SAFETY: `Mmio` has same invariant as `MmioOwned`
> + unsafe { Mmio::from_raw(self.0) }
> }
> }
>
> @@ -967,13 +980,6 @@ pub unsafe fn from_raw(raw: &MmioRaw<Region<SIZE>>) -> &Self {
> // SAFETY: `MmioOwned` is a transparent wrapper around `MmioRaw`.
> unsafe { &*core::ptr::from_ref(raw).cast() }
> }
> -
> - /// Return a view that covers the full region.
> - #[inline]
> - pub fn as_view(&self) -> Mmio<'_, Region<SIZE>> {
> - // SAFETY: `Mmio` has same invariant as `MmioOwned`.
> - unsafe { Mmio::from_raw(self.0) }
> - }
> }
>
> /// [`Mmio`] but using relaxed accessors.
> @@ -992,17 +998,34 @@ fn clone(&self) -> Self {
> }
> }
>
> -impl<T: ?Sized + KnownSize> Io for RelaxedMmio<'_, T> {
> - type Target = T;
> +/// I/O Backend for memory-mapped I/O, with relaxed access semantics.
> +pub struct RelaxedMmioBackend;
> +
> +impl IoBackend for RelaxedMmioBackend {
> + type View<'a, T: ?Sized + KnownSize> = RelaxedMmio<'a, T>;
>
> #[inline]
> - fn addr(self) -> usize {
> - self.0.addr()
> + fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T {
> + MmioBackend::as_ptr(view.0)
> }
>
> #[inline]
> - fn maxsize(self) -> usize {
> - self.0.maxsize()
> + 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.
> + RelaxedMmio(unsafe { MmioBackend::project_view(view.0, ptr) })
> + }
> +}
> +
> +impl<'a, T: ?Sized + KnownSize> Io<'a> for RelaxedMmio<'a, T> {
> + type Backend = RelaxedMmioBackend;
> + type Target = T;
> +
> + #[inline]
> + fn as_view(self) -> RelaxedMmio<'a, T> {
> + self
> }
> }
>
> @@ -1035,14 +1058,9 @@ pub fn relaxed(self) -> RelaxedMmio<'a, T> {
> }
>
> // MMIO regions support 8, 16, and 32-bit accesses.
> -impl_mmio_io_capable!(RelaxedMmio, u8, readb_relaxed, writeb_relaxed);
> -impl_mmio_io_capable!(RelaxedMmio, u16, readw_relaxed, writew_relaxed);
> -impl_mmio_io_capable!(RelaxedMmio, u32, readl_relaxed, writel_relaxed);
> +impl_mmio_io_capable!(RelaxedMmioBackend, u8, readb_relaxed, writeb_relaxed);
> +impl_mmio_io_capable!(RelaxedMmioBackend, u16, readw_relaxed, writew_relaxed);
> +impl_mmio_io_capable!(RelaxedMmioBackend, u32, readl_relaxed, writel_relaxed);
> // MMIO regions on 64-bit systems also support 64-bit accesses.
> -impl_mmio_io_capable!(
> - RelaxedMmio,
> - #[cfg(CONFIG_64BIT)]
> - u64,
> - readq_relaxed,
> - writeq_relaxed
> -);
> +#[cfg(CONFIG_64BIT)]
> +impl_mmio_io_capable!(RelaxedMmioBackend, u64, readq_relaxed, writeq_relaxed);
> diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs
> index 89f4bb483a7f..e67c1e3694fb 100644
> --- a/rust/kernel/pci/io.rs
> +++ b/rust/kernel/pci/io.rs
> @@ -9,6 +9,7 @@
> devres::Devres,
> io::{
> Io,
> + IoBackend,
> IoCapable,
> MmioOwned,
> MmioRaw,
> @@ -84,32 +85,57 @@ unsafe impl<T: ?Sized + Sync> Send for ConfigSpace<'_, T> {}
> // SAFETY: `ConfigSpace<'_, T>` is conceptually `&T` but in I/O memory.
> unsafe impl<T: ?Sized + Sync> Sync for ConfigSpace<'_, T> {}
>
> +/// I/O Backend for PCI configuration space.
> +pub struct ConfigSpaceBackend;
> +
> +impl IoBackend for ConfigSpaceBackend {
> + type View<'a, T: ?Sized + KnownSize> = ConfigSpace<'a, T>;
> +
> + #[inline]
> + fn as_ptr<'a, T: ?Sized + KnownSize>(view: ConfigSpace<'a, T>) -> *mut T {
> + view.ptr
> + }
> +
> + #[inline]
> + unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
> + view: Self::View<'a, T>,
> + ptr: *mut U,
> + ) -> Self::View<'a, U> {
> + // INVARIANT: Per safety requirement.
> + ConfigSpace {
> + pdev: view.pdev,
> + ptr,
> + }
> + }
> +}
> +
> /// Implements [`IoCapable`] 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<'a, T: ?Sized> IoCapable<$ty> for ConfigSpace<'a, T> {
> - unsafe fn io_read(self, address: usize) -> $ty {
> + impl IoCapable<$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,
> + // so the value always fits within `i32` without truncation or sign change.
> + let addr = view.ptr.addr() as i32;
> +
> let mut val: $ty = 0;
>
> // Return value from C function is ignored in infallible accessors.
> - let _ret =
> - // SAFETY: By the type invariant `self.pdev` is a valid address.
> - // 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,
> - // so the value always fits within `i32` without truncation or sign change.
> - unsafe { bindings::$read_fn(self.pdev.as_raw(), address as i32, &mut val) };
> -
> + // SAFETY: By the type invariant `pdev` is a valid address.
> + let _ = unsafe { bindings::$read_fn(view.pdev.as_raw(), addr, &mut val) };
> val
> }
>
> - unsafe fn io_write(self, value: $ty, address: usize) {
> + 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,
> + // so the value always fits within `i32` without truncation or sign change.
> + let addr = view.ptr.addr() as i32;
> +
> // Return value from C function is ignored in infallible accessors.
> - let _ret =
> - // SAFETY: By the type invariant `self.pdev` is a valid address.
> - // 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,
> - // so the value always fits within `i32` without truncation or sign change.
> - unsafe { bindings::$write_fn(self.pdev.as_raw(), address as i32, value) };
> + // SAFETY: By the type invariant `pdev` is a valid address.
> + let _ = unsafe { bindings::$write_fn(view.pdev.as_raw(), addr, value) };
> }
> }
> };
> @@ -120,17 +146,13 @@ unsafe fn io_write(self, value: $ty, address: usize) {
> impl_config_space_io_capable!(u16, pci_read_config_word, pci_write_config_word);
> impl_config_space_io_capable!(u32, pci_read_config_dword, pci_write_config_dword);
>
> -impl<'a, T: ?Sized + KnownSize> Io for ConfigSpace<'a, T> {
> +impl<'a, T: ?Sized + KnownSize> Io<'a> for ConfigSpace<'a, T> {
> + type Backend = ConfigSpaceBackend;
> type Target = T;
>
> #[inline]
> - fn addr(self) -> usize {
> - self.ptr.addr()
> - }
> -
> - #[inline]
> - fn maxsize(self) -> usize {
> - KnownSize::size(self.ptr)
> + fn as_view(self) -> ConfigSpace<'a, T> {
> + self
> }
> }
>
>
> --
> 2.54.0
>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
next prev parent reply other threads:[~2026-07-08 14:37 UTC|newest]
Thread overview: 45+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 12:44 [PATCH v6 00/20] rust: I/O type generalization and projection Gary Guo
2026-07-06 12:44 ` [PATCH v6 01/20] rust: io: add dynamically-sized `Region` type Gary Guo
2026-07-07 17:14 ` Daniel Almeida
2026-07-07 20:02 ` Gary Guo
2026-07-08 10:34 ` Miguel Ojeda
2026-07-06 12:44 ` [PATCH v6 02/20] rust: io: add missing safety requirement in `IoCapable` methods Gary Guo
2026-07-07 18:40 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 03/20] rust: io: restrict untyped IO access and `register!` to `Region` Gary Guo
2026-07-08 0:23 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 04/20] rust: io: implement `Io` on reference types instead Gary Guo
2026-07-08 11:58 ` Daniel Almeida
2026-07-08 12:05 ` Gary Guo
2026-07-06 12:44 ` [PATCH v6 05/20] rust: io: generalize `MmioRaw` to pointer to arbitrary type Gary Guo
2026-07-08 12:22 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 06/20] rust: io: rename `Mmio` to `MmioOwned` Gary Guo
2026-07-08 12:26 ` Daniel Almeida
2026-07-08 12:35 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 07/20] rust: io: implement `Mmio` as view type Gary Guo
2026-07-08 13:10 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 08/20] rust: pci: io: make `ConfigSpace` a view Gary Guo
2026-07-08 13:28 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 09/20] rust: io: use view types instead of addresses for `Io` Gary Guo
2026-07-08 14:36 ` Daniel Almeida [this message]
2026-07-08 15:25 ` Miguel Ojeda
2026-07-06 12:44 ` [PATCH v6 10/20] pwm: th1520: remove unnecessary `deref` Gary Guo
2026-07-08 14:37 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 11/20] rust: io: remove `MmioOwned` Gary Guo
2026-07-08 14:48 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 12/20] rust: io: move `Io` methods to extension trait Gary Guo
2026-07-08 15:15 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 13/20] rust: io: add projection macro and methods Gary Guo
2026-07-08 17:58 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 14/20] rust: io: add I/O backend for system memory with volatile access Gary Guo
2026-07-11 1:26 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 15/20] rust: io: implement a view type for `Coherent` Gary Guo
2026-07-11 1:32 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 16/20] rust: io: add `read_val` and `write_val` functions on `Io` Gary Guo
2026-07-11 1:56 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 17/20] gpu: nova-core: use I/O projection for cleaner encapsulation Gary Guo
2026-07-06 12:44 ` [PATCH v6 18/20] rust: dma: drop `dma_read!` and `dma_write!` API Gary Guo
2026-07-11 2:04 ` Daniel Almeida
2026-07-06 12:44 ` [PATCH v6 19/20] rust: io: add copying methods Gary Guo
2026-07-07 12:17 ` Alexandre Courbot
2026-07-06 12:44 ` [PATCH v6 20/20] rust: io: implement `IoSysMap` Gary Guo
2026-07-11 16:10 ` [PATCH v6 00/20] rust: I/O type generalization and projection Danilo Krummrich
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=280FBE45-5653-499B-9BB0-A2C77030A826@collabora.com \
--to=daniel.almeida@collabora.com \
--cc=a.hindborg@kernel.org \
--cc=abdiel.janulgue@gmail.com \
--cc=acourbot@nvidia.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=bhelgaas@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=driver-core@lists.linux.dev \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=kwilczynski@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=m.wilczynski@samsung.com \
--cc=nova-gpu@lists.linux.dev \
--cc=ojeda@kernel.org \
--cc=rafael@kernel.org \
--cc=robin.murphy@arm.com \
--cc=rust-for-linux@vger.kernel.org \
--cc=simona@ffwll.ch \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=ukleinek@kernel.org \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox