xref: /linux/drivers/gpu/drm/scheduler/tests/tests_basic.c (revision 9b9b5a3605b9a5ef1d412e47b2ae70090c8d3580)
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 
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 
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 
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 
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
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 
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 
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 
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 
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 
290 static struct kunit_case drm_sched_timeout_tests[] = {
291 	KUNIT_CASE(drm_sched_basic_timeout),
292 	{}
293 };
294 
295 static struct kunit_suite drm_sched_timeout = {
296 	.name = "drm_sched_basic_timeout_tests",
297 	.init = drm_sched_timeout_init,
298 	.exit = drm_sched_basic_exit,
299 	.test_cases = drm_sched_timeout_tests,
300 };
301 
302 static void drm_sched_priorities(struct kunit *test)
303 {
304 	struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
305 	struct drm_mock_scheduler *sched = test->priv;
306 	struct drm_mock_sched_job *job;
307 	const unsigned int qd = 100;
308 	unsigned int i, cur_ent = 0;
309 	enum drm_sched_priority p;
310 	bool done;
311 
312 	/*
313 	 * Submit a bunch of jobs against entities configured with different
314 	 * priorities.
315 	 */
316 
317 	BUILD_BUG_ON(DRM_SCHED_PRIORITY_KERNEL > DRM_SCHED_PRIORITY_LOW);
318 	BUILD_BUG_ON(ARRAY_SIZE(entity) != DRM_SCHED_PRIORITY_COUNT);
319 
320 	for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
321 		entity[p] = drm_mock_sched_entity_new(test, p, sched);
322 
323 	for (i = 0; i < qd; i++) {
324 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
325 		cur_ent %= ARRAY_SIZE(entity);
326 		drm_mock_sched_job_set_duration_us(job, 1000);
327 		drm_mock_sched_job_submit(job);
328 	}
329 
330 	done = drm_mock_sched_job_wait_finished(job, HZ);
331 	KUNIT_ASSERT_TRUE(test, done);
332 
333 	for (i = 0; i < ARRAY_SIZE(entity); i++)
334 		drm_mock_sched_entity_free(entity[i]);
335 }
336 
337 static void drm_sched_change_priority(struct kunit *test)
338 {
339 	struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
340 	struct drm_mock_scheduler *sched = test->priv;
341 	struct drm_mock_sched_job *job;
342 	const unsigned int qd = 1000;
343 	unsigned int i, cur_ent = 0;
344 	enum drm_sched_priority p;
345 
346 	/*
347 	 * Submit a bunch of jobs against entities configured with different
348 	 * priorities and while waiting for them to complete, periodically keep
349 	 * changing their priorities.
350 	 *
351 	 * We set up the queue-depth (qd) and job duration so the priority
352 	 * changing loop has some time to interact with submissions to the
353 	 * backend and job completions as they progress.
354 	 */
355 
356 	for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
357 		entity[p] = drm_mock_sched_entity_new(test, p, sched);
358 
359 	for (i = 0; i < qd; i++) {
360 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
361 		cur_ent %= ARRAY_SIZE(entity);
362 		drm_mock_sched_job_set_duration_us(job, 1000);
363 		drm_mock_sched_job_submit(job);
364 	}
365 
366 	do {
367 		drm_sched_entity_set_priority(&entity[cur_ent]->base,
368 					      (entity[cur_ent]->base.priority + 1) %
369 					      DRM_SCHED_PRIORITY_COUNT);
370 		cur_ent++;
371 		cur_ent %= ARRAY_SIZE(entity);
372 		usleep_range(200, 500);
373 	} while (!drm_mock_sched_job_is_finished(job));
374 
375 	for (i = 0; i < ARRAY_SIZE(entity); i++)
376 		drm_mock_sched_entity_free(entity[i]);
377 }
378 
379 static struct kunit_case drm_sched_priority_tests[] = {
380 	KUNIT_CASE(drm_sched_priorities),
381 	KUNIT_CASE(drm_sched_change_priority),
382 	{}
383 };
384 
385 static struct kunit_suite drm_sched_priority = {
386 	.name = "drm_sched_basic_priority_tests",
387 	.init = drm_sched_basic_init,
388 	.exit = drm_sched_basic_exit,
389 	.test_cases = drm_sched_priority_tests,
390 };
391 
392 static void drm_sched_test_modify_sched(struct kunit *test)
393 {
394 	unsigned int i, cur_ent = 0, cur_sched = 0;
395 	struct drm_mock_sched_entity *entity[13];
396 	struct drm_mock_scheduler *sched[3];
397 	struct drm_mock_sched_job *job;
398 	const unsigned int qd = 1000;
399 
400 	/*
401 	 * Submit a bunch of jobs against entities configured with different
402 	 * schedulers and while waiting for them to complete, periodically keep
403 	 * changing schedulers associated with each entity.
404 	 *
405 	 * We set up the queue-depth (qd) and job duration so the sched modify
406 	 * loop has some time to interact with submissions to the backend and
407 	 * job completions as they progress.
408 	 *
409 	 * For the number of schedulers and entities we use primes in order to
410 	 * perturb the entity->sched assignments with less of a regular pattern.
411 	 */
412 
413 	for (i = 0; i < ARRAY_SIZE(sched); i++)
414 		sched[i] = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
415 
416 	for (i = 0; i < ARRAY_SIZE(entity); i++)
417 		entity[i] = drm_mock_sched_entity_new(test,
418 						      DRM_SCHED_PRIORITY_NORMAL,
419 						      sched[i % ARRAY_SIZE(sched)]);
420 
421 	for (i = 0; i < qd; i++) {
422 		job = drm_mock_sched_job_new(test, entity[cur_ent++]);
423 		cur_ent %= ARRAY_SIZE(entity);
424 		drm_mock_sched_job_set_duration_us(job, 1000);
425 		drm_mock_sched_job_submit(job);
426 	}
427 
428 	do {
429 		struct drm_gpu_scheduler *modify;
430 
431 		usleep_range(200, 500);
432 		cur_ent++;
433 		cur_ent %= ARRAY_SIZE(entity);
434 		cur_sched++;
435 		cur_sched %= ARRAY_SIZE(sched);
436 		modify = &sched[cur_sched]->base;
437 		drm_sched_entity_modify_sched(&entity[cur_ent]->base, &modify,
438 					      1);
439 	} while (!drm_mock_sched_job_is_finished(job));
440 
441 	for (i = 0; i < ARRAY_SIZE(entity); i++)
442 		drm_mock_sched_entity_free(entity[i]);
443 
444 	for (i = 0; i < ARRAY_SIZE(sched); i++)
445 		drm_mock_sched_fini(sched[i]);
446 }
447 
448 static struct kunit_case drm_sched_modify_sched_tests[] = {
449 	KUNIT_CASE(drm_sched_test_modify_sched),
450 	{}
451 };
452 
453 static struct kunit_suite drm_sched_modify_sched = {
454 	.name = "drm_sched_basic_modify_sched_tests",
455 	.test_cases = drm_sched_modify_sched_tests,
456 };
457 
458 static void drm_sched_test_credits(struct kunit *test)
459 {
460 	struct drm_mock_sched_entity *entity;
461 	struct drm_mock_scheduler *sched;
462 	struct drm_mock_sched_job *job[2];
463 	bool done;
464 	int i;
465 
466 	/*
467 	 * Check that the configured credit limit is respected.
468 	 */
469 
470 	sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
471 	sched->base.credit_limit = 1;
472 
473 	entity = drm_mock_sched_entity_new(test,
474 					   DRM_SCHED_PRIORITY_NORMAL,
475 					   sched);
476 
477 	job[0] = drm_mock_sched_job_new(test, entity);
478 	job[1] = drm_mock_sched_job_new(test, entity);
479 
480 	drm_mock_sched_job_submit(job[0]);
481 	drm_mock_sched_job_submit(job[1]);
482 
483 	done = drm_mock_sched_job_wait_scheduled(job[0], HZ);
484 	KUNIT_ASSERT_TRUE(test, done);
485 
486 	done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
487 	KUNIT_ASSERT_FALSE(test, done);
488 
489 	i = drm_mock_sched_advance(sched, 1);
490 	KUNIT_ASSERT_EQ(test, i, 1);
491 
492 	done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
493 	KUNIT_ASSERT_TRUE(test, done);
494 
495 	i = drm_mock_sched_advance(sched, 1);
496 	KUNIT_ASSERT_EQ(test, i, 1);
497 
498 	done = drm_mock_sched_job_wait_finished(job[1], HZ);
499 	KUNIT_ASSERT_TRUE(test, done);
500 
501 	drm_mock_sched_entity_free(entity);
502 	drm_mock_sched_fini(sched);
503 }
504 
505 static struct kunit_case drm_sched_credits_tests[] = {
506 	KUNIT_CASE(drm_sched_test_credits),
507 	{}
508 };
509 
510 static struct kunit_suite drm_sched_credits = {
511 	.name = "drm_sched_basic_credits_tests",
512 	.test_cases = drm_sched_credits_tests,
513 };
514 
515 kunit_test_suites(&drm_sched_basic,
516 		  &drm_sched_timeout,
517 		  &drm_sched_cancel,
518 		  &drm_sched_priority,
519 		  &drm_sched_modify_sched,
520 		  &drm_sched_credits);
521