From fcada3064ed8beca21a3e1a29ba1137caae2abf7 Mon Sep 17 00:00:00 2001
From: Hiroshi DOYU <hdoyu@nvidia.com>
Date: Fri, 23 Dec 2011 14:10:33 +0200
Subject: [PATCH 1/1] gpu: ion: Add IOMMU heap allocator with DMA API

Implemented IOMMU heap allocator("ion_iommu_heap_ops") with DMA API.

---
 drivers/gpu/ion/Kconfig          |    5 +
 drivers/gpu/ion/Makefile         |    1 +
 drivers/gpu/ion/ion_heap.c       |    6 +
 drivers/gpu/ion/ion_iommu_heap.c |  235 ++++++++++++++++++++++++++++++++++++++
 drivers/gpu/ion/ion_priv.h       |   16 +++-
 include/linux/ion.h              |    3 +
 6 files changed, 265 insertions(+), 1 deletions(-)

diff --git a/drivers/gpu/ion/Kconfig b/drivers/gpu/ion/Kconfig
index 5b48b4e..9a8cbdd 100644
--- a/drivers/gpu/ion/Kconfig
+++ b/drivers/gpu/ion/Kconfig
@@ -4,9 +4,14 @@ menuconfig ION
 	help
 	  Chose this option to enable the ION Memory Manager.
 
+config ION_IOMMU
+       bool
+
 config ION_TEGRA
 	tristate "Ion for Tegra"
 	depends on ARCH_TEGRA && ION
+	select TEGRA_IOMMU_SMMU if !ARCH_TEGRA_2x_SOC
+	select ION_IOMMU if TEGRA_IOMMU_SMMU
 	help
 	  Choose this option if you wish to use ion on an nVidia Tegra.
 
diff --git a/drivers/gpu/ion/Makefile b/drivers/gpu/ion/Makefile
index 73fe3fa..4ddc78e 100644
--- a/drivers/gpu/ion/Makefile
+++ b/drivers/gpu/ion/Makefile
@@ -1,2 +1,3 @@
 obj-$(CONFIG_ION) +=	ion.o ion_heap.o ion_system_heap.o ion_carveout_heap.o
+obj-$(CONFIG_ION_IOMMU)	+= ion_iommu_heap.o
 obj-$(CONFIG_ION_TEGRA) += tegra/
diff --git a/drivers/gpu/ion/ion_heap.c b/drivers/gpu/ion/ion_heap.c
index 8ce3c19..6d09778 100644
--- a/drivers/gpu/ion/ion_heap.c
+++ b/drivers/gpu/ion/ion_heap.c
@@ -32,6 +32,9 @@ struct ion_heap *ion_heap_create(struct ion_platform_heap *heap_data)
 	case ION_HEAP_TYPE_CARVEOUT:
 		heap = ion_carveout_heap_create(heap_data);
 		break;
+	case ION_HEAP_TYPE_IOMMU:
+		heap = ion_iommu_heap_create(heap_data);
+		break;
 	default:
 		pr_err("%s: Invalid heap type %d\n", __func__,
 		       heap_data->type);
@@ -65,6 +68,9 @@ void ion_heap_destroy(struct ion_heap *heap)
 	case ION_HEAP_TYPE_CARVEOUT:
 		ion_carveout_heap_destroy(heap);
 		break;
+	case ION_HEAP_TYPE_IOMMU:
+		ion_iommu_heap_destroy(heap);
+		break;
 	default:
 		pr_err("%s: Invalid heap type %d\n", __func__,
 		       heap->type);
diff --git a/drivers/gpu/ion/ion_iommu_heap.c b/drivers/gpu/ion/ion_iommu_heap.c
new file mode 100644
index 0000000..1428cf9
--- /dev/null
+++ b/drivers/gpu/ion/ion_iommu_heap.c
@@ -0,0 +1,235 @@
+/*
+ * drivers/gpu/ion/ion_iommu_heap.c
+ *
+ * Copyright (c) 2012, NVIDIA CORPORATION.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define pr_fmt(fmt)	"%s(): " fmt, __func__
+
+#include <linux/spinlock.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/ion.h>
+#include <linux/mm.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/iommu.h>
+#include <linux/highmem.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/ion.h>
+
+#include <asm/dma-iommu.h>
+
+#include "ion_priv.h"
+
+extern int arm_iommu_map_sg(struct device *dev, struct scatterlist *sg, int nents,
+		     enum dma_data_direction dir, struct dma_attrs *attrs);
+
+extern void arm_iommu_unmap_sg(struct device *dev, struct scatterlist *sg, int nents,
+			enum dma_data_direction dir, struct dma_attrs *attrs);
+
+#define NUM_PAGES(buf)	(PAGE_ALIGN((buf)->size) >> PAGE_SHIFT)
+
+struct ion_iommu_heap {
+	struct ion_heap		heap;
+	struct device		*dev;
+};
+
+static int ion_buffer_allocate(struct ion_buffer *buf)
+{
+	int i, npages = NUM_PAGES(buf);
+
+	buf->pages = kmalloc(npages * sizeof(*buf->pages), GFP_KERNEL);
+	if (!buf->pages)
+		goto err_pages;
+
+	buf->sglist = vzalloc(npages * sizeof(*buf->sglist));
+	if (!buf->sglist)
+		goto err_sgl;
+
+	sg_init_table(buf->sglist, npages);
+
+	for (i = 0; i < npages; i++) {
+		struct page *page;
+		phys_addr_t pa;
+
+		page = alloc_page((GFP_KERNEL | __GFP_HIGHMEM | __GFP_NOWARN));
+		if (!page)
+			goto err_pgalloc;
+		pa = page_to_phys(page);
+
+		sg_set_page(&buf->sglist[i], page, PAGE_SIZE, 0);
+
+		flush_dcache_page(page);
+		outer_flush_range(pa, pa + PAGE_SIZE);
+
+		buf->pages[i] = page;
+
+		pr_debug_once("pa:%08x\n", pa);
+	}
+	return 0;
+
+err_pgalloc:
+	while (i-- > 0)
+		__free_page(buf->pages[i]);
+	vfree(buf->sglist);
+err_sgl:
+	kfree(buf->pages);
+err_pages:
+	return -ENOMEM;
+}
+
+static void ion_buffer_free(struct ion_buffer *buf)
+{
+	int i, npages = NUM_PAGES(buf);
+
+	for (i = 0; i < npages; i++)
+		__free_page(buf->pages[i]);
+	vfree(buf->sglist);
+	kfree(buf->pages);
+}
+
+static int iommu_heap_allocate(struct ion_heap *heap, struct ion_buffer *buf,
+			       unsigned long len, unsigned long align,
+			       unsigned long flags)
+{
+	int err, count;
+	struct ion_iommu_heap *h =
+		container_of(heap, struct ion_iommu_heap, heap);
+	DEFINE_DMA_ATTRS(attrs);
+
+	len = round_up(len, PAGE_SIZE);
+	buf->size = len;
+
+	err = ion_buffer_allocate(buf);
+	if (err)
+		goto err_alloc_buf;
+
+	dma_set_attr(DMA_ATTR_NON_CONSISTENT, &attrs);
+	count = arm_iommu_map_sg(h->dev, buf->sglist, NUM_PAGES(buf),
+				 DMA_BIDIRECTIONAL, &attrs);
+	BUG_ON(!count);
+	buf->vaddr = 0;
+	buf->priv_virt = (void *)sg_dma_address(buf->sglist);
+	pr_info("da:%p(%lx/%x)\n", buf->priv_virt, count * PAGE_SIZE, buf->size);
+	return 0;
+
+err_alloc_buf:
+	buf->size = 0;
+	buf->pages = NULL;
+	buf->priv_virt = NULL;
+	return err;
+}
+
+static void iommu_heap_free(struct ion_buffer *buf)
+{
+	struct ion_heap *heap = buf->heap;
+	struct ion_iommu_heap *h =
+		container_of(heap, struct ion_iommu_heap, heap);
+	void *da = buf->priv_virt;
+	DEFINE_DMA_ATTRS(attrs);
+
+	dma_set_attr(DMA_ATTR_NON_CONSISTENT, &attrs);
+	arm_iommu_unmap_sg(h->dev, buf->sglist, NUM_PAGES(buf),
+			   DMA_BIDIRECTIONAL, &attrs);
+
+	ion_buffer_free(buf);
+	buf->pages = NULL;
+	buf->priv_virt = NULL;
+	pr_debug("da:%p\n", da);
+}
+
+static int iommu_heap_phys(struct ion_heap *heap, struct ion_buffer *buf,
+			   ion_phys_addr_t *addr, size_t *len)
+{
+	*addr = (unsigned long)buf->priv_virt;
+	*len = buf->size;
+	pr_info("da:%08lx(%x)\n", *addr, *len);
+	return 0;
+}
+
+static void *iommu_heap_map_kernel(struct ion_heap *heap,
+				   struct ion_buffer *buf)
+{
+	int npages = NUM_PAGES(buf);
+
+	BUG_ON(!buf->pages);
+	buf->vaddr = vm_map_ram(buf->pages, npages, -1,
+				pgprot_noncached(pgprot_kernel));
+	pr_debug("va:%p\n", buf->vaddr);
+	WARN_ON(!buf->vaddr);
+	return buf->vaddr;
+}
+
+static void iommu_heap_unmap_kernel(struct ion_heap *heap,
+				    struct ion_buffer *buf)
+{
+	int npages = NUM_PAGES(buf);
+
+	BUG_ON(!buf->pages);
+	WARN_ON(!buf->vaddr);
+	vm_unmap_ram(buf->vaddr, npages);
+	buf->vaddr = NULL;
+	pr_debug("va:%p\n", buf->vaddr);
+}
+
+static int iommu_heap_map_user(struct ion_heap *mapper,
+			       struct ion_buffer *buf,
+			       struct vm_area_struct *vma)
+{
+	struct ion_heap *heap = buf->heap;
+	struct ion_iommu_heap *h =
+		container_of(heap, struct ion_iommu_heap, heap);
+
+	return dma_mmap_coherent(h->dev, vma, buf->vaddr,
+				 (dma_addr_t)buf->priv_virt, buf->size);
+}
+
+static struct ion_heap_ops iommu_heap_ops = {
+	.allocate	= iommu_heap_allocate,
+	.free		= iommu_heap_free,
+	.phys		= iommu_heap_phys,
+	.map_kernel	= iommu_heap_map_kernel,
+	.unmap_kernel	= iommu_heap_unmap_kernel,
+	.map_user	= iommu_heap_map_user,
+};
+
+struct ion_heap *ion_iommu_heap_create(struct ion_platform_heap *data)
+{
+	int err;
+	struct ion_iommu_heap *h;
+	struct dma_iommu_mapping *map;
+
+	h = kzalloc(sizeof(*h), GFP_KERNEL);
+	BUG_ON(!h);
+	h->heap.ops = &iommu_heap_ops;
+	h->dev = data->priv;
+
+	map = arm_iommu_create_mapping(&platform_bus_type,
+				       data->base, data->size, 0);
+	BUG_ON(!map);
+	err = arm_iommu_attach_device(h->dev, map);
+	BUG_ON(err);
+
+	return &h->heap;
+}
+
+void ion_iommu_heap_destroy(struct ion_heap *heap)
+{
+	/* FIXME */
+}
diff --git a/drivers/gpu/ion/ion_priv.h b/drivers/gpu/ion/ion_priv.h
index 8c75ff5..c8415b8 100644
--- a/drivers/gpu/ion/ion_priv.h
+++ b/drivers/gpu/ion/ion_priv.h
@@ -145,7 +145,8 @@ void ion_handle_add(struct ion_client *client, struct ion_handle *handle);
  * @vaddr:		the kenrel mapping if kmap_cnt is not zero
  * @dmap_cnt:		number of times the buffer is mapped for dma
  * @sglist:		the scatterlist for the buffer is dmap_cnt is not zero
-*/
+ * @pages:		list for allocated pages for the buffer
+ */
 struct ion_buffer {
 	struct kref ref;
 	struct rb_node node;
@@ -162,6 +163,7 @@ struct ion_buffer {
 	void *vaddr;
 	int dmap_cnt;
 	struct scatterlist *sglist;
+	struct page **pages;
 };
 
 /**
@@ -266,6 +268,18 @@ ion_phys_addr_t ion_carveout_allocate(struct ion_heap *heap, unsigned long size,
 				      unsigned long align);
 void ion_carveout_free(struct ion_heap *heap, ion_phys_addr_t addr,
 		       unsigned long size);
+#ifdef CONFIG_ION_IOMMU
+struct ion_heap *ion_iommu_heap_create(struct ion_platform_heap *);
+void ion_iommu_heap_destroy(struct ion_heap *);
+#else
+static inline struct ion_heap *ion_iommu_heap_create(struct ion_platform_heap *)
+{
+	return NULL;
+}
+static inline void ion_iommu_heap_destroy(struct ion_heap *)
+{
+}
+#endif
 /**
  * The carveout heap returns physical addresses, since 0 may be a valid
  * physical address, this is used to indicate allocation failed
diff --git a/include/linux/ion.h b/include/linux/ion.h
index aed8349..9a32243 100644
--- a/include/linux/ion.h
+++ b/include/linux/ion.h
@@ -33,6 +33,7 @@ enum ion_heap_type {
 	ION_HEAP_TYPE_SYSTEM,
 	ION_HEAP_TYPE_SYSTEM_CONTIG,
 	ION_HEAP_TYPE_CARVEOUT,
+	ION_HEAP_TYPE_IOMMU,
 	ION_HEAP_TYPE_CUSTOM, /* must be last so device specific heaps always
 				 are at the end of this enum */
 	ION_NUM_HEAPS,
@@ -63,6 +64,7 @@ struct ion_buffer;
  * @name:	used for debug purposes
  * @base:	base address of heap in physical memory if applicable
  * @size:	size of the heap in bytes if applicable
+ * @priv:	heap specific data
  *
  * Provided by the board file.
  */
@@ -72,6 +74,7 @@ struct ion_platform_heap {
 	const char *name;
 	ion_phys_addr_t base;
 	size_t size;
+	void *priv;
 };
 
 /**
-- 
1.7.5.4

