Skip to contentSkip to conversation

arm64: IOMMU-backed DMA mapping

PATCH Superseded Robin Murphy 2 participants 12 messages Jun 11, 2015 – Jul 7, 2015

Subsystems (3)

v2 of 6 Superseded Latest is v6 →

lore
Share & tools
lore

Robin Murphy

author maintainer
#0 lore raw

v2 cover arm64: IOMMU-backed DMA mapping

Hi all,

Here's a quick repost of [1] with a couple of minor fixes:
- fix scatterlist dma_len for segments with nonzero offset
- adjust the bus notifier priority with a less silly value

The branch at [2] has been updated as well.

Robin.

[1]:http://thread.gmane.org/gmane.linux.kernel.iommu/9721 [2]:git://linux-arm.org/linux-rm iommu/dma

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 v2 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 v2 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
#3 lore raw

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

Hi Robin,
while our drm test, we meet a problem while dma-mmap.

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 7 lines
drivers/iommu/dma-iommu.c new file +7 -0
+/**
+ * iommu_dma_mmap - Map a buffer into provided user VMA
+ * @pages: Array representing buffer from iommu_dma_alloc()
+ * @size: Size of buffer in bytes
+ * @vma: VMA describing requested userspace mapping
+ *
+ * Maps the pages of the buffer in @pages into @vma. The caller is responsible

drivers/iommu/dma-iommu.c line 393

Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+ * for verifying the correct size and protection of @vma beforehand.
+ */
+
+int iommu_dma_mmap(struct page **pages, size_t size, struct vm_area_struct *vma)
+{
+	unsigned long uaddr = vma->vm_start;
+	unsigned int i, count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+	int ret = -ENXIO;
+
+	for (i = vma->vm_pgoff; i < count && uaddr < vma->vm_end; i++) {

Is it : for (i = 0; i < count && uaddr < vma->vm_end; i++)

Quoted 8 lines
drivers/iommu/dma-iommu.c new file +8 -0
+		ret = vm_insert_page(vma, uaddr, pages[i]);
+		if (ret)
+			break;
+		uaddr += PAGE_SIZE;
+	}
+	return ret;
+}
+
author maintainer
#4 lore raw

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

Quoted 29 lines · Yong Wu

Hi Robin,
while our drm test, we meet a problem while dma-mmap.

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 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+/**
+ * iommu_dma_mmap - Map a buffer into provided user VMA
+ * @pages: Array representing buffer from iommu_dma_alloc()
+ * @size: Size of buffer in bytes
+ * @vma: VMA describing requested userspace mapping
+ *
+ * Maps the pages of the buffer in @pages into @vma. The caller is responsible
+ * for verifying the correct size and protection of @vma beforehand.
+ */
+
Quoted 8 lines

drivers/iommu/dma-iommu.c line 393

Quoted 7 lines
drivers/iommu/dma-iommu.c new file +7 -0
+int iommu_dma_mmap(struct page **pages, size_t size, struct vm_area_struct *vma)
+{
+	unsigned long uaddr = vma->vm_start;
+	unsigned int i, count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+	int ret = -ENXIO;
+
+	for (i = vma->vm_pgoff; i < count && uaddr < vma->vm_end; i++) {

Is it : for (i = 0; i < count && uaddr < vma->vm_end; i++)

According to the patches at [1], respecting vma->pgoff is necessary for users who only want to mmap part of the buffer. Does your DRM driver by any chance need the same fix as the Rockchip one?

Quoted 8 lines

drivers/iommu/dma-iommu.c line 401

Quoted 8 lines
drivers/iommu/dma-iommu.c new file +8 -0
+		ret = vm_insert_page(vma, uaddr, pages[i]);
+		if (ret)
+			break;
+		uaddr += PAGE_SIZE;
+	}
+	return ret;
+}
+

And to save another email:

Quoted 3 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	if (WARN_ON(!area || area->pages))

Is it: if (WARN_ON(!area || !area->pages))

Review

Quoted 1 line
(quoted diff) +1 -0
+		return -ENXIO;

You're absolutely right, thanks. I've got a couple more bugfixes in progress too, so I'll try to get an updated branch pushed out soon; I'll hold off posting a v3 to the list until after the upcoming merge window, though.

Robin.

[1]:http://thread.gmane.org/gmane.linux.kernel/1932343

#5 lore raw

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

Quoted 39 lines · Robin Murphy
Quoted 29 lines · Yong Wu

Hi Robin,
while our drm test, we meet a problem while dma-mmap.

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 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+/**
+ * iommu_dma_mmap - Map a buffer into provided user VMA
+ * @pages: Array representing buffer from iommu_dma_alloc()
+ * @size: Size of buffer in bytes
+ * @vma: VMA describing requested userspace mapping
+ *
+ * Maps the pages of the buffer in @pages into @vma. The caller is responsible
+ * for verifying the correct size and protection of @vma beforehand.
+ */
+
Quoted 8 lines

drivers/iommu/dma-iommu.c line 393

Quoted 7 lines
drivers/iommu/dma-iommu.c new file +7 -0
+int iommu_dma_mmap(struct page **pages, size_t size, struct vm_area_struct *vma)
+{
+	unsigned long uaddr = vma->vm_start;
+	unsigned int i, count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+	int ret = -ENXIO;
+
+	for (i = vma->vm_pgoff; i < count && uaddr < vma->vm_end; i++) {

Is it : for (i = 0; i < count && uaddr < vma->vm_end; i++)

Quoted 3 lines

According to the patches at [1], respecting vma->pgoff is necessary for users who only want to mmap part of the buffer. Does your DRM driver by any chance need the same fix as the Rockchip one?

Thanks. We will try to add it.

Quoted 25 lines
Quoted 9 lines

drivers/iommu/dma-iommu.c line 401

Quoted 9 lines
drivers/iommu/dma-iommu.c new file +8 -0
+		ret = vm_insert_page(vma, uaddr, pages[i]);
+		if (ret)
+			break;
+		uaddr += PAGE_SIZE;
+	}
+	return ret;
+}
+

And to save another email:

Quoted 3 lines

Review

Quoted 1 line
(quoted diff) +1 -0
+	if (WARN_ON(!area || area->pages))

Is it: if (WARN_ON(!area || !area->pages))

Review

Quoted 1 line
(quoted diff) +1 -0
+		return -ENXIO;

You're absolutely right, thanks. I've got a couple more bugfixes in progress too, so I'll try to get an updated branch pushed out soon; I'll hold off posting a v3 to the list until after the upcoming merge window, though.

Robin.

[1]:http://thread.gmane.org/gmane.linux.kernel/1932343

#6 lore raw

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

In reply to Robin Murphy
Quoted 44 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
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

[snip]

Quoted 24 lines
drivers/iommu/Makefile +24 -0
+
+/**
+ * iommu_dma_alloc - Allocate and map a buffer contiguous in IOVA space
+ * @dev: Device to allocate memory for. Must be a real device
+ *	 attached to an iommu_dma_domain
+ * @size: Size of buffer in bytes
+ * @gfp: Allocation flags
+ * @prot: IOMMU mapping flags
+ * @coherent: Which dma_mask to base IOVA allocation on
+ * @handle: Out argument for allocated DMA handle
+ * @flush_page: Arch callback to flush a single page from caches as
+ *		necessary. May be NULL for coherent allocations
+ *
+ * If @size is less than PAGE_SIZE, then a full CPU page will be allocated,
+ * but an IOMMU which supports smaller pages might not map the whole thing.
+ * For now, the buffer is unconditionally zeroed for compatibility
+ *
+ * Return: Array of struct page pointers describing the buffer,
+ *	   or NULL on failure.
+ */
+struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,
+		int prot, bool coherent, dma_addr_t *handle,
+		void (*flush_page)(const void *, phys_addr_t))
+{

drivers/iommu/Makefile

Quoted 10 lines
drivers/iommu/Makefile +10 -0
+	struct iommu_dma_domain *dom = arch_get_dma_domain(dev);
+	struct iova_domain *iovad = dom->iovad;
+	struct iova *iova;
+	struct page **pages;
+	struct sg_table sgt;
+	struct sg_mapping_iter miter;
+	dma_addr_t dma_addr;
+	unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+
+	*handle = DMA_ERROR_CODE;

Hi Robin,

Compare with the dma before, You delete this line here.

          size = PAGE_ALIGN(size);

Do you expect the user should make sure the size must be aligned?

so do __iommu_free_attrs.

Quoted 41 lines
drivers/iommu/Makefile +41 -0
+
+	/* IOMMU can map any pages, so himem can also be used here */
+	gfp |= __GFP_NOWARN | __GFP_HIGHMEM;
+	pages = __iommu_dma_alloc_pages(count, gfp);
+	if (!pages)
+		return NULL;
+
+	iova = __alloc_iova(dev, size, coherent);
+	if (!iova)
+		goto out_free_pages;
+
+	if (sg_alloc_table_from_pages(&sgt, pages, count, 0, size, GFP_KERNEL))
+		goto out_free_iova;
+
+	dma_addr = iova_dma_addr(iovad, iova);
+	if (iommu_map_sg(dom->domain, dma_addr, sgt.sgl, sgt.orig_nents, prot)
+			< size)
+		goto out_free_sg;
+
+	/* Using the non-flushing flag since we're doing our own */
+	sg_miter_start(&miter, sgt.sgl, sgt.orig_nents, SG_MITER_FROM_SG);
+	while (sg_miter_next(&miter)) {
+		memset(miter.addr, 0, PAGE_SIZE);
+		if (flush_page)
+			flush_page(miter.addr, page_to_phys(miter.page));
+	}
+	sg_miter_stop(&miter);
+	sg_free_table(&sgt);
+
+	*handle = dma_addr;
+	return pages;
+
+out_free_sg:
+	sg_free_table(&sgt);
+out_free_iova:
+	__free_iova(iovad, iova);
+out_free_pages:
+	__iommu_dma_free_pages(pages, count);
+	return NULL;
+}
+

[...]

Quoted 3 lines
drivers/iommu/Makefile +3 -0
+
+#endif	/* __KERNEL__ */
+#endif	/* __DMA_IOMMU_H */
author maintainer
#7 lore raw

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

On 03/07/15 10:27, Yong Wu wrote:
[...]

Quoted 36 lines
Quoted 23 lines
drivers/iommu/dma-iommu.c new file +23 -0
+/**
+ * iommu_dma_alloc - Allocate and map a buffer contiguous in IOVA space
+ * @dev: Device to allocate memory for. Must be a real device
+ *	 attached to an iommu_dma_domain
+ * @size: Size of buffer in bytes
+ * @gfp: Allocation flags
+ * @prot: IOMMU mapping flags
+ * @coherent: Which dma_mask to base IOVA allocation on
+ * @handle: Out argument for allocated DMA handle
+ * @flush_page: Arch callback to flush a single page from caches as
+ *		necessary. May be NULL for coherent allocations
+ *
+ * If @size is less than PAGE_SIZE, then a full CPU page will be allocated,
+ * but an IOMMU which supports smaller pages might not map the whole thing.
+ * For now, the buffer is unconditionally zeroed for compatibility
+ *
+ * Return: Array of struct page pointers describing the buffer,
+ *	   or NULL on failure.
+ */
+struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,
+		int prot, bool coherent, dma_addr_t *handle,
+		void (*flush_page)(const void *, phys_addr_t))
+{

drivers/iommu/dma-iommu.c line 335

Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+	struct iommu_dma_domain *dom = arch_get_dma_domain(dev);
+	struct iova_domain *iovad = dom->iovad;
+	struct iova *iova;
+	struct page **pages;
+	struct sg_table sgt;
+	struct sg_mapping_iter miter;
+	dma_addr_t dma_addr;
+	unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+
+	*handle = DMA_ERROR_CODE;

Hi Robin,

Quoted 7 lines

Compare with the dma before, You delete this line here.

           size = PAGE_ALIGN(size);

Do you expect the user should make sure the size must be aligned?

so do __iommu_free_attrs.

Yeah, there is an oversight here due to some back-and-forth refactoring
- both the page allocation and the IOVA allocation do get suitably aligned as they should, but the segments of the temporary scatterlist don't. Somehow I managed not to hit an iommu_map_sg failure due to that until some time after this posting (it seems most of the drivers I test with only make page-sized allocations...)

I've currently got the fixup below waiting for the next posting.

Robin.

Quoted 11 lines

drivers/iommu/dma-iommu.c line 346

Quoted 11 lines
drivers/iommu/dma-iommu.c new file +11 -0
+
+	/* IOMMU can map any pages, so himem can also be used here */
+	gfp |= __GFP_NOWARN | __GFP_HIGHMEM;
+	pages = __iommu_dma_alloc_pages(count, gfp);
+	if (!pages)
+		return NULL;
+
+	iova = __alloc_iova(dev, size, coherent);
+	if (!iova)
+		goto out_free_pages;
+

+ size = iova_align(iovad, size);

Quoted 36 lines
Quoted 30 lines
drivers/iommu/dma-iommu.c new file +30 -0
+	if (sg_alloc_table_from_pages(&sgt, pages, count, 0, size, GFP_KERNEL))
+		goto out_free_iova;
+
+	dma_addr = iova_dma_addr(iovad, iova);
+	if (iommu_map_sg(dom->domain, dma_addr, sgt.sgl, sgt.orig_nents, prot)
+			< size)
+		goto out_free_sg;
+
+	/* Using the non-flushing flag since we're doing our own */
+	sg_miter_start(&miter, sgt.sgl, sgt.orig_nents, SG_MITER_FROM_SG);
+	while (sg_miter_next(&miter)) {
+		memset(miter.addr, 0, PAGE_SIZE);
+		if (flush_page)
+			flush_page(miter.addr, page_to_phys(miter.page));
+	}
+	sg_miter_stop(&miter);
+	sg_free_table(&sgt);
+
+	*handle = dma_addr;
+	return pages;
+
+out_free_sg:
+	sg_free_table(&sgt);
+out_free_iova:
+	__free_iova(iovad, iova);
+out_free_pages:
+	__iommu_dma_free_pages(pages, count);
+	return NULL;
+}
+

[...]

Quoted 3 lines
include/linux/dma-iommu.h new file +3 -0
+
+#endif	/* __KERNEL__ */
+#endif	/* __DMA_IOMMU_H */
#8 lore raw

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

Quoted 53 lines · Robin Murphy

On 03/07/15 10:27, Yong Wu wrote:
[...]

Quoted 36 lines
Quoted 23 lines
drivers/iommu/dma-iommu.c new file +23 -0
+/**
+ * iommu_dma_alloc - Allocate and map a buffer contiguous in IOVA space
+ * @dev: Device to allocate memory for. Must be a real device
+ *	 attached to an iommu_dma_domain
+ * @size: Size of buffer in bytes
+ * @gfp: Allocation flags
+ * @prot: IOMMU mapping flags
+ * @coherent: Which dma_mask to base IOVA allocation on
+ * @handle: Out argument for allocated DMA handle
+ * @flush_page: Arch callback to flush a single page from caches as
+ *		necessary. May be NULL for coherent allocations
+ *
+ * If @size is less than PAGE_SIZE, then a full CPU page will be allocated,
+ * but an IOMMU which supports smaller pages might not map the whole thing.
+ * For now, the buffer is unconditionally zeroed for compatibility
+ *
+ * Return: Array of struct page pointers describing the buffer,
+ *	   or NULL on failure.
+ */
+struct page **iommu_dma_alloc(struct device *dev, size_t size, gfp_t gfp,
+		int prot, bool coherent, dma_addr_t *handle,
+		void (*flush_page)(const void *, phys_addr_t))
+{

drivers/iommu/dma-iommu.c line 335

Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10 -0
+	struct iommu_dma_domain *dom = arch_get_dma_domain(dev);
+	struct iova_domain *iovad = dom->iovad;
+	struct iova *iova;
+	struct page **pages;
+	struct sg_table sgt;
+	struct sg_mapping_iter miter;
+	dma_addr_t dma_addr;
+	unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
+
+	*handle = DMA_ERROR_CODE;

Hi Robin,

Quoted 7 lines

Compare with the dma before, You delete this line here.

           size = PAGE_ALIGN(size);

Do you expect the user should make sure the size must be aligned?

so do __iommu_free_attrs.

Yeah, there is an oversight here due to some back-and-forth refactoring
- both the page allocation and the IOVA allocation do get suitably aligned as they should, but the segments of the temporary scatterlist don't. Somehow I managed not to hit an iommu_map_sg failure due to that until some time after this posting (it seems most of the drivers I test with only make page-sized allocations...)

Quoted 3 lines

I've currently got the fixup below waiting for the next posting.

Robin.

Hi Robin,
Could we know the status and ETA of the DMA next version. We are preparing the mtk-iommu next patch as our project request. We could help test it if you send the new patch. Thanks.

Quoted 52 lines
Quoted 12 lines
Quoted 12 lines
drivers/iommu/dma-iommu.c new file +11 -0
+
+	/* IOMMU can map any pages, so himem can also be used here */
+	gfp |= __GFP_NOWARN | __GFP_HIGHMEM;
+	pages = __iommu_dma_alloc_pages(count, gfp);
+	if (!pages)
+		return NULL;
+
+	iova = __alloc_iova(dev, size, coherent);
+	if (!iova)
+		goto out_free_pages;
+
(quoted diff) +1 -0
+	size = iova_align(iovad, size);
Quoted 37 lines
Quoted 31 lines
drivers/iommu/dma-iommu.c new file +30 -0
+	if (sg_alloc_table_from_pages(&sgt, pages, count, 0, size, GFP_KERNEL))
+		goto out_free_iova;
+
+	dma_addr = iova_dma_addr(iovad, iova);
+	if (iommu_map_sg(dom->domain, dma_addr, sgt.sgl, sgt.orig_nents, prot)
+			< size)
+		goto out_free_sg;
+
+	/* Using the non-flushing flag since we're doing our own */
+	sg_miter_start(&miter, sgt.sgl, sgt.orig_nents, SG_MITER_FROM_SG);
+	while (sg_miter_next(&miter)) {
+		memset(miter.addr, 0, PAGE_SIZE);
+		if (flush_page)
+			flush_page(miter.addr, page_to_phys(miter.page));
+	}
+	sg_miter_stop(&miter);
+	sg_free_table(&sgt);
+
+	*handle = dma_addr;
+	return pages;
+
+out_free_sg:
+	sg_free_table(&sgt);
+out_free_iova:
+	__free_iova(iovad, iova);
+out_free_pages:
+	__iommu_dma_free_pages(pages, count);
+	return NULL;
+}
+

[...]

Quoted 3 lines
include/linux/dma-iommu.h new file +3 -0
+
+#endif	/* __KERNEL__ */
+#endif	/* __DMA_IOMMU_H */
author maintainer
#9 lore raw

PATCH v2 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
#10 lore raw

PATCH v2 3/4 arm64: Add IOMMU dma_ops

Quoted 15 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 5 lines
arch/arm64/mm/dma-mapping.c +5 -0
+static int __iommu_mmap_attrs(struct device *dev, struct vm_area_struct *vma,
+			      void *cpu_addr, dma_addr_t dma_addr, size_t size,
+			      struct dma_attrs *attrs)
+{
+	struct vm_struct *area;

arch/arm64/mm/dma-mapping.c static int __init dma_debug_do_init(void) · line 523

Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10 -0
+	int ret;
+
+	vma->vm_page_prot = __get_dma_pgprot(attrs, vma->vm_page_prot,
+					     is_device_dma_coherent(dev));
+
+	if (dma_mmap_from_coherent(dev, vma, cpu_addr, size, &ret))
+		return ret;
+
+	area = find_vm_area(cpu_addr);
+	if (WARN_ON(!area || area->pages))

Is it: if (WARN_ON(!area || !area->pages))

Quoted 4 lines
arch/arm64/mm/dma-mapping.c +4 -0
+		return -ENXIO;
+
+	return iommu_dma_mmap(area->pages, size, vma);
+}

[snip]

Quoted 2 lines

#endif /* __KERNEL__ */
#endif /* __DMA_IOMMU_H */

author maintainer
#11 lore raw

PATCH v2 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 51b7ae5..c1e1a07 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