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