[PATCH RFC 3/4] rust: drm: Add DRM buddy allocator bindings

Joel Fernandes
2025-10-30T19:06:41Z

Add safe Rust abstractions over the Linux kernel's DRM buddy
allocator for physical memory management. The DRM buddy allocator
implements a binary buddy system for useful for GPU physical memory
allocation. nova-core will use it for physical memory allocation.

Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
---
rust/bindings/bindings_helper.h | 11 +
rust/helpers/drm.c | 23 +-
rust/kernel/drm/buddy.rs | 357 ++++++++++++++++++++++++++++++++
rust/kernel/drm/mod.rs | 1 +
4 files changed, 391 insertions(+), 1 deletion(-)
create mode 100644 rust/kernel/drm/buddy.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 6b973589a546..747d4c7ef935 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -29,6 +29,7 @@
#include <linux/hrtimer_types.h>

#include <linux/acpi.h>
+#include <drm/drm_buddy.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
@@ -112,6 +113,16 @@ const gfp_t RUST_CONST_HELPER_XA_FLAGS_ALLOC = XA_FLAGS_ALLOC;
const gfp_t RUST_CONST_HELPER_XA_FLAGS_ALLOC1 = XA_FLAGS_ALLOC1;
const vm_flags_t RUST_CONST_HELPER_VM_MERGEABLE = VM_MERGEABLE;

+#if IS_ENABLED(CONFIG_DRM_BUDDY)
+const unsigned long RUST_CONST_HELPER_DRM_BUDDY_RANGE_ALLOCATION = DRM_BUDDY_RANGE_ALLOCATION;
+const unsigned long RUST_CONST_HELPER_DRM_BUDDY_TOPDOWN_ALLOCATION = DRM_BUDDY_TOPDOWN_ALLOCATION;
+const unsigned long RUST_CONST_HELPER_DRM_BUDDY_CONTIGUOUS_ALLOCATION =
+ DRM_BUDDY_CONTIGUOUS_ALLOCATION;
+const unsigned long RUST_CONST_HELPER_DRM_BUDDY_CLEAR_ALLOCATION = DRM_BUDDY_CLEAR_ALLOCATION;
+const unsigned long RUST_CONST_HELPER_DRM_BUDDY_CLEARED = DRM_BUDDY_CLEARED;
+const unsigned long RUST_CONST_HELPER_DRM_BUDDY_TRIM_DISABLE = DRM_BUDDY_TRIM_DISABLE;
+#endif
+
#if IS_ENABLED(CONFIG_ANDROID_BINDER_IPC_RUST)
#include "../../drivers/android/binder/rust_binder.h"
#include "../../drivers/android/binder/rust_binder_events.h"
diff --git a/rust/helpers/drm.c b/rust/helpers/drm.c
index 450b406c6f27..bd8748ade3f5 100644
--- a/rust/helpers/drm.c
+++ b/rust/helpers/drm.c
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0

+#include <drm/drm_buddy.h>
#include <drm/drm_gem.h>
#include <drm/drm_vma_manager.h>

@@ -20,4 +21,24 @@ __u64 rust_helper_drm_vma_node_offset_addr(struct drm_vma_offset_node *node)
return drm_vma_node_offset_addr(node);
}

-#endif
+#ifdef CONFIG_DRM_BUDDY
+
+u64 rust_helper_drm_buddy_block_offset(const struct drm_buddy_block *block)
+{
+ return drm_buddy_block_offset(block);
+}
+
+unsigned int rust_helper_drm_buddy_block_order(struct drm_buddy_block *block)
+{
+ return drm_buddy_block_order(block);
+}
+
+u64 rust_helper_drm_buddy_block_size(struct drm_buddy *mm,
+ struct drm_buddy_block *block)
+{
+ return drm_buddy_block_size(mm, block);
+}
+
+#endif /* CONFIG_DRM_BUDDY */
+
+#endif /* CONFIG_DRM */
diff --git a/rust/kernel/drm/buddy.rs b/rust/kernel/drm/buddy.rs
new file mode 100644
index 000000000000..b1cd23f81838
--- /dev/null
+++ b/rust/kernel/drm/buddy.rs
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! DRM buddy allocator bindings.
+//!
+//! C header: [`include/drm/drm_buddy.h`](srctree/include/drm/drm_buddy.h)
+//!
+//! This module provides Rust abstractions over the Linux kernel's DRM buddy
+//! allocator, which implements a binary buddy memory allocation system.
+//!
+//! The buddy allocator manages a contiguous address space and allocates blocks
+//! in power-of-two sizes. It's commonly used for physical memory management.
+//!
+//! # Examples
+//!
+//! ```ignore
+//! use kernel::{
+//! drm::buddy::{BuddyFlags, DrmBuddy},
+//! prelude::*,
+//! sizes::*, //
+//! };
+//!
+//! let buddy = DrmBuddy::new(SZ_1G, SZ_4K)?;
+//! let allocated = buddy.alloc_blocks(
+//! 0, 0, SZ_16M, SZ_4K,
+//! BuddyFlags::RANGE_ALLOCATION,
+//! GFP_KERNEL,
+//! )?;
+//!
+//! for block in &allocated {
+//! // Use block.
+//! }
+//! // Blocks are automatically freed when `allocated` goes out of scope.
+//! ```
+
+use crate::{
+ alloc::Flags,
+ bindings,
+ clist,
+ container_of,
+ error::{
+ to_result,
+ Result, //
+ },
+ prelude::KBox,
+ types::Opaque, //
+};
+use core::ptr::NonNull;
+
+/// Flags for DRM buddy allocator operations.
+///
+/// These flags control the allocation behavior of the buddy allocator.
+#[derive(Clone, Copy, PartialEq)]
+pub struct BuddyFlags(u64);
+
+impl BuddyFlags {
+ /// Range-based allocation from start to end addresses.
+ pub const RANGE_ALLOCATION: BuddyFlags =
+ BuddyFlags(bindings::DRM_BUDDY_RANGE_ALLOCATION as u64);
+
+ /// Allocate from top of address space downward.
+ pub const TOPDOWN_ALLOCATION: BuddyFlags =
+ BuddyFlags(bindings::DRM_BUDDY_TOPDOWN_ALLOCATION as u64);
+
+ /// Allocate physically contiguous blocks.
+ pub const CONTIGUOUS_ALLOCATION: BuddyFlags =
+ BuddyFlags(bindings::DRM_BUDDY_CONTIGUOUS_ALLOCATION as u64);
+
+ /// Clear allocated blocks (zero them).
+ pub const CLEAR_ALLOCATION: BuddyFlags =
+ BuddyFlags(bindings::DRM_BUDDY_CLEAR_ALLOCATION as u64);
+
+ /// Block has been cleared - internal flag.
+ pub const CLEARED: BuddyFlags = BuddyFlags(bindings::DRM_BUDDY_CLEARED as u64);
+
+ /// Disable trimming of partially used blocks.
+ pub const TRIM_DISABLE: BuddyFlags = BuddyFlags(bindings::DRM_BUDDY_TRIM_DISABLE as u64);
+
+ /// Get raw value for FFI.
+ pub(crate) fn as_raw(self) -> u64 {
+ self.0
+ }
+}
+
+impl core::ops::BitOr for BuddyFlags {
+ type Output = Self;
+
+ fn bitor(self, rhs: Self) -> Self::Output {
+ Self(self.0 | rhs.0)
+ }
+}
+
+/// DRM buddy allocator instance.
+///
+/// This structure wraps the C `drm_buddy` allocator.
+///
+/// # Safety
+///
+/// Not thread-safe. Concurrent alloc/free operations require external
+/// synchronization (e.g., wrapping in `Arc<Mutex<DrmBuddy>>`).
+///
+/// # Invariants
+///
+/// - `mm` is initialized via `drm_buddy_init()` and remains valid until Drop.
+pub struct DrmBuddy {
+ mm: Opaque<bindings::drm_buddy>,
+}
+
+impl DrmBuddy {
+ /// Create a new buddy allocator.
+ ///
+ /// Creates a buddy allocator that manages a contiguous address space of the given
+ /// size, with the specified minimum allocation unit (chunk_size must be at least 4KB).
+ ///
+ /// # Examples
+ ///
+ /// See the complete example in the documentation comments for [`AllocatedBlocks`].
+ pub fn new(size: usize, chunk_size: usize) -> Result<Self> {
+ // Create buddy allocator with zeroed memory.
+ let buddy = Self {
+ mm: Opaque::zeroed(),
+ };
+
+ // Initialize the C buddy structure.
+ // SAFETY: buddy.mm points to valid, zeroed memory.
+ unsafe {
+ to_result(bindings::drm_buddy_init(
+ buddy.mm.get(),
+ size as u64,
+ chunk_size as u64,
+ ))?;
+ }
+
+ Ok(buddy)
+ }
+
+ /// Get a raw pointer to the underlying C drm_buddy structure.
+ ///
+ /// # Safety
+ ///
+ /// Caller must ensure the returned pointer is not used after this
+ /// structure is dropped.
+ pub unsafe fn as_raw(&self) -> *mut bindings::drm_buddy {
+ self.mm.get()
+ }
+
+ /// Get the chunk size (minimum allocation unit).
+ pub fn chunk_size(&self) -> u64 {
+ // SAFETY: mm is initialized and valid per struct invariant.
+ unsafe { (*self.as_raw()).chunk_size }
+ }
+
+ /// Get the total managed size.
+ pub fn size(&self) -> u64 {
+ // SAFETY: mm is initialized and valid per struct invariant.
+ unsafe { (*self.as_raw()).size }
+ }
+
+ /// Get the available (free) memory.
+ pub fn avail(&self) -> u64 {
+ // SAFETY: mm is initialized and valid per struct invariant.
+ unsafe { (*self.as_raw()).avail }
+ }
+
+ /// Allocate blocks from the buddy allocator.
+ ///
+ /// Returns an [`AllocatedBlocks`] structure that owns the allocated blocks and automatically
+ /// frees them when dropped. Allocation of `list_head` uses the `gfp` flags passed.
+ pub fn alloc_blocks(
+ &self,
+ start: usize,
+ end: usize,
+ size: usize,
+ min_block_size: usize,
+ flags: BuddyFlags,
+ gfp: Flags,
+ ) -> Result<AllocatedBlocks<'_>> {
+ // Allocate list_head on the heap.
+ let mut list_head = KBox::new(bindings::list_head::default(), gfp)?;
+
+ // SAFETY: list_head is valid and heap-allocated.
+ unsafe {
+ bindings::INIT_LIST_HEAD(&mut *list_head as *mut _);
+ }
+
+ // SAFETY: mm is a valid DrmBuddy object per the type's invariants.
+ unsafe {
+ to_result(bindings::drm_buddy_alloc_blocks(
+ self.as_raw(),
+ start as u64,
+ end as u64,
+ size as u64,
+ min_block_size as u64,
+ &mut *list_head as *mut _,
+ flags.as_raw() as usize,
+ ))?;
+ }
+
+ // `list_head` is now the head of a list that contains allocated blocks
+ // from C code. The allocated blocks will be automatically freed when
+ // `AllocatedBlocks` is dropped.
+ Ok(AllocatedBlocks {
+ list_head,
+ buddy: self,
+ })
+ }
+}
+
+impl Drop for DrmBuddy {
+ fn drop(&mut self) {
+ // SAFETY: self.mm is initialized and valid. drm_buddy_fini properly
+ // cleans up all resources. This is called exactly once during Drop.
+ unsafe {
+ bindings::drm_buddy_fini(self.as_raw());
+ }
+ }
+}
+
+// SAFETY: DrmBuddy can be sent between threads. Caller is responsible for
+// ensuring thread-safe access if needed (e.g., via Mutex).
+unsafe impl Send for DrmBuddy {}
+
+/// 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. Blocks may be iterated over and are
+/// read-only after allocation (iteration via [`IntoIterator`] and
+/// automatic cleanup via [`Drop`] only). To share across threads, wrap
+/// in `Arc<AllocatedBlocks>`. Rust owns the head list head of the
+/// allocated blocks; C allocates blocks and links them to the head
+/// list head. Clean up of the allocated blocks is handled by C code.
+///
+/// # Invariants
+///
+/// - `list_head` is an owned, valid, initialized list_head.
+/// - `buddy` points to a valid, initialized [`DrmBuddy`].
+pub struct AllocatedBlocks<'a> {
+ list_head: KBox<bindings::list_head>,
+ buddy: &'a DrmBuddy,
+}
+
+impl Drop for AllocatedBlocks<'_> {
+ fn drop(&mut self) {
+ // Free all blocks automatically when dropped.
+ // SAFETY: list_head is a valid list of blocks per the type's invariants.
+ unsafe {
+ bindings::drm_buddy_free_list(self.buddy.as_raw(), &mut *self.list_head as *mut _, 0);
+ }
+ }
+}
+
+impl<'a> AllocatedBlocks<'a> {
+ /// Check if the block list is empty.
+ pub fn is_empty(&self) -> bool {
+ // SAFETY: list_head is a valid list of blocks per the type's invariants.
+ unsafe { clist::list_empty(&*self.list_head as *const _) }
+ }
+
+ /// Iterate over allocated blocks.
+ pub fn iter(&self) -> clist::ClistIter<'_, Block> {
+ // SAFETY: list_head is a valid list of blocks per the type's invariants.
+ clist::iter_list_head::<Block>(&*self.list_head)
+ }
+}
+
+/// Iteration support for allocated blocks.
+///
+/// # Examples
+///
+/// ```ignore
+/// for block in &allocated_blocks {
+/// // Use block.
+/// }
+/// ```
+impl<'a> IntoIterator for &'a AllocatedBlocks<'_> {
+ type Item = Block;
+ type IntoIter = clist::ClistIter<'a, Block>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+}
+
+/// A DRM buddy block.
+///
+/// Wraps a pointer to a C `drm_buddy_block` structure. This is returned
+/// from allocation operations and used to free blocks.
+///
+/// # Invariants
+///
+/// `drm_buddy_block_ptr` points to a valid `drm_buddy_block` managed by the buddy allocator.
+pub struct Block {
+ drm_buddy_block_ptr: NonNull<bindings::drm_buddy_block>,
+}
+
+impl Block {
+ /// Get the block's offset in the address space.
+ pub fn offset(&self) -> u64 {
+ // SAFETY: drm_buddy_block_ptr is valid per the type's invariants.
+ unsafe { bindings::drm_buddy_block_offset(self.drm_buddy_block_ptr.as_ptr()) }
+ }
+
+ /// Get the block order (size = chunk_size << order).
+ pub fn order(&self) -> u32 {
+ // SAFETY: drm_buddy_block_ptr is valid per the type's invariants.
+ unsafe { bindings::drm_buddy_block_order(self.drm_buddy_block_ptr.as_ptr()) }
+ }
+
+ /// Get the block's size in bytes.
+ ///
+ /// Requires the buddy allocator to calculate size from order.
+ pub fn size(&self, buddy: &DrmBuddy) -> u64 {
+ // SAFETY: Both pointers are valid per the type's invariants.
+ unsafe { bindings::drm_buddy_block_size(buddy.as_raw(), self.drm_buddy_block_ptr.as_ptr()) }
+ }
+
+ /// Get a raw pointer to the underlying C block.
+ ///
+ /// # Safety
+ ///
+ /// Caller must ensure the pointer is not used after the block is freed.
+ pub unsafe fn as_ptr(&self) -> *mut bindings::drm_buddy_block {
+ self.drm_buddy_block_ptr.as_ptr()
+ }
+}
+
+impl clist::FromListHead for Block {
+ unsafe fn from_list_head(link: *const bindings::list_head) -> Self {
+ // SAFETY: link points to a valid list_head embedded in drm_buddy_block.
+ // The container_of macro calculates the containing struct pointer.
+ // We need to account for the union field __bindgen_anon_1.link.
+ //
+ // The link is embedded in a union within drm_buddy_block:
+ // struct drm_buddy_block {
+ // [...]
+ // union {
+ // struct rb_node rb;
+ // struct list_head link;
+ // };
+ // }
+ //
+ // This is why we perform a double container_of calculation: first to get
+ // to the union, then to get to the containing drm_buddy_block.
+ unsafe {
+ // First get to the union.
+ let union_ptr = container_of!(link, bindings::drm_buddy_block__bindgen_ty_1, link);
+ // Then get to the containing drm_buddy_block.
+ let block_ptr =
+ container_of!(union_ptr, bindings::drm_buddy_block, __bindgen_anon_1) as *mut _;
+ Block {
+ drm_buddy_block_ptr: NonNull::new_unchecked(block_ptr),
+ }
+ }
+ }
+}
+
+// SAFETY: Block is just a pointer wrapper and can be safely sent between threads.
+unsafe impl Send for Block {}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index 1b82b6945edf..ee173729eac5 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -2,6 +2,7 @@

//! DRM subsystem abstractions.

+pub mod buddy;
pub mod device;
pub mod driver;
pub mod file;
--
2.34.1