xref: /linux/rust/kernel/gpu/buddy.rs (revision 4a57e0913e8c7fff407e97909f4ae48caa84d612)
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