// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2025 Valve Corporation */ #include #include #include #include #include #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);