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