From mboxrd@z Thu Jan 1 00:00:00 1970 From: Claude Code Review Bot To: dri-devel-reviews@example.com Subject: Claude review: rust: gpu: Add GPU buddy allocator bindings Date: Fri, 13 Feb 2026 06:27:36 +1000 Message-ID: In-Reply-To: <20260210233204.790524-3-joelagnelf@nvidia.com> References: <20260210233204.790524-1-joelagnelf@nvidia.com> <20260210233204.790524-3-joelagnelf@nvidia.com> Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Mailer: Claude Code Patch Reviewer Patch Review > +pub struct BuddyFlags(usize); > + > + pub fn try_new(flags: usize) -> Result { > + // Flags must not exceed u32::MAX to satisfy the GPU buddy allocator C API. > + if flags > u32::MAX as usize { > + return Err(EINVAL); > + } The comment says "satisfy the GPU buddy allocator C API," but looking at the C API, `drm_buddy_alloc_blocks` takes `unsigned long flags` (which on 64-bit is 8 bytes, same as `usize`), while `drm_buddy_free_list` takes `unsigned int flags` (4 bytes). The validation to `u32::MAX` is actually only necessary for the free path. The alloc path could accept the full `unsigned long` range. This is not a bug since it is more restrictive, but the comment is somewhat inaccurate about why. > + pub fn alloc_blocks( > + &self, > + params: &GpuBuddyAllocParams, > + ) -> impl PinInit { > + ... > + try_pin_init!(AllocatedBlocks { > + buddy: buddy_arc, > + list <- CListHead::new(), > + flags: flags, > + _: { > + let guard = buddy.lock(); > + to_result(unsafe { > + bindings::gpu_buddy_alloc_blocks( > + guard.as_raw(), > + start, > + end, > + size, > + min_block_size, > + list.as_raw(), > + flags.as_raw(), > + ) > + })? > + } > + }) > + } The `try_pin_init!` pattern with `_:` initializer block is interesting - it runs the allocation after the list and buddy are already initialized. If `gpu_buddy_alloc_blocks` fails, the `try_pin_init!` should properly drop the already-initialized fields. The `list` will have `INIT_LIST_HEAD` called on it, and the `buddy` `Arc` clone will be dropped. This looks correct. One concern: `flags.as_raw()` returns `usize`, but the C function `gpu_buddy_alloc_blocks` takes `unsigned long flags`. On 64-bit Linux, `usize` and `unsigned long` are both 64-bit, so this is fine. But for the free path: > +#[pinned_drop] > +impl PinnedDrop for AllocatedBlocks { > + fn drop(self: Pin<&mut Self>) { > + let guard = self.buddy.lock(); > + unsafe { > + bindings::gpu_buddy_free_list( > + guard.as_raw(), > + self.list.as_raw(), > + self.flags.as_raw() as u32, > + ); > + } > + } > +} The `as u32` cast is safe because `try_new()` validated flags fit in u32. No issue here. > +impl AllocatedBlock<'_> { > + pub fn size(&self) -> u64 { > + self.alloc.buddy.chunk_size << self.block.order() > + } > +} This mirrors the C implementation `mm->chunk_size << drm_buddy_block_order(block)`. The `order()` returns `u32`. Shifting a `u64` left by a `u32` - if `order()` returned a value >= 64, this would be undefined behavior in C but would panic in Rust debug builds or produce 0 in release builds. However, since the buddy allocator constrains orders based on the address space size and chunk size, and `DRM_BUDDY_HEADER_ORDER` is masked to 6 bits (max 63), values above 63 should not occur in practice. For a 1GB allocator with 4KB chunk size, max order would be 18. This is not a real concern. > +#[pinned_drop] > +impl PinnedDrop for GpuBuddyInner { > + fn drop(self: Pin<&mut Self>) { > + let guard = self.lock(); > + unsafe { > + bindings::gpu_buddy_fini(guard.as_raw()); > + } > + } > +} This calls `lock()` on `self` inside `drop`. Since `drop` takes `Pin<&mut Self>`, and `lock()` takes `&self`, this works via auto-deref. The mutex lock is acquired to serialize with any concurrent `AllocatedBlocks` drops that might be running on other threads. However - if this is the last reference to the `Arc` (which it must be, since `drop` is running), then no other thread can hold a reference, meaning there cannot be concurrent `AllocatedBlocks` drops. So the lock acquisition here is technically unnecessary but harmless - it provides defense-in-depth. > + fn lock(&self) -> GpuBuddyGuard<'_> { > + GpuBuddyGuard { > + inner: self, > + _guard: self.lock.lock(), > + } > + } Wait - there is a potential issue here. The method is named `lock` and the field is also named `lock`. `self.lock` refers to the field (type `Mutex<()>`), and `self.lock.lock()` calls `Mutex::lock()` on that field. This is not ambiguous because Rust resolves method calls on the concrete type, but it is confusing to read. Not a bug, just a naming observation. The `rust_helper_gpu_buddy_block_offset` helper takes `const struct gpu_buddy_block *block`, but the Rust `Block::offset()` method calls it with `self.as_raw()` which returns `*mut`. Passing `*mut` where `*const` is expected is fine in C (implicit conversion), and bindgen should handle this correctly. No issue. Overall this is a solid set of bindings. The synchronization model is sound, the lifetimes are correctly constrained, and the cleanup paths are properly handled. --- Generated by Claude Code Patch Reviewer