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 | */ |
22 | struct drm_mock_sched_entity * |
23 | drm_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 | */ |
53 | void drm_mock_sched_entity_free(struct drm_mock_sched_entity *entity) |
54 | { |
55 | drm_sched_entity_destroy(entity: &entity->base); |
56 | } |
57 | |
58 | static 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 | |
71 | static enum hrtimer_restart |
72 | drm_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 | */ |
107 | struct drm_mock_sched_job * |
108 | drm_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 | |
134 | static const char *drm_mock_sched_hw_fence_driver_name(struct dma_fence *fence) |
135 | { |
136 | return "drm_mock_sched"; |
137 | } |
138 | |
139 | static const char * |
140 | drm_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 | |
148 | static 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 | |
158 | static 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 | |
164 | static 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 | |
203 | static enum drm_gpu_sched_stat |
204 | mock_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 | |
213 | static 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 | |
231 | static 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 | */ |
245 | struct 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 | */ |
281 | void 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 | */ |
332 | unsigned 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 | } |
352 | unlock: |
353 | spin_unlock_irqrestore(lock: &sched->lock, flags); |
354 | |
355 | return found; |
356 | } |
357 | |
358 | MODULE_DESCRIPTION("DRM mock scheduler and tests"); |
359 | MODULE_LICENSE("GPL"); |
360 |
Definitions
- drm_mock_sched_entity_new
- drm_mock_sched_entity_free
- drm_mock_sched_job_complete
- drm_mock_sched_job_signal_timer
- drm_mock_sched_job_new
- drm_mock_sched_hw_fence_driver_name
- drm_mock_sched_hw_fence_timeline_name
- drm_mock_sched_hw_fence_release
- drm_mock_sched_hw_fence_ops
- mock_sched_run_job
- mock_sched_timedout_job
- mock_sched_free_job
- drm_mock_scheduler_ops
- drm_mock_sched_new
- drm_mock_sched_fini
Improve your Profiling and Debugging skills
Find out more