diff options
Diffstat (limited to 'drivers/video/tegra/host/nvhost_cdma.c')
-rw-r--r-- | drivers/video/tegra/host/nvhost_cdma.c | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/drivers/video/tegra/host/nvhost_cdma.c b/drivers/video/tegra/host/nvhost_cdma.c new file mode 100644 index 00000000000..775d761e65c --- /dev/null +++ b/drivers/video/tegra/host/nvhost_cdma.c | |||
@@ -0,0 +1,508 @@ | |||
1 | /* | ||
2 | * drivers/video/tegra/host/nvhost_cdma.c | ||
3 | * | ||
4 | * Tegra Graphics Host Command DMA | ||
5 | * | ||
6 | * Copyright (c) 2010-2012, NVIDIA Corporation. | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms and conditions of the GNU General Public License, | ||
10 | * version 2, as published by the Free Software Foundation. | ||
11 | * | ||
12 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
13 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
14 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
15 | * more details. | ||
16 | * | ||
17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | ||
20 | |||
21 | #include "nvhost_cdma.h" | ||
22 | #include "dev.h" | ||
23 | #include <asm/cacheflush.h> | ||
24 | |||
25 | #include <linux/slab.h> | ||
26 | #include <linux/kfifo.h> | ||
27 | #include <trace/events/nvhost.h> | ||
28 | #include <linux/interrupt.h> | ||
29 | |||
30 | /* | ||
31 | * TODO: | ||
32 | * stats | ||
33 | * - for figuring out what to optimize further | ||
34 | * resizable push buffer | ||
35 | * - some channels hardly need any, some channels (3d) could use more | ||
36 | */ | ||
37 | |||
38 | /** | ||
39 | * Add an entry to the sync queue. | ||
40 | */ | ||
41 | static void add_to_sync_queue(struct nvhost_cdma *cdma, | ||
42 | struct nvhost_job *job, | ||
43 | u32 nr_slots, | ||
44 | u32 first_get) | ||
45 | { | ||
46 | BUG_ON(job->syncpt_id == NVSYNCPT_INVALID); | ||
47 | |||
48 | job->first_get = first_get; | ||
49 | job->num_slots = nr_slots; | ||
50 | nvhost_job_get(job); | ||
51 | list_add_tail(&job->list, &cdma->sync_queue); | ||
52 | } | ||
53 | |||
54 | /** | ||
55 | * Return the status of the cdma's sync queue or push buffer for the given event | ||
56 | * - sq empty: returns 1 for empty, 0 for not empty (as in "1 empty queue" :-) | ||
57 | * - pb space: returns the number of free slots in the channel's push buffer | ||
58 | * Must be called with the cdma lock held. | ||
59 | */ | ||
60 | static unsigned int cdma_status_locked(struct nvhost_cdma *cdma, | ||
61 | enum cdma_event event) | ||
62 | { | ||
63 | switch (event) { | ||
64 | case CDMA_EVENT_SYNC_QUEUE_EMPTY: | ||
65 | return list_empty(&cdma->sync_queue) ? 1 : 0; | ||
66 | case CDMA_EVENT_PUSH_BUFFER_SPACE: { | ||
67 | struct push_buffer *pb = &cdma->push_buffer; | ||
68 | BUG_ON(!cdma_pb_op(cdma).space); | ||
69 | return cdma_pb_op(cdma).space(pb); | ||
70 | } | ||
71 | default: | ||
72 | return 0; | ||
73 | } | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * Sleep (if necessary) until the requested event happens | ||
78 | * - CDMA_EVENT_SYNC_QUEUE_EMPTY : sync queue is completely empty. | ||
79 | * - Returns 1 | ||
80 | * - CDMA_EVENT_PUSH_BUFFER_SPACE : there is space in the push buffer | ||
81 | * - Return the amount of space (> 0) | ||
82 | * Must be called with the cdma lock held. | ||
83 | */ | ||
84 | unsigned int nvhost_cdma_wait_locked(struct nvhost_cdma *cdma, | ||
85 | enum cdma_event event) | ||
86 | { | ||
87 | for (;;) { | ||
88 | unsigned int space = cdma_status_locked(cdma, event); | ||
89 | if (space) | ||
90 | return space; | ||
91 | |||
92 | trace_nvhost_wait_cdma(cdma_to_channel(cdma)->dev->name, | ||
93 | event); | ||
94 | |||
95 | BUG_ON(cdma->event != CDMA_EVENT_NONE); | ||
96 | cdma->event = event; | ||
97 | |||
98 | mutex_unlock(&cdma->lock); | ||
99 | down(&cdma->sem); | ||
100 | mutex_lock(&cdma->lock); | ||
101 | } | ||
102 | return 0; | ||
103 | } | ||
104 | |||
105 | /** | ||
106 | * Start timer for a buffer submition that has completed yet. | ||
107 | * Must be called with the cdma lock held. | ||
108 | */ | ||
109 | static void cdma_start_timer_locked(struct nvhost_cdma *cdma, | ||
110 | struct nvhost_job *job) | ||
111 | { | ||
112 | BUG_ON(!job); | ||
113 | if (cdma->timeout.clientid) { | ||
114 | /* timer already started */ | ||
115 | return; | ||
116 | } | ||
117 | |||
118 | cdma->timeout.ctx = job->hwctx; | ||
119 | cdma->timeout.clientid = job->clientid; | ||
120 | cdma->timeout.syncpt_id = job->syncpt_id; | ||
121 | cdma->timeout.syncpt_val = job->syncpt_end; | ||
122 | cdma->timeout.start_ktime = ktime_get(); | ||
123 | |||
124 | schedule_delayed_work(&cdma->timeout.wq, | ||
125 | msecs_to_jiffies(job->timeout)); | ||
126 | } | ||
127 | |||
128 | /** | ||
129 | * Stop timer when a buffer submition completes. | ||
130 | * Must be called with the cdma lock held. | ||
131 | */ | ||
132 | static void stop_cdma_timer_locked(struct nvhost_cdma *cdma) | ||
133 | { | ||
134 | cancel_delayed_work(&cdma->timeout.wq); | ||
135 | cdma->timeout.ctx = NULL; | ||
136 | cdma->timeout.clientid = 0; | ||
137 | } | ||
138 | |||
139 | /** | ||
140 | * For all sync queue entries that have already finished according to the | ||
141 | * current sync point registers: | ||
142 | * - unpin & unref their mems | ||
143 | * - pop their push buffer slots | ||
144 | * - remove them from the sync queue | ||
145 | * This is normally called from the host code's worker thread, but can be | ||
146 | * called manually if necessary. | ||
147 | * Must be called with the cdma lock held. | ||
148 | */ | ||
149 | static void update_cdma_locked(struct nvhost_cdma *cdma) | ||
150 | { | ||
151 | bool signal = false; | ||
152 | struct nvhost_master *dev = cdma_to_dev(cdma); | ||
153 | struct nvhost_syncpt *sp = &dev->syncpt; | ||
154 | struct nvhost_job *job, *n; | ||
155 | |||
156 | BUG_ON(!cdma->running); | ||
157 | |||
158 | /* | ||
159 | * Walk the sync queue, reading the sync point registers as necessary, | ||
160 | * to consume as many sync queue entries as possible without blocking | ||
161 | */ | ||
162 | list_for_each_entry_safe(job, n, &cdma->sync_queue, list) { | ||
163 | BUG_ON(job->syncpt_id == NVSYNCPT_INVALID); | ||
164 | |||
165 | /* Check whether this syncpt has completed, and bail if not */ | ||
166 | if (!nvhost_syncpt_is_expired(sp, | ||
167 | job->syncpt_id, job->syncpt_end)) { | ||
168 | /* Start timer on next pending syncpt */ | ||
169 | if (job->timeout) | ||
170 | cdma_start_timer_locked(cdma, job); | ||
171 | break; | ||
172 | } | ||
173 | |||
174 | /* Cancel timeout, when a buffer completes */ | ||
175 | if (cdma->timeout.clientid) | ||
176 | stop_cdma_timer_locked(cdma); | ||
177 | |||
178 | /* Unpin the memory */ | ||
179 | nvhost_job_unpin(job); | ||
180 | |||
181 | /* Pop push buffer slots */ | ||
182 | if (job->num_slots) { | ||
183 | struct push_buffer *pb = &cdma->push_buffer; | ||
184 | BUG_ON(!cdma_pb_op(cdma).pop_from); | ||
185 | cdma_pb_op(cdma).pop_from(pb, job->num_slots); | ||
186 | if (cdma->event == CDMA_EVENT_PUSH_BUFFER_SPACE) | ||
187 | signal = true; | ||
188 | } | ||
189 | |||
190 | list_del(&job->list); | ||
191 | nvhost_job_put(job); | ||
192 | } | ||
193 | |||
194 | if (list_empty(&cdma->sync_queue) && | ||
195 | cdma->event == CDMA_EVENT_SYNC_QUEUE_EMPTY) | ||
196 | signal = true; | ||
197 | |||
198 | /* Wake up CdmaWait() if the requested event happened */ | ||
199 | if (signal) { | ||
200 | cdma->event = CDMA_EVENT_NONE; | ||
201 | up(&cdma->sem); | ||
202 | } | ||
203 | } | ||
204 | |||
205 | void nvhost_cdma_update_sync_queue(struct nvhost_cdma *cdma, | ||
206 | struct nvhost_syncpt *syncpt, struct device *dev) | ||
207 | { | ||
208 | u32 get_restart; | ||
209 | u32 syncpt_incrs; | ||
210 | bool exec_ctxsave; | ||
211 | struct nvhost_job *job = NULL; | ||
212 | u32 syncpt_val; | ||
213 | |||
214 | syncpt_val = nvhost_syncpt_update_min(syncpt, cdma->timeout.syncpt_id); | ||
215 | |||
216 | dev_dbg(dev, | ||
217 | "%s: starting cleanup (thresh %d)\n", | ||
218 | __func__, syncpt_val); | ||
219 | |||
220 | /* | ||
221 | * Move the sync_queue read pointer to the first entry that hasn't | ||
222 | * completed based on the current HW syncpt value. It's likely there | ||
223 | * won't be any (i.e. we're still at the head), but covers the case | ||
224 | * where a syncpt incr happens just prior/during the teardown. | ||
225 | */ | ||
226 | |||
227 | dev_dbg(dev, | ||
228 | "%s: skip completed buffers still in sync_queue\n", | ||
229 | __func__); | ||
230 | |||
231 | list_for_each_entry(job, &cdma->sync_queue, list) { | ||
232 | if (syncpt_val < job->syncpt_end) | ||
233 | break; | ||
234 | |||
235 | nvhost_job_dump(dev, job); | ||
236 | } | ||
237 | |||
238 | /* | ||
239 | * Walk the sync_queue, first incrementing with the CPU syncpts that | ||
240 | * are partially executed (the first buffer) or fully skipped while | ||
241 | * still in the current context (slots are also NOP-ed). | ||
242 | * | ||
243 | * At the point contexts are interleaved, syncpt increments must be | ||
244 | * done inline with the pushbuffer from a GATHER buffer to maintain | ||
245 | * the order (slots are modified to be a GATHER of syncpt incrs). | ||
246 | * | ||
247 | * Note: save in get_restart the location where the timed out buffer | ||
248 | * started in the PB, so we can start the refetch from there (with the | ||
249 | * modified NOP-ed PB slots). This lets things appear to have completed | ||
250 | * properly for this buffer and resources are freed. | ||
251 | */ | ||
252 | |||
253 | dev_dbg(dev, | ||
254 | "%s: perform CPU incr on pending same ctx buffers\n", | ||
255 | __func__); | ||
256 | |||
257 | get_restart = cdma->last_put; | ||
258 | if (!list_empty(&cdma->sync_queue)) | ||
259 | get_restart = job->first_get; | ||
260 | |||
261 | /* do CPU increments as long as this context continues */ | ||
262 | list_for_each_entry_from(job, &cdma->sync_queue, list) { | ||
263 | /* different context, gets us out of this loop */ | ||
264 | if (job->clientid != cdma->timeout.clientid) | ||
265 | break; | ||
266 | |||
267 | /* won't need a timeout when replayed */ | ||
268 | job->timeout = 0; | ||
269 | |||
270 | syncpt_incrs = job->syncpt_end - syncpt_val; | ||
271 | dev_dbg(dev, | ||
272 | "%s: CPU incr (%d)\n", __func__, syncpt_incrs); | ||
273 | |||
274 | nvhost_job_dump(dev, job); | ||
275 | |||
276 | /* safe to use CPU to incr syncpts */ | ||
277 | cdma_op(cdma).timeout_cpu_incr(cdma, | ||
278 | job->first_get, | ||
279 | syncpt_incrs, | ||
280 | job->syncpt_end, | ||
281 | job->num_slots); | ||
282 | |||
283 | syncpt_val += syncpt_incrs; | ||
284 | } | ||
285 | |||
286 | dev_dbg(dev, | ||
287 | "%s: GPU incr blocked interleaved ctx buffers\n", | ||
288 | __func__); | ||
289 | |||
290 | exec_ctxsave = false; | ||
291 | |||
292 | /* setup GPU increments */ | ||
293 | list_for_each_entry_from(job, &cdma->sync_queue, list) { | ||
294 | /* same context, increment in the pushbuffer */ | ||
295 | if (job->clientid == cdma->timeout.clientid) { | ||
296 | /* won't need a timeout when replayed */ | ||
297 | job->timeout = 0; | ||
298 | |||
299 | /* update buffer's syncpts in the pushbuffer */ | ||
300 | cdma_op(cdma).timeout_pb_incr(cdma, | ||
301 | job->first_get, | ||
302 | job->syncpt_incrs, | ||
303 | job->num_slots, | ||
304 | exec_ctxsave); | ||
305 | |||
306 | exec_ctxsave = false; | ||
307 | } else { | ||
308 | dev_dbg(dev, | ||
309 | "%s: switch to a different userctx\n", | ||
310 | __func__); | ||
311 | /* | ||
312 | * If previous context was the timed out context | ||
313 | * then clear its CTXSAVE in this slot. | ||
314 | */ | ||
315 | exec_ctxsave = true; | ||
316 | } | ||
317 | |||
318 | nvhost_job_dump(dev, job); | ||
319 | } | ||
320 | |||
321 | dev_dbg(dev, | ||
322 | "%s: finished sync_queue modification\n", __func__); | ||
323 | |||
324 | /* roll back DMAGET and start up channel again */ | ||
325 | cdma_op(cdma).timeout_teardown_end(cdma, get_restart); | ||
326 | |||
327 | if (cdma->timeout.ctx) | ||
328 | cdma->timeout.ctx->has_timedout = true; | ||
329 | } | ||
330 | |||
331 | /** | ||
332 | * Create a cdma | ||
333 | */ | ||
334 | int nvhost_cdma_init(struct nvhost_cdma *cdma) | ||
335 | { | ||
336 | int err; | ||
337 | struct push_buffer *pb = &cdma->push_buffer; | ||
338 | BUG_ON(!cdma_pb_op(cdma).init); | ||
339 | mutex_init(&cdma->lock); | ||
340 | sema_init(&cdma->sem, 0); | ||
341 | |||
342 | INIT_LIST_HEAD(&cdma->sync_queue); | ||
343 | |||
344 | cdma->event = CDMA_EVENT_NONE; | ||
345 | cdma->running = false; | ||
346 | cdma->torndown = false; | ||
347 | |||
348 | err = cdma_pb_op(cdma).init(pb); | ||
349 | if (err) | ||
350 | return err; | ||
351 | return 0; | ||
352 | } | ||
353 | |||
354 | /** | ||
355 | * Destroy a cdma | ||
356 | */ | ||
357 | void nvhost_cdma_deinit(struct nvhost_cdma *cdma) | ||
358 | { | ||
359 | struct push_buffer *pb = &cdma->push_buffer; | ||
360 | |||
361 | BUG_ON(!cdma_pb_op(cdma).destroy); | ||
362 | BUG_ON(cdma->running); | ||
363 | cdma_pb_op(cdma).destroy(pb); | ||
364 | cdma_op(cdma).timeout_destroy(cdma); | ||
365 | } | ||
366 | |||
367 | /** | ||
368 | * Begin a cdma submit | ||
369 | */ | ||
370 | int nvhost_cdma_begin(struct nvhost_cdma *cdma, struct nvhost_job *job) | ||
371 | { | ||
372 | mutex_lock(&cdma->lock); | ||
373 | |||
374 | if (job->timeout) { | ||
375 | /* init state on first submit with timeout value */ | ||
376 | if (!cdma->timeout.initialized) { | ||
377 | int err; | ||
378 | BUG_ON(!cdma_op(cdma).timeout_init); | ||
379 | err = cdma_op(cdma).timeout_init(cdma, | ||
380 | job->syncpt_id); | ||
381 | if (err) { | ||
382 | mutex_unlock(&cdma->lock); | ||
383 | return err; | ||
384 | } | ||
385 | } | ||
386 | } | ||
387 | if (!cdma->running) { | ||
388 | BUG_ON(!cdma_op(cdma).start); | ||
389 | cdma_op(cdma).start(cdma); | ||
390 | } | ||
391 | cdma->slots_free = 0; | ||
392 | cdma->slots_used = 0; | ||
393 | cdma->first_get = cdma_pb_op(cdma).putptr(&cdma->push_buffer); | ||
394 | return 0; | ||
395 | } | ||
396 | |||
397 | /** | ||
398 | * Push two words into a push buffer slot | ||
399 | * Blocks as necessary if the push buffer is full. | ||
400 | */ | ||
401 | void nvhost_cdma_push(struct nvhost_cdma *cdma, u32 op1, u32 op2) | ||
402 | { | ||
403 | nvhost_cdma_push_gather(cdma, NULL, NULL, op1, op2); | ||
404 | } | ||
405 | |||
406 | /** | ||
407 | * Push two words into a push buffer slot | ||
408 | * Blocks as necessary if the push buffer is full. | ||
409 | */ | ||
410 | void nvhost_cdma_push_gather(struct nvhost_cdma *cdma, | ||
411 | struct nvmap_client *client, | ||
412 | struct nvmap_handle *handle, u32 op1, u32 op2) | ||
413 | { | ||
414 | u32 slots_free = cdma->slots_free; | ||
415 | struct push_buffer *pb = &cdma->push_buffer; | ||
416 | BUG_ON(!cdma_pb_op(cdma).push_to); | ||
417 | BUG_ON(!cdma_op(cdma).kick); | ||
418 | if (slots_free == 0) { | ||
419 | cdma_op(cdma).kick(cdma); | ||
420 | slots_free = nvhost_cdma_wait_locked(cdma, | ||
421 | CDMA_EVENT_PUSH_BUFFER_SPACE); | ||
422 | } | ||
423 | cdma->slots_free = slots_free - 1; | ||
424 | cdma->slots_used++; | ||
425 | cdma_pb_op(cdma).push_to(pb, client, handle, op1, op2); | ||
426 | } | ||
427 | |||
428 | /** | ||
429 | * End a cdma submit | ||
430 | * Kick off DMA, add job to the sync queue, and a number of slots to be freed | ||
431 | * from the pushbuffer. The handles for a submit must all be pinned at the same | ||
432 | * time, but they can be unpinned in smaller chunks. | ||
433 | */ | ||
434 | void nvhost_cdma_end(struct nvhost_cdma *cdma, | ||
435 | struct nvhost_job *job) | ||
436 | { | ||
437 | bool was_idle = list_empty(&cdma->sync_queue); | ||
438 | |||
439 | BUG_ON(!cdma_op(cdma).kick); | ||
440 | cdma_op(cdma).kick(cdma); | ||
441 | |||
442 | BUG_ON(job->syncpt_id == NVSYNCPT_INVALID); | ||
443 | |||
444 | add_to_sync_queue(cdma, | ||
445 | job, | ||
446 | cdma->slots_used, | ||
447 | cdma->first_get); | ||
448 | |||
449 | /* start timer on idle -> active transitions */ | ||
450 | if (job->timeout && was_idle) | ||
451 | cdma_start_timer_locked(cdma, job); | ||
452 | |||
453 | mutex_unlock(&cdma->lock); | ||
454 | } | ||
455 | |||
456 | /** | ||
457 | * Update cdma state according to current sync point values | ||
458 | */ | ||
459 | void nvhost_cdma_update(struct nvhost_cdma *cdma) | ||
460 | { | ||
461 | mutex_lock(&cdma->lock); | ||
462 | update_cdma_locked(cdma); | ||
463 | mutex_unlock(&cdma->lock); | ||
464 | } | ||
465 | |||
466 | /** | ||
467 | * Wait for push buffer to be empty. | ||
468 | * @cdma pointer to channel cdma | ||
469 | * @timeout timeout in ms | ||
470 | * Returns -ETIME if timeout was reached, zero if push buffer is empty. | ||
471 | */ | ||
472 | int nvhost_cdma_flush(struct nvhost_cdma *cdma, int timeout) | ||
473 | { | ||
474 | unsigned int space, err = 0; | ||
475 | unsigned long end_jiffies = jiffies + msecs_to_jiffies(timeout); | ||
476 | |||
477 | /* | ||
478 | * Wait for at most timeout ms. Recalculate timeout at each iteration | ||
479 | * to better keep within given timeout. | ||
480 | */ | ||
481 | while(!err && time_before(jiffies, end_jiffies)) { | ||
482 | int timeout_jiffies = end_jiffies - jiffies; | ||
483 | |||
484 | mutex_lock(&cdma->lock); | ||
485 | space = cdma_status_locked(cdma, | ||
486 | CDMA_EVENT_SYNC_QUEUE_EMPTY); | ||
487 | if (space) { | ||
488 | mutex_unlock(&cdma->lock); | ||
489 | return 0; | ||
490 | } | ||
491 | |||
492 | /* | ||
493 | * Wait for sync queue to become empty. If there is already | ||
494 | * an event pending, we need to poll. | ||
495 | */ | ||
496 | if (cdma->event != CDMA_EVENT_NONE) { | ||
497 | mutex_unlock(&cdma->lock); | ||
498 | schedule(); | ||
499 | } else { | ||
500 | cdma->event = CDMA_EVENT_SYNC_QUEUE_EMPTY; | ||
501 | |||
502 | mutex_unlock(&cdma->lock); | ||
503 | err = down_timeout(&cdma->sem, | ||
504 | jiffies_to_msecs(timeout_jiffies)); | ||
505 | } | ||
506 | } | ||
507 | return err; | ||
508 | } | ||