Skip to contentSkip to conversation

iommu/ipmmu-vmsa: Experimental r8a7795 support

PATCH In review Magnus Damm 2 participants 12 messages Dec 15, 2015 – Dec 16, 2015

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover iommu/ipmmu-vmsa: Experimental r8a7795 support

iommu/ipmmu-vmsa: Experimental r8a7795 support

[PATCH/RFC 01/10] iommu/ipmmu-vmsa: Introduce features, break out alias [PATCH/RFC 02/10] iommu/ipmmu-vmsa: Add optional root device feature [PATCH/RFC 03/10] iommu/ipmmu-vmsa: Write IMCTR twice [PATCH/RFC 04/10] iommu/ipmmu-vmsa: Make IMBUSCTR setup optional [PATCH/RFC 05/10] iommu/ipmmu-vmsa: Allow two bit SL0 [PATCH/RFC 06/10] iommu/ipmmu-vmsa: Also use IOMMU_OF_DECLARE() [PATCH/RFC 07/10] iommu/ipmmu-vmsa: Break out utlb parsing code [PATCH/RFC 08/10] iommu/ipmmu-vmsa: Break out domain allocation code [PATCH/RFC 09/10] iommu/ipmmu-vmsa: Add new IOMMU_DOMAIN_DMA ops [PATCH/RFC 10/10] iommu/ipmmu-vmsa: Hook up r8a7795 DT matching code

Add experimental r8a7795 support for the IPMMU driver. At this point the code is in rather early stages of development and not ready for upstream merge. The DT binding is undocumented and will change.

The r8a7795 IPMMU is almost register compatible with earlier devices like r8a7790-r8a7794, however some bitfields have been shifted slightly. On a grander scale topology has been added and interrupts have been reworked. So now there are several "cache" IPMMU units without interrupt that somehow communicate with IPMMU-MM that is the only instance that supports interrupts. The code refers to IPMMU-MM as a "root" device and the other ones as leaf nodes. In the future the plan is to use DT to describe the relationship.

To make this more interesting the IPMMU driver needs to be shared between 32-bit ARM for r8a7790-r8a7794 and 64-bit ARM for r8a7795. In practice this means that two separate implementations are needed inside the driver to attach to the rather different architecture specific code.

CONFIG_IOMMU_DMA=y is needed on 64-bit ARM while on 32-bit ARM the arch specific dma-mapping code is hooked up rather directly. During init 64-bit ARM IPMMU support is relying on IOMMU_OF_DECLARE().

The code is known to build on 32-bit and 64-bit ARM and x86_64. Tested on 32-bit ARM and 64-bit ARM with addtional DT integration changes.

---

Developed on top of renesas-drivers-2015-12-08-v4.4-rc4 and [PATCH 00/06] iommu/ipmmu-vmsa: IPMMU multi-arch update

 drivers/iommu/ipmmu-vmsa.c |  438 ++++++++++++++++++++++++++++++++++----------
 1 file changed, 344 insertions(+), 94 deletions(-)
#1 lore raw

PATCH 1/10 iommu/ipmmu-vmsa: Introduce features, break out alias

From: Magnus Damm <[email protected]>

Introduce struct ipmmu_features to track various hardware and software implementation changes inside the driver for various kinds of IPMMU hardware. Add use_ns_alias_offset as a first example of a feature to control if the secure register bank offset should be used or not.

---

 drivers/iommu/ipmmu-vmsa.c |   34 ++++++++++++++++++++++++++++------
 1 file changed, 28 insertions(+), 6 deletions(-)
drivers/iommu/ipmmu-vmsa.c +28 -6
@@ -31,6 +31,10 @@
 
 #define IPMMU_CTX_MAX 1
 
+struct ipmmu_features {
+	bool use_ns_alias_offset;
+};
+
 struct ipmmu_vmsa_device {
 	struct device *dev;
 	void __iomem *base;
@@ -43,6 +47,7 @@ struct ipmmu_vmsa_device {
 #ifdef CONFIG_ARM
 	struct dma_iommu_mapping *mapping;
 #endif
+	const struct ipmmu_features *features;
 };
 
 struct ipmmu_vmsa_domain {
@@ -817,13 +822,33 @@ static void ipmmu_device_reset(struct ip
 		ipmmu_write(mmu, i * IM_CTX_SIZE + IMCTR, 0);
 }
 
+static const struct ipmmu_features ipmmu_features_default = {
+	.use_ns_alias_offset = true,
+};
+
+static const struct of_device_id ipmmu_of_ids[] = {
+	{
+		.compatible = "renesas,ipmmu-vmsa",
+		.data = &ipmmu_features_default,
+	}, {
+		/* Terminator */
+	},
+};
+
+MODULE_DEVICE_TABLE(of, ipmmu_of_ids);
+
 static int ipmmu_probe(struct platform_device *pdev)
 {
 	struct ipmmu_vmsa_device *mmu;
+	const struct of_device_id *match;
 	struct resource *res;
 	int irq;
 	int ret;
 
+	match = of_match_node(ipmmu_of_ids, pdev->dev.of_node);
+	if (!match)
+		return -EINVAL;
+
 	mmu = devm_kzalloc(&pdev->dev, sizeof(*mmu), GFP_KERNEL);
 	if (!mmu) {
 		dev_err(&pdev->dev, "cannot allocate device data\n");
@@ -833,6 +858,7 @@ static int ipmmu_probe(struct platform_d
 	mmu->dev = &pdev->dev;
 	mmu->num_utlbs = 32;
 	bitmap_zero(mmu->ctx, IPMMU_CTX_MAX);
+	mmu->features = match->data;
 
 	/* Map I/O memory and request IRQ. */
 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
@@ -852,7 +878,8 @@ static int ipmmu_probe(struct platform_d
 	 * Offset the registers base unconditionally to point to the non-secure
 	 * alias space for now.
 	 */
-	mmu->base += IM_NS_ALIAS_OFFSET;
+	if (mmu->features->use_ns_alias_offset)
+		mmu->base += IM_NS_ALIAS_OFFSET;
 
 	irq = platform_get_irq(pdev, 0);
 	if (irq < 0) {
@@ -899,11 +926,6 @@ static int ipmmu_remove(struct platform_
 	return 0;
 }
 
-static const struct of_device_id ipmmu_of_ids[] = {
-	{ .compatible = "renesas,ipmmu-vmsa", },
-	{ }
-};
-
 static struct platform_driver ipmmu_driver = {
 	.driver = {
 		.name = "ipmmu-vmsa",
#2 lore raw

Re: [PATCH/RFC 01/10] iommu/ipmmu-vmsa: Introduce features, break out alias

Hi Magnus,

Quoted 17 lines · Magnus Damm

From: Magnus Damm <[email protected]>

Introduce struct ipmmu_features to track various hardware and software implementation changes inside the driver for various kinds of IPMMU hardware. Add use_ns_alias_offset as a first example of a feature to control if the secure register bank offset should be used or not.

---

 drivers/iommu/ipmmu-vmsa.c |   34 ++++++++++++++++++++++++++++------
 1 file changed, 28 insertions(+), 6 deletions(-)

drivers/iommu/ipmmu-vmsa.c 2015-12-15 13:41:22.600513000 +0900 line 36

Quoted 6 lines
drivers/iommu/ipmmu-vmsa.c 2015-12-15 13:41:22.600513000 +0900 renamed from drivers/iommu/ipmmu-vmsa.c +3 -0
@@ -31,6 +31,10 @@
 
 #define IPMMU_CTX_MAX 1
 
+struct ipmmu_features {
+       bool use_ns_alias_offset;
+};

Given you're adding more than 4 features in this series, you may want to consider using an unsigned int bitfield instead.

That way you could also store it directly in of_device_id.data, if you want.

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

#3 lore raw

PATCH 2/10 iommu/ipmmu-vmsa: Add optional root device feature

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Add root device handling to the IPMMU driver by allowing certain DT compat strings to enable had_cache_leaf_nodes that in turn will support both root devices with interrupts and leaf devices that face the actual IPMMU consumer devices.

---

 drivers/iommu/ipmmu-vmsa.c |   86 +++++++++++++++++++++++++++++++++++---------
 1 file changed, 69 insertions(+), 17 deletions(-)
drivers/iommu/ipmmu-vmsa.c +69 -17
@@ -33,6 +33,7 @@
 
 struct ipmmu_features {
 	bool use_ns_alias_offset;
+	bool has_cache_leaf_nodes;
 };
 
 struct ipmmu_vmsa_device {
@@ -48,10 +49,12 @@ struct ipmmu_vmsa_device {
 	struct dma_iommu_mapping *mapping;
 #endif
 	const struct ipmmu_features *features;
+	bool is_leaf;
 };
 
 struct ipmmu_vmsa_domain {
 	struct ipmmu_vmsa_device *mmu;
+	struct ipmmu_vmsa_device *root;
 	struct iommu_domain io_domain;
 
 	struct io_pgtable_cfg cfg;
@@ -210,6 +213,36 @@ static void set_dev_data(struct device *
 }
 
 /* -----------------------------------------------------------------------------
+ * Root device handling
+ */
+
+static bool ipmmu_is_root(struct ipmmu_vmsa_device *mmu)
+{
+	if (mmu->features->has_cache_leaf_nodes)
+		return mmu->is_leaf ? false : true;
+	else
+		return true; /* older IPMMU hardware treated as single root */
+}
+
+static struct ipmmu_vmsa_device *ipmmu_find_root(struct ipmmu_vmsa_device *leaf)
+{
+	struct ipmmu_vmsa_device *mmu;
+
+	if (ipmmu_is_root(leaf))
+		return leaf;
+
+	spin_lock(&ipmmu_devices_lock);
+
+	list_for_each_entry(mmu, &ipmmu_devices, list) {
+		if (ipmmu_is_root(mmu))
+			break;
+	}
+
+	spin_unlock(&ipmmu_devices_lock);
+	return mmu;
+}
+
+/* -----------------------------------------------------------------------------
  * Read/Write Access
  */
 
@@ -226,13 +259,13 @@ static void ipmmu_write(struct ipmmu_vms
 
 static u32 ipmmu_ctx_read(struct ipmmu_vmsa_domain *domain, unsigned int reg)
 {
-	return ipmmu_read(domain->mmu, domain->context_id * IM_CTX_SIZE + reg);
+	return ipmmu_read(domain->root, domain->context_id * IM_CTX_SIZE + reg);
 }
 
 static void ipmmu_ctx_write(struct ipmmu_vmsa_domain *domain, unsigned int reg,
 			    u32 data)
 {
-	ipmmu_write(domain->mmu, domain->context_id * IM_CTX_SIZE + reg, data);
+	ipmmu_write(domain->root, domain->context_id * IM_CTX_SIZE + reg, data);
 }
 
 /* -----------------------------------------------------------------------------
@@ -355,7 +388,7 @@ static int ipmmu_domain_init_context(str
 	 * TODO: Add support for coherent walk through CCI with DVM and remove
 	 * cache handling. For now, delegate it to the io-pgtable code.
 	 */
-	domain->cfg.iommu_dev = domain->mmu->dev;
+	domain->cfg.iommu_dev = domain->root->dev;
 
 	domain->iop = alloc_io_pgtable_ops(ARM_32_LPAE_S1, &domain->cfg,
 					   domain);
@@ -365,14 +398,14 @@ static int ipmmu_domain_init_context(str
 	/*
 	 * Find an unused context.
 	 */
-	ret = bitmap_find_free_region(domain->mmu->ctx, IPMMU_CTX_MAX, 0);
+	ret = bitmap_find_free_region(domain->root->ctx, IPMMU_CTX_MAX, 0);
 	if (ret < 0) {
 		free_io_pgtable_ops(domain->iop);
 		return ret;
 	}
 
 	domain->context_id = ret;
-	domain->mmu->domains[ret] = domain;
+	domain->root->domains[ret] = domain;
 
 	/* TTBR0 */
 	ttbr = domain->cfg.arm_lpae_s1_cfg.ttbr[0];
@@ -532,7 +565,7 @@ static int ipmmu_attach_device(struct io
 			       struct device *dev)
 {
 	struct ipmmu_vmsa_dev_data *dev_data = get_dev_data(dev);
-	struct ipmmu_vmsa_device *mmu = dev_data->mmu;
+	struct ipmmu_vmsa_device *root, *mmu = dev_data->mmu;
 	struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
 	unsigned long flags;
 	int ret = 0;
@@ -542,11 +575,18 @@ static int ipmmu_attach_device(struct io
 		return -ENXIO;
 	}
 
+	root = ipmmu_find_root(dev_data->mmu);
+	if (!root) {
+		dev_err(dev, "Unable to locate root IPMMU\n");
+		return -EAGAIN;
+	}
+
 	spin_lock_irqsave(&domain->lock, flags);
 
 	if (!domain->mmu) {
 		/* The domain hasn't been used yet, initialize it. */
 		domain->mmu = mmu;
+		domain->root = root;
 		ret = ipmmu_domain_init_context(domain);
 	} else if (domain->mmu != mmu) {
 		/*
@@ -824,6 +864,7 @@ static void ipmmu_device_reset(struct ip
 
 static const struct ipmmu_features ipmmu_features_default = {
 	.use_ns_alias_offset = true,
+	.has_cache_leaf_nodes = false,
 };
 
 static const struct of_device_id ipmmu_of_ids[] = {
@@ -882,19 +923,30 @@ static int ipmmu_probe(struct platform_d
 		mmu->base += IM_NS_ALIAS_OFFSET;
 
 	irq = platform_get_irq(pdev, 0);
-	if (irq < 0) {
-		dev_err(&pdev->dev, "no IRQ found\n");
-		return irq;
-	}
 
-	ret = devm_request_irq(&pdev->dev, irq, ipmmu_irq, 0,
-			       dev_name(&pdev->dev), mmu);
-	if (ret < 0) {
-		dev_err(&pdev->dev, "failed to request IRQ %d\n", irq);
-		return ret;
-	}
+	/*
+	 * Determine if this IPMMU instance is a leaf device by checking
+	 * if an interrupt is availabile or not
+	 */
+	if (mmu->features->has_cache_leaf_nodes && irq < 0)
+		mmu->is_leaf = true;
+
+	/* Root devices have mandatory IRQs */
+	if (ipmmu_is_root(mmu)) {
+		if (irq < 0) {
+			dev_err(&pdev->dev, "no IRQ found\n");
+			return irq;
+		}
 
-	ipmmu_device_reset(mmu);
+		ret = devm_request_irq(&pdev->dev, irq, ipmmu_irq, 0,
+				       dev_name(&pdev->dev), mmu);
+		if (ret < 0) {
+			dev_err(&pdev->dev, "failed to request IRQ %d\n", irq);
+			return ret;
+		}
+
+		ipmmu_device_reset(mmu);
+	}
 
 	/*
 	 * We can't create the ARM mapping here as it requires the bus to have
#4 lore raw

PATCH 3/10 iommu/ipmmu-vmsa: Write IMCTR twice

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Write IMCTR both in the root device and the leaf node.

---

 drivers/iommu/ipmmu-vmsa.c |   17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)
drivers/iommu/ipmmu-vmsa.c +14 -3
@@ -268,6 +268,16 @@ static void ipmmu_ctx_write(struct ipmmu
 	ipmmu_write(domain->root, domain->context_id * IM_CTX_SIZE + reg, data);
 }
 
+static void ipmmu_ctx_write2(struct ipmmu_vmsa_domain *domain, unsigned int reg,
+			     u32 data)
+{
+	if (domain->mmu != domain->root)
+		ipmmu_write(domain->mmu,
+			    domain->context_id * IM_CTX_SIZE + reg, data);
+
+	ipmmu_write(domain->root, domain->context_id * IM_CTX_SIZE + reg, data);
+}
+
 /* -----------------------------------------------------------------------------
  * TLB and microTLB Management
  */
@@ -294,7 +304,7 @@ static void ipmmu_tlb_invalidate(struct
 
 	reg = ipmmu_ctx_read(domain, IMCTR);
 	reg |= IMCTR_FLUSH;
-	ipmmu_ctx_write(domain, IMCTR, reg);
+	ipmmu_ctx_write2(domain, IMCTR, reg);
 
 	ipmmu_tlb_sync(domain);
 }
@@ -442,7 +452,8 @@ static int ipmmu_domain_init_context(str
 	 * software management as we have no use for it. Flush the TLB as
 	 * required when modifying the context registers.
 	 */
-	ipmmu_ctx_write(domain, IMCTR, IMCTR_INTEN | IMCTR_FLUSH | IMCTR_MMUEN);
+	ipmmu_ctx_write2(domain, IMCTR,
+			 IMCTR_INTEN | IMCTR_FLUSH | IMCTR_MMUEN);
 
 	return 0;
 }
@@ -457,7 +468,7 @@ static void ipmmu_domain_destroy_context
 	 *
 	 * TODO: Is TLB flush really needed ?
 	 */
-	ipmmu_ctx_write(domain, IMCTR, IMCTR_FLUSH);
+	ipmmu_ctx_write2(domain, IMCTR, IMCTR_FLUSH);
 	ipmmu_tlb_sync(domain);
 }
 
#5 lore raw

PATCH 4/10 iommu/ipmmu-vmsa: Make IMBUSCTR setup optional

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Allow certain DT compat strings to opt-out of setting up IMBUSCR. The default case is unchanged.

---

 drivers/iommu/ipmmu-vmsa.c |   10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)
drivers/iommu/ipmmu-vmsa.c +6 -4
@@ -34,6 +34,7 @@
 struct ipmmu_features {
 	bool use_ns_alias_offset;
 	bool has_cache_leaf_nodes;
+	bool setup_imbuscr;
 };
 
 struct ipmmu_vmsa_device {
@@ -435,10 +436,10 @@ static int ipmmu_domain_init_context(str
 	ipmmu_ctx_write(domain, IMMAIR0, domain->cfg.arm_lpae_s1_cfg.mair[0]);
 
 	/* IMBUSCR */
-	ipmmu_ctx_write(domain, IMBUSCR,
-			ipmmu_ctx_read(domain, IMBUSCR) &
-			~(IMBUSCR_DVM | IMBUSCR_BUSSEL_MASK));
-
+	if (domain->root->features->setup_imbuscr)
+		ipmmu_ctx_write(domain, IMBUSCR,
+				ipmmu_ctx_read(domain, IMBUSCR) &
+				~(IMBUSCR_DVM | IMBUSCR_BUSSEL_MASK));
 	/*
 	 * IMSTR
 	 * Clear all interrupt flags.
@@ -876,6 +877,7 @@ static void ipmmu_device_reset(struct ip
 static const struct ipmmu_features ipmmu_features_default = {
 	.use_ns_alias_offset = true,
 	.has_cache_leaf_nodes = false,
+	.setup_imbuscr = true,
 };
 
 static const struct of_device_id ipmmu_of_ids[] = {
#6 lore raw

PATCH 5/10 iommu/ipmmu-vmsa: Allow two bit SL0

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Introduce support for two bit SL0 bitfield in IMTTBCR by using a separate feature flag.

---

 drivers/iommu/ipmmu-vmsa.c |   15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)
drivers/iommu/ipmmu-vmsa.c +14 -1
@@ -35,6 +35,7 @@ struct ipmmu_features {
 	bool use_ns_alias_offset;
 	bool has_cache_leaf_nodes;
 	bool setup_imbuscr;
+	bool twobit_imttbcr_sl0;
 };
 
 struct ipmmu_vmsa_device {
@@ -139,6 +140,10 @@ static struct ipmmu_vmsa_domain *to_vmsa
 #define IMTTBCR_TSZ0_MASK		(7 << 0)
 #define IMTTBCR_TSZ0_SHIFT		O
 
+#define IMTTBCR_SL0_TWOBIT_LVL_3	(0 << 6)
+#define IMTTBCR_SL0_TWOBIT_LVL_2	(1 << 6)
+#define IMTTBCR_SL0_TWOBIT_LVL_1	(2 << 6)
+
 #define IMBUSCR				0x000c
 #define IMBUSCR_DVM			(1 << 2)
 #define IMBUSCR_BUSSEL_SYS		(0 << 0)
@@ -377,6 +382,7 @@ static struct iommu_gather_ops ipmmu_gat
 static int ipmmu_domain_init_context(struct ipmmu_vmsa_domain *domain)
 {
 	phys_addr_t ttbr;
+	u32 tmp;
 	int ret;
 
 	/*
@@ -428,9 +434,15 @@ static int ipmmu_domain_init_context(str
 	 * We use long descriptors with inner-shareable WBWA tables and allocate
 	 * the whole 32-bit VA space to TTBR0.
 	 */
+
+	if (domain->root->features->twobit_imttbcr_sl0)
+		tmp = IMTTBCR_SL0_TWOBIT_LVL_1;
+	else
+		tmp = IMTTBCR_SL0_LVL_1;
+
 	ipmmu_ctx_write(domain, IMTTBCR, IMTTBCR_EAE |
 			IMTTBCR_SH0_INNER_SHAREABLE | IMTTBCR_ORGN0_WB_WA |
-			IMTTBCR_IRGN0_WB_WA | IMTTBCR_SL0_LVL_1);
+			IMTTBCR_IRGN0_WB_WA | tmp);
 
 	/* MAIR0 */
 	ipmmu_ctx_write(domain, IMMAIR0, domain->cfg.arm_lpae_s1_cfg.mair[0]);
@@ -878,6 +890,7 @@ static const struct ipmmu_features ipmmu
 	.use_ns_alias_offset = true,
 	.has_cache_leaf_nodes = false,
 	.setup_imbuscr = true,
+	.twobit_imttbcr_sl0 = false,
 };
 
 static const struct of_device_id ipmmu_of_ids[] = {
#7 lore raw

PATCH 6/10 iommu/ipmmu-vmsa: Also use IOMMU_OF_DECLARE()

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Hook up IOMMU_OF_DECLARE() support. The default case has the use_of_iommu feature disabled so the init order remains unchanged for the default case.

---

 drivers/iommu/ipmmu-vmsa.c |   35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)
drivers/iommu/ipmmu-vmsa.c +34 -1
@@ -18,6 +18,8 @@
 #include <linux/iommu.h>
 #include <linux/module.h>
 #include <linux/of.h>
+#include <linux/of_iommu.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <linux/sizes.h>
 #include <linux/slab.h>
@@ -36,6 +38,7 @@ struct ipmmu_features {
 	bool has_cache_leaf_nodes;
 	bool setup_imbuscr;
 	bool twobit_imttbcr_sl0;
+	bool use_of_iommu;
 };
 
 struct ipmmu_vmsa_device {
@@ -891,6 +894,7 @@ static const struct ipmmu_features ipmmu
 	.has_cache_leaf_nodes = false,
 	.setup_imbuscr = true,
 	.twobit_imttbcr_sl0 = false,
+	.use_of_iommu = false,
 };
 
 static const struct of_device_id ipmmu_of_ids[] = {
@@ -1015,15 +1019,21 @@ static struct platform_driver ipmmu_driv
 
 static int __init ipmmu_init(void)
 {
+	static bool setup_done;
 	int ret;
 
+	if (setup_done)
+		return 0;
+
 	ret = platform_driver_register(&ipmmu_driver);
 	if (ret < 0)
 		return ret;
 
+#ifdef CONFIG_ARM
 	if (!iommu_present(&platform_bus_type))
 		bus_set_iommu(&platform_bus_type, &ipmmu_ops);
-
+#endif
+	setup_done = true;
 	return 0;
 }
 
@@ -1035,6 +1045,29 @@ static void __exit ipmmu_exit(void)
 subsys_initcall(ipmmu_init);
 module_exit(ipmmu_exit);
 
+static int __init ipmmu_vmsa_iommu_of_setup(struct device_node *np)
+{
+	const struct of_device_id *match;
+	const struct ipmmu_features *feature;
+	static const struct iommu_ops *ops = &ipmmu_ops;
+
+	ipmmu_init();
+
+	match = of_match_node(ipmmu_of_ids, np);
+	if (match) {
+		feature = match->data;
+		if (feature->use_of_iommu) {
+			of_iommu_set_ops(np, (struct iommu_ops *)ops);
+			if (!iommu_present(&platform_bus_type))
+				bus_set_iommu(&platform_bus_type, ops);
+		}
+	}
+	return 0;
+}
+
+IOMMU_OF_DECLARE(ipmmu_vmsa_iommu_of, "renesas,ipmmu-vmsa",
+		 ipmmu_vmsa_iommu_of_setup);
+
 MODULE_DESCRIPTION("IOMMU API for Renesas VMSA-compatible IPMMU");
 MODULE_AUTHOR("Laurent Pinchart <[email protected]>");
 MODULE_LICENSE("GPL v2");
#8 lore raw

PATCH 7/10 iommu/ipmmu-vmsa: Break out utlb parsing code

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Break out the utlb parsing code and dev_data allocation into a separate function. This is preparation for future code sharing.

---

 drivers/iommu/ipmmu-vmsa.c |  126 ++++++++++++++++++++++++--------------------
 1 file changed, 70 insertions(+), 56 deletions(-)
drivers/iommu/ipmmu-vmsa.c +70 -56
@@ -607,6 +607,69 @@ static int ipmmu_find_utlbs(struct ipmmu
 	return 0;
 }
 
+static int ipmmu_init_platform_device(struct device *dev,
+				      struct iommu_group *group)
+{
+	struct ipmmu_vmsa_dev_data *dev_data;
+	struct ipmmu_vmsa_device *mmu;
+	unsigned int *utlbs;
+	unsigned int i;
+	int num_utlbs;
+	int ret = -ENODEV;
+
+	/* Find the master corresponding to the device. */
+
+	num_utlbs = of_count_phandle_with_args(dev->of_node, "iommus",
+					       "#iommu-cells");
+	if (num_utlbs < 0)
+		return -ENODEV;
+
+	utlbs = kcalloc(num_utlbs, sizeof(*utlbs), GFP_KERNEL);
+	if (!utlbs)
+		return -ENOMEM;
+
+	spin_lock(&ipmmu_devices_lock);
+
+	list_for_each_entry(mmu, &ipmmu_devices, list) {
+		ret = ipmmu_find_utlbs(mmu, dev, utlbs, num_utlbs);
+		if (!ret) {
+			/*
+			 * TODO Take a reference to the MMU to protect
+			 * against device removal.
+			 */
+			break;
+		}
+	}
+
+	spin_unlock(&ipmmu_devices_lock);
+
+	if (ret < 0)
+		goto error;
+
+	for (i = 0; i < num_utlbs; ++i) {
+		if (utlbs[i] >= mmu->num_utlbs) {
+			ret = -EINVAL;
+			goto error;
+		}
+	}
+
+	dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
+	if (!dev_data) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	dev_data->mmu = mmu;
+	dev_data->utlbs = utlbs;
+	dev_data->num_utlbs = num_utlbs;
+	set_dev_data(dev, dev_data);
+	return 0;
+
+error:
+	kfree(utlbs);
+	return ret;
+}
+
 #ifdef CONFIG_ARM
 static int ipmmu_map_attach(struct device *dev, struct ipmmu_vmsa_device *mmu)
 {
@@ -663,56 +726,15 @@ static inline void ipmmu_release_mapping
 
 static int ipmmu_add_device(struct device *dev)
 {
-	struct ipmmu_vmsa_dev_data *dev_data = get_dev_data(dev);
-	struct ipmmu_vmsa_device *mmu;
-	struct iommu_group *group = NULL;
-	unsigned int *utlbs;
-	unsigned int i;
-	int num_utlbs;
-	int ret = -ENODEV;
+	struct iommu_group *group;
+	int ret;
 
-	if (dev_data) {
+	if (get_dev_data(dev)) {
 		dev_warn(dev, "IOMMU driver already assigned to device %s\n",
 			 dev_name(dev));
 		return -EINVAL;
 	}
 
-	/* Find the master corresponding to the device. */
-
-	num_utlbs = of_count_phandle_with_args(dev->of_node, "iommus",
-					       "#iommu-cells");
-	if (num_utlbs < 0)
-		return -ENODEV;
-
-	utlbs = kcalloc(num_utlbs, sizeof(*utlbs), GFP_KERNEL);
-	if (!utlbs)
-		return -ENOMEM;
-
-	spin_lock(&ipmmu_devices_lock);
-
-	list_for_each_entry(mmu, &ipmmu_devices, list) {
-		ret = ipmmu_find_utlbs(mmu, dev, utlbs, num_utlbs);
-		if (!ret) {
-			/*
-			 * TODO Take a reference to the MMU to protect
-			 * against device removal.
-			 */
-			break;
-		}
-	}
-
-	spin_unlock(&ipmmu_devices_lock);
-
-	if (ret < 0)
-		return -ENODEV;
-
-	for (i = 0; i < num_utlbs; ++i) {
-		if (utlbs[i] >= mmu->num_utlbs) {
-			ret = -EINVAL;
-			goto error;
-		}
-	}
-
 	/* Create a device group and add the device to it. */
 	group = iommu_group_alloc();
 	if (IS_ERR(group)) {
@@ -730,27 +752,19 @@ static int ipmmu_add_device(struct devic
 		goto error;
 	}
 
-	dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL);
-	if (!dev_data) {
-		ret = -ENOMEM;
+	ret = ipmmu_init_platform_device(dev, group);
+	if (ret < 0) {
+		dev_err(dev, "Failed to init platform device\n");
 		goto error;
 	}
 
-	dev_data->mmu = mmu;
-	dev_data->utlbs = utlbs;
-	dev_data->num_utlbs = num_utlbs;
-	set_dev_data(dev, dev_data);
-
-	ret = ipmmu_map_attach(dev, mmu);
+	ret = ipmmu_map_attach(dev, get_dev_data(dev)->mmu);
 	if (ret < 0)
 		goto error;
 
 	return 0;
 
 error:
-	kfree(dev_data);
-	kfree(utlbs);
-
 	set_dev_data(dev, NULL);
 
 	if (!IS_ERR_OR_NULL(group))
#9 lore raw

PATCH 8/10 iommu/ipmmu-vmsa: Break out domain allocation code

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Break out the domain allocation code into a separate function. This is preparation for future code sharing.

---

 drivers/iommu/ipmmu-vmsa.c |   13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)
drivers/iommu/ipmmu-vmsa.c +9 -4
@@ -457,13 +457,10 @@ static irqreturn_t ipmmu_domain_irq(int
  * IOMMU Operations
  */
 
-static struct iommu_domain *ipmmu_domain_alloc(unsigned type)
+static struct iommu_domain *__ipmmu_domain_alloc(unsigned type)
 {
 	struct ipmmu_vmsa_domain *domain;
 
-	if (type != IOMMU_DOMAIN_UNMANAGED)
-		return NULL;
-
 	domain = kzalloc(sizeof(*domain), GFP_KERNEL);
 	if (!domain)
 		return NULL;
@@ -473,6 +470,14 @@ static struct iommu_domain *ipmmu_domain
 	return &domain->io_domain;
 }
 
+static struct iommu_domain *ipmmu_domain_alloc(unsigned type)
+{
+	if (type != IOMMU_DOMAIN_UNMANAGED)
+		return NULL;
+
+	return __ipmmu_domain_alloc(type);
+}
+
 static void ipmmu_domain_free(struct iommu_domain *io_domain)
 {
 	struct ipmmu_vmsa_domain *domain = to_vmsa_domain(io_domain);
#10 lore raw

PATCH 9/10 iommu/ipmmu-vmsa: Add new IOMMU_DOMAIN_DMA ops

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Introduce a new set of iommu_ops suitable for 64-bit ARM and IOMMU_OF_DECLARE() used on r8a7795. The ->of_xlate() callback is wrapped in #ifdefs to also compile of x86_64.

---

 drivers/iommu/ipmmu-vmsa.c |   89 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 88 insertions(+), 1 deletion(-)
drivers/iommu/ipmmu-vmsa.c +88 -1
@@ -10,6 +10,7 @@
 
 #include <linux/bitmap.h>
 #include <linux/delay.h>
+#include <linux/dma-iommu.h>
 #include <linux/dma-mapping.h>
 #include <linux/err.h>
 #include <linux/export.h>
@@ -895,6 +896,92 @@ static const struct iommu_ops ipmmu_ops
 	.pgsize_bitmap = SZ_1G | SZ_2M | SZ_4K,
 };
 
+static struct iommu_domain *ipmmu_domain_alloc_dma(unsigned type)
+{
+	struct iommu_domain *io_domain;
+
+	if (type != IOMMU_DOMAIN_DMA)
+		return NULL;
+
+	io_domain = __ipmmu_domain_alloc(type);
+	if (io_domain)
+		iommu_get_dma_cookie(io_domain);
+
+	return io_domain;
+}
+
+static void ipmmu_domain_free_dma(struct iommu_domain *io_domain)
+{
+	iommu_put_dma_cookie(io_domain);
+	ipmmu_domain_free(io_domain);
+}
+
+static int ipmmu_add_device_dma(struct device *dev)
+{
+	struct iommu_group *group;
+
+	/* only accept devices with iommus property */
+	if (of_count_phandle_with_args(dev->of_node, "iommus",
+				       "#iommu-cells") < 0)
+		return -ENODEV;
+
+	group = iommu_group_get_for_dev(dev);
+	if (IS_ERR(group))
+		return PTR_ERR(group);
+
+	return 0;
+}
+
+static void ipmmu_remove_device_dma(struct device *dev)
+{
+	iommu_group_remove_device(dev);
+}
+
+static struct iommu_group *ipmmu_device_group_dma(struct device *dev)
+{
+	struct iommu_group *group;
+	int ret;
+
+	group = generic_device_group(dev);
+	if (IS_ERR(group))
+		return group;
+
+	ret = ipmmu_init_platform_device(dev, group);
+	if (ret) {
+		iommu_group_put(group);
+		group = ERR_PTR(ret);
+	}
+
+	return group;
+}
+
+#ifdef CONFIG_OF_IOMMU
+static int ipmmu_of_xlate_dma(struct device *dev,
+			      struct of_phandle_args *spec)
+{
+	/* dummy callback to satisfy of_iommu_configure() */
+	return 0;
+}
+#endif
+
+static const struct iommu_ops ipmmu_ops_dma = {
+	.domain_alloc = ipmmu_domain_alloc_dma,
+	.domain_free = ipmmu_domain_free_dma,
+	.attach_dev = ipmmu_attach_device,
+	.detach_dev = ipmmu_detach_device,
+	.map = ipmmu_map,
+	.unmap = ipmmu_unmap,
+	.map_sg = default_iommu_map_sg,
+	.iova_to_phys = ipmmu_iova_to_phys,
+	.add_device = ipmmu_add_device_dma,
+	.remove_device = ipmmu_remove_device_dma,
+	.device_group = ipmmu_device_group_dma,
+	.pgsize_bitmap = SZ_4K,
+#ifdef CONFIG_OF_IOMMU
+	.of_xlate = ipmmu_of_xlate_dma,
+#endif
+};
+
 /* -----------------------------------------------------------------------------
  * Probe/remove and init
  */
@@ -1068,7 +1155,7 @@ static int __init ipmmu_vmsa_iommu_of_se
 {
 	const struct of_device_id *match;
 	const struct ipmmu_features *feature;
-	static const struct iommu_ops *ops = &ipmmu_ops;
+	static const struct iommu_ops *ops = &ipmmu_ops_dma;
 
 	ipmmu_init();
 
#11 lore raw

PATCH 10/10 iommu/ipmmu-vmsa: Hook up r8a7795 DT matching code

In reply to Magnus Damm

From: Magnus Damm <[email protected]>

Tie in r8a7795 features and update the IOMMU_OF_DECLARE compat string to hook up the updated compat string.

TODO:
- Describe IPMMU topology via DT
- Document DT binding and topology
- Figure out how to avoid multiple IOMMU_OF_DECLARE()

---

 drivers/iommu/ipmmu-vmsa.c |   13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)
drivers/iommu/ipmmu-vmsa.c +12 -1
@@ -999,11 +999,22 @@ static const struct ipmmu_features ipmmu
 	.use_of_iommu = false,
 };
 
+static const struct ipmmu_features ipmmu_features_r8a7795 = {
+	.use_ns_alias_offset = false,
+	.has_cache_leaf_nodes = true,
+	.setup_imbuscr = false,
+	.twobit_imttbcr_sl0 = true,
+	.use_of_iommu = true,
+};
+
 static const struct of_device_id ipmmu_of_ids[] = {
 	{
 		.compatible = "renesas,ipmmu-vmsa",
 		.data = &ipmmu_features_default,
 	}, {
+		.compatible = "renesas,ipmmu-r8a7795",
+		.data = &ipmmu_features_r8a7795,
+	}, {
 		/* Terminator */
 	},
 };
@@ -1163,7 +1174,7 @@ static int __init ipmmu_vmsa_iommu_of_se
 	return 0;
 }
 
-IOMMU_OF_DECLARE(ipmmu_vmsa_iommu_of, "renesas,ipmmu-vmsa",
+IOMMU_OF_DECLARE(ipmmu_vmsa_iommu_of, "renesas,ipmmu-r8a7795",
 		 ipmmu_vmsa_iommu_of_setup);
 
 MODULE_DESCRIPTION("IOMMU API for Renesas VMSA-compatible IPMMU");