1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (c) 2025 Valve Corporation */ 3 4 #include <linux/delay.h> 5 #include <linux/kthread.h> 6 #include <linux/ktime.h> 7 #include <linux/math64.h> 8 9 #include "sched_tests.h" 10 11 /* 12 * DRM scheduler tests exercise load balancing decisions ie. entity selection 13 * logic. 14 */ 15 16 static int drm_sched_scheduler_init(struct kunit *test) 17 { 18 struct drm_mock_scheduler *sched; 19 20 sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); 21 sched->base.credit_limit = 1; 22 23 test->priv = sched; 24 25 return 0; 26 } 27 28 static int drm_sched_scheduler_init2(struct kunit *test) 29 { 30 struct drm_mock_scheduler *sched; 31 32 sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); 33 sched->base.credit_limit = 2; 34 35 test->priv = sched; 36 37 return 0; 38 } 39 40 static void drm_sched_scheduler_exit(struct kunit *test) 41 { 42 struct drm_mock_scheduler *sched = test->priv; 43 44 drm_mock_sched_fini(sched); 45 } 46 47 static void drm_sched_scheduler_queue_overhead(struct kunit *test) 48 { 49 struct drm_mock_scheduler *sched = test->priv; 50 struct drm_mock_sched_entity *entity; 51 const unsigned int job_us = 1000; 52 const unsigned int jobs = 1000; 53 const unsigned int total_us = jobs * job_us; 54 struct drm_mock_sched_job *job, *first; 55 ktime_t start, end; 56 bool done; 57 int i; 58 59 /* 60 * Deep queue job at a time processing (single credit). 61 * 62 * This measures the overhead of picking and processing a job at a time 63 * by comparing the ideal total "GPU" time of all submitted jobs versus 64 * the time actually taken. 65 */ 66 67 KUNIT_ASSERT_EQ(test, sched->base.credit_limit, 1); 68 69 entity = drm_mock_sched_entity_new(test, 70 DRM_SCHED_PRIORITY_NORMAL, 71 sched); 72 73 for (i = 0; i <= jobs; i++) { 74 job = drm_mock_sched_job_new(test, entity); 75 if (i == 0) 76 first = job; /* Extra first job blocks the queue */ 77 else 78 drm_mock_sched_job_set_duration_us(job, job_us); 79 drm_mock_sched_job_submit(job); 80 } 81 82 done = drm_mock_sched_job_wait_scheduled(first, HZ); 83 KUNIT_ASSERT_TRUE(test, done); 84 85 start = ktime_get(); 86 i = drm_mock_sched_advance(sched, 1); /* Release the queue */ 87 KUNIT_ASSERT_EQ(test, i, 1); 88 89 /* Wait with a safe margin to avoid every failing. */ 90 done = drm_mock_sched_job_wait_finished(job, 91 usecs_to_jiffies(total_us) * 5); 92 end = ktime_get(); 93 KUNIT_ASSERT_TRUE(test, done); 94 95 pr_info("Expected %uus, actual %lldus\n", 96 total_us, 97 ktime_to_us(ktime_sub(end, start))); 98 99 drm_mock_sched_entity_free(entity); 100 } 101 102 static void drm_sched_scheduler_ping_pong(struct kunit *test) 103 { 104 struct drm_mock_sched_job *job, *first, *prev = NULL; 105 struct drm_mock_scheduler *sched = test->priv; 106 struct drm_mock_sched_entity *entity[2]; 107 const unsigned int job_us = 1000; 108 const unsigned int jobs = 1000; 109 const unsigned int total_us = jobs * job_us; 110 ktime_t start, end; 111 bool done; 112 int i; 113 114 /* 115 * Two entitites in inter-dependency chain. 116 * 117 * This measures the overhead of picking and processing a job at a time, 118 * where each job depends on the previous one from the diffferent 119 * entity, by comparing the ideal total "GPU" time of all submitted jobs 120 * versus the time actually taken. 121 */ 122 123 KUNIT_ASSERT_EQ(test, sched->base.credit_limit, 1); 124 125 for (i = 0; i < ARRAY_SIZE(entity); i++) 126 entity[i] = drm_mock_sched_entity_new(test, 127 DRM_SCHED_PRIORITY_NORMAL, 128 sched); 129 130 for (i = 0; i <= jobs; i++) { 131 job = drm_mock_sched_job_new(test, entity[i & 1]); 132 if (i == 0) 133 first = job; /* Extra first job blocks the queue */ 134 else 135 drm_mock_sched_job_set_duration_us(job, job_us); 136 if (prev) 137 drm_sched_job_add_dependency(&job->base, 138 dma_fence_get(&prev->base.s_fence->finished)); 139 drm_mock_sched_job_submit(job); 140 prev = job; 141 } 142 143 done = drm_mock_sched_job_wait_scheduled(first, HZ); 144 KUNIT_ASSERT_TRUE(test, done); 145 146 start = ktime_get(); 147 i = drm_mock_sched_advance(sched, 1); /* Release the queue */ 148 KUNIT_ASSERT_EQ(test, i, 1); 149 150 /* Wait with a safe margin to avoid every failing. */ 151 done = drm_mock_sched_job_wait_finished(job, 152 usecs_to_jiffies(total_us) * 5); 153 end = ktime_get(); 154 KUNIT_ASSERT_TRUE(test, done); 155 156 pr_info("Expected %uus, actual %lldus\n", 157 total_us, 158 ktime_to_us(ktime_sub(end, start))); 159 160 for (i = 0; i < ARRAY_SIZE(entity); i++) 161 drm_mock_sched_entity_free(entity[i]); 162 } 163 164 static struct kunit_case drm_sched_scheduler_overhead_tests[] = { 165 KUNIT_CASE_SLOW(drm_sched_scheduler_queue_overhead), 166 KUNIT_CASE_SLOW(drm_sched_scheduler_ping_pong), 167 {} 168 }; 169 170 static struct kunit_suite drm_sched_scheduler_overhead = { 171 .name = "drm_sched_scheduler_overhead_tests", 172 .init = drm_sched_scheduler_init, 173 .exit = drm_sched_scheduler_exit, 174 .test_cases = drm_sched_scheduler_overhead_tests, 175 }; 176 177 /* 178 * struct drm_sched_client_params - describe a workload emitted from a client 179 * 180 * A simulated client will create an entity with a scheduling @priority and emit 181 * jobs in a loop where each iteration will consist of: 182 * 183 * 1. Submit @job_cnt jobs, each with a set duration of @job_us. 184 * 2. If @sync is true wait for last submitted job to finish. 185 * 3. Sleep for @wait_us micro-seconds. 186 * 4. Repeat. 187 */ 188 struct drm_sched_client_params { 189 enum drm_sched_priority priority; 190 unsigned int job_cnt; 191 unsigned int job_us; 192 bool sync; 193 unsigned int wait_us; 194 }; 195 196 struct drm_sched_test_params { 197 const char *description; 198 unsigned int num_clients; 199 struct drm_sched_client_params client[2]; 200 }; 201 202 static const struct drm_sched_test_params drm_sched_cases[] = { 203 { 204 .description = "Normal priority and normal priority", 205 .client[0] = { 206 .priority = DRM_SCHED_PRIORITY_NORMAL, 207 .job_cnt = 1, 208 .job_us = 8000, 209 .wait_us = 0, 210 .sync = false, 211 }, 212 .client[1] = { 213 .priority = DRM_SCHED_PRIORITY_NORMAL, 214 .job_cnt = 1, 215 .job_us = 8000, 216 .wait_us = 0, 217 .sync = false, 218 }, 219 }, 220 { 221 .description = "Normal priority and low priority", 222 .client[0] = { 223 .priority = DRM_SCHED_PRIORITY_NORMAL, 224 .job_cnt = 1, 225 .job_us = 8000, 226 .wait_us = 0, 227 .sync = false, 228 }, 229 .client[1] = { 230 .priority = DRM_SCHED_PRIORITY_LOW, 231 .job_cnt = 1, 232 .job_us = 8000, 233 .wait_us = 0, 234 .sync = false, 235 }, 236 }, 237 { 238 .description = "High priority and normal priority", 239 .client[0] = { 240 .priority = DRM_SCHED_PRIORITY_HIGH, 241 .job_cnt = 1, 242 .job_us = 8000, 243 .wait_us = 0, 244 .sync = false, 245 }, 246 .client[1] = { 247 .priority = DRM_SCHED_PRIORITY_NORMAL, 248 .job_cnt = 1, 249 .job_us = 8000, 250 .wait_us = 0, 251 .sync = false, 252 }, 253 }, 254 { 255 .description = "High priority and low priority", 256 .client[0] = { 257 .priority = DRM_SCHED_PRIORITY_HIGH, 258 .job_cnt = 1, 259 .job_us = 8000, 260 .wait_us = 0, 261 .sync = false, 262 }, 263 .client[1] = { 264 .priority = DRM_SCHED_PRIORITY_LOW, 265 .job_cnt = 1, 266 .job_us = 8000, 267 .wait_us = 0, 268 .sync = false, 269 }, 270 }, 271 { 272 .description = "50% and 50%", 273 .client[0] = { 274 .priority = DRM_SCHED_PRIORITY_NORMAL, 275 .job_cnt = 1, 276 .job_us = 1500, 277 .wait_us = 1500, 278 .sync = true, 279 }, 280 .client[1] = { 281 .priority = DRM_SCHED_PRIORITY_NORMAL, 282 .job_cnt = 1, 283 .job_us = 2500, 284 .wait_us = 2500, 285 .sync = true, 286 }, 287 }, 288 { 289 .description = "50% and 50% low priority", 290 .client[0] = { 291 .priority = DRM_SCHED_PRIORITY_NORMAL, 292 .job_cnt = 1, 293 .job_us = 1500, 294 .wait_us = 1500, 295 .sync = true, 296 }, 297 .client[1] = { 298 .priority = DRM_SCHED_PRIORITY_LOW, 299 .job_cnt = 1, 300 .job_us = 2500, 301 .wait_us = 2500, 302 .sync = true, 303 }, 304 }, 305 { 306 .description = "50% high priority and 50%", 307 .client[0] = { 308 .priority = DRM_SCHED_PRIORITY_HIGH, 309 .job_cnt = 1, 310 .job_us = 1500, 311 .wait_us = 1500, 312 .sync = true, 313 }, 314 .client[1] = { 315 .priority = DRM_SCHED_PRIORITY_NORMAL, 316 .job_cnt = 1, 317 .job_us = 2500, 318 .wait_us = 2500, 319 .sync = true, 320 }, 321 }, 322 { 323 .description = "Low priority hog and interactive client", 324 .client[0] = { 325 .priority = DRM_SCHED_PRIORITY_LOW, 326 .job_cnt = 3, 327 .job_us = 2500, 328 .wait_us = 500, 329 .sync = false, 330 }, 331 .client[1] = { 332 .priority = DRM_SCHED_PRIORITY_NORMAL, 333 .job_cnt = 1, 334 .job_us = 500, 335 .wait_us = 10000, 336 .sync = true, 337 }, 338 }, 339 { 340 .description = "Heavy rendering and interactive client", 341 .client[0] = { 342 .priority = DRM_SCHED_PRIORITY_NORMAL, 343 .job_cnt = 3, 344 .job_us = 2500, 345 .wait_us = 2500, 346 .sync = true, 347 }, 348 .client[1] = { 349 .priority = DRM_SCHED_PRIORITY_NORMAL, 350 .job_cnt = 1, 351 .job_us = 1000, 352 .wait_us = 9000, 353 .sync = true, 354 }, 355 }, 356 { 357 .description = "Very heavy rendering and interactive client", 358 .client[0] = { 359 .priority = DRM_SCHED_PRIORITY_NORMAL, 360 .job_cnt = 4, 361 .job_us = 50000, 362 .wait_us = 1, 363 .sync = true, 364 }, 365 .client[1] = { 366 .priority = DRM_SCHED_PRIORITY_NORMAL, 367 .job_cnt = 1, 368 .job_us = 1000, 369 .wait_us = 9000, 370 .sync = true, 371 }, 372 }, 373 }; 374 375 static void 376 drm_sched_desc(const struct drm_sched_test_params *params, char *desc) 377 { 378 strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE); 379 } 380 381 KUNIT_ARRAY_PARAM(drm_sched_scheduler_two_clients, 382 drm_sched_cases, 383 drm_sched_desc); 384 385 /* 386 * struct test_client_stats - track client stats 387 * 388 * For each client executing a simulated workload we track some timings for 389 * which we are interested in the minimum of all iterations (@min_us), maximum 390 * (@max_us) and the overall total for all iterations (@tot_us). 391 */ 392 struct test_client_stats { 393 unsigned int min_us; 394 unsigned int max_us; 395 unsigned long tot_us; 396 }; 397 398 /* 399 * struct test_client - a simulated userspace client submitting scheduler work 400 * 401 * Each client executing a simulated workload is represented by one of these. 402 * 403 * Each of them instantiates a scheduling @entity and executes a workloads as 404 * defined in @params. Based on those @params the theoretical execution time of 405 * the client is calculated as @ideal_duration, while the actual wall time is 406 * tracked in @duration (calculated based on the @start and @end client time- 407 * stamps). 408 * 409 * Numerical @id is assigned to each for logging purposes. 410 * 411 * @worker and @work are used to provide an independent execution context from 412 * which scheduler jobs are submitted. 413 * 414 * During execution statistics on how long it took to submit and execute one 415 * iteration (whether or not synchronous) is kept in @cycle_time, while 416 * @latency_time tracks the @cycle_time minus the ideal duration of the one 417 * cycle. 418 * 419 * Once the client has completed the set number of iterations it will write the 420 * completion status into @done. 421 */ 422 struct test_client { 423 struct kunit *test; /* Backpointer to the kunit test. */ 424 425 struct drm_mock_sched_entity *entity; 426 struct kthread_worker *worker; 427 struct kthread_work work; 428 429 struct drm_sched_client_params params; 430 431 unsigned int id; 432 ktime_t duration; 433 ktime_t ideal_duration; 434 unsigned int cycles; 435 unsigned int cycle; 436 ktime_t start; 437 ktime_t end; 438 bool done; 439 440 struct test_client_stats cycle_time; 441 struct test_client_stats latency_time; 442 }; 443 444 static void 445 update_stats(struct test_client_stats *stats, unsigned int us) 446 { 447 if (us > stats->max_us) 448 stats->max_us = us; 449 if (us < stats->min_us) 450 stats->min_us = us; 451 stats->tot_us += us; 452 } 453 454 static unsigned int 455 get_stats_avg(struct test_client_stats *stats, unsigned int cycles) 456 { 457 return div_u64(stats->tot_us, cycles); 458 } 459 460 static void drm_sched_client_work(struct kthread_work *work) 461 { 462 struct test_client *client = container_of(work, typeof(*client), work); 463 const long sync_wait = MAX_SCHEDULE_TIMEOUT; 464 unsigned int cycle, work_us, period_us; 465 struct drm_mock_sched_job *job = NULL; 466 467 work_us = client->params.job_cnt * client->params.job_us; 468 period_us = work_us + client->params.wait_us; 469 client->cycles = 470 DIV_ROUND_UP((unsigned int)ktime_to_us(client->duration), 471 period_us); 472 client->ideal_duration = us_to_ktime(client->cycles * period_us); 473 474 client->start = ktime_get(); 475 476 for (cycle = 0; cycle < client->cycles; cycle++) { 477 ktime_t cycle_time; 478 unsigned int batch; 479 unsigned long us; 480 481 if (READ_ONCE(client->done)) 482 break; 483 484 cycle_time = ktime_get(); 485 for (batch = 0; batch < client->params.job_cnt; batch++) { 486 job = drm_mock_sched_job_new(client->test, 487 client->entity); 488 drm_mock_sched_job_set_duration_us(job, 489 client->params.job_us); 490 drm_mock_sched_job_submit(job); 491 } 492 493 if (client->params.sync) 494 drm_mock_sched_job_wait_finished(job, sync_wait); 495 496 cycle_time = ktime_sub(ktime_get(), cycle_time); 497 us = ktime_to_us(cycle_time); 498 update_stats(&client->cycle_time, us); 499 if (ktime_to_us(cycle_time) >= (long)work_us) 500 us = ktime_to_us(cycle_time) - work_us; 501 else if (WARN_ON_ONCE(client->params.sync)) /* GPU job took less than expected. */ 502 us = 0; 503 update_stats(&client->latency_time, us); 504 WRITE_ONCE(client->cycle, cycle); 505 506 if (READ_ONCE(client->done)) 507 break; 508 509 if (client->params.wait_us) 510 fsleep(client->params.wait_us); 511 else if (!client->params.sync) 512 cond_resched(); /* Do not hog the CPU if fully async. */ 513 } 514 515 client->done = drm_mock_sched_job_wait_finished(job, sync_wait); 516 client->end = ktime_get(); 517 } 518 519 static const char *prio_str(enum drm_sched_priority prio) 520 { 521 switch (prio) { 522 case DRM_SCHED_PRIORITY_KERNEL: 523 return "kernel"; 524 case DRM_SCHED_PRIORITY_LOW: 525 return "low"; 526 case DRM_SCHED_PRIORITY_NORMAL: 527 return "normal"; 528 case DRM_SCHED_PRIORITY_HIGH: 529 return "high"; 530 default: 531 return "???"; 532 } 533 } 534 535 static bool client_done(struct test_client *client) 536 { 537 return READ_ONCE(client->done); /* READ_ONCE to document lockless read from a loop. */ 538 } 539 540 static void drm_sched_scheduler_two_clients_test(struct kunit *test) 541 { 542 const struct drm_sched_test_params *params = test->param_value; 543 struct drm_mock_scheduler *sched = test->priv; 544 struct test_client client[2] = { }; 545 unsigned int prev_cycle[2] = { }; 546 unsigned int i, j; 547 ktime_t start; 548 549 /* 550 * Same job stream from two clients. 551 */ 552 553 for (i = 0; i < ARRAY_SIZE(client); i++) 554 client[i].entity = 555 drm_mock_sched_entity_new(test, 556 params->client[i].priority, 557 sched); 558 559 for (i = 0; i < ARRAY_SIZE(client); i++) { 560 client[i].test = test; 561 client[i].id = i; 562 client[i].duration = ms_to_ktime(1000); 563 client[i].params = params->client[i]; 564 client[i].cycle_time.min_us = ~0U; 565 client[i].latency_time.min_us = ~0U; 566 client[i].worker = 567 kthread_create_worker(0, "%s-%u", __func__, i); 568 if (IS_ERR(client[i].worker)) { 569 for (j = 0; j < i; j++) 570 kthread_destroy_worker(client[j].worker); 571 KUNIT_FAIL(test, "Failed to create worker!\n"); 572 } 573 574 kthread_init_work(&client[i].work, drm_sched_client_work); 575 } 576 577 for (i = 0; i < ARRAY_SIZE(client); i++) 578 kthread_queue_work(client[i].worker, &client[i].work); 579 580 /* 581 * The clients (workers) can be a mix of async (deep submission queue), 582 * sync (one job at a time), or something in between. Therefore it is 583 * difficult to display a single metric representing their progress. 584 * 585 * Each struct drm_sched_client_params describes the actual submission 586 * pattern which happens in the following steps: 587 * 1. Submit N jobs 588 * 2. Wait for last submitted job to finish 589 * 3. Sleep for U micro-seconds 590 * 4. Goto 1. for C cycles 591 * 592 * Where number of cycles is calculated to match the target client 593 * duration from the respective struct drm_sched_test_params. 594 * 595 * To asses scheduling behaviour what we output for both clients is: 596 * - pct: Percentage progress of the jobs submitted 597 * - cps: "Cycles" per second (where one cycle is one complete 598 * iteration from the above) 599 * - qd: Number of outstanding jobs in the client/entity 600 */ 601 602 pr_info(" [pct] - Job sumission progress\n" 603 " [cps] - Cycles per second\n" 604 " [qd] - Number of outstanding jobs in the client/entity\n"); 605 pr_info("%s:\n\t pct1 cps1 qd1; pct2 cps2 qd2\n", 606 params->description); 607 start = ktime_get(); 608 while (!client_done(&client[0]) || !client_done(&client[1])) { 609 const unsigned int period_ms = 100; 610 const unsigned int frequency = 1000 / period_ms; 611 unsigned int pct[2], qd[2], cycle[2], cps[2]; 612 613 for (i = 0; i < ARRAY_SIZE(client); i++) { 614 qd[i] = spsc_queue_count(&client[i].entity->base.job_queue); 615 cycle[i] = READ_ONCE(client[i].cycle); 616 cps[i] = DIV_ROUND_UP(100 * frequency * 617 (cycle[i] - prev_cycle[i]), 618 100); 619 if (client[i].cycles) 620 pct[i] = DIV_ROUND_UP(100 * (1 + cycle[i]), 621 client[i].cycles); 622 else 623 pct[i] = 0; 624 prev_cycle[i] = cycle[i]; 625 } 626 627 if (client_done(&client[0])) 628 pr_info("\t+%6lldms: ; %3u %5u %4u\n", 629 ktime_to_ms(ktime_sub(ktime_get(), start)), 630 pct[1], cps[1], qd[1]); 631 else if (client_done(&client[1])) 632 pr_info("\t+%6lldms: %3u %5u %4u;\n", 633 ktime_to_ms(ktime_sub(ktime_get(), start)), 634 pct[0], cps[0], qd[0]); 635 else 636 pr_info("\t+%6lldms: %3u %5u %4u; %3u %5u %4u\n", 637 ktime_to_ms(ktime_sub(ktime_get(), start)), 638 pct[0], cps[0], qd[0], 639 pct[1], cps[1], qd[1]); 640 641 msleep(period_ms); 642 } 643 644 for (i = 0; i < ARRAY_SIZE(client); i++) { 645 kthread_flush_work(&client[i].work); 646 kthread_destroy_worker(client[i].worker); 647 } 648 649 for (i = 0; i < ARRAY_SIZE(client); i++) 650 KUNIT_ASSERT_TRUE(test, client[i].done); 651 652 for (i = 0; i < ARRAY_SIZE(client); i++) { 653 pr_info(" %u: prio=%s sync=%u elapsed_ms=%lldms (ideal_ms=%lldms) cycle_time(min,avg,max)=%u,%u,%u us latency_time(min,avg,max)=%u,%u,%u us", 654 i, 655 prio_str(params->client[i].priority), 656 params->client[i].sync, 657 ktime_to_ms(ktime_sub(client[i].end, client[i].start)), 658 ktime_to_ms(client[i].ideal_duration), 659 client[i].cycle_time.min_us, 660 get_stats_avg(&client[i].cycle_time, client[i].cycles), 661 client[i].cycle_time.max_us, 662 client[i].latency_time.min_us, 663 get_stats_avg(&client[i].latency_time, client[i].cycles), 664 client[i].latency_time.max_us); 665 drm_mock_sched_entity_free(client[i].entity); 666 } 667 } 668 669 static const struct kunit_attributes drm_sched_scheduler_two_clients_attr = { 670 .speed = KUNIT_SPEED_SLOW, 671 }; 672 673 static struct kunit_case drm_sched_scheduler_two_clients_tests[] = { 674 KUNIT_CASE_PARAM_ATTR(drm_sched_scheduler_two_clients_test, 675 drm_sched_scheduler_two_clients_gen_params, 676 drm_sched_scheduler_two_clients_attr), 677 {} 678 }; 679 680 static struct kunit_suite drm_sched_scheduler_two_clients1 = { 681 .name = "drm_sched_scheduler_two_clients_one_credit_tests", 682 .init = drm_sched_scheduler_init, 683 .exit = drm_sched_scheduler_exit, 684 .test_cases = drm_sched_scheduler_two_clients_tests, 685 }; 686 687 static struct kunit_suite drm_sched_scheduler_two_clients2 = { 688 .name = "drm_sched_scheduler_two_clients_two_credits_tests", 689 .init = drm_sched_scheduler_init2, 690 .exit = drm_sched_scheduler_exit, 691 .test_cases = drm_sched_scheduler_two_clients_tests, 692 }; 693 694 static const struct drm_sched_test_params drm_sched_many_cases[] = { 695 { 696 .description = "2 clients", 697 .num_clients = 2, 698 .client[0] = { 699 .priority = DRM_SCHED_PRIORITY_NORMAL, 700 .job_cnt = 4, 701 .job_us = 1000, 702 .wait_us = 0, 703 .sync = true, 704 }, 705 }, 706 { 707 .description = "3 clients", 708 .num_clients = 3, 709 .client[0] = { 710 .priority = DRM_SCHED_PRIORITY_NORMAL, 711 .job_cnt = 4, 712 .job_us = 1000, 713 .wait_us = 0, 714 .sync = true, 715 }, 716 }, 717 { 718 .description = "7 clients", 719 .num_clients = 7, 720 .client[0] = { 721 .priority = DRM_SCHED_PRIORITY_NORMAL, 722 .job_cnt = 4, 723 .job_us = 1000, 724 .wait_us = 0, 725 .sync = true, 726 }, 727 }, 728 { 729 .description = "13 clients", 730 .num_clients = 13, 731 .client[0] = { 732 .priority = DRM_SCHED_PRIORITY_NORMAL, 733 .job_cnt = 4, 734 .job_us = 1000, 735 .wait_us = 0, 736 .sync = true, 737 }, 738 }, 739 { 740 .description = "31 clients", 741 .num_clients = 31, 742 .client[0] = { 743 .priority = DRM_SCHED_PRIORITY_NORMAL, 744 .job_cnt = 2, 745 .job_us = 1000, 746 .wait_us = 0, 747 .sync = true, 748 }, 749 }, 750 }; 751 752 KUNIT_ARRAY_PARAM(drm_sched_scheduler_many_clients, 753 drm_sched_many_cases, 754 drm_sched_desc); 755 756 static void drm_sched_scheduler_many_clients_test(struct kunit *test) 757 { 758 const struct drm_sched_test_params *params = test->param_value; 759 struct drm_mock_scheduler *sched = test->priv; 760 const unsigned int clients = params->num_clients; 761 unsigned int i, j, delta_total = 0, loops = 0; 762 struct test_client *client; 763 unsigned int *prev_cycle; 764 ktime_t start; 765 char *buf; 766 767 /* 768 * Many clients with deep-ish async queues. 769 */ 770 771 buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL); 772 KUNIT_ASSERT_NOT_NULL(test, buf); 773 client = kunit_kcalloc(test, clients, sizeof(*client), GFP_KERNEL); 774 KUNIT_ASSERT_NOT_NULL(test, client); 775 prev_cycle = kunit_kcalloc(test, clients, sizeof(*prev_cycle), 776 GFP_KERNEL); 777 KUNIT_ASSERT_NOT_NULL(test, prev_cycle); 778 779 for (i = 0; i < clients; i++) 780 client[i].entity = 781 drm_mock_sched_entity_new(test, 782 DRM_SCHED_PRIORITY_NORMAL, 783 sched); 784 785 for (i = 0; i < clients; i++) { 786 client[i].test = test; 787 client[i].id = i; 788 client[i].params = params->client[0]; 789 client[i].duration = ms_to_ktime(1000 / clients); 790 client[i].cycle_time.min_us = ~0U; 791 client[i].latency_time.min_us = ~0U; 792 client[i].worker = 793 kthread_create_worker(0, "%s-%u", __func__, i); 794 if (IS_ERR(client[i].worker)) { 795 for (j = 0; j < i; j++) 796 kthread_destroy_worker(client[j].worker); 797 KUNIT_FAIL(test, "Failed to create worker!\n"); 798 } 799 800 kthread_init_work(&client[i].work, drm_sched_client_work); 801 } 802 803 for (i = 0; i < clients; i++) 804 kthread_queue_work(client[i].worker, &client[i].work); 805 806 start = ktime_get(); 807 pr_info("%u clients:\n\tt\t\tcycle:\t min avg max : ...\n", clients); 808 for (;;) { 809 unsigned int min = ~0; 810 unsigned int max = 0; 811 unsigned int total = 0; 812 bool done = true; 813 char pbuf[16]; 814 815 memset(buf, 0, PAGE_SIZE); 816 for (i = 0; i < clients; i++) { 817 unsigned int cycle, cycles; 818 819 /* Read current progress from the threaded worker. */ 820 cycle = READ_ONCE(client[i].cycle); 821 cycles = READ_ONCE(client[i].cycles); 822 823 snprintf(pbuf, sizeof(pbuf), " %3d", cycle); 824 strncat(buf, pbuf, PAGE_SIZE); 825 826 total += cycle; 827 if (cycle < min) 828 min = cycle; 829 if (cycle > max) 830 max = cycle; 831 832 if (!min || (cycle + 1) < cycles) 833 done = false; 834 } 835 836 loops++; 837 delta_total += max - min; 838 839 pr_info("\t+%6lldms\t\t %3u %3u %3u :%s\n", 840 ktime_to_ms(ktime_sub(ktime_get(), start)), 841 min, DIV_ROUND_UP(total, clients), max, buf); 842 843 if (done) 844 break; 845 846 msleep(100); 847 } 848 849 pr_info(" avg_max_min_delta(x100)=%u\n", 850 loops ? DIV_ROUND_UP(delta_total * 100, loops) : 0); 851 852 for (i = 0; i < clients; i++) { 853 kthread_flush_work(&client[i].work); 854 kthread_destroy_worker(client[i].worker); 855 } 856 857 for (i = 0; i < clients; i++) 858 drm_mock_sched_entity_free(client[i].entity); 859 } 860 861 static const struct kunit_attributes drm_sched_scheduler_many_clients_attr = { 862 .speed = KUNIT_SPEED_SLOW, 863 }; 864 865 static struct kunit_case drm_sched_scheduler_many_clients_tests[] = { 866 KUNIT_CASE_PARAM_ATTR(drm_sched_scheduler_many_clients_test, 867 drm_sched_scheduler_many_clients_gen_params, 868 drm_sched_scheduler_many_clients_attr), 869 {} 870 }; 871 872 static struct kunit_suite drm_sched_scheduler_many_clients = { 873 .name = "drm_sched_scheduler_many_clients_tests", 874 .init = drm_sched_scheduler_init2, 875 .exit = drm_sched_scheduler_exit, 876 .test_cases = drm_sched_scheduler_many_clients_tests, 877 }; 878 879 kunit_test_suites(&drm_sched_scheduler_overhead, 880 &drm_sched_scheduler_two_clients1, 881 &drm_sched_scheduler_two_clients2, 882 &drm_sched_scheduler_many_clients); 883