[PATCH v2 2/5] drm/scheduler: Add scheduler unit testing infrastructure and some basic tests
Tvrtko Ursulin
tvrtko.ursulin at igalia.com
Mon Mar 3 15:53:33 UTC 2025
On 03/03/2025 11:38, Philipp Stanner wrote:
> Sry for being late to the party, am a bit busy with defusing my own
> dynamite <.<
>
> On Fri, 2025-02-21 at 12:09 +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 at igalia.com>
>> Suggested-by: Philipp Stanner <phasta at kernel.org>
>> Cc: Christian König <christian.koenig at amd.com>
>> Cc: Danilo Krummrich <dakr at kernel.org>
>> Cc: Matthew Brost <matthew.brost at intel.com>
>> Cc: Philipp Stanner <phasta at kernel.org>
>> ---
>> drivers/gpu/drm/Kconfig.debug | 12 +
>> drivers/gpu/drm/scheduler/.kunitconfig | 12 +
>> drivers/gpu/drm/scheduler/Makefile | 2 +
>> drivers/gpu/drm/scheduler/tests/Makefile | 6 +
>> .../gpu/drm/scheduler/tests/mock_scheduler.c | 317
>> ++++++++++++++++++
>> drivers/gpu/drm/scheduler/tests/sched_tests.h | 218 ++++++++++++
>> drivers/gpu/drm/scheduler/tests/tests_basic.c | 197 +++++++++++
>> 7 files changed, 764 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/mock_scheduler.c
>> create mode 100644 drivers/gpu/drm/scheduler/tests/sched_tests.h
>> create mode 100644 drivers/gpu/drm/scheduler/tests/tests_basic.c
>>
>> diff --git a/drivers/gpu/drm/Kconfig.debug
>> b/drivers/gpu/drm/Kconfig.debug
>> index 601d7e07d421..6fd4c5669400 100644
>> --- a/drivers/gpu/drm/Kconfig.debug
>> +++ b/drivers/gpu/drm/Kconfig.debug
>> @@ -99,5 +99,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..6e13e4c63e9d 100644
>> --- a/drivers/gpu/drm/scheduler/Makefile
>> +++ b/drivers/gpu/drm/scheduler/Makefile
>> @@ -23,3 +23,5 @@
>> 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..51d275a18cf4
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/Makefile
>> @@ -0,0 +1,6 @@
>> +
>> +drm-sched-tests-y := \
>> + mock_scheduler.o \
>> + tests_basic.o
>> +
>> +obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += drm-sched-tests.o
>> diff --git a/drivers/gpu/drm/scheduler/tests/mock_scheduler.c
>> b/drivers/gpu/drm/scheduler/tests/mock_scheduler.c
>> new file mode 100644
>> index 000000000000..d73a9f0337da
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/mock_scheduler.c
>> @@ -0,0 +1,317 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +#include "sched_tests.h"
>> +
>> +/*
>> + * Here we implement the mock "GPU" (or the scheduler backend) which
>> is used by
>> + * the DRM scheduler unit tests in order to exercise the core
>> functionality.
>> + *
>> + * Test cases are implemented in a separate file.
>> + */
>> +
>> +/**
>> + * drm_mock_new_sched_entity - Create a new mock scheduler entity
>> + *
>> + * @test: KUnit test owning the entity
>> + * @priority: Scheduling priority
>> + * @sched: Mock scheduler on which the entity can be scheduled
>> + *
>> + * Returns: New mock scheduler entity with allocation managed by the
>> test
>> + */
>> +struct drm_mock_sched_entity *
>> +drm_mock_new_sched_entity(struct kunit *test,
>
> This naming seems to be inconsistent with its counterpart,
> drm_mock_sched_entity_free().
>
> Better drm_mock_sched_entity_new()
Yes this is the only incosistent one and it was deliberate (somehow it
read better to me when looking at the final code) but if you insist I
can swap it around. Let me know.
>
>
>> + 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;
>> +}
>> +
>> +/**
>> + * drm_mock_sched_entity_free - Destroys a mock scheduler entity
>> + *
>> + * @entity: Entity to destroy
>> + *
>> + * To be used from the test cases once done with the 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 *job =
>> + container_of(hrtimer, typeof(*job), timer);
>> + struct drm_mock_scheduler *sched =
>> + drm_sched_to_mock_sched(job->base.sched);
>> + struct drm_mock_sched_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;
>> +}
>> +
>> +/**
>> + * drm_mock_new_sched_job - Create a new mock scheduler job
>> + *
>> + * @test: KUnit test owning the job
>> + * @entity: Scheduler entity of the job
>> + *
>> + * Returns: New mock scheduler job with allocation managed by the
>> test
>> + */
>> +struct drm_mock_sched_job *
>> +drm_mock_new_sched_job(struct kunit *test,
>
> IMO we could even think about establishing this style
>
> return type stuff *
> function_foo()
> {
>
> for all scheduler unit test files. Would always be consistent. WDYT?
I prefer a single line when it fits. I can change it, although in
general I don't think being as strict on this detail is very important
if that would be a rule applicable just to this sub-directory.
>> + 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)
>
> here for example; see comment above.
>
>> +{
>> + 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
>> +};
>> +
>> +/**
>> + * drm_mock_new_scheduler - Create a new mock scheduler
>> + *
>> + * @test: KUnit test owning the job
>> + *
>> + * Returns: New mock scheduler with allocation managed by the test
>> + */
>> +struct drm_mock_scheduler *drm_mock_new_scheduler(struct kunit
>> *test)
>> +{
>> + struct drm_sched_init_args args = {
>> + .ops = &drm_mock_scheduler_ops,
>> + .num_rqs = DRM_SCHED_PRIORITY_COUNT,
>> + .credit_limit = U32_MAX,
>> + .hang_limit = UINT_MAX,
>> + .timeout = MAX_SCHEDULE_TIMEOUT,
>> + .name = "drm-mock-scheduler",
>> + };
>> + 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, &args);
>> + 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;
>> +}
>> +
>> +/**
>> + * drm_mock_scheduler_fini - Destroys a mock scheduler
>> + *
>> + * @sched: Scheduler to destroy
>> + *
>> + * To be used from the test cases once done with the scheduler.
>> + */
>> +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);
>> +}
>> +
>> +/**
>> + * drm_mock_sched_advance - Advances the mock scheduler timeline
>> + *
>> + * @sched: Scheduler timeline to advance
>> + * @num: By how many jobs to advance
>> + *
>> + * Advancing the scheduler timeline by a number of seqnos will
>> trigger
>> + * signalling of the hardware fences and unlinking the jobs from the
>> internal
>> + * scheduler tracking.
>> + *
>> + * This can be used from test cases which want complete control of
>> the simulated
>> + * job execution timing. For example submitting one job with no set
>> duration
>> + * would never complete it before test cases advances the timeline
>> by one.
>> + */
>> +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;
>> +}
>> +
>> +MODULE_DESCRIPTION("DRM mock scheduler and tests");
>> +MODULE_LICENSE("GPL");
>> diff --git a/drivers/gpu/drm/scheduler/tests/sched_tests.h
>> b/drivers/gpu/drm/scheduler/tests/sched_tests.h
>> new file mode 100644
>> index 000000000000..0614bc901dd1
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/sched_tests.h
>> @@ -0,0 +1,218 @@
>> +/* SPDX-License-Identifier: GPL-2.0 */
>> +
>> +#ifndef _SCHED_TESTS_H_
>> +#define _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>
>> +
>> +/*
>> + * DOC: Mock DRM scheduler data structures
>> + *
>> + * drm_mock_* data structures are used to implement a mock "GPU".
>> + *
>> + * They subclass the core DRM scheduler objects and add their data
>> on top, which
>> + * enables tracking the submitted jobs and simulating their
>> execution with the
>> + * attributes as specified by the test case.
>> + */
>> +
>> +/**
>> + * struct drm_mock_scheduler - implements a trivial mock GPU
>> execution engine
>> + *
>> + * @base: DRM scheduler base class
>> + * @test: Backpointer to owning the kunit test case
>> + * @lock: Lock to protect the simulated @hw_timeline and the
>> @job_list
>> + * @job_list: List of jobs submitted to the mock GPU
>> + * @hw_timeline: Simulated hardware timeline has a @context,
>> @next_seqno and
>> + * @cur_seqno for implementing a struct dma_fence
>> signaling the
>> + * simulated job completion.
>> + *
>> + * Trivial mock GPU execution engine tracks submitted jobs and
>> enables
>> + * completing them strictly in submission order.
>> + */
>> +struct drm_mock_scheduler {
>> + struct drm_gpu_scheduler base;
>> +
>> + struct kunit *test;
>> +
>> + spinlock_t lock;
>> + struct list_head job_list;
>> +
>> + struct {
>> + u64 context;
>> + atomic_t next_seqno;
>> + unsigned int cur_seqno;
>> + } hw_timeline;
>> +};
>> +
>> +/**
>> + * struct drm_mock_sched_entity - implements a mock GPU sched entity
>> + *
>> + * @base: DRM scheduler entity base class
>> + * @test: Backpointer to owning the kunit test case
>> + *
>> + * Mock GPU sched entity is used by the test cases to submit jobs to
>> the mock
>> + * scheduler.
>> + */
>> +struct drm_mock_sched_entity {
>> + struct drm_sched_entity base;
>> +
>> + struct kunit *test;
>> +};
>> +
>> +/**
>> + * struct drm_mock_sched_job - implements a mock GPU job
>> + *
>> + * @base: DRM sched job base class
>> + * @link: List head element used by job tracking by the
>> drm_mock_scheduler
>> + * @timer: Timer used for simulating job execution duration
>> + * @duration_us: Simulated job duration in micro seconds, or zero if
>> in manual
>> + * timeline advance mode
>> + * @finish_at: Absolute time when the jobs with set duration will
>> complete
>> + * @lock: Lock used for @hw_fence
>> + * @hw_fence: Fence returned to DRM scheduler as the hardware fence
>
> Can you ellaborate a bit more about lock's purpose (here in the thread
> at first)?
>
> The hw_fence has its own internal lock for adding callbacks and the
> like, so why is this one here necessary?
It _is_ the internal fence lock, the one that is passed to dma_fence_init().
> Furthermore, at least in this patch here, it seems that whenever
> hw_fence is changed, its being protected by the scheduler's lock. Why
> is that?
I don't follow - what do you consider "hw_fence is changed"?
Hw_fence is initialized in the ->run_job() callback and is signalled
outside the mock_sched->lock.
>
>> + * @test: Backpointer to owning the kunit test case
>> + *
>> + * Mock GPU sched job is used by the test cases to submit jobs to
>> the mock
>> + * scheduler.
>> + */
>> +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);
>> +
>> +/**
>> + * drm_mock_sched_job_submit - Arm and submit a job in one go
>> + *
>> + * @job: Job to arm and submit
>> + */
>> +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);
>> +}
>> +
>> +/**
>> + * drm_mock_sched_job_set_duration_us - Set a job duration
>> + *
>> + * @job: Job to set the duration for
>> + * @duration_us: Duration in micro seconds
>> + *
>> + * Jobs with duration set will be automatically completed by the
>> mock scheduler
>> + * as the timeline progress, unless a job without a set duration is
>
> s/progress/progresses
>
>> encountered
>> + * in the timelime in which case calling drm_mock_sched_advance will
>
> s/drm_mock_sched_advance/drm_mock_sched_advanace()
>
> As I learned recently, the brackets are what actually teaches the doc
> generator that this is a function that shall be linked in the docu.
Both done.
>
>> be required
>> + * to bump the timeline.
>> + */
>> +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;
>> +}
>> +
>> +/**
>> + * drm_mock_sched_job_is_finished - Check if a job is finished
>> + *
>> + * @job: Job to check
>> + *
>> + * Returns: true if finished
>> + */
>> +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);
>> +}
>> +
>> +/**
>> + * drm_mock_sched_job_wait_finished - Wait until a job is finished
>> + *
>> + * @job: Job to wait for
>> + * @timeout: Wait time in jiffies
>> + *
>> + * Returns: true if finished within the timeout provided, otherwise
>> false
>> + */
>> +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;
>> +}
>> +
>> +/**
>> + * drm_mock_sched_job_wait_finished - Wait until a job is scheduled
>> + *
>> + * @job: Job to wait for
>> + * @timeout: Wait time in jiffies
>> + *
>> + * Returns: true if scheduled within the timeout provided, otherwise
>> false
>> + */
>> +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/tests_basic.c
>> b/drivers/gpu/drm/scheduler/tests/tests_basic.c
>> new file mode 100644
>> index 000000000000..c12368a22a39
>> --- /dev/null
>> +++ b/drivers/gpu/drm/scheduler/tests/tests_basic.c
>> @@ -0,0 +1,197 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +
>> +#include "sched_tests.h"
>> +
>> +/*
>> + * DRM scheduler basic tests should check the basic functional
>> correctness of
>> + * the scheduler, including some very light smoke testing. More
>> targeted tests,
>> + * for example focusing on testing specific bugs and other more
>> complicated test
>> + * scenarios, should be implemented in a separate source units.
>
> s/a//
Done.
>
>> + */
>> +
>> +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.
>> + */
>
> That seems to be the description of the entire function? If yes, we
> probably want it above the function's begin.
I find it more useful closer to the action so would prefer to keep it as
is. But if you insist I can move.
Regards,
Tvrtko
>
>
> Thx
> P.
>
>> +
>> + 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);
>
More information about the dri-devel
mailing list