xref: /linux/drivers/gpu/drm/scheduler/tests/tests_basic.c (revision 570f7e331f5febb30f1384817463c7e42b65ca7d)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2025 Valve Corporation */
3 
4 #include <linux/completion.h>
5 #include <linux/delay.h>
6 #include <linux/minmax.h>
7 #include <linux/time.h>
8 #include <linux/workqueue.h>
9 
10 #include "sched_tests.h"
11 
12 #define MOCK_TIMEOUT (HZ / 5)
13 
14 /*
15  * DRM scheduler basic tests should check the basic functional correctness of
16  * the scheduler, including some very light smoke testing. More targeted tests,
17  * for example focusing on testing specific bugs and other more complicated test
18  * scenarios, should be implemented in separate source units.
19  */
20 
21 static int drm_sched_basic_init(struct kunit *test)
22 {
23 	test->priv = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
24 
25 	return 0;
26 }
27 
28 static void drm_sched_basic_exit(struct kunit *test)
29 {
30 	struct drm_mock_scheduler *sched = test->priv;
31 
32 	drm_mock_sched_fini(sched);
33 }
34 
35 static int drm_sched_timeout_init(struct kunit *test)
36 {
37 	test->priv = drm_mock_sched_new(test, MOCK_TIMEOUT);
38 
39 	return 0;
40 }
41 
42 static void drm_sched_basic_submit(struct kunit *test)
43 {
44 	struct drm_mock_scheduler *sched = test->priv;
45 	struct drm_mock_sched_entity *entity;
46 	struct drm_mock_sched_job *job;
47 	unsigned int i;
48 	bool done;
49 
50 	/*
51 	 * Submit one job to the scheduler and verify that it gets scheduled
52 	 * and completed only when the mock hw backend processes it.
53 	 */
54 
55 	entity = drm_mock_sched_entity_new(test,
56 					   DRM_SCHED_PRIORITY_NORMAL,
57 					   sched);
58 	job = drm_mock_sched_job_new(test, entity);
59 
60 	drm_mock_sched_job_submit(job);
61 
62 	done = drm_mock_sched_job_wait_scheduled(job, HZ);
63 	KUNIT_ASSERT_TRUE(test, done);
64 
65 	done = drm_mock_sched_job_wait_finished(job, HZ / 2);
66 	KUNIT_ASSERT_FALSE(test, done);
67 
68 	i = drm_mock_sched_advance(sched, 1);
69 	KUNIT_ASSERT_EQ(test, i, 1);
70 
71 	done = drm_mock_sched_job_wait_finished(job, HZ);
72 	KUNIT_ASSERT_TRUE(test, done);
73 
74 	drm_mock_sched_entity_free(entity);
75 }
76 
77 struct drm_sched_basic_params {
78 	const char *description;
79 	unsigned int queue_depth;
80 	unsigned int num_entities;
81 	unsigned int job_us;
82 	bool dep_chain;
83 };
84 
85 static const struct drm_sched_basic_params drm_sched_basic_cases[] = {
86 	{
87 		.description = "A queue of jobs in a single entity",
88 		.queue_depth = 100,
89 		.job_us = 1000,
90 		.num_entities = 1,
91 	},
92 	{
93 		.description = "A chain of dependent jobs across multiple entities",
94 		.queue_depth = 100,
95 		.job_us = 1000,
96 		.num_entities = 1,
97 		.dep_chain = true,
98 	},
99 	{
100 		.description = "Multiple independent job queues",
101 		.queue_depth = 100,
102 		.job_us = 1000,
103 		.num_entities = 4,
104 	},
105 	{
106 		.description = "Multiple inter-dependent job queues",
107 		.queue_depth = 100,
108 		.job_us = 1000,
109 		.num_entities = 4,
110 		.dep_chain = true,
111 	},
112 };
113 
114 static void
115 drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc)
116 {
117 	strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
118 }
119 
120 KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc);
121 
122 static void drm_sched_basic_test(struct kunit *test)
123 {
124 	const struct drm_sched_basic_params *params = test->param_value;
125 	struct drm_mock_scheduler *sched = test->priv;
126 	struct drm_mock_sched_job *job, *prev = NULL;
127 	struct drm_mock_sched_entity **entity;
128 	unsigned int i, cur_ent = 0;
129 	bool done;
130 
131 	entity = kunit_kcalloc(test, params->num_entities, sizeof(*entity),
132 			       GFP_KERNEL);
133 	KUNIT_ASSERT_NOT_NULL(test, entity);
134 
135 	for (i = 0; i < params->num_entities; i++)
136 		entity[i] = drm_mock_sched_entity_new(test,
137 						      DRM_SCHED_PRIORITY_NORMAL,
138 						      sched);
139 
140 	for (i = 0; i < params->queue_depth; i++) {
141 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
142 		cur_ent %= params->num_entities;
143 		drm_mock_sched_job_set_duration_us(job, params->job_us);
144 		if (params->dep_chain && prev)
145 			drm_sched_job_add_dependency(&job->base,
146 						     dma_fence_get(&prev->base.s_fence->finished));
147 		drm_mock_sched_job_submit(job);
148 		prev = job;
149 	}
150 
151 	done = drm_mock_sched_job_wait_finished(job, HZ);
152 	KUNIT_ASSERT_TRUE(test, done);
153 
154 	for (i = 0; i < params->num_entities; i++)
155 		drm_mock_sched_entity_free(entity[i]);
156 }
157 
158 static void drm_sched_basic_entity_cleanup(struct kunit *test)
159 {
160 	struct drm_mock_sched_job *job, *mid, *prev = NULL;
161 	struct drm_mock_scheduler *sched = test->priv;
162 	struct drm_mock_sched_entity *entity[4];
163 	const unsigned int qd = 100;
164 	unsigned int i, cur_ent = 0;
165 	bool done;
166 
167 	/*
168 	 * Submit a queue of jobs across different entities with an explicit
169 	 * chain of dependencies between them and trigger entity cleanup while
170 	 * the queue is still being processed.
171 	 */
172 
173 	for (i = 0; i < ARRAY_SIZE(entity); i++)
174 		entity[i] = drm_mock_sched_entity_new(test,
175 						      DRM_SCHED_PRIORITY_NORMAL,
176 						      sched);
177 
178 	for (i = 0; i < qd; i++) {
179 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
180 		cur_ent %= ARRAY_SIZE(entity);
181 		drm_mock_sched_job_set_duration_us(job, 1000);
182 		if (prev)
183 			drm_sched_job_add_dependency(&job->base,
184 						     dma_fence_get(&prev->base.s_fence->finished));
185 		drm_mock_sched_job_submit(job);
186 		if (i == qd / 2)
187 			mid = job;
188 		prev = job;
189 	}
190 
191 	done = drm_mock_sched_job_wait_finished(mid, HZ);
192 	KUNIT_ASSERT_TRUE(test, done);
193 
194 	/* Exit with half of the queue still pending to be executed. */
195 	for (i = 0; i < ARRAY_SIZE(entity); i++)
196 		drm_mock_sched_entity_free(entity[i]);
197 }
198 
199 static struct kunit_case drm_sched_basic_tests[] = {
200 	KUNIT_CASE(drm_sched_basic_submit),
201 	KUNIT_CASE_PARAM(drm_sched_basic_test, drm_sched_basic_gen_params),
202 	KUNIT_CASE(drm_sched_basic_entity_cleanup),
203 	{}
204 };
205 
206 static struct kunit_suite drm_sched_basic = {
207 	.name = "drm_sched_basic_tests",
208 	.init = drm_sched_basic_init,
209 	.exit = drm_sched_basic_exit,
210 	.test_cases = drm_sched_basic_tests,
211 };
212 
213 static void drm_sched_basic_cancel(struct kunit *test)
214 {
215 	struct drm_mock_sched_entity *entity;
216 	struct drm_mock_scheduler *sched;
217 	struct drm_mock_sched_job *job;
218 	bool done;
219 
220 	/*
221 	 * Check that drm_sched_fini() uses the cancel_job() callback to cancel
222 	 * jobs that are still pending.
223 	 */
224 
225 	sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
226 	entity = drm_mock_sched_entity_new(test, DRM_SCHED_PRIORITY_NORMAL,
227 					   sched);
228 
229 	job = drm_mock_sched_job_new(test, entity);
230 
231 	drm_mock_sched_job_submit(job);
232 
233 	done = drm_mock_sched_job_wait_scheduled(job, HZ);
234 	KUNIT_ASSERT_TRUE(test, done);
235 
236 	drm_mock_sched_entity_free(entity);
237 	drm_mock_sched_fini(sched);
238 
239 	KUNIT_ASSERT_EQ(test, job->hw_fence.error, -ECANCELED);
240 }
241 
242 struct sched_concurrent_context {
243 	struct drm_mock_scheduler *sched;
244 	struct workqueue_struct *sub_wq;
245 	struct kunit *test;
246 	struct completion wait_go;
247 };
248 
249 KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_fini_wrap, drm_mock_sched_fini,
250 			    struct drm_mock_scheduler *);
251 
252 KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_entity_free_wrap, drm_mock_sched_entity_free,
253 			    struct drm_mock_sched_entity *);
254 
255 static void complete_destroy_workqueue(void *context)
256 {
257 	struct sched_concurrent_context *ctx = context;
258 
259 	complete_all(&ctx->wait_go);
260 
261 	destroy_workqueue(ctx->sub_wq);
262 }
263 
264 static int drm_sched_concurrent_init(struct kunit *test)
265 {
266 	struct sched_concurrent_context *ctx;
267 	int ret;
268 
269 	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
270 	KUNIT_ASSERT_NOT_NULL(test, ctx);
271 
272 	init_completion(&ctx->wait_go);
273 
274 	ctx->sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
275 
276 	ret = kunit_add_action_or_reset(test, drm_mock_sched_fini_wrap, ctx->sched);
277 	KUNIT_ASSERT_EQ(test, ret, 0);
278 
279 	/* Use an unbounded workqueue to maximize job submission concurrency */
280 	ctx->sub_wq = alloc_workqueue("drm-sched-submitters-wq", WQ_UNBOUND,
281 				      WQ_UNBOUND_MAX_ACTIVE);
282 	KUNIT_ASSERT_NOT_NULL(test, ctx->sub_wq);
283 
284 	ret = kunit_add_action_or_reset(test, complete_destroy_workqueue, ctx);
285 	KUNIT_ASSERT_EQ(test, ret, 0);
286 
287 	ctx->test = test;
288 	test->priv = ctx;
289 
290 	return 0;
291 }
292 
293 struct drm_sched_parallel_params {
294 	const char *description;
295 	unsigned int num_jobs;
296 	unsigned int num_workers;
297 };
298 
299 static const struct drm_sched_parallel_params drm_sched_parallel_cases[] = {
300 	{
301 		.description = "Parallel submission of multiple jobs per worker",
302 		.num_jobs = 8,
303 		.num_workers = 16,
304 	},
305 };
306 
307 static void
308 drm_sched_parallel_desc(const struct drm_sched_parallel_params *params, char *desc)
309 {
310 	strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
311 }
312 
313 KUNIT_ARRAY_PARAM(drm_sched_parallel, drm_sched_parallel_cases, drm_sched_parallel_desc);
314 
315 struct parallel_worker {
316 	struct work_struct work;
317 	struct sched_concurrent_context *ctx;
318 	struct drm_mock_sched_entity *entity;
319 	struct drm_mock_sched_job **jobs;
320 	unsigned int id;
321 };
322 
323 static void drm_sched_parallel_worker(struct work_struct *work)
324 {
325 	const struct drm_sched_parallel_params *params;
326 	struct sched_concurrent_context *test_ctx;
327 	struct parallel_worker *worker;
328 	unsigned int i;
329 
330 	worker = container_of(work, struct parallel_worker, work);
331 	test_ctx = worker->ctx;
332 	params = test_ctx->test->param_value;
333 
334 	wait_for_completion(&test_ctx->wait_go);
335 
336 	kunit_info(test_ctx->test, "Parallel worker %u submitting %u jobs started\n",
337 		   worker->id, params->num_jobs);
338 
339 	for (i = 0; i < params->num_jobs; i++)
340 		drm_mock_sched_job_submit(worker->jobs[i]);
341 }
342 
343 /*
344  * Spawns workers that submit a sequence of jobs to the mock scheduler.
345  * Once all jobs are submitted, the timeline is manually advanced.
346  */
347 static void drm_sched_parallel_submit_test(struct kunit *test)
348 {
349 	struct sched_concurrent_context *ctx = test->priv;
350 	const struct drm_sched_parallel_params *params = test->param_value;
351 	struct parallel_worker *workers, *worker;
352 	struct drm_mock_sched_job *job;
353 	unsigned int i, j, completed_jobs, total_jobs;
354 	bool done;
355 	int ret;
356 
357 	KUNIT_ASSERT_GT(test, params->num_workers, 0);
358 	KUNIT_ASSERT_GT(test, params->num_jobs, 0);
359 
360 	workers = kunit_kcalloc(test, params->num_workers, sizeof(*workers),
361 				GFP_KERNEL);
362 	KUNIT_ASSERT_NOT_NULL(test, workers);
363 
364 	/*
365 	 * Init workers only after all jobs and entities have been successfully
366 	 * allocated. In this way, the cleanup logic for when an assertion fail
367 	 * can be simplified.
368 	 */
369 	for (i = 0; i < params->num_workers; i++) {
370 		worker = &workers[i];
371 		worker->id = i;
372 		worker->ctx = ctx;
373 		worker->entity = drm_mock_sched_entity_new(test,
374 							   DRM_SCHED_PRIORITY_NORMAL,
375 							   ctx->sched);
376 
377 		worker->jobs = kunit_kcalloc(test, params->num_jobs,
378 					     sizeof(*worker->jobs), GFP_KERNEL);
379 		KUNIT_ASSERT_NOT_NULL(test, worker->jobs);
380 
381 		for (j = 0; j < params->num_jobs; j++) {
382 			job = drm_mock_sched_job_new(test, worker->entity);
383 			worker->jobs[j] = job;
384 		}
385 
386 		ret = kunit_add_action_or_reset(test, drm_mock_sched_entity_free_wrap,
387 						worker->entity);
388 		KUNIT_ASSERT_EQ(test, ret, 0);
389 	}
390 
391 	for (i = 0; i < params->num_workers; i++) {
392 		worker = &workers[i];
393 		INIT_WORK(&worker->work, drm_sched_parallel_worker);
394 		queue_work(ctx->sub_wq, &worker->work);
395 	}
396 
397 	complete_all(&ctx->wait_go);
398 	flush_workqueue(ctx->sub_wq);
399 
400 	for (i = 0; i < params->num_workers; i++) {
401 		worker = &workers[i];
402 		for (j = 0; j < params->num_jobs; j++) {
403 			job = worker->jobs[j];
404 			done = drm_mock_sched_job_wait_scheduled(job, HZ);
405 			KUNIT_EXPECT_TRUE(test, done);
406 		}
407 	}
408 
409 	total_jobs = params->num_workers * params->num_jobs;
410 	completed_jobs = drm_mock_sched_advance(ctx->sched, total_jobs);
411 	KUNIT_EXPECT_EQ(test, completed_jobs, total_jobs);
412 
413 	for (i = 0; i < params->num_workers; i++) {
414 		worker = &workers[i];
415 		for (j = 0; j < params->num_jobs; j++) {
416 			job = worker->jobs[j];
417 			done = drm_mock_sched_job_wait_finished(job, HZ);
418 			KUNIT_EXPECT_TRUE(test, done);
419 		}
420 	}
421 }
422 
423 struct drm_sched_interleaved_params {
424 	const char *description;
425 	unsigned int test_duration_ms;
426 	unsigned int job_base_duration_us;
427 	unsigned int num_workers;
428 	unsigned int num_in_flight_jobs;
429 };
430 
431 static const struct drm_sched_interleaved_params drm_sched_interleaved_cases[] = {
432 	{
433 		.description = "Interleaved submission of multiple jobs per worker",
434 		.test_duration_ms = 1000,
435 		.job_base_duration_us = 100,
436 		.num_workers = 16,
437 		.num_in_flight_jobs = 8,
438 	},
439 };
440 
441 static void
442 drm_sched_interleaved_desc(const struct drm_sched_interleaved_params *params, char *desc)
443 {
444 	strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
445 }
446 
447 KUNIT_ARRAY_PARAM(drm_sched_interleaved, drm_sched_interleaved_cases,
448 		  drm_sched_interleaved_desc);
449 
450 struct interleaved_worker {
451 	struct work_struct work;
452 	struct sched_concurrent_context *ctx;
453 	struct drm_mock_sched_entity *entity;
454 	struct drm_mock_sched_job **jobs;
455 	unsigned int id;
456 	unsigned int job_count;
457 	unsigned int job_duration_us;
458 };
459 
460 static void drm_sched_interleaved_worker(struct work_struct *work)
461 {
462 	struct sched_concurrent_context *test_ctx;
463 	const struct drm_sched_interleaved_params *params;
464 	struct interleaved_worker *worker;
465 	unsigned int i, j, max_in_flight_job;
466 	unsigned long timeout;
467 	bool done;
468 
469 	worker = container_of(work, struct interleaved_worker, work);
470 	test_ctx = worker->ctx;
471 	params = test_ctx->test->param_value;
472 
473 	wait_for_completion(&test_ctx->wait_go);
474 
475 	kunit_info(test_ctx->test, "Worker %u submitting %u jobs of %u us started\n",
476 		   worker->id, worker->job_count, worker->job_duration_us);
477 
478 	timeout = msecs_to_jiffies(params->test_duration_ms * 2);
479 
480 	/* Fill the submission window */
481 	max_in_flight_job = min(worker->job_count, params->num_in_flight_jobs);
482 	for (i = 0; i < max_in_flight_job; i++)
483 		drm_mock_sched_job_submit(worker->jobs[i]);
484 
485 	/* Keep the window full by submitting a new job at once until done */
486 	for (i = 0; i < worker->job_count; i++) {
487 		done = drm_mock_sched_job_wait_finished(worker->jobs[i], timeout);
488 		if (!done)
489 			kunit_info(test_ctx->test, "Job %u of worker %u timed out\n",
490 				   i, worker->id);
491 
492 		j = i + max_in_flight_job;
493 		if (j < worker->job_count)
494 			drm_mock_sched_job_submit(worker->jobs[j]);
495 	}
496 }
497 
498 /*
499  * Spawns workers that submit a sequence of jobs to the mock scheduler. Job
500  * durations are chosen as multiples of a base duration value specified as
501  * a test parameter. Since the scheduler serializes jobs from all workers,
502  * the total test duration budget is divided into equal shares among workers.
503  * These shares are then used to compute the number of jobs that each worker
504  * can submit.
505  */
506 static void drm_sched_interleaved_submit_test(struct kunit *test)
507 {
508 	const struct drm_sched_interleaved_params *params = test->param_value;
509 	struct sched_concurrent_context *ctx = test->priv;
510 	struct interleaved_worker *workers, *worker;
511 	struct drm_mock_sched_job *job;
512 	unsigned int worker_share_us;
513 	unsigned int i, j;
514 	bool done;
515 	int ret;
516 
517 	KUNIT_ASSERT_GT(test, params->num_workers, 0);
518 	KUNIT_ASSERT_GT(test, params->job_base_duration_us, 0);
519 
520 	workers = kunit_kcalloc(test, params->num_workers, sizeof(*workers),
521 				GFP_KERNEL);
522 	KUNIT_ASSERT_NOT_NULL(test, workers);
523 
524 	/* Divide the available test time into equal shares among the workers */
525 	worker_share_us = (params->test_duration_ms * USEC_PER_MSEC) /
526 			  params->num_workers;
527 
528 	/*
529 	 * Init workers only after all jobs and entities have been successfully
530 	 * allocated. In this way, the cleanup logic for when an assertion fails
531 	 * can be simplified.
532 	 */
533 	for (i = 0; i < params->num_workers; i++) {
534 		worker = &workers[i];
535 		worker->id = i;
536 		worker->ctx = ctx;
537 
538 		worker->job_duration_us = params->job_base_duration_us * (i + 1);
539 		worker->job_count = worker_share_us / worker->job_duration_us;
540 		worker->job_count = max(1U, worker->job_count);
541 
542 		worker->entity = drm_mock_sched_entity_new(test,
543 							   DRM_SCHED_PRIORITY_NORMAL,
544 							   ctx->sched);
545 
546 		worker->jobs = kunit_kcalloc(test, worker->job_count,
547 					     sizeof(*worker->jobs), GFP_KERNEL);
548 		KUNIT_ASSERT_NOT_NULL(test, worker->jobs);
549 
550 		for (j = 0; j < worker->job_count; j++) {
551 			job = drm_mock_sched_job_new(test, worker->entity);
552 			drm_mock_sched_job_set_duration_us(job, worker->job_duration_us);
553 
554 			worker->jobs[j] = job;
555 		}
556 
557 		ret = kunit_add_action_or_reset(test, drm_mock_sched_entity_free_wrap,
558 						worker->entity);
559 		KUNIT_ASSERT_EQ(test, ret, 0);
560 	}
561 
562 	for (i = 0; i < params->num_workers; i++) {
563 		worker = &workers[i];
564 		INIT_WORK(&worker->work, drm_sched_interleaved_worker);
565 		queue_work(ctx->sub_wq, &worker->work);
566 	}
567 
568 	complete_all(&ctx->wait_go);
569 	flush_workqueue(ctx->sub_wq);
570 
571 	for (i = 0; i < params->num_workers; i++) {
572 		worker = &workers[i];
573 		for (j = 0; j < worker->job_count; j++) {
574 			job = worker->jobs[j];
575 			done = drm_mock_sched_job_is_finished(job);
576 			KUNIT_EXPECT_TRUE(test, done);
577 		}
578 	}
579 }
580 
581 static struct kunit_case drm_sched_concurrent_tests[] = {
582 	KUNIT_CASE_PARAM(drm_sched_parallel_submit_test, drm_sched_parallel_gen_params),
583 	KUNIT_CASE_PARAM(drm_sched_interleaved_submit_test, drm_sched_interleaved_gen_params),
584 	{}
585 };
586 
587 static struct kunit_suite drm_sched_concurrent = {
588 	.name = "drm_sched_concurrent_tests",
589 	.init = drm_sched_concurrent_init,
590 	.test_cases = drm_sched_concurrent_tests,
591 	.attr = {
592 		.speed = KUNIT_SPEED_SLOW,
593 	},
594 };
595 
596 static struct kunit_case drm_sched_cancel_tests[] = {
597 	KUNIT_CASE(drm_sched_basic_cancel),
598 	{}
599 };
600 
601 static struct kunit_suite drm_sched_cancel = {
602 	.name = "drm_sched_basic_cancel_tests",
603 	.init = drm_sched_basic_init,
604 	.exit = drm_sched_basic_exit,
605 	.test_cases = drm_sched_cancel_tests,
606 };
607 
608 static void drm_sched_basic_timeout(struct kunit *test)
609 {
610 	struct drm_mock_scheduler *sched = test->priv;
611 	struct drm_mock_sched_entity *entity;
612 	struct drm_mock_sched_job *job;
613 	bool done;
614 
615 	/*
616 	 * Submit a single job against a scheduler with the timeout configured
617 	 * and verify that the timeout handling will run if the backend fails
618 	 * to complete it in time.
619 	 */
620 
621 	entity = drm_mock_sched_entity_new(test,
622 					   DRM_SCHED_PRIORITY_NORMAL,
623 					   sched);
624 	job = drm_mock_sched_job_new(test, entity);
625 
626 	drm_mock_sched_job_submit(job);
627 
628 	done = drm_mock_sched_job_wait_scheduled(job, HZ);
629 	KUNIT_ASSERT_TRUE(test, done);
630 
631 	done = drm_mock_sched_job_wait_finished(job, MOCK_TIMEOUT / 2);
632 	KUNIT_ASSERT_FALSE(test, done);
633 
634 	KUNIT_ASSERT_EQ(test,
635 			job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT,
636 			0);
637 
638 	done = drm_mock_sched_job_wait_finished(job, MOCK_TIMEOUT);
639 	KUNIT_ASSERT_FALSE(test, done);
640 
641 	KUNIT_ASSERT_EQ(test,
642 			job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT,
643 			DRM_MOCK_SCHED_JOB_TIMEDOUT);
644 
645 	drm_mock_sched_entity_free(entity);
646 }
647 
648 static void drm_sched_skip_reset(struct kunit *test)
649 {
650 	struct drm_mock_scheduler *sched = test->priv;
651 	struct drm_mock_sched_entity *entity;
652 	struct drm_mock_sched_job *job;
653 	unsigned int i;
654 	bool done;
655 
656 	/*
657 	 * Submit a single job against a scheduler with the timeout configured
658 	 * and verify that if the job is still running, the timeout handler
659 	 * will skip the reset and allow the job to complete.
660 	 */
661 
662 	entity = drm_mock_sched_entity_new(test,
663 					   DRM_SCHED_PRIORITY_NORMAL,
664 					   sched);
665 	job = drm_mock_sched_job_new(test, entity);
666 
667 	job->flags = DRM_MOCK_SCHED_JOB_DONT_RESET;
668 
669 	drm_mock_sched_job_submit(job);
670 
671 	done = drm_mock_sched_job_wait_scheduled(job, HZ);
672 	KUNIT_ASSERT_TRUE(test, done);
673 
674 	done = drm_mock_sched_job_wait_finished(job, 2 * MOCK_TIMEOUT);
675 	KUNIT_ASSERT_FALSE(test, done);
676 
677 	KUNIT_ASSERT_EQ(test,
678 			job->flags & DRM_MOCK_SCHED_JOB_RESET_SKIPPED,
679 			DRM_MOCK_SCHED_JOB_RESET_SKIPPED);
680 
681 	i = drm_mock_sched_advance(sched, 1);
682 	KUNIT_ASSERT_EQ(test, i, 1);
683 
684 	done = drm_mock_sched_job_wait_finished(job, HZ);
685 	KUNIT_ASSERT_TRUE(test, done);
686 
687 	drm_mock_sched_entity_free(entity);
688 }
689 
690 static struct kunit_case drm_sched_timeout_tests[] = {
691 	KUNIT_CASE(drm_sched_basic_timeout),
692 	KUNIT_CASE(drm_sched_skip_reset),
693 	{}
694 };
695 
696 static struct kunit_suite drm_sched_timeout = {
697 	.name = "drm_sched_basic_timeout_tests",
698 	.init = drm_sched_timeout_init,
699 	.exit = drm_sched_basic_exit,
700 	.test_cases = drm_sched_timeout_tests,
701 };
702 
703 static void drm_sched_priorities(struct kunit *test)
704 {
705 	struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
706 	struct drm_mock_scheduler *sched = test->priv;
707 	struct drm_mock_sched_job *job;
708 	const unsigned int qd = 100;
709 	unsigned int i, cur_ent = 0;
710 	enum drm_sched_priority p;
711 	bool done;
712 
713 	/*
714 	 * Submit a bunch of jobs against entities configured with different
715 	 * priorities.
716 	 */
717 
718 	BUILD_BUG_ON(DRM_SCHED_PRIORITY_KERNEL > DRM_SCHED_PRIORITY_LOW);
719 	BUILD_BUG_ON(ARRAY_SIZE(entity) != DRM_SCHED_PRIORITY_COUNT);
720 
721 	for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
722 		entity[p] = drm_mock_sched_entity_new(test, p, sched);
723 
724 	for (i = 0; i < qd; i++) {
725 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
726 		cur_ent %= ARRAY_SIZE(entity);
727 		drm_mock_sched_job_set_duration_us(job, 1000);
728 		drm_mock_sched_job_submit(job);
729 	}
730 
731 	done = drm_mock_sched_job_wait_finished(job, HZ);
732 	KUNIT_ASSERT_TRUE(test, done);
733 
734 	for (i = 0; i < ARRAY_SIZE(entity); i++)
735 		drm_mock_sched_entity_free(entity[i]);
736 }
737 
738 static void drm_sched_change_priority(struct kunit *test)
739 {
740 	struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
741 	struct drm_mock_scheduler *sched = test->priv;
742 	struct drm_mock_sched_job *job;
743 	const unsigned int qd = 1000;
744 	unsigned int i, cur_ent = 0;
745 	enum drm_sched_priority p;
746 
747 	/*
748 	 * Submit a bunch of jobs against entities configured with different
749 	 * priorities and while waiting for them to complete, periodically keep
750 	 * changing their priorities.
751 	 *
752 	 * We set up the queue-depth (qd) and job duration so the priority
753 	 * changing loop has some time to interact with submissions to the
754 	 * backend and job completions as they progress.
755 	 */
756 
757 	for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
758 		entity[p] = drm_mock_sched_entity_new(test, p, sched);
759 
760 	for (i = 0; i < qd; i++) {
761 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
762 		cur_ent %= ARRAY_SIZE(entity);
763 		drm_mock_sched_job_set_duration_us(job, 1000);
764 		drm_mock_sched_job_submit(job);
765 	}
766 
767 	do {
768 		drm_sched_entity_set_priority(&entity[cur_ent]->base,
769 					      (entity[cur_ent]->base.priority + 1) %
770 					      DRM_SCHED_PRIORITY_COUNT);
771 		cur_ent++;
772 		cur_ent %= ARRAY_SIZE(entity);
773 		usleep_range(200, 500);
774 	} while (!drm_mock_sched_job_is_finished(job));
775 
776 	for (i = 0; i < ARRAY_SIZE(entity); i++)
777 		drm_mock_sched_entity_free(entity[i]);
778 }
779 
780 static struct kunit_case drm_sched_priority_tests[] = {
781 	KUNIT_CASE(drm_sched_priorities),
782 	KUNIT_CASE_SLOW(drm_sched_change_priority),
783 	{}
784 };
785 
786 static struct kunit_suite drm_sched_priority = {
787 	.name = "drm_sched_basic_priority_tests",
788 	.init = drm_sched_basic_init,
789 	.exit = drm_sched_basic_exit,
790 	.test_cases = drm_sched_priority_tests,
791 };
792 
793 static void drm_sched_test_modify_sched(struct kunit *test)
794 {
795 	unsigned int i, cur_ent = 0, cur_sched = 0;
796 	struct drm_mock_sched_entity *entity[13];
797 	struct drm_mock_scheduler *sched[3];
798 	struct drm_mock_sched_job *job;
799 	const unsigned int qd = 1000;
800 
801 	/*
802 	 * Submit a bunch of jobs against entities configured with different
803 	 * schedulers and while waiting for them to complete, periodically keep
804 	 * changing schedulers associated with each entity.
805 	 *
806 	 * We set up the queue-depth (qd) and job duration so the sched modify
807 	 * loop has some time to interact with submissions to the backend and
808 	 * job completions as they progress.
809 	 *
810 	 * For the number of schedulers and entities we use primes in order to
811 	 * perturb the entity->sched assignments with less of a regular pattern.
812 	 */
813 
814 	for (i = 0; i < ARRAY_SIZE(sched); i++)
815 		sched[i] = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
816 
817 	for (i = 0; i < ARRAY_SIZE(entity); i++)
818 		entity[i] = drm_mock_sched_entity_new(test,
819 						      DRM_SCHED_PRIORITY_NORMAL,
820 						      sched[i % ARRAY_SIZE(sched)]);
821 
822 	for (i = 0; i < qd; i++) {
823 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
824 		cur_ent %= ARRAY_SIZE(entity);
825 		drm_mock_sched_job_set_duration_us(job, 1000);
826 		drm_mock_sched_job_submit(job);
827 	}
828 
829 	do {
830 		struct drm_gpu_scheduler *modify;
831 
832 		usleep_range(200, 500);
833 		cur_ent++;
834 		cur_ent %= ARRAY_SIZE(entity);
835 		cur_sched++;
836 		cur_sched %= ARRAY_SIZE(sched);
837 		modify = &sched[cur_sched]->base;
838 		drm_sched_entity_modify_sched(&entity[cur_ent]->base, &modify,
839 					      1);
840 	} while (!drm_mock_sched_job_is_finished(job));
841 
842 	for (i = 0; i < ARRAY_SIZE(entity); i++)
843 		drm_mock_sched_entity_free(entity[i]);
844 
845 	for (i = 0; i < ARRAY_SIZE(sched); i++)
846 		drm_mock_sched_fini(sched[i]);
847 }
848 
849 static struct kunit_case drm_sched_modify_sched_tests[] = {
850 	KUNIT_CASE(drm_sched_test_modify_sched),
851 	{}
852 };
853 
854 static struct kunit_suite drm_sched_modify_sched = {
855 	.name = "drm_sched_basic_modify_sched_tests",
856 	.test_cases = drm_sched_modify_sched_tests,
857 };
858 
859 static void drm_sched_test_credits(struct kunit *test)
860 {
861 	struct drm_mock_sched_entity *entity;
862 	struct drm_mock_scheduler *sched;
863 	struct drm_mock_sched_job *job[2];
864 	bool done;
865 	int i;
866 
867 	/*
868 	 * Check that the configured credit limit is respected.
869 	 */
870 
871 	sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
872 	sched->base.credit_limit = 1;
873 
874 	entity = drm_mock_sched_entity_new(test,
875 					   DRM_SCHED_PRIORITY_NORMAL,
876 					   sched);
877 
878 	job[0] = drm_mock_sched_job_new(test, entity);
879 	job[1] = drm_mock_sched_job_new(test, entity);
880 
881 	drm_mock_sched_job_submit(job[0]);
882 	drm_mock_sched_job_submit(job[1]);
883 
884 	done = drm_mock_sched_job_wait_scheduled(job[0], HZ);
885 	KUNIT_ASSERT_TRUE(test, done);
886 
887 	done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
888 	KUNIT_ASSERT_FALSE(test, done);
889 
890 	i = drm_mock_sched_advance(sched, 1);
891 	KUNIT_ASSERT_EQ(test, i, 1);
892 
893 	done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
894 	KUNIT_ASSERT_TRUE(test, done);
895 
896 	i = drm_mock_sched_advance(sched, 1);
897 	KUNIT_ASSERT_EQ(test, i, 1);
898 
899 	done = drm_mock_sched_job_wait_finished(job[1], HZ);
900 	KUNIT_ASSERT_TRUE(test, done);
901 
902 	drm_mock_sched_entity_free(entity);
903 	drm_mock_sched_fini(sched);
904 }
905 
906 static struct kunit_case drm_sched_credits_tests[] = {
907 	KUNIT_CASE_SLOW(drm_sched_test_credits),
908 	{}
909 };
910 
911 static struct kunit_suite drm_sched_credits = {
912 	.name = "drm_sched_basic_credits_tests",
913 	.test_cases = drm_sched_credits_tests,
914 };
915 
916 kunit_test_suites(&drm_sched_basic,
917 		  &drm_sched_concurrent,
918 		  &drm_sched_timeout,
919 		  &drm_sched_cancel,
920 		  &drm_sched_priority,
921 		  &drm_sched_modify_sched,
922 		  &drm_sched_credits);
923