* [PATCH v12 1/2] rust: id_pool: do not round capacity up to BitmapVec::MAX_INLINE_LEN
2026-11-12 12:37 [PATCH v12 0/2] rust: Add support for reserving of ranges of IDs Eliot Courtney
@ 2026-11-12 12:37 ` Eliot Courtney
2026-11-12 12:37 ` [PATCH v12 2/2] gpu: nova-core: add ChannelIdPool Eliot Courtney
1 sibling, 0 replies; 3+ messages in thread
From: Eliot Courtney @ 2026-11-12 12:37 UTC (permalink / raw)
To: Alice Ryhl, Burak Emir, Yury Norov, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter
Cc: Greg Kroah-Hartman, John Hubbard, Alistair Popple, Timur Tabi,
Zhi Wang, rust-for-linux, linux-kernel, nova-gpu, dri-devel,
Eliot Courtney
Current code in IdPool::with_capacity rounds the capacity up to
BitmapVec::MAX_INLINE_LEN, but BitmapVec::new works fine with values
smaller than this and still uses an inline representation. Remove this
behaviour.
Change `grow_request` to grow to at least `BitmapVec::MAX_INLINE_LEN`
instead, mirroring the capacity floor in `shrink_request`.
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Reviewed-by: Burak Emir <burak.emir@gmail.com>
Reviewed-by: Alexandre Courbot <acourbot@nvidia.com>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
rust/kernel/id_pool.rs | 38 ++++++++++++++++++++++++++++++++------
1 file changed, 32 insertions(+), 6 deletions(-)
diff --git a/rust/kernel/id_pool.rs b/rust/kernel/id_pool.rs
index 92eeb7caa243..f77bd90325ff 100644
--- a/rust/kernel/id_pool.rs
+++ b/rust/kernel/id_pool.rs
@@ -112,13 +112,8 @@ pub fn new() -> Self {
}
/// Constructs a new [`IdPool`] with space for a specific number of bits.
- ///
- /// A capacity below [`MAX_INLINE_LEN`] is adjusted to [`MAX_INLINE_LEN`].
- ///
- /// [`MAX_INLINE_LEN`]: BitmapVec::MAX_INLINE_LEN
#[inline]
pub fn with_capacity(num_ids: usize, flags: Flags) -> Result<Self, AllocError> {
- let num_ids = usize::max(num_ids, BitmapVec::MAX_INLINE_LEN);
let map = BitmapVec::new(num_ids, flags)?;
Ok(Self { map })
}
@@ -152,6 +147,13 @@ pub fn capacity(&self) -> usize {
/// let resizer = alloc_request.realloc(GFP_KERNEL)?;
/// pool.shrink(resizer);
/// assert_eq!(pool.capacity(), BitmapVec::MAX_INLINE_LEN);
+ ///
+ /// // A pool at the `MAX_INLINE_LEN` floor cannot shrink further.
+ /// assert!(pool.shrink_request().is_none());
+ ///
+ /// // Neither can a pool with a capacity below `MAX_INLINE_LEN`.
+ /// let small = IdPool::with_capacity(8, GFP_KERNEL)?;
+ /// assert!(small.shrink_request().is_none());
/// # Ok::<(), AllocError>(())
/// ```
#[inline]
@@ -198,12 +200,36 @@ pub fn shrink(&mut self, mut resizer: PoolResizer) {
/// Returns a [`ReallocRequest`] for growing this [`IdPool`], if possible.
///
+ /// Grows to at least [`MAX_INLINE_LEN`].
/// The capacity of an [`IdPool`] cannot be grown above [`MAX_LEN`].
///
+ /// [`MAX_INLINE_LEN`]: BitmapVec::MAX_INLINE_LEN
/// [`MAX_LEN`]: BitmapVec::MAX_LEN
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use kernel::{
+ /// alloc::AllocError,
+ /// bitmap::BitmapVec,
+ /// id_pool::IdPool, //
+ /// };
+ ///
+ /// // Grow goes to at least BitmapVec::MAX_INLINE_LEN.
+ /// let mut pool = IdPool::with_capacity(1, GFP_KERNEL)?;
+ /// let resizer = pool.grow_request().ok_or(AllocError)?.realloc(GFP_KERNEL)?;
+ /// pool.grow(resizer);
+ /// assert_eq!(pool.capacity(), BitmapVec::MAX_INLINE_LEN);
+ ///
+ /// // Grow doubles if at least BitmapVec::MAX_INLINE_LEN.
+ /// let resizer = pool.grow_request().ok_or(AllocError)?.realloc(GFP_KERNEL)?;
+ /// pool.grow(resizer);
+ /// assert_eq!(pool.capacity(), 2 * BitmapVec::MAX_INLINE_LEN);
+ /// # Ok::<(), AllocError>(())
+ /// ```
#[inline]
pub fn grow_request(&self) -> Option<ReallocRequest> {
- let num_ids = self.capacity() * 2;
+ let num_ids = usize::max(BitmapVec::MAX_INLINE_LEN, self.capacity() * 2);
if num_ids > BitmapVec::MAX_LEN {
return None;
}
--
2.56.0
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH v12 2/2] gpu: nova-core: add ChannelIdPool
2026-11-12 12:37 [PATCH v12 0/2] rust: Add support for reserving of ranges of IDs Eliot Courtney
2026-11-12 12:37 ` [PATCH v12 1/2] rust: id_pool: do not round capacity up to BitmapVec::MAX_INLINE_LEN Eliot Courtney
@ 2026-11-12 12:37 ` Eliot Courtney
1 sibling, 0 replies; 3+ messages in thread
From: Eliot Courtney @ 2026-11-12 12:37 UTC (permalink / raw)
To: Alice Ryhl, Burak Emir, Yury Norov, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter
Cc: Greg Kroah-Hartman, John Hubbard, Alistair Popple, Timur Tabi,
Zhi Wang, rust-for-linux, linux-kernel, nova-gpu, dri-devel,
Eliot Courtney
Add `ChannelIdPool` which adds automatic tracking and releasing of
channel IDs on top of `IdPool`. This is necessary for apportioning
ranges of channel IDs to be used in e.g. vGPU.
Channel IDs are allocated as a contiguous sequence with a specific
length and sometimes a specific alignment [1] for vGPU. The ID space is
small (limited to 2048) and allocation is not on a hot path, so a
bitmap-backed `IdPool` is a better fit than IDA/xarray (which allocate a
single ID within a range, not a contiguous sequence) or a maple tree
(where aligned allocation needs an alloc_range()+erase() retry loop that
essentially reimplements bitmap_find_next_zero_area()) [2]. It is
also faster than maple tree [3].
Link: https://lore.kernel.org/all/84bc8bd2-e292-4b84-9580-a1b5df4c5bdc@nvidia.com/ # [1]
Link: https://lore.kernel.org/all/20260710-chid-maple-v1-1-4ee869055268@nvidia.com/ # [2]
Link: https://lore.kernel.org/all/20260717053241.916441-1-ynorov@nvidia.com/ # [3]
Reviewed-by: Alice Ryhl <aliceryhl@google.com>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/gpu.rs | 2 +
drivers/gpu/nova-core/gpu/channel.rs | 198 +++++++++++++++++++++++++++++++++++
2 files changed, 200 insertions(+)
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index d185e927233f..90b1d24cdf00 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -53,6 +53,8 @@
vgpu::VgpuManager, //
};
+#[cfg_attr(not(CONFIG_KUNIT = "y"), expect(dead_code))]
+mod channel;
mod hal;
mod regs;
diff --git a/drivers/gpu/nova-core/gpu/channel.rs b/drivers/gpu/nova-core/gpu/channel.rs
new file mode 100644
index 000000000000..485efaba059d
--- /dev/null
+++ b/drivers/gpu/nova-core/gpu/channel.rs
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! Channel ID allocation.
+
+use core::{
+ num::NonZero,
+ ops::{
+ Deref,
+ Range, //
+ }, //
+};
+
+use kernel::{
+ id_pool::IdPool,
+ prelude::*,
+ ptr::Alignment,
+ sync::{
+ new_mutex,
+ Mutex, //
+ }, //
+};
+
+/// Pool for tracking reservations of channel IDs.
+#[pin_data]
+pub(crate) struct ChannelIdPool {
+ #[pin]
+ inner: Mutex<IdPool>,
+}
+
+impl ChannelIdPool {
+ /// Creates a pool managing `num_chids` channel IDs.
+ pub(crate) fn new(num_chids: NonZero<usize>) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self {
+ inner <- new_mutex!(IdPool::with_capacity(num_chids.get(), GFP_KERNEL)?),
+ })
+ }
+
+ /// Reserves a contiguous area of `count` channel IDs starting at a multiple of `align`,
+ /// returning a guard that releases the area on drop.
+ pub(crate) fn reserve_ids(
+ &self,
+ count: NonZero<usize>,
+ align: Alignment,
+ ) -> Result<ChannelIdReservation<'_>> {
+ let mut ids = self.inner.lock();
+ let range = ids.reserve_ids(0, count, align).ok_or(ENOSPC)?;
+ Ok(ChannelIdReservation { pool: self, range })
+ }
+}
+
+/// A reserved contiguous area of channel IDs.
+///
+/// Releases the whole area back to its [`ChannelIdPool`] when dropped. Releasing locks a
+/// sleeping [`Mutex`], so the area must be dropped in a context that is allowed to sleep.
+#[must_use = "the channel ID reservation is released immediately when unused"]
+pub(crate) struct ChannelIdReservation<'a> {
+ pool: &'a ChannelIdPool,
+ range: Range<usize>,
+}
+
+impl Drop for ChannelIdReservation<'_> {
+ fn drop(&mut self) {
+ self.pool.inner.lock().release_ids(&self.range);
+ }
+}
+
+impl Deref for ChannelIdReservation<'_> {
+ type Target = Range<usize>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.range
+ }
+}
+
+#[kunit_tests(nova_core_channel)]
+mod tests {
+ use super::*;
+ use kernel::sizes::SizeConstants;
+
+ #[test]
+ fn chid_reservation() -> Result {
+ let pool = KBox::pin_init(ChannelIdPool::new(cv!(2048)), GFP_KERNEL)?;
+
+ let first = pool.reserve_ids(cv!(48), Alignment::SZ_1)?;
+ assert_eq!(0, first.start);
+ assert_eq!(48, first.len());
+ assert_eq!(48, first.end);
+
+ let second = pool.reserve_ids(cv!(48), Alignment::SZ_1)?;
+ assert!(first.end <= second.start || second.end <= first.start);
+
+ let first_start = first.start;
+ drop(first);
+ assert_eq!(
+ first_start,
+ pool.reserve_ids(cv!(48), Alignment::SZ_1)?.start
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn chid_reservation_drop() -> Result {
+ let pool = KBox::pin_init(ChannelIdPool::new(cv!(8)), GFP_KERNEL)?;
+
+ let a = pool.reserve_ids(cv!(3), Alignment::SZ_1)?;
+ let b = pool.reserve_ids(cv!(3), Alignment::SZ_1)?;
+ let c = pool.reserve_ids(cv!(2), Alignment::SZ_1)?;
+ assert_eq!(0, a.start);
+ assert_eq!(3, b.start);
+ assert_eq!(6, c.start);
+
+ drop(b);
+
+ // Only have space for 3 IDs right now.
+ assert_eq!(
+ Err(ENOSPC),
+ pool.reserve_ids(cv!(4), Alignment::SZ_1).map(|_| ())
+ );
+ let b = pool.reserve_ids(cv!(3), Alignment::SZ_1)?;
+ assert_eq!(3, b.start);
+
+ drop(a);
+ drop(c);
+ drop(b);
+
+ // Everything was dropped so the pool should be empty.
+ assert_eq!(0, pool.reserve_ids(cv!(8), Alignment::SZ_1)?.start);
+ Ok(())
+ }
+
+ #[test]
+ fn chid_bounded_by_num_chids() -> Result {
+ let pool = KBox::pin_init(ChannelIdPool::new(cv!(4)), GFP_KERNEL)?;
+
+ {
+ let a = pool.reserve_ids(cv!(1), Alignment::SZ_1)?;
+ let b = pool.reserve_ids(cv!(1), Alignment::SZ_1)?;
+ let c = pool.reserve_ids(cv!(1), Alignment::SZ_1)?;
+ let d = pool.reserve_ids(cv!(1), Alignment::SZ_1)?;
+ assert_eq!(0, a.start);
+ assert_eq!(1, b.start);
+ assert_eq!(2, c.start);
+ assert_eq!(3, d.start);
+ assert_eq!(
+ Err(ENOSPC),
+ pool.reserve_ids(cv!(1), Alignment::SZ_1).map(|_| ())
+ );
+ }
+
+ assert_eq!(0, pool.reserve_ids(cv!(4), Alignment::SZ_1)?.start);
+ assert_eq!(
+ Err(ENOSPC),
+ pool.reserve_ids(cv!(5), Alignment::SZ_1).map(|_| ())
+ );
+
+ let head = pool.reserve_ids(cv!(3), Alignment::SZ_1)?;
+ assert_eq!(0, head.start);
+ assert_eq!(
+ Err(ENOSPC),
+ pool.reserve_ids(cv!(2), Alignment::SZ_1).map(|_| ())
+ );
+ assert_eq!(3, pool.reserve_ids(cv!(1), Alignment::SZ_1)?.start);
+ Ok(())
+ }
+
+ #[test]
+ fn chid_reservation_aligned() -> Result {
+ let pool = KBox::pin_init(ChannelIdPool::new(cv!(16)), GFP_KERNEL)?;
+
+ // Alloc 0 so the first fit for the next area is unaligned.
+ let pad = pool.reserve_ids(cv!(1), Alignment::SZ_1)?;
+ assert_eq!(0, pad.start);
+
+ let a = pool.reserve_ids(cv!(4), Alignment::SZ_4)?;
+ assert_eq!(4, a.start);
+
+ // The area skipped over by the aligned allocation should still be available.
+ let b = pool.reserve_ids(cv!(1), Alignment::SZ_1)?;
+ assert_eq!(1, b.start);
+
+ let c = pool.reserve_ids(cv!(8), Alignment::SZ_8)?;
+ assert_eq!(8, c.start);
+
+ // Only 2 IDs left.
+ assert_eq!(
+ Err(ENOSPC),
+ pool.reserve_ids(cv!(4), Alignment::SZ_4).map(|_| ())
+ );
+ assert_eq!(
+ Err(ENOSPC),
+ pool.reserve_ids(cv!(1), Alignment::SZ_32).map(|_| ())
+ );
+
+ assert_eq!(2, pool.reserve_ids(cv!(2), Alignment::SZ_1)?.start);
+ Ok(())
+ }
+}
--
2.56.0
^ permalink raw reply [flat|nested] 3+ messages in thread