From: sashiko-bot@kernel.org
To: "Philipp Stanner" <phasta@kernel.org>
Cc: media-ci@linuxtv.org, gary@garyguo.net, ojeda@kernel.org,
dri-devel@lists.freedesktop.org
Subject: Re: [RFC PATCH v5 6/6] rust: drm: Add JobQueue
Date: Fri, 09 Oct 2026 19:24:20 +0000 [thread overview]
Message-ID: <sashiko-outbox-165998@kernel.org> (raw)
In-Reply-To: <20261009191124.1022902-8-phasta@kernel.org>
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] drm/job_queue: recursive spinlock deadlock when signaling intra-queue dependencies
- [High] drm/job_queue: job is permanently lost when queue capacity is exceeded
- [High] drm/job_queue: submit_worker completely ignores dependencies and runs unready jobs
- [High] drm/job_queue: synchronous job completion is permanently missed during run_job
- [High] drm: JobQueue runs run_job in atomic context due to RevocableGuard
- [Medium] drm/job_queue: capacity is leaked when SubmitWorker cleans up signaled jobs
- [Medium] drm/job_queue: JobQueue API is unusable due to borrow checker conflict
--
commit 4ab6c27823ae4ac383ac1e5112edd966e2143dbd
Author: Philipp Stanner <phasta@kernel.org>
rust: drm: Add JobQueue
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.
[ ... ]
> diff --git a/rust/kernel/drm/job_queue.rs b/rust/kernel/drm/job_queue.rs
> new file mode 100644
> index 0000000000000..b5c7f4ecb3f46
> --- /dev/null
> +++ b/rust/kernel/drm/job_queue.rs
> @@ -0,0 +1,525 @@
> +// SPDX-License-Identifier: GPL-2.0
[ ... ]
> +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;
> + };
[Severity: High]
The JobQueue documentation explicitly states that jobs are submitted in
process context, which normally permits sleeping. However, does this
try_access() call enter an atomic context?
When try_access() returns a RevocableGuard, it intrinsically holds
rcu::read_lock(). Because the jq_accessor guard remains alive across the
subsequent call to T::run_job(), the driver callback is executed entirely
within an RCU read-side critical section, where sleeping is forbidden.
Will this regression cause a "scheduling while atomic" panic if a driver relies
on the documented process context guarantee to sleep, wait on mutexes, or use
GFP_KERNEL allocations inside run_job?
> +
> + 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;
[Severity: High]
Does this bypass the dependency system? JobQueue::submit() pushes jobs
unconditionally to waiting_jobs, and this loop iterates over them and executes
them as long as there is sufficient queue capacity.
However, it never checks if job.nr_of_deps == 0 before executing the job. Will
this regression result in jobs being sent to the hardware immediately,
regardless of pending dependency fences?
> + if new_capacity < 0 {
> + break;
> + }
[Severity: High]
When this loop encounters a job that exceeds the available capacity, it executes
a break to exit. Because the job was already removed from the waiting_jobs list
via pop_front(), breaking the loop drops the local job variable, destroying it.
Since the job is never pushed back onto waiting_jobs, its hardware operations
will be lost and its completion fence will never signal. Will this regression
cause a permanent hang for anything depending on it?
> + *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);
[Severity: High]
The code drops the queue lock before calling T::run_job(). If the driver
completes the job immediately (e.g. via synchronous execution or a very fast
hardware interrupt) and calls complete_jobs_up_to_seqno(), the completion
handler will search running_jobs.
Since the worker hasn't yet pushed the job to running_jobs here, the handler
will completely miss it. If this is the last job in a burst, and no subsequent
jobs trigger completion, will this regression leave the job hanging forever in
running_jobs without its fence signaling?
> + }
> +
> + // 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;
> + }
[Severity: Medium]
This cleanup loop iterates over running_jobs and removes any job whose fence
is signaled. Unlike the standard completion path in
complete_jobs_up_to_seqno() which restores capacity via
*jq.capacity += job.cost, this loop drops the job without updating jq.capacity.
Will this regression cause a permanent reduction in the queue's tracked capacity?
[ ... ]
> + /// Create a new job.
> + pub fn new_job(&'a self, cost: u32, data: T::JobDataType) -> Result<Job<'a, T>> {
[Severity: Medium]
This borrows the JobQueue immutably (&'a self) for the lifetime of the
returned Job. Later, JobQueue::submit takes a mutable reference (&mut self) and
the Job.
Because self cannot be borrowed mutably while the job's immutable borrow is
active, this creates a strict compile error. While the documentation comment
acknowledges this problem, does this regression make the API structurally
impossible to call from driver code?
[ ... ]
> + 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);
[Severity: Critical]
This function acquires the queue's SpinLockIrq (jq.lock()) and calls
fence.signal() on completed jobs. If another job in the same queue was
configured to depend on this fence, DependencyWaker::on_signal() is executed
synchronously by the dma-fence subsystem.
Since on_signal() then attempts to acquire the exact same queue spinlock via
jq.deref().lock(), will this regression result in a guaranteed recursive
spinlock deadlock on the same CPU?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261009191124.1022902-2-phasta@kernel.org?part=6
next prev parent reply other threads:[~2026-10-09 19:24 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
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:24 ` sashiko-bot
2026-10-09 19:11 ` [RFC PATCH v5 2/6] rust: DmaFence: Implement Deref for FenceContext Philipp Stanner
2026-10-09 19:19 ` sashiko-bot
2026-10-09 19:11 ` [RFC PATCH v5 3/6] rust: DmaFence: Don't drop on signal Philipp Stanner
2026-10-09 19:26 ` sashiko-bot
2026-10-09 19:11 ` [RFC PATCH v5 4/6] rust: DmaFence: [RFC] Add Signaler Philipp Stanner
2026-10-09 19:23 ` sashiko-bot
2026-10-09 19:11 ` [RFC PATCH v5 5/6] rust: DmaFence: Replace call_rcu() with synchronize_rcu() Philipp Stanner
2026-10-09 19:25 ` sashiko-bot
2026-10-09 19:11 ` [RFC PATCH v5 6/6] rust: drm: Add JobQueue Philipp Stanner
2026-10-09 19:24 ` sashiko-bot [this message]
2026-10-11 0:22 ` [RFC PATCH v5 0/6] rust: Add drm::JobQueue Daniel Almeida
2026-10-12 8:17 ` Philipp Stanner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-165998@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=gary@garyguo.net \
--cc=media-ci@linuxtv.org \
--cc=ojeda@kernel.org \
--cc=phasta@kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.