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