Add an `io_project!()` macro allows 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. Signed-off-by: Gary Guo <[email protected]> --- rust/kernel/io.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 4 deletions(-) diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs index 470ee2ed9849..c7a9952af995 100644 --- a/rust/kernel/io.rs +++ b/rust/kernel/io.rs @@ -14,7 +14,7 @@ ptr::{ Alignment, KnownSize, // - }, // + }, }; pub mod mem; @@ -44,12 +44,12 @@ /// 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). /// -/// This must be 4-byte aligned. -/// /// # Invariants /// -/// Size of the region is at least as large as the `SIZE` generic parameter. +/// - 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], } @@ -91,6 +91,15 @@ 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))`). +unsafe impl<const SIZE: usize> IntoBytes for Region<SIZE> { + #[inline] + fn only_derive_is_allowed_to_implement_this_trait() {} +} + /// Raw representation of an MMIO region. /// /// `MmioRaw<T>` is equivalent to `T __iomem *` in C. @@ -295,6 +304,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. + /// + /// # Examples + /// + /// ```no_run + /// use kernel::io::{ + /// io_project, + /// Mmio, + /// Io, + /// Region, + /// }; + /// #[derive(FromBytes, IntoBytes)] + /// 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()) }) + } + /// Returns a view for a given `offset`, performing compile-time bound checks. // Always inline to optimize out error path of `build_assert`. #[inline(always)] @@ -988,3 +1040,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. + #[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, } +/// +/// # 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
