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