summaryrefslogtreecommitdiff
path: root/rust/kernel/io.rs
diff options
context:
space:
mode:
authorGary Guo <gary@garyguo.net>2026-07-06 13:44:14 +0100
committerDanilo Krummrich <dakr@kernel.org>2026-07-11 17:42:56 +0200
commit426c92ca1bdd33dcbc01d6d66bb5bb4a356f2c54 (patch)
tree3138a45b7274cadc1f61a8cb9f9a07837dde516d /rust/kernel/io.rs
parentdc59e4fea9d83f03bad6bddf3fa2e52491777482 (diff)
downloadlinux-next-426c92ca1bdd33dcbc01d6d66bb5bb4a356f2c54.tar.gz
linux-next-426c92ca1bdd33dcbc01d6d66bb5bb4a356f2c54.zip
rust: io: add dynamically-sized `Region` type
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 4-byte 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. 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-1-72cd5d055d54@garyguo.net [ Add brief explanation on MIN_ALIGN. - Danilo ] Signed-off-by: Danilo Krummrich <dakr@kernel.org>
Diffstat (limited to 'rust/kernel/io.rs')
-rw-r--r--rust/kernel/io.rs130
1 files changed, 86 insertions, 44 deletions
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index fcc7678fd9e3..b4cfa3588098 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,58 @@ pub type PhysAddr = bindings::phys_addr_t;
/// `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).
+///
+/// # Invariants
+///
+/// - Size of the region is at least as large as the `SIZE` generic parameter.
+/// - Size of the region is multiple of 4.
+#[repr(C, 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`, and `base` and `size` should
+ /// be 4-byte aligned 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(4) != 0 || !size.is_multiple_of(4) {
+ 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;
+ // Alignment of 4 is the most common; different base types can be added once required.
+ const MIN_ALIGN: Alignment = Alignment::new::<4>();
+
+ #[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 +141,6 @@ impl<const SIZE: usize> MmioRaw<SIZE> {
/// ffi::c_void,
/// io::{
/// Io,
-/// IoKnownSize,
/// Mmio,
/// MmioRaw,
/// PhysAddr,
@@ -241,6 +296,9 @@ impl_usize_ioloc!(u8, u16, u32, u64);
/// 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;
@@ -248,6 +306,16 @@ pub trait Io {
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]
fn io_addr<U>(&self, offset: usize) -> Result<usize> {
@@ -336,7 +404,7 @@ pub trait Io {
#[inline(always)]
fn read8(&self, offset: usize) -> u8
where
- Self: IoKnownSize + IoCapable<u8>,
+ Self: IoCapable<u8>,
{
self.read(offset)
}
@@ -345,7 +413,7 @@ pub trait Io {
#[inline(always)]
fn read16(&self, offset: usize) -> u16
where
- Self: IoKnownSize + IoCapable<u16>,
+ Self: IoCapable<u16>,
{
self.read(offset)
}
@@ -354,7 +422,7 @@ pub trait Io {
#[inline(always)]
fn read32(&self, offset: usize) -> u32
where
- Self: IoKnownSize + IoCapable<u32>,
+ Self: IoCapable<u32>,
{
self.read(offset)
}
@@ -363,7 +431,7 @@ pub trait Io {
#[inline(always)]
fn read64(&self, offset: usize) -> u64
where
- Self: IoKnownSize + IoCapable<u64>,
+ Self: IoCapable<u64>,
{
self.read(offset)
}
@@ -372,7 +440,7 @@ pub trait Io {
#[inline(always)]
fn write8(&self, value: u8, offset: usize)
where
- Self: IoKnownSize + IoCapable<u8>,
+ Self: IoCapable<u8>,
{
self.write(offset, value)
}
@@ -381,7 +449,7 @@ pub trait Io {
#[inline(always)]
fn write16(&self, value: u16, offset: usize)
where
- Self: IoKnownSize + IoCapable<u16>,
+ Self: IoCapable<u16>,
{
self.write(offset, value)
}
@@ -390,7 +458,7 @@ pub trait Io {
#[inline(always)]
fn write32(&self, value: u32, offset: usize)
where
- Self: IoKnownSize + IoCapable<u32>,
+ Self: IoCapable<u32>,
{
self.write(offset, value)
}
@@ -399,7 +467,7 @@ pub trait Io {
#[inline(always)]
fn write64(&self, value: u64, offset: usize)
where
- Self: IoKnownSize + IoCapable<u64>,
+ Self: IoCapable<u64>,
{
self.write(offset, value)
}
@@ -582,7 +650,7 @@ pub trait Io {
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 +682,7 @@ pub trait Io {
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 +726,7 @@ pub trait Io {
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 +758,7 @@ pub trait Io {
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 +772,6 @@ pub trait Io {
}
}
-/// 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 +804,8 @@ impl_mmio_io_capable!(
);
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 +819,6 @@ impl<const SIZE: usize> Io for Mmio<SIZE> {
}
}
-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 +842,8 @@ impl<const SIZE: usize> Mmio<SIZE> {
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 +855,6 @@ impl<const SIZE: usize> Io for RelaxedMmio<SIZE> {
}
}
-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.
///