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