// 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 voiddrm_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 voiddrm_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 voiddrm_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 asLines 1–500 of 922. Load more
// 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