1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (C) 2015 Etnaviv Project
6 #include <drm/drm_file.h>
7 #include <linux/dma-fence-array.h>
8 #include <linux/file.h>
9 #include <linux/pm_runtime.h>
10 #include <linux/dma-resv.h>
11 #include <linux/sync_file.h>
12 #include <linux/uaccess.h>
13 #include <linux/vmalloc.h>
15 #include "etnaviv_cmdbuf.h"
16 #include "etnaviv_drv.h"
17 #include "etnaviv_gpu.h"
18 #include "etnaviv_gem.h"
19 #include "etnaviv_perfmon.h"
20 #include "etnaviv_sched.h"
23 * Cmdstream submission:
26 #define BO_INVALID_FLAGS ~(ETNA_SUBMIT_BO_READ | ETNA_SUBMIT_BO_WRITE)
27 /* make sure these don't conflict w/ ETNAVIV_SUBMIT_BO_x */
28 #define BO_LOCKED 0x4000
29 #define BO_PINNED 0x2000
31 static struct etnaviv_gem_submit
*submit_create(struct drm_device
*dev
,
32 struct etnaviv_gpu
*gpu
, size_t nr_bos
, size_t nr_pmrs
)
34 struct etnaviv_gem_submit
*submit
;
35 size_t sz
= size_vstruct(nr_bos
, sizeof(submit
->bos
[0]), sizeof(*submit
));
37 submit
= kzalloc(sz
, GFP_KERNEL
);
41 submit
->pmrs
= kcalloc(nr_pmrs
, sizeof(struct etnaviv_perfmon_request
),
47 submit
->nr_pmrs
= nr_pmrs
;
50 kref_init(&submit
->refcount
);
55 static int submit_lookup_objects(struct etnaviv_gem_submit
*submit
,
56 struct drm_file
*file
, struct drm_etnaviv_gem_submit_bo
*submit_bos
,
59 struct drm_etnaviv_gem_submit_bo
*bo
;
63 spin_lock(&file
->table_lock
);
65 for (i
= 0, bo
= submit_bos
; i
< nr_bos
; i
++, bo
++) {
66 struct drm_gem_object
*obj
;
68 if (bo
->flags
& BO_INVALID_FLAGS
) {
69 DRM_ERROR("invalid flags: %x\n", bo
->flags
);
74 submit
->bos
[i
].flags
= bo
->flags
;
75 if (submit
->flags
& ETNA_SUBMIT_SOFTPIN
) {
76 if (bo
->presumed
< ETNAVIV_SOFTPIN_START_ADDRESS
) {
77 DRM_ERROR("invalid softpin address\n");
81 submit
->bos
[i
].va
= bo
->presumed
;
84 /* normally use drm_gem_object_lookup(), but for bulk lookup
85 * all under single table_lock just hit object_idr directly:
87 obj
= idr_find(&file
->object_idr
, bo
->handle
);
89 DRM_ERROR("invalid handle %u at index %u\n",
96 * Take a refcount on the object. The file table lock
97 * prevents the object_idr's refcount on this being dropped.
99 drm_gem_object_get(obj
);
101 submit
->bos
[i
].obj
= to_etnaviv_bo(obj
);
106 spin_unlock(&file
->table_lock
);
111 static void submit_unlock_object(struct etnaviv_gem_submit
*submit
, int i
)
113 if (submit
->bos
[i
].flags
& BO_LOCKED
) {
114 struct drm_gem_object
*obj
= &submit
->bos
[i
].obj
->base
;
116 dma_resv_unlock(obj
->resv
);
117 submit
->bos
[i
].flags
&= ~BO_LOCKED
;
121 static int submit_lock_objects(struct etnaviv_gem_submit
*submit
,
122 struct ww_acquire_ctx
*ticket
)
124 int contended
, slow_locked
= -1, i
, ret
= 0;
127 for (i
= 0; i
< submit
->nr_bos
; i
++) {
128 struct drm_gem_object
*obj
= &submit
->bos
[i
].obj
->base
;
130 if (slow_locked
== i
)
135 if (!(submit
->bos
[i
].flags
& BO_LOCKED
)) {
136 ret
= dma_resv_lock_interruptible(obj
->resv
, ticket
);
137 if (ret
== -EALREADY
)
138 DRM_ERROR("BO at index %u already on submit list\n",
142 submit
->bos
[i
].flags
|= BO_LOCKED
;
146 ww_acquire_done(ticket
);
152 submit_unlock_object(submit
, i
);
155 submit_unlock_object(submit
, slow_locked
);
157 if (ret
== -EDEADLK
) {
158 struct drm_gem_object
*obj
;
160 obj
= &submit
->bos
[contended
].obj
->base
;
162 /* we lost out in a seqno race, lock and retry.. */
163 ret
= dma_resv_lock_slow_interruptible(obj
->resv
, ticket
);
165 submit
->bos
[contended
].flags
|= BO_LOCKED
;
166 slow_locked
= contended
;
174 static int submit_fence_sync(struct etnaviv_gem_submit
*submit
)
178 for (i
= 0; i
< submit
->nr_bos
; i
++) {
179 struct etnaviv_gem_submit_bo
*bo
= &submit
->bos
[i
];
180 struct dma_resv
*robj
= bo
->obj
->base
.resv
;
182 if (!(bo
->flags
& ETNA_SUBMIT_BO_WRITE
)) {
183 ret
= dma_resv_reserve_shared(robj
, 1);
188 if (submit
->flags
& ETNA_SUBMIT_NO_IMPLICIT
)
191 if (bo
->flags
& ETNA_SUBMIT_BO_WRITE
) {
192 ret
= dma_resv_get_fences_rcu(robj
, &bo
->excl
,
198 bo
->excl
= dma_resv_get_excl_rcu(robj
);
206 static void submit_attach_object_fences(struct etnaviv_gem_submit
*submit
)
210 for (i
= 0; i
< submit
->nr_bos
; i
++) {
211 struct drm_gem_object
*obj
= &submit
->bos
[i
].obj
->base
;
213 if (submit
->bos
[i
].flags
& ETNA_SUBMIT_BO_WRITE
)
214 dma_resv_add_excl_fence(obj
->resv
,
217 dma_resv_add_shared_fence(obj
->resv
,
220 submit_unlock_object(submit
, i
);
224 static int submit_pin_objects(struct etnaviv_gem_submit
*submit
)
228 for (i
= 0; i
< submit
->nr_bos
; i
++) {
229 struct etnaviv_gem_object
*etnaviv_obj
= submit
->bos
[i
].obj
;
230 struct etnaviv_vram_mapping
*mapping
;
232 mapping
= etnaviv_gem_mapping_get(&etnaviv_obj
->base
,
235 if (IS_ERR(mapping
)) {
236 ret
= PTR_ERR(mapping
);
240 if ((submit
->flags
& ETNA_SUBMIT_SOFTPIN
) &&
241 submit
->bos
[i
].va
!= mapping
->iova
) {
242 etnaviv_gem_mapping_unreference(mapping
);
246 atomic_inc(&etnaviv_obj
->gpu_active
);
248 submit
->bos
[i
].flags
|= BO_PINNED
;
249 submit
->bos
[i
].mapping
= mapping
;
255 static int submit_bo(struct etnaviv_gem_submit
*submit
, u32 idx
,
256 struct etnaviv_gem_submit_bo
**bo
)
258 if (idx
>= submit
->nr_bos
) {
259 DRM_ERROR("invalid buffer index: %u (out of %u)\n",
260 idx
, submit
->nr_bos
);
264 *bo
= &submit
->bos
[idx
];
269 /* process the reloc's and patch up the cmdstream as needed: */
270 static int submit_reloc(struct etnaviv_gem_submit
*submit
, void *stream
,
271 u32 size
, const struct drm_etnaviv_gem_submit_reloc
*relocs
,
274 u32 i
, last_offset
= 0;
278 /* Submits using softpin don't blend with relocs */
279 if ((submit
->flags
& ETNA_SUBMIT_SOFTPIN
) && nr_relocs
!= 0)
282 for (i
= 0; i
< nr_relocs
; i
++) {
283 const struct drm_etnaviv_gem_submit_reloc
*r
= relocs
+ i
;
284 struct etnaviv_gem_submit_bo
*bo
;
287 if (unlikely(r
->flags
)) {
288 DRM_ERROR("invalid reloc flags\n");
292 if (r
->submit_offset
% 4) {
293 DRM_ERROR("non-aligned reloc offset: %u\n",
298 /* offset in dwords: */
299 off
= r
->submit_offset
/ 4;
301 if ((off
>= size
) ||
302 (off
< last_offset
)) {
303 DRM_ERROR("invalid offset %u at reloc %u\n", off
, i
);
307 ret
= submit_bo(submit
, r
->reloc_idx
, &bo
);
311 if (r
->reloc_offset
> bo
->obj
->base
.size
- sizeof(*ptr
)) {
312 DRM_ERROR("relocation %u outside object\n", i
);
316 ptr
[off
] = bo
->mapping
->iova
+ r
->reloc_offset
;
324 static int submit_perfmon_validate(struct etnaviv_gem_submit
*submit
,
325 u32 exec_state
, const struct drm_etnaviv_gem_submit_pmr
*pmrs
)
329 for (i
= 0; i
< submit
->nr_pmrs
; i
++) {
330 const struct drm_etnaviv_gem_submit_pmr
*r
= pmrs
+ i
;
331 struct etnaviv_gem_submit_bo
*bo
;
334 ret
= submit_bo(submit
, r
->read_idx
, &bo
);
338 /* at offset 0 a sequence number gets stored used for userspace sync */
339 if (r
->read_offset
== 0) {
340 DRM_ERROR("perfmon request: offset is 0");
344 if (r
->read_offset
>= bo
->obj
->base
.size
- sizeof(u32
)) {
345 DRM_ERROR("perfmon request: offset %u outside object", i
);
349 if (r
->flags
& ~(ETNA_PM_PROCESS_PRE
| ETNA_PM_PROCESS_POST
)) {
350 DRM_ERROR("perfmon request: flags are not valid");
354 if (etnaviv_pm_req_validate(r
, exec_state
)) {
355 DRM_ERROR("perfmon request: domain or signal not valid");
359 submit
->pmrs
[i
].flags
= r
->flags
;
360 submit
->pmrs
[i
].domain
= r
->domain
;
361 submit
->pmrs
[i
].signal
= r
->signal
;
362 submit
->pmrs
[i
].sequence
= r
->sequence
;
363 submit
->pmrs
[i
].offset
= r
->read_offset
;
364 submit
->pmrs
[i
].bo_vma
= etnaviv_gem_vmap(&bo
->obj
->base
);
370 static void submit_cleanup(struct kref
*kref
)
372 struct etnaviv_gem_submit
*submit
=
373 container_of(kref
, struct etnaviv_gem_submit
, refcount
);
376 if (submit
->runtime_resumed
)
377 pm_runtime_put_autosuspend(submit
->gpu
->dev
);
379 if (submit
->cmdbuf
.suballoc
)
380 etnaviv_cmdbuf_free(&submit
->cmdbuf
);
382 if (submit
->mmu_context
)
383 etnaviv_iommu_context_put(submit
->mmu_context
);
385 if (submit
->prev_mmu_context
)
386 etnaviv_iommu_context_put(submit
->prev_mmu_context
);
388 for (i
= 0; i
< submit
->nr_bos
; i
++) {
389 struct etnaviv_gem_object
*etnaviv_obj
= submit
->bos
[i
].obj
;
391 /* unpin all objects */
392 if (submit
->bos
[i
].flags
& BO_PINNED
) {
393 etnaviv_gem_mapping_unreference(submit
->bos
[i
].mapping
);
394 atomic_dec(&etnaviv_obj
->gpu_active
);
395 submit
->bos
[i
].mapping
= NULL
;
396 submit
->bos
[i
].flags
&= ~BO_PINNED
;
399 /* if the GPU submit failed, objects might still be locked */
400 submit_unlock_object(submit
, i
);
401 drm_gem_object_put(&etnaviv_obj
->base
);
404 wake_up_all(&submit
->gpu
->fence_event
);
406 if (submit
->in_fence
)
407 dma_fence_put(submit
->in_fence
);
408 if (submit
->out_fence
) {
409 /* first remove from IDR, so fence can not be found anymore */
410 mutex_lock(&submit
->gpu
->fence_lock
);
411 idr_remove(&submit
->gpu
->fence_idr
, submit
->out_fence_id
);
412 mutex_unlock(&submit
->gpu
->fence_lock
);
413 dma_fence_put(submit
->out_fence
);
419 void etnaviv_submit_put(struct etnaviv_gem_submit
*submit
)
421 kref_put(&submit
->refcount
, submit_cleanup
);
424 int etnaviv_ioctl_gem_submit(struct drm_device
*dev
, void *data
,
425 struct drm_file
*file
)
427 struct etnaviv_file_private
*ctx
= file
->driver_priv
;
428 struct etnaviv_drm_private
*priv
= dev
->dev_private
;
429 struct drm_etnaviv_gem_submit
*args
= data
;
430 struct drm_etnaviv_gem_submit_reloc
*relocs
;
431 struct drm_etnaviv_gem_submit_pmr
*pmrs
;
432 struct drm_etnaviv_gem_submit_bo
*bos
;
433 struct etnaviv_gem_submit
*submit
;
434 struct etnaviv_gpu
*gpu
;
435 struct sync_file
*sync_file
= NULL
;
436 struct ww_acquire_ctx ticket
;
437 int out_fence_fd
= -1;
441 if (args
->pipe
>= ETNA_MAX_PIPES
)
444 gpu
= priv
->gpu
[args
->pipe
];
448 if (args
->stream_size
% 4) {
449 DRM_ERROR("non-aligned cmdstream buffer size: %u\n",
454 if (args
->exec_state
!= ETNA_PIPE_3D
&&
455 args
->exec_state
!= ETNA_PIPE_2D
&&
456 args
->exec_state
!= ETNA_PIPE_VG
) {
457 DRM_ERROR("invalid exec_state: 0x%x\n", args
->exec_state
);
461 if (args
->flags
& ~ETNA_SUBMIT_FLAGS
) {
462 DRM_ERROR("invalid flags: 0x%x\n", args
->flags
);
466 if ((args
->flags
& ETNA_SUBMIT_SOFTPIN
) &&
467 priv
->mmu_global
->version
!= ETNAVIV_IOMMU_V2
) {
468 DRM_ERROR("softpin requested on incompatible MMU\n");
473 * Copy the command submission and bo array to kernel space in
474 * one go, and do this outside of any locks.
476 bos
= kvmalloc_array(args
->nr_bos
, sizeof(*bos
), GFP_KERNEL
);
477 relocs
= kvmalloc_array(args
->nr_relocs
, sizeof(*relocs
), GFP_KERNEL
);
478 pmrs
= kvmalloc_array(args
->nr_pmrs
, sizeof(*pmrs
), GFP_KERNEL
);
479 stream
= kvmalloc_array(1, args
->stream_size
, GFP_KERNEL
);
480 if (!bos
|| !relocs
|| !pmrs
|| !stream
) {
482 goto err_submit_cmds
;
485 ret
= copy_from_user(bos
, u64_to_user_ptr(args
->bos
),
486 args
->nr_bos
* sizeof(*bos
));
489 goto err_submit_cmds
;
492 ret
= copy_from_user(relocs
, u64_to_user_ptr(args
->relocs
),
493 args
->nr_relocs
* sizeof(*relocs
));
496 goto err_submit_cmds
;
499 ret
= copy_from_user(pmrs
, u64_to_user_ptr(args
->pmrs
),
500 args
->nr_pmrs
* sizeof(*pmrs
));
503 goto err_submit_cmds
;
506 ret
= copy_from_user(stream
, u64_to_user_ptr(args
->stream
),
510 goto err_submit_cmds
;
513 if (args
->flags
& ETNA_SUBMIT_FENCE_FD_OUT
) {
514 out_fence_fd
= get_unused_fd_flags(O_CLOEXEC
);
515 if (out_fence_fd
< 0) {
517 goto err_submit_cmds
;
521 ww_acquire_init(&ticket
, &reservation_ww_class
);
523 submit
= submit_create(dev
, gpu
, args
->nr_bos
, args
->nr_pmrs
);
526 goto err_submit_ww_acquire
;
529 ret
= etnaviv_cmdbuf_init(priv
->cmdbuf_suballoc
, &submit
->cmdbuf
,
530 ALIGN(args
->stream_size
, 8) + 8);
532 goto err_submit_objects
;
534 submit
->ctx
= file
->driver_priv
;
535 etnaviv_iommu_context_get(submit
->ctx
->mmu
);
536 submit
->mmu_context
= submit
->ctx
->mmu
;
537 submit
->exec_state
= args
->exec_state
;
538 submit
->flags
= args
->flags
;
540 ret
= submit_lookup_objects(submit
, file
, bos
, args
->nr_bos
);
542 goto err_submit_objects
;
544 if ((priv
->mmu_global
->version
!= ETNAVIV_IOMMU_V2
) &&
545 !etnaviv_cmd_validate_one(gpu
, stream
, args
->stream_size
/ 4,
546 relocs
, args
->nr_relocs
)) {
548 goto err_submit_objects
;
551 if (args
->flags
& ETNA_SUBMIT_FENCE_FD_IN
) {
552 submit
->in_fence
= sync_file_get_fence(args
->fence_fd
);
553 if (!submit
->in_fence
) {
555 goto err_submit_objects
;
559 ret
= submit_pin_objects(submit
);
561 goto err_submit_objects
;
563 ret
= submit_reloc(submit
, stream
, args
->stream_size
/ 4,
564 relocs
, args
->nr_relocs
);
566 goto err_submit_objects
;
568 ret
= submit_perfmon_validate(submit
, args
->exec_state
, pmrs
);
570 goto err_submit_objects
;
572 memcpy(submit
->cmdbuf
.vaddr
, stream
, args
->stream_size
);
574 ret
= submit_lock_objects(submit
, &ticket
);
576 goto err_submit_objects
;
578 ret
= submit_fence_sync(submit
);
580 goto err_submit_objects
;
582 ret
= etnaviv_sched_push_job(&ctx
->sched_entity
[args
->pipe
], submit
);
584 goto err_submit_objects
;
586 submit_attach_object_fences(submit
);
588 if (args
->flags
& ETNA_SUBMIT_FENCE_FD_OUT
) {
590 * This can be improved: ideally we want to allocate the sync
591 * file before kicking off the GPU job and just attach the
592 * fence to the sync file here, eliminating the ENOMEM
593 * possibility at this stage.
595 sync_file
= sync_file_create(submit
->out_fence
);
598 goto err_submit_objects
;
600 fd_install(out_fence_fd
, sync_file
->file
);
603 args
->fence_fd
= out_fence_fd
;
604 args
->fence
= submit
->out_fence_id
;
607 etnaviv_submit_put(submit
);
609 err_submit_ww_acquire
:
610 ww_acquire_fini(&ticket
);
613 if (ret
&& (out_fence_fd
>= 0))
614 put_unused_fd(out_fence_fd
);