* [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* Re: [PATCH] rust: dma: add `Range` type
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
0 siblings, 1 reply; 3+ messages in thread
From: Robin Murphy @ 2026-08-06 11:23 UTC (permalink / raw)
To: Vasileios Almpanis, Danilo Krummrich, Abdiel Janulgue,
Daniel Almeida, 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
On 2026-08-06 8:46 am, Vasileios Almpanis wrote:
> 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 base address may be 64-bit, but the underlying dma_alloc_*/dma_map_*
APIs all express lengths in size_t, so we shouldn't need to accommodate
anything larger in Rust either. Sizes-as-address-types always seem a bit
weird and clunky (lookin' at you, resource_size_t...), so probably
better avoided if not absolutely necessary, IMO.
Thanks,
Robin.
> - 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,
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH] rust: dma: add `Range` type
2026-08-06 11:23 ` Robin Murphy
@ 2026-08-06 13:40 ` Vasileios Almpanis
0 siblings, 0 replies; 3+ messages in thread
From: Vasileios Almpanis @ 2026-08-06 13:40 UTC (permalink / raw)
To: Robin Murphy, Danilo Krummrich, Abdiel Janulgue, Daniel Almeida,
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
On 8/6/26 1:23 PM, Robin Murphy wrote:
> On 2026-08-06 8:46 am, Vasileios Almpanis wrote:
>> 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 base address may be 64-bit, but the underlying
> dma_alloc_*/dma_map_* APIs all express lengths in size_t, so we
> shouldn't need to accommodate anything larger in Rust either.
> Sizes-as-address-types always seem a bit weird and clunky (lookin' at
> you, resource_size_t...), so probably better avoided if not absolutely
> necessary, IMO.
>
> Thanks,
> Robin.
You are right, lengths come from size_t taking APIs anyway, so usize is
the right type. Will change len and offset together with some things
sashiko found in v2
>
>> - 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,
>
^ permalink raw reply [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