From nobody Tue Dec 2 02:49:44 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CF358228CB0; Tue, 18 Nov 2025 13:27:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763472448; cv=none; b=D3aLVVLgMmPSPZlAS0k/bISKYGgXdlezCoGVG+1UT8GppncTqJ6OJAd5Sm9mbCjh7pC77lOlXZ9liM5z90d9oS20MGEnUQFsX765zODtj+MKG4VHg35NmACr4rU/PtFV0uH6X41LDp6r+ple7hVYUc7W0kQJkRHXuRqAQyzabbE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763472448; c=relaxed/simple; bh=+vC2aosMFk7TAuVXidYijONEtqq2NTUeKHA8bbK0DN4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=HzyE12KWJ/H+LAJ6Vak2JnbyM+a4UI7R7wIU8KSi1pA1eOQmvzFoslxrFytXVOqrz0OS0+E1HIyojOxQ1/b/XcVoebAdGjVy7dq1oWcdQzBnnXMEzb75fPhemtXulf/zRmoGn6+S3tVJvR+N9Sp+paj8Z/jn4IcO/rma8caC6DA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=N0CXGW3J; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="N0CXGW3J" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E9321C116D0; Tue, 18 Nov 2025 13:27:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1763472448; bh=+vC2aosMFk7TAuVXidYijONEtqq2NTUeKHA8bbK0DN4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=N0CXGW3JWp0veavKLZUuVe4IUD5q8w2IEqBoBiSrO4nckhqYK2crhsipIQzSFmjWz CAjqIGMxUP8y1uBCfBRL9CToTthbkTqw/9yZQcPYJAdT6uXBBRIcMPZmdDoHK8XR8j uermgdp41qsnYS7ZoN8867QLUu7/oTMjRSM1JVIyfnVVnN/dUKQIgrD+lniDdN2mY1 DMa9NKKvnkIGmulzgwnm3H04ya0afDOCsfkF20omdRJy7zOYHy8u0uDFyIwlnhfSNv Nq93mNa8bOP16kjzUEfUoJtw3NIJcqyjP6U3/OWmxj1Jwx9YN4ePzfBKauo8PUWYUZ 6wHGY47oCKS0A== From: Philipp Stanner To: Alice Ryhl , Danilo Krummrich , =?UTF-8?q?Christian=20K=C3=B6nig?= , Tvrtko Ursulin , Alexandre Courbot , Daniel Almeida , Boris Brezillon , Dave Airlie , Lyude Paul , Peter Colberg Cc: dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Philipp Stanner , stable@vger.kernel.org Subject: [RFC WIP 1/3] rust: list: Add unsafe for container_of Date: Tue, 18 Nov 2025 14:25:17 +0100 Message-ID: <20251118132520.266179-3-phasta@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20251118132520.266179-2-phasta@kernel.org> References: <20251118132520.266179-2-phasta@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" impl_list_item_mod.rs calls container_of() without unsafe blocks at a couple of places. Since container_of() is an unsafe macro / function, the blocks are strictly necessary. For unknown reasons, that problem was so far not visible and only gets visible once one utilizes the list implementation from within the core crate: error[E0133]: call to unsafe function `core::ptr::mut_ptr::::b= yte_sub` is unsafe and requires unsafe block --> rust/kernel/lib.rs:252:29 | 252 | let container_ptr =3D field_ptr.byte_sub(offset).cast::<$Co= ntainer>(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsa= fe function | ::: rust/kernel/drm/jq.rs:98:1 | 98 | / impl_list_item! { 99 | | impl ListItem<0> for BasicItem { using ListLinks { self.links }= ; } 100 | | } | |_- in this macro invocation | note: an unsafe function restricts its caller, but its body is safe by defa= ult --> rust/kernel/list/impl_list_item_mod.rs:216:13 | 216 | unsafe fn view_value(me: *mut $crate::list::ListLinks<$= num>) -> *const Self { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^= ^^^^^^^^^^^^^^^^^^^^ | ::: rust/kernel/drm/jq.rs:98:1 | 98 | / impl_list_item! { 99 | | impl ListItem<0> for BasicItem { using ListLinks { self.links }= ; } 100 | | } | |_- in this macro invocation =3D note: requested on the command line with `-D unsafe-op-in-unsafe-fn` =3D note: this error originates in the macro `$crate::container_of` whi= ch comes from the expansion of the macro `impl_list_item` Add unsafe blocks to container_of to fix the issue. Cc: stable@vger.kernel.org # v6.17+ Fixes: c77f85b347dd ("rust: list: remove OFFSET constants") Suggested-by: Alice Ryhl Signed-off-by: Philipp Stanner --- rust/kernel/list/impl_list_item_mod.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rust/kernel/list/impl_list_item_mod.rs b/rust/kernel/list/impl= _list_item_mod.rs index 202bc6f97c13..7052095efde5 100644 --- a/rust/kernel/list/impl_list_item_mod.rs +++ b/rust/kernel/list/impl_list_item_mod.rs @@ -217,7 +217,7 @@ unsafe fn view_value(me: *mut $crate::list::ListLinks<$= num>) -> *const Self { // SAFETY: `me` originates from the most recent call to `p= repare_to_insert`, so it // points at the field `$field` in a value of type `Self`.= Thus, reversing that // operation is still in-bounds of the allocation. - $crate::container_of!(me, Self, $($field).*) + unsafe { $crate::container_of!(me, Self, $($field).*) } } =20 // GUARANTEES: @@ -242,7 +242,7 @@ unsafe fn post_remove(me: *mut $crate::list::ListLinks<= $num>) -> *const Self { // SAFETY: `me` originates from the most recent call to `p= repare_to_insert`, so it // points at the field `$field` in a value of type `Self`.= Thus, reversing that // operation is still in-bounds of the allocation. - $crate::container_of!(me, Self, $($field).*) + unsafe { $crate::container_of!(me, Self, $($field).*) } } } )*}; @@ -270,9 +270,9 @@ unsafe fn prepare_to_insert(me: *const Self) -> *mut $c= rate::list::ListLinks<$nu // SAFETY: The caller promises that `me` points at a valid= value of type `Self`. let links_field =3D unsafe { >::view_links(me) }; =20 - let container =3D $crate::container_of!( + let container =3D unsafe { $crate::container_of!( links_field, $crate::list::ListLinksSelfPtr, inner - ); + ) }; =20 // SAFETY: By the same reasoning above, `links_field` is a= valid pointer. let self_ptr =3D unsafe { @@ -319,9 +319,9 @@ unsafe fn view_links(me: *const Self) -> *mut $crate::l= ist::ListLinks<$num> { // `ListArc` containing `Self` until the next call to `post_= remove`. The value cannot // be destroyed while a `ListArc` reference exists. unsafe fn view_value(links_field: *mut $crate::list::ListLinks= <$num>) -> *const Self { - let container =3D $crate::container_of!( + let container =3D unsafe { $crate::container_of!( links_field, $crate::list::ListLinksSelfPtr, inner - ); + ) }; =20 // SAFETY: By the same reasoning above, `links_field` is a= valid pointer. let self_ptr =3D unsafe { --=20 2.49.0 From nobody Tue Dec 2 02:49:44 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E8A81228CB0; Tue, 18 Nov 2025 13:27:32 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763472453; cv=none; b=utC1URMnSPzvFwUfWVwVbNK09jECt4ZBNDWfbxeKJNSYyKF75RNafKqro8BadEunTpJOdzSQw9gSb/SyHioKZNW7BdnPT5ZQmIHZg8iL1GRbYWTVPwfHKPZJyveEm1BUgFcDhq4M1AtHnBkJ9n1zT7FjMRtL9CwXa47u9NzKad4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763472453; c=relaxed/simple; bh=O6w8yE+bQAU4FwyqlCvQpDdc1jWO3Vrz2pdLrX3KRAs=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=GDdN0NVfvC22hd7z6c39QmhOqfphNhoY+HePBLq0MJLeXykEdTMNSB3A5aYWQN4gXHOp2owi7/O4ALxK9svF8F3jKRvOUOxP/zB7QJ7cpex4GTncswfA3XBp3cDYOvW128rrMlmIM2lrCP1qJ5i8qslnecMiyvcUKMbptUD5prk= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=sSW27ozh; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="sSW27ozh" Received: by smtp.kernel.org (Postfix) with ESMTPSA id CE36BC4CEF1; Tue, 18 Nov 2025 13:27:28 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1763472452; bh=O6w8yE+bQAU4FwyqlCvQpDdc1jWO3Vrz2pdLrX3KRAs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sSW27ozhxydZh+w4GiB5IRjKKwLLBj/nRTZruTWHa/gPU/6yXinllb4JeI4/uJdd+ zQWRbQ2MkGimhOWkEGtLPyVGagoHmEDwjKUGUToaExi4fDwga8L2elfJXd7qRYzY+x LwuUshugcNGZ4TnWyjM3ARCrnvRPb5WiLTqwYkJQeyb6KTAo+vE6V36UZxWDOQnhOv Yc40zpi40zkk+r4upgBj8cPVhKJgBAq1zgrTs8uXvpTGxiUBoZ8pxePvF8bhExnp1J szSkha4XM+50OkTiBA+NrWbhh/KTGGsqqu9eRLitNfX4seBwHYLjevTKp3EJfL21uh d597D7wlLbtIg== From: Philipp Stanner To: Alice Ryhl , Danilo Krummrich , =?UTF-8?q?Christian=20K=C3=B6nig?= , Tvrtko Ursulin , Alexandre Courbot , Daniel Almeida , Boris Brezillon , Dave Airlie , Lyude Paul , Peter Colberg Cc: dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Philipp Stanner Subject: [RFC WIP 2/3] rust: sync: Add dma_fence abstractions Date: Tue, 18 Nov 2025 14:25:18 +0100 Message-ID: <20251118132520.266179-4-phasta@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20251118132520.266179-2-phasta@kernel.org> References: <20251118132520.266179-2-phasta@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" dma_fence is a synchronization mechanism which is needed by virtually all GPU drivers. A dma_fence offers many features, among which the most important ones are registering callbacks (for example to kick off a work item) which get executed once a fence gets signalled. dma_fence has a number of callbacks. Only the two most basic ones (get_driver_name(), get_timeline_name() are abstracted since they are enough to enable the basic functionality. Callbacks in Rust are registered by passing driver data which implements the Rust callback trait, whose function will be called by the C backend. dma_fence's are always refcounted, so implement AlwaysRefcounted for them. Once a reference drops to zero, the C backend calls a release function, where we implement drop_in_place() to conveniently marry that C-cleanup mechanism with Rust's ownership concepts. This patch provides basic functionality, but is still missing: - An implementation of PinInit for all driver data. - A clever implementation for working dma_fence_begin_signalling() guards. See the corresponding TODO in the code. - Additional useful helper functions such as dma_fence_is_signaled(). These _should_ be relatively trivial to implement, though. Signed-off-by: Philipp Stanner --- rust/bindings/bindings_helper.h | 1 + rust/helpers/dma_fence.c | 23 ++ rust/helpers/helpers.c | 1 + rust/helpers/spinlock.c | 5 + rust/kernel/sync.rs | 2 + rust/kernel/sync/dma_fence.rs | 380 ++++++++++++++++++++++++++++++++ 6 files changed, 412 insertions(+) create mode 100644 rust/helpers/dma_fence.c create mode 100644 rust/kernel/sync/dma_fence.rs diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 84d60635e8a9..107cb6b6f4a4 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/dma_fence.c b/rust/helpers/dma_fence.c new file mode 100644 index 000000000000..a9fc4829bbae --- /dev/null +++ b/rust/helpers/dma_fence.c @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include + +void rust_helper_dma_fence_get(struct dma_fence *f) +{ + dma_fence_get(f); +} + +void rust_helper_dma_fence_put(struct dma_fence *f) +{ + dma_fence_put(f); +} + +bool rust_helper_dma_fence_begin_signalling(void) +{ + return dma_fence_begin_signalling(); +} + +void rust_helper_dma_fence_end_signalling(bool cookie) +{ + dma_fence_end_signalling(cookie); +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 7cf7fe95e41d..99a7f7834c03 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -20,6 +20,7 @@ #include "cred.c" #include "device.c" #include "dma.c" +#include "dma_fence.c" #include "drm.c" #include "err.c" #include "fs.c" diff --git a/rust/helpers/spinlock.c b/rust/helpers/spinlock.c index 42c4bf01a23e..017ac447ebbd 100644 --- a/rust/helpers/spinlock.c +++ b/rust/helpers/spinlock.c @@ -16,6 +16,11 @@ void rust_helper___spin_lock_init(spinlock_t *lock, cons= t char *name, #endif /* CONFIG_DEBUG_SPINLOCK */ } =20 +void rust_helper_spin_lock_init(spinlock_t *lock) +{ + spin_lock_init(lock); +} + void rust_helper_spin_lock(spinlock_t *lock) { spin_lock(lock); diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs index 00f9b558a3ad..84c406b6b9e1 100644 --- a/rust/kernel/sync.rs +++ b/rust/kernel/sync.rs @@ -12,6 +12,7 @@ mod arc; pub mod aref; pub mod completion; +pub mod dma_fence; mod condvar; pub mod lock; mod locked_by; @@ -20,6 +21,7 @@ =20 pub use arc::{Arc, ArcBorrow, UniqueArc}; pub use completion::Completion; +pub use dma_fence::{DmaFence, DmaFenceCtx, DmaFenceCb, DmaFenceCbFunc}; pub use condvar::{new_condvar, CondVar, CondVarTimeoutResult}; pub use lock::global::{global_lock, GlobalGuard, GlobalLock, GlobalLockBac= kend, GlobalLockedBy}; pub use lock::mutex::{new_mutex, Mutex, MutexGuard}; diff --git a/rust/kernel/sync/dma_fence.rs b/rust/kernel/sync/dma_fence.rs new file mode 100644 index 000000000000..d9a59dde0210 --- /dev/null +++ b/rust/kernel/sync/dma_fence.rs @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! DmaFence support. +//! +//! Reference: +//! +//! C header: [`include/linux/dma-fence.h`](srctree/include/linux/dma-fenc= e.h) + +use crate::{ + bindings, + prelude::*, + types::ForeignOwnable, + types::{ARef, AlwaysRefCounted, Opaque}, +}; + +use core::{ + ptr::{drop_in_place, NonNull}, + sync::atomic::{AtomicU64, Ordering}, +}; + +use kernel::sync::{Arc, ArcBorrow}; +use kernel::c_str; + +/// Defines the callback function the dma-fence backend will call once the= fence gets signalled. +pub trait DmaFenceCbFunc { + /// The callback function. `cb` is a container of the data which the d= river passed in + /// [`DmaFence::register_callback`]. + fn callback(cb: Pin>>) + where + Self: Sized; +} + +/// Container for driver data which the driver gets back in its callback o= nce the fence gets +/// signalled. +#[pin_data] +pub struct DmaFenceCb { + /// C struct needed for the backend. + #[pin] + inner: Opaque, + /// Driver data. + #[pin] + pub data: T, +} + +impl DmaFenceCb { + fn new(data: impl PinInit) -> Result>> { + let cb =3D try_pin_init!(Self { + inner: Opaque::zeroed(), // This gets initialized by the C bac= kend. + data <- data, + }); + + KBox::pin_init(cb, GFP_KERNEL) + } + + /// Callback for the C dma_fence backend. + /// + /// # Safety + /// All data used and cast in this function was validly created by + /// [`DmaFence::register_callback`] and isn't modified by the C backen= d until this callback + /// here has run. + unsafe extern "C" fn callback( + _fence_ptr: *mut bindings::dma_fence, + cb_ptr: *mut bindings::dma_fence_cb, + ) { + let cb_ptr =3D Opaque::cast_from(cb_ptr); + + // SAFETY: The constructor guarantees that `cb_ptr` is always `inn= er` of a DmaFenceCb. + let cb_ptr =3D unsafe { crate::container_of!(cb_ptr, Self, inner) = }.cast_mut() as *mut c_void; + // SAFETY: `cp_ptr` is the heap memory of a Pin> becaus= e it was created by + // invoking ForeignOwnable::into_foreign() on such an instance. + let cb =3D unsafe { > as ForeignOwnable>::from_fore= ign(cb_ptr) }; + + // Pass ownership back over to the driver. + T::callback(cb); + } +} + +/// A dma-fence context. A fence context takes care of associating related= fences with each other, +/// providing each with raising sequence numbers and a common identifier. +#[pin_data] +pub struct DmaFenceCtx { + /// An opaque spinlock. Only ever passed to the C backend, never used = by Rust. + #[pin] + lock: Opaque, + /// The fence context number. + nr: u64, + /// The sequence number for the next fence created. + seqno: AtomicU64, +} + +impl DmaFenceCtx { + /// Create a new `DmaFenceCtx`. + pub fn new() -> Result> { + let ctx =3D pin_init!(Self { + // Feed in a non-Rust spinlock for now, since the Rust side ne= ver needs the lock. + lock <- Opaque::ffi_init(|slot: *mut bindings::spinlock| { + // SAFETY: `slot` is a valid pointer to an uninitialized `= struct spinlock_t`. + unsafe { bindings::spin_lock_init(slot) }; + }), + // SAFETY: `dma_fence_context_alloc()` merely works on a globa= l atomic. Parameter `1` + // is the number of contexts we want to allocate. + nr: unsafe { bindings::dma_fence_context_alloc(1) }, + seqno: AtomicU64::new(0), + }); + + Arc::pin_init(ctx, GFP_KERNEL) + } + + fn get_new_fence_seqno(&self) -> u64 { + self.seqno.fetch_add(1, Ordering::Relaxed) + } +} + +impl ArcBorrow<'_, DmaFenceCtx> { + /// Create a new fence, consuming `data`. + /// + /// The fence will increment the refcount of the fence context associa= ted with this + /// [`DmaFenceCtx`]. + pub fn new_fence( + &mut self, + data: impl PinInit, + ) -> Result>> { + let fctx: Arc =3D (*self).into(); + let seqno: u64 =3D fctx.get_new_fence_seqno(); + + // TODO: Should we reset seqno in case of failure? + // Pass `fctx` by value so that the fence will hold a reference to= the DmaFenceCtx as long + // as it lives. + DmaFence::new(fctx, data, &self.lock, self.nr, seqno) + } +} + +/// A synchronization primitive mainly for GPU drivers. +/// +/// DmaFences are always reference counted. The typical use case is that o= ne side registers +/// callbacks on the fence which will perform a certain action (such as qu= eueing work) once the +/// other side signals the fence. +/// +/// # Examples +/// +/// ``` +/// use kernel::sync::{Arc, ArcBorrow, DmaFence, DmaFenceCtx, DmaFenceCb, = DmaFenceCbFunc}; +/// use core::sync::atomic::{self, AtomicBool}; +/// +/// static mut CHECKER: AtomicBool =3D AtomicBool::new(false); +/// +/// struct CallbackData { +/// i: u32, +/// } +/// +/// impl CallbackData { +/// fn new() -> Self { +/// Self { i: 9 } +/// } +/// } +/// +/// impl DmaFenceCbFunc for CallbackData { +/// fn callback(cb: Pin>>) where Self: Sized { +/// assert_eq!(cb.data.i, 9); +/// // SAFETY: Just to have an easy way for testing. This cannot r= ace with the checker +/// // because the fence signalling callbacks are executed synchro= nously. +/// unsafe { CHECKER.store(true, atomic::Ordering::Relaxed); } +/// } +/// } +/// +/// struct DriverData { +/// i: u32, +/// } +/// +/// impl DriverData { +/// fn new() -> Self { +/// Self { i: 5 } +/// } +/// } +/// +/// let data =3D DriverData::new(); +/// let fctx =3D DmaFenceCtx::new()?; +/// +/// let mut fence =3D fctx.as_arc_borrow().new_fence(data)?; +/// +/// let cb_data =3D CallbackData::new(); +/// fence.register_callback(cb_data); +/// // fence.begin_signalling(); +/// fence.signal()?; +/// // Now check wehether the callback was actually executed. +/// // SAFETY: `fence.signal()` above works sequentially. We just check he= re whether the signalling +/// // actually did set the boolean correctly. +/// unsafe { assert_eq!(CHECKER.load(atomic::Ordering::Relaxed), true); } +/// +/// Ok::<(), Error>(()) +/// ``` +#[pin_data] +pub struct DmaFence { + /// The actual dma_fence passed to C. + #[pin] + inner: Opaque, + /// User data. + #[pin] + data: T, + /// Marks whether the fence is currently in the signalling critical se= ction. + signalling: bool, + /// A boolean needed for the C backend's lockdep guard. + signalling_cookie: bool, + /// A reference to the associated [`DmaFenceCtx`] so that it cannot be= dropped while there are + /// still fences around. + fctx: Arc, +} + +// SAFETY: `DmaFence` is safe to be sent to any task. +unsafe impl Send for DmaFence {} + +// SAFETY: `DmaFence` is safe to be accessed concurrently. +unsafe impl Sync for DmaFence {} + +// SAFETY: These implement the C backends refcounting methods which are pr= oven to work correctly. +unsafe impl AlwaysRefCounted for DmaFence { + fn inc_ref(&self) { + // SAFETY: `self.as_raw()` is a pointer to a valid `struct dma_fen= ce`. + unsafe { bindings::dma_fence_get(self.as_raw()) } + } + + /// # Safety + /// + /// `ptr`must be a valid pointer to a [`DmaFence`]. + unsafe fn dec_ref(ptr: NonNull) { + // SAFETY: `ptr` is never a NULL pointer; and when `dec_ref()` is = called + // the fence is by definition still valid. + let fence =3D unsafe { (*ptr.as_ptr()).inner.get() }; + + // SAFETY: Valid because `fence` was created validly above. + unsafe { bindings::dma_fence_put(fence) } + } +} + +impl DmaFence { + // TODO: There could be a subtle potential problem here? The LLVM comp= iler backend can create + // several versions of this constant. Their content would be identical= , but their addresses + // different. + const OPS: bindings::dma_fence_ops =3D Self::ops_create(); + + /// Create an initializer for a new [`DmaFence`]. + fn new( + fctx: Arc, + data: impl PinInit, // TODO: The driver data should implement P= inInit + lock: &Opaque, + context: u64, + seqno: u64, + ) -> Result> { + let fence =3D pin_init!(Self { + inner <- Opaque::ffi_init(|slot: *mut bindings::dma_fence| { + let lock_ptr =3D &raw const (*lock); + // SAFETY: `slot` is a valid pointer to an uninitialized `= struct dma_fence`. + // `lock_ptr` is a pointer to the spinlock of the fence co= ntext, which is shared + // among all the fences. This can't become a UAF because e= ach fence takes a + // reference of the fence context. + unsafe { bindings::dma_fence_init(slot, &Self::OPS, Opaque= ::cast_into(lock_ptr), context, seqno) }; + }), + data <- data, + signalling: false, + signalling_cookie: false, + fctx: fctx, + }); + + let b =3D KBox::pin_init(fence, GFP_KERNEL)?; + + // SAFETY: We don't move the contents of `b` anywhere here. After = unwrapping it, ARef will + // take care of preventing memory moves. + let rawptr =3D KBox::into_raw(unsafe { Pin::into_inner_unchecked(b= ) }); + + // SAFETY: `rawptr` was created validly above. + let aref =3D unsafe { ARef::from_raw(NonNull::new_unchecked(rawptr= )) }; + + Ok(aref) + } + + /// Mark the beginning of a DmaFence signalling critical section. Shou= ld be called once a fence + /// gets published. + /// + /// The signalling critical section is marked as finished automaticall= y once the fence signals. + pub fn begin_signalling(&mut self) { + // FIXME: this needs to be mutable, obviously, but we can't borrow= mutably. *sigh* + self.signalling =3D true; + // TODO: Should we warn if a user tries to do this several times f= or the same + // fence? And should we ignore the request if the fence is already= signalled? + + // SAFETY: `dma_fence_begin_signalling()` works on global lockdep = data and calling it is + // always safe. + self.signalling_cookie =3D unsafe { bindings::dma_fence_begin_sign= alling() }; + } + + const fn ops_create() -> bindings::dma_fence_ops { + // SAFETY: Zeroing out memory on the stack is always safe. + let mut ops: bindings::dma_fence_ops =3D unsafe { core::mem::zeroe= d() }; + + ops.get_driver_name =3D Some(Self::get_driver_name); + ops.get_timeline_name =3D Some(Self::get_timeline_name); + ops.release =3D Some(Self::release); + + ops + } + + // The C backend demands the following two callbacks. They are intende= d for + // cross-driver communication, i.e., for another driver to figure out = to + // whom a fence belongs. As we don't support that currently in the Rust + // implementation, let's go for dummy data. By the way it has already = been + // proposed to remove those callbacks from C, since there are barely a= ny + // users. + // + // And implementing them properly in Rust would require a mandatory in= terface + // and potentially open questions about UAF bugs when the module gets = unloaded. + extern "C" fn get_driver_name(_ptr: *mut bindings::dma_fence) -> *cons= t c_char { + c_str!("DRIVER_NAME_UNUSED").as_char_ptr() + } + + extern "C" fn get_timeline_name(_ptr: *mut bindings::dma_fence) -> *co= nst c_char { + c_str!("TIMELINE_NAME_UNUSED").as_char_ptr() + } + + /// The release function called by the C backend once the refcount dro= ps to 0. We use this to + /// drop the Rust dma-fence, too. Since [`DmaFence`] implements [`Alwa= ysRefCounted`], this is + /// perfectly safe and a convenient way to concile the two release mec= hanisms of C and Rust. + unsafe extern "C" fn release(ptr: *mut bindings::dma_fence) { + let ptr =3D Opaque::cast_from(ptr); + + // SAFETY: The constructor guarantees that `ptr` is always the inn= er fence of a DmaFence. + let fence =3D unsafe { crate::container_of!(ptr, Self, inner) }.ca= st_mut(); + + // SAFETY: See above. Also, the release callback will only be call= ed once, when the + // refcount drops to 0, and when that happens the fence is by defi= nition still valid. + unsafe { drop_in_place(fence) }; + } + + /// Signal the fence. This will invoke all registered callbacks. + pub fn signal(&self) -> Result { + // SAFETY: `self` is refcounted. + let ret =3D unsafe { bindings::dma_fence_signal(self.as_raw()) }; + if ret !=3D 0 { + return Err(Error::from_errno(ret)); + } + + if self.signalling { + // SAFETY: `dma_fence_end_signalling()` works on global lockde= p data. The only + // parameter is a boolean passed by value. + unsafe { bindings::dma_fence_end_signalling(self.signalling_co= okie) }; + } + + Ok(()) + } + + /// Register a callback on a [`DmaFence`]. The callback will be invoke= d in the fence's + /// signalling path, i.e., critical section. + /// + /// Consumes `data`. `data` is passed back to the implemented callback= function when the fence + /// gets signalled. + pub fn register_callback(&self, data: imp= l PinInit) -> Result { + let cb =3D DmaFenceCb::new(data)?; + let ptr =3D cb.into_foreign() as *mut DmaFenceCb; + // SAFETY: `ptr` was created validly directly above. + let inner_cb =3D unsafe { (*ptr).inner.get() }; + + // SAFETY: `self.as_raw()` is valid because `self` is refcounted, = `inner_cb` was created + // validly above and was turned into a ForeignOwnable, so it won't= be dropped. `callback` + // has static life time. + let ret =3D unsafe { + bindings::dma_fence_add_callback( + self.as_raw(), + inner_cb, + Some(DmaFenceCb::::callback), + ) + }; + if ret !=3D 0 { + return Err(Error::from_errno(ret)); + } + Ok(()) + } + + fn as_raw(&self) -> *mut bindings::dma_fence { + self.inner.get() + } +} --=20 2.49.0 From nobody Tue Dec 2 02:49:44 2025 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F007B355050; Tue, 18 Nov 2025 13:27:37 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763472458; cv=none; b=f/MmK+0ob/Bh5bNqsQdHupxLm9RObl4cGGir2sNKU6JfmdlEb5hVSYm7UDrhOhJp/pSZKPL3xXjyxVyZO8A3k/5Kjp5XJZzzs+0A2+73jBVTzMsYj+qY6pv2vCzxuEYFuSMyi9TXhWElk5NQ5kWejB7Q07orUBoKgMRRK1mMFAM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1763472458; c=relaxed/simple; bh=OVR6mVP21sm9fzL9r2vnhG2F6BDvIoURwGoNLywV05I=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=g3MEkPNJJkry28gyDZLiafcHe1ppYzQ3aWP12PfiJ2SqZG1Wmr3QPmzhrwdqDEv1dD0ueevuLMOuQV1+HxefASE9WRbJsV82zCt0yFOrHZrIRpNsXs+50sx9V439Dm1FGcVRscDdv+wrdSsiCtqjmUwp1jpSpTkQwX1d6HV7tG8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=opNF97Ly; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="opNF97Ly" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 60A12C19422; Tue, 18 Nov 2025 13:27:33 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1763472457; bh=OVR6mVP21sm9fzL9r2vnhG2F6BDvIoURwGoNLywV05I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=opNF97LycH4qRrTqaRnRp0Vk/GF1X2inv1r8Pcv/2Fu9k8ZdInvbYxy5W954/WOr+ tf+2tni3OjZM5horerJPtfQ91PYONH60LPb3tK8goNFJUaeDc/OSntvPB6ZgoOdmuG 2FtJPRtPE9okl3GYhE6Uad2HUqUzf4q5528gLv/17xaKA3jmhAiMlNbgvzgvU1XMZb tA2HHFlWzozAr/w9bqyblrBB0DYX8D2HnJiCJOllaQ5P9aVfVbwjPqP7m4narcSQJ8 qLPHc57N6Ot1hXbM4eCvbXLiRKShUQ/GDK8cJOoToSInenyCIdjajiVyrqQqo0e0CQ OGnvedCON4Rpw== From: Philipp Stanner To: Alice Ryhl , Danilo Krummrich , =?UTF-8?q?Christian=20K=C3=B6nig?= , Tvrtko Ursulin , Alexandre Courbot , Daniel Almeida , Boris Brezillon , Dave Airlie , Lyude Paul , Peter Colberg Cc: dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Philipp Stanner Subject: [RFC WIP 3/3] rust/drm: Add initial jobqueue sceleton Date: Tue, 18 Nov 2025 14:25:19 +0100 Message-ID: <20251118132520.266179-5-phasta@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20251118132520.266179-2-phasta@kernel.org> References: <20251118132520.266179-2-phasta@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" DRM jobqueue is intended to become a load balancer, dependency manager and timeout handler for GPU drivers with firmware scheduling. The presented code shall give the reader an overview over the intended architecture, notably over the API functions, DmaFence callbacks, job lists and job control flow. This code compiles (with warnings) but is incomplete. Notable missing features are: - Actually registering the fence callbacks - workqueue - timeout handling - actually calling the driver callback for job submissions Moreover, the implementation of the waiting_jobs and running_jobs lists is currently not operational because I've got trouble with getting it to work with generic Job data. Verifyable by commenting in the push_job() call in the submit_job() function. Some WIP code is commented out, but is probably worth reading nevertheless since it completes the picture. Signed-off-by: Philipp Stanner --- rust/kernel/drm/jq.rs | 398 +++++++++++++++++++++++++++++++++++++++++ rust/kernel/drm/mod.rs | 2 + 2 files changed, 400 insertions(+) create mode 100644 rust/kernel/drm/jq.rs diff --git a/rust/kernel/drm/jq.rs b/rust/kernel/drm/jq.rs new file mode 100644 index 000000000000..b3f7ab4655cf --- /dev/null +++ b/rust/kernel/drm/jq.rs @@ -0,0 +1,398 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2025 Red Hat Inc.: +// - Philipp Stanner +// - Danilo Krummrich +// - David Airlie + +//! DrmJobqueue. A load balancer, dependency manager and timeout handler f= or +//! GPU job submissions. + +use crate::{ + prelude::*, + types::ARef, +}; +use kernel::sync::{Arc, SpinLock, new_spinlock, DmaFence, DmaFenceCtx, Dma= FenceCb, DmaFenceCbFunc}; +use kernel::list::*; +use kernel::revocable::Revocable; + + +#[pin_data] +pub struct Job { + credits: u32, +// dependencies: List, // TODO implement dependency list + #[pin] + data: T, +} + +impl Job { + /// Create a new job that can be submitted to [`Jobqueue`]. + /// + /// Jobs contain driver data that will later be made available to the = driver's + /// run_job() callback in which the job gets pushed to the GPU. + pub fn new(credits: u32, data: impl PinInit) -> Result>> { + let job =3D pin_init!(Self { + credits, + data <- data, + }); + + KBox::pin_init(job, GFP_KERNEL) + } + + /// Add a callback to the job. When the job gets submitted, all added = callbacks will be + /// registered on the [`DmaFence`] the jobqueue returns for that job. + pub fn add_callback() -> Result { + Ok(()) + } + + /// Add a [`DmaFence`] or a [`DoneFence`] as this job's dependency. Th= e job + /// will only be executed after that dependency has been finished. + pub fn add_dependency() -> Result { + // TODO: Enqueue passed DmaFence into the job's dependency list. + Ok(()) + } + + /// Check if there are dependencies for this job. Register the jobqueue + /// waker if yes. + fn arm_deps() -> Result { + // TODO: Register DependencyWaker here if applicable. + Ok(()) + } +} + +// Dummy trait for the linked list. +trait JobData { + fn access_data(&self) -> i32; +} + +#[pin_data] +struct EnqueuedJob { + inner: Pin>>, + #[pin] + links: ListLinksSelfPtr>, + done_fence: ARef>, // i32 is just dummy data. TODO: allo= w for replacing with `()` + // The hardware_fence can by definition only be set at an unknown poin= t in + // time. + // TODO: Think about replacing this with a `struct RunningJob` which c= onsumes + // an `EnqueuedJob`. + hardware_fence: Option>>, // i32 is dummy data unti= l there's DmaFence + // without data. + nr_of_deps: u32, +} + +impl EnqueuedJob { + fn new(inner: Pin>>, fctx: &Arc) -> Result> { + let pseudo_data: i32 =3D 42; + let done_fence =3D fctx.as_arc_borrow().new_fence(pseudo_data)?; + + ListArc::pin_init(try_pin_init!(Self { + inner, + links <- ListLinksSelfPtr::new(), + done_fence, + hardware_fence: None, + nr_of_deps: 0, // TODO implement + }), GFP_KERNEL) + } +} + +impl_list_arc_safe! { + impl{T: ?Sized} ListArcSafe<0> for EnqueuedJob { untracked; } +} + +impl_list_item! { + impl ListItem<0> for EnqueuedJob { using ListLinksSelfPtr= { self.links }; } +} + +// Callback item for the hardware fences to wake / progress the jobqueue. +struct HwFenceWaker { + jobq: Arc>>, + job: ListArc>, +} + +impl DmaFenceCbFunc for HwFenceWaker { + fn callback(cb: Pin>>) where Self: Sized { + // This prevents against deadlock. See Jobqueue's drop() for deta= ils. + let jq_guard =3D cb.data.jobq.try_access(); + if jq_guard.is_none() { + return; + } + let jq_guard =3D jq_guard.unwrap(); + + // Take Jobqueue lock. + let jq =3D jq_guard.lock(); + // Remove job from running list. + //let _ =3D unsafe { cb.data.job.remove() }; + // Signal done_fence. + // TODO: It's more robust if the JQ makes sure that fences get si= gnalled + // in order, even if the driver should signal them chaotically. + let _ =3D cb.data.job.done_fence.signal(); + // Run more ready jobs if there's capacity. + //jq.start_submit_worker(); + } +} + +// Callback item for the dependency fences to wake / progress the jobqueue. +struct DependencyWaker { + jobq: Arc>>, + job: ListArc>, +} + +impl DmaFenceCbFunc for DependencyWaker { + fn callback(cb: Pin>>) where Self: Sized { + // This prevents against deadlock. See Jobqueue's drop() for detai= ls. + let jq_guard =3D cb.data.jobq.try_access(); + if jq_guard.is_none() { + return; + } + let jq_guard =3D jq_guard.unwrap(); + + // Take Jobqueue lock. + let jq =3D jq_guard.lock(); + + // TODO: Lock Contention + // + // Alright, so the Jobqueue is currently designed around a big cen= tral + // lock, which also protects the jobs. submit_job(), the JQ's cb o= n the + // hw_fences and its cbs on the (external) dependency fences compe= te for + // the lock. The first two should ever only run sequentially, so l= ikely + // aren't a problem. + // + // Dependency callbacks, however, could be registered and then sig= nalled + // by the thousands and then all compete for the lock possibly for= nothing. + // + // That can likely be improved. Maybe by just making the nr_of_deps + // counter atomic? + + // Decrement dep counter. + // cb.data.job.nr_of_deps -=3D 1; // TODO needs to be DerefMut + // If counter =3D=3D 0, a new job somewhere in the queue just got = ready. + // Check if it was the head job and if yes, run all jobs possible. + if cb.data.job.nr_of_deps =3D=3D 0 { +// jq.start_submit_worker(); + } + } +} + +struct InnerJobqueue { + capacity: u32, + waiting_jobs: List>, + running_jobs: List>, + submit_worker_active: bool, +} + +impl InnerJobqueue { + fn new(capacity: u32) -> Self { + let waiting_jobs =3D List::>::new(); + let running_jobs =3D List::>::new(); + + Self { + capacity, + waiting_jobs, + running_jobs, + submit_worker_active: false, + } + } + + fn has_waiting_jobs(&self) -> bool { + !self.waiting_jobs.is_empty() + } + + fn has_capacity_left(&self, cost: u32) -> bool { + let cost =3D cost as i64; + let capacity =3D self.capacity as i64; + + if capacity - cost >=3D 0 { + return true; + } + + false + } + + fn update_capacity(&mut self, cost: u32) { + self.capacity -=3D cost; + } + + + // Called by the hw_fence callbacks, dependency callbacks, and submit_= job(). + // TODO: does submit_job() ever have to call it? + fn start_submit_worker(&mut self) { + if self.submit_worker_active { + return; + } + + // TODO run submit work item + + self.submit_worker_active =3D true; + } + + /* + + /// Push a job immediately. + /// + /// Returns true if the job ran immediately, false otherwise. + fn run_job(&mut self, job: &EnqueuedJob) -> bool { + // TODO remove job from waiting list. + + // TODO Call the driver's run_job() callback. + let hardware_fence =3D run_job(&job); + job.hardware_fence =3D Some(hardware_fence); + + // TODO check whether hardware_fence raced and is already signalle= d. + + self.running_jobs.push_back(job); + + // TODO Register HwFenceWaker on the hw_fence. + } + + // Submits all ready jobs as long as there's capacity. + fn run_all_ready_jobs(&mut self) { + for job in self.waiting_jobs.reverse() { + if job.nr_of_deps > 0 { + return; + } + + if self.has_capacity_left(job.credits) { + if !self.run_job(&job) { + // run_job() didn't run the job immediately (because t= he + // hw_fence did not race). Subtract the credits. + self.update_capacity(job.credits); + } + } else { + return; + } + } + } + */ +} + +//#[pin_data] +pub struct Jobqueue { + inner: Arc>>, + fctx: Arc, // TODO currently has a separate lock shared w= ith fences +// #[pin] +// data: T, +} + +impl Jobqueue { + /// Create a new [`Jobqueue`] with `capacity` space for jobs. `run_job= ` is + /// your driver's callback which the jobqueue will call to push a subm= itted + /// job to the hardware. + pub fn new(capacity: u32, _run_job: fn(&Pin>>) -> AR= ef>) -> Result { + let inner =3D Arc::pin_init(Revocable::new(new_spinlock!(InnerJobq= ueue::new(capacity))), GFP_KERNEL)?; + let fctx =3D DmaFenceCtx::new()?; + + Ok (Self { + inner, + fctx, + }) + } + + /// Submit a job to the jobqueue. + /// + /// The jobqueue takes ownership over the job and later passes it back= to the + /// driver by reference through the driver's run_job callback. Jobs are + /// passed back by reference instead of by value partially to allow fo= r later + /// adding a job resubmission mechanism to be added to [`Jobqueue`]. + /// + /// Jobs get run and their done_fences get signalled in submission ord= er. + /// + /// Returns the "done_fence" on success, which gets signalled once the + /// hardware has completed the job and once the jobqueue is done with = a job. + pub fn submit_job(&self, job: Pin>>) -> Result>> { + let job_cost =3D job.credits; + // TODO: It would be nice if the done_fence's seqno actually match= es the + // submission order. To do that, however, we'd need to protect job + // creation with InnerJobqueue's spinlock. Is that worth it? + let enq =3D EnqueuedJob::new(job, &self.fctx)?; + let done_fence =3D enq.done_fence.clone(); // Get the fence for th= e user. + + // TODO register job's callbacks on done_fence. + + let guard =3D self.inner.try_access(); + if guard.is_none() { + // Can never happen. JQ gets only revoked when it drops. + return Err(ENODEV); + } + let jobq =3D guard.unwrap(); + + let jobq =3D jobq.lock(); + + // Check if there are dependencies and, if yes, register rewake + // callbacks on their fences. Must be done under the JQ lock's pro= tection + // since the callbacks will access JQ data. + //job.arm_deps(); + //jobq.waiting_jobs.push_back(job); + + if jobq.has_waiting_jobs() { + // Jobs waiting means that there is either currently no capaci= ty + // for more jobs, or the jobqueue is blocked by a job with + // unfullfilled dependencies. Either the hardware fences' call= backs + // or those of the dependency fences will pull in more jobs on= ce + // there is capacity. + return Ok(done_fence); + } else if !jobq.submit_worker_active && jobq.has_capacity_left(job= _cost) { + // This is the first waiting job. No one (i.e., no hw_fence) h= as + // woken the worker yet, but there is space. Awake it manually. + //jobq.start_submit_worker(); + } + + // If there was no capacity for the job, the callbacks registered = on the + // already running jobs' hardware fences will check if there's spa= ce for + // the next job, guaranteeing progress. + // + // If no jobs were running, there was by definition still space an= d the + // job will get pushed by the worker. + // + // If a job couldn't be pushed because there were unfinished depen= dencies, + // then the hardware fences' callbacks mentioned above will detect= that + // and not yet push the job. + // + // Each dependency's fence has its own callback which checks: + // a) whether all other callbacks are fullfilled and if yes: + // b) whether there is now enough credits available. + // + // If a) and b) are fullfilled, the job gets pushed. + // + // If there are no jobs currently running, credits must be availab= le by + // definition. + + Ok(done_fence) + + } +} + +impl Drop for Jobqueue { + fn drop(&mut self) { + // The hardware fences might outlive the jobqueue. So hw_fence cal= lbacks + // could very well still call into job queue code, resulting in + // data UAF or, should the jobqueue code be unloaded, even code UA= F. + // + // Thus, the jobqueue needs to be cleanly decoupled from the hardw= are + // fences when it drops, in other words, it needs to deregister al= l its + // hw_fence callbacks. + // + // This, however, could easily deadlock when a hw_fence signals: + // + // Step | Jobqueue step | hw_fence step + // ---------------------------------------------------------------= --- + // 1 | JQ starts drop | fence signals + // 2 | JQ lock taken | fence lock taken + // 3 | Tries to take fence lock | Tries to take JQ l= ock + // 4 | ***DEADLOCK*** | ***DEADLOCK*** + // + // In order to prevent deadlock, we first have to revoke access to= the + // JQ so that all fence callbacks can't try to take the lock anymo= re, + // and then deregister all JQ callbacks. + self.inner.revoke(); + + /* + let guard =3D self.inner.lock(); + for job in self.inner.waiting_jobs { + job.deregister_dep_fences(); + } + for job in self.inner.running_jobs { + job.deregister_hw_fence(); + } + */ + } +} diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index 1b82b6945edf..803bed36231b 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -7,12 +7,14 @@ pub mod file; pub mod gem; pub mod ioctl; +pub mod jq; =20 pub use self::device::Device; pub use self::driver::Driver; pub use self::driver::DriverInfo; pub use self::driver::Registration; pub use self::file::File; +pub use self::jq::Jobqueue; =20 pub(crate) mod private { pub trait Sealed {} --=20 2.49.0