* [PATCH] gpu: nova-core: add ChannelIdPool
@ 2026-07-10 13:42 Eliot Courtney
2026-07-10 21:29 ` John Hubbard
0 siblings, 1 reply; 3+ messages in thread
From: Eliot Courtney @ 2026-07-10 13:42 UTC (permalink / raw)
To: Danilo Krummrich, Alexandre Courbot, Alice Ryhl, David Airlie,
Simona Vetter, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein, Onur Özkan
Cc: Greg KH, Burak Emir, Yury Norov, John Hubbard, Alistair Popple,
Timur Tabi, Zhi Wang, linux-kernel, nova-gpu, dri-devel,
rust-for-linux, Eliot Courtney
Add `ChannelIdPool` which does allocation of channel IDs on top of a
maple tree. This is necessary for allotting ranges of channel IDs to be
used in e.g. vGPU and also later for allocating single channel IDs for
the host.
Pre-blackwell needs to allocate channel IDs with a particular alignment.
For this case, loop on `MapleTreeAlloc::alloc_range` while aligning up.
For unaligned cases, this always terminates on the first iteration.
Use a Mutex to ensure that the multi step insert-check-erase can't race.
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
Add tracking of channel ID allocations, needed for allotting ranges of
channel IDs for vGPU guests and also (later) for regular host channel ID
reservation. Allocating channel IDs is not on a hot path (absolute peak
~100 per second, on average < 1 per second).
For pre-blackwell we need to be able to allocate a range of channel IDs
with a particular alignment. The approach I took here is to wrap
MapleTreeAlloc in a Mutex and loop on regular `alloc_range` lookups. An
alternative would be adding rust bindings for maple tree
`mas_empty_area` and doing a try+restart kind of thing.
---
drivers/gpu/nova-core/gpu.rs | 2 +
drivers/gpu/nova-core/gpu/channel.rs | 174 +++++++++++++++++++++++++++++++++++
2 files changed, 176 insertions(+)
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 43c3f4f8df71..0c3b5de7d849 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -31,6 +31,8 @@
regs,
};
+#[cfg_attr(not(CONFIG_KUNIT = "y"), expect(dead_code))]
+mod channel;
mod hal;
macro_rules! define_chipset {
diff --git a/drivers/gpu/nova-core/gpu/channel.rs b/drivers/gpu/nova-core/gpu/channel.rs
new file mode 100644
index 000000000000..1f6299aa9f58
--- /dev/null
+++ b/drivers/gpu/nova-core/gpu/channel.rs
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0
+// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+
+//! Channel related code.
+
+use core::{
+ num::NonZero,
+ ops::{
+ Deref,
+ Range, //
+ }, //
+};
+
+use kernel::{
+ maple_tree::MapleTreeAlloc,
+ prelude::*,
+ ptr::{
+ Alignable,
+ Alignment, //
+ },
+ sync::{
+ new_mutex,
+ Mutex, //
+ }, //
+};
+
+/// Pool for tracking reservations of channel IDs.
+#[pin_data]
+pub(crate) struct ChannelIdPool {
+ #[pin]
+ inner: Mutex<MapleTreeAlloc<()>>,
+ num_chids: usize,
+}
+
+impl ChannelIdPool {
+ /// Creates a pool managing `num_chids` channel IDs.
+ pub(crate) fn new(num_chids: usize) -> impl PinInit<Self> {
+ pin_init!(Self {
+ inner <- new_mutex!(MapleTreeAlloc::new()),
+ num_chids,
+ })
+ }
+
+ /// Reserves a contiguous area of `count` channel IDs starting at a multiple of `align`.
+ /// Returns a guard that releases the area on drop.
+ pub(crate) fn alloc_area(
+ &self,
+ count: NonZero<usize>,
+ align: Alignment,
+ ) -> Result<ChannelIdArea<'_>> {
+ // Hold the lock over the whole loop so the release and retry below cannot race with another
+ // allocation.
+ let tree = self.inner.lock();
+ let mut base = 0;
+ while base < self.num_chids {
+ let start = tree.alloc_range(count.get(), (), base..self.num_chids, GFP_KERNEL)?;
+ let aligned = start.align_up(align);
+
+ if aligned == Some(start) {
+ return Ok(ChannelIdArea {
+ pool: self,
+ range: start..start + count.get(),
+ });
+ }
+
+ // Release the unaligned area and retry from the next aligned channel ID.
+ tree.erase(start);
+ base = aligned.ok_or(EBUSY)?;
+ }
+ Err(EBUSY)
+ }
+}
+
+/// A reserved contiguous area of channel IDs.
+///
+/// Releases the whole area back to its [`ChannelIdPool`] when dropped. Releasing locks a sleeping
+/// [`Mutex`] and may allocate memory with `GFP_KERNEL`, so the area must be dropped in a context
+/// that is allowed to do so.
+#[must_use = "the channel ID area is released immediately when unused"]
+pub(crate) struct ChannelIdArea<'a> {
+ pool: &'a ChannelIdPool,
+ range: Range<usize>,
+}
+
+impl Drop for ChannelIdArea<'_> {
+ fn drop(&mut self) {
+ self.pool.inner.lock().erase(self.range.start);
+ }
+}
+
+impl Deref for ChannelIdArea<'_> {
+ type Target = Range<usize>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.range
+ }
+}
+
+#[kunit_tests(nova_core_channel)]
+mod tests {
+ use super::*;
+
+ const fn nz(count: usize) -> NonZero<usize> {
+ NonZero::new(count).unwrap()
+ }
+
+ #[test]
+ fn chid_area() -> Result {
+ let pool = KBox::pin_init(ChannelIdPool::new(2048), GFP_KERNEL)?;
+ let unaligned = Alignment::new::<1>();
+
+ let first = pool.alloc_area(nz(48), unaligned)?;
+ let second = pool.alloc_area(nz(48), unaligned)?;
+ assert_eq!(0, first.start);
+ assert_eq!(48, second.start);
+
+ drop(first);
+ let third = pool.alloc_area(nz(48), unaligned)?;
+ assert_eq!(0, third.start);
+ assert_eq!(96, pool.alloc_area(nz(48), unaligned)?.start);
+ Ok(())
+ }
+
+ #[test]
+ fn chid_bounded_by_num_chids() -> Result {
+ let pool = KBox::pin_init(ChannelIdPool::new(4), GFP_KERNEL)?;
+ let unaligned = Alignment::new::<1>();
+
+ assert_eq!(0, pool.alloc_area(nz(4), unaligned)?.start);
+
+ // An area larger than the pool is rejected by the maple tree itself.
+ assert_eq!(Some(EINVAL), pool.alloc_area(nz(5), unaligned).err());
+
+ let a = pool.alloc_area(nz(3), unaligned)?;
+ assert_eq!(0, a.start);
+ assert_eq!(Some(EBUSY), pool.alloc_area(nz(2), unaligned).err());
+
+ let b = pool.alloc_area(nz(1), unaligned)?;
+ assert_eq!(3, b.start);
+ assert_eq!(Some(EBUSY), pool.alloc_area(nz(1), unaligned).err());
+ Ok(())
+ }
+
+ #[test]
+ fn chid_area_aligned() -> Result {
+ let pool = KBox::pin_init(ChannelIdPool::new(16), GFP_KERNEL)?;
+ let unaligned = Alignment::new::<1>();
+ let align4 = Alignment::new::<4>();
+
+ // Alloc 0 so the first fit for the next area is unaligned.
+ let pad = pool.alloc_area(nz(1), unaligned)?;
+ assert_eq!(0, pad.start);
+
+ let a = pool.alloc_area(nz(4), align4)?;
+ assert_eq!(4, a.start);
+
+ // The area skipped over by the aligned allocation should still be available.
+ let b = pool.alloc_area(nz(1), unaligned)?;
+ assert_eq!(1, b.start);
+
+ let c = pool.alloc_area(nz(8), Alignment::new::<8>())?;
+ assert_eq!(8, c.start);
+
+ // Only 2 IDs left.
+ assert_eq!(Some(EBUSY), pool.alloc_area(nz(4), align4).err());
+ assert_eq!(
+ Some(EBUSY),
+ pool.alloc_area(nz(1), Alignment::new::<32>()).err()
+ );
+
+ assert_eq!(2, pool.alloc_area(nz(2), unaligned)?.start);
+ Ok(())
+ }
+}
---
base-commit: 78900738d981c97aad929e33620340d0d9bcfc82
change-id: 20260709-chid-maple-3d696554e0a3
Best regards,
--
Eliot Courtney <ecourtney@nvidia.com>
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH] gpu: nova-core: add ChannelIdPool
2026-07-10 13:42 [PATCH] gpu: nova-core: add ChannelIdPool Eliot Courtney
@ 2026-07-10 21:29 ` John Hubbard
2026-07-10 22:42 ` Yury Norov
0 siblings, 1 reply; 3+ messages in thread
From: John Hubbard @ 2026-07-10 21:29 UTC (permalink / raw)
To: Eliot Courtney, Danilo Krummrich, Alexandre Courbot, Alice Ryhl,
David Airlie, Simona Vetter, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein, Onur Özkan
Cc: Greg KH, Burak Emir, Yury Norov, Alistair Popple, Timur Tabi,
Zhi Wang, linux-kernel, nova-gpu, dri-devel, rust-for-linux
On 7/10/26 6:42 AM, Eliot Courtney wrote:
...
> +use kernel::{
> + maple_tree::MapleTreeAlloc,
Hi Eliot, Alice, all,
Eliot already laid out the maple costs (the alloc_range+erase loop
replicating find_next_zero_area, the extra mutex, allocation on
alloc and free), so I won't rehash them. What I can add is why the
alignment is a hard requirement, since that is what seems to make
the decision clearer to me at least.
Pre-Blackwell, USERD sits in BAR1 at page granularity, 8 channels to a
4K page (chid = page*8 + slot). Giving a VM its own channels means
giving it whole USERD pages, so a VM's chid range has to be 8-aligned
and a multiple of 8. Blackwell moves submit to a per-function doorbell
keyed by (runlist, chid), and the constraint goes away. So it's a
constraint we're stuck with pre-Blackwell, not one we can design away.
Given that, the bitmap version is hard to argue with. The aligned
allocation is the one call the API is built around, roughly:
let mut ids = self.inner.lock();
let area = ids.find_unused_area(0, count, align_mask)
.ok_or(ENOSPC)?;
// area.acquire() reserves and returns the range, drop clears it
One lock, no retry, and release is a bitmap_clear that can't fail. For
2048 IDs the backing store is a 256-byte array. find_next_zero_area()
is also the existing idiom for this (IOMMU, DMA, IRQ), so it answers
Greg's reuse-what-exists point as well.
I'd go with the bitmap id_pool.
thanks,
--
John Hubbard
^ permalink raw reply [flat|nested] 3+ messages in thread* Re: [PATCH] gpu: nova-core: add ChannelIdPool
2026-07-10 21:29 ` John Hubbard
@ 2026-07-10 22:42 ` Yury Norov
0 siblings, 0 replies; 3+ messages in thread
From: Yury Norov @ 2026-07-10 22:42 UTC (permalink / raw)
To: John Hubbard
Cc: Eliot Courtney, Danilo Krummrich, Alexandre Courbot, Alice Ryhl,
David Airlie, Simona Vetter, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein, Onur Özkan,
Greg KH, Burak Emir, Yury Norov, Alistair Popple, Timur Tabi,
Zhi Wang, linux-kernel, nova-gpu, dri-devel, rust-for-linux
On Fri, Jul 10, 2026 at 02:29:39PM -0700, John Hubbard wrote:
> On 7/10/26 6:42 AM, Eliot Courtney wrote:
> ...
> > +use kernel::{
> > + maple_tree::MapleTreeAlloc,
>
> Hi Eliot, Alice, all,
>
> Eliot already laid out the maple costs (the alloc_range+erase loop
> replicating find_next_zero_area, the extra mutex, allocation on
> alloc and free), so I won't rehash them. What I can add is why the
> alignment is a hard requirement, since that is what seems to make
> the decision clearer to me at least.
>
> Pre-Blackwell, USERD sits in BAR1 at page granularity, 8 channels to a
> 4K page (chid = page*8 + slot). Giving a VM its own channels means
> giving it whole USERD pages, so a VM's chid range has to be 8-aligned
> and a multiple of 8. Blackwell moves submit to a per-function doorbell
> keyed by (runlist, chid), and the constraint goes away. So it's a
> constraint we're stuck with pre-Blackwell, not one we can design away.
>
> Given that, the bitmap version is hard to argue with. The aligned
> allocation is the one call the API is built around, roughly:
>
> let mut ids = self.inner.lock();
> let area = ids.find_unused_area(0, count, align_mask)
> .ok_or(ENOSPC)?;
> // area.acquire() reserves and returns the range, drop clears it
>
> One lock, no retry, and release is a bitmap_clear that can't fail. For
> 2048 IDs the backing store is a 256-byte array. find_next_zero_area()
> is also the existing idiom for this (IOMMU, DMA, IRQ), so it answers
> Greg's reuse-what-exists point as well.
>
> I'd go with the bitmap id_pool.
Hi John,
I agree. Bitmaps are hard to beat, especially when it comes to just
a couple thousands of bits.
Eliot,
If you consider something more complicated, I'd suggest to estimate
memory and time consumption in a typical scenario, then weight the
cost of maintenance of one solution vs another.
If you're hard-limited with 2048 bits, bitmap is your natural choice.
> Pre-Blackwell, USERD sits in BAR1 at page granularity, 8 channels to a
> 4K page (chid = page*8 + slot). Giving a VM its own channels means
> giving it whole USERD pages, so a VM's chid range has to be 8-aligned
> and a multiple of 8.
If you always allocate 8-bit aligned area and never partially release IDs,
you can have just 256-bit bitmap, where each bit represents 8 IDs. For
example, bit #1 represents IDs 8..15.
There's no real code using it in the patch, unfortunately, so it's hard
to say if that's doable.
Thanks,
Yury
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-10 22:42 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-10 13:42 [PATCH] gpu: nova-core: add ChannelIdPool Eliot Courtney
2026-07-10 21:29 ` John Hubbard
2026-07-10 22:42 ` Yury Norov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox