arm64: IOMMU-backed DMA mapping
Subsystems (3)
Robin Murphy
maintainercover 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
Robin Murphy
maintainerPATCH 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.
--- 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
@@ -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
@@ -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
Robin Murphy
maintainerPATCH 2/4 iommu: Implement common IOMMU ops for DMA mapping
In reply to Robin MurphyTaking 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
@@ -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
@@ -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
PATCH 2/4 iommu: Implement common IOMMU ops for DMA mapping
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/armcode, 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.Signed-off-by Robin Murphy <[email protected]>--- 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, butsg_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]
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 indma-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. Themap_sgin mtk-iommu usedefault_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/armcode, 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.Signed-off-by Robin Murphy <[email protected]>--- 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_addresshave addeds_offset, butsg_dma_len(seg)still iss_dma_len.
In the first loop,s_dma_lenis froms->lengthwhich is alignd by
"s_length=iova_align(iovad, s_length + s_offset);" in the interfaceiommu_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->lengthis 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]
Robin Murphy
maintainerPATCH 2/4 iommu: Implement common IOMMU ops for DMA mapping
In reply to Yong WuQuoted 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 indma-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. Themap_sgin mtk-iommu usedefault_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/armcode, 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.Signed-off-by Robin Murphy <[email protected]>--- 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 100644include/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.
Robin Murphy
maintainerPATCH 3/4 arm64: Add IOMMU dma_ops
In reply to Robin MurphyTaking 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
@@ -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
@@ -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
@@ -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
PATCH 3/4 arm64: Add IOMMU dma_ops
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/armcode, 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->archdatais 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.Signed-off-by Robin Murphy <[email protected]>--- 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); +} +
Robin Murphy
maintainerPATCH 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:
//========
intmtk_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_devicewill 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 orderto 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 "structiommu_domain*" from the current iommu device, we should use it whiledevm_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_xlateandIOMMU_OF_DECLARE, I calliommu_dma_create_domainin themtk_iommu_probe, but I can not get the "iommu_dma_ops" which is static inarch/arm64/mm/dma-mapping.cto assign todev->archdata->dma_opsfor 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/armcode, 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->archdatais 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.Signed-off-by Robin Murphy <[email protected]>--- 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); +} +
Robin Murphy
maintainerPATCH 4/4 arm64: Hook up IOMMU dma_ops
In reply to Robin MurphyWith 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
@@ -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
@@ -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
@@ -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
Joerg Roedel
maintainercover arm64: IOMMU-backed DMA mapping
In reply to Robin MurphyHi 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_devicecallback 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