[PATCH v3 01/19] rust: io: add dynamically-sized `Region` type

From: Gary Guo

Date: Mon Jun 08 2026 - 15:59:39 EST


Currently many I/O related structs carry a `SIZE` parameter to denote the
minimum size of the I/O region, while they also carry a field indicating
the actual size. Proliferation of the pattern creates a lot of duplicated
code, and makes it hard to create typed views of I/O.

Introduce a `Region` type that carries the `SIZE` parameter. It is a
wrapper of `[u8]`, which makes it dynamically sized with a metadata of
`usize`. This way, pointers to `Region` naturally carry size information.
This type is required to be naturally aligned.

Expose the minimum size information via `MIN_SIZE` constant of the
`KnownSize` trait. Similarly, expose the minimum alignment information via
`KnownSize::MIN_ALIGN`.

With these changes, it is possible to add an associated type to `Io` trait
to represent the type of I/O region. For untyped regions, this is the newly
added `Region` type. Remove `IoKnownSize` as it is no longer necessary. Use
the same mechanism to indicate minimum size of PCI config spaces.

Signed-off-by: Gary Guo <gary@xxxxxxxxxxx>
---
rust/kernel/devres.rs | 6 +--
rust/kernel/io.rs | 131 +++++++++++++++++++++++++++++++++-----------------
rust/kernel/pci.rs | 1 -
rust/kernel/pci/io.rs | 40 ++++++---------
rust/kernel/ptr.rs | 12 +++++
5 files changed, 116 insertions(+), 74 deletions(-)

diff --git a/rust/kernel/devres.rs b/rust/kernel/devres.rs
index 11ce500e9b76..ed30ccc6e68e 100644
--- a/rust/kernel/devres.rs
+++ b/rust/kernel/devres.rs
@@ -68,7 +68,6 @@ struct Inner<T> {
/// devres::Devres,
/// io::{
/// Io,
-/// IoKnownSize,
/// Mmio,
/// MmioRaw,
/// PhysAddr, //
@@ -297,10 +296,7 @@ pub fn device(&self) -> &Device {
/// use kernel::{
/// device::Core,
/// devres::Devres,
- /// io::{
- /// Io,
- /// IoKnownSize, //
- /// },
+ /// io::Io,
/// pci, //
/// };
///
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index fcc7678fd9e3..dcf3b40ffa48 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -6,7 +6,11 @@

use crate::{
bindings,
- prelude::*, //
+ prelude::*,
+ ptr::{
+ Alignment,
+ KnownSize, //
+ }, //
};

pub mod mem;
@@ -31,6 +35,59 @@
/// `CONFIG_PHYS_ADDR_T_64BIT`, and it can be a u64 even on 32-bit architectures.
pub type ResourceSize = bindings::resource_size_t;

+/// Untyped I/O region.
+///
+/// This type can be used when an I/O region without known type information has a compile-time known
+/// minimum size (and a runtime known actual size).
+///
+/// This must be naturally aligned to `usize`.
+///
+/// # Invariants
+///
+/// Size of the region is at least as large as the `SIZE` generic parameter.
+#[repr(C)]
+#[cfg_attr(CONFIG_64BIT, repr(align(8)))]
+#[cfg_attr(not(CONFIG_64BIT), repr(align(4)))]
+pub struct Region<const SIZE: usize = 0> {
+ inner: [u8],
+}
+
+impl<const SIZE: usize> Region<SIZE> {
+ /// Create a raw mutable pointer from given base address and size.
+ ///
+ /// `size` should be at least as large as the minimum size `SIZE` to uphold the type invariant.
+ ///
+ /// Just like other methods on raw pointers, it is not unsafe to create a raw pointer
+ /// that does not uphold the type invariants. However such pointers are not valid.
+ #[inline]
+ pub fn ptr_from_raw_parts_mut(base: *mut u8, size: usize) -> *mut Self {
+ core::ptr::slice_from_raw_parts_mut(base, size) as *mut Region<SIZE>
+ }
+
+ /// Create a raw mutable pointer from given base address and size.
+ ///
+ /// The alignment of `base` is checked, and `size` is checked against the minimum size specified
+ /// via const generics.
+ #[inline]
+ pub fn ptr_try_from_raw_parts_mut(base: *mut u8, size: usize) -> Result<*mut Self> {
+ if size < SIZE || base.align_offset(size_of::<usize>()) != 0 {
+ return Err(EINVAL);
+ }
+
+ Ok(Self::ptr_from_raw_parts_mut(base, size))
+ }
+}
+
+impl<const SIZE: usize> KnownSize for Region<SIZE> {
+ const MIN_SIZE: usize = SIZE;
+ const MIN_ALIGN: Alignment = Alignment::new::<{ size_of::<usize>() }>();
+
+ #[inline(always)]
+ fn size(p: *const Self) -> usize {
+ (p as *const [u8]).len()
+ }
+}
+
/// Raw representation of an MMIO region.
///
/// By itself, the existence of an instance of this structure does not provide any guarantees that
@@ -85,7 +142,6 @@ pub fn maxsize(&self) -> usize {
/// ffi::c_void,
/// io::{
/// Io,
-/// IoKnownSize,
/// Mmio,
/// MmioRaw,
/// PhysAddr,
@@ -241,12 +297,25 @@ fn offset(self) -> usize {
/// 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 {
+ /// 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 {
+ build_assert!(offset_valid::<U>(offset, Self::Target::MIN_SIZE));
+
+ self.addr() + offset
+ }
+
/// Returns the absolute I/O address for a given `offset`,
/// performing runtime bound checks.
#[inline]
@@ -336,7 +405,7 @@ fn try_write64(&self, value: u64, offset: usize) -> Result
#[inline(always)]
fn read8(&self, offset: usize) -> u8
where
- Self: IoKnownSize + IoCapable<u8>,
+ Self: IoCapable<u8>,
{
self.read(offset)
}
@@ -345,7 +414,7 @@ fn read8(&self, offset: usize) -> u8
#[inline(always)]
fn read16(&self, offset: usize) -> u16
where
- Self: IoKnownSize + IoCapable<u16>,
+ Self: IoCapable<u16>,
{
self.read(offset)
}
@@ -354,7 +423,7 @@ fn read16(&self, offset: usize) -> u16
#[inline(always)]
fn read32(&self, offset: usize) -> u32
where
- Self: IoKnownSize + IoCapable<u32>,
+ Self: IoCapable<u32>,
{
self.read(offset)
}
@@ -363,7 +432,7 @@ fn read32(&self, offset: usize) -> u32
#[inline(always)]
fn read64(&self, offset: usize) -> u64
where
- Self: IoKnownSize + IoCapable<u64>,
+ Self: IoCapable<u64>,
{
self.read(offset)
}
@@ -372,7 +441,7 @@ fn read64(&self, offset: usize) -> u64
#[inline(always)]
fn write8(&self, value: u8, offset: usize)
where
- Self: IoKnownSize + IoCapable<u8>,
+ Self: IoCapable<u8>,
{
self.write(offset, value)
}
@@ -381,7 +450,7 @@ fn write8(&self, value: u8, offset: usize)
#[inline(always)]
fn write16(&self, value: u16, offset: usize)
where
- Self: IoKnownSize + IoCapable<u16>,
+ Self: IoCapable<u16>,
{
self.write(offset, value)
}
@@ -390,7 +459,7 @@ fn write16(&self, value: u16, offset: usize)
#[inline(always)]
fn write32(&self, value: u32, offset: usize)
where
- Self: IoKnownSize + IoCapable<u32>,
+ Self: IoCapable<u32>,
{
self.write(offset, value)
}
@@ -399,7 +468,7 @@ fn write32(&self, value: u32, offset: usize)
#[inline(always)]
fn write64(&self, value: u64, offset: usize)
where
- Self: IoKnownSize + IoCapable<u64>,
+ Self: IoCapable<u64>,
{
self.write(offset, value)
}
@@ -582,7 +651,7 @@ fn try_update<T, L, F>(&self, location: L, f: F) -> Result
fn read<T, L>(&self, location: L) -> T
where
L: IoLoc<T>,
- Self: IoKnownSize + IoCapable<L::IoType>,
+ Self: IoCapable<L::IoType>,
{
let address = self.io_addr_assert::<L::IoType>(location.offset());

@@ -614,7 +683,7 @@ fn read<T, L>(&self, location: L) -> T
fn write<T, L>(&self, location: L, value: T)
where
L: IoLoc<T>,
- Self: IoKnownSize + IoCapable<L::IoType>,
+ Self: IoCapable<L::IoType>,
{
let address = self.io_addr_assert::<L::IoType>(location.offset());
let io_value = value.into();
@@ -658,7 +727,7 @@ fn write_reg<T, L, V>(&self, value: V)
where
L: IoLoc<T>,
V: LocatedRegister<Location = L, Value = T>,
- Self: IoKnownSize + IoCapable<L::IoType>,
+ Self: IoCapable<L::IoType>,
{
let (location, value) = value.into_io_op();

@@ -690,7 +759,7 @@ fn write_reg<T, L, V>(&self, value: V)
fn update<T, L, F>(&self, location: L, f: F)
where
L: IoLoc<T>,
- Self: IoKnownSize + IoCapable<L::IoType> + Sized,
+ Self: IoCapable<L::IoType> + Sized,
F: FnOnce(T) -> T,
{
let address = self.io_addr_assert::<L::IoType>(location.offset());
@@ -704,28 +773,6 @@ fn update<T, L, F>(&self, location: L, f: F)
}
}

-/// Trait for types with a known size at compile time.
-///
-/// This trait is implemented by I/O backends that have a compile-time known size,
-/// enabling the use of infallible I/O accessors with compile-time bounds checking.
-///
-/// Types implementing this trait can use the infallible methods in [`Io`] trait
-/// (e.g., `read8`, `write32`), which require `Self: IoKnownSize` bound.
-pub trait IoKnownSize: Io {
- /// Minimum usable size of this region.
- const MIN_SIZE: 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 {
- build_assert!(offset_valid::<U>(offset, Self::MIN_SIZE));
-
- self.addr() + offset
- }
-}
-
/// Implements [`IoCapable`] on `$mmio` 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) => {
@@ -758,6 +805,8 @@ unsafe fn io_write(&self, value: $ty, address: usize) {
);

impl<const SIZE: usize> Io for Mmio<SIZE> {
+ type Target = Region<SIZE>;
+
/// Returns the base address of this mapping.
#[inline]
fn addr(&self) -> usize {
@@ -771,10 +820,6 @@ fn maxsize(&self) -> usize {
}
}

-impl<const SIZE: usize> IoKnownSize for Mmio<SIZE> {
- const MIN_SIZE: usize = SIZE;
-}
-
impl<const SIZE: usize> Mmio<SIZE> {
/// Converts an `MmioRaw` into an `Mmio` instance, providing the accessors to the MMIO mapping.
///
@@ -798,6 +843,8 @@ pub unsafe fn from_raw(raw: &MmioRaw<SIZE>) -> &Self {
pub struct RelaxedMmio<const SIZE: usize = 0>(Mmio<SIZE>);

impl<const SIZE: usize> Io for RelaxedMmio<SIZE> {
+ type Target = Region<SIZE>;
+
#[inline]
fn addr(&self) -> usize {
self.0.addr()
@@ -809,10 +856,6 @@ fn maxsize(&self) -> usize {
}
}

-impl<const SIZE: usize> IoKnownSize for RelaxedMmio<SIZE> {
- const MIN_SIZE: usize = SIZE;
-}
-
impl<const SIZE: usize> Mmio<SIZE> {
/// Returns a [`RelaxedMmio`] reference that performs relaxed I/O operations.
///
diff --git a/rust/kernel/pci.rs b/rust/kernel/pci.rs
index 5071cae6543f..c6d6bd8f251d 100644
--- a/rust/kernel/pci.rs
+++ b/rust/kernel/pci.rs
@@ -43,7 +43,6 @@
pub use self::io::{
Bar,
ConfigSpace,
- ConfigSpaceKind,
ConfigSpaceSize,
Extended,
Normal, //
diff --git a/rust/kernel/pci/io.rs b/rust/kernel/pci/io.rs
index 0461e01aaa20..b4996aa059d8 100644
--- a/rust/kernel/pci/io.rs
+++ b/rust/kernel/pci/io.rs
@@ -10,11 +10,12 @@
io::{
Io,
IoCapable,
- IoKnownSize,
Mmio,
- MmioRaw, //
+ MmioRaw,
+ Region, //
},
- prelude::*, //
+ prelude::*,
+ ptr::KnownSize, //
};
use core::{
marker::PhantomData,
@@ -46,28 +47,21 @@ pub const fn into_raw(self) -> usize {
}
}

-/// Marker type for normal (256-byte) PCI configuration space.
-pub struct Normal;
+/// Alias for normal (256-byte) PCI configuration space.
+pub type Normal = Region<256>;

-/// Marker type for extended (4096-byte) PCIe configuration space.
-pub struct Extended;
+/// Alias for extended (4096-byte) PCIe configuration space.
+pub type Extended = Region<4096>;

/// Trait for PCI configuration space size markers.
///
/// This trait is implemented by [`Normal`] and [`Extended`] to provide
/// compile-time knowledge of the configuration space size.
-pub trait ConfigSpaceKind {
- /// The size of this configuration space in bytes.
- const SIZE: usize;
-}
+pub trait ConfigSpaceKind: KnownSize {}

-impl ConfigSpaceKind for Normal {
- const SIZE: usize = 256;
-}
+impl ConfigSpaceKind for Normal {}

-impl ConfigSpaceKind for Extended {
- const SIZE: usize = 4096;
-}
+impl ConfigSpaceKind for Extended {}

/// The PCI configuration space of a device.
///
@@ -77,7 +71,7 @@ impl ConfigSpaceKind for Extended {
/// The generic parameter `S` indicates the maximum size of the configuration space.
/// Use [`Normal`] for 256-byte legacy configuration space or [`Extended`] for
/// 4096-byte PCIe extended configuration space (default).
-pub struct ConfigSpace<'a, S: ConfigSpaceKind = Extended> {
+pub struct ConfigSpace<'a, S: ?Sized + ConfigSpaceKind = Extended> {
pub(crate) pdev: &'a Device<device::Bound>,
_marker: PhantomData<S>,
}
@@ -85,7 +79,7 @@ pub struct ConfigSpace<'a, S: 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: ConfigSpaceKind> IoCapable<$ty> for ConfigSpace<'a, S> {
+ impl<'a, S: ?Sized + ConfigSpaceKind> IoCapable<$ty> for ConfigSpace<'a, S> {
unsafe fn io_read(&self, address: usize) -> $ty {
let mut val: $ty = 0;

@@ -118,7 +112,9 @@ 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: 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 {
@@ -132,10 +128,6 @@ fn maxsize(&self) -> usize {
}
}

-impl<'a, S: ConfigSpaceKind> IoKnownSize for ConfigSpace<'a, S> {
- const MIN_SIZE: usize = S::SIZE;
-}
-
/// A PCI BAR to perform I/O-Operations on.
///
/// I/O backend assumes that the device is little-endian and will automatically
diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs
index 3f3e529e9f58..82acb531b17b 100644
--- a/rust/kernel/ptr.rs
+++ b/rust/kernel/ptr.rs
@@ -235,11 +235,20 @@ 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;
+
+ /// Minimum alignment of this type known at compile-time.
+ const MIN_ALIGN: Alignment;
+
/// 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 = size_of::<T>();
+ const MIN_ALIGN: Alignment = Alignment::of::<T>();
+
#[inline(always)]
fn size(_: *const Self) -> usize {
size_of::<T>()
@@ -247,6 +256,9 @@ fn size(_: *const Self) -> usize {
}

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

--
2.54.0