xref: /linux/drivers/gpu/drm/scheduler/tests/tests_basic.c (revision 220994d61cebfc04f071d69049127657c7e8191b)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2025 Valve Corporation */
3 
4 #include <linux/delay.h>
5 
6 #include "sched_tests.h"
7 
8 #define MOCK_TIMEOUT (HZ / 5)
9 
10 /*
11  * DRM scheduler basic tests should check the basic functional correctness of
12  * the scheduler, including some very light smoke testing. More targeted tests,
13  * for example focusing on testing specific bugs and other more complicated test
14  * scenarios, should be implemented in separate source units.
15  */
16 
drm_sched_basic_init(struct kunit * test)17 static int drm_sched_basic_init(struct kunit *test)
18 {
19 	test->priv = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
20 
21 	return 0;
22 }
23 
drm_sched_basic_exit(struct kunit * test)24 static void drm_sched_basic_exit(struct kunit *test)
25 {
26 	struct drm_mock_scheduler *sched = test->priv;
27 
28 	drm_mock_sched_fini(sched);
29 }
30 
drm_sched_timeout_init(struct kunit * test)31 static int drm_sched_timeout_init(struct kunit *test)
32 {
33 	test->priv = drm_mock_sched_new(test, MOCK_TIMEOUT);
34 
35 	return 0;
36 }
37 
drm_sched_basic_submit(struct kunit * test)38 static void drm_sched_basic_submit(struct kunit *test)
39 {
40 	struct drm_mock_scheduler *sched = test->priv;
41 	struct drm_mock_sched_entity *entity;
42 	struct drm_mock_sched_job *job;
43 	unsigned int i;
44 	bool done;
45 
46 	/*
47 	 * Submit one job to the scheduler and verify that it gets scheduled
48 	 * and completed only when the mock hw backend processes it.
49 	 */
50 
51 	entity = drm_mock_sched_entity_new(test,
52 					   DRM_SCHED_PRIORITY_NORMAL,
53 					   sched);
54 	job = drm_mock_sched_job_new(test, entity);
55 
56 	drm_mock_sched_job_submit(job);
57 
58 	done = drm_mock_sched_job_wait_scheduled(job, HZ);
59 	KUNIT_ASSERT_TRUE(test, done);
60 
61 	done = drm_mock_sched_job_wait_finished(job, HZ / 2);
62 	KUNIT_ASSERT_FALSE(test, done);
63 
64 	i = drm_mock_sched_advance(sched, 1);
65 	KUNIT_ASSERT_EQ(test, i, 1);
66 
67 	done = drm_mock_sched_job_wait_finished(job, HZ);
68 	KUNIT_ASSERT_TRUE(test, done);
69 
70 	drm_mock_sched_entity_free(entity);
71 }
72 
73 struct drm_sched_basic_params {
74 	const char *description;
75 	unsigned int queue_depth;
76 	unsigned int num_entities;
77 	unsigned int job_us;
78 	bool dep_chain;
79 };
80 
81 static const struct drm_sched_basic_params drm_sched_basic_cases[] = {
82 	{
83 		.description = "A queue of jobs in a single entity",
84 		.queue_depth = 100,
85 		.job_us = 1000,
86 		.num_entities = 1,
87 	},
88 	{
89 		.description = "A chain of dependent jobs across multiple entities",
90 		.queue_depth = 100,
91 		.job_us = 1000,
92 		.num_entities = 1,
93 		.dep_chain = true,
94 	},
95 	{
96 		.description = "Multiple independent job queues",
97 		.queue_depth = 100,
98 		.job_us = 1000,
99 		.num_entities = 4,
100 	},
101 	{
102 		.description = "Multiple inter-dependent job queues",
103 		.queue_depth = 100,
104 		.job_us = 1000,
105 		.num_entities = 4,
106 		.dep_chain = true,
107 	},
108 };
109 
110 static void
drm_sched_basic_desc(const struct drm_sched_basic_params * params,char * desc)111 drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc)
112 {
113 	strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
114 }
115 
116 KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc);
117 
drm_sched_basic_test(struct kunit * test)118 static void drm_sched_basic_test(struct kunit *test)
119 {
120 	const struct drm_sched_basic_params *params = test->param_value;
121 	struct drm_mock_scheduler *sched = test->priv;
122 	struct drm_mock_sched_job *job, *prev = NULL;
123 	struct drm_mock_sched_entity **entity;
124 	unsigned int i, cur_ent = 0;
125 	bool done;
126 
127 	entity = kunit_kcalloc(test, params->num_entities, sizeof(*entity),
128 			       GFP_KERNEL);
129 	KUNIT_ASSERT_NOT_NULL(test, entity);
130 
131 	for (i = 0; i < params->num_entities; i++)
132 		entity[i] = drm_mock_sched_entity_new(test,
133 						      DRM_SCHED_PRIORITY_NORMAL,
134 						      sched);
135 
136 	for (i = 0; i < params->queue_depth; i++) {
137 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
138 		cur_ent %= params->num_entities;
139 		drm_mock_sched_job_set_duration_us(job, params->job_us);
140 		if (params->dep_chain && prev)
141 			drm_sched_job_add_dependency(&job->base,
142 						     dma_fence_get(&prev->base.s_fence->finished));
143 		drm_mock_sched_job_submit(job);
144 		prev = job;
145 	}
146 
147 	done = drm_mock_sched_job_wait_finished(job, HZ);
148 	KUNIT_ASSERT_TRUE(test, done);
149 
150 	for (i = 0; i < params->num_entities; i++)
151 		drm_mock_sched_entity_free(entity[i]);
152 }
153 
drm_sched_basic_entity_cleanup(struct kunit * test)154 static void drm_sched_basic_entity_cleanup(struct kunit *test)
155 {
156 	struct drm_mock_sched_job *job, *mid, *prev = NULL;
157 	struct drm_mock_scheduler *sched = test->priv;
158 	struct drm_mock_sched_entity *entity[4];
159 	const unsigned int qd = 100;
160 	unsigned int i, cur_ent = 0;
161 	bool done;
162 
163 	/*
164 	 * Submit a queue of jobs across different entities with an explicit
165 	 * chain of dependencies between them and trigger entity cleanup while
166 	 * the queue is still being processed.
167 	 */
168 
169 	for (i = 0; i < ARRAY_SIZE(entity); i++)
170 		entity[i] = drm_mock_sched_entity_new(test,
171 						      DRM_SCHED_PRIORITY_NORMAL,
172 						      sched);
173 
174 	for (i = 0; i < qd; i++) {
175 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
176 		cur_ent %= ARRAY_SIZE(entity);
177 		drm_mock_sched_job_set_duration_us(job, 1000);
178 		if (prev)
179 			drm_sched_job_add_dependency(&job->base,
180 						     dma_fence_get(&prev->base.s_fence->finished));
181 		drm_mock_sched_job_submit(job);
182 		if (i == qd / 2)
183 			mid = job;
184 		prev = job;
185 	}
186 
187 	done = drm_mock_sched_job_wait_finished(mid, HZ);
188 	KUNIT_ASSERT_TRUE(test, done);
189 
190 	/* Exit with half of the queue still pending to be executed. */
191 	for (i = 0; i < ARRAY_SIZE(entity); i++)
192 		drm_mock_sched_entity_free(entity[i]);
193 }
194 
195 static struct kunit_case drm_sched_basic_tests[] = {
196 	KUNIT_CASE(drm_sched_basic_submit),
197 	KUNIT_CASE_PARAM(drm_sched_basic_test, drm_sched_basic_gen_params),
198 	KUNIT_CASE(drm_sched_basic_entity_cleanup),
199 	{}
200 };
201 
202 static struct kunit_suite drm_sched_basic = {
203 	.name = "drm_sched_basic_tests",
204 	.init = drm_sched_basic_init,
205 	.exit = drm_sched_basic_exit,
206 	.test_cases = drm_sched_basic_tests,
207 };
208 
drm_sched_basic_cancel(struct kunit * test)209 static void drm_sched_basic_cancel(struct kunit *test)
210 {
211 	struct drm_mock_sched_entity *entity;
212 	struct drm_mock_scheduler *sched;
213 	struct drm_mock_sched_job *job;
214 	bool done;
215 
216 	/*
217 	 * Check that drm_sched_fini() uses the cancel_job() callback to cancel
218 	 * jobs that are still pending.
219 	 */
220 
221 	sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
222 	entity = drm_mock_sched_entity_new(test, DRM_SCHED_PRIORITY_NORMAL,
223 					   sched);
224 
225 	job = drm_mock_sched_job_new(test, entity);
226 
227 	drm_mock_sched_job_submit(job);
228 
229 	done = drm_mock_sched_job_wait_scheduled(job, HZ);
230 	KUNIT_ASSERT_TRUE(test, done);
231 
232 	drm_mock_sched_entity_free(entity);
233 	drm_mock_sched_fini(sched);
234 
235 	KUNIT_ASSERT_EQ(test, job->hw_fence.error, -ECANCELED);
236 }
237 
238 static struct kunit_case drm_sched_cancel_tests[] = {
239 	KUNIT_CASE(drm_sched_basic_cancel),
240 	{}
241 };
242 
243 static struct kunit_suite drm_sched_cancel = {
244 	.name = "drm_sched_basic_cancel_tests",
245 	.init = drm_sched_basic_init,
246 	.exit = drm_sched_basic_exit,
247 	.test_cases = drm_sched_cancel_tests,
248 };
249 
drm_sched_basic_timeout(struct kunit * test)250 static void drm_sched_basic_timeout(struct kunit *test)
251 {
252 	struct drm_mock_scheduler *sched = test->priv;
253 	struct drm_mock_sched_entity *entity;
254 	struct drm_mock_sched_job *job;
255 	bool done;
256 
257 	/*
258 	 * Submit a single job against a scheduler with the timeout configured
259 	 * and verify that the timeout handling will run if the backend fails
260 	 * to complete it in time.
261 	 */
262 
263 	entity = drm_mock_sched_entity_new(test,
264 					   DRM_SCHED_PRIORITY_NORMAL,
265 					   sched);
266 	job = drm_mock_sched_job_new(test, entity);
267 
268 	drm_mock_sched_job_submit(job);
269 
270 	done = drm_mock_sched_job_wait_scheduled(job, HZ);
271 	KUNIT_ASSERT_TRUE(test, done);
272 
273 	done = drm_mock_sched_job_wait_finished(job, MOCK_TIMEOUT / 2);
274 	KUNIT_ASSERT_FALSE(test, done);
275 
276 	KUNIT_ASSERT_EQ(test,
277 			job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT,
278 			0);
279 
280 	done = drm_mock_sched_job_wait_finished(job, MOCK_TIMEOUT);
281 	KUNIT_ASSERT_FALSE(test, done);
282 
283 	KUNIT_ASSERT_EQ(test,
284 			job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT,
285 			DRM_MOCK_SCHED_JOB_TIMEDOUT);
286 
287 	drm_mock_sched_entity_free(entity);
288 }
289 
drm_sched_skip_reset(struct kunit * test)290 static void drm_sched_skip_reset(struct kunit *test)
291 {
292 	struct drm_mock_scheduler *sched = test->priv;
293 	struct drm_mock_sched_entity *entity;
294 	struct drm_mock_sched_job *job;
295 	unsigned int i;
296 	bool done;
297 
298 	/*
299 	 * Submit a single job against a scheduler with the timeout configured
300 	 * and verify that if the job is still running, the timeout handler
301 	 * will skip the reset and allow the job to complete.
302 	 */
303 
304 	entity = drm_mock_sched_entity_new(test,
305 					   DRM_SCHED_PRIORITY_NORMAL,
306 					   sched);
307 	job = drm_mock_sched_job_new(test, entity);
308 
309 	job->flags = DRM_MOCK_SCHED_JOB_DONT_RESET;
310 
311 	drm_mock_sched_job_submit(job);
312 
313 	done = drm_mock_sched_job_wait_scheduled(job, HZ);
314 	KUNIT_ASSERT_TRUE(test, done);
315 
316 	done = drm_mock_sched_job_wait_finished(job, 2 * MOCK_TIMEOUT);
317 	KUNIT_ASSERT_FALSE(test, done);
318 
319 	KUNIT_ASSERT_EQ(test,
320 			job->flags & DRM_MOCK_SCHED_JOB_DONT_RESET,
321 			0);
322 
323 	i = drm_mock_sched_advance(sched, 1);
324 	KUNIT_ASSERT_EQ(test, i, 1);
325 
326 	done = drm_mock_sched_job_wait_finished(job, HZ);
327 	KUNIT_ASSERT_TRUE(test, done);
328 
329 	drm_mock_sched_entity_free(entity);
330 }
331 
332 static struct kunit_case drm_sched_timeout_tests[] = {
333 	KUNIT_CASE(drm_sched_basic_timeout),
334 	KUNIT_CASE(drm_sched_skip_reset),
335 	{}
336 };
337 
338 static struct kunit_suite drm_sched_timeout = {
339 	.name = "drm_sched_basic_timeout_tests",
340 	.init = drm_sched_timeout_init,
341 	.exit = drm_sched_basic_exit,
342 	.test_cases = drm_sched_timeout_tests,
343 };
344 
drm_sched_priorities(struct kunit * test)345 static void drm_sched_priorities(struct kunit *test)
346 {
347 	struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
348 	struct drm_mock_scheduler *sched = test->priv;
349 	struct drm_mock_sched_job *job;
350 	const unsigned int qd = 100;
351 	unsigned int i, cur_ent = 0;
352 	enum drm_sched_priority p;
353 	bool done;
354 
355 	/*
356 	 * Submit a bunch of jobs against entities configured with different
357 	 * priorities.
358 	 */
359 
360 	BUILD_BUG_ON(DRM_SCHED_PRIORITY_KERNEL > DRM_SCHED_PRIORITY_LOW);
361 	BUILD_BUG_ON(ARRAY_SIZE(entity) != DRM_SCHED_PRIORITY_COUNT);
362 
363 	for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
364 		entity[p] = drm_mock_sched_entity_new(test, p, sched);
365 
366 	for (i = 0; i < qd; i++) {
367 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
368 		cur_ent %= ARRAY_SIZE(entity);
369 		drm_mock_sched_job_set_duration_us(job, 1000);
370 		drm_mock_sched_job_submit(job);
371 	}
372 
373 	done = drm_mock_sched_job_wait_finished(job, HZ);
374 	KUNIT_ASSERT_TRUE(test, done);
375 
376 	for (i = 0; i < ARRAY_SIZE(entity); i++)
377 		drm_mock_sched_entity_free(entity[i]);
378 }
379 
drm_sched_change_priority(struct kunit * test)380 static void drm_sched_change_priority(struct kunit *test)
381 {
382 	struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
383 	struct drm_mock_scheduler *sched = test->priv;
384 	struct drm_mock_sched_job *job;
385 	const unsigned int qd = 1000;
386 	unsigned int i, cur_ent = 0;
387 	enum drm_sched_priority p;
388 
389 	/*
390 	 * Submit a bunch of jobs against entities configured with different
391 	 * priorities and while waiting for them to complete, periodically keep
392 	 * changing their priorities.
393 	 *
394 	 * We set up the queue-depth (qd) and job duration so the priority
395 	 * changing loop has some time to interact with submissions to the
396 	 * backend and job completions as they progress.
397 	 */
398 
399 	for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
400 		entity[p] = drm_mock_sched_entity_new(test, p, sched);
401 
402 	for (i = 0; i < qd; i++) {
403 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
404 		cur_ent %= ARRAY_SIZE(entity);
405 		drm_mock_sched_job_set_duration_us(job, 1000);
406 		drm_mock_sched_job_submit(job);
407 	}
408 
409 	do {
410 		drm_sched_entity_set_priority(&entity[cur_ent]->base,
411 					      (entity[cur_ent]->base.priority + 1) %
412 					      DRM_SCHED_PRIORITY_COUNT);
413 		cur_ent++;
414 		cur_ent %= ARRAY_SIZE(entity);
415 		usleep_range(200, 500);
416 	} while (!drm_mock_sched_job_is_finished(job));
417 
418 	for (i = 0; i < ARRAY_SIZE(entity); i++)
419 		drm_mock_sched_entity_free(entity[i]);
420 }
421 
422 static struct kunit_case drm_sched_priority_tests[] = {
423 	KUNIT_CASE(drm_sched_priorities),
424 	KUNIT_CASE(drm_sched_change_priority),
425 	{}
426 };
427 
428 static struct kunit_suite drm_sched_priority = {
429 	.name = "drm_sched_basic_priority_tests",
430 	.init = drm_sched_basic_init,
431 	.exit = drm_sched_basic_exit,
432 	.test_cases = drm_sched_priority_tests,
433 };
434 
drm_sched_test_modify_sched(struct kunit * test)435 static void drm_sched_test_modify_sched(struct kunit *test)
436 {
437 	unsigned int i, cur_ent = 0, cur_sched = 0;
438 	struct drm_mock_sched_entity *entity[13];
439 	struct drm_mock_scheduler *sched[3];
440 	struct drm_mock_sched_job *job;
441 	const unsigned int qd = 1000;
442 
443 	/*
444 	 * Submit a bunch of jobs against entities configured with different
445 	 * schedulers and while waiting for them to complete, periodically keep
446 	 * changing schedulers associated with each entity.
447 	 *
448 	 * We set up the queue-depth (qd) and job duration so the sched modify
449 	 * loop has some time to interact with submissions to the backend and
450 	 * job completions as they progress.
451 	 *
452 	 * For the number of schedulers and entities we use primes in order to
453 	 * perturb the entity->sched assignments with less of a regular pattern.
454 	 */
455 
456 	for (i = 0; i < ARRAY_SIZE(sched); i++)
457 		sched[i] = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
458 
459 	for (i = 0; i < ARRAY_SIZE(entity); i++)
460 		entity[i] = drm_mock_sched_entity_new(test,
461 						      DRM_SCHED_PRIORITY_NORMAL,
462 						      sched[i % ARRAY_SIZE(sched)]);
463 
464 	for (i = 0; i < qd; i++) {
465 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
466 		cur_ent %= ARRAY_SIZE(entity);
467 		drm_mock_sched_job_set_duration_us(job, 1000);
468 		drm_mock_sched_job_submit(job);
469 	}
470 
471 	do {
472 		struct drm_gpu_scheduler *modify;
473 
474 		usleep_range(200, 500);
475 		cur_ent++;
476 		cur_ent %= ARRAY_SIZE(entity);
477 		cur_sched++;
478 		cur_sched %= ARRAY_SIZE(sched);
479 		modify = &sched[cur_sched]->base;
480 		drm_sched_entity_modify_sched(&entity[cur_ent]->base, &modify,
481 					      1);
482 	} while (!drm_mock_sched_job_is_finished(job));
483 
484 	for (i = 0; i < ARRAY_SIZE(entity); i++)
485 		drm_mock_sched_entity_free(entity[i]);
486 
487 	for (i = 0; i < ARRAY_SIZE(sched); i++)
488 		drm_mock_sched_fini(sched[i]);
489 }
490 
491 static struct kunit_case drm_sched_modify_sched_tests[] = {
492 	KUNIT_CASE(drm_sched_test_modify_sched),
493 	{}
494 };
495 
496 static struct kunit_suite drm_sched_modify_sched = {
497 	.name = "drm_sched_basic_modify_sched_tests",
498 	.test_cases = drm_sched_modify_sched_tests,
499 };
500 
drm_sched_test_credits(struct kunit * test)501 static void drm_sched_test_credits(struct kunit *test)
502 {
503 	struct drm_mock_sched_entity *entity;
504 	struct drm_mock_scheduler *sched;
505 	struct drm_mock_sched_job *job[2];
506 	bool done;
507 	int i;
508 
509 	/*
510 	 * Check that the configured credit limit is respected.
511 	 */
512 
513 	sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
514 	sched->base.credit_limit = 1;
515 
516 	entity = drm_mock_sched_entity_new(test,
517 					   DRM_SCHED_PRIORITY_NORMAL,
518 					   sched);
519 
520 	job[0] = drm_mock_sched_job_new(test, entity);
521 	job[1] = drm_mock_sched_job_new(test, entity);
522 
523 	drm_mock_sched_job_submit(job[0]);
524 	drm_mock_sched_job_submit(job[1]);
525 
526 	done = drm_mock_sched_job_wait_scheduled(job[0], HZ);
527 	KUNIT_ASSERT_TRUE(test, done);
528 
529 	done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
530 	KUNIT_ASSERT_FALSE(test, done);
531 
532 	i = drm_mock_sched_advance(sched, 1);
533 	KUNIT_ASSERT_EQ(test, i, 1);
534 
535 	done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
536 	KUNIT_ASSERT_TRUE(test, done);
537 
538 	i = drm_mock_sched_advance(sched, 1);
539 	KUNIT_ASSERT_EQ(test, i, 1);
540 
541 	done = drm_mock_sched_job_wait_finished(job[1], HZ);
542 	KUNIT_ASSERT_TRUE(test, done);
543 
544 	drm_mock_sched_entity_free(entity);
545 	drm_mock_sched_fini(sched);
546 }
547 
548 static struct kunit_case drm_sched_credits_tests[] = {
549 	KUNIT_CASE(drm_sched_test_credits),
550 	{}
551 };
552 
553 static struct kunit_suite drm_sched_credits = {
554 	.name = "drm_sched_basic_credits_tests",
555 	.test_cases = drm_sched_credits_tests,
556 };
557 
558 kunit_test_suites(&drm_sched_basic,
559 		  &drm_sched_timeout,
560 		  &drm_sched_cancel,
561 		  &drm_sched_priority,
562 		  &drm_sched_modify_sched,
563 		  &drm_sched_credits);
564