614 lines · 19.2 KB · Rust
RawDownload
// 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.