[PATCH 1/8] drm/sched: Convert drm scheduler to use a work queue rather than kthread

Matthew Brost matthew.brost at intel.com
Thu Aug 3 14:43:12 UTC 2023


On Thu, Aug 03, 2023 at 11:11:13AM +0100, Tvrtko Ursulin wrote:
> 
> On 01/08/2023 21:50, Matthew Brost wrote:
> > In XE, the new Intel GPU driver, a choice has made to have a 1 to 1
> > mapping between a drm_gpu_scheduler and drm_sched_entity. At first this
> > seems a bit odd but let us explain the reasoning below.
> > 
> > 1. In XE the submission order from multiple drm_sched_entity is not
> > guaranteed to be the same completion even if targeting the same hardware
> > engine. This is because in XE we have a firmware scheduler, the GuC,
> > which allowed to reorder, timeslice, and preempt submissions. If a using
> > shared drm_gpu_scheduler across multiple drm_sched_entity, the TDR falls
> > apart as the TDR expects submission order == completion order. Using a
> > dedicated drm_gpu_scheduler per drm_sched_entity solve this problem.
> > 
> > 2. In XE submissions are done via programming a ring buffer (circular
> > buffer), a drm_gpu_scheduler provides a limit on number of jobs, if the
> > limit of number jobs is set to RING_SIZE / MAX_SIZE_PER_JOB we get flow
> > control on the ring for free.
> > 
> > A problem with this design is currently a drm_gpu_scheduler uses a
> > kthread for submission / job cleanup. This doesn't scale if a large
> > number of drm_gpu_scheduler are used. To work around the scaling issue,
> > use a worker rather than kthread for submission / job cleanup.
> > 
> > v2:
> >    - (Rob Clark) Fix msm build
> >    - Pass in run work queue
> > v3:
> >    - (Boris) don't have loop in worker
> > 
> > Signed-off-by: Matthew Brost <matthew.brost at intel.com>
> > ---
> >   drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c |  14 +-
> >   drivers/gpu/drm/amd/amdgpu/amdgpu_device.c  |  14 +-
> >   drivers/gpu/drm/etnaviv/etnaviv_sched.c     |   2 +-
> >   drivers/gpu/drm/lima/lima_sched.c           |   2 +-
> >   drivers/gpu/drm/msm/adreno/adreno_device.c  |   6 +-
> >   drivers/gpu/drm/msm/msm_ringbuffer.c        |   2 +-
> >   drivers/gpu/drm/panfrost/panfrost_job.c     |   2 +-
> >   drivers/gpu/drm/scheduler/sched_main.c      | 136 +++++++++++---------
> >   drivers/gpu/drm/v3d/v3d_sched.c             |  10 +-
> >   include/drm/gpu_scheduler.h                 |  14 +-
> >   10 files changed, 113 insertions(+), 89 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> > index f60753f97ac5..9c2a10aeb0b3 100644
> > --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> > +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_debugfs.c
> > @@ -1489,9 +1489,9 @@ static int amdgpu_debugfs_test_ib_show(struct seq_file *m, void *unused)
> >   	for (i = 0; i < AMDGPU_MAX_RINGS; i++) {
> >   		struct amdgpu_ring *ring = adev->rings[i];
> > -		if (!ring || !ring->sched.thread)
> > +		if (!ring || !ring->sched.ready)
> >   			continue;
> > -		kthread_park(ring->sched.thread);
> > +		drm_sched_run_wq_stop(&ring->sched);
> 
> It would be good to split out adding of these wrappers (including adding one
> for ring->sched.thread/ready) to a standalong preceding patch. That way at
> least some mechanical changes to various drivers would be separated from
> functional changes.
>

Sure.
 
> Also, perhaps do not have the wq in the name if it is not really needed to
> be verbose with the underlying implementation like that? Like would
> drm_sched_run/pause. Or even __drm_sched_start/stop, dunno, just an idea.
>

Sure.
 
> >   	}
> >   	seq_printf(m, "run ib test:\n");
> > @@ -1505,9 +1505,9 @@ static int amdgpu_debugfs_test_ib_show(struct seq_file *m, void *unused)
> >   	for (i = 0; i < AMDGPU_MAX_RINGS; i++) {
> >   		struct amdgpu_ring *ring = adev->rings[i];
> > -		if (!ring || !ring->sched.thread)
> > +		if (!ring || !ring->sched.ready)
> >   			continue;
> > -		kthread_unpark(ring->sched.thread);
> > +		drm_sched_run_wq_start(&ring->sched);
> >   	}
> >   	up_write(&adev->reset_domain->sem);
> > @@ -1727,7 +1727,7 @@ static int amdgpu_debugfs_ib_preempt(void *data, u64 val)
> >   	ring = adev->rings[val];
> > -	if (!ring || !ring->funcs->preempt_ib || !ring->sched.thread)
> > +	if (!ring || !ring->funcs->preempt_ib || !ring->sched.ready)
> >   		return -EINVAL;
> >   	/* the last preemption failed */
> > @@ -1745,7 +1745,7 @@ static int amdgpu_debugfs_ib_preempt(void *data, u64 val)
> >   		goto pro_end;
> >   	/* stop the scheduler */
> > -	kthread_park(ring->sched.thread);
> > +	drm_sched_run_wq_stop(&ring->sched);
> >   	/* preempt the IB */
> >   	r = amdgpu_ring_preempt_ib(ring);
> > @@ -1779,7 +1779,7 @@ static int amdgpu_debugfs_ib_preempt(void *data, u64 val)
> >   failure:
> >   	/* restart the scheduler */
> > -	kthread_unpark(ring->sched.thread);
> > +	drm_sched_run_wq_start(&ring->sched);
> >   	up_read(&adev->reset_domain->sem);
> > diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
> > index fac9312b1695..00c9c03c8f94 100644
> > --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
> > +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_device.c
> > @@ -2364,7 +2364,7 @@ static int amdgpu_device_init_schedulers(struct amdgpu_device *adev)
> >   			break;
> >   		}
> > -		r = drm_sched_init(&ring->sched, &amdgpu_sched_ops,
> > +		r = drm_sched_init(&ring->sched, &amdgpu_sched_ops, NULL,
> >   				   ring->num_hw_submission, amdgpu_job_hang_limit,
> >   				   timeout, adev->reset_domain->wq,
> >   				   ring->sched_score, ring->name,
> > @@ -4627,7 +4627,7 @@ bool amdgpu_device_has_job_running(struct amdgpu_device *adev)
> >   	for (i = 0; i < AMDGPU_MAX_RINGS; ++i) {
> >   		struct amdgpu_ring *ring = adev->rings[i];
> > -		if (!ring || !ring->sched.thread)
> > +		if (!ring || !ring->sched.ready)
> >   			continue;
> >   		spin_lock(&ring->sched.job_list_lock);
> > @@ -4753,7 +4753,7 @@ int amdgpu_device_pre_asic_reset(struct amdgpu_device *adev,
> >   	for (i = 0; i < AMDGPU_MAX_RINGS; ++i) {
> >   		struct amdgpu_ring *ring = adev->rings[i];
> > -		if (!ring || !ring->sched.thread)
> > +		if (!ring || !ring->sched.ready)
> >   			continue;
> >   		/*clear job fence from fence drv to avoid force_completion
> > @@ -5294,7 +5294,7 @@ int amdgpu_device_gpu_recover(struct amdgpu_device *adev,
> >   		for (i = 0; i < AMDGPU_MAX_RINGS; ++i) {
> >   			struct amdgpu_ring *ring = tmp_adev->rings[i];
> > -			if (!ring || !ring->sched.thread)
> > +			if (!ring || !ring->sched.ready)
> >   				continue;
> >   			drm_sched_stop(&ring->sched, job ? &job->base : NULL);
> > @@ -5369,7 +5369,7 @@ int amdgpu_device_gpu_recover(struct amdgpu_device *adev,
> >   		for (i = 0; i < AMDGPU_MAX_RINGS; ++i) {
> >   			struct amdgpu_ring *ring = tmp_adev->rings[i];
> > -			if (!ring || !ring->sched.thread)
> > +			if (!ring || !ring->sched.ready)
> >   				continue;
> >   			drm_sched_start(&ring->sched, true);
> > @@ -5696,7 +5696,7 @@ pci_ers_result_t amdgpu_pci_error_detected(struct pci_dev *pdev, pci_channel_sta
> >   		for (i = 0; i < AMDGPU_MAX_RINGS; ++i) {
> >   			struct amdgpu_ring *ring = adev->rings[i];
> > -			if (!ring || !ring->sched.thread)
> > +			if (!ring || !ring->sched.ready)
> >   				continue;
> >   			drm_sched_stop(&ring->sched, NULL);
> > @@ -5824,7 +5824,7 @@ void amdgpu_pci_resume(struct pci_dev *pdev)
> >   	for (i = 0; i < AMDGPU_MAX_RINGS; ++i) {
> >   		struct amdgpu_ring *ring = adev->rings[i];
> > -		if (!ring || !ring->sched.thread)
> > +		if (!ring || !ring->sched.ready)
> >   			continue;
> >   		drm_sched_start(&ring->sched, true);
> > diff --git a/drivers/gpu/drm/etnaviv/etnaviv_sched.c b/drivers/gpu/drm/etnaviv/etnaviv_sched.c
> > index 1ae87dfd19c4..8486a2923f1b 100644
> > --- a/drivers/gpu/drm/etnaviv/etnaviv_sched.c
> > +++ b/drivers/gpu/drm/etnaviv/etnaviv_sched.c
> > @@ -133,7 +133,7 @@ int etnaviv_sched_init(struct etnaviv_gpu *gpu)
> >   {
> >   	int ret;
> > -	ret = drm_sched_init(&gpu->sched, &etnaviv_sched_ops,
> > +	ret = drm_sched_init(&gpu->sched, &etnaviv_sched_ops, NULL,
> >   			     etnaviv_hw_jobs_limit, etnaviv_job_hang_limit,
> >   			     msecs_to_jiffies(500), NULL, NULL,
> >   			     dev_name(gpu->dev), gpu->dev);
> > diff --git a/drivers/gpu/drm/lima/lima_sched.c b/drivers/gpu/drm/lima/lima_sched.c
> > index ff003403fbbc..54f53bece27c 100644
> > --- a/drivers/gpu/drm/lima/lima_sched.c
> > +++ b/drivers/gpu/drm/lima/lima_sched.c
> > @@ -488,7 +488,7 @@ int lima_sched_pipe_init(struct lima_sched_pipe *pipe, const char *name)
> >   	INIT_WORK(&pipe->recover_work, lima_sched_recover_work);
> > -	return drm_sched_init(&pipe->base, &lima_sched_ops, 1,
> > +	return drm_sched_init(&pipe->base, &lima_sched_ops, NULL, 1,
> >   			      lima_job_hang_limit,
> >   			      msecs_to_jiffies(timeout), NULL,
> >   			      NULL, name, pipe->ldev->dev);
> > diff --git a/drivers/gpu/drm/msm/adreno/adreno_device.c b/drivers/gpu/drm/msm/adreno/adreno_device.c
> > index c5c4c93b3689..f76ce11a5384 100644
> > --- a/drivers/gpu/drm/msm/adreno/adreno_device.c
> > +++ b/drivers/gpu/drm/msm/adreno/adreno_device.c
> > @@ -662,7 +662,8 @@ static void suspend_scheduler(struct msm_gpu *gpu)
> >   	 */
> >   	for (i = 0; i < gpu->nr_rings; i++) {
> >   		struct drm_gpu_scheduler *sched = &gpu->rb[i]->sched;
> > -		kthread_park(sched->thread);
> > +
> > +		drm_sched_run_wq_stop(sched);
> >   	}
> >   }
> > @@ -672,7 +673,8 @@ static void resume_scheduler(struct msm_gpu *gpu)
> >   	for (i = 0; i < gpu->nr_rings; i++) {
> >   		struct drm_gpu_scheduler *sched = &gpu->rb[i]->sched;
> > -		kthread_unpark(sched->thread);
> > +
> > +		drm_sched_run_wq_start(sched);
> >   	}
> >   }
> > diff --git a/drivers/gpu/drm/msm/msm_ringbuffer.c b/drivers/gpu/drm/msm/msm_ringbuffer.c
> > index 57a8e9564540..5879fc262047 100644
> > --- a/drivers/gpu/drm/msm/msm_ringbuffer.c
> > +++ b/drivers/gpu/drm/msm/msm_ringbuffer.c
> > @@ -95,7 +95,7 @@ struct msm_ringbuffer *msm_ringbuffer_new(struct msm_gpu *gpu, int id,
> >   	 /* currently managing hangcheck ourselves: */
> >   	sched_timeout = MAX_SCHEDULE_TIMEOUT;
> > -	ret = drm_sched_init(&ring->sched, &msm_sched_ops,
> > +	ret = drm_sched_init(&ring->sched, &msm_sched_ops, NULL,
> >   			num_hw_submissions, 0, sched_timeout,
> >   			NULL, NULL, to_msm_bo(ring->bo)->name, gpu->dev->dev);
> >   	if (ret) {
> > diff --git a/drivers/gpu/drm/panfrost/panfrost_job.c b/drivers/gpu/drm/panfrost/panfrost_job.c
> > index dbc597ab46fb..f48b07056a16 100644
> > --- a/drivers/gpu/drm/panfrost/panfrost_job.c
> > +++ b/drivers/gpu/drm/panfrost/panfrost_job.c
> > @@ -815,7 +815,7 @@ int panfrost_job_init(struct panfrost_device *pfdev)
> >   		js->queue[j].fence_context = dma_fence_context_alloc(1);
> >   		ret = drm_sched_init(&js->queue[j].sched,
> > -				     &panfrost_sched_ops,
> > +				     &panfrost_sched_ops, NULL,
> >   				     nentries, 0,
> >   				     msecs_to_jiffies(JOB_TIMEOUT_MS),
> >   				     pfdev->reset.wq,
> > diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c
> > index a18c8f5e8cc0..c3eed9e8062a 100644
> > --- a/drivers/gpu/drm/scheduler/sched_main.c
> > +++ b/drivers/gpu/drm/scheduler/sched_main.c
> > @@ -44,7 +44,6 @@
> >    * The jobs in a entity are always scheduled in the order that they were pushed.
> >    */
> > -#include <linux/kthread.h>
> >   #include <linux/wait.h>
> >   #include <linux/sched.h>
> >   #include <linux/completion.h>
> > @@ -252,6 +251,47 @@ drm_sched_rq_select_entity_fifo(struct drm_sched_rq *rq)
> >   	return rb ? rb_entry(rb, struct drm_sched_entity, rb_tree_node) : NULL;
> >   }
> > +/**
> > + * drm_sched_run_wq_stop - stop scheduler run worker
> > + *
> > + * @sched: scheduler instance to stop run worker
> > + */
> > +void drm_sched_run_wq_stop(struct drm_gpu_scheduler *sched)
> > +{
> > +	WRITE_ONCE(sched->pause_run_wq, true);
> > +	cancel_work_sync(&sched->work_run);
> > +}
> > +EXPORT_SYMBOL(drm_sched_run_wq_stop);
> > +
> > +/**
> > + * drm_sched_run_wq_start - start scheduler run worker
> > + *
> > + * @sched: scheduler instance to start run worker
> > + */
> > +void drm_sched_run_wq_start(struct drm_gpu_scheduler *sched)
> > +{
> > +	WRITE_ONCE(sched->pause_run_wq, false);
> > +	queue_work(sched->run_wq, &sched->work_run);
> > +}
> > +EXPORT_SYMBOL(drm_sched_run_wq_start);
> > +
> > +/**
> > + * drm_sched_run_wq_queue - queue scheduler run worker
> > + *
> > + * @sched: scheduler instance to queue run worker
> > + */
> > +static void drm_sched_run_wq_queue(struct drm_gpu_scheduler *sched)
> > +{
> > +	/*
> > +	 * Try not to schedule work if pause_run_wq set but not the end of world
> > +	 * if we do as either it will be cancelled by the above
> > +	 * cancel_work_sync, or drm_sched_main turns into a NOP while
> > +	 * pause_run_wq is set.
> > +	 */
> > +	if (!READ_ONCE(sched->pause_run_wq))
> > +		queue_work(sched->run_wq, &sched->work_run);
> > +}
> > +
> >   /**
> >    * drm_sched_job_done - complete a job
> >    * @s_job: pointer to the job which is done
> > @@ -271,7 +311,7 @@ static void drm_sched_job_done(struct drm_sched_job *s_job)
> >   	dma_fence_get(&s_fence->finished);
> >   	drm_sched_fence_finished(s_fence);
> >   	dma_fence_put(&s_fence->finished);
> > -	wake_up_interruptible(&sched->wake_up_worker);
> > +	drm_sched_run_wq_queue(sched);
> >   }
> >   /**
> > @@ -434,7 +474,7 @@ void drm_sched_stop(struct drm_gpu_scheduler *sched, struct drm_sched_job *bad)
> >   {
> >   	struct drm_sched_job *s_job, *tmp;
> > -	kthread_park(sched->thread);
> > +	drm_sched_run_wq_stop(sched);
> >   	/*
> >   	 * Reinsert back the bad job here - now it's safe as
> > @@ -547,7 +587,7 @@ void drm_sched_start(struct drm_gpu_scheduler *sched, bool full_recovery)
> >   		spin_unlock(&sched->job_list_lock);
> >   	}
> > -	kthread_unpark(sched->thread);
> > +	drm_sched_run_wq_start(sched);
> >   }
> >   EXPORT_SYMBOL(drm_sched_start);
> > @@ -864,7 +904,7 @@ static bool drm_sched_ready(struct drm_gpu_scheduler *sched)
> >   void drm_sched_wakeup(struct drm_gpu_scheduler *sched)
> >   {
> >   	if (drm_sched_ready(sched))
> > -		wake_up_interruptible(&sched->wake_up_worker);
> > +		drm_sched_run_wq_queue(sched);
> >   }
> >   /**
> > @@ -974,61 +1014,42 @@ drm_sched_pick_best(struct drm_gpu_scheduler **sched_list,
> >   }
> >   EXPORT_SYMBOL(drm_sched_pick_best);
> > -/**
> > - * drm_sched_blocked - check if the scheduler is blocked
> > - *
> > - * @sched: scheduler instance
> > - *
> > - * Returns true if blocked, otherwise false.
> > - */
> > -static bool drm_sched_blocked(struct drm_gpu_scheduler *sched)
> > -{
> > -	if (kthread_should_park()) {
> > -		kthread_parkme();
> > -		return true;
> > -	}
> > -
> > -	return false;
> > -}
> > -
> >   /**
> >    * drm_sched_main - main scheduler thread
> >    *
> >    * @param: scheduler instance
> > - *
> > - * Returns 0.
> >    */
> > -static int drm_sched_main(void *param)
> > +static void drm_sched_main(struct work_struct *w)
> >   {
> > -	struct drm_gpu_scheduler *sched = (struct drm_gpu_scheduler *)param;
> > +	struct drm_gpu_scheduler *sched =
> > +		container_of(w, struct drm_gpu_scheduler, work_run);
> > +	struct drm_sched_entity *entity;
> > +	struct drm_sched_job *cleanup_job;
> >   	int r;
> > -	sched_set_fifo_low(current);
> > +	if (READ_ONCE(sched->pause_run_wq))
> > +		return;
> 
> Is there a point to this check given the comment in drm_sched_run_wq_queue?
> 

I think so.

> > -	while (!kthread_should_stop()) {
> > -		struct drm_sched_entity *entity = NULL;
> > -		struct drm_sched_fence *s_fence;
> > -		struct drm_sched_job *sched_job;
> > -		struct dma_fence *fence;
> > -		struct drm_sched_job *cleanup_job = NULL;
> > +	cleanup_job = drm_sched_get_cleanup_job(sched);
> > +	entity = drm_sched_select_entity(sched);
> > -		wait_event_interruptible(sched->wake_up_worker,
> > -					 (cleanup_job = drm_sched_get_cleanup_job(sched)) ||
> > -					 (!drm_sched_blocked(sched) &&
> > -					  (entity = drm_sched_select_entity(sched))) ||
> > -					 kthread_should_stop());
> > +	if (!entity && !cleanup_job)
> > +		return;	/* No more work */
> > -		if (cleanup_job)
> > -			sched->ops->free_job(cleanup_job);
> > +	if (cleanup_job)
> > +		sched->ops->free_job(cleanup_job);
> > -		if (!entity)
> > -			continue;
> > +	if (entity) {
> > +		struct dma_fence *fence;
> > +		struct drm_sched_fence *s_fence;
> > +		struct drm_sched_job *sched_job;
> >   		sched_job = drm_sched_entity_pop_job(entity);
> > -
> >   		if (!sched_job) {
> >   			complete_all(&entity->entity_idle);
> > -			continue;
> > +			if (!cleanup_job)
> > +				return;	/* No more work */
> > +			goto again;
> 
> Loop is gone but now it re-arms itself which is needed to avoid starvation?
> Is it guaranteed to be effective by the wq contract?
>

Yea.
 
> >   		}
> >   		s_fence = sched_job->s_fence;
> > @@ -1055,14 +1076,17 @@ static int drm_sched_main(void *param)
> >   					  r);
> >   		} else {
> >   			if (IS_ERR(fence))
> > -				dma_fence_set_error(&s_fence->finished, PTR_ERR(fence));
> > +				dma_fence_set_error(&s_fence->finished,
> > +						    PTR_ERR(fence));
> >   			drm_sched_job_done(sched_job);
> >   		}
> >   		wake_up(&sched->job_scheduled);
> >   	}
> > -	return 0;
> > +
> > +again:
> > +	drm_sched_run_wq_queue(sched);
> >   }
> >   /**
> > @@ -1070,6 +1094,7 @@ static int drm_sched_main(void *param)
> >    *
> >    * @sched: scheduler instance
> >    * @ops: backend operations for this scheduler
> > + * @run_wq: workqueue to use for run work. If NULL, the system_wq is used
> >    * @hw_submission: number of hw submissions that can be in flight
> >    * @hang_limit: number of times to allow a job to hang before dropping it
> >    * @timeout: timeout value in jiffies for the scheduler
> > @@ -1083,14 +1108,16 @@ static int drm_sched_main(void *param)
> >    */
> >   int drm_sched_init(struct drm_gpu_scheduler *sched,
> >   		   const struct drm_sched_backend_ops *ops,
> > +		   struct workqueue_struct *run_wq,
> >   		   unsigned hw_submission, unsigned hang_limit,
> >   		   long timeout, struct workqueue_struct *timeout_wq,
> >   		   atomic_t *score, const char *name, struct device *dev)
> >   {
> > -	int i, ret;
> > +	int i;
> >   	sched->ops = ops;
> >   	sched->hw_submission_limit = hw_submission;
> >   	sched->name = name;
> > +	sched->run_wq = run_wq ? : system_wq;
> 
> I still think it is not nice to implicitly move everyone over to the shared
> system wq. Maybe even more so with now one at a time execution, since effect
> on latency can be even greater.
>

No one that has a stake in this has pushed back that I can recall. Open
to feedback stakeholders (maintainers of drivers that use the drm
scheduler). The i915 doesn't use the DRM scheduler last time I looked.
Has that changed?
 
> Have you considered kthread_work as a backend? Maybe it would work to have
> callers pass in a kthread_worker they create, or provide a drm_sched helper
> to create one, which would then be passed to drm_sched_init.
>
> That would enable per driver kthread_worker, or per device, or whatever
> granularity each driver would want/need/desire.
> 
> driver init:
> struct drm_sched_worker = drm_sched_create_worker(...);
> 
> queue/whatever init:
> drm_sched_init(.., worker, ...);
>

This idea doesn't seem to work for varitey of reasons. Will type it out
if needed but not going to spend time on this unless someone with a
stake raises this as an issue.
 
> You could create one inside drm_sched_init if not passed in, which would
> keep the behaviour for existing drivers more similar - they would still have
> a 1:1 kthread context for their exclusive use.
> 

Part of the idea of a work queue is so a user can't directly create a
kthread via an IOCTL (XE_EXEC_QUEUE_CREATE). What you suggesting exposes
this issue.

> And I *think* self-re-arming would be less problematic latency wise since
> kthread_worker consumes everything queued without relinquishing control and
> execution context would be guaranteed not to be shared with random system
> stuff.
> 

So this is essentially so we can use a loop? Seems like a lot effort for
what is pure speculation. Again if a stakeholder raises an issue we can
address then.

Matt

> Regards,
> 
> Tvrtko
> 
> >   	sched->timeout = timeout;
> >   	sched->timeout_wq = timeout_wq ? : system_wq;
> >   	sched->hang_limit = hang_limit;
> > @@ -1099,23 +1126,15 @@ int drm_sched_init(struct drm_gpu_scheduler *sched,
> >   	for (i = DRM_SCHED_PRIORITY_MIN; i < DRM_SCHED_PRIORITY_COUNT; i++)
> >   		drm_sched_rq_init(sched, &sched->sched_rq[i]);
> > -	init_waitqueue_head(&sched->wake_up_worker);
> >   	init_waitqueue_head(&sched->job_scheduled);
> >   	INIT_LIST_HEAD(&sched->pending_list);
> >   	spin_lock_init(&sched->job_list_lock);
> >   	atomic_set(&sched->hw_rq_count, 0);
> >   	INIT_DELAYED_WORK(&sched->work_tdr, drm_sched_job_timedout);
> > +	INIT_WORK(&sched->work_run, drm_sched_main);
> >   	atomic_set(&sched->_score, 0);
> >   	atomic64_set(&sched->job_id_count, 0);
> > -
> > -	/* Each scheduler will run on a seperate kernel thread */
> > -	sched->thread = kthread_run(drm_sched_main, sched, sched->name);
> > -	if (IS_ERR(sched->thread)) {
> > -		ret = PTR_ERR(sched->thread);
> > -		sched->thread = NULL;
> > -		DRM_DEV_ERROR(sched->dev, "Failed to create scheduler for %s.\n", name);
> > -		return ret;
> > -	}
> > +	sched->pause_run_wq = false;
> >   	sched->ready = true;
> >   	return 0;
> > @@ -1134,8 +1153,7 @@ void drm_sched_fini(struct drm_gpu_scheduler *sched)
> >   	struct drm_sched_entity *s_entity;
> >   	int i;
> > -	if (sched->thread)
> > -		kthread_stop(sched->thread);
> > +	drm_sched_run_wq_stop(sched);
> >   	for (i = DRM_SCHED_PRIORITY_COUNT - 1; i >= DRM_SCHED_PRIORITY_MIN; i--) {
> >   		struct drm_sched_rq *rq = &sched->sched_rq[i];
> > diff --git a/drivers/gpu/drm/v3d/v3d_sched.c b/drivers/gpu/drm/v3d/v3d_sched.c
> > index 06238e6d7f5c..38e092ea41e6 100644
> > --- a/drivers/gpu/drm/v3d/v3d_sched.c
> > +++ b/drivers/gpu/drm/v3d/v3d_sched.c
> > @@ -388,7 +388,7 @@ v3d_sched_init(struct v3d_dev *v3d)
> >   	int ret;
> >   	ret = drm_sched_init(&v3d->queue[V3D_BIN].sched,
> > -			     &v3d_bin_sched_ops,
> > +			     &v3d_bin_sched_ops, NULL,
> >   			     hw_jobs_limit, job_hang_limit,
> >   			     msecs_to_jiffies(hang_limit_ms), NULL,
> >   			     NULL, "v3d_bin", v3d->drm.dev);
> > @@ -396,7 +396,7 @@ v3d_sched_init(struct v3d_dev *v3d)
> >   		return ret;
> >   	ret = drm_sched_init(&v3d->queue[V3D_RENDER].sched,
> > -			     &v3d_render_sched_ops,
> > +			     &v3d_render_sched_ops, NULL,
> >   			     hw_jobs_limit, job_hang_limit,
> >   			     msecs_to_jiffies(hang_limit_ms), NULL,
> >   			     NULL, "v3d_render", v3d->drm.dev);
> > @@ -404,7 +404,7 @@ v3d_sched_init(struct v3d_dev *v3d)
> >   		goto fail;
> >   	ret = drm_sched_init(&v3d->queue[V3D_TFU].sched,
> > -			     &v3d_tfu_sched_ops,
> > +			     &v3d_tfu_sched_ops, NULL,
> >   			     hw_jobs_limit, job_hang_limit,
> >   			     msecs_to_jiffies(hang_limit_ms), NULL,
> >   			     NULL, "v3d_tfu", v3d->drm.dev);
> > @@ -413,7 +413,7 @@ v3d_sched_init(struct v3d_dev *v3d)
> >   	if (v3d_has_csd(v3d)) {
> >   		ret = drm_sched_init(&v3d->queue[V3D_CSD].sched,
> > -				     &v3d_csd_sched_ops,
> > +				     &v3d_csd_sched_ops, NULL,
> >   				     hw_jobs_limit, job_hang_limit,
> >   				     msecs_to_jiffies(hang_limit_ms), NULL,
> >   				     NULL, "v3d_csd", v3d->drm.dev);
> > @@ -421,7 +421,7 @@ v3d_sched_init(struct v3d_dev *v3d)
> >   			goto fail;
> >   		ret = drm_sched_init(&v3d->queue[V3D_CACHE_CLEAN].sched,
> > -				     &v3d_cache_clean_sched_ops,
> > +				     &v3d_cache_clean_sched_ops, NULL,
> >   				     hw_jobs_limit, job_hang_limit,
> >   				     msecs_to_jiffies(hang_limit_ms), NULL,
> >   				     NULL, "v3d_cache_clean", v3d->drm.dev);
> > diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h
> > index c0586d832260..98fb5f85eba6 100644
> > --- a/include/drm/gpu_scheduler.h
> > +++ b/include/drm/gpu_scheduler.h
> > @@ -473,17 +473,16 @@ struct drm_sched_backend_ops {
> >    * @timeout: the time after which a job is removed from the scheduler.
> >    * @name: name of the ring for which this scheduler is being used.
> >    * @sched_rq: priority wise array of run queues.
> > - * @wake_up_worker: the wait queue on which the scheduler sleeps until a job
> > - *                  is ready to be scheduled.
> >    * @job_scheduled: once @drm_sched_entity_do_release is called the scheduler
> >    *                 waits on this wait queue until all the scheduled jobs are
> >    *                 finished.
> >    * @hw_rq_count: the number of jobs currently in the hardware queue.
> >    * @job_id_count: used to assign unique id to the each job.
> > + * @run_wq: workqueue used to queue @work_run
> >    * @timeout_wq: workqueue used to queue @work_tdr
> > + * @work_run: schedules jobs and cleans up entities
> >    * @work_tdr: schedules a delayed call to @drm_sched_job_timedout after the
> >    *            timeout interval is over.
> > - * @thread: the kthread on which the scheduler which run.
> >    * @pending_list: the list of jobs which are currently in the job queue.
> >    * @job_list_lock: lock to protect the pending_list.
> >    * @hang_limit: once the hangs by a job crosses this limit then it is marked
> > @@ -492,6 +491,7 @@ struct drm_sched_backend_ops {
> >    * @_score: score used when the driver doesn't provide one
> >    * @ready: marks if the underlying HW is ready to work
> >    * @free_guilty: A hit to time out handler to free the guilty job.
> > + * @pause_run_wq: pause queuing of @work_run on @run_wq
> >    * @dev: system &struct device
> >    *
> >    * One scheduler is implemented for each hardware ring.
> > @@ -502,13 +502,13 @@ struct drm_gpu_scheduler {
> >   	long				timeout;
> >   	const char			*name;
> >   	struct drm_sched_rq		sched_rq[DRM_SCHED_PRIORITY_COUNT];
> > -	wait_queue_head_t		wake_up_worker;
> >   	wait_queue_head_t		job_scheduled;
> >   	atomic_t			hw_rq_count;
> >   	atomic64_t			job_id_count;
> > +	struct workqueue_struct		*run_wq;
> >   	struct workqueue_struct		*timeout_wq;
> > +	struct work_struct		work_run;
> >   	struct delayed_work		work_tdr;
> > -	struct task_struct		*thread;
> >   	struct list_head		pending_list;
> >   	spinlock_t			job_list_lock;
> >   	int				hang_limit;
> > @@ -516,11 +516,13 @@ struct drm_gpu_scheduler {
> >   	atomic_t                        _score;
> >   	bool				ready;
> >   	bool				free_guilty;
> > +	bool				pause_run_wq;
> >   	struct device			*dev;
> >   };
> >   int drm_sched_init(struct drm_gpu_scheduler *sched,
> >   		   const struct drm_sched_backend_ops *ops,
> > +		   struct workqueue_struct *run_wq,
> >   		   uint32_t hw_submission, unsigned hang_limit,
> >   		   long timeout, struct workqueue_struct *timeout_wq,
> >   		   atomic_t *score, const char *name, struct device *dev);
> > @@ -550,6 +552,8 @@ void drm_sched_entity_modify_sched(struct drm_sched_entity *entity,
> >   void drm_sched_job_cleanup(struct drm_sched_job *job);
> >   void drm_sched_wakeup(struct drm_gpu_scheduler *sched);
> > +void drm_sched_run_wq_stop(struct drm_gpu_scheduler *sched);
> > +void drm_sched_run_wq_start(struct drm_gpu_scheduler *sched);
> >   void drm_sched_stop(struct drm_gpu_scheduler *sched, struct drm_sched_job *bad);
> >   void drm_sched_start(struct drm_gpu_scheduler *sched, bool full_recovery);
> >   void drm_sched_resubmit_jobs(struct drm_gpu_scheduler *sched);


More information about the dri-devel mailing list