[PATCH 2/2 v3] drm/exynos: added userptr feature.

Inki Dae inki.dae at samsung.com
Tue May 8 23:17:39 PDT 2012


this feature is used to import user space region allocated by malloc() or
mmaped into a gem. and to guarantee the pages to user space not to be
swapped out, the VMAs within the user space would be locked and then unlocked
when the pages are released.

but this lock might result in significant degradation of system performance
because the pages couldn't be swapped out so we limit user-desired userptr
size to pre-defined.

Signed-off-by: Inki Dae <inki.dae at samsung.com>
Signed-off-by: Kyungmin Park <kyungmin.park at samsung.com>
---
 drivers/gpu/drm/exynos/exynos_drm_drv.c |    2 +
 drivers/gpu/drm/exynos/exynos_drm_gem.c |  334 +++++++++++++++++++++++++++++++
 drivers/gpu/drm/exynos/exynos_drm_gem.h |   17 ++-
 include/drm/exynos_drm.h                |   26 +++-
 4 files changed, 376 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/exynos/exynos_drm_drv.c b/drivers/gpu/drm/exynos/exynos_drm_drv.c
index 1e68ec2..e8ae3f1 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_drv.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_drv.c
@@ -220,6 +220,8 @@ static struct drm_ioctl_desc exynos_ioctls[] = {
 	DRM_IOCTL_DEF_DRV(EXYNOS_GEM_MAP_OFFSET,
 			exynos_drm_gem_map_offset_ioctl, DRM_UNLOCKED |
 			DRM_AUTH),
+	DRM_IOCTL_DEF_DRV(EXYNOS_GEM_USERPTR,
+			exynos_drm_gem_userptr_ioctl, DRM_UNLOCKED),
 	DRM_IOCTL_DEF_DRV(EXYNOS_GEM_MMAP,
 			exynos_drm_gem_mmap_ioctl, DRM_UNLOCKED | DRM_AUTH),
 	DRM_IOCTL_DEF_DRV(EXYNOS_GEM_GET,
diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.c b/drivers/gpu/drm/exynos/exynos_drm_gem.c
index e6abb66..ccc6e3d 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_gem.c
+++ b/drivers/gpu/drm/exynos/exynos_drm_gem.c
@@ -68,6 +68,83 @@ static int check_gem_flags(unsigned int flags)
 	return 0;
 }
 
+static struct vm_area_struct *get_vma(struct vm_area_struct *vma)
+{
+	struct vm_area_struct *vma_copy;
+
+	vma_copy = kmalloc(sizeof(*vma_copy), GFP_KERNEL);
+	if (!vma_copy)
+		return NULL;
+
+	if (vma->vm_ops && vma->vm_ops->open)
+		vma->vm_ops->open(vma);
+
+	if (vma->vm_file)
+		get_file(vma->vm_file);
+
+	memcpy(vma_copy, vma, sizeof(*vma));
+
+	vma_copy->vm_mm = NULL;
+	vma_copy->vm_next = NULL;
+	vma_copy->vm_prev = NULL;
+
+	return vma_copy;
+}
+
+
+static void put_vma(struct vm_area_struct *vma)
+{
+	if (!vma)
+		return;
+
+	if (vma->vm_ops && vma->vm_ops->close)
+		vma->vm_ops->close(vma);
+
+	if (vma->vm_file)
+		fput(vma->vm_file);
+
+	kfree(vma);
+}
+
+/*
+ * lock_userptr_vma - lock VMAs within user address space
+ *
+ * this function locks vma within user address space to avoid pages
+ * to the userspace from being swapped out.
+ * if this vma isn't locked, the pages to the userspace could be swapped out
+ * so unprivileged user might access different pages and dma of any device
+ * could access physical memory region not intended once swap-in.
+ */
+static int lock_userptr_vma(struct exynos_drm_gem_buf *buf, unsigned int lock)
+{
+	struct vm_area_struct *vma;
+	unsigned long start, end;
+
+	start = buf->userptr;
+	end = buf->userptr + buf->size - 1;
+
+	down_write(&current->mm->mmap_sem);
+
+	do {
+		vma = find_vma(current->mm, start);
+		if (!vma) {
+			up_write(&current->mm->mmap_sem);
+			return -EFAULT;
+		}
+
+		if (lock)
+			vma->vm_flags |= VM_LOCKED;
+		else
+			vma->vm_flags &= ~VM_LOCKED;
+
+		start = vma->vm_end + 1;
+	} while (vma->vm_end < end);
+
+	up_write(&current->mm->mmap_sem);
+
+	return 0;
+}
+
 static void update_vm_cache_attr(struct exynos_drm_gem_obj *obj,
 					struct vm_area_struct *vma)
 {
@@ -256,6 +333,44 @@ static void exynos_drm_gem_put_pages(struct drm_gem_object *obj)
 	/* add some codes for UNCACHED type here. TODO */
 }
 
+static void exynos_drm_put_userptr(struct drm_gem_object *obj)
+{
+	struct exynos_drm_gem_obj *exynos_gem_obj;
+	struct exynos_drm_gem_buf *buf;
+	struct vm_area_struct *vma;
+	int npages;
+
+	exynos_gem_obj = to_exynos_gem_obj(obj);
+	buf = exynos_gem_obj->buffer;
+	vma = exynos_gem_obj->vma;
+
+	if (vma && (vma->vm_flags & VM_PFNMAP) && (vma->vm_pgoff)) {
+		put_vma(exynos_gem_obj->vma);
+		goto out;
+	}
+
+	npages = buf->size >> PAGE_SHIFT;
+
+	if (exynos_gem_obj->flags & EXYNOS_BO_USERPTR && !buf->pfnmap)
+		lock_userptr_vma(buf, 0);
+
+	npages--;
+	while (npages >= 0) {
+		if (buf->write)
+			set_page_dirty_lock(buf->pages[npages]);
+
+		put_page(buf->pages[npages]);
+		npages--;
+	}
+
+out:
+	kfree(buf->pages);
+	buf->pages = NULL;
+
+	kfree(buf->sgt);
+	buf->sgt = NULL;
+}
+
 static int exynos_drm_gem_handle_create(struct drm_gem_object *obj,
 					struct drm_file *file_priv,
 					unsigned int *handle)
@@ -295,6 +410,8 @@ void exynos_drm_gem_destroy(struct exynos_drm_gem_obj *exynos_gem_obj)
 
 	if (exynos_gem_obj->flags & EXYNOS_BO_NONCONTIG)
 		exynos_drm_gem_put_pages(obj);
+	else if (exynos_gem_obj->flags & EXYNOS_BO_USERPTR)
+		exynos_drm_put_userptr(obj);
 	else
 		exynos_drm_free_buf(obj->dev, exynos_gem_obj->flags, buf);
 
@@ -606,6 +723,223 @@ int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data,
 	return 0;
 }
 
+static int exynos_drm_get_userptr(struct drm_device *dev,
+				struct exynos_drm_gem_obj *obj,
+				unsigned long userptr,
+				unsigned int write)
+{
+	unsigned int get_npages;
+	unsigned long npages = 0;
+	struct vm_area_struct *vma;
+	struct exynos_drm_gem_buf *buf = obj->buffer;
+	int ret;
+
+	down_read(&current->mm->mmap_sem);
+	vma = find_vma(current->mm, userptr);
+
+	/* the memory region mmaped with VM_PFNMAP. */
+	if (vma && (vma->vm_flags & VM_PFNMAP) && (vma->vm_pgoff)) {
+		unsigned long this_pfn, prev_pfn, pa;
+		unsigned long start, end, offset;
+		struct scatterlist *sgl;
+		int ret;
+
+		start = userptr;
+		offset = userptr & ~PAGE_MASK;
+		end = start + buf->size;
+		sgl = buf->sgt->sgl;
+
+		for (prev_pfn = 0; start < end; start += PAGE_SIZE) {
+			ret = follow_pfn(vma, start, &this_pfn);
+			if (ret)
+				goto err;
+
+			if (prev_pfn == 0) {
+				pa = this_pfn << PAGE_SHIFT;
+				buf->dma_addr = pa + offset;
+			} else if (this_pfn != prev_pfn + 1) {
+				ret = -EINVAL;
+				goto err;
+			}
+
+			sg_dma_address(sgl) = (pa + offset);
+			sg_dma_len(sgl) = PAGE_SIZE;
+			prev_pfn = this_pfn;
+			pa += PAGE_SIZE;
+			npages++;
+			sgl = sg_next(sgl);
+		}
+
+		obj->vma = get_vma(vma);
+		if (!obj->vma) {
+			ret = -ENOMEM;
+			goto err;
+		}
+
+		up_read(&current->mm->mmap_sem);
+		buf->pfnmap = true;
+
+		return npages;
+err:
+		buf->dma_addr = 0;
+		up_read(&current->mm->mmap_sem);
+
+		return ret;
+	}
+
+	up_read(&current->mm->mmap_sem);
+
+	/*
+	 * lock the vma within userptr to avoid userspace buffer
+	 * from being swapped out.
+	 */
+	ret = lock_userptr_vma(buf, 1);
+	if (ret < 0) {
+		DRM_ERROR("failed to lock vma for userptr.\n");
+		lock_userptr_vma(buf, 0);
+		return 0;
+	}
+
+	buf->write = write;
+	npages = buf->size >> PAGE_SHIFT;
+
+	down_read(&current->mm->mmap_sem);
+	get_npages = get_user_pages(current, current->mm, userptr,
+					npages, write, 1, buf->pages, NULL);
+	up_read(&current->mm->mmap_sem);
+	if (get_npages != npages)
+		DRM_ERROR("failed to get user_pages.\n");
+
+	buf->userptr = userptr;
+	buf->pfnmap = false;
+
+	return get_npages;
+}
+
+int exynos_drm_gem_userptr_ioctl(struct drm_device *dev, void *data,
+				      struct drm_file *file_priv)
+{
+	struct exynos_drm_private *priv = dev->dev_private;
+	struct exynos_drm_gem_obj *exynos_gem_obj;
+	struct drm_exynos_gem_userptr *args = data;
+	struct exynos_drm_gem_buf *buf;
+	struct scatterlist *sgl;
+	unsigned long size, userptr;
+	unsigned int npages;
+	int ret, get_npages;
+
+	DRM_DEBUG_KMS("%s\n", __FILE__);
+
+	if (!args->size) {
+		DRM_ERROR("invalid size.\n");
+		return -EINVAL;
+	}
+
+	ret = check_gem_flags(args->flags);
+	if (ret)
+		return ret;
+
+	size = roundup_gem_size(args->size, EXYNOS_BO_USERPTR);
+
+	if (size > priv->userptr_limit) {
+		DRM_ERROR("excessed maximum size of userptr.\n");
+		return -EINVAL;
+	}
+
+	userptr = args->userptr;
+
+	buf = exynos_drm_init_buf(dev, size);
+	if (!buf)
+		return -ENOMEM;
+
+	exynos_gem_obj = exynos_drm_gem_init(dev, size);
+	if (!exynos_gem_obj) {
+		ret = -ENOMEM;
+		goto err_free_buffer;
+	}
+
+	buf->sgt = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
+	if (!buf->sgt) {
+		DRM_ERROR("failed to allocate buf->sgt.\n");
+		ret = -ENOMEM;
+		goto err_release_gem;
+	}
+
+	npages = size >> PAGE_SHIFT;
+
+	ret = sg_alloc_table(buf->sgt, npages, GFP_KERNEL);
+	if (ret < 0) {
+		DRM_ERROR("failed to initailize sg table.\n");
+		goto err_free_sgt;
+	}
+
+	buf->pages = kzalloc(npages * sizeof(struct page *), GFP_KERNEL);
+	if (!buf->pages) {
+		DRM_ERROR("failed to allocate buf->pages\n");
+		ret = -ENOMEM;
+		goto err_free_table;
+	}
+
+	exynos_gem_obj->buffer = buf;
+
+	get_npages = exynos_drm_get_userptr(dev, exynos_gem_obj, userptr, 1);
+	if (get_npages != npages) {
+		DRM_ERROR("failed to get user_pages.\n");
+		ret = get_npages;
+		goto err_release_userptr;
+	}
+
+	ret = exynos_drm_gem_handle_create(&exynos_gem_obj->base, file_priv,
+						&args->handle);
+	if (ret < 0) {
+		DRM_ERROR("failed to create gem handle.\n");
+		goto err_release_userptr;
+	}
+
+	sgl = buf->sgt->sgl;
+
+	/*
+	 * if buf->pfnmap is true then update sgl of sgt with pages but
+	 * if buf->pfnmap is false then it means the sgl was updated already
+	 * so it doesn't need to update the sgl.
+	 */
+	if (!buf->pfnmap) {
+		unsigned int i = 0;
+
+		/* set all pages to sg list. */
+		while (i < npages) {
+			sg_set_page(sgl, buf->pages[i], PAGE_SIZE, 0);
+			sg_dma_address(sgl) = page_to_phys(buf->pages[i]);
+			i++;
+			sgl = sg_next(sgl);
+		}
+	}
+
+	/* always use EXYNOS_BO_USERPTR as memory type for userptr. */
+	exynos_gem_obj->flags |= EXYNOS_BO_USERPTR;
+
+	return 0;
+
+err_release_userptr:
+	get_npages--;
+	while (get_npages >= 0)
+		put_page(buf->pages[get_npages--]);
+	kfree(buf->pages);
+	buf->pages = NULL;
+err_free_table:
+	sg_free_table(buf->sgt);
+err_free_sgt:
+	kfree(buf->sgt);
+	buf->sgt = NULL;
+err_release_gem:
+	drm_gem_object_release(&exynos_gem_obj->base);
+	kfree(exynos_gem_obj);
+	exynos_gem_obj = NULL;
+err_free_buffer:
+	exynos_drm_free_buf(dev, 0, buf);
+	return ret;
+}
+
 int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data,
 				      struct drm_file *file_priv)
 {	struct exynos_drm_gem_obj *exynos_gem_obj;
diff --git a/drivers/gpu/drm/exynos/exynos_drm_gem.h b/drivers/gpu/drm/exynos/exynos_drm_gem.h
index 3334c9f..72bd993 100644
--- a/drivers/gpu/drm/exynos/exynos_drm_gem.h
+++ b/drivers/gpu/drm/exynos/exynos_drm_gem.h
@@ -29,27 +29,35 @@
 #define to_exynos_gem_obj(x)	container_of(x,\
 			struct exynos_drm_gem_obj, base)
 
-#define IS_NONCONTIG_BUFFER(f)		(f & EXYNOS_BO_NONCONTIG)
+#define IS_NONCONTIG_BUFFER(f)	((f & EXYNOS_BO_NONCONTIG) ||\
+					(f & EXYNOS_BO_USERPTR))
 
 /*
  * exynos drm gem buffer structure.
  *
  * @kvaddr: kernel virtual address to allocated memory region.
+ * @userptr: user space address.
  * @dma_addr: bus address(accessed by dma) to allocated memory region.
  *	- this address could be physical address without IOMMU and
  *	device address with IOMMU.
+ * @write: whether pages will be written to by the caller.
  * @sgt: sg table to transfer page data.
  * @pages: contain all pages to allocated memory region.
  * @page_size: could be 4K, 64K or 1MB.
  * @size: size of allocated memory region.
+ * @pfnmap: indicate whether memory region from userptr is mmaped with
+ *	VM_PFNMAP or not.
  */
 struct exynos_drm_gem_buf {
 	void __iomem		*kvaddr;
+	unsigned long		userptr;
 	dma_addr_t		dma_addr;
+	unsigned int		write;
 	struct sg_table		*sgt;
 	struct page		**pages;
 	unsigned long		page_size;
 	unsigned long		size;
+	bool			pfnmap;
 };
 
 /*
@@ -64,6 +72,8 @@ struct exynos_drm_gem_buf {
  *	continuous memory region allocated by user request
  *	or at framebuffer creation.
  * @size: total memory size to physically non-continuous memory region.
+ * @vma: a pointer to the vma to user address space and used to release
+ *	the pages to user space.
  * @flags: indicate memory type to allocated buffer and cache attruibute.
  *
  * P.S. this object would be transfered to user as kms_bo.handle so
@@ -73,6 +83,7 @@ struct exynos_drm_gem_obj {
 	struct drm_gem_object		base;
 	struct exynos_drm_gem_buf	*buffer;
 	unsigned long			size;
+	struct vm_area_struct		*vma;
 	unsigned int			flags;
 };
 
@@ -130,6 +141,10 @@ int exynos_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data,
 int exynos_drm_gem_mmap_ioctl(struct drm_device *dev, void *data,
 			      struct drm_file *file_priv);
 
+/* map user space allocated by malloc to pages. */
+int exynos_drm_gem_userptr_ioctl(struct drm_device *dev, void *data,
+				      struct drm_file *file_priv);
+
 /* get buffer information to memory region allocated by gem. */
 int exynos_drm_gem_get_ioctl(struct drm_device *dev, void *data,
 				      struct drm_file *file_priv);
diff --git a/include/drm/exynos_drm.h b/include/drm/exynos_drm.h
index 52465dc..33fa1e2 100644
--- a/include/drm/exynos_drm.h
+++ b/include/drm/exynos_drm.h
@@ -77,6 +77,23 @@ struct drm_exynos_gem_mmap {
 };
 
 /**
+ * User-requested user space importing structure
+ *
+ * @userptr: user space address allocated by malloc.
+ * @size: size to the buffer allocated by malloc.
+ * @flags: indicate user-desired cache attribute to map the allocated buffer
+ *	to kernel space.
+ * @handle: a returned handle to created gem object.
+ *	- this handle will be set by gem module of kernel side.
+ */
+struct drm_exynos_gem_userptr {
+	uint64_t userptr;
+	uint64_t size;
+	unsigned int flags;
+	unsigned int handle;
+};
+
+/**
  * A structure to gem information.
  *
  * @handle: a handle to gem object created.
@@ -135,8 +152,10 @@ enum e_drm_exynos_gem_mem_type {
 	EXYNOS_BO_CACHABLE	= 1 << 1,
 	/* write-combine mapping. */
 	EXYNOS_BO_WC		= 1 << 2,
+	/* user space memory allocated by malloc. */
+	EXYNOS_BO_USERPTR	= 1 << 3,
 	EXYNOS_BO_MASK		= EXYNOS_BO_NONCONTIG | EXYNOS_BO_CACHABLE |
-					EXYNOS_BO_WC
+					EXYNOS_BO_WC | EXYNOS_BO_USERPTR
 };
 
 struct drm_exynos_g2d_get_ver {
@@ -173,7 +192,7 @@ struct drm_exynos_g2d_exec {
 #define DRM_EXYNOS_GEM_CREATE		0x00
 #define DRM_EXYNOS_GEM_MAP_OFFSET	0x01
 #define DRM_EXYNOS_GEM_MMAP		0x02
-/* Reserved 0x03 ~ 0x05 for exynos specific gem ioctl */
+#define DRM_EXYNOS_GEM_USERPTR		0x03
 #define DRM_EXYNOS_GEM_GET		0x04
 #define DRM_EXYNOS_USER_LIMIT		0x05
 #define DRM_EXYNOS_PLANE_SET_ZPOS	0x06
@@ -193,6 +212,9 @@ struct drm_exynos_g2d_exec {
 #define DRM_IOCTL_EXYNOS_GEM_MMAP	DRM_IOWR(DRM_COMMAND_BASE + \
 		DRM_EXYNOS_GEM_MMAP, struct drm_exynos_gem_mmap)
 
+#define DRM_IOCTL_EXYNOS_GEM_USERPTR	DRM_IOWR(DRM_COMMAND_BASE + \
+		DRM_EXYNOS_GEM_USERPTR, struct drm_exynos_gem_userptr)
+
 #define DRM_IOCTL_EXYNOS_GEM_GET	DRM_IOWR(DRM_COMMAND_BASE + \
 		DRM_EXYNOS_GEM_GET,	struct drm_exynos_gem_info)
 
-- 
1.7.4.1



More information about the dri-devel mailing list