[PATCH] drm/panfrost: Fix a race in the job timeout handling (again)
Boris Brezillon
boris.brezillon at collabora.com
Mon Oct 26 17:31:32 UTC 2020
On Mon, 26 Oct 2020 16:16:49 +0000
Steven Price <steven.price at arm.com> wrote:
> On 26/10/2020 15:32, Boris Brezillon wrote:
> > In our last attempt to fix races in the panfrost_job_timedout() path we
> > overlooked the case where a re-submitted job immediately triggers a
> > fault. This lead to a situation where we try to stop a scheduler that's
> > not resumed yet and lose the 'timedout' event without restarting the
> > timeout, thus blocking the whole queue.
> >
> > Let's fix that by tracking timeouts occurring between the
> > drm_sched_resubmit_jobs() and drm_sched_start() calls.
> >
> > Fixes: 1a11a88cfd9a ("drm/panfrost: Fix job timeout handling")
> > Cc: <stable at vger.kernel.org>
> > Signed-off-by: Boris Brezillon <boris.brezillon at collabora.com>
> > ---
> > drivers/gpu/drm/panfrost/panfrost_job.c | 42 ++++++++++++++++++++-----
> > 1 file changed, 34 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/panfrost/panfrost_job.c b/drivers/gpu/drm/panfrost/panfrost_job.c
> > index d0469e944143..96c2c21a4205 100644
> > --- a/drivers/gpu/drm/panfrost/panfrost_job.c
> > +++ b/drivers/gpu/drm/panfrost/panfrost_job.c
> > @@ -26,6 +26,7 @@
> > struct panfrost_queue_state {
> > struct drm_gpu_scheduler sched;
> > bool stopped;
> > + bool timedout;
> > struct mutex lock;
> > u64 fence_context;
> > u64 emit_seqno;
> > @@ -383,11 +384,33 @@ static bool panfrost_scheduler_stop(struct panfrost_queue_state *queue,
> > queue->stopped = true;
> > stopped = true;
> > }
> > + queue->timedout = true;
> > mutex_unlock(&queue->lock);
> >
> > return stopped;
> > }
> >
> > +static void panfrost_scheduler_start(struct panfrost_queue_state *queue)
> > +{
> > + if (WARN_ON(!queue->stopped))
>
> I *think* this can be hit, see below.
>
> > + return;
> > +
> > + mutex_lock(&queue->lock);
> > + drm_sched_start(&queue->sched, true);
> > +
> > + /*
> > + * We might have missed fault-timeouts (AKA immediate timeouts) while
> > + * the scheduler was stopped. Let's fake a new fault to trigger an
> > + * immediate reset.
> > + */
> > + if (queue->timedout)
> > + drm_sched_fault(&queue->sched);
> > +
> > + queue->timedout = false;
> > + queue->stopped = false;
> > + mutex_unlock(&queue->lock);
> > +}
> > +
> > static void panfrost_job_timedout(struct drm_sched_job *sched_job)
> > {
> > struct panfrost_job *job = to_panfrost_job(sched_job);
> > @@ -437,12 +460,6 @@ static void panfrost_job_timedout(struct drm_sched_job *sched_job)
> > */
> > if (panfrost_scheduler_stop(&pfdev->js->queue[i], NULL))
> > cancel_delayed_work_sync(&sched->work_tdr);
> > -
> > - /*
> > - * Now that we cancelled the pending timeouts, we can safely
> > - * reset the stopped state.
> > - */
> > - pfdev->js->queue[i].stopped = false;
> > }
> >
> > spin_lock_irqsave(&pfdev->js->job_lock, flags);
> > @@ -457,14 +474,23 @@ static void panfrost_job_timedout(struct drm_sched_job *sched_job)
> >
> > panfrost_device_reset(pfdev);
> >
> > - for (i = 0; i < NUM_JOB_SLOTS; i++)
> > + for (i = 0; i < NUM_JOB_SLOTS; i++) {
> > + /*
> > + * The GPU is idle, and the scheduler is stopped, we can safely
> > + * reset the ->timedout state without taking any lock. We need
> > + * to do that before calling drm_sched_resubmit_jobs() though,
> > + * because the resubmission might trigger immediate faults
> > + * which we want to catch.
> > + */
> > + pfdev->js->queue[i].timedout = false;
> > drm_sched_resubmit_jobs(&pfdev->js->queue[i].sched);
> > + }
> >
> > mutex_unlock(&pfdev->reset_lock);
>
> In here we've resubmitted the jobs and are no longer holding the mutex.
> So AFAICT if one of those jobs fails we may re-enter
> panfrost_job_timedout() and stop (no-op) the scheduler.
Actually, we won't even try to stop the scheduler, because the
scheduler is still marked as 'stopped', and we bail out early in that
case.
> The first thread
> could then proceed to start the scheduler (possibly during the GPU reset
> handled by the second thread which could be interesting in itself),
> followed by the second thread attempting to start the scheduler which
> then hits the WARN_ON().
Right, one of the queue might be started while another thread
(attached to a different queue) is resetting the GPU, but I'm wondering
if that's really an issue. I mean, the thread resetting the GPU will
wait for all pending timeout handlers to return before proceeding
(cancel_delayed_work_sync() call). Things are then serialized until
we call drm_sched_resubmit_jobs() which restarts the bad jobs and might
lead to new faults. But the queues are still marked as stopped between
drm_sched_resubmit_jobs() and panfrost_scheduler_start(), meaning that
the timeout handlers are effectively NOOPs during that period of time.
This being said, I agree that the current implementation is
cumbersome, and I'd prefer to have something simpler if we can.
More information about the dri-devel
mailing list