linux/drivers/gpu/drm/virtio/virtgpu_submit.c
Linus Torvalds bf4afc53b7 Convert 'alloc_obj' family to use the new default GFP_KERNEL argument
This was done entirely with mindless brute force, using

    git grep -l '\<k[vmz]*alloc_objs*(.*, GFP_KERNEL)' |
        xargs sed -i 's/\(alloc_objs*(.*\), GFP_KERNEL)/\1)/'

to convert the new alloc_obj() users that had a simple GFP_KERNEL
argument to just drop that argument.

Note that due to the extreme simplicity of the scripting, any slightly
more complex cases spread over multiple lines would not be triggered:
they definitely exist, but this covers the vast bulk of the cases, and
the resulting diff is also then easier to check automatically.

For the same reason the 'flex' versions will be done as a separate
conversion.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2026-02-21 17:09:51 -08:00

542 lines
12 KiB
C

// 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/drm_syncobj.h>
#include <drm/virtgpu_drm.h>
#include "virtgpu_drv.h"
struct virtio_gpu_submit_post_dep {
struct drm_syncobj *syncobj;
struct dma_fence_chain *chain;
u64 point;
};
struct virtio_gpu_submit {
struct virtio_gpu_submit_post_dep *post_deps;
unsigned int num_out_syncobjs;
struct drm_syncobj **in_syncobjs;
unsigned int num_in_syncobjs;
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)
{
u64 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 void virtio_gpu_free_syncobjs(struct drm_syncobj **syncobjs,
u32 nr_syncobjs)
{
u32 i = nr_syncobjs;
while (i--) {
if (syncobjs[i])
drm_syncobj_put(syncobjs[i]);
}
kvfree(syncobjs);
}
static int
virtio_gpu_parse_deps(struct virtio_gpu_submit *submit)
{
struct drm_virtgpu_execbuffer *exbuf = submit->exbuf;
struct drm_virtgpu_execbuffer_syncobj syncobj_desc;
size_t syncobj_stride = exbuf->syncobj_stride;
u32 num_in_syncobjs = exbuf->num_in_syncobjs;
struct drm_syncobj **syncobjs;
int ret = 0, i;
if (!num_in_syncobjs)
return 0;
/*
* kvmalloc() at first tries to allocate memory using kmalloc() and
* falls back to vmalloc() only on failure. It also uses __GFP_NOWARN
* internally for allocations larger than a page size, preventing
* storm of KMSG warnings.
*/
syncobjs = kvzalloc_objs(*syncobjs, num_in_syncobjs);
if (!syncobjs)
return -ENOMEM;
for (i = 0; i < num_in_syncobjs; i++) {
u64 address = exbuf->in_syncobjs + i * syncobj_stride;
struct dma_fence *fence;
memset(&syncobj_desc, 0, sizeof(syncobj_desc));
if (copy_from_user(&syncobj_desc,
u64_to_user_ptr(address),
min(syncobj_stride, sizeof(syncobj_desc)))) {
ret = -EFAULT;
break;
}
if (syncobj_desc.flags & ~VIRTGPU_EXECBUF_SYNCOBJ_FLAGS) {
ret = -EINVAL;
break;
}
ret = drm_syncobj_find_fence(submit->file, syncobj_desc.handle,
syncobj_desc.point, 0, &fence);
if (ret)
break;
ret = virtio_gpu_dma_fence_wait(submit, fence);
dma_fence_put(fence);
if (ret)
break;
if (syncobj_desc.flags & VIRTGPU_EXECBUF_SYNCOBJ_RESET) {
syncobjs[i] = drm_syncobj_find(submit->file,
syncobj_desc.handle);
if (!syncobjs[i]) {
ret = -EINVAL;
break;
}
}
}
if (ret) {
virtio_gpu_free_syncobjs(syncobjs, i);
return ret;
}
submit->num_in_syncobjs = num_in_syncobjs;
submit->in_syncobjs = syncobjs;
return ret;
}
static void virtio_gpu_reset_syncobjs(struct drm_syncobj **syncobjs,
u32 nr_syncobjs)
{
u32 i;
for (i = 0; i < nr_syncobjs; i++) {
if (syncobjs[i])
drm_syncobj_replace_fence(syncobjs[i], NULL);
}
}
static void
virtio_gpu_free_post_deps(struct virtio_gpu_submit_post_dep *post_deps,
u32 nr_syncobjs)
{
u32 i = nr_syncobjs;
while (i--) {
kfree(post_deps[i].chain);
drm_syncobj_put(post_deps[i].syncobj);
}
kvfree(post_deps);
}
static int virtio_gpu_parse_post_deps(struct virtio_gpu_submit *submit)
{
struct drm_virtgpu_execbuffer *exbuf = submit->exbuf;
struct drm_virtgpu_execbuffer_syncobj syncobj_desc;
struct virtio_gpu_submit_post_dep *post_deps;
u32 num_out_syncobjs = exbuf->num_out_syncobjs;
size_t syncobj_stride = exbuf->syncobj_stride;
int ret = 0, i;
if (!num_out_syncobjs)
return 0;
post_deps = kvzalloc_objs(*post_deps, num_out_syncobjs);
if (!post_deps)
return -ENOMEM;
for (i = 0; i < num_out_syncobjs; i++) {
u64 address = exbuf->out_syncobjs + i * syncobj_stride;
memset(&syncobj_desc, 0, sizeof(syncobj_desc));
if (copy_from_user(&syncobj_desc,
u64_to_user_ptr(address),
min(syncobj_stride, sizeof(syncobj_desc)))) {
ret = -EFAULT;
break;
}
post_deps[i].point = syncobj_desc.point;
if (syncobj_desc.flags) {
ret = -EINVAL;
break;
}
if (syncobj_desc.point) {
post_deps[i].chain = dma_fence_chain_alloc();
if (!post_deps[i].chain) {
ret = -ENOMEM;
break;
}
}
post_deps[i].syncobj = drm_syncobj_find(submit->file,
syncobj_desc.handle);
if (!post_deps[i].syncobj) {
kfree(post_deps[i].chain);
ret = -EINVAL;
break;
}
}
if (ret) {
virtio_gpu_free_post_deps(post_deps, i);
return ret;
}
submit->num_out_syncobjs = num_out_syncobjs;
submit->post_deps = post_deps;
return 0;
}
static void
virtio_gpu_process_post_deps(struct virtio_gpu_submit *submit)
{
struct virtio_gpu_submit_post_dep *post_deps = submit->post_deps;
if (post_deps) {
struct dma_fence *fence = &submit->out_fence->f;
u32 i;
for (i = 0; i < submit->num_out_syncobjs; i++) {
if (post_deps[i].chain) {
drm_syncobj_add_point(post_deps[i].syncobj,
post_deps[i].chain,
fence, post_deps[i].point);
post_deps[i].chain = NULL;
} else {
drm_syncobj_replace_fence(post_deps[i].syncobj,
fence);
}
}
}
}
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_fence_event *e = NULL;
int ret;
e = kzalloc_obj(*e);
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)
{
virtio_gpu_reset_syncobjs(submit->in_syncobjs, submit->num_in_syncobjs);
virtio_gpu_free_syncobjs(submit->in_syncobjs, submit->num_in_syncobjs);
virtio_gpu_free_post_deps(submit->post_deps, submit->num_out_syncobjs);
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_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;
bool drm_fence_event;
int err;
memset(submit, 0, sizeof(*submit));
if ((exbuf->flags & VIRTGPU_EXECBUF_RING_IDX) &&
(vfpriv->ring_idx_mask & BIT_ULL(ring_idx)))
drm_fence_event = true;
else
drm_fence_event = false;
if ((exbuf->flags & VIRTGPU_EXECBUF_FENCE_FD_OUT) ||
exbuf->num_out_syncobjs ||
exbuf->num_bo_handles ||
drm_fence_event)
out_fence = virtio_gpu_fence_alloc(vgdev, fence_ctx, ring_idx);
else
out_fence = NULL;
if (drm_fence_event) {
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;
ret = virtio_gpu_parse_post_deps(&submit);
if (ret)
goto cleanup;
ret = virtio_gpu_parse_deps(&submit);
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 user-out data after submitting the job to optimize
* the job submission path.
*/
virtio_gpu_install_out_fence_fd(&submit);
virtio_gpu_process_post_deps(&submit);
virtio_gpu_complete_submit(&submit);
cleanup:
virtio_gpu_cleanup_submit(&submit);
return ret;
}