388 lines · 9.7 KB · C
RawDownload
/* * Copyright 2014 Canonical * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * Authors: Andreas Pokorny */ #include <drm/drm_prime.h>#include <drm/drm_print.h>#include <linux/virtio_dma_buf.h> #include "virtgpu_drv.h" MODULE_IMPORT_NS("DMA_BUF"); static int virtgpu_virtio_get_uuid(struct dma_buf *buf,				   uuid_t *uuid){	struct drm_gem_object *obj = buf->priv;	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);	struct virtio_gpu_device *vgdev = obj->dev->dev_private; 	wait_event(vgdev->resp_wq, bo->uuid_state != STATE_INITIALIZING);	if (bo->uuid_state != STATE_OK)		return -ENODEV; 	uuid_copy(uuid, &bo->uuid); 	return 0;} static struct sg_table *virtgpu_gem_map_dma_buf(struct dma_buf_attachment *attach,			enum dma_data_direction dir){	struct drm_gem_object *obj = attach->dmabuf->priv;	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj); 	if (virtio_gpu_is_vram(bo))		return virtio_gpu_vram_map_dma_buf(bo, attach->dev, dir); 	return drm_gem_map_dma_buf(attach, dir);} static void virtgpu_gem_unmap_dma_buf(struct dma_buf_attachment *attach,				      struct sg_table *sgt,				      enum dma_data_direction dir){	struct drm_gem_object *obj = attach->dmabuf->priv;	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj); 	if (virtio_gpu_is_vram(bo)) {		virtio_gpu_vram_unmap_dma_buf(attach->dev, sgt, dir);		return;	} 	drm_gem_unmap_dma_buf(attach, sgt, dir);} static const struct virtio_dma_buf_ops virtgpu_dmabuf_ops =  {	.ops = {		.attach = virtio_dma_buf_attach,		.detach = drm_gem_map_detach,		.map_dma_buf = virtgpu_gem_map_dma_buf,		.unmap_dma_buf = virtgpu_gem_unmap_dma_buf,		.release = drm_gem_dmabuf_release,		.mmap = drm_gem_dmabuf_mmap,		.vmap = drm_gem_dmabuf_vmap,		.vunmap = drm_gem_dmabuf_vunmap,	},	.device_attach = drm_gem_map_attach,	.get_uuid = virtgpu_virtio_get_uuid,}; int virtio_gpu_resource_assign_uuid(struct virtio_gpu_device *vgdev,				    struct virtio_gpu_object *bo){	struct virtio_gpu_object_array *objs; 	objs = virtio_gpu_array_alloc(1);	if (!objs)		return -ENOMEM; 	virtio_gpu_array_add_obj(objs, &bo->base.base); 	return virtio_gpu_cmd_resource_assign_uuid(vgdev, objs);} struct dma_buf *virtgpu_gem_prime_export(struct drm_gem_object *obj,					 int flags){	struct dma_buf *buf;	struct drm_device *dev = obj->dev;	struct virtio_gpu_device *vgdev = dev->dev_private;	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);	int ret = 0;	bool blob = bo->host3d_blob || bo->guest_blob;	DEFINE_DMA_BUF_EXPORT_INFO(exp_info); 	if (!blob) {		if (vgdev->has_resource_assign_uuid) {			ret = virtio_gpu_resource_assign_uuid(vgdev, bo);			if (ret)				return ERR_PTR(ret); 			virtio_gpu_notify(vgdev);		} else {			bo->uuid_state = STATE_ERR;		}	} else if (!(bo->blob_flags & VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE)) {		bo->uuid_state = STATE_ERR;	} 	exp_info.ops = &virtgpu_dmabuf_ops.ops;	exp_info.size = obj->size;	exp_info.flags = flags;	exp_info.priv = obj;	exp_info.resv = obj->resv; 	buf = virtio_dma_buf_export(&exp_info);	if (IS_ERR(buf))		return buf; 	drm_dev_get(dev);	drm_gem_object_get(obj); 	return buf;} int virtgpu_dma_buf_import_sgt(struct virtio_gpu_mem_entry **ents,			       unsigned int *nents,			       struct virtio_gpu_object *bo,			       struct dma_buf_attachment *attach){	struct scatterlist *sl;	struct sg_table *sgt;	long i, ret; 	dma_resv_assert_held(attach->dmabuf->resv); 	ret = dma_resv_wait_timeout(attach->dmabuf->resv,				    DMA_RESV_USAGE_KERNEL,				    false, MAX_SCHEDULE_TIMEOUT);	if (ret <= 0)		return ret < 0 ? ret : -ETIMEDOUT; 	sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);	if (IS_ERR(sgt))		return PTR_ERR(sgt); 	*ents = kvmalloc_objs(struct virtio_gpu_mem_entry, sgt->nents);	if (!(*ents)) {		dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL);		return -ENOMEM;	} 	*nents = sgt->nents;	for_each_sgtable_dma_sg(sgt, sl, i) {		(*ents)[i].addr = cpu_to_le64(sg_dma_address(sl));		(*ents)[i].length = cpu_to_le32(sg_dma_len(sl));		(*ents)[i].padding = 0;	} 	bo->sgt = sgt;	return 0;} static void virtgpu_dma_buf_unmap(struct virtio_gpu_object *bo){	struct dma_buf_attachment *attach = bo->base.base.import_attach; 	dma_resv_assert_held(attach->dmabuf->resv); 	if (bo->created) {		virtio_gpu_detach_object_fenced(bo); 		if (bo->sgt)			dma_buf_unmap_attachment(attach, bo->sgt,						 DMA_BIDIRECTIONAL); 		bo->sgt = NULL;	}} static void virtgpu_dma_buf_free_obj(struct drm_gem_object *obj){	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj);	struct virtio_gpu_device *vgdev = obj->dev->dev_private;	struct dma_buf_attachment *attach = obj->import_attach; 	if (drm_gem_is_imported(obj)) {		struct dma_buf *dmabuf = attach->dmabuf; 		dma_resv_lock(dmabuf->resv, NULL);		virtgpu_dma_buf_unmap(bo);		dma_resv_unlock(dmabuf->resv); 		dma_buf_detach(dmabuf, attach);		dma_buf_put(dmabuf);	} 	if (bo->created) {		virtio_gpu_remove_from_restore_list(bo);		virtio_gpu_cmd_unref_resource(vgdev, bo, false);		virtio_gpu_notify(vgdev);		return;	}	virtio_gpu_cleanup_object(bo);} static int virtgpu_dma_buf_init_obj(struct drm_device *dev,				    struct virtio_gpu_object *bo,				    struct dma_buf_attachment *attach){	struct virtio_gpu_device *vgdev = dev->dev_private;	struct virtio_gpu_object_params params = { 0 };	struct dma_resv *resv = attach->dmabuf->resv;	struct virtio_gpu_mem_entry *ents = NULL;	unsigned int nents;	int ret; 	ret = virtio_gpu_resource_id_get(vgdev, &bo->hw_res_handle);	if (ret) {		virtgpu_dma_buf_free_obj(&bo->base.base);		return ret;	} 	dma_resv_lock(resv, NULL); 	ret = dma_buf_pin(attach);	if (ret)		goto err_pin; 	ret = virtgpu_dma_buf_import_sgt(&ents, &nents, bo, attach);	if (ret)		goto err_import; 	params.blob = true;	params.blob_mem = VIRTGPU_BLOB_MEM_GUEST;	params.blob_flags = VIRTGPU_BLOB_FLAG_USE_SHAREABLE;	params.size = attach->dmabuf->size; 	virtio_gpu_cmd_resource_create_blob(vgdev, bo, &params,					    ents, nents);	bo->guest_blob = true; 	dma_buf_unpin(attach);	dma_resv_unlock(resv); 	/*	 * Store the dmabuf imported object with its params to the	 * restore list.	 */	bo->params = params;	virtio_gpu_add_object_to_restore_list(vgdev, bo); 	return 0; err_import:	dma_buf_unpin(attach);err_pin:	dma_resv_unlock(resv);	virtgpu_dma_buf_free_obj(&bo->base.base);	return ret;} int virtgpu_dma_buf_obj_resubmit(struct virtio_gpu_device *vgdev,				 struct virtio_gpu_object *bo){	struct virtio_gpu_mem_entry *ents;	struct scatterlist *sl;	int i; 	if (!bo->sgt) {		DRM_ERROR("no sgt bound to virtio_gpu_object\n");		return -ENOMEM;	} 	ents = kvmalloc_objs(struct virtio_gpu_mem_entry, bo->sgt->nents);	if (!ents) {		DRM_ERROR("failed to allocate ent list\n");		return -ENOMEM;	} 	for_each_sgtable_dma_sg(bo->sgt, sl, i) {		ents[i].addr = cpu_to_le64(sg_dma_address(sl));		ents[i].length = cpu_to_le32(sg_dma_len(sl));		ents[i].padding = 0;	} 	virtio_gpu_cmd_resource_create_blob(vgdev, bo, &bo->params,					    ents, bo->sgt->nents); 	return 0;} static const struct drm_gem_object_funcs virtgpu_gem_dma_buf_funcs = {	.free = virtgpu_dma_buf_free_obj,}; static void virtgpu_dma_buf_move_notify(struct dma_buf_attachment *attach){	struct drm_gem_object *obj = attach->importer_priv;	struct virtio_gpu_object *bo = gem_to_virtio_gpu_obj(obj); 	virtgpu_dma_buf_unmap(bo);} static const struct dma_buf_attach_ops virtgpu_dma_buf_attach_ops = {	.allow_peer2peer = true,	.invalidate_mappings = virtgpu_dma_buf_move_notify}; struct drm_gem_object *virtgpu_gem_prime_import(struct drm_device *dev,						struct dma_buf *buf){	struct virtio_gpu_device *vgdev = dev->dev_private;	struct dma_buf_attachment *attach;	struct virtio_gpu_object *bo;	struct drm_gem_object *obj;	int ret; 	if (buf->ops == &virtgpu_dmabuf_ops.ops) {		obj = buf->priv;		if (obj->dev == dev) {			/*			 * Importing dmabuf exported from our own gem increases			 * refcount on gem itself instead of f_count of dmabuf.			 */			drm_gem_object_get(obj);			return obj;		}	} 	if (!vgdev->has_resource_blob)		return drm_gem_prime_import(dev, buf); 	bo = kzalloc_obj(*bo);	if (!bo)		return ERR_PTR(-ENOMEM); 	INIT_LIST_HEAD(&bo->restore_node); 	obj = &bo->base.base;	obj->resv = buf->resv;	obj->funcs = &virtgpu_gem_dma_buf_funcs;	drm_gem_private_object_init(dev, obj, buf->size); 	attach = dma_buf_dynamic_attach(buf, dev->dev,					&virtgpu_dma_buf_attach_ops, obj);	if (IS_ERR(attach)) {		kfree(bo);		return ERR_CAST(attach);	} 	obj->import_attach = attach;	get_dma_buf(buf); 	ret = virtgpu_dma_buf_init_obj(dev, bo, attach);	if (ret < 0)		return ERR_PTR(ret); 	return obj;} struct drm_gem_object *virtgpu_gem_prime_import_sg_table(	struct drm_device *dev, struct dma_buf_attachment *attach,	struct sg_table *table){	return ERR_PTR(-ENODEV);}