[PATCH v2 02/11] rust: io: generalize `Mmio` to arbitrary type

Gary Guo
2026-04-21T14:56:31Z

Currently, `io::Mmio` always represent an untyped region of a compile-time
known minimum size, which is roughly equivalent to `void __iomem*` (but
with bound checks). However, it is useful to also be to represent I/O
memory of a specific type, e.g. `u32 __iomem*` or `struct foo __iomem*`.

Thus, make `Mmio` generic on arbitrary `T`, where `T` is a sized type, or a
DST that implements `KnownSize`. Similar to the `MmioRaw` change, the
existing behaviour is preserved in the form of `Mmio<Region<SIZE>>`. This
change brings the MMIO closer to the DMA coherent allocation types that we
have, which is already typed.

To be able to implement `IoKnownSize`, add a `MIN_SIZE` constant to
`KnownSize` trait to represent compile-time known minimum size of a
specific type.

Acked-by: Miguel Ojeda <ojeda@kernel.org>
Signed-off-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/devres.rs | 2 +-
rust/kernel/io.rs | 63 +++++++++++++++++++++++++++-------------------
rust/kernel/io/mem.rs | 4 +--
rust/kernel/io/poll.rs | 6 +++--
rust/kernel/io/register.rs | 19 ++++++++------
rust/kernel/pci/io.rs | 2 +-
rust/kernel/ptr.rs | 7 ++++++
7 files changed, 64 insertions(+), 39 deletions(-)

diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs
index 65a4082122af..3e22c63efb98 100644
--- a/rust/kernel/devres.rs
+++ b/rust/kernel/devres.rs
@@ -106,7 +106,7 @@ struct Inner<T> {
/// }
///
/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
-/// type Target = Mmio<SIZE>;
+/// type Target = Mmio<Region<SIZE>>;
///
/// fn deref(&self) -> &Self::Target {
/// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index d7f2145fa9b9..0b9c97c0a1d7 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -44,6 +44,8 @@ pub struct Region<const SIZE: usize = 0> {
}

impl<const SIZE: usize> KnownSize for Region<SIZE> {
+ const MIN_SIZE: usize = SIZE;
+
#[inline(always)]
fn size(p: *const Self) -> usize {
(p as *const [u8]).len()
@@ -169,7 +171,7 @@ pub fn size(&self) -> usize {
/// }
///
/// impl<const SIZE: usize> Deref for IoMem<SIZE> {
-/// type Target = Mmio<SIZE>;
+/// type Target = Mmio<Region<SIZE>>;
///
/// fn deref(&self) -> &Self::Target {
/// // SAFETY: The memory range stored in `self` has been properly mapped in `Self::new`.
@@ -187,7 +189,7 @@ pub fn size(&self) -> usize {
/// # }
/// ```
#[repr(transparent)]
-pub struct Mmio<const SIZE: usize = 0>(MmioRaw<Region<SIZE>>);
+pub struct Mmio<T: ?Sized>(MmioRaw<T>);

/// Checks whether an access of type `U` at the given `offset`
/// is valid within this region.
@@ -462,9 +464,10 @@ fn write64(&self, value: u64, offset: usize)
/// use kernel::io::{
/// Io,
/// Mmio,
+ /// Region,
/// };
///
- /// fn do_reads(io: &Mmio) -> Result {
+ /// fn do_reads(io: &Mmio<Region>) -> Result {
/// // 32-bit read from address `0x10`.
/// let v: u32 = io.try_read(0x10)?;
///
@@ -496,9 +499,10 @@ fn try_read<T, L>(&self, location: L) -> Result<T>
/// use kernel::io::{
/// Io,
/// Mmio,
+ /// Region,
/// };
///
- /// fn do_writes(io: &Mmio) -> Result {
+ /// fn do_writes(io: &Mmio<Region>) -> Result {
/// // 32-bit write of value `1` at address `0x10`.
/// io.try_write(0x10, 1u32)?;
///
@@ -534,6 +538,7 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result
/// register,
/// Io,
/// Mmio,
+ /// Region,
/// };
///
/// register! {
@@ -549,7 +554,7 @@ fn try_write<T, L>(&self, location: L, value: T) -> Result
/// }
/// }
///
- /// fn do_write_reg(io: &Mmio) -> Result {
+ /// fn do_write_reg(io: &Mmio<Region>) -> Result {
///
/// io.try_write_reg(VERSION::new(1, 0))
/// }
@@ -579,9 +584,10 @@ fn try_write_reg<T, L, V>(&self, value: V) -> Result
/// use kernel::io::{
/// Io,
/// Mmio,
+ /// Region,
/// };
///
- /// fn do_update(io: &Mmio<0x1000>) -> Result {
+ /// fn do_update(io: &Mmio<Region<0x1000>>) -> Result {
/// io.try_update(0x10, |v: u32| {
/// v + 1
/// })
@@ -616,9 +622,10 @@ fn try_update<T, L, F>(&self, location: L, f: F) -> Result
/// use kernel::io::{
/// Io,
/// Mmio,
+ /// Region,
/// };
///
- /// fn do_reads(io: &Mmio<0x1000>) {
+ /// fn do_reads(io: &Mmio<Region<0x1000>>) {
/// // 32-bit read from address `0x10`.
/// let v: u32 = io.read(0x10);
///
@@ -648,9 +655,10 @@ fn read<T, L>(&self, location: L) -> T
/// use kernel::io::{
/// Io,
/// Mmio,
+ /// Region,
/// };
///
- /// fn do_writes(io: &Mmio<0x1000>) {
+ /// fn do_writes(io: &Mmio<Region<0x1000>>) {
/// // 32-bit write of value `1` at address `0x10`.
/// io.write(0x10, 1u32);
///
@@ -682,6 +690,7 @@ fn write<T, L>(&self, location: L, value: T)
/// register,
/// Io,
/// Mmio,
+ /// Region,
/// };
///
/// register! {
@@ -697,7 +706,7 @@ fn write<T, L>(&self, location: L, value: T)
/// }
/// }
///
- /// fn do_write_reg(io: &Mmio<0x1000>) {
+ /// fn do_write_reg(io: &Mmio<Region<0x1000>>) {
/// io.write_reg(VERSION::new(1, 0));
/// }
/// ```
@@ -726,9 +735,10 @@ fn write_reg<T, L, V>(&self, value: V)
/// use kernel::io::{
/// Io,
/// Mmio,
+ /// Region,
/// };
///
- /// fn do_update(io: &Mmio<0x1000>) {
+ /// fn do_update(io: &Mmio<Region<0x1000>>) {
/// io.update(0x10, |v: u32| {
/// v + 1
/// })
@@ -778,7 +788,7 @@ fn io_addr_assert(&self, offset: usize) -> usize {
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> {
+ impl<T: ?Sized> IoCapable<$ty> for $mmio<T> {
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) }
@@ -805,7 +815,7 @@ unsafe fn io_write(&self, value: $ty, address: usize) {
writeq
);

-impl<const SIZE: usize> Io for Mmio<SIZE> {
+impl<T: ?Sized + KnownSize> Io for Mmio<T> {
/// Returns the base address of this mapping.
#[inline]
fn addr(&self) -> usize {
@@ -819,18 +829,18 @@ fn maxsize(&self) -> usize {
}
}

-impl<const SIZE: usize> IoKnownSize for Mmio<SIZE> {
- const MIN_SIZE: usize = SIZE;
+impl<T: ?Sized + KnownSize> IoKnownSize for Mmio<T> {
+ const MIN_SIZE: usize = T::MIN_SIZE;
}

-impl<const SIZE: usize> Mmio<SIZE> {
+impl<T: ?Sized + KnownSize> Mmio<T> {
/// Converts an `MmioRaw` into an `Mmio` instance, providing the accessors to the MMIO mapping.
///
/// # Safety
///
/// Callers must ensure that `addr` is the start of a valid I/O mapped memory region of size
- /// `maxsize`.
- pub unsafe fn from_raw(raw: &MmioRaw<Region<SIZE>>) -> &Self {
+ /// `addr.size()`.
+ pub unsafe fn from_raw(raw: &MmioRaw<T>) -> &Self {
// SAFETY: `Mmio` is a transparent wrapper around `MmioRaw`.
unsafe { &*core::ptr::from_ref(raw).cast() }
}
@@ -843,9 +853,9 @@ pub unsafe fn from_raw(raw: &MmioRaw<Region<SIZE>>) -> &Self {
///
/// See [`Mmio::relaxed`] for a usage example.
#[repr(transparent)]
-pub struct RelaxedMmio<const SIZE: usize = 0>(Mmio<SIZE>);
+pub struct RelaxedMmio<T: ?Sized>(Mmio<T>);

-impl<const SIZE: usize> Io for RelaxedMmio<SIZE> {
+impl<T: ?Sized + KnownSize> Io for RelaxedMmio<T> {
#[inline]
fn addr(&self) -> usize {
self.0.addr()
@@ -857,11 +867,11 @@ fn maxsize(&self) -> usize {
}
}

-impl<const SIZE: usize> IoKnownSize for RelaxedMmio<SIZE> {
- const MIN_SIZE: usize = SIZE;
+impl<T: ?Sized + KnownSize> IoKnownSize for RelaxedMmio<T> {
+ const MIN_SIZE: usize = T::MIN_SIZE;
}

-impl<const SIZE: usize> Mmio<SIZE> {
+impl<T: ?Sized> Mmio<T> {
/// Returns a [`RelaxedMmio`] reference that performs relaxed I/O operations.
///
/// Relaxed accessors do not provide ordering guarantees with respect to DMA or memory accesses
@@ -873,18 +883,19 @@ impl<const SIZE: usize> Mmio<SIZE> {
/// use kernel::io::{
/// Io,
/// Mmio,
+ /// Region,
/// RelaxedMmio,
/// };
///
- /// fn do_io(io: &Mmio<0x100>) {
+ /// fn do_io(io: &Mmio<Region<0x100>>) {
/// // The access is performed using `readl_relaxed` instead of `readl`.
/// let v = io.relaxed().read32(0x10);
/// }
///
/// ```
- pub fn relaxed(&self) -> &RelaxedMmio<SIZE> {
- // SAFETY: `RelaxedMmio` is `#[repr(transparent)]` over `Mmio`, so `Mmio<SIZE>` and
- // `RelaxedMmio<SIZE>` have identical layout.
+ pub fn relaxed(&self) -> &RelaxedMmio<T> {
+ // SAFETY: `RelaxedMmio` is `#[repr(transparent)]` over `Mmio`, so `Mmio<T>` and
+ // `RelaxedMmio<T>` have identical layout.
unsafe { core::mem::transmute(self) }
}
}
diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs
index 9117d417f99c..a6292f4ebfa4 100644
--- a/rust/kernel/io/mem.rs
+++ b/rust/kernel/io/mem.rs
@@ -214,7 +214,7 @@ pub fn new<'a>(io_request: IoRequest<'a>) -> impl PinInit<Devres<Self>, Error> +
}

impl<const SIZE: usize> Deref for ExclusiveIoMem<SIZE> {
- type Target = Mmio<SIZE>;
+ type Target = Mmio<super::Region<SIZE>>;

fn deref(&self) -> &Self::Target {
&self.iomem
@@ -289,7 +289,7 @@ fn drop(&mut self) {
}

impl<const SIZE: usize> Deref for IoMem<SIZE> {
- type Target = Mmio<SIZE>;
+ type Target = Mmio<super::Region<SIZE>>;

fn deref(&self) -> &Self::Target {
// SAFETY: Safe as by the invariant of `IoMem`.
diff --git a/rust/kernel/io/poll.rs b/rust/kernel/io/poll.rs
index 75d1b3e8596c..2dce2b24b5ff 100644
--- a/rust/kernel/io/poll.rs
+++ b/rust/kernel/io/poll.rs
@@ -48,13 +48,14 @@
/// use kernel::io::{
/// Io,
/// Mmio,
+/// Region,
/// poll::read_poll_timeout, //
/// };
/// use kernel::time::Delta;
///
/// const HW_READY: u16 = 0x01;
///
-/// fn wait_for_hardware<const SIZE: usize>(io: &Mmio<SIZE>) -> Result {
+/// fn wait_for_hardware<const SIZE: usize>(io: &Mmio<Region<SIZE>>) -> Result {
/// read_poll_timeout(
/// // The `op` closure reads the value of a specific status register.
/// || io.try_read16(0x1000),
@@ -135,13 +136,14 @@ pub fn read_poll_timeout<Op, Cond, T>(
/// use kernel::io::{
/// Io,
/// Mmio,
+/// Region,
/// poll::read_poll_timeout_atomic, //
/// };
/// use kernel::time::Delta;
///
/// const HW_READY: u16 = 0x01;
///
-/// fn wait_for_hardware<const SIZE: usize>(io: &Mmio<SIZE>) -> Result {
+/// fn wait_for_hardware<const SIZE: usize>(io: &Mmio<Region<SIZE>>) -> Result {
/// read_poll_timeout_atomic(
/// // The `op` closure reads the value of a specific status register.
/// || io.try_read16(0x1000),
diff --git a/rust/kernel/io/register.rs b/rust/kernel/io/register.rs
index abc49926abfe..1a407fc35edc 100644
--- a/rust/kernel/io/register.rs
+++ b/rust/kernel/io/register.rs
@@ -55,6 +55,7 @@
//! register,
//! Io,
//! IoLoc,
+//! Region,
//! },
//! num::Bounded,
//! };
@@ -66,7 +67,7 @@
//! # 3:0 minor_revision;
//! # }
//! # }
-//! # fn test(io: &Mmio<0x1000>) {
+//! # fn test(io: &Mmio<Region<0x1000>>) {
//! # fn obtain_vendor_id() -> u8 { 0xff }
//!
//! // Read from the register's defined offset (0x100).
@@ -441,6 +442,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// io::{
/// register,
/// Io,
+/// Region,
/// },
/// };
/// # use kernel::io::Mmio;
@@ -452,7 +454,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// }
/// }
///
-/// # fn test(io: &Mmio<0x1000>) {
+/// # fn test(io: &Mmio<Region<0x1000>>) {
/// let val = io.read(FIXED_REG);
///
/// // Write from an already-existing value.
@@ -554,6 +556,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// WithBase,
/// },
/// Io,
+/// Region,
/// },
/// };
/// # use kernel::io::Mmio;
@@ -581,7 +584,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// }
/// }
///
-/// # fn test(io: Mmio<0x1000>) {
+/// # fn test(io: Mmio<Region<0x1000>>) {
/// // Read the status of `Cpu0`.
/// let cpu0_started = io.read(CPU_CTL::of::<Cpu0>());
///
@@ -598,7 +601,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// }
/// }
///
-/// # fn test2(io: Mmio<0x1000>) {
+/// # fn test2(io: Mmio<Region<0x1000>>) {
/// // Start the aliased `CPU0`, leaving its other fields untouched.
/// io.update(CPU_CTL_ALIAS::of::<Cpu0>(), |r| r.with_alias_start(true));
/// # }
@@ -633,6 +636,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// register,
/// register::Array,
/// Io,
+/// Region,
/// },
/// };
/// # use kernel::io::Mmio;
@@ -648,7 +652,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// }
/// }
///
-/// # fn test(io: &Mmio<0x1000>)
+/// # fn test(io: &Mmio<Region<0x1000>>)
/// # -> Result<(), Error>{
/// // Read scratch register 0, i.e. I/O address `0x80`.
/// let scratch_0 = io.read(SCRATCH::at(0)).value();
@@ -719,6 +723,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// WithBase,
/// },
/// Io,
+/// Region,
/// },
/// };
/// # use kernel::io::Mmio;
@@ -749,7 +754,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// }
/// }
///
-/// # fn test(io: &Mmio<0x1000>) -> Result<(), Error> {
+/// # fn test(io: &Mmio<Region<0x1000>>) -> Result<(), Error> {
/// // Read scratch register 0 of CPU0.
/// let scratch = io.read(CPU_SCRATCH::of::<Cpu0>().at(0));
///
@@ -791,7 +796,7 @@ fn into_io_op(self) -> (FixedRegisterLoc<T>, T) {
/// }
/// }
///
-/// # fn test2(io: &Mmio<0x1000>) -> Result<(), Error> {
+/// # fn test2(io: &Mmio<Region<0x1000>>) -> Result<(), Error> {
/// let cpu0_status = io.read(CPU_FIRMWARE_STATUS::of::<Cpu0>()).status();
/// # Ok(())
/// # }
diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs
index 0335b5068f69..e048370fb8c1 100644
--- a/rust/kernel/pci/io.rs
+++ b/rust/kernel/pci/io.rs
@@ -238,7 +238,7 @@ fn drop(&mut self) {
}

impl<const SIZE: usize> Deref for Bar<SIZE> {
- type Target = Mmio<SIZE>;
+ type Target = Mmio<crate::io::Region<SIZE>>;

fn deref(&self) -> &Self::Target {
// SAFETY: By the type invariant of `Self`, the MMIO range in `self.io` is properly mapped.
diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs
index 3f3e529e9f58..566e68f567a2 100644
--- a/rust/kernel/ptr.rs
+++ b/rust/kernel/ptr.rs
@@ -235,11 +235,16 @@ fn align_up(self, alignment: Alignment) -> Option<Self> {
///
/// This is a generalization of [`size_of`] that works for dynamically sized types.
pub trait KnownSize {
+ /// Minimum size of this type known at compile-time.
+ const MIN_SIZE: usize;
+
/// Get the size of an object of this type in bytes, with the metadata of the given pointer.
fn size(p: *const Self) -> usize;
}

impl<T> KnownSize for T {
+ const MIN_SIZE: usize = core::mem::size_of::<T>();
+
#[inline(always)]
fn size(_: *const Self) -> usize {
size_of::<T>()
@@ -247,6 +252,8 @@ fn size(_: *const Self) -> usize {
}

impl<T> KnownSize for [T] {
+ const MIN_SIZE: usize = 0;
+
#[inline(always)]
fn size(p: *const Self) -> usize {
p.len() * size_of::<T>()

--
2.51.2