Skip to contentSkip to conversation

arm64: IOMMU-backed DMA mapping

PATCH New Robin Murphy 4 participants 11 messages May 27, 2015 – Jun 11, 2015

Subsystems (3)
lore
Share & tools
lore

Robin Murphy

author maintainer
#0 lore raw

cover arm64: IOMMU-backed DMA mapping

Hi all,

Here's the cleaned-up and nominally ready-to-go IOMMU DMA mapping series, with (I think) all the outstanding comments[1] addressed. I appreciate I've probably missed the boat for 4.2, but I'm ever the optimist...

Major changes since RFC:
- Renamed things, shuffled things around, and removed some of the less useful abstraction for simplicity and clarity.
- Fixed iommu_dma_alloc to handle compound pages and map buffers as scatterlists.
- Filled the API holes like size-aligned allocations and get_sgtable.
- Solved the problem of attching devices to domains before the devices are created, which confuses IOMMU drivers like the ARM SMMU.

A branch is available at:
git://linux-arm.org/linux-rm iommu/dma

In terms of where we go from here, this is more or less what I envisage:
- Get the current functionality merged to unblock arm64 IOMMU work
- Implement proper managed IOMMU domains around this, integrating the iova_domain into the iommu_domain itself, and getting rid of the iommu_dma_domain wrapper.
- Find all of the ad-hoc "managed domain" implementations in drivers currently using the arm_iommu_* functions and convert them to "proper" managed domains.
- Finally, with the external users taken care of, bring arch/arm over to the common DMA mapping code.

In the meantime, Laurent's proposal for probe deferral[2] offers hope that the bus notifier dance may only need to be short-lived, and I'm hoping to spend some more time on DT-based IOMMU group handling for platform devices, which should hopefully tie in with default domains to make the add_device callback mostly redundant.

[1]:http://thread.gmane.org/gmane.linux.kernel.iommu/8773 [2]:http://thread.gmane.org/gmane.linux.kernel.iommu/9552

Robin Murphy (4):
iommu/iova: Avoid over-allocating when size-aligned
iommu: Implement common IOMMU ops for DMA mapping
arm64: Add IOMMU dma_ops
arm64: Hook up IOMMU dma_ops

 arch/arm64/Kconfig                   |   1 +
 arch/arm64/include/asm/device.h      |   3 +
 arch/arm64/include/asm/dma-mapping.h |  25 +-
 arch/arm64/mm/dma-mapping.c          | 357 ++++++++++++++++++++++
 drivers/iommu/Kconfig                |   7 +
 drivers/iommu/Makefile               |   1 +
 drivers/iommu/dma-iommu.c            | 560 +++++++++++++++++++++++++++++++++++
 drivers/iommu/intel-iommu.c          |   2 +
 drivers/iommu/iova.c                 |  23 +-
 include/linux/dma-iommu.h            |  94 ++++++
 10 files changed, 1051 insertions(+), 22 deletions(-)
 create mode 100644 drivers/iommu/dma-iommu.c
 create mode 100644 include/linux/dma-iommu.h
Signature · 1 line
1.9.1
author maintainer
#1 lore raw

PATCH 1/4 iommu/iova: Avoid over-allocating when size-aligned

Currently, allocating a size-aligned IOVA region quietly adjusts the actual allocation size in the process, returning a rounded-up power-of-two-sized allocation. This results in mismatched behaviour in the IOMMU driver if the original size was not a power of two, where the original size is mapped, but the rounded-up IOVA size is unmapped.

Whilst some IOMMUs will happily unmap already-unmapped pages, others consider this an error, so fix it by computing the necessary alignment padding without altering the actual allocation size. Also clean up by making pad_size unsigned, since negative padding makes no sense.

CC David Woodhouse <[email protected]>
---
 drivers/iommu/intel-iommu.c |  2 ++
 drivers/iommu/iova.c        | 23 ++++++-----------------
 2 files changed, 8 insertions(+), 17 deletions(-)
drivers/iommu/intel-iommu.c +2 -0
index 68d43be..ada1fb9 100644
@@ -2910,6 +2910,8 @@ static struct iova *intel_alloc_iova(struct device *dev,
 
 	/* Restrict dma_mask to the width that the iommu can handle */
 	dma_mask = min_t(uint64_t, DOMAIN_MAX_ADDR(domain->gaw), dma_mask);
+	/* Ensure we reserve the whole size-aligned region */
+	nrpages = __roundup_pow_of_two(nrpages);
 
 	if (!dmar_forcedac && dma_mask > DMA_BIT_MASK(32)) {
 		/*
drivers/iommu/iova.c +6 -17
index 9dd8208..2c4a51b 100644
@@ -120,19 +120,14 @@ __cached_rbnode_delete_update(struct iova_domain *iovad, struct iova *free)
 	}
 }
 
-/* Computes the padding size required, to make the
- * the start address naturally aligned on its size
+/*
+ * Computes the padding size required, to make the start address
+ * naturally aligned on the power-of-two order of its size
  */
-static int
-iova_get_pad_size(int size, unsigned int limit_pfn)
+static unsigned int
+iova_get_pad_size(unsigned int size, unsigned int limit_pfn)
 {
-	unsigned int pad_size = 0;
-	unsigned int order = ilog2(size);
-
-	if (order)
-		pad_size = (limit_pfn + 1) % (1 << order);
-
-	return pad_size;
+	return (limit_pfn + 1 - size) & (__roundup_pow_of_two(size) - 1);
 }
 
 static int __alloc_and_insert_iova_range(struct iova_domain *iovad,
@@ -264,12 +259,6 @@ alloc_iova(struct iova_domain *iovad, unsigned long size,
 	if (!new_iova)
 		return NULL;
 
-	/* If size aligned is set then round the size to
-	 * to next power of two.
-	 */
-	if (size_aligned)
-		size = __roundup_pow_of_two(size);
-
 	ret = __alloc_and_insert_iova_range(iovad, size, limit_pfn,
 			new_iova, size_aligned);
 
Signature · 1 line
1.9.1
author maintainer
#2 lore raw

PATCH 2/4 iommu: Implement common IOMMU ops for DMA mapping

In reply to Robin Murphy

Taking inspiration from the existing arch/arm code, break out some generic functions to interface the DMA-API to the IOMMU-API. This will do the bulk of the heavy lifting for IOMMU-backed dma-mapping.

---
 drivers/iommu/Kconfig     |   7 +
 drivers/iommu/Makefile    |   1 +
 drivers/iommu/dma-iommu.c | 560 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/dma-iommu.h |  94 ++++++++
 4 files changed, 662 insertions(+)
 create mode 100644 drivers/iommu/dma-iommu.c
 create mode 100644 include/linux/dma-iommu.h
drivers/iommu/Kconfig +7 -0
index 1ae4e54..9107b6e 100644
@@ -48,6 +48,13 @@ config OF_IOMMU
        def_bool y
        depends on OF && IOMMU_API
 
+# IOMMU-agnostic DMA-mapping layer
+config IOMMU_DMA
+	bool
+	depends on NEED_SG_DMA_LENGTH
+	select IOMMU_API
+	select IOMMU_IOVA
+
 config FSL_PAMU
 	bool "Freescale IOMMU support"
 	depends on PPC32
drivers/iommu/Makefile +1 -0
index 080ffab..574b241 100644
@@ -1,6 +1,7 @@
 obj-$(CONFIG_IOMMU_API) += iommu.o
 obj-$(CONFIG_IOMMU_API) += iommu-traces.o
 obj-$(CONFIG_IOMMU_API) += iommu-sysfs.o
+obj-$(CONFIG_IOMMU_DMA) += dma-iommu.o
 obj-$(CONFIG_IOMMU_IO_PGTABLE) += io-pgtable.o
 obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o
 obj-$(CONFIG_IOMMU_IOVA) += iova.o
drivers/iommu/dma-iommu.c new file +560 -0

560 lines not on this page · raw message

include/linux/dma-iommu.h new file +94 -0

94 lines not on this page · raw message

Signature · 1 line
1.9.1
author maintainer
#6 lore raw

PATCH 3/4 arm64: Add IOMMU dma_ops

In reply to Robin Murphy

Taking some inspiration from the arch/arm code, implement the arch-specific side of the DMA mapping ops using the new IOMMU-DMA layer.

Whilst proliferating per-device private IOMMU data via dev->archdata is less than ideal, it will do the job for now, especially since we can't easily handle the kind of problematic system topologies in the current IOMMU API anyway.

---
 arch/arm64/include/asm/device.h      |   3 +
 arch/arm64/include/asm/dma-mapping.h |  14 ++
 arch/arm64/mm/dma-mapping.c          | 342 +++++++++++++++++++++++++++++++++++
 include/linux/dma-iommu.h            |   4 +-
 4 files changed, 361 insertions(+), 2 deletions(-)
arch/arm64/include/asm/device.h +3 -0
index 243ef25..510cee1 100644
@@ -20,6 +20,9 @@ struct dev_archdata {
 	struct dma_map_ops *dma_ops;
 #ifdef CONFIG_IOMMU_API
 	void *iommu;			/* private IOMMU data */
+#ifdef CONFIG_IOMMU_DMA
+	struct iommu_dma_domain *dma_domain;
+#endif
 #endif
 	bool dma_coherent;
 };
arch/arm64/include/asm/dma-mapping.h +14 -0
index 9437e3d..835a8d1 100644
@@ -61,6 +61,20 @@ static inline bool is_device_dma_coherent(struct device *dev)
 }
 
 #include <asm-generic/dma-mapping-common.h>
+#include <linux/dma-iommu.h>
+
+#ifdef CONFIG_IOMMU_DMA
+static inline struct iommu_dma_domain *arch_get_dma_domain(struct device *dev)
+{
+	return dev->archdata.dma_domain;
+}
+
+static inline void arch_set_dma_domain(struct device *dev,
+				  struct iommu_dma_domain *dma_domain)
+{
+	dev->archdata.dma_domain = dma_domain;
+}
+#endif
 
 static inline dma_addr_t phys_to_dma(struct device *dev, phys_addr_t paddr)
 {
arch/arm64/mm/dma-mapping.c +342 -0

345 lines not on this page · raw message

include/linux/dma-iommu.h +2 -2
index 6708a8a..8062ed7 100644
@@ -79,8 +79,6 @@ static inline int iommu_dma_init(void)
 	return 0;
 }
 
-#endif  /* CONFIG_IOMMU_DMA */
-
 static inline struct iommu_dma_domain *arch_get_dma_domain(struct device *dev)
 {
 	return NULL;
@@ -90,5 +88,7 @@ static inline void arch_set_dma_domain(struct device *dev,
 		struct iommu_dma_domain *dma_domain)
 { }
 
+#endif	/* CONFIG_IOMMU_DMA */
+
 #endif	/* __KERNEL__ */
 #endif	/* __DMA_IOMMU_H */
Signature · 1 line
1.9.1
author maintainer
#9 lore raw

PATCH 4/4 arm64: Hook up IOMMU dma_ops

In reply to Robin Murphy

With iommu_dma_ops in place, hook them up to the configuration code, so IOMMU-fronted devices will get them automatically.

---
 arch/arm64/Kconfig                   |  1 +
 arch/arm64/include/asm/dma-mapping.h | 11 ++++++-----
 arch/arm64/mm/dma-mapping.c          | 15 +++++++++++++++
 3 files changed, 22 insertions(+), 5 deletions(-)
arch/arm64/Kconfig +1 -0
index 7796af4..257cd7e 100644
@@ -70,6 +70,7 @@ config ARM64
 	select HAVE_PERF_USER_STACK_DUMP
 	select HAVE_RCU_TABLE_FREE
 	select HAVE_SYSCALL_TRACEPOINTS
+	select IOMMU_DMA if IOMMU_SUPPORT
 	select IRQ_DOMAIN
 	select MODULES_USE_ELF_RELA
 	select NO_BOOTMEM
arch/arm64/include/asm/dma-mapping.h +6 -5
index 835a8d1..a87b507 100644
@@ -45,11 +45,8 @@ static inline struct dma_map_ops *get_dma_ops(struct device *dev)
 		return __generic_dma_ops(dev);
 }
 
-static inline void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
-				      struct iommu_ops *iommu, bool coherent)
-{
-	dev->archdata.dma_coherent = coherent;
-}
+void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
+			struct iommu_ops *iommu, bool coherent);
 #define arch_setup_dma_ops	arch_setup_dma_ops
 
 /* do not use this function in a driver */
@@ -64,6 +61,10 @@ static inline bool is_device_dma_coherent(struct device *dev)
 #include <linux/dma-iommu.h>
 
 #ifdef CONFIG_IOMMU_DMA
+
+void arch_teardown_dma_ops(struct device *dev);
+#define arch_teardown_dma_ops	arch_teardown_dma_ops
+
 static inline struct iommu_dma_domain *arch_get_dma_domain(struct device *dev)
 {
 	return dev->archdata.dma_domain;
arch/arm64/mm/dma-mapping.c +15 -0
index 189477b..f9d4184 100644
@@ -769,6 +769,14 @@ static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
 	mutex_unlock(&iommu_dma_notifier_lock);
 }
 
+void arch_teardown_dma_ops(struct device *dev)
+{
+	if (dev->archdata.dma_domain) {
+		iommu_dma_detach_device(dev);
+		dev->archdata.dma_ops = NULL;
+	}
+}
+
 #else
 
 static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
@@ -776,3 +784,10 @@ static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
 { }
 
 #endif  /* CONFIG_IOMMU_DMA */
+
+void arch_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
+			struct iommu_ops *iommu, bool coherent)
+{
+	dev->archdata.dma_coherent = coherent;
+	__iommu_setup_dma_ops(dev, dma_base, size, iommu);
+}
Signature · 1 line
1.9.1
#3 lore raw

PATCH 2/4 iommu: Implement common IOMMU ops for DMA mapping

In reply to Robin Murphy

Hi Robin,
Thanks.

While we test venc in v4l2, we get a problem:
When we enter the funtion[0], it will be break unexpectedly in the
funcion[1] while the offset of sg table is not zero. It is ok if the offset is zero. Then I add more log in dma-iommu.c, please help check below.
All we tested it based on dma v2. and have not tested it on v3 yet.
The code of iommu-map-sg seems the same. if it's fixed in v3, I'm very sorry. The map_sg in mtk-iommu use default_iommu_map_sg.
Any question please tell me, Thanks very much.

[0]http://lxr.free-electrons.com/source/drivers/media/v4l2-core/videobuf2-dma-contig.c#L564 [1]http://lxr.free-electrons.com/source/drivers/media/v4l2-core/videobuf2-dma-contig.c#L70

Quoted 14 lines · Robin Murphy

Taking inspiration from the existing arch/arm code, break out some generic functions to interface the DMA-API to the IOMMU-API. This will do the bulk of the heavy lifting for IOMMU-backed dma-mapping.

---
 drivers/iommu/Kconfig     |   7 +
 drivers/iommu/Makefile    |   1 +
 drivers/iommu/dma-iommu.c | 560 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/dma-iommu.h |  94 ++++++++
 4 files changed, 662 insertions(+)
 create mode 100644 drivers/iommu/dma-iommu.c
 create mode 100644 include/linux/dma-iommu.h

[snip]

Quoted 22 lines
drivers/iommu/dma-iommu.c new file +22 -0
+static int __finalise_sg(struct device *dev, struct scatterlist *sg, int nents,
+		dma_addr_t dma_addr)
+{
+	struct scatterlist *s, *seg = sg;
+	unsigned long seg_mask = dma_get_seg_boundary(dev);
+	unsigned int max_len = dma_get_max_seg_size(dev);
+	unsigned int seg_len = 0, seg_dma = 0;
+	int i, count = 1;
+
+	for_each_sg(sg, s, nents, i) {
+		/* Un-swizzling the fields here, hence the naming mismatch */
+		unsigned int s_offset = sg_dma_address(s);
+		unsigned int s_length = sg_dma_len(s);
+		unsigned int s_dma_len = s->length;
+
+		s->offset = s_offset;
+		s->length = s_length;
+		sg_dma_address(s) = DMA_ERROR_CODE;
+		sg_dma_len(s) = 0;
+
+		if (seg_len && (seg_dma + seg_len == dma_addr + s_offset) &&
+		    (seg_len + s_dma_len <= max_len) &&

drivers/iommu/dma-iommu.c line 461

Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+		    ((seg_dma & seg_mask) <= seg_mask - (seg_len + s_length))
+		   ) {
+			sg_dma_len(seg) += s_dma_len;
+		} else {
+			if (seg_len) {
+				seg = sg_next(seg);
+				count++;
+			}
+			sg_dma_len(seg) = s_dma_len;
+			sg_dma_address(seg) = dma_addr + s_offset;

Here the value of sg_dma_address have added s_offset, but
sg_dma_len(seg) still is s_dma_len.
In the first loop, s_dma_len is from s->length which is alignd by
"s_length = iova_align(iovad, s_length + s_offset);" in the interface iommu_dma_map_sg.

Quoted 40 lines
drivers/iommu/dma-iommu.c new file +40 -0
+
+			seg_len = s_offset;
+			seg_dma = dma_addr + s_offset;
+		}
+		seg_len += s_length;
+		dma_addr += s_dma_len;
+	}
+	return count;
+}
+
+static void __invalidate_sg(struct scatterlist *sg, int nents)
+{
+	struct scatterlist *s;
+	int i;
+
+	for_each_sg(sg, s, nents, i) {
+		if (sg_dma_address(s) != DMA_ERROR_CODE)
+			s->offset = sg_dma_address(s);
+		if (sg_dma_len(s))
+			s->length = sg_dma_len(s);
+		sg_dma_address(s) = DMA_ERROR_CODE;
+		sg_dma_len(s) = 0;
+	}
+}
+
+int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg,
+		int nents, int prot, bool coherent)
+{
+	struct iommu_dma_domain *dom = arch_get_dma_domain(dev);
+	struct iova_domain *iovad = dom->iovad;
+	struct iova *iova;
+	struct scatterlist *s;
+	dma_addr_t dma_addr;
+	size_t iova_len = 0;
+	int i;
+
+	/*
+	 * Work out how much IOVA space we need, and align the segments to
+	 * IOVA granules for the IOMMU driver to handle. With some clever
+	 * trickery we can modify the list in a reversible manner.

drivers/iommu/dma-iommu.c line 511

Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+	 */
+	for_each_sg(sg, s, nents, i) {
+		size_t s_offset = iova_offset(iovad, s->offset);
+		size_t s_length = s->length;
+
+		sg_dma_address(s) = s->offset;
+		sg_dma_len(s) = s_length;
+		s->offset -= s_offset;
+		s_length = iova_align(iovad, s_length + s_offset);
+		s->length = s_length;

At the begging, s->length is the length of valid data. but it's aligned here.

Quoted 25 lines
drivers/iommu/dma-iommu.c new file +25 -0
+
+		iova_len += s_length;
+	}
+
+	iova = __alloc_iova(dev, iova_len, coherent);
+	if (!iova)
+		goto out_restore_sg;
+
+	/*
+	 * We'll leave any physical concatenation to the IOMMU driver's
+	 * implementation - it knows better than we do.
+	 */
+	dma_addr = iova_dma_addr(iovad, iova);
+	if (iommu_map_sg(dom->domain, dma_addr, sg, nents, prot) < iova_len)
+		goto out_free_iova;
+
+	return __finalise_sg(dev, sg, nents, dev_dma_addr(dev, dma_addr));
+
+out_free_iova:
+	__free_iova(iovad, iova);
+out_restore_sg:
+	__invalidate_sg(sg, nents);
+	return 0;
+}
+

[snip]

#4 lore raw

PATCH 2/4 iommu: Implement common IOMMU ops for DMA mapping

Hi Robin,

More info, hope this make it clearer. We are calling dma_map_sg_attrs with the following 2 sg. With IOMMU, we are expecting it got merged into 1 contiguous va range, but instead we get 2 va range.

sg0 dma_address 0xfeeddc00 size 0x400, offset 0xc00 sg1 dma_address 0xfeede000 size 0x1000, offset 0x0

Joe.C

Quoted 149 lines · Yong Wu

Hi Robin,
Thanks.

While we test venc in v4l2, we get a problem:
When we enter the funtion[0], it will be break unexpectedly in the
funcion[1] while the offset of sg table is not zero. It is ok if the offset is zero. Then I add more log in dma-iommu.c, please help check below.
All we tested it based on dma v2. and have not tested it on v3 yet.
The code of iommu-map-sg seems the same. if it's fixed in v3, I'm very sorry. The map_sg in mtk-iommu use default_iommu_map_sg.
Any question please tell me, Thanks very much.

[0]http://lxr.free-electrons.com/source/drivers/media/v4l2-core/videobuf2-dma-contig.c#L564 [1]http://lxr.free-electrons.com/source/drivers/media/v4l2-core/videobuf2-dma-contig.c#L70

Quoted 14 lines · Robin Murphy

Taking inspiration from the existing arch/arm code, break out some generic functions to interface the DMA-API to the IOMMU-API. This will do the bulk of the heavy lifting for IOMMU-backed dma-mapping.

---
 drivers/iommu/Kconfig     |   7 +
 drivers/iommu/Makefile    |   1 +
 drivers/iommu/dma-iommu.c | 560 ++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/dma-iommu.h |  94 ++++++++
 4 files changed, 662 insertions(+)
 create mode 100644 drivers/iommu/dma-iommu.c
 create mode 100644 include/linux/dma-iommu.h

[snip]

Quoted 22 lines
drivers/iommu/dma-iommu.c new file +22 -0
+static int __finalise_sg(struct device *dev, struct scatterlist *sg, int nents,
+		dma_addr_t dma_addr)
+{
+	struct scatterlist *s, *seg = sg;
+	unsigned long seg_mask = dma_get_seg_boundary(dev);
+	unsigned int max_len = dma_get_max_seg_size(dev);
+	unsigned int seg_len = 0, seg_dma = 0;
+	int i, count = 1;
+
+	for_each_sg(sg, s, nents, i) {
+		/* Un-swizzling the fields here, hence the naming mismatch */
+		unsigned int s_offset = sg_dma_address(s);
+		unsigned int s_length = sg_dma_len(s);
+		unsigned int s_dma_len = s->length;
+
+		s->offset = s_offset;
+		s->length = s_length;
+		sg_dma_address(s) = DMA_ERROR_CODE;
+		sg_dma_len(s) = 0;
+
+		if (seg_len && (seg_dma + seg_len == dma_addr + s_offset) &&
+		    (seg_len + s_dma_len <= max_len) &&

drivers/iommu/dma-iommu.c line 461

Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+		    ((seg_dma & seg_mask) <= seg_mask - (seg_len + s_length))
+		   ) {
+			sg_dma_len(seg) += s_dma_len;
+		} else {
+			if (seg_len) {
+				seg = sg_next(seg);
+				count++;
+			}
+			sg_dma_len(seg) = s_dma_len;
+			sg_dma_address(seg) = dma_addr + s_offset;

Here the value of sg_dma_address have added s_offset, but
sg_dma_len(seg) still is s_dma_len.
In the first loop, s_dma_len is from s->length which is alignd by
"s_length = iova_align(iovad, s_length + s_offset);" in the interface iommu_dma_map_sg.

Quoted 40 lines
drivers/iommu/dma-iommu.c new file +40 -0
+
+			seg_len = s_offset;
+			seg_dma = dma_addr + s_offset;
+		}
+		seg_len += s_length;
+		dma_addr += s_dma_len;
+	}
+	return count;
+}
+
+static void __invalidate_sg(struct scatterlist *sg, int nents)
+{
+	struct scatterlist *s;
+	int i;
+
+	for_each_sg(sg, s, nents, i) {
+		if (sg_dma_address(s) != DMA_ERROR_CODE)
+			s->offset = sg_dma_address(s);
+		if (sg_dma_len(s))
+			s->length = sg_dma_len(s);
+		sg_dma_address(s) = DMA_ERROR_CODE;
+		sg_dma_len(s) = 0;
+	}
+}
+
+int iommu_dma_map_sg(struct device *dev, struct scatterlist *sg,
+		int nents, int prot, bool coherent)
+{
+	struct iommu_dma_domain *dom = arch_get_dma_domain(dev);
+	struct iova_domain *iovad = dom->iovad;
+	struct iova *iova;
+	struct scatterlist *s;
+	dma_addr_t dma_addr;
+	size_t iova_len = 0;
+	int i;
+
+	/*
+	 * Work out how much IOVA space we need, and align the segments to
+	 * IOVA granules for the IOMMU driver to handle. With some clever
+	 * trickery we can modify the list in a reversible manner.

drivers/iommu/dma-iommu.c line 511

Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+	 */
+	for_each_sg(sg, s, nents, i) {
+		size_t s_offset = iova_offset(iovad, s->offset);
+		size_t s_length = s->length;
+
+		sg_dma_address(s) = s->offset;
+		sg_dma_len(s) = s_length;
+		s->offset -= s_offset;
+		s_length = iova_align(iovad, s_length + s_offset);
+		s->length = s_length;

At the begging, s->length is the length of valid data. but it's aligned here.

Quoted 25 lines
drivers/iommu/dma-iommu.c new file +25 -0
+
+		iova_len += s_length;
+	}
+
+	iova = __alloc_iova(dev, iova_len, coherent);
+	if (!iova)
+		goto out_restore_sg;
+
+	/*
+	 * We'll leave any physical concatenation to the IOMMU driver's
+	 * implementation - it knows better than we do.
+	 */
+	dma_addr = iova_dma_addr(iovad, iova);
+	if (iommu_map_sg(dom->domain, dma_addr, sg, nents, prot) < iova_len)
+		goto out_free_iova;
+
+	return __finalise_sg(dev, sg, nents, dev_dma_addr(dev, dma_addr));
+
+out_free_iova:
+	__free_iova(iovad, iova);
+out_restore_sg:
+	__invalidate_sg(sg, nents);
+	return 0;
+}
+

[snip]

maintainer
#10 lore raw

cover arm64: IOMMU-backed DMA mapping

In reply to Robin Murphy

Hi Robin,

Thanks for your work. We are really moving forward to a common DMA-API implementation for all iommu drivers.

Quoted 5 lines · Robin Murphy

In the meantime, Laurent's proposal for probe deferral[2] offers hope that the bus notifier dance may only need to be short-lived, and I'm hoping to spend some more time on DT-based IOMMU group handling for platform devices, which should hopefully tie in with default domains to make the add_device callback mostly redundant.

Yes, this would be great. I think it makes a lot of sense to make use of the default domain feature. Can you have a look at how well this integrates with my current patches for default domains and provide some feedback?

Thanks,

Joerg

author maintainer
#5 lore raw

PATCH 2/4 iommu: Implement common IOMMU ops for DMA mapping

In reply to Yong Wu
Quoted 4 lines · Yong Wu

Hi Robin,
Thanks.

While we test venc in v4l2, we get a problem:

Thanks as always for testing!

Quoted 4 lines

When we enter the funtion[0], it will be break unexpectedly in the
funcion[1] while the offset of sg table is not zero. It is ok if the offset is zero. Then I add more log in dma-iommu.c, please help check below.

Bah, non-zero offset was about the only scatterlist case I wasn't able to get out of the EHCI driver...

Quoted 47 lines

All we tested it based on dma v2. and have not tested it on v3 yet.
The code of iommu-map-sg seems the same. if it's fixed in v3, I'm very sorry. The map_sg in mtk-iommu use default_iommu_map_sg.
Any question please tell me, Thanks very much.

[0]http://lxr.free-electrons.com/source/drivers/media/v4l2-core/videobuf2-dma-contig.c#L564 [1]http://lxr.free-electrons.com/source/drivers/media/v4l2-core/videobuf2-dma-contig.c#L70

Quoted 14 lines · Robin Murphy

Taking inspiration from the existing arch/arm code, break out some generic functions to interface the DMA-API to the IOMMU-API. This will do the bulk of the heavy lifting for IOMMU-backed dma-mapping.

---
  drivers/iommu/Kconfig     |   7 +
  drivers/iommu/Makefile    |   1 +
  drivers/iommu/dma-iommu.c | 560 ++++++++++++++++++++++++++++++++++++++++++++++
  include/linux/dma-iommu.h |  94 ++++++++
  4 files changed, 662 insertions(+)

create mode 100644 drivers/iommu/dma-iommu.c
create mode 100644 include/linux/dma-iommu.h

[snip]

Quoted 23 lines
drivers/iommu/dma-iommu.c new file +23 -0
+static int __finalise_sg(struct device *dev, struct scatterlist *sg, int nents,
+		dma_addr_t dma_addr)
+{
+	struct scatterlist *s, *seg = sg;
+	unsigned long seg_mask = dma_get_seg_boundary(dev);
+	unsigned int max_len = dma_get_max_seg_size(dev);
+	unsigned int seg_len = 0, seg_dma = 0;
+	int i, count = 1;
+
+	for_each_sg(sg, s, nents, i) {
+		/* Un-swizzling the fields here, hence the naming mismatch */
+		unsigned int s_offset = sg_dma_address(s);
+		unsigned int s_length = sg_dma_len(s);
+		unsigned int s_dma_len = s->length;
+
+		s->offset = s_offset;
+		s->length = s_length;
+		sg_dma_address(s) = DMA_ERROR_CODE;
+		sg_dma_len(s) = 0;
+
+		if (seg_len && (seg_dma + seg_len == dma_addr + s_offset) &&
+		    (seg_len + s_dma_len <= max_len) &&
+		    ((seg_dma & seg_mask) <= seg_mask - (seg_len + s_length))
Quoted 8 lines

drivers/iommu/dma-iommu.c line 460

Quoted 8 lines
drivers/iommu/dma-iommu.c new file +8 -0
+		   ) {
+			sg_dma_len(seg) += s_dma_len;
+		} else {
+			if (seg_len) {
+				seg = sg_next(seg);
+				count++;
+			}
+			sg_dma_len(seg) = s_dma_len;

Does changing this to

			sg_dma_len(seg) = s_dma_len - s_offset;

make things behave as expected? Since the new unmap code no longer relies on adding up the individual segments to equal the IOVA size I don't _think_ that'll break anything here.

Robin.

#7 lore raw

PATCH 3/4 arm64: Add IOMMU dma_ops

In reply to Robin Murphy

Hi Robin,
Sorry to disturb you. When prepare iommu based on this dma-v3. I
meet some problem about how to use the dma-iommu.
I list some sample code like below:
//========
int mtk_iommu_of_xlate(struct device *dev, struct of_phandle_args *args)

{
        if (args->args_count != 2) {            
                return -EINVAL;
        }

        /* save the private data from args  
         * the private data is larbid and portid in our platform */
        priv->larbid = args->args[0];
        priv->portid = args->args[1];

        /*list the private data */
        list_add_tail(&priv->client, &head->client);

        /* save the list to dev->archdata->iommu */
        ...
}
static int mtk_iommu_init_fn(struct device_node *np)
{
        of_iommu_set_ops(np, &mtk_iommu_ops);   
        return 0;
}

IOMMU_OF_DECLARE(mtkm4u, "mediatek,mt8173-m4u", mtk_iommu_init_fn); //=======

Question :
1: if there are 2 client devices use iommu, it will be like this in
dtsi,

                disp:testiommu at 1400c000 {
                        compatible = "mediatek,mt8173-test";
                        iommus = <&iommu M4U_LARB0_ID 0>,
                                 <&iommu M4U_LARB1_ID 3>;
                };
                disp1:testiommu at 1400D000 {
                        compatible = "mediatek,mt8173-disptest1";
                        iommus = <&iommu M4U_LARB0_ID 8>, 
                                 <&iommu M4U_LARB1_ID 2>;
                };
    Then both devices will enter arch_setup_dma_ops and their parameter

"struct iommu_ops *" is not zero, then it will create two dma-iommu-domain.
If we expect all the client device share a iommu domain, then how
should i do?

2. iommu_dma_attach_device will be called automatically in the notify
"__iommu_attach_notifier". But it run before the probe of our iommu
device.
Then we can't write the register to enable iommu in *_attach_device
because we have not parsed the dtsi at that time.
->I have tried to move parse dtsi into mtk_iommu_of_xlate, in order
to read/write register in attach_device.
But it will be warning like this:
(150604_09:43:41.094)WARNING: CPU: 0 PID: 1
at /proj/mtk40525/upstreamdev/chromebook_kernelonly/kernel/mediatek/drivers/base/dd.c:286 driver_probe_device+0x25c/0x29c()

3.In mtk_iommu_probe, we can't get "struct iommu_domain *" from the current iommu device, we should use it while devm_request_irq. I have to use the global variable here?!

4.If I don't implement the of_xlate and IOMMU_OF_DECLARE, I call iommu_dma_create_domain in the mtk_iommu_probe, but I can not get the "iommu_dma_ops" which is static in arch/arm64/mm/dma-mapping.c to assign to dev->archdata->dma_ops for the iommu client device.

If i miss something, please tell me and help give some suggestion. Thanks very much.

Quoted 16 lines · Robin Murphy

Taking some inspiration from the arch/arm code, implement the arch-specific side of the DMA mapping ops using the new IOMMU-DMA layer.

Whilst proliferating per-device private IOMMU data via dev->archdata is less than ideal, it will do the job for now, especially since we can't easily handle the kind of problematic system topologies in the current IOMMU API anyway.

---
 arch/arm64/include/asm/device.h      |   3 +
 arch/arm64/include/asm/dma-mapping.h |  14 ++
 arch/arm64/mm/dma-mapping.c          | 342 +++++++++++++++++++++++++++++++++++
 include/linux/dma-iommu.h            |   4 +-
 4 files changed, 361 insertions(+), 2 deletions(-)

[snip]

Quoted 104 lines
arch/arm64/mm/dma-mapping.c +104 -0
+struct iommu_dma_notifier_data {
+	struct list_head list;
+	struct device *dev;
+	struct iommu_dma_domain *dma_domain;
+};
+static LIST_HEAD(iommu_dma_masters);
+static DEFINE_MUTEX(iommu_dma_notifier_lock);
+
+static int __iommu_attach_notifier(struct notifier_block *nb,
+				   unsigned long action, void *data)
+{
+	struct iommu_dma_notifier_data *master, *tmp;
+
+	if (action != BUS_NOTIFY_ADD_DEVICE)
+		return 0;
+	/*
+	 * We expect the list to only contain the most recent addition,
+	 * which *should* be the same device as @data, so just process the
+	 * whole thing blindly. If any previous attachments did happen to
+	 * fail, they get a free retry since the domains are still live.
+	 */
+	mutex_lock(&iommu_dma_notifier_lock);
+	list_for_each_entry_safe(master, tmp, &iommu_dma_masters, list) {
+		if (iommu_dma_attach_device(master->dev, master->dma_domain)) {
+			pr_warn("Failed to attach device %s to IOMMU mapping; retaining platform DMA ops\n",
+				dev_name(master->dev));
+		} else {
+			master->dev->archdata.dma_ops = &iommu_dma_ops;
+			/* it's safe to drop the initial refcount now */
+			iommu_dma_release_domain(master->dma_domain);
+			list_del(&master->list);
+			kfree(master);
+		}
+	}
+	mutex_unlock(&iommu_dma_notifier_lock);
+	return 0;
+}
+
+static int register_iommu_dma_ops_notifier(struct bus_type *bus)
+{
+	int ret;
+	struct notifier_block *nb = kzalloc(sizeof(*nb), GFP_KERNEL);
+
+	/*
+	 * The device must be attached to a domain before its driver probe,
+	 * in case the driver allocates DMA buffers immediately. However, most
+	 * IOMMU drivers are currently configuring groups in their add_device
+	 * callback, so the attach should happen after that. Since the IOMMU
+	 * core uses a bus notifier for add_device, do the same but with a
+	 * stupidly low priority to ensure the appropriate ordering.
+	 *
+	 * This can hopefully all go away once we have default domains in the
+	 * IOMMU core.
+	 */
+	nb->notifier_call = __iommu_attach_notifier;
+	nb->priority = INT_MIN;
+
+	ret = bus_register_notifier(bus, nb);
+	if (ret) {
+		pr_warn("Failed to register DMA domain notifier; IOMMU DMA ops unavailable on bus '%s'\n",
+			bus->name);
+		kfree(nb);
+	}
+	return ret;
+}
+
+static int __init arm64_iommu_dma_init(void)
+{
+	int ret;
+
+	ret = iommu_dma_init();
+	if (!ret)
+		ret = register_iommu_dma_ops_notifier(&platform_bus_type);
+	if (!ret)
+		ret = register_iommu_dma_ops_notifier(&amba_bustype);
+	return ret;
+}
+arch_initcall(arm64_iommu_dma_init);
+
+static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
+				  const struct iommu_ops *ops)
+{
+	struct iommu_dma_notifier_data *iommudata;
+
+	if (!ops)
+		return;
+
+	iommudata = kzalloc(sizeof(*iommudata), GFP_KERNEL);
+	if (!iommudata)
+		return;
+
+	iommudata->dev = dev;
+	iommudata->dma_domain = iommu_dma_create_domain(ops, dma_base, size);
+	if (!iommudata->dma_domain) {
+		pr_warn("Failed to create %llu-byte IOMMU mapping for device %s\n",
+				size, dev_name(dev));
+		kfree(iommudata);
+		return;
+	}
+	mutex_lock(&iommu_dma_notifier_lock);
+	list_add(&iommudata->list, &iommu_dma_masters);
+	mutex_unlock(&iommu_dma_notifier_lock);
+}
+
author maintainer
#8 lore raw

PATCH 3/4 arm64: Add IOMMU dma_ops

Quoted 45 lines · Yong Wu

Hi Robin,
Sorry to disturb you. When prepare iommu based on this dma-v3. I
meet some problem about how to use the dma-iommu.
I list some sample code like below:
//========
int mtk_iommu_of_xlate(struct device *dev, struct of_phandle_args *args)

{
         if (args->args_count != 2) {
                 return -EINVAL;
         }

         /* save the private data from args
          * the private data is larbid and portid in our platform */
         priv->larbid = args->args[0];
         priv->portid = args->args[1];

         /*list the private data */
         list_add_tail(&priv->client, &head->client);

         /* save the list to dev->archdata->iommu */
         ...
}
static int mtk_iommu_init_fn(struct device_node *np)
{
         of_iommu_set_ops(np, &mtk_iommu_ops);
         return 0;
}

IOMMU_OF_DECLARE(mtkm4u, "mediatek,mt8173-m4u", mtk_iommu_init_fn); //=======

Question :
1: if there are 2 client devices use iommu, it will be like this in
dtsi,

                 disp:testiommu at 1400c000 {
                         compatible = "mediatek,mt8173-test";
                         iommus = <&iommu M4U_LARB0_ID 0>,
                                  <&iommu M4U_LARB1_ID 3>;
                 };
                 disp1:testiommu at 1400D000 {
                         compatible = "mediatek,mt8173-disptest1";
                         iommus = <&iommu M4U_LARB0_ID 8>,
                                  <&iommu M4U_LARB1_ID 2>;
                 };
     Then both devices will enter arch_setup_dma_ops and their parameter
Quoted 4 lines

"struct iommu_ops *" is not zero, then it will create two dma-iommu-domain.
If we expect all the client device share a iommu domain, then how
should i do?

Until we get IOMMU core default domains sorted out, there's no nice way around this. The DMA mapping layer doesn't know enough to be able to create per-IOMMU DMA domains, so the best it can do is create one DMA domain per device (a single global domain wouldn't work across multiple IOMMUs). For now, the only reasonable option I can come up with is your driver using yet another bus notifier to run after the dma-iommu one, detaching the relevant devices from their automatic DMA domains and attaching them to its own private one.

Quoted 10 lines

2. iommu_dma_attach_device will be called automatically in the notify "__iommu_attach_notifier". But it run before the probe of our iommu device.
Then we can't write the register to enable iommu in *_attach_device
because we have not parsed the dtsi at that time.

    ->I have tried to move parse dtsi into mtk_iommu_of_xlate, in order

to read/write register in attach_device.
But it will be warning like this:
(150604_09:43:41.094)WARNING: CPU: 0 PID: 1
at /proj/mtk40525/upstreamdev/chromebook_kernelonly/kernel/mediatek/drivers/base/dd.c:286 driver_probe_device+0x25c/0x29c()

The current sort-of-acceptable workaround for that is to explicitly call of_platform_device_create from mtk_iommu_init_fn - see Marek's exynos-iommu implementation[1] or my work-in-progress iommu/smmu branch for examples. Please take a look at Laurent's probe-deferral series[2] if you'd like to help with the "proper" solution.

Quoted 3 lines

3.In mtk_iommu_probe, we can't get "struct iommu_domain *" from the current iommu device, we should use it while devm_request_irq. I have to use the global variable here?!

I'm not sure I follow - I was imagining you'd allocate your private DMA domain during IOMMU device creation, then stash it in your mtk_iommu_info and retrieve it as needed. If it's a "global" interrupt it seems more logical to pass that iommu data to the handler anyway, but if it's purely a "domain fault" interrupt couldn't you register it when creating the domain, rather than at device probe? That seems to be how we do it in the ARM SMMU driver, for example.

Robin.

[1]:http://article.gmane.org/gmane.linux.kernel.samsung-soc/45423 [2]:http://thread.gmane.org/gmane.linux.kernel.iommu/9552

Quoted 132 lines

4.If I don't implement the of_xlate and IOMMU_OF_DECLARE, I call iommu_dma_create_domain in the mtk_iommu_probe, but I can not get the "iommu_dma_ops" which is static in arch/arm64/mm/dma-mapping.c to assign to dev->archdata->dma_ops for the iommu client device.

If i miss something, please tell me and help give some suggestion. Thanks very much.

Quoted 16 lines · Robin Murphy

Taking some inspiration from the arch/arm code, implement the arch-specific side of the DMA mapping ops using the new IOMMU-DMA layer.

Whilst proliferating per-device private IOMMU data via dev->archdata is less than ideal, it will do the job for now, especially since we can't easily handle the kind of problematic system topologies in the current IOMMU API anyway.

---
  arch/arm64/include/asm/device.h      |   3 +
  arch/arm64/include/asm/dma-mapping.h |  14 ++
  arch/arm64/mm/dma-mapping.c          | 342 +++++++++++++++++++++++++++++++++++
  include/linux/dma-iommu.h            |   4 +-
  4 files changed, 361 insertions(+), 2 deletions(-)

[snip]

Quoted 104 lines
arch/arm64/mm/dma-mapping.c +104 -0
+struct iommu_dma_notifier_data {
+	struct list_head list;
+	struct device *dev;
+	struct iommu_dma_domain *dma_domain;
+};
+static LIST_HEAD(iommu_dma_masters);
+static DEFINE_MUTEX(iommu_dma_notifier_lock);
+
+static int __iommu_attach_notifier(struct notifier_block *nb,
+				   unsigned long action, void *data)
+{
+	struct iommu_dma_notifier_data *master, *tmp;
+
+	if (action != BUS_NOTIFY_ADD_DEVICE)
+		return 0;
+	/*
+	 * We expect the list to only contain the most recent addition,
+	 * which *should* be the same device as @data, so just process the
+	 * whole thing blindly. If any previous attachments did happen to
+	 * fail, they get a free retry since the domains are still live.
+	 */
+	mutex_lock(&iommu_dma_notifier_lock);
+	list_for_each_entry_safe(master, tmp, &iommu_dma_masters, list) {
+		if (iommu_dma_attach_device(master->dev, master->dma_domain)) {
+			pr_warn("Failed to attach device %s to IOMMU mapping; retaining platform DMA ops\n",
+				dev_name(master->dev));
+		} else {
+			master->dev->archdata.dma_ops = &iommu_dma_ops;
+			/* it's safe to drop the initial refcount now */
+			iommu_dma_release_domain(master->dma_domain);
+			list_del(&master->list);
+			kfree(master);
+		}
+	}
+	mutex_unlock(&iommu_dma_notifier_lock);
+	return 0;
+}
+
+static int register_iommu_dma_ops_notifier(struct bus_type *bus)
+{
+	int ret;
+	struct notifier_block *nb = kzalloc(sizeof(*nb), GFP_KERNEL);
+
+	/*
+	 * The device must be attached to a domain before its driver probe,
+	 * in case the driver allocates DMA buffers immediately. However, most
+	 * IOMMU drivers are currently configuring groups in their add_device
+	 * callback, so the attach should happen after that. Since the IOMMU
+	 * core uses a bus notifier for add_device, do the same but with a
+	 * stupidly low priority to ensure the appropriate ordering.
+	 *
+	 * This can hopefully all go away once we have default domains in the
+	 * IOMMU core.
+	 */
+	nb->notifier_call = __iommu_attach_notifier;
+	nb->priority = INT_MIN;
+
+	ret = bus_register_notifier(bus, nb);
+	if (ret) {
+		pr_warn("Failed to register DMA domain notifier; IOMMU DMA ops unavailable on bus '%s'\n",
+			bus->name);
+		kfree(nb);
+	}
+	return ret;
+}
+
+static int __init arm64_iommu_dma_init(void)
+{
+	int ret;
+
+	ret = iommu_dma_init();
+	if (!ret)
+		ret = register_iommu_dma_ops_notifier(&platform_bus_type);
+	if (!ret)
+		ret = register_iommu_dma_ops_notifier(&amba_bustype);
+	return ret;
+}
+arch_initcall(arm64_iommu_dma_init);
+
+static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,
+				  const struct iommu_ops *ops)
+{
+	struct iommu_dma_notifier_data *iommudata;
+
+	if (!ops)
+		return;
+
+	iommudata = kzalloc(sizeof(*iommudata), GFP_KERNEL);
+	if (!iommudata)
+		return;
+
+	iommudata->dev = dev;
+	iommudata->dma_domain = iommu_dma_create_domain(ops, dma_base, size);
+	if (!iommudata->dma_domain) {
+		pr_warn("Failed to create %llu-byte IOMMU mapping for device %s\n",
+				size, dev_name(dev));
+		kfree(iommudata);
+		return;
+	}
+	mutex_lock(&iommu_dma_notifier_lock);
+	list_add(&iommudata->list, &iommu_dma_masters);
+	mutex_unlock(&iommu_dma_notifier_lock);
+}
+