diff mbox series

[RFC,2/5] drm/scheduler: Add scheduler unit testing infrastructure and some basic tests

Message ID 20250207143337.60154-3-tvrtko.ursulin@igalia.com (mailing list archive)
State New, archived
Headers show
Series DRM scheduler kunit tests | expand

Commit Message

Tvrtko Ursulin Feb. 7, 2025, 2:33 p.m. UTC
Implement a mock scheduler backend and add some basic test to exercise the
core scheduler code paths.

Mock backend (kind of like a very simple mock GPU) can either process jobs
by tests manually advancing the "timeline" job at a time, or alternatively
jobs can be configured with a time duration in which case they get
completed asynchronously from the unit test code.

Core scheduler classes are subclassed to support this mock implementation.

The tests added are just a few simple submission patterns.

Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
Suggested-by: Philipp Stanner <phasta@kernel.org>
Cc: Christian König <christian.koenig@amd.com>
Cc: Danilo Krummrich <dakr@kernel.org>
Cc: Matthew Brost <matthew.brost@intel.com>
Cc: Philipp Stanner <phasta@kernel.org>
---
 drivers/gpu/drm/Kconfig.debug                 |  12 +
 drivers/gpu/drm/scheduler/.kunitconfig        |  12 +
 drivers/gpu/drm/scheduler/Makefile            |   1 +
 drivers/gpu/drm/scheduler/tests/Makefile      |   4 +
 .../gpu/drm/scheduler/tests/drm_mock_entity.c |  29 ++
 .../gpu/drm/scheduler/tests/drm_mock_job.c    |   3 +
 .../drm/scheduler/tests/drm_mock_scheduler.c  | 255 ++++++++++++++++++
 .../gpu/drm/scheduler/tests/drm_sched_tests.h | 128 +++++++++
 .../scheduler/tests/drm_sched_tests_basic.c   | 188 +++++++++++++
 9 files changed, 632 insertions(+)
 create mode 100644 drivers/gpu/drm/scheduler/.kunitconfig
 create mode 100644 drivers/gpu/drm/scheduler/tests/Makefile
 create mode 100644 drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
 create mode 100644 drivers/gpu/drm/scheduler/tests/drm_mock_job.c
 create mode 100644 drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
 create mode 100644 drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
 create mode 100644 drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c

Comments

Philipp Stanner Feb. 13, 2025, 1:05 p.m. UTC | #1
I'm in principle OK with this series. We'll have to discuss a few more
things, but from my POV you could send the next revision as a v1.

Some more input below..

On Fri, 2025-02-07 at 14:33 +0000, Tvrtko Ursulin wrote:
> Implement a mock scheduler backend and add some basic test to
> exercise the
> core scheduler code paths.
> 
> Mock backend (kind of like a very simple mock GPU) can either process
> jobs
> by tests manually advancing the "timeline" job at a time, or
> alternatively
> jobs can be configured with a time duration in which case they get
> completed asynchronously from the unit test code.
> 
> Core scheduler classes are subclassed to support this mock
> implementation.
> 
> The tests added are just a few simple submission patterns.
> 
> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
> Suggested-by: Philipp Stanner <phasta@kernel.org>
> Cc: Christian König <christian.koenig@amd.com>
> Cc: Danilo Krummrich <dakr@kernel.org>
> Cc: Matthew Brost <matthew.brost@intel.com>
> Cc: Philipp Stanner <phasta@kernel.org>
> ---
>  drivers/gpu/drm/Kconfig.debug                 |  12 +
>  drivers/gpu/drm/scheduler/.kunitconfig        |  12 +
>  drivers/gpu/drm/scheduler/Makefile            |   1 +
>  drivers/gpu/drm/scheduler/tests/Makefile      |   4 +
>  .../gpu/drm/scheduler/tests/drm_mock_entity.c |  29 ++
>  .../gpu/drm/scheduler/tests/drm_mock_job.c    |   3 +
>  .../drm/scheduler/tests/drm_mock_scheduler.c  | 255
> ++++++++++++++++++
>  .../gpu/drm/scheduler/tests/drm_sched_tests.h | 128 +++++++++
>  .../scheduler/tests/drm_sched_tests_basic.c   | 188 +++++++++++++
>  9 files changed, 632 insertions(+)
>  create mode 100644 drivers/gpu/drm/scheduler/.kunitconfig
>  create mode 100644 drivers/gpu/drm/scheduler/tests/Makefile
>  create mode 100644 drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
>  create mode 100644 drivers/gpu/drm/scheduler/tests/drm_mock_job.c
>  create mode 100644
> drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
>  create mode 100644 drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
>  create mode 100644
> drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c

I'm still not that convinced about the naming convention of those
files. They reside in drm/scheduler, but are called
drm_mock_{something}.c, almost reading as if they are about DRM in
general.

I think we should keep it consistent with the files in drm/scheduler/
and just call those drm/scheduler/tests/sched_mock_job.c etc.

> 
> diff --git a/drivers/gpu/drm/Kconfig.debug
> b/drivers/gpu/drm/Kconfig.debug
> index a35d74171b7b..89f777f21e95 100644
> --- a/drivers/gpu/drm/Kconfig.debug
> +++ b/drivers/gpu/drm/Kconfig.debug
> @@ -88,5 +88,17 @@ config DRM_TTM_KUNIT_TEST
>  
>  	  If in doubt, say "N".
>  
> +config DRM_SCHED_KUNIT_TEST
> +	tristate "KUnit tests for the DRM scheduler" if
> !KUNIT_ALL_TESTS
> +	select DRM_SCHED
> +	depends on DRM && KUNIT
> +	default KUNIT_ALL_TESTS
> +	help
> +	  Choose this option to build unit tests for the DRM
> scheduler.
> +
> +	  Recommended for driver developers only.
> +
> +	  If in doubt, say "N".
> +
>  config DRM_EXPORT_FOR_TESTS
>  	bool
> diff --git a/drivers/gpu/drm/scheduler/.kunitconfig
> b/drivers/gpu/drm/scheduler/.kunitconfig
> new file mode 100644
> index 000000000000..cece53609fcf
> --- /dev/null
> +++ b/drivers/gpu/drm/scheduler/.kunitconfig
> @@ -0,0 +1,12 @@
> +CONFIG_KUNIT=y
> +CONFIG_DRM=y
> +CONFIG_DRM_SCHED_KUNIT_TEST=y
> +CONFIG_EXPERT=y
> +CONFIG_DEBUG_SPINLOCK=y
> +CONFIG_DEBUG_MUTEXES=y
> +CONFIG_DEBUG_ATOMIC_SLEEP=y
> +CONFIG_LOCK_DEBUGGING_SUPPORT=y
> +CONFIG_PROVE_LOCKING=y
> +CONFIG_LOCKDEP=y
> +CONFIG_DEBUG_LOCKDEP=y
> +CONFIG_DEBUG_LIST=y
> diff --git a/drivers/gpu/drm/scheduler/Makefile
> b/drivers/gpu/drm/scheduler/Makefile
> index 53863621829f..46dfdca0758a 100644
> --- a/drivers/gpu/drm/scheduler/Makefile
> +++ b/drivers/gpu/drm/scheduler/Makefile
> @@ -23,3 +23,4 @@
>  gpu-sched-y := sched_main.o sched_fence.o sched_entity.o
>  
>  obj-$(CONFIG_DRM_SCHED) += gpu-sched.o
> +obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += tests/
> diff --git a/drivers/gpu/drm/scheduler/tests/Makefile
> b/drivers/gpu/drm/scheduler/tests/Makefile
> new file mode 100644
> index 000000000000..d69eab6a2e9b
> --- /dev/null
> +++ b/drivers/gpu/drm/scheduler/tests/Makefile
> @@ -0,0 +1,4 @@
> +
> +obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += \
> +        drm_mock_scheduler.o \
> +        drm_sched_tests_basic.o
> diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
> b/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
> new file mode 100644
> index 000000000000..c9205f9ff524
> --- /dev/null
> +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
> @@ -0,0 +1,29 @@
> +
> +#include "drm_sched_tests.h"
> +
> +struct drm_mock_sched_entity *
> +drm_mock_sched_entity_new(struct kunit *test,
> +			  enum drm_sched_priority priority,
> +			  struct drm_mock_scheduler *sched)
> +{
> +	struct drm_sched_mock_entity *entity;
> +	int ret;
> +
> +	entity = kunit_kmalloc(test, sizeof(*entity), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, entity);
> +
> +	ret = drm_sched_entity_init(&entity->base,
> +				    priority,
> +				    &sched->base, 1,
> +				    NULL);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +
> +	entity->test = test;
> +
> +	return entity;
> +}
> +
> +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
> *entity)
> +{
> +	drm_sched_entity_fini(&entity->base);
> +}
> diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
> b/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
> new file mode 100644
> index 000000000000..d94568cf3da9
> --- /dev/null
> +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
> @@ -0,0 +1,3 @@
> +
> +#include "drm_sched_tests.h"
> +
> diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
> b/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
> new file mode 100644
> index 000000000000..f65f7c0cfaf7
> --- /dev/null
> +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
> @@ -0,0 +1,255 @@
> +
> +#include "drm_sched_tests.h"
> +
> +struct drm_mock_sched_entity *
> +drm_mock_new_sched_entity(struct kunit *test,
> +			  enum drm_sched_priority priority,
> +			  struct drm_mock_scheduler *sched)
> +{
> +	struct drm_mock_sched_entity *entity;
> +	struct drm_gpu_scheduler *drm_sched;
> +	int ret;
> +
> +	entity = kunit_kzalloc(test, sizeof(*entity), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, entity);
> +
> +	drm_sched = &sched->base;
> +	ret = drm_sched_entity_init(&entity->base,
> +				    priority,
> +				    &drm_sched, 1,
> +				    NULL);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +
> +	entity->test = test;
> +
> +	return entity;
> +}
> +
> +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
> *entity)
> +{
> +	drm_sched_entity_destroy(&entity->base);
> +}
> +
> +static enum hrtimer_restart
> +drm_mock_sched_job_signal_timer(struct hrtimer *hrtimer)
> +{
> +	struct drm_mock_sched_job *upto =
> +		container_of(hrtimer, typeof(*upto), timer);
> +	struct drm_mock_scheduler *sched =
> +		drm_sched_to_mock_sched(upto->base.sched);
> +	struct drm_mock_sched_job *job, *next;
> +	ktime_t now = ktime_get();
> +	unsigned long flags;
> +	LIST_HEAD(signal);
> +
> +	spin_lock_irqsave(&sched->lock, flags);
> +	list_for_each_entry_safe(job, next, &sched->job_list, link)
> {
> +		if (!job->duration_us)
> +			break;
> +
> +		if (ktime_before(now, job->finish_at))
> +			break;
> +
> +		list_move_tail(&job->link, &signal);
> +		sched->hw_timeline.cur_seqno = job->hw_fence.seqno;
> +	}
> +	spin_unlock_irqrestore(&sched->lock, flags);
> +
> +	list_for_each_entry(job, &signal, link) {
> +		dma_fence_signal(&job->hw_fence);
> +		dma_fence_put(&job->hw_fence);
> +	}
> +
> +	return HRTIMER_NORESTART;
> +}
> +
> +struct drm_mock_sched_job *
> +drm_mock_new_sched_job(struct kunit *test,
> +		       struct drm_mock_sched_entity *entity)
> +{
> +	struct drm_mock_sched_job *job;
> +	int ret;
> +
> +	job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, job);
> +
> +	ret = drm_sched_job_init(&job->base,
> +				 &entity->base,
> +				 1,
> +				 NULL);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +
> +	job->test = test;
> +
> +	spin_lock_init(&job->lock);
> +	INIT_LIST_HEAD(&job->link);
> +	hrtimer_init(&job->timer, CLOCK_MONOTONIC,
> HRTIMER_MODE_ABS);
> +	job->timer.function = drm_mock_sched_job_signal_timer;
> +
> +	return job;
> +}
> +
> +static const char *drm_mock_sched_hw_fence_driver_name(struct
> dma_fence *fence)
> +{
> +	return "drm_mock_sched";
> +}
> +
> +static const char *
> +drm_mock_sched_hw_fence_timeline_name(struct dma_fence *fence)
> +{
> +	struct drm_mock_sched_job *job =
> +		container_of(fence, typeof(*job), hw_fence);
> +
> +	return (const char *)job->base.sched->name;
> +}
> +static void drm_mock_sched_hw_fence_release(struct dma_fence *fence)

Regarding the function naming convention here everywhere,
"drm_mock_sched_{something}" I guess that's alright as far as I'm
concerned.

But just for some context, have you looked how other unit tests in
drivers/drm/ do their naming convention? Grep doesn't reveal any
drm_mock_* lines.

If there is a convention, would be great if drm/sched/ can be
congruent.


Thx,
P.


> +{
> +	struct drm_mock_sched_job *job =
> +		container_of(fence, typeof(*job), hw_fence);
> +
> +	hrtimer_cancel(&job->timer);
> +
> +	/* Freed by the kunit framework */
> +}
> +
> +static const struct dma_fence_ops drm_mock_sched_hw_fence_ops = {
> +	.get_driver_name = drm_mock_sched_hw_fence_driver_name,
> +	.get_timeline_name = drm_mock_sched_hw_fence_timeline_name,
> +	.release = drm_mock_sched_hw_fence_release,
> +};
> +
> +static struct dma_fence *mock_sched_run_job(struct drm_sched_job
> *sched_job)
> +{
> +	struct drm_mock_scheduler *sched =
> +		drm_sched_to_mock_sched(sched_job->sched);
> +	struct drm_mock_sched_job *job =
> drm_sched_job_to_mock_job(sched_job);
> +
> +	dma_fence_init(&job->hw_fence,
> +		       &drm_mock_sched_hw_fence_ops,
> +		       &job->lock,
> +		       sched->hw_timeline.context,
> +		       atomic_inc_return(&sched-
> >hw_timeline.next_seqno));
> +
> +	dma_fence_get(&job->hw_fence); /* Reference for the job_list
> */
> +
> +	spin_lock_irq(&sched->lock);
> +	if (job->duration_us) {
> +		ktime_t prev_finish_at = 0;
> +
> +		if (!list_empty(&sched->job_list)) {
> +			struct drm_mock_sched_job *prev =
> +				list_last_entry(&sched->job_list,
> typeof(*prev),
> +						link);
> +
> +			prev_finish_at = prev->finish_at;
> +		}
> +
> +		if (!prev_finish_at)
> +			prev_finish_at = ktime_get();
> +
> +		job->finish_at = ktime_add_us(prev_finish_at, job-
> >duration_us);
> +	}
> +	list_add_tail(&job->link, &sched->job_list);
> +	if (job->finish_at)
> +		hrtimer_start(&job->timer, job->finish_at,
> HRTIMER_MODE_ABS);
> +	spin_unlock_irq(&sched->lock);
> +
> +	return &job->hw_fence;
> +}
> +
> +static enum drm_gpu_sched_stat
> +mock_sched_timedout_job(struct drm_sched_job *sched_job)
> +{
> +	return DRM_GPU_SCHED_STAT_ENODEV;
> +}
> +
> +static void mock_sched_free_job(struct drm_sched_job *sched_job)
> +{
> +	drm_sched_job_cleanup(sched_job);
> +}
> +
> +static const struct drm_sched_backend_ops drm_mock_scheduler_ops = {
> +	.run_job = mock_sched_run_job,
> +	.timedout_job = mock_sched_timedout_job,
> +	.free_job = mock_sched_free_job
> +};
> +
> +struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit
> *test)
> +{
> +	struct drm_mock_scheduler *sched;
> +	int ret;
> +
> +	sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, sched);
> +
> +	ret = drm_sched_init(&sched->base,
> +			     &drm_mock_scheduler_ops,
> +			     NULL, /* wq */
> +			     DRM_SCHED_PRIORITY_COUNT,
> +			     U32_MAX, /* max credits */
> +			     UINT_MAX, /* hang limit */
> +			     MAX_SCHEDULE_TIMEOUT, /* timeout */
> +			     NULL, /* timeout wq */
> +			     NULL, /* score */
> +			     "drm-mock-scheduler",
> +			     NULL /* dev */);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +
> +	sched->test = test;
> +	sched->hw_timeline.context = dma_fence_context_alloc(1);
> +	atomic_set(&sched->hw_timeline.next_seqno, 0);
> +	INIT_LIST_HEAD(&sched->job_list);
> +	spin_lock_init(&sched->lock);
> +
> +	return sched;
> +}
> +
> +void drm_mock_scheduler_fini(struct drm_mock_scheduler *sched)
> +{
> +	struct drm_mock_sched_job *job, *next;
> +	unsigned long flags;
> +	LIST_HEAD(signal);
> +
> +	spin_lock_irqsave(&sched->lock, flags);
> +	list_for_each_entry_safe(job, next, &sched->job_list, link)
> +		list_move_tail(&job->link, &signal);
> +	spin_unlock_irqrestore(&sched->lock, flags);
> +
> +	list_for_each_entry(job, &signal, link) {
> +		hrtimer_cancel(&job->timer);
> +		dma_fence_put(&job->hw_fence);
> +	}
> +
> +	drm_sched_fini(&sched->base);
> +}
> +
> +unsigned int drm_mock_sched_advance(struct drm_mock_scheduler
> *sched,
> +				    unsigned int num)
> +{
> +	struct drm_mock_sched_job *job, *next;
> +	unsigned int found = 0;
> +	unsigned long flags;
> +	LIST_HEAD(signal);
> +
> +	spin_lock_irqsave(&sched->lock, flags);
> +	if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num <
> +			 sched->hw_timeline.cur_seqno))
> +		goto unlock;
> +	sched->hw_timeline.cur_seqno += num;
> +	list_for_each_entry_safe(job, next, &sched->job_list, link)
> {
> +		if (sched->hw_timeline.cur_seqno < job-
> >hw_fence.seqno)
> +			break;
> +
> +		list_move_tail(&job->link, &signal);
> +		found++;
> +	}
> +unlock:
> +	spin_unlock_irqrestore(&sched->lock, flags);
> +
> +	list_for_each_entry(job, &signal, link) {
> +		dma_fence_signal(&job->hw_fence);
> +		dma_fence_put(&job->hw_fence);
> +	}
> +
> +	return found;
> +}
> diff --git a/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
> b/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
> new file mode 100644
> index 000000000000..e22f7ead6d4e
> --- /dev/null
> +++ b/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
> @@ -0,0 +1,128 @@
> +#ifndef _DRM_SCHED_TESTS_H_
> +#define _DRM_SCHED_TESTS_H_
> +
> +#include <kunit/test.h>
> +#include <linux/atomic.h>
> +#include <linux/dma-fence.h>
> +#include <linux/hrtimer.h>
> +#include <linux/ktime.h>
> +#include <linux/list.h>
> +#include <linux/atomic.h>
> +#include <linux/mutex.h>
> +#include <linux/types.h>
> +
> +#include <drm/gpu_scheduler.h>
> +
> +struct drm_mock_scheduler {
> +	struct drm_gpu_scheduler base;
> +
> +	struct kunit		*test;
> +
> +	spinlock_t		lock;
> +	struct list_head	job_list; /* Protected by the lock
> */
> +
> +	struct {
> +		u64		context;
> +		atomic_t	next_seqno;
> +		unsigned int	cur_seqno; /* Protected by the lock
> */
> +	} hw_timeline;
> +};
> +
> +struct drm_mock_sched_entity {
> +	struct drm_sched_entity base;
> +
> +	struct kunit		*test;
> +};
> +
> +struct drm_mock_sched_job {
> +	struct drm_sched_job	base;
> +
> +	struct list_head	link;
> +	struct hrtimer		timer;
> +
> +	unsigned int		duration_us;
> +	ktime_t			finish_at;
> +
> +	spinlock_t		lock;
> +	struct dma_fence	hw_fence;
> +
> +	struct kunit		*test;
> +};
> +
> +static inline struct drm_mock_scheduler *
> +drm_sched_to_mock_sched(struct drm_gpu_scheduler *sched)
> +{
> +	return container_of(sched, struct drm_mock_scheduler, base);
> +};
> +
> +static inline struct drm_mock_sched_entity *
> +drm_sched_entity_to_mock_entity(struct drm_sched_entity
> *sched_entity)
> +{
> +	return container_of(sched_entity, struct
> drm_mock_sched_entity, base);
> +};
> +
> +static inline struct drm_mock_sched_job *
> +drm_sched_job_to_mock_job(struct drm_sched_job *sched_job)
> +{
> +	return container_of(sched_job, struct drm_mock_sched_job,
> base);
> +};
> +
> +struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit
> *test);
> +void drm_mock_scheduler_fini(struct drm_mock_scheduler *sched);
> +unsigned int drm_mock_sched_advance(struct drm_mock_scheduler
> *sched,
> +				    unsigned int num);
> +
> +struct drm_mock_sched_entity *
> +drm_mock_new_sched_entity(struct kunit *test,
> +			  enum drm_sched_priority priority,
> +			  struct drm_mock_scheduler *sched);
> +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
> *entity);
> +
> +struct drm_mock_sched_job *
> +drm_mock_new_sched_job(struct kunit *test,
> +		       struct drm_mock_sched_entity *entity);
> +
> +static inline void drm_mock_sched_job_submit(struct
> drm_mock_sched_job *job)
> +{
> +	drm_sched_job_arm(&job->base);
> +	drm_sched_entity_push_job(&job->base);
> +}
> +
> +static inline void
> +drm_mock_sched_job_set_duration_us(struct drm_mock_sched_job *job,
> +				   unsigned int duration_us)
> +{
> +	job->duration_us = duration_us;
> +}
> +
> +static inline bool
> +drm_mock_sched_job_is_finished(struct drm_mock_sched_job *job)
> +{
> +	return dma_fence_is_signaled(&job->base.s_fence->finished);
> +}
> +
> +static inline bool
> +drm_mock_sched_job_wait_finished(struct drm_mock_sched_job *job,
> long timeout)
> +{
> +	long ret;
> +
> +	ret = dma_fence_wait_timeout(&job->base.s_fence->finished,
> +				      false,
> +				      timeout);
> +
> +	return ret != 0;
> +}
> +
> +static inline long
> +drm_mock_sched_job_wait_scheduled(struct drm_mock_sched_job *job,
> long timeout)
> +{
> +	long ret;
> +
> +	ret = dma_fence_wait_timeout(&job->base.s_fence->scheduled,
> +				      false,
> +				      timeout);
> +
> +	return ret != 0;
> +}
> +
> +#endif
> diff --git a/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
> b/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
> new file mode 100644
> index 000000000000..030d7e6ea484
> --- /dev/null
> +++ b/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
> @@ -0,0 +1,188 @@
> +
> +#include "drm_sched_tests.h"
> +
> +static int drm_sched_basic_init(struct kunit *test)
> +{
> +	test->priv = drm_mock_new_scheduler(test);
> +
> +	return 0;
> +}
> +
> +static void drm_sched_basic_exit(struct kunit *test)
> +{
> +	struct drm_mock_scheduler *sched = test->priv;
> +
> +	drm_mock_scheduler_fini(sched);
> +}
> +
> +static void drm_sched_basic_submit(struct kunit *test)
> +{
> +	struct drm_mock_scheduler *sched = test->priv;
> +	struct drm_mock_sched_entity *entity;
> +	struct drm_mock_sched_job *job;
> +	unsigned int i;
> +	bool done;
> +
> +	/*
> +	 * Submit one job to the scheduler and verify that it gets
> scheduled
> +	 * and completed only when the mock hw backend processes it.
> +	 */
> +
> +	entity = drm_mock_new_sched_entity(test,
> +					  
> DRM_SCHED_PRIORITY_NORMAL,
> +					   sched);
> +	job = drm_mock_new_sched_job(test, entity);
> +
> +	drm_mock_sched_job_submit(job);
> +
> +	done = drm_mock_sched_job_wait_scheduled(job, HZ);
> +	KUNIT_ASSERT_EQ(test, done, true);
> +
> +	done = drm_mock_sched_job_wait_finished(job, HZ / 2);
> +	KUNIT_ASSERT_EQ(test, done, false);
> +
> +	i = drm_mock_sched_advance(sched, 1);
> +	KUNIT_ASSERT_EQ(test, i, 1);
> +
> +	done = drm_mock_sched_job_wait_finished(job, HZ);
> +	KUNIT_ASSERT_EQ(test, done, true);
> +
> +	drm_mock_sched_entity_free(entity);
> +}
> +
> +struct drm_sched_basic_params {
> +	const char *description;
> +	unsigned int queue_depth;
> +	unsigned int num_entities;
> +	unsigned int job_us;
> +	bool dep_chain;
> +};
> +
> +static const struct drm_sched_basic_params drm_sched_basic_cases[] =
> {
> +	{
> +		.description = "A queue of jobs in a single entity",
> +		.queue_depth = 100,
> +		.job_us = 1000,
> +		.num_entities = 1,
> +	},
> +	{
> +		.description = "A chain of dependent jobs across
> multiple entities",
> +		.queue_depth = 100,
> +		.job_us = 1000,
> +		.num_entities = 1,
> +		.dep_chain = true,
> +	},
> +	{
> +		.description = "Multiple independent job queues",
> +		.queue_depth = 100,
> +		.job_us = 1000,
> +		.num_entities = 4,
> +	},
> +	{
> +		.description = "Multiple inter-dependent job
> queues",
> +		.queue_depth = 100,
> +		.job_us = 1000,
> +		.num_entities = 4,
> +		.dep_chain = true,
> +	},
> +};
> +
> +static void
> +drm_sched_basic_desc(const struct drm_sched_basic_params *params,
> char *desc)
> +{
> +	strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
> +}
> +
> +KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases,
> drm_sched_basic_desc);
> +
> +static void drm_sched_basic_test(struct kunit *test)
> +{
> +	const struct drm_sched_basic_params *params = test-
> >param_value;
> +	struct drm_mock_scheduler *sched = test->priv;
> +	struct drm_mock_sched_job *job, *prev = NULL;
> +	struct drm_mock_sched_entity **entity;
> +	unsigned int i, cur_ent = 0;
> +	bool done;
> +
> +	entity = kunit_kcalloc(test, params->num_entities,
> sizeof(*entity),
> +			       GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, entity);
> +
> +	for (i = 0; i < params->num_entities; i++)
> +		entity[i] = drm_mock_new_sched_entity(test,
> +						     
> DRM_SCHED_PRIORITY_NORMAL,
> +						      sched);
> +
> +	for (i = 0; i < params->queue_depth; i++) {
> +		job = drm_mock_new_sched_job(test,
> entity[cur_ent++]);
> +		cur_ent %= params->num_entities;
> +		drm_mock_sched_job_set_duration_us(job, params-
> >job_us);
> +		if (params->dep_chain && prev)
> +			drm_sched_job_add_dependency(&job->base,
> +						    
> dma_fence_get(&prev->base.s_fence->finished));
> +		drm_mock_sched_job_submit(job);
> +		prev = job;
> +	}
> +
> +	done = drm_mock_sched_job_wait_finished(job, HZ);
> +	KUNIT_ASSERT_EQ(test, done, true);
> +
> +	for (i = 0; i < params->num_entities; i++)
> +		drm_mock_sched_entity_free(entity[i]);
> +}
> +
> +static void drm_sched_basic_entity_cleanup(struct kunit *test)
> +{
> +	struct drm_mock_sched_job *job, *mid, *prev = NULL;
> +	struct drm_mock_scheduler *sched = test->priv;
> +	struct drm_mock_sched_entity *entity[4];
> +	const unsigned int qd = 100;
> +	unsigned int i, cur_ent = 0;
> +	bool done;
> +
> +	/*
> +	 * Submit a queue of jobs across different entities with an
> explicit
> +	 * chain of dependencies between them and trigger entity
> cleanup while
> +	 * the queue is still being processed.
> +	 */
> +
> +	for (i = 0; i < ARRAY_SIZE(entity); i++)
> +		entity[i] = drm_mock_new_sched_entity(test,
> +						     
> DRM_SCHED_PRIORITY_NORMAL,
> +						      sched);
> +
> +	for (i = 0; i < qd; i++) {
> +		job = drm_mock_new_sched_job(test,
> entity[cur_ent++]);
> +		cur_ent %= ARRAY_SIZE(entity);
> +		drm_mock_sched_job_set_duration_us(job, 1000);
> +		if (prev)
> +			drm_sched_job_add_dependency(&job->base,
> +						    
> dma_fence_get(&prev->base.s_fence->finished));
> +		drm_mock_sched_job_submit(job);
> +		if (i == qd / 2)
> +			mid = job;
> +		prev = job;
> +	}
> +
> +	done = drm_mock_sched_job_wait_finished(mid, HZ);
> +	KUNIT_ASSERT_EQ(test, done, true);
> +
> +	/* Exit with half of the queue still pending to be executed.
> */
> +	for (i = 0; i < ARRAY_SIZE(entity); i++)
> +		drm_mock_sched_entity_free(entity[i]);}
> +
> +static struct kunit_case drm_sched_basic_tests[] = {
> +	KUNIT_CASE(drm_sched_basic_submit),
> +	KUNIT_CASE_PARAM(drm_sched_basic_test,
> drm_sched_basic_gen_params),
> +	KUNIT_CASE(drm_sched_basic_entity_cleanup),
> +	{}
> +};
> +
> +static struct kunit_suite drm_sched_basic = {
> +	.name = "drm_sched_basic_tests",
> +	.init = drm_sched_basic_init,
> +	.exit = drm_sched_basic_exit,
> +	.test_cases = drm_sched_basic_tests,
> +};
> +
> +kunit_test_suite(drm_sched_basic);
Tvrtko Ursulin Feb. 13, 2025, 2:18 p.m. UTC | #2
On 13/02/2025 13:05, Philipp Stanner wrote:
> I'm in principle OK with this series. We'll have to discuss a few more
> things, but from my POV you could send the next revision as a v1.
> 
> Some more input below..
> 
> On Fri, 2025-02-07 at 14:33 +0000, Tvrtko Ursulin wrote:
>> Implement a mock scheduler backend and add some basic test to
>> exercise the
>> core scheduler code paths.
>>
>> Mock backend (kind of like a very simple mock GPU) can either process
>> jobs
>> by tests manually advancing the "timeline" job at a time, or
>> alternatively
>> jobs can be configured with a time duration in which case they get
>> completed asynchronously from the unit test code.
>>
>> Core scheduler classes are subclassed to support this mock
>> implementation.
>>
>> The tests added are just a few simple submission patterns.
>>
>> Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
>> Suggested-by: Philipp Stanner <phasta@kernel.org>
>> Cc: Christian König <christian.koenig@amd.com>
>> Cc: Danilo Krummrich <dakr@kernel.org>
>> Cc: Matthew Brost <matthew.brost@intel.com>
>> Cc: Philipp Stanner <phasta@kernel.org>
>> ---
>>   drivers/gpu/drm/Kconfig.debug                 |  12 +
>>   drivers/gpu/drm/scheduler/.kunitconfig        |  12 +
>>   drivers/gpu/drm/scheduler/Makefile            |   1 +
>>   drivers/gpu/drm/scheduler/tests/Makefile      |   4 +
>>   .../gpu/drm/scheduler/tests/drm_mock_entity.c |  29 ++
>>   .../gpu/drm/scheduler/tests/drm_mock_job.c    |   3 +
>>   .../drm/scheduler/tests/drm_mock_scheduler.c  | 255
>> ++++++++++++++++++
>>   .../gpu/drm/scheduler/tests/drm_sched_tests.h | 128 +++++++++
>>   .../scheduler/tests/drm_sched_tests_basic.c   | 188 +++++++++++++
>>   9 files changed, 632 insertions(+)
>>   create mode 100644 drivers/gpu/drm/scheduler/.kunitconfig
>>   create mode 100644 drivers/gpu/drm/scheduler/tests/Makefile
>>   create mode 100644 drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
>>   create mode 100644 drivers/gpu/drm/scheduler/tests/drm_mock_job.c
>>   create mode 100644
>> drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
>>   create mode 100644 drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
>>   create mode 100644
>> drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
> 
> I'm still not that convinced about the naming convention of those
> files. They reside in drm/scheduler, but are called
> drm_mock_{something}.c, almost reading as if they are about DRM in
> general.
> 
> I think we should keep it consistent with the files in drm/scheduler/
> and just call those drm/scheduler/tests/sched_mock_job.c etc.

Dropping the redundant prefixes makes sense. How about then a step 
further and make it drm/scheduler/tests/mock_job.c?

>> diff --git a/drivers/gpu/drm/Kconfig.debug
>> b/drivers/gpu/drm/Kconfig.debug
>> index a35d74171b7b..89f777f21e95 100644
>> --- a/drivers/gpu/drm/Kconfig.debug
>> +++ b/drivers/gpu/drm/Kconfig.debug
>> @@ -88,5 +88,17 @@ config DRM_TTM_KUNIT_TEST
>>   
>>   	  If in doubt, say "N".
>>   
>> +config DRM_SCHED_KUNIT_TEST
>> +	tristate "KUnit tests for the DRM scheduler" if
>> !KUNIT_ALL_TESTS
>> +	select DRM_SCHED
>> +	depends on DRM && KUNIT
>> +	default KUNIT_ALL_TESTS
>> +	help
>> +	  Choose this option to build unit tests for the DRM
>> scheduler.
>> +
>> +	  Recommended for driver developers only.
>> +
>> +	  If in doubt, say "N".
>> +
>>   config DRM_EXPORT_FOR_TESTS
>>   	bool
>> diff --git a/drivers/gpu/drm/scheduler/.kunitconfig
>> b/drivers/gpu/drm/scheduler/.kunitconfig
>> new file mode 100644
>> index 000000000000..cece53609fcf
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/.kunitconfig
>> @@ -0,0 +1,12 @@
>> +CONFIG_KUNIT=y
>> +CONFIG_DRM=y
>> +CONFIG_DRM_SCHED_KUNIT_TEST=y
>> +CONFIG_EXPERT=y
>> +CONFIG_DEBUG_SPINLOCK=y
>> +CONFIG_DEBUG_MUTEXES=y
>> +CONFIG_DEBUG_ATOMIC_SLEEP=y
>> +CONFIG_LOCK_DEBUGGING_SUPPORT=y
>> +CONFIG_PROVE_LOCKING=y
>> +CONFIG_LOCKDEP=y
>> +CONFIG_DEBUG_LOCKDEP=y
>> +CONFIG_DEBUG_LIST=y
>> diff --git a/drivers/gpu/drm/scheduler/Makefile
>> b/drivers/gpu/drm/scheduler/Makefile
>> index 53863621829f..46dfdca0758a 100644
>> --- a/drivers/gpu/drm/scheduler/Makefile
>> +++ b/drivers/gpu/drm/scheduler/Makefile
>> @@ -23,3 +23,4 @@
>>   gpu-sched-y := sched_main.o sched_fence.o sched_entity.o
>>   
>>   obj-$(CONFIG_DRM_SCHED) += gpu-sched.o
>> +obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += tests/
>> diff --git a/drivers/gpu/drm/scheduler/tests/Makefile
>> b/drivers/gpu/drm/scheduler/tests/Makefile
>> new file mode 100644
>> index 000000000000..d69eab6a2e9b
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/Makefile
>> @@ -0,0 +1,4 @@
>> +
>> +obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += \
>> +        drm_mock_scheduler.o \
>> +        drm_sched_tests_basic.o
>> diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
>> b/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
>> new file mode 100644
>> index 000000000000..c9205f9ff524
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
>> @@ -0,0 +1,29 @@
>> +
>> +#include "drm_sched_tests.h"
>> +
>> +struct drm_mock_sched_entity *
>> +drm_mock_sched_entity_new(struct kunit *test,
>> +			  enum drm_sched_priority priority,
>> +			  struct drm_mock_scheduler *sched)
>> +{
>> +	struct drm_sched_mock_entity *entity;
>> +	int ret;
>> +
>> +	entity = kunit_kmalloc(test, sizeof(*entity), GFP_KERNEL);
>> +	KUNIT_ASSERT_NOT_NULL(test, entity);
>> +
>> +	ret = drm_sched_entity_init(&entity->base,
>> +				    priority,
>> +				    &sched->base, 1,
>> +				    NULL);
>> +	KUNIT_ASSERT_EQ(test, ret, 0);
>> +
>> +	entity->test = test;
>> +
>> +	return entity;
>> +}
>> +
>> +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
>> *entity)
>> +{
>> +	drm_sched_entity_fini(&entity->base);
>> +}
>> diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
>> b/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
>> new file mode 100644
>> index 000000000000..d94568cf3da9
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
>> @@ -0,0 +1,3 @@
>> +
>> +#include "drm_sched_tests.h"
>> +
>> diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
>> b/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
>> new file mode 100644
>> index 000000000000..f65f7c0cfaf7
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
>> @@ -0,0 +1,255 @@
>> +
>> +#include "drm_sched_tests.h"
>> +
>> +struct drm_mock_sched_entity *
>> +drm_mock_new_sched_entity(struct kunit *test,
>> +			  enum drm_sched_priority priority,
>> +			  struct drm_mock_scheduler *sched)
>> +{
>> +	struct drm_mock_sched_entity *entity;
>> +	struct drm_gpu_scheduler *drm_sched;
>> +	int ret;
>> +
>> +	entity = kunit_kzalloc(test, sizeof(*entity), GFP_KERNEL);
>> +	KUNIT_ASSERT_NOT_NULL(test, entity);
>> +
>> +	drm_sched = &sched->base;
>> +	ret = drm_sched_entity_init(&entity->base,
>> +				    priority,
>> +				    &drm_sched, 1,
>> +				    NULL);
>> +	KUNIT_ASSERT_EQ(test, ret, 0);
>> +
>> +	entity->test = test;
>> +
>> +	return entity;
>> +}
>> +
>> +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
>> *entity)
>> +{
>> +	drm_sched_entity_destroy(&entity->base);
>> +}
>> +
>> +static enum hrtimer_restart
>> +drm_mock_sched_job_signal_timer(struct hrtimer *hrtimer)
>> +{
>> +	struct drm_mock_sched_job *upto =
>> +		container_of(hrtimer, typeof(*upto), timer);
>> +	struct drm_mock_scheduler *sched =
>> +		drm_sched_to_mock_sched(upto->base.sched);
>> +	struct drm_mock_sched_job *job, *next;
>> +	ktime_t now = ktime_get();
>> +	unsigned long flags;
>> +	LIST_HEAD(signal);
>> +
>> +	spin_lock_irqsave(&sched->lock, flags);
>> +	list_for_each_entry_safe(job, next, &sched->job_list, link)
>> {
>> +		if (!job->duration_us)
>> +			break;
>> +
>> +		if (ktime_before(now, job->finish_at))
>> +			break;
>> +
>> +		list_move_tail(&job->link, &signal);
>> +		sched->hw_timeline.cur_seqno = job->hw_fence.seqno;
>> +	}
>> +	spin_unlock_irqrestore(&sched->lock, flags);
>> +
>> +	list_for_each_entry(job, &signal, link) {
>> +		dma_fence_signal(&job->hw_fence);
>> +		dma_fence_put(&job->hw_fence);
>> +	}
>> +
>> +	return HRTIMER_NORESTART;
>> +}
>> +
>> +struct drm_mock_sched_job *
>> +drm_mock_new_sched_job(struct kunit *test,
>> +		       struct drm_mock_sched_entity *entity)
>> +{
>> +	struct drm_mock_sched_job *job;
>> +	int ret;
>> +
>> +	job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL);
>> +	KUNIT_ASSERT_NOT_NULL(test, job);
>> +
>> +	ret = drm_sched_job_init(&job->base,
>> +				 &entity->base,
>> +				 1,
>> +				 NULL);
>> +	KUNIT_ASSERT_EQ(test, ret, 0);
>> +
>> +	job->test = test;
>> +
>> +	spin_lock_init(&job->lock);
>> +	INIT_LIST_HEAD(&job->link);
>> +	hrtimer_init(&job->timer, CLOCK_MONOTONIC,
>> HRTIMER_MODE_ABS);
>> +	job->timer.function = drm_mock_sched_job_signal_timer;
>> +
>> +	return job;
>> +}
>> +
>> +static const char *drm_mock_sched_hw_fence_driver_name(struct
>> dma_fence *fence)
>> +{
>> +	return "drm_mock_sched";
>> +}
>> +
>> +static const char *
>> +drm_mock_sched_hw_fence_timeline_name(struct dma_fence *fence)
>> +{
>> +	struct drm_mock_sched_job *job =
>> +		container_of(fence, typeof(*job), hw_fence);
>> +
>> +	return (const char *)job->base.sched->name;
>> +}
>> +static void drm_mock_sched_hw_fence_release(struct dma_fence *fence)
> 
> Regarding the function naming convention here everywhere,
> "drm_mock_sched_{something}" I guess that's alright as far as I'm
> concerned.
> 
> But just for some context, have you looked how other unit tests in
> drivers/drm/ do their naming convention? Grep doesn't reveal any
> drm_mock_* lines.
> 
> If there is a convention, would be great if drm/sched/ can be
> congruent.

There is mention of mock object in kerneldoc for functions prefixed with 
drm_kunit_helper. For me drm_mock_$something is better than drm_kunit 
helper_$something so I would rather keep my scheme.

I don't think we should have illusions this early that the "mock" 
infrastructure can/will be shared. And as the ones I am adding live 
locally in drm/scheduler/ I think it is fine as it is.

Regards,

Tvrtko

>> +{
>> +	struct drm_mock_sched_job *job =
>> +		container_of(fence, typeof(*job), hw_fence);
>> +
>> +	hrtimer_cancel(&job->timer);
>> +
>> +	/* Freed by the kunit framework */
>> +}
>> +
>> +static const struct dma_fence_ops drm_mock_sched_hw_fence_ops = {
>> +	.get_driver_name = drm_mock_sched_hw_fence_driver_name,
>> +	.get_timeline_name = drm_mock_sched_hw_fence_timeline_name,
>> +	.release = drm_mock_sched_hw_fence_release,
>> +};
>> +
>> +static struct dma_fence *mock_sched_run_job(struct drm_sched_job
>> *sched_job)
>> +{
>> +	struct drm_mock_scheduler *sched =
>> +		drm_sched_to_mock_sched(sched_job->sched);
>> +	struct drm_mock_sched_job *job =
>> drm_sched_job_to_mock_job(sched_job);
>> +
>> +	dma_fence_init(&job->hw_fence,
>> +		       &drm_mock_sched_hw_fence_ops,
>> +		       &job->lock,
>> +		       sched->hw_timeline.context,
>> +		       atomic_inc_return(&sched-
>>> hw_timeline.next_seqno));
>> +
>> +	dma_fence_get(&job->hw_fence); /* Reference for the job_list
>> */
>> +
>> +	spin_lock_irq(&sched->lock);
>> +	if (job->duration_us) {
>> +		ktime_t prev_finish_at = 0;
>> +
>> +		if (!list_empty(&sched->job_list)) {
>> +			struct drm_mock_sched_job *prev =
>> +				list_last_entry(&sched->job_list,
>> typeof(*prev),
>> +						link);
>> +
>> +			prev_finish_at = prev->finish_at;
>> +		}
>> +
>> +		if (!prev_finish_at)
>> +			prev_finish_at = ktime_get();
>> +
>> +		job->finish_at = ktime_add_us(prev_finish_at, job-
>>> duration_us);
>> +	}
>> +	list_add_tail(&job->link, &sched->job_list);
>> +	if (job->finish_at)
>> +		hrtimer_start(&job->timer, job->finish_at,
>> HRTIMER_MODE_ABS);
>> +	spin_unlock_irq(&sched->lock);
>> +
>> +	return &job->hw_fence;
>> +}
>> +
>> +static enum drm_gpu_sched_stat
>> +mock_sched_timedout_job(struct drm_sched_job *sched_job)
>> +{
>> +	return DRM_GPU_SCHED_STAT_ENODEV;
>> +}
>> +
>> +static void mock_sched_free_job(struct drm_sched_job *sched_job)
>> +{
>> +	drm_sched_job_cleanup(sched_job);
>> +}
>> +
>> +static const struct drm_sched_backend_ops drm_mock_scheduler_ops = {
>> +	.run_job = mock_sched_run_job,
>> +	.timedout_job = mock_sched_timedout_job,
>> +	.free_job = mock_sched_free_job
>> +};
>> +
>> +struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit
>> *test)
>> +{
>> +	struct drm_mock_scheduler *sched;
>> +	int ret;
>> +
>> +	sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL);
>> +	KUNIT_ASSERT_NOT_NULL(test, sched);
>> +
>> +	ret = drm_sched_init(&sched->base,
>> +			     &drm_mock_scheduler_ops,
>> +			     NULL, /* wq */
>> +			     DRM_SCHED_PRIORITY_COUNT,
>> +			     U32_MAX, /* max credits */
>> +			     UINT_MAX, /* hang limit */
>> +			     MAX_SCHEDULE_TIMEOUT, /* timeout */
>> +			     NULL, /* timeout wq */
>> +			     NULL, /* score */
>> +			     "drm-mock-scheduler",
>> +			     NULL /* dev */);
>> +	KUNIT_ASSERT_EQ(test, ret, 0);
>> +
>> +	sched->test = test;
>> +	sched->hw_timeline.context = dma_fence_context_alloc(1);
>> +	atomic_set(&sched->hw_timeline.next_seqno, 0);
>> +	INIT_LIST_HEAD(&sched->job_list);
>> +	spin_lock_init(&sched->lock);
>> +
>> +	return sched;
>> +}
>> +
>> +void drm_mock_scheduler_fini(struct drm_mock_scheduler *sched)
>> +{
>> +	struct drm_mock_sched_job *job, *next;
>> +	unsigned long flags;
>> +	LIST_HEAD(signal);
>> +
>> +	spin_lock_irqsave(&sched->lock, flags);
>> +	list_for_each_entry_safe(job, next, &sched->job_list, link)
>> +		list_move_tail(&job->link, &signal);
>> +	spin_unlock_irqrestore(&sched->lock, flags);
>> +
>> +	list_for_each_entry(job, &signal, link) {
>> +		hrtimer_cancel(&job->timer);
>> +		dma_fence_put(&job->hw_fence);
>> +	}
>> +
>> +	drm_sched_fini(&sched->base);
>> +}
>> +
>> +unsigned int drm_mock_sched_advance(struct drm_mock_scheduler
>> *sched,
>> +				    unsigned int num)
>> +{
>> +	struct drm_mock_sched_job *job, *next;
>> +	unsigned int found = 0;
>> +	unsigned long flags;
>> +	LIST_HEAD(signal);
>> +
>> +	spin_lock_irqsave(&sched->lock, flags);
>> +	if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num <
>> +			 sched->hw_timeline.cur_seqno))
>> +		goto unlock;
>> +	sched->hw_timeline.cur_seqno += num;
>> +	list_for_each_entry_safe(job, next, &sched->job_list, link)
>> {
>> +		if (sched->hw_timeline.cur_seqno < job-
>>> hw_fence.seqno)
>> +			break;
>> +
>> +		list_move_tail(&job->link, &signal);
>> +		found++;
>> +	}
>> +unlock:
>> +	spin_unlock_irqrestore(&sched->lock, flags);
>> +
>> +	list_for_each_entry(job, &signal, link) {
>> +		dma_fence_signal(&job->hw_fence);
>> +		dma_fence_put(&job->hw_fence);
>> +	}
>> +
>> +	return found;
>> +}
>> diff --git a/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
>> b/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
>> new file mode 100644
>> index 000000000000..e22f7ead6d4e
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
>> @@ -0,0 +1,128 @@
>> +#ifndef _DRM_SCHED_TESTS_H_
>> +#define _DRM_SCHED_TESTS_H_
>> +
>> +#include <kunit/test.h>
>> +#include <linux/atomic.h>
>> +#include <linux/dma-fence.h>
>> +#include <linux/hrtimer.h>
>> +#include <linux/ktime.h>
>> +#include <linux/list.h>
>> +#include <linux/atomic.h>
>> +#include <linux/mutex.h>
>> +#include <linux/types.h>
>> +
>> +#include <drm/gpu_scheduler.h>
>> +
>> +struct drm_mock_scheduler {
>> +	struct drm_gpu_scheduler base;
>> +
>> +	struct kunit		*test;
>> +
>> +	spinlock_t		lock;
>> +	struct list_head	job_list; /* Protected by the lock
>> */
>> +
>> +	struct {
>> +		u64		context;
>> +		atomic_t	next_seqno;
>> +		unsigned int	cur_seqno; /* Protected by the lock
>> */
>> +	} hw_timeline;
>> +};
>> +
>> +struct drm_mock_sched_entity {
>> +	struct drm_sched_entity base;
>> +
>> +	struct kunit		*test;
>> +};
>> +
>> +struct drm_mock_sched_job {
>> +	struct drm_sched_job	base;
>> +
>> +	struct list_head	link;
>> +	struct hrtimer		timer;
>> +
>> +	unsigned int		duration_us;
>> +	ktime_t			finish_at;
>> +
>> +	spinlock_t		lock;
>> +	struct dma_fence	hw_fence;
>> +
>> +	struct kunit		*test;
>> +};
>> +
>> +static inline struct drm_mock_scheduler *
>> +drm_sched_to_mock_sched(struct drm_gpu_scheduler *sched)
>> +{
>> +	return container_of(sched, struct drm_mock_scheduler, base);
>> +};
>> +
>> +static inline struct drm_mock_sched_entity *
>> +drm_sched_entity_to_mock_entity(struct drm_sched_entity
>> *sched_entity)
>> +{
>> +	return container_of(sched_entity, struct
>> drm_mock_sched_entity, base);
>> +};
>> +
>> +static inline struct drm_mock_sched_job *
>> +drm_sched_job_to_mock_job(struct drm_sched_job *sched_job)
>> +{
>> +	return container_of(sched_job, struct drm_mock_sched_job,
>> base);
>> +};
>> +
>> +struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit
>> *test);
>> +void drm_mock_scheduler_fini(struct drm_mock_scheduler *sched);
>> +unsigned int drm_mock_sched_advance(struct drm_mock_scheduler
>> *sched,
>> +				    unsigned int num);
>> +
>> +struct drm_mock_sched_entity *
>> +drm_mock_new_sched_entity(struct kunit *test,
>> +			  enum drm_sched_priority priority,
>> +			  struct drm_mock_scheduler *sched);
>> +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
>> *entity);
>> +
>> +struct drm_mock_sched_job *
>> +drm_mock_new_sched_job(struct kunit *test,
>> +		       struct drm_mock_sched_entity *entity);
>> +
>> +static inline void drm_mock_sched_job_submit(struct
>> drm_mock_sched_job *job)
>> +{
>> +	drm_sched_job_arm(&job->base);
>> +	drm_sched_entity_push_job(&job->base);
>> +}
>> +
>> +static inline void
>> +drm_mock_sched_job_set_duration_us(struct drm_mock_sched_job *job,
>> +				   unsigned int duration_us)
>> +{
>> +	job->duration_us = duration_us;
>> +}
>> +
>> +static inline bool
>> +drm_mock_sched_job_is_finished(struct drm_mock_sched_job *job)
>> +{
>> +	return dma_fence_is_signaled(&job->base.s_fence->finished);
>> +}
>> +
>> +static inline bool
>> +drm_mock_sched_job_wait_finished(struct drm_mock_sched_job *job,
>> long timeout)
>> +{
>> +	long ret;
>> +
>> +	ret = dma_fence_wait_timeout(&job->base.s_fence->finished,
>> +				      false,
>> +				      timeout);
>> +
>> +	return ret != 0;
>> +}
>> +
>> +static inline long
>> +drm_mock_sched_job_wait_scheduled(struct drm_mock_sched_job *job,
>> long timeout)
>> +{
>> +	long ret;
>> +
>> +	ret = dma_fence_wait_timeout(&job->base.s_fence->scheduled,
>> +				      false,
>> +				      timeout);
>> +
>> +	return ret != 0;
>> +}
>> +
>> +#endif
>> diff --git a/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
>> b/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
>> new file mode 100644
>> index 000000000000..030d7e6ea484
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
>> @@ -0,0 +1,188 @@
>> +
>> +#include "drm_sched_tests.h"
>> +
>> +static int drm_sched_basic_init(struct kunit *test)
>> +{
>> +	test->priv = drm_mock_new_scheduler(test);
>> +
>> +	return 0;
>> +}
>> +
>> +static void drm_sched_basic_exit(struct kunit *test)
>> +{
>> +	struct drm_mock_scheduler *sched = test->priv;
>> +
>> +	drm_mock_scheduler_fini(sched);
>> +}
>> +
>> +static void drm_sched_basic_submit(struct kunit *test)
>> +{
>> +	struct drm_mock_scheduler *sched = test->priv;
>> +	struct drm_mock_sched_entity *entity;
>> +	struct drm_mock_sched_job *job;
>> +	unsigned int i;
>> +	bool done;
>> +
>> +	/*
>> +	 * Submit one job to the scheduler and verify that it gets
>> scheduled
>> +	 * and completed only when the mock hw backend processes it.
>> +	 */
>> +
>> +	entity = drm_mock_new_sched_entity(test,
>> +					
>> DRM_SCHED_PRIORITY_NORMAL,
>> +					   sched);
>> +	job = drm_mock_new_sched_job(test, entity);
>> +
>> +	drm_mock_sched_job_submit(job);
>> +
>> +	done = drm_mock_sched_job_wait_scheduled(job, HZ);
>> +	KUNIT_ASSERT_EQ(test, done, true);
>> +
>> +	done = drm_mock_sched_job_wait_finished(job, HZ / 2);
>> +	KUNIT_ASSERT_EQ(test, done, false);
>> +
>> +	i = drm_mock_sched_advance(sched, 1);
>> +	KUNIT_ASSERT_EQ(test, i, 1);
>> +
>> +	done = drm_mock_sched_job_wait_finished(job, HZ);
>> +	KUNIT_ASSERT_EQ(test, done, true);
>> +
>> +	drm_mock_sched_entity_free(entity);
>> +}
>> +
>> +struct drm_sched_basic_params {
>> +	const char *description;
>> +	unsigned int queue_depth;
>> +	unsigned int num_entities;
>> +	unsigned int job_us;
>> +	bool dep_chain;
>> +};
>> +
>> +static const struct drm_sched_basic_params drm_sched_basic_cases[] =
>> {
>> +	{
>> +		.description = "A queue of jobs in a single entity",
>> +		.queue_depth = 100,
>> +		.job_us = 1000,
>> +		.num_entities = 1,
>> +	},
>> +	{
>> +		.description = "A chain of dependent jobs across
>> multiple entities",
>> +		.queue_depth = 100,
>> +		.job_us = 1000,
>> +		.num_entities = 1,
>> +		.dep_chain = true,
>> +	},
>> +	{
>> +		.description = "Multiple independent job queues",
>> +		.queue_depth = 100,
>> +		.job_us = 1000,
>> +		.num_entities = 4,
>> +	},
>> +	{
>> +		.description = "Multiple inter-dependent job
>> queues",
>> +		.queue_depth = 100,
>> +		.job_us = 1000,
>> +		.num_entities = 4,
>> +		.dep_chain = true,
>> +	},
>> +};
>> +
>> +static void
>> +drm_sched_basic_desc(const struct drm_sched_basic_params *params,
>> char *desc)
>> +{
>> +	strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
>> +}
>> +
>> +KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases,
>> drm_sched_basic_desc);
>> +
>> +static void drm_sched_basic_test(struct kunit *test)
>> +{
>> +	const struct drm_sched_basic_params *params = test-
>>> param_value;
>> +	struct drm_mock_scheduler *sched = test->priv;
>> +	struct drm_mock_sched_job *job, *prev = NULL;
>> +	struct drm_mock_sched_entity **entity;
>> +	unsigned int i, cur_ent = 0;
>> +	bool done;
>> +
>> +	entity = kunit_kcalloc(test, params->num_entities,
>> sizeof(*entity),
>> +			       GFP_KERNEL);
>> +	KUNIT_ASSERT_NOT_NULL(test, entity);
>> +
>> +	for (i = 0; i < params->num_entities; i++)
>> +		entity[i] = drm_mock_new_sched_entity(test,
>> +						
>> DRM_SCHED_PRIORITY_NORMAL,
>> +						      sched);
>> +
>> +	for (i = 0; i < params->queue_depth; i++) {
>> +		job = drm_mock_new_sched_job(test,
>> entity[cur_ent++]);
>> +		cur_ent %= params->num_entities;
>> +		drm_mock_sched_job_set_duration_us(job, params-
>>> job_us);
>> +		if (params->dep_chain && prev)
>> +			drm_sched_job_add_dependency(&job->base,
>> +						
>> dma_fence_get(&prev->base.s_fence->finished));
>> +		drm_mock_sched_job_submit(job);
>> +		prev = job;
>> +	}
>> +
>> +	done = drm_mock_sched_job_wait_finished(job, HZ);
>> +	KUNIT_ASSERT_EQ(test, done, true);
>> +
>> +	for (i = 0; i < params->num_entities; i++)
>> +		drm_mock_sched_entity_free(entity[i]);
>> +}
>> +
>> +static void drm_sched_basic_entity_cleanup(struct kunit *test)
>> +{
>> +	struct drm_mock_sched_job *job, *mid, *prev = NULL;
>> +	struct drm_mock_scheduler *sched = test->priv;
>> +	struct drm_mock_sched_entity *entity[4];
>> +	const unsigned int qd = 100;
>> +	unsigned int i, cur_ent = 0;
>> +	bool done;
>> +
>> +	/*
>> +	 * Submit a queue of jobs across different entities with an
>> explicit
>> +	 * chain of dependencies between them and trigger entity
>> cleanup while
>> +	 * the queue is still being processed.
>> +	 */
>> +
>> +	for (i = 0; i < ARRAY_SIZE(entity); i++)
>> +		entity[i] = drm_mock_new_sched_entity(test,
>> +						
>> DRM_SCHED_PRIORITY_NORMAL,
>> +						      sched);
>> +
>> +	for (i = 0; i < qd; i++) {
>> +		job = drm_mock_new_sched_job(test,
>> entity[cur_ent++]);
>> +		cur_ent %= ARRAY_SIZE(entity);
>> +		drm_mock_sched_job_set_duration_us(job, 1000);
>> +		if (prev)
>> +			drm_sched_job_add_dependency(&job->base,
>> +						
>> dma_fence_get(&prev->base.s_fence->finished));
>> +		drm_mock_sched_job_submit(job);
>> +		if (i == qd / 2)
>> +			mid = job;
>> +		prev = job;
>> +	}
>> +
>> +	done = drm_mock_sched_job_wait_finished(mid, HZ);
>> +	KUNIT_ASSERT_EQ(test, done, true);
>> +
>> +	/* Exit with half of the queue still pending to be executed.
>> */
>> +	for (i = 0; i < ARRAY_SIZE(entity); i++)
>> +		drm_mock_sched_entity_free(entity[i]);}
>> +
>> +static struct kunit_case drm_sched_basic_tests[] = {
>> +	KUNIT_CASE(drm_sched_basic_submit),
>> +	KUNIT_CASE_PARAM(drm_sched_basic_test,
>> drm_sched_basic_gen_params),
>> +	KUNIT_CASE(drm_sched_basic_entity_cleanup),
>> +	{}
>> +};
>> +
>> +static struct kunit_suite drm_sched_basic = {
>> +	.name = "drm_sched_basic_tests",
>> +	.init = drm_sched_basic_init,
>> +	.exit = drm_sched_basic_exit,
>> +	.test_cases = drm_sched_basic_tests,
>> +};
>> +
>> +kunit_test_suite(drm_sched_basic);
>
Philipp Stanner Feb. 13, 2025, 2:27 p.m. UTC | #3
On Thu, 2025-02-13 at 14:18 +0000, Tvrtko Ursulin wrote:
> 
> On 13/02/2025 13:05, Philipp Stanner wrote:
> > I'm in principle OK with this series. We'll have to discuss a few
> > more
> > things, but from my POV you could send the next revision as a v1.
> > 
> > Some more input below..
> > 
> > On Fri, 2025-02-07 at 14:33 +0000, Tvrtko Ursulin wrote:
> > > Implement a mock scheduler backend and add some basic test to
> > > exercise the
> > > core scheduler code paths.
> > > 
> > > Mock backend (kind of like a very simple mock GPU) can either
> > > process
> > > jobs
> > > by tests manually advancing the "timeline" job at a time, or
> > > alternatively
> > > jobs can be configured with a time duration in which case they
> > > get
> > > completed asynchronously from the unit test code.
> > > 
> > > Core scheduler classes are subclassed to support this mock
> > > implementation.
> > > 
> > > The tests added are just a few simple submission patterns.
> > > 
> > > Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com>
> > > Suggested-by: Philipp Stanner <phasta@kernel.org>
> > > Cc: Christian König <christian.koenig@amd.com>
> > > Cc: Danilo Krummrich <dakr@kernel.org>
> > > Cc: Matthew Brost <matthew.brost@intel.com>
> > > Cc: Philipp Stanner <phasta@kernel.org>
> > > ---
> > >   drivers/gpu/drm/Kconfig.debug                 |  12 +
> > >   drivers/gpu/drm/scheduler/.kunitconfig        |  12 +
> > >   drivers/gpu/drm/scheduler/Makefile            |   1 +
> > >   drivers/gpu/drm/scheduler/tests/Makefile      |   4 +
> > >   .../gpu/drm/scheduler/tests/drm_mock_entity.c |  29 ++
> > >   .../gpu/drm/scheduler/tests/drm_mock_job.c    |   3 +
> > >   .../drm/scheduler/tests/drm_mock_scheduler.c  | 255
> > > ++++++++++++++++++
> > >   .../gpu/drm/scheduler/tests/drm_sched_tests.h | 128 +++++++++
> > >   .../scheduler/tests/drm_sched_tests_basic.c   | 188
> > > +++++++++++++
> > >   9 files changed, 632 insertions(+)
> > >   create mode 100644 drivers/gpu/drm/scheduler/.kunitconfig
> > >   create mode 100644 drivers/gpu/drm/scheduler/tests/Makefile
> > >   create mode 100644
> > > drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
> > >   create mode 100644
> > > drivers/gpu/drm/scheduler/tests/drm_mock_job.c
> > >   create mode 100644
> > > drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
> > >   create mode 100644
> > > drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
> > >   create mode 100644
> > > drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
> > 
> > I'm still not that convinced about the naming convention of those
> > files. They reside in drm/scheduler, but are called
> > drm_mock_{something}.c, almost reading as if they are about DRM in
> > general.
> > 
> > I think we should keep it consistent with the files in
> > drm/scheduler/
> > and just call those drm/scheduler/tests/sched_mock_job.c etc.
> 
> Dropping the redundant prefixes makes sense. How about then a step 
> further and make it drm/scheduler/tests/mock_job.c?
> 

Works for me

> 
> > > diff --git a/drivers/gpu/drm/Kconfig.debug
> > > b/drivers/gpu/drm/Kconfig.debug
> > > index a35d74171b7b..89f777f21e95 100644
> > > --- a/drivers/gpu/drm/Kconfig.debug
> > > +++ b/drivers/gpu/drm/Kconfig.debug
> > > @@ -88,5 +88,17 @@ config DRM_TTM_KUNIT_TEST
> > >   
> > >   	  If in doubt, say "N".
> > >   
> > > +config DRM_SCHED_KUNIT_TEST
> > > +	tristate "KUnit tests for the DRM scheduler" if
> > > !KUNIT_ALL_TESTS
> > > +	select DRM_SCHED
> > > +	depends on DRM && KUNIT
> > > +	default KUNIT_ALL_TESTS
> > > +	help
> > > +	  Choose this option to build unit tests for the DRM
> > > scheduler.
> > > +
> > > +	  Recommended for driver developers only.
> > > +
> > > +	  If in doubt, say "N".
> > > +
> > >   config DRM_EXPORT_FOR_TESTS
> > >   	bool
> > > diff --git a/drivers/gpu/drm/scheduler/.kunitconfig
> > > b/drivers/gpu/drm/scheduler/.kunitconfig
> > > new file mode 100644
> > > index 000000000000..cece53609fcf
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/scheduler/.kunitconfig
> > > @@ -0,0 +1,12 @@
> > > +CONFIG_KUNIT=y
> > > +CONFIG_DRM=y
> > > +CONFIG_DRM_SCHED_KUNIT_TEST=y
> > > +CONFIG_EXPERT=y
> > > +CONFIG_DEBUG_SPINLOCK=y
> > > +CONFIG_DEBUG_MUTEXES=y
> > > +CONFIG_DEBUG_ATOMIC_SLEEP=y
> > > +CONFIG_LOCK_DEBUGGING_SUPPORT=y
> > > +CONFIG_PROVE_LOCKING=y
> > > +CONFIG_LOCKDEP=y
> > > +CONFIG_DEBUG_LOCKDEP=y
> > > +CONFIG_DEBUG_LIST=y
> > > diff --git a/drivers/gpu/drm/scheduler/Makefile
> > > b/drivers/gpu/drm/scheduler/Makefile
> > > index 53863621829f..46dfdca0758a 100644
> > > --- a/drivers/gpu/drm/scheduler/Makefile
> > > +++ b/drivers/gpu/drm/scheduler/Makefile
> > > @@ -23,3 +23,4 @@
> > >   gpu-sched-y := sched_main.o sched_fence.o sched_entity.o
> > >   
> > >   obj-$(CONFIG_DRM_SCHED) += gpu-sched.o
> > > +obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += tests/
> > > diff --git a/drivers/gpu/drm/scheduler/tests/Makefile
> > > b/drivers/gpu/drm/scheduler/tests/Makefile
> > > new file mode 100644
> > > index 000000000000..d69eab6a2e9b
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/scheduler/tests/Makefile
> > > @@ -0,0 +1,4 @@
> > > +
> > > +obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += \
> > > +        drm_mock_scheduler.o \
> > > +        drm_sched_tests_basic.o
> > > diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
> > > b/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
> > > new file mode 100644
> > > index 000000000000..c9205f9ff524
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
> > > @@ -0,0 +1,29 @@
> > > +
> > > +#include "drm_sched_tests.h"
> > > +
> > > +struct drm_mock_sched_entity *
> > > +drm_mock_sched_entity_new(struct kunit *test,
> > > +			  enum drm_sched_priority priority,
> > > +			  struct drm_mock_scheduler *sched)
> > > +{
> > > +	struct drm_sched_mock_entity *entity;
> > > +	int ret;
> > > +
> > > +	entity = kunit_kmalloc(test, sizeof(*entity),
> > > GFP_KERNEL);
> > > +	KUNIT_ASSERT_NOT_NULL(test, entity);
> > > +
> > > +	ret = drm_sched_entity_init(&entity->base,
> > > +				    priority,
> > > +				    &sched->base, 1,
> > > +				    NULL);
> > > +	KUNIT_ASSERT_EQ(test, ret, 0);
> > > +
> > > +	entity->test = test;
> > > +
> > > +	return entity;
> > > +}
> > > +
> > > +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
> > > *entity)
> > > +{
> > > +	drm_sched_entity_fini(&entity->base);
> > > +}
> > > diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
> > > b/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
> > > new file mode 100644
> > > index 000000000000..d94568cf3da9
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
> > > @@ -0,0 +1,3 @@
> > > +
> > > +#include "drm_sched_tests.h"
> > > +
> > > diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
> > > b/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
> > > new file mode 100644
> > > index 000000000000..f65f7c0cfaf7
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
> > > @@ -0,0 +1,255 @@
> > > +
> > > +#include "drm_sched_tests.h"
> > > +
> > > +struct drm_mock_sched_entity *
> > > +drm_mock_new_sched_entity(struct kunit *test,
> > > +			  enum drm_sched_priority priority,
> > > +			  struct drm_mock_scheduler *sched)
> > > +{
> > > +	struct drm_mock_sched_entity *entity;
> > > +	struct drm_gpu_scheduler *drm_sched;
> > > +	int ret;
> > > +
> > > +	entity = kunit_kzalloc(test, sizeof(*entity),
> > > GFP_KERNEL);
> > > +	KUNIT_ASSERT_NOT_NULL(test, entity);
> > > +
> > > +	drm_sched = &sched->base;
> > > +	ret = drm_sched_entity_init(&entity->base,
> > > +				    priority,
> > > +				    &drm_sched, 1,
> > > +				    NULL);
> > > +	KUNIT_ASSERT_EQ(test, ret, 0);
> > > +
> > > +	entity->test = test;
> > > +
> > > +	return entity;
> > > +}
> > > +
> > > +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
> > > *entity)
> > > +{
> > > +	drm_sched_entity_destroy(&entity->base);
> > > +}
> > > +
> > > +static enum hrtimer_restart
> > > +drm_mock_sched_job_signal_timer(struct hrtimer *hrtimer)
> > > +{
> > > +	struct drm_mock_sched_job *upto =
> > > +		container_of(hrtimer, typeof(*upto), timer);
> > > +	struct drm_mock_scheduler *sched =
> > > +		drm_sched_to_mock_sched(upto->base.sched);
> > > +	struct drm_mock_sched_job *job, *next;
> > > +	ktime_t now = ktime_get();
> > > +	unsigned long flags;
> > > +	LIST_HEAD(signal);
> > > +
> > > +	spin_lock_irqsave(&sched->lock, flags);
> > > +	list_for_each_entry_safe(job, next, &sched->job_list,
> > > link)
> > > {
> > > +		if (!job->duration_us)
> > > +			break;
> > > +
> > > +		if (ktime_before(now, job->finish_at))
> > > +			break;
> > > +
> > > +		list_move_tail(&job->link, &signal);
> > > +		sched->hw_timeline.cur_seqno = job-
> > > >hw_fence.seqno;
> > > +	}
> > > +	spin_unlock_irqrestore(&sched->lock, flags);
> > > +
> > > +	list_for_each_entry(job, &signal, link) {
> > > +		dma_fence_signal(&job->hw_fence);
> > > +		dma_fence_put(&job->hw_fence);
> > > +	}
> > > +
> > > +	return HRTIMER_NORESTART;
> > > +}
> > > +
> > > +struct drm_mock_sched_job *
> > > +drm_mock_new_sched_job(struct kunit *test,
> > > +		       struct drm_mock_sched_entity *entity)
> > > +{
> > > +	struct drm_mock_sched_job *job;
> > > +	int ret;
> > > +
> > > +	job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL);
> > > +	KUNIT_ASSERT_NOT_NULL(test, job);
> > > +
> > > +	ret = drm_sched_job_init(&job->base,
> > > +				 &entity->base,
> > > +				 1,
> > > +				 NULL);
> > > +	KUNIT_ASSERT_EQ(test, ret, 0);
> > > +
> > > +	job->test = test;
> > > +
> > > +	spin_lock_init(&job->lock);
> > > +	INIT_LIST_HEAD(&job->link);
> > > +	hrtimer_init(&job->timer, CLOCK_MONOTONIC,
> > > HRTIMER_MODE_ABS);
> > > +	job->timer.function = drm_mock_sched_job_signal_timer;
> > > +
> > > +	return job;
> > > +}
> > > +
> > > +static const char *drm_mock_sched_hw_fence_driver_name(struct
> > > dma_fence *fence)
> > > +{
> > > +	return "drm_mock_sched";
> > > +}
> > > +
> > > +static const char *
> > > +drm_mock_sched_hw_fence_timeline_name(struct dma_fence *fence)
> > > +{
> > > +	struct drm_mock_sched_job *job =
> > > +		container_of(fence, typeof(*job), hw_fence);
> > > +
> > > +	return (const char *)job->base.sched->name;
> > > +}
> > > +static void drm_mock_sched_hw_fence_release(struct dma_fence
> > > *fence)
> > 
> > Regarding the function naming convention here everywhere,
> > "drm_mock_sched_{something}" I guess that's alright as far as I'm
> > concerned.
> > 
> > But just for some context, have you looked how other unit tests in
> > drivers/drm/ do their naming convention? Grep doesn't reveal any
> > drm_mock_* lines.
> > 
> > If there is a convention, would be great if drm/sched/ can be
> > congruent.
> 
> There is mention of mock object in kerneldoc for functions prefixed
> with 
> drm_kunit_helper. For me drm_mock_$something is better than drm_kunit
> helper_$something so I would rather keep my scheme.
> 
> I don't think we should have illusions this early that the "mock" 
> infrastructure can/will be shared. And as the ones I am adding live 
> locally in drm/scheduler/ I think it is fine as it is.

No no, I wasn't talking about sharing. Just about consistent style
throughout DRM, if possible

If you have a scheme it would be neat if you can write a few lines of
documentation at the top of a suitable file. Something like:

"The scheduler's unit tests components have the following naming
scheme:
- […] _mock[…] for foo
- […] _test[…] for bar

To make it a bit more obvious what a mock and what a test is. Not much
needed, just a few lines

P.

> 
> Regards,
> 
> Tvrtko
> 
> > > +{
> > > +	struct drm_mock_sched_job *job =
> > > +		container_of(fence, typeof(*job), hw_fence);
> > > +
> > > +	hrtimer_cancel(&job->timer);
> > > +
> > > +	/* Freed by the kunit framework */
> > > +}
> > > +
> > > +static const struct dma_fence_ops drm_mock_sched_hw_fence_ops =
> > > {
> > > +	.get_driver_name = drm_mock_sched_hw_fence_driver_name,
> > > +	.get_timeline_name =
> > > drm_mock_sched_hw_fence_timeline_name,
> > > +	.release = drm_mock_sched_hw_fence_release,
> > > +};
> > > +
> > > +static struct dma_fence *mock_sched_run_job(struct drm_sched_job
> > > *sched_job)
> > > +{
> > > +	struct drm_mock_scheduler *sched =
> > > +		drm_sched_to_mock_sched(sched_job->sched);
> > > +	struct drm_mock_sched_job *job =
> > > drm_sched_job_to_mock_job(sched_job);
> > > +
> > > +	dma_fence_init(&job->hw_fence,
> > > +		       &drm_mock_sched_hw_fence_ops,
> > > +		       &job->lock,
> > > +		       sched->hw_timeline.context,
> > > +		       atomic_inc_return(&sched-
> > > > hw_timeline.next_seqno));
> > > +
> > > +	dma_fence_get(&job->hw_fence); /* Reference for the
> > > job_list
> > > */
> > > +
> > > +	spin_lock_irq(&sched->lock);
> > > +	if (job->duration_us) {
> > > +		ktime_t prev_finish_at = 0;
> > > +
> > > +		if (!list_empty(&sched->job_list)) {
> > > +			struct drm_mock_sched_job *prev =
> > > +				list_last_entry(&sched-
> > > >job_list,
> > > typeof(*prev),
> > > +						link);
> > > +
> > > +			prev_finish_at = prev->finish_at;
> > > +		}
> > > +
> > > +		if (!prev_finish_at)
> > > +			prev_finish_at = ktime_get();
> > > +
> > > +		job->finish_at = ktime_add_us(prev_finish_at,
> > > job-
> > > > duration_us);
> > > +	}
> > > +	list_add_tail(&job->link, &sched->job_list);
> > > +	if (job->finish_at)
> > > +		hrtimer_start(&job->timer, job->finish_at,
> > > HRTIMER_MODE_ABS);
> > > +	spin_unlock_irq(&sched->lock);
> > > +
> > > +	return &job->hw_fence;
> > > +}
> > > +
> > > +static enum drm_gpu_sched_stat
> > > +mock_sched_timedout_job(struct drm_sched_job *sched_job)
> > > +{
> > > +	return DRM_GPU_SCHED_STAT_ENODEV;
> > > +}
> > > +
> > > +static void mock_sched_free_job(struct drm_sched_job *sched_job)
> > > +{
> > > +	drm_sched_job_cleanup(sched_job);
> > > +}
> > > +
> > > +static const struct drm_sched_backend_ops drm_mock_scheduler_ops
> > > = {
> > > +	.run_job = mock_sched_run_job,
> > > +	.timedout_job = mock_sched_timedout_job,
> > > +	.free_job = mock_sched_free_job
> > > +};
> > > +
> > > +struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit
> > > *test)
> > > +{
> > > +	struct drm_mock_scheduler *sched;
> > > +	int ret;
> > > +
> > > +	sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL);
> > > +	KUNIT_ASSERT_NOT_NULL(test, sched);
> > > +
> > > +	ret = drm_sched_init(&sched->base,
> > > +			     &drm_mock_scheduler_ops,
> > > +			     NULL, /* wq */
> > > +			     DRM_SCHED_PRIORITY_COUNT,
> > > +			     U32_MAX, /* max credits */
> > > +			     UINT_MAX, /* hang limit */
> > > +			     MAX_SCHEDULE_TIMEOUT, /* timeout */
> > > +			     NULL, /* timeout wq */
> > > +			     NULL, /* score */
> > > +			     "drm-mock-scheduler",
> > > +			     NULL /* dev */);
> > > +	KUNIT_ASSERT_EQ(test, ret, 0);
> > > +
> > > +	sched->test = test;
> > > +	sched->hw_timeline.context = dma_fence_context_alloc(1);
> > > +	atomic_set(&sched->hw_timeline.next_seqno, 0);
> > > +	INIT_LIST_HEAD(&sched->job_list);
> > > +	spin_lock_init(&sched->lock);
> > > +
> > > +	return sched;
> > > +}
> > > +
> > > +void drm_mock_scheduler_fini(struct drm_mock_scheduler *sched)
> > > +{
> > > +	struct drm_mock_sched_job *job, *next;
> > > +	unsigned long flags;
> > > +	LIST_HEAD(signal);
> > > +
> > > +	spin_lock_irqsave(&sched->lock, flags);
> > > +	list_for_each_entry_safe(job, next, &sched->job_list,
> > > link)
> > > +		list_move_tail(&job->link, &signal);
> > > +	spin_unlock_irqrestore(&sched->lock, flags);
> > > +
> > > +	list_for_each_entry(job, &signal, link) {
> > > +		hrtimer_cancel(&job->timer);
> > > +		dma_fence_put(&job->hw_fence);
> > > +	}
> > > +
> > > +	drm_sched_fini(&sched->base);
> > > +}
> > > +
> > > +unsigned int drm_mock_sched_advance(struct drm_mock_scheduler
> > > *sched,
> > > +				    unsigned int num)
> > > +{
> > > +	struct drm_mock_sched_job *job, *next;
> > > +	unsigned int found = 0;
> > > +	unsigned long flags;
> > > +	LIST_HEAD(signal);
> > > +
> > > +	spin_lock_irqsave(&sched->lock, flags);
> > > +	if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num <
> > > +			 sched->hw_timeline.cur_seqno))
> > > +		goto unlock;
> > > +	sched->hw_timeline.cur_seqno += num;
> > > +	list_for_each_entry_safe(job, next, &sched->job_list,
> > > link)
> > > {
> > > +		if (sched->hw_timeline.cur_seqno < job-
> > > > hw_fence.seqno)
> > > +			break;
> > > +
> > > +		list_move_tail(&job->link, &signal);
> > > +		found++;
> > > +	}
> > > +unlock:
> > > +	spin_unlock_irqrestore(&sched->lock, flags);
> > > +
> > > +	list_for_each_entry(job, &signal, link) {
> > > +		dma_fence_signal(&job->hw_fence);
> > > +		dma_fence_put(&job->hw_fence);
> > > +	}
> > > +
> > > +	return found;
> > > +}
> > > diff --git a/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
> > > b/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
> > > new file mode 100644
> > > index 000000000000..e22f7ead6d4e
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
> > > @@ -0,0 +1,128 @@
> > > +#ifndef _DRM_SCHED_TESTS_H_
> > > +#define _DRM_SCHED_TESTS_H_
> > > +
> > > +#include <kunit/test.h>
> > > +#include <linux/atomic.h>
> > > +#include <linux/dma-fence.h>
> > > +#include <linux/hrtimer.h>
> > > +#include <linux/ktime.h>
> > > +#include <linux/list.h>
> > > +#include <linux/atomic.h>
> > > +#include <linux/mutex.h>
> > > +#include <linux/types.h>
> > > +
> > > +#include <drm/gpu_scheduler.h>
> > > +
> > > +struct drm_mock_scheduler {
> > > +	struct drm_gpu_scheduler base;
> > > +
> > > +	struct kunit		*test;
> > > +
> > > +	spinlock_t		lock;
> > > +	struct list_head	job_list; /* Protected by the
> > > lock
> > > */
> > > +
> > > +	struct {
> > > +		u64		context;
> > > +		atomic_t	next_seqno;
> > > +		unsigned int	cur_seqno; /* Protected by the
> > > lock
> > > */
> > > +	} hw_timeline;
> > > +};
> > > +
> > > +struct drm_mock_sched_entity {
> > > +	struct drm_sched_entity base;
> > > +
> > > +	struct kunit		*test;
> > > +};
> > > +
> > > +struct drm_mock_sched_job {
> > > +	struct drm_sched_job	base;
> > > +
> > > +	struct list_head	link;
> > > +	struct hrtimer		timer;
> > > +
> > > +	unsigned int		duration_us;
> > > +	ktime_t			finish_at;
> > > +
> > > +	spinlock_t		lock;
> > > +	struct dma_fence	hw_fence;
> > > +
> > > +	struct kunit		*test;
> > > +};
> > > +
> > > +static inline struct drm_mock_scheduler *
> > > +drm_sched_to_mock_sched(struct drm_gpu_scheduler *sched)
> > > +{
> > > +	return container_of(sched, struct drm_mock_scheduler,
> > > base);
> > > +};
> > > +
> > > +static inline struct drm_mock_sched_entity *
> > > +drm_sched_entity_to_mock_entity(struct drm_sched_entity
> > > *sched_entity)
> > > +{
> > > +	return container_of(sched_entity, struct
> > > drm_mock_sched_entity, base);
> > > +};
> > > +
> > > +static inline struct drm_mock_sched_job *
> > > +drm_sched_job_to_mock_job(struct drm_sched_job *sched_job)
> > > +{
> > > +	return container_of(sched_job, struct
> > > drm_mock_sched_job,
> > > base);
> > > +};
> > > +
> > > +struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit
> > > *test);
> > > +void drm_mock_scheduler_fini(struct drm_mock_scheduler *sched);
> > > +unsigned int drm_mock_sched_advance(struct drm_mock_scheduler
> > > *sched,
> > > +				    unsigned int num);
> > > +
> > > +struct drm_mock_sched_entity *
> > > +drm_mock_new_sched_entity(struct kunit *test,
> > > +			  enum drm_sched_priority priority,
> > > +			  struct drm_mock_scheduler *sched);
> > > +void drm_mock_sched_entity_free(struct drm_mock_sched_entity
> > > *entity);
> > > +
> > > +struct drm_mock_sched_job *
> > > +drm_mock_new_sched_job(struct kunit *test,
> > > +		       struct drm_mock_sched_entity *entity);
> > > +
> > > +static inline void drm_mock_sched_job_submit(struct
> > > drm_mock_sched_job *job)
> > > +{
> > > +	drm_sched_job_arm(&job->base);
> > > +	drm_sched_entity_push_job(&job->base);
> > > +}
> > > +
> > > +static inline void
> > > +drm_mock_sched_job_set_duration_us(struct drm_mock_sched_job
> > > *job,
> > > +				   unsigned int duration_us)
> > > +{
> > > +	job->duration_us = duration_us;
> > > +}
> > > +
> > > +static inline bool
> > > +drm_mock_sched_job_is_finished(struct drm_mock_sched_job *job)
> > > +{
> > > +	return dma_fence_is_signaled(&job->base.s_fence-
> > > >finished);
> > > +}
> > > +
> > > +static inline bool
> > > +drm_mock_sched_job_wait_finished(struct drm_mock_sched_job *job,
> > > long timeout)
> > > +{
> > > +	long ret;
> > > +
> > > +	ret = dma_fence_wait_timeout(&job->base.s_fence-
> > > >finished,
> > > +				      false,
> > > +				      timeout);
> > > +
> > > +	return ret != 0;
> > > +}
> > > +
> > > +static inline long
> > > +drm_mock_sched_job_wait_scheduled(struct drm_mock_sched_job
> > > *job,
> > > long timeout)
> > > +{
> > > +	long ret;
> > > +
> > > +	ret = dma_fence_wait_timeout(&job->base.s_fence-
> > > >scheduled,
> > > +				      false,
> > > +				      timeout);
> > > +
> > > +	return ret != 0;
> > > +}
> > > +
> > > +#endif
> > > diff --git
> > > a/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
> > > b/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
> > > new file mode 100644
> > > index 000000000000..030d7e6ea484
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
> > > @@ -0,0 +1,188 @@
> > > +
> > > +#include "drm_sched_tests.h"
> > > +
> > > +static int drm_sched_basic_init(struct kunit *test)
> > > +{
> > > +	test->priv = drm_mock_new_scheduler(test);
> > > +
> > > +	return 0;
> > > +}
> > > +
> > > +static void drm_sched_basic_exit(struct kunit *test)
> > > +{
> > > +	struct drm_mock_scheduler *sched = test->priv;
> > > +
> > > +	drm_mock_scheduler_fini(sched);
> > > +}
> > > +
> > > +static void drm_sched_basic_submit(struct kunit *test)
> > > +{
> > > +	struct drm_mock_scheduler *sched = test->priv;
> > > +	struct drm_mock_sched_entity *entity;
> > > +	struct drm_mock_sched_job *job;
> > > +	unsigned int i;
> > > +	bool done;
> > > +
> > > +	/*
> > > +	 * Submit one job to the scheduler and verify that it
> > > gets
> > > scheduled
> > > +	 * and completed only when the mock hw backend processes
> > > it.
> > > +	 */
> > > +
> > > +	entity = drm_mock_new_sched_entity(test,
> > > +					
> > > DRM_SCHED_PRIORITY_NORMAL,
> > > +					   sched);
> > > +	job = drm_mock_new_sched_job(test, entity);
> > > +
> > > +	drm_mock_sched_job_submit(job);
> > > +
> > > +	done = drm_mock_sched_job_wait_scheduled(job, HZ);
> > > +	KUNIT_ASSERT_EQ(test, done, true);
> > > +
> > > +	done = drm_mock_sched_job_wait_finished(job, HZ / 2);
> > > +	KUNIT_ASSERT_EQ(test, done, false);
> > > +
> > > +	i = drm_mock_sched_advance(sched, 1);
> > > +	KUNIT_ASSERT_EQ(test, i, 1);
> > > +
> > > +	done = drm_mock_sched_job_wait_finished(job, HZ);
> > > +	KUNIT_ASSERT_EQ(test, done, true);
> > > +
> > > +	drm_mock_sched_entity_free(entity);
> > > +}
> > > +
> > > +struct drm_sched_basic_params {
> > > +	const char *description;
> > > +	unsigned int queue_depth;
> > > +	unsigned int num_entities;
> > > +	unsigned int job_us;
> > > +	bool dep_chain;
> > > +};
> > > +
> > > +static const struct drm_sched_basic_params
> > > drm_sched_basic_cases[] =
> > > {
> > > +	{
> > > +		.description = "A queue of jobs in a single
> > > entity",
> > > +		.queue_depth = 100,
> > > +		.job_us = 1000,
> > > +		.num_entities = 1,
> > > +	},
> > > +	{
> > > +		.description = "A chain of dependent jobs across
> > > multiple entities",
> > > +		.queue_depth = 100,
> > > +		.job_us = 1000,
> > > +		.num_entities = 1,
> > > +		.dep_chain = true,
> > > +	},
> > > +	{
> > > +		.description = "Multiple independent job
> > > queues",
> > > +		.queue_depth = 100,
> > > +		.job_us = 1000,
> > > +		.num_entities = 4,
> > > +	},
> > > +	{
> > > +		.description = "Multiple inter-dependent job
> > > queues",
> > > +		.queue_depth = 100,
> > > +		.job_us = 1000,
> > > +		.num_entities = 4,
> > > +		.dep_chain = true,
> > > +	},
> > > +};
> > > +
> > > +static void
> > > +drm_sched_basic_desc(const struct drm_sched_basic_params
> > > *params,
> > > char *desc)
> > > +{
> > > +	strscpy(desc, params->description,
> > > KUNIT_PARAM_DESC_SIZE);
> > > +}
> > > +
> > > +KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases,
> > > drm_sched_basic_desc);
> > > +
> > > +static void drm_sched_basic_test(struct kunit *test)
> > > +{
> > > +	const struct drm_sched_basic_params *params = test-
> > > > param_value;
> > > +	struct drm_mock_scheduler *sched = test->priv;
> > > +	struct drm_mock_sched_job *job, *prev = NULL;
> > > +	struct drm_mock_sched_entity **entity;
> > > +	unsigned int i, cur_ent = 0;
> > > +	bool done;
> > > +
> > > +	entity = kunit_kcalloc(test, params->num_entities,
> > > sizeof(*entity),
> > > +			       GFP_KERNEL);
> > > +	KUNIT_ASSERT_NOT_NULL(test, entity);
> > > +
> > > +	for (i = 0; i < params->num_entities; i++)
> > > +		entity[i] = drm_mock_new_sched_entity(test,
> > > +						
> > > DRM_SCHED_PRIORITY_NORMAL,
> > > +						      sched);
> > > +
> > > +	for (i = 0; i < params->queue_depth; i++) {
> > > +		job = drm_mock_new_sched_job(test,
> > > entity[cur_ent++]);
> > > +		cur_ent %= params->num_entities;
> > > +		drm_mock_sched_job_set_duration_us(job, params-
> > > > job_us);
> > > +		if (params->dep_chain && prev)
> > > +			drm_sched_job_add_dependency(&job->base,
> > > +						
> > > dma_fence_get(&prev->base.s_fence->finished));
> > > +		drm_mock_sched_job_submit(job);
> > > +		prev = job;
> > > +	}
> > > +
> > > +	done = drm_mock_sched_job_wait_finished(job, HZ);
> > > +	KUNIT_ASSERT_EQ(test, done, true);
> > > +
> > > +	for (i = 0; i < params->num_entities; i++)
> > > +		drm_mock_sched_entity_free(entity[i]);
> > > +}
> > > +
> > > +static void drm_sched_basic_entity_cleanup(struct kunit *test)
> > > +{
> > > +	struct drm_mock_sched_job *job, *mid, *prev = NULL;
> > > +	struct drm_mock_scheduler *sched = test->priv;
> > > +	struct drm_mock_sched_entity *entity[4];
> > > +	const unsigned int qd = 100;
> > > +	unsigned int i, cur_ent = 0;
> > > +	bool done;
> > > +
> > > +	/*
> > > +	 * Submit a queue of jobs across different entities with
> > > an
> > > explicit
> > > +	 * chain of dependencies between them and trigger entity
> > > cleanup while
> > > +	 * the queue is still being processed.
> > > +	 */
> > > +
> > > +	for (i = 0; i < ARRAY_SIZE(entity); i++)
> > > +		entity[i] = drm_mock_new_sched_entity(test,
> > > +						
> > > DRM_SCHED_PRIORITY_NORMAL,
> > > +						      sched);
> > > +
> > > +	for (i = 0; i < qd; i++) {
> > > +		job = drm_mock_new_sched_job(test,
> > > entity[cur_ent++]);
> > > +		cur_ent %= ARRAY_SIZE(entity);
> > > +		drm_mock_sched_job_set_duration_us(job, 1000);
> > > +		if (prev)
> > > +			drm_sched_job_add_dependency(&job->base,
> > > +						
> > > dma_fence_get(&prev->base.s_fence->finished));
> > > +		drm_mock_sched_job_submit(job);
> > > +		if (i == qd / 2)
> > > +			mid = job;
> > > +		prev = job;
> > > +	}
> > > +
> > > +	done = drm_mock_sched_job_wait_finished(mid, HZ);
> > > +	KUNIT_ASSERT_EQ(test, done, true);
> > > +
> > > +	/* Exit with half of the queue still pending to be
> > > executed.
> > > */
> > > +	for (i = 0; i < ARRAY_SIZE(entity); i++)
> > > +		drm_mock_sched_entity_free(entity[i]);}
> > > +
> > > +static struct kunit_case drm_sched_basic_tests[] = {
> > > +	KUNIT_CASE(drm_sched_basic_submit),
> > > +	KUNIT_CASE_PARAM(drm_sched_basic_test,
> > > drm_sched_basic_gen_params),
> > > +	KUNIT_CASE(drm_sched_basic_entity_cleanup),
> > > +	{}
> > > +};
> > > +
> > > +static struct kunit_suite drm_sched_basic = {
> > > +	.name = "drm_sched_basic_tests",
> > > +	.init = drm_sched_basic_init,
> > > +	.exit = drm_sched_basic_exit,
> > > +	.test_cases = drm_sched_basic_tests,
> > > +};
> > > +
> > > +kunit_test_suite(drm_sched_basic);
> > 
>
diff mbox series

Patch

diff --git a/drivers/gpu/drm/Kconfig.debug b/drivers/gpu/drm/Kconfig.debug
index a35d74171b7b..89f777f21e95 100644
--- a/drivers/gpu/drm/Kconfig.debug
+++ b/drivers/gpu/drm/Kconfig.debug
@@ -88,5 +88,17 @@  config DRM_TTM_KUNIT_TEST
 
 	  If in doubt, say "N".
 
+config DRM_SCHED_KUNIT_TEST
+	tristate "KUnit tests for the DRM scheduler" if !KUNIT_ALL_TESTS
+	select DRM_SCHED
+	depends on DRM && KUNIT
+	default KUNIT_ALL_TESTS
+	help
+	  Choose this option to build unit tests for the DRM scheduler.
+
+	  Recommended for driver developers only.
+
+	  If in doubt, say "N".
+
 config DRM_EXPORT_FOR_TESTS
 	bool
diff --git a/drivers/gpu/drm/scheduler/.kunitconfig b/drivers/gpu/drm/scheduler/.kunitconfig
new file mode 100644
index 000000000000..cece53609fcf
--- /dev/null
+++ b/drivers/gpu/drm/scheduler/.kunitconfig
@@ -0,0 +1,12 @@ 
+CONFIG_KUNIT=y
+CONFIG_DRM=y
+CONFIG_DRM_SCHED_KUNIT_TEST=y
+CONFIG_EXPERT=y
+CONFIG_DEBUG_SPINLOCK=y
+CONFIG_DEBUG_MUTEXES=y
+CONFIG_DEBUG_ATOMIC_SLEEP=y
+CONFIG_LOCK_DEBUGGING_SUPPORT=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_LOCKDEP=y
+CONFIG_DEBUG_LOCKDEP=y
+CONFIG_DEBUG_LIST=y
diff --git a/drivers/gpu/drm/scheduler/Makefile b/drivers/gpu/drm/scheduler/Makefile
index 53863621829f..46dfdca0758a 100644
--- a/drivers/gpu/drm/scheduler/Makefile
+++ b/drivers/gpu/drm/scheduler/Makefile
@@ -23,3 +23,4 @@ 
 gpu-sched-y := sched_main.o sched_fence.o sched_entity.o
 
 obj-$(CONFIG_DRM_SCHED) += gpu-sched.o
+obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += tests/
diff --git a/drivers/gpu/drm/scheduler/tests/Makefile b/drivers/gpu/drm/scheduler/tests/Makefile
new file mode 100644
index 000000000000..d69eab6a2e9b
--- /dev/null
+++ b/drivers/gpu/drm/scheduler/tests/Makefile
@@ -0,0 +1,4 @@ 
+
+obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += \
+        drm_mock_scheduler.o \
+        drm_sched_tests_basic.o
diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c b/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
new file mode 100644
index 000000000000..c9205f9ff524
--- /dev/null
+++ b/drivers/gpu/drm/scheduler/tests/drm_mock_entity.c
@@ -0,0 +1,29 @@ 
+
+#include "drm_sched_tests.h"
+
+struct drm_mock_sched_entity *
+drm_mock_sched_entity_new(struct kunit *test,
+			  enum drm_sched_priority priority,
+			  struct drm_mock_scheduler *sched)
+{
+	struct drm_sched_mock_entity *entity;
+	int ret;
+
+	entity = kunit_kmalloc(test, sizeof(*entity), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, entity);
+
+	ret = drm_sched_entity_init(&entity->base,
+				    priority,
+				    &sched->base, 1,
+				    NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	entity->test = test;
+
+	return entity;
+}
+
+void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity)
+{
+	drm_sched_entity_fini(&entity->base);
+}
diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_job.c b/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
new file mode 100644
index 000000000000..d94568cf3da9
--- /dev/null
+++ b/drivers/gpu/drm/scheduler/tests/drm_mock_job.c
@@ -0,0 +1,3 @@ 
+
+#include "drm_sched_tests.h"
+
diff --git a/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c b/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
new file mode 100644
index 000000000000..f65f7c0cfaf7
--- /dev/null
+++ b/drivers/gpu/drm/scheduler/tests/drm_mock_scheduler.c
@@ -0,0 +1,255 @@ 
+
+#include "drm_sched_tests.h"
+
+struct drm_mock_sched_entity *
+drm_mock_new_sched_entity(struct kunit *test,
+			  enum drm_sched_priority priority,
+			  struct drm_mock_scheduler *sched)
+{
+	struct drm_mock_sched_entity *entity;
+	struct drm_gpu_scheduler *drm_sched;
+	int ret;
+
+	entity = kunit_kzalloc(test, sizeof(*entity), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, entity);
+
+	drm_sched = &sched->base;
+	ret = drm_sched_entity_init(&entity->base,
+				    priority,
+				    &drm_sched, 1,
+				    NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	entity->test = test;
+
+	return entity;
+}
+
+void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity)
+{
+	drm_sched_entity_destroy(&entity->base);
+}
+
+static enum hrtimer_restart
+drm_mock_sched_job_signal_timer(struct hrtimer *hrtimer)
+{
+	struct drm_mock_sched_job *upto =
+		container_of(hrtimer, typeof(*upto), timer);
+	struct drm_mock_scheduler *sched =
+		drm_sched_to_mock_sched(upto->base.sched);
+	struct drm_mock_sched_job *job, *next;
+	ktime_t now = ktime_get();
+	unsigned long flags;
+	LIST_HEAD(signal);
+
+	spin_lock_irqsave(&sched->lock, flags);
+	list_for_each_entry_safe(job, next, &sched->job_list, link) {
+		if (!job->duration_us)
+			break;
+
+		if (ktime_before(now, job->finish_at))
+			break;
+
+		list_move_tail(&job->link, &signal);
+		sched->hw_timeline.cur_seqno = job->hw_fence.seqno;
+	}
+	spin_unlock_irqrestore(&sched->lock, flags);
+
+	list_for_each_entry(job, &signal, link) {
+		dma_fence_signal(&job->hw_fence);
+		dma_fence_put(&job->hw_fence);
+	}
+
+	return HRTIMER_NORESTART;
+}
+
+struct drm_mock_sched_job *
+drm_mock_new_sched_job(struct kunit *test,
+		       struct drm_mock_sched_entity *entity)
+{
+	struct drm_mock_sched_job *job;
+	int ret;
+
+	job = kunit_kzalloc(test, sizeof(*job), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, job);
+
+	ret = drm_sched_job_init(&job->base,
+				 &entity->base,
+				 1,
+				 NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	job->test = test;
+
+	spin_lock_init(&job->lock);
+	INIT_LIST_HEAD(&job->link);
+	hrtimer_init(&job->timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
+	job->timer.function = drm_mock_sched_job_signal_timer;
+
+	return job;
+}
+
+static const char *drm_mock_sched_hw_fence_driver_name(struct dma_fence *fence)
+{
+	return "drm_mock_sched";
+}
+
+static const char *
+drm_mock_sched_hw_fence_timeline_name(struct dma_fence *fence)
+{
+	struct drm_mock_sched_job *job =
+		container_of(fence, typeof(*job), hw_fence);
+
+	return (const char *)job->base.sched->name;
+}
+static void drm_mock_sched_hw_fence_release(struct dma_fence *fence)
+{
+	struct drm_mock_sched_job *job =
+		container_of(fence, typeof(*job), hw_fence);
+
+	hrtimer_cancel(&job->timer);
+
+	/* Freed by the kunit framework */
+}
+
+static const struct dma_fence_ops drm_mock_sched_hw_fence_ops = {
+	.get_driver_name = drm_mock_sched_hw_fence_driver_name,
+	.get_timeline_name = drm_mock_sched_hw_fence_timeline_name,
+	.release = drm_mock_sched_hw_fence_release,
+};
+
+static struct dma_fence *mock_sched_run_job(struct drm_sched_job *sched_job)
+{
+	struct drm_mock_scheduler *sched =
+		drm_sched_to_mock_sched(sched_job->sched);
+	struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job);
+
+	dma_fence_init(&job->hw_fence,
+		       &drm_mock_sched_hw_fence_ops,
+		       &job->lock,
+		       sched->hw_timeline.context,
+		       atomic_inc_return(&sched->hw_timeline.next_seqno));
+
+	dma_fence_get(&job->hw_fence); /* Reference for the job_list */
+
+	spin_lock_irq(&sched->lock);
+	if (job->duration_us) {
+		ktime_t prev_finish_at = 0;
+
+		if (!list_empty(&sched->job_list)) {
+			struct drm_mock_sched_job *prev =
+				list_last_entry(&sched->job_list, typeof(*prev),
+						link);
+
+			prev_finish_at = prev->finish_at;
+		}
+
+		if (!prev_finish_at)
+			prev_finish_at = ktime_get();
+
+		job->finish_at = ktime_add_us(prev_finish_at, job->duration_us);
+	}
+	list_add_tail(&job->link, &sched->job_list);
+	if (job->finish_at)
+		hrtimer_start(&job->timer, job->finish_at, HRTIMER_MODE_ABS);
+	spin_unlock_irq(&sched->lock);
+
+	return &job->hw_fence;
+}
+
+static enum drm_gpu_sched_stat
+mock_sched_timedout_job(struct drm_sched_job *sched_job)
+{
+	return DRM_GPU_SCHED_STAT_ENODEV;
+}
+
+static void mock_sched_free_job(struct drm_sched_job *sched_job)
+{
+	drm_sched_job_cleanup(sched_job);
+}
+
+static const struct drm_sched_backend_ops drm_mock_scheduler_ops = {
+	.run_job = mock_sched_run_job,
+	.timedout_job = mock_sched_timedout_job,
+	.free_job = mock_sched_free_job
+};
+
+struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit *test)
+{
+	struct drm_mock_scheduler *sched;
+	int ret;
+
+	sched = kunit_kzalloc(test, sizeof(*sched), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, sched);
+
+	ret = drm_sched_init(&sched->base,
+			     &drm_mock_scheduler_ops,
+			     NULL, /* wq */
+			     DRM_SCHED_PRIORITY_COUNT,
+			     U32_MAX, /* max credits */
+			     UINT_MAX, /* hang limit */
+			     MAX_SCHEDULE_TIMEOUT, /* timeout */
+			     NULL, /* timeout wq */
+			     NULL, /* score */
+			     "drm-mock-scheduler",
+			     NULL /* dev */);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	sched->test = test;
+	sched->hw_timeline.context = dma_fence_context_alloc(1);
+	atomic_set(&sched->hw_timeline.next_seqno, 0);
+	INIT_LIST_HEAD(&sched->job_list);
+	spin_lock_init(&sched->lock);
+
+	return sched;
+}
+
+void drm_mock_scheduler_fini(struct drm_mock_scheduler *sched)
+{
+	struct drm_mock_sched_job *job, *next;
+	unsigned long flags;
+	LIST_HEAD(signal);
+
+	spin_lock_irqsave(&sched->lock, flags);
+	list_for_each_entry_safe(job, next, &sched->job_list, link)
+		list_move_tail(&job->link, &signal);
+	spin_unlock_irqrestore(&sched->lock, flags);
+
+	list_for_each_entry(job, &signal, link) {
+		hrtimer_cancel(&job->timer);
+		dma_fence_put(&job->hw_fence);
+	}
+
+	drm_sched_fini(&sched->base);
+}
+
+unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched,
+				    unsigned int num)
+{
+	struct drm_mock_sched_job *job, *next;
+	unsigned int found = 0;
+	unsigned long flags;
+	LIST_HEAD(signal);
+
+	spin_lock_irqsave(&sched->lock, flags);
+	if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num <
+			 sched->hw_timeline.cur_seqno))
+		goto unlock;
+	sched->hw_timeline.cur_seqno += num;
+	list_for_each_entry_safe(job, next, &sched->job_list, link) {
+		if (sched->hw_timeline.cur_seqno < job->hw_fence.seqno)
+			break;
+
+		list_move_tail(&job->link, &signal);
+		found++;
+	}
+unlock:
+	spin_unlock_irqrestore(&sched->lock, flags);
+
+	list_for_each_entry(job, &signal, link) {
+		dma_fence_signal(&job->hw_fence);
+		dma_fence_put(&job->hw_fence);
+	}
+
+	return found;
+}
diff --git a/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h b/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
new file mode 100644
index 000000000000..e22f7ead6d4e
--- /dev/null
+++ b/drivers/gpu/drm/scheduler/tests/drm_sched_tests.h
@@ -0,0 +1,128 @@ 
+#ifndef _DRM_SCHED_TESTS_H_
+#define _DRM_SCHED_TESTS_H_
+
+#include <kunit/test.h>
+#include <linux/atomic.h>
+#include <linux/dma-fence.h>
+#include <linux/hrtimer.h>
+#include <linux/ktime.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+#include <drm/gpu_scheduler.h>
+
+struct drm_mock_scheduler {
+	struct drm_gpu_scheduler base;
+
+	struct kunit		*test;
+
+	spinlock_t		lock;
+	struct list_head	job_list; /* Protected by the lock */
+
+	struct {
+		u64		context;
+		atomic_t	next_seqno;
+		unsigned int	cur_seqno; /* Protected by the lock */
+	} hw_timeline;
+};
+
+struct drm_mock_sched_entity {
+	struct drm_sched_entity base;
+
+	struct kunit		*test;
+};
+
+struct drm_mock_sched_job {
+	struct drm_sched_job	base;
+
+	struct list_head	link;
+	struct hrtimer		timer;
+
+	unsigned int		duration_us;
+	ktime_t			finish_at;
+
+	spinlock_t		lock;
+	struct dma_fence	hw_fence;
+
+	struct kunit		*test;
+};
+
+static inline struct drm_mock_scheduler *
+drm_sched_to_mock_sched(struct drm_gpu_scheduler *sched)
+{
+	return container_of(sched, struct drm_mock_scheduler, base);
+};
+
+static inline struct drm_mock_sched_entity *
+drm_sched_entity_to_mock_entity(struct drm_sched_entity *sched_entity)
+{
+	return container_of(sched_entity, struct drm_mock_sched_entity, base);
+};
+
+static inline struct drm_mock_sched_job *
+drm_sched_job_to_mock_job(struct drm_sched_job *sched_job)
+{
+	return container_of(sched_job, struct drm_mock_sched_job, base);
+};
+
+struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit *test);
+void drm_mock_scheduler_fini(struct drm_mock_scheduler *sched);
+unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched,
+				    unsigned int num);
+
+struct drm_mock_sched_entity *
+drm_mock_new_sched_entity(struct kunit *test,
+			  enum drm_sched_priority priority,
+			  struct drm_mock_scheduler *sched);
+void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity);
+
+struct drm_mock_sched_job *
+drm_mock_new_sched_job(struct kunit *test,
+		       struct drm_mock_sched_entity *entity);
+
+static inline void drm_mock_sched_job_submit(struct drm_mock_sched_job *job)
+{
+	drm_sched_job_arm(&job->base);
+	drm_sched_entity_push_job(&job->base);
+}
+
+static inline void
+drm_mock_sched_job_set_duration_us(struct drm_mock_sched_job *job,
+				   unsigned int duration_us)
+{
+	job->duration_us = duration_us;
+}
+
+static inline bool
+drm_mock_sched_job_is_finished(struct drm_mock_sched_job *job)
+{
+	return dma_fence_is_signaled(&job->base.s_fence->finished);
+}
+
+static inline bool
+drm_mock_sched_job_wait_finished(struct drm_mock_sched_job *job, long timeout)
+{
+	long ret;
+
+	ret = dma_fence_wait_timeout(&job->base.s_fence->finished,
+				      false,
+				      timeout);
+
+	return ret != 0;
+}
+
+static inline long
+drm_mock_sched_job_wait_scheduled(struct drm_mock_sched_job *job, long timeout)
+{
+	long ret;
+
+	ret = dma_fence_wait_timeout(&job->base.s_fence->scheduled,
+				      false,
+				      timeout);
+
+	return ret != 0;
+}
+
+#endif
diff --git a/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c b/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
new file mode 100644
index 000000000000..030d7e6ea484
--- /dev/null
+++ b/drivers/gpu/drm/scheduler/tests/drm_sched_tests_basic.c
@@ -0,0 +1,188 @@ 
+
+#include "drm_sched_tests.h"
+
+static int drm_sched_basic_init(struct kunit *test)
+{
+	test->priv = drm_mock_new_scheduler(test);
+
+	return 0;
+}
+
+static void drm_sched_basic_exit(struct kunit *test)
+{
+	struct drm_mock_scheduler *sched = test->priv;
+
+	drm_mock_scheduler_fini(sched);
+}
+
+static void drm_sched_basic_submit(struct kunit *test)
+{
+	struct drm_mock_scheduler *sched = test->priv;
+	struct drm_mock_sched_entity *entity;
+	struct drm_mock_sched_job *job;
+	unsigned int i;
+	bool done;
+
+	/*
+	 * Submit one job to the scheduler and verify that it gets scheduled
+	 * and completed only when the mock hw backend processes it.
+	 */
+
+	entity = drm_mock_new_sched_entity(test,
+					   DRM_SCHED_PRIORITY_NORMAL,
+					   sched);
+	job = drm_mock_new_sched_job(test, entity);
+
+	drm_mock_sched_job_submit(job);
+
+	done = drm_mock_sched_job_wait_scheduled(job, HZ);
+	KUNIT_ASSERT_EQ(test, done, true);
+
+	done = drm_mock_sched_job_wait_finished(job, HZ / 2);
+	KUNIT_ASSERT_EQ(test, done, false);
+
+	i = drm_mock_sched_advance(sched, 1);
+	KUNIT_ASSERT_EQ(test, i, 1);
+
+	done = drm_mock_sched_job_wait_finished(job, HZ);
+	KUNIT_ASSERT_EQ(test, done, true);
+
+	drm_mock_sched_entity_free(entity);
+}
+
+struct drm_sched_basic_params {
+	const char *description;
+	unsigned int queue_depth;
+	unsigned int num_entities;
+	unsigned int job_us;
+	bool dep_chain;
+};
+
+static const struct drm_sched_basic_params drm_sched_basic_cases[] = {
+	{
+		.description = "A queue of jobs in a single entity",
+		.queue_depth = 100,
+		.job_us = 1000,
+		.num_entities = 1,
+	},
+	{
+		.description = "A chain of dependent jobs across multiple entities",
+		.queue_depth = 100,
+		.job_us = 1000,
+		.num_entities = 1,
+		.dep_chain = true,
+	},
+	{
+		.description = "Multiple independent job queues",
+		.queue_depth = 100,
+		.job_us = 1000,
+		.num_entities = 4,
+	},
+	{
+		.description = "Multiple inter-dependent job queues",
+		.queue_depth = 100,
+		.job_us = 1000,
+		.num_entities = 4,
+		.dep_chain = true,
+	},
+};
+
+static void
+drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc)
+{
+	strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc);
+
+static void drm_sched_basic_test(struct kunit *test)
+{
+	const struct drm_sched_basic_params *params = test->param_value;
+	struct drm_mock_scheduler *sched = test->priv;
+	struct drm_mock_sched_job *job, *prev = NULL;
+	struct drm_mock_sched_entity **entity;
+	unsigned int i, cur_ent = 0;
+	bool done;
+
+	entity = kunit_kcalloc(test, params->num_entities, sizeof(*entity),
+			       GFP_KERNEL);
+	KUNIT_ASSERT_NOT_NULL(test, entity);
+
+	for (i = 0; i < params->num_entities; i++)
+		entity[i] = drm_mock_new_sched_entity(test,
+						      DRM_SCHED_PRIORITY_NORMAL,
+						      sched);
+
+	for (i = 0; i < params->queue_depth; i++) {
+		job = drm_mock_new_sched_job(test, entity[cur_ent++]);
+		cur_ent %= params->num_entities;
+		drm_mock_sched_job_set_duration_us(job, params->job_us);
+		if (params->dep_chain && prev)
+			drm_sched_job_add_dependency(&job->base,
+						     dma_fence_get(&prev->base.s_fence->finished));
+		drm_mock_sched_job_submit(job);
+		prev = job;
+	}
+
+	done = drm_mock_sched_job_wait_finished(job, HZ);
+	KUNIT_ASSERT_EQ(test, done, true);
+
+	for (i = 0; i < params->num_entities; i++)
+		drm_mock_sched_entity_free(entity[i]);
+}
+
+static void drm_sched_basic_entity_cleanup(struct kunit *test)
+{
+	struct drm_mock_sched_job *job, *mid, *prev = NULL;
+	struct drm_mock_scheduler *sched = test->priv;
+	struct drm_mock_sched_entity *entity[4];
+	const unsigned int qd = 100;
+	unsigned int i, cur_ent = 0;
+	bool done;
+
+	/*
+	 * Submit a queue of jobs across different entities with an explicit
+	 * chain of dependencies between them and trigger entity cleanup while
+	 * the queue is still being processed.
+	 */
+
+	for (i = 0; i < ARRAY_SIZE(entity); i++)
+		entity[i] = drm_mock_new_sched_entity(test,
+						      DRM_SCHED_PRIORITY_NORMAL,
+						      sched);
+
+	for (i = 0; i < qd; i++) {
+		job = drm_mock_new_sched_job(test, entity[cur_ent++]);
+		cur_ent %= ARRAY_SIZE(entity);
+		drm_mock_sched_job_set_duration_us(job, 1000);
+		if (prev)
+			drm_sched_job_add_dependency(&job->base,
+						     dma_fence_get(&prev->base.s_fence->finished));
+		drm_mock_sched_job_submit(job);
+		if (i == qd / 2)
+			mid = job;
+		prev = job;
+	}
+
+	done = drm_mock_sched_job_wait_finished(mid, HZ);
+	KUNIT_ASSERT_EQ(test, done, true);
+
+	/* Exit with half of the queue still pending to be executed. */
+	for (i = 0; i < ARRAY_SIZE(entity); i++)
+		drm_mock_sched_entity_free(entity[i]);}
+
+static struct kunit_case drm_sched_basic_tests[] = {
+	KUNIT_CASE(drm_sched_basic_submit),
+	KUNIT_CASE_PARAM(drm_sched_basic_test, drm_sched_basic_gen_params),
+	KUNIT_CASE(drm_sched_basic_entity_cleanup),
+	{}
+};
+
+static struct kunit_suite drm_sched_basic = {
+	.name = "drm_sched_basic_tests",
+	.init = drm_sched_basic_init,
+	.exit = drm_sched_basic_exit,
+	.test_cases = drm_sched_basic_tests,
+};
+
+kunit_test_suite(drm_sched_basic);