rust: io: implement Io on reference types instead

Currently, `Io` is implemented on owned I/O objects (e.g. `Bar`). This is
going to change with I/O projections, as then `Io` needs to work both for
owned objects and views of them. Views are themselves reference-like
(however they obviously cannot be references, because they belong to a
different address space).

To facilitate the change, change `Io` to be implemented on reference types
for the owned I/O objects, and make methods take `self` instead of `&self`.
When I/O views are implemented, we can then naturally implement `Io` for
these objects.

Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Signed-off-by: Gary Guo <gary@garyguo.net>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Link: https://patch.msgid.link/20260706-io_projection-v6-4-72cd5d055d54@garyguo.net
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
Gary Guo 2026-07-06 13:44:17 +01:00 committed by Danilo Krummrich
parent 6461c5776b
commit 46b1b54139
2 changed files with 48 additions and 46 deletions

View File

@ -224,7 +224,7 @@ pub trait IoCapable<T> {
///
/// - 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;
unsafe fn io_read(self, address: usize) -> T;
/// Performs an I/O write of `value` at `address`.
///
@ -232,7 +232,7 @@ pub trait IoCapable<T> {
///
/// - 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);
unsafe fn io_write(self, value: T, address: usize);
}
/// Describes a given I/O location: its offset, width, and type to convert the raw value from and
@ -295,25 +295,27 @@ fn offset(self) -> usize {
/// Which I/O methods are available depends on which [`IoCapable<T>`] traits
/// are implemented for the type.
///
/// 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 {
pub trait Io: Copy {
/// 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;
fn addr(self) -> usize;
/// Returns the maximum size of this mapping.
fn maxsize(&self) -> usize;
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 {
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`.
@ -326,7 +328,7 @@ fn io_addr_assert<U>(&self, offset: usize) -> usize {
/// Returns the absolute I/O address for a given `offset`,
/// performing runtime bound checks.
#[inline]
fn io_addr<U>(&self, offset: usize) -> Result<usize> {
fn io_addr<U>(self, offset: usize) -> Result<usize> {
if !offset_valid::<U>(self.addr(), offset, self.maxsize()) {
return Err(EINVAL);
}
@ -338,7 +340,7 @@ fn io_addr<U>(&self, offset: usize) -> Result<usize> {
/// Fallible 8-bit read with runtime bounds check.
#[inline(always)]
fn try_read8(&self, offset: usize) -> Result<u8>
fn try_read8(self, offset: usize) -> Result<u8>
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
Self: IoCapable<u8>,
@ -348,7 +350,7 @@ fn try_read8(&self, offset: usize) -> Result<u8>
/// Fallible 16-bit read with runtime bounds check.
#[inline(always)]
fn try_read16(&self, offset: usize) -> Result<u16>
fn try_read16(self, offset: usize) -> Result<u16>
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
Self: IoCapable<u16>,
@ -358,7 +360,7 @@ fn try_read16(&self, offset: usize) -> Result<u16>
/// Fallible 32-bit read with runtime bounds check.
#[inline(always)]
fn try_read32(&self, offset: usize) -> Result<u32>
fn try_read32(self, offset: usize) -> Result<u32>
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
Self: IoCapable<u32>,
@ -368,7 +370,7 @@ fn try_read32(&self, offset: usize) -> Result<u32>
/// Fallible 64-bit read with runtime bounds check.
#[inline(always)]
fn try_read64(&self, offset: usize) -> Result<u64>
fn try_read64(self, offset: usize) -> Result<u64>
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
Self: IoCapable<u64>,
@ -378,7 +380,7 @@ fn try_read64(&self, offset: usize) -> Result<u64>
/// Fallible 8-bit write with runtime bounds check.
#[inline(always)]
fn try_write8(&self, value: u8, offset: usize) -> Result
fn try_write8(self, value: u8, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
Self: IoCapable<u8>,
@ -388,7 +390,7 @@ fn try_write8(&self, value: u8, offset: usize) -> Result
/// Fallible 16-bit write with runtime bounds check.
#[inline(always)]
fn try_write16(&self, value: u16, offset: usize) -> Result
fn try_write16(self, value: u16, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
Self: IoCapable<u16>,
@ -398,7 +400,7 @@ fn try_write16(&self, value: u16, offset: usize) -> Result
/// Fallible 32-bit write with runtime bounds check.
#[inline(always)]
fn try_write32(&self, value: u32, offset: usize) -> Result
fn try_write32(self, value: u32, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
Self: IoCapable<u32>,
@ -408,7 +410,7 @@ fn try_write32(&self, value: u32, offset: usize) -> Result
/// Fallible 64-bit write with runtime bounds check.
#[inline(always)]
fn try_write64(&self, value: u64, offset: usize) -> Result
fn try_write64(self, value: u64, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
Self: IoCapable<u64>,
@ -418,7 +420,7 @@ fn try_write64(&self, value: u64, offset: usize) -> Result
/// Infallible 8-bit read with compile-time bounds check.
#[inline(always)]
fn read8(&self, offset: usize) -> u8
fn read8(self, offset: usize) -> u8
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
Self: IoCapable<u8>,
@ -428,7 +430,7 @@ fn read8(&self, offset: usize) -> u8
/// Infallible 16-bit read with compile-time bounds check.
#[inline(always)]
fn read16(&self, offset: usize) -> u16
fn read16(self, offset: usize) -> u16
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
Self: IoCapable<u16>,
@ -438,7 +440,7 @@ fn read16(&self, offset: usize) -> u16
/// Infallible 32-bit read with compile-time bounds check.
#[inline(always)]
fn read32(&self, offset: usize) -> u32
fn read32(self, offset: usize) -> u32
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
Self: IoCapable<u32>,
@ -448,7 +450,7 @@ fn read32(&self, offset: usize) -> u32
/// Infallible 64-bit read with compile-time bounds check.
#[inline(always)]
fn read64(&self, offset: usize) -> u64
fn read64(self, offset: usize) -> u64
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
Self: IoCapable<u64>,
@ -458,7 +460,7 @@ fn read64(&self, offset: usize) -> u64
/// Infallible 8-bit write with compile-time bounds check.
#[inline(always)]
fn write8(&self, value: u8, offset: usize)
fn write8(self, value: u8, offset: usize)
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
Self: IoCapable<u8>,
@ -468,7 +470,7 @@ fn write8(&self, value: u8, offset: usize)
/// Infallible 16-bit write with compile-time bounds check.
#[inline(always)]
fn write16(&self, value: u16, offset: usize)
fn write16(self, value: u16, offset: usize)
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
Self: IoCapable<u16>,
@ -478,7 +480,7 @@ fn write16(&self, value: u16, offset: usize)
/// Infallible 32-bit write with compile-time bounds check.
#[inline(always)]
fn write32(&self, value: u32, offset: usize)
fn write32(self, value: u32, offset: usize)
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
Self: IoCapable<u32>,
@ -488,7 +490,7 @@ fn write32(&self, value: u32, offset: usize)
/// Infallible 64-bit write with compile-time bounds check.
#[inline(always)]
fn write64(&self, value: u64, offset: usize)
fn write64(self, value: u64, offset: usize)
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
Self: IoCapable<u64>,
@ -519,7 +521,7 @@ fn write64(&self, value: u64, offset: usize)
/// }
/// ```
#[inline(always)]
fn try_read<T, L>(&self, location: L) -> Result<T>
fn try_read<T, L>(self, location: L) -> Result<T>
where
L: IoLoc<Self::Target, T>,
Self: IoCapable<L::IoType>,
@ -553,7 +555,7 @@ fn try_read<T, L>(&self, location: L) -> Result<T>
/// }
/// ```
#[inline(always)]
fn try_write<T, L>(&self, location: L, value: T) -> Result
fn try_write<T, L>(self, location: L, value: T) -> Result
where
L: IoLoc<Self::Target, T>,
Self: IoCapable<L::IoType>,
@ -599,7 +601,7 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result
/// }
/// ```
#[inline(always)]
fn try_write_reg<T, L, V>(&self, value: V) -> Result
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>,
@ -632,7 +634,7 @@ fn try_write_reg<T, L, V>(&self, value: V) -> Result
/// }
/// ```
#[inline(always)]
fn try_update<T, L, F>(&self, location: L, f: F) -> Result
fn try_update<T, L, F>(self, location: L, f: F) -> Result
where
L: IoLoc<Self::Target, T>,
Self: IoCapable<L::IoType>,
@ -671,7 +673,7 @@ fn try_update<T, L, F>(&self, location: L, f: F) -> Result
/// }
/// ```
#[inline(always)]
fn read<T, L>(&self, location: L) -> T
fn read<T, L>(self, location: L) -> T
where
L: IoLoc<Self::Target, T>,
Self: IoCapable<L::IoType>,
@ -703,7 +705,7 @@ fn read<T, L>(&self, location: L) -> T
/// }
/// ```
#[inline(always)]
fn write<T, L>(&self, location: L, value: T)
fn write<T, L>(self, location: L, value: T)
where
L: IoLoc<Self::Target, T>,
Self: IoCapable<L::IoType>,
@ -746,7 +748,7 @@ fn write<T, L>(&self, location: L, value: T)
/// }
/// ```
#[inline(always)]
fn write_reg<T, L, V>(&self, value: V)
fn write_reg<T, L, V>(self, value: V)
where
L: IoLoc<Self::Target, T>,
V: LocatedRegister<Self::Target, Location = L, Value = T>,
@ -779,7 +781,7 @@ fn write_reg<T, L, V>(&self, value: V)
/// }
/// ```
#[inline(always)]
fn update<T, L, F>(&self, location: L, f: F)
fn update<T, L, F>(self, location: L, f: F)
where
L: IoLoc<Self::Target, T>,
Self: IoCapable<L::IoType>,
@ -800,13 +802,13 @@ fn update<T, L, F>(&self, location: L, f: F)
macro_rules! impl_mmio_io_capable {
($mmio:ident, $(#[$attr:meta])* $ty:ty, $read_fn:ident, $write_fn:ident) => {
$(#[$attr])*
impl<const SIZE: usize> IoCapable<$ty> for $mmio<SIZE> {
unsafe fn io_read(&self, address: usize) -> $ty {
impl<const SIZE: usize> IoCapable<$ty> for &$mmio<SIZE> {
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) }
}
unsafe fn io_write(&self, value: $ty, address: usize) {
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) }
}
@ -827,18 +829,18 @@ unsafe fn io_write(&self, value: $ty, address: usize) {
writeq
);
impl<const SIZE: usize> Io for Mmio<SIZE> {
impl<'a, const SIZE: usize> Io for &'a Mmio<SIZE> {
type Target = Region<SIZE>;
/// Returns the base address of this mapping.
#[inline]
fn addr(&self) -> usize {
fn addr(self) -> usize {
self.0.addr()
}
/// Returns the maximum size of this mapping.
#[inline]
fn maxsize(&self) -> usize {
fn maxsize(self) -> usize {
self.0.maxsize()
}
}
@ -865,16 +867,16 @@ pub unsafe fn from_raw(raw: &MmioRaw<SIZE>) -> &Self {
#[repr(transparent)]
pub struct RelaxedMmio<const SIZE: usize = 0>(Mmio<SIZE>);
impl<const SIZE: usize> Io for RelaxedMmio<SIZE> {
impl<'a, const SIZE: usize> Io for &'a RelaxedMmio<SIZE> {
type Target = Region<SIZE>;
#[inline]
fn addr(&self) -> usize {
fn addr(self) -> usize {
self.0.addr()
}
#[inline]
fn maxsize(&self) -> usize {
fn maxsize(self) -> usize {
self.0.maxsize()
}
}

View File

@ -79,8 +79,8 @@ pub struct ConfigSpace<'a, S: ?Sized + ConfigSpaceKind = Extended> {
/// 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, S: ?Sized + ConfigSpaceKind> IoCapable<$ty> for ConfigSpace<'a, S> {
unsafe fn io_read(&self, address: usize) -> $ty {
impl<'a, S: ?Sized + ConfigSpaceKind> IoCapable<$ty> for &ConfigSpace<'a, S> {
unsafe fn io_read(self, address: usize) -> $ty {
let mut val: $ty = 0;
// Return value from C function is ignored in infallible accessors.
@ -94,7 +94,7 @@ unsafe fn io_read(&self, address: usize) -> $ty {
val
}
unsafe fn io_write(&self, value: $ty, address: usize) {
unsafe fn io_write(self, value: $ty, address: usize) {
// Return value from C function is ignored in infallible accessors.
let _ret =
// SAFETY: By the type invariant `self.pdev` is a valid address.
@ -112,18 +112,18 @@ 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, S: ?Sized + ConfigSpaceKind> Io for ConfigSpace<'a, S> {
impl<'a, S: ?Sized + ConfigSpaceKind> Io for &ConfigSpace<'a, S> {
type Target = S;
/// Returns the base address of the I/O region. It is always 0 for configuration space.
#[inline]
fn addr(&self) -> usize {
fn addr(self) -> usize {
0
}
/// Returns the maximum size of the configuration space.
#[inline]
fn maxsize(&self) -> usize {
fn maxsize(self) -> usize {
self.pdev.cfg_size().into_raw()
}
}