On Mon Mar 9, 2026 at 10:53 PM JST, Joel Fernandes wrote:
> Add safe Rust abstractions over the Linux kernel's GPU buddy
> allocator for physical memory management. The GPU buddy allocator
> implements a binary buddy system useful for GPU physical memory
> allocation. nova-core will use it for physical memory allocation.
>
> Christian Koenig mentioned he'd like to step down from reviewer role for
> GPU buddy so updated accordingly. Arun/Matthew agree on the modified entry.
>
> Cc: Nikola Djukic <[email protected]>
> Signed-off-by: Joel Fernandes <[email protected]>

This is getting close IMHO. But `make rustdoc` fails on the examples,
and there are still clippy warnings - please make sure to address them.

A few more comments below.

<snip>
> diff --git a/rust/kernel/gpu/buddy.rs b/rust/kernel/gpu/buddy.rs
> new file mode 100644
> index 000000000000..9027c9a7778f
> --- /dev/null
> +++ b/rust/kernel/gpu/buddy.rs
> @@ -0,0 +1,611 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! GPU buddy allocator bindings.
> +//!
> +//! C header: 
> [`include/linux/gpu_buddy.h`](srctree/include/linux/gpu_buddy.h)
> +//!
> +//! This module provides Rust abstractions over the Linux kernel's GPU buddy
> +//! allocator, which implements a binary buddy memory allocator.
> +//!
> +//! The buddy allocator manages a contiguous address space and allocates 
> blocks
> +//! in power-of-two sizes, useful for GPU physical memory management.
> +//!
> +//! # Examples
> +//!
> +//! Create a buddy allocator and perform a basic range allocation:
> +//!
> +//! ```
> +//! use kernel::{
> +//!     gpu::buddy::{GpuBuddy, GpuBuddyAllocMode, GpuBuddyAllocFlags, 
> GpuBuddyParams},

nit: should also use kernel formatting style.

> +//!     prelude::*,
> +//!     ptr::Alignment,
> +//!     sizes::*, //
> +//! };
> +//!
> +//! // Create a 1GB buddy allocator with 4KB minimum chunk size.
> +//! let buddy = GpuBuddy::new(GpuBuddyParams {
> +//!     base_offset: 0,
> +//!     physical_memory_size: SZ_1G as u64,
> +//!     chunk_size: SZ_4K,

`chunk_size` is an interesting case. The C API uses a `u64`, but I think
we can reasonably consider that we won't ever need chunks larger than
4GB (or can we :O). I'm actually ok with using a `usize` for this one.

One of the first things the C code does is throwing an error if it is
not a power of 2, so maybe we can even request an `Alignment`?

I'm a bit torn as to whether we should use a `u64` to conform with the C
API, but doing so would mean we cannot use an `Alignment`...

> +//! })?;
> +//!
> +//! assert_eq!(buddy.size(), SZ_1G as u64);
> +//! assert_eq!(buddy.chunk_size(), SZ_4K);
> +//! let initial_free = buddy.free_memory();
> +//!
> +//! // Allocate 16MB, results in a single 16MB block at offset 0.
> +//! let allocated = KBox::pin_init(
> +//!     buddy.alloc_blocks(
> +//!         GpuBuddyAllocMode::Range { start: 0, end: 0 },

This zero-sized range looks confusing for the example. I understand the
C API allows this, but I don't think we should. Is there a difference
with just using `GpuBuddyAllocMode::Simple`? If not, let's switch to
that, and reject zero-sized ranges in the same spirit as we don't allow
invalid flag combinations.

> +//!         SZ_16M,
> +//!         Alignment::new::<SZ_16M>(),
> +//!         GpuBuddyAllocFlags::default(),
> +//!     ),
> +//!     GFP_KERNEL,
> +//! )?;
> +//! assert_eq!(buddy.free_memory(), initial_free - SZ_16M as u64);
> +//!
> +//! let block = allocated.iter().next().expect("expected one block");
> +//! assert_eq!(block.offset(), 0);
> +//! assert_eq!(block.order(), 12); // 2^12 pages = 16MB
> +//! assert_eq!(block.size(), SZ_16M);

Here we should also check that there is not a second block.

> +//!
> +//! // Dropping the allocation returns the memory to the buddy allocator.

s/memory/range - technically we are not returning physical memory.

> +//! drop(allocated);
> +//! assert_eq!(buddy.free_memory(), initial_free);
> +//! # Ok::<(), Error>(())
> +//! ```
> +//!
> +//! Top-down allocation allocates from the highest addresses:
> +//!
> +//! ```
> +//! # use kernel::{
> +//! #     gpu::buddy::{GpuBuddy, GpuBuddyAllocMode, GpuBuddyAllocFlags, 
> GpuBuddyParams},
> +//! #     prelude::*,
> +//! #     ptr::Alignment,
> +//! #     sizes::*, //

`make rustdoc` fails to build:

error[E0433]: failed to resolve: use of undeclared type `GpuBuddyAllocFlags`
    --> rust/doctests_kernel_generated.rs:6182:9
     |
6182 |         GpuBuddyAllocFlags::default(),
     |         ^^^^^^^^^^^^^^^^^^ use of undeclared type `GpuBuddyAllocFlags`
     |
help: an enum with a similar name exists
     |
6182 -         GpuBuddyAllocFlags::default(),
6182 +         GpuBuddyAllocFlag::default(),
     |
help: consider importing this struct
     |
   3 + use kernel::gpu::buddy::GpuBuddyAllocFlags;
     |

error[E0433]: failed to resolve: use of undeclared type `GpuBuddyAllocFlags`
    --> rust/doctests_kernel_generated.rs:6195:9
     |
6195 |         GpuBuddyAllocFlags::default(),
     |         ^^^^^^^^^^^^^^^^^^ use of undeclared type `GpuBuddyAllocFlags`
     |
help: an enum with a similar name exists
     |
6195 -         GpuBuddyAllocFlags::default(),
6195 +         GpuBuddyAllocFlag::default(),
     |
help: consider importing this struct
     |
   3 + use kernel::gpu::buddy::GpuBuddyAllocFlags;

> +//! # };
> +//! # let buddy = GpuBuddy::new(GpuBuddyParams {
> +//! #     base_offset: 0,
> +//! #     physical_memory_size: SZ_1G as u64,
> +//! #     chunk_size: SZ_4K,
> +//! # })?;
> +//! # let initial_free = buddy.free_memory();
> +//! let topdown = KBox::pin_init(
> +//!     buddy.alloc_blocks(
> +//!         GpuBuddyAllocMode::TopDown,
> +//!         SZ_16M,
> +//!         Alignment::new::<SZ_16M>(),
> +//!         GpuBuddyAllocFlags::default(),
> +//!     ),
> +//!     GFP_KERNEL,
> +//! )?;
> +//! assert_eq!(buddy.free_memory(), initial_free - SZ_16M as u64);
> +//!
> +//! let block = topdown.iter().next().expect("expected one block");
> +//! assert_eq!(block.offset(), (SZ_1G - SZ_16M) as u64);
> +//! assert_eq!(block.order(), 12);
> +//! assert_eq!(block.size(), SZ_16M);
> +//!
> +//! // Dropping the allocation returns the memory to the buddy allocator.
> +//! drop(topdown);
> +//! assert_eq!(buddy.free_memory(), initial_free);
> +//! # Ok::<(), Error>(())
> +//! ```
> +//!
> +//! Non-contiguous allocation can fill fragmented memory by returning 
> multiple
> +//! blocks:
> +//!
> +//! ```
> +//! # use kernel::{
> +//! #     gpu::buddy::{
> +//! #         GpuBuddy, GpuBuddyAllocFlags, GpuBuddyAllocMode, 
> GpuBuddyParams,
> +//! #     },
> +//! #     prelude::*,
> +//! #     ptr::Alignment,
> +//! #     sizes::*, //
> +//! # };
> +//! # let buddy = GpuBuddy::new(GpuBuddyParams {
> +//! #     base_offset: 0,
> +//! #     physical_memory_size: SZ_1G as u64,
> +//! #     chunk_size: SZ_4K,
> +//! # })?;
> +//! # let initial_free = buddy.free_memory();
> +//! // Create fragmentation by allocating 4MB blocks at [0,4M) and [8M,12M).
> +//! let frag1 = KBox::pin_init(
> +//!     buddy.alloc_blocks(
> +//!         GpuBuddyAllocMode::Range { start: 0, end: SZ_4M as u64 },
> +//!         SZ_4M,
> +//!         Alignment::new::<SZ_4M>(),
> +//!         GpuBuddyAllocFlags::default(),
> +//!     ),
> +//!     GFP_KERNEL,
> +//! )?;
> +//! assert_eq!(buddy.free_memory(), initial_free - SZ_4M as u64);
> +//!
> +//! let frag2 = KBox::pin_init(
> +//!     buddy.alloc_blocks(
> +//!         GpuBuddyAllocMode::Range {
> +//!             start: SZ_8M as u64,
> +//!             end: (SZ_8M + SZ_4M) as u64,
> +//!         },
> +//!         SZ_4M,
> +//!         Alignment::new::<SZ_4M>(),
> +//!         GpuBuddyAllocFlags::default(),
> +//!     ),
> +//!     GFP_KERNEL,
> +//! )?;
> +//! assert_eq!(buddy.free_memory(), initial_free - SZ_8M as u64);
> +//!
> +//! // Allocate 8MB, this returns 2 blocks from the holes.
> +//! let fragmented = KBox::pin_init(
> +//!     buddy.alloc_blocks(
> +//!         GpuBuddyAllocMode::Range { start: 0, end: SZ_16M as u64 },
> +//!         SZ_8M,
> +//!         Alignment::new::<SZ_4M>(),
> +//!         GpuBuddyAllocFlags::default(),
> +//!     ),
> +//!     GFP_KERNEL,
> +//! )?;
> +//! assert_eq!(buddy.free_memory(), initial_free - SZ_16M as u64);
> +//!
> +//! let (mut count, mut total) = (0u32, 0usize);
> +//! for block in fragmented.iter() {
> +//!     assert_eq!(block.size(), SZ_4M);
> +//!     total += block.size();
> +//!     count += 1;
> +//! }

Note that we can avoid mutable variables with this:

//! let total_size: usize = fragmented.iter()
//!      .inspect(|block| assert_eq!(block.size(), SZ_4M))
//!      .map(|block| block.size())
//!      .sum();
//! assert_eq!(total_size, SZ_8M);
//! assert_eq!(fragmented.iter().count(), 2);

But your call as to whether this is an improvement.

> +//! assert_eq!(total, SZ_8M);
> +//! assert_eq!(count, 2);
> +//! # Ok::<(), Error>(())
> +//! ```
> +//!
> +//! Contiguous allocation fails when only fragmented space is available:
> +//!
> +//! ```
> +//! # use kernel::{
> +//! #     gpu::buddy::{
> +//! #         GpuBuddy, GpuBuddyAllocFlag, GpuBuddyAllocMode, GpuBuddyParams,
> +//! #     },
> +//! #     prelude::*,
> +//! #     ptr::Alignment,
> +//! #     sizes::*, //
> +//! # };
> +//! // Create a small 16MB buddy allocator with fragmented memory.
> +//! let small = GpuBuddy::new(GpuBuddyParams {
> +//!     base_offset: 0,
> +//!     physical_memory_size: SZ_16M as u64,
> +//!     chunk_size: SZ_4K,
> +//! })?;
> +//!
> +//! let _hole1 = KBox::pin_init(
> +//!     small.alloc_blocks(
> +//!         GpuBuddyAllocMode::Range { start: 0, end: SZ_4M as u64 },
> +//!         SZ_4M,
> +//!         Alignment::new::<SZ_4M>(),
> +//!         GpuBuddyAllocFlags::default(),
> +//!     ),
> +//!     GFP_KERNEL,
> +//! )?;
> +//!
> +//! let _hole2 = KBox::pin_init(
> +//!     small.alloc_blocks(
> +//!         GpuBuddyAllocMode::Range {
> +//!             start: SZ_8M as u64,
> +//!             end: (SZ_8M + SZ_4M) as u64,
> +//!         },
> +//!         SZ_4M,
> +//!         Alignment::new::<SZ_4M>(),
> +//!         GpuBuddyAllocFlags::default(),
> +//!     ),
> +//!     GFP_KERNEL,
> +//! )?;
> +//!
> +//! // 8MB contiguous should fail, only two non-contiguous 4MB holes exist.
> +//! let result = KBox::pin_init(
> +//!     small.alloc_blocks(
> +//!         GpuBuddyAllocMode::Simple,
> +//!         SZ_8M,
> +//!         Alignment::new::<SZ_4M>(),
> +//!         GpuBuddyAllocFlag::Contiguous.into(),
> +//!     ),
> +//!     GFP_KERNEL,
> +//! );
> +//! assert!(result.is_err());
> +//! # Ok::<(), Error>(())
> +//! ```

I think these last two examples are great both as documentation and
tests - the doc has also become much more readable!

> +
> +use crate::{
> +    bindings,
> +    clist_create,
> +    error::to_result,
> +    interop::list::CListHead,
> +    new_mutex,
> +    prelude::*,
> +    ptr::Alignment,
> +    sync::{
> +        lock::mutex::MutexGuard,
> +        Arc,
> +        Mutex, //
> +    },
> +    types::Opaque, //
> +};
> +
> +/// Allocation mode for the GPU buddy allocator.
> +///
> +/// The mode determines the primary allocation strategy. Modes are mutually
> +/// exclusive: an allocation is either simple, range-constrained, or 
> top-down.
> +///
> +/// Orthogonal modifier flags (e.g., contiguous, clear) are specified 
> separately
> +/// via [`GpuBuddyAllocFlags`].
> +#[derive(Copy, Clone, Debug, PartialEq, Eq)]
> +pub enum GpuBuddyAllocMode {
> +    /// Simple allocation without constraints.
> +    Simple,
> +    /// Range-based allocation between `start` and `end` addresses.
> +    Range {
> +        /// Start of the allocation range.
> +        start: u64,
> +        /// End of the allocation range.
> +        end: u64,
> +    },
> +    /// Allocate from top of address space downward.
> +    TopDown,
> +}
> +
> +impl GpuBuddyAllocMode {
> +    // Returns the C flags corresponding to the allocation mode.
> +    fn into_flags(self) -> usize {
> +        match self {
> +            Self::Simple => 0,
> +            Self::Range { .. } => bindings::GPU_BUDDY_RANGE_ALLOCATION as 
> usize,
> +            Self::TopDown => bindings::GPU_BUDDY_TOPDOWN_ALLOCATION as usize,
> +        }
> +    }
> +
> +    // Extracts the range start/end, defaulting to (0, 0) for non-range 
> modes.
> +    fn range(self) -> (u64, u64) {
> +        match self {
> +            Self::Range { start, end } => (start, end),
> +            _ => (0, 0),
> +        }
> +    }
> +}
> +
> +crate::impl_flags!(
> +    /// Modifier flags for GPU buddy allocation.
> +    ///
> +    /// These flags can be combined with any [`GpuBuddyAllocMode`] to control
> +    /// additional allocation behavior.
> +    #[derive(Clone, Copy, Default, PartialEq, Eq)]
> +    pub struct GpuBuddyAllocFlags(u32);
> +
> +    /// Individual modifier flag for GPU buddy allocation.
> +    #[derive(Clone, Copy, PartialEq, Eq)]
> +    pub enum GpuBuddyAllocFlag {
> +        /// Allocate physically contiguous blocks.
> +        Contiguous = bindings::GPU_BUDDY_CONTIGUOUS_ALLOCATION as u32,
> +
> +        /// Request allocation from cleared (zeroed) memory.
> +        Clear = bindings::GPU_BUDDY_CLEAR_ALLOCATION as u32,
> +
> +        /// Disable trimming of partially used blocks.
> +        TrimDisable = bindings::GPU_BUDDY_TRIM_DISABLE as u32,
> +    }
> +);
> +
> +/// Parameters for creating a GPU buddy allocator.
> +pub struct GpuBuddyParams {
> +    /// Base offset (in bytes) where the managed memory region starts.
> +    /// Allocations will be offset by this value.
> +    pub base_offset: u64,
> +    /// Total physical memory size (in bytes) managed by the allocator.
> +    pub physical_memory_size: u64,
> +    /// Minimum allocation unit / chunk size (in bytes), must be >= 4KB.
> +    pub chunk_size: usize,

As I mentioned above, let's consider if we can store this as an `Alignment`.

> +}
> +
> +/// Inner structure holding the actual buddy allocator.
> +///
> +/// # Synchronization
> +///
> +/// The C `gpu_buddy` API requires synchronization (see 
> `include/linux/gpu_buddy.h`).
> +/// [`GpuBuddyGuard`] ensures that the lock is held for all
> +/// allocator and free operations, preventing races between concurrent 
> allocations
> +/// and the freeing that occurs when [`AllocatedBlocks`] is dropped.

`GpuBuddyGuard` is now private, so we should avoid mentioning it in the
public documentation as it will just confuse users.

Users won't care about such implementation details - we can just say
that internal locking ensures all operations are properly synchronized.

> +///
> +/// # Invariants
> +///
> +/// The inner [`Opaque`] contains an initialized buddy allocator.
> +#[pin_data(PinnedDrop)]
> +struct GpuBuddyInner {
> +    #[pin]
> +    inner: Opaque<bindings::gpu_buddy>,
> +
> +    // TODO: Replace `Mutex<()>` with `Mutex<Opaque<..>>` once `Mutex::new()`
> +    // accepts `impl PinInit<T>`.
> +    #[pin]
> +    lock: Mutex<()>,
> +    /// Cached creation parameters (do not change after init).
> +    params: GpuBuddyParams,
> +}
> +
> +impl GpuBuddyInner {
> +    /// Create a pin-initializer for the buddy allocator.
> +    fn new(params: GpuBuddyParams) -> impl PinInit<Self, Error> {
> +        let size = params.physical_memory_size;
> +        let chunk_size = params.chunk_size;
> +
> +        // INVARIANT: `gpu_buddy_init` returns 0 on success, at which point 
> the
> +        // `gpu_buddy` structure is initialized and ready for use with all
> +        // `gpu_buddy_*` APIs. `try_pin_init!` only completes if all fields 
> succeed,
> +        // so the invariant holds when construction finishes.
> +        try_pin_init!(Self {
> +            inner <- Opaque::try_ffi_init(|ptr| {
> +                // SAFETY: `ptr` points to valid uninitialized memory from 
> the pin-init
> +                // infrastructure. `gpu_buddy_init` will initialize the 
> structure.
> +                to_result(unsafe { bindings::gpu_buddy_init(ptr, size, 
> chunk_size as u64) })
> +            }),
> +            lock <- new_mutex!(()),
> +            params,
> +        })
> +    }
> +
> +    /// Lock the mutex and return a guard for accessing the allocator.
> +    fn lock(&self) -> GpuBuddyGuard<'_> {
> +        GpuBuddyGuard {
> +            inner: self,
> +            _guard: self.lock.lock(),
> +        }
> +    }
> +}
> +
> +#[pinned_drop]
> +impl PinnedDrop for GpuBuddyInner {
> +    fn drop(self: Pin<&mut Self>) {
> +        let guard = self.lock();
> +
> +        // SAFETY: Per the type invariant, `inner` contains an initialized
> +        // allocator. `guard` provides exclusive access.
> +        unsafe {
> +            bindings::gpu_buddy_fini(guard.as_raw());
> +        }
> +    }
> +}
> +
> +// SAFETY: GpuBuddyInner can be sent between threads.
> +unsafe impl Send for GpuBuddyInner {}
> +
> +// SAFETY: `GpuBuddyInner` is `Sync` because `GpuBuddyInner::lock`
> +// serializes all access to the C allocator, preventing data races.
> +unsafe impl Sync for GpuBuddyInner {}
> +
> +// Guard that proves the lock is held, enabling access to the allocator.
> +// The `_guard` holds the lock for the duration of this guard's lifetime.
> +struct GpuBuddyGuard<'a> {
> +    inner: &'a GpuBuddyInner,
> +    _guard: MutexGuard<'a, ()>,
> +}
> +
> +impl GpuBuddyGuard<'_> {
> +    /// Get a raw pointer to the underlying C `gpu_buddy` structure.
> +    fn as_raw(&self) -> *mut bindings::gpu_buddy {
> +        self.inner.inner.get()
> +    }
> +}
> +
> +/// GPU buddy allocator instance.
> +///
> +/// This structure wraps the C `gpu_buddy` allocator using reference 
> counting.
> +/// The allocator is automatically cleaned up when all references are 
> dropped.
> +///
> +/// Refer to the module-level documentation for usage examples.
> +pub struct GpuBuddy(Arc<GpuBuddyInner>);
> +
> +impl GpuBuddy {
> +    /// Create a new buddy allocator.
> +    ///
> +    /// Creates a buddy allocator that manages a contiguous address space of 
> the given
> +    /// size, with the specified minimum allocation unit (chunk_size must be 
> at least 4KB).
> +    pub fn new(params: GpuBuddyParams) -> Result<Self> {
> +        Ok(Self(Arc::pin_init(GpuBuddyInner::new(params), GFP_KERNEL)?))

Can be written as:

        Arc::pin_init(GpuBuddyInner::new(params), GFP_KERNEL).map(Self)

I prefer this form as it avoids the `?` and re-wrapping into `Ok` for
something that is already a `Result`.

>
> +    }
> +
> +    /// Get the base offset for allocations.
> +    pub fn base_offset(&self) -> u64 {
> +        self.0.params.base_offset
> +    }
> +
> +    /// Get the chunk size (minimum allocation unit).
> +    pub fn chunk_size(&self) -> usize {

If my suggestion above works this could return an `Alignment`.

> +        self.0.params.chunk_size
> +    }
> +
> +    /// Get the total managed size.
> +    pub fn size(&self) -> u64 {
> +        self.0.params.physical_memory_size
> +    }
> +
> +    /// Get the available (free) memory in bytes.
> +    pub fn free_memory(&self) -> u64 {
> +        let guard = self.0.lock();
> +
> +        // SAFETY: Per the type invariant, `inner` contains an initialized 
> allocator.
> +        // `guard` provides exclusive access.
> +        unsafe { (*guard.as_raw()).avail }
> +    }
> +
> +    /// Allocate blocks from the buddy allocator.
> +    ///
> +    /// Returns a pin-initializer for [`AllocatedBlocks`].
> +    ///
> +    /// Takes `&self` instead of `&mut self` because the internal [`Mutex`] 
> provides
> +    /// synchronization - no external `&mut` exclusivity needed.

This is another implementation detail - the fact this takes `&self` and
is not `unsafe` is already proof that synchronization is taken care of.

> +    pub fn alloc_blocks(
> +        &self,
> +        mode: GpuBuddyAllocMode,
> +        size: usize,

For this parameter I am pretty sure we want to conform to the C API and
use a `u64` - there is no benefit in not doing so, and buffers larger
than 4GB *are* a reality nowadays, (maybe not for graphics, but this
will also be used in compute scenarios).

> +        min_block_size: Alignment,
> +        flags: GpuBuddyAllocFlags,
> +    ) -> impl PinInit<AllocatedBlocks, Error> {
> +        let buddy_arc = Arc::clone(&self.0);
> +        let (start, end) = mode.range();
> +        let mode_flags = mode.into_flags();
> +        let modifier_flags = u32::from(flags) as usize;
> +
> +        // Create pin-initializer that initializes list and allocates blocks.
> +        try_pin_init!(AllocatedBlocks {
> +            buddy: buddy_arc,
> +            list <- CListHead::new(),
> +            _: {
> +                // Lock while allocating to serialize with concurrent frees.
> +                let guard = buddy.lock();
> +
> +                // SAFETY: Per the type invariant, `inner` contains an 
> initialized
> +                // allocator. `guard` provides exclusive access.
> +                to_result(unsafe {
> +                    bindings::gpu_buddy_alloc_blocks(
> +                        guard.as_raw(),
> +                        start,
> +                        end,
> +                        size as u64,
> +                        min_block_size.as_usize() as u64,
> +                        list.as_raw(),
> +                        mode_flags | modifier_flags,
> +                    )
> +                })?
> +            }
> +        })
> +    }
> +}
> +
> +/// Allocated blocks from the buddy allocator with automatic cleanup.
> +///
> +/// This structure owns a list of allocated blocks and ensures they are
> +/// automatically freed when dropped. Use `iter()` to iterate over all
> +/// allocated blocks.
> +///
> +/// # Invariants
> +///
> +/// - `list` is an initialized, valid list head containing allocated blocks.
> +#[pin_data(PinnedDrop)]
> +pub struct AllocatedBlocks {
> +    #[pin]
> +    list: CListHead,
> +    buddy: Arc<GpuBuddyInner>,
> +}
> +
> +impl AllocatedBlocks {
> +    /// Check if the block list is empty.
> +    pub fn is_empty(&self) -> bool {
> +        // An empty list head points to itself.
> +        !self.list.is_linked()
> +    }
> +
> +    /// Iterate over allocated blocks.
> +    ///
> +    /// Returns an iterator yielding [`AllocatedBlock`] values. Each 
> [`AllocatedBlock`]
> +    /// borrows `self` and is only valid for the duration of that borrow.
> +    pub fn iter(&self) -> impl Iterator<Item = AllocatedBlock<'_>> + '_ {
> +        // SAFETY:
> +        // - Per the type invariant, `list` is an initialized sentinel 
> `list_head`
> +        //   and is not concurrently modified (we hold a `&self` borrow).
> +        // - The list contains `gpu_buddy_block` items linked via
> +        //   `__bindgen_anon_1.link`.
> +        // - `Block` is `#[repr(transparent)]` over `gpu_buddy_block`.
> +        let clist = clist_create!(unsafe {
> +            self.list.as_raw(),
> +            Block,
> +            bindings::gpu_buddy_block,
> +            __bindgen_anon_1.link
> +        });
> +
> +        clist
> +            .iter()
> +            .map(|this| AllocatedBlock { this, blocks: self })
> +    }
> +}
> +
> +#[pinned_drop]
> +impl PinnedDrop for AllocatedBlocks {
> +    fn drop(self: Pin<&mut Self>) {
> +        let guard = self.buddy.lock();
> +
> +        // SAFETY:
> +        // - list is valid per the type's invariants.
> +        // - guard provides exclusive access to the allocator.
> +        unsafe {
> +            bindings::gpu_buddy_free_list(guard.as_raw(), 
> self.list.as_raw(), 0);
> +        }
> +    }
> +}
> +
> +/// A GPU buddy block.
> +///
> +/// Transparent wrapper over C `gpu_buddy_block` structure. This type is 
> returned
> +/// as references during iteration over [`AllocatedBlocks`].
> +///
> +/// # Invariants
> +///
> +/// The inner [`Opaque`] contains a valid, allocated `gpu_buddy_block`.
> +#[repr(transparent)]
> +struct Block(Opaque<bindings::gpu_buddy_block>);
> +
> +impl Block {
> +    /// Get a raw pointer to the underlying C block.
> +    fn as_raw(&self) -> *mut bindings::gpu_buddy_block {
> +        self.0.get()
> +    }
> +
> +    /// Get the block's raw offset in the buddy address space (without base 
> offset).
> +    fn offset(&self) -> u64 {
> +        // SAFETY: `self.as_raw()` is valid per the type's invariants.
> +        unsafe { bindings::gpu_buddy_block_offset(self.as_raw()) }
> +    }
> +
> +    /// Get the block order.
> +    fn order(&self) -> u32 {
> +        // SAFETY: `self.as_raw()` is valid per the type's invariants.
> +        unsafe { bindings::gpu_buddy_block_order(self.as_raw()) }
> +    }

Speaking of synchronization - I only had a quick look at the C API, but
are we sure these methods can all be called without holding the lock?

> +}
> +
> +// SAFETY: `Block` is a wrapper around `gpu_buddy_block` which can be
> +// sent across threads safely.
> +unsafe impl Send for Block {}
> +
> +// SAFETY: `Block` is only accessed through shared references after
> +// allocation, and thus safe to access concurrently across threads.
> +unsafe impl Sync for Block {}
> +
> +/// A buddy block paired with its owning [`AllocatedBlocks`] context.
> +///
> +/// Unlike a raw block, which only knows its offset within the buddy address
> +/// space, an [`AllocatedBlock`] also has access to the allocator's 
> `base_offset`
> +/// and `chunk_size`, enabling it to compute absolute offsets and byte sizes.
> +///
> +/// Returned by [`AllocatedBlocks::iter()`].
> +pub struct AllocatedBlock<'a> {
> +    this: &'a Block,
> +    blocks: &'a AllocatedBlocks,
> +}
> +
> +impl AllocatedBlock<'_> {
> +    /// Get the block's offset in the address space.
> +    ///
> +    /// Returns the absolute offset including the allocator's base offset.
> +    /// This is the actual address to use for accessing the allocated memory.
> +    pub fn offset(&self) -> u64 {
> +        self.blocks.buddy.params.base_offset + self.this.offset()
> +    }
> +
> +    /// Get the block order (size = chunk_size << order).
> +    pub fn order(&self) -> u32 {
> +        self.this.order()
> +    }
> +
> +    /// Get the block's size in bytes.
> +    pub fn size(&self) -> usize {
> +        self.blocks.buddy.params.chunk_size << self.this.order()
> +    }
> +}
> diff --git a/rust/kernel/gpu/mod.rs b/rust/kernel/gpu/mod.rs

Let's use `gpu.rs` as the file for this module.

> new file mode 100644
> index 000000000000..8f25e6367edc
> --- /dev/null
> +++ b/rust/kernel/gpu/mod.rs
> @@ -0,0 +1,5 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +//! GPU subsystem abstractions.
> +
> +pub mod buddy;

IMHO we should have a `#[cfg(CONFIG_GPU_BUDDY = "y")]` here for
defensiveness...

>
> diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
> index bb741f1e0dfd..63e3f656eb6c 100644
> --- a/rust/kernel/lib.rs
> +++ b/rust/kernel/lib.rs
> @@ -98,6 +98,8 @@
>  pub mod firmware;
>  pub mod fmt;
>  pub mod fs;
> +#[cfg(CONFIG_GPU_BUDDY = "y")]
> +pub mod gpu;

... because in the future I suspect the condition for enabling that
module will become broader. I think it's fine to keep it as-is for now
though.

Reply via email to