> On 6 Jul 2026, at 09:44, Gary Guo <[email protected]> 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 <[email protected]>
> Signed-off-by: Gary Guo <[email protected]>
> ---
> 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 <[email protected]>