* [RFC PATCH v5 0/6] rust: Add drm::JobQueue
@ 2026-10-09 19:11 Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 1/6] rust: DmaFence: remove static lifetime Philipp Stanner
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Philipp Stanner @ 2026-10-09 19:11 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Sumit Semwal, Christian König,
Philipp Stanner, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter,
Boris Brezillon, John.harrison, da.gomez
Cc: linux-kernel, linux-media, dri-devel, rust-for-linux
Changes in v5:
- Add back the Revocable<JobQueueInner…> from my earliest RFCs. Reason
is still the same: JQ dropping while dependency fences signal could
deadlock. Similarly, this solves a dependency kicking of the work
item again while drop() is running.
- Do not call run_job() with a lock held, since run_job() might sleep.
AFAICS, this also makes the proposed XArray solution impossible and
demands that we use a list.
- Re-add the earlier credit-count-system, so that run_job() is
infallible. Simplifies the design quite a bit. (Danilo)
- Drop jobs in a deferred manner through the work item so that job
payload data can do atomic-hostile stuff.
- Because of the point above, I suggest that we stop dropping a
DriverFence on signal (Danilo's and my idea). It has no real
advantage, and getting rid of it allows for better controlling what
drops when through JobQueue. For fence it's only relevant that there
is an RCU grace period, thus:
- Replace DriverFence::drop()'s call_rcu() with synchronize_rcu(). We
would, therefore, demand that everyone who's got a problem with the
delay drops via work item.
This can still not be tested as I'm blocked by pin-init vs self-ref.
Just FYI.
I would appreciate confirmation of these following thoughts on locking
design:
1.
I believe that callers of jq.new_job() and jq.submit_job() do not need
an outer serialization mutex. Reason is that JQ serializes with its
internal lock. Sequence numbers cannot take over older seqnos because it
is submit_job() who sets the seqno.
2.
I, furthermore, believe that a driver will not need to hold a
driver-lock while calling jq.complete_jobs_up_to_seqno().
IOW, I am suggesting that a driver can always safely use JQ without an
outer lock, and actually *should* do so to avoid potential locking
issues.
Reason:
let driver_stuff = foo.lock();
let current_gpu_seqno = driver_stuff.get_seqno();
drop(driver_stuff);
jq.complete_jobs_up_to_seqno(current_gpu_seqno);
Thus, I believe that it is OK for me to take the JQ lock in
jq.complete_jobs_up_to_seqno().
Please correct me if I'm missing something.
Should I be wrong and a lock inversion could occur, then we could
hypothetically add a fence Signaler object, with which we could signal
fences with a different lock. Similar to how drm_sched does it with the
hardware_fence.
Regards
P.
====
Cover letter from v4:
As the commit messages and code comments detail, progressing JobQueue is
currently somewhat blocked because of an issue with self-referential
PinInit, which Gary generously offered to investigate.
This code compiles, but the example does not because of the
aformentioned issue. Nevertheless, I wanted to provide another RFC here
so that we can move our discussions forward in the meantime, especially
since very much about JobQueue has changed.
Our, now upstreamed, DmaFence abstractions informed some of the notable
changes in JobQueue. Most notably, JobQueue now owns the FenceContext,
Jobs are created on the queue and own the DriverFence. Jobs, again, are
owned by the JobQueue. We hope to enforce correct behavior that way,
having FenceContext, JobQueue and firmware ring all correspond with each
other 1:1:1.
I suspect that a potential deadlock on JQ drop still exists. I
previously solved that with Revocable, which I tend to think will also
be the way to go here.
(One very great news btw is that we almost magically solved a number of
issues with the new DmaFence design – in combination with this JobQueue
design, for the first time it would be possible to fully support the
dma_fence backend_ops. The driver-unload-problem previously had most
users use intermediate fences, like the drm_sched_fence, which means
that callbacks, e.g. from userspace, could not be passed through to the
driver. Now, with FenceContextOps <-> JobQueueOps, we can theoretically
support all of them)
The differences between this draft and Daniel / Tyr's prototype which
probably are most noteworthy are the different lock design
("Philipp"-JobQueue has one big lock, wheres Tyr-JobQueue has 2, 3 if
you count the XArray lock) and the used data structure.
The presented solution uses lists over XArray because:
1. XArray is semantically more complex and has an additional lock.
2. An Xarray-as-ringbuffer needs own algorithms for index tracking,
wrapp around etc. With list you enqueue into a waiting list, and
for running you move into the running_list.
3. The memory reservations we need for pre-allocating everything for
our job-submission path are solved in one go with list, because a
job simply contains a list head.
4. Most notably, it is unclear how XArray behaves for ever-increasing
indices with a sliding window, whereas the list semantic is well
understood and deterministic.
I obviously don't claim to own all the wisdom in that regard; it's just
that I still propose this solution because these arguments make me
believe that it is the right one.
Since all the list handling is done with iterators, there is currently
no unsafe needed.
I hope we can discuss many things here before we, hopefully soon, can
address the lifetime issue and move to a v1.
This is based on drm-rust-next (10a6623a24a8), plus Danilo's ScopedWork
[1] and my patches [2][3] regarding 'static and lock errors.
Regards,
Philipp
[1] https://lore.kernel.org/rust-for-linux/20260807165252.3849875-1-dakr@kernel.org/
[2] https://lore.kernel.org/rust-for-linux/20260922083631.444614-2-phasta@kernel.org/
[3] https://lore.kernel.org/rust-for-linux/20260924085523.2620704-2-phasta@kernel.org/
Philipp Stanner (6):
rust: DmaFence: remove static lifetime
rust: DmaFence: Implement Deref for FenceContext
rust: DmaFence: Don't drop on signal
rust: DmaFence: [RFC] Add Signaler
rust: DmaFence: Replace call_rcu() with synchronize_rcu()
rust: drm: Add JobQueue
rust/kernel/dma_buf/dma_fence.rs | 139 ++++----
rust/kernel/drm/job_queue.rs | 525 +++++++++++++++++++++++++++++++
rust/kernel/drm/mod.rs | 4 +
3 files changed, 606 insertions(+), 62 deletions(-)
create mode 100644 rust/kernel/drm/job_queue.rs
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH v5 1/6] rust: DmaFence: remove static lifetime
2026-10-09 19:11 [RFC PATCH v5 0/6] rust: Add drm::JobQueue Philipp Stanner
@ 2026-10-09 19:11 ` Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 2/6] rust: DmaFence: Implement Deref for FenceContext Philipp Stanner
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Philipp Stanner @ 2026-10-09 19:11 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Sumit Semwal, Christian König,
Philipp Stanner, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter,
Boris Brezillon, John.harrison, da.gomez
Cc: linux-kernel, linux-media, dri-devel, rust-for-linux
(will be upstreamed separately)
Signed-off-by: Philipp Stanner <phasta@kernel.org>
---
rust/kernel/dma_buf/dma_fence.rs | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/rust/kernel/dma_buf/dma_fence.rs b/rust/kernel/dma_buf/dma_fence.rs
index 18a43e1bb442..eef2b9e93f35 100644
--- a/rust/kernel/dma_buf/dma_fence.rs
+++ b/rust/kernel/dma_buf/dma_fence.rs
@@ -291,7 +291,7 @@ fn from(e: AllocError) -> Self {
/// }
/// }
/// ```
-pub trait FenceCallback: Send + 'static {
+pub trait FenceCallback: Send {
/// Called when the fence is signaled.
///
/// This is called from the fence signaling path, which may be in interrupt
@@ -310,7 +310,7 @@ pub trait FenceCallback: Send + 'static {
/// When this object is dropped, the callback is automatically removed if it
/// hasn't been called yet.
#[pin_data(PinnedDrop)]
-pub struct FenceCallbackRegistration<T: FenceCallback + 'static> {
+pub struct FenceCallbackRegistration<T: FenceCallback> {
#[pin]
callback_foreign: Opaque<bindings::dma_fence_cb>,
callback: ManuallyDrop<T>,
@@ -326,7 +326,7 @@ impl<T: FenceCallback> FenceCallbackRegistration<T> {
/// On success the callback is pinned in place and will fire when the fence
/// signals. On `AlreadySignaled` the callback is returned to the caller so
/// that owned resources can be reclaimed.
- pub fn new<'a>(fence: &'a Fence, callback: T) -> impl PinInit<Self, CallbackError<T>> + 'a
+ pub unsafe fn new<'a>(fence: &'a Fence, callback: T) -> impl PinInit<Self, CallbackError<T>> + 'a
where
T: 'a,
{
@@ -693,7 +693,8 @@ struct DriverFenceData<'a, T: Send + Sync + FenceContextOps> {
///
/// let cb_data = CallbackData { };
/// let waiting_fence = ARef::from(fence.as_fence());
-/// let cb_reg = FenceCallbackRegistration::new(&waiting_fence, cb_data);
+/// // SAFETY: `cb_data`'s content is not forgotten.
+/// let cb_reg = unsafe { FenceCallbackRegistration::new(&waiting_fence, cb_data) };
/// let cb_reg = KBox::pin_init(cb_reg, GFP_KERNEL)?;
///
/// // TODO signalling guards
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH v5 2/6] rust: DmaFence: Implement Deref for FenceContext
2026-10-09 19:11 [RFC PATCH v5 0/6] rust: Add drm::JobQueue Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 1/6] rust: DmaFence: remove static lifetime Philipp Stanner
@ 2026-10-09 19:11 ` Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 3/6] rust: DmaFence: Don't drop on signal Philipp Stanner
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Philipp Stanner @ 2026-10-09 19:11 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Sumit Semwal, Christian König,
Philipp Stanner, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter,
Boris Brezillon, John.harrison, da.gomez
Cc: linux-kernel, linux-media, dri-devel, rust-for-linux
A FenceContext carries data, which the user should be able to access.
Implement Deref for FenceContext.
Signed-off-by: Philipp Stanner <phasta@kernel.org>
---
rust/kernel/dma_buf/dma_fence.rs | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/rust/kernel/dma_buf/dma_fence.rs b/rust/kernel/dma_buf/dma_fence.rs
index eef2b9e93f35..0028124e6aa6 100644
--- a/rust/kernel/dma_buf/dma_fence.rs
+++ b/rust/kernel/dma_buf/dma_fence.rs
@@ -86,6 +86,14 @@ pub struct FenceContext<T: FenceContextOps + Send + Sync> {
data: T,
}
+impl<T: Send + Sync + FenceContextOps> Deref for FenceContext<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
impl<'a, T: Send + Sync + FenceContextOps> FenceContext<T> {
// This can later be extended as a vtable in case other parties need support
// for the more "exotic" callbacks.
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH v5 3/6] rust: DmaFence: Don't drop on signal
2026-10-09 19:11 [RFC PATCH v5 0/6] rust: Add drm::JobQueue Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 1/6] rust: DmaFence: remove static lifetime Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 2/6] rust: DmaFence: Implement Deref for FenceContext Philipp Stanner
@ 2026-10-09 19:11 ` Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 4/6] rust: DmaFence: [RFC] Add Signaler Philipp Stanner
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Philipp Stanner @ 2026-10-09 19:11 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Sumit Semwal, Christian König,
Philipp Stanner, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter,
Boris Brezillon, John.harrison, da.gomez
Cc: linux-kernel, linux-media, dri-devel, rust-for-linux
The original design for DmaFence intended to comply as strictly as
possible with the "dma_fence contract", which, according to some, says
that a fence must only be signaled once. Thus, the implementation was
made so that a DriverFence drops on signal.
While extending JobQueue's capabilities, it was discovered that this
characteristic causes - unnecessary - trouble: fence-data must be
dropped in an RCU-deferred manner because the C backend demands that.
However, fences are often signaled in atomic context (e.g., interrupt
handler) where you don't want to drop, but signal as quickly as
possible.
Actually, there is no real hard reason to enforce that a fence can only
get signaled once, because the C backend is robust against multiple
signal attempts.
What is decisive is that a) all fences signal and b) they only drop no
earlier than 1 RCU grace period after signaling.
Don't drop a DriverFence on signal().
Signed-off-by: Philipp Stanner <phasta@kernel.org>
---
rust/kernel/dma_buf/dma_fence.rs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rust/kernel/dma_buf/dma_fence.rs b/rust/kernel/dma_buf/dma_fence.rs
index 0028124e6aa6..b67121c7abd4 100644
--- a/rust/kernel/dma_buf/dma_fence.rs
+++ b/rust/kernel/dma_buf/dma_fence.rs
@@ -806,7 +806,7 @@ pub fn as_fence(&self) -> &Fence {
}
/// Signal the fence. This will invoke all registered callbacks.
- pub fn signal(self, res: Result) {
+ pub fn signal(&self, res: Result) {
let fence = self.as_fence().lock();
// SAFETY: `fence` is valid because `self` is valid. The lock must be
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH v5 4/6] rust: DmaFence: [RFC] Add Signaler
2026-10-09 19:11 [RFC PATCH v5 0/6] rust: Add drm::JobQueue Philipp Stanner
` (2 preceding siblings ...)
2026-10-09 19:11 ` [RFC PATCH v5 3/6] rust: DmaFence: Don't drop on signal Philipp Stanner
@ 2026-10-09 19:11 ` Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 5/6] rust: DmaFence: Replace call_rcu() with synchronize_rcu() Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 6/6] rust: drm: Add JobQueue Philipp Stanner
5 siblings, 0 replies; 7+ messages in thread
From: Philipp Stanner @ 2026-10-09 19:11 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Sumit Semwal, Christian König,
Philipp Stanner, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter,
Boris Brezillon, John.harrison, da.gomez
Cc: linux-kernel, linux-media, dri-devel, rust-for-linux
Depending on what the JobQueue locking design shall look like, it might
be necessary to be able to signal fences without having to access the
*locked* data structure the fences reside in.
Add a Signaler type to achieve that.
This type is not yet used in this patch series. The patch merely serves
showing the idea so that we have material to discuss.
Signed-off-by: Philipp Stanner <phasta@kernel.org>
---
rust/kernel/dma_buf/dma_fence.rs | 49 ++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/rust/kernel/dma_buf/dma_fence.rs b/rust/kernel/dma_buf/dma_fence.rs
index b67121c7abd4..4e9b4a6a9471 100644
--- a/rust/kernel/dma_buf/dma_fence.rs
+++ b/rust/kernel/dma_buf/dma_fence.rs
@@ -590,6 +590,45 @@ unsafe fn dec_ref(ptr: NonNull<Self>) {
}
}
+/// An object that can signal an associated [`DriverFence`], independently of
+/// that one's lifetime.
+///
+/// Use with great caucion! Only the producer of a fence should ever be able to
+/// signal it.
+///
+/// This helper is necessary to signal DriverFences that are hidden behind locks
+/// which cannot be taken in certain situations.
+pub struct FenceSignaler {
+ fence: ARef<Fence>,
+}
+
+impl FenceSignaler {
+ /// Signal the associated [`DriverFence`].
+ pub fn signal(&self, res: Result) {
+ let fence = self.fence.lock();
+
+ // SAFETY: `fence` is valid because `self` is valid. The lock must be
+ // held, which we acquired directly above.
+ if !unsafe { bindings::dma_fence_test_signaled_flag(fence.as_raw()) } {
+ if let Err(err) = res {
+ // SAFETY: `fence` is valid because `self` is valid. The fence
+ // must not have been signaled yet, which we check directly above.
+ unsafe { bindings::dma_fence_set_error(fence.as_raw(), err.to_errno()) };
+ }
+ // SAFETY: `fence` is valid because `self` is valid. The lock must
+ // be held, which we acquired above.
+ unsafe { bindings::dma_fence_signal_locked(fence.as_raw()) };
+ }
+
+ // TODO: This should decrement the nr-of-unsignaled-fences tracker in fctx
+ }
+
+ /// Get the associated fence's sequence number.
+ pub fn seqno(&self) -> u64 {
+ self.fence.seqno()
+ }
+}
+
// Necessary to guarantee that `inner` always comes first and can be freed by C.
// Also useful for using casts instead of container_of().
#[repr(C)]
@@ -805,6 +844,16 @@ pub fn as_fence(&self) -> &Fence {
unsafe { Fence::from_raw(self.as_raw()) }
}
+ /// Create a [`FenceSignaler`] associated with this `DriverFence`.
+ ///
+ /// Use with caution! Only the producer of a [`DriverFence`] should be able
+ /// to signal it.
+ pub fn new_signaler(&self) -> FenceSignaler {
+ FenceSignaler {
+ fence: ARef::from(self.as_fence()),
+ }
+ }
+
/// Signal the fence. This will invoke all registered callbacks.
pub fn signal(&self, res: Result) {
let fence = self.as_fence().lock();
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH v5 5/6] rust: DmaFence: Replace call_rcu() with synchronize_rcu()
2026-10-09 19:11 [RFC PATCH v5 0/6] rust: Add drm::JobQueue Philipp Stanner
` (3 preceding siblings ...)
2026-10-09 19:11 ` [RFC PATCH v5 4/6] rust: DmaFence: [RFC] Add Signaler Philipp Stanner
@ 2026-10-09 19:11 ` Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 6/6] rust: drm: Add JobQueue Philipp Stanner
5 siblings, 0 replies; 7+ messages in thread
From: Philipp Stanner @ 2026-10-09 19:11 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Sumit Semwal, Christian König,
Philipp Stanner, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter,
Boris Brezillon, John.harrison, da.gomez
Cc: linux-kernel, linux-media, dri-devel, rust-for-linux
The C dma_fence contract demands that a fence's data disappears no
earlier than 1 RCU grace period after the DriverFence was signaled.
So far, we achieved this with dropping the DriverFence's data in a
deferred manner with a manual, raw binding call to call_rcu().
However, this call_rcu() does not solve one problem: It is conceivable
that some driver data must not drop in atomic context; but call_rcu()'s
payload *can* be executed in atomic context.
Moreover, the current developments in drm::JobQueue, where a DriverFence
is always coupled 1:1 with a Job, allows for dropping job and fence
through a work item.
The undesirable blocking behavior of synchronize_rcu() would, thus, not
be annoying any thread anymore. The direct users of DmaFence could
achieve the same by dropping their DriverFences through work items.
The only conceivable other solution, queue_rcu_work(), would, for many
users, schedule a work_item from another work_item, which seems
undesirable.
Additionally, using the existing synchronize_rcu() abstraction has the
advantage of us getting rid of a raw bindings call.
Replace DriverFence::drop()'s call_rcu() with synchronize_rcu().
Signed-off-by: Philipp Stanner <phasta@kernel.org>
---
rust/kernel/dma_buf/dma_fence.rs | 71 +++++++-------------------------
1 file changed, 14 insertions(+), 57 deletions(-)
diff --git a/rust/kernel/dma_buf/dma_fence.rs b/rust/kernel/dma_buf/dma_fence.rs
index 4e9b4a6a9471..cefcde80bf09 100644
--- a/rust/kernel/dma_buf/dma_fence.rs
+++ b/rust/kernel/dma_buf/dma_fence.rs
@@ -42,7 +42,8 @@
Atomic,
Relaxed, //
},
- rcu::rcu_barrier, //
+ rcu::rcu_barrier,
+ rcu::synchronize_rcu, //
}, //
};
@@ -150,7 +151,6 @@ pub fn new_fence_allocation(
data: T::FenceDataType,
) -> Result<DriverFenceAllocation<'_, T>> {
let fence_data = DriverFenceData {
- rcu_head: Default::default(),
// `inner` remains uninitialized until a `DriverFence` takes over.
inner: Fence {
inner: Opaque::uninit(),
@@ -640,8 +640,6 @@ struct DriverFenceData<'a, T: Send + Sync + FenceContextOps> {
// necessary so that the C backend can free the allocation (coming from our
// Rust code) with kfree_rcu().
inner: Fence,
- /// Callback head for dropping this in a deferred manner through RCU.
- rcu_head: bindings::callback_head,
/// Reference to access the FenceContext.
fctx: &'a FenceContext<T>,
/// The API user's data. It is essential that the data only performs
@@ -1022,59 +1020,18 @@ fn drop(&mut self) {
return;
}
- // SAFETY: Valid because `self` is valid.
- let rcu_head_ptr = unsafe { &raw mut (*self.data.as_ptr()).rcu_head };
+ // Make sure none of the fence backend_ops can access data anymore.
+ //
+ // TODO:
+ // This would not be necessary if the C dma_fence backend were using a
+ // spinlock to properly synchronize its signaled state. Fix it in C and
+ // then remove synchronize_rcu().
+ synchronize_rcu();
- // SAFETY: `call_rcu()` is always safe to be called. `rcu_head_ptr` was
- // created validly above. The module must perform a `synchronize_rcu()`
- // or `rcu_barrier()` call to guard against module unload.
- unsafe { bindings::call_rcu(rcu_head_ptr, Some(drop_driver_fence_data::<T>)) };
+ // SAFETY: Valid because `self` is valid.
+ unsafe { drop_in_place(&raw mut self.data) };
+
+ // SAFETY: The `synchronize_rcu()` above ensures all accessors are gone.
+ unsafe { bindings::dma_fence_put(self.as_raw()) };
}
}
-
-// TODO:
-// The entire call_rcu() mechanism in the drop above and the code below would be
-// unnecessary if C's dma_fence_signal() could be reworked in a way that after it
-// ran, the caller knows that no fence_ops callbacks can be running anymore.
-// In other words, if the dma_fence backend would use its spinlock for full
-// synchronization.
-//
-// Then we could move the drop_in_place() and dma_fence_put() upwards into the
-// drop() implementation and call it a day.
-
-/// Finally really drop this `DriverFence<T>`
-///
-/// # Safety
-///
-/// `head` references the `rcu_head` field of an `DriverFenceData<T>`. All
-/// accessors to that `DriverFenceData<T>` must be gone by now. This must be
-/// ensured by signalling the associated `DriverFence<T>` and then waiting
-/// for a grace period until calling this function here.
-unsafe extern "C" fn drop_driver_fence_data<T: Send + Sync + FenceContextOps>(
- head: *mut bindings::callback_head,
-) {
- // SAFETY: Caller provides a pointer to the `rcu_head` field of a `DriverFenceData<C>`.
- let fence_data = unsafe { container_of!(head, DriverFenceData<'_, T>, rcu_head) };
-
- // SAFETY: `fence_data` was created validly above. All the fence's data will
- // only drop below, but the raw pointer to the raw C `dma_fence` remains
- // valid because the reference count is only decremented at the end of the
- // function.
- let fence = unsafe { (*fence_data).inner.inner.get() };
-
- // SAFETY: `fence_data` was created validly above. The user has already
- // dropped the only conventional accessor to the user data, the `DriverFence`,
- // one grace period ago. All accessors are gone now.
- unsafe { drop_in_place(&raw mut (*fence_data).data) };
-
- // The inner `Fence` explicitly does not get dropped because there may be
- // many more users / consumers, each holding their own reference.
-
- // SAFETY: Once a `DriverFence` is initialized, the inner `fence` is valid
- // and initialized. It is valid until the refcount drops to 0, which can
- // earliest happen once we drop the `DriverFence`'s reference here.
- unsafe { bindings::dma_fence_put(fence) };
-
- // The actual memory the data associated with a `DriverFence` lives in
- // gets freed by the C dma_fence backend once the fence's refcount reaches 0.
-}
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [RFC PATCH v5 6/6] rust: drm: Add JobQueue
2026-10-09 19:11 [RFC PATCH v5 0/6] rust: Add drm::JobQueue Philipp Stanner
` (4 preceding siblings ...)
2026-10-09 19:11 ` [RFC PATCH v5 5/6] rust: DmaFence: Replace call_rcu() with synchronize_rcu() Philipp Stanner
@ 2026-10-09 19:11 ` Philipp Stanner
5 siblings, 0 replies; 7+ messages in thread
From: Philipp Stanner @ 2026-10-09 19:11 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Sumit Semwal, Christian König,
Philipp Stanner, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, David Airlie, Simona Vetter,
Boris Brezillon, John.harrison, da.gomez
Cc: linux-kernel, linux-media, dri-devel, rust-for-linux
JobQueue is a pure Rust component that handles job submissions for GPUs
with firmware scheduling, i.e., GPUs that support one exclusive ring per
execution context.
JobQueue's primary task is dependency handling, where the user can set
dependencies, each in the form of a `dma_buf::Fence`, on a job and then
submit it to the queue.
Jobs are being executed in order, following the FIFO principle. Only
jobs whose dependencies are all fullfilled get executed, otherwise the
JobQueue pauses.
A core design principle, just like with DmaFence, is to avoid all
allocations in job submission paths, since this runs danger of
deadlocking.
Another design principle, new when compared to the older drafts, is the
ownership relationship with fences:
1. The JobQueue owns the FenceContext exclusively.
2. Jobs are created on the JobQueue, ensuring correct fence sequence
number ordering.
3. Jobs own the respective DriverFence.
4. Jobs are owned by the JobQueue.
Consequently, a driver can only interact with fences and FenceContext
indirectly through the JobQueue, and can also only complete jobs (and,
hence, signal fences) through the JobQueue.
Unfortunately, since jobs need a reference to the JobQueue for their
fence dependency objects (DependencyWaker), this code can currently not
be used in this form because some work on self-referential PinInit is
required. See the respective comments in the Example.
When a job shall be run, both its JobQueue::data and Job::data get
borrowed back to it, together with the sequence number of this job, so
that the driver can perform the appropriate interaction with the
hardware.
The submit_worker runs all ready jobs based on a credit count system, so
that run_job() can be infallible. The same worker drops jobs (and their
fences) that are already completed. Should this proof to be a
performance issue, a second worker could trivially be added for this.
Signed-off-by: Philipp Stanner <phasta@kernel.org>
---
rust/kernel/drm/job_queue.rs | 525 +++++++++++++++++++++++++++++++++++
rust/kernel/drm/mod.rs | 4 +
2 files changed, 529 insertions(+)
create mode 100644 rust/kernel/drm/job_queue.rs
diff --git a/rust/kernel/drm/job_queue.rs b/rust/kernel/drm/job_queue.rs
new file mode 100644
index 000000000000..b5c7f4ecb3f4
--- /dev/null
+++ b/rust/kernel/drm/job_queue.rs
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2026 Red Hat Inc.
+ * Author: Philipp Stanner <pstanner@redhat.com>
+ */
+
+//! DRM's job dependency manager and load balancer for GPUs with firmware
+//! scheduling, i.e., GPUs that spawn one ring per user context.
+//!
+//! JobQueue abstracts the entire handling of [`dma_buf::FenceContext`] and its
+//! associated fences for the user.
+//!
+//! Jobs created on the JobQueue take over ownership of userspace provided data
+//! and prepare all memory allocations. A later job submission sets up the fence
+//! and performs no fallible operations. A fence sequence number always
+//! corresponds 1:1 with the job sequence number.
+//!
+//! Before jobs are submitted, dependencies in the form of [`dma_buf::Fence`]
+//! can be set on them. Jobs get submitted by the JobQueue in process context
+//! through a work item in FIFO order. Only jobs whose dependency fences have
+//! all been signaled get submitted. If the front job's dependency is not yet
+//! fullfilled, the JobQueue pauses until that's the case.
+//!
+//! The driver tells the JobQueue which jobs are finished through
+//! [`JobQueue::complete_jobs_up_to_seqno`].
+//!
+//! This is pure Rust code that does not directly abstract C.
+
+use crate::prelude::*;
+
+use core::{
+ convert::Infallible,
+ ops::Deref, //
+};
+
+use kernel::{
+ dma_buf::dma_fence::{
+ DriverFence,
+ DriverFenceAllocation,
+ Fence,
+ FenceContext,
+ FenceCallback,
+ FenceCallbackRegistration,
+ FenceContextOps, //
+ },
+ list::*,
+ revocable::Revocable,
+ str::CString,
+ sync::{
+ aref::ARef,
+ SpinLockIrq,
+ new_spinlock_irq,
+ atomic::{
+ Atomic,
+ Relaxed, //
+ }, //
+ }, //
+ workqueue::{
+ self,
+ new_scoped_work,
+// ScopedQueue,
+ ScopedWork,
+ ScopedWorkItem,
+ ScopedWorkRef,
+ }, //
+};
+
+struct DependencyWaker<'b, T: JobQueueOps + Send + Sync> {
+ nr_of_deps: &'b Atomic<u32>,
+ jq: &'b JobQueue<'b, T>,
+}
+
+impl<'b, T: JobQueueOps + Send + Sync> FenceCallback for DependencyWaker<'b, T> {
+ fn on_signal(&mut self) {
+ // Last dependency kicks off the worker.
+ if self.nr_of_deps.fetch_sub(1, Relaxed) == 1 {
+ if let Some(jq) = self.jq.inner.try_access() {
+ let mut jq = jq.deref().lock();
+ jq.as_mut().check_start_submit_worker();
+ }
+ }
+ }
+}
+
+/// A JobQueue job before it has been submitted.
+///
+/// This is only useful for registering dependencies on and for submitting it.
+pub struct Job<'a, T: JobQueueOps + Send + Sync> {
+ inner: ListArc<JobInternal<'a, T>>,
+}
+
+impl<'a, T: JobQueueOps + Send + Sync> Job<'a, T> {
+ /// Add `fence` as a dependency for this job.
+ ///
+ /// A JobQueue will only push a job once all dependency fences have been
+ /// signaled.
+ pub fn add_dependency(&'a mut self, fence: &Fence) -> Result {
+ let nr_of_deps = &raw const self.inner.nr_of_deps;
+
+ let waker = DependencyWaker {
+ // SAFETY: `nr_of_deps` lives as long as the job.
+ // TODO RFC: Is there a better way to do it? The mutable-immutable references
+ // otherwise run into conflict when `nr_of_deps` is used below.
+ nr_of_deps: unsafe { &*nr_of_deps },
+ jq: self.inner.jq,
+ };
+
+ let job = &mut self.inner;
+
+ job.dependencies().reserve(1, GFP_KERNEL)?;
+
+ // If the registration below gets registered successfully, a signaling
+ // fence will decrement the atomic. That could race. Thus, increment in
+ // advance. Ordering is ensured thanks to the dma_fence backend.
+ job.nr_of_deps.fetch_add(1, Relaxed);
+
+ // SAFETY: The `registration` is not forgotten. It drops with JobQueue.
+ let registration = unsafe { FenceCallbackRegistration::new(fence, waker) };
+ match KBox::pin_init(registration, GFP_KERNEL) {
+ Err(e) => {
+ let _ = job.nr_of_deps.fetch_sub(1, Relaxed);
+ if e == ENOENT {
+ // Was already signaled. No reason to bother the caller.
+ return Ok(());
+ }
+ return Err(e);
+ },
+ Ok(dep) => {
+ if let Err(e) = job.dependencies().push_within_capacity(dep) {
+ // Impossible because of the reservation.
+ pr_err!("Job::add_dependency(): reserved memory not available.\n");
+ return Err(e.into());
+ }
+ },
+ }
+
+ Ok(())
+ }
+}
+
+#[pin_data]
+#[repr(C)]
+struct JobInternal<'b, T: JobQueueOps + Send + Sync> {
+ #[pin]
+ links: ListLinks,
+ cost: u32,
+ fence_allocation: ListArcField<Option<DriverFenceAllocation<'b, DriverDataWrapper<T>>>, 0>,
+ fence: ListArcField<Option<DriverFence<'b, DriverDataWrapper<T>>>>,
+ dependencies: ListArcField<KVec<Pin<KBox<FenceCallbackRegistration<DependencyWaker<'b, T>>>>>>,
+ nr_of_deps: Atomic<u32>,
+ jq: &'b JobQueue<'b, T>,
+}
+
+impl_list_arc_safe! {
+ impl{'b, T: JobQueueOps + Send + Sync} ListArcSafe<0> for JobInternal<'b, T> { untracked; }
+}
+impl_list_item! {
+ impl{'b, T: JobQueueOps + Send + Sync} ListItem<0> for JobInternal<'b, T> { using ListLinks { self.links }; }
+}
+
+
+impl<'b, T: JobQueueOps + Send + Sync> JobInternal<'b, T> {
+// const LIST_JOB_ID: u64 = 0xe7e445f2bb9e97fc;
+
+ kernel::list::define_list_arc_field_getter! {
+ pub(crate) fn fence_allocation(&mut self<0>) -> &mut Option<DriverFenceAllocation<'b, DriverDataWrapper<T>>> { fence_allocation }
+ pub(crate) fn fence(&mut self<0>) -> &mut Option<DriverFence<'b, DriverDataWrapper<T>>> { fence }
+ pub(crate) fn dependencies(&mut self<0>) -> &mut KVec<Pin<KBox<FenceCallbackRegistration<DependencyWaker<'b, T>>>>> { dependencies }
+ }
+}
+
+#[pin_data]
+struct SubmitWorker<'a, T: JobQueueOps + Send + Sync> {
+ #[pin]
+ jq: &'a JobQueue<'a, T>,
+}
+
+impl<T: JobQueueOps + Send + Sync> ScopedWorkItem for SubmitWorker<'_, T> {
+ fn run(work: &ScopedWorkRef<Self>) {
+ let Some(jq_accessor) = work.jq.inner.try_access() else {
+ return;
+ };
+
+ let mut jq = jq_accessor.lock();
+
+ while let Some(mut job) = jq.as_mut().project().waiting_jobs.pop_front() {
+ let new_capacity = jq.capacity as i64 - job.cost as i64;
+ if new_capacity < 0 {
+ break;
+ }
+ *jq.as_mut().project().capacity = new_capacity as u64;
+
+ let runnable_job = JobRunnable {
+ seqno: job.fence().as_ref().expect("fence not present").as_fence().seqno(),
+ data: job.fence().as_ref().expect("fence not present").deref(),
+ };
+
+ let jq_data = work.jq.fctx.deref().deref();
+
+ drop(jq);
+ T::run_job(jq_data, runnable_job);
+ // The driver could now have taken the lock here and try to complete
+ // this very job. It would then not get signaled because it was not
+ // yet in the running list. However, since the driver completes jobs
+ // by sequence number, its subsequent calls to jq.complete_jobs_up_to_seqno()
+ // will signal this job, because then it will be in the running_jobs
+ // list.
+ // We accept this race because it makes the implementation much
+ // simpler and avoids locking issues. Otherwise, complete_jobs_up_to_seqno()
+ // would need a distinct signaler-object-list to avoid races between
+ // this work item and said function.
+ jq = jq_accessor.lock();
+
+ jq.as_mut().project().running_jobs.push_back(job);
+ }
+
+ // Now clean up all already-signaled jobs, dropping their fences.
+ let mut jq = jq.as_mut().project();
+
+ let mut cursor = jq.running_jobs.cursor_front();
+
+ while let Some(job) = cursor.peek_next() {
+ let mut job = job.remove();
+
+ if !job.fence().as_mut().expect("fence not present").as_fence().is_signaled() {
+ jq.running_jobs.push_front(job);
+ // Jobs complete in order. No point in checking the rest.
+ break;
+ }
+ }
+
+ *jq.submit_worker_active = false;
+ }
+}
+
+#[pin_data]
+struct JobQueueInner<'a, T: JobQueueOps + Send + Sync> {
+ submit_worker_active: bool,
+ #[pin]
+ submit_worker: ScopedWork<SubmitWorker<'a, T>>,
+ #[pin]
+ waiting_jobs: List<JobInternal<'a, T>>,
+ #[pin]
+ running_jobs: List<JobInternal<'a, T>>,
+ capacity: u64,
+}
+
+impl<T: JobQueueOps + Send + Sync> JobQueueInner<'_, T> {
+ fn check_start_submit_worker(self: Pin<&mut Self>) {
+ if self.submit_worker_active {
+ return;
+ }
+
+ // TODO this shouldn't be the system wq
+ // SAFETY: The worker is carried by the JobQueue and, thus, cannot be
+ // forgotten.
+ unsafe { workqueue::system_dfl().enqueue_scoped(&*self.submit_worker) };
+
+ *self.project().submit_worker_active = true;
+ }
+
+}
+
+/// A JobQueue.
+///
+/// # Examples
+///
+/// ```
+/// use core::ops::Deref;
+///
+/// use kernel::{
+/// str::CString,
+/// dma_buf::*,
+/// drm::{
+/// JobQueue,
+/// JobQueueOps,
+/// JobRunnable,
+/// },
+/// };
+///
+/// let driver_name = CString::try_from_fmt(fmt!("dummy_driver"))?;
+/// let timeline_name = CString::try_from_fmt(fmt!("dummy_timeline"))?;
+///
+/// #[pin_data]
+/// struct JqData {}
+///
+/// impl JqData {
+/// fn new() -> impl PinInit<Self> {
+/// pin_init!(Self {})
+/// }
+/// }
+///
+/// struct JobData {
+/// data: CString,
+/// }
+///
+/// impl JobQueueOps for JqData {
+/// type JobDataType = JobData;
+///
+/// fn run_job<'a>(&self, job: JobRunnable<'a, JobData>) {
+///
+/// }
+/// }
+///
+/// let jq_data = JqData::new();
+/// let jq = KBox::pin_init(JobQueue::new(0, driver_name, timeline_name, 1000, jq_data), GFP_KERNEL)?;
+///
+/// struct Callback {}
+///
+/// impl FenceCallback for Callback {
+/// fn on_signal(&mut self) {
+///
+/// }
+/// }
+///
+/// let data = CString::try_from_fmt(fmt!("hello there!"))?;
+/// let data = JobData { data };
+///
+/// // TODO RFC:
+/// // This is the problem where self-references come into play.
+/// // Jobs need a reference to the JobQueue due to their DependencyWaker
+/// // callbacks. At the same time, the JobQueue owns the jobs.
+/// // So the following outcommented code would not compile.
+/// // Gary is working on a solution for that.
+///
+/// //let job = jq.new_job(data)?;
+/// //jq.submit(job);
+///
+/// //drop(job);
+/// //drop(jq);
+///
+/// Ok::<(), Error>(())
+/// ```
+#[pin_data(PinnedDrop)]
+pub struct JobQueue<'a, T: JobQueueOps + Send + Sync> {
+ #[pin]
+ // Revocable to prevent a DependencyWaker firing in the moment of JQ-drop
+ // from deadlocking.
+ inner: Revocable<SpinLockIrq<JobQueueInner<'a, T>>>,
+ #[pin]
+ fctx: FenceContext<DriverDataWrapper<T>>,
+}
+
+// SAFETY: The JobQueue is locked and pinned.
+unsafe impl<T: JobQueueOps + Send + Sync> Sync for JobQueue<'_, T> {}
+// SAFETY: The JobQueue is locked and pinned.
+unsafe impl<T: JobQueueOps + Send + Sync> Send for JobQueue<'_, T> {}
+
+/// Object passed to the driver to run a job.
+pub struct JobRunnable<'a, T> {
+ /// The job's sequence number.
+ pub seqno: u64,
+ /// The associated job data.
+ pub data: &'a T,
+}
+
+/// Ops for the JobQueue.
+pub trait JobQueueOps {
+ /// The type of the data passed over in [`JobQueue::new_job`].
+ type JobDataType: Send + Sync;
+
+ /// Instructs the driver to run this job.
+ ///
+ /// Returns true if the job was run, false if there was no capacity.
+ /// In case of an other error, the driver's callback implementation likely
+ /// wants to trigger termination of the ring and, thus, dropping of the
+ /// respective [`JobQueue`].
+ fn run_job<'a>(&self, job: JobRunnable<'a, Self::JobDataType>);
+}
+
+#[pin_data]
+struct DriverDataWrapper<T: JobQueueOps + Send + Sync> {
+ #[pin]
+ data: T,
+}
+
+impl<T: JobQueueOps + Send + Sync> Deref for DriverDataWrapper<T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T: JobQueueOps + Send + Sync> DriverDataWrapper<T> {
+ fn new<E>(data: impl PinInit<T, E>) -> impl PinInit<Self, E>
+ where
+ Error: From<E>,
+ E: From<Infallible>,
+ {
+ try_pin_init!(Self {
+ data <- data,
+ }? E)
+ }
+}
+
+impl<T: JobQueueOps + Send + Sync> FenceContextOps for DriverDataWrapper<T> {
+ type FenceDataType = T::JobDataType;
+}
+
+impl<'a, T: JobQueueOps + Send + Sync> JobQueue<'a, T> {
+ /// Create a new JobQueue.
+ pub fn new<E>(
+ initial_seqno: u64,
+ // TODO: pass these as &CStr. Figure out how to get the lifetimes for PinInit right.
+ driver_name: CString,
+ timeline_name: CString,
+ capacity: u64,
+ data: impl PinInit<T, E>,
+ ) -> impl PinInit<Self, Error>
+ where
+ Error: From<E>,
+ E: From<Infallible>,
+ {
+ try_pin_init!(&this in Self {
+ inner <- Revocable::new(new_spinlock_irq!(try_pin_init!(JobQueueInner {
+ submit_worker_active: false,
+ // SAFETY: `this` is valid. The lifetimes ensure that the reference remains valid.
+ submit_worker <- new_scoped_work!("JobQueueSubmitWorker", Ok(SubmitWorker { jq: unsafe { &*this.as_ptr() } })),
+ waiting_jobs: List::new(),
+ running_jobs: List::new(),
+ capacity,
+ }))),
+ fctx <- FenceContext::new(initial_seqno, &driver_name, &timeline_name, DriverDataWrapper::new(data)),
+ })
+ }
+
+ /// Create a new job.
+ pub fn new_job(&'a self, cost: u32, data: T::JobDataType) -> Result<Job<'a, T>> {
+ let fence_allocation = self.fctx.new_fence_allocation(data)?;
+ let job = try_pin_init!(JobInternal {
+ links <- ListLinks::new(),
+ cost,
+ fence_allocation: ListArcField::new(Some(fence_allocation)),
+ fence: ListArcField::new(None),
+ nr_of_deps: Atomic::new(0),
+ dependencies <- ListArcField::new(KVec::new()),
+ jq: &self,
+ });
+
+ let inner = ListArc::pin_init(job, GFP_KERNEL)?;
+
+ Ok(Job { inner })
+ }
+
+ // TODO RFC the lifetime ensures that only jobs can be submitted that were
+ // actually created on this JQ, right?
+ /// Submit a job that has previously been created with [`JobQueue::new_job()`].
+ ///
+ /// Returns a [`dma_buf::Fence`] on which the user can register callbacks.
+ /// The fence will be signaled once the job is completed through
+ /// [`JobQueue::complete_jobs_up_to_seqno`], which can happen immediately
+ /// after the job has been submitted.
+ ///
+ /// This function does all fence initialization under the JobQueue lock in
+ /// one go, including setting of the fence sequence numbers. Thus, it is
+ /// typically unnecessary (and advaised against) calling this function with
+ /// a driver lock held.
+ // Mutable `self` so that the user is forced to ensure synchronization.
+ // That's a bit better so that the fence sequence numbers stay ordered.
+ pub fn submit(&mut self, job: Job<'a, T>) -> ARef<Fence> {
+ // unwrap() can't fire because `self` is valid.
+ let jq = self.inner.try_access().unwrap();
+ // The lock orders setting of the sequence number, so no driver lock is needed.
+ let mut jq = jq.lock();
+ let mut job = job.inner;
+ let fence = job.fence_allocation().take().expect("fence_allocation not present").new_fence();
+
+ let ret_fence = ARef::from(fence.as_fence());
+
+ *job.fence() = Some(fence);
+
+ jq.as_mut().project().waiting_jobs.push_back(job);
+ jq.as_mut().check_start_submit_worker();
+
+ ret_fence
+ }
+
+ /// Complete all the jobs in the queue's running_list, up to `seqno`.
+ ///
+ /// This signals the jobs' fences and drops both job and fence.
+ ///
+ /// This function tells the implementation that there is now more capacity
+ /// for more jobs to be pushed into the GPU ring.
+ pub fn complete_jobs_up_to_seqno(&self, seqno: u64, status: Result) {
+ // unwrap() can't fire because `self` is valid.
+ let jq = self.inner.try_access().unwrap();
+ let mut jq_locked = jq.lock();
+ let mut jq = jq_locked.as_mut().project();
+
+ let mut cursor = jq.running_jobs.cursor_front();
+
+ while let Some(job) = cursor.peek_next() {
+ let mut job = job.remove();
+
+ if job.fence().as_mut().expect("fence not present").as_fence().seqno() > seqno {
+ jq.running_jobs.push_front(job);
+ break;
+ }
+
+ *jq.capacity += job.cost as u64;
+
+ if let Some(fence) = job.fence() {
+ fence.signal(status);
+ } else {
+ panic!("fence not present.");
+ }
+ }
+
+ jq_locked.as_mut().check_start_submit_worker();
+ }
+}
+
+#[pinned_drop]
+impl<T: JobQueueOps + Send + Sync> PinnedDrop for JobQueue<'_, T> {
+ fn drop(self: Pin<&mut Self>) {
+ // Prevent all dependency callbacks (and the submit_worker) from running
+ // into `self.inner`. Drops the DependencyWaker objects, deregistering
+ // their events on foreign fences. Thus, this revoke also protects against
+ // deadlock since deregistering takes the fence locks in reverse order
+ // to the JobQueue lock, which we would need here without the Revocable
+ // for said deregistering.
+ let _ = self.inner.revoke();
+ }
+}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index fd6ed35bc35a..f2b7fbbce5bb 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -8,6 +8,7 @@
pub mod gem;
pub mod gpuvm;
pub mod ioctl;
+pub mod job_queue;
pub use self::device::Device;
pub use self::device::DeviceContext;
@@ -20,6 +21,9 @@
pub use self::driver::DriverInfo;
pub use self::driver::Registration;
pub use self::file::File;
+pub use self::job_queue::JobQueue;
+pub use self::job_queue::JobQueueOps;
+pub use self::job_queue::JobRunnable;
pub(crate) mod private {
pub trait Sealed {}
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-10-09 19:14 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-09 19:11 [RFC PATCH v5 0/6] rust: Add drm::JobQueue Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 1/6] rust: DmaFence: remove static lifetime Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 2/6] rust: DmaFence: Implement Deref for FenceContext Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 3/6] rust: DmaFence: Don't drop on signal Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 4/6] rust: DmaFence: [RFC] Add Signaler Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 5/6] rust: DmaFence: Replace call_rcu() with synchronize_rcu() Philipp Stanner
2026-10-09 19:11 ` [RFC PATCH v5 6/6] rust: drm: Add JobQueue Philipp Stanner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®