From: Vasileios Almpanis <vasilisalmpanis@gmail.com>
To: "Danilo Krummrich" <dakr@kernel.org>,
"Abdiel Janulgue" <abdiel.janulgue@gmail.com>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Robin Murphy" <robin.murphy@arm.com>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>
Cc: driver-core@lists.linux.dev, rust-for-linux@vger.kernel.org,
linux-kernel@vger.kernel.org,
Vasileios Almpanis <vasilisalmpanis@gmail.com>
Subject: [PATCH] rust: dma: add `Range` type
Date: Thu, 06 Aug 2026 09:46:14 +0200 [thread overview]
Message-ID: <20260806-dma-v1-1-e8a39327c7f5@gmail.com> (raw)
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>
next reply other threads:[~2026-08-06 7:46 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-06 7:46 Vasileios Almpanis [this message]
2026-08-06 11:23 ` [PATCH] rust: dma: add `Range` type Robin Murphy
2026-08-06 13:40 ` Vasileios Almpanis
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260806-dma-v1-1-e8a39327c7f5@gmail.com \
--to=vasilisalmpanis@gmail.com \
--cc=a.hindborg@kernel.org \
--cc=abdiel.janulgue@gmail.com \
--cc=acourbot@nvidia.com \
--cc=aliceryhl@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=driver-core@lists.linux.dev \
--cc=gary@garyguo.net \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=robin.murphy@arm.com \
--cc=rust-for-linux@vger.kernel.org \
--cc=tamird@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.