Re: [PATCH v6 13/20] rust: io: add projection macro and methods
Daniel Almeida <[email protected]> Wed, 8 Jul 2026 14:58:29 -0300
| Newsgroups | org.kernel.vger.linux-pwm,dev.linux.lists.driver-core,dev.linux.lists.nova-gpu,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pci,org.kernel.vger.rust-for-linux |
|---|---|
| Message-ID | <[email protected]> |
> 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]>