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