diff options
| author | Gary Guo <gary@garyguo.net> | 2026-07-06 13:44:14 +0100 |
|---|---|---|
| committer | Danilo Krummrich <dakr@kernel.org> | 2026-07-11 17:42:56 +0200 |
| commit | 426c92ca1bdd33dcbc01d6d66bb5bb4a356f2c54 (patch) | |
| tree | 3138a45b7274cadc1f61a8cb9f9a07837dde516d | |
| parent | dc59e4fea9d83f03bad6bddf3fa2e52491777482 (diff) | |
| download | linux-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>
| -rw-r--r-- | rust/kernel/devres.rs | 6 | ||||
| -rw-r--r-- | rust/kernel/io.rs | 130 | ||||
| -rw-r--r-- | rust/kernel/lib.rs | 3 | ||||
| -rw-r--r-- | rust/kernel/pci.rs | 1 | ||||
| -rw-r--r-- | rust/kernel/pci/io.rs | 40 | ||||
| -rw-r--r-- | rust/kernel/ptr.rs | 12 |
6 files changed, 118 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 @@ impl<T: Send + 'static> Devres<T> { /// 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..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. /// diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 9512af7156df..68f4d9a3425d 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -16,6 +16,9 @@ // Please see https://github.com/Rust-for-Linux/linux/issues/2 for details on // the unstable features in use. // +// Stable since Rust 1.87.0. +#![feature(unsigned_is_multiple_of)] +// // Stable since Rust 1.89.0. #![feature(generic_arg_infer)] // 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::id::{ 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 @@ use crate::{ io::{ Io, IoCapable, - IoKnownSize, Mmio, - MmioRaw, // + MmioRaw, + Region, // }, - prelude::*, // + prelude::*, + ptr::KnownSize, // }; use core::{ marker::PhantomData, @@ -46,28 +47,21 @@ impl ConfigSpaceSize { } } -/// 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 @@ impl_config_space_io_capable!(u8, pci_read_config_byte, pci_write_config_byte); 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 @@ impl<'a, S: ConfigSpaceKind> Io for ConfigSpace<'a, S> { } } -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 @@ impl_alignable_uint!(u8, u16, u32, u64, usize); /// /// 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 @@ impl<T> KnownSize for T { } 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>() |
