[PATCH 6/9] rust: io: add checked offset copy helpers

From: Mike Lothian

Date: Wed Aug 26 2026 - 12:34:50 EST


I/O mappings often need to copy a bounded byte range rather than
the complete mapping. Add checked helpers that project the requested
range before using the backend copy operation. This keeps raw backend
pointers out of consumers and reports invalid ranges instead.

Assisted-by: Claude:claude-opus-5
Signed-off-by: Mike Lothian <mike@xxxxxxxxxxxxxx>
---
rust/kernel/io.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)

diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 95f46bb75f9e..aea346b8b79e 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -225,6 +225,30 @@ fn io_view<'a, IO: Io<'a>, U>(
Ok(unsafe { IO::Backend::project_view(view, projected_ptr) })
}

+/// Returns a byte-slice view for a given range, performing runtime bounds checks.
+#[inline]
+fn io_byte_slice<'a, IO>(
+ this: IO,
+ offset: usize,
+ len: usize,
+) -> Result<<IO::Backend as IoBackend>::View<'a, [u8]>>
+where
+ IO: Io<'a, Target = [u8]>,
+{
+ let view = this.as_view();
+ let ptr = IO::Backend::as_ptr(view);
+ let end = offset.checked_add(len).ok_or(EINVAL)?;
+
+ if end > ptr.len() {
+ return Err(EINVAL);
+ }
+
+ let projected_ptr =
+ core::ptr::slice_from_raw_parts_mut(ptr.cast::<u8>().wrapping_add(offset), len);
+ // SAFETY: The bounds check above proves that `projected_ptr` is a sub-slice of `ptr`.
+ Ok(unsafe { IO::Backend::project_view(view, projected_ptr) })
+}
+
/// I/O backends.
///
/// This is an abstract representation to be implemented by arbitrary I/O
@@ -640,6 +664,32 @@ fn copy_to_slice(self, data: &mut [u8])
}
}

+ /// Copy bytes from `data` to a range of I/O memory.
+ ///
+ /// Returns [`EINVAL`] if `offset..offset + data.len()` is outside the I/O region.
+ #[inline]
+ fn try_copy_from_slice(self, offset: usize, data: &[u8]) -> Result
+ where
+ Self::Backend: IoCopyable,
+ Self: Io<'a, Target = [u8]>,
+ {
+ io_byte_slice(self, offset, data.len())?.copy_from_slice(data);
+ Ok(())
+ }
+
+ /// Copy a range of I/O memory to `data`.
+ ///
+ /// Returns [`EINVAL`] if `offset..offset + data.len()` is outside the I/O region.
+ #[inline]
+ fn try_copy_to_slice(self, offset: usize, data: &mut [u8]) -> Result
+ where
+ Self::Backend: IoCopyable,
+ Self: Io<'a, Target = [u8]>,
+ {
+ io_byte_slice(self, offset, data.len())?.copy_to_slice(data);
+ Ok(())
+ }
+
/// Fallible 8-bit read with runtime bounds check.
#[inline(always)]
fn try_read8(self, offset: usize) -> Result<u8>