1 // SPDX-License-Identifier: GPL-2.0 2 /* Copyright (c) 2025 Valve Corporation */ 3 4 #include <linux/delay.h> 5 6 #include "sched_tests.h" 7 8 #define MOCK_TIMEOUT (HZ / 5) 9 10 /* 11 * DRM scheduler basic tests should check the basic functional correctness of 12 * the scheduler, including some very light smoke testing. More targeted tests, 13 * for example focusing on testing specific bugs and other more complicated test 14 * scenarios, should be implemented in separate source units. 15 */ 16 17 static int drm_sched_basic_init(struct kunit *test) 18 { 19 test->priv = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); 20 21 return 0; 22 } 23 24 static void drm_sched_basic_exit(struct kunit *test) 25 { 26 struct drm_mock_scheduler *sched = test->priv; 27 28 drm_mock_sched_fini(sched); 29 } 30 31 static int drm_sched_timeout_init(struct kunit *test) 32 { 33 test->priv = drm_mock_sched_new(test, MOCK_TIMEOUT); 34 35 return 0; 36 } 37 38 static void drm_sched_basic_submit(struct kunit *test) 39 { 40 struct drm_mock_scheduler *sched = test->priv; 41 struct drm_mock_sched_entity *entity; 42 struct drm_mock_sched_job *job; 43 unsigned int i; 44 bool done; 45 46 /* 47 * Submit one job to the scheduler and verify that it gets scheduled 48 * and completed only when the mock hw backend processes it. 49 */ 50 51 entity = drm_mock_sched_entity_new(test, 52 DRM_SCHED_PRIORITY_NORMAL, 53 sched); 54 job = drm_mock_sched_job_new(test, entity); 55 56 drm_mock_sched_job_submit(job); 57 58 done = drm_mock_sched_job_wait_scheduled(job, HZ); 59 KUNIT_ASSERT_TRUE(test, done); 60 61 done = drm_mock_sched_job_wait_finished(job, HZ / 2); 62 KUNIT_ASSERT_FALSE(test, done); 63 64 i = drm_mock_sched_advance(sched, 1); 65 KUNIT_ASSERT_EQ(test, i, 1); 66 67 done = drm_mock_sched_job_wait_finished(job, HZ); 68 KUNIT_ASSERT_TRUE(test, done); 69 70 drm_mock_sched_entity_free(entity); 71 } 72 73 struct drm_sched_basic_params { 74 const char *description; 75 unsigned int queue_depth; 76 unsigned int num_entities; 77 unsigned int job_us; 78 bool dep_chain; 79 }; 80 81 static const struct drm_sched_basic_params drm_sched_basic_cases[] = { 82 { 83 .description = "A queue of jobs in a single entity", 84 .queue_depth = 100, 85 .job_us = 1000, 86 .num_entities = 1, 87 }, 88 { 89 .description = "A chain of dependent jobs across multiple entities", 90 .queue_depth = 100, 91 .job_us = 1000, 92 .num_entities = 1, 93 .dep_chain = true, 94 }, 95 { 96 .description = "Multiple independent job queues", 97 .queue_depth = 100, 98 .job_us = 1000, 99 .num_entities = 4, 100 }, 101 { 102 .description = "Multiple inter-dependent job queues", 103 .queue_depth = 100, 104 .job_us = 1000, 105 .num_entities = 4, 106 .dep_chain = true, 107 }, 108 }; 109 110 static void 111 drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc) 112 { 113 strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE); 114 } 115 116 KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc); 117 118 static void drm_sched_basic_test(struct kunit *test) 119 { 120 const struct drm_sched_basic_params *params = test->param_value; 121 struct drm_mock_scheduler *sched = test->priv; 122 struct drm_mock_sched_job *job, *prev = NULL; 123 struct drm_mock_sched_entity **entity; 124 unsigned int i, cur_ent = 0; 125 bool done; 126 127 entity = kunit_kcalloc(test, params->num_entities, sizeof(*entity), 128 GFP_KERNEL); 129 KUNIT_ASSERT_NOT_NULL(test, entity); 130 131 for (i = 0; i < params->num_entities; i++) 132 entity[i] = drm_mock_sched_entity_new(test, 133 DRM_SCHED_PRIORITY_NORMAL, 134 sched); 135 136 for (i = 0; i < params->queue_depth; i++) { 137 job = drm_mock_sched_job_new(test, entity[cur_ent++]); 138 cur_ent %= params->num_entities; 139 drm_mock_sched_job_set_duration_us(job, params->job_us); 140 if (params->dep_chain && prev) 141 drm_sched_job_add_dependency(&job->base, 142 dma_fence_get(&prev->base.s_fence->finished)); 143 drm_mock_sched_job_submit(job); 144 prev = job; 145 } 146 147 done = drm_mock_sched_job_wait_finished(job, HZ); 148 KUNIT_ASSERT_TRUE(test, done); 149 150 for (i = 0; i < params->num_entities; i++) 151 drm_mock_sched_entity_free(entity[i]); 152 } 153 154 static void drm_sched_basic_entity_cleanup(struct kunit *test) 155 { 156 struct drm_mock_sched_job *job, *mid, *prev = NULL; 157 struct drm_mock_scheduler *sched = test->priv; 158 struct drm_mock_sched_entity *entity[4]; 159 const unsigned int qd = 100; 160 unsigned int i, cur_ent = 0; 161 bool done; 162 163 /* 164 * Submit a queue of jobs across different entities with an explicit 165 * chain of dependencies between them and trigger entity cleanup while 166 * the queue is still being processed. 167 */ 168 169 for (i = 0; i < ARRAY_SIZE(entity); i++) 170 entity[i] = drm_mock_sched_entity_new(test, 171 DRM_SCHED_PRIORITY_NORMAL, 172 sched); 173 174 for (i = 0; i < qd; i++) { 175 job = drm_mock_sched_job_new(test, entity[cur_ent++]); 176 cur_ent %= ARRAY_SIZE(entity); 177 drm_mock_sched_job_set_duration_us(job, 1000); 178 if (prev) 179 drm_sched_job_add_dependency(&job->base, 180 dma_fence_get(&prev->base.s_fence->finished)); 181 drm_mock_sched_job_submit(job); 182 if (i == qd / 2) 183 mid = job; 184 prev = job; 185 } 186 187 done = drm_mock_sched_job_wait_finished(mid, HZ); 188 KUNIT_ASSERT_TRUE(test, done); 189 190 /* Exit with half of the queue still pending to be executed. */ 191 for (i = 0; i < ARRAY_SIZE(entity); i++) 192 drm_mock_sched_entity_free(entity[i]); 193 } 194 195 static struct kunit_case drm_sched_basic_tests[] = { 196 KUNIT_CASE(drm_sched_basic_submit), 197 KUNIT_CASE_PARAM(drm_sched_basic_test, drm_sched_basic_gen_params), 198 KUNIT_CASE(drm_sched_basic_entity_cleanup), 199 {} 200 }; 201 202 static struct kunit_suite drm_sched_basic = { 203 .name = "drm_sched_basic_tests", 204 .init = drm_sched_basic_init, 205 .exit = drm_sched_basic_exit, 206 .test_cases = drm_sched_basic_tests, 207 }; 208 209 static void drm_sched_basic_cancel(struct kunit *test) 210 { 211 struct drm_mock_sched_entity *entity; 212 struct drm_mock_scheduler *sched; 213 struct drm_mock_sched_job *job; 214 bool done; 215 216 /* 217 * Check that drm_sched_fini() uses the cancel_job() callback to cancel 218 * jobs that are still pending. 219 */ 220 221 sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); 222 entity = drm_mock_sched_entity_new(test, DRM_SCHED_PRIORITY_NORMAL, 223 sched); 224 225 job = drm_mock_sched_job_new(test, entity); 226 227 drm_mock_sched_job_submit(job); 228 229 done = drm_mock_sched_job_wait_scheduled(job, HZ); 230 KUNIT_ASSERT_TRUE(test, done); 231 232 drm_mock_sched_entity_free(entity); 233 drm_mock_sched_fini(sched); 234 235 KUNIT_ASSERT_EQ(test, job->hw_fence.error, -ECANCELED); 236 } 237 238 static struct kunit_case drm_sched_cancel_tests[] = { 239 KUNIT_CASE(drm_sched_basic_cancel), 240 {} 241 }; 242 243 static struct kunit_suite drm_sched_cancel = { 244 .name = "drm_sched_basic_cancel_tests", 245 .init = drm_sched_basic_init, 246 .exit = drm_sched_basic_exit, 247 .test_cases = drm_sched_cancel_tests, 248 }; 249 250 static void drm_sched_basic_timeout(struct kunit *test) 251 { 252 struct drm_mock_scheduler *sched = test->priv; 253 struct drm_mock_sched_entity *entity; 254 struct drm_mock_sched_job *job; 255 bool done; 256 257 /* 258 * Submit a single job against a scheduler with the timeout configured 259 * and verify that the timeout handling will run if the backend fails 260 * to complete it in time. 261 */ 262 263 entity = drm_mock_sched_entity_new(test, 264 DRM_SCHED_PRIORITY_NORMAL, 265 sched); 266 job = drm_mock_sched_job_new(test, entity); 267 268 drm_mock_sched_job_submit(job); 269 270 done = drm_mock_sched_job_wait_scheduled(job, HZ); 271 KUNIT_ASSERT_TRUE(test, done); 272 273 done = drm_mock_sched_job_wait_finished(job, MOCK_TIMEOUT / 2); 274 KUNIT_ASSERT_FALSE(test, done); 275 276 KUNIT_ASSERT_EQ(test, 277 job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT, 278 0); 279 280 done = drm_mock_sched_job_wait_finished(job, MOCK_TIMEOUT); 281 KUNIT_ASSERT_FALSE(test, done); 282 283 KUNIT_ASSERT_EQ(test, 284 job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT, 285 DRM_MOCK_SCHED_JOB_TIMEDOUT); 286 287 drm_mock_sched_entity_free(entity); 288 } 289 290 static 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 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 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 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 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