From patchwork Tue Mar 25 23:54:34 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Danilo Krummrich X-Patchwork-Id: 14029657 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 45211C36005 for ; Tue, 25 Mar 2025 23:56:32 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id AB6FD10E637; Tue, 25 Mar 2025 23:56:31 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=kernel.org header.i=@kernel.org header.b="BAIIM2l8"; dkim-atps=neutral Received: from sea.source.kernel.org (sea.source.kernel.org [172.234.252.31]) by gabe.freedesktop.org (Postfix) with ESMTPS id 9F4D110E637 for ; Tue, 25 Mar 2025 23:56:28 +0000 (UTC) Received: from smtp.kernel.org (transwarp.subspace.kernel.org [100.75.92.58]) by sea.source.kernel.org (Postfix) with ESMTP id 3372C438E2; Tue, 25 Mar 2025 23:56:28 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id 4DA72C4CEE4; Tue, 25 Mar 2025 23:56:24 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1742946988; bh=Unbns6cM6thPK50Y8xATZc4QBrp1iCJlmxfWp5AjRDg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BAIIM2l8e7gL7JTKpK/K7AxmGYyfYJSNU6FPmKQgR8XMsGhP1dOw5PyKmrXXusFSP fWEuNA65c2ehHqbLOALMkvxAmcn3vBfscNMHz/JqPs8JeNJ0Q4gjXSoYpQeAakKcI9 fJcZ8lDMl+nelsOy2RGKyAdJ2f0u9r+eDXNACSa6PYqU3bLET2+9SCq0FgcdAxrrWN 1sIWDIL4n1m8qoc/j0thN8jI2HdTFVKQNixjIH+ICoM3JZZBIwQ9wmk5hNp4JQOlnx DbMrjx2FiE9nzblD0sGjenGDrMHCIX+N1ZzqJyLNKqeG1xvH0xbqICNUgymi4Nr/zO GA0d4/40icYQA== From: Danilo Krummrich To: airlied@gmail.com, simona@ffwll.ch, maarten.lankhorst@linux.intel.com, mripard@kernel.org, tzimmermann@suse.de, lyude@redhat.com, acurrid@nvidia.com, lina@asahilina.net, daniel.almeida@collabora.com, j@jannau.net Cc: ojeda@kernel.org, alex.gaynor@gmail.com, boqun.feng@gmail.com, gary@garyguo.net, bjorn3_gh@protonmail.com, benno.lossin@proton.me, a.hindborg@kernel.org, aliceryhl@google.com, tmgross@umich.edu, dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, Danilo Krummrich Subject: [PATCH 7/8] rust: drm: gem: Add GEM object abstraction Date: Wed, 26 Mar 2025 00:54:34 +0100 Message-ID: <20250325235522.3992-8-dakr@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20250325235522.3992-1-dakr@kernel.org> References: <20250325235522.3992-1-dakr@kernel.org> MIME-Version: 1.0 X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" DRM GEM is the DRM memory management subsystem used by most modern drivers; add a Rust abstraction for DRM GEM. This includes the BaseObject trait, which contains operations shared by all GEM object classes. Co-developed-by: Asahi Lina Signed-off-by: Asahi Lina Signed-off-by: Danilo Krummrich --- rust/bindings/bindings_helper.h | 2 + rust/helpers/drm.c | 19 ++ rust/helpers/helpers.c | 1 + rust/kernel/drm/device.rs | 4 +- rust/kernel/drm/driver.rs | 2 +- rust/kernel/drm/gem/mod.rs | 321 ++++++++++++++++++++++++++++++++ rust/kernel/drm/mod.rs | 1 + 7 files changed, 348 insertions(+), 2 deletions(-) create mode 100644 rust/helpers/drm.c create mode 100644 rust/kernel/drm/gem/mod.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 8b268c6c52df..e6020ba5b002 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -53,3 +54,4 @@ const gfp_t RUST_CONST_HELPER___GFP_ZERO = __GFP_ZERO; const gfp_t RUST_CONST_HELPER___GFP_HIGHMEM = ___GFP_HIGHMEM; const gfp_t RUST_CONST_HELPER___GFP_NOWARN = ___GFP_NOWARN; const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL = BLK_FEAT_ROTATIONAL; +const fop_flags_t RUST_CONST_HELPER_FOP_UNSIGNED_OFFSET = FOP_UNSIGNED_OFFSET; diff --git a/rust/helpers/drm.c b/rust/helpers/drm.c new file mode 100644 index 000000000000..0c8f7200d29e --- /dev/null +++ b/rust/helpers/drm.c @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include + +void rust_helper_drm_gem_object_get(struct drm_gem_object *obj) +{ + drm_gem_object_get(obj); +} + +void rust_helper_drm_gem_object_put(struct drm_gem_object *obj) +{ + drm_gem_object_put(obj); +} + +__u64 rust_helper_drm_vma_node_offset_addr(struct drm_vma_offset_node *node) +{ + return drm_vma_node_offset_addr(node); +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index d744af85e3b2..7a06d6bc4853 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -14,6 +14,7 @@ #include "build_bug.c" #include "cred.c" #include "device.c" +#include "drm.c" #include "err.c" #include "fs.c" #include "io.c" diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index f7d7abf83fa4..c5a279e63010 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -84,9 +84,11 @@ impl Device { driver_features: drm::driver::FEAT_GEM, ioctls: T::IOCTLS.as_ptr(), num_ioctls: T::IOCTLS.len() as i32, - fops: core::ptr::null_mut() as _, + fops: &Self::GEM_FOPS as _, }; + const GEM_FOPS: bindings::file_operations = drm::gem::create_fops(); + /// Create a new `drm::Device` for a `drm::Driver`. pub fn new(dev: &device::Device, data: impl PinInit) -> Result> { // SAFETY: diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs index 9840302de06a..8a571845dad0 100644 --- a/rust/kernel/drm/driver.rs +++ b/rust/kernel/drm/driver.rs @@ -117,7 +117,7 @@ pub struct AllocOps { } /// Trait for memory manager implementations. Implemented internally. -pub trait AllocImpl: super::private::Sealed { +pub trait AllocImpl: super::private::Sealed + drm::gem::IntoGEMObject { /// The C callback operations for this memory manager. const ALLOC_OPS: AllocOps; } diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs new file mode 100644 index 000000000000..ec2cdbe79b0e --- /dev/null +++ b/rust/kernel/drm/gem/mod.rs @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +//! DRM GEM API +//! +//! C header: [`include/linux/drm/drm_gem.h`](srctree/include/linux/drm/drm_gem.h) + +use crate::{ + alloc::flags::*, + bindings, drm, + drm::driver::{AllocImpl, AllocOps}, + error::{to_result, Result}, + prelude::*, + types::{ARef, Opaque}, +}; +use core::{mem, ops::Deref, ptr}; + +/// GEM object functions, which must be implemented by drivers. +pub trait BaseDriverObject: Sync + Send + Sized { + /// Create a new driver data object for a GEM object of a given size. + fn new(dev: &drm::Device, size: usize) -> impl PinInit; + + /// Open a new handle to an existing object, associated with a File. + fn open( + _obj: &<::Driver as drm::Driver>::Object, + _file: &drm::File<<::Driver as drm::Driver>::File>, + ) -> Result { + Ok(()) + } + + /// Close a handle to an existing object, associated with a File. + fn close( + _obj: &<::Driver as drm::Driver>::Object, + _file: &drm::File<<::Driver as drm::Driver>::File>, + ) { + } +} + +/// Trait that represents a GEM object subtype +pub trait IntoGEMObject: Sized + super::private::Sealed { + /// Owning driver for this type + type Driver: drm::Driver; + + /// Returns a reference to the raw `drm_gem_object` structure, which must be valid as long as + /// this owning object is valid. + #[allow(clippy::wrong_self_convention)] + fn into_gem_obj(&self) -> &Opaque; + + /// Converts a pointer to a `struct drm_gem_object` into a pointer to `Self`. + fn from_gem_obj(obj: *mut bindings::drm_gem_object) -> *mut Self; +} + +/// Trait which must be implemented by drivers using base GEM objects. +pub trait DriverObject: BaseDriverObject> { + /// Parent `Driver` for this object. + type Driver: drm::Driver; +} + +extern "C" fn open_callback, U: BaseObject>( + raw_obj: *mut bindings::drm_gem_object, + raw_file: *mut bindings::drm_file, +) -> core::ffi::c_int { + // SAFETY: `open_callback` is only ever called with a valid pointer to a `struct drm_file`. + let file = unsafe { + drm::File::<<::Driver as drm::Driver>::File>::as_ref(raw_file) + }; + let obj = + <<::Driver as drm::Driver>::Object as IntoGEMObject>::from_gem_obj( + raw_obj, + ); + + // SAFETY: `from_gem_obj()` returns a valid pointer as long as the type is correct and the + // `raw_obj` we got is valid. + match T::open(unsafe { &*obj }, file) { + Err(e) => e.to_errno(), + Ok(()) => 0, + } +} + +extern "C" fn close_callback, U: BaseObject>( + raw_obj: *mut bindings::drm_gem_object, + raw_file: *mut bindings::drm_file, +) { + // SAFETY: `open_callback` is only ever called with a valid pointer to a `struct drm_file`. + let file = unsafe { + drm::File::<<::Driver as drm::Driver>::File>::as_ref(raw_file) + }; + let obj = + <<::Driver as drm::Driver>::Object as IntoGEMObject>::from_gem_obj( + raw_obj, + ); + + // SAFETY: `from_gem_obj()` returns a valid pointer as long as the type is correct and the + // `raw_obj` we got is valid. + T::close(unsafe { &*obj }, file); +} + +impl IntoGEMObject for Object { + type Driver = T::Driver; + + fn into_gem_obj(&self) -> &Opaque { + &self.obj + } + + fn from_gem_obj(obj: *mut bindings::drm_gem_object) -> *mut Self { + // SAFETY: All of our objects are Object. + unsafe { crate::container_of!(obj, Object, obj).cast_mut() } + } +} + +/// Base operations shared by all GEM object classes +pub trait BaseObject +where + Self: crate::types::AlwaysRefCounted + IntoGEMObject, +{ + /// Returns the size of the object in bytes. + fn size(&self) -> usize { + // SAFETY: `self.into_gem_obj()` is guaranteed to be a pointer to a valid `struct + // drm_gem_object`. + unsafe { (*self.into_gem_obj().get()).size } + } + + /// Creates a new handle for the object associated with a given `File` + /// (or returns an existing one). + fn create_handle( + &self, + file: &drm::File<<::Driver as drm::Driver>::File>, + ) -> Result { + let mut handle: u32 = 0; + // SAFETY: The arguments are all valid per the type invariants. + to_result(unsafe { + bindings::drm_gem_handle_create( + file.as_raw().cast(), + self.into_gem_obj().get(), + &mut handle, + ) + })?; + Ok(handle) + } + + /// Looks up an object by its handle for a given `File`. + fn lookup_handle( + file: &drm::File<<::Driver as drm::Driver>::File>, + handle: u32, + ) -> Result> { + // SAFETY: The arguments are all valid per the type invariants. + let ptr = unsafe { bindings::drm_gem_object_lookup(file.as_raw().cast(), handle) }; + let ptr = ::from_gem_obj(ptr); + let ptr = ptr::NonNull::new(ptr).ok_or(ENOENT)?; + + // SAFETY: We take ownership of the reference of `drm_gem_object_lookup()`. + Ok(unsafe { ARef::from_raw(ptr) }) + } + + /// Creates an mmap offset to map the object from userspace. + fn create_mmap_offset(&self) -> Result { + // SAFETY: The arguments are valid per the type invariant. + to_result(unsafe { bindings::drm_gem_create_mmap_offset(self.into_gem_obj().get()) })?; + + // SAFETY: The arguments are valid per the type invariant. + Ok(unsafe { + bindings::drm_vma_node_offset_addr(ptr::addr_of_mut!( + (*self.into_gem_obj().get()).vma_node + )) + }) + } +} + +impl BaseObject for T where Self: crate::types::AlwaysRefCounted + IntoGEMObject {} + +/// A base GEM object. +/// +/// Invariants +/// +/// `self.dev` is always a valid pointer to a `struct drm_device`. +#[repr(C)] +#[pin_data] +pub struct Object { + obj: Opaque, + dev: ptr::NonNull, + #[pin] + data: T, +} + +impl Object { + /// The size of this object's structure. + pub const SIZE: usize = mem::size_of::(); + + const OBJECT_FUNCS: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs { + free: Some(Self::free_callback), + open: Some(open_callback::>), + close: Some(close_callback::>), + print_info: None, + export: None, + pin: None, + unpin: None, + get_sg_table: None, + vmap: None, + vunmap: None, + mmap: None, + status: None, + vm_ops: core::ptr::null_mut(), + evict: None, + rss: None, + }; + + /// Create a new GEM object. + pub fn new(dev: &drm::Device, size: usize) -> Result> { + let obj: Pin> = KBox::pin_init( + try_pin_init!(Self { + obj: Opaque::zeroed(), + data <- T::new(dev, size), + // INVARIANT: The drm subsystem guarantees that the `struct drm_device` will live + // as long as the GEM object lives. + // + // SAFETY: By the type invariants of `drm::Device`, `dev.as_raw()` must be valid. + dev: unsafe { ptr::NonNull::new_unchecked(dev.as_raw()) }, + }), + GFP_KERNEL, + )?; + + // SAFETY: `obj.as_raw()` is guaranteed to be valid by the initialization above. + unsafe { (*obj.as_raw()).funcs = &Self::OBJECT_FUNCS }; + + // SAFETY: The arguments are all valid per the type invariants. + to_result(unsafe { bindings::drm_gem_object_init(dev.as_raw(), obj.obj.get(), size) })?; + + // SAFETY: We never move out of `Self`. + let ptr = KBox::into_raw(unsafe { Pin::into_inner_unchecked(obj) }); + + // SAFETY: `ptr` comes from `KBox::into_raw` and hence can't be NULL. + let ptr = unsafe { ptr::NonNull::new_unchecked(ptr) }; + + // SAFETY: We take over the initial reference count from `drm_gem_object_init()`. + Ok(unsafe { ARef::from_raw(ptr) }) + } + + /// Returns the `Device` that owns this GEM object. + pub fn dev(&self) -> &drm::Device { + // SAFETY: The DRM subsystem guarantees that the `struct drm_device` will live as long as + // the GEM object lives, so we can just borrow from the raw pointer. + unsafe { drm::Device::as_ref(self.dev.as_ptr()) } + } + + fn as_raw(&self) -> *mut bindings::drm_gem_object { + self.obj.get() + } + + extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) { + // SAFETY: All of our objects are of type `Object`. + let this = unsafe { crate::container_of!(obj, Self, obj) }.cast_mut(); + + // SAFETY: The C code only ever calls this callback with a valid pointer to a `struct + // drm_gem_object`. + unsafe { bindings::drm_gem_object_release(obj) }; + + // SAFETY: All of our objects are allocated via `KBox`, and we're in the + // free callback which guarantees this object has zero remaining references, + // so we can drop it. + let _ = unsafe { KBox::from_raw(this) }; + } +} + +// SAFETY: Instances of `Object` are always reference-counted. +unsafe impl crate::types::AlwaysRefCounted for Object { + fn inc_ref(&self) { + // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero. + unsafe { bindings::drm_gem_object_get(self.as_raw()) }; + } + + unsafe fn dec_ref(obj: ptr::NonNull) { + // SAFETY: `obj` is a valid pointer to an `Object`. + let obj = unsafe { obj.as_ref() }; + + // SAFETY: The safety requirements guarantee that the refcount is non-zero. + unsafe { bindings::drm_gem_object_put(obj.as_raw()) } + } +} + +impl super::private::Sealed for Object {} + +impl Deref for Object { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +impl AllocImpl for Object { + const ALLOC_OPS: AllocOps = AllocOps { + gem_create_object: None, + prime_handle_to_fd: None, + prime_fd_to_handle: None, + gem_prime_import: None, + gem_prime_import_sg_table: None, + dumb_create: None, + dumb_map_offset: None, + }; +} + +pub(super) const fn create_fops() -> bindings::file_operations { + // SAFETY: As by the type invariant, it is safe to initialize `bindings::file_operations` + // zeroed. + let mut fops: bindings::file_operations = unsafe { core::mem::zeroed() }; + + fops.owner = core::ptr::null_mut(); + fops.open = Some(bindings::drm_open); + fops.release = Some(bindings::drm_release); + fops.unlocked_ioctl = Some(bindings::drm_ioctl); + #[cfg(CONFIG_COMPAT)] + { + fops.compat_ioctl = Some(bindings::drm_compat_ioctl); + } + fops.poll = Some(bindings::drm_poll); + fops.read = Some(bindings::drm_read); + fops.llseek = Some(bindings::noop_llseek); + fops.mmap = Some(bindings::drm_gem_mmap); + fops.fop_flags = bindings::FOP_UNSIGNED_OFFSET; + + fops +} diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index b36223e5bd98..1b82b6945edf 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -5,6 +5,7 @@ pub mod device; pub mod driver; pub mod file; +pub mod gem; pub mod ioctl; pub use self::device::Device;