1// SPDX-License-Identifier: GPL-2.0
2/* Copyright (c) 2025 Valve Corporation */
3
4#include "sched_tests.h"
5
6/*
7 * Here we implement the mock "GPU" (or the scheduler backend) which is used by
8 * the DRM scheduler unit tests in order to exercise the core functionality.
9 *
10 * Test cases are implemented in a separate file.
11 */
12
13/**
14 * drm_mock_sched_entity_new - Create a new mock scheduler entity
15 *
16 * @test: KUnit test owning the entity
17 * @priority: Scheduling priority
18 * @sched: Mock scheduler on which the entity can be scheduled
19 *
20 * Returns: New mock scheduler entity with allocation managed by the test
21 */
22struct drm_mock_sched_entity *
23drm_mock_sched_entity_new(struct kunit *test,
24 enum drm_sched_priority priority,
25 struct drm_mock_scheduler *sched)
26{
27 struct drm_mock_sched_entity *entity;
28 struct drm_gpu_scheduler *drm_sched;
29 int ret;
30
31 entity = kunit_kzalloc(test, size: sizeof(*entity), GFP_KERNEL);
32 KUNIT_ASSERT_NOT_NULL(test, entity);
33
34 drm_sched = &sched->base;
35 ret = drm_sched_entity_init(entity: &entity->base,
36 priority,
37 sched_list: &drm_sched, num_sched_list: 1,
38 NULL);
39 KUNIT_ASSERT_EQ(test, ret, 0);
40
41 entity->test = test;
42
43 return entity;
44}
45
46/**
47 * drm_mock_sched_entity_free - Destroys a mock scheduler entity
48 *
49 * @entity: Entity to destroy
50 *
51 * To be used from the test cases once done with the entity.
52 */
53void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity)
54{
55 drm_sched_entity_destroy(entity: &entity->base);
56}
57
58static void drm_mock_sched_job_complete(struct drm_mock_sched_job *job)
59{
60 struct drm_mock_scheduler *sched =
61 drm_sched_to_mock_sched(sched: job->base.sched);
62
63 lockdep_assert_held(&sched->lock);
64
65 job->flags |= DRM_MOCK_SCHED_JOB_DONE;
66 list_move_tail(list: &job->link, head: &sched->done_list);
67 dma_fence_signal(fence: &job->hw_fence);
68 complete(&job->done);
69}
70
71static enum hrtimer_restart
72drm_mock_sched_job_signal_timer(struct hrtimer *hrtimer)
73{
74 struct drm_mock_sched_job *job =
75 container_of(hrtimer, typeof(*job), timer);
76 struct drm_mock_scheduler *sched =
77 drm_sched_to_mock_sched(sched: job->base.sched);
78 struct drm_mock_sched_job *next;
79 ktime_t now = ktime_get();
80 unsigned long flags;
81 LIST_HEAD(signal);
82
83 spin_lock_irqsave(&sched->lock, flags);
84 list_for_each_entry_safe(job, next, &sched->job_list, link) {
85 if (!job->duration_us)
86 break;
87
88 if (ktime_before(cmp1: now, cmp2: job->finish_at))
89 break;
90
91 sched->hw_timeline.cur_seqno = job->hw_fence.seqno;
92 drm_mock_sched_job_complete(job);
93 }
94 spin_unlock_irqrestore(lock: &sched->lock, flags);
95
96 return HRTIMER_NORESTART;
97}
98
99/**
100 * drm_mock_sched_job_new - Create a new mock scheduler job
101 *
102 * @test: KUnit test owning the job
103 * @entity: Scheduler entity of the job
104 *
105 * Returns: New mock scheduler job with allocation managed by the test
106 */
107struct drm_mock_sched_job *
108drm_mock_sched_job_new(struct kunit *test,
109 struct drm_mock_sched_entity *entity)
110{
111 struct drm_mock_sched_job *job;
112 int ret;
113
114 job = kunit_kzalloc(test, size: sizeof(*job), GFP_KERNEL);
115 KUNIT_ASSERT_NOT_NULL(test, job);
116
117 ret = drm_sched_job_init(job: &job->base,
118 entity: &entity->base,
119 credits: 1,
120 NULL);
121 KUNIT_ASSERT_EQ(test, ret, 0);
122
123 job->test = test;
124
125 init_completion(x: &job->done);
126 spin_lock_init(&job->lock);
127 INIT_LIST_HEAD(list: &job->link);
128 hrtimer_setup(timer: &job->timer, function: drm_mock_sched_job_signal_timer,
129 CLOCK_MONOTONIC, mode: HRTIMER_MODE_ABS);
130
131 return job;
132}
133
134static const char *drm_mock_sched_hw_fence_driver_name(struct dma_fence *fence)
135{
136 return "drm_mock_sched";
137}
138
139static const char *
140drm_mock_sched_hw_fence_timeline_name(struct dma_fence *fence)
141{
142 struct drm_mock_sched_job *job =
143 container_of(fence, typeof(*job), hw_fence);
144
145 return (const char *)job->base.sched->name;
146}
147
148static void drm_mock_sched_hw_fence_release(struct dma_fence *fence)
149{
150 struct drm_mock_sched_job *job =
151 container_of(fence, typeof(*job), hw_fence);
152
153 hrtimer_cancel(timer: &job->timer);
154
155 /* Containing job is freed by the kunit framework */
156}
157
158static const struct dma_fence_ops drm_mock_sched_hw_fence_ops = {
159 .get_driver_name = drm_mock_sched_hw_fence_driver_name,
160 .get_timeline_name = drm_mock_sched_hw_fence_timeline_name,
161 .release = drm_mock_sched_hw_fence_release,
162};
163
164static struct dma_fence *mock_sched_run_job(struct drm_sched_job *sched_job)
165{
166 struct drm_mock_scheduler *sched =
167 drm_sched_to_mock_sched(sched: sched_job->sched);
168 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job);
169
170 dma_fence_init(fence: &job->hw_fence,
171 ops: &drm_mock_sched_hw_fence_ops,
172 lock: &job->lock,
173 context: sched->hw_timeline.context,
174 seqno: atomic_inc_return(v: &sched->hw_timeline.next_seqno));
175
176 dma_fence_get(fence: &job->hw_fence); /* Reference for the job_list */
177
178 spin_lock_irq(lock: &sched->lock);
179 if (job->duration_us) {
180 ktime_t prev_finish_at = 0;
181
182 if (!list_empty(head: &sched->job_list)) {
183 struct drm_mock_sched_job *prev =
184 list_last_entry(&sched->job_list, typeof(*prev),
185 link);
186
187 prev_finish_at = prev->finish_at;
188 }
189
190 if (!prev_finish_at)
191 prev_finish_at = ktime_get();
192
193 job->finish_at = ktime_add_us(kt: prev_finish_at, usec: job->duration_us);
194 }
195 list_add_tail(new: &job->link, head: &sched->job_list);
196 if (job->finish_at)
197 hrtimer_start(timer: &job->timer, tim: job->finish_at, mode: HRTIMER_MODE_ABS);
198 spin_unlock_irq(lock: &sched->lock);
199
200 return &job->hw_fence;
201}
202
203static enum drm_gpu_sched_stat
204mock_sched_timedout_job(struct drm_sched_job *sched_job)
205{
206 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job);
207
208 job->flags |= DRM_MOCK_SCHED_JOB_TIMEDOUT;
209
210 return DRM_GPU_SCHED_STAT_NOMINAL;
211}
212
213static void mock_sched_free_job(struct drm_sched_job *sched_job)
214{
215 struct drm_mock_scheduler *sched =
216 drm_sched_to_mock_sched(sched: sched_job->sched);
217 struct drm_mock_sched_job *job = drm_sched_job_to_mock_job(sched_job);
218 unsigned long flags;
219
220 /* Remove from the scheduler done list. */
221 spin_lock_irqsave(&sched->lock, flags);
222 list_del(entry: &job->link);
223 spin_unlock_irqrestore(lock: &sched->lock, flags);
224 dma_fence_put(fence: &job->hw_fence);
225
226 drm_sched_job_cleanup(job: sched_job);
227
228 /* Mock job itself is freed by the kunit framework. */
229}
230
231static const struct drm_sched_backend_ops drm_mock_scheduler_ops = {
232 .run_job = mock_sched_run_job,
233 .timedout_job = mock_sched_timedout_job,
234 .free_job = mock_sched_free_job
235};
236
237/**
238 * drm_mock_sched_new - Create a new mock scheduler
239 *
240 * @test: KUnit test owning the job
241 * @timeout: Job timeout to set
242 *
243 * Returns: New mock scheduler with allocation managed by the test
244 */
245struct drm_mock_scheduler *drm_mock_sched_new(struct kunit *test, long timeout)
246{
247 struct drm_sched_init_args args = {
248 .ops = &drm_mock_scheduler_ops,
249 .num_rqs = DRM_SCHED_PRIORITY_COUNT,
250 .credit_limit = U32_MAX,
251 .hang_limit = 1,
252 .timeout = timeout,
253 .name = "drm-mock-scheduler",
254 };
255 struct drm_mock_scheduler *sched;
256 int ret;
257
258 sched = kunit_kzalloc(test, size: sizeof(*sched), GFP_KERNEL);
259 KUNIT_ASSERT_NOT_NULL(test, sched);
260
261 ret = drm_sched_init(sched: &sched->base, args: &args);
262 KUNIT_ASSERT_EQ(test, ret, 0);
263
264 sched->test = test;
265 sched->hw_timeline.context = dma_fence_context_alloc(num: 1);
266 atomic_set(v: &sched->hw_timeline.next_seqno, i: 0);
267 INIT_LIST_HEAD(list: &sched->job_list);
268 INIT_LIST_HEAD(list: &sched->done_list);
269 spin_lock_init(&sched->lock);
270
271 return sched;
272}
273
274/**
275 * drm_mock_sched_fini - Destroys a mock scheduler
276 *
277 * @sched: Scheduler to destroy
278 *
279 * To be used from the test cases once done with the scheduler.
280 */
281void drm_mock_sched_fini(struct drm_mock_scheduler *sched)
282{
283 struct drm_mock_sched_job *job, *next;
284 unsigned long flags;
285 LIST_HEAD(list);
286
287 drm_sched_wqueue_stop(sched: &sched->base);
288
289 /* Force complete all unfinished jobs. */
290 spin_lock_irqsave(&sched->lock, flags);
291 list_for_each_entry_safe(job, next, &sched->job_list, link)
292 list_move_tail(list: &job->link, head: &list);
293 spin_unlock_irqrestore(lock: &sched->lock, flags);
294
295 list_for_each_entry(job, &list, link)
296 hrtimer_cancel(timer: &job->timer);
297
298 spin_lock_irqsave(&sched->lock, flags);
299 list_for_each_entry_safe(job, next, &list, link)
300 drm_mock_sched_job_complete(job);
301 spin_unlock_irqrestore(lock: &sched->lock, flags);
302
303 /*
304 * Free completed jobs and jobs not yet processed by the DRM scheduler
305 * free worker.
306 */
307 spin_lock_irqsave(&sched->lock, flags);
308 list_for_each_entry_safe(job, next, &sched->done_list, link)
309 list_move_tail(list: &job->link, head: &list);
310 spin_unlock_irqrestore(lock: &sched->lock, flags);
311
312 list_for_each_entry_safe(job, next, &list, link)
313 mock_sched_free_job(sched_job: &job->base);
314
315 drm_sched_fini(sched: &sched->base);
316}
317
318/**
319 * drm_mock_sched_advance - Advances the mock scheduler timeline
320 *
321 * @sched: Scheduler timeline to advance
322 * @num: By how many jobs to advance
323 *
324 * Advancing the scheduler timeline by a number of seqnos will trigger
325 * signalling of the hardware fences and unlinking the jobs from the internal
326 * scheduler tracking.
327 *
328 * This can be used from test cases which want complete control of the simulated
329 * job execution timing. For example submitting one job with no set duration
330 * would never complete it before test cases advances the timeline by one.
331 */
332unsigned int drm_mock_sched_advance(struct drm_mock_scheduler *sched,
333 unsigned int num)
334{
335 struct drm_mock_sched_job *job, *next;
336 unsigned int found = 0;
337 unsigned long flags;
338 LIST_HEAD(signal);
339
340 spin_lock_irqsave(&sched->lock, flags);
341 if (WARN_ON_ONCE(sched->hw_timeline.cur_seqno + num <
342 sched->hw_timeline.cur_seqno))
343 goto unlock;
344 sched->hw_timeline.cur_seqno += num;
345 list_for_each_entry_safe(job, next, &sched->job_list, link) {
346 if (sched->hw_timeline.cur_seqno < job->hw_fence.seqno)
347 break;
348
349 drm_mock_sched_job_complete(job);
350 found++;
351 }
352unlock:
353 spin_unlock_irqrestore(lock: &sched->lock, flags);
354
355 return found;
356}
357
358MODULE_DESCRIPTION("DRM mock scheduler and tests");
359MODULE_LICENSE("GPL");
360

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

source code of linux/drivers/gpu/drm/scheduler/tests/mock_scheduler.c