Joel Fernandes
2025-Oct-30 19:06 UTC
[PATCH RFC 3/4] rust: drm: Add DRM buddy allocator bindings
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 at 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
Danilo Krummrich
2025-Oct-30 21:27 UTC
[PATCH RFC 3/4] rust: drm: Add DRM buddy allocator bindings
On Thu Oct 30, 2025 at 8:06 PM CET, Joel Fernandes wrote:> + /// > + /// 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 _); > + }Not a full review, but a quick drive-by comment: bindings::list_head has to be pinned in memory it should be let list_head = KBox::pin_init(Opaque::ffi_init(|slot: *mut bindings::list_head| { // SAFETY: `slot` is a valid pointer to uninitialized memory. unsafe { bindings::INIT_LIST_HEAD(slot) }; }), gfp)?; if you're doing it by hand, but as mentioned in a previous patch, I think it would be nice to have a transparent wrapper type, CListHead, for this.
Alice Ryhl
2025-Oct-31 09:25 UTC
[PATCH RFC 3/4] rust: drm: Add DRM buddy allocator bindings
On Thu, Oct 30, 2025 at 03:06:12PM -0400, Joel Fernandes wrote:> 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 at 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.Usually an invariant is a statement about the present rather than about the past. I would say that `mm` is a valid buddy allocator.> +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(),After this call to drm_buddy_init, you return it which moves the struct. Is the struct safe to move from one location to another? Also I usually put the to_result outside of the unsafe block.> + 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 {}Generally, we should implement both Send and Sync unless we really can't do so. If methods require external synchronization, then those methods should be marked &mut self and then you implement Sync. If you instead omit Sync and make the methods &self, then the caller is severely restricted and can't e.g. store it in an Arc.> +/// 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>, > +}This type is exposed to the user by ownership (as opposed to being exposed behind a reference), and has no lifetime annotation. This implies that the caller is allowed to keep it alive for arbitrary amounts of time. However, it looks like dropping AllocatedBlock would also free this Block object. That is a problem. The ownership of Block should probably be tied to AllocatedBlock so that the borrow-checker prevents dropping AllocatedBlock while Block objects exist. Or this code should be changed so that Block keeps the underlying AllocatedBlock alive using a refcount. Or similar. It depends on how it will be used - if Block is stored long-term in structs, then you should avoid lifetimes, but if it's a view into AllocatedBlock that is not stored long-term, then lifetimes are the right choice.> +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 >
Alexandre Courbot
2025-Nov-01 05:08 UTC
[PATCH RFC 3/4] rust: drm: Add DRM buddy allocator bindings
On Fri Oct 31, 2025 at 4:06 AM JST, Joel Fernandes wrote: <snip>> +/// 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>, > +}not a big deal, but usually such wrapping structures are defined as follows: pub struct DrmBuddy(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(),Isn't `Opaque::uninit` more appropriate here, since `drm_buddy_init` below will overwrite the data? <snip>> +// 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, > +}Isn't the lifetime going to severely restrict how this can be used? For instance, after allocating a list of blocks I suppose you will want to store it somewhere, do some other business, and free it much later in a completely different code path. The lifetime is going to make this very difficult. For instance, try and adapt the unit test in the following patch to allocate some driver object on the heap (representing a bound device), and store both the `DrmBuddy` and the allocated blocks into it. I don't think the borrow checker will let you do that. I think this calls for a reference-counted design instead - this will move lifetime management to runtime, and solve the issue.> + > +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>, > +}This also looks like a good change to use a transparent struct with an opaque. I guess once you adapt the CList design as suggested it will come to this naturally.
Alexandre Courbot
2025-Nov-01 05:19 UTC
[PATCH RFC 3/4] rust: drm: Add DRM buddy allocator bindings
On Fri Oct 31, 2025 at 4:06 AM JST, Joel Fernandes wrote: <snip>> + /// 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<'_>> {It looks like some of the flags heavily modify the behavior of the allocator, and make some of the parameters irrelevant (for instance, IIUC `start` and `end` only make sense if `RANGE_ALLOCATION` is passed?). In that case, we should either have several allocation methods, or have an `AllocationRequest` enum which variants are the allocation type and its relevant parameters. E.g (very naively): enum AllocationRange { Whole, Ranged(Range<u64>), ... } struct AllocationRequest { range: AllocationRange, top_down: bool, contiguous: bool, clear: bool, } I.e. use the type system to make sure we can only express things that make sense, and never pass data that will end up being ignored. If we do that well, I think we can drop the `BuddyFlags` type altogether, which is great as it seems to serve several different purposes.