Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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
15static 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
22static 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
29static int drm_sched_timeout_init(struct kunit *test)
30{
31 test->priv = drm_mock_sched_new(test, HZ);
32
33 return 0;
34}
35
36static 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
71struct 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
79static 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
108static void
109drm_sched_basic_desc(const struct drm_sched_basic_params *params, char *desc)
110{
111 strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
112}
113
114KUNIT_ARRAY_PARAM(drm_sched_basic, drm_sched_basic_cases, drm_sched_basic_desc);
115
116static 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
152static 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
193static 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
200static 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
207static void drm_sched_basic_timeout(struct kunit *test)
208{
209 struct drm_mock_scheduler *sched = test->priv;
210 struct drm_mock_sched_entity *entity;
211 struct drm_mock_sched_job *job;
212 bool done;
213
214 /*
215 * Submit a single job against a scheduler with the timeout configured
216 * and verify that the timeout handling will run if the backend fails
217 * to complete it in time.
218 */
219
220 entity = drm_mock_sched_entity_new(test,
221 DRM_SCHED_PRIORITY_NORMAL,
222 sched);
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 done = drm_mock_sched_job_wait_finished(job, HZ / 2);
231 KUNIT_ASSERT_FALSE(test, done);
232
233 KUNIT_ASSERT_EQ(test,
234 job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT,
235 0);
236
237 done = drm_mock_sched_job_wait_finished(job, HZ);
238 KUNIT_ASSERT_FALSE(test, done);
239
240 KUNIT_ASSERT_EQ(test,
241 job->flags & DRM_MOCK_SCHED_JOB_TIMEDOUT,
242 DRM_MOCK_SCHED_JOB_TIMEDOUT);
243
244 drm_mock_sched_entity_free(entity);
245}
246
247static struct kunit_case drm_sched_timeout_tests[] = {
248 KUNIT_CASE(drm_sched_basic_timeout),
249 {}
250};
251
252static struct kunit_suite drm_sched_timeout = {
253 .name = "drm_sched_basic_timeout_tests",
254 .init = drm_sched_timeout_init,
255 .exit = drm_sched_basic_exit,
256 .test_cases = drm_sched_timeout_tests,
257};
258
259static void drm_sched_priorities(struct kunit *test)
260{
261 struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
262 struct drm_mock_scheduler *sched = test->priv;
263 struct drm_mock_sched_job *job;
264 const unsigned int qd = 100;
265 unsigned int i, cur_ent = 0;
266 enum drm_sched_priority p;
267 bool done;
268
269 /*
270 * Submit a bunch of jobs against entities configured with different
271 * priorities.
272 */
273
274 BUILD_BUG_ON(DRM_SCHED_PRIORITY_KERNEL > DRM_SCHED_PRIORITY_LOW);
275 BUILD_BUG_ON(ARRAY_SIZE(entity) != DRM_SCHED_PRIORITY_COUNT);
276
277 for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
278 entity[p] = drm_mock_sched_entity_new(test, p, sched);
279
280 for (i = 0; i < qd; i++) {
281 job = drm_mock_sched_job_new(test, entity[cur_ent++]);
282 cur_ent %= ARRAY_SIZE(entity);
283 drm_mock_sched_job_set_duration_us(job, 1000);
284 drm_mock_sched_job_submit(job);
285 }
286
287 done = drm_mock_sched_job_wait_finished(job, HZ);
288 KUNIT_ASSERT_TRUE(test, done);
289
290 for (i = 0; i < ARRAY_SIZE(entity); i++)
291 drm_mock_sched_entity_free(entity[i]);
292}
293
294static void drm_sched_change_priority(struct kunit *test)
295{
296 struct drm_mock_sched_entity *entity[DRM_SCHED_PRIORITY_COUNT];
297 struct drm_mock_scheduler *sched = test->priv;
298 struct drm_mock_sched_job *job;
299 const unsigned int qd = 1000;
300 unsigned int i, cur_ent = 0;
301 enum drm_sched_priority p;
302
303 /*
304 * Submit a bunch of jobs against entities configured with different
305 * priorities and while waiting for them to complete, periodically keep
306 * changing their priorities.
307 *
308 * We set up the queue-depth (qd) and job duration so the priority
309 * changing loop has some time to interact with submissions to the
310 * backend and job completions as they progress.
311 */
312
313 for (p = DRM_SCHED_PRIORITY_KERNEL; p <= DRM_SCHED_PRIORITY_LOW; p++)
314 entity[p] = drm_mock_sched_entity_new(test, p, sched);
315
316 for (i = 0; i < qd; i++) {
317 job = drm_mock_sched_job_new(test, entity[cur_ent++]);
318 cur_ent %= ARRAY_SIZE(entity);
319 drm_mock_sched_job_set_duration_us(job, 1000);
320 drm_mock_sched_job_submit(job);
321 }
322
323 do {
324 drm_sched_entity_set_priority(&entity[cur_ent]->base,
325 (entity[cur_ent]->base.priority + 1) %
326 DRM_SCHED_PRIORITY_COUNT);
327 cur_ent++;
328 cur_ent %= ARRAY_SIZE(entity);
329 usleep_range(200, 500);
330 } while (!drm_mock_sched_job_is_finished(job));
331
332 for (i = 0; i < ARRAY_SIZE(entity); i++)
333 drm_mock_sched_entity_free(entity[i]);
334}
335
336static struct kunit_case drm_sched_priority_tests[] = {
337 KUNIT_CASE(drm_sched_priorities),
338 KUNIT_CASE(drm_sched_change_priority),
339 {}
340};
341
342static struct kunit_suite drm_sched_priority = {
343 .name = "drm_sched_basic_priority_tests",
344 .init = drm_sched_basic_init,
345 .exit = drm_sched_basic_exit,
346 .test_cases = drm_sched_priority_tests,
347};
348
349static void drm_sched_test_modify_sched(struct kunit *test)
350{
351 unsigned int i, cur_ent = 0, cur_sched = 0;
352 struct drm_mock_sched_entity *entity[13];
353 struct drm_mock_scheduler *sched[3];
354 struct drm_mock_sched_job *job;
355 const unsigned int qd = 1000;
356
357 /*
358 * Submit a bunch of jobs against entities configured with different
359 * schedulers and while waiting for them to complete, periodically keep
360 * changing schedulers associated with each entity.
361 *
362 * We set up the queue-depth (qd) and job duration so the sched modify
363 * loop has some time to interact with submissions to the backend and
364 * job completions as they progress.
365 *
366 * For the number of schedulers and entities we use primes in order to
367 * perturb the entity->sched assignments with less of a regular pattern.
368 */
369
370 for (i = 0; i < ARRAY_SIZE(sched); i++)
371 sched[i] = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
372
373 for (i = 0; i < ARRAY_SIZE(entity); i++)
374 entity[i] = drm_mock_sched_entity_new(test,
375 DRM_SCHED_PRIORITY_NORMAL,
376 sched[i % ARRAY_SIZE(sched)]);
377
378 for (i = 0; i < qd; i++) {
379 job = drm_mock_sched_job_new(test, entity[cur_ent++]);
380 cur_ent %= ARRAY_SIZE(entity);
381 drm_mock_sched_job_set_duration_us(job, 1000);
382 drm_mock_sched_job_submit(job);
383 }
384
385 do {
386 struct drm_gpu_scheduler *modify;
387
388 usleep_range(200, 500);
389 cur_ent++;
390 cur_ent %= ARRAY_SIZE(entity);
391 cur_sched++;
392 cur_sched %= ARRAY_SIZE(sched);
393 modify = &sched[cur_sched]->base;
394 drm_sched_entity_modify_sched(&entity[cur_ent]->base, &modify,
395 1);
396 } while (!drm_mock_sched_job_is_finished(job));
397
398 for (i = 0; i < ARRAY_SIZE(entity); i++)
399 drm_mock_sched_entity_free(entity[i]);
400
401 for (i = 0; i < ARRAY_SIZE(sched); i++)
402 drm_mock_sched_fini(sched[i]);
403}
404
405static struct kunit_case drm_sched_modify_sched_tests[] = {
406 KUNIT_CASE(drm_sched_test_modify_sched),
407 {}
408};
409
410static struct kunit_suite drm_sched_modify_sched = {
411 .name = "drm_sched_basic_modify_sched_tests",
412 .test_cases = drm_sched_modify_sched_tests,
413};
414
415static void drm_sched_test_credits(struct kunit *test)
416{
417 struct drm_mock_sched_entity *entity;
418 struct drm_mock_scheduler *sched;
419 struct drm_mock_sched_job *job[2];
420 bool done;
421 int i;
422
423 /*
424 * Check that the configured credit limit is respected.
425 */
426
427 sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
428 sched->base.credit_limit = 1;
429
430 entity = drm_mock_sched_entity_new(test,
431 DRM_SCHED_PRIORITY_NORMAL,
432 sched);
433
434 job[0] = drm_mock_sched_job_new(test, entity);
435 job[1] = drm_mock_sched_job_new(test, entity);
436
437 drm_mock_sched_job_submit(job[0]);
438 drm_mock_sched_job_submit(job[1]);
439
440 done = drm_mock_sched_job_wait_scheduled(job[0], HZ);
441 KUNIT_ASSERT_TRUE(test, done);
442
443 done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
444 KUNIT_ASSERT_FALSE(test, done);
445
446 i = drm_mock_sched_advance(sched, 1);
447 KUNIT_ASSERT_EQ(test, i, 1);
448
449 done = drm_mock_sched_job_wait_scheduled(job[1], HZ);
450 KUNIT_ASSERT_TRUE(test, done);
451
452 i = drm_mock_sched_advance(sched, 1);
453 KUNIT_ASSERT_EQ(test, i, 1);
454
455 done = drm_mock_sched_job_wait_finished(job[1], HZ);
456 KUNIT_ASSERT_TRUE(test, done);
457
458 drm_mock_sched_entity_free(entity);
459 drm_mock_sched_fini(sched);
460}
461
462static struct kunit_case drm_sched_credits_tests[] = {
463 KUNIT_CASE(drm_sched_test_credits),
464 {}
465};
466
467static struct kunit_suite drm_sched_credits = {
468 .name = "drm_sched_basic_credits_tests",
469 .test_cases = drm_sched_credits_tests,
470};
471
472kunit_test_suites(&drm_sched_basic,
473 &drm_sched_timeout,
474 &drm_sched_priority,
475 &drm_sched_modify_sched,
476 &drm_sched_credits);