mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-28 00:29:57 +00:00

Add a test to 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. Reviewed-by: Tvrtko Ursulin <tvrtko.ursulin@igalia.com> Reviewed-by: Philipp Stanner <phasta@kernel.org> Link: https://lore.kernel.org/r/20250714-sched-skip-reset-v6-4-5c5ba4f55039@igalia.com Signed-off-by: Maíra Canal <mcanal@igalia.com>
564 lines
15 KiB
C
564 lines
15 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2025 Valve Corporation */
|
|
|
|
#include <linux/delay.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);
|
|
}
|
|
|
|
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_DONT_RESET,
|
|
0);
|
|
|
|
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(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(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_timeout,
|
|
&drm_sched_cancel,
|
|
&drm_sched_priority,
|
|
&drm_sched_modify_sched,
|
|
&drm_sched_credits);
|