xref: /linux/drivers/gpu/drm/scheduler/tests/tests_basic.c (revision 25489a4f556414445d342951615178368ee45cde)
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_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 
247 static struct kunit_case drm_sched_timeout_tests[] = {
248 	KUNIT_CASE(drm_sched_basic_timeout),
249 	{}
250 };
251 
252 static 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 
259 static 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 
294 static 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 
336 static struct kunit_case drm_sched_priority_tests[] = {
337 	KUNIT_CASE(drm_sched_priorities),
338 	KUNIT_CASE(drm_sched_change_priority),
339 	{}
340 };
341 
342 static 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 
349 static 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 
405 static struct kunit_case drm_sched_modify_sched_tests[] = {
406 	KUNIT_CASE(drm_sched_test_modify_sched),
407 	{}
408 };
409 
410 static 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 
415 static 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 
462 static struct kunit_case drm_sched_credits_tests[] = {
463 	KUNIT_CASE(drm_sched_test_credits),
464 	{}
465 };
466 
467 static struct kunit_suite drm_sched_credits = {
468 	.name = "drm_sched_basic_credits_tests",
469 	.test_cases = drm_sched_credits_tests,
470 };
471 
472 kunit_test_suites(&drm_sched_basic,
473 		  &drm_sched_timeout,
474 		  &drm_sched_priority,
475 		  &drm_sched_modify_sched,
476 		  &drm_sched_credits);
477