Rust for Linux List
 help / color / mirror / Atom feed
* [PATCH] rust: dma: add `Range` type
@ 2026-08-06  7:46 Vasileios Almpanis
  2026-08-06 11:23 ` Robin Murphy
  0 siblings, 1 reply; 3+ messages in thread
From: Vasileios Almpanis @ 2026-08-06  7:46 UTC (permalink / raw)
  To: Danilo Krummrich, Abdiel Janulgue, Daniel Almeida, Robin Murphy,
	Andreas Hindborg, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Alice Ryhl, Trevor Gross,
	Tamir Duberstein, Alexandre Courbot, Onur Özkan
  Cc: driver-core, rust-for-linux, linux-kernel, Vasileios Almpanis

The base dma address of `Coherent` and `CoherentHandle` is a bare
`DmaAddress` integer so any arithmetic, a driver does on it is unchecked
and can potentially go past the end of the allocation or overflow.

Add a `dma::Range` type that couples a base `DmaAddress` with a length
and only hands out addresses and sub-ranges within `[start, start + len)`
with all arithmetics checked against both the length and the overflow of
of the underlying `dma_addr_t`. Let `Coherent` and `CoherentHandle`
provide the `Range` covering their allocation.

Suggested-by: Danilo Krummrich <dakr@kernel.org>
Link: https://github.com/Rust-for-Linux/linux/issues/1248
Signed-off-by: Vasileios Almpanis <vasilisalmpanis@gmail.com>
---
This is one of my first rust-for-linux patches, so any suggestions are
extremely welcome. Some words about the decisions taken:

- I chose lengths offsets to be `DmaAddress` instead of usize since the
  bus space can be 64-bit on 32-bit CPUS.

- The constructor returns EOVERFLOW for `dma_addr_t` overflow, while
  out-of-bounds requests return EINVAL; happy to use a single error
  code if preferred.

- `dma_range()` is added to both `Coherent` and `CoherentHandle` so the
  type has users from the start. I could split it into a follow-up if
  that is preferred.

Tested with `make rustdoc`, `make rustfmtcheck`, `CLIPPY=1`, and the
KUnit doctests `rust_doctests_kernel`.
---
 rust/kernel/dma.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 174 insertions(+)

diff --git a/rust/kernel/dma.rs b/rust/kernel/dma.rs
index 200def84fb69e006bca0b1c578bac9f1dc8da708..e65e99ae4d915ac2e385df8b72fc518c07e4c82d 100644
--- a/rust/kernel/dma.rs
+++ b/rust/kernel/dma.rs
@@ -41,6 +41,142 @@
 /// Note that this may be `u64` even on 32-bit architectures.
 pub type DmaAddress = bindings::dma_addr_t;
 
+/// A range of DMA addresses.
+///
+/// Couples a base [`DmaAddress`] with the length in bytes of the region it belongs to,
+/// representing the half-open range `[start, start + len)` of DMA addresses.
+///
+/// Unlike a bare [`DmaAddress`], a [`Range`] only hands out addresses and sub-ranges that are
+/// guaranteed to lie within `[start, start + len)`; all arithmetic is checked against both the
+/// length of the range and overflow of the underlying [`DmaAddress`].
+///
+/// # Invariants
+///
+/// `start + len` does not overflow [`DmaAddress`].
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct Range {
+    start: DmaAddress,
+    len: DmaAddress,
+}
+
+impl Range {
+    /// Creates a new [`Range`] of `len` bytes, starting at `start`.
+    ///
+    /// Returns [`EOVERFLOW`] if `start + len` overflows [`DmaAddress`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::dma::{DmaAddress, Range};
+    ///
+    /// let range = Range::new(0x1000, 0x200)?;
+    /// assert_eq!(range.start(), 0x1000);
+    /// assert_eq!(range.end(), 0x1200);
+    /// assert_eq!(range.len(), 0x200);
+    ///
+    /// assert!(Range::new(DmaAddress::MAX, 1).is_err());
+    /// # Ok::<(), Error>(())
+    /// ```
+    #[inline]
+    pub const fn new(start: DmaAddress, len: DmaAddress) -> Result<Self> {
+        if start.checked_add(len).is_none() {
+            return Err(EOVERFLOW);
+        }
+
+        // INVARIANT: We just checked that `start + len` does not overflow `DmaAddress`.
+        Ok(Self { start, len })
+    }
+
+    /// Returns the first address of the range.
+    #[inline]
+    pub const fn start(&self) -> DmaAddress {
+        self.start
+    }
+
+    /// Returns the first address after the end of the range.
+    #[inline]
+    pub const fn end(&self) -> DmaAddress {
+        // By the type invariant, `start + len` does not overflow `DmaAddress`.
+        self.start + self.len
+    }
+
+    /// Returns the length of the range in bytes.
+    #[inline]
+    pub const fn len(&self) -> DmaAddress {
+        self.len
+    }
+
+    /// Returns `true` if the range is empty.
+    #[inline]
+    pub const fn is_empty(&self) -> bool {
+        self.len == 0
+    }
+
+    /// Returns the address at `offset` bytes into the range.
+    ///
+    /// The returned address is guaranteed to lie within the range; returns [`EINVAL`] if `offset`
+    /// is not smaller than the length of the range.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::dma::Range;
+    ///
+    /// let range = Range::new(0x1000, 0x200)?;
+    ///
+    /// assert_eq!(range.address(0)?, 0x1000);
+    /// assert_eq!(range.address(0x1ff)?, 0x11ff);
+    /// assert!(range.address(0x200).is_err());
+    /// # Ok::<(), Error>(())
+    /// ```
+    #[inline]
+    pub const fn address(&self, offset: DmaAddress) -> Result<DmaAddress> {
+        if offset >= self.len {
+            return Err(EINVAL);
+        }
+
+        // By the type invariant, `start + offset < start + len` does not overflow `DmaAddress`.
+        Ok(self.start + offset)
+    }
+
+    /// Returns the sub-range of `len` bytes, starting `offset` bytes into the range.
+    ///
+    /// The returned range is guaranteed to lie within the range; returns [`EINVAL`] if
+    /// `offset + len` overflows [`DmaAddress`] or exceeds the length of the range.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use kernel::dma::Range;
+    ///
+    /// let range = Range::new(0x1000, 0x200)?;
+    ///
+    /// let sub = range.subrange(0x100, 0x80)?;
+    /// assert_eq!(sub.start(), 0x1100);
+    /// assert_eq!(sub.end(), 0x1180);
+    ///
+    /// assert!(range.subrange(0x100, 0x101).is_err());
+    /// # Ok::<(), Error>(())
+    /// ```
+    #[inline]
+    pub const fn subrange(&self, offset: DmaAddress, len: DmaAddress) -> Result<Self> {
+        let Some(end) = offset.checked_add(len) else {
+            return Err(EINVAL);
+        };
+
+        if end > self.len {
+            return Err(EINVAL);
+        }
+
+        // INVARIANT: `start + offset + len <= start + self.len`, which by the type invariant of
+        // `self` does not overflow `DmaAddress`.
+        Ok(Self {
+            start: self.start + offset,
+            len,
+        })
+    }
+}
+
 /// Trait to be implemented by DMA capable bus devices.
 ///
 /// The [`dma::Device`](Device) trait should be implemented by bus specific device representations,
@@ -626,6 +762,25 @@ pub fn dma_handle(&self) -> DmaAddress {
         self.dma_handle
     }
 
+    /// Returns the [`Range`] of DMA addresses covering this allocation.
+    ///
+    /// Unlike [`Self::dma_handle`], which hands out the base address as a bare integer, the
+    /// returned [`Range`] couples the base address with the size of the allocation, such that
+    /// any offset arithmetic performed on it is checked.
+    #[inline]
+    pub fn dma_range(&self) -> Range {
+        // INVARIANT: By the type invariants of `Self`, `dma_handle` is the DMA address base of an
+        // allocated region of `self.size()` bytes; the DMA API guarantees that a mapped region
+        // never wraps the DMA address space, hence `dma_handle + size` does not overflow
+        // `DmaAddress`.
+        Range {
+            start: self.dma_handle,
+            // CAST: `usize` always fits in `DmaAddress`, which is at least 32 bits wide and
+            // always 64 bits wide on 64-bit architectures.
+            len: self.size() as DmaAddress,
+        }
+    }
+
     /// Returns a reference to the data in the region.
     ///
     /// # Safety
@@ -1101,6 +1256,25 @@ pub fn dma_handle(&self) -> DmaAddress {
         self.dma_handle
     }
 
+    /// Returns the [`Range`] of DMA addresses covering this allocation.
+    ///
+    /// Unlike [`Self::dma_handle`], which hands out the base address as a bare integer, the
+    /// returned [`Range`] couples the base address with the size of the allocation, such that
+    /// any offset arithmetic performed on it is checked.
+    #[inline]
+    pub fn dma_range(&self) -> Range {
+        // INVARIANT: By the type invariants of `Self`, `dma_handle` is the DMA address base of an
+        // allocated region of `self.size` bytes; the DMA API guarantees that a mapped region
+        // never wraps the DMA address space, hence `dma_handle + size` does not overflow
+        // `DmaAddress`.
+        Range {
+            start: self.dma_handle,
+            // CAST: `usize` always fits in `DmaAddress`, which is at least 32 bits wide and
+            // always 64 bits wide on 64-bit architectures.
+            len: self.size as DmaAddress,
+        }
+    }
+
     /// Returns the size in bytes of this allocation.
     #[inline]
     pub fn size(&self) -> usize {

---
base-commit: dc01dfb37b34beeefcfe1c3055364d41a4070c7e
change-id: 20260805-dma-7c5330baadb6

Best regards,
-- 
Vasileios Almpanis <vasilisalmpanis@gmail.com>


^ permalink raw reply related	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-06 13:40 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06  7:46 [PATCH] rust: dma: add `Range` type Vasileios Almpanis
2026-08-06 11:23 ` Robin Murphy
2026-08-06 13:40   ` Vasileios Almpanis

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox