]> git.proxmox.com Git - mirror_ubuntu-focal-kernel.git/blame - drivers/gpu/drm/etnaviv/etnaviv_gem_submit.c
drm/etnaviv: move object unpinning to submit cleanup
[mirror_ubuntu-focal-kernel.git] / drivers / gpu / drm / etnaviv / etnaviv_gem_submit.c
CommitLineData
a8c21a54
T
1/*
2 * Copyright (C) 2015 Etnaviv Project
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License version 2 as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but WITHOUT
9 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
10 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
11 * more details.
12 *
13 * You should have received a copy of the GNU General Public License along with
14 * this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
9ad59fea 17#include <linux/dma-fence-array.h>
a8c21a54 18#include <linux/reservation.h>
9ad59fea 19#include <linux/sync_file.h>
ea1f5729 20#include "etnaviv_cmdbuf.h"
a8c21a54
T
21#include "etnaviv_drv.h"
22#include "etnaviv_gpu.h"
23#include "etnaviv_gem.h"
c8e4a7fd 24#include "etnaviv_perfmon.h"
a8c21a54
T
25
26/*
27 * Cmdstream submission:
28 */
29
30#define BO_INVALID_FLAGS ~(ETNA_SUBMIT_BO_READ | ETNA_SUBMIT_BO_WRITE)
31/* make sure these don't conflict w/ ETNAVIV_SUBMIT_BO_x */
32#define BO_LOCKED 0x4000
33#define BO_PINNED 0x2000
34
a8c21a54
T
35static struct etnaviv_gem_submit *submit_create(struct drm_device *dev,
36 struct etnaviv_gpu *gpu, size_t nr)
37{
38 struct etnaviv_gem_submit *submit;
39 size_t sz = size_vstruct(nr, sizeof(submit->bos[0]), sizeof(*submit));
40
c5283723
LS
41 submit = kzalloc(sz, GFP_KERNEL);
42 if (!submit)
43 return NULL;
a8c21a54 44
c5283723 45 submit->gpu = gpu;
a8c21a54 46
c5283723 47 ww_acquire_init(&submit->ticket, &reservation_ww_class);
a8c21a54
T
48
49 return submit;
50}
51
52static int submit_lookup_objects(struct etnaviv_gem_submit *submit,
53 struct drm_file *file, struct drm_etnaviv_gem_submit_bo *submit_bos,
54 unsigned nr_bos)
55{
56 struct drm_etnaviv_gem_submit_bo *bo;
57 unsigned i;
58 int ret = 0;
59
60 spin_lock(&file->table_lock);
61
62 for (i = 0, bo = submit_bos; i < nr_bos; i++, bo++) {
63 struct drm_gem_object *obj;
64
65 if (bo->flags & BO_INVALID_FLAGS) {
66 DRM_ERROR("invalid flags: %x\n", bo->flags);
67 ret = -EINVAL;
68 goto out_unlock;
69 }
70
71 submit->bos[i].flags = bo->flags;
72
73 /* normally use drm_gem_object_lookup(), but for bulk lookup
74 * all under single table_lock just hit object_idr directly:
75 */
76 obj = idr_find(&file->object_idr, bo->handle);
77 if (!obj) {
78 DRM_ERROR("invalid handle %u at index %u\n",
79 bo->handle, i);
80 ret = -EINVAL;
81 goto out_unlock;
82 }
83
84 /*
85 * Take a refcount on the object. The file table lock
86 * prevents the object_idr's refcount on this being dropped.
87 */
23d1dd03 88 drm_gem_object_get(obj);
a8c21a54
T
89
90 submit->bos[i].obj = to_etnaviv_bo(obj);
91 }
92
93out_unlock:
94 submit->nr_bos = i;
95 spin_unlock(&file->table_lock);
96
97 return ret;
98}
99
100static void submit_unlock_object(struct etnaviv_gem_submit *submit, int i)
101{
102 if (submit->bos[i].flags & BO_LOCKED) {
103 struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj;
104
105 ww_mutex_unlock(&etnaviv_obj->resv->lock);
106 submit->bos[i].flags &= ~BO_LOCKED;
107 }
108}
109
110static int submit_lock_objects(struct etnaviv_gem_submit *submit)
111{
112 int contended, slow_locked = -1, i, ret = 0;
113
114retry:
115 for (i = 0; i < submit->nr_bos; i++) {
116 struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj;
117
118 if (slow_locked == i)
119 slow_locked = -1;
120
121 contended = i;
122
123 if (!(submit->bos[i].flags & BO_LOCKED)) {
124 ret = ww_mutex_lock_interruptible(&etnaviv_obj->resv->lock,
125 &submit->ticket);
126 if (ret == -EALREADY)
127 DRM_ERROR("BO at index %u already on submit list\n",
128 i);
129 if (ret)
130 goto fail;
131 submit->bos[i].flags |= BO_LOCKED;
132 }
133 }
134
135 ww_acquire_done(&submit->ticket);
136
137 return 0;
138
139fail:
140 for (; i >= 0; i--)
141 submit_unlock_object(submit, i);
142
143 if (slow_locked > 0)
144 submit_unlock_object(submit, slow_locked);
145
146 if (ret == -EDEADLK) {
147 struct etnaviv_gem_object *etnaviv_obj;
148
149 etnaviv_obj = submit->bos[contended].obj;
150
151 /* we lost out in a seqno race, lock and retry.. */
152 ret = ww_mutex_lock_slow_interruptible(&etnaviv_obj->resv->lock,
153 &submit->ticket);
154 if (!ret) {
155 submit->bos[contended].flags |= BO_LOCKED;
156 slow_locked = contended;
157 goto retry;
158 }
159 }
160
161 return ret;
162}
163
164static int submit_fence_sync(const struct etnaviv_gem_submit *submit)
165{
166 unsigned int context = submit->gpu->fence_context;
167 int i, ret = 0;
168
169 for (i = 0; i < submit->nr_bos; i++) {
170 struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj;
171 bool write = submit->bos[i].flags & ETNA_SUBMIT_BO_WRITE;
426ef1bb 172 bool explicit = !!(submit->flags & ETNA_SUBMIT_NO_IMPLICIT);
a8c21a54 173
9ad59fea
PZ
174 ret = etnaviv_gpu_fence_sync_obj(etnaviv_obj, context, write,
175 explicit);
a8c21a54
T
176 if (ret)
177 break;
178 }
179
9efabd73
LS
180 if (submit->flags & ETNA_SUBMIT_FENCE_FD_IN) {
181 /*
182 * Wait if the fence is from a foreign context, or if the fence
183 * array contains any fence from a foreign context.
184 */
185 if (!dma_fence_match_context(submit->in_fence, context))
186 ret = dma_fence_wait(submit->in_fence, true);
187 }
188
a8c21a54
T
189 return ret;
190}
191
0236efe9
LS
192static void submit_attach_object_fences(struct etnaviv_gem_submit *submit)
193{
194 int i;
195
196 for (i = 0; i < submit->nr_bos; i++) {
197 struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj;
198
199 if (submit->bos[i].flags & ETNA_SUBMIT_BO_WRITE)
200 reservation_object_add_excl_fence(etnaviv_obj->resv,
10009ea2 201 submit->out_fence);
0236efe9
LS
202 else
203 reservation_object_add_shared_fence(etnaviv_obj->resv,
10009ea2 204 submit->out_fence);
0236efe9
LS
205
206 submit_unlock_object(submit, i);
207 }
208}
209
a8c21a54
T
210static int submit_pin_objects(struct etnaviv_gem_submit *submit)
211{
212 int i, ret = 0;
213
214 for (i = 0; i < submit->nr_bos; i++) {
215 struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj;
b6325f40 216 struct etnaviv_vram_mapping *mapping;
a8c21a54 217
b6325f40
RK
218 mapping = etnaviv_gem_mapping_get(&etnaviv_obj->base,
219 submit->gpu);
220 if (IS_ERR(mapping)) {
221 ret = PTR_ERR(mapping);
a8c21a54 222 break;
b6325f40 223 }
a8c21a54
T
224
225 submit->bos[i].flags |= BO_PINNED;
b6325f40 226 submit->bos[i].mapping = mapping;
a8c21a54
T
227 }
228
229 return ret;
230}
231
232static int submit_bo(struct etnaviv_gem_submit *submit, u32 idx,
8779aa8f 233 struct etnaviv_gem_submit_bo **bo)
a8c21a54
T
234{
235 if (idx >= submit->nr_bos) {
236 DRM_ERROR("invalid buffer index: %u (out of %u)\n",
237 idx, submit->nr_bos);
238 return -EINVAL;
239 }
240
8779aa8f 241 *bo = &submit->bos[idx];
a8c21a54
T
242
243 return 0;
244}
245
246/* process the reloc's and patch up the cmdstream as needed: */
247static int submit_reloc(struct etnaviv_gem_submit *submit, void *stream,
248 u32 size, const struct drm_etnaviv_gem_submit_reloc *relocs,
249 u32 nr_relocs)
250{
251 u32 i, last_offset = 0;
252 u32 *ptr = stream;
253 int ret;
254
255 for (i = 0; i < nr_relocs; i++) {
256 const struct drm_etnaviv_gem_submit_reloc *r = relocs + i;
8779aa8f
RK
257 struct etnaviv_gem_submit_bo *bo;
258 u32 off;
a8c21a54
T
259
260 if (unlikely(r->flags)) {
261 DRM_ERROR("invalid reloc flags\n");
262 return -EINVAL;
263 }
264
265 if (r->submit_offset % 4) {
266 DRM_ERROR("non-aligned reloc offset: %u\n",
267 r->submit_offset);
268 return -EINVAL;
269 }
270
271 /* offset in dwords: */
272 off = r->submit_offset / 4;
273
274 if ((off >= size ) ||
275 (off < last_offset)) {
276 DRM_ERROR("invalid offset %u at reloc %u\n", off, i);
277 return -EINVAL;
278 }
279
8779aa8f 280 ret = submit_bo(submit, r->reloc_idx, &bo);
a8c21a54
T
281 if (ret)
282 return ret;
283
d6f756e0
WL
284 if (r->reloc_offset > bo->obj->base.size - sizeof(*ptr)) {
285 DRM_ERROR("relocation %u outside object\n", i);
a8c21a54
T
286 return -EINVAL;
287 }
288
8779aa8f 289 ptr[off] = bo->mapping->iova + r->reloc_offset;
a8c21a54
T
290
291 last_offset = off;
292 }
293
294 return 0;
295}
296
c8e4a7fd
CG
297static int submit_perfmon_validate(struct etnaviv_gem_submit *submit,
298 struct etnaviv_cmdbuf *cmdbuf,
299 const struct drm_etnaviv_gem_submit_pmr *pmrs,
300 u32 nr_pms)
301{
302 u32 i;
303
304 for (i = 0; i < nr_pms; i++) {
305 const struct drm_etnaviv_gem_submit_pmr *r = pmrs + i;
306 struct etnaviv_gem_submit_bo *bo;
307 int ret;
308
309 ret = submit_bo(submit, r->read_idx, &bo);
310 if (ret)
311 return ret;
312
313 /* at offset 0 a sequence number gets stored used for userspace sync */
314 if (r->read_offset == 0) {
315 DRM_ERROR("perfmon request: offset is 0");
316 return -EINVAL;
317 }
318
319 if (r->read_offset >= bo->obj->base.size - sizeof(u32)) {
320 DRM_ERROR("perfmon request: offset %u outside object", i);
321 return -EINVAL;
322 }
323
324 if (r->flags & ~(ETNA_PM_PROCESS_PRE | ETNA_PM_PROCESS_POST)) {
325 DRM_ERROR("perfmon request: flags are not valid");
326 return -EINVAL;
327 }
328
329 if (etnaviv_pm_req_validate(r, cmdbuf->exec_state)) {
330 DRM_ERROR("perfmon request: domain or signal not valid");
331 return -EINVAL;
332 }
333
334 cmdbuf->pmrs[i].flags = r->flags;
335 cmdbuf->pmrs[i].domain = r->domain;
336 cmdbuf->pmrs[i].signal = r->signal;
337 cmdbuf->pmrs[i].sequence = r->sequence;
338 cmdbuf->pmrs[i].offset = r->read_offset;
339 cmdbuf->pmrs[i].bo_vma = etnaviv_gem_vmap(&bo->obj->base);
340 }
341
342 return 0;
343}
344
a8c21a54
T
345static void submit_cleanup(struct etnaviv_gem_submit *submit)
346{
347 unsigned i;
348
349 for (i = 0; i < submit->nr_bos; i++) {
350 struct etnaviv_gem_object *etnaviv_obj = submit->bos[i].obj;
351
33a63e68
LS
352 /* unpin all objects */
353 if (submit->bos[i].flags & BO_PINNED) {
354 etnaviv_gem_mapping_unreference(submit->bos[i].mapping);
355 submit->bos[i].mapping = NULL;
356 submit->bos[i].flags &= ~BO_PINNED;
357 }
358
0236efe9 359 /* if the GPU submit failed, objects might still be locked */
a8c21a54 360 submit_unlock_object(submit, i);
23d1dd03 361 drm_gem_object_put_unlocked(&etnaviv_obj->base);
a8c21a54
T
362 }
363
364 ww_acquire_fini(&submit->ticket);
9efabd73
LS
365 if (submit->in_fence)
366 dma_fence_put(submit->in_fence);
10009ea2
LS
367 if (submit->out_fence)
368 dma_fence_put(submit->out_fence);
a8c21a54
T
369 kfree(submit);
370}
371
372int etnaviv_ioctl_gem_submit(struct drm_device *dev, void *data,
373 struct drm_file *file)
374{
375 struct etnaviv_drm_private *priv = dev->dev_private;
376 struct drm_etnaviv_gem_submit *args = data;
377 struct drm_etnaviv_gem_submit_reloc *relocs;
c8e4a7fd 378 struct drm_etnaviv_gem_submit_pmr *pmrs;
a8c21a54
T
379 struct drm_etnaviv_gem_submit_bo *bos;
380 struct etnaviv_gem_submit *submit;
381 struct etnaviv_cmdbuf *cmdbuf;
382 struct etnaviv_gpu *gpu;
78ec187f
PZ
383 struct sync_file *sync_file = NULL;
384 int out_fence_fd = -1;
a8c21a54
T
385 void *stream;
386 int ret;
387
388 if (args->pipe >= ETNA_MAX_PIPES)
389 return -EINVAL;
390
391 gpu = priv->gpu[args->pipe];
392 if (!gpu)
393 return -ENXIO;
394
395 if (args->stream_size % 4) {
396 DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
397 args->stream_size);
398 return -EINVAL;
399 }
400
401 if (args->exec_state != ETNA_PIPE_3D &&
402 args->exec_state != ETNA_PIPE_2D &&
403 args->exec_state != ETNA_PIPE_VG) {
404 DRM_ERROR("invalid exec_state: 0x%x\n", args->exec_state);
405 return -EINVAL;
406 }
407
9ad59fea
PZ
408 if (args->flags & ~ETNA_SUBMIT_FLAGS) {
409 DRM_ERROR("invalid flags: 0x%x\n", args->flags);
410 return -EINVAL;
411 }
412
a8c21a54
T
413 /*
414 * Copy the command submission and bo array to kernel space in
415 * one go, and do this outside of any locks.
416 */
2098105e
MH
417 bos = kvmalloc_array(args->nr_bos, sizeof(*bos), GFP_KERNEL);
418 relocs = kvmalloc_array(args->nr_relocs, sizeof(*relocs), GFP_KERNEL);
c8e4a7fd 419 pmrs = kvmalloc_array(args->nr_pmrs, sizeof(*pmrs), GFP_KERNEL);
2098105e 420 stream = kvmalloc_array(1, args->stream_size, GFP_KERNEL);
e66774dd
LS
421 cmdbuf = etnaviv_cmdbuf_new(gpu->cmdbuf_suballoc,
422 ALIGN(args->stream_size, 8) + 8,
c8e4a7fd
CG
423 args->nr_bos, args->nr_pmrs);
424 if (!bos || !relocs || !pmrs || !stream || !cmdbuf) {
a8c21a54
T
425 ret = -ENOMEM;
426 goto err_submit_cmds;
427 }
428
429 cmdbuf->exec_state = args->exec_state;
430 cmdbuf->ctx = file->driver_priv;
431
3ed605bc 432 ret = copy_from_user(bos, u64_to_user_ptr(args->bos),
a8c21a54
T
433 args->nr_bos * sizeof(*bos));
434 if (ret) {
435 ret = -EFAULT;
436 goto err_submit_cmds;
437 }
438
3ed605bc 439 ret = copy_from_user(relocs, u64_to_user_ptr(args->relocs),
a8c21a54
T
440 args->nr_relocs * sizeof(*relocs));
441 if (ret) {
442 ret = -EFAULT;
443 goto err_submit_cmds;
444 }
445
c8e4a7fd
CG
446 ret = copy_from_user(pmrs, u64_to_user_ptr(args->pmrs),
447 args->nr_pmrs * sizeof(*pmrs));
448 if (ret) {
449 ret = -EFAULT;
450 goto err_submit_cmds;
451 }
452 cmdbuf->nr_pmrs = args->nr_pmrs;
453
3ed605bc 454 ret = copy_from_user(stream, u64_to_user_ptr(args->stream),
a8c21a54
T
455 args->stream_size);
456 if (ret) {
457 ret = -EFAULT;
458 goto err_submit_cmds;
459 }
460
78ec187f
PZ
461 if (args->flags & ETNA_SUBMIT_FENCE_FD_OUT) {
462 out_fence_fd = get_unused_fd_flags(O_CLOEXEC);
463 if (out_fence_fd < 0) {
464 ret = out_fence_fd;
465 goto err_submit_cmds;
466 }
467 }
468
a8c21a54
T
469 submit = submit_create(dev, gpu, args->nr_bos);
470 if (!submit) {
471 ret = -ENOMEM;
472 goto err_submit_cmds;
473 }
474
9ad59fea
PZ
475 submit->flags = args->flags;
476
a8c21a54
T
477 ret = submit_lookup_objects(submit, file, bos, args->nr_bos);
478 if (ret)
479 goto err_submit_objects;
480
481 ret = submit_lock_objects(submit);
482 if (ret)
483 goto err_submit_objects;
484
485 if (!etnaviv_cmd_validate_one(gpu, stream, args->stream_size / 4,
486 relocs, args->nr_relocs)) {
487 ret = -EINVAL;
488 goto err_submit_objects;
489 }
490
9ad59fea 491 if (args->flags & ETNA_SUBMIT_FENCE_FD_IN) {
9efabd73
LS
492 submit->in_fence = sync_file_get_fence(args->fence_fd);
493 if (!submit->in_fence) {
9ad59fea
PZ
494 ret = -EINVAL;
495 goto err_submit_objects;
496 }
9ad59fea
PZ
497 }
498
a8c21a54
T
499 ret = submit_fence_sync(submit);
500 if (ret)
501 goto err_submit_objects;
502
503 ret = submit_pin_objects(submit);
504 if (ret)
33a63e68 505 goto err_submit_objects;
a8c21a54
T
506
507 ret = submit_reloc(submit, stream, args->stream_size / 4,
508 relocs, args->nr_relocs);
509 if (ret)
33a63e68 510 goto err_submit_objects;
a8c21a54 511
c8e4a7fd
CG
512 ret = submit_perfmon_validate(submit, cmdbuf, pmrs, args->nr_pmrs);
513 if (ret)
33a63e68 514 goto err_submit_objects;
c8e4a7fd 515
a8c21a54
T
516 memcpy(cmdbuf->vaddr, stream, args->stream_size);
517 cmdbuf->user_size = ALIGN(args->stream_size, 8);
518
519 ret = etnaviv_gpu_submit(gpu, submit, cmdbuf);
5a642e6b 520 if (ret)
33a63e68 521 goto err_submit_objects;
5a642e6b 522
0236efe9
LS
523 submit_attach_object_fences(submit);
524
5a642e6b 525 cmdbuf = NULL;
a8c21a54 526
78ec187f
PZ
527 if (args->flags & ETNA_SUBMIT_FENCE_FD_OUT) {
528 /*
529 * This can be improved: ideally we want to allocate the sync
530 * file before kicking off the GPU job and just attach the
531 * fence to the sync file here, eliminating the ENOMEM
532 * possibility at this stage.
533 */
10009ea2 534 sync_file = sync_file_create(submit->out_fence);
78ec187f
PZ
535 if (!sync_file) {
536 ret = -ENOMEM;
33a63e68 537 goto err_submit_objects;
78ec187f
PZ
538 }
539 fd_install(out_fence_fd, sync_file->file);
540 }
541
542 args->fence_fd = out_fence_fd;
10009ea2 543 args->fence = submit->out_fence->seqno;
a8c21a54 544
a8c21a54
T
545err_submit_objects:
546 submit_cleanup(submit);
547
548err_submit_cmds:
78ec187f
PZ
549 if (ret && (out_fence_fd >= 0))
550 put_unused_fd(out_fence_fd);
a8c21a54
T
551 /* if we still own the cmdbuf */
552 if (cmdbuf)
ea1f5729 553 etnaviv_cmdbuf_free(cmdbuf);
a8c21a54 554 if (stream)
2098105e 555 kvfree(stream);
a8c21a54 556 if (bos)
2098105e 557 kvfree(bos);
a8c21a54 558 if (relocs)
2098105e 559 kvfree(relocs);
c8e4a7fd
CG
560 if (pmrs)
561 kvfree(pmrs);
a8c21a54
T
562
563 return ret;
564}