Re: [PATCH v5 3/5] rust: bitmap: add contiguous area operations
"Eliot Courtney" <[email protected]>
| Newsgroups | dev.linux.lists.nova-gpu,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel,org.kernel.vger.rust-for-linux |
|---|---|
| Message-ID | <[email protected]> |
On Thu Aug 13, 2026 at 5:31 AM JST, Yury Norov wrote: > On Wed, Aug 12, 2026 at 05:51:23PM +0900, Eliot Courtney wrote: >> Add bindings for area operations on bitmaps. Each one is >> made safe by adding some extra checks compared to the underlying C code >> (for example, checking bounds) and with additional checks to catch >> likely erroneous usage if `CONFIG_RUST_BITMAP_HARDENED` is on. >> >> Add tests demonstrating the edge cases. >> >> Signed-off-by: Eliot Courtney <[email protected]> >> --- >> rust/kernel/bitmap.rs | 236 ++++++++++++++++++++++++++++++++++++++++++++++++++ >> 1 file changed, 236 insertions(+) >> >> diff --git a/rust/kernel/bitmap.rs b/rust/kernel/bitmap.rs >> index fdcfc0409773..74c92cc452c9 100644 >> --- a/rust/kernel/bitmap.rs >> +++ b/rust/kernel/bitmap.rs >> @@ -10,6 +10,7 @@ >> use crate::bindings; >> #[cfg(not(CONFIG_RUST_BITMAP_HARDENED))] >> use crate::pr_err; >> +use crate::ptr::Alignment; >> use core::ptr::NonNull; >> >> /// Represents a C bitmap. Wraps underlying C bitmap API. >> @@ -523,6 +524,139 @@ pub fn next_zero_bit(&self, start: usize) -> Option<usize> { >> Some(index) >> } >> } >> + >> + /// Finds a contiguous area of `nbits` zero bits at or after `start`, where the area plus >> + /// `align_offset` is aligned to `align`. >> + /// >> + /// Returns the bit index of the start of the area, or [`None`] if no such area fitting in >> + /// the bitmap exists. >> + /// >> + /// The returned index plus `align_offset` is a multiple of `align`. >> + /// >> + /// # Panics >> + /// >> + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and `start` is out of bounds. >> + #[inline] >> + pub fn next_zero_area_off( >> + &self, >> + start: usize, >> + nbits: usize, >> + align: Alignment, >> + align_offset: usize, >> + ) -> Option<usize> { >> + bitmap_assert!( >> + start < self.len(), >> + "`start` must be < {}, was {}", >> + self.len(), >> + start >> + ); >> + >> + let nr = u32::try_from(nbits).ok()?; > > What about nbits == 0? In C, this is a undef, and thus in the current > rust implementation. Maybe make it NonZero? > > The same question about align and align_offset. NonZero sounds good to me for `nbits`. For `align`, it's already guaranteed to be at least 1. For `align_offset`, passing 0 is normal and valid (and we need to for implementing `next_zero_area` just below) > >> + let align_mask = align.as_usize() - 1; >> + >> + // The C alignment and end arithmetic must not overflow, or it can read out of bounds. >> + // Overflow is only possible on 32-bit. >> + #[cfg(not(CONFIG_64BIT))] >> + align_mask.checked_add(self.len())?.checked_add(nbits)?; >> + >> + // SAFETY: `bitmap_find_next_zero_area_off` is safe to use with an out of bounds `start` >> + // value and, given the overflow check above, never reads beyond `self.len()` bits. >> + let index = unsafe { >> + bindings::bitmap_find_next_zero_area_off( >> + self.as_ptr().cast_mut(), >> + self.len(), >> + start, >> + nr, >> + align_mask, >> + align_offset, >> + ) >> + }; >> + >> + (index < self.len()).then_some(index) >> + } >> + >> + /// Finds a contiguous area of `nbits` zero bits at or after `start`, aligned to `align`. >> + /// >> + /// Returns the bit index of the start of the area, or [`None`] if no such area fitting in >> + /// the bitmap exists. >> + /// >> + /// The returned index is a multiple of `align`. >> + /// >> + /// # Panics >> + /// >> + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and `start` is out of bounds. >> + /// >> + /// # Examples >> + /// >> + /// ``` >> + /// use kernel::alloc::{AllocError, flags::GFP_KERNEL}; >> + /// use kernel::bitmap::BitmapVec; >> + /// use kernel::ptr::Alignment; >> + /// >> + /// let mut b = BitmapVec::new(64, GFP_KERNEL)?; >> + /// let unaligned = Alignment::new::<1>(); >> + /// >> + /// assert_eq!(Some(0), b.next_zero_area(0, 8, unaligned)); >> + /// b.set(0, 5); >> + /// assert_eq!(Some(5), b.next_zero_area(0, 8, unaligned)); >> + /// assert_eq!(Some(8), b.next_zero_area(0, 8, Alignment::new::<8>())); >> + /// assert_eq!(None, b.next_zero_area(0, 65, unaligned)); >> + /// # Ok::<(), AllocError>(()) >> + /// ``` >> + #[inline] >> + pub fn next_zero_area(&self, start: usize, nbits: usize, align: Alignment) -> Option<usize> { >> + self.next_zero_area_off(start, nbits, align, 0) >> + } >> + >> + /// Sets a contiguous area of `nbits` bits starting at `start`. >> + /// >> + /// If CONFIG_RUST_BITMAP_HARDENED is not enabled and the area `start..start + nbits` is out of >> + /// bounds, does nothing. >> + /// >> + /// # Panics >> + /// >> + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and the area `start..start + nbits` is out >> + /// of bounds. >> + #[inline] >> + pub fn set(&mut self, start: usize, nbits: usize) { >> + bitmap_assert_return!( >> + start >> + .checked_add(nbits) >> + .is_some_and(|end| end <= self.len()), >> + "Area `start..start + nbits` ({}..{}) must be within bounds {}", >> + start, >> + start.saturating_add(nbits), >> + self.len() >> + ); >> + // SAFETY: The area `start..start + nbits` is within bounds and a `Bitmap` is at most >> + // `i32::MAX` bits, so the casts are lossless. >> + unsafe { bindings::__bitmap_set(self.as_mut_ptr(), start as u32, nbits as i32) }; >> + } > > In the case of bitmap_set/clear(), nbits == 0 makes it a no-op, and > guarantees that the pointer is not dereferenced. So, no undefined > behavior. But in rust case, I believe, it should be a stronger policy. > > I'd add an assertion, at least, or better make it NonZero. > > Thanks, > Yury Yeah, NonZero sounds good to me here too. Thanks! > >> + >> + /// Clears a contiguous area of `nbits` bits starting at `start`. >> + /// >> + /// If CONFIG_RUST_BITMAP_HARDENED is not enabled and the area `start..start + nbits` is out of >> + /// bounds, does nothing. >> + /// >> + /// # Panics >> + /// >> + /// Panics if CONFIG_RUST_BITMAP_HARDENED is enabled and the area `start..start + nbits` is out >> + /// of bounds. >> + #[inline] >> + pub fn clear(&mut self, start: usize, nbits: usize) { >> + bitmap_assert_return!( >> + start >> + .checked_add(nbits) >> + .is_some_and(|end| end <= self.len()), >> + "Area `start..start + nbits` ({}..{}) must be within bounds {}", >> + start, >> + start.saturating_add(nbits), >> + self.len() >> + ); >> + // SAFETY: The area `start..start + nbits` is within bounds and a `Bitmap` is at most >> + // `i32::MAX` bits, so the casts are lossless. >> + unsafe { bindings::__bitmap_clear(self.as_mut_ptr(), start as u32, nbits as i32) }; >> + } >> }