> On 6 Jul 2026, at 09:44, Gary Guo <[email protected]> wrote:
>
> Add an `io_project!()` macro to allow projection from `Io` to a subview of
> it, using the pointer projection mechanism to perform compile-time checks.
>
> For cases where type-casting is required, the `try_cast()` function may be
> used where the size and alignment checks are performed at runtime.
>
> Reviewed-by: Alexandre Courbot <[email protected]>
> Signed-off-by: Gary Guo <[email protected]>
> ---
> rust/kernel/io.rs | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 128 insertions(+)
>
> diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
> index ab970e8b2e78..62643b23e904 100644
> --- a/rust/kernel/io.rs
> +++ b/rust/kernel/io.rs
> @@ -49,6 +49,7 @@
> /// - 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))]
> +#[derive(FromBytes)]
> pub struct Region<const SIZE: usize = 0> {
> inner: [u8],
> }
> @@ -90,6 +91,19 @@ fn size(p: *const Self) -> usize {
> }
> }
>
> +// SAFETY:
> +// - Values read from I/O are always treated as initialized.
> +// - Per type invariant the size is multiple of 4 and the type is 4-byte
> aligned, so it is padding
> +// free.
> +//
> +// This cannot be derived as `derive(IntoBytes)` as the padding free
> property comes from type
> +// invariant which the macro does not know.
> +unsafe impl<const SIZE: usize> IntoBytes for Region<SIZE> {
> + #[inline]
> + #[allow(unused)] // Rust 1.87+ stops requiring this and will emit unused
> warnings.
> + fn only_derive_is_allowed_to_implement_this_trait() {}
> +}
> +
> /// Raw representation of an MMIO region.
> ///
> /// `MmioRaw<T>` is equivalent to `T __iomem *` in C.
> @@ -339,6 +353,51 @@ fn size(self) -> usize {
> KnownSize::size(Self::Backend::as_ptr(self.as_view()))
> }
>
> + /// Try to convert into a different typed I/O view.
> + ///
> + /// A runtime check is performed to ensure that the target type is of
> same or smaller size to
> + /// current type, and the current view is properly aligned for the
> target type. Returns
> + /// `Err(EINVAL)` if the runtime check fails.
> + ///
> + /// # Examples
> + ///
> + /// ```no_run
> + /// use kernel::io::{
> + /// io_project,
> + /// Mmio,
> + /// Io,
> + /// Region,
> + /// };
> + /// #[derive(FromBytes, IntoBytes)]
> + /// #[repr(C)]
> + /// struct MyStruct { field: u32, }
> + ///
> + /// # fn test(mmio: &Mmio<'_, Region>) -> Result {
> + /// // let mmio: Mmio<'_, Region>;
> + /// let whole: Mmio<'_, MyStruct> = mmio.try_cast()?;
> + /// # Ok::<(), Error>(()) }
> + /// ```
> + #[inline]
> + fn try_cast<U>(self) -> Result<<Self::Backend as IoBackend>::View<'a, U>>
> + where
> + Self::Target: FromBytes + IntoBytes,
> + U: FromBytes + IntoBytes,
> + {
> + let view = self.as_view();
> + let ptr = Self::Backend::as_ptr(view);
> +
> + if size_of::<U>() > KnownSize::size(ptr) {
> + return Err(EINVAL);
> + }
> +
> + if ptr.addr() % align_of::<U>() != 0 {
> + return Err(EINVAL);
> + }
> +
> + // SAFETY: We have checked bounds and alignment, so this is a valid
> projection.
> + Ok(unsafe { Self::Backend::project_view(view, ptr.cast()) })
> + }
> +
> /// Fallible 8-bit read with runtime bounds check.
> #[inline(always)]
> fn try_read8(self, offset: usize) -> Result<u8>
> @@ -979,3 +1038,72 @@ pub fn relaxed(self) -> RelaxedMmio<'a, T> {
> // MMIO regions on 64-bit systems also support 64-bit accesses.
> #[cfg(CONFIG_64BIT)]
> impl_mmio_io_capable!(RelaxedMmioBackend, u64, readq_relaxed, writeq_relaxed);
> +
> +// This helper turns associated functions to methods so it can be invoked in
> macro.
> +// Used by `io_project!()` only.
> +#[doc(hidden)]
> +#[derive(Clone, Copy)]
> +pub struct ProjectHelper<T>(pub T);
> +
> +impl<'a, T> ProjectHelper<T>
> +where
> + T: Io<'a, Backend: IoBackend<View<'a, T::Target> = T>>,
> +{
> + // These helper methods must not have symbols present in the binary to
> avoid confusion.
> + #[inline(always)]
> + pub fn as_ptr(self) -> *mut T::Target {
> + T::Backend::as_ptr(self.0)
> + }
> +
> + /// # Safety
> + ///
> + /// Same as `IoBackend::project_view`
> + #[inline(always)]
> + pub unsafe fn project_view<U: ?Sized + KnownSize>(
> + self,
> + ptr: *mut U,
> + ) -> <T::Backend as IoBackend>::View<'a, U> {
> + // SAFETY: Per safety requirement.
> + unsafe { T::Backend::project_view::<T::Target, _>(self.0, ptr) }
> + }
> +}
> +
> +/// Project an I/O type to a subview of it.
> +///
> +/// The syntax is of form `io_project!(io, proj)` where `io` is an
> expression to a type that
> +/// implements [`Io`] and `proj` is a [projection
> specification](kernel::ptr::project!).
> +///
> +/// # Examples
> +///
> +/// ```
> +/// use kernel::io::{
> +/// io_project,
> +/// Mmio,
> +/// };
> +/// #[repr(C)]
> +/// struct MyStruct { field: u32, }
> +///
> +/// # fn test(mmio: Mmio<'_, [MyStruct]>) -> Result {
> +/// // let mmio: Mmio<[MyStruct]>;
> +/// let field: Mmio<'_, u32> = io_project!(mmio, [try: 1].field);
> +/// let whole: Mmio<'_, MyStruct> = io_project!(mmio, [try: 2]);
> +/// let nested: Mmio<'_, u32> = io_project!(whole, .field);
> +/// # Ok::<(), Error>(()) }
> +/// ```
> +#[macro_export]
> +#[doc(hidden)]
> +macro_rules! io_project {
> + ($io:expr, $($proj:tt)*) => {{
> + #[allow(unused)]
> + use $crate::io::IoBase as _;
> + let view = $crate::io::ProjectHelper($io.as_view());
> + let ptr = $crate::ptr::project!(
> + mut view.as_ptr(), $($proj)*
> + );
> + #[allow(unused_unsafe)]
> + // SAFETY: `ptr` is a projection.
> + unsafe { view.project_view(ptr) }
> + }};
> +}
> +#[doc(inline)]
> +pub use crate::io_project;
>
> --
> 2.54.0
>
Reviewed-by: Daniel Almeida <[email protected]>