// 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,//! GpuBuddyAllocFlags,//! GpuBuddyAllocMode,//! GpuBuddyParams, ////! },//! prelude::*,//! ptr::Alignment,//! sizes::*, ////! };//!//! // Create a 1GB buddy allocator with 4KB minimum chunk size.//! let buddy = GpuBuddy::new(GpuBuddyParams {//! base_offset: 0,//! size: SZ_1G as u64,//! chunk_size: Alignment::new::<SZ_4K>(),//! })?;//!//! assert_eq!(buddy.size(), SZ_1G as u64);//! assert_eq!(buddy.chunk_size(), Alignment::new::<SZ_4K>());//! let initial_free = buddy.avail();//!//! // Allocate 16MB. Block lands at the top of the address range.//! let allocated = KBox::pin_init(//! buddy.alloc_blocks(//! GpuBuddyAllocMode::Simple,//! SZ_16M as u64,//! Alignment::new::<SZ_16M>(),//! GpuBuddyAllocFlags::default(),//! ),//! GFP_KERNEL,//! )?;//! assert_eq!(buddy.avail(), initial_free - SZ_16M as u64);//!//! let block = allocated.iter().next().expect("expected one block");//! assert_eq!(block.offset(), (SZ_1G - SZ_16M) as u64);//! assert_eq!(block.order(), 12); // 2^12 pages = 16MB//! assert_eq!(block.size(), SZ_16M as u64);//! assert_eq!(allocated.iter().count(), 1);//!//! // Dropping the allocation returns the range to the buddy allocator.//! drop(allocated);//! assert_eq!(buddy.avail(), initial_free);//! # Ok::<(), Error>(())//! ```//!//! Top-down allocation allocates from the highest addresses://!//! ```//! # use kernel::{//! # gpu::buddy::{GpuBuddy, GpuBuddyAllocMode, GpuBuddyAllocFlags, GpuBuddyParams},//! # prelude::*,//! # ptr::Alignment,//! # sizes::*, ////! # };//! # let buddy = GpuBuddy::new(GpuBuddyParams {//! # base_offset: 0,//! # size: SZ_1G as u64,//! # chunk_size: Alignment::new::<SZ_4K>(),//! # })?;//! # let initial_free = buddy.avail();//! let topdown = KBox::pin_init(//! buddy.alloc_blocks(//! GpuBuddyAllocMode::TopDown,//! SZ_16M as u64,//! Alignment::new::<SZ_16M>(),//! GpuBuddyAllocFlags::default(),//! ),//! GFP_KERNEL,//! )?;//! assert_eq!(buddy.avail(), 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 as u64);//!//! // Dropping the allocation returns the range to the buddy allocator.//! drop(topdown);//! assert_eq!(buddy.avail(), 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,//! # size: SZ_1G as u64,//! # chunk_size: Alignment::new::<SZ_4K>(),//! # })?;//! # let initial_free = buddy.avail();//! // Create fragmentation by allocating 4MB blocks at [0,4M) and [8M,12M).//! let frag1 = KBox::pin_init(//! buddy.alloc_blocks(//! GpuBuddyAllocMode::Range(0..SZ_4M as u64),//! SZ_4M as u64,//! Alignment::new::<SZ_4M>(),//! GpuBuddyAllocFlags::default(),//! ),//! GFP_KERNEL,//! )?;//! assert_eq!(buddy.avail(), initial_free - SZ_4M as u64);//!//! let frag2 = KBox::pin_init(//! buddy.alloc_blocks(//! GpuBuddyAllocMode::Range(SZ_8M as u64..(SZ_8M + SZ_4M) as u64),//! SZ_4M as u64,//! Alignment::new::<SZ_4M>(),//! GpuBuddyAllocFlags::default(),//! ),//! GFP_KERNEL,//! )?;//! assert_eq!(buddy.avail(), 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(0..SZ_16M as u64),//! SZ_8M as u64,//! Alignment::new::<SZ_4M>(),//! GpuBuddyAllocFlags::default(),//! ),//! GFP_KERNEL,//! )?;//! assert_eq!(buddy.avail(), initial_free - SZ_16M as u64);//!//! let (mut count, mut total) = (0u32, 0u64);//! for block in fragmented.iter() {//! assert_eq!(block.size(), SZ_4M as u64);//! total += block.size();//! count += 1;//! }//! assert_eq!(total, SZ_8M as u64);//! assert_eq!(count, 2);//! # Ok::<(), Error>(())//! ```//!//! Contiguous allocation fails when only fragmented space is available://!//! ```//! # use kernel::{//! # gpu::buddy::{//! # GpuBuddy, GpuBuddyAllocFlag, GpuBuddyAllocFlags, GpuBuddyAllocMode, GpuBuddyParams,//! # },//! # prelude::*,//! # ptr::Alignment,//! # sizes::*, ////! # };//! // Create a small 16MB buddy allocator with fragmented memory.//! let small = GpuBuddy::new(GpuBuddyParams {//! base_offset: 0,//! size: SZ_16M as u64,//! chunk_size: Alignment::new::<SZ_4K>(),//! })?;//!//! let _hole1 = KBox::pin_init(//! small.alloc_blocks(//! GpuBuddyAllocMode::Range(0..SZ_4M as u64),//! SZ_4M as u64,//! Alignment::new::<SZ_4M>(),//! GpuBuddyAllocFlags::default(),//! ),//! GFP_KERNEL,//! )?;//!//! let _hole2 = KBox::pin_init(//! small.alloc_blocks(//! GpuBuddyAllocMode::Range(SZ_8M as u64..(SZ_8M + SZ_4M) as u64),//! SZ_4M as u64,//! 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 as u64,//! Alignment::new::<SZ_4M>(),//! GpuBuddyAllocFlag::Contiguous,//! ),//! GFP_KERNEL,//! );//! assert!(result.is_err());//! # Ok::<(), Error>(())//! ``` use core::ops::Range; 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(Clone, Debug, PartialEq, Eq)]pub enum GpuBuddyAllocMode { /// Simple allocation without constraints. Simple, /// Range-based allocation within the given address range. Range(Range<u64>), /// Allocate from top of address space downward. TopDown,} impl GpuBuddyAllocMode { /// Returns the C flags corresponding to the allocation mode. fn as_flags(&self) -> usize { match self { Self::Simple => 0, Self::Range(_) => bindings::GPU_BUDDY_RANGE_ALLOCATION, Self::TopDown => bindings::GPU_BUDDY_TOPDOWN_ALLOCATION, } } /// Extracts the range start/end, defaulting to `(0, 0)` for non-range modes. fn range(&self) -> (u64, u64) { match self { Self::Range(range) => (range.start, range.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(usize); /// 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, /// Request allocation from cleared (zeroed) memory. Clear = bindings::GPU_BUDDY_CLEAR_ALLOCATION, /// Disable trimming of partially used blocks. TrimDisable = bindings::GPU_BUDDY_TRIM_DISABLE, }); /// 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 size (in bytes) of the address space managed by the allocator. pub size: u64, /// Minimum allocation unit / chunk size; must be >= 4KB. pub chunk_size: Alignment,} /// Inner structure holding the actual buddy allocator.////// # Synchronization////// The C `gpu_buddy` API requires synchronization (see `include/linux/gpu_buddy.h`)./// Internal locking ensures all allocator and free operations are properly/// synchronized, preventing races between concurrent allocations and the/// freeing that occurs when [`AllocatedBlocks`] is dropped.////// # 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.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_usize() 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. /// /// The allocator 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> { Arc::pin_init(GpuBuddyInner::new(params), GFP_KERNEL).map(Self) } /// 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) -> Alignment { self.0.params.chunk_size } /// Get the total managed size. pub fn size(&self) -> u64 { self.0.params.size } /// Get the available (free) memory in bytes. pub fn avail(&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`]. pub fn alloc_blocks( &self, mode: GpuBuddyAllocMode, size: u64, min_block_size: Alignment, flags: impl Into<GpuBuddyAllocFlags>, ) -> impl PinInit<AllocatedBlocks, Error> { let buddy_arc = Arc::clone(&self.0); let (start, end) = mode.range(); let mode_flags = mode.as_flags(); let modifier_flags = flags.into(); // Create pin-initializer that initializes list and allocates blocks. try_pin_init!(AllocatedBlocks { buddy: buddy_arc, list <- CListHead::new(), _: { // Reject zero-sized or inverted ranges. if let GpuBuddyAllocMode::Range(range) = &mode { if range.is_empty() { Err::<(), Error>(EINVAL)?; } } // 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, min_block_size.as_usize() as u64, list.as_raw(), mode_flags | usize::from(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 {Lines 1–500 of 614. Load more
// 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, //! GpuBuddyAllocFlags, //! GpuBuddyAllocMode, //! GpuBuddyParams, // //! }, //! prelude::*, //! ptr::Alignment, //! sizes::*, // //! }; //! //! // Create a 1GB buddy allocator with 4KB minimum chunk size. //! let buddy = GpuBuddy::new(GpuBuddyParams { //! base_offset: 0, //! size: SZ_1G as u64, //! chunk_size: Alignment::new::<SZ_4K>(), //! })?; //! //! assert_eq!(buddy.size(), SZ_1G as u64); //! assert_eq!(buddy.chunk_size(), Alignment::new::<SZ_4K>()); //! let initial_free = buddy.avail(); //! //! // Allocate 16MB. Block lands at the top of the address range. //! let allocated = KBox::pin_init( //! buddy.alloc_blocks( //! GpuBuddyAllocMode::Simple, //! SZ_16M as u64, //! Alignment::new::<SZ_16M>(), //! GpuBuddyAllocFlags::default(), //! ), //! GFP_KERNEL, //! )?; //! assert_eq!(buddy.avail(), initial_free - SZ_16M as u64); //! //! let block = allocated.iter().next().expect("expected one block"); //! assert_eq!(block.offset(), (SZ_1G - SZ_16M) as u64); //! assert_eq!(block.order(), 12); // 2^12 pages = 16MB //! assert_eq!(block.size(), SZ_16M as u64); //! assert_eq!(allocated.iter().count(), 1); //! //! // Dropping the allocation returns the range to the buddy allocator. //! drop(allocated); //! assert_eq!(buddy.avail(), initial_free); //! # Ok::<(), Error>(()) //! ``` //! //! Top-down allocation allocates from the highest addresses: //! //! ``` //! # use kernel::{ //! # gpu::buddy::{GpuBuddy, GpuBuddyAllocMode, GpuBuddyAllocFlags, GpuBuddyParams}, //! # prelude::*, //! # ptr::Alignment, //! # sizes::*, // //! # }; //! # let buddy = GpuBuddy::new(GpuBuddyParams { //! # base_offset: 0, //! # size: SZ_1G as u64, //! # chunk_size: Alignment::new::<SZ_4K>(), //! # })?; //! # let initial_free = buddy.avail(); //! let topdown = KBox::pin_init( //! buddy.alloc_blocks( //! GpuBuddyAllocMode::TopDown, //! SZ_16M as u64, //! Alignment::new::<SZ_16M>(), //! GpuBuddyAllocFlags::default(), //! ), //! GFP_KERNEL, //! )?; //! assert_eq!(buddy.avail(), 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 as u64); //! //! // Dropping the allocation returns the range to the buddy allocator. //! drop(topdown); //! assert_eq!(buddy.avail(), 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, //! # size: SZ_1G as u64, //! # chunk_size: Alignment::new::<SZ_4K>(), //! # })?; //! # let initial_free = buddy.avail(); //! // Create fragmentation by allocating 4MB blocks at [0,4M) and [8M,12M). //! let frag1 = KBox::pin_init( //! buddy.alloc_blocks( //! GpuBuddyAllocMode::Range(0..SZ_4M as u64), //! SZ_4M as u64, //! Alignment::new::<SZ_4M>(), //! GpuBuddyAllocFlags::default(), //! ), //! GFP_KERNEL, //! )?; //! assert_eq!(buddy.avail(), initial_free - SZ_4M as u64); //! //! let frag2 = KBox::pin_init( //! buddy.alloc_blocks( //! GpuBuddyAllocMode::Range(SZ_8M as u64..(SZ_8M + SZ_4M) as u64), //! SZ_4M as u64, //! Alignment::new::<SZ_4M>(), //! GpuBuddyAllocFlags::default(), //! ), //! GFP_KERNEL, //! )?; //! assert_eq!(buddy.avail(), 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(0..SZ_16M as u64), //! SZ_8M as u64, //! Alignment::new::<SZ_4M>(), //! GpuBuddyAllocFlags::default(), //! ), //! GFP_KERNEL, //! )?; //! assert_eq!(buddy.avail(), initial_free - SZ_16M as u64); //! //! let (mut count, mut total) = (0u32, 0u64); //! for block in fragmented.iter() { //! assert_eq!(block.size(), SZ_4M as u64); //! total += block.size(); //! count += 1; //! } //! assert_eq!(total, SZ_8M as u64); //! assert_eq!(count, 2); //! # Ok::<(), Error>(()) //! ``` //! //! Contiguous allocation fails when only fragmented space is available: //! //! ``` //! # use kernel::{ //! # gpu::buddy::{ //! # GpuBuddy, GpuBuddyAllocFlag, GpuBuddyAllocFlags, GpuBuddyAllocMode, GpuBuddyParams, //! # }, //! # prelude::*, //! # ptr::Alignment, //! # sizes::*, // //! # }; //! // Create a small 16MB buddy allocator with fragmented memory. //! let small = GpuBuddy::new(GpuBuddyParams { //! base_offset: 0, //! size: SZ_16M as u64, //! chunk_size: Alignment::new::<SZ_4K>(), //! })?; //! //! let _hole1 = KBox::pin_init( //! small.alloc_blocks( //! GpuBuddyAllocMode::Range(0..SZ_4M as u64), //! SZ_4M as u64, //! Alignment::new::<SZ_4M>(), //! GpuBuddyAllocFlags::default(), //! ), //! GFP_KERNEL, //! )?; //! //! let _hole2 = KBox::pin_init( //! small.alloc_blocks( //! GpuBuddyAllocMode::Range(SZ_8M as u64..(SZ_8M + SZ_4M) as u64), //! SZ_4M as u64, //! 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 as u64, //! Alignment::new::<SZ_4M>(), //! GpuBuddyAllocFlag::Contiguous, //! ), //! GFP_KERNEL, //! ); //! assert!(result.is_err()); //! # Ok::<(), Error>(()) //! ``` use core::ops::Range; 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(Clone, Debug, PartialEq, Eq)] pub enum GpuBuddyAllocMode { /// Simple allocation without constraints. Simple, /// Range-based allocation within the given address range. Range(Range<u64>), /// Allocate from top of address space downward. TopDown, } impl GpuBuddyAllocMode { /// Returns the C flags corresponding to the allocation mode. fn as_flags(&self) -> usize { match self { Self::Simple => 0, Self::Range(_) => bindings::GPU_BUDDY_RANGE_ALLOCATION, Self::TopDown => bindings::GPU_BUDDY_TOPDOWN_ALLOCATION, } } /// Extracts the range start/end, defaulting to `(0, 0)` for non-range modes. fn range(&self) -> (u64, u64) { match self { Self::Range(range) => (range.start, range.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(usize); /// 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, /// Request allocation from cleared (zeroed) memory. Clear = bindings::GPU_BUDDY_CLEAR_ALLOCATION, /// Disable trimming of partially used blocks. TrimDisable = bindings::GPU_BUDDY_TRIM_DISABLE, } ); /// 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 size (in bytes) of the address space managed by the allocator. pub size: u64, /// Minimum allocation unit / chunk size; must be >= 4KB. pub chunk_size: Alignment, } /// Inner structure holding the actual buddy allocator. /// /// # Synchronization /// /// The C `gpu_buddy` API requires synchronization (see `include/linux/gpu_buddy.h`). /// Internal locking ensures all allocator and free operations are properly /// synchronized, preventing races between concurrent allocations and the /// freeing that occurs when [`AllocatedBlocks`] is dropped. /// /// # 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.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_usize() 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. /// /// The allocator 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> { Arc::pin_init(GpuBuddyInner::new(params), GFP_KERNEL).map(Self) } /// 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) -> Alignment { self.0.params.chunk_size } /// Get the total managed size. pub fn size(&self) -> u64 { self.0.params.size } /// Get the available (free) memory in bytes. pub fn avail(&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`]. pub fn alloc_blocks( &self, mode: GpuBuddyAllocMode, size: u64, min_block_size: Alignment, flags: impl Into<GpuBuddyAllocFlags>, ) -> impl PinInit<AllocatedBlocks, Error> { let buddy_arc = Arc::clone(&self.0); let (start, end) = mode.range(); let mode_flags = mode.as_flags(); let modifier_flags = flags.into(); // Create pin-initializer that initializes list and allocates blocks. try_pin_init!(AllocatedBlocks { buddy: buddy_arc, list <- CListHead::new(), _: { // Reject zero-sized or inverted ranges. if let GpuBuddyAllocMode::Range(range) = &mode { if range.is_empty() { Err::<(), Error>(EINVAL)?; } } // 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, min_block_size.as_usize() as u64, list.as_raw(), mode_flags | usize::from(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 {