1 // SPDX-License-Identifier: MIT
2 /* Copyright 2015 Advanced Micro Devices, Inc. */
3 /* Copyright (c) 2025 Valve Corporation */
4
5 #include <linux/rbtree.h>
6
7 #include <drm/drm_print.h>
8 #include <drm/gpu_scheduler.h>
9
10 #include "sched_internal.h"
11
12 static __always_inline bool
drm_sched_entity_compare_before(struct rb_node * a,const struct rb_node * b)13 drm_sched_entity_compare_before(struct rb_node *a, const struct rb_node *b)
14 {
15 struct drm_sched_entity *ea =
16 rb_entry((a), struct drm_sched_entity, rb_tree_node);
17 struct drm_sched_entity *eb =
18 rb_entry((b), struct drm_sched_entity, rb_tree_node);
19
20 return ktime_before(ea->oldest_job_waiting, eb->oldest_job_waiting);
21 }
22
drm_sched_rq_update_prio(struct drm_sched_rq * rq)23 static void drm_sched_rq_update_prio(struct drm_sched_rq *rq)
24 {
25 enum drm_sched_priority prio = DRM_SCHED_PRIORITY_INVALID;
26 struct rb_node *rb;
27
28 lockdep_assert_held(&rq->lock);
29
30 rb = rb_first_cached(&rq->rb_tree_root);
31 if (rb) {
32 struct drm_sched_entity *entity =
33 rb_entry(rb, typeof(*entity), rb_tree_node);
34
35 /*
36 * The normal locking order is entity then run-queue so taking
37 * the entity lock here would be a locking inversion for the
38 * case when the current head of the run-queue is different from
39 * the one we already have locked. The unlocked read is fine
40 * though, because if the priority had just changed it is no big
41 * deal for our algorithm, but just a transient reachable only
42 * by drivers with userspace dynamic priority changes API. Equal
43 * in effect to the priority change becoming visible a few
44 * instructions later.
45 */
46 prio = READ_ONCE(entity->priority);
47 }
48
49 rq->head_prio = prio;
50 }
51
drm_sched_rq_remove_fifo_locked(struct drm_sched_entity * entity,struct drm_sched_rq * rq)52 static void drm_sched_rq_remove_fifo_locked(struct drm_sched_entity *entity,
53 struct drm_sched_rq *rq)
54 {
55 lockdep_assert_held(&entity->lock);
56 lockdep_assert_held(&rq->lock);
57
58 if (!RB_EMPTY_NODE(&entity->rb_tree_node)) {
59 rb_erase_cached(&entity->rb_tree_node, &rq->rb_tree_root);
60 RB_CLEAR_NODE(&entity->rb_tree_node);
61 drm_sched_rq_update_prio(rq);
62 }
63 }
64
drm_sched_rq_update_fifo_locked(struct drm_sched_entity * entity,struct drm_sched_rq * rq,ktime_t ts)65 static void drm_sched_rq_update_fifo_locked(struct drm_sched_entity *entity,
66 struct drm_sched_rq *rq,
67 ktime_t ts)
68 {
69 /*
70 * Both locks need to be grabbed, one to protect from entity->rq change
71 * for entity from within concurrent drm_sched_entity_select_rq and the
72 * other to update the rb tree structure.
73 */
74 lockdep_assert_held(&entity->lock);
75 lockdep_assert_held(&rq->lock);
76
77 drm_sched_rq_remove_fifo_locked(entity, rq);
78
79 entity->oldest_job_waiting = ts;
80
81 rb_add_cached(&entity->rb_tree_node, &rq->rb_tree_root,
82 drm_sched_entity_compare_before);
83 drm_sched_rq_update_prio(rq);
84 }
85
86 /**
87 * drm_sched_rq_init - initialize a given run queue struct
88 * @sched: scheduler instance to associate with this run queue
89 * @rq: scheduler run queue
90 *
91 * Initializes a scheduler runqueue.
92 */
drm_sched_rq_init(struct drm_gpu_scheduler * sched,struct drm_sched_rq * rq)93 void drm_sched_rq_init(struct drm_gpu_scheduler *sched,
94 struct drm_sched_rq *rq)
95 {
96 spin_lock_init(&rq->lock);
97 INIT_LIST_HEAD(&rq->entities);
98 rq->rb_tree_root = RB_ROOT_CACHED;
99 rq->sched = sched;
100 rq->head_prio = DRM_SCHED_PRIORITY_INVALID;
101 }
102
103 /*
104 * Core part of the CFS-like algorithm is that the virtual runtime of lower
105 * priority tasks should grow quicker than the higher priority ones, so that
106 * when we then schedule entities with the aim of keeping their accumulated
107 * virtual time balanced, we can approach fair distribution of GPU time.
108 *
109 * For converting the real GPU time into virtual we pick some multipliers with
110 * the idea to achieve the following GPU time distribution:
111 *
112 * - Kernel priority gets roughly 2x GPU time compared to high.
113 * - High gets ~4x relative to normal.
114 * - Normal gets ~8x relative to low.
115 */
116 static const unsigned int vruntime_shift[] = {
117 [DRM_SCHED_PRIORITY_KERNEL] = 1,
118 [DRM_SCHED_PRIORITY_HIGH] = 2,
119 [DRM_SCHED_PRIORITY_NORMAL] = 4,
120 [DRM_SCHED_PRIORITY_LOW] = 7,
121 };
122
123 static ktime_t
drm_sched_rq_get_min_vruntime(struct drm_sched_rq * rq)124 drm_sched_rq_get_min_vruntime(struct drm_sched_rq *rq)
125 {
126 ktime_t vruntime = 0;
127 struct rb_node *rb;
128
129 lockdep_assert_held(&rq->lock);
130
131 rb = rb_first_cached(&rq->rb_tree_root);
132 if (rb) {
133 struct drm_sched_entity *entity =
134 rb_entry(rb, typeof(*entity), rb_tree_node);
135 struct drm_sched_entity_stats *stats = entity->stats;
136
137 spin_lock(&stats->lock);
138 vruntime = stats->vruntime;
139 spin_unlock(&stats->lock);
140 }
141
142 return vruntime;
143 }
144
145 static void
drm_sched_entity_save_vruntime(struct drm_sched_entity * entity,ktime_t min_vruntime)146 drm_sched_entity_save_vruntime(struct drm_sched_entity *entity,
147 ktime_t min_vruntime)
148 {
149 struct drm_sched_entity_stats *stats = entity->stats;
150 ktime_t vruntime;
151
152 spin_lock(&stats->lock);
153 vruntime = stats->vruntime;
154 if (min_vruntime && vruntime > min_vruntime)
155 vruntime = ktime_sub(vruntime, min_vruntime);
156 else
157 vruntime = 0;
158 stats->vruntime = vruntime;
159 spin_unlock(&stats->lock);
160 }
161
162 static ktime_t
drm_sched_entity_restore_vruntime(struct drm_sched_entity * entity,ktime_t min_vruntime,enum drm_sched_priority rq_prio)163 drm_sched_entity_restore_vruntime(struct drm_sched_entity *entity,
164 ktime_t min_vruntime,
165 enum drm_sched_priority rq_prio)
166 {
167 struct drm_sched_entity_stats *stats = entity->stats;
168 struct drm_gpu_scheduler *sched = entity->rq->sched;
169 enum drm_sched_priority prio = entity->priority;
170 unsigned long avg_us, sched_avg_us;
171 ktime_t vruntime;
172
173 BUILD_BUG_ON(DRM_SCHED_PRIORITY_NORMAL < DRM_SCHED_PRIORITY_HIGH);
174
175 spin_lock(&stats->lock);
176 vruntime = stats->vruntime;
177 avg_us = ewma_drm_sched_avgtime_read(&stats->avg_job_us);
178 /*
179 * Unlocked read of the scheduler average is fine since it is just
180 * heuristics and data type is a natural word size.
181 */
182 sched_avg_us = ewma_drm_sched_avgtime_read(&sched->avg_job_us);
183
184 /*
185 * Special handling for entities which were picked from the top of the
186 * queue and are now re-joining the top with another one already there.
187 */
188 if (!vruntime && rq_prio != DRM_SCHED_PRIORITY_INVALID) {
189 if (prio > rq_prio) {
190 /*
191 * Lower priority should not overtake higher when re-
192 * joining at the top of the queue so push it back
193 * somewhere behind the "middle" of the run-queue,
194 * proportional to the scheduler and entity average job
195 * durations.
196 */
197 vruntime = us_to_ktime((1 + avg_us + sched_avg_us) <<
198 vruntime_shift[prio]);
199 } else if (prio < rq_prio) {
200 /*
201 * Higher priority can go first.
202 */
203 vruntime = -ns_to_ktime(rq_prio - prio);
204 } else {
205 /* Favour entity with shorter jobs (interactivity). */
206 if (avg_us <= sched_avg_us)
207 vruntime = -ns_to_ktime(1);
208 else
209 vruntime = ns_to_ktime(1);
210 }
211 }
212
213 /*
214 * Restore saved relative position in the queue.
215 */
216 vruntime = ktime_add(min_vruntime, vruntime);
217
218 stats->vruntime = vruntime;
219 spin_unlock(&stats->lock);
220
221 return vruntime;
222 }
223
drm_sched_entity_update_vruntime(struct drm_sched_entity * entity)224 static ktime_t drm_sched_entity_update_vruntime(struct drm_sched_entity *entity)
225 {
226 struct drm_sched_entity_stats *stats = entity->stats;
227 ktime_t runtime, prev;
228
229 spin_lock(&stats->lock);
230 prev = stats->prev_runtime;
231 runtime = stats->runtime;
232 stats->prev_runtime = runtime;
233 runtime = ktime_add_ns(stats->vruntime,
234 ktime_to_ns(ktime_sub(runtime, prev)) <<
235 vruntime_shift[entity->priority]);
236 stats->vruntime = runtime;
237 spin_unlock(&stats->lock);
238
239 return runtime;
240 }
241
drm_sched_entity_get_job_ts(struct drm_sched_entity * entity)242 static ktime_t drm_sched_entity_get_job_ts(struct drm_sched_entity *entity)
243 {
244 return drm_sched_entity_update_vruntime(entity);
245 }
246
247 /**
248 * drm_sched_rq_add_entity - add an entity
249 * @entity: scheduler entity
250 * @ts: submission timestamp
251 *
252 * Adds a scheduler entity to the run queue.
253 *
254 * Return: DRM scheduler selected to handle this entity or NULL if entity has
255 * been stopped and cannot be submitted to.
256 */
257 struct drm_gpu_scheduler *
drm_sched_rq_add_entity(struct drm_sched_entity * entity,ktime_t ts)258 drm_sched_rq_add_entity(struct drm_sched_entity *entity, ktime_t ts)
259 {
260 struct drm_sched_rq *rq = entity->rq;
261 struct drm_gpu_scheduler *sched;
262
263 /* Add the entity to the run queue */
264 lockdep_assert_held(&entity->lock);
265
266 if (entity->stopped) {
267 DRM_ERROR("Trying to push to a killed entity\n");
268 return NULL;
269 }
270
271 spin_lock(&rq->lock);
272 sched = rq->sched;
273
274 if (list_empty(&entity->list)) {
275 atomic_inc(sched->score);
276 list_add_tail(&entity->list, &rq->entities);
277 }
278
279 if (drm_sched_policy == DRM_SCHED_POLICY_FAIR) {
280 ts = drm_sched_rq_get_min_vruntime(rq);
281 ts = drm_sched_entity_restore_vruntime(entity, ts,
282 rq->head_prio);
283 } else if (drm_sched_policy == DRM_SCHED_POLICY_RR) {
284 ts = entity->rr_ts;
285 }
286
287 drm_sched_rq_update_fifo_locked(entity, rq, ts);
288
289 spin_unlock(&rq->lock);
290
291 return sched;
292 }
293
294 /**
295 * drm_sched_rq_remove_entity - remove an entity
296 * @rq: scheduler run queue
297 * @entity: scheduler entity
298 *
299 * Removes a scheduler entity from the run queue.
300 */
drm_sched_rq_remove_entity(struct drm_sched_rq * rq,struct drm_sched_entity * entity)301 void drm_sched_rq_remove_entity(struct drm_sched_rq *rq,
302 struct drm_sched_entity *entity)
303 {
304 lockdep_assert_held(&entity->lock);
305
306 if (list_empty(&entity->list))
307 return;
308
309 spin_lock(&rq->lock);
310
311 atomic_dec(rq->sched->score);
312 list_del_init(&entity->list);
313
314 drm_sched_rq_remove_fifo_locked(entity, rq);
315
316 spin_unlock(&rq->lock);
317 }
318
319 static ktime_t
drm_sched_rq_next_rr_ts(struct drm_sched_rq * rq,struct drm_sched_entity * entity)320 drm_sched_rq_next_rr_ts(struct drm_sched_rq *rq,
321 struct drm_sched_entity *entity)
322 {
323 ktime_t ts;
324
325 lockdep_assert_held(&entity->lock);
326 lockdep_assert_held(&rq->lock);
327
328 ts = ktime_add_ns(rq->rr_ts, 1);
329 entity->rr_ts = ts;
330 rq->rr_ts = ts;
331
332 return ts;
333 }
334
335 /**
336 * drm_sched_rq_pop_entity - pops an entity
337 * @entity: scheduler entity
338 *
339 * To be called every time after a job is popped from the entity.
340 */
drm_sched_rq_pop_entity(struct drm_sched_entity * entity)341 void drm_sched_rq_pop_entity(struct drm_sched_entity *entity)
342 {
343 struct drm_sched_rq *rq = entity->rq;
344 struct drm_sched_job *next_job;
345
346 lockdep_assert_held(&entity->lock);
347
348 spin_lock(&rq->lock);
349
350 /*
351 * Update the entity's location in the min heap according to
352 * the timestamp of the next job, if any.
353 */
354 next_job = drm_sched_entity_queue_peek(entity);
355 if (next_job) {
356 ktime_t ts;
357
358 if (drm_sched_policy == DRM_SCHED_POLICY_FAIR)
359 ts = drm_sched_entity_get_job_ts(entity);
360 else if (drm_sched_policy == DRM_SCHED_POLICY_FIFO)
361 ts = next_job->submit_ts;
362 else
363 ts = drm_sched_rq_next_rr_ts(rq, entity);
364
365 drm_sched_rq_update_fifo_locked(entity, rq, ts);
366 } else {
367 drm_sched_rq_remove_fifo_locked(entity, rq);
368
369 if (drm_sched_policy == DRM_SCHED_POLICY_FAIR) {
370 ktime_t min_vruntime;
371
372 min_vruntime = drm_sched_rq_get_min_vruntime(rq);
373 drm_sched_entity_save_vruntime(entity, min_vruntime);
374 }
375 }
376
377 spin_unlock(&rq->lock);
378 }
379
380 /**
381 * drm_sched_rq_select_entity - Select an entity which provides a job to run
382 * @sched: the gpu scheduler
383 * @rq: scheduler run queue to check.
384 *
385 * Find oldest waiting ready entity.
386 *
387 * Return an entity if one is found; return an error-pointer (!NULL) if an
388 * entity was ready, but the scheduler had insufficient credits to accommodate
389 * its job; return NULL, if no ready entity was found.
390 */
391 struct drm_sched_entity *
drm_sched_rq_select_entity(struct drm_gpu_scheduler * sched,struct drm_sched_rq * rq)392 drm_sched_rq_select_entity(struct drm_gpu_scheduler *sched,
393 struct drm_sched_rq *rq)
394 {
395 struct rb_node *rb;
396
397 spin_lock(&rq->lock);
398 for (rb = rb_first_cached(&rq->rb_tree_root); rb; rb = rb_next(rb)) {
399 struct drm_sched_entity *entity;
400
401 entity = rb_entry(rb, struct drm_sched_entity, rb_tree_node);
402 if (drm_sched_entity_is_ready(entity)) {
403 /* If we can't queue yet, preserve the current entity in
404 * terms of fairness.
405 */
406 if (!drm_sched_can_queue(sched, entity)) {
407 spin_unlock(&rq->lock);
408 return ERR_PTR(-ENOSPC);
409 }
410
411 reinit_completion(&entity->entity_idle);
412 break;
413 }
414 }
415 spin_unlock(&rq->lock);
416
417 return rb ? rb_entry(rb, struct drm_sched_entity, rb_tree_node) : NULL;
418 }
419