544 lines · 12.0 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/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) {			dma_fence_put(itr.chain);			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 intvirtio_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 voidvirtio_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 voidvirtio_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;

Lines 1–500 of 544.