// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Valve Corporation */
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/minmax.h>
#include <linux/time.h>
#include <linux/workqueue.h>
#include "sched_tests.h"
#define MOCK_TIMEOUT (HZ / 5)
/*
* 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, MAX_SCHEDULE_TIMEOUT);
return 0;
}
static void drm_sched_basic_exit(struct kunit *test)
{
struct drm_mock_scheduler *sched = test->priv;
drm_mock_sched_fini(sched);
}
static int drm_sched_timeout_init(struct kunit *test)
{
test->priv = drm_mock_sched_new(test, MOCK_TIMEOUT);
return 0;
}
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,
};
static void drm_sched_basic_cancel(struct kunit *test)
{
struct drm_mock_sched_entity *entity;
struct drm_mock_scheduler *sched;
struct drm_mock_sched_job *job;
bool done;
/*
* Check that drm_sched_fini() uses the cancel_job() callback to cancel
* jobs that are still pending.
*/
sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
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);
drm_mock_sched_entity_free(entity);
drm_mock_sched_fini(sched);
KUNIT_ASSERT_EQ(test, job->hw_fence.error, -ECANCELED);
}
struct sched_concurrent_context {
struct drm_mock_scheduler *sched;
struct workqueue_struct *sub_wq;
struct kunit *test;
struct completion wait_go;
};
KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_fini_wrap, drm_mock_sched_fini,
struct drm_mock_scheduler *);
KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_entity_free_wrap, drm_mock_sched_entity_free,
struct drm_mock_sched_entity *);
static void complete_destroy_workqueue(void *context)
{
struct sched_concurrent_context *ctx = context;
complete_all(&ctx->wait_go);
destroy_workqueue(ctx->sub_wq);
}
static int drm_sched_concurrent_init(struct kunit *test)
{
struct sched_concurrent_context *ctx;
int ret;
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, ctx);
init_completion(&ctx->wait_go);
ctx->sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
ret = kunit_add_action_or_reset(test, drm_mock_sched_fini_wrap, ctx->sched);
KUNIT_ASSERT_EQ(test, ret, 0);
/* Use an unbounded workqueue to maximize job submission concurrency */
ctx->sub_wq = alloc_workqueue("drm-sched-submitters-wq", WQ_UNBOUND,
WQ_UNBOUND_MAX_ACTIVE);
KUNIT_ASSERT_NOT_NULL(test, ctx->sub_wq);
ret = kunit_add_action_or_reset(test, complete_destroy_workqueue, ctx);
KUNIT_ASSERT_EQ(test, ret, 0);
ctx->test = test;
test->priv = ctx;
return 0;
}
struct drm_sched_parallel_params {
const char *description;
unsigned int num_jobs;
unsigned int num_workers;
};
static const struct drm_sched_parallel_params drm_sched_parallel_cases[] = {
{
.description = "Parallel submission of multiple jobs per worker",
.num_jobs = 8,
.num_workers = 16,
},
};
static void
drm_sched_parallel_desc(const struct drm_sched_parallel_params *params, char *desc)
{
strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(drm_sched_parallel, drm_sched_parallel_cases, drm_sched_parallel_desc);
struct parallel_worker {
struct work_struct work;
struct sched_concurrent_context *ctx;
struct drm_mock_sched_entity *entity;
struct drm_mock_sched_job **jobs;
unsigned int id;
};
static void drm_sched_parallel_worker(struct work_struct *work)
{
const struct drm_sched_parallel_params *params;
struct sched_concurrent_context *test_ctx;
struct parallel_worker *worker;
unsigned int i;
worker = container_of(work, struct parallel_worker, work);
test_ctx = worker->ctx;
params = test_ctx->test->param_value;
wait_for_completion(&test_ctx->wait_go);
kunit_info(test_ctx->test, "Parallel worker %u submitting %u jobs started\n",
worker->id, params->num_jobs);
for (i = 0; i < params->num_jobs; i++)
drm_mock_sched_job_submit(worker->jobs[i]);
}
/*
* Spawns workers that submit a sequence of jobs to the mock scheduler.
* Once all jobs are submitted, the timeline is manually advanced.
*/
static void drm_sched_parallel_submit_test(struct kunit *test)
{
struct sched_concurrent_context *ctx = test->priv;
const struct drm_sched_parallel_params *params = test->param_value;
struct parallel_worker *workers, *worker;
struct drm_mock_sched_job *job;
unsigned int i, j, completed_jobs, total_jobs;
bool done;
int ret;
KUNIT_ASSERT_GT(test, params->num_workers, 0);
KUNIT_ASSERT_GT(test, params->num_jobs, 0);
workers = kunit_kcalloc(test, params->num_workers, sizeof(*workers),
GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, workers);
/*
* Init workers only after all jobs and entities have been successfully
* allocated. In this way, the cleanup logic for when an assertion fail
* can be simplified.
*/
for (i = 0; i < params->num_workers; i++) {
worker = &workers[i];
worker->id = i;
worker->ctx = ctx;
worker->entity = drm_mock_sched_entity_new(test,
DRM_SCHED_PRIORITY_NORMAL,
ctx->sched);
worker->jobs = kunit_kcalloc(test, params->num_jobs,
sizeof(*worker->jobs), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, worker->jobs);
for (j = 0; j < params->num_jobs; j++) {
job = drm_mock_sched_job_new(test, worker->entity);
worker->jobs[j] = job;
}
ret = kunit_add_action_or_reset(test, drm_mock_sched_entity_free_wrap,
worker->entity);
KUNIT_ASSERT_EQ(test, ret, 0);
}
for (i = 0; i < params->num_workers; i++) {
worker = &workers[i];
INIT_WORK(&worker->work, drm_sched_parallel_worker);
queue_work(ctx->sub_wq, &worker->work);
}
complete_all(&ctx->wait_go);
flush_workqueue(ctx->sub_wq);
for (i = 0; i < params->num_workers; i++) {
worker = &workers[i];
for (j = 0; j < params->num_jobs; j++) {
job = worker->jobs[j];
done = drm_mock_sched_job_wait_scheduled(job, HZ);
KUNIT_EXPECT_TRUE(test, done);
}
}
total_jobs = params->num_workers * params->num_jobs;
completed_jobs = drm_mock_sched_advance(ctx->sched, total_jobs);
KUNIT_EXPECT_EQ(test, completed_jobs, total_jobs);
for (i = 0; i < params->num_workers; i++) {
worker = &workers[i];
for (j = 0; j < params->num_jobs; j++) {
job = worker->jobs[j];
done = drm_mock_sched_job_wait_finished(job, HZ);
KUNIT_EXPECT_TRUE(test, done);
}
}
}
struct drm_sched_interleaved_params {
const char *description;
unsigned int test_duration_ms;
unsigned int job_base_duration_us;
unsigned int num_workers;
unsigned int num_in_flight_jobs;
};
static const struct drm_sched_interleaved_params drm_sched_interleaved_cases[] = {
{
.description = "Interleaved submission of multiple jobs per worker",
.test_duration_ms = 1000,
.job_base_duration_us = 100,
.num_workers = 16,
.num_in_flight_jobs = 8,
},
};
static void
drm_sched_interleaved_desc(const struct drm_sched_interleaved_params *params, char *desc)
{
strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
}
KUNIT_ARRAY_PARAM(drm_sched_interleaved, drm_sched_interleaved_cases,
drm_sched_interleaved_desc);
struct interleaved_worker {
struct work_struct work;
struct sched_concurrent_context *ctx;
struct drm_mock_sched_entity *entity;
struct drm_mock_sched_job **jobs;
unsigned int id;
unsigned int job_count;
unsigned int job_duration_us;
};
static void drm_sched_interleaved_worker(struct work_struct *work)
{
struct sched_concurrent_context *test_ctx;
const struct drm_sched_interleaved_params *params;
struct interleaved_worker *worker;
unsigned int i, j, max_in_flight_job;
unsigned long timeout;
bool done;
worker = container_of(work, struct interleaved_worker, work);
test_ctx = worker->ctx;
params = test_ctx->test->param_value;
wait_for_completion(&test_ctx->wait_go);
kunit_info(test_ctx->test, "Worker %u submitting %u jobs of %u us started\n",
worker->id, worker->job_count, worker->job_duration_us);
timeout = msecs_to_jiffies(params->test_duration_ms * 2);
/* Fill the submission window */
max_in_flight_job = min(worker->job_count, params->num_in_flight_jobs);
for (i = 0; i < max_in_flight_job; i++)
drm_mock_sched_job_submit(worker->jobs[i]);
/* Keep the window full by submitting a new job at once until done */
for (i = 0; i < worker->job_count; i++) {
done = drm_mock_sched_job_wait_finished(worker->jobs[i], timeout);
if (!done)
kunit_info(test_ctx->test, "Job %u of worker %u timed out\n",
i, worker->id);
j = i + max_in_flight_job;
if (j < worker->job_count)
drm_mock_sched_job_submit(worker->jobs[j]);
}
}
/*
* Spawns workers that submit a sequence of jobs to the mock scheduler. Job
* durations are chosen as multiples of a base duration value specified as
* a test parameter. Since the scheduler serializes jobs from all workers,
* the total test duration budget is divided into equal shares among workers.
* These shares are then used to compute the number of jobs that each worker
* can submit.
*/
static void drm_sched_interleaved_submit_test(struct kunit *test)
{
const struct drm_sched_interleaved_params *params = test->param_value;
struct sched_concurrent_context *ctx = test->priv;
struct interleaved_worker *workers, *worker;
struct drm_mock_sched_job *job;
unsigned int worker_share_us;
unsigned int i, j;
bool done;
int ret;
KUNIT_ASSERT_GT(test, params->num_workers, 0);
KUNIT_ASSERT_GT(test, params->job_base_duration_us, 0);
workers = kunit_kcalloc(test, params->num_workers, sizeof(*workers),
GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, workers);
/* Divide the available test time into equal shares among the workers */
worker_share_us = (params->test_duration_ms * USEC_PER_MSEC) /
params->num_workers;
/*
* Init workers only after all jobs and entities have been successfully
* allocated. In this way, the cleanup logic for when an assertion fails
* can be simplified.
*/
for (i = 0; i < params->num_workers; i++) {
worker = &workers[i];
worker->id = i;
worker->ctx = ctx;
worker->job_duration_us = params->job_base_duration_us * (i + 1);
worker->job_count = worker_share_us / worker->job_duration_us;
worker->job_count = max(1U, worker->job_count);
worker->entity = drm_mock_sched_entity_new(test,
DRM_SCHED_PRIORITY_NORMAL,
ctx->sched);
worker->jobs = kunit_kcalloc(test, worker->job_count,
sizeof(*worker->jobs), GFP_KERNEL);
KUNIT_ASSERT_NOT_NULL(test, worker->jobs);
for (j = 0; j < worker->job_count; j++) {
job = drm_mock_sched_job_new(test, worker->entity);
drm_mock_sched_job_set_duration_us(job, worker->job_duration_us);
worker->jobs[j] = job;
}
ret = kunit_add_action_or_reset(test, drm_mock_sched_entity_free_wrap,
worker->entity);
KUNIT_ASSERT_EQ(test, ret, 0);
}
for (i = 0; i < params->num_workers; i++) {
worker = &workers[i];
INIT_WORK(&worker->work, drm_sched_interleaved_worker);
queue_work(ctx->sub_wq, &worker->work);
}
complete_all(&ctx->wait_go);
flush_workqueue(ctx->sub_wq);
for (i = 0; i < params->num_workers; i++) {
worker = &workers[i];
for (j = 0; j < worker->job_count; j++) {
job = worker->jobs[j];
done = drm_mock_sched_job_is_finished(job);
KUNIT_EXPECT_TRUE(test, done);
}
}
}
static struct kunit_case drm_sched_concurrent_tests[] = {
KUNIT_CASE_PARAM(drm_sched_parallel_submit_test, drm_sched_parallel_gen_params),
KUNIT_CASE_PARAM(drm_sched_interleaved_submit_test, drm_sched_interleaved_gen_params),
{}
};
static struct kunit_suite drm_sched_concurrent = {
.name = "drm_sched_concurrent_tests",
.init = drm_sched_concurrent_init,
.test_cases = drm_sched_concurrent_tests,
.attr = {
.speed = KUNIT_SPEED_SLOW,
},
};
static struct kunit_case drm_sched_cancel_tests[] = {
KUNIT_CASE(drm_sched_basic_cancel),
{}
};
static struct kunit_suite drm_sched_cancel = {
.name = "drm_sched_basic_cancel_tests",
.init = drm_sched_basic_init,
.exit = drm_sched_basic_exit,
.test_cases = drm_sched_cancel_tests,
};
static void drm_sched_basic_timeout(struct kunit *test)
{
struct drm_mock_scheduler *sched = test->priv;
struct drm_mock_sched_entity *entity;
struct drm_mock_sched_job *job;
bool done;
/*
* Submit a single job against a scheduler with the timeout configured
* and verify that the timeout handling will run if the backend fails
* to complete it in time.
*/
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, MOCK_TIMEOUT / 2);
KUNIT_ASSERT_FALSE(test, done);
KUNIT_ASSERT_EQ(test,
job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT,
0);
done = drm_mock_sched_job_wait_finished(job, MOCK_TIMEOUT);
KUNIT_ASSERT_FALSE(test, done);
KUNIT_ASSERT_EQ(test,
job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT,
DRM_MOCK_SCHED_JOB_TIMEDOUT);
drm_mock_sched_entity_free(entity);
}
static void drm_sched_skip_reset(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 a single job against a scheduler with the timeout configured
* and verify that if the job is still running, the timeout handler
* will skip the reset and allow the job to complete.
*/
entity = drm_mock_sched_entity_new(test,
DRM_SCHED_PRIORITY_NORMAL,
sched);
job = drm_mock_sched_job_new(test, entity);
job->flags = DRM_MOCK_SCHED_JOB_DONT_RESET;
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, 2 * MOCK_TIMEOUT);
KUNIT_ASSERT_FALSE(test, done);
KUNIT_ASSERT_EQ(test,
job->flags & DRM_MOCK_SCHED_JOB_RESET_SKIPPED,
DRM_MOCK_SCHED_JOB_RESET_SKIPPED);
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);
}
static struct kunit_case drm_sched_timeout_tests[] = {
KUNIT_CASE(drm_sched_basic_timeout),
KUNIT_CASE(drm_sched_skip_reset),
{}
};
static struct kunit_suite drm_sched_timeout = {
.name = "drm_sched_basic_timeout_tests",
.init = drm_sched_timeout_init,
.exit = drm_sched_basic_exit,
.test_cases = drm_sched_timeout_tests,
};
static void drm_sched_priorities(struct kunit *test)
{
struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
struct drm_mock_scheduler *sched = test->priv;
struct drm_mock_sched_job *job;
const unsigned int qd = 100;
unsigned int i, cur_ent = 0;
enum drm_sched_priority p;
bool done;
/*
* Submit a bunch of jobs against entities configured with different
* priorities.
*/
BUILD_BUG_ON(DRM_SCHED_PRIORITY_KERNEL > DRM_SCHED_PRIORITY_LOW);
BUILD_BUG_ON(ARRAY_SIZE(entity) != DRM_SCHED_PRIORITY_COUNT);
for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
entity[p] = drm_mock_sched_entity_new(test, p, 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);
drm_mock_sched_job_submit(job);
}
done = drm_mock_sched_job_wait_finished(job, HZ);
KUNIT_ASSERT_TRUE(test, done);
for (i = 0; i < ARRAY_SIZE(entity); i++)
drm_mock_sched_entity_free(entity[i]);
}
static void drm_sched_change_priority(struct kunit *test)
{
struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
struct drm_mock_scheduler *sched = test->priv;
struct drm_mock_sched_job *job;
const unsigned int qd = 1000;
unsigned int i, cur_ent = 0;
enum drm_sched_priority p;
/*
* Submit a bunch of jobs against entities configured with different
* priorities and while waiting for them to complete, periodically keep
* changing their priorities.
*
* We set up the queue-depth (qd) and job duration so the priority
* changing loop has some time to interact with submissions to the
* backend and job completions as they progress.
*/
for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
entity[p] = drm_mock_sched_entity_new(test, p, 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);
drm_mock_sched_job_submit(job);
}
do {
drm_sched_entity_set_priority(&entity[cur_ent]->base,
(entity[cur_ent]->base.priority + 1) %
DRM_SCHED_PRIORITY_COUNT);
cur_ent++;
cur_ent %= ARRAY_SIZE(entity);
usleep_range(200, 500);
} while (!drm_mock_sched_job_is_finished(job));
for (i = 0; i < ARRAY_SIZE(entity); i++)
drm_mock_sched_entity_free(entity[i]);
}
static struct kunit_case drm_sched_priority_tests[] = {
KUNIT_CASE(drm_sched_priorities),
KUNIT_CASE_SLOW(drm_sched_change_priority),
{}
};
static struct kunit_suite drm_sched_priority = {
.name = "drm_sched_basic_priority_tests",
.init = drm_sched_basic_init,
.exit = drm_sched_basic_exit,
.test_cases = drm_sched_priority_tests,
};
static void drm_sched_test_modify_sched(struct kunit *test)
{
unsigned int i, cur_ent = 0, cur_sched = 0;
struct drm_mock_sched_entity *entity[13];
struct drm_mock_scheduler *sched[3];
struct drm_mock_sched_job *job;
const unsigned int qd = 1000;
/*
* Submit a bunch of jobs against entities configured with different
* schedulers and while waiting for them to complete, periodically keep
* changing schedulers associated with each entity.
*
* We set up the queue-depth (qd) and job duration so the sched modify
* loop has some time to interact with submissions to the backend and
* job completions as they progress.
*
* For the number of schedulers and entities we use primes in order to
* perturb the entity->sched assignments with less of a regular pattern.
*/
for (i = 0; i < ARRAY_SIZE(sched); i++)
sched[i] = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
for (i = 0; i < ARRAY_SIZE(entity); i++)
entity[i] = drm_mock_sched_entity_new(test,
DRM_SCHED_PRIORITY_NORMAL,
sched[i % ARRAY_SIZE(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);
drm_mock_sched_job_submit(job);
}
do {
struct drm_gpu_scheduler *modify;
usleep_range(200, 500);
cur_ent++;
cur_ent %= ARRAY_SIZE(entity);
cur_sched++;
cur_sched %= ARRAY_SIZE(sched);
modify = &sched[cur_sched]->base;
drm_sched_entity_modify_sched(&entity[cur_ent]->base, &modify,
1);
} while (!drm_mock_sched_job_is_finished(job));
for (i = 0; i < ARRAY_SIZE(entity); i++)
drm_mock_sched_entity_free(entity[i]);
for (i = 0; i < ARRAY_SIZE(sched); i++)
drm_mock_sched_fini(sched[i]);
}
static struct kunit_case drm_sched_modify_sched_tests[] = {
KUNIT_CASE(drm_sched_test_modify_sched),
{}
};
static struct kunit_suite drm_sched_modify_sched = {
.name = "drm_sched_basic_modify_sched_tests",
.test_cases = drm_sched_modify_sched_tests,
};
static void drm_sched_test_credits(struct kunit *test)
{
struct drm_mock_sched_entity *entity;
struct drm_mock_scheduler *sched;
struct drm_mock_sched_job *job[2];
bool done;
int i;
/*
* Check that the configured credit limit is respected.
*/
sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
sched->base.credit_limit = 1;
entity = drm_mock_sched_entity_new(test,
DRM_SCHED_PRIORITY_NORMAL,
sched);
job[0] = drm_mock_sched_job_new(test, entity);
job[1] = drm_mock_sched_job_new(test, entity);
drm_mock_sched_job_submit(job[0]);
drm_mock_sched_job_submit(job[1]);
done = drm_mock_sched_job_wait_scheduled(job[0], HZ);
KUNIT_ASSERT_TRUE(test, done);
done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
KUNIT_ASSERT_FALSE(test, done);
i = drm_mock_sched_advance(sched, 1);
KUNIT_ASSERT_EQ(test, i, 1);
done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
KUNIT_ASSERT_TRUE(test, done);
i = drm_mock_sched_advance(sched, 1);
KUNIT_ASSERT_EQ(test, i, 1);
done = drm_mock_sched_job_wait_finished(job[1], HZ);
KUNIT_ASSERT_TRUE(test, done);
drm_mock_sched_entity_free(entity);
drm_mock_sched_fini(sched);
}
static struct kunit_case drm_sched_credits_tests[] = {
KUNIT_CASE_SLOW(drm_sched_test_credits),
{}
};
static struct kunit_suite drm_sched_credits = {
.name = "drm_sched_basic_credits_tests",
.test_cases = drm_sched_credits_tests,
};
kunit_test_suites(&drm_sched_basic,
&drm_sched_concurrent,
&drm_sched_timeout,
&drm_sched_cancel,
&drm_sched_priority,
&drm_sched_modify_sched,
&drm_sched_credits);