1*b9616d97SJoel Fernandes // SPDX-License-Identifier: GPL-2.0 2*b9616d97SJoel Fernandes 3*b9616d97SJoel Fernandes //! GPU buddy allocator bindings. 4*b9616d97SJoel Fernandes //! 5*b9616d97SJoel Fernandes //! C header: [`include/linux/gpu_buddy.h`](srctree/include/linux/gpu_buddy.h) 6*b9616d97SJoel Fernandes //! 7*b9616d97SJoel Fernandes //! This module provides Rust abstractions over the Linux kernel's GPU buddy 8*b9616d97SJoel Fernandes //! allocator, which implements a binary buddy memory allocator. 9*b9616d97SJoel Fernandes //! 10*b9616d97SJoel Fernandes //! The buddy allocator manages a contiguous address space and allocates blocks 11*b9616d97SJoel Fernandes //! in power-of-two sizes, useful for GPU physical memory management. 12*b9616d97SJoel Fernandes //! 13*b9616d97SJoel Fernandes //! # Examples 14*b9616d97SJoel Fernandes //! 15*b9616d97SJoel Fernandes //! Create a buddy allocator and perform a basic range allocation: 16*b9616d97SJoel Fernandes //! 17*b9616d97SJoel Fernandes //! ``` 18*b9616d97SJoel Fernandes //! use kernel::{ 19*b9616d97SJoel Fernandes //! gpu::buddy::{ 20*b9616d97SJoel Fernandes //! GpuBuddy, 21*b9616d97SJoel Fernandes //! GpuBuddyAllocFlags, 22*b9616d97SJoel Fernandes //! GpuBuddyAllocMode, 23*b9616d97SJoel Fernandes //! GpuBuddyParams, // 24*b9616d97SJoel Fernandes //! }, 25*b9616d97SJoel Fernandes //! prelude::*, 26*b9616d97SJoel Fernandes //! ptr::Alignment, 27*b9616d97SJoel Fernandes //! sizes::*, // 28*b9616d97SJoel Fernandes //! }; 29*b9616d97SJoel Fernandes //! 30*b9616d97SJoel Fernandes //! // Create a 1GB buddy allocator with 4KB minimum chunk size. 31*b9616d97SJoel Fernandes //! let buddy = GpuBuddy::new(GpuBuddyParams { 32*b9616d97SJoel Fernandes //! base_offset: 0, 33*b9616d97SJoel Fernandes //! size: SZ_1G as u64, 34*b9616d97SJoel Fernandes //! chunk_size: Alignment::new::<SZ_4K>(), 35*b9616d97SJoel Fernandes //! })?; 36*b9616d97SJoel Fernandes //! 37*b9616d97SJoel Fernandes //! assert_eq!(buddy.size(), SZ_1G as u64); 38*b9616d97SJoel Fernandes //! assert_eq!(buddy.chunk_size(), Alignment::new::<SZ_4K>()); 39*b9616d97SJoel Fernandes //! let initial_free = buddy.avail(); 40*b9616d97SJoel Fernandes //! 41*b9616d97SJoel Fernandes //! // Allocate 16MB. Block lands at the top of the address range. 42*b9616d97SJoel Fernandes //! let allocated = KBox::pin_init( 43*b9616d97SJoel Fernandes //! buddy.alloc_blocks( 44*b9616d97SJoel Fernandes //! GpuBuddyAllocMode::Simple, 45*b9616d97SJoel Fernandes //! SZ_16M as u64, 46*b9616d97SJoel Fernandes //! Alignment::new::<SZ_16M>(), 47*b9616d97SJoel Fernandes //! GpuBuddyAllocFlags::default(), 48*b9616d97SJoel Fernandes //! ), 49*b9616d97SJoel Fernandes //! GFP_KERNEL, 50*b9616d97SJoel Fernandes //! )?; 51*b9616d97SJoel Fernandes //! assert_eq!(buddy.avail(), initial_free - SZ_16M as u64); 52*b9616d97SJoel Fernandes //! 53*b9616d97SJoel Fernandes //! let block = allocated.iter().next().expect("expected one block"); 54*b9616d97SJoel Fernandes //! assert_eq!(block.offset(), (SZ_1G - SZ_16M) as u64); 55*b9616d97SJoel Fernandes //! assert_eq!(block.order(), 12); // 2^12 pages = 16MB 56*b9616d97SJoel Fernandes //! assert_eq!(block.size(), SZ_16M as u64); 57*b9616d97SJoel Fernandes //! assert_eq!(allocated.iter().count(), 1); 58*b9616d97SJoel Fernandes //! 59*b9616d97SJoel Fernandes //! // Dropping the allocation returns the range to the buddy allocator. 60*b9616d97SJoel Fernandes //! drop(allocated); 61*b9616d97SJoel Fernandes //! assert_eq!(buddy.avail(), initial_free); 62*b9616d97SJoel Fernandes //! # Ok::<(), Error>(()) 63*b9616d97SJoel Fernandes //! ``` 64*b9616d97SJoel Fernandes //! 65*b9616d97SJoel Fernandes //! Top-down allocation allocates from the highest addresses: 66*b9616d97SJoel Fernandes //! 67*b9616d97SJoel Fernandes //! ``` 68*b9616d97SJoel Fernandes //! # use kernel::{ 69*b9616d97SJoel Fernandes //! # gpu::buddy::{GpuBuddy, GpuBuddyAllocMode, GpuBuddyAllocFlags, GpuBuddyParams}, 70*b9616d97SJoel Fernandes //! # prelude::*, 71*b9616d97SJoel Fernandes //! # ptr::Alignment, 72*b9616d97SJoel Fernandes //! # sizes::*, // 73*b9616d97SJoel Fernandes //! # }; 74*b9616d97SJoel Fernandes //! # let buddy = GpuBuddy::new(GpuBuddyParams { 75*b9616d97SJoel Fernandes //! # base_offset: 0, 76*b9616d97SJoel Fernandes //! # size: SZ_1G as u64, 77*b9616d97SJoel Fernandes //! # chunk_size: Alignment::new::<SZ_4K>(), 78*b9616d97SJoel Fernandes //! # })?; 79*b9616d97SJoel Fernandes //! # let initial_free = buddy.avail(); 80*b9616d97SJoel Fernandes //! let topdown = KBox::pin_init( 81*b9616d97SJoel Fernandes //! buddy.alloc_blocks( 82*b9616d97SJoel Fernandes //! GpuBuddyAllocMode::TopDown, 83*b9616d97SJoel Fernandes //! SZ_16M as u64, 84*b9616d97SJoel Fernandes //! Alignment::new::<SZ_16M>(), 85*b9616d97SJoel Fernandes //! GpuBuddyAllocFlags::default(), 86*b9616d97SJoel Fernandes //! ), 87*b9616d97SJoel Fernandes //! GFP_KERNEL, 88*b9616d97SJoel Fernandes //! )?; 89*b9616d97SJoel Fernandes //! assert_eq!(buddy.avail(), initial_free - SZ_16M as u64); 90*b9616d97SJoel Fernandes //! 91*b9616d97SJoel Fernandes //! let block = topdown.iter().next().expect("expected one block"); 92*b9616d97SJoel Fernandes //! assert_eq!(block.offset(), (SZ_1G - SZ_16M) as u64); 93*b9616d97SJoel Fernandes //! assert_eq!(block.order(), 12); 94*b9616d97SJoel Fernandes //! assert_eq!(block.size(), SZ_16M as u64); 95*b9616d97SJoel Fernandes //! 96*b9616d97SJoel Fernandes //! // Dropping the allocation returns the range to the buddy allocator. 97*b9616d97SJoel Fernandes //! drop(topdown); 98*b9616d97SJoel Fernandes //! assert_eq!(buddy.avail(), initial_free); 99*b9616d97SJoel Fernandes //! # Ok::<(), Error>(()) 100*b9616d97SJoel Fernandes //! ``` 101*b9616d97SJoel Fernandes //! 102*b9616d97SJoel Fernandes //! Non-contiguous allocation can fill fragmented memory by returning multiple 103*b9616d97SJoel Fernandes //! blocks: 104*b9616d97SJoel Fernandes //! 105*b9616d97SJoel Fernandes //! ``` 106*b9616d97SJoel Fernandes //! # use kernel::{ 107*b9616d97SJoel Fernandes //! # gpu::buddy::{ 108*b9616d97SJoel Fernandes //! # GpuBuddy, GpuBuddyAllocFlags, GpuBuddyAllocMode, GpuBuddyParams, 109*b9616d97SJoel Fernandes //! # }, 110*b9616d97SJoel Fernandes //! # prelude::*, 111*b9616d97SJoel Fernandes //! # ptr::Alignment, 112*b9616d97SJoel Fernandes //! # sizes::*, // 113*b9616d97SJoel Fernandes //! # }; 114*b9616d97SJoel Fernandes //! # let buddy = GpuBuddy::new(GpuBuddyParams { 115*b9616d97SJoel Fernandes //! # base_offset: 0, 116*b9616d97SJoel Fernandes //! # size: SZ_1G as u64, 117*b9616d97SJoel Fernandes //! # chunk_size: Alignment::new::<SZ_4K>(), 118*b9616d97SJoel Fernandes //! # })?; 119*b9616d97SJoel Fernandes //! # let initial_free = buddy.avail(); 120*b9616d97SJoel Fernandes //! // Create fragmentation by allocating 4MB blocks at [0,4M) and [8M,12M). 121*b9616d97SJoel Fernandes //! let frag1 = KBox::pin_init( 122*b9616d97SJoel Fernandes //! buddy.alloc_blocks( 123*b9616d97SJoel Fernandes //! GpuBuddyAllocMode::Range(0..SZ_4M as u64), 124*b9616d97SJoel Fernandes //! SZ_4M as u64, 125*b9616d97SJoel Fernandes //! Alignment::new::<SZ_4M>(), 126*b9616d97SJoel Fernandes //! GpuBuddyAllocFlags::default(), 127*b9616d97SJoel Fernandes //! ), 128*b9616d97SJoel Fernandes //! GFP_KERNEL, 129*b9616d97SJoel Fernandes //! )?; 130*b9616d97SJoel Fernandes //! assert_eq!(buddy.avail(), initial_free - SZ_4M as u64); 131*b9616d97SJoel Fernandes //! 132*b9616d97SJoel Fernandes //! let frag2 = KBox::pin_init( 133*b9616d97SJoel Fernandes //! buddy.alloc_blocks( 134*b9616d97SJoel Fernandes //! GpuBuddyAllocMode::Range(SZ_8M as u64..(SZ_8M + SZ_4M) as u64), 135*b9616d97SJoel Fernandes //! SZ_4M as u64, 136*b9616d97SJoel Fernandes //! Alignment::new::<SZ_4M>(), 137*b9616d97SJoel Fernandes //! GpuBuddyAllocFlags::default(), 138*b9616d97SJoel Fernandes //! ), 139*b9616d97SJoel Fernandes //! GFP_KERNEL, 140*b9616d97SJoel Fernandes //! )?; 141*b9616d97SJoel Fernandes //! assert_eq!(buddy.avail(), initial_free - SZ_8M as u64); 142*b9616d97SJoel Fernandes //! 143*b9616d97SJoel Fernandes //! // Allocate 8MB, this returns 2 blocks from the holes. 144*b9616d97SJoel Fernandes //! let fragmented = KBox::pin_init( 145*b9616d97SJoel Fernandes //! buddy.alloc_blocks( 146*b9616d97SJoel Fernandes //! GpuBuddyAllocMode::Range(0..SZ_16M as u64), 147*b9616d97SJoel Fernandes //! SZ_8M as u64, 148*b9616d97SJoel Fernandes //! Alignment::new::<SZ_4M>(), 149*b9616d97SJoel Fernandes //! GpuBuddyAllocFlags::default(), 150*b9616d97SJoel Fernandes //! ), 151*b9616d97SJoel Fernandes //! GFP_KERNEL, 152*b9616d97SJoel Fernandes //! )?; 153*b9616d97SJoel Fernandes //! assert_eq!(buddy.avail(), initial_free - SZ_16M as u64); 154*b9616d97SJoel Fernandes //! 155*b9616d97SJoel Fernandes //! let (mut count, mut total) = (0u32, 0u64); 156*b9616d97SJoel Fernandes //! for block in fragmented.iter() { 157*b9616d97SJoel Fernandes //! assert_eq!(block.size(), SZ_4M as u64); 158*b9616d97SJoel Fernandes //! total += block.size(); 159*b9616d97SJoel Fernandes //! count += 1; 160*b9616d97SJoel Fernandes //! } 161*b9616d97SJoel Fernandes //! assert_eq!(total, SZ_8M as u64); 162*b9616d97SJoel Fernandes //! assert_eq!(count, 2); 163*b9616d97SJoel Fernandes //! # Ok::<(), Error>(()) 164*b9616d97SJoel Fernandes //! ``` 165*b9616d97SJoel Fernandes //! 166*b9616d97SJoel Fernandes //! Contiguous allocation fails when only fragmented space is available: 167*b9616d97SJoel Fernandes //! 168*b9616d97SJoel Fernandes //! ``` 169*b9616d97SJoel Fernandes //! # use kernel::{ 170*b9616d97SJoel Fernandes //! # gpu::buddy::{ 171*b9616d97SJoel Fernandes //! # GpuBuddy, GpuBuddyAllocFlag, GpuBuddyAllocFlags, GpuBuddyAllocMode, GpuBuddyParams, 172*b9616d97SJoel Fernandes //! # }, 173*b9616d97SJoel Fernandes //! # prelude::*, 174*b9616d97SJoel Fernandes //! # ptr::Alignment, 175*b9616d97SJoel Fernandes //! # sizes::*, // 176*b9616d97SJoel Fernandes //! # }; 177*b9616d97SJoel Fernandes //! // Create a small 16MB buddy allocator with fragmented memory. 178*b9616d97SJoel Fernandes //! let small = GpuBuddy::new(GpuBuddyParams { 179*b9616d97SJoel Fernandes //! base_offset: 0, 180*b9616d97SJoel Fernandes //! size: SZ_16M as u64, 181*b9616d97SJoel Fernandes //! chunk_size: Alignment::new::<SZ_4K>(), 182*b9616d97SJoel Fernandes //! })?; 183*b9616d97SJoel Fernandes //! 184*b9616d97SJoel Fernandes //! let _hole1 = KBox::pin_init( 185*b9616d97SJoel Fernandes //! small.alloc_blocks( 186*b9616d97SJoel Fernandes //! GpuBuddyAllocMode::Range(0..SZ_4M as u64), 187*b9616d97SJoel Fernandes //! SZ_4M as u64, 188*b9616d97SJoel Fernandes //! Alignment::new::<SZ_4M>(), 189*b9616d97SJoel Fernandes //! GpuBuddyAllocFlags::default(), 190*b9616d97SJoel Fernandes //! ), 191*b9616d97SJoel Fernandes //! GFP_KERNEL, 192*b9616d97SJoel Fernandes //! )?; 193*b9616d97SJoel Fernandes //! 194*b9616d97SJoel Fernandes //! let _hole2 = KBox::pin_init( 195*b9616d97SJoel Fernandes //! small.alloc_blocks( 196*b9616d97SJoel Fernandes //! GpuBuddyAllocMode::Range(SZ_8M as u64..(SZ_8M + SZ_4M) as u64), 197*b9616d97SJoel Fernandes //! SZ_4M as u64, 198*b9616d97SJoel Fernandes //! Alignment::new::<SZ_4M>(), 199*b9616d97SJoel Fernandes //! GpuBuddyAllocFlags::default(), 200*b9616d97SJoel Fernandes //! ), 201*b9616d97SJoel Fernandes //! GFP_KERNEL, 202*b9616d97SJoel Fernandes //! )?; 203*b9616d97SJoel Fernandes //! 204*b9616d97SJoel Fernandes //! // 8MB contiguous should fail, only two non-contiguous 4MB holes exist. 205*b9616d97SJoel Fernandes //! let result = KBox::pin_init( 206*b9616d97SJoel Fernandes //! small.alloc_blocks( 207*b9616d97SJoel Fernandes //! GpuBuddyAllocMode::Simple, 208*b9616d97SJoel Fernandes //! SZ_8M as u64, 209*b9616d97SJoel Fernandes //! Alignment::new::<SZ_4M>(), 210*b9616d97SJoel Fernandes //! GpuBuddyAllocFlag::Contiguous, 211*b9616d97SJoel Fernandes //! ), 212*b9616d97SJoel Fernandes //! GFP_KERNEL, 213*b9616d97SJoel Fernandes //! ); 214*b9616d97SJoel Fernandes //! assert!(result.is_err()); 215*b9616d97SJoel Fernandes //! # Ok::<(), Error>(()) 216*b9616d97SJoel Fernandes //! ``` 217*b9616d97SJoel Fernandes 218*b9616d97SJoel Fernandes use core::ops::Range; 219*b9616d97SJoel Fernandes 220*b9616d97SJoel Fernandes use crate::{ 221*b9616d97SJoel Fernandes bindings, 222*b9616d97SJoel Fernandes clist_create, 223*b9616d97SJoel Fernandes error::to_result, 224*b9616d97SJoel Fernandes interop::list::CListHead, 225*b9616d97SJoel Fernandes new_mutex, 226*b9616d97SJoel Fernandes prelude::*, 227*b9616d97SJoel Fernandes ptr::Alignment, 228*b9616d97SJoel Fernandes sync::{ 229*b9616d97SJoel Fernandes lock::mutex::MutexGuard, 230*b9616d97SJoel Fernandes Arc, 231*b9616d97SJoel Fernandes Mutex, // 232*b9616d97SJoel Fernandes }, 233*b9616d97SJoel Fernandes types::Opaque, // 234*b9616d97SJoel Fernandes }; 235*b9616d97SJoel Fernandes 236*b9616d97SJoel Fernandes /// Allocation mode for the GPU buddy allocator. 237*b9616d97SJoel Fernandes /// 238*b9616d97SJoel Fernandes /// The mode determines the primary allocation strategy. Modes are mutually 239*b9616d97SJoel Fernandes /// exclusive: an allocation is either simple, range-constrained, or top-down. 240*b9616d97SJoel Fernandes /// 241*b9616d97SJoel Fernandes /// Orthogonal modifier flags (e.g., contiguous, clear) are specified separately 242*b9616d97SJoel Fernandes /// via [`GpuBuddyAllocFlags`]. 243*b9616d97SJoel Fernandes #[derive(Clone, Debug, PartialEq, Eq)] 244*b9616d97SJoel Fernandes pub enum GpuBuddyAllocMode { 245*b9616d97SJoel Fernandes /// Simple allocation without constraints. 246*b9616d97SJoel Fernandes Simple, 247*b9616d97SJoel Fernandes /// Range-based allocation within the given address range. 248*b9616d97SJoel Fernandes Range(Range<u64>), 249*b9616d97SJoel Fernandes /// Allocate from top of address space downward. 250*b9616d97SJoel Fernandes TopDown, 251*b9616d97SJoel Fernandes } 252*b9616d97SJoel Fernandes 253*b9616d97SJoel Fernandes impl GpuBuddyAllocMode { 254*b9616d97SJoel Fernandes /// Returns the C flags corresponding to the allocation mode. 255*b9616d97SJoel Fernandes fn as_flags(&self) -> usize { 256*b9616d97SJoel Fernandes match self { 257*b9616d97SJoel Fernandes Self::Simple => 0, 258*b9616d97SJoel Fernandes Self::Range(_) => bindings::GPU_BUDDY_RANGE_ALLOCATION, 259*b9616d97SJoel Fernandes Self::TopDown => bindings::GPU_BUDDY_TOPDOWN_ALLOCATION, 260*b9616d97SJoel Fernandes } 261*b9616d97SJoel Fernandes } 262*b9616d97SJoel Fernandes 263*b9616d97SJoel Fernandes /// Extracts the range start/end, defaulting to `(0, 0)` for non-range modes. 264*b9616d97SJoel Fernandes fn range(&self) -> (u64, u64) { 265*b9616d97SJoel Fernandes match self { 266*b9616d97SJoel Fernandes Self::Range(range) => (range.start, range.end), 267*b9616d97SJoel Fernandes _ => (0, 0), 268*b9616d97SJoel Fernandes } 269*b9616d97SJoel Fernandes } 270*b9616d97SJoel Fernandes } 271*b9616d97SJoel Fernandes 272*b9616d97SJoel Fernandes crate::impl_flags!( 273*b9616d97SJoel Fernandes /// Modifier flags for GPU buddy allocation. 274*b9616d97SJoel Fernandes /// 275*b9616d97SJoel Fernandes /// These flags can be combined with any [`GpuBuddyAllocMode`] to control 276*b9616d97SJoel Fernandes /// additional allocation behavior. 277*b9616d97SJoel Fernandes #[derive(Clone, Copy, Default, PartialEq, Eq)] 278*b9616d97SJoel Fernandes pub struct GpuBuddyAllocFlags(usize); 279*b9616d97SJoel Fernandes 280*b9616d97SJoel Fernandes /// Individual modifier flag for GPU buddy allocation. 281*b9616d97SJoel Fernandes #[derive(Clone, Copy, PartialEq, Eq)] 282*b9616d97SJoel Fernandes pub enum GpuBuddyAllocFlag { 283*b9616d97SJoel Fernandes /// Allocate physically contiguous blocks. 284*b9616d97SJoel Fernandes Contiguous = bindings::GPU_BUDDY_CONTIGUOUS_ALLOCATION, 285*b9616d97SJoel Fernandes 286*b9616d97SJoel Fernandes /// Request allocation from cleared (zeroed) memory. 287*b9616d97SJoel Fernandes Clear = bindings::GPU_BUDDY_CLEAR_ALLOCATION, 288*b9616d97SJoel Fernandes 289*b9616d97SJoel Fernandes /// Disable trimming of partially used blocks. 290*b9616d97SJoel Fernandes TrimDisable = bindings::GPU_BUDDY_TRIM_DISABLE, 291*b9616d97SJoel Fernandes } 292*b9616d97SJoel Fernandes ); 293*b9616d97SJoel Fernandes 294*b9616d97SJoel Fernandes /// Parameters for creating a GPU buddy allocator. 295*b9616d97SJoel Fernandes pub struct GpuBuddyParams { 296*b9616d97SJoel Fernandes /// Base offset (in bytes) where the managed memory region starts. 297*b9616d97SJoel Fernandes /// Allocations will be offset by this value. 298*b9616d97SJoel Fernandes pub base_offset: u64, 299*b9616d97SJoel Fernandes /// Total size (in bytes) of the address space managed by the allocator. 300*b9616d97SJoel Fernandes pub size: u64, 301*b9616d97SJoel Fernandes /// Minimum allocation unit / chunk size; must be >= 4KB. 302*b9616d97SJoel Fernandes pub chunk_size: Alignment, 303*b9616d97SJoel Fernandes } 304*b9616d97SJoel Fernandes 305*b9616d97SJoel Fernandes /// Inner structure holding the actual buddy allocator. 306*b9616d97SJoel Fernandes /// 307*b9616d97SJoel Fernandes /// # Synchronization 308*b9616d97SJoel Fernandes /// 309*b9616d97SJoel Fernandes /// The C `gpu_buddy` API requires synchronization (see `include/linux/gpu_buddy.h`). 310*b9616d97SJoel Fernandes /// Internal locking ensures all allocator and free operations are properly 311*b9616d97SJoel Fernandes /// synchronized, preventing races between concurrent allocations and the 312*b9616d97SJoel Fernandes /// freeing that occurs when [`AllocatedBlocks`] is dropped. 313*b9616d97SJoel Fernandes /// 314*b9616d97SJoel Fernandes /// # Invariants 315*b9616d97SJoel Fernandes /// 316*b9616d97SJoel Fernandes /// The inner [`Opaque`] contains an initialized buddy allocator. 317*b9616d97SJoel Fernandes #[pin_data(PinnedDrop)] 318*b9616d97SJoel Fernandes struct GpuBuddyInner { 319*b9616d97SJoel Fernandes #[pin] 320*b9616d97SJoel Fernandes inner: Opaque<bindings::gpu_buddy>, 321*b9616d97SJoel Fernandes 322*b9616d97SJoel Fernandes // TODO: Replace `Mutex<()>` with `Mutex<Opaque<..>>` once `Mutex::new()` 323*b9616d97SJoel Fernandes // accepts `impl PinInit<T>`. 324*b9616d97SJoel Fernandes #[pin] 325*b9616d97SJoel Fernandes lock: Mutex<()>, 326*b9616d97SJoel Fernandes /// Cached creation parameters (do not change after init). 327*b9616d97SJoel Fernandes params: GpuBuddyParams, 328*b9616d97SJoel Fernandes } 329*b9616d97SJoel Fernandes 330*b9616d97SJoel Fernandes impl GpuBuddyInner { 331*b9616d97SJoel Fernandes /// Create a pin-initializer for the buddy allocator. 332*b9616d97SJoel Fernandes fn new(params: GpuBuddyParams) -> impl PinInit<Self, Error> { 333*b9616d97SJoel Fernandes let size = params.size; 334*b9616d97SJoel Fernandes let chunk_size = params.chunk_size; 335*b9616d97SJoel Fernandes 336*b9616d97SJoel Fernandes // INVARIANT: `gpu_buddy_init` returns 0 on success, at which point the 337*b9616d97SJoel Fernandes // `gpu_buddy` structure is initialized and ready for use with all 338*b9616d97SJoel Fernandes // `gpu_buddy_*` APIs. `try_pin_init!` only completes if all fields succeed, 339*b9616d97SJoel Fernandes // so the invariant holds when construction finishes. 340*b9616d97SJoel Fernandes try_pin_init!(Self { 341*b9616d97SJoel Fernandes inner <- Opaque::try_ffi_init(|ptr| { 342*b9616d97SJoel Fernandes // SAFETY: `ptr` points to valid uninitialized memory from the pin-init 343*b9616d97SJoel Fernandes // infrastructure. `gpu_buddy_init` will initialize the structure. 344*b9616d97SJoel Fernandes to_result(unsafe { 345*b9616d97SJoel Fernandes bindings::gpu_buddy_init(ptr, size, chunk_size.as_usize() as u64) 346*b9616d97SJoel Fernandes }) 347*b9616d97SJoel Fernandes }), 348*b9616d97SJoel Fernandes lock <- new_mutex!(()), 349*b9616d97SJoel Fernandes params, 350*b9616d97SJoel Fernandes }) 351*b9616d97SJoel Fernandes } 352*b9616d97SJoel Fernandes 353*b9616d97SJoel Fernandes /// Lock the mutex and return a guard for accessing the allocator. 354*b9616d97SJoel Fernandes fn lock(&self) -> GpuBuddyGuard<'_> { 355*b9616d97SJoel Fernandes GpuBuddyGuard { 356*b9616d97SJoel Fernandes inner: self, 357*b9616d97SJoel Fernandes _guard: self.lock.lock(), 358*b9616d97SJoel Fernandes } 359*b9616d97SJoel Fernandes } 360*b9616d97SJoel Fernandes } 361*b9616d97SJoel Fernandes 362*b9616d97SJoel Fernandes #[pinned_drop] 363*b9616d97SJoel Fernandes impl PinnedDrop for GpuBuddyInner { 364*b9616d97SJoel Fernandes fn drop(self: Pin<&mut Self>) { 365*b9616d97SJoel Fernandes let guard = self.lock(); 366*b9616d97SJoel Fernandes 367*b9616d97SJoel Fernandes // SAFETY: Per the type invariant, `inner` contains an initialized 368*b9616d97SJoel Fernandes // allocator. `guard` provides exclusive access. 369*b9616d97SJoel Fernandes unsafe { bindings::gpu_buddy_fini(guard.as_raw()) }; 370*b9616d97SJoel Fernandes } 371*b9616d97SJoel Fernandes } 372*b9616d97SJoel Fernandes 373*b9616d97SJoel Fernandes // SAFETY: `GpuBuddyInner` can be sent between threads. 374*b9616d97SJoel Fernandes unsafe impl Send for GpuBuddyInner {} 375*b9616d97SJoel Fernandes 376*b9616d97SJoel Fernandes // SAFETY: `GpuBuddyInner` is `Sync` because `GpuBuddyInner::lock` 377*b9616d97SJoel Fernandes // serializes all access to the C allocator, preventing data races. 378*b9616d97SJoel Fernandes unsafe impl Sync for GpuBuddyInner {} 379*b9616d97SJoel Fernandes 380*b9616d97SJoel Fernandes /// Guard that proves the lock is held, enabling access to the allocator. 381*b9616d97SJoel Fernandes /// 382*b9616d97SJoel Fernandes /// The `_guard` holds the lock for the duration of this guard's lifetime. 383*b9616d97SJoel Fernandes struct GpuBuddyGuard<'a> { 384*b9616d97SJoel Fernandes inner: &'a GpuBuddyInner, 385*b9616d97SJoel Fernandes _guard: MutexGuard<'a, ()>, 386*b9616d97SJoel Fernandes } 387*b9616d97SJoel Fernandes 388*b9616d97SJoel Fernandes impl GpuBuddyGuard<'_> { 389*b9616d97SJoel Fernandes /// Get a raw pointer to the underlying C `gpu_buddy` structure. 390*b9616d97SJoel Fernandes fn as_raw(&self) -> *mut bindings::gpu_buddy { 391*b9616d97SJoel Fernandes self.inner.inner.get() 392*b9616d97SJoel Fernandes } 393*b9616d97SJoel Fernandes } 394*b9616d97SJoel Fernandes 395*b9616d97SJoel Fernandes /// GPU buddy allocator instance. 396*b9616d97SJoel Fernandes /// 397*b9616d97SJoel Fernandes /// This structure wraps the C `gpu_buddy` allocator using reference counting. 398*b9616d97SJoel Fernandes /// The allocator is automatically cleaned up when all references are dropped. 399*b9616d97SJoel Fernandes /// 400*b9616d97SJoel Fernandes /// Refer to the module-level documentation for usage examples. 401*b9616d97SJoel Fernandes pub struct GpuBuddy(Arc<GpuBuddyInner>); 402*b9616d97SJoel Fernandes 403*b9616d97SJoel Fernandes impl GpuBuddy { 404*b9616d97SJoel Fernandes /// Create a new buddy allocator. 405*b9616d97SJoel Fernandes /// 406*b9616d97SJoel Fernandes /// The allocator manages a contiguous address space of the given size, with the 407*b9616d97SJoel Fernandes /// specified minimum allocation unit (chunk_size must be at least 4KB). 408*b9616d97SJoel Fernandes pub fn new(params: GpuBuddyParams) -> Result<Self> { 409*b9616d97SJoel Fernandes Arc::pin_init(GpuBuddyInner::new(params), GFP_KERNEL).map(Self) 410*b9616d97SJoel Fernandes } 411*b9616d97SJoel Fernandes 412*b9616d97SJoel Fernandes /// Get the base offset for allocations. 413*b9616d97SJoel Fernandes pub fn base_offset(&self) -> u64 { 414*b9616d97SJoel Fernandes self.0.params.base_offset 415*b9616d97SJoel Fernandes } 416*b9616d97SJoel Fernandes 417*b9616d97SJoel Fernandes /// Get the chunk size (minimum allocation unit). 418*b9616d97SJoel Fernandes pub fn chunk_size(&self) -> Alignment { 419*b9616d97SJoel Fernandes self.0.params.chunk_size 420*b9616d97SJoel Fernandes } 421*b9616d97SJoel Fernandes 422*b9616d97SJoel Fernandes /// Get the total managed size. 423*b9616d97SJoel Fernandes pub fn size(&self) -> u64 { 424*b9616d97SJoel Fernandes self.0.params.size 425*b9616d97SJoel Fernandes } 426*b9616d97SJoel Fernandes 427*b9616d97SJoel Fernandes /// Get the available (free) memory in bytes. 428*b9616d97SJoel Fernandes pub fn avail(&self) -> u64 { 429*b9616d97SJoel Fernandes let guard = self.0.lock(); 430*b9616d97SJoel Fernandes 431*b9616d97SJoel Fernandes // SAFETY: Per the type invariant, `inner` contains an initialized allocator. 432*b9616d97SJoel Fernandes // `guard` provides exclusive access. 433*b9616d97SJoel Fernandes unsafe { (*guard.as_raw()).avail } 434*b9616d97SJoel Fernandes } 435*b9616d97SJoel Fernandes 436*b9616d97SJoel Fernandes /// Allocate blocks from the buddy allocator. 437*b9616d97SJoel Fernandes /// 438*b9616d97SJoel Fernandes /// Returns a pin-initializer for [`AllocatedBlocks`]. 439*b9616d97SJoel Fernandes pub fn alloc_blocks( 440*b9616d97SJoel Fernandes &self, 441*b9616d97SJoel Fernandes mode: GpuBuddyAllocMode, 442*b9616d97SJoel Fernandes size: u64, 443*b9616d97SJoel Fernandes min_block_size: Alignment, 444*b9616d97SJoel Fernandes flags: impl Into<GpuBuddyAllocFlags>, 445*b9616d97SJoel Fernandes ) -> impl PinInit<AllocatedBlocks, Error> { 446*b9616d97SJoel Fernandes let buddy_arc = Arc::clone(&self.0); 447*b9616d97SJoel Fernandes let (start, end) = mode.range(); 448*b9616d97SJoel Fernandes let mode_flags = mode.as_flags(); 449*b9616d97SJoel Fernandes let modifier_flags = flags.into(); 450*b9616d97SJoel Fernandes 451*b9616d97SJoel Fernandes // Create pin-initializer that initializes list and allocates blocks. 452*b9616d97SJoel Fernandes try_pin_init!(AllocatedBlocks { 453*b9616d97SJoel Fernandes buddy: buddy_arc, 454*b9616d97SJoel Fernandes list <- CListHead::new(), 455*b9616d97SJoel Fernandes _: { 456*b9616d97SJoel Fernandes // Reject zero-sized or inverted ranges. 457*b9616d97SJoel Fernandes if let GpuBuddyAllocMode::Range(range) = &mode { 458*b9616d97SJoel Fernandes if range.is_empty() { 459*b9616d97SJoel Fernandes Err::<(), Error>(EINVAL)?; 460*b9616d97SJoel Fernandes } 461*b9616d97SJoel Fernandes } 462*b9616d97SJoel Fernandes 463*b9616d97SJoel Fernandes // Lock while allocating to serialize with concurrent frees. 464*b9616d97SJoel Fernandes let guard = buddy.lock(); 465*b9616d97SJoel Fernandes 466*b9616d97SJoel Fernandes // SAFETY: Per the type invariant, `inner` contains an initialized 467*b9616d97SJoel Fernandes // allocator. `guard` provides exclusive access. 468*b9616d97SJoel Fernandes to_result(unsafe { 469*b9616d97SJoel Fernandes bindings::gpu_buddy_alloc_blocks( 470*b9616d97SJoel Fernandes guard.as_raw(), 471*b9616d97SJoel Fernandes start, 472*b9616d97SJoel Fernandes end, 473*b9616d97SJoel Fernandes size, 474*b9616d97SJoel Fernandes min_block_size.as_usize() as u64, 475*b9616d97SJoel Fernandes list.as_raw(), 476*b9616d97SJoel Fernandes mode_flags | usize::from(modifier_flags), 477*b9616d97SJoel Fernandes ) 478*b9616d97SJoel Fernandes })? 479*b9616d97SJoel Fernandes } 480*b9616d97SJoel Fernandes }) 481*b9616d97SJoel Fernandes } 482*b9616d97SJoel Fernandes } 483*b9616d97SJoel Fernandes 484*b9616d97SJoel Fernandes /// Allocated blocks from the buddy allocator with automatic cleanup. 485*b9616d97SJoel Fernandes /// 486*b9616d97SJoel Fernandes /// This structure owns a list of allocated blocks and ensures they are 487*b9616d97SJoel Fernandes /// automatically freed when dropped. Use `iter()` to iterate over all 488*b9616d97SJoel Fernandes /// allocated blocks. 489*b9616d97SJoel Fernandes /// 490*b9616d97SJoel Fernandes /// # Invariants 491*b9616d97SJoel Fernandes /// 492*b9616d97SJoel Fernandes /// - `list` is an initialized, valid list head containing allocated blocks. 493*b9616d97SJoel Fernandes #[pin_data(PinnedDrop)] 494*b9616d97SJoel Fernandes pub struct AllocatedBlocks { 495*b9616d97SJoel Fernandes #[pin] 496*b9616d97SJoel Fernandes list: CListHead, 497*b9616d97SJoel Fernandes buddy: Arc<GpuBuddyInner>, 498*b9616d97SJoel Fernandes } 499*b9616d97SJoel Fernandes 500*b9616d97SJoel Fernandes impl AllocatedBlocks { 501*b9616d97SJoel Fernandes /// Check if the block list is empty. 502*b9616d97SJoel Fernandes pub fn is_empty(&self) -> bool { 503*b9616d97SJoel Fernandes // An empty list head points to itself. 504*b9616d97SJoel Fernandes !self.list.is_linked() 505*b9616d97SJoel Fernandes } 506*b9616d97SJoel Fernandes 507*b9616d97SJoel Fernandes /// Iterate over allocated blocks. 508*b9616d97SJoel Fernandes /// 509*b9616d97SJoel Fernandes /// Returns an iterator yielding [`AllocatedBlock`] values. Each [`AllocatedBlock`] 510*b9616d97SJoel Fernandes /// borrows `self` and is only valid for the duration of that borrow. 511*b9616d97SJoel Fernandes pub fn iter(&self) -> impl Iterator<Item = AllocatedBlock<'_>> + '_ { 512*b9616d97SJoel Fernandes let head = self.list.as_raw(); 513*b9616d97SJoel Fernandes // SAFETY: Per the type invariant, `list` is an initialized sentinel `list_head` 514*b9616d97SJoel Fernandes // and is not concurrently modified (we hold a `&self` borrow). The list contains 515*b9616d97SJoel Fernandes // `gpu_buddy_block` items linked via `__bindgen_anon_1.link`. `Block` is 516*b9616d97SJoel Fernandes // `#[repr(transparent)]` over `gpu_buddy_block`. 517*b9616d97SJoel Fernandes let clist = unsafe { 518*b9616d97SJoel Fernandes clist_create!( 519*b9616d97SJoel Fernandes head, 520*b9616d97SJoel Fernandes Block, 521*b9616d97SJoel Fernandes bindings::gpu_buddy_block, 522*b9616d97SJoel Fernandes __bindgen_anon_1.link 523*b9616d97SJoel Fernandes ) 524*b9616d97SJoel Fernandes }; 525*b9616d97SJoel Fernandes 526*b9616d97SJoel Fernandes clist 527*b9616d97SJoel Fernandes .iter() 528*b9616d97SJoel Fernandes .map(|this| AllocatedBlock { this, blocks: self }) 529*b9616d97SJoel Fernandes } 530*b9616d97SJoel Fernandes } 531*b9616d97SJoel Fernandes 532*b9616d97SJoel Fernandes #[pinned_drop] 533*b9616d97SJoel Fernandes impl PinnedDrop for AllocatedBlocks { 534*b9616d97SJoel Fernandes fn drop(self: Pin<&mut Self>) { 535*b9616d97SJoel Fernandes let guard = self.buddy.lock(); 536*b9616d97SJoel Fernandes 537*b9616d97SJoel Fernandes // SAFETY: 538*b9616d97SJoel Fernandes // - list is valid per the type's invariants. 539*b9616d97SJoel Fernandes // - guard provides exclusive access to the allocator. 540*b9616d97SJoel Fernandes unsafe { 541*b9616d97SJoel Fernandes bindings::gpu_buddy_free_list(guard.as_raw(), self.list.as_raw(), 0); 542*b9616d97SJoel Fernandes } 543*b9616d97SJoel Fernandes } 544*b9616d97SJoel Fernandes } 545*b9616d97SJoel Fernandes 546*b9616d97SJoel Fernandes /// A GPU buddy block. 547*b9616d97SJoel Fernandes /// 548*b9616d97SJoel Fernandes /// Transparent wrapper over C `gpu_buddy_block` structure. This type is returned 549*b9616d97SJoel Fernandes /// as references during iteration over [`AllocatedBlocks`]. 550*b9616d97SJoel Fernandes /// 551*b9616d97SJoel Fernandes /// # Invariants 552*b9616d97SJoel Fernandes /// 553*b9616d97SJoel Fernandes /// The inner [`Opaque`] contains a valid, allocated `gpu_buddy_block`. 554*b9616d97SJoel Fernandes #[repr(transparent)] 555*b9616d97SJoel Fernandes struct Block(Opaque<bindings::gpu_buddy_block>); 556*b9616d97SJoel Fernandes 557*b9616d97SJoel Fernandes impl Block { 558*b9616d97SJoel Fernandes /// Get a raw pointer to the underlying C block. 559*b9616d97SJoel Fernandes fn as_raw(&self) -> *mut bindings::gpu_buddy_block { 560*b9616d97SJoel Fernandes self.0.get() 561*b9616d97SJoel Fernandes } 562*b9616d97SJoel Fernandes 563*b9616d97SJoel Fernandes /// Get the block's raw offset in the buddy address space (without base offset). 564*b9616d97SJoel Fernandes fn offset(&self) -> u64 { 565*b9616d97SJoel Fernandes // SAFETY: `self.as_raw()` is valid per the type's invariants. 566*b9616d97SJoel Fernandes unsafe { bindings::gpu_buddy_block_offset(self.as_raw()) } 567*b9616d97SJoel Fernandes } 568*b9616d97SJoel Fernandes 569*b9616d97SJoel Fernandes /// Get the block order. 570*b9616d97SJoel Fernandes fn order(&self) -> u32 { 571*b9616d97SJoel Fernandes // SAFETY: `self.as_raw()` is valid per the type's invariants. 572*b9616d97SJoel Fernandes unsafe { bindings::gpu_buddy_block_order(self.as_raw()) } 573*b9616d97SJoel Fernandes } 574*b9616d97SJoel Fernandes } 575*b9616d97SJoel Fernandes 576*b9616d97SJoel Fernandes // SAFETY: `Block` is a wrapper around `gpu_buddy_block` which can be 577*b9616d97SJoel Fernandes // sent across threads safely. 578*b9616d97SJoel Fernandes unsafe impl Send for Block {} 579*b9616d97SJoel Fernandes 580*b9616d97SJoel Fernandes // SAFETY: `Block` is only accessed through shared references after 581*b9616d97SJoel Fernandes // allocation, and thus safe to access concurrently across threads. 582*b9616d97SJoel Fernandes unsafe impl Sync for Block {} 583*b9616d97SJoel Fernandes 584*b9616d97SJoel Fernandes /// A buddy block paired with its owning [`AllocatedBlocks`] context. 585*b9616d97SJoel Fernandes /// 586*b9616d97SJoel Fernandes /// Unlike a raw block, which only knows its offset within the buddy address 587*b9616d97SJoel Fernandes /// space, an [`AllocatedBlock`] also has access to the allocator's `base_offset` 588*b9616d97SJoel Fernandes /// and `chunk_size`, enabling it to compute absolute offsets and byte sizes. 589*b9616d97SJoel Fernandes /// 590*b9616d97SJoel Fernandes /// Returned by [`AllocatedBlocks::iter()`]. 591*b9616d97SJoel Fernandes pub struct AllocatedBlock<'a> { 592*b9616d97SJoel Fernandes this: &'a Block, 593*b9616d97SJoel Fernandes blocks: &'a AllocatedBlocks, 594*b9616d97SJoel Fernandes } 595*b9616d97SJoel Fernandes 596*b9616d97SJoel Fernandes impl AllocatedBlock<'_> { 597*b9616d97SJoel Fernandes /// Get the block's offset in the address space. 598*b9616d97SJoel Fernandes /// 599*b9616d97SJoel Fernandes /// Returns the absolute offset including the allocator's base offset. 600*b9616d97SJoel Fernandes /// This is the actual address to use for accessing the allocated memory. 601*b9616d97SJoel Fernandes pub fn offset(&self) -> u64 { 602*b9616d97SJoel Fernandes self.blocks.buddy.params.base_offset + self.this.offset() 603*b9616d97SJoel Fernandes } 604*b9616d97SJoel Fernandes 605*b9616d97SJoel Fernandes /// Get the block order (size = chunk_size << order). 606*b9616d97SJoel Fernandes pub fn order(&self) -> u32 { 607*b9616d97SJoel Fernandes self.this.order() 608*b9616d97SJoel Fernandes } 609*b9616d97SJoel Fernandes 610*b9616d97SJoel Fernandes /// Get the block's size in bytes. 611*b9616d97SJoel Fernandes pub fn size(&self) -> u64 { 612*b9616d97SJoel Fernandes (self.blocks.buddy.params.chunk_size.as_usize() as u64) << self.this.order() 613*b9616d97SJoel Fernandes } 614*b9616d97SJoel Fernandes } 615