// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2025 Valve Corporation */ #include #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, 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, HZ); 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_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, HZ / 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, HZ); 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 struct kunit_case drm_sched_timeout_tests[] = { KUNIT_CASE(drm_sched_basic_timeout), {} }; 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_priority, &drm_sched_modify_sched, &drm_sched_credits);