diff options
Diffstat (limited to 'drivers/gpu/drm/amd/scheduler/gpu_scheduler.c')
-rw-r--r-- | drivers/gpu/drm/amd/scheduler/gpu_scheduler.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/drivers/gpu/drm/amd/scheduler/gpu_scheduler.c b/drivers/gpu/drm/amd/scheduler/gpu_scheduler.c new file mode 100644 index 000000000000..265d3e2f63cc --- /dev/null +++ b/drivers/gpu/drm/amd/scheduler/gpu_scheduler.c | |||
@@ -0,0 +1,462 @@ | |||
1 | /* | ||
2 | * Copyright 2015 Advanced Micro Devices, Inc. | ||
3 | * | ||
4 | * Permission is hereby granted, free of charge, to any person obtaining a | ||
5 | * copy of this software and associated documentation files (the "Software"), | ||
6 | * to deal in the Software without restriction, including without limitation | ||
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
8 | * and/or sell copies of the Software, and to permit persons to whom the | ||
9 | * Software is furnished to do so, subject to the following conditions: | ||
10 | * | ||
11 | * The above copyright notice and this permission notice shall be included in | ||
12 | * all copies or substantial portions of the Software. | ||
13 | * | ||
14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | ||
17 | * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR | ||
18 | * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, | ||
19 | * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR | ||
20 | * OTHER DEALINGS IN THE SOFTWARE. | ||
21 | * | ||
22 | * | ||
23 | */ | ||
24 | #include <linux/kthread.h> | ||
25 | #include <linux/wait.h> | ||
26 | #include <linux/sched.h> | ||
27 | #include <drm/drmP.h> | ||
28 | #include "gpu_scheduler.h" | ||
29 | |||
30 | /* Initialize a given run queue struct */ | ||
31 | static void amd_sched_rq_init(struct amd_sched_rq *rq) | ||
32 | { | ||
33 | INIT_LIST_HEAD(&rq->entities); | ||
34 | mutex_init(&rq->lock); | ||
35 | rq->current_entity = NULL; | ||
36 | } | ||
37 | |||
38 | static void amd_sched_rq_add_entity(struct amd_sched_rq *rq, | ||
39 | struct amd_sched_entity *entity) | ||
40 | { | ||
41 | mutex_lock(&rq->lock); | ||
42 | list_add_tail(&entity->list, &rq->entities); | ||
43 | mutex_unlock(&rq->lock); | ||
44 | } | ||
45 | |||
46 | static void amd_sched_rq_remove_entity(struct amd_sched_rq *rq, | ||
47 | struct amd_sched_entity *entity) | ||
48 | { | ||
49 | mutex_lock(&rq->lock); | ||
50 | list_del_init(&entity->list); | ||
51 | if (rq->current_entity == entity) | ||
52 | rq->current_entity = NULL; | ||
53 | mutex_unlock(&rq->lock); | ||
54 | } | ||
55 | |||
56 | /** | ||
57 | * Select next entity from a specified run queue with round robin policy. | ||
58 | * It could return the same entity as current one if current is the only | ||
59 | * available one in the queue. Return NULL if nothing available. | ||
60 | */ | ||
61 | static struct amd_sched_entity * | ||
62 | amd_sched_rq_select_entity(struct amd_sched_rq *rq) | ||
63 | { | ||
64 | struct amd_sched_entity *entity = rq->current_entity; | ||
65 | |||
66 | if (entity) { | ||
67 | list_for_each_entry_continue(entity, &rq->entities, list) { | ||
68 | if (!kfifo_is_empty(&entity->job_queue)) { | ||
69 | rq->current_entity = entity; | ||
70 | return rq->current_entity; | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
75 | list_for_each_entry(entity, &rq->entities, list) { | ||
76 | |||
77 | if (!kfifo_is_empty(&entity->job_queue)) { | ||
78 | rq->current_entity = entity; | ||
79 | return rq->current_entity; | ||
80 | } | ||
81 | |||
82 | if (entity == rq->current_entity) | ||
83 | break; | ||
84 | } | ||
85 | |||
86 | return NULL; | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Note: This function should only been called inside scheduler main | ||
91 | * function for thread safety, there is no other protection here. | ||
92 | * return ture if scheduler has something ready to run. | ||
93 | * | ||
94 | * For active_hw_rq, there is only one producer(scheduler thread) and | ||
95 | * one consumer(ISR). It should be safe to use this function in scheduler | ||
96 | * main thread to decide whether to continue emit more IBs. | ||
97 | */ | ||
98 | static bool is_scheduler_ready(struct amd_gpu_scheduler *sched) | ||
99 | { | ||
100 | unsigned long flags; | ||
101 | bool full; | ||
102 | |||
103 | spin_lock_irqsave(&sched->queue_lock, flags); | ||
104 | full = atomic64_read(&sched->hw_rq_count) < | ||
105 | sched->hw_submission_limit ? true : false; | ||
106 | spin_unlock_irqrestore(&sched->queue_lock, flags); | ||
107 | |||
108 | return full; | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * Select next entity from the kernel run queue, if not available, | ||
113 | * return null. | ||
114 | */ | ||
115 | static struct amd_sched_entity * | ||
116 | kernel_rq_select_context(struct amd_gpu_scheduler *sched) | ||
117 | { | ||
118 | struct amd_sched_entity *sched_entity; | ||
119 | struct amd_sched_rq *rq = &sched->kernel_rq; | ||
120 | |||
121 | mutex_lock(&rq->lock); | ||
122 | sched_entity = amd_sched_rq_select_entity(rq); | ||
123 | mutex_unlock(&rq->lock); | ||
124 | return sched_entity; | ||
125 | } | ||
126 | |||
127 | /** | ||
128 | * Select next entity containing real IB submissions | ||
129 | */ | ||
130 | static struct amd_sched_entity * | ||
131 | select_context(struct amd_gpu_scheduler *sched) | ||
132 | { | ||
133 | struct amd_sched_entity *wake_entity = NULL; | ||
134 | struct amd_sched_entity *tmp; | ||
135 | struct amd_sched_rq *rq; | ||
136 | |||
137 | if (!is_scheduler_ready(sched)) | ||
138 | return NULL; | ||
139 | |||
140 | /* Kernel run queue has higher priority than normal run queue*/ | ||
141 | tmp = kernel_rq_select_context(sched); | ||
142 | if (tmp != NULL) | ||
143 | goto exit; | ||
144 | |||
145 | rq = &sched->sched_rq; | ||
146 | mutex_lock(&rq->lock); | ||
147 | tmp = amd_sched_rq_select_entity(rq); | ||
148 | mutex_unlock(&rq->lock); | ||
149 | exit: | ||
150 | if (sched->current_entity && (sched->current_entity != tmp)) | ||
151 | wake_entity = sched->current_entity; | ||
152 | sched->current_entity = tmp; | ||
153 | if (wake_entity && wake_entity->need_wakeup) | ||
154 | wake_up(&wake_entity->wait_queue); | ||
155 | return tmp; | ||
156 | } | ||
157 | |||
158 | /** | ||
159 | * Init a context entity used by scheduler when submit to HW ring. | ||
160 | * | ||
161 | * @sched The pointer to the scheduler | ||
162 | * @entity The pointer to a valid amd_sched_entity | ||
163 | * @rq The run queue this entity belongs | ||
164 | * @kernel If this is an entity for the kernel | ||
165 | * @jobs The max number of jobs in the job queue | ||
166 | * | ||
167 | * return 0 if succeed. negative error code on failure | ||
168 | */ | ||
169 | int amd_sched_entity_init(struct amd_gpu_scheduler *sched, | ||
170 | struct amd_sched_entity *entity, | ||
171 | struct amd_sched_rq *rq, | ||
172 | uint32_t jobs) | ||
173 | { | ||
174 | uint64_t seq_ring = 0; | ||
175 | char name[20]; | ||
176 | |||
177 | if (!(sched && entity && rq)) | ||
178 | return -EINVAL; | ||
179 | |||
180 | memset(entity, 0, sizeof(struct amd_sched_entity)); | ||
181 | seq_ring = ((uint64_t)sched->ring_id) << 60; | ||
182 | spin_lock_init(&entity->lock); | ||
183 | entity->belongto_rq = rq; | ||
184 | entity->scheduler = sched; | ||
185 | init_waitqueue_head(&entity->wait_queue); | ||
186 | init_waitqueue_head(&entity->wait_emit); | ||
187 | entity->fence_context = fence_context_alloc(1); | ||
188 | snprintf(name, sizeof(name), "c_entity[%llu]", entity->fence_context); | ||
189 | memcpy(entity->name, name, 20); | ||
190 | entity->need_wakeup = false; | ||
191 | if(kfifo_alloc(&entity->job_queue, | ||
192 | jobs * sizeof(void *), | ||
193 | GFP_KERNEL)) | ||
194 | return -EINVAL; | ||
195 | |||
196 | spin_lock_init(&entity->queue_lock); | ||
197 | atomic64_set(&entity->last_queued_v_seq, seq_ring); | ||
198 | atomic64_set(&entity->last_signaled_v_seq, seq_ring); | ||
199 | |||
200 | /* Add the entity to the run queue */ | ||
201 | amd_sched_rq_add_entity(rq, entity); | ||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | /** | ||
206 | * Query if entity is initialized | ||
207 | * | ||
208 | * @sched Pointer to scheduler instance | ||
209 | * @entity The pointer to a valid scheduler entity | ||
210 | * | ||
211 | * return true if entity is initialized, false otherwise | ||
212 | */ | ||
213 | static bool is_context_entity_initialized(struct amd_gpu_scheduler *sched, | ||
214 | struct amd_sched_entity *entity) | ||
215 | { | ||
216 | return entity->scheduler == sched && | ||
217 | entity->belongto_rq != NULL; | ||
218 | } | ||
219 | |||
220 | static bool is_context_entity_idle(struct amd_gpu_scheduler *sched, | ||
221 | struct amd_sched_entity *entity) | ||
222 | { | ||
223 | /** | ||
224 | * Idle means no pending IBs, and the entity is not | ||
225 | * currently being used. | ||
226 | */ | ||
227 | barrier(); | ||
228 | if ((sched->current_entity != entity) && | ||
229 | kfifo_is_empty(&entity->job_queue)) | ||
230 | return true; | ||
231 | |||
232 | return false; | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * Destroy a context entity | ||
237 | * | ||
238 | * @sched Pointer to scheduler instance | ||
239 | * @entity The pointer to a valid scheduler entity | ||
240 | * | ||
241 | * return 0 if succeed. negative error code on failure | ||
242 | */ | ||
243 | int amd_sched_entity_fini(struct amd_gpu_scheduler *sched, | ||
244 | struct amd_sched_entity *entity) | ||
245 | { | ||
246 | int r = 0; | ||
247 | struct amd_sched_rq *rq = entity->belongto_rq; | ||
248 | |||
249 | if (!is_context_entity_initialized(sched, entity)) | ||
250 | return 0; | ||
251 | entity->need_wakeup = true; | ||
252 | /** | ||
253 | * The client will not queue more IBs during this fini, consume existing | ||
254 | * queued IBs | ||
255 | */ | ||
256 | r = wait_event_timeout( | ||
257 | entity->wait_queue, | ||
258 | is_context_entity_idle(sched, entity), | ||
259 | msecs_to_jiffies(AMD_GPU_WAIT_IDLE_TIMEOUT_IN_MS) | ||
260 | ) ? 0 : -1; | ||
261 | |||
262 | if (r) { | ||
263 | if (entity->is_pending) | ||
264 | DRM_INFO("Entity %p is in waiting state during fini,\ | ||
265 | all pending ibs will be canceled.\n", | ||
266 | entity); | ||
267 | } | ||
268 | |||
269 | amd_sched_rq_remove_entity(rq, entity); | ||
270 | kfifo_free(&entity->job_queue); | ||
271 | return r; | ||
272 | } | ||
273 | |||
274 | /** | ||
275 | * Submit a normal job to the job queue | ||
276 | * | ||
277 | * @sched The pointer to the scheduler | ||
278 | * @c_entity The pointer to amd_sched_entity | ||
279 | * @job The pointer to job required to submit | ||
280 | * return 0 if succeed. -1 if failed. | ||
281 | * -2 indicate queue is full for this client, client should wait untill | ||
282 | * scheduler consum some queued command. | ||
283 | * -1 other fail. | ||
284 | */ | ||
285 | int amd_sched_push_job(struct amd_gpu_scheduler *sched, | ||
286 | struct amd_sched_entity *c_entity, | ||
287 | void *data, | ||
288 | struct amd_sched_fence **fence) | ||
289 | { | ||
290 | struct amd_sched_job *job; | ||
291 | |||
292 | if (!fence) | ||
293 | return -EINVAL; | ||
294 | job = kzalloc(sizeof(struct amd_sched_job), GFP_KERNEL); | ||
295 | if (!job) | ||
296 | return -ENOMEM; | ||
297 | job->sched = sched; | ||
298 | job->s_entity = c_entity; | ||
299 | job->data = data; | ||
300 | *fence = amd_sched_fence_create(c_entity); | ||
301 | if ((*fence) == NULL) { | ||
302 | kfree(job); | ||
303 | return -EINVAL; | ||
304 | } | ||
305 | fence_get(&(*fence)->base); | ||
306 | job->s_fence = *fence; | ||
307 | while (kfifo_in_spinlocked(&c_entity->job_queue, &job, sizeof(void *), | ||
308 | &c_entity->queue_lock) != sizeof(void *)) { | ||
309 | /** | ||
310 | * Current context used up all its IB slots | ||
311 | * wait here, or need to check whether GPU is hung | ||
312 | */ | ||
313 | schedule(); | ||
314 | } | ||
315 | /* first job wake up scheduler */ | ||
316 | if ((kfifo_len(&c_entity->job_queue) / sizeof(void *)) == 1) | ||
317 | wake_up_interruptible(&sched->wait_queue); | ||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | static void amd_sched_process_job(struct fence *f, struct fence_cb *cb) | ||
322 | { | ||
323 | struct amd_sched_job *sched_job = | ||
324 | container_of(cb, struct amd_sched_job, cb); | ||
325 | struct amd_gpu_scheduler *sched; | ||
326 | unsigned long flags; | ||
327 | |||
328 | sched = sched_job->sched; | ||
329 | atomic64_set(&sched_job->s_entity->last_signaled_v_seq, | ||
330 | sched_job->s_fence->v_seq); | ||
331 | amd_sched_fence_signal(sched_job->s_fence); | ||
332 | spin_lock_irqsave(&sched->queue_lock, flags); | ||
333 | list_del(&sched_job->list); | ||
334 | atomic64_dec(&sched->hw_rq_count); | ||
335 | spin_unlock_irqrestore(&sched->queue_lock, flags); | ||
336 | |||
337 | sched->ops->process_job(sched, sched_job); | ||
338 | fence_put(&sched_job->s_fence->base); | ||
339 | kfree(sched_job); | ||
340 | wake_up_interruptible(&sched->wait_queue); | ||
341 | } | ||
342 | |||
343 | static int amd_sched_main(void *param) | ||
344 | { | ||
345 | int r; | ||
346 | struct amd_sched_job *job; | ||
347 | struct sched_param sparam = {.sched_priority = 1}; | ||
348 | struct amd_sched_entity *c_entity = NULL; | ||
349 | struct amd_gpu_scheduler *sched = (struct amd_gpu_scheduler *)param; | ||
350 | |||
351 | sched_setscheduler(current, SCHED_FIFO, &sparam); | ||
352 | |||
353 | while (!kthread_should_stop()) { | ||
354 | struct fence *fence; | ||
355 | |||
356 | wait_event_interruptible(sched->wait_queue, | ||
357 | is_scheduler_ready(sched) && | ||
358 | (c_entity = select_context(sched))); | ||
359 | r = kfifo_out(&c_entity->job_queue, &job, sizeof(void *)); | ||
360 | if (r != sizeof(void *)) | ||
361 | continue; | ||
362 | r = sched->ops->prepare_job(sched, c_entity, job); | ||
363 | if (!r) { | ||
364 | unsigned long flags; | ||
365 | spin_lock_irqsave(&sched->queue_lock, flags); | ||
366 | list_add_tail(&job->list, &sched->active_hw_rq); | ||
367 | atomic64_inc(&sched->hw_rq_count); | ||
368 | spin_unlock_irqrestore(&sched->queue_lock, flags); | ||
369 | } | ||
370 | mutex_lock(&sched->sched_lock); | ||
371 | fence = sched->ops->run_job(sched, c_entity, job); | ||
372 | if (fence) { | ||
373 | r = fence_add_callback(fence, &job->cb, | ||
374 | amd_sched_process_job); | ||
375 | if (r == -ENOENT) | ||
376 | amd_sched_process_job(fence, &job->cb); | ||
377 | else if (r) | ||
378 | DRM_ERROR("fence add callback failed (%d)\n", r); | ||
379 | fence_put(fence); | ||
380 | } | ||
381 | mutex_unlock(&sched->sched_lock); | ||
382 | } | ||
383 | return 0; | ||
384 | } | ||
385 | |||
386 | /** | ||
387 | * Create a gpu scheduler | ||
388 | * | ||
389 | * @device The device context for this scheduler | ||
390 | * @ops The backend operations for this scheduler. | ||
391 | * @id The scheduler is per ring, here is ring id. | ||
392 | * @granularity The minumum ms unit the scheduler will scheduled. | ||
393 | * @preemption Indicate whether this ring support preemption, 0 is no. | ||
394 | * | ||
395 | * return the pointer to scheduler for success, otherwise return NULL | ||
396 | */ | ||
397 | struct amd_gpu_scheduler *amd_sched_create(void *device, | ||
398 | struct amd_sched_backend_ops *ops, | ||
399 | unsigned ring, | ||
400 | unsigned granularity, | ||
401 | unsigned preemption, | ||
402 | unsigned hw_submission) | ||
403 | { | ||
404 | struct amd_gpu_scheduler *sched; | ||
405 | char name[20]; | ||
406 | |||
407 | sched = kzalloc(sizeof(struct amd_gpu_scheduler), GFP_KERNEL); | ||
408 | if (!sched) | ||
409 | return NULL; | ||
410 | |||
411 | sched->device = device; | ||
412 | sched->ops = ops; | ||
413 | sched->granularity = granularity; | ||
414 | sched->ring_id = ring; | ||
415 | sched->preemption = preemption; | ||
416 | sched->hw_submission_limit = hw_submission; | ||
417 | snprintf(name, sizeof(name), "gpu_sched[%d]", ring); | ||
418 | mutex_init(&sched->sched_lock); | ||
419 | spin_lock_init(&sched->queue_lock); | ||
420 | amd_sched_rq_init(&sched->sched_rq); | ||
421 | amd_sched_rq_init(&sched->kernel_rq); | ||
422 | |||
423 | init_waitqueue_head(&sched->wait_queue); | ||
424 | INIT_LIST_HEAD(&sched->active_hw_rq); | ||
425 | atomic64_set(&sched->hw_rq_count, 0); | ||
426 | /* Each scheduler will run on a seperate kernel thread */ | ||
427 | sched->thread = kthread_create(amd_sched_main, sched, name); | ||
428 | if (sched->thread) { | ||
429 | wake_up_process(sched->thread); | ||
430 | return sched; | ||
431 | } | ||
432 | |||
433 | DRM_ERROR("Failed to create scheduler for id %d.\n", ring); | ||
434 | kfree(sched); | ||
435 | return NULL; | ||
436 | } | ||
437 | |||
438 | /** | ||
439 | * Destroy a gpu scheduler | ||
440 | * | ||
441 | * @sched The pointer to the scheduler | ||
442 | * | ||
443 | * return 0 if succeed. -1 if failed. | ||
444 | */ | ||
445 | int amd_sched_destroy(struct amd_gpu_scheduler *sched) | ||
446 | { | ||
447 | kthread_stop(sched->thread); | ||
448 | kfree(sched); | ||
449 | return 0; | ||
450 | } | ||
451 | |||
452 | /** | ||
453 | * Get next queued sequence number | ||
454 | * | ||
455 | * @entity The context entity | ||
456 | * | ||
457 | * return the next queued sequence number | ||
458 | */ | ||
459 | uint64_t amd_sched_next_queued_seq(struct amd_sched_entity *c_entity) | ||
460 | { | ||
461 | return atomic64_read(&c_entity->last_queued_v_seq) + 1; | ||
462 | } | ||