922 lines · 25.3 KB · C
RawDownload
// 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 as

Lines 1–500 of 922.