mirror of
https://github.com/torvalds/linux.git
synced 2025-11-03 01:59:51 +02:00
drm/sched: Add scheduler unit testing infrastructure and some basic tests
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> Acked-by: Christian König <christian.koenig@amd.com> Signed-off-by: Philipp Stanner <phasta@kernel.org> Link: https://patchwork.freedesktop.org/patch/msgid/20250324092633.49746-3-tvrtko.ursulin@igalia.com
This commit is contained in:
parent
8e623137f1
commit
5a99350794
7 changed files with 809 additions and 0 deletions
|
|
@ -100,5 +100,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
|
||||
|
|
|
|||
12
drivers/gpu/drm/scheduler/.kunitconfig
Normal file
12
drivers/gpu/drm/scheduler/.kunitconfig
Normal file
|
|
@ -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
|
||||
|
|
@ -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/
|
||||
|
|
|
|||
7
drivers/gpu/drm/scheduler/tests/Makefile
Normal file
7
drivers/gpu/drm/scheduler/tests/Makefile
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
drm-sched-tests-y := \
|
||||
mock_scheduler.o \
|
||||
tests_basic.o
|
||||
|
||||
obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += drm-sched-tests.o
|
||||
354
drivers/gpu/drm/scheduler/tests/mock_scheduler.c
Normal file
354
drivers/gpu/drm/scheduler/tests/mock_scheduler.c
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Valve Corporation */
|
||||
|
||||
#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_sched_entity_new - 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_sched_entity_new(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 void drm_mock_sched_job_complete(struct drm_mock_sched_job *job)
|
||||
{
|
||||
struct drm_mock_scheduler *sched =
|
||||
drm_sched_to_mock_sched(job->base.sched);
|
||||
|
||||
lockdep_assert_held(&sched->lock);
|
||||
|
||||
job->flags |= DRM_MOCK_SCHED_JOB_DONE;
|
||||
list_move_tail(&job->link, &sched->done_list);
|
||||
dma_fence_signal(&job->hw_fence);
|
||||
complete(&job->done);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
sched->hw_timeline.cur_seqno = job->hw_fence.seqno;
|
||||
drm_mock_sched_job_complete(job);
|
||||
}
|
||||
spin_unlock_irqrestore(&sched->lock, flags);
|
||||
|
||||
return HRTIMER_NORESTART;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_mock_sched_job_new - 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_sched_job_new(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;
|
||||
|
||||
init_completion(&job->done);
|
||||
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);
|
||||
|
||||
/* Containing job is 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)
|
||||
{
|
||||
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);
|
||||
unsigned long flags;
|
||||
|
||||
/* Remove from the scheduler done list. */
|
||||
spin_lock_irqsave(&sched->lock, flags);
|
||||
list_del(&job->link);
|
||||
spin_unlock_irqrestore(&sched->lock, flags);
|
||||
dma_fence_put(&job->hw_fence);
|
||||
|
||||
drm_sched_job_cleanup(sched_job);
|
||||
|
||||
/* Mock job itself is freed by the kunit framework. */
|
||||
}
|
||||
|
||||
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_sched_new - 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_sched_new(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 = 1,
|
||||
.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);
|
||||
INIT_LIST_HEAD(&sched->done_list);
|
||||
spin_lock_init(&sched->lock);
|
||||
|
||||
return sched;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_mock_sched_fini - Destroys a mock scheduler
|
||||
*
|
||||
* @sched: Scheduler to destroy
|
||||
*
|
||||
* To be used from the test cases once done with the scheduler.
|
||||
*/
|
||||
void drm_mock_sched_fini(struct drm_mock_scheduler *sched)
|
||||
{
|
||||
struct drm_mock_sched_job *job, *next;
|
||||
unsigned long flags;
|
||||
LIST_HEAD(list);
|
||||
|
||||
drm_sched_wqueue_stop(&sched->base);
|
||||
|
||||
/* Force complete all unfinished jobs. */
|
||||
spin_lock_irqsave(&sched->lock, flags);
|
||||
list_for_each_entry_safe(job, next, &sched->job_list, link)
|
||||
list_move_tail(&job->link, &list);
|
||||
spin_unlock_irqrestore(&sched->lock, flags);
|
||||
|
||||
list_for_each_entry(job, &list, link)
|
||||
hrtimer_cancel(&job->timer);
|
||||
|
||||
spin_lock_irqsave(&sched->lock, flags);
|
||||
list_for_each_entry_safe(job, next, &list, link)
|
||||
drm_mock_sched_job_complete(job);
|
||||
spin_unlock_irqrestore(&sched->lock, flags);
|
||||
|
||||
/*
|
||||
* Free completed jobs and jobs not yet processed by the DRM scheduler
|
||||
* free worker.
|
||||
*/
|
||||
spin_lock_irqsave(&sched->lock, flags);
|
||||
list_for_each_entry_safe(job, next, &sched->done_list, link)
|
||||
list_move_tail(&job->link, &list);
|
||||
spin_unlock_irqrestore(&sched->lock, flags);
|
||||
|
||||
list_for_each_entry_safe(job, next, &list, link)
|
||||
mock_sched_free_job(&job->base);
|
||||
|
||||
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;
|
||||
|
||||
drm_mock_sched_job_complete(job);
|
||||
found++;
|
||||
}
|
||||
unlock:
|
||||
spin_unlock_irqrestore(&sched->lock, flags);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
MODULE_DESCRIPTION("DRM mock scheduler and tests");
|
||||
MODULE_LICENSE("GPL");
|
||||
224
drivers/gpu/drm/scheduler/tests/sched_tests.h
Normal file
224
drivers/gpu/drm/scheduler/tests/sched_tests.h
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/* Copyright (c) 2025 Valve Corporation */
|
||||
|
||||
#ifndef _SCHED_TESTS_H_
|
||||
#define _SCHED_TESTS_H_
|
||||
|
||||
#include <kunit/test.h>
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/completion.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, @job_list and @done_list
|
||||
* @job_list: List of jobs submitted to the mock GPU
|
||||
* @done_list: List of jobs completed by 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 list_head done_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
|
||||
* @done: Completion signaling job completion.
|
||||
* @flags: Flags designating job state.
|
||||
* @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
|
||||
* @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 completion done;
|
||||
|
||||
#define DRM_MOCK_SCHED_JOB_DONE 0x1
|
||||
unsigned long flags;
|
||||
|
||||
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_sched_new(struct kunit *test);
|
||||
void drm_mock_sched_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_sched_entity_new(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_sched_job_new(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 progresses, unless a job without a set duration is
|
||||
* encountered in the timelime in which case calling drm_mock_sched_advance()
|
||||
* will 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 job->flags & DRM_MOCK_SCHED_JOB_DONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
if (job->flags & DRM_MOCK_SCHED_JOB_DONE)
|
||||
return true;
|
||||
|
||||
return wait_for_completion_timeout(&job->done, timeout) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_mock_sched_job_wait_scheduled - 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 bool
|
||||
drm_mock_sched_job_wait_scheduled(struct drm_mock_sched_job *job, long timeout)
|
||||
{
|
||||
KUNIT_ASSERT_EQ(job->test, job->flags & DRM_MOCK_SCHED_JOB_DONE, 0);
|
||||
|
||||
return dma_fence_wait_timeout(&job->base.s_fence->scheduled,
|
||||
false,
|
||||
timeout) != 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
198
drivers/gpu/drm/scheduler/tests/tests_basic.c
Normal file
198
drivers/gpu/drm/scheduler/tests/tests_basic.c
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Valve Corporation */
|
||||
|
||||
#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 separate source units.
|
||||
*/
|
||||
|
||||
static int drm_sched_basic_init(struct kunit *test)
|
||||
{
|
||||
test->priv = drm_mock_sched_new(test);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void drm_sched_basic_exit(struct kunit *test)
|
||||
{
|
||||
struct drm_mock_scheduler *sched = test->priv;
|
||||
|
||||
drm_mock_sched_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_sched_entity_new(test,
|
||||
DRM_SCHED_PRIORITY_NORMAL,
|
||||
sched);
|
||||
job = drm_mock_sched_job_new(test, entity);
|
||||
|
||||
drm_mock_sched_job_submit(job);
|
||||
|
||||
done = drm_mock_sched_job_wait_scheduled(job, HZ);
|
||||
KUNIT_ASSERT_TRUE(test, done);
|
||||
|
||||
done = drm_mock_sched_job_wait_finished(job, HZ / 2);
|
||||
KUNIT_ASSERT_FALSE(test, done);
|
||||
|
||||
i = drm_mock_sched_advance(sched, 1);
|
||||
KUNIT_ASSERT_EQ(test, i, 1);
|
||||
|
||||
done = drm_mock_sched_job_wait_finished(job, HZ);
|
||||
KUNIT_ASSERT_TRUE(test, done);
|
||||
|
||||
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_sched_entity_new(test,
|
||||
DRM_SCHED_PRIORITY_NORMAL,
|
||||
sched);
|
||||
|
||||
for (i = 0; i < params->queue_depth; i++) {
|
||||
job = drm_mock_sched_job_new(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_TRUE(test, done);
|
||||
|
||||
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_sched_entity_new(test,
|
||||
DRM_SCHED_PRIORITY_NORMAL,
|
||||
sched);
|
||||
|
||||
for (i = 0; i < qd; i++) {
|
||||
job = drm_mock_sched_job_new(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_TRUE(test, done);
|
||||
|
||||
/* 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);
|
||||
Loading…
Reference in a new issue