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 /* 9 * DRM scheduler basic tests should check the basic functional correctness of 10 * the scheduler, including some very light smoke testing. More targeted tests, 11 * for example focusing on testing specific bugs and other more complicated test 12 * scenarios, should be implemented in separate source units. 13 */ 14 15 static int drm_sched_basic_init(struct kunit *test) 16 { 17 test->priv = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); 18 19 return 0; 20 } 21 22 static void drm_sched_basic_exit(struct kunit *test) 23 { 24 struct drm_mock_scheduler *sched = test->priv; 25 26 drm_mock_sched_fini(sched); 27 } 28 29 static int drm_sched_timeout_init(struct kunit *test) 30 { 31 test->priv = drm_mock_sched_new(test, HZ); 32 33 return 0; 34 } 35 36 static void drm_sched_basic_submit(struct kunit *test) 37 { 38 struct drm_mock_scheduler *sched = test->priv; 39 struct drm_mock_sched_entity *entity; 40 struct drm_mock_sched_job *job; 41 unsigned int i; 42 bool done; 43 44 /* 45 * Submit one job to the scheduler and verify that it gets scheduled 46 * and completed only when the mock hw backend processes it. 47 */ 48 49 entity = drm_mock_sched_entity_new(test, 50 DRM_SCHED_PRIORITY_NORMAL, 51 sched); 52 job = drm_mock_sched_job_new(test, entity); 53 54 drm_mock_sched_job_submit(job); 55 56 done = drm_mock_sched_job_wait_scheduled(job, HZ); 57 KUNIT_ASSERT_TRUE(test, done); 58 59 done = drm_mock_sched_job_wait_finished(job, HZ / 2); 60 KUNIT_ASSERT_FALSE(test, done); 61 62 i = drm_mock_sched_advance(sched, 1); 63 KUNIT_ASSERT_EQ(test, i, 1); 64 65 done = drm_mock_sched_job_wait_finished(job, HZ); 66 KUNIT_ASSERT_TRUE(test, done); 67 68 drm_mock_sched_entity_free(entity); 69 } 70 71 struct drm_sched_basic_params { 72 const char *description; 73 unsigned int queue_depth; 74 unsigned int num_entities; 75 unsigned int job_us; 76 bool dep_chain; 77 }; 78 79 static const struct drm_sched_basic_params drm_sched_basic_cases[] = { 80 { 81 .description = "A queue of jobs in a single entity", 82 .queue_depth = 100, 83 .job_us = 1000, 84 .num_entities = 1, 85 }, 86 { 87 .description = "A chain of dependent jobs across multiple entities", 88 .queue_depth = 100, 89 .job_us = 1000, 90 .num_entities = 1, 91 .dep_chain = true, 92 }, 93 { 94 .description = "Multiple independent job queues", 95 .queue_depth = 100, 96 .job_us = 1000, 97 .num_entities = 4, 98 }, 99 { 100 .description = "Multiple inter-dependent job queues", 101 .queue_depth = 100, 102 .job_us = 1000, 103 .num_entities = 4, 104 .dep_chain = true, 105 }, 106 }; 107 108 static void 109 drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc) 110 { 111 strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE); 112 } 113 114 KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc); 115 116 static void drm_sched_basic_test(struct kunit *test) 117 { 118 const struct drm_sched_basic_params *params = test->param_value; 119 struct drm_mock_scheduler *sched = test->priv; 120 struct drm_mock_sched_job *job, *prev = NULL; 121 struct drm_mock_sched_entity **entity; 122 unsigned int i, cur_ent = 0; 123 bool done; 124 125 entity = kunit_kcalloc(test, params->num_entities, sizeof(*entity), 126 GFP_KERNEL); 127 KUNIT_ASSERT_NOT_NULL(test, entity); 128 129 for (i = 0; i < params->num_entities; i++) 130 entity[i] = drm_mock_sched_entity_new(test, 131 DRM_SCHED_PRIORITY_NORMAL, 132 sched); 133 134 for (i = 0; i < params->queue_depth; i++) { 135 job = drm_mock_sched_job_new(test, entity[cur_ent++]); 136 cur_ent %= params->num_entities; 137 drm_mock_sched_job_set_duration_us(job, params->job_us); 138 if (params->dep_chain && prev) 139 drm_sched_job_add_dependency(&job->base, 140 dma_fence_get(&prev->base.s_fence->finished)); 141 drm_mock_sched_job_submit(job); 142 prev = job; 143 } 144 145 done = drm_mock_sched_job_wait_finished(job, HZ); 146 KUNIT_ASSERT_TRUE(test, done); 147 148 for (i = 0; i < params->num_entities; i++) 149 drm_mock_sched_entity_free(entity[i]); 150 } 151 152 static void drm_sched_basic_entity_cleanup(struct kunit *test) 153 { 154 struct drm_mock_sched_job *job, *mid, *prev = NULL; 155 struct drm_mock_scheduler *sched = test->priv; 156 struct drm_mock_sched_entity *entity[4]; 157 const unsigned int qd = 100; 158 unsigned int i, cur_ent = 0; 159 bool done; 160 161 /* 162 * Submit a queue of jobs across different entities with an explicit 163 * chain of dependencies between them and trigger entity cleanup while 164 * the queue is still being processed. 165 */ 166 167 for (i = 0; i < ARRAY_SIZE(entity); i++) 168 entity[i] = drm_mock_sched_entity_new(test, 169 DRM_SCHED_PRIORITY_NORMAL, 170 sched); 171 172 for (i = 0; i < qd; i++) { 173 job = drm_mock_sched_job_new(test, entity[cur_ent++]); 174 cur_ent %= ARRAY_SIZE(entity); 175 drm_mock_sched_job_set_duration_us(job, 1000); 176 if (prev) 177 drm_sched_job_add_dependency(&job->base, 178 dma_fence_get(&prev->base.s_fence->finished)); 179 drm_mock_sched_job_submit(job); 180 if (i == qd / 2) 181 mid = job; 182 prev = job; 183 } 184 185 done = drm_mock_sched_job_wait_finished(mid, HZ); 186 KUNIT_ASSERT_TRUE(test, done); 187 188 /* Exit with half of the queue still pending to be executed. */ 189 for (i = 0; i < ARRAY_SIZE(entity); i++) 190 drm_mock_sched_entity_free(entity[i]); 191 } 192 193 static struct kunit_case drm_sched_basic_tests[] = { 194 KUNIT_CASE(drm_sched_basic_submit), 195 KUNIT_CASE_PARAM(drm_sched_basic_test, drm_sched_basic_gen_params), 196 KUNIT_CASE(drm_sched_basic_entity_cleanup), 197 {} 198 }; 199 200 static struct kunit_suite drm_sched_basic = { 201 .name = "drm_sched_basic_tests", 202 .init = drm_sched_basic_init, 203 .exit = drm_sched_basic_exit, 204 .test_cases = drm_sched_basic_tests, 205 }; 206 207 static void drm_sched_basic_cancel(struct kunit *test) 208 { 209 struct drm_mock_sched_entity *entity; 210 struct drm_mock_scheduler *sched; 211 struct drm_mock_sched_job *job; 212 bool done; 213 214 /* 215 * Check that drm_sched_fini() uses the cancel_job() callback to cancel 216 * jobs that are still pending. 217 */ 218 219 sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); 220 entity = drm_mock_sched_entity_new(test, DRM_SCHED_PRIORITY_NORMAL, 221 sched); 222 223 job = drm_mock_sched_job_new(test, entity); 224 225 drm_mock_sched_job_submit(job); 226 227 done = drm_mock_sched_job_wait_scheduled(job, HZ); 228 KUNIT_ASSERT_TRUE(test, done); 229 230 drm_mock_sched_entity_free(entity); 231 drm_mock_sched_fini(sched); 232 233 KUNIT_ASSERT_EQ(test, job->hw_fence.error, -ECANCELED); 234 } 235 236 static struct kunit_case drm_sched_cancel_tests[] = { 237 KUNIT_CASE(drm_sched_basic_cancel), 238 {} 239 }; 240 241 static struct kunit_suite drm_sched_cancel = { 242 .name = "drm_sched_basic_cancel_tests", 243 .init = drm_sched_basic_init, 244 .exit = drm_sched_basic_exit, 245 .test_cases = drm_sched_cancel_tests, 246 }; 247 248 static void drm_sched_basic_timeout(struct kunit *test) 249 { 250 struct drm_mock_scheduler *sched = test->priv; 251 struct drm_mock_sched_entity *entity; 252 struct drm_mock_sched_job *job; 253 bool done; 254 255 /* 256 * Submit a single job against a scheduler with the timeout configured 257 * and verify that the timeout handling will run if the backend fails 258 * to complete it in time. 259 */ 260 261 entity = drm_mock_sched_entity_new(test, 262 DRM_SCHED_PRIORITY_NORMAL, 263 sched); 264 job = drm_mock_sched_job_new(test, entity); 265 266 drm_mock_sched_job_submit(job); 267 268 done = drm_mock_sched_job_wait_scheduled(job, HZ); 269 KUNIT_ASSERT_TRUE(test, done); 270 271 done = drm_mock_sched_job_wait_finished(job, HZ / 2); 272 KUNIT_ASSERT_FALSE(test, done); 273 274 KUNIT_ASSERT_EQ(test, 275 job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT, 276 0); 277 278 done = drm_mock_sched_job_wait_finished(job, HZ); 279 KUNIT_ASSERT_FALSE(test, done); 280 281 KUNIT_ASSERT_EQ(test, 282 job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT, 283 DRM_MOCK_SCHED_JOB_TIMEDOUT); 284 285 drm_mock_sched_entity_free(entity); 286 } 287 288 static struct kunit_case drm_sched_timeout_tests[] = { 289 KUNIT_CASE(drm_sched_basic_timeout), 290 {} 291 }; 292 293 static struct kunit_suite drm_sched_timeout = { 294 .name = "drm_sched_basic_timeout_tests", 295 .init = drm_sched_timeout_init, 296 .exit = drm_sched_basic_exit, 297 .test_cases = drm_sched_timeout_tests, 298 }; 299 300 static void drm_sched_priorities(struct kunit *test) 301 { 302 struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT]; 303 struct drm_mock_scheduler *sched = test->priv; 304 struct drm_mock_sched_job *job; 305 const unsigned int qd = 100; 306 unsigned int i, cur_ent = 0; 307 enum drm_sched_priority p; 308 bool done; 309 310 /* 311 * Submit a bunch of jobs against entities configured with different 312 * priorities. 313 */ 314 315 BUILD_BUG_ON(DRM_SCHED_PRIORITY_KERNEL > DRM_SCHED_PRIORITY_LOW); 316 BUILD_BUG_ON(ARRAY_SIZE(entity) != DRM_SCHED_PRIORITY_COUNT); 317 318 for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++) 319 entity[p] = drm_mock_sched_entity_new(test, p, sched); 320 321 for (i = 0; i < qd; i++) { 322 job = drm_mock_sched_job_new(test, entity[cur_ent++]); 323 cur_ent %= ARRAY_SIZE(entity); 324 drm_mock_sched_job_set_duration_us(job, 1000); 325 drm_mock_sched_job_submit(job); 326 } 327 328 done = drm_mock_sched_job_wait_finished(job, HZ); 329 KUNIT_ASSERT_TRUE(test, done); 330 331 for (i = 0; i < ARRAY_SIZE(entity); i++) 332 drm_mock_sched_entity_free(entity[i]); 333 } 334 335 static void drm_sched_change_priority(struct kunit *test) 336 { 337 struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT]; 338 struct drm_mock_scheduler *sched = test->priv; 339 struct drm_mock_sched_job *job; 340 const unsigned int qd = 1000; 341 unsigned int i, cur_ent = 0; 342 enum drm_sched_priority p; 343 344 /* 345 * Submit a bunch of jobs against entities configured with different 346 * priorities and while waiting for them to complete, periodically keep 347 * changing their priorities. 348 * 349 * We set up the queue-depth (qd) and job duration so the priority 350 * changing loop has some time to interact with submissions to the 351 * backend and job completions as they progress. 352 */ 353 354 for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++) 355 entity[p] = drm_mock_sched_entity_new(test, p, sched); 356 357 for (i = 0; i < qd; i++) { 358 job = drm_mock_sched_job_new(test, entity[cur_ent++]); 359 cur_ent %= ARRAY_SIZE(entity); 360 drm_mock_sched_job_set_duration_us(job, 1000); 361 drm_mock_sched_job_submit(job); 362 } 363 364 do { 365 drm_sched_entity_set_priority(&entity[cur_ent]->base, 366 (entity[cur_ent]->base.priority + 1) % 367 DRM_SCHED_PRIORITY_COUNT); 368 cur_ent++; 369 cur_ent %= ARRAY_SIZE(entity); 370 usleep_range(200, 500); 371 } while (!drm_mock_sched_job_is_finished(job)); 372 373 for (i = 0; i < ARRAY_SIZE(entity); i++) 374 drm_mock_sched_entity_free(entity[i]); 375 } 376 377 static struct kunit_case drm_sched_priority_tests[] = { 378 KUNIT_CASE(drm_sched_priorities), 379 KUNIT_CASE(drm_sched_change_priority), 380 {} 381 }; 382 383 static struct kunit_suite drm_sched_priority = { 384 .name = "drm_sched_basic_priority_tests", 385 .init = drm_sched_basic_init, 386 .exit = drm_sched_basic_exit, 387 .test_cases = drm_sched_priority_tests, 388 }; 389 390 static void drm_sched_test_modify_sched(struct kunit *test) 391 { 392 unsigned int i, cur_ent = 0, cur_sched = 0; 393 struct drm_mock_sched_entity *entity[13]; 394 struct drm_mock_scheduler *sched[3]; 395 struct drm_mock_sched_job *job; 396 const unsigned int qd = 1000; 397 398 /* 399 * Submit a bunch of jobs against entities configured with different 400 * schedulers and while waiting for them to complete, periodically keep 401 * changing schedulers associated with each entity. 402 * 403 * We set up the queue-depth (qd) and job duration so the sched modify 404 * loop has some time to interact with submissions to the backend and 405 * job completions as they progress. 406 * 407 * For the number of schedulers and entities we use primes in order to 408 * perturb the entity->sched assignments with less of a regular pattern. 409 */ 410 411 for (i = 0; i < ARRAY_SIZE(sched); i++) 412 sched[i] = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); 413 414 for (i = 0; i < ARRAY_SIZE(entity); i++) 415 entity[i] = drm_mock_sched_entity_new(test, 416 DRM_SCHED_PRIORITY_NORMAL, 417 sched[i % ARRAY_SIZE(sched)]); 418 419 for (i = 0; i < qd; i++) { 420 job = drm_mock_sched_job_new(test, entity[cur_ent++]); 421 cur_ent %= ARRAY_SIZE(entity); 422 drm_mock_sched_job_set_duration_us(job, 1000); 423 drm_mock_sched_job_submit(job); 424 } 425 426 do { 427 struct drm_gpu_scheduler *modify; 428 429 usleep_range(200, 500); 430 cur_ent++; 431 cur_ent %= ARRAY_SIZE(entity); 432 cur_sched++; 433 cur_sched %= ARRAY_SIZE(sched); 434 modify = &sched[cur_sched]->base; 435 drm_sched_entity_modify_sched(&entity[cur_ent]->base, &modify, 436 1); 437 } while (!drm_mock_sched_job_is_finished(job)); 438 439 for (i = 0; i < ARRAY_SIZE(entity); i++) 440 drm_mock_sched_entity_free(entity[i]); 441 442 for (i = 0; i < ARRAY_SIZE(sched); i++) 443 drm_mock_sched_fini(sched[i]); 444 } 445 446 static struct kunit_case drm_sched_modify_sched_tests[] = { 447 KUNIT_CASE(drm_sched_test_modify_sched), 448 {} 449 }; 450 451 static struct kunit_suite drm_sched_modify_sched = { 452 .name = "drm_sched_basic_modify_sched_tests", 453 .test_cases = drm_sched_modify_sched_tests, 454 }; 455 456 static void drm_sched_test_credits(struct kunit *test) 457 { 458 struct drm_mock_sched_entity *entity; 459 struct drm_mock_scheduler *sched; 460 struct drm_mock_sched_job *job[2]; 461 bool done; 462 int i; 463 464 /* 465 * Check that the configured credit limit is respected. 466 */ 467 468 sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT); 469 sched->base.credit_limit = 1; 470 471 entity = drm_mock_sched_entity_new(test, 472 DRM_SCHED_PRIORITY_NORMAL, 473 sched); 474 475 job[0] = drm_mock_sched_job_new(test, entity); 476 job[1] = drm_mock_sched_job_new(test, entity); 477 478 drm_mock_sched_job_submit(job[0]); 479 drm_mock_sched_job_submit(job[1]); 480 481 done = drm_mock_sched_job_wait_scheduled(job[0], HZ); 482 KUNIT_ASSERT_TRUE(test, done); 483 484 done = drm_mock_sched_job_wait_scheduled(job[1], HZ); 485 KUNIT_ASSERT_FALSE(test, done); 486 487 i = drm_mock_sched_advance(sched, 1); 488 KUNIT_ASSERT_EQ(test, i, 1); 489 490 done = drm_mock_sched_job_wait_scheduled(job[1], HZ); 491 KUNIT_ASSERT_TRUE(test, done); 492 493 i = drm_mock_sched_advance(sched, 1); 494 KUNIT_ASSERT_EQ(test, i, 1); 495 496 done = drm_mock_sched_job_wait_finished(job[1], HZ); 497 KUNIT_ASSERT_TRUE(test, done); 498 499 drm_mock_sched_entity_free(entity); 500 drm_mock_sched_fini(sched); 501 } 502 503 static struct kunit_case drm_sched_credits_tests[] = { 504 KUNIT_CASE(drm_sched_test_credits), 505 {} 506 }; 507 508 static struct kunit_suite drm_sched_credits = { 509 .name = "drm_sched_basic_credits_tests", 510 .test_cases = drm_sched_credits_tests, 511 }; 512 513 kunit_test_suites(&drm_sched_basic, 514 &drm_sched_timeout, 515 &drm_sched_cancel, 516 &drm_sched_priority, 517 &drm_sched_modify_sched, 518 &drm_sched_credits); 519