linux-arm-kernel.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-24  0:38 [PATCH 0/4] iommu/exynos: Add IOMMU and Enhance System MMU for Exynos4 KyongHo Cho
@ 2011-09-24  0:38 ` KyongHo Cho
  0 siblings, 0 replies; 21+ messages in thread
From: KyongHo Cho @ 2011-09-24  0:38 UTC (permalink / raw)
  To: linux-arm-kernel

This is the System MMU driver and IOMMU API implementation for
Exynos4 SOC platforms. Exynos4 platforms has more than 10 System
MMUs dedicated for each multimedia accellerators.

Signed-off-by: KyongHo Cho <pullip.cho@samsung.com>
---
 drivers/iommu/Kconfig        |   14 +
 drivers/iommu/Makefile       |    1 +
 drivers/iommu/exynos_iommu.c |  859 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 874 insertions(+), 0 deletions(-)
 create mode 100644 drivers/iommu/exynos_iommu.c

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index b57b3fa..1c754cd 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -107,4 +107,18 @@ config INTR_REMAP
 	  To use x2apic mode in the CPU's which support x2APIC enhancements or
 	  to support platforms with CPU's having > 8 bit APIC ID, say Y.
 
+# EXYNOS IOMMU support
+config EXYNOS_IOMMU
+	bool "Exynos IOMMU Support"
+	depends on ARCH_EXYNOS4
+	select IOMMU_API
+	select EXYNOS4_DEV_SYSMMU
+	help
+	  Support for the IOMMUs (System MMUs) Samsung Exynos application
+	  processor family. This enables H/W multimedia accellerators to view
+	  non-linear physical memory chunks as a linear memory in their virtual
+	  address spaces.
+
+	  If unsure, say N here.
+
 endif # IOMMU_SUPPORT
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 4d4d77d..1eb924f 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -3,3 +3,4 @@ obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
 obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
 obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
 obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o
+obj-$(CONFIG_EXYNOS_IOMMU) += exynos_iommu.o
diff --git a/drivers/iommu/exynos_iommu.c b/drivers/iommu/exynos_iommu.c
new file mode 100644
index 0000000..fe5b5d8
--- /dev/null
+++ b/drivers/iommu/exynos_iommu.c
@@ -0,0 +1,859 @@
+/* linux/drivers/iommu/exynos_iommu.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/iommu.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+
+#include <asm/cacheflush.h>
+#include <asm/pgtable.h>
+
+#include <mach/map.h>
+#include <mach/regs-sysmmu.h>
+#include <mach/sysmmu.h>
+
+#define CTRL_ENABLE	0x5
+#define CTRL_BLOCK	0x7
+#define CTRL_DISABLE	0x0
+
+enum S5P_SYSMMU_INTERRUPT_TYPE {
+	SYSMMU_PAGEFAULT,
+	SYSMMU_AR_MULTIHIT,
+	SYSMMU_AW_MULTIHIT,
+	SYSMMU_BUSERROR,
+	SYSMMU_AR_SECURITY,
+	SYSMMU_AR_ACCESS,
+	SYSMMU_AW_SECURITY,
+	SYSMMU_AW_PROTECTION, /* 7 */
+	SYSMMU_FAULTS_NUM
+};
+
+static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
+	S5P_PAGE_FAULT_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR,
+	S5P_DEFAULT_SLAVE_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR
+};
+
+static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
+	"PAGE FAULT",
+	"AR MULTI-HIT FAULT",
+	"AW MULTI-HIT FAULT",
+	"BUS ERROR",
+	"AR SECURITY PROTECTION FAULT",
+	"AR ACCESS PROTECTION FAULT",
+	"AW SECURITY PROTECTION FAULT",
+	"AW ACCESS PROTECTION FAULT"
+};
+
+struct exynos_iommu_domain {
+	struct device *dev;
+	unsigned long *pgtable;
+	spinlock_t lock;
+	spinlock_t pgtablelock;
+};
+
+/* List of sysmmu_platdata */
+static LIST_HEAD(sysmmu_list);
+
+static inline struct sysmmu_platdata *get_sysmmu_data(struct device *owner,
+						struct sysmmu_platdata *start)
+{
+	struct list_head *pos, *head;
+
+	head = (start) ? &start->node : &sysmmu_list;
+
+	list_for_each(pos, head) {
+		struct sysmmu_platdata *mmudata =
+				container_of(pos, struct sysmmu_platdata, node);
+
+		if (pos == &sysmmu_list)
+			return NULL;
+
+		if (mmudata->owner == owner)
+			return mmudata;
+	}
+
+	return NULL;
+}
+
+static inline struct sysmmu_platdata *get_platdata(struct device *dev)
+{
+	return dev_get_platdata(dev);
+}
+
+static inline bool set_sysmmu_active(struct sysmmu_platdata *mmudata)
+{
+	/* return true if the System MMU was not active previously
+	   and it needs to be initialized */
+
+	return atomic_inc_return(&mmudata->activations) == 1;
+}
+
+static inline bool set_sysmmu_inactive(struct sysmmu_platdata *mmudata)
+{
+	/* return true if the System MMU is needed to be disabled */
+	int ref;
+
+	ref = atomic_dec_return(&mmudata->activations);
+
+	if (ref == 0)
+		return true;
+
+	if (WARN_ON(ref < 0)) {
+		/* System MMU is already disabled */
+		atomic_set(&mmudata->activations, 0);
+		ref = 0;
+	}
+
+	return false;
+}
+
+static inline bool is_sysmmu_active(struct sysmmu_platdata *mmudata)
+{
+	return atomic_read(&mmudata->activations) != 0;
+}
+
+static inline void sysmmu_block(void __iomem *sfrbase)
+{
+	__raw_writel(CTRL_BLOCK, sfrbase + S5P_MMU_CTRL);
+}
+
+static inline void sysmmu_unblock(void __iomem *sfrbase)
+{
+	__raw_writel(CTRL_ENABLE, sfrbase + S5P_MMU_CTRL);
+}
+
+static inline void __sysmmu_tlb_invalidate(void __iomem *sfrbase)
+{
+	__raw_writel(0x1, sfrbase + S5P_MMU_FLUSH);
+}
+
+static inline void __sysmmu_set_ptbase(void __iomem *sfrbase,
+				       unsigned long pgd)
+{
+	if (unlikely(pgd == 0)) {
+		pgd = (unsigned long)ZERO_PAGE(0);
+		__raw_writel(0x20, sfrbase + S5P_MMU_CFG); /* 4KB LV1 */
+	} else {
+		__raw_writel(0x0, sfrbase + S5P_MMU_CFG); /* 16KB LV1 */
+	}
+
+	__raw_writel(pgd, sfrbase + S5P_PT_BASE_ADDR);
+
+	__sysmmu_tlb_invalidate(sfrbase);
+}
+
+static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
+{
+	/* SYSMMU is in blocked when interrupt occurred. */
+	unsigned long addr;
+	struct sysmmu_platdata *mmudata = dev_id;
+	enum S5P_SYSMMU_INTERRUPT_TYPE itype;
+
+	WARN_ON(!is_sysmmu_active(mmudata));
+
+	itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
+		__ffs(__raw_readl(mmudata->sfrbase + S5P_INT_STATUS));
+
+	BUG_ON(!((itype >= 0) && (itype < 8)));
+
+	dev_alert(mmudata->dev, "SYSTEM MMU FAULT!!!.\n");
+
+	if (!mmudata->domain)
+		return IRQ_NONE;
+
+	addr = __raw_readl(mmudata->sfrbase + fault_reg_offset[itype]);
+
+	if (!report_iommu_fault(mmudata->domain, mmudata->owner, addr, itype)) {
+		__raw_writel(1 << itype, mmudata->sfrbase + S5P_INT_CLEAR);
+		dev_notice(mmudata->dev,
+				"%s is resolved. Retrying translation.\n",
+				sysmmu_fault_name[itype]);
+		sysmmu_unblock(mmudata->sfrbase);
+	} else {
+		dev_notice(mmudata->dev, "%s is not handled.\n",
+						sysmmu_fault_name[itype]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+void exynos_sysmmu_set_tablebase_pgd(struct device *owner, unsigned long pgd)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+
+	while ((mmudata = get_sysmmu_data(owner, mmudata))) {
+		if (is_sysmmu_active(mmudata)) {
+			sysmmu_block(mmudata->sfrbase);
+			__sysmmu_set_ptbase(mmudata->sfrbase, pgd);
+			sysmmu_unblock(mmudata->sfrbase);
+			dev_dbg(mmudata->dev, "New page table base is %p\n",
+								(void *)pgd);
+		} else {
+			dev_dbg(mmudata->dev,
+			"Disabled: Skipping setting page table base.\n");
+		}
+	}
+}
+
+int exynos_sysmmu_enable(struct iommu_domain *domain)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+	bool enabled = false;
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (!priv || !priv->dev)
+		return -EINVAL;
+
+	/* There are some devices that control more System MMUs than one such
+	 * as MFC.
+	 */
+	while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
+		enabled = true;
+
+		if (!set_sysmmu_active(mmudata)) {
+			dev_dbg(mmudata->dev, "Already enabled.\n");
+			continue;
+		}
+
+		pm_runtime_get_sync(mmudata->dev);
+
+		clk_enable(mmudata->clk);
+
+		__sysmmu_set_ptbase(mmudata->sfrbase, __pa(priv->pgtable));
+
+		__raw_writel(CTRL_ENABLE, mmudata->sfrbase + S5P_MMU_CTRL);
+
+		mmudata->domain = domain;
+
+		dev_dbg(mmudata->dev, "Enabled.\n");
+	}
+
+	return (enabled) ? 0 : -ENODEV;
+}
+
+void exynos_sysmmu_disable(struct iommu_domain *domain)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+	bool disabled = false;
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (!priv || !priv->dev)
+		return;
+
+	while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
+		disabled = true;
+
+		if (!set_sysmmu_inactive(mmudata)) {
+			dev_dbg(mmudata->dev,
+					"Inactivation request ignorred\n");
+			continue;
+		}
+
+		__raw_writel(CTRL_DISABLE, mmudata->sfrbase + S5P_MMU_CTRL);
+
+		clk_disable(mmudata->clk);
+
+		pm_runtime_put_sync(mmudata->dev);
+
+		mmudata->domain = NULL;
+
+		dev_dbg(mmudata->dev, "Disabled.\n");
+	}
+
+	BUG_ON(!disabled);
+}
+
+void exynos_sysmmu_tlb_invalidate(struct device *owner)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+
+	while ((mmudata = get_sysmmu_data(owner, mmudata))) {
+		if (is_sysmmu_active(mmudata)) {
+			sysmmu_block(mmudata->sfrbase);
+			__sysmmu_tlb_invalidate(mmudata->sfrbase);
+			sysmmu_unblock(mmudata->sfrbase);
+		} else {
+			dev_dbg(mmudata->dev,
+				"Disabled: Skipping invalidating TLB.\n");
+		}
+	}
+}
+
+static int exynos_sysmmu_probe(struct platform_device *pdev)
+{
+	struct resource *res, *ioarea;
+	int ret;
+	int irq;
+	struct device *dev;
+	void *sfr;
+
+	dev = &pdev->dev;
+	if (!get_platdata(dev) || (get_platdata(dev)->owner == NULL)) {
+		dev_err(dev, "Failed to probing system MMU: "
+						"Owner device is not set.");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev,
+			"Failed probing system MMU: failed to get resource.");
+		return -ENOENT;
+	}
+
+	ioarea = request_mem_region(res->start, resource_size(res), pdev->name);
+	if (ioarea == NULL) {
+		dev_err(dev, "Failed probing system MMU: "
+					"failed to request memory region.");
+		return -ENOMEM;
+	}
+
+	sfr = ioremap(res->start, resource_size(res));
+	if (!sfr) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to call ioremap().");
+		ret = -ENOENT;
+		goto err_ioremap;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq <= 0) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to get irq resource.");
+		ret = irq;
+		goto err_irq;
+	}
+
+	if (request_irq(irq, exynos_sysmmu_irq, 0, dev_name(&pdev->dev),
+						dev_get_platdata(dev))) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to request irq.");
+		ret = -ENOENT;
+		goto err_irq;
+	}
+
+	get_platdata(dev)->clk = clk_get(dev, "sysmmu");
+
+	if (IS_ERR_OR_NULL(get_platdata(dev)->clk)) {
+		dev_err(dev, "Failed to probing System MMU: "
+					"failed to get clock descriptor");
+		ret = -ENOENT;
+		goto err_clk;
+	}
+
+	get_platdata(dev)->sfrbase = sfr;
+
+	list_add(&get_platdata(dev)->node, &sysmmu_list);
+
+	if (dev->parent)
+		pm_runtime_enable(dev);
+
+	dev_dbg(dev, "Initialized for %s.\n",
+					dev_name(get_platdata(dev)->owner));
+	return 0;
+err_clk:
+	free_irq(irq, dev_get_platdata(dev));
+err_irq:
+	iounmap(get_platdata(dev)->sfrbase);
+err_ioremap:
+	release_resource(ioarea);
+	kfree(ioarea);
+	dev_err(dev, "Probing system MMU failed.");
+	return ret;
+}
+
+static int exynos_sysmmu_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+int exynos_sysmmu_runtime_suspend(struct device *dev)
+{
+	if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
+		return -EFAULT;
+
+	return 0;
+}
+
+int exynos_sysmmu_runtime_resume(struct device *dev)
+{
+	if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
+		return -EFAULT;
+
+	return 0;
+}
+
+const struct dev_pm_ops exynos_sysmmu_pm_ops = {
+	.runtime_suspend	= exynos_sysmmu_runtime_suspend,
+	.runtime_resume		= exynos_sysmmu_runtime_resume,
+};
+
+static struct platform_driver exynos_sysmmu_driver = {
+	.probe		= exynos_sysmmu_probe,
+	.remove		= exynos_sysmmu_remove,
+	.driver		= {
+		.owner		= THIS_MODULE,
+		.name		= "s5p-sysmmu",
+		.pm		= &exynos_sysmmu_pm_ops,
+	}
+};
+
+static int __init exynos_sysmmu_init(void)
+{
+	return platform_driver_register(&exynos_sysmmu_driver);
+}
+arch_initcall(exynos_sysmmu_init);
+
+/* We does not consider super section mapping (16MB) */
+#define S5P_SPAGE_SHIFT		12
+#define S5P_LPAGE_SHIFT		16
+#define S5P_SECTION_SHIFT	20
+
+#define S5P_SPAGE_SIZE		(1 << S5P_SPAGE_SHIFT)
+#define S5P_LPAGE_SIZE		(1 << S5P_LPAGE_SHIFT)
+#define S5P_SECTION_SIZE	(1 << S5P_SECTION_SHIFT)
+
+#define S5P_SPAGE_MASK		(~(S5P_SPAGE_SIZE - 1))
+#define S5P_LPAGE_MASK		(~(S5P_LPAGE_SIZE - 1))
+#define S5P_SECTION_MASK	(~(S5P_SECTION_SIZE - 1))
+
+#define S5P_SPAGE_ORDER		(S5P_SPAGE_SHIFT - PAGE_SHIFT)
+#define S5P_LPAGE_ORDER		(S5P_LPAGE_SHIFT - S5P_SPAGE_SHIFT)
+#define S5P_SECTION_ORDER	(S5P_SECTION_SHIFT - S5P_SPAGE_SHIFT)
+
+#define S5P_LV1TABLE_ENTRIES	(1 << (BITS_PER_LONG - S5P_SECTION_SHIFT))
+
+#define S5P_LV2TABLE_ENTRIES	(1 << S5P_SECTION_ORDER)
+#define S5P_LV2TABLE_SIZE	(S5P_LV2TABLE_ENTRIES * sizeof(long))
+#define S5P_LV2TABLE_MASK	(~(S5P_LV2TABLE_SIZE - 1)) /* 0xFFFFFC00 */
+
+#define S5P_SECTION_LV1_ENTRY(entry)	((entry & 0x40003) == 2)
+#define S5P_SUPSECT_LV1_ENTRY(entry)	((entry & 0x40003) == 0x40002)
+#define S5P_PAGE_LV1_ENTRY(entry)	((entry & 3) == 1)
+#define S5P_FAULT_LV1_ENTRY(entry) (((entry & 3) == 0) || (entry & 3) == 3)
+
+#define S5P_LPAGE_LV2_ENTRY(entry)	((entry & 3) == 1)
+#define S5P_SPAGE_LV2_ENTRY(entry)	((entry & 2) == 2)
+#define S5P_FAULT_LV2_ENTRY(entry)	((entry & 3) == 0)
+
+#define MAKE_FAULT_ENTRY(entry)		do { entry = 0; } while (0)
+#define MAKE_SECTION_ENTRY(entry, pa)	do { entry = pa | 2; } while (0)
+#define MAKE_SUPSECT_ENTRY(entry, pa)	do { entry = pa | 0x40002; } while (0)
+#define MAKE_LV2TABLE_ENTRY(entry, pa)	do { entry = pa | 1; } while (0)
+
+#define MAKE_LPAGE_ENTRY(entry, pa)	do { entry = pa | 1; } while (0)
+#define MAKE_SPAGE_ENTRY(entry, pa)	do { entry = pa | 3; } while (0)
+
+#define GET_LV2ENTRY(entry, iova) (\
+	(unsigned long *)phys_to_virt(entry & S5P_LV2TABLE_MASK) +\
+	((iova & (~S5P_SECTION_MASK)) >> S5P_SPAGE_SHIFT))
+
+/* slab cache for level 2 page tables */
+static struct kmem_cache *l2table_cachep;
+
+static inline void pgtable_flush(void *vastart, void *vaend)
+{
+	dmac_flush_range(vastart, vaend);
+	outer_flush_range(virt_to_phys(vastart),
+				virt_to_phys(vaend));
+}
+
+static int exynos_iommu_fault_handler(struct iommu_domain *domain,
+			struct device *dev, unsigned long iova, int flags)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
+				sysmmu_fault_name[flags], (void *)iova,
+				(void *)(__pa(priv->pgtable)));
+	dev_err(priv->dev, "\t\tGenerating Kernel OOPS...\n");
+	dev_err(priv->dev, "\t\tbecause it is unrecoverable.\n");
+	dev_err(priv->dev,
+		"\t\tSet Fault handler with iommu_set_fault_handler().\n");
+	dev_err(priv->dev, "\t\tto handle System MMU fault.\n");
+
+	BUG();
+
+	return 0;
+}
+
+static int exynos_iommu_domain_init(struct iommu_domain *domain)
+{
+	struct exynos_iommu_domain *priv;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL,
+		(S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
+	if (!priv->pgtable) {
+		kfree(priv);
+		return -ENOMEM;
+	}
+
+	memset(priv->pgtable, 0, S5P_LV1TABLE_ENTRIES * sizeof(unsigned long));
+	pgtable_flush(priv->pgtable, priv->pgtable + S5P_LV1TABLE_ENTRIES);
+
+	spin_lock_init(&priv->lock);
+
+	domain->priv = priv;
+
+	iommu_set_fault_handler(domain, &exynos_iommu_fault_handler);
+
+	return 0;
+}
+
+static void exynos_iommu_domain_destroy(struct iommu_domain *domain)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	free_pages((unsigned long)priv->pgtable,
+		(S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
+
+	kfree(priv);
+
+	domain->priv = NULL;
+}
+
+static int exynos_iommu_attach_device(struct iommu_domain *domain,
+				   struct device *dev)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	int ret;
+
+	spin_lock(&priv->lock);
+
+	priv->dev = dev;
+
+	ret = exynos_sysmmu_enable(domain);
+	if (ret)
+		return ret;
+
+	spin_unlock(&priv->lock);
+
+	return 0;
+}
+
+static void exynos_iommu_detach_device(struct iommu_domain *domain,
+				    struct device *dev)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	spin_lock(&priv->lock);
+
+	if (priv->dev == dev) {
+		exynos_sysmmu_disable(domain);
+		priv->dev = NULL;
+	}
+
+	spin_unlock(&priv->lock);
+}
+
+static bool section_available(struct iommu_domain *domain,
+			      unsigned long *lv1entry)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (S5P_SECTION_LV1_ENTRY(*lv1entry)) {
+		dev_err(priv->dev, "1MB entry alread exists at 0x%08x\n",
+				(lv1entry - priv->pgtable) * SZ_1M);
+		return false;
+	}
+
+	if (S5P_PAGE_LV1_ENTRY(*lv1entry)) {
+		unsigned long *lv2end, *lv2base;
+
+		lv2base = phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK);
+		lv2end = lv2base + S5P_LV2TABLE_ENTRIES;
+		while (lv2base != lv2end) {
+			if (!S5P_FAULT_LV2_ENTRY(*lv2base)) {
+				dev_err(priv->dev, "Failed to free L2 page "
+						"table for section mapping.\n");
+				return false;
+			}
+			lv2base++;
+		}
+
+		kmem_cache_free(l2table_cachep,
+				phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK));
+
+		MAKE_FAULT_ENTRY(*lv1entry);
+	}
+
+	return true;
+}
+
+static bool write_lpage(unsigned long *head_entry, unsigned long phys_addr)
+{
+	unsigned long *entry, *end;
+
+	entry = head_entry;
+	end = entry + (1 << S5P_LPAGE_ORDER);
+
+	while (entry != end) {
+		if (!S5P_FAULT_LV2_ENTRY(*entry))
+			break;
+
+		MAKE_LPAGE_ENTRY(*entry, phys_addr);
+
+		entry++;
+	}
+
+	if (entry != end) {
+		end = entry;
+		while (entry != head_entry)
+			MAKE_FAULT_ENTRY(*(--entry));
+
+		return false;
+	}
+
+	return true;
+}
+
+static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova,
+			 phys_addr_t paddr, int gfp_order, int prot)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *start_entry, *entry, *end_entry;
+	int num_entry;
+	int ret = 0;
+	unsigned long flags;
+
+	BUG_ON(priv->pgtable == NULL);
+
+	spin_lock_irqsave(&priv->pgtablelock, flags);
+
+	start_entry = entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (gfp_order >= S5P_SECTION_ORDER) {
+		BUG_ON((paddr | iova) & ~S5P_SECTION_MASK);
+		/* 1MiB mapping */
+
+		num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
+		end_entry = entry + num_entry;
+
+		while (entry != end_entry) {
+			if (!section_available(domain, entry))
+				break;
+
+			MAKE_SECTION_ENTRY(*entry, paddr);
+
+			paddr += S5P_SECTION_SIZE;
+			entry++;
+		}
+
+		if (entry != end_entry)
+			goto mapping_error;
+
+		pgtable_flush(start_entry, entry);
+		goto mapping_done;
+	}
+
+	if (S5P_FAULT_LV1_ENTRY(*entry)) {
+		unsigned long *l2table;
+
+		l2table = kmem_cache_zalloc(l2table_cachep, GFP_KERNEL);
+		if (!l2table) {
+			ret = -ENOMEM;
+			goto nomem_error;
+		}
+
+		pgtable_flush(entry, entry + S5P_LV2TABLE_ENTRIES);
+
+		MAKE_LV2TABLE_ENTRY(*entry, virt_to_phys(l2table));
+		pgtable_flush(entry, entry + 1);
+	}
+
+	/* 'entry' points level 2 entries, hereafter */
+	entry = GET_LV2ENTRY(*entry, iova);
+
+	start_entry = entry;
+	num_entry = 1 << gfp_order;
+	end_entry = entry + num_entry;
+
+	if (gfp_order >= S5P_LPAGE_ORDER) {
+		/* large page(64KiB) mapping */
+		BUG_ON((paddr | iova) & ~S5P_LPAGE_MASK);
+
+		while (entry != end_entry) {
+			if (!write_lpage(entry, paddr)) {
+				pr_err("%s: Failed to allocate large page"
+						" entry.\n", __func__);
+				break;
+			}
+
+			paddr += S5P_LPAGE_SIZE;
+			entry += (1 << S5P_LPAGE_ORDER);
+		}
+
+		if (entry != end_entry) {
+			entry -= 1 << S5P_LPAGE_ORDER;
+			goto mapping_error;
+		}
+	} else {
+		/* page (4KiB) mapping */
+		while (entry != end_entry && S5P_FAULT_LV2_ENTRY(*entry)) {
+
+			MAKE_SPAGE_ENTRY(*entry, paddr);
+
+			entry++;
+			paddr += S5P_SPAGE_SIZE;
+		}
+
+		if (entry != end_entry) {
+			pr_err("%s: Failed to allocate small page entry.\n",
+								__func__);
+			goto mapping_error;
+		}
+	}
+
+	pgtable_flush(start_entry, entry);
+mapping_error:
+	if (entry != end_entry) {
+		unsigned long *current_entry = entry;
+		while (entry != start_entry)
+			MAKE_FAULT_ENTRY(*(--entry));
+		pgtable_flush(start_entry, current_entry);
+		ret = -EADDRINUSE;
+	}
+
+nomem_error:
+mapping_done:
+	spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+	return 0;
+}
+
+static int exynos_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
+			   int gfp_order)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *entry;
+	int num_entry;
+	unsigned long flags;
+
+	BUG_ON(priv->pgtable == NULL);
+
+	spin_lock_irqsave(&priv->pgtablelock, flags);
+
+	entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (gfp_order >= S5P_SECTION_ORDER) {
+		gfp_order -= S5P_SECTION_ORDER;
+		num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
+		while (num_entry--) {
+			if (S5P_SECTION_LV1_ENTRY(*entry)) {
+				MAKE_FAULT_ENTRY(*entry);
+			} else if (S5P_PAGE_LV1_ENTRY(*entry)) {
+				unsigned long *lv2beg, *lv2end;
+				lv2beg = phys_to_virt(
+						*entry & S5P_LV2TABLE_MASK);
+				lv2end = lv2beg + S5P_LV2TABLE_ENTRIES;
+				while (lv2beg != lv2end) {
+					MAKE_FAULT_ENTRY(*lv2beg);
+					lv2beg++;
+				}
+			}
+			entry++;
+		}
+	} else {
+		entry = GET_LV2ENTRY(*entry, iova);
+
+		BUG_ON(S5P_LPAGE_LV2_ENTRY(*entry) &&
+						(gfp_order < S5P_LPAGE_ORDER));
+
+		num_entry = 1 << gfp_order;
+
+		while (num_entry--) {
+			MAKE_FAULT_ENTRY(*entry);
+			entry++;
+		}
+	}
+
+	if (priv->dev)
+		exynos_sysmmu_tlb_invalidate(priv->dev);
+
+	spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+	return 0;
+}
+
+static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain,
+					  unsigned long iova)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *entry;
+	unsigned long offset;
+
+	entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (S5P_FAULT_LV1_ENTRY(*entry))
+		return 0;
+
+	offset = iova & ~S5P_SECTION_MASK;
+
+	if (S5P_SECTION_LV1_ENTRY(*entry))
+		return (*entry & S5P_SECTION_MASK) + offset;
+
+	entry = GET_LV2ENTRY(*entry, iova);
+
+	if (S5P_SPAGE_LV2_ENTRY(*entry))
+		return (*entry & S5P_SPAGE_MASK) + (iova & ~S5P_SPAGE_MASK);
+
+	if (S5P_LPAGE_LV2_ENTRY(*entry))
+		return (*entry & S5P_LPAGE_MASK) + (iova & ~S5P_LPAGE_MASK);
+
+	return 0;
+}
+
+static int exynos_iommu_domain_has_cap(struct iommu_domain *domain,
+				    unsigned long cap)
+{
+	return 0;
+}
+
+static struct iommu_ops exynos_iommu_ops = {
+	.domain_init = &exynos_iommu_domain_init,
+	.domain_destroy = &exynos_iommu_domain_destroy,
+	.attach_dev = &exynos_iommu_attach_device,
+	.detach_dev = &exynos_iommu_detach_device,
+	.map = &exynos_iommu_map,
+	.unmap = &exynos_iommu_unmap,
+	.iova_to_phys = &exynos_iommu_iova_to_phys,
+	.domain_has_cap = &exynos_iommu_domain_has_cap,
+};
+
+static int __init exynos_iommu_init(void)
+{
+	l2table_cachep = kmem_cache_create("SysMMU Lv2 Tables",
+				S5P_LV2TABLE_SIZE, S5P_LV2TABLE_SIZE, 0, NULL);
+	if (!l2table_cachep)
+		return -ENOMEM;
+
+	register_iommu(&exynos_iommu_ops);
+
+	return 0;
+}
+arch_initcall(exynos_iommu_init);
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-24  2:02 [PATCH 0/4] iommu/exynos: Add IOMMU and Enhance System MMU driver for Exynos4 KyongHo Cho
@ 2011-09-24  2:02 ` KyongHo Cho
  0 siblings, 0 replies; 21+ messages in thread
From: KyongHo Cho @ 2011-09-24  2:02 UTC (permalink / raw)
  To: linux-arm-kernel

This is the System MMU driver and IOMMU API implementation for
Exynos4 SOC platforms. Exynos4 platforms has more than 10 System
MMUs dedicated for each multimedia accellerators.

Signed-off-by: KyongHo Cho <pullip.cho@samsung.com>
---
 drivers/iommu/Kconfig        |   14 +
 drivers/iommu/Makefile       |    1 +
 drivers/iommu/exynos_iommu.c |  859 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 874 insertions(+), 0 deletions(-)
 create mode 100644 drivers/iommu/exynos_iommu.c

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index b57b3fa..1c754cd 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -107,4 +107,18 @@ config INTR_REMAP
 	  To use x2apic mode in the CPU's which support x2APIC enhancements or
 	  to support platforms with CPU's having > 8 bit APIC ID, say Y.
 
+# EXYNOS IOMMU support
+config EXYNOS_IOMMU
+	bool "Exynos IOMMU Support"
+	depends on ARCH_EXYNOS4
+	select IOMMU_API
+	select EXYNOS4_DEV_SYSMMU
+	help
+	  Support for the IOMMUs (System MMUs) Samsung Exynos application
+	  processor family. This enables H/W multimedia accellerators to view
+	  non-linear physical memory chunks as a linear memory in their virtual
+	  address spaces.
+
+	  If unsure, say N here.
+
 endif # IOMMU_SUPPORT
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 4d4d77d..1eb924f 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -3,3 +3,4 @@ obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
 obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
 obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
 obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o
+obj-$(CONFIG_EXYNOS_IOMMU) += exynos_iommu.o
diff --git a/drivers/iommu/exynos_iommu.c b/drivers/iommu/exynos_iommu.c
new file mode 100644
index 0000000..fe5b5d8
--- /dev/null
+++ b/drivers/iommu/exynos_iommu.c
@@ -0,0 +1,859 @@
+/* linux/drivers/iommu/exynos_iommu.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/iommu.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+
+#include <asm/cacheflush.h>
+#include <asm/pgtable.h>
+
+#include <mach/map.h>
+#include <mach/regs-sysmmu.h>
+#include <mach/sysmmu.h>
+
+#define CTRL_ENABLE	0x5
+#define CTRL_BLOCK	0x7
+#define CTRL_DISABLE	0x0
+
+enum S5P_SYSMMU_INTERRUPT_TYPE {
+	SYSMMU_PAGEFAULT,
+	SYSMMU_AR_MULTIHIT,
+	SYSMMU_AW_MULTIHIT,
+	SYSMMU_BUSERROR,
+	SYSMMU_AR_SECURITY,
+	SYSMMU_AR_ACCESS,
+	SYSMMU_AW_SECURITY,
+	SYSMMU_AW_PROTECTION, /* 7 */
+	SYSMMU_FAULTS_NUM
+};
+
+static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
+	S5P_PAGE_FAULT_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR,
+	S5P_DEFAULT_SLAVE_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR
+};
+
+static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
+	"PAGE FAULT",
+	"AR MULTI-HIT FAULT",
+	"AW MULTI-HIT FAULT",
+	"BUS ERROR",
+	"AR SECURITY PROTECTION FAULT",
+	"AR ACCESS PROTECTION FAULT",
+	"AW SECURITY PROTECTION FAULT",
+	"AW ACCESS PROTECTION FAULT"
+};
+
+struct exynos_iommu_domain {
+	struct device *dev;
+	unsigned long *pgtable;
+	spinlock_t lock;
+	spinlock_t pgtablelock;
+};
+
+/* List of sysmmu_platdata */
+static LIST_HEAD(sysmmu_list);
+
+static inline struct sysmmu_platdata *get_sysmmu_data(struct device *owner,
+						struct sysmmu_platdata *start)
+{
+	struct list_head *pos, *head;
+
+	head = (start) ? &start->node : &sysmmu_list;
+
+	list_for_each(pos, head) {
+		struct sysmmu_platdata *mmudata =
+				container_of(pos, struct sysmmu_platdata, node);
+
+		if (pos == &sysmmu_list)
+			return NULL;
+
+		if (mmudata->owner == owner)
+			return mmudata;
+	}
+
+	return NULL;
+}
+
+static inline struct sysmmu_platdata *get_platdata(struct device *dev)
+{
+	return dev_get_platdata(dev);
+}
+
+static inline bool set_sysmmu_active(struct sysmmu_platdata *mmudata)
+{
+	/* return true if the System MMU was not active previously
+	   and it needs to be initialized */
+
+	return atomic_inc_return(&mmudata->activations) == 1;
+}
+
+static inline bool set_sysmmu_inactive(struct sysmmu_platdata *mmudata)
+{
+	/* return true if the System MMU is needed to be disabled */
+	int ref;
+
+	ref = atomic_dec_return(&mmudata->activations);
+
+	if (ref == 0)
+		return true;
+
+	if (WARN_ON(ref < 0)) {
+		/* System MMU is already disabled */
+		atomic_set(&mmudata->activations, 0);
+		ref = 0;
+	}
+
+	return false;
+}
+
+static inline bool is_sysmmu_active(struct sysmmu_platdata *mmudata)
+{
+	return atomic_read(&mmudata->activations) != 0;
+}
+
+static inline void sysmmu_block(void __iomem *sfrbase)
+{
+	__raw_writel(CTRL_BLOCK, sfrbase + S5P_MMU_CTRL);
+}
+
+static inline void sysmmu_unblock(void __iomem *sfrbase)
+{
+	__raw_writel(CTRL_ENABLE, sfrbase + S5P_MMU_CTRL);
+}
+
+static inline void __sysmmu_tlb_invalidate(void __iomem *sfrbase)
+{
+	__raw_writel(0x1, sfrbase + S5P_MMU_FLUSH);
+}
+
+static inline void __sysmmu_set_ptbase(void __iomem *sfrbase,
+				       unsigned long pgd)
+{
+	if (unlikely(pgd == 0)) {
+		pgd = (unsigned long)ZERO_PAGE(0);
+		__raw_writel(0x20, sfrbase + S5P_MMU_CFG); /* 4KB LV1 */
+	} else {
+		__raw_writel(0x0, sfrbase + S5P_MMU_CFG); /* 16KB LV1 */
+	}
+
+	__raw_writel(pgd, sfrbase + S5P_PT_BASE_ADDR);
+
+	__sysmmu_tlb_invalidate(sfrbase);
+}
+
+static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
+{
+	/* SYSMMU is in blocked when interrupt occurred. */
+	unsigned long addr;
+	struct sysmmu_platdata *mmudata = dev_id;
+	enum S5P_SYSMMU_INTERRUPT_TYPE itype;
+
+	WARN_ON(!is_sysmmu_active(mmudata));
+
+	itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
+		__ffs(__raw_readl(mmudata->sfrbase + S5P_INT_STATUS));
+
+	BUG_ON(!((itype >= 0) && (itype < 8)));
+
+	dev_alert(mmudata->dev, "SYSTEM MMU FAULT!!!.\n");
+
+	if (!mmudata->domain)
+		return IRQ_NONE;
+
+	addr = __raw_readl(mmudata->sfrbase + fault_reg_offset[itype]);
+
+	if (!report_iommu_fault(mmudata->domain, mmudata->owner, addr, itype)) {
+		__raw_writel(1 << itype, mmudata->sfrbase + S5P_INT_CLEAR);
+		dev_notice(mmudata->dev,
+				"%s is resolved. Retrying translation.\n",
+				sysmmu_fault_name[itype]);
+		sysmmu_unblock(mmudata->sfrbase);
+	} else {
+		dev_notice(mmudata->dev, "%s is not handled.\n",
+						sysmmu_fault_name[itype]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+void exynos_sysmmu_set_tablebase_pgd(struct device *owner, unsigned long pgd)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+
+	while ((mmudata = get_sysmmu_data(owner, mmudata))) {
+		if (is_sysmmu_active(mmudata)) {
+			sysmmu_block(mmudata->sfrbase);
+			__sysmmu_set_ptbase(mmudata->sfrbase, pgd);
+			sysmmu_unblock(mmudata->sfrbase);
+			dev_dbg(mmudata->dev, "New page table base is %p\n",
+								(void *)pgd);
+		} else {
+			dev_dbg(mmudata->dev,
+			"Disabled: Skipping setting page table base.\n");
+		}
+	}
+}
+
+int exynos_sysmmu_enable(struct iommu_domain *domain)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+	bool enabled = false;
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (!priv || !priv->dev)
+		return -EINVAL;
+
+	/* There are some devices that control more System MMUs than one such
+	 * as MFC.
+	 */
+	while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
+		enabled = true;
+
+		if (!set_sysmmu_active(mmudata)) {
+			dev_dbg(mmudata->dev, "Already enabled.\n");
+			continue;
+		}
+
+		pm_runtime_get_sync(mmudata->dev);
+
+		clk_enable(mmudata->clk);
+
+		__sysmmu_set_ptbase(mmudata->sfrbase, __pa(priv->pgtable));
+
+		__raw_writel(CTRL_ENABLE, mmudata->sfrbase + S5P_MMU_CTRL);
+
+		mmudata->domain = domain;
+
+		dev_dbg(mmudata->dev, "Enabled.\n");
+	}
+
+	return (enabled) ? 0 : -ENODEV;
+}
+
+void exynos_sysmmu_disable(struct iommu_domain *domain)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+	bool disabled = false;
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (!priv || !priv->dev)
+		return;
+
+	while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
+		disabled = true;
+
+		if (!set_sysmmu_inactive(mmudata)) {
+			dev_dbg(mmudata->dev,
+					"Inactivation request ignorred\n");
+			continue;
+		}
+
+		__raw_writel(CTRL_DISABLE, mmudata->sfrbase + S5P_MMU_CTRL);
+
+		clk_disable(mmudata->clk);
+
+		pm_runtime_put_sync(mmudata->dev);
+
+		mmudata->domain = NULL;
+
+		dev_dbg(mmudata->dev, "Disabled.\n");
+	}
+
+	BUG_ON(!disabled);
+}
+
+void exynos_sysmmu_tlb_invalidate(struct device *owner)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+
+	while ((mmudata = get_sysmmu_data(owner, mmudata))) {
+		if (is_sysmmu_active(mmudata)) {
+			sysmmu_block(mmudata->sfrbase);
+			__sysmmu_tlb_invalidate(mmudata->sfrbase);
+			sysmmu_unblock(mmudata->sfrbase);
+		} else {
+			dev_dbg(mmudata->dev,
+				"Disabled: Skipping invalidating TLB.\n");
+		}
+	}
+}
+
+static int exynos_sysmmu_probe(struct platform_device *pdev)
+{
+	struct resource *res, *ioarea;
+	int ret;
+	int irq;
+	struct device *dev;
+	void *sfr;
+
+	dev = &pdev->dev;
+	if (!get_platdata(dev) || (get_platdata(dev)->owner == NULL)) {
+		dev_err(dev, "Failed to probing system MMU: "
+						"Owner device is not set.");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev,
+			"Failed probing system MMU: failed to get resource.");
+		return -ENOENT;
+	}
+
+	ioarea = request_mem_region(res->start, resource_size(res), pdev->name);
+	if (ioarea == NULL) {
+		dev_err(dev, "Failed probing system MMU: "
+					"failed to request memory region.");
+		return -ENOMEM;
+	}
+
+	sfr = ioremap(res->start, resource_size(res));
+	if (!sfr) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to call ioremap().");
+		ret = -ENOENT;
+		goto err_ioremap;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq <= 0) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to get irq resource.");
+		ret = irq;
+		goto err_irq;
+	}
+
+	if (request_irq(irq, exynos_sysmmu_irq, 0, dev_name(&pdev->dev),
+						dev_get_platdata(dev))) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to request irq.");
+		ret = -ENOENT;
+		goto err_irq;
+	}
+
+	get_platdata(dev)->clk = clk_get(dev, "sysmmu");
+
+	if (IS_ERR_OR_NULL(get_platdata(dev)->clk)) {
+		dev_err(dev, "Failed to probing System MMU: "
+					"failed to get clock descriptor");
+		ret = -ENOENT;
+		goto err_clk;
+	}
+
+	get_platdata(dev)->sfrbase = sfr;
+
+	list_add(&get_platdata(dev)->node, &sysmmu_list);
+
+	if (dev->parent)
+		pm_runtime_enable(dev);
+
+	dev_dbg(dev, "Initialized for %s.\n",
+					dev_name(get_platdata(dev)->owner));
+	return 0;
+err_clk:
+	free_irq(irq, dev_get_platdata(dev));
+err_irq:
+	iounmap(get_platdata(dev)->sfrbase);
+err_ioremap:
+	release_resource(ioarea);
+	kfree(ioarea);
+	dev_err(dev, "Probing system MMU failed.");
+	return ret;
+}
+
+static int exynos_sysmmu_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+int exynos_sysmmu_runtime_suspend(struct device *dev)
+{
+	if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
+		return -EFAULT;
+
+	return 0;
+}
+
+int exynos_sysmmu_runtime_resume(struct device *dev)
+{
+	if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
+		return -EFAULT;
+
+	return 0;
+}
+
+const struct dev_pm_ops exynos_sysmmu_pm_ops = {
+	.runtime_suspend	= exynos_sysmmu_runtime_suspend,
+	.runtime_resume		= exynos_sysmmu_runtime_resume,
+};
+
+static struct platform_driver exynos_sysmmu_driver = {
+	.probe		= exynos_sysmmu_probe,
+	.remove		= exynos_sysmmu_remove,
+	.driver		= {
+		.owner		= THIS_MODULE,
+		.name		= "s5p-sysmmu",
+		.pm		= &exynos_sysmmu_pm_ops,
+	}
+};
+
+static int __init exynos_sysmmu_init(void)
+{
+	return platform_driver_register(&exynos_sysmmu_driver);
+}
+arch_initcall(exynos_sysmmu_init);
+
+/* We does not consider super section mapping (16MB) */
+#define S5P_SPAGE_SHIFT		12
+#define S5P_LPAGE_SHIFT		16
+#define S5P_SECTION_SHIFT	20
+
+#define S5P_SPAGE_SIZE		(1 << S5P_SPAGE_SHIFT)
+#define S5P_LPAGE_SIZE		(1 << S5P_LPAGE_SHIFT)
+#define S5P_SECTION_SIZE	(1 << S5P_SECTION_SHIFT)
+
+#define S5P_SPAGE_MASK		(~(S5P_SPAGE_SIZE - 1))
+#define S5P_LPAGE_MASK		(~(S5P_LPAGE_SIZE - 1))
+#define S5P_SECTION_MASK	(~(S5P_SECTION_SIZE - 1))
+
+#define S5P_SPAGE_ORDER		(S5P_SPAGE_SHIFT - PAGE_SHIFT)
+#define S5P_LPAGE_ORDER		(S5P_LPAGE_SHIFT - S5P_SPAGE_SHIFT)
+#define S5P_SECTION_ORDER	(S5P_SECTION_SHIFT - S5P_SPAGE_SHIFT)
+
+#define S5P_LV1TABLE_ENTRIES	(1 << (BITS_PER_LONG - S5P_SECTION_SHIFT))
+
+#define S5P_LV2TABLE_ENTRIES	(1 << S5P_SECTION_ORDER)
+#define S5P_LV2TABLE_SIZE	(S5P_LV2TABLE_ENTRIES * sizeof(long))
+#define S5P_LV2TABLE_MASK	(~(S5P_LV2TABLE_SIZE - 1)) /* 0xFFFFFC00 */
+
+#define S5P_SECTION_LV1_ENTRY(entry)	((entry & 0x40003) == 2)
+#define S5P_SUPSECT_LV1_ENTRY(entry)	((entry & 0x40003) == 0x40002)
+#define S5P_PAGE_LV1_ENTRY(entry)	((entry & 3) == 1)
+#define S5P_FAULT_LV1_ENTRY(entry) (((entry & 3) == 0) || (entry & 3) == 3)
+
+#define S5P_LPAGE_LV2_ENTRY(entry)	((entry & 3) == 1)
+#define S5P_SPAGE_LV2_ENTRY(entry)	((entry & 2) == 2)
+#define S5P_FAULT_LV2_ENTRY(entry)	((entry & 3) == 0)
+
+#define MAKE_FAULT_ENTRY(entry)		do { entry = 0; } while (0)
+#define MAKE_SECTION_ENTRY(entry, pa)	do { entry = pa | 2; } while (0)
+#define MAKE_SUPSECT_ENTRY(entry, pa)	do { entry = pa | 0x40002; } while (0)
+#define MAKE_LV2TABLE_ENTRY(entry, pa)	do { entry = pa | 1; } while (0)
+
+#define MAKE_LPAGE_ENTRY(entry, pa)	do { entry = pa | 1; } while (0)
+#define MAKE_SPAGE_ENTRY(entry, pa)	do { entry = pa | 3; } while (0)
+
+#define GET_LV2ENTRY(entry, iova) (\
+	(unsigned long *)phys_to_virt(entry & S5P_LV2TABLE_MASK) +\
+	((iova & (~S5P_SECTION_MASK)) >> S5P_SPAGE_SHIFT))
+
+/* slab cache for level 2 page tables */
+static struct kmem_cache *l2table_cachep;
+
+static inline void pgtable_flush(void *vastart, void *vaend)
+{
+	dmac_flush_range(vastart, vaend);
+	outer_flush_range(virt_to_phys(vastart),
+				virt_to_phys(vaend));
+}
+
+static int exynos_iommu_fault_handler(struct iommu_domain *domain,
+			struct device *dev, unsigned long iova, int flags)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
+				sysmmu_fault_name[flags], (void *)iova,
+				(void *)(__pa(priv->pgtable)));
+	dev_err(priv->dev, "\t\tGenerating Kernel OOPS...\n");
+	dev_err(priv->dev, "\t\tbecause it is unrecoverable.\n");
+	dev_err(priv->dev,
+		"\t\tSet Fault handler with iommu_set_fault_handler().\n");
+	dev_err(priv->dev, "\t\tto handle System MMU fault.\n");
+
+	BUG();
+
+	return 0;
+}
+
+static int exynos_iommu_domain_init(struct iommu_domain *domain)
+{
+	struct exynos_iommu_domain *priv;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL,
+		(S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
+	if (!priv->pgtable) {
+		kfree(priv);
+		return -ENOMEM;
+	}
+
+	memset(priv->pgtable, 0, S5P_LV1TABLE_ENTRIES * sizeof(unsigned long));
+	pgtable_flush(priv->pgtable, priv->pgtable + S5P_LV1TABLE_ENTRIES);
+
+	spin_lock_init(&priv->lock);
+
+	domain->priv = priv;
+
+	iommu_set_fault_handler(domain, &exynos_iommu_fault_handler);
+
+	return 0;
+}
+
+static void exynos_iommu_domain_destroy(struct iommu_domain *domain)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	free_pages((unsigned long)priv->pgtable,
+		(S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
+
+	kfree(priv);
+
+	domain->priv = NULL;
+}
+
+static int exynos_iommu_attach_device(struct iommu_domain *domain,
+				   struct device *dev)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	int ret;
+
+	spin_lock(&priv->lock);
+
+	priv->dev = dev;
+
+	ret = exynos_sysmmu_enable(domain);
+	if (ret)
+		return ret;
+
+	spin_unlock(&priv->lock);
+
+	return 0;
+}
+
+static void exynos_iommu_detach_device(struct iommu_domain *domain,
+				    struct device *dev)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	spin_lock(&priv->lock);
+
+	if (priv->dev == dev) {
+		exynos_sysmmu_disable(domain);
+		priv->dev = NULL;
+	}
+
+	spin_unlock(&priv->lock);
+}
+
+static bool section_available(struct iommu_domain *domain,
+			      unsigned long *lv1entry)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (S5P_SECTION_LV1_ENTRY(*lv1entry)) {
+		dev_err(priv->dev, "1MB entry alread exists at 0x%08x\n",
+				(lv1entry - priv->pgtable) * SZ_1M);
+		return false;
+	}
+
+	if (S5P_PAGE_LV1_ENTRY(*lv1entry)) {
+		unsigned long *lv2end, *lv2base;
+
+		lv2base = phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK);
+		lv2end = lv2base + S5P_LV2TABLE_ENTRIES;
+		while (lv2base != lv2end) {
+			if (!S5P_FAULT_LV2_ENTRY(*lv2base)) {
+				dev_err(priv->dev, "Failed to free L2 page "
+						"table for section mapping.\n");
+				return false;
+			}
+			lv2base++;
+		}
+
+		kmem_cache_free(l2table_cachep,
+				phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK));
+
+		MAKE_FAULT_ENTRY(*lv1entry);
+	}
+
+	return true;
+}
+
+static bool write_lpage(unsigned long *head_entry, unsigned long phys_addr)
+{
+	unsigned long *entry, *end;
+
+	entry = head_entry;
+	end = entry + (1 << S5P_LPAGE_ORDER);
+
+	while (entry != end) {
+		if (!S5P_FAULT_LV2_ENTRY(*entry))
+			break;
+
+		MAKE_LPAGE_ENTRY(*entry, phys_addr);
+
+		entry++;
+	}
+
+	if (entry != end) {
+		end = entry;
+		while (entry != head_entry)
+			MAKE_FAULT_ENTRY(*(--entry));
+
+		return false;
+	}
+
+	return true;
+}
+
+static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova,
+			 phys_addr_t paddr, int gfp_order, int prot)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *start_entry, *entry, *end_entry;
+	int num_entry;
+	int ret = 0;
+	unsigned long flags;
+
+	BUG_ON(priv->pgtable == NULL);
+
+	spin_lock_irqsave(&priv->pgtablelock, flags);
+
+	start_entry = entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (gfp_order >= S5P_SECTION_ORDER) {
+		BUG_ON((paddr | iova) & ~S5P_SECTION_MASK);
+		/* 1MiB mapping */
+
+		num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
+		end_entry = entry + num_entry;
+
+		while (entry != end_entry) {
+			if (!section_available(domain, entry))
+				break;
+
+			MAKE_SECTION_ENTRY(*entry, paddr);
+
+			paddr += S5P_SECTION_SIZE;
+			entry++;
+		}
+
+		if (entry != end_entry)
+			goto mapping_error;
+
+		pgtable_flush(start_entry, entry);
+		goto mapping_done;
+	}
+
+	if (S5P_FAULT_LV1_ENTRY(*entry)) {
+		unsigned long *l2table;
+
+		l2table = kmem_cache_zalloc(l2table_cachep, GFP_KERNEL);
+		if (!l2table) {
+			ret = -ENOMEM;
+			goto nomem_error;
+		}
+
+		pgtable_flush(entry, entry + S5P_LV2TABLE_ENTRIES);
+
+		MAKE_LV2TABLE_ENTRY(*entry, virt_to_phys(l2table));
+		pgtable_flush(entry, entry + 1);
+	}
+
+	/* 'entry' points level 2 entries, hereafter */
+	entry = GET_LV2ENTRY(*entry, iova);
+
+	start_entry = entry;
+	num_entry = 1 << gfp_order;
+	end_entry = entry + num_entry;
+
+	if (gfp_order >= S5P_LPAGE_ORDER) {
+		/* large page(64KiB) mapping */
+		BUG_ON((paddr | iova) & ~S5P_LPAGE_MASK);
+
+		while (entry != end_entry) {
+			if (!write_lpage(entry, paddr)) {
+				pr_err("%s: Failed to allocate large page"
+						" entry.\n", __func__);
+				break;
+			}
+
+			paddr += S5P_LPAGE_SIZE;
+			entry += (1 << S5P_LPAGE_ORDER);
+		}
+
+		if (entry != end_entry) {
+			entry -= 1 << S5P_LPAGE_ORDER;
+			goto mapping_error;
+		}
+	} else {
+		/* page (4KiB) mapping */
+		while (entry != end_entry && S5P_FAULT_LV2_ENTRY(*entry)) {
+
+			MAKE_SPAGE_ENTRY(*entry, paddr);
+
+			entry++;
+			paddr += S5P_SPAGE_SIZE;
+		}
+
+		if (entry != end_entry) {
+			pr_err("%s: Failed to allocate small page entry.\n",
+								__func__);
+			goto mapping_error;
+		}
+	}
+
+	pgtable_flush(start_entry, entry);
+mapping_error:
+	if (entry != end_entry) {
+		unsigned long *current_entry = entry;
+		while (entry != start_entry)
+			MAKE_FAULT_ENTRY(*(--entry));
+		pgtable_flush(start_entry, current_entry);
+		ret = -EADDRINUSE;
+	}
+
+nomem_error:
+mapping_done:
+	spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+	return 0;
+}
+
+static int exynos_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
+			   int gfp_order)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *entry;
+	int num_entry;
+	unsigned long flags;
+
+	BUG_ON(priv->pgtable == NULL);
+
+	spin_lock_irqsave(&priv->pgtablelock, flags);
+
+	entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (gfp_order >= S5P_SECTION_ORDER) {
+		gfp_order -= S5P_SECTION_ORDER;
+		num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
+		while (num_entry--) {
+			if (S5P_SECTION_LV1_ENTRY(*entry)) {
+				MAKE_FAULT_ENTRY(*entry);
+			} else if (S5P_PAGE_LV1_ENTRY(*entry)) {
+				unsigned long *lv2beg, *lv2end;
+				lv2beg = phys_to_virt(
+						*entry & S5P_LV2TABLE_MASK);
+				lv2end = lv2beg + S5P_LV2TABLE_ENTRIES;
+				while (lv2beg != lv2end) {
+					MAKE_FAULT_ENTRY(*lv2beg);
+					lv2beg++;
+				}
+			}
+			entry++;
+		}
+	} else {
+		entry = GET_LV2ENTRY(*entry, iova);
+
+		BUG_ON(S5P_LPAGE_LV2_ENTRY(*entry) &&
+						(gfp_order < S5P_LPAGE_ORDER));
+
+		num_entry = 1 << gfp_order;
+
+		while (num_entry--) {
+			MAKE_FAULT_ENTRY(*entry);
+			entry++;
+		}
+	}
+
+	if (priv->dev)
+		exynos_sysmmu_tlb_invalidate(priv->dev);
+
+	spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+	return 0;
+}
+
+static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain,
+					  unsigned long iova)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *entry;
+	unsigned long offset;
+
+	entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (S5P_FAULT_LV1_ENTRY(*entry))
+		return 0;
+
+	offset = iova & ~S5P_SECTION_MASK;
+
+	if (S5P_SECTION_LV1_ENTRY(*entry))
+		return (*entry & S5P_SECTION_MASK) + offset;
+
+	entry = GET_LV2ENTRY(*entry, iova);
+
+	if (S5P_SPAGE_LV2_ENTRY(*entry))
+		return (*entry & S5P_SPAGE_MASK) + (iova & ~S5P_SPAGE_MASK);
+
+	if (S5P_LPAGE_LV2_ENTRY(*entry))
+		return (*entry & S5P_LPAGE_MASK) + (iova & ~S5P_LPAGE_MASK);
+
+	return 0;
+}
+
+static int exynos_iommu_domain_has_cap(struct iommu_domain *domain,
+				    unsigned long cap)
+{
+	return 0;
+}
+
+static struct iommu_ops exynos_iommu_ops = {
+	.domain_init = &exynos_iommu_domain_init,
+	.domain_destroy = &exynos_iommu_domain_destroy,
+	.attach_dev = &exynos_iommu_attach_device,
+	.detach_dev = &exynos_iommu_detach_device,
+	.map = &exynos_iommu_map,
+	.unmap = &exynos_iommu_unmap,
+	.iova_to_phys = &exynos_iommu_iova_to_phys,
+	.domain_has_cap = &exynos_iommu_domain_has_cap,
+};
+
+static int __init exynos_iommu_init(void)
+{
+	l2table_cachep = kmem_cache_create("SysMMU Lv2 Tables",
+				S5P_LV2TABLE_SIZE, S5P_LV2TABLE_SIZE, 0, NULL);
+	if (!l2table_cachep)
+		return -ENOMEM;
+
+	register_iommu(&exynos_iommu_ops);
+
+	return 0;
+}
+arch_initcall(exynos_iommu_init);
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
@ 2011-09-24  7:30 조경호
  2011-09-26  7:59 ` Ohad Ben-Cohen
  0 siblings, 1 reply; 21+ messages in thread
From: 조경호 @ 2011-09-24  7:30 UTC (permalink / raw)
  To: linux-arm-kernel

This is the System MMU driver and IOMMU API implementation for
Exynos4 SOC platforms. Exynos4 platforms has more than 10 System
MMUs dedicated for each multimedia accellerators.

Signed-off-by: KyongHo Cho <pullip.cho@samsung.com>
---
 drivers/iommu/Kconfig        |   14 +
 drivers/iommu/Makefile       |    1 +
 drivers/iommu/exynos_iommu.c |  859 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 874 insertions(+), 0 deletions(-)
 create mode 100644 drivers/iommu/exynos_iommu.c

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index b57b3fa..1c754cd 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -107,4 +107,18 @@ config INTR_REMAP
 	  To use x2apic mode in the CPU's which support x2APIC enhancements or
 	  to support platforms with CPU's having > 8 bit APIC ID, say Y.
 
+# EXYNOS IOMMU support
+config EXYNOS_IOMMU
+	bool "Exynos IOMMU Support"
+	depends on ARCH_EXYNOS4
+	select IOMMU_API
+	select EXYNOS4_DEV_SYSMMU
+	help
+	  Support for the IOMMUs (System MMUs) Samsung Exynos application
+	  processor family. This enables H/W multimedia accellerators to view
+	  non-linear physical memory chunks as a linear memory in their virtual
+	  address spaces.
+
+	  If unsure, say N here.
+
 endif # IOMMU_SUPPORT
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 4d4d77d..1eb924f 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -3,3 +3,4 @@ obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
 obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
 obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
 obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o
+obj-$(CONFIG_EXYNOS_IOMMU) += exynos_iommu.o
diff --git a/drivers/iommu/exynos_iommu.c b/drivers/iommu/exynos_iommu.c
new file mode 100644
index 0000000..fe5b5d8
--- /dev/null
+++ b/drivers/iommu/exynos_iommu.c
@@ -0,0 +1,859 @@
+/* linux/drivers/iommu/exynos_iommu.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <linux/iommu.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+
+#include <asm/cacheflush.h>
+#include <asm/pgtable.h>
+
+#include <mach/map.h>
+#include <mach/regs-sysmmu.h>
+#include <mach/sysmmu.h>
+
+#define CTRL_ENABLE	0x5
+#define CTRL_BLOCK	0x7
+#define CTRL_DISABLE	0x0
+
+enum S5P_SYSMMU_INTERRUPT_TYPE {
+	SYSMMU_PAGEFAULT,
+	SYSMMU_AR_MULTIHIT,
+	SYSMMU_AW_MULTIHIT,
+	SYSMMU_BUSERROR,
+	SYSMMU_AR_SECURITY,
+	SYSMMU_AR_ACCESS,
+	SYSMMU_AW_SECURITY,
+	SYSMMU_AW_PROTECTION, /* 7 */
+	SYSMMU_FAULTS_NUM
+};
+
+static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
+	S5P_PAGE_FAULT_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR,
+	S5P_DEFAULT_SLAVE_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AR_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR,
+	S5P_AW_FAULT_ADDR
+};
+
+static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
+	"PAGE FAULT",
+	"AR MULTI-HIT FAULT",
+	"AW MULTI-HIT FAULT",
+	"BUS ERROR",
+	"AR SECURITY PROTECTION FAULT",
+	"AR ACCESS PROTECTION FAULT",
+	"AW SECURITY PROTECTION FAULT",
+	"AW ACCESS PROTECTION FAULT"
+};
+
+struct exynos_iommu_domain {
+	struct device *dev;
+	unsigned long *pgtable;
+	spinlock_t lock;
+	spinlock_t pgtablelock;
+};
+
+/* List of sysmmu_platdata */
+static LIST_HEAD(sysmmu_list);
+
+static inline struct sysmmu_platdata *get_sysmmu_data(struct device *owner,
+						struct sysmmu_platdata *start)
+{
+	struct list_head *pos, *head;
+
+	head = (start) ? &start->node : &sysmmu_list;
+
+	list_for_each(pos, head) {
+		struct sysmmu_platdata *mmudata =
+				container_of(pos, struct sysmmu_platdata, node);
+
+		if (pos == &sysmmu_list)
+			return NULL;
+
+		if (mmudata->owner == owner)
+			return mmudata;
+	}
+
+	return NULL;
+}
+
+static inline struct sysmmu_platdata *get_platdata(struct device *dev)
+{
+	return dev_get_platdata(dev);
+}
+
+static inline bool set_sysmmu_active(struct sysmmu_platdata *mmudata)
+{
+	/* return true if the System MMU was not active previously
+	   and it needs to be initialized */
+
+	return atomic_inc_return(&mmudata->activations) == 1;
+}
+
+static inline bool set_sysmmu_inactive(struct sysmmu_platdata *mmudata)
+{
+	/* return true if the System MMU is needed to be disabled */
+	int ref;
+
+	ref = atomic_dec_return(&mmudata->activations);
+
+	if (ref == 0)
+		return true;
+
+	if (WARN_ON(ref < 0)) {
+		/* System MMU is already disabled */
+		atomic_set(&mmudata->activations, 0);
+		ref = 0;
+	}
+
+	return false;
+}
+
+static inline bool is_sysmmu_active(struct sysmmu_platdata *mmudata)
+{
+	return atomic_read(&mmudata->activations) != 0;
+}
+
+static inline void sysmmu_block(void __iomem *sfrbase)
+{
+	__raw_writel(CTRL_BLOCK, sfrbase + S5P_MMU_CTRL);
+}
+
+static inline void sysmmu_unblock(void __iomem *sfrbase)
+{
+	__raw_writel(CTRL_ENABLE, sfrbase + S5P_MMU_CTRL);
+}
+
+static inline void __sysmmu_tlb_invalidate(void __iomem *sfrbase)
+{
+	__raw_writel(0x1, sfrbase + S5P_MMU_FLUSH);
+}
+
+static inline void __sysmmu_set_ptbase(void __iomem *sfrbase,
+				       unsigned long pgd)
+{
+	if (unlikely(pgd == 0)) {
+		pgd = (unsigned long)ZERO_PAGE(0);
+		__raw_writel(0x20, sfrbase + S5P_MMU_CFG); /* 4KB LV1 */
+	} else {
+		__raw_writel(0x0, sfrbase + S5P_MMU_CFG); /* 16KB LV1 */
+	}
+
+	__raw_writel(pgd, sfrbase + S5P_PT_BASE_ADDR);
+
+	__sysmmu_tlb_invalidate(sfrbase);
+}
+
+static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
+{
+	/* SYSMMU is in blocked when interrupt occurred. */
+	unsigned long addr;
+	struct sysmmu_platdata *mmudata = dev_id;
+	enum S5P_SYSMMU_INTERRUPT_TYPE itype;
+
+	WARN_ON(!is_sysmmu_active(mmudata));
+
+	itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
+		__ffs(__raw_readl(mmudata->sfrbase + S5P_INT_STATUS));
+
+	BUG_ON(!((itype >= 0) && (itype < 8)));
+
+	dev_alert(mmudata->dev, "SYSTEM MMU FAULT!!!.\n");
+
+	if (!mmudata->domain)
+		return IRQ_NONE;
+
+	addr = __raw_readl(mmudata->sfrbase + fault_reg_offset[itype]);
+
+	if (!report_iommu_fault(mmudata->domain, mmudata->owner, addr, itype)) {
+		__raw_writel(1 << itype, mmudata->sfrbase + S5P_INT_CLEAR);
+		dev_notice(mmudata->dev,
+				"%s is resolved. Retrying translation.\n",
+				sysmmu_fault_name[itype]);
+		sysmmu_unblock(mmudata->sfrbase);
+	} else {
+		dev_notice(mmudata->dev, "%s is not handled.\n",
+						sysmmu_fault_name[itype]);
+	}
+
+	return IRQ_HANDLED;
+}
+
+void exynos_sysmmu_set_tablebase_pgd(struct device *owner, unsigned long pgd)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+
+	while ((mmudata = get_sysmmu_data(owner, mmudata))) {
+		if (is_sysmmu_active(mmudata)) {
+			sysmmu_block(mmudata->sfrbase);
+			__sysmmu_set_ptbase(mmudata->sfrbase, pgd);
+			sysmmu_unblock(mmudata->sfrbase);
+			dev_dbg(mmudata->dev, "New page table base is %p\n",
+								(void *)pgd);
+		} else {
+			dev_dbg(mmudata->dev,
+			"Disabled: Skipping setting page table base.\n");
+		}
+	}
+}
+
+int exynos_sysmmu_enable(struct iommu_domain *domain)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+	bool enabled = false;
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (!priv || !priv->dev)
+		return -EINVAL;
+
+	/* There are some devices that control more System MMUs than one such
+	 * as MFC.
+	 */
+	while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
+		enabled = true;
+
+		if (!set_sysmmu_active(mmudata)) {
+			dev_dbg(mmudata->dev, "Already enabled.\n");
+			continue;
+		}
+
+		pm_runtime_get_sync(mmudata->dev);
+
+		clk_enable(mmudata->clk);
+
+		__sysmmu_set_ptbase(mmudata->sfrbase, __pa(priv->pgtable));
+
+		__raw_writel(CTRL_ENABLE, mmudata->sfrbase + S5P_MMU_CTRL);
+
+		mmudata->domain = domain;
+
+		dev_dbg(mmudata->dev, "Enabled.\n");
+	}
+
+	return (enabled) ? 0 : -ENODEV;
+}
+
+void exynos_sysmmu_disable(struct iommu_domain *domain)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+	bool disabled = false;
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (!priv || !priv->dev)
+		return;
+
+	while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
+		disabled = true;
+
+		if (!set_sysmmu_inactive(mmudata)) {
+			dev_dbg(mmudata->dev,
+					"Inactivation request ignorred\n");
+			continue;
+		}
+
+		__raw_writel(CTRL_DISABLE, mmudata->sfrbase + S5P_MMU_CTRL);
+
+		clk_disable(mmudata->clk);
+
+		pm_runtime_put_sync(mmudata->dev);
+
+		mmudata->domain = NULL;
+
+		dev_dbg(mmudata->dev, "Disabled.\n");
+	}
+
+	BUG_ON(!disabled);
+}
+
+void exynos_sysmmu_tlb_invalidate(struct device *owner)
+{
+	struct sysmmu_platdata *mmudata = NULL;
+
+	while ((mmudata = get_sysmmu_data(owner, mmudata))) {
+		if (is_sysmmu_active(mmudata)) {
+			sysmmu_block(mmudata->sfrbase);
+			__sysmmu_tlb_invalidate(mmudata->sfrbase);
+			sysmmu_unblock(mmudata->sfrbase);
+		} else {
+			dev_dbg(mmudata->dev,
+				"Disabled: Skipping invalidating TLB.\n");
+		}
+	}
+}
+
+static int exynos_sysmmu_probe(struct platform_device *pdev)
+{
+	struct resource *res, *ioarea;
+	int ret;
+	int irq;
+	struct device *dev;
+	void *sfr;
+
+	dev = &pdev->dev;
+	if (!get_platdata(dev) || (get_platdata(dev)->owner == NULL)) {
+		dev_err(dev, "Failed to probing system MMU: "
+						"Owner device is not set.");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev,
+			"Failed probing system MMU: failed to get resource.");
+		return -ENOENT;
+	}
+
+	ioarea = request_mem_region(res->start, resource_size(res), pdev->name);
+	if (ioarea == NULL) {
+		dev_err(dev, "Failed probing system MMU: "
+					"failed to request memory region.");
+		return -ENOMEM;
+	}
+
+	sfr = ioremap(res->start, resource_size(res));
+	if (!sfr) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to call ioremap().");
+		ret = -ENOENT;
+		goto err_ioremap;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq <= 0) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to get irq resource.");
+		ret = irq;
+		goto err_irq;
+	}
+
+	if (request_irq(irq, exynos_sysmmu_irq, 0, dev_name(&pdev->dev),
+						dev_get_platdata(dev))) {
+		dev_err(dev, "Failed probing system MMU: "
+						"failed to request irq.");
+		ret = -ENOENT;
+		goto err_irq;
+	}
+
+	get_platdata(dev)->clk = clk_get(dev, "sysmmu");
+
+	if (IS_ERR_OR_NULL(get_platdata(dev)->clk)) {
+		dev_err(dev, "Failed to probing System MMU: "
+					"failed to get clock descriptor");
+		ret = -ENOENT;
+		goto err_clk;
+	}
+
+	get_platdata(dev)->sfrbase = sfr;
+
+	list_add(&get_platdata(dev)->node, &sysmmu_list);
+
+	if (dev->parent)
+		pm_runtime_enable(dev);
+
+	dev_dbg(dev, "Initialized for %s.\n",
+					dev_name(get_platdata(dev)->owner));
+	return 0;
+err_clk:
+	free_irq(irq, dev_get_platdata(dev));
+err_irq:
+	iounmap(get_platdata(dev)->sfrbase);
+err_ioremap:
+	release_resource(ioarea);
+	kfree(ioarea);
+	dev_err(dev, "Probing system MMU failed.");
+	return ret;
+}
+
+static int exynos_sysmmu_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+int exynos_sysmmu_runtime_suspend(struct device *dev)
+{
+	if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
+		return -EFAULT;
+
+	return 0;
+}
+
+int exynos_sysmmu_runtime_resume(struct device *dev)
+{
+	if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
+		return -EFAULT;
+
+	return 0;
+}
+
+const struct dev_pm_ops exynos_sysmmu_pm_ops = {
+	.runtime_suspend	= exynos_sysmmu_runtime_suspend,
+	.runtime_resume		= exynos_sysmmu_runtime_resume,
+};
+
+static struct platform_driver exynos_sysmmu_driver = {
+	.probe		= exynos_sysmmu_probe,
+	.remove		= exynos_sysmmu_remove,
+	.driver		= {
+		.owner		= THIS_MODULE,
+		.name		= "s5p-sysmmu",
+		.pm		= &exynos_sysmmu_pm_ops,
+	}
+};
+
+static int __init exynos_sysmmu_init(void)
+{
+	return platform_driver_register(&exynos_sysmmu_driver);
+}
+arch_initcall(exynos_sysmmu_init);
+
+/* We does not consider super section mapping (16MB) */
+#define S5P_SPAGE_SHIFT		12
+#define S5P_LPAGE_SHIFT		16
+#define S5P_SECTION_SHIFT	20
+
+#define S5P_SPAGE_SIZE		(1 << S5P_SPAGE_SHIFT)
+#define S5P_LPAGE_SIZE		(1 << S5P_LPAGE_SHIFT)
+#define S5P_SECTION_SIZE	(1 << S5P_SECTION_SHIFT)
+
+#define S5P_SPAGE_MASK		(~(S5P_SPAGE_SIZE - 1))
+#define S5P_LPAGE_MASK		(~(S5P_LPAGE_SIZE - 1))
+#define S5P_SECTION_MASK	(~(S5P_SECTION_SIZE - 1))
+
+#define S5P_SPAGE_ORDER		(S5P_SPAGE_SHIFT - PAGE_SHIFT)
+#define S5P_LPAGE_ORDER		(S5P_LPAGE_SHIFT - S5P_SPAGE_SHIFT)
+#define S5P_SECTION_ORDER	(S5P_SECTION_SHIFT - S5P_SPAGE_SHIFT)
+
+#define S5P_LV1TABLE_ENTRIES	(1 << (BITS_PER_LONG - S5P_SECTION_SHIFT))
+
+#define S5P_LV2TABLE_ENTRIES	(1 << S5P_SECTION_ORDER)
+#define S5P_LV2TABLE_SIZE	(S5P_LV2TABLE_ENTRIES * sizeof(long))
+#define S5P_LV2TABLE_MASK	(~(S5P_LV2TABLE_SIZE - 1)) /* 0xFFFFFC00 */
+
+#define S5P_SECTION_LV1_ENTRY(entry)	((entry & 0x40003) == 2)
+#define S5P_SUPSECT_LV1_ENTRY(entry)	((entry & 0x40003) == 0x40002)
+#define S5P_PAGE_LV1_ENTRY(entry)	((entry & 3) == 1)
+#define S5P_FAULT_LV1_ENTRY(entry) (((entry & 3) == 0) || (entry & 3) == 3)
+
+#define S5P_LPAGE_LV2_ENTRY(entry)	((entry & 3) == 1)
+#define S5P_SPAGE_LV2_ENTRY(entry)	((entry & 2) == 2)
+#define S5P_FAULT_LV2_ENTRY(entry)	((entry & 3) == 0)
+
+#define MAKE_FAULT_ENTRY(entry)		do { entry = 0; } while (0)
+#define MAKE_SECTION_ENTRY(entry, pa)	do { entry = pa | 2; } while (0)
+#define MAKE_SUPSECT_ENTRY(entry, pa)	do { entry = pa | 0x40002; } while (0)
+#define MAKE_LV2TABLE_ENTRY(entry, pa)	do { entry = pa | 1; } while (0)
+
+#define MAKE_LPAGE_ENTRY(entry, pa)	do { entry = pa | 1; } while (0)
+#define MAKE_SPAGE_ENTRY(entry, pa)	do { entry = pa | 3; } while (0)
+
+#define GET_LV2ENTRY(entry, iova) (\
+	(unsigned long *)phys_to_virt(entry & S5P_LV2TABLE_MASK) +\
+	((iova & (~S5P_SECTION_MASK)) >> S5P_SPAGE_SHIFT))
+
+/* slab cache for level 2 page tables */
+static struct kmem_cache *l2table_cachep;
+
+static inline void pgtable_flush(void *vastart, void *vaend)
+{
+	dmac_flush_range(vastart, vaend);
+	outer_flush_range(virt_to_phys(vastart),
+				virt_to_phys(vaend));
+}
+
+static int exynos_iommu_fault_handler(struct iommu_domain *domain,
+			struct device *dev, unsigned long iova, int flags)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
+				sysmmu_fault_name[flags], (void *)iova,
+				(void *)(__pa(priv->pgtable)));
+	dev_err(priv->dev, "\t\tGenerating Kernel OOPS...\n");
+	dev_err(priv->dev, "\t\tbecause it is unrecoverable.\n");
+	dev_err(priv->dev,
+		"\t\tSet Fault handler with iommu_set_fault_handler().\n");
+	dev_err(priv->dev, "\t\tto handle System MMU fault.\n");
+
+	BUG();
+
+	return 0;
+}
+
+static int exynos_iommu_domain_init(struct iommu_domain *domain)
+{
+	struct exynos_iommu_domain *priv;
+
+	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL,
+		(S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
+	if (!priv->pgtable) {
+		kfree(priv);
+		return -ENOMEM;
+	}
+
+	memset(priv->pgtable, 0, S5P_LV1TABLE_ENTRIES * sizeof(unsigned long));
+	pgtable_flush(priv->pgtable, priv->pgtable + S5P_LV1TABLE_ENTRIES);
+
+	spin_lock_init(&priv->lock);
+
+	domain->priv = priv;
+
+	iommu_set_fault_handler(domain, &exynos_iommu_fault_handler);
+
+	return 0;
+}
+
+static void exynos_iommu_domain_destroy(struct iommu_domain *domain)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	free_pages((unsigned long)priv->pgtable,
+		(S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
+
+	kfree(priv);
+
+	domain->priv = NULL;
+}
+
+static int exynos_iommu_attach_device(struct iommu_domain *domain,
+				   struct device *dev)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	int ret;
+
+	spin_lock(&priv->lock);
+
+	priv->dev = dev;
+
+	ret = exynos_sysmmu_enable(domain);
+	if (ret)
+		return ret;
+
+	spin_unlock(&priv->lock);
+
+	return 0;
+}
+
+static void exynos_iommu_detach_device(struct iommu_domain *domain,
+				    struct device *dev)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	spin_lock(&priv->lock);
+
+	if (priv->dev == dev) {
+		exynos_sysmmu_disable(domain);
+		priv->dev = NULL;
+	}
+
+	spin_unlock(&priv->lock);
+}
+
+static bool section_available(struct iommu_domain *domain,
+			      unsigned long *lv1entry)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+
+	if (S5P_SECTION_LV1_ENTRY(*lv1entry)) {
+		dev_err(priv->dev, "1MB entry alread exists at 0x%08x\n",
+				(lv1entry - priv->pgtable) * SZ_1M);
+		return false;
+	}
+
+	if (S5P_PAGE_LV1_ENTRY(*lv1entry)) {
+		unsigned long *lv2end, *lv2base;
+
+		lv2base = phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK);
+		lv2end = lv2base + S5P_LV2TABLE_ENTRIES;
+		while (lv2base != lv2end) {
+			if (!S5P_FAULT_LV2_ENTRY(*lv2base)) {
+				dev_err(priv->dev, "Failed to free L2 page "
+						"table for section mapping.\n");
+				return false;
+			}
+			lv2base++;
+		}
+
+		kmem_cache_free(l2table_cachep,
+				phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK));
+
+		MAKE_FAULT_ENTRY(*lv1entry);
+	}
+
+	return true;
+}
+
+static bool write_lpage(unsigned long *head_entry, unsigned long phys_addr)
+{
+	unsigned long *entry, *end;
+
+	entry = head_entry;
+	end = entry + (1 << S5P_LPAGE_ORDER);
+
+	while (entry != end) {
+		if (!S5P_FAULT_LV2_ENTRY(*entry))
+			break;
+
+		MAKE_LPAGE_ENTRY(*entry, phys_addr);
+
+		entry++;
+	}
+
+	if (entry != end) {
+		end = entry;
+		while (entry != head_entry)
+			MAKE_FAULT_ENTRY(*(--entry));
+
+		return false;
+	}
+
+	return true;
+}
+
+static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova,
+			 phys_addr_t paddr, int gfp_order, int prot)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *start_entry, *entry, *end_entry;
+	int num_entry;
+	int ret = 0;
+	unsigned long flags;
+
+	BUG_ON(priv->pgtable == NULL);
+
+	spin_lock_irqsave(&priv->pgtablelock, flags);
+
+	start_entry = entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (gfp_order >= S5P_SECTION_ORDER) {
+		BUG_ON((paddr | iova) & ~S5P_SECTION_MASK);
+		/* 1MiB mapping */
+
+		num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
+		end_entry = entry + num_entry;
+
+		while (entry != end_entry) {
+			if (!section_available(domain, entry))
+				break;
+
+			MAKE_SECTION_ENTRY(*entry, paddr);
+
+			paddr += S5P_SECTION_SIZE;
+			entry++;
+		}
+
+		if (entry != end_entry)
+			goto mapping_error;
+
+		pgtable_flush(start_entry, entry);
+		goto mapping_done;
+	}
+
+	if (S5P_FAULT_LV1_ENTRY(*entry)) {
+		unsigned long *l2table;
+
+		l2table = kmem_cache_zalloc(l2table_cachep, GFP_KERNEL);
+		if (!l2table) {
+			ret = -ENOMEM;
+			goto nomem_error;
+		}
+
+		pgtable_flush(entry, entry + S5P_LV2TABLE_ENTRIES);
+
+		MAKE_LV2TABLE_ENTRY(*entry, virt_to_phys(l2table));
+		pgtable_flush(entry, entry + 1);
+	}
+
+	/* 'entry' points level 2 entries, hereafter */
+	entry = GET_LV2ENTRY(*entry, iova);
+
+	start_entry = entry;
+	num_entry = 1 << gfp_order;
+	end_entry = entry + num_entry;
+
+	if (gfp_order >= S5P_LPAGE_ORDER) {
+		/* large page(64KiB) mapping */
+		BUG_ON((paddr | iova) & ~S5P_LPAGE_MASK);
+
+		while (entry != end_entry) {
+			if (!write_lpage(entry, paddr)) {
+				pr_err("%s: Failed to allocate large page"
+						" entry.\n", __func__);
+				break;
+			}
+
+			paddr += S5P_LPAGE_SIZE;
+			entry += (1 << S5P_LPAGE_ORDER);
+		}
+
+		if (entry != end_entry) {
+			entry -= 1 << S5P_LPAGE_ORDER;
+			goto mapping_error;
+		}
+	} else {
+		/* page (4KiB) mapping */
+		while (entry != end_entry && S5P_FAULT_LV2_ENTRY(*entry)) {
+
+			MAKE_SPAGE_ENTRY(*entry, paddr);
+
+			entry++;
+			paddr += S5P_SPAGE_SIZE;
+		}
+
+		if (entry != end_entry) {
+			pr_err("%s: Failed to allocate small page entry.\n",
+								__func__);
+			goto mapping_error;
+		}
+	}
+
+	pgtable_flush(start_entry, entry);
+mapping_error:
+	if (entry != end_entry) {
+		unsigned long *current_entry = entry;
+		while (entry != start_entry)
+			MAKE_FAULT_ENTRY(*(--entry));
+		pgtable_flush(start_entry, current_entry);
+		ret = -EADDRINUSE;
+	}
+
+nomem_error:
+mapping_done:
+	spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+	return 0;
+}
+
+static int exynos_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
+			   int gfp_order)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *entry;
+	int num_entry;
+	unsigned long flags;
+
+	BUG_ON(priv->pgtable == NULL);
+
+	spin_lock_irqsave(&priv->pgtablelock, flags);
+
+	entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (gfp_order >= S5P_SECTION_ORDER) {
+		gfp_order -= S5P_SECTION_ORDER;
+		num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
+		while (num_entry--) {
+			if (S5P_SECTION_LV1_ENTRY(*entry)) {
+				MAKE_FAULT_ENTRY(*entry);
+			} else if (S5P_PAGE_LV1_ENTRY(*entry)) {
+				unsigned long *lv2beg, *lv2end;
+				lv2beg = phys_to_virt(
+						*entry & S5P_LV2TABLE_MASK);
+				lv2end = lv2beg + S5P_LV2TABLE_ENTRIES;
+				while (lv2beg != lv2end) {
+					MAKE_FAULT_ENTRY(*lv2beg);
+					lv2beg++;
+				}
+			}
+			entry++;
+		}
+	} else {
+		entry = GET_LV2ENTRY(*entry, iova);
+
+		BUG_ON(S5P_LPAGE_LV2_ENTRY(*entry) &&
+						(gfp_order < S5P_LPAGE_ORDER));
+
+		num_entry = 1 << gfp_order;
+
+		while (num_entry--) {
+			MAKE_FAULT_ENTRY(*entry);
+			entry++;
+		}
+	}
+
+	if (priv->dev)
+		exynos_sysmmu_tlb_invalidate(priv->dev);
+
+	spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+	return 0;
+}
+
+static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain,
+					  unsigned long iova)
+{
+	struct exynos_iommu_domain *priv = domain->priv;
+	unsigned long *entry;
+	unsigned long offset;
+
+	entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+	if (S5P_FAULT_LV1_ENTRY(*entry))
+		return 0;
+
+	offset = iova & ~S5P_SECTION_MASK;
+
+	if (S5P_SECTION_LV1_ENTRY(*entry))
+		return (*entry & S5P_SECTION_MASK) + offset;
+
+	entry = GET_LV2ENTRY(*entry, iova);
+
+	if (S5P_SPAGE_LV2_ENTRY(*entry))
+		return (*entry & S5P_SPAGE_MASK) + (iova & ~S5P_SPAGE_MASK);
+
+	if (S5P_LPAGE_LV2_ENTRY(*entry))
+		return (*entry & S5P_LPAGE_MASK) + (iova & ~S5P_LPAGE_MASK);
+
+	return 0;
+}
+
+static int exynos_iommu_domain_has_cap(struct iommu_domain *domain,
+				    unsigned long cap)
+{
+	return 0;
+}
+
+static struct iommu_ops exynos_iommu_ops = {
+	.domain_init = &exynos_iommu_domain_init,
+	.domain_destroy = &exynos_iommu_domain_destroy,
+	.attach_dev = &exynos_iommu_attach_device,
+	.detach_dev = &exynos_iommu_detach_device,
+	.map = &exynos_iommu_map,
+	.unmap = &exynos_iommu_unmap,
+	.iova_to_phys = &exynos_iommu_iova_to_phys,
+	.domain_has_cap = &exynos_iommu_domain_has_cap,
+};
+
+static int __init exynos_iommu_init(void)
+{
+	l2table_cachep = kmem_cache_create("SysMMU Lv2 Tables",
+				S5P_LV2TABLE_SIZE, S5P_LV2TABLE_SIZE, 0, NULL);
+	if (!l2table_cachep)
+		return -ENOMEM;
+
+	register_iommu(&exynos_iommu_ops);
+
+	return 0;
+}
+arch_initcall(exynos_iommu_init);
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
@ 2011-09-24  7:35 조경호
  2011-09-24  7:40 ` KyongHo Cho
  2011-09-24  9:38 ` Russell King - ARM Linux
  0 siblings, 2 replies; 21+ messages in thread
From: 조경호 @ 2011-09-24  7:35 UTC (permalink / raw)
  To: linux-arm-kernel

This is the System MMU driver and IOMMU API implementation for
Exynos4 SOC platforms. Exynos4 platforms has more than 10 System
MMUs dedicated for each multimedia accellerators.

Signed-off-by: KyongHo Cho 
---
drivers/iommu/Kconfig        |   14 +
drivers/iommu/Makefile       |    1 +
drivers/iommu/exynos_iommu.c |  859 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 874 insertions(+), 0 deletions(-)
create mode 100644 drivers/iommu/exynos_iommu.c

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index b57b3fa..1c754cd 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -107,4 +107,18 @@ config INTR_REMAP
  To use x2apic mode in the CPU's which support x2APIC enhancements or
  to support platforms with CPU's having > 8 bit APIC ID, say Y.

+# EXYNOS IOMMU support
+config EXYNOS_IOMMU
+ bool "Exynos IOMMU Support"
+ depends on ARCH_EXYNOS4
+ select IOMMU_API
+ select EXYNOS4_DEV_SYSMMU
+ help
+   Support for the IOMMUs (System MMUs) Samsung Exynos application
+   processor family. This enables H/W multimedia accellerators to view
+   non-linear physical memory chunks as a linear memory in their virtual
+   address spaces.
+
+   If unsure, say N here.
+
endif # IOMMU_SUPPORT
diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
index 4d4d77d..1eb924f 100644
--- a/drivers/iommu/Makefile
+++ b/drivers/iommu/Makefile
@@ -3,3 +3,4 @@ obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o
+obj-$(CONFIG_EXYNOS_IOMMU) += exynos_iommu.o
diff --git a/drivers/iommu/exynos_iommu.c b/drivers/iommu/exynos_iommu.c
new file mode 100644
index 0000000..fe5b5d8
--- /dev/null
+++ b/drivers/iommu/exynos_iommu.c
@@ -0,0 +1,859 @@
+/* linux/drivers/iommu/exynos_iommu.c
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd.
+ * http://www.samsung.com
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+#include 
+
+#include 
+#include 
+
+#include 
+#include 
+#include 
+
+#define CTRL_ENABLE 0x5
+#define CTRL_BLOCK 0x7
+#define CTRL_DISABLE 0x0
+
+enum S5P_SYSMMU_INTERRUPT_TYPE {
+ SYSMMU_PAGEFAULT,
+ SYSMMU_AR_MULTIHIT,
+ SYSMMU_AW_MULTIHIT,
+ SYSMMU_BUSERROR,
+ SYSMMU_AR_SECURITY,
+ SYSMMU_AR_ACCESS,
+ SYSMMU_AW_SECURITY,
+ SYSMMU_AW_PROTECTION, /* 7 */
+ SYSMMU_FAULTS_NUM
+};
+
+static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
+ S5P_PAGE_FAULT_ADDR,
+ S5P_AR_FAULT_ADDR,
+ S5P_AW_FAULT_ADDR,
+ S5P_DEFAULT_SLAVE_ADDR,
+ S5P_AR_FAULT_ADDR,
+ S5P_AR_FAULT_ADDR,
+ S5P_AW_FAULT_ADDR,
+ S5P_AW_FAULT_ADDR
+};
+
+static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
+ "PAGE FAULT",
+ "AR MULTI-HIT FAULT",
+ "AW MULTI-HIT FAULT",
+ "BUS ERROR",
+ "AR SECURITY PROTECTION FAULT",
+ "AR ACCESS PROTECTION FAULT",
+ "AW SECURITY PROTECTION FAULT",
+ "AW ACCESS PROTECTION FAULT"
+};
+
+struct exynos_iommu_domain {
+ struct device *dev;
+ unsigned long *pgtable;
+ spinlock_t lock;
+ spinlock_t pgtablelock;
+};
+
+/* List of sysmmu_platdata */
+static LIST_HEAD(sysmmu_list);
+
+static inline struct sysmmu_platdata *get_sysmmu_data(struct device *owner,
+ struct sysmmu_platdata *start)
+{
+ struct list_head *pos, *head;
+
+ head = (start) ? &start->node : &sysmmu_list;
+
+ list_for_each(pos, head) {
+ struct sysmmu_platdata *mmudata =
+ container_of(pos, struct sysmmu_platdata, node);
+
+ if (pos == &sysmmu_list)
+ return NULL;
+
+ if (mmudata->owner == owner)
+ return mmudata;
+ }
+
+ return NULL;
+}
+
+static inline struct sysmmu_platdata *get_platdata(struct device *dev)
+{
+ return dev_get_platdata(dev);
+}
+
+static inline bool set_sysmmu_active(struct sysmmu_platdata *mmudata)
+{
+ /* return true if the System MMU was not active previously
+    and it needs to be initialized */
+
+ return atomic_inc_return(&mmudata->activations) == 1;
+}
+
+static inline bool set_sysmmu_inactive(struct sysmmu_platdata *mmudata)
+{
+ /* return true if the System MMU is needed to be disabled */
+ int ref;
+
+ ref = atomic_dec_return(&mmudata->activations);
+
+ if (ref == 0)
+ return true;
+
+ if (WARN_ON(ref < 0)) {
+ /* System MMU is already disabled */
+ atomic_set(&mmudata->activations, 0);
+ ref = 0;
+ }
+
+ return false;
+}
+
+static inline bool is_sysmmu_active(struct sysmmu_platdata *mmudata)
+{
+ return atomic_read(&mmudata->activations) != 0;
+}
+
+static inline void sysmmu_block(void __iomem *sfrbase)
+{
+ __raw_writel(CTRL_BLOCK, sfrbase + S5P_MMU_CTRL);
+}
+
+static inline void sysmmu_unblock(void __iomem *sfrbase)
+{
+ __raw_writel(CTRL_ENABLE, sfrbase + S5P_MMU_CTRL);
+}
+
+static inline void __sysmmu_tlb_invalidate(void __iomem *sfrbase)
+{
+ __raw_writel(0x1, sfrbase + S5P_MMU_FLUSH);
+}
+
+static inline void __sysmmu_set_ptbase(void __iomem *sfrbase,
+        unsigned long pgd)
+{
+ if (unlikely(pgd == 0)) {
+ pgd = (unsigned long)ZERO_PAGE(0);
+ __raw_writel(0x20, sfrbase + S5P_MMU_CFG); /* 4KB LV1 */
+ } else {
+ __raw_writel(0x0, sfrbase + S5P_MMU_CFG); /* 16KB LV1 */
+ }
+
+ __raw_writel(pgd, sfrbase + S5P_PT_BASE_ADDR);
+
+ __sysmmu_tlb_invalidate(sfrbase);
+}
+
+static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
+{
+ /* SYSMMU is in blocked when interrupt occurred. */
+ unsigned long addr;
+ struct sysmmu_platdata *mmudata = dev_id;
+ enum S5P_SYSMMU_INTERRUPT_TYPE itype;
+
+ WARN_ON(!is_sysmmu_active(mmudata));
+
+ itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
+ __ffs(__raw_readl(mmudata->sfrbase + S5P_INT_STATUS));
+
+ BUG_ON(!((itype >= 0) && (itype < 8)));
+
+ dev_alert(mmudata->dev, "SYSTEM MMU FAULT!!!.\n");
+
+ if (!mmudata->domain)
+ return IRQ_NONE;
+
+ addr = __raw_readl(mmudata->sfrbase + fault_reg_offset[itype]);
+
+ if (!report_iommu_fault(mmudata->domain, mmudata->owner, addr, itype)) {
+ __raw_writel(1 << itype, mmudata->sfrbase + S5P_INT_CLEAR);
+ dev_notice(mmudata->dev,
+ "%s is resolved. Retrying translation.\n",
+ sysmmu_fault_name[itype]);
+ sysmmu_unblock(mmudata->sfrbase);
+ } else {
+ dev_notice(mmudata->dev, "%s is not handled.\n",
+ sysmmu_fault_name[itype]);
+ }
+
+ return IRQ_HANDLED;
+}
+
+void exynos_sysmmu_set_tablebase_pgd(struct device *owner, unsigned long pgd)
+{
+ struct sysmmu_platdata *mmudata = NULL;
+
+ while ((mmudata = get_sysmmu_data(owner, mmudata))) {
+ if (is_sysmmu_active(mmudata)) {
+ sysmmu_block(mmudata->sfrbase);
+ __sysmmu_set_ptbase(mmudata->sfrbase, pgd);
+ sysmmu_unblock(mmudata->sfrbase);
+ dev_dbg(mmudata->dev, "New page table base is %p\n",
+ (void *)pgd);
+ } else {
+ dev_dbg(mmudata->dev,
+ "Disabled: Skipping setting page table base.\n");
+ }
+ }
+}
+
+int exynos_sysmmu_enable(struct iommu_domain *domain)
+{
+ struct sysmmu_platdata *mmudata = NULL;
+ bool enabled = false;
+ struct exynos_iommu_domain *priv = domain->priv;
+
+ if (!priv || !priv->dev)
+ return -EINVAL;
+
+ /* There are some devices that control more System MMUs than one such
+ * as MFC.
+ */
+ while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
+ enabled = true;
+
+ if (!set_sysmmu_active(mmudata)) {
+ dev_dbg(mmudata->dev, "Already enabled.\n");
+ continue;
+ }
+
+ pm_runtime_get_sync(mmudata->dev);
+
+ clk_enable(mmudata->clk);
+
+ __sysmmu_set_ptbase(mmudata->sfrbase, __pa(priv->pgtable));
+
+ __raw_writel(CTRL_ENABLE, mmudata->sfrbase + S5P_MMU_CTRL);
+
+ mmudata->domain = domain;
+
+ dev_dbg(mmudata->dev, "Enabled.\n");
+ }
+
+ return (enabled) ? 0 : -ENODEV;
+}
+
+void exynos_sysmmu_disable(struct iommu_domain *domain)
+{
+ struct sysmmu_platdata *mmudata = NULL;
+ bool disabled = false;
+ struct exynos_iommu_domain *priv = domain->priv;
+
+ if (!priv || !priv->dev)
+ return;
+
+ while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
+ disabled = true;
+
+ if (!set_sysmmu_inactive(mmudata)) {
+ dev_dbg(mmudata->dev,
+ "Inactivation request ignorred\n");
+ continue;
+ }
+
+ __raw_writel(CTRL_DISABLE, mmudata->sfrbase + S5P_MMU_CTRL);
+
+ clk_disable(mmudata->clk);
+
+ pm_runtime_put_sync(mmudata->dev);
+
+ mmudata->domain = NULL;
+
+ dev_dbg(mmudata->dev, "Disabled.\n");
+ }
+
+ BUG_ON(!disabled);
+}
+
+void exynos_sysmmu_tlb_invalidate(struct device *owner)
+{
+ struct sysmmu_platdata *mmudata = NULL;
+
+ while ((mmudata = get_sysmmu_data(owner, mmudata))) {
+ if (is_sysmmu_active(mmudata)) {
+ sysmmu_block(mmudata->sfrbase);
+ __sysmmu_tlb_invalidate(mmudata->sfrbase);
+ sysmmu_unblock(mmudata->sfrbase);
+ } else {
+ dev_dbg(mmudata->dev,
+ "Disabled: Skipping invalidating TLB.\n");
+ }
+ }
+}
+
+static int exynos_sysmmu_probe(struct platform_device *pdev)
+{
+ struct resource *res, *ioarea;
+ int ret;
+ int irq;
+ struct device *dev;
+ void *sfr;
+
+ dev = &pdev->dev;
+ if (!get_platdata(dev) || (get_platdata(dev)->owner == NULL)) {
+ dev_err(dev, "Failed to probing system MMU: "
+ "Owner device is not set.");
+ return -ENXIO;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res) {
+ dev_err(dev,
+ "Failed probing system MMU: failed to get resource.");
+ return -ENOENT;
+ }
+
+ ioarea = request_mem_region(res->start, resource_size(res), pdev->name);
+ if (ioarea == NULL) {
+ dev_err(dev, "Failed probing system MMU: "
+ "failed to request memory region.");
+ return -ENOMEM;
+ }
+
+ sfr = ioremap(res->start, resource_size(res));
+ if (!sfr) {
+ dev_err(dev, "Failed probing system MMU: "
+ "failed to call ioremap().");
+ ret = -ENOENT;
+ goto err_ioremap;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq <= 0) {
+ dev_err(dev, "Failed probing system MMU: "
+ "failed to get irq resource.");
+ ret = irq;
+ goto err_irq;
+ }
+
+ if (request_irq(irq, exynos_sysmmu_irq, 0, dev_name(&pdev->dev),
+ dev_get_platdata(dev))) {
+ dev_err(dev, "Failed probing system MMU: "
+ "failed to request irq.");
+ ret = -ENOENT;
+ goto err_irq;
+ }
+
+ get_platdata(dev)->clk = clk_get(dev, "sysmmu");
+
+ if (IS_ERR_OR_NULL(get_platdata(dev)->clk)) {
+ dev_err(dev, "Failed to probing System MMU: "
+ "failed to get clock descriptor");
+ ret = -ENOENT;
+ goto err_clk;
+ }
+
+ get_platdata(dev)->sfrbase = sfr;
+
+ list_add(&get_platdata(dev)->node, &sysmmu_list);
+
+ if (dev->parent)
+ pm_runtime_enable(dev);
+
+ dev_dbg(dev, "Initialized for %s.\n",
+ dev_name(get_platdata(dev)->owner));
+ return 0;
+err_clk:
+ free_irq(irq, dev_get_platdata(dev));
+err_irq:
+ iounmap(get_platdata(dev)->sfrbase);
+err_ioremap:
+ release_resource(ioarea);
+ kfree(ioarea);
+ dev_err(dev, "Probing system MMU failed.");
+ return ret;
+}
+
+static int exynos_sysmmu_remove(struct platform_device *pdev)
+{
+ return 0;
+}
+
+int exynos_sysmmu_runtime_suspend(struct device *dev)
+{
+ if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
+ return -EFAULT;
+
+ return 0;
+}
+
+int exynos_sysmmu_runtime_resume(struct device *dev)
+{
+ if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
+ return -EFAULT;
+
+ return 0;
+}
+
+const struct dev_pm_ops exynos_sysmmu_pm_ops = {
+ .runtime_suspend = exynos_sysmmu_runtime_suspend,
+ .runtime_resume = exynos_sysmmu_runtime_resume,
+};
+
+static struct platform_driver exynos_sysmmu_driver = {
+ .probe = exynos_sysmmu_probe,
+ .remove = exynos_sysmmu_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "s5p-sysmmu",
+ .pm = &exynos_sysmmu_pm_ops,
+ }
+};
+
+static int __init exynos_sysmmu_init(void)
+{
+ return platform_driver_register(&exynos_sysmmu_driver);
+}
+arch_initcall(exynos_sysmmu_init);
+
+/* We does not consider super section mapping (16MB) */
+#define S5P_SPAGE_SHIFT 12
+#define S5P_LPAGE_SHIFT 16
+#define S5P_SECTION_SHIFT 20
+
+#define S5P_SPAGE_SIZE (1 << S5P_SPAGE_SHIFT)
+#define S5P_LPAGE_SIZE (1 << S5P_LPAGE_SHIFT)
+#define S5P_SECTION_SIZE (1 << S5P_SECTION_SHIFT)
+
+#define S5P_SPAGE_MASK (~(S5P_SPAGE_SIZE - 1))
+#define S5P_LPAGE_MASK (~(S5P_LPAGE_SIZE - 1))
+#define S5P_SECTION_MASK (~(S5P_SECTION_SIZE - 1))
+
+#define S5P_SPAGE_ORDER (S5P_SPAGE_SHIFT - PAGE_SHIFT)
+#define S5P_LPAGE_ORDER (S5P_LPAGE_SHIFT - S5P_SPAGE_SHIFT)
+#define S5P_SECTION_ORDER (S5P_SECTION_SHIFT - S5P_SPAGE_SHIFT)
+
+#define S5P_LV1TABLE_ENTRIES (1 << (BITS_PER_LONG - S5P_SECTION_SHIFT))
+
+#define S5P_LV2TABLE_ENTRIES (1 << S5P_SECTION_ORDER)
+#define S5P_LV2TABLE_SIZE (S5P_LV2TABLE_ENTRIES * sizeof(long))
+#define S5P_LV2TABLE_MASK (~(S5P_LV2TABLE_SIZE - 1)) /* 0xFFFFFC00 */
+
+#define S5P_SECTION_LV1_ENTRY(entry) ((entry & 0x40003) == 2)
+#define S5P_SUPSECT_LV1_ENTRY(entry) ((entry & 0x40003) == 0x40002)
+#define S5P_PAGE_LV1_ENTRY(entry) ((entry & 3) == 1)
+#define S5P_FAULT_LV1_ENTRY(entry) (((entry & 3) == 0) || (entry & 3) == 3)
+
+#define S5P_LPAGE_LV2_ENTRY(entry) ((entry & 3) == 1)
+#define S5P_SPAGE_LV2_ENTRY(entry) ((entry & 2) == 2)
+#define S5P_FAULT_LV2_ENTRY(entry) ((entry & 3) == 0)
+
+#define MAKE_FAULT_ENTRY(entry) do { entry = 0; } while (0)
+#define MAKE_SECTION_ENTRY(entry, pa) do { entry = pa | 2; } while (0)
+#define MAKE_SUPSECT_ENTRY(entry, pa) do { entry = pa | 0x40002; } while (0)
+#define MAKE_LV2TABLE_ENTRY(entry, pa) do { entry = pa | 1; } while (0)
+
+#define MAKE_LPAGE_ENTRY(entry, pa) do { entry = pa | 1; } while (0)
+#define MAKE_SPAGE_ENTRY(entry, pa) do { entry = pa | 3; } while (0)
+
+#define GET_LV2ENTRY(entry, iova) (\
+ (unsigned long *)phys_to_virt(entry & S5P_LV2TABLE_MASK) +\
+ ((iova & (~S5P_SECTION_MASK)) >> S5P_SPAGE_SHIFT))
+
+/* slab cache for level 2 page tables */
+static struct kmem_cache *l2table_cachep;
+
+static inline void pgtable_flush(void *vastart, void *vaend)
+{
+ dmac_flush_range(vastart, vaend);
+ outer_flush_range(virt_to_phys(vastart),
+ virt_to_phys(vaend));
+}
+
+static int exynos_iommu_fault_handler(struct iommu_domain *domain,
+ struct device *dev, unsigned long iova, int flags)
+{
+ struct exynos_iommu_domain *priv = domain->priv;
+
+ dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
+ sysmmu_fault_name[flags], (void *)iova,
+ (void *)(__pa(priv->pgtable)));
+ dev_err(priv->dev, "\t\tGenerating Kernel OOPS...\n");
+ dev_err(priv->dev, "\t\tbecause it is unrecoverable.\n");
+ dev_err(priv->dev,
+ "\t\tSet Fault handler with iommu_set_fault_handler().\n");
+ dev_err(priv->dev, "\t\tto handle System MMU fault.\n");
+
+ BUG();
+
+ return 0;
+}
+
+static int exynos_iommu_domain_init(struct iommu_domain *domain)
+{
+ struct exynos_iommu_domain *priv;
+
+ priv = kzalloc(sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL,
+ (S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
+ if (!priv->pgtable) {
+ kfree(priv);
+ return -ENOMEM;
+ }
+
+ memset(priv->pgtable, 0, S5P_LV1TABLE_ENTRIES * sizeof(unsigned long));
+ pgtable_flush(priv->pgtable, priv->pgtable + S5P_LV1TABLE_ENTRIES);
+
+ spin_lock_init(&priv->lock);
+
+ domain->priv = priv;
+
+ iommu_set_fault_handler(domain, &exynos_iommu_fault_handler);
+
+ return 0;
+}
+
+static void exynos_iommu_domain_destroy(struct iommu_domain *domain)
+{
+ struct exynos_iommu_domain *priv = domain->priv;
+
+ free_pages((unsigned long)priv->pgtable,
+ (S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
+
+ kfree(priv);
+
+ domain->priv = NULL;
+}
+
+static int exynos_iommu_attach_device(struct iommu_domain *domain,
+    struct device *dev)
+{
+ struct exynos_iommu_domain *priv = domain->priv;
+ int ret;
+
+ spin_lock(&priv->lock);
+
+ priv->dev = dev;
+
+ ret = exynos_sysmmu_enable(domain);
+ if (ret)
+ return ret;
+
+ spin_unlock(&priv->lock);
+
+ return 0;
+}
+
+static void exynos_iommu_detach_device(struct iommu_domain *domain,
+     struct device *dev)
+{
+ struct exynos_iommu_domain *priv = domain->priv;
+
+ spin_lock(&priv->lock);
+
+ if (priv->dev == dev) {
+ exynos_sysmmu_disable(domain);
+ priv->dev = NULL;
+ }
+
+ spin_unlock(&priv->lock);
+}
+
+static bool section_available(struct iommu_domain *domain,
+       unsigned long *lv1entry)
+{
+ struct exynos_iommu_domain *priv = domain->priv;
+
+ if (S5P_SECTION_LV1_ENTRY(*lv1entry)) {
+ dev_err(priv->dev, "1MB entry alread exists at 0x%08x\n",
+ (lv1entry - priv->pgtable) * SZ_1M);
+ return false;
+ }
+
+ if (S5P_PAGE_LV1_ENTRY(*lv1entry)) {
+ unsigned long *lv2end, *lv2base;
+
+ lv2base = phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK);
+ lv2end = lv2base + S5P_LV2TABLE_ENTRIES;
+ while (lv2base != lv2end) {
+ if (!S5P_FAULT_LV2_ENTRY(*lv2base)) {
+ dev_err(priv->dev, "Failed to free L2 page "
+ "table for section mapping.\n");
+ return false;
+ }
+ lv2base++;
+ }
+
+ kmem_cache_free(l2table_cachep,
+ phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK));
+
+ MAKE_FAULT_ENTRY(*lv1entry);
+ }
+
+ return true;
+}
+
+static bool write_lpage(unsigned long *head_entry, unsigned long phys_addr)
+{
+ unsigned long *entry, *end;
+
+ entry = head_entry;
+ end = entry + (1 << S5P_LPAGE_ORDER);
+
+ while (entry != end) {
+ if (!S5P_FAULT_LV2_ENTRY(*entry))
+ break;
+
+ MAKE_LPAGE_ENTRY(*entry, phys_addr);
+
+ entry++;
+ }
+
+ if (entry != end) {
+ end = entry;
+ while (entry != head_entry)
+ MAKE_FAULT_ENTRY(*(--entry));
+
+ return false;
+ }
+
+ return true;
+}
+
+static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova,
+ phys_addr_t paddr, int gfp_order, int prot)
+{
+ struct exynos_iommu_domain *priv = domain->priv;
+ unsigned long *start_entry, *entry, *end_entry;
+ int num_entry;
+ int ret = 0;
+ unsigned long flags;
+
+ BUG_ON(priv->pgtable == NULL);
+
+ spin_lock_irqsave(&priv->pgtablelock, flags);
+
+ start_entry = entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+ if (gfp_order >= S5P_SECTION_ORDER) {
+ BUG_ON((paddr | iova) & ~S5P_SECTION_MASK);
+ /* 1MiB mapping */
+
+ num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
+ end_entry = entry + num_entry;
+
+ while (entry != end_entry) {
+ if (!section_available(domain, entry))
+ break;
+
+ MAKE_SECTION_ENTRY(*entry, paddr);
+
+ paddr += S5P_SECTION_SIZE;
+ entry++;
+ }
+
+ if (entry != end_entry)
+ goto mapping_error;
+
+ pgtable_flush(start_entry, entry);
+ goto mapping_done;
+ }
+
+ if (S5P_FAULT_LV1_ENTRY(*entry)) {
+ unsigned long *l2table;
+
+ l2table = kmem_cache_zalloc(l2table_cachep, GFP_KERNEL);
+ if (!l2table) {
+ ret = -ENOMEM;
+ goto nomem_error;
+ }
+
+ pgtable_flush(entry, entry + S5P_LV2TABLE_ENTRIES);
+
+ MAKE_LV2TABLE_ENTRY(*entry, virt_to_phys(l2table));
+ pgtable_flush(entry, entry + 1);
+ }
+
+ /* 'entry' points level 2 entries, hereafter */
+ entry = GET_LV2ENTRY(*entry, iova);
+
+ start_entry = entry;
+ num_entry = 1 << gfp_order;
+ end_entry = entry + num_entry;
+
+ if (gfp_order >= S5P_LPAGE_ORDER) {
+ /* large page(64KiB) mapping */
+ BUG_ON((paddr | iova) & ~S5P_LPAGE_MASK);
+
+ while (entry != end_entry) {
+ if (!write_lpage(entry, paddr)) {
+ pr_err("%s: Failed to allocate large page"
+ " entry.\n", __func__);
+ break;
+ }
+
+ paddr += S5P_LPAGE_SIZE;
+ entry += (1 << S5P_LPAGE_ORDER);
+ }
+
+ if (entry != end_entry) {
+ entry -= 1 << S5P_LPAGE_ORDER;
+ goto mapping_error;
+ }
+ } else {
+ /* page (4KiB) mapping */
+ while (entry != end_entry && S5P_FAULT_LV2_ENTRY(*entry)) {
+
+ MAKE_SPAGE_ENTRY(*entry, paddr);
+
+ entry++;
+ paddr += S5P_SPAGE_SIZE;
+ }
+
+ if (entry != end_entry) {
+ pr_err("%s: Failed to allocate small page entry.\n",
+ __func__);
+ goto mapping_error;
+ }
+ }
+
+ pgtable_flush(start_entry, entry);
+mapping_error:
+ if (entry != end_entry) {
+ unsigned long *current_entry = entry;
+ while (entry != start_entry)
+ MAKE_FAULT_ENTRY(*(--entry));
+ pgtable_flush(start_entry, current_entry);
+ ret = -EADDRINUSE;
+ }
+
+nomem_error:
+mapping_done:
+ spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+ return 0;
+}
+
+static int exynos_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
+    int gfp_order)
+{
+ struct exynos_iommu_domain *priv = domain->priv;
+ unsigned long *entry;
+ int num_entry;
+ unsigned long flags;
+
+ BUG_ON(priv->pgtable == NULL);
+
+ spin_lock_irqsave(&priv->pgtablelock, flags);
+
+ entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+ if (gfp_order >= S5P_SECTION_ORDER) {
+ gfp_order -= S5P_SECTION_ORDER;
+ num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
+ while (num_entry--) {
+ if (S5P_SECTION_LV1_ENTRY(*entry)) {
+ MAKE_FAULT_ENTRY(*entry);
+ } else if (S5P_PAGE_LV1_ENTRY(*entry)) {
+ unsigned long *lv2beg, *lv2end;
+ lv2beg = phys_to_virt(
+ *entry & S5P_LV2TABLE_MASK);
+ lv2end = lv2beg + S5P_LV2TABLE_ENTRIES;
+ while (lv2beg != lv2end) {
+ MAKE_FAULT_ENTRY(*lv2beg);
+ lv2beg++;
+ }
+ }
+ entry++;
+ }
+ } else {
+ entry = GET_LV2ENTRY(*entry, iova);
+
+ BUG_ON(S5P_LPAGE_LV2_ENTRY(*entry) &&
+ (gfp_order < S5P_LPAGE_ORDER));
+
+ num_entry = 1 << gfp_order;
+
+ while (num_entry--) {
+ MAKE_FAULT_ENTRY(*entry);
+ entry++;
+ }
+ }
+
+ if (priv->dev)
+ exynos_sysmmu_tlb_invalidate(priv->dev);
+
+ spin_unlock_irqrestore(&priv->pgtablelock, flags);
+
+ return 0;
+}
+
+static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain,
+   unsigned long iova)
+{
+ struct exynos_iommu_domain *priv = domain->priv;
+ unsigned long *entry;
+ unsigned long offset;
+
+ entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
+
+ if (S5P_FAULT_LV1_ENTRY(*entry))
+ return 0;
+
+ offset = iova & ~S5P_SECTION_MASK;
+
+ if (S5P_SECTION_LV1_ENTRY(*entry))
+ return (*entry & S5P_SECTION_MASK) + offset;
+
+ entry = GET_LV2ENTRY(*entry, iova);
+
+ if (S5P_SPAGE_LV2_ENTRY(*entry))
+ return (*entry & S5P_SPAGE_MASK) + (iova & ~S5P_SPAGE_MASK);
+
+ if (S5P_LPAGE_LV2_ENTRY(*entry))
+ return (*entry & S5P_LPAGE_MASK) + (iova & ~S5P_LPAGE_MASK);
+
+ return 0;
+}
+
+static int exynos_iommu_domain_has_cap(struct iommu_domain *domain,
+     unsigned long cap)
+{
+ return 0;
+}
+
+static struct iommu_ops exynos_iommu_ops = {
+ .domain_init = &exynos_iommu_domain_init,
+ .domain_destroy = &exynos_iommu_domain_destroy,
+ .attach_dev = &exynos_iommu_attach_device,
+ .detach_dev = &exynos_iommu_detach_device,
+ .map = &exynos_iommu_map,
+ .unmap = &exynos_iommu_unmap,
+ .iova_to_phys = &exynos_iommu_iova_to_phys,
+ .domain_has_cap = &exynos_iommu_domain_has_cap,
+};
+
+static int __init exynos_iommu_init(void)
+{
+ l2table_cachep = kmem_cache_create("SysMMU Lv2 Tables",
+ S5P_LV2TABLE_SIZE, S5P_LV2TABLE_SIZE, 0, NULL);
+ if (!l2table_cachep)
+ return -ENOMEM;
+
+ register_iommu(&exynos_iommu_ops);
+
+ return 0;
+}
+arch_initcall(exynos_iommu_init);
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-24  7:35 조경호
@ 2011-09-24  7:40 ` KyongHo Cho
  2011-09-24  7:59   ` jiaweiwei
  2011-09-24  9:38 ` Russell King - ARM Linux
  1 sibling, 1 reply; 21+ messages in thread
From: KyongHo Cho @ 2011-09-24  7:40 UTC (permalink / raw)
  To: linux-arm-kernel

I am very sorry about the duplicate emails
and the second email of this patch is incorrect.
My email system may have some problem :(

Regards,

Cho KyongHo

2011/9/24 ??? <pullip.cho@samsung.com>:
> This is the System MMU driver and IOMMU API implementation for
> Exynos4 SOC platforms. Exynos4 platforms has more than 10 System
> MMUs dedicated for each multimedia accellerators.
>
> Signed-off-by: KyongHo Cho
> ---
> drivers/iommu/Kconfig        |   14 +
> drivers/iommu/Makefile       |    1 +
> drivers/iommu/exynos_iommu.c |  859 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 874 insertions(+), 0 deletions(-)
> create mode 100644 drivers/iommu/exynos_iommu.c
>
> diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
> index b57b3fa..1c754cd 100644
> --- a/drivers/iommu/Kconfig
> +++ b/drivers/iommu/Kconfig
> @@ -107,4 +107,18 @@ config INTR_REMAP
>  To use x2apic mode in the CPU's which support x2APIC enhancements or
>  to support platforms with CPU's having > 8 bit APIC ID, say Y.
>
> +# EXYNOS IOMMU support
> +config EXYNOS_IOMMU
> + bool "Exynos IOMMU Support"
> + depends on ARCH_EXYNOS4
> + select IOMMU_API
> + select EXYNOS4_DEV_SYSMMU
> + help
> +   Support for the IOMMUs (System MMUs) Samsung Exynos application
> +   processor family. This enables H/W multimedia accellerators to view
> +   non-linear physical memory chunks as a linear memory in their virtual
> +   address spaces.
> +
> +   If unsure, say N here.
> +
> endif # IOMMU_SUPPORT
> diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
> index 4d4d77d..1eb924f 100644
> --- a/drivers/iommu/Makefile
> +++ b/drivers/iommu/Makefile
> @@ -3,3 +3,4 @@ obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
> obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
> obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
> obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o
> +obj-$(CONFIG_EXYNOS_IOMMU) += exynos_iommu.o
> diff --git a/drivers/iommu/exynos_iommu.c b/drivers/iommu/exynos_iommu.c
> new file mode 100644
> index 0000000..fe5b5d8
> --- /dev/null
> +++ b/drivers/iommu/exynos_iommu.c
> @@ -0,0 +1,859 @@
> +/* linux/drivers/iommu/exynos_iommu.c
> + *
> + * Copyright (c) 2011 Samsung Electronics Co., Ltd.
> + * http://www.samsung.com
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +#include
> +
> +#include
> +#include
> +
> +#include
> +#include
> +#include
> +
> +#define CTRL_ENABLE 0x5
> +#define CTRL_BLOCK 0x7
> +#define CTRL_DISABLE 0x0
> +
> +enum S5P_SYSMMU_INTERRUPT_TYPE {
> + SYSMMU_PAGEFAULT,
> + SYSMMU_AR_MULTIHIT,
> + SYSMMU_AW_MULTIHIT,
> + SYSMMU_BUSERROR,
> + SYSMMU_AR_SECURITY,
> + SYSMMU_AR_ACCESS,
> + SYSMMU_AW_SECURITY,
> + SYSMMU_AW_PROTECTION, /* 7 */
> + SYSMMU_FAULTS_NUM
> +};
> +
> +static unsigned short fault_reg_offset[SYSMMU_FAULTS_NUM] = {
> + S5P_PAGE_FAULT_ADDR,
> + S5P_AR_FAULT_ADDR,
> + S5P_AW_FAULT_ADDR,
> + S5P_DEFAULT_SLAVE_ADDR,
> + S5P_AR_FAULT_ADDR,
> + S5P_AR_FAULT_ADDR,
> + S5P_AW_FAULT_ADDR,
> + S5P_AW_FAULT_ADDR
> +};
> +
> +static char *sysmmu_fault_name[SYSMMU_FAULTS_NUM] = {
> + "PAGE FAULT",
> + "AR MULTI-HIT FAULT",
> + "AW MULTI-HIT FAULT",
> + "BUS ERROR",
> + "AR SECURITY PROTECTION FAULT",
> + "AR ACCESS PROTECTION FAULT",
> + "AW SECURITY PROTECTION FAULT",
> + "AW ACCESS PROTECTION FAULT"
> +};
> +
> +struct exynos_iommu_domain {
> + struct device *dev;
> + unsigned long *pgtable;
> + spinlock_t lock;
> + spinlock_t pgtablelock;
> +};
> +
> +/* List of sysmmu_platdata */
> +static LIST_HEAD(sysmmu_list);
> +
> +static inline struct sysmmu_platdata *get_sysmmu_data(struct device *owner,
> + struct sysmmu_platdata *start)
> +{
> + struct list_head *pos, *head;
> +
> + head = (start) ? &start->node : &sysmmu_list;
> +
> + list_for_each(pos, head) {
> + struct sysmmu_platdata *mmudata =
> + container_of(pos, struct sysmmu_platdata, node);
> +
> + if (pos == &sysmmu_list)
> + return NULL;
> +
> + if (mmudata->owner == owner)
> + return mmudata;
> + }
> +
> + return NULL;
> +}
> +
> +static inline struct sysmmu_platdata *get_platdata(struct device *dev)
> +{
> + return dev_get_platdata(dev);
> +}
> +
> +static inline bool set_sysmmu_active(struct sysmmu_platdata *mmudata)
> +{
> + /* return true if the System MMU was not active previously
> +    and it needs to be initialized */
> +
> + return atomic_inc_return(&mmudata->activations) == 1;
> +}
> +
> +static inline bool set_sysmmu_inactive(struct sysmmu_platdata *mmudata)
> +{
> + /* return true if the System MMU is needed to be disabled */
> + int ref;
> +
> + ref = atomic_dec_return(&mmudata->activations);
> +
> + if (ref == 0)
> + return true;
> +
> + if (WARN_ON(ref < 0)) {
> + /* System MMU is already disabled */
> + atomic_set(&mmudata->activations, 0);
> + ref = 0;
> + }
> +
> + return false;
> +}
> +
> +static inline bool is_sysmmu_active(struct sysmmu_platdata *mmudata)
> +{
> + return atomic_read(&mmudata->activations) != 0;
> +}
> +
> +static inline void sysmmu_block(void __iomem *sfrbase)
> +{
> + __raw_writel(CTRL_BLOCK, sfrbase + S5P_MMU_CTRL);
> +}
> +
> +static inline void sysmmu_unblock(void __iomem *sfrbase)
> +{
> + __raw_writel(CTRL_ENABLE, sfrbase + S5P_MMU_CTRL);
> +}
> +
> +static inline void __sysmmu_tlb_invalidate(void __iomem *sfrbase)
> +{
> + __raw_writel(0x1, sfrbase + S5P_MMU_FLUSH);
> +}
> +
> +static inline void __sysmmu_set_ptbase(void __iomem *sfrbase,
> +        unsigned long pgd)
> +{
> + if (unlikely(pgd == 0)) {
> + pgd = (unsigned long)ZERO_PAGE(0);
> + __raw_writel(0x20, sfrbase + S5P_MMU_CFG); /* 4KB LV1 */
> + } else {
> + __raw_writel(0x0, sfrbase + S5P_MMU_CFG); /* 16KB LV1 */
> + }
> +
> + __raw_writel(pgd, sfrbase + S5P_PT_BASE_ADDR);
> +
> + __sysmmu_tlb_invalidate(sfrbase);
> +}
> +
> +static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
> +{
> + /* SYSMMU is in blocked when interrupt occurred. */
> + unsigned long addr;
> + struct sysmmu_platdata *mmudata = dev_id;
> + enum S5P_SYSMMU_INTERRUPT_TYPE itype;
> +
> + WARN_ON(!is_sysmmu_active(mmudata));
> +
> + itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
> + __ffs(__raw_readl(mmudata->sfrbase + S5P_INT_STATUS));
> +
> + BUG_ON(!((itype >= 0) && (itype < 8)));
> +
> + dev_alert(mmudata->dev, "SYSTEM MMU FAULT!!!.\n");
> +
> + if (!mmudata->domain)
> + return IRQ_NONE;
> +
> + addr = __raw_readl(mmudata->sfrbase + fault_reg_offset[itype]);
> +
> + if (!report_iommu_fault(mmudata->domain, mmudata->owner, addr, itype)) {
> + __raw_writel(1 << itype, mmudata->sfrbase + S5P_INT_CLEAR);
> + dev_notice(mmudata->dev,
> + "%s is resolved. Retrying translation.\n",
> + sysmmu_fault_name[itype]);
> + sysmmu_unblock(mmudata->sfrbase);
> + } else {
> + dev_notice(mmudata->dev, "%s is not handled.\n",
> + sysmmu_fault_name[itype]);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +void exynos_sysmmu_set_tablebase_pgd(struct device *owner, unsigned long pgd)
> +{
> + struct sysmmu_platdata *mmudata = NULL;
> +
> + while ((mmudata = get_sysmmu_data(owner, mmudata))) {
> + if (is_sysmmu_active(mmudata)) {
> + sysmmu_block(mmudata->sfrbase);
> + __sysmmu_set_ptbase(mmudata->sfrbase, pgd);
> + sysmmu_unblock(mmudata->sfrbase);
> + dev_dbg(mmudata->dev, "New page table base is %p\n",
> + (void *)pgd);
> + } else {
> + dev_dbg(mmudata->dev,
> + "Disabled: Skipping setting page table base.\n");
> + }
> + }
> +}
> +
> +int exynos_sysmmu_enable(struct iommu_domain *domain)
> +{
> + struct sysmmu_platdata *mmudata = NULL;
> + bool enabled = false;
> + struct exynos_iommu_domain *priv = domain->priv;
> +
> + if (!priv || !priv->dev)
> + return -EINVAL;
> +
> + /* There are some devices that control more System MMUs than one such
> + * as MFC.
> + */
> + while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
> + enabled = true;
> +
> + if (!set_sysmmu_active(mmudata)) {
> + dev_dbg(mmudata->dev, "Already enabled.\n");
> + continue;
> + }
> +
> + pm_runtime_get_sync(mmudata->dev);
> +
> + clk_enable(mmudata->clk);
> +
> + __sysmmu_set_ptbase(mmudata->sfrbase, __pa(priv->pgtable));
> +
> + __raw_writel(CTRL_ENABLE, mmudata->sfrbase + S5P_MMU_CTRL);
> +
> + mmudata->domain = domain;
> +
> + dev_dbg(mmudata->dev, "Enabled.\n");
> + }
> +
> + return (enabled) ? 0 : -ENODEV;
> +}
> +
> +void exynos_sysmmu_disable(struct iommu_domain *domain)
> +{
> + struct sysmmu_platdata *mmudata = NULL;
> + bool disabled = false;
> + struct exynos_iommu_domain *priv = domain->priv;
> +
> + if (!priv || !priv->dev)
> + return;
> +
> + while ((mmudata = get_sysmmu_data(priv->dev, mmudata))) {
> + disabled = true;
> +
> + if (!set_sysmmu_inactive(mmudata)) {
> + dev_dbg(mmudata->dev,
> + "Inactivation request ignorred\n");
> + continue;
> + }
> +
> + __raw_writel(CTRL_DISABLE, mmudata->sfrbase + S5P_MMU_CTRL);
> +
> + clk_disable(mmudata->clk);
> +
> + pm_runtime_put_sync(mmudata->dev);
> +
> + mmudata->domain = NULL;
> +
> + dev_dbg(mmudata->dev, "Disabled.\n");
> + }
> +
> + BUG_ON(!disabled);
> +}
> +
> +void exynos_sysmmu_tlb_invalidate(struct device *owner)
> +{
> + struct sysmmu_platdata *mmudata = NULL;
> +
> + while ((mmudata = get_sysmmu_data(owner, mmudata))) {
> + if (is_sysmmu_active(mmudata)) {
> + sysmmu_block(mmudata->sfrbase);
> + __sysmmu_tlb_invalidate(mmudata->sfrbase);
> + sysmmu_unblock(mmudata->sfrbase);
> + } else {
> + dev_dbg(mmudata->dev,
> + "Disabled: Skipping invalidating TLB.\n");
> + }
> + }
> +}
> +
> +static int exynos_sysmmu_probe(struct platform_device *pdev)
> +{
> + struct resource *res, *ioarea;
> + int ret;
> + int irq;
> + struct device *dev;
> + void *sfr;
> +
> + dev = &pdev->dev;
> + if (!get_platdata(dev) || (get_platdata(dev)->owner == NULL)) {
> + dev_err(dev, "Failed to probing system MMU: "
> + "Owner device is not set.");
> + return -ENXIO;
> + }
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res) {
> + dev_err(dev,
> + "Failed probing system MMU: failed to get resource.");
> + return -ENOENT;
> + }
> +
> + ioarea = request_mem_region(res->start, resource_size(res), pdev->name);
> + if (ioarea == NULL) {
> + dev_err(dev, "Failed probing system MMU: "
> + "failed to request memory region.");
> + return -ENOMEM;
> + }
> +
> + sfr = ioremap(res->start, resource_size(res));
> + if (!sfr) {
> + dev_err(dev, "Failed probing system MMU: "
> + "failed to call ioremap().");
> + ret = -ENOENT;
> + goto err_ioremap;
> + }
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq <= 0) {
> + dev_err(dev, "Failed probing system MMU: "
> + "failed to get irq resource.");
> + ret = irq;
> + goto err_irq;
> + }
> +
> + if (request_irq(irq, exynos_sysmmu_irq, 0, dev_name(&pdev->dev),
> + dev_get_platdata(dev))) {
> + dev_err(dev, "Failed probing system MMU: "
> + "failed to request irq.");
> + ret = -ENOENT;
> + goto err_irq;
> + }
> +
> + get_platdata(dev)->clk = clk_get(dev, "sysmmu");
> +
> + if (IS_ERR_OR_NULL(get_platdata(dev)->clk)) {
> + dev_err(dev, "Failed to probing System MMU: "
> + "failed to get clock descriptor");
> + ret = -ENOENT;
> + goto err_clk;
> + }
> +
> + get_platdata(dev)->sfrbase = sfr;
> +
> + list_add(&get_platdata(dev)->node, &sysmmu_list);
> +
> + if (dev->parent)
> + pm_runtime_enable(dev);
> +
> + dev_dbg(dev, "Initialized for %s.\n",
> + dev_name(get_platdata(dev)->owner));
> + return 0;
> +err_clk:
> + free_irq(irq, dev_get_platdata(dev));
> +err_irq:
> + iounmap(get_platdata(dev)->sfrbase);
> +err_ioremap:
> + release_resource(ioarea);
> + kfree(ioarea);
> + dev_err(dev, "Probing system MMU failed.");
> + return ret;
> +}
> +
> +static int exynos_sysmmu_remove(struct platform_device *pdev)
> +{
> + return 0;
> +}
> +
> +int exynos_sysmmu_runtime_suspend(struct device *dev)
> +{
> + if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +int exynos_sysmmu_runtime_resume(struct device *dev)
> +{
> + if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +const struct dev_pm_ops exynos_sysmmu_pm_ops = {
> + .runtime_suspend = exynos_sysmmu_runtime_suspend,
> + .runtime_resume = exynos_sysmmu_runtime_resume,
> +};
> +
> +static struct platform_driver exynos_sysmmu_driver = {
> + .probe = exynos_sysmmu_probe,
> + .remove = exynos_sysmmu_remove,
> + .driver = {
> + .owner = THIS_MODULE,
> + .name = "s5p-sysmmu",
> + .pm = &exynos_sysmmu_pm_ops,
> + }
> +};
> +
> +static int __init exynos_sysmmu_init(void)
> +{
> + return platform_driver_register(&exynos_sysmmu_driver);
> +}
> +arch_initcall(exynos_sysmmu_init);
> +
> +/* We does not consider super section mapping (16MB) */
> +#define S5P_SPAGE_SHIFT 12
> +#define S5P_LPAGE_SHIFT 16
> +#define S5P_SECTION_SHIFT 20
> +
> +#define S5P_SPAGE_SIZE (1 << S5P_SPAGE_SHIFT)
> +#define S5P_LPAGE_SIZE (1 << S5P_LPAGE_SHIFT)
> +#define S5P_SECTION_SIZE (1 << S5P_SECTION_SHIFT)
> +
> +#define S5P_SPAGE_MASK (~(S5P_SPAGE_SIZE - 1))
> +#define S5P_LPAGE_MASK (~(S5P_LPAGE_SIZE - 1))
> +#define S5P_SECTION_MASK (~(S5P_SECTION_SIZE - 1))
> +
> +#define S5P_SPAGE_ORDER (S5P_SPAGE_SHIFT - PAGE_SHIFT)
> +#define S5P_LPAGE_ORDER (S5P_LPAGE_SHIFT - S5P_SPAGE_SHIFT)
> +#define S5P_SECTION_ORDER (S5P_SECTION_SHIFT - S5P_SPAGE_SHIFT)
> +
> +#define S5P_LV1TABLE_ENTRIES (1 << (BITS_PER_LONG - S5P_SECTION_SHIFT))
> +
> +#define S5P_LV2TABLE_ENTRIES (1 << S5P_SECTION_ORDER)
> +#define S5P_LV2TABLE_SIZE (S5P_LV2TABLE_ENTRIES * sizeof(long))
> +#define S5P_LV2TABLE_MASK (~(S5P_LV2TABLE_SIZE - 1)) /* 0xFFFFFC00 */
> +
> +#define S5P_SECTION_LV1_ENTRY(entry) ((entry & 0x40003) == 2)
> +#define S5P_SUPSECT_LV1_ENTRY(entry) ((entry & 0x40003) == 0x40002)
> +#define S5P_PAGE_LV1_ENTRY(entry) ((entry & 3) == 1)
> +#define S5P_FAULT_LV1_ENTRY(entry) (((entry & 3) == 0) || (entry & 3) == 3)
> +
> +#define S5P_LPAGE_LV2_ENTRY(entry) ((entry & 3) == 1)
> +#define S5P_SPAGE_LV2_ENTRY(entry) ((entry & 2) == 2)
> +#define S5P_FAULT_LV2_ENTRY(entry) ((entry & 3) == 0)
> +
> +#define MAKE_FAULT_ENTRY(entry) do { entry = 0; } while (0)
> +#define MAKE_SECTION_ENTRY(entry, pa) do { entry = pa | 2; } while (0)
> +#define MAKE_SUPSECT_ENTRY(entry, pa) do { entry = pa | 0x40002; } while (0)
> +#define MAKE_LV2TABLE_ENTRY(entry, pa) do { entry = pa | 1; } while (0)
> +
> +#define MAKE_LPAGE_ENTRY(entry, pa) do { entry = pa | 1; } while (0)
> +#define MAKE_SPAGE_ENTRY(entry, pa) do { entry = pa | 3; } while (0)
> +
> +#define GET_LV2ENTRY(entry, iova) (\
> + (unsigned long *)phys_to_virt(entry & S5P_LV2TABLE_MASK) +\
> + ((iova & (~S5P_SECTION_MASK)) >> S5P_SPAGE_SHIFT))
> +
> +/* slab cache for level 2 page tables */
> +static struct kmem_cache *l2table_cachep;
> +
> +static inline void pgtable_flush(void *vastart, void *vaend)
> +{
> + dmac_flush_range(vastart, vaend);
> + outer_flush_range(virt_to_phys(vastart),
> + virt_to_phys(vaend));
> +}
> +
> +static int exynos_iommu_fault_handler(struct iommu_domain *domain,
> + struct device *dev, unsigned long iova, int flags)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> +
> + dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
> + sysmmu_fault_name[flags], (void *)iova,
> + (void *)(__pa(priv->pgtable)));
> + dev_err(priv->dev, "\t\tGenerating Kernel OOPS...\n");
> + dev_err(priv->dev, "\t\tbecause it is unrecoverable.\n");
> + dev_err(priv->dev,
> + "\t\tSet Fault handler with iommu_set_fault_handler().\n");
> + dev_err(priv->dev, "\t\tto handle System MMU fault.\n");
> +
> + BUG();
> +
> + return 0;
> +}
> +
> +static int exynos_iommu_domain_init(struct iommu_domain *domain)
> +{
> + struct exynos_iommu_domain *priv;
> +
> + priv = kzalloc(sizeof(*priv), GFP_KERNEL);
> + if (!priv)
> + return -ENOMEM;
> +
> + priv->pgtable = (unsigned long *)__get_free_pages(GFP_KERNEL,
> + (S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
> + if (!priv->pgtable) {
> + kfree(priv);
> + return -ENOMEM;
> + }
> +
> + memset(priv->pgtable, 0, S5P_LV1TABLE_ENTRIES * sizeof(unsigned long));
> + pgtable_flush(priv->pgtable, priv->pgtable + S5P_LV1TABLE_ENTRIES);
> +
> + spin_lock_init(&priv->lock);
> +
> + domain->priv = priv;
> +
> + iommu_set_fault_handler(domain, &exynos_iommu_fault_handler);
> +
> + return 0;
> +}
> +
> +static void exynos_iommu_domain_destroy(struct iommu_domain *domain)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> +
> + free_pages((unsigned long)priv->pgtable,
> + (S5P_LV1TABLE_ENTRIES * sizeof(unsigned long)) >> PAGE_SHIFT);
> +
> + kfree(priv);
> +
> + domain->priv = NULL;
> +}
> +
> +static int exynos_iommu_attach_device(struct iommu_domain *domain,
> +    struct device *dev)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> + int ret;
> +
> + spin_lock(&priv->lock);
> +
> + priv->dev = dev;
> +
> + ret = exynos_sysmmu_enable(domain);
> + if (ret)
> + return ret;
> +
> + spin_unlock(&priv->lock);
> +
> + return 0;
> +}
> +
> +static void exynos_iommu_detach_device(struct iommu_domain *domain,
> +     struct device *dev)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> +
> + spin_lock(&priv->lock);
> +
> + if (priv->dev == dev) {
> + exynos_sysmmu_disable(domain);
> + priv->dev = NULL;
> + }
> +
> + spin_unlock(&priv->lock);
> +}
> +
> +static bool section_available(struct iommu_domain *domain,
> +       unsigned long *lv1entry)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> +
> + if (S5P_SECTION_LV1_ENTRY(*lv1entry)) {
> + dev_err(priv->dev, "1MB entry alread exists at 0x%08x\n",
> + (lv1entry - priv->pgtable) * SZ_1M);
> + return false;
> + }
> +
> + if (S5P_PAGE_LV1_ENTRY(*lv1entry)) {
> + unsigned long *lv2end, *lv2base;
> +
> + lv2base = phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK);
> + lv2end = lv2base + S5P_LV2TABLE_ENTRIES;
> + while (lv2base != lv2end) {
> + if (!S5P_FAULT_LV2_ENTRY(*lv2base)) {
> + dev_err(priv->dev, "Failed to free L2 page "
> + "table for section mapping.\n");
> + return false;
> + }
> + lv2base++;
> + }
> +
> + kmem_cache_free(l2table_cachep,
> + phys_to_virt(*lv1entry & S5P_LV2TABLE_MASK));
> +
> + MAKE_FAULT_ENTRY(*lv1entry);
> + }
> +
> + return true;
> +}
> +
> +static bool write_lpage(unsigned long *head_entry, unsigned long phys_addr)
> +{
> + unsigned long *entry, *end;
> +
> + entry = head_entry;
> + end = entry + (1 << S5P_LPAGE_ORDER);
> +
> + while (entry != end) {
> + if (!S5P_FAULT_LV2_ENTRY(*entry))
> + break;
> +
> + MAKE_LPAGE_ENTRY(*entry, phys_addr);
> +
> + entry++;
> + }
> +
> + if (entry != end) {
> + end = entry;
> + while (entry != head_entry)
> + MAKE_FAULT_ENTRY(*(--entry));
> +
> + return false;
> + }
> +
> + return true;
> +}
> +
> +static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova,
> + phys_addr_t paddr, int gfp_order, int prot)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> + unsigned long *start_entry, *entry, *end_entry;
> + int num_entry;
> + int ret = 0;
> + unsigned long flags;
> +
> + BUG_ON(priv->pgtable == NULL);
> +
> + spin_lock_irqsave(&priv->pgtablelock, flags);
> +
> + start_entry = entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
> +
> + if (gfp_order >= S5P_SECTION_ORDER) {
> + BUG_ON((paddr | iova) & ~S5P_SECTION_MASK);
> + /* 1MiB mapping */
> +
> + num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
> + end_entry = entry + num_entry;
> +
> + while (entry != end_entry) {
> + if (!section_available(domain, entry))
> + break;
> +
> + MAKE_SECTION_ENTRY(*entry, paddr);
> +
> + paddr += S5P_SECTION_SIZE;
> + entry++;
> + }
> +
> + if (entry != end_entry)
> + goto mapping_error;
> +
> + pgtable_flush(start_entry, entry);
> + goto mapping_done;
> + }
> +
> + if (S5P_FAULT_LV1_ENTRY(*entry)) {
> + unsigned long *l2table;
> +
> + l2table = kmem_cache_zalloc(l2table_cachep, GFP_KERNEL);
> + if (!l2table) {
> + ret = -ENOMEM;
> + goto nomem_error;
> + }
> +
> + pgtable_flush(entry, entry + S5P_LV2TABLE_ENTRIES);
> +
> + MAKE_LV2TABLE_ENTRY(*entry, virt_to_phys(l2table));
> + pgtable_flush(entry, entry + 1);
> + }
> +
> + /* 'entry' points level 2 entries, hereafter */
> + entry = GET_LV2ENTRY(*entry, iova);
> +
> + start_entry = entry;
> + num_entry = 1 << gfp_order;
> + end_entry = entry + num_entry;
> +
> + if (gfp_order >= S5P_LPAGE_ORDER) {
> + /* large page(64KiB) mapping */
> + BUG_ON((paddr | iova) & ~S5P_LPAGE_MASK);
> +
> + while (entry != end_entry) {
> + if (!write_lpage(entry, paddr)) {
> + pr_err("%s: Failed to allocate large page"
> + " entry.\n", __func__);
> + break;
> + }
> +
> + paddr += S5P_LPAGE_SIZE;
> + entry += (1 << S5P_LPAGE_ORDER);
> + }
> +
> + if (entry != end_entry) {
> + entry -= 1 << S5P_LPAGE_ORDER;
> + goto mapping_error;
> + }
> + } else {
> + /* page (4KiB) mapping */
> + while (entry != end_entry && S5P_FAULT_LV2_ENTRY(*entry)) {
> +
> + MAKE_SPAGE_ENTRY(*entry, paddr);
> +
> + entry++;
> + paddr += S5P_SPAGE_SIZE;
> + }
> +
> + if (entry != end_entry) {
> + pr_err("%s: Failed to allocate small page entry.\n",
> + __func__);
> + goto mapping_error;
> + }
> + }
> +
> + pgtable_flush(start_entry, entry);
> +mapping_error:
> + if (entry != end_entry) {
> + unsigned long *current_entry = entry;
> + while (entry != start_entry)
> + MAKE_FAULT_ENTRY(*(--entry));
> + pgtable_flush(start_entry, current_entry);
> + ret = -EADDRINUSE;
> + }
> +
> +nomem_error:
> +mapping_done:
> + spin_unlock_irqrestore(&priv->pgtablelock, flags);
> +
> + return 0;
> +}
> +
> +static int exynos_iommu_unmap(struct iommu_domain *domain, unsigned long iova,
> +    int gfp_order)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> + unsigned long *entry;
> + int num_entry;
> + unsigned long flags;
> +
> + BUG_ON(priv->pgtable == NULL);
> +
> + spin_lock_irqsave(&priv->pgtablelock, flags);
> +
> + entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
> +
> + if (gfp_order >= S5P_SECTION_ORDER) {
> + gfp_order -= S5P_SECTION_ORDER;
> + num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
> + while (num_entry--) {
> + if (S5P_SECTION_LV1_ENTRY(*entry)) {
> + MAKE_FAULT_ENTRY(*entry);
> + } else if (S5P_PAGE_LV1_ENTRY(*entry)) {
> + unsigned long *lv2beg, *lv2end;
> + lv2beg = phys_to_virt(
> + *entry & S5P_LV2TABLE_MASK);
> + lv2end = lv2beg + S5P_LV2TABLE_ENTRIES;
> + while (lv2beg != lv2end) {
> + MAKE_FAULT_ENTRY(*lv2beg);
> + lv2beg++;
> + }
> + }
> + entry++;
> + }
> + } else {
> + entry = GET_LV2ENTRY(*entry, iova);
> +
> + BUG_ON(S5P_LPAGE_LV2_ENTRY(*entry) &&
> + (gfp_order < S5P_LPAGE_ORDER));
> +
> + num_entry = 1 << gfp_order;
> +
> + while (num_entry--) {
> + MAKE_FAULT_ENTRY(*entry);
> + entry++;
> + }
> + }
> +
> + if (priv->dev)
> + exynos_sysmmu_tlb_invalidate(priv->dev);
> +
> + spin_unlock_irqrestore(&priv->pgtablelock, flags);
> +
> + return 0;
> +}
> +
> +static phys_addr_t exynos_iommu_iova_to_phys(struct iommu_domain *domain,
> +   unsigned long iova)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> + unsigned long *entry;
> + unsigned long offset;
> +
> + entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
> +
> + if (S5P_FAULT_LV1_ENTRY(*entry))
> + return 0;
> +
> + offset = iova & ~S5P_SECTION_MASK;
> +
> + if (S5P_SECTION_LV1_ENTRY(*entry))
> + return (*entry & S5P_SECTION_MASK) + offset;
> +
> + entry = GET_LV2ENTRY(*entry, iova);
> +
> + if (S5P_SPAGE_LV2_ENTRY(*entry))
> + return (*entry & S5P_SPAGE_MASK) + (iova & ~S5P_SPAGE_MASK);
> +
> + if (S5P_LPAGE_LV2_ENTRY(*entry))
> + return (*entry & S5P_LPAGE_MASK) + (iova & ~S5P_LPAGE_MASK);
> +
> + return 0;
> +}
> +
> +static int exynos_iommu_domain_has_cap(struct iommu_domain *domain,
> +     unsigned long cap)
> +{
> + return 0;
> +}
> +
> +static struct iommu_ops exynos_iommu_ops = {
> + .domain_init = &exynos_iommu_domain_init,
> + .domain_destroy = &exynos_iommu_domain_destroy,
> + .attach_dev = &exynos_iommu_attach_device,
> + .detach_dev = &exynos_iommu_detach_device,
> + .map = &exynos_iommu_map,
> + .unmap = &exynos_iommu_unmap,
> + .iova_to_phys = &exynos_iommu_iova_to_phys,
> + .domain_has_cap = &exynos_iommu_domain_has_cap,
> +};
> +
> +static int __init exynos_iommu_init(void)
> +{
> + l2table_cachep = kmem_cache_create("SysMMU Lv2 Tables",
> + S5P_LV2TABLE_SIZE, S5P_LV2TABLE_SIZE, 0, NULL);
> + if (!l2table_cachep)
> + return -ENOMEM;
> +
> + register_iommu(&exynos_iommu_ops);
> +
> + return 0;
> +}
> +arch_initcall(exynos_iommu_init);
> --
> 1.7.1
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-24  7:40 ` KyongHo Cho
@ 2011-09-24  7:59   ` jiaweiwei
  0 siblings, 0 replies; 21+ messages in thread
From: jiaweiwei @ 2011-09-24  7:59 UTC (permalink / raw)
  To: linux-arm-kernel

2011/9/24 KyongHo Cho <pullip.cho@samsung.com>
>
> I am very sorry about the duplicate emails
> and the second email of this patch is incorrect.
> My email system may have some problem :(
>
> Regards,
>
> Cho KyongHo
>
> 2011/9/24 ??? <pullip.cho@samsung.com>:
> > This is the System MMU driver and IOMMU API implementation for
> > Exynos4 SOC platforms. Exynos4 platforms has more than 10 System
> > MMUs dedicated for each multimedia accellerators.
> >
> > Signed-off-by: KyongHo Cho
> > ---
> > drivers/iommu/Kconfig        |   14 +
> > drivers/iommu/Makefile       |    1 +
> > drivers/iommu/exynos_iommu.c |  859 ++++++++++++++++++++++++++++++++++++++++++
> > 3 files changed, 874 insertions(+), 0 deletions(-)
> > create mode 100644 drivers/iommu/exynos_iommu.c
> >
> > diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
> > index b57b3fa..1c754cd 100644
> > --- a/drivers/iommu/Kconfig
> > +++ b/drivers/iommu/Kconfig
> > @@ -107,4 +107,18 @@ config INTR_REMAP
> >  To use x2apic mode in the CPU's which support x2APIC enhancements or
> >  to support platforms with CPU's having > 8 bit APIC ID, say Y.
> >
> > +# EXYNOS IOMMU support
> > +config EXYNOS_IOMMU
> > + bool "Exynos IOMMU Support"
> > + depends on ARCH_EXYNOS4
> > + select IOMMU_API
> > + select EXYNOS4_DEV_SYSMMU
> > + help
> > +   Support for the IOMMUs (System MMUs) Samsung Exynos application
> > +   processor family. This enables H/W multimedia accellerators to view
> > +   non-linear physical memory chunks as a linear memory in their virtual
> > +   address spaces.
> > +
> > +   If unsure, say N here.
> > +
> > endif # IOMMU_SUPPORT
> > diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
> > index 4d4d77d..1eb924f 100644
> > --- a/drivers/iommu/Makefile
> > +++ b/drivers/iommu/Makefile
> > @@ -3,3 +3,4 @@ obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
> > obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
> > obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
> > obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o
> > +obj-$(CONFIG_EXYNOS_IOMMU) += exynos_iommu.o
> > diff --git a/drivers/iommu/exynos_iommu.c b/drivers/iommu/exynos_iommu.c
> > new file mode 100644
> > index 0000000..fe5b5d8
> > --- /dev/null
> > +++ b/drivers/iommu/exynos_iommu.c
> > @@ -0,0 +1,859 @@
> > +/* linux/drivers/iommu/exynos_iommu.c
> > + *
> > + * Copyright (c) 2011 Samsung Electronics Co., Ltd.
> > + * http://www.samsung.com
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License version 2 as
> > + * published by the Free Software Foundation.
> > + */
> > +
> > +#include
> > +#include
> > +#include
> > +#include

Hi KyongHo,
You have lost the header file. I can just see "#include". Please check your
patch again.

Thanks
Harry Wei

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-24  7:35 조경호
  2011-09-24  7:40 ` KyongHo Cho
@ 2011-09-24  9:38 ` Russell King - ARM Linux
  2011-09-26  0:44   ` KyongHo Cho
  1 sibling, 1 reply; 21+ messages in thread
From: Russell King - ARM Linux @ 2011-09-24  9:38 UTC (permalink / raw)
  To: linux-arm-kernel

On Sat, Sep 24, 2011 at 07:35:45AM +0000, ??? wrote:
> +# EXYNOS IOMMU support
> +config EXYNOS_IOMMU
> + bool "Exynos IOMMU Support"
> + depends on ARCH_EXYNOS4
> + select IOMMU_API
> + select EXYNOS4_DEV_SYSMMU
> + help

Looks like your mailer converted tabs to one space.  This makes it more
difficult to read this patch as a whole.  Don't expect good reviews as
long as your mailer breaks stuff in this way.

> diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile
> index 4d4d77d..1eb924f 100644
> --- a/drivers/iommu/Makefile
> +++ b/drivers/iommu/Makefile
> @@ -3,3 +3,4 @@ obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
> obj-$(CONFIG_AMD_IOMMU) += amd_iommu.o amd_iommu_init.o
> obj-$(CONFIG_DMAR) += dmar.o iova.o intel-iommu.o
> obj-$(CONFIG_INTR_REMAP) += dmar.o intr_remapping.o

Looks like your mailer eliminated spaces at the start of these lines.

> +obj-$(CONFIG_EXYNOS_IOMMU) += exynos_iommu.o
> diff --git a/drivers/iommu/exynos_iommu.c b/drivers/iommu/exynos_iommu.c
> new file mode 100644
> index 0000000..fe5b5d8
> --- /dev/null
> +++ b/drivers/iommu/exynos_iommu.c
> @@ -0,0 +1,859 @@
> +/* linux/drivers/iommu/exynos_iommu.c
> + *
> + * Copyright (c) 2011 Samsung Electronics Co., Ltd.
> + * http://www.samsung.com
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +#include 
> +
> +#include 
> +#include 
> +
> +#include 
> +#include 
> +#include 

Looks like your mailer... well... is totally broken beyond belief.

> +static inline struct sysmmu_platdata *get_platdata(struct device *dev)
> +{
> + return dev_get_platdata(dev);
> +}
> +
> +static inline bool set_sysmmu_active(struct sysmmu_platdata *mmudata)
> +{
> + /* return true if the System MMU was not active previously
> +    and it needs to be initialized */
> +
> + return atomic_inc_return(&mmudata->activations) == 1;
> +}
> +
> +static inline bool set_sysmmu_inactive(struct sysmmu_platdata *mmudata)
> +{
> + /* return true if the System MMU is needed to be disabled */
> + int ref;
> +
> + ref = atomic_dec_return(&mmudata->activations);
> +
> + if (ref == 0)
> + return true;
> +
> + if (WARN_ON(ref < 0)) {
> + /* System MMU is already disabled */
> + atomic_set(&mmudata->activations, 0);
> + ref = 0;
> + }
> +
> + return false;
> +}
> +
> +static inline bool is_sysmmu_active(struct sysmmu_platdata *mmudata)
> +{
> + return atomic_read(&mmudata->activations) != 0;
> +}

This use of an atomic type is total rubbish.  There's nothing magical
about 'atomic_*' stuff which suddenly makes the rest of your code
magically correct.  Think about this:

	Thread0			Thread1
	atomic_inc_return(&v)
		== 1
				atomic_dec_return(&v)
					== 0
				disables MMU
	enables MMU

Now you have the situation where the atomic value is '0' yet the MMU is
enabled.

Repeat after me: atomic types do not make code atomic.  atomic types
just make modifications to the type itself atomic.  Never use atomic
types for code synchronization.

> +static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
> +{
> + /* SYSMMU is in blocked when interrupt occurred. */
> + unsigned long addr;
> + struct sysmmu_platdata *mmudata = dev_id;
> + enum S5P_SYSMMU_INTERRUPT_TYPE itype;
> +
> + WARN_ON(!is_sysmmu_active(mmudata));
> +
> + itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
> + __ffs(__raw_readl(mmudata->sfrbase + S5P_INT_STATUS));
> +
> + BUG_ON(!((itype >= 0) && (itype < 8)));
> +
> + dev_alert(mmudata->dev, "SYSTEM MMU FAULT!!!.\n");

Do the exclaimation marks do anything for this error message?

> +
> + if (!mmudata->domain)
> + return IRQ_NONE;
> +
> + addr = __raw_readl(mmudata->sfrbase + fault_reg_offset[itype]);
> +
> + if (!report_iommu_fault(mmudata->domain, mmudata->owner, addr, itype)) {
> + __raw_writel(1 << itype, mmudata->sfrbase + S5P_INT_CLEAR);
> + dev_notice(mmudata->dev,
> + "%s is resolved. Retrying translation.\n",

So, this is a notice severity, yet we've printed at alert level.  Either
this condition satisfies being an alert or not.

> + sysmmu_fault_name[itype]);
> + sysmmu_unblock(mmudata->sfrbase);
> + } else {
> + dev_notice(mmudata->dev, "%s is not handled.\n",
> + sysmmu_fault_name[itype]);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +void exynos_sysmmu_set_tablebase_pgd(struct device *owner, unsigned long pgd)
> +{
> + struct sysmmu_platdata *mmudata = NULL;
> +
> + while ((mmudata = get_sysmmu_data(owner, mmudata))) {
> + if (is_sysmmu_active(mmudata)) {
> + sysmmu_block(mmudata->sfrbase);
> + __sysmmu_set_ptbase(mmudata->sfrbase, pgd);
> + sysmmu_unblock(mmudata->sfrbase);
> + dev_dbg(mmudata->dev, "New page table base is %p\n",
> + (void *)pgd);

If it's unsigned long use %08lx and don't cast.

> +static int exynos_sysmmu_probe(struct platform_device *pdev)
> +{
> + struct resource *res, *ioarea;
> + int ret;
> + int irq;
> + struct device *dev;
> + void *sfr;

void __iomem *sfr;

> +
> + dev = &pdev->dev;
> + if (!get_platdata(dev) || (get_platdata(dev)->owner == NULL)) {
> + dev_err(dev, "Failed to probing system MMU: "
> + "Owner device is not set.");
> + return -ENXIO;
> + }
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!res) {
> + dev_err(dev,
> + "Failed probing system MMU: failed to get resource.");
> + return -ENOENT;
> + }
> +
> + ioarea = request_mem_region(res->start, resource_size(res), pdev->name);
> + if (ioarea == NULL) {
> + dev_err(dev, "Failed probing system MMU: "
> + "failed to request memory region.");
> + return -ENOMEM;
> + }
> +
> + sfr = ioremap(res->start, resource_size(res));
> + if (!sfr) {
> + dev_err(dev, "Failed probing system MMU: "
> + "failed to call ioremap().");
> + ret = -ENOENT;
> + goto err_ioremap;
> + }
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq <= 0) {
> + dev_err(dev, "Failed probing system MMU: "
> + "failed to get irq resource.");
> + ret = irq;
> + goto err_irq;
> + }
> +
> + if (request_irq(irq, exynos_sysmmu_irq, 0, dev_name(&pdev->dev),
> + dev_get_platdata(dev))) {
> + dev_err(dev, "Failed probing system MMU: "
> + "failed to request irq.");
> + ret = -ENOENT;
> + goto err_irq;
> + }
> +
> + get_platdata(dev)->clk = clk_get(dev, "sysmmu");

It's rather horrible to see drivers modifying their platform data, rather
than drivers using the driver data pointer in struct device.  As far as
drivers are concerned, the platform data should only ever be read.

> +
> + if (IS_ERR_OR_NULL(get_platdata(dev)->clk)) {

IS_ERR() only please.  The *only* allowable failure value for drivers to
be concerned about here is if the return value is an error pointer.
Drivers have no business rejecting a NULL pointer here.

> +static int exynos_sysmmu_remove(struct platform_device *pdev)
> +{
> + return 0;
> +}

That doesn't look good.  If you can't remove it don't provide the
function at all.

> +
> +int exynos_sysmmu_runtime_suspend(struct device *dev)
> +{
> + if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
> + return -EFAULT;
> +
> + return 0;
> +}
> +
> +int exynos_sysmmu_runtime_resume(struct device *dev)
> +{
> + if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
> + return -EFAULT;
> +
> + return 0;
> +}

Is there much point to having runtime PM support in this driver?

> +static int exynos_iommu_fault_handler(struct iommu_domain *domain,
> + struct device *dev, unsigned long iova, int flags)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> +
> + dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
> + sysmmu_fault_name[flags], (void *)iova,
> + (void *)(__pa(priv->pgtable)));

Again, you want to get rid of these casts and use a proper format string
for what these are.

> +static int exynos_iommu_attach_device(struct iommu_domain *domain,
> +    struct device *dev)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> + int ret;
> +
> + spin_lock(&priv->lock);
> +
> + priv->dev = dev;
> +
> + ret = exynos_sysmmu_enable(domain);
> + if (ret)
> + return ret;
> +
> + spin_unlock(&priv->lock);
> +
> + return 0;

This function has an indeterminant return state for the spinlock - it
sometimes returns with it locked and sometimes with it unlocked.  That's
bad practice.

Getting rid of the 'if (ret) return ret;' and changing this 'return 0;'
to 'return ret;' is a simple way of solving it - and results in less
complex code.

> +static int exynos_iommu_map(struct iommu_domain *domain, unsigned long iova,
> + phys_addr_t paddr, int gfp_order, int prot)
> +{
> + struct exynos_iommu_domain *priv = domain->priv;
> + unsigned long *start_entry, *entry, *end_entry;
> + int num_entry;
> + int ret = 0;
> + unsigned long flags;
> +
> + BUG_ON(priv->pgtable == NULL);
> +
> + spin_lock_irqsave(&priv->pgtablelock, flags);
> +
> + start_entry = entry = priv->pgtable + (iova >> S5P_SECTION_SHIFT);
> +
> + if (gfp_order >= S5P_SECTION_ORDER) {
> + BUG_ON((paddr | iova) & ~S5P_SECTION_MASK);
> + /* 1MiB mapping */
> +
> + num_entry = 1 << (gfp_order - S5P_SECTION_ORDER);
> + end_entry = entry + num_entry;
> +
> + while (entry != end_entry) {
> + if (!section_available(domain, entry))
> + break;
> +
> + MAKE_SECTION_ENTRY(*entry, paddr);
> +
> + paddr += S5P_SECTION_SIZE;
> + entry++;
> + }
> +
> + if (entry != end_entry)
> + goto mapping_error;
> +
> + pgtable_flush(start_entry, entry);
> + goto mapping_done;
> + }
> +
> + if (S5P_FAULT_LV1_ENTRY(*entry)) {
> + unsigned long *l2table;
> +
> + l2table = kmem_cache_zalloc(l2table_cachep, GFP_KERNEL);
> + if (!l2table) {
> + ret = -ENOMEM;
> + goto nomem_error;
> + }
> +
> + pgtable_flush(entry, entry + S5P_LV2TABLE_ENTRIES);
> +
> + MAKE_LV2TABLE_ENTRY(*entry, virt_to_phys(l2table));
> + pgtable_flush(entry, entry + 1);
> + }
> +
> + /* 'entry' points level 2 entries, hereafter */
> + entry = GET_LV2ENTRY(*entry, iova);
> +
> + start_entry = entry;
> + num_entry = 1 << gfp_order;
> + end_entry = entry + num_entry;
> +
> + if (gfp_order >= S5P_LPAGE_ORDER) {
> + /* large page(64KiB) mapping */
> + BUG_ON((paddr | iova) & ~S5P_LPAGE_MASK);
> +
> + while (entry != end_entry) {
> + if (!write_lpage(entry, paddr)) {
> + pr_err("%s: Failed to allocate large page"
> + " entry.\n", __func__);

Don't wrap error messages, irrespective of what checkpatch may tell you.
They make them impossible to grep for.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-24  9:38 ` Russell King - ARM Linux
@ 2011-09-26  0:44   ` KyongHo Cho
  2011-09-26  2:17     ` Wanlong Gao
  0 siblings, 1 reply; 21+ messages in thread
From: KyongHo Cho @ 2011-09-26  0:44 UTC (permalink / raw)
  To: linux-arm-kernel

Hi.

On Sat, Sep 24, 2011 at 6:38 PM, Russell King - ARM Linux
<linux@arm.linux.org.uk> wrote:
> On Sat, Sep 24, 2011 at 07:35:45AM +0000, ??? wrote:
>> +# EXYNOS IOMMU support
>> +config EXYNOS_IOMMU
>> + bool "Exynos IOMMU Support"
>> + depends on ARCH_EXYNOS4
>> + select IOMMU_API
>> + select EXYNOS4_DEV_SYSMMU
>> + help
>
> Looks like your mailer converted tabs to one space. ?This makes it more
> difficult to read this patch as a whole. ?Don't expect good reviews as
> long as your mailer breaks stuff in this way.
Sorry for that :(
I will try another way to send patches correctly.

>> +static inline bool is_sysmmu_active(struct sysmmu_platdata *mmudata)
>> +{
>> + return atomic_read(&mmudata->activations) != 0;
>> +}
>
> This use of an atomic type is total rubbish. ?There's nothing magical
> about 'atomic_*' stuff which suddenly makes the rest of your code
> magically correct. ?Think about this:
>
> ? ? ? ?Thread0 ? ? ? ? ? ? ? ? Thread1
> ? ? ? ?atomic_inc_return(&v)
> ? ? ? ? ? ? ? ?== 1
> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?atomic_dec_return(&v)
> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?== 0
> ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?disables MMU
> ? ? ? ?enables MMU
>
> Now you have the situation where the atomic value is '0' yet the MMU is
> enabled.
>
> Repeat after me: atomic types do not make code atomic. ?atomic types
> just make modifications to the type itself atomic. ?Never use atomic
> types for code synchronization.
>
You're right. I also found that atomic type is not required for that.

I think that the atomic type is need to be changed into a primitive type
And the drivers that handles System MMU need to care about the synchronization
of the state of System MMU.
Since the only way to handle System MMU from the outside of System MMU driver
is to use IOMMU API, the state of System MMU will be retained by IOMMU API
implementations for our System MMU.

>> +static irqreturn_t exynos_sysmmu_irq(int irq, void *dev_id)
>> +{
>> + /* SYSMMU is in blocked when interrupt occurred. */
>> + unsigned long addr;
>> + struct sysmmu_platdata *mmudata = dev_id;
>> + enum S5P_SYSMMU_INTERRUPT_TYPE itype;
>> +
>> + WARN_ON(!is_sysmmu_active(mmudata));
>> +
>> + itype = (enum S5P_SYSMMU_INTERRUPT_TYPE)
>> + __ffs(__raw_readl(mmudata->sfrbase + S5P_INT_STATUS));
>> +
>> + BUG_ON(!((itype >= 0) && (itype < 8)));
>> +
>> + dev_alert(mmudata->dev, "SYSTEM MMU FAULT!!!.\n");
>
> Do the exclaimation marks do anything for this error message?
>
It invokes report_iommu_fault() that is a handler of IOMMU fault.
Since faults are always handled by the fault handler,
the above message is not required and it is just a debug purpose.

As you mentioned, it does not mean anything and needed to be removed.

>> +
>> + if (!mmudata->domain)
>> + return IRQ_NONE;
>> +
>> + addr = __raw_readl(mmudata->sfrbase + fault_reg_offset[itype]);
>> +
>> + if (!report_iommu_fault(mmudata->domain, mmudata->owner, addr, itype)) {
>> + __raw_writel(1 << itype, mmudata->sfrbase + S5P_INT_CLEAR);
>> + dev_notice(mmudata->dev,
>> + "%s is resolved. Retrying translation.\n",
>
> So, this is a notice severity, yet we've printed at alert level. ?Either
> this condition satisfies being an alert or not.

It does not need to be notice severity and should be debug like above
alert message
because the fault handler has a chance to catch the fault that must not happen.

>
>> + sysmmu_fault_name[itype]);
>> + sysmmu_unblock(mmudata->sfrbase);
>> + } else {
>> + dev_notice(mmudata->dev, "%s is not handled.\n",
>> + sysmmu_fault_name[itype]);
>> + }
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +void exynos_sysmmu_set_tablebase_pgd(struct device *owner, unsigned long pgd)
>> +{
>> + struct sysmmu_platdata *mmudata = NULL;
>> +
>> + while ((mmudata = get_sysmmu_data(owner, mmudata))) {
>> + if (is_sysmmu_active(mmudata)) {
>> + sysmmu_block(mmudata->sfrbase);
>> + __sysmmu_set_ptbase(mmudata->sfrbase, pgd);
>> + sysmmu_unblock(mmudata->sfrbase);
>> + dev_dbg(mmudata->dev, "New page table base is %p\n",
>> + (void *)pgd);
>
> If it's unsigned long use %08lx and don't cast.
>
Ok.

>> + if (request_irq(irq, exynos_sysmmu_irq, 0, dev_name(&pdev->dev),
>> + dev_get_platdata(dev))) {
>> + dev_err(dev, "Failed probing system MMU: "
>> + "failed to request irq.");
>> + ret = -ENOENT;
>> + goto err_irq;
>> + }
>> +
>> + get_platdata(dev)->clk = clk_get(dev, "sysmmu");
>
> It's rather horrible to see drivers modifying their platform data, rather
> than drivers using the driver data pointer in struct device. ?As far as
> drivers are concerned, the platform data should only ever be read.
>
Thank you. That's true.
All data that are set in the driver will be moved to drvdata.

>> +
>> + if (IS_ERR_OR_NULL(get_platdata(dev)->clk)) {
>
> IS_ERR() only please. ?The *only* allowable failure value for drivers to
> be concerned about here is if the return value is an error pointer.
> Drivers have no business rejecting a NULL pointer here.
>
Right. it will be changed.

>> +static int exynos_sysmmu_remove(struct platform_device *pdev)
>> +{
>> + return 0;
>> +}
>
> That doesn't look good. ?If you can't remove it don't provide the
> function at all.
>
Yes it does not required. I will remove it.

>> +
>> +int exynos_sysmmu_runtime_suspend(struct device *dev)
>> +{
>> + if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
>> + return -EFAULT;
>> +
>> + return 0;
>> +}
>> +
>> +int exynos_sysmmu_runtime_resume(struct device *dev)
>> +{
>> + if (WARN_ON(is_sysmmu_active(dev_get_platdata(dev))))
>> + return -EFAULT;
>> +
>> + return 0;
>> +}
>
> Is there much point to having runtime PM support in this driver?
>
A System MMU is included in the power domain of the device that is
master of System MMU.
If the system MMU dirver does not support power gating, the driver of
master device must care about System MMU's internal state.
 - Master device driver must disable System MMU before suspending the
master device.
 - Master device driver must check if the master device is resumed to
invalidate TLB
   when updating the page table.
I thought that System MMU driver must care about everything about its
internal state
and outside of the drivers including the device driver of master
device do not need
to care about it.

>> +static int exynos_iommu_fault_handler(struct iommu_domain *domain,
>> + struct device *dev, unsigned long iova, int flags)
>> +{
>> + struct exynos_iommu_domain *priv = domain->priv;
>> +
>> + dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
>> + sysmmu_fault_name[flags], (void *)iova,
>> + (void *)(__pa(priv->pgtable)));
>
> Again, you want to get rid of these casts and use a proper format string
> for what these are.
Ok.

>
>> +static int exynos_iommu_attach_device(struct iommu_domain *domain,
>> + ? ?struct device *dev)
>> +{
>> + struct exynos_iommu_domain *priv = domain->priv;
>> + int ret;
>> +
>> + spin_lock(&priv->lock);
>> +
>> + priv->dev = dev;
>> +
>> + ret = exynos_sysmmu_enable(domain);
>> + if (ret)
>> + return ret;
>> +
>> + spin_unlock(&priv->lock);
>> +
>> + return 0;
>
> This function has an indeterminant return state for the spinlock - it
> sometimes returns with it locked and sometimes with it unlocked. ?That's
> bad practice.
>
> Getting rid of the 'if (ret) return ret;' and changing this 'return 0;'
> to 'return ret;' is a simple way of solving it - and results in less
> complex code.
>
That is my mistake.
Thank you.

>> + while (entry != end_entry) {
>> + if (!write_lpage(entry, paddr)) {
>> + pr_err("%s: Failed to allocate large page"
>> + " entry.\n", __func__);
>
> Don't wrap error messages, irrespective of what checkpatch may tell you.
> They make them impossible to grep for.
You are right.
I missed that.
I will fix that.

Thank you for your detailed review.

Regards,
Cho KyongHo.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26  0:44   ` KyongHo Cho
@ 2011-09-26  2:17     ` Wanlong Gao
  2011-09-26  4:47       ` KyongHo Cho
  0 siblings, 1 reply; 21+ messages in thread
From: Wanlong Gao @ 2011-09-26  2:17 UTC (permalink / raw)
  To: linux-arm-kernel

On 09/26/2011 08:44 AM, KyongHo Cho wrote:

> Hi.
> 
> On Sat, Sep 24, 2011 at 6:38 PM, Russell King - ARM Linux
> <linux@arm.linux.org.uk> wrote:
>> On Sat, Sep 24, 2011 at 07:35:45AM +0000, ??? wrote:
>>> +# EXYNOS IOMMU support
>>> +config EXYNOS_IOMMU
>>> + bool "Exynos IOMMU Support"
>>> + depends on ARCH_EXYNOS4
>>> + select IOMMU_API
>>> + select EXYNOS4_DEV_SYSMMU
>>> + help
>>
>> Looks like your mailer converted tabs to one space.  This makes it more
>> difficult to read this patch as a whole.  Don't expect good reviews as
>> long as your mailer breaks stuff in this way.
> Sorry for that :(
> I will try another way to send patches correctly.


Use *git send-email* instead.

Thanks

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26  2:17     ` Wanlong Gao
@ 2011-09-26  4:47       ` KyongHo Cho
  0 siblings, 0 replies; 21+ messages in thread
From: KyongHo Cho @ 2011-09-26  4:47 UTC (permalink / raw)
  To: linux-arm-kernel

Thanks. I will try.

Regards,
Cho KyongHo.

On Mon, Sep 26, 2011 at 11:17 AM, Wanlong Gao <gaowanlong@cn.fujitsu.com> wrote:
> On 09/26/2011 08:44 AM, KyongHo Cho wrote:
>
>> Hi.
>>
>> On Sat, Sep 24, 2011 at 6:38 PM, Russell King - ARM Linux
>> <linux@arm.linux.org.uk> wrote:
>>> On Sat, Sep 24, 2011 at 07:35:45AM +0000, ??? wrote:
>>>> +# EXYNOS IOMMU support
>>>> +config EXYNOS_IOMMU
>>>> + bool "Exynos IOMMU Support"
>>>> + depends on ARCH_EXYNOS4
>>>> + select IOMMU_API
>>>> + select EXYNOS4_DEV_SYSMMU
>>>> + help
>>>
>>> Looks like your mailer converted tabs to one space. ?This makes it more
>>> difficult to read this patch as a whole. ?Don't expect good reviews as
>>> long as your mailer breaks stuff in this way.
>> Sorry for that :(
>> I will try another way to send patches correctly.
>
>
> Use *git send-email* instead.
>
> Thanks
> --
> To unsubscribe from this list: send the line "unsubscribe linux-samsung-soc" in
> the body of a message to majordomo at vger.kernel.org
> More majordomo info at ?http://vger.kernel.org/majordomo-info.html
>

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-24  7:30 [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms 조경호
@ 2011-09-26  7:59 ` Ohad Ben-Cohen
  2011-09-26  8:58   ` KyongHo Cho
  0 siblings, 1 reply; 21+ messages in thread
From: Ohad Ben-Cohen @ 2011-09-26  7:59 UTC (permalink / raw)
  To: linux-arm-kernel

2011/9/24 ??? <pullip.cho@samsung.com>:
> This is the System MMU driver and IOMMU API implementation for
> Exynos4 SOC platforms. Exynos4 platforms has more than 10 System
> MMUs dedicated for each multimedia accellerators.
>
> Signed-off-by: KyongHo Cho <pullip.cho@samsung.com>
...
> +static int exynos_iommu_fault_handler(struct iommu_domain *domain,
> +                       struct device *dev, unsigned long iova, int flags)
> +{
> +       struct exynos_iommu_domain *priv = domain->priv;
> +
> +       dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
> +                               sysmmu_fault_name[flags], (void *)iova,
> +                               (void *)(__pa(priv->pgtable)));
> +       dev_err(priv->dev, "\t\tGenerating Kernel OOPS...\n");
> +       dev_err(priv->dev, "\t\tbecause it is unrecoverable.\n");
> +       dev_err(priv->dev,
> +               "\t\tSet Fault handler with iommu_set_fault_handler().\n");
> +       dev_err(priv->dev, "\t\tto handle System MMU fault.\n");
> +
> +       BUG();
> +
> +       return 0;
> +}
> +
> +static int exynos_iommu_domain_init(struct iommu_domain *domain)
> +{
...
> +       iommu_set_fault_handler(domain, &exynos_iommu_fault_handler);

It doesn't make a lot of sense to set an iommu fault handler here; the
intention of iommu_set_fault_handler() is to allow upper layers to do
that.

Moreover, exynos_sysmmu_irq() anyway knows whenever a fault occurs, so
calling report_iommu_fault() just to have it call into the driver
again seems redundant.

If you want an exynos-specific behavior to occur whenever there's an
iommu fault, you should do that in exynos_sysmmu_irq itself. If you
just want a generic default logging behavior to occur whenever there's
no other fault handler installed, you might even want to consider
adding it to the IOMMU core.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26  7:59 ` Ohad Ben-Cohen
@ 2011-09-26  8:58   ` KyongHo Cho
  2011-09-26  9:05     ` Ohad Ben-Cohen
  0 siblings, 1 reply; 21+ messages in thread
From: KyongHo Cho @ 2011-09-26  8:58 UTC (permalink / raw)
  To: linux-arm-kernel

Hi.

On Mon, Sep 26, 2011 at 4:59 PM, Ohad Ben-Cohen <ohad@wizery.com> wrote:
> 2011/9/24 ??? <pullip.cho@samsung.com>:
>> This is the System MMU driver and IOMMU API implementation for
>> Exynos4 SOC platforms. Exynos4 platforms has more than 10 System
>> MMUs dedicated for each multimedia accellerators.
>>
>> Signed-off-by: KyongHo Cho <pullip.cho@samsung.com>
> ...
>> +static int exynos_iommu_fault_handler(struct iommu_domain *domain,
>> + ? ? ? ? ? ? ? ? ? ? ? struct device *dev, unsigned long iova, int flags)
>> +{
>> + ? ? ? struct exynos_iommu_domain *priv = domain->priv;
>> +
>> + ? ? ? dev_err(priv->dev, "%s occured at %p(Page table base: %p)\n",
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sysmmu_fault_name[flags], (void *)iova,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (void *)(__pa(priv->pgtable)));
>> + ? ? ? dev_err(priv->dev, "\t\tGenerating Kernel OOPS...\n");
>> + ? ? ? dev_err(priv->dev, "\t\tbecause it is unrecoverable.\n");
>> + ? ? ? dev_err(priv->dev,
>> + ? ? ? ? ? ? ? "\t\tSet Fault handler with iommu_set_fault_handler().\n");
>> + ? ? ? dev_err(priv->dev, "\t\tto handle System MMU fault.\n");
>> +
>> + ? ? ? BUG();
>> +
>> + ? ? ? return 0;
>> +}
>> +
>> +static int exynos_iommu_domain_init(struct iommu_domain *domain)
>> +{
> ...
>> + ? ? ? iommu_set_fault_handler(domain, &exynos_iommu_fault_handler);
>
> It doesn't make a lot of sense to set an iommu fault handler here; the
> intention of iommu_set_fault_handler() is to allow upper layers to do
> that.

As you've noticed, it is default behavior for iommu fault.
If an upper layer calls iommu_set_fault_handler(),
it is replaced with the new one.
>
> Moreover, exynos_sysmmu_irq() anyway knows whenever a fault occurs, so
> calling report_iommu_fault() just to have it call into the driver
> again seems redundant.
>
I think calling report_iommu_fault() in exynos_sysmmu_irq() is rather simple.
To do the default behavior in exynos_sysmmu_irq(),
it must check if domain->handler is NULL or not.
But the checking is also performed in report_iommu_fault() also.

I think just calling report_iommu_fault() reduces redundancy.

> If you want an exynos-specific behavior to occur whenever there's an
> iommu fault, you should do that in exynos_sysmmu_irq itself. If you
> just want a generic default logging behavior to occur whenever there's
> no other fault handler installed, you might even want to consider
> adding it to the IOMMU core.

It is neither a specific behavior nor a generic logging.

Just a default MMU fault report that can be replaceable with a fault
handler that a device driver provides.

Regards,
Cho KyongHo.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26  8:58   ` KyongHo Cho
@ 2011-09-26  9:05     ` Ohad Ben-Cohen
  2011-09-26  9:21       ` KyongHo Cho
  0 siblings, 1 reply; 21+ messages in thread
From: Ohad Ben-Cohen @ 2011-09-26  9:05 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Sep 26, 2011 at 11:58 AM, KyongHo Cho <pullip.cho@samsung.com> wrote:
> As you've noticed, it is default behavior for iommu fault.

Then why not add it to the IOMMU core ? it's quite generic and can be
useful for others, too.

Thanks,
Ohad.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26  9:05     ` Ohad Ben-Cohen
@ 2011-09-26  9:21       ` KyongHo Cho
  2011-09-26  9:36         ` Ohad Ben-Cohen
  0 siblings, 1 reply; 21+ messages in thread
From: KyongHo Cho @ 2011-09-26  9:21 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Sep 26, 2011 at 6:05 PM, Ohad Ben-Cohen <ohad@wizery.com> wrote:
> On Mon, Sep 26, 2011 at 11:58 AM, KyongHo Cho <pullip.cho@samsung.com> wrote:
>> As you've noticed, it is default behavior for iommu fault.
>
> Then why not add it to the IOMMU core ? it's quite generic and can be
> useful for others, too.
>
Every fault in IOMMU (System MMU) is an error logically
and must not happen in our multimedia device drivers
because, our device drivers always construct page table completely
before address translation in System MMU
and TLB loads translation information automatically when TLB miss.

I am not sure if the default behavior of MMU fault in our IOMMU driver
is also correct for other IOMMU drivers.

BTW,
I think we need more fault type than IOMMU_FAULT_READ and IOMMU_FAULT_WRITE.
We have page fault, access fault(read, write), security fault
and bus error (translated physical address is not available)

> Thanks,
> Ohad.

Thank you.
Cho KyongHo.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26  9:21       ` KyongHo Cho
@ 2011-09-26  9:36         ` Ohad Ben-Cohen
  2011-09-26 14:48           ` KyongHo Cho
  0 siblings, 1 reply; 21+ messages in thread
From: Ohad Ben-Cohen @ 2011-09-26  9:36 UTC (permalink / raw)
  To: linux-arm-kernel

Hi,

On Mon, Sep 26, 2011 at 12:21 PM, KyongHo Cho <pullip.cho@samsung.com> wrote:
> Every fault in IOMMU (System MMU) is an error logically
> and must not happen in our multimedia device drivers
> because, our device drivers always construct page table completely
> before address translation in System MMU
> and TLB loads translation information automatically when TLB miss.

Yeah, this is pretty much the same for OMAP too.

> I am not sure if the default behavior of MMU fault in our IOMMU driver
> is also correct for other IOMMU drivers.

It seems your default behavior just prints an error message with the
details of the fault (addresses, flags, etc..). We can probably have
this unified.

In case you want to print exonys-specific info when the fault is not
handled by upper layers, we can change report_iommu_fault() to return
an error whenever a handler is not installed. This way
exynos_sysmmu_irq() can easily tell whether to proceed with the
default behavior or not, without installing a fault handler in the
driver:

diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index 297893f..dc4b282 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -103,7 +103,7 @@ extern void iommu_set_fault_handler(struct iommu_domain *dom
 static inline int report_iommu_fault(struct iommu_domain *domain,
                struct device *dev, unsigned long iova, int flags)
 {
-       int ret = 0;
+       int ret = -EFAULT;


> BTW,
> I think we need more fault type than IOMMU_FAULT_READ and IOMMU_FAULT_WRITE.
> We have page fault, access fault(read, write), security fault
> and bus error (translated physical address is not available)

Initially we had a argument for the type of the fault, but dropped it
because it was not used. Feel free to add it back if you need it.

Thanks,
Ohad.

^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26  9:36         ` Ohad Ben-Cohen
@ 2011-09-26 14:48           ` KyongHo Cho
  2011-09-26 15:59             ` Ohad Ben-Cohen
  0 siblings, 1 reply; 21+ messages in thread
From: KyongHo Cho @ 2011-09-26 14:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Sep 26, 2011 at 6:36 PM, Ohad Ben-Cohen <ohad@wizery.com> wrote:

> It seems your default behavior just prints an error message with the
> details of the fault (addresses, flags, etc..). We can probably have
> this unified.
>
> In case you want to print exonys-specific info when the fault is not
> handled by upper layers, we can change report_iommu_fault() to return
> an error whenever a handler is not installed. This way
> exynos_sysmmu_irq() can easily tell whether to proceed with the
> default behavior or not, without installing a fault handler in the
> driver:
>
> diff --git a/include/linux/iommu.h b/include/linux/iommu.h
> index 297893f..dc4b282 100644
> --- a/include/linux/iommu.h
> +++ b/include/linux/iommu.h
> @@ -103,7 +103,7 @@ extern void iommu_set_fault_handler(struct iommu_domain *dom
> ?static inline int report_iommu_fault(struct iommu_domain *domain,
> ? ? ? ? ? ? ? ?struct device *dev, unsigned long iova, int flags)
> ?{
> - ? ? ? int ret = 0;
> + ? ? ? int ret = -EFAULT;
>
That looks better for me :)
However, I think the irq handler need to identify if the return value
is an error
returned by a fault handler or a notice that no fault handler is installed.

I am sorry but I still think that
installing default fault handler is quite simple and straightforward.

Providing generic default behavior for all architectures is also good
if it is available for other architectures.
My intention with the default behavior for unrecoverable fault from IOMMU is
to show what the problem is, where the problem caused and
thus, providing a hint  to solve or catch the problem
for the device drivers and the developers.

>> BTW,
>> I think we need more fault type than IOMMU_FAULT_READ and IOMMU_FAULT_WRITE.
>> We have page fault, access fault(read, write), security fault
>> and bus error (translated physical address is not available)
>
> Initially we had a argument for the type of the fault, but dropped it
> because it was not used. Feel free to add it back if you need it.
>
OK. thanks.

> Thanks,
> Ohad.

Thank you.
Cho KyongHo.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26 14:48           ` KyongHo Cho
@ 2011-09-26 15:59             ` Ohad Ben-Cohen
  2011-09-27  0:56               ` KyongHo Cho
  0 siblings, 1 reply; 21+ messages in thread
From: Ohad Ben-Cohen @ 2011-09-26 15:59 UTC (permalink / raw)
  To: linux-arm-kernel

On Mon, Sep 26, 2011 at 5:48 PM, KyongHo Cho <pullip.cho@samsung.com> wrote:
> I am sorry but I still think that
> installing default fault handler is quite simple and straightforward.

... and abusing the IOMMU API.

Please don't do that. Interfaces are written for specific goals, and
this one was added for IOMMU users, not drivers. Just like all the
other map/unmap/attach/... APIs.

By using this from the IOMMU driver itself, you are adding burden on
future development and evolution of this API. As use cases
materialize, we will have to change it to support them, while
considering existing use cases. Using this API inappropriately will
make our life harder later on.

Moreover, I'm really not sure how exactly are you going to use this.

If you're having an IOMMU user which installs its own fault handler,
then this is all moot since the default behavior you provide here will
be overridden.
If you're not having an IOMMU user which installs its own fault
handler, then just provide the default behavior in your irq handler,
regardless whether you report the fault to the IOMMU core or not. Or
only when it fails. and yes, we can provide different error values for
different scenarios. it's not rocket science :).

Really, there's a myriad of ways to do this right. Please explain your
exact settings/use-case and I'll gladly help you find a clean
solution.

Thanks,
Ohad.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-26 15:59             ` Ohad Ben-Cohen
@ 2011-09-27  0:56               ` KyongHo Cho
  2011-09-27  8:48                 ` Ohad Ben-Cohen
  0 siblings, 1 reply; 21+ messages in thread
From: KyongHo Cho @ 2011-09-27  0:56 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Sep 27, 2011 at 12:59 AM, Ohad Ben-Cohen <ohad@wizery.com> wrote:
> On Mon, Sep 26, 2011 at 5:48 PM, KyongHo Cho <pullip.cho@samsung.com> wrote:
>> I am sorry but I still think that
>> installing default fault handler is quite simple and straightforward.
>
> ... and abusing the IOMMU API.
>
> Please don't do that. Interfaces are written for specific goals, and
> this one was added for IOMMU users, not drivers. Just like all the
> other map/unmap/attach/... APIs.
>
> By using this from the IOMMU driver itself, you are adding burden on
> future development and evolution of this API. As use cases
> materialize, we will have to change it to support them, while
> considering existing use cases. Using this API inappropriately will
> make our life harder later on.
I didn't think about this case.
It can be a problem if IOMMU driver is dependent upon the change of
IOMMU API as you told above.

>
> Moreover, I'm really not sure how exactly are you going to use this.
>
> If you're having an IOMMU user which installs its own fault handler,
> then this is all moot since the default behavior you provide here will
> be overridden.
Yes, this is what I really want.
I wanted the IOMMU driver to provide default fault handler to its users
that does not provide fault handler for debugging purpose.
If a user want to handle MMU fault, IOMMU driver welcomes to overwrite
the existing 'default' fault handler.

> If you're not having an IOMMU user which installs its own fault
> handler, then just provide the default behavior in your irq handler,
> regardless whether you report the fault to the IOMMU core or not. Or
> only when it fails. and yes, we can provide different error values for
> different scenarios. it's not rocket science :).
>
> Really, there's a myriad of ways to do this right. Please explain your
> exact settings/use-case and I'll gladly help you find a clean
> solution.
>
Thank you.

As I explained before, MMU fault in our system is abnormal and
the situation cannot be recovered because mapping information is set up
completely before System MMU starts address translation with the mapping
information.
However we have to capture the MMU fault for debugging purpose.
That's why our fault handler just prints debugging messages and
generates kernel OOPS

A user of IOMMU may handle MMU faults due to the following reason:
 - Its IOMMU sees the virtual address space of the user process (IOMMU
refers the page table of CPU)
   and virtual memory region allocated by userspace is not filled with
physical memory.
   Device driver which is user of IOMMU does not call get_user_pages()
for the region but
   handle page fault generated by IOMMU.
 - A device driver working in the Monitor mode in ARM processors need
to protect a memory region
   from access in the Normal mode. If it happens, the device driver
must catch the situation,
   stop the current operation and inform the problem to the userspace.

For the above reasons, some user of IOMMU may override the default
fault handler.

> Thanks,
> Ohad.

Thanks and regards,
Cho KyongHo.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-27  0:56               ` KyongHo Cho
@ 2011-09-27  8:48                 ` Ohad Ben-Cohen
  2011-09-27  9:01                   ` KyongHo Cho
  0 siblings, 1 reply; 21+ messages in thread
From: Ohad Ben-Cohen @ 2011-09-27  8:48 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Sep 27, 2011 at 3:56 AM, KyongHo Cho <pullip.cho@samsung.com> wrote:
> I wanted the IOMMU driver to provide default fault handler to its users
> that does not provide fault handler for debugging purpose.
> If a user want to handle MMU fault, IOMMU driver welcomes to overwrite
> the existing 'default' fault handler.

Ok, this one patch below should allow you to do that.
report_iommu_fault() will now reliably tell you if a fault handler
failed or not, or if one isn't even installed.

This way your IOMMU driver doesn't need to define a fault handler of
its own, and instead, can do everything directly in
exynos_sysmmu_irq() where all the required information is now
available.

Thanks,
Ohad.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-27  8:48                 ` Ohad Ben-Cohen
@ 2011-09-27  9:01                   ` KyongHo Cho
  2011-09-27  9:19                     ` Ohad Ben-Cohen
  0 siblings, 1 reply; 21+ messages in thread
From: KyongHo Cho @ 2011-09-27  9:01 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Sep 27, 2011 at 5:48 PM, Ohad Ben-Cohen <ohad@wizery.com> wrote:
> diff --git a/include/linux/iommu.h b/include/linux/iommu.h
> index d084e87..2987199 100644
> --- a/include/linux/iommu.h
> +++ b/include/linux/iommu.h
> @@ -98,11 +98,12 @@ extern void iommu_set_fault_handler(struct
> iommu_domain *domain,
> ?* Returns 0 on success and an appropriate error code otherwise (if dynamic
> ?* PTE/TLB loading will one day be supported, implementations will be able
> ?* to tell whether it succeeded or not according to this return value).
> + * If a fault handler isn't installed, -ENOSYS is returned.
> ?*/
> ?static inline int report_iommu_fault(struct iommu_domain *domain,
> ? ? ? ? ? ? ? ?struct device *dev, unsigned long iova, int flags)
> ?{
> - ? ? ? int ret = 0;
> + ? ? ? int ret = -ENOSYS;
>
> ? ? ? ?/*
> ? ? ? ? * if upper layers showed interest and installed a fault handler,
It looks good for me.

I will prepare new patches with the changed API.

Thank you.
Cho KyongHo.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms
  2011-09-27  9:01                   ` KyongHo Cho
@ 2011-09-27  9:19                     ` Ohad Ben-Cohen
  0 siblings, 0 replies; 21+ messages in thread
From: Ohad Ben-Cohen @ 2011-09-27  9:19 UTC (permalink / raw)
  To: linux-arm-kernel

On Tue, Sep 27, 2011 at 12:01 PM, KyongHo Cho <pullip.cho@samsung.com> wrote:
> It looks good for me.
>
> I will prepare new patches with the changed API.

Ok, thanks.

(I've just submitted it to the iommu ML too)

Ohad.

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2011-09-27  9:19 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-09-24  7:30 [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms 조경호
2011-09-26  7:59 ` Ohad Ben-Cohen
2011-09-26  8:58   ` KyongHo Cho
2011-09-26  9:05     ` Ohad Ben-Cohen
2011-09-26  9:21       ` KyongHo Cho
2011-09-26  9:36         ` Ohad Ben-Cohen
2011-09-26 14:48           ` KyongHo Cho
2011-09-26 15:59             ` Ohad Ben-Cohen
2011-09-27  0:56               ` KyongHo Cho
2011-09-27  8:48                 ` Ohad Ben-Cohen
2011-09-27  9:01                   ` KyongHo Cho
2011-09-27  9:19                     ` Ohad Ben-Cohen
  -- strict thread matches above, loose matches on Subject: below --
2011-09-24  7:35 조경호
2011-09-24  7:40 ` KyongHo Cho
2011-09-24  7:59   ` jiaweiwei
2011-09-24  9:38 ` Russell King - ARM Linux
2011-09-26  0:44   ` KyongHo Cho
2011-09-26  2:17     ` Wanlong Gao
2011-09-26  4:47       ` KyongHo Cho
2011-09-24  2:02 [PATCH 0/4] iommu/exynos: Add IOMMU and Enhance System MMU driver for Exynos4 KyongHo Cho
2011-09-24  2:02 ` [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms KyongHo Cho
2011-09-24  0:38 [PATCH 0/4] iommu/exynos: Add IOMMU and Enhance System MMU for Exynos4 KyongHo Cho
2011-09-24  0:38 ` [PATCH 3/4] iommu/exynos: Add iommu driver for Exynos4 Platforms KyongHo Cho

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).