projects/linux·blob master · refs

922 lines · 25.3 KiB

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
21static 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
28static 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
35static 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
42static 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
77struct 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
85static 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
114static void
115drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc)
116{
117 strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
118}
119
120KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc);
121
122static 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
158static 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
199static 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
206static 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
213static 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
242struct 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
249KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_fini_wrap, drm_mock_sched_fini,
250 struct drm_mock_scheduler *);
251
252KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_entity_free_wrap, drm_mock_sched_entity_free,
253 struct drm_mock_sched_entity *);
254
255static 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
264static 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
293struct drm_sched_parallel_params {
294 const char *description;
295 unsigned int num_jobs;
296 unsigned int num_workers;
297};
298
299static 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
307static void
308drm_sched_parallel_desc(const struct drm_sched_parallel_params *params, char *desc)
309{
310 strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
311}
312
313KUNIT_ARRAY_PARAM(drm_sched_parallel, drm_sched_parallel_cases, drm_sched_parallel_desc);
314
315struct 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
323static 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 */
347static 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
423struct 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
431static 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
441static void
442drm_sched_interleaved_desc(const struct drm_sched_interleaved_params *params, char *desc)
443{
444 strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
445}
446
447KUNIT_ARRAY_PARAM(drm_sched_interleaved, drm_sched_interleaved_cases,
448 drm_sched_interleaved_desc);
449
450struct 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
460static 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 */
506static 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
581static 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
587static 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
596static struct kunit_case drm_sched_cancel_tests[] = {
597 KUNIT_CASE(drm_sched_basic_cancel),
598 {}
599};
600
601static 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
608static 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
648static 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
690static struct kunit_case drm_sched_timeout_tests[] = {
691 KUNIT_CASE(drm_sched_basic_timeout),
692 KUNIT_CASE(drm_sched_skip_reset),
693 {}
694};
695
696static 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
703static 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
738static 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
780static struct kunit_case drm_sched_priority_tests[] = {
781 KUNIT_CASE(drm_sched_priorities),
782 KUNIT_CASE_SLOW(drm_sched_change_priority),
783 {}
784};
785
786static 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
793static 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
849static struct kunit_case drm_sched_modify_sched_tests[] = {
850 KUNIT_CASE(drm_sched_test_modify_sched),
851 {}
852};
853
854static 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
859static 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
906static struct kunit_case drm_sched_credits_tests[] = {
907 KUNIT_CASE_SLOW(drm_sched_test_credits),
908 {}
909};
910
911static struct kunit_suite drm_sched_credits = {
912 .name = "drm_sched_basic_credits_tests",
913 .test_cases = drm_sched_credits_tests,
914};
915
916kunit_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);