From nobody Sun Feb 8 01:30:16 2026 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 1543839448E; Tue, 3 Feb 2026 08:14:40 +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=1770106480; cv=none; b=XHLcvzRIOHxcLx7qblI6IpRKvAQMjoJoLYGQn9IS5LTqu2wwmGwJiSRe1s0Wnjckb2OQueQ0D2Q4sN1Mv6vTrCEPbsvYQ5RTvgGR5AsLHdAn9FMn6qXUHnKEjmAE/xcBrVHqzRm7t8Q55Id6/63A5MiDbHVkQtEXbR7mlYkoPhk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770106480; c=relaxed/simple; bh=+vC2aosMFk7TAuVXidYijONEtqq2NTUeKHA8bbK0DN4=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=NjXhfKc+QZe9QxuyHLU7FjSR/Y00Cuz4tUAqz33rOkffuP9vByrj7A9mM0+5oixi+lMxXNWA6ukd1lzXrqRCpQKQZjvqjEtJPcolpiRllxBwwDxncbAaw2ZKuZkjIxkvcVzINCCq7q8TZE++XP80nOnGSsynnNbRBl2MtqXQbeE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=PQ0J6jRy; 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="PQ0J6jRy" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 52EC8C19421; Tue, 3 Feb 2026 08:14:35 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770106479; bh=+vC2aosMFk7TAuVXidYijONEtqq2NTUeKHA8bbK0DN4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PQ0J6jRylrunBvIq7xYaf/tUum/oiLRCk3qOT4PPsIqidyJ1PhBod8TjAM+BWNmm7 DWU4sXvrQ87RQVqQfRZ9yvdjmeUOZZtx9dpXsJ4JoMihakbrnF+LgAQkmJ3eRmheaw +//SDdR+LIpmauXfp00UbJpc9NhEly90wYdLYzKoo/CRYIf845r3xOgexLmcRZeJFY mQNUI2wPEZmhqssUdw5ibhxZgpoGb0ybqrUg5iGQ7L9O5TKe/hfD5t0/40rlqHWb93 JJzDBY99A/+83Z9A3PooZkvvignXovLR7ar1wibPbRrCT8twqBT0YoQbB4dcTcn7d0 8oeIt3X6ZSLkQ== From: Philipp Stanner To: David Airlie , Simona Vetter , Danilo Krummrich , Alice Ryhl , Gary Guo , Benno Lossin , =?UTF-8?q?Christian=20K=C3=B6nig?= , Boris Brezillon , Daniel Almeida , Joel Fernandes Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, Philipp Stanner , stable@vger.kernel.org Subject: [RFC PATCH 1/4] rust: list: Add unsafe for container_of Date: Tue, 3 Feb 2026 09:14:00 +0100 Message-ID: <20260203081403.68733-3-phasta@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20260203081403.68733-2-phasta@kernel.org> References: <20260203081403.68733-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 Reviewed-by: Alice Ryhl Reviewed-by: Gary Guo --- 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 Sun Feb 8 01:30:16 2026 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 3D42E394487; Tue, 3 Feb 2026 08:14:45 +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=1770106485; cv=none; b=RTfmeRu2rp6PHqaHvhiwEHH0BmwA09BI2KmUI2YGMZ7ByyTRz6L/WAYqGGsk8Vy6hvzvo1FyeIkfHtIfdHGoLjHlN+MYCbWce8SGP5kiDJvuoyLHzz26r6lxMcgR3mUKaDO4N2UEkre2hZ4ZKUZ97RV2El2GRa526ymOaRuj1jY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770106485; c=relaxed/simple; bh=4jV6OAA7zxM0/z17MeJQYVmcnGkjyXbh/eLjPCREVUQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Zi9CIyJgexiYfNspFlC3YIzXeM/5Sixd/6o+xY8H/Hd1zks1iSr6j73hqJaRKfIL8GoR/N3YuieKYMFw0fcCKnCxoMzOuwX4rxsAMoFn35nPqVS7SF2XP0rAxRLfFlH6/ascfDncH+TnmyFFDLpfQOMp2y7f02eq7oUV7bvTXJo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ah6zZfOP; 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="ah6zZfOP" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 70366C19425; Tue, 3 Feb 2026 08:14:40 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770106484; bh=4jV6OAA7zxM0/z17MeJQYVmcnGkjyXbh/eLjPCREVUQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ah6zZfOPmSkT11Uh6fXcRxDUZdG/NCrm8zuj3MJva9x4rvNYbgBiBC+SViw+k5KaP +Axy9i9Cz/+r0Ea9llfT0SgBPPMVri66sHph00HX13nrqzbySr23k02hJruZjjTkEu +GMYQLfDC30sZQqpBlu12AyzwvEChUu6rqWqmHFg3kLtZ3bZnpDoTwMmJVMfozfGGO tuaTnC4/ByioIHrqNL0bSgdQwtx9ADaZEA8SCNIoNsu5YanzRNpq1mGvMcLS0m+4kr kjdVSWnzECXgcUaYt970LsJPUZC0LDMajmFc1Vcm/cFjmpMrtrhEL7gL3SDzTLIWm1 N4J892ApAhlDw== From: Philipp Stanner To: David Airlie , Simona Vetter , Danilo Krummrich , Alice Ryhl , Gary Guo , Benno Lossin , =?UTF-8?q?Christian=20K=C3=B6nig?= , Boris Brezillon , Daniel Almeida , Joel Fernandes Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, Philipp Stanner Subject: [RFC PATCH 2/4] rust: sync: Add dma_fence abstractions Date: Tue, 3 Feb 2026 09:14:01 +0100 Message-ID: <20260203081403.68733-4-phasta@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20260203081403.68733-2-phasta@kernel.org> References: <20260203081403.68733-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 fundamental 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. - Abstractions for dma_fence_remove_callback() - needed to cleanly decouple third parties from fences for life time soundness. Signed-off-by: Philipp Stanner --- rust/bindings/bindings_helper.h | 1 + rust/helpers/dma_fence.c | 28 +++ rust/helpers/helpers.c | 1 + rust/helpers/spinlock.c | 5 + rust/kernel/sync.rs | 2 + rust/kernel/sync/dma_fence.rs | 396 ++++++++++++++++++++++++++++++++ 6 files changed, 433 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 2e43c66635a2..fc3cb5eb0be5 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -51,6 +51,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..cc93297db4b1 --- /dev/null +++ b/rust/helpers/dma_fence.c @@ -0,0 +1,28 @@ +// 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); +} + +bool rust_helper_dma_fence_is_signaled(struct dma_fence *f) +{ + return dma_fence_is_signaled(f); +} diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 551da6c9b506..70c690bdb0a5 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -25,6 +25,7 @@ #include "cred.c" #include "device.c" #include "dma.c" +#include "dma_fence.c" #include "drm.c" #include "err.c" #include "irq.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 cf5b638a097d..85e524ea9118 100644 --- a/rust/kernel/sync.rs +++ b/rust/kernel/sync.rs @@ -14,6 +14,7 @@ pub mod atomic; pub mod barrier; pub mod completion; +pub mod dma_fence; mod condvar; pub mod lock; mod locked_by; @@ -23,6 +24,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..9c15426f8432 --- /dev/null +++ b/rust/kernel/sync/dma_fence.rs @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2025, 2026 Red Hat Inc.: +// - Philipp Stanner + +//! 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) + } +} + +// SAFETY: The DmaFenceCtx is merely a wrapper around an atomic integer. +unsafe impl Send for DmaFenceCtx {} +// SAFETY: The DmaFenceCtx is merely a wrapper around an atomic integer. +unsafe impl Sync for DmaFenceCtx {} + +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(()) + } + + /// Check whether the fence was signalled at the moment of the functio= n call. + pub fn is_signaled(&self) -> bool { + // SAFETY: self is by definition still valid. The backend ensures = proper + // locking. We don't implement the dma_fence fastpath backend_ops + // callbacks, so this merely checks a boolean and has no side effe= cts. + unsafe { bindings::dma_fence_is_signaled(self.as_raw()) } + } + + /// 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 Sun Feb 8 01:30:16 2026 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 7E423394494; Tue, 3 Feb 2026 08:14:50 +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=1770106490; cv=none; b=SLen0/gkdvP6Wr6KGJgOOLVnxljttnTPhzCbE45hsHQhASwg/s6bhYfdAirWJ0jewkkHCHWXJ5K+AxwjP1jGVLQv3bV5X9W/jTgBlOQXtivXMqjz0VdnpD2MeJ5rKI2m8PhxvyELM+D3mNWTzE8SoL1G1B6xpSSKN1hKssdOXJQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770106490; c=relaxed/simple; bh=1wKE9m82xnOwxwC/YUHuIsHd7GkBMwmR+CKg/tGKICE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=Sun5Z3OroxPSzEaV8XKtz6E70IZVQaiMLMhJEiuiEorQ8KsMt9KoarpSSv/TZtPuZ1PsGWmzC/TgGzS2zLuHwchXUbWe7ljRCjkMMs0096Fwem3Mbgq2HNBuabKrwUsljN3ji4F4jAMzD6PDPFxYAHCWlBqC9/HG5kUca2cxgxo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ukc1Ah68; 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="ukc1Ah68" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3F9E6C19422; Tue, 3 Feb 2026 08:14:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770106489; bh=1wKE9m82xnOwxwC/YUHuIsHd7GkBMwmR+CKg/tGKICE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ukc1Ah68LvvWYrK18LPqCbm5mrFThplooIC/SgqzMKUbC+/HnXoC2jyhg/pw2jrb4 c5q/8Ymw9MyFoPUcQR6z98qOKYv1o0Gv0iNo71yfmUXGXcZujbYffKC59kFCaYIaPb wJ0Z4lxVn2pxhfTS4VVWQY0GJfQGuNvYWpN9m83LnhrntpzY0SEQclUI/53P6KAAqA sUVpJvXvTAl3e/FTjQf/O3wJlPqeSzT2zPmC4LQ4pITd44zUZNPbNAyF4tihY80c7C 4w0qw9PmovZaPi4h83LJjIU5mzj0d2SsBsE1gpr2qqi3EBzlOGnWosxbHvp3ItWSlv gGI8c9aoeoLmA== From: Philipp Stanner To: David Airlie , Simona Vetter , Danilo Krummrich , Alice Ryhl , Gary Guo , Benno Lossin , =?UTF-8?q?Christian=20K=C3=B6nig?= , Boris Brezillon , Daniel Almeida , Joel Fernandes Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, Philipp Stanner Subject: [RFC PATCH 3/4] rust/drm: Add DRM Jobqueue Date: Tue, 3 Feb 2026 09:14:02 +0100 Message-ID: <20260203081403.68733-5-phasta@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20260203081403.68733-2-phasta@kernel.org> References: <20260203081403.68733-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 a load balancer, dependency manager and timeout handler for GPU drivers with firmware scheduling, i.e. drivers which spawn one firmware ring for each userspace instance for running jobs on the hardware. This patch provides: - Jobs which the user can create and load with custom data. - Functionality to register dependencies (DmaFence's) on jobs. - The actual Jobqueue, into which you can push jobs. Jobqueue submits jobs to your driver through a provided driver callback. It always submits jobs in order. It only submits jobs whose dependencies have all been signalled. Additionally, Jobqueue implements a credit count system so it can take your hardware's queue depth into account. When creating a Jobqueue, you provide the number of credits that are available for that queue. Each job you submit has a specified credit cost which will be subtracted from the Jobqueue's capacity. If the Jobqueue runs out of capacity, it will still accept more jobs and run those once more capacity becomes available through finishing jobs. This code compiles and was tested and is judget to be ready for beta testers. However, the code is still plastered with TODOs. Still missing features are: - Timeout handling - Complete decoupling from DmaFences. Jobqueue shall in the future completely detach itself from all related DmaFence's. This is currently incomplete. While data-UAF should be impossible, code-UAF through DmaFence's could occur if the Jobqueue code were unloaded while unsignaled fences are still alive. Signed-off-by: Philipp Stanner --- rust/kernel/drm/jq.rs | 680 +++++++++++++++++++++++++++++++++++++++++ rust/kernel/drm/mod.rs | 2 + 2 files changed, 682 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..fd5641f40a61 --- /dev/null +++ b/rust/kernel/drm/jq.rs @@ -0,0 +1,680 @@ +// SPDX-License-Identifier: GPL-2.0 +// +// Copyright (C) 2025, 2026 Red Hat Inc.: +// - Philipp Stanner + +//! DrmJobqueue. A load balancer, dependency manager and timeout handler f= or +//! GPU job submissions. + +use crate::{prelude::*, types::ARef}; +use core::sync::atomic::{AtomicU32, Ordering}; +use kernel::list::*; +use kernel::revocable::Revocable; +use kernel::sync::{ + new_spinlock, Arc, DmaFence, DmaFenceCb, DmaFenceCbFunc, DmaFenceCtx, = SpinLock, +}; +use kernel::workqueue::{self, impl_has_work, new_work, Work, WorkItem}; + +#[pin_data] +struct Dependency { + #[pin] + links: ListLinks, + fence: ARef>, +} + +impl Dependency { + fn new(fence: ARef>) -> Result> { + ListArc::pin_init( + try_pin_init!(Self { + links <- ListLinks::new(), + fence, + }), + GFP_KERNEL, + ) + } +} + +impl_list_arc_safe! { + impl ListArcSafe<0> for Dependency { untracked; } +} +impl_list_item! { + impl ListItem<0> for Dependency { using ListLinks { self.links }; } +} +// Callback item for the dependency fences to wake / progress the jobqueue. +struct DependencyWaker { + jobq: Arc>>>, + // Scary raw pointer! See justification at the unsafe block below. + // + // What would be the alternatives to the rawpointer? I can see two: + // 1. Refcount the jobs and have the dependency callbacks take a ref= erence. + // That would require then, however, to guard the jobs with a Spi= nLock. + // That SpinLock would just exist, however, to satisfy the Rust c= ompiler. + // From a kernel-engineering perspective, that would be undesirab= le, + // because the only thing within a job that might be accessed by= multiple + // CPUs in parallel is `Job::nr_of_deps`. It's certainly conceiva= ble + // that some userspace applications with a great many dependencie= s would + // then suffer from lock contention, just to modify an integer. + // 2. Clever Hackyness just to avoid an unsafe that's provably corre= ct: + // We could replace this rawpointer with a Arc, the Jo= b + // holding another reference. Would work. But is that worth it? + // Share your opinion on-list :) + job: *const Job, +} + +impl DependencyWaker { + fn new(jobq: Arc>>>, job: *const J= ob) -> Self { + Self { jobq, job } + } +} + +impl DmaFenceCbFunc for DependencyWaker { + fn callback(cb: Pin>>) + where + Self: Sized, + { + let jq_guard =3D cb.data.jobq.try_access(); + if jq_guard.is_none() { + return; + } + let outer_jq =3D jq_guard.unwrap(); + + // SAFETY: + // `job` is only needed to modify the dependency counter within th= e job. + // That counter is atomic, so concurrent modifications are safe. + // + // As for the life time: Jobs that have pending dependencies are h= eld by + // `InnerJobqueue::waiting_jobs`. As long as any of these dependen= cy + // callbacks here are active, a job can by definition not move to = the + // `InnerJobqueue::running_jobs` list and can, thus, not be freed. + // + // In case `Jobqueue` drops, the revocable-check above will guard = against + // UAF. Moreover, jobqueue will deregister all of those dma_fence + // callbacks and thereby cleanly decouple itself. The dma_fences t= hat + // these callbacks are registered on can, after all, outlive the j= obqueue. + let job: &Job =3D unsafe { &*cb.data.job }; + + let old_nr_of_deps =3D job.nr_of_deps.fetch_sub(1, Ordering::Relax= ed); + // If counter =3D=3D 0, a new job somewhere in the queue just got = ready. + // Run all ready jobs. + if old_nr_of_deps =3D=3D 1 { + let mut jq =3D outer_jq.lock(); + jq.check_start_submit_worker(cb.data.jobq.clone()); + } + + // TODO remove the Dependency from the job's dep list, so that when + // `Jobqueue` gets dropped it won't try to deregister callbacks for + // already-signalled fences. + } +} + +/// A jobqueue Job. +/// +/// You can stuff your data in it. The job will be borrowed back to your d= river +/// once the time has come to run it. +/// +/// Jobs are consumed by [`Jobqueue::submit_job`] by value (ownership tran= sfer). +/// You can set multiple [`DmaFence`] as dependencies for a job. It will o= nly +/// get run once all dependency fences have been signaled. +/// +/// Jobs cost credits. Jobs will only be run if there are is enough capaci= ty in +/// the jobqueue for the job's credits. It is legal to specify jobs costin= g 0 +/// credits, effectively disabling that mechanism. +#[pin_data] +pub struct Job { + cost: u32, + #[pin] + pub data: T, + done_fence: Option>>, + hardware_fence: Option>>, + nr_of_deps: AtomicU32, + dependencies: List, +} + +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(cost: u32, data: impl PinInit) -> Result>= > { + let job =3D pin_init!(Self { + cost, + data <- data, + done_fence: None, + hardware_fence: None, + nr_of_deps: AtomicU32::new(0), + dependencies <- List::::new(), + }); + + 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. + // TODO is callback a good name? We could call it "consequences" for e= xample. + 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(&mut self, fence: ARef>) -> Result= { + let dependency =3D Dependency::new(fence)?; + + self.dependencies.push_back(dependency); + self.nr_of_deps.fetch_add(1, Ordering::Relaxed); + + Ok(()) + } + + /// Check if there are dependencies for this job. Register the jobqueue + /// waker if yes. + fn arm_deps(&mut self, jobq: Arc>>= >) { + let job_ptr =3D &raw const *self; + let mut cursor =3D self.dependencies.cursor_front(); + + while let Some(dep) =3D cursor.peek_next() { + let waker =3D DependencyWaker::new(jobq.clone(), job_ptr); + if dep.fence.register_callback(waker).is_err() { + // TODO precise error check + // The fence raced or was already signaled. But the hardwa= re_fence + // waker is not yet registered. Thus, it's OK to just decr= ement + // the dependency count. + self.nr_of_deps.fetch_sub(1, Ordering::Relaxed); + // TODO this dependency must be removed from the list so t= hat + // `Jobqueue::drop()` doesn't try to deregister the callba= ck. + } + + cursor.move_next(); + } + } +} + +#[pin_data] +struct JobWrap { + #[pin] + links: ListLinks, + inner: Pin>>, +} + +impl JobWrap { + fn new(job: Pin>>) -> Result> { + ListArc::pin_init( + try_pin_init!(Self { + links <- ListLinks::new(), + inner: job, + }), + GFP_KERNEL, + ) + } +} + +impl_list_arc_safe! { + impl{T: Send} ListArcSafe<0> for JobWrap { untracked; } +} +impl_list_item! { + impl{T: Send} ListItem<0> for JobWrap { using ListLinks { self.link= s }; } +} + +struct InnerJobqueue { + capacity: u32, + waiting_jobs: List>, + running_jobs: List>, + submit_worker_active: bool, + run_job: fn(&Pin<&mut Job>) -> ARef>, +} + +// SAFETY: We use `List` with effectively a `UniqueArc`, so it can be `Sen= d` when elements are `Send`. +unsafe impl Send for InnerJobqueue {} + +impl InnerJobqueue { + fn new(capacity: u32, run_job: fn(&Pin<&mut Job>) -> ARef>) -> Self { + let waiting_jobs =3D List::>::new(); + let running_jobs =3D List::>::new(); + + Self { + capacity, + waiting_jobs, + running_jobs, + submit_worker_active: false, + run_job, + } + } + + 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 check_start_submit_worker(&mut self, outer: Arc>>) { + if self.submit_worker_active { + return; + } + self.submit_worker_active =3D true; + + // TODO the work item should likely be moved into the JQ struct, s= ince + // only ever 1 worker needs to run at a time. But if we do it that= way, + // how can we store a reference to the JQ? We obviously can't stor= e it + // in the JQ itself because circular dependency -> memory leak. + let submit_work =3D SubmitWorker::new(outer).unwrap(); // TODO err= or + let _ =3D workqueue::system().enqueue(submit_work); // TODO error + } +} + +// Callback item for the hardware fences to wake / progress the jobqueue. +struct HwFenceWaker { + jobq: Arc>>>, + // Another scary raw pointer! + // This one is necessary so that a) a job can be removed from `InnerJo= bqueue::running_jobs`, + // and b) its done_fence be accessed and signaled. + // + // What would be the alternatives to this rawpointer? Two come to mind: + // 1. Refcount the job. Then the job would have to be locked to sati= sfy Rust. + // Locking it is not necessary, however. See the below safety com= ment + // for details. + // 2. Clever hacky tricks: We could assign a unique ID per job and s= tore it + // in this callback. Then, we could find the associated job via i= terating + // over `jobq.running_jobs`. So to access a job and signal its do= ne_fence, + // we'd have to do a list iteration, which is undesirable perform= ance-wise. + // Moreover, the unique ID parent would have to be stored in `Job= queue`, + // requiring us to generate jobs on the jobqueue object. + job: *const JobWrap, +} + +impl HwFenceWaker { + fn new(jobq: Arc>>>, job: *const J= obWrap) -> Self { + Self { jobq, job } + } +} + +impl DmaFenceCbFunc for HwFenceWaker { + 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() { + // The JQ itself will signal all done_fences with an error whe= n it drops. + return; + } + let jq_guard =3D jq_guard.unwrap(); + + let mut jobq =3D jq_guard.lock(); + + // SAFETY: + // We need the job to remove it from `InnerJobqueue::running_jobs`= and to + // access its done_fence. There is always only one hardware_fence = waker + // callback per job. It's the only party which will remove the job= from + // the running_jobs list. This callback only exists once all Depen= dency + // callbacks have already ran. As for the done_fence, the DmaFence + // implementation guarantees synchronization and correctness. Thus, + // unlocked access is safe. + // + // As for the life time: Only when this callback here has ran will= a job + // be removed from the running_jobs list and, thus, be dropped. + // `InnerJobqueue`, which owns running_jobs, can only drop once + // `Jobqueue` got dropped. The latter will deregister all hardware= fence + // callbacks while dropping, thereby preventing UAF through dma_fe= nce + // callbacks on jobs. + let job: &JobWrap =3D unsafe { &*cb.data.job }; + + jobq.capacity +=3D job.inner.cost; + let _ =3D job.inner.done_fence.as_ref().expect("done_fence not pre= sent").signal(); // TODO err + + // SAFETY: This callback function gets registered only once per jo= b, + // and the registering party (`run_all_ready_jobs()`) adds the job= to + // the list. + // + // This is the only reference (incl. refcount) to this job. Thus, = it + // may be removed only after all accesses above have been performe= d. + unsafe { jobq.running_jobs.remove(job) }; + + // Run more ready jobs if there's capacity. + jobq.check_start_submit_worker(cb.data.jobq.clone()); + } +} + +/// Push a job immediately. +/// +/// Returns true if the hardware_fence raced, false otherwise. +fn run_job( + driver_cb: fn(&Pin<&mut Job>) -> ARef>, + waker: HwFenceWaker, + job: Pin<&mut Job>, +) -> bool { + let hardware_fence =3D driver_cb(&job); + + // If a GPU is very fast (or is processing jobs synchronously or sth.)= it + // could be that the hw_fence is already signaled. In case that happen= s, we + // signal the done_fence for userspace & Co. immediately. + + // TODO catch for exact error (currently backend only ever errors if i= t raced. + // But still, robustness, you know. + if hardware_fence.register_callback(waker).is_err() { + // TODO: Print into log in case of error. + let _ =3D job.done_fence.as_ref().expect("done_fence not present")= .signal(); + return true; + } + + *job.project().hardware_fence =3D Some(hardware_fence); + + false +} + +// Submits all ready jobs as long as there's capacity. +fn run_all_ready_jobs( + jobq: &mut InnerJobqueue, + outer_jobq: Arc>>>, + driver_cb: fn(&Pin<&mut Job>) -> ARef>, +) { + let mut cursor =3D jobq.waiting_jobs.cursor_front(); + + while let Some(job) =3D cursor.peek_next() { + if job.inner.nr_of_deps.load(Ordering::Relaxed) > 0 { + return; + } + + let cost =3D job.inner.cost as i64; + if jobq.capacity as i64 - cost < 0 { + return; + } + + let runnable_job =3D job.remove(); + // To obtain a mutable reference to the list element, we need to c= ast + // into a UniqueArc. unwrap() cannot fire because by the jobqueue = design + // a job is only ever in the waiting_jobs OR running_jobs list. + let mut unique_job =3D Arc::>::into_unique_or_drop(runn= able_job.into_arc()).unwrap(); + let job_ptr: *const JobWrap =3D &raw const *unique_job; + + let runnable_inner_job /* &mut Pin>> */ =3D unique_job= .as_mut().project().inner; + + let hw_fence_waker =3D HwFenceWaker::new(outer_jobq.clone(), job_p= tr); + if !run_job(driver_cb, hw_fence_waker, runnable_inner_job.as_mut()= ) { + // run_job() didn't run the job immediately (because the + // hw_fence did not race). Subtract the credits. + jobq.capacity -=3D cost as u32; + } + + // We gave up our ownership above. And we couldn't clone the Arc, = because + // we needed a UniqueArc for the mutable access. So turn it back n= ow. + let running_job =3D ListArc::from(unique_job); + jobq.running_jobs.push_back(running_job); + } +} + +#[pin_data] +struct SubmitWorker { + jobq: Arc>>>, + #[pin] + work: Work>, +} + +impl SubmitWorker { + fn new( + jobq: Arc>>>, + ) -> Result> { + Arc::pin_init( + pin_init!(Self { + jobq, + work <- new_work!("Jobqueue::SubmitWorker")}), + GFP_KERNEL, + ) + } +} + +impl_has_work! { + impl{T: Send} HasWork for SubmitWorker { self.work } +} + +impl WorkItem for SubmitWorker { + type Pointer =3D Arc>; + + fn run(this: Arc>) { + let outer_jobq_copy =3D this.jobq.clone(); + + let guard =3D this.jobq.try_access(); + if guard.is_none() { + // Can never happen. JQ gets only revoked when it drops, and w= e hold + // a reference. + return; + } + let jobq =3D guard.unwrap(); + + let mut jobq =3D jobq.lock(); + let run_job =3D jobq.run_job; + + run_all_ready_jobs(&mut jobq, outer_jobq_copy, run_job); + jobq.submit_worker_active =3D false; + } +} + +/// A job load balancer, dependency manager and timeout handler for GPUs. +/// +/// The JQ allows you to submit [`Job`]s. It will run all jobs whose depen= decny +/// fences have been signalled, as long as there's capacity. Running jobs = happens +/// by borrowing them back to your driver's run_job callback. +/// +/// # Examples +/// +/// ``` +/// use kernel::sync::{DmaFenceCtx, DmaFence, Arc}; +/// use kernel::drm::jq::{Job, Jobqueue}; +/// use kernel::types::{ARef}; +/// use kernel::time::{delay::fsleep, Delta}; +/// +/// let fctx =3D DmaFenceCtx::new()?; +/// +/// fn run_job(job: &Pin<&mut Job>>) -> ARef> { +/// let fence =3D job.data.as_arc_borrow().new_fence(42 as i32).unwrap= (); +/// +/// // Our GPU is so damn fast that it executes each job immediately! +/// fence.signal(); +/// fence +/// } +/// +/// let jq1 =3D Jobqueue::new(1_000_000, run_job)?; +/// let jq2 =3D Jobqueue::new(1_000_000, run_job)?; +/// +/// let job1 =3D Job::new(1, fctx.clone())?; +/// let job2 =3D Job::new(1, fctx.clone())?; +/// +/// +/// // Test normal submission of jobs without dependencies. +/// let fence1 =3D jq1.submit_job(job1)?; +/// let fence2 =3D jq1.submit_job(job2)?; +/// +/// fsleep(Delta::from_secs(1)); +/// assert_eq!(fence1.is_signaled(), true); +/// assert_eq!(fence2.is_signaled(), true); +/// +/// +/// // Test whether a job with a fullfilled dependency gets executed. +/// let mut job3 =3D Job::new(1, fctx.clone())?; +/// job3.add_dependency(fence1)?; +/// +/// let fence3 =3D jq2.submit_job(job3)?; +/// fsleep(Delta::from_secs(1)); +/// assert_eq!(fence3.is_signaled(), true); +/// +/// +/// // Test whether a job with an unfullfilled dependency does not get exe= cuted. +/// let unsignaled_fence =3D fctx.as_arc_borrow().new_fence(9001 as i32)?; +/// +/// let mut job4 =3D Job::new(1, fctx.clone())?; +/// job4.add_dependency(unsignaled_fence.clone())?; +/// +/// let blocked_job_fence =3D jq2.submit_job(job4)?; +/// fsleep(Delta::from_secs(1)); +/// assert_eq!(blocked_job_fence.is_signaled(), false); +/// +/// +/// // Test whether job4 from above actually gets executed once its dep is= met. +/// unsignaled_fence.signal()?; +/// fsleep(Delta::from_secs(1)); +/// assert_eq!(blocked_job_fence.is_signaled(), true); +/// +/// Ok::<(), Error>(()) +/// ``` +pub struct Jobqueue { + inner: Arc>>>, + fctx: Arc, // TODO currently has a separate lock shared w= ith fences + run_job: fn(&Pin<&mut Job>) -> ARef>, +} + +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. + /// + /// If you don't want to use the capacity mechanism, set it to a value + /// unequal 0 and instead set [`Job`]'s cost to 0. + pub fn new( + capacity: u32, + run_job: fn(&Pin<&mut Job>) -> ARef>, + ) -> Result { + if capacity =3D=3D 0 { + return Err(EINVAL); + } + + let inner =3D Arc::pin_init( + Revocable::new(new_spinlock!(InnerJobqueue::::new(capacity,= run_job))), + GFP_KERNEL, + )?; + let fctx =3D DmaFenceCtx::new()?; + + Ok(Self { + inner, + fctx, + run_job, + }) + } + + /// 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. + // TODO: Return a DmaFence-wrapper that users cannot signal. + pub fn submit_job(&self, mut job: Pin>>) -> Result>> { + let job_cost =3D job.cost; + // 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 done_fence =3D self.fctx.as_arc_borrow().new_fence(42 as i32)?; + *job.as_mut().project().done_fence =3D Some(done_fence.clone()); + + // 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 mut jobq =3D jobq.lock(); + + let had_waiting_jobs_already =3D jobq.has_waiting_jobs(); + + // 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. + // SAFETY: `job` was submitted perfectly validly above. We don't m= ove + // the contents; arm_deps() merely iterates over the dependency-li= st. + // TODO: Supposedely this unsafe is unnecessary if you do some mag= ic. + let pure_job =3D unsafe { Pin::into_inner_unchecked(job.as_mut()) = }; + pure_job.arm_deps(self.inner.clone()); + + let wrapped_job =3D JobWrap::new(job)?; + jobq.waiting_jobs.push_back(wrapped_job); + + if had_waiting_jobs_already { + // 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 + // the conditions are met. + return Ok(done_fence); + } else if jobq.has_capacity_left(job_cost) { + // This is the first waiting job. Wake the submit_worker if ne= cessary. + jobq.check_start_submit_worker(self.inner.clone()); + } + + // If the conditions for running now were not met, the callbacks r= egistered + // on the already running jobs' hardware fences will check if ther= e's space + // 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 are 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 and dependency fences might outlive the jobqueue. + // So fence callbacks could very well still call into job queue co= de, + // resulting in data UAF or, should the jobqueue code be unloaded, + // even code UAF. + // + // Thus, the jobqueue needs to be cleanly decoupled from those fen= ces + // when it drops; in other words, it needs to deregister all its + // 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 on the fences. + 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(); + } + + TODO: signall all remaining done_fences with an error. + */ + } +} 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 From nobody Sun Feb 8 01:30:16 2026 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 EDE4239448B; Tue, 3 Feb 2026 08:14:54 +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=1770106495; cv=none; b=H9yyNOUuPzo87UZx8HD5HCuTfMItk3JPudFlcG/2tDJHqRPbGIzHmIChHBJlxATxDc9/TcVQchla9DMVsfCh1fF4EAbu+SpmXhwWl7Qt3N0szBLTutkRSDQo8rC8chWqLE3c2UtUnRZ5Vy3/HW4gqNJqHeEfX4h9Sf4t/cT/WkQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1770106495; c=relaxed/simple; bh=awtL86U6XqX/kvHZstMumWgEZU5Qs0bhnkawIWJ9XUQ=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=S2h7CuOD38WcvFaYlGa8X8HrAlKBcybunm+ma5KvOBjM+MJmy0w+RFgscnsH3qobW/2F0JM6rJ76ebKKKFRBV5b0q2mX4AN0NgywH384o/lWYb1emeik6gyOq6IUjVcpY060RfZO3vFEMzwzHIc+ncpwU1at/ZAiqs1yaYHmM+M= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=hWJdZrKp; 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="hWJdZrKp" Received: by smtp.kernel.org (Postfix) with ESMTPSA id F0287C116D0; Tue, 3 Feb 2026 08:14:49 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1770106494; bh=awtL86U6XqX/kvHZstMumWgEZU5Qs0bhnkawIWJ9XUQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hWJdZrKp9NFjFL7eBR4eXM/oDnjsBw3t6/GDl3o2rkMQ8NldRV+fCR3kyZ3iGI0cp oShZK3GuD0kLP+9OrhooGnb31Cl4BFzvHJaDwgPvllay1XaJStGN49x1A3U/FZUBuG RpmY+oq/NGqidMJs7gMxHwzF3Nur3euIpq82FrrcAJm1fXM9JyDvzGQ+ssp7PnKKMW JUv+7Kusq3LAuTNphJoSNwRUBhv+YWQ6k+FUdlhSJ3j4NV3QlZw7vZatZ2FB4LtCsk 2RqL3hZ1i2043OHyWUydPJIsdLumDJRlF08o8CdATtTmCuYqs+o4iDHZAwZoXK/6Ns RzgRb4Y5VqCrQ== From: Philipp Stanner To: David Airlie , Simona Vetter , Danilo Krummrich , Alice Ryhl , Gary Guo , Benno Lossin , =?UTF-8?q?Christian=20K=C3=B6nig?= , Boris Brezillon , Daniel Almeida , Joel Fernandes Cc: linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org, Philipp Stanner Subject: [RFC PATCH 4/4] samples: rust: Add jobqueue tester Date: Tue, 3 Feb 2026 09:14:03 +0100 Message-ID: <20260203081403.68733-6-phasta@kernel.org> X-Mailer: git-send-email 2.49.0 In-Reply-To: <20260203081403.68733-2-phasta@kernel.org> References: <20260203081403.68733-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" The DRM Jobqueue is a new piece of (highly asynchronous) infrastructure for submitting jobs to graphics processing units (GPUs). It is difficult to test such a mechanism purely with unit tests. Thus, provide this driver solely for testing drm::Jobqueue. Signed-off-by: Philipp Stanner --- samples/rust/Kconfig | 11 ++ samples/rust/Makefile | 1 + samples/rust/rust_jobqueue_tester.rs | 180 +++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 samples/rust/rust_jobqueue_tester.rs diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index c376eb899b7a..a9a3a671bb0b 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -145,4 +145,15 @@ config SAMPLE_RUST_HOSTPROGS =20 If unsure, say N. =20 +config SAMPLE_RUST_JOBQUEUE_TESTER + tristate "Jobqueue Tester" + select JOBQUEUE_TESTER + help + This option builds the Rust Jobqueue Tester. + + To compile this as a module, choose M here: + the module will be called rust_jobqueue_tester. + + If unsure, say N. + endif # SAMPLES_RUST diff --git a/samples/rust/Makefile b/samples/rust/Makefile index cf8422f8f219..9cc1f021dc39 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_USB) +=3D rust_driver_us= b.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX) +=3D rust_driver_faux.o obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY) +=3D rust_driver_auxiliary.o obj-$(CONFIG_SAMPLE_RUST_CONFIGFS) +=3D rust_configfs.o +obj-$(CONFIG_SAMPLE_RUST_JOBQUEUE_TESTER) +=3D rust_jobqueue_tester.o =20 rust_print-y :=3D rust_print_main.o rust_print_events.o =20 diff --git a/samples/rust/rust_jobqueue_tester.rs b/samples/rust/rust_jobqu= eue_tester.rs new file mode 100644 index 000000000000..c2590a1b4f8a --- /dev/null +++ b/samples/rust/rust_jobqueue_tester.rs @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Small example demonstrating how to use [`drm::Jobqueue`]. + +use kernel::prelude::*; +use kernel::sync::{DmaFenceCtx, DmaFence, Arc}; +use kernel::drm::jq::{Job, Jobqueue}; +use kernel::types::{ARef}; +use kernel::time::{delay::fsleep, Delta}; +use kernel::workqueue::{self, impl_has_work, new_work, Work, WorkItem}; + +module! { + type: RustJobqueueTester, + name: "rust_jobqueue_tester", + authors: ["Philipp Stanner"], + description: "Rust minimal sample", + license: "GPL", +} + +#[pin_data] +struct GPUWorker { + hw_fence: ARef>, + #[pin] + work: Work, +} + +impl GPUWorker { + fn new( + hw_fence: ARef>, + ) -> Result> { + Arc::pin_init( + pin_init!(Self {hw_fence, work <- new_work!("Jobqueue::GPUWork= er")}), + GFP_KERNEL, + ) + } +} + +impl_has_work! { + impl HasWork for GPUWorker { self.work } +} + +impl WorkItem for GPUWorker { + type Pointer =3D Arc; + + fn run(this: Arc) { + fsleep(Delta::from_secs(1)); + this.hw_fence.signal().unwrap(); + } +} + +fn run_job(job: &Pin<&mut Job>>) -> ARef> { + let fence =3D job.data.as_arc_borrow().new_fence(42 as i32).unwrap(); + + let gpu_worker =3D GPUWorker::new(fence.clone()).unwrap(); + let _ =3D workqueue::system().enqueue(gpu_worker); + + fence +} + +struct RustJobqueueTester { } + +impl kernel::Module for RustJobqueueTester { + fn init(_module: &'static ThisModule) -> Result { + pr_info!("Rust Jobqueue Tester (init)\n"); + pr_info!("Am I built-in? {}\n", !cfg!(MODULE)); + + let dep_fctx =3D DmaFenceCtx::new()?; + let hw_fctx =3D DmaFenceCtx::new()?; + let jq =3D Jobqueue::new(1_000_000, run_job)?; + + + pr_info!("Test 1: Test submitting two jobs without dependencies.\n= "); + let job1 =3D Job::new(1, hw_fctx.clone())?; + let job2 =3D Job::new(1, hw_fctx.clone())?; + + let fence1 =3D jq.submit_job(job1)?; + let fence2 =3D jq.submit_job(job2)?; + + while !fence1.is_signaled() || !fence2.is_signaled() { + fsleep(Delta::from_secs(2)); + } + pr_info!("Test 1 succeeded.\n"); + + + pr_info!("Test 2: Test submitting a job with already-fullfilled de= pendency.\n"); + let mut job3 =3D Job::new(1, hw_fctx.clone())?; + job3.add_dependency(fence1)?; + + let fence3 =3D jq.submit_job(job3)?; + fsleep(Delta::from_secs(2)); + if !fence3.is_signaled() { + pr_info!("Test 2 failed.\n"); + return Err(EAGAIN); + } + pr_info!("Test 2 succeeded.\n"); + + + pr_info!("Test 3: Test that a job with unfullfilled dependency get= s never run.\n"); + let unsignaled_fence =3D dep_fctx.as_arc_borrow().new_fence(9001 a= s i32)?; + + let mut job4 =3D Job::new(1, hw_fctx.clone())?; + job4.add_dependency(unsignaled_fence.clone())?; + + let blocked_job_fence =3D jq.submit_job(job4)?; + fsleep(Delta::from_secs(2)); + if blocked_job_fence.is_signaled() { + pr_info!("Test 3 failed.\n"); + return Err(EAGAIN); + } + pr_info!("Test 3 succeeded.\n"); + + + pr_info!("Test 4: Test whether Test 3's blocked job can be unblock= ed.\n"); + unsignaled_fence.signal()?; + while !blocked_job_fence.is_signaled() { + fsleep(Delta::from_secs(2)); + } + pr_info!("Test 4 succeeded.\n"); + + + pr_info!("Test 5: Submit a bunch of unblocked jobs, then a blocked= one, then an unblocked one.\n"); + let job1 =3D Job::new(1, hw_fctx.clone())?; + let job2 =3D Job::new(1, hw_fctx.clone())?; + let mut job3 =3D Job::new(1, hw_fctx.clone())?; + let job4 =3D Job::new(1, hw_fctx.clone())?; + let job5 =3D Job::new(1, hw_fctx.clone())?; + + let unsignaled_fence1 =3D dep_fctx.as_arc_borrow().new_fence(9001 = as i32)?; + let unsignaled_fence2 =3D dep_fctx.as_arc_borrow().new_fence(9001 = as i32)?; + let unsignaled_fence3 =3D dep_fctx.as_arc_borrow().new_fence(9001 = as i32)?; + job3.add_dependency(unsignaled_fence1.clone())?; + job3.add_dependency(unsignaled_fence2.clone())?; + job3.add_dependency(unsignaled_fence3.clone())?; + + let fence1 =3D jq.submit_job(job1)?; + let fence2 =3D jq.submit_job(job2)?; + let fence3 =3D jq.submit_job(job3)?; + + fsleep(Delta::from_secs(2)); + if fence3.is_signaled() || !fence1.is_signaled() || !fence2.is_sig= naled() { + pr_info!("Test 5 failed.\n"); + return Err(EAGAIN); + } + + unsignaled_fence1.signal()?; + unsignaled_fence3.signal()?; + fsleep(Delta::from_secs(2)); + if fence3.is_signaled() { + pr_info!("Test 5 failed.\n"); + return Err(EAGAIN); + } + + unsignaled_fence2.signal()?; + fsleep(Delta::from_secs(2)); + if !fence3.is_signaled() { + pr_info!("Test 5 failed.\n"); + return Err(EAGAIN); + } + + let fence4 =3D jq.submit_job(job4)?; + let fence5 =3D jq.submit_job(job5)?; + + fsleep(Delta::from_secs(2)); + + if !fence4.is_signaled() || !fence5.is_signaled() { + pr_info!("Test 5 failed.\n"); + return Err(EAGAIN); + } + pr_info!("Test 5 succeeded.\n"); + + + Ok(RustJobqueueTester { }) + } +} + +impl Drop for RustJobqueueTester { + fn drop(&mut self) { + pr_info!("Rust Jobqueue Tester (exit)\n"); + } +} --=20 2.49.0