311 lines · 6.9 KB · C
RawDownload
// 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;}