xref: /linux/drivers/gpu/drm/scheduler/tests/tests_scheduler.c (revision c06b6cde2a1c3bcbb561bd57bb6f34eae9030921)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (c) 2025 Valve Corporation */
3 
4 #include <linux/delay.h>
5 #include <linux/kthread.h>
6 #include <linux/ktime.h>
7 #include <linux/math64.h>
8 
9 #include "sched_tests.h"
10 
11 /*
12  * DRM scheduler tests exercise load balancing decisions ie. entity selection
13  * logic.
14  */
15 
16 static int drm_sched_scheduler_init(struct kunit *test)
17 {
18 	struct drm_mock_scheduler *sched;
19 
20 	sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
21 	sched->base.credit_limit = 1;
22 
23 	test->priv = sched;
24 
25 	return 0;
26 }
27 
28 static int drm_sched_scheduler_init2(struct kunit *test)
29 {
30 	struct drm_mock_scheduler *sched;
31 
32 	sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
33 	sched->base.credit_limit = 2;
34 
35 	test->priv = sched;
36 
37 	return 0;
38 }
39 
40 static void drm_sched_scheduler_exit(struct kunit *test)
41 {
42 	struct drm_mock_scheduler *sched = test->priv;
43 
44 	drm_mock_sched_fini(sched);
45 }
46 
47 static void drm_sched_scheduler_queue_overhead(struct kunit *test)
48 {
49 	struct drm_mock_scheduler *sched = test->priv;
50 	struct drm_mock_sched_entity *entity;
51 	const unsigned int job_us = 1000;
52 	const unsigned int jobs = 1000;
53 	const unsigned int total_us = jobs * job_us;
54 	struct drm_mock_sched_job *job, *first;
55 	ktime_t start, end;
56 	bool done;
57 	int i;
58 
59 	/*
60 	 * Deep queue job at a time processing (single credit).
61 	 *
62 	 * This measures the overhead of picking and processing a job at a time
63 	 * by comparing the ideal total "GPU" time of all submitted jobs versus
64 	 * the time actually taken.
65 	 */
66 
67 	KUNIT_ASSERT_EQ(test, sched->base.credit_limit, 1);
68 
69 	entity = drm_mock_sched_entity_new(test,
70 					   DRM_SCHED_PRIORITY_NORMAL,
71 					   sched);
72 
73 	for (i = 0; i <= jobs; i++) {
74 		job = drm_mock_sched_job_new(test, entity);
75 		if (i == 0)
76 			first = job; /* Extra first job blocks the queue */
77 		else
78 			drm_mock_sched_job_set_duration_us(job, job_us);
79 		drm_mock_sched_job_submit(job);
80 	}
81 
82 	done = drm_mock_sched_job_wait_scheduled(first, HZ);
83 	KUNIT_ASSERT_TRUE(test, done);
84 
85 	start = ktime_get();
86 	i = drm_mock_sched_advance(sched, 1); /* Release the queue */
87 	KUNIT_ASSERT_EQ(test, i, 1);
88 
89 	/* Wait with a safe margin to avoid every failing. */
90 	done = drm_mock_sched_job_wait_finished(job,
91 						usecs_to_jiffies(total_us) * 5);
92 	end = ktime_get();
93 	KUNIT_ASSERT_TRUE(test, done);
94 
95 	pr_info("Expected %uus, actual %lldus\n",
96 		total_us,
97 		ktime_to_us(ktime_sub(end, start)));
98 
99 	drm_mock_sched_entity_free(entity);
100 }
101 
102 static void drm_sched_scheduler_ping_pong(struct kunit *test)
103 {
104 	struct drm_mock_sched_job *job, *first, *prev = NULL;
105 	struct drm_mock_scheduler *sched = test->priv;
106 	struct drm_mock_sched_entity *entity[2];
107 	const unsigned int job_us = 1000;
108 	const unsigned int jobs = 1000;
109 	const unsigned int total_us = jobs * job_us;
110 	ktime_t start, end;
111 	bool done;
112 	int i;
113 
114 	/*
115 	 * Two entitites in inter-dependency chain.
116 	 *
117 	 * This measures the overhead of picking and processing a job at a time,
118 	 * where each job depends on the previous one from the diffferent
119 	 * entity, by comparing the ideal total "GPU" time of all submitted jobs
120 	 * versus the time actually taken.
121 	 */
122 
123 	KUNIT_ASSERT_EQ(test, sched->base.credit_limit, 1);
124 
125 	for (i = 0; i < ARRAY_SIZE(entity); i++)
126 		entity[i] = drm_mock_sched_entity_new(test,
127 						      DRM_SCHED_PRIORITY_NORMAL,
128 						      sched);
129 
130 	for (i = 0; i <= jobs; i++) {
131 		job = drm_mock_sched_job_new(test, entity[i & 1]);
132 		if (i == 0)
133 			first = job; /* Extra first job blocks the queue */
134 		else
135 			drm_mock_sched_job_set_duration_us(job, job_us);
136 		if (prev)
137 			drm_sched_job_add_dependency(&job->base,
138 						     dma_fence_get(&prev->base.s_fence->finished));
139 		drm_mock_sched_job_submit(job);
140 		prev = job;
141 	}
142 
143 	done = drm_mock_sched_job_wait_scheduled(first, HZ);
144 	KUNIT_ASSERT_TRUE(test, done);
145 
146 	start = ktime_get();
147 	i = drm_mock_sched_advance(sched, 1); /* Release the queue */
148 	KUNIT_ASSERT_EQ(test, i, 1);
149 
150 	/* Wait with a safe margin to avoid every failing. */
151 	done = drm_mock_sched_job_wait_finished(job,
152 						usecs_to_jiffies(total_us) * 5);
153 	end = ktime_get();
154 	KUNIT_ASSERT_TRUE(test, done);
155 
156 	pr_info("Expected %uus, actual %lldus\n",
157 		total_us,
158 		ktime_to_us(ktime_sub(end, start)));
159 
160 	for (i = 0; i < ARRAY_SIZE(entity); i++)
161 		drm_mock_sched_entity_free(entity[i]);
162 }
163 
164 static struct kunit_case drm_sched_scheduler_overhead_tests[] = {
165 	KUNIT_CASE_SLOW(drm_sched_scheduler_queue_overhead),
166 	KUNIT_CASE_SLOW(drm_sched_scheduler_ping_pong),
167 	{}
168 };
169 
170 static struct kunit_suite drm_sched_scheduler_overhead = {
171 	.name = "drm_sched_scheduler_overhead_tests",
172 	.init = drm_sched_scheduler_init,
173 	.exit = drm_sched_scheduler_exit,
174 	.test_cases = drm_sched_scheduler_overhead_tests,
175 };
176 
177 /*
178  * struct drm_sched_client_params - describe a workload emitted from a client
179  *
180  * A simulated client will create an entity with a scheduling @priority and emit
181  * jobs in a loop where each iteration will consist of:
182  *
183  * 1. Submit @job_cnt jobs, each with a set duration of @job_us.
184  * 2. If @sync is true wait for last submitted job to finish.
185  * 3. Sleep for @wait_us micro-seconds.
186  * 4. Repeat.
187  */
188 struct drm_sched_client_params {
189 	enum drm_sched_priority priority;
190 	unsigned int job_cnt;
191 	unsigned int job_us;
192 	bool sync;
193 	unsigned int wait_us;
194 };
195 
196 struct drm_sched_test_params {
197 	const char *description;
198 	unsigned int num_clients;
199 	struct drm_sched_client_params client[2];
200 };
201 
202 static const struct drm_sched_test_params drm_sched_cases[] = {
203 	{
204 		.description = "Normal priority and normal priority",
205 		.client[0] = {
206 			.priority = DRM_SCHED_PRIORITY_NORMAL,
207 			.job_cnt = 1,
208 			.job_us = 8000,
209 			.wait_us = 0,
210 			.sync = false,
211 		},
212 		.client[1] = {
213 			.priority = DRM_SCHED_PRIORITY_NORMAL,
214 			.job_cnt = 1,
215 			.job_us = 8000,
216 			.wait_us = 0,
217 			.sync = false,
218 		},
219 	},
220 	{
221 		.description = "Normal priority and low priority",
222 		.client[0] = {
223 			.priority = DRM_SCHED_PRIORITY_NORMAL,
224 			.job_cnt = 1,
225 			.job_us = 8000,
226 			.wait_us = 0,
227 			.sync = false,
228 		},
229 		.client[1] = {
230 			.priority = DRM_SCHED_PRIORITY_LOW,
231 			.job_cnt = 1,
232 			.job_us = 8000,
233 			.wait_us = 0,
234 			.sync = false,
235 		},
236 	},
237 	{
238 		.description = "High priority and normal priority",
239 		.client[0] = {
240 			.priority = DRM_SCHED_PRIORITY_HIGH,
241 			.job_cnt = 1,
242 			.job_us = 8000,
243 			.wait_us = 0,
244 			.sync = false,
245 		},
246 		.client[1] = {
247 			.priority = DRM_SCHED_PRIORITY_NORMAL,
248 			.job_cnt = 1,
249 			.job_us = 8000,
250 			.wait_us = 0,
251 			.sync = false,
252 		},
253 	},
254 	{
255 		.description = "High priority and low priority",
256 		.client[0] = {
257 			.priority = DRM_SCHED_PRIORITY_HIGH,
258 			.job_cnt = 1,
259 			.job_us = 8000,
260 			.wait_us = 0,
261 			.sync = false,
262 		},
263 		.client[1] = {
264 			.priority = DRM_SCHED_PRIORITY_LOW,
265 			.job_cnt = 1,
266 			.job_us = 8000,
267 			.wait_us = 0,
268 			.sync = false,
269 		},
270 	},
271 	{
272 		.description = "50% and 50%",
273 		.client[0] = {
274 			.priority = DRM_SCHED_PRIORITY_NORMAL,
275 			.job_cnt = 1,
276 			.job_us = 1500,
277 			.wait_us = 1500,
278 			.sync = true,
279 		},
280 		.client[1] = {
281 			.priority = DRM_SCHED_PRIORITY_NORMAL,
282 			.job_cnt = 1,
283 			.job_us = 2500,
284 			.wait_us = 2500,
285 			.sync = true,
286 		},
287 	},
288 	{
289 		.description = "50% and 50% low priority",
290 		.client[0] = {
291 			.priority = DRM_SCHED_PRIORITY_NORMAL,
292 			.job_cnt = 1,
293 			.job_us = 1500,
294 			.wait_us = 1500,
295 			.sync = true,
296 		},
297 		.client[1] = {
298 			.priority = DRM_SCHED_PRIORITY_LOW,
299 			.job_cnt = 1,
300 			.job_us = 2500,
301 			.wait_us = 2500,
302 			.sync = true,
303 		},
304 	},
305 	{
306 		.description = "50% high priority and 50%",
307 		.client[0] = {
308 			.priority = DRM_SCHED_PRIORITY_HIGH,
309 			.job_cnt = 1,
310 			.job_us = 1500,
311 			.wait_us = 1500,
312 			.sync = true,
313 		},
314 		.client[1] = {
315 			.priority = DRM_SCHED_PRIORITY_NORMAL,
316 			.job_cnt = 1,
317 			.job_us = 2500,
318 			.wait_us = 2500,
319 			.sync = true,
320 		},
321 	},
322 	{
323 		.description = "Low priority hog and interactive client",
324 		.client[0] = {
325 			.priority = DRM_SCHED_PRIORITY_LOW,
326 			.job_cnt = 3,
327 			.job_us = 2500,
328 			.wait_us = 500,
329 			.sync = false,
330 		},
331 		.client[1] = {
332 			.priority = DRM_SCHED_PRIORITY_NORMAL,
333 			.job_cnt = 1,
334 			.job_us = 500,
335 			.wait_us = 10000,
336 			.sync = true,
337 		},
338 	},
339 	{
340 		.description = "Heavy rendering and interactive client",
341 		.client[0] = {
342 			.priority = DRM_SCHED_PRIORITY_NORMAL,
343 			.job_cnt = 3,
344 			.job_us = 2500,
345 			.wait_us = 2500,
346 			.sync = true,
347 		},
348 		.client[1] = {
349 			.priority = DRM_SCHED_PRIORITY_NORMAL,
350 			.job_cnt = 1,
351 			.job_us = 1000,
352 			.wait_us = 9000,
353 			.sync = true,
354 		},
355 	},
356 	{
357 		.description = "Very heavy rendering and interactive client",
358 		.client[0] = {
359 			.priority = DRM_SCHED_PRIORITY_NORMAL,
360 			.job_cnt = 4,
361 			.job_us = 50000,
362 			.wait_us = 1,
363 			.sync = true,
364 		},
365 		.client[1] = {
366 			.priority = DRM_SCHED_PRIORITY_NORMAL,
367 			.job_cnt = 1,
368 			.job_us = 1000,
369 			.wait_us = 9000,
370 			.sync = true,
371 		},
372 	},
373 };
374 
375 static void
376 drm_sched_desc(const struct drm_sched_test_params *params, char *desc)
377 {
378 	strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
379 }
380 
381 KUNIT_ARRAY_PARAM(drm_sched_scheduler_two_clients,
382 		  drm_sched_cases,
383 		  drm_sched_desc);
384 
385 /*
386  * struct test_client_stats - track client stats
387  *
388  * For each client executing a simulated workload we track some timings for
389  * which we are interested in the minimum of all iterations (@min_us), maximum
390  * (@max_us) and the overall total for all iterations (@tot_us).
391  */
392 struct test_client_stats {
393 	unsigned int min_us;
394 	unsigned int max_us;
395 	unsigned long tot_us;
396 };
397 
398 /*
399  * struct test_client - a simulated userspace client submitting scheduler work
400  *
401  * Each client executing a simulated workload is represented by one of these.
402  *
403  * Each of them instantiates a scheduling @entity and executes a workloads as
404  * defined in @params. Based on those @params the theoretical execution time of
405  * the client is calculated as @ideal_duration, while the actual wall time is
406  * tracked in @duration (calculated based on the @start and @end client time-
407  * stamps).
408  *
409  * Numerical @id is assigned to each for logging purposes.
410  *
411  * @worker and @work are used to provide an independent execution context from
412  * which scheduler jobs are submitted.
413  *
414  * During execution statistics on how long it took to submit and execute one
415  * iteration (whether or not synchronous) is kept in @cycle_time, while
416  * @latency_time tracks the @cycle_time minus the ideal duration of the one
417  * cycle.
418  *
419  * Once the client has completed the set number of iterations it will write the
420  * completion status into @done.
421  */
422 struct test_client {
423 	struct kunit	*test; /* Backpointer to the kunit test. */
424 
425 	struct drm_mock_sched_entity	*entity;
426 	struct kthread_worker		*worker;
427 	struct kthread_work		work;
428 
429 	struct drm_sched_client_params	params;
430 
431 	unsigned int	id;
432 	ktime_t		duration;
433 	ktime_t		ideal_duration;
434 	unsigned int	cycles;
435 	unsigned int	cycle;
436 	ktime_t		start;
437 	ktime_t		end;
438 	bool		done;
439 
440 	struct test_client_stats	cycle_time;
441 	struct test_client_stats	latency_time;
442 };
443 
444 static void
445 update_stats(struct test_client_stats *stats, unsigned int us)
446 {
447 	if (us > stats->max_us)
448 		stats->max_us = us;
449 	if (us < stats->min_us)
450 		stats->min_us = us;
451 	stats->tot_us += us;
452 }
453 
454 static unsigned int
455 get_stats_avg(struct test_client_stats *stats, unsigned int cycles)
456 {
457 	return div_u64(stats->tot_us, cycles);
458 }
459 
460 static void drm_sched_client_work(struct kthread_work *work)
461 {
462 	struct test_client *client = container_of(work, typeof(*client), work);
463 	const long sync_wait = MAX_SCHEDULE_TIMEOUT;
464 	unsigned int cycle, work_us, period_us;
465 	struct drm_mock_sched_job *job = NULL;
466 
467 	work_us = client->params.job_cnt * client->params.job_us;
468 	period_us = work_us + client->params.wait_us;
469 	client->cycles =
470 		DIV_ROUND_UP((unsigned int)ktime_to_us(client->duration),
471 			     period_us);
472 	client->ideal_duration = us_to_ktime(client->cycles * period_us);
473 
474 	client->start = ktime_get();
475 
476 	for (cycle = 0; cycle < client->cycles; cycle++) {
477 		ktime_t cycle_time;
478 		unsigned int batch;
479 		unsigned long us;
480 
481 		if (READ_ONCE(client->done))
482 			break;
483 
484 		cycle_time = ktime_get();
485 		for (batch = 0; batch < client->params.job_cnt; batch++) {
486 			job = drm_mock_sched_job_new(client->test,
487 						     client->entity);
488 			drm_mock_sched_job_set_duration_us(job,
489 							   client->params.job_us);
490 			drm_mock_sched_job_submit(job);
491 		}
492 
493 		if (client->params.sync)
494 			drm_mock_sched_job_wait_finished(job, sync_wait);
495 
496 		cycle_time = ktime_sub(ktime_get(), cycle_time);
497 		us = ktime_to_us(cycle_time);
498 		update_stats(&client->cycle_time, us);
499 		if (ktime_to_us(cycle_time) >= (long)work_us)
500 			us = ktime_to_us(cycle_time) - work_us;
501 		else if (WARN_ON_ONCE(client->params.sync)) /* GPU job took less than expected. */
502 			us = 0;
503 		update_stats(&client->latency_time, us);
504 		WRITE_ONCE(client->cycle, cycle);
505 
506 		if (READ_ONCE(client->done))
507 			break;
508 
509 		if (client->params.wait_us)
510 			fsleep(client->params.wait_us);
511 		else if (!client->params.sync)
512 			cond_resched(); /* Do not hog the CPU if fully async. */
513 	}
514 
515 	client->done = drm_mock_sched_job_wait_finished(job, sync_wait);
516 	client->end = ktime_get();
517 }
518 
519 static const char *prio_str(enum drm_sched_priority prio)
520 {
521 	switch (prio) {
522 	case DRM_SCHED_PRIORITY_KERNEL:
523 		return "kernel";
524 	case DRM_SCHED_PRIORITY_LOW:
525 		return "low";
526 	case DRM_SCHED_PRIORITY_NORMAL:
527 		return "normal";
528 	case DRM_SCHED_PRIORITY_HIGH:
529 		return "high";
530 	default:
531 		return "???";
532 	}
533 }
534 
535 static bool client_done(struct test_client *client)
536 {
537 	return READ_ONCE(client->done); /* READ_ONCE to document lockless read from a loop. */
538 }
539 
540 static void drm_sched_scheduler_two_clients_test(struct kunit *test)
541 {
542 	const struct drm_sched_test_params *params = test->param_value;
543 	struct drm_mock_scheduler *sched = test->priv;
544 	struct test_client client[2] = { };
545 	unsigned int prev_cycle[2] = { };
546 	unsigned int i, j;
547 	ktime_t start;
548 
549 	/*
550 	 * Same job stream from two clients.
551 	 */
552 
553 	for (i = 0; i < ARRAY_SIZE(client); i++)
554 		client[i].entity =
555 			drm_mock_sched_entity_new(test,
556 						  params->client[i].priority,
557 						  sched);
558 
559 	for (i = 0; i < ARRAY_SIZE(client); i++) {
560 		client[i].test = test;
561 		client[i].id = i;
562 		client[i].duration = ms_to_ktime(1000);
563 		client[i].params = params->client[i];
564 		client[i].cycle_time.min_us = ~0U;
565 		client[i].latency_time.min_us = ~0U;
566 		client[i].worker =
567 			kthread_create_worker(0, "%s-%u", __func__, i);
568 		if (IS_ERR(client[i].worker)) {
569 			for (j = 0; j < i; j++)
570 				kthread_destroy_worker(client[j].worker);
571 			KUNIT_FAIL(test, "Failed to create worker!\n");
572 		}
573 
574 		kthread_init_work(&client[i].work, drm_sched_client_work);
575 	}
576 
577 	for (i = 0; i < ARRAY_SIZE(client); i++)
578 		kthread_queue_work(client[i].worker, &client[i].work);
579 
580 	/*
581 	 * The clients (workers) can be a mix of async (deep submission queue),
582 	 * sync (one job at a time), or something in between. Therefore it is
583 	 * difficult to display a single metric representing their progress.
584 	 *
585 	 * Each struct drm_sched_client_params describes the actual submission
586 	 * pattern which happens in the following steps:
587 	 *  1. Submit N jobs
588 	 *  2. Wait for last submitted job to finish
589 	 *  3. Sleep for U micro-seconds
590 	 *  4. Goto 1. for C cycles
591 	 *
592 	 * Where number of cycles is calculated to match the target client
593 	 * duration from the respective struct drm_sched_test_params.
594 	 *
595 	 * To asses scheduling behaviour what we output for both clients is:
596 	 *  - pct: Percentage progress of the jobs submitted
597 	 *  - cps: "Cycles" per second (where one cycle is one complete
598 	 *         iteration from the above)
599 	 *  -  qd: Number of outstanding jobs in the client/entity
600 	 */
601 
602 	pr_info(" [pct] - Job sumission progress\n"
603 " [cps] - Cycles per second\n"
604 "  [qd] - Number of outstanding jobs in the client/entity\n");
605 	pr_info("%s:\n\t            pct1 cps1 qd1;  pct2 cps2 qd2\n",
606 		params->description);
607 	start = ktime_get();
608 	while (!client_done(&client[0]) || !client_done(&client[1])) {
609 		const unsigned int period_ms = 100;
610 		const unsigned int frequency = 1000 / period_ms;
611 		unsigned int pct[2], qd[2], cycle[2], cps[2];
612 
613 		for (i = 0; i < ARRAY_SIZE(client); i++) {
614 			qd[i] = spsc_queue_count(&client[i].entity->base.job_queue);
615 			cycle[i] = READ_ONCE(client[i].cycle);
616 			cps[i] = DIV_ROUND_UP(100 * frequency *
617 					      (cycle[i] - prev_cycle[i]),
618 					      100);
619 			if (client[i].cycles)
620 				pct[i] = DIV_ROUND_UP(100 * (1 + cycle[i]),
621 						      client[i].cycles);
622 			else
623 				pct[i] = 0;
624 			prev_cycle[i] = cycle[i];
625 		}
626 
627 		if (client_done(&client[0]))
628 			pr_info("\t+%6lldms:               ; %3u %5u %4u\n",
629 				ktime_to_ms(ktime_sub(ktime_get(), start)),
630 				pct[1], cps[1], qd[1]);
631 		else if (client_done(&client[1]))
632 			pr_info("\t+%6lldms: %3u %5u %4u;\n",
633 				ktime_to_ms(ktime_sub(ktime_get(), start)),
634 				pct[0], cps[0], qd[0]);
635 		else
636 			pr_info("\t+%6lldms: %3u %5u %4u; %3u %5u %4u\n",
637 				ktime_to_ms(ktime_sub(ktime_get(), start)),
638 				pct[0], cps[0], qd[0],
639 				pct[1], cps[1], qd[1]);
640 
641 		msleep(period_ms);
642 	}
643 
644 	for (i = 0; i < ARRAY_SIZE(client); i++) {
645 		kthread_flush_work(&client[i].work);
646 		kthread_destroy_worker(client[i].worker);
647 	}
648 
649 	for (i = 0; i < ARRAY_SIZE(client); i++)
650 		KUNIT_ASSERT_TRUE(test, client[i].done);
651 
652 	for (i = 0; i < ARRAY_SIZE(client); i++) {
653 		pr_info("    %u: prio=%s sync=%u elapsed_ms=%lldms (ideal_ms=%lldms) cycle_time(min,avg,max)=%u,%u,%u us latency_time(min,avg,max)=%u,%u,%u us",
654 			i,
655 			prio_str(params->client[i].priority),
656 			params->client[i].sync,
657 			ktime_to_ms(ktime_sub(client[i].end, client[i].start)),
658 			ktime_to_ms(client[i].ideal_duration),
659 			client[i].cycle_time.min_us,
660 			get_stats_avg(&client[i].cycle_time, client[i].cycles),
661 			client[i].cycle_time.max_us,
662 			client[i].latency_time.min_us,
663 			get_stats_avg(&client[i].latency_time, client[i].cycles),
664 			client[i].latency_time.max_us);
665 		drm_mock_sched_entity_free(client[i].entity);
666 	}
667 }
668 
669 static const struct kunit_attributes drm_sched_scheduler_two_clients_attr = {
670 	.speed = KUNIT_SPEED_SLOW,
671 };
672 
673 static struct kunit_case drm_sched_scheduler_two_clients_tests[] = {
674 	KUNIT_CASE_PARAM_ATTR(drm_sched_scheduler_two_clients_test,
675 			      drm_sched_scheduler_two_clients_gen_params,
676 			      drm_sched_scheduler_two_clients_attr),
677 	{}
678 };
679 
680 static struct kunit_suite drm_sched_scheduler_two_clients1 = {
681 	.name = "drm_sched_scheduler_two_clients_one_credit_tests",
682 	.init = drm_sched_scheduler_init,
683 	.exit = drm_sched_scheduler_exit,
684 	.test_cases = drm_sched_scheduler_two_clients_tests,
685 };
686 
687 static struct kunit_suite drm_sched_scheduler_two_clients2 = {
688 	.name = "drm_sched_scheduler_two_clients_two_credits_tests",
689 	.init = drm_sched_scheduler_init2,
690 	.exit = drm_sched_scheduler_exit,
691 	.test_cases = drm_sched_scheduler_two_clients_tests,
692 };
693 
694 static const struct drm_sched_test_params drm_sched_many_cases[] = {
695 	{
696 		.description = "2 clients",
697 		.num_clients = 2,
698 		.client[0] = {
699 			.priority = DRM_SCHED_PRIORITY_NORMAL,
700 			.job_cnt = 4,
701 			.job_us = 1000,
702 			.wait_us = 0,
703 			.sync = true,
704 		},
705 	},
706 	{
707 		.description = "3 clients",
708 		.num_clients = 3,
709 		.client[0] = {
710 			.priority = DRM_SCHED_PRIORITY_NORMAL,
711 			.job_cnt = 4,
712 			.job_us = 1000,
713 			.wait_us = 0,
714 			.sync = true,
715 		},
716 	},
717 	{
718 		.description = "7 clients",
719 		.num_clients = 7,
720 		.client[0] = {
721 			.priority = DRM_SCHED_PRIORITY_NORMAL,
722 			.job_cnt = 4,
723 			.job_us = 1000,
724 			.wait_us = 0,
725 			.sync = true,
726 		},
727 	},
728 	{
729 		.description = "13 clients",
730 		.num_clients = 13,
731 		.client[0] = {
732 			.priority = DRM_SCHED_PRIORITY_NORMAL,
733 			.job_cnt = 4,
734 			.job_us = 1000,
735 			.wait_us = 0,
736 			.sync = true,
737 		},
738 	},
739 	{
740 		.description = "31 clients",
741 		.num_clients = 31,
742 		.client[0] = {
743 			.priority = DRM_SCHED_PRIORITY_NORMAL,
744 			.job_cnt = 2,
745 			.job_us = 1000,
746 			.wait_us = 0,
747 			.sync = true,
748 		},
749 	},
750 };
751 
752 KUNIT_ARRAY_PARAM(drm_sched_scheduler_many_clients,
753 		  drm_sched_many_cases,
754 		  drm_sched_desc);
755 
756 static void drm_sched_scheduler_many_clients_test(struct kunit *test)
757 {
758 	const struct drm_sched_test_params *params = test->param_value;
759 	struct drm_mock_scheduler *sched = test->priv;
760 	const unsigned int clients = params->num_clients;
761 	unsigned int i, j, delta_total = 0, loops = 0;
762 	struct test_client *client;
763 	unsigned int *prev_cycle;
764 	ktime_t start;
765 	char *buf;
766 
767 	/*
768 	 * Many clients with deep-ish async queues.
769 	 */
770 
771 	buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL);
772 	KUNIT_ASSERT_NOT_NULL(test, buf);
773 	client = kunit_kcalloc(test, clients, sizeof(*client), GFP_KERNEL);
774 	KUNIT_ASSERT_NOT_NULL(test, client);
775 	prev_cycle = kunit_kcalloc(test, clients, sizeof(*prev_cycle),
776 				   GFP_KERNEL);
777 	KUNIT_ASSERT_NOT_NULL(test, prev_cycle);
778 
779 	for (i = 0; i < clients; i++)
780 		client[i].entity =
781 			drm_mock_sched_entity_new(test,
782 						  DRM_SCHED_PRIORITY_NORMAL,
783 						  sched);
784 
785 	for (i = 0; i < clients; i++) {
786 		client[i].test = test;
787 		client[i].id = i;
788 		client[i].params = params->client[0];
789 		client[i].duration = ms_to_ktime(1000 / clients);
790 		client[i].cycle_time.min_us = ~0U;
791 		client[i].latency_time.min_us = ~0U;
792 		client[i].worker =
793 			kthread_create_worker(0, "%s-%u", __func__, i);
794 		if (IS_ERR(client[i].worker)) {
795 			for (j = 0; j < i; j++)
796 				kthread_destroy_worker(client[j].worker);
797 			KUNIT_FAIL(test, "Failed to create worker!\n");
798 		}
799 
800 		kthread_init_work(&client[i].work, drm_sched_client_work);
801 	}
802 
803 	for (i = 0; i < clients; i++)
804 		kthread_queue_work(client[i].worker, &client[i].work);
805 
806 	start = ktime_get();
807 	pr_info("%u clients:\n\tt\t\tcycle:\t  min    avg    max : ...\n", clients);
808 	for (;;) {
809 		unsigned int min = ~0;
810 		unsigned int max = 0;
811 		unsigned int total = 0;
812 		bool done = true;
813 		char pbuf[16];
814 
815 		memset(buf, 0, PAGE_SIZE);
816 		for (i = 0; i < clients; i++) {
817 			unsigned int cycle, cycles;
818 
819 			/* Read current progress from the threaded worker. */
820 			cycle = READ_ONCE(client[i].cycle);
821 			cycles = READ_ONCE(client[i].cycles);
822 
823 			snprintf(pbuf, sizeof(pbuf), " %3d", cycle);
824 			strncat(buf, pbuf, PAGE_SIZE);
825 
826 			total += cycle;
827 			if (cycle < min)
828 				min = cycle;
829 			if (cycle > max)
830 				max = cycle;
831 
832 			if (!min || (cycle + 1) < cycles)
833 				done = false;
834 		}
835 
836 		loops++;
837 		delta_total += max - min;
838 
839 		pr_info("\t+%6lldms\t\t  %3u  %3u  %3u :%s\n",
840 			ktime_to_ms(ktime_sub(ktime_get(), start)),
841 			min, DIV_ROUND_UP(total, clients), max, buf);
842 
843 		if (done)
844 			break;
845 
846 		msleep(100);
847 	}
848 
849 	pr_info("    avg_max_min_delta(x100)=%u\n",
850 		loops ? DIV_ROUND_UP(delta_total * 100, loops) : 0);
851 
852 	for (i = 0; i < clients; i++) {
853 		kthread_flush_work(&client[i].work);
854 		kthread_destroy_worker(client[i].worker);
855 	}
856 
857 	for (i = 0; i < clients; i++)
858 		drm_mock_sched_entity_free(client[i].entity);
859 }
860 
861 static const struct kunit_attributes drm_sched_scheduler_many_clients_attr = {
862 	.speed = KUNIT_SPEED_SLOW,
863 };
864 
865 static struct kunit_case drm_sched_scheduler_many_clients_tests[] = {
866 	KUNIT_CASE_PARAM_ATTR(drm_sched_scheduler_many_clients_test,
867 			      drm_sched_scheduler_many_clients_gen_params,
868 			      drm_sched_scheduler_many_clients_attr),
869 	{}
870 };
871 
872 static struct kunit_suite drm_sched_scheduler_many_clients = {
873 	.name = "drm_sched_scheduler_many_clients_tests",
874 	.init = drm_sched_scheduler_init2,
875 	.exit = drm_sched_scheduler_exit,
876 	.test_cases = drm_sched_scheduler_many_clients_tests,
877 };
878 
879 kunit_test_suites(&drm_sched_scheduler_overhead,
880 		  &drm_sched_scheduler_two_clients1,
881 		  &drm_sched_scheduler_two_clients2,
882 		  &drm_sched_scheduler_many_clients);
883