| 1 | |
| 2 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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 |
|
| 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);
|