From: Philipp Stanner <phasta@mailbox.org>
To: "Maíra Canal" <mcanal@igalia.com>,
"Tomeu Vizoso" <tomeu@tomeuvizoso.net>,
"Oded Gabbay" <ogabbay@kernel.org>,
"Christian König" <christian.koenig@amd.com>,
"Christian König" <ckoenig.leichtzumerken@gmail.com>,
"Rob Herring" <robh@kernel.org>,
"Matthew Brost" <matthew.brost@intel.com>,
"Danilo Krummrich" <dakr@kernel.org>,
"Philipp Stanner" <phasta@kernel.org>,
"Sumit Semwal" <sumit.semwal@linaro.org>
Cc: kernel-dev@igalia.com, dri-devel@lists.freedesktop.org
Subject: Re: [PATCH 2/3] drm/rocket: Submit one drm_sched_job per task
Date: Mon, 08 Jun 2026 12:02:19 +0200 [thread overview]
Message-ID: <d8d4ffb3488dd2e1950fb1fc291fab68e2d953bb.camel@mailbox.org> (raw)
In-Reply-To: <20260605-rocket-per-task-jobs-v1-2-e3319adf3c49@igalia.com>
Hi,
On Fri, 2026-06-05 at 13:06 -0300, Maíra Canal wrote:
> The current submission model breaks the DRM scheduler's design in two
> ways.
>
> First, a job spawns further hardware work from outside the scheduler.
> rocket_job_run() submits only the first task of an inference; every
> subsequent task is submitted by the threaded IRQ handler, which calls
> rocket_job_hw_submit() directly.
> The scheduler expects all of a job's
> hardware submission to happen in run_job(). Driving it from the IRQ
> handler instead is invisible to the scheduler, and drm_sched_stop() only
> synchronizes the scheduler's workqueue, not the threaded IRQ, so the
> reset path races these IRQ-driven submissions. The job_lock mutex and the
> reset.pending flag exist only as a workaround that self-inflicted race.
nit: missing word "for"
>
> Second, the submission path returns after arming a job. rocket_job_push()
> calls drm_sched_job_arm() and only then acquires the BO fences, bailing
> out and cleaning the job up if that fails. But arming is a point of no
> return: per its documentation, "Once this function was called, you *must*
> submit @job with drm_sched_entity_push_job()", because it publishes the
> job's fences.
Oh dear. Yeah, got catch.
>
> Redesign the submission so each task is its own drm_sched_job, which is
> what the scheduler's model actually expects:
>
> - Every submission to the NPU flows through run_job(), and the IRQ
> handler only signals the task's fence. Nothing is started without the
> knowledge of the scheduler, so it can serialize submission against
> reset.
+1
>
> - The BO reservations and implicit dependencies are acquired before any
> task is armed, so the only fallible step precedes the point of no
> return.
>
> struct rocket_job becomes the refcounted inference shared by its tasks;
If they're now shared, do they also require synchronization?
> struct rocket_task becomes the per-task scheduled unit, and each task
> holds a reference to the inference, so the last task to be freed marks it
> done. Ordering between tasks comes from the per-file entity's FIFO and
> arming all tasks up front pins the inference to one core. The implicit
> BO dependencies and the completion fence are anchored on the last task.
>
> The IOMMU domain is attached once by the first task to run and detached
> when the last task drops its reference. On timeout the inference is marked
> cancelled before the scheduler restarts; the remaining tasks observe the
> flag in run_job() and fail with -ECANCELED without touching hardware, so
> only the affected inference is abandoned.
>
> With this, the Rocket driver can comply to the DRM scheduler's
> expectations.
>
> Signed-off-by: Maíra Canal <mcanal@igalia.com>
Some more fly-by comments below, but not a super deep review:
> ---
> drivers/accel/rocket/rocket_core.h | 5 +-
> drivers/accel/rocket/rocket_job.c | 257 ++++++++++++++++++++-----------------
> drivers/accel/rocket/rocket_job.h | 26 +++-
> 3 files changed, 158 insertions(+), 130 deletions(-)
>
> diff --git a/drivers/accel/rocket/rocket_core.h b/drivers/accel/rocket/rocket_core.h
> index 4d163a6ac653..f74dc99d07bb 100644
> --- a/drivers/accel/rocket/rocket_core.h
> +++ b/drivers/accel/rocket/rocket_core.h
> @@ -41,14 +41,13 @@ struct rocket_core {
>
> struct iommu_group *iommu_group;
>
> - struct mutex job_lock;
> - struct rocket_job *in_flight_job;
> + /* Task currently running on the hardware. */
> + struct rocket_task *in_flight_task;
>
> spinlock_t fence_lock;
>
> struct {
> struct workqueue_struct *wq;
> - atomic_t pending;
> } reset;
>
> struct drm_gpu_scheduler sched;
> diff --git a/drivers/accel/rocket/rocket_job.c b/drivers/accel/rocket/rocket_job.c
> index 8a26139a44f4..871041eb7a1d 100644
> --- a/drivers/accel/rocket/rocket_job.c
> +++ b/drivers/accel/rocket/rocket_job.c
> @@ -20,10 +20,10 @@
>
> #define JOB_TIMEOUT_MS 500
>
> -static struct rocket_job *
> -to_rocket_job(struct drm_sched_job *sched_job)
> +static struct rocket_task *
> +to_rocket_task(struct drm_sched_job *sched_job)
> {
> - return container_of(sched_job, struct rocket_job, base);
> + return container_of(sched_job, struct rocket_task, base);
> }
>
> static const char *rocket_fence_get_driver_name(struct dma_fence *fence)
> @@ -61,8 +61,6 @@ rocket_copy_tasks(struct drm_device *dev,
> struct drm_rocket_job *job,
> struct rocket_job *rjob)
> {
> - int ret = 0;
> -
> if (job->task_struct_size < sizeof(struct drm_rocket_task))
> return -EINVAL;
>
> @@ -71,7 +69,7 @@ rocket_copy_tasks(struct drm_device *dev,
> if (!rjob->task_count)
> return 0;
>
> - rjob->tasks = kvmalloc_objs(*rjob->tasks, job->task_count);
> + rjob->tasks = kvzalloc_objs(*rjob->tasks, job->task_count);
> if (!rjob->tasks) {
> drm_dbg(dev, "Failed to allocate task array\n");
> return -ENOMEM;
> @@ -84,14 +82,12 @@ rocket_copy_tasks(struct drm_device *dev,
> u64_to_user_ptr(job->tasks) + i * job->task_struct_size,
> sizeof(task))) {
> drm_dbg(dev, "Failed to copy incoming tasks\n");
> - ret = -EFAULT;
> - goto fail;
> + return -EFAULT;
> }
>
> if (task.regcmd_count == 0) {
> drm_dbg(dev, "regcmd_count field in drm_rocket_task should be > 0.\n");
> - ret = -EINVAL;
> - goto fail;
> + return -EINVAL;
> }
>
> rjob->tasks[i].regcmd = task.regcmd;
> @@ -99,26 +95,14 @@ rocket_copy_tasks(struct drm_device *dev,
> }
>
> return 0;
> -
> -fail:
> - kvfree(rjob->tasks);
> - return ret;
Not super obvious to the reader why this is now unnecessary?
Could these changes be a separate patch?
> }
>
[…]
>
> static struct rocket_core *sched_to_core(struct rocket_device *rdev,
> @@ -286,108 +307,102 @@ static struct rocket_core *sched_to_core(struct rocket_device *rdev,
>
> static struct dma_fence *rocket_job_run(struct drm_sched_job *sched_job)
> {
> - struct rocket_job *job = to_rocket_job(sched_job);
> - struct rocket_device *rdev = job->rdev;
> - struct rocket_core *core = sched_to_core(rdev, sched_job->sched);
> + struct rocket_task *task = to_rocket_task(sched_job);
> + struct rocket_job *job = task->job;
> + struct rocket_core *core = sched_to_core(job->rdev, sched_job->sched);
> struct dma_fence *fence = NULL;
> int ret;
>
> - if (unlikely(job->base.s_fence->finished.error))
> + if (unlikely(sched_job->s_fence->finished.error))
> return NULL;
>
> - /*
> - * Nothing to execute: can happen if the job has finished while
> - * we were resetting the NPU.
> - */
> - if (job->next_task_idx == job->task_count)
> + /* An earlier task of this inference timed out, abandon the inference. */
> + if (atomic_read(&job->cancelled)) {
I see that you touch some mutexes in this patch. Some seem to
disappear. These atomics stay.
They look a bit racy, too, don't they?
> + dma_fence_set_error(&sched_job->s_fence->finished, -ECANCELED);
> return NULL;
> + }
>
> fence = rocket_fence_create(core);
> if (IS_ERR(fence))
> return fence;
>
> - if (job->done_fence)
> - dma_fence_put(job->done_fence);
> - job->done_fence = dma_fence_get(fence);
> + if (task->done_fence)
> + dma_fence_put(task->done_fence);
> + task->done_fence = dma_fence_get(fence);
>
> - ret = pm_runtime_get_sync(core->dev);
> - if (ret < 0)
> - return fence;
> -
> - ret = iommu_attach_group(job->domain->domain, core->iommu_group);
> - if (ret < 0)
> - return fence;
> -
> - scoped_guard(mutex, &core->job_lock) {
> - core->in_flight_job = job;
> - rocket_job_hw_submit(core, job);
> + ret = pm_runtime_resume_and_get(core->dev);
> + if (ret < 0) {
> + dma_fence_put(fence);
> + return ERR_PTR(ret);
> }
>
> + /* Attach the domain once for the whole inference. */
> + if (!job->core) {
> + ret = iommu_attach_group(job->domain->domain, core->iommu_group);
> + if (ret < 0) {
> + pm_runtime_put_autosuspend(core->dev);
> + dma_fence_put(fence);
> + return ERR_PTR(ret);
> + }
> + job->core = core;
> + }
> +
> + WRITE_ONCE(core->in_flight_task, task);
The scoped_guard above disappears, and now a WRITE_ONCE appears. Hmm.
The core doesn't have a lock?
> + rocket_job_hw_submit(core, task);
> +
> return fence;
> }
>
> static void rocket_job_handle_irq(struct rocket_core *core)
> {
> + struct rocket_task *task;
> +
> pm_runtime_mark_last_busy(core->dev);
>
> rocket_pc_writel(core, OPERATION_ENABLE, 0x0);
> rocket_pc_writel(core, INTERRUPT_CLEAR, 0x1ffff);
>
> - scoped_guard(mutex, &core->job_lock)
> - if (core->in_flight_job) {
> - if (core->in_flight_job->next_task_idx < core->in_flight_job->task_count) {
> - rocket_job_hw_submit(core, core->in_flight_job);
> - return;
> - }
> -
> - iommu_detach_group(NULL, iommu_group_get(core->dev));
> - dma_fence_signal(core->in_flight_job->done_fence);
> - pm_runtime_put_autosuspend(core->dev);
> - core->in_flight_job = NULL;
> - }
> + /*
> + * Claim the in-flight task: the reset path may run concurrently, so
> + * whichever of us wins owns the PM put.
> + */
> + task = xchg(&core->in_flight_task, NULL);
Why is it sometimes WRITE_ONCE and sometimes xchg?
I don't understand much of rocket's design, but this makes me feel
uncomfortable about the overall locking / synchronization design.
Regards
P.
next prev parent reply other threads:[~2026-06-08 10:02 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-05 16:06 [PATCH 0/3] drm/rocket: Stop submitting hardware work from the IRQ handler Maíra Canal
2026-06-05 16:06 ` [PATCH 1/3] drm/rocket: Remove unused reset worker Maíra Canal
2026-06-05 16:25 ` sashiko-bot
2026-06-05 16:06 ` [PATCH 2/3] drm/rocket: Submit one drm_sched_job per task Maíra Canal
2026-06-05 16:20 ` sashiko-bot
2026-06-08 10:02 ` Philipp Stanner [this message]
2026-06-05 16:07 ` [PATCH 3/3] drm/rocket: Drop the dedicated reset workqueue Maíra Canal
2026-06-05 18:20 ` sashiko-bot
2026-06-08 10:08 ` Philipp Stanner
2026-06-05 16:40 ` [RFC PATCH 0/3] drm/rocket: Stop submitting hardware work from the IRQ handler Maíra Canal
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=d8d4ffb3488dd2e1950fb1fc291fab68e2d953bb.camel@mailbox.org \
--to=phasta@mailbox.org \
--cc=christian.koenig@amd.com \
--cc=ckoenig.leichtzumerken@gmail.com \
--cc=dakr@kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=kernel-dev@igalia.com \
--cc=matthew.brost@intel.com \
--cc=mcanal@igalia.com \
--cc=ogabbay@kernel.org \
--cc=phasta@kernel.org \
--cc=robh@kernel.org \
--cc=sumit.semwal@linaro.org \
--cc=tomeu@tomeuvizoso.net \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).