| // SPDX-License-Identifier: MIT | |
| /* | |
| * Copyright (C) 2015 Red Hat, Inc. | |
| * All Rights Reserved. | |
| * | |
| * Authors: | |
| * Dave Airlie | |
| * Alon Levy | |
| */ | |
| #include <linux/dma-fence-unwrap.h> | |
| #include <linux/file.h> | |
| #include <linux/sync_file.h> | |
| #include <linux/uaccess.h> | |
| #include <drm/drm_file.h> | |
| #include <drm/virtgpu_drm.h> | |
| #include "virtgpu_drv.h" | |
| struct virtio_gpu_submit { | |
| struct virtio_gpu_object_array *buflist; | |
| struct drm_virtgpu_execbuffer *exbuf; | |
| struct virtio_gpu_fence *out_fence; | |
| struct virtio_gpu_fpriv *vfpriv; | |
| struct virtio_gpu_device *vgdev; | |
| struct sync_file *sync_file; | |
| struct drm_file *file; | |
| int out_fence_fd; | |
| u64 fence_ctx; | |
| u32 ring_idx; | |
| void *buf; | |
| }; | |
| static int virtio_gpu_do_fence_wait(struct virtio_gpu_submit *submit, | |
| struct dma_fence *in_fence) | |
| { | |
| u32 context = submit->fence_ctx + submit->ring_idx; | |
| if (dma_fence_match_context(in_fence, context)) | |
| return 0; | |
| return dma_fence_wait(in_fence, true); | |
| } | |
| static int virtio_gpu_dma_fence_wait(struct virtio_gpu_submit *submit, | |
| struct dma_fence *fence) | |
| { | |
| struct dma_fence_unwrap itr; | |
| struct dma_fence *f; | |
| int err; | |
| dma_fence_unwrap_for_each(f, &itr, fence) { | |
| err = virtio_gpu_do_fence_wait(submit, f); | |
| if (err) | |
| return err; | |
| } | |
| return 0; | |
| } | |
| static int virtio_gpu_fence_event_create(struct drm_device *dev, | |
| struct drm_file *file, | |
| struct virtio_gpu_fence *fence, | |
| u32 ring_idx) | |
| { | |
| struct virtio_gpu_fpriv *vfpriv = file->driver_priv; | |
| struct virtio_gpu_fence_event *e = NULL; | |
| int ret; | |
| if (!(vfpriv->ring_idx_mask & BIT_ULL(ring_idx))) | |
| return 0; | |
| e = kzalloc(sizeof(*e), GFP_KERNEL); | |
| if (!e) | |
| return -ENOMEM; | |
| e->event.type = VIRTGPU_EVENT_FENCE_SIGNALED; | |
| e->event.length = sizeof(e->event); | |
| ret = drm_event_reserve_init(dev, file, &e->base, &e->event); | |
| if (ret) { | |
| kfree(e); | |
| return ret; | |
| } | |
| fence->e = e; | |
| return 0; | |
| } | |
| static int virtio_gpu_init_submit_buflist(struct virtio_gpu_submit *submit) | |
| { | |
| struct drm_virtgpu_execbuffer *exbuf = submit->exbuf; | |
| u32 *bo_handles; | |
| if (!exbuf->num_bo_handles) | |
| return 0; | |
| bo_handles = kvmalloc_array(exbuf->num_bo_handles, sizeof(*bo_handles), | |
| GFP_KERNEL); | |
| if (!bo_handles) | |
| return -ENOMEM; | |
| if (copy_from_user(bo_handles, u64_to_user_ptr(exbuf->bo_handles), | |
| exbuf->num_bo_handles * sizeof(*bo_handles))) { | |
| kvfree(bo_handles); | |
| return -EFAULT; | |
| } | |
| submit->buflist = virtio_gpu_array_from_handles(submit->file, bo_handles, | |
| exbuf->num_bo_handles); | |
| if (!submit->buflist) { | |
| kvfree(bo_handles); | |
| return -ENOENT; | |
| } | |
| kvfree(bo_handles); | |
| return 0; | |
| } | |
| static void virtio_gpu_cleanup_submit(struct virtio_gpu_submit *submit) | |
| { | |
| if (!IS_ERR(submit->buf)) | |
| kvfree(submit->buf); | |
| if (submit->buflist) | |
| virtio_gpu_array_put_free(submit->buflist); | |
| if (submit->out_fence_fd >= 0) | |
| put_unused_fd(submit->out_fence_fd); | |
| if (submit->out_fence) | |
| dma_fence_put(&submit->out_fence->f); | |
| if (submit->sync_file) | |
| fput(submit->sync_file->file); | |
| } | |
| static void virtio_gpu_submit(struct virtio_gpu_submit *submit) | |
| { | |
| virtio_gpu_cmd_submit(submit->vgdev, submit->buf, submit->exbuf->size, | |
| submit->vfpriv->ctx_id, submit->buflist, | |
| submit->out_fence); | |
| virtio_gpu_notify(submit->vgdev); | |
| } | |
| static void virtio_gpu_complete_submit(struct virtio_gpu_submit *submit) | |
| { | |
| submit->buf = NULL; | |
| submit->buflist = NULL; | |
| submit->sync_file = NULL; | |
| submit->out_fence = NULL; | |
| submit->out_fence_fd = -1; | |
| } | |
| static int virtio_gpu_init_submit(struct virtio_gpu_submit *submit, | |
| struct drm_virtgpu_execbuffer *exbuf, | |
| struct drm_device *dev, | |
| struct drm_file *file, | |
| u64 fence_ctx, u32 ring_idx) | |
| { | |
| struct virtio_gpu_fpriv *vfpriv = file->driver_priv; | |
| struct virtio_gpu_device *vgdev = dev->dev_private; | |
| struct virtio_gpu_fence *out_fence; | |
| int err; | |
| memset(submit, 0, sizeof(*submit)); | |
| out_fence = virtio_gpu_fence_alloc(vgdev, fence_ctx, ring_idx); | |
| if (!out_fence) | |
| return -ENOMEM; | |
| err = virtio_gpu_fence_event_create(dev, file, out_fence, ring_idx); | |
| if (err) { | |
| dma_fence_put(&out_fence->f); | |
| return err; | |
| } | |
| submit->out_fence = out_fence; | |
| submit->fence_ctx = fence_ctx; | |
| submit->ring_idx = ring_idx; | |
| submit->out_fence_fd = -1; | |
| submit->vfpriv = vfpriv; | |
| submit->vgdev = vgdev; | |
| submit->exbuf = exbuf; | |
| submit->file = file; | |
| err = virtio_gpu_init_submit_buflist(submit); | |
| if (err) | |
| return err; | |
| submit->buf = vmemdup_user(u64_to_user_ptr(exbuf->command), exbuf->size); | |
| if (IS_ERR(submit->buf)) | |
| return PTR_ERR(submit->buf); | |
| if (exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_OUT) { | |
| err = get_unused_fd_flags(O_CLOEXEC); | |
| if (err < 0) | |
| return err; | |
| submit->out_fence_fd = err; | |
| submit->sync_file = sync_file_create(&out_fence->f); | |
| if (!submit->sync_file) | |
| return -ENOMEM; | |
| } | |
| return 0; | |
| } | |
| static int virtio_gpu_wait_in_fence(struct virtio_gpu_submit *submit) | |
| { | |
| int ret = 0; | |
| if (submit->exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_IN) { | |
| struct dma_fence *in_fence = | |
| sync_file_get_fence(submit->exbuf->fence_fd); | |
| if (!in_fence) | |
| return -EINVAL; | |
| /* | |
| * Wait if the fence is from a foreign context, or if the | |
| * fence array contains any fence from a foreign context. | |
| */ | |
| ret = virtio_gpu_dma_fence_wait(submit, in_fence); | |
| dma_fence_put(in_fence); | |
| } | |
| return ret; | |
| } | |
| static void virtio_gpu_install_out_fence_fd(struct virtio_gpu_submit *submit) | |
| { | |
| if (submit->sync_file) { | |
| submit->exbuf->fence_fd = submit->out_fence_fd; | |
| fd_install(submit->out_fence_fd, submit->sync_file->file); | |
| } | |
| } | |
| static int virtio_gpu_lock_buflist(struct virtio_gpu_submit *submit) | |
| { | |
| if (submit->buflist) | |
| return virtio_gpu_array_lock_resv(submit->buflist); | |
| return 0; | |
| } | |
| int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data, | |
| struct drm_file *file) | |
| { | |
| struct virtio_gpu_device *vgdev = dev->dev_private; | |
| struct virtio_gpu_fpriv *vfpriv = file->driver_priv; | |
| u64 fence_ctx = vgdev->fence_drv.context; | |
| struct drm_virtgpu_execbuffer *exbuf = data; | |
| struct virtio_gpu_submit submit; | |
| u32 ring_idx = 0; | |
| int ret = -EINVAL; | |
| if (!vgdev->has_virgl_3d) | |
| return -ENOSYS; | |
| if (exbuf->flags & ~VIRTGPU_EXECBUF_FLAGS) | |
| return ret; | |
| if (exbuf->flags & VIRTGPU_EXECBUF_RING_IDX) { | |
| if (exbuf->ring_idx >= vfpriv->num_rings) | |
| return ret; | |
| if (!vfpriv->base_fence_ctx) | |
| return ret; | |
| fence_ctx = vfpriv->base_fence_ctx; | |
| ring_idx = exbuf->ring_idx; | |
| } | |
| virtio_gpu_create_context(dev, file); | |
| ret = virtio_gpu_init_submit(&submit, exbuf, dev, file, | |
| fence_ctx, ring_idx); | |
| if (ret) | |
| goto cleanup; | |
| /* | |
| * Await in-fences in the end of the job submission path to | |
| * optimize the path by proceeding directly to the submission | |
| * to virtio after the waits. | |
| */ | |
| ret = virtio_gpu_wait_in_fence(&submit); | |
| if (ret) | |
| goto cleanup; | |
| ret = virtio_gpu_lock_buflist(&submit); | |
| if (ret) | |
| goto cleanup; | |
| virtio_gpu_submit(&submit); | |
| /* | |
| * Set up usr-out data after submitting the job to optimize | |
| * the job submission path. | |
| */ | |
| virtio_gpu_install_out_fence_fd(&submit); | |
| virtio_gpu_complete_submit(&submit); | |
| cleanup: | |
| virtio_gpu_cleanup_submit(&submit); | |
| return ret; | |
| } |