rust: io: add projection macro and methods

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 <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-13-72cd5d055d54@garyguo.net
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
Gary Guo 2026-07-06 13:44:26 +01:00 committed by Danilo Krummrich
parent 9b36c13cbd
commit 1f989555fe

View File

@ -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],
}
@ -91,6 +92,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.
@ -340,6 +354,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>
@ -982,3 +1041,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;