On Fri Jun 26, 2026 at 11:45 PM JST, Gary Guo wrote: > Add an `io_project!()` macro allows projection from `Io` to a subview of
nit: "to allow projection"? > 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. > > Signed-off-by: Gary Guo <[email protected]> > --- > rust/kernel/io.rs | 124 > ++++++++++++++++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 124 insertions(+) > > diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs > index 0746b0d209ef..96962498af77 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,16 @@ fn size(p: *const Self) -> usize { > } > } > > +// SAFETY: Values read from I/O are always treated as initialized. > +// > +// This cannot be derived as `derive(IntoBytes)` does not know that this > type is padding free (given > +// `repr(align(4))`). In that case the `SAFETY` comment should probably also justify that there is no padding by citing the invariant of `Region` (which will also allow us to remove the second paragraph). > +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 +350,49 @@ fn size(self) -> usize { > KnownSize::size(Self::Backend::as_ptr(self.as_view())) > } > > + /// Try to convert into a different typed I/O view. > + /// > + /// The target type must be of same or smaller size to current type, and > the current view must > + /// be properly aligned for the target type. Maybe "Returns `EINVAL` if the target type..." to make it clear this is runtime-checked and not a static requirement. > + /// > + /// # Examples > + /// > + /// ```no_run > + /// use kernel::io::{ > + /// io_project, > + /// Mmio, > + /// Io, > + /// Region, > + /// }; > + /// #[derive(FromBytes, IntoBytes)] > + /// struct MyStruct { field: u32, } Do we want to make this `#[repr(C)]` to give the right example to driver authors looking for inspiration here? > + /// > + /// # 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 +1033,73 @@ 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 binary to avoid > confusion. nit: "in the binary", lest the reader thinks we are talking about symbols spelled with 0s and 1s. :) > + #[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!). > +/// > +/// In addition to projecting from [`Io`], you may also project from a > [`View`] of an [`Io`]. > +/// > +/// # Examples > +/// > +/// ``` > +/// use kernel::io::{ > +/// io_project, > +/// Mmio, > +/// }; > +/// struct MyStruct { field: u32, } Same remark about `#[repr(C)]`. With these considered, Reviewed-by: Alexandre Courbot <[email protected]>
