// 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;}
// 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;
}