arm64: IOMMU-backed DMA mapping
Subsystems (3)
Robin Murphy
maintainerv2 cover arm64: IOMMU-backed DMA mapping
Hi all,
This is an updated RFC to address some of the initial comments[1]. The first two patches of the original posting, along with the IOVA series, are now in -next so aren't included here.
If this is starting to look tidy enough, then I'll get to work on porting arch/arm as well so I can repost as a formal patch set.
Thanks,
Robin.
A branch with all the necessary bits is available at:
git://linux-arm.org/linux-rm iommu/dma
Main changes since v1:
* rebased to -rc7
* moved common code to drivers/iommu, and tidied up the config dependencies
* renamed iommu_dma_mapping to better reflect that it's a managed domain
* abstracted away the explicit use of archdata
* finished the map_sg implementation
* fixed up the mmap implementation with the arch/arm one for now
* removed the iova_to_phys accessor in favour of (more flexibly) exposing the iommu domain for where it's needed
More complicated things still to do:
* understand and sort out the compound pages issue
* get domain and group allocation right - I'd like to try integrating Joerg's proposals for that area[2][3], which should help.
[1]:http://article.gmane.org/gmane.linux.kernel.iommu/8213 [2]:http://thread.gmane.org/gmane.linux.ports.tegra/20907 [3]:http://thread.gmane.org/gmane.linux.kernel.iommu/8492
Robin Murphy (3):
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 | 28 +- arch/arm64/mm/dma-mapping.c | 335 +++++++++++++++++++++ drivers/iommu/Kconfig | 7 + drivers/iommu/Makefile | 1 + drivers/iommu/dma-iommu.c | 552 +++++++++++++++++++++++++++++++++++ include/linux/dma-iommu.h | 94 ++++++ 8 files changed, 1016 insertions(+), 5 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
maintainerRFC PATCH v2 1/3 iommu: implement common IOMMU ops for DMA mapping
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.
Whilst this RFC series is aimed at enabling arm64, once any remaining obvious issues in the common code are addressed we can complete the refactoring by porting arch/arm over for a merge-worthy series.
--- drivers/iommu/Kconfig | 7 + drivers/iommu/Makefile | 1 + drivers/iommu/dma-iommu.c | 552 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/dma-iommu.h | 94 ++++++++ 4 files changed, 654 insertions(+) create mode 100644 drivers/iommu/dma-iommu.c create mode 100644 include/linux/dma-iommu.h
drivers/iommu/Kconfig +7 -0
@@ -20,6 +20,13 @@ config OF_IOMMU def_bool y depends on OF && IOMMU_API +# IOMMU-agnostic DMA-mapping layer +config IOMMU_DMA + def_bool n + depends on NEED_SG_DMA_LENGTH + select IOMMU_API + select IOMMU_IOVA + config FSL_PAMU bool "Freescale IOMMU support" depends on PPC_E500MC
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_IOVA) += iova.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.o
drivers/iommu/dma-iommu.c new file +552 -0
552 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
RFC PATCH v2 1/3 iommu: implement common IOMMU ops for DMA mapping
Hi Robin,
Quoted 23 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.Whilst this RFC series is aimed at enabling arm64, once any remaining obvious issues in the common code are addressed we can complete the refactoring by porting
arch/armover for a merge-worthy series.Signed-off-by Robin Murphy <[email protected]>--- drivers/iommu/Kconfig | 7 + drivers/iommu/Makefile | 1 + drivers/iommu/dma-iommu.c | 552 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/dma-iommu.h | 94 ++++++++ 4 files changed, 654 insertions(+) create mode 100644 drivers/iommu/dma-iommu.c create mode 100644 include/linux/dma-iommu.hdrivers/iommu/Kconfig +0
-0
drivers/iommu/Kconfig config OF_IOMMU · line 25
Quoted 6 lines
drivers/iommu/Kconfig +3
-0@@ -20,6 +20,13 @@ config OF_IOMMU def_bool y depends on OF && IOMMU_API +# IOMMU-agnostic DMA-mapping layer +config IOMMU_DMA + def_bool n
This can just be `bool' (n is the default).
Quoted 56 lines
drivers/iommu/Kconfig +4
-0+ depends on NEED_SG_DMA_LENGTH + select IOMMU_API + select IOMMU_IOVA + config FSL_PAMU bool "Freescale IOMMU support" depends on PPC_E500MCdrivers/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_IOVA) += iova.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.odrivers/iommu/dma-iommu.c new file +31
-0@@ -0,0 +1,552 @@ +/* +* A fairly generic DMA-API to IOMMU-API glue layer. +* +* Copyright (C) 2014 ARM Ltd. +* +* based in part on arch/arm/mm/dma-mapping.c: +* Copyright (C) 2000-2004 Russell King +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define pr_fmt (fmt) "%s: " fmt, __func__ + +#include <linux/dma-contiguous.h> +#include <linux/dma-iommu.h> +#include <linux/iova.h> + +int iommu_dma_init (void ) +{ +return iommu_iova_cache_init (); +}
drivers/iommu/dma-iommu.c line 41
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+ +struct iommu_dma_domain { +struct iommu_domain *domain; +struct iova_domain *iovad; +struct kref kref; +}; + +static inline dma_addr_t dev_dma_addr (struct device *dev,dma_addr_t addr) +{ +BUG_ON (addr < dev->dma_pfn_offset);
Shouldn't we convert the pfn_offset to an address before this check?
Quoted 8 lines
drivers/iommu/dma-iommu.c new file +8
-0+return addr - ((dma_addr_t )dev->dma_pfn_offset <<PAGE_SHIFT ); +} + +static int __dma_direction_to_prot (enum dma_data_direction dir,bool coherent) +{ +int prot = coherent ?IOMMU_CACHE :0 ; + +switch (dir) {
drivers/iommu/dma-iommu.c line 59
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+case DMA_BIDIRECTIONAL : +return prot |IOMMU_READ |IOMMU_WRITE ; +case DMA_TO_DEVICE : +return prot |IOMMU_READ ; +case DMA_FROM_DEVICE : +return prot |IOMMU_WRITE ; +default : +return 0 ; + } +}
We could actually stick this into a header file as a generic helper. arch/arm/ already uses its own flavour, for example.
drivers/iommu/dma-iommu.c line 68
Quoted 8 lines
drivers/iommu/dma-iommu.c new file +8
-0+static struct iova *__alloc_iova (struct device *dev,size_t size,bool coherent) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +unsigned long shift =iova_shift (iovad); +unsigned long length =iova_align (iovad, size) >> shift; +unsigned long limit_pfn = iovad->dma_32bit_pfn; +u64 dma_limit = coherent ? dev->coherent_dma_mask : *dev->dma_mask;
Should we use dma_get_mask in the non-coherent case in case the mask pointer is NULL?
drivers/iommu/dma-iommu.c line 70
Quoted 1 line
drivers/iommu/dma-iommu.c new file +1
-0+ limit_pfn =min (limit_pfn, (unsigned long )(dma_limit >> shift));
min_t avoids the explicit cast.
Quoted 5 lines
drivers/iommu/dma-iommu.c new file +5
-0+/* Alignment should probably come from a domain/device attribute... */ +return alloc_iova (iovad, length, limit_pfn, false); +} + +/*
drivers/iommu/dma-iommu.c line 85
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+* Create a mapping in device IO address space for specified pages +*/ +dma_addr_t iommu_dma_create_iova_mapping (struct device *dev, +struct page **pages,size_t size,bool coherent) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +struct iommu_domain *domain = dom->domain; +struct iova *iova; +unsigned int count =PAGE_ALIGN (size) >>PAGE_SHIFT ;
Why not just make the size parameter npages instead?
Quoted 13 lines
drivers/iommu/dma-iommu.c new file +13
-0+dma_addr_t addr_lo, addr_hi; +int i, prot =__dma_direction_to_prot (DMA_BIDIRECTIONAL , coherent); + + iova =__alloc_iova (dev, size, coherent); +if (!iova) +return DMA_ERROR_CODE ; + + addr_hi = addr_lo =iova_dma_addr (iovad, iova); +for (i =0 ; i < count; ) { +unsigned int next_pfn =page_to_pfn (pages[i]) +1 ; +phys_addr_t phys =page_to_phys (pages[i]); +unsigned int len, j; +
drivers/iommu/dma-iommu.c line 108
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+for (j = i+1 ; j < count; j++, next_pfn++) +if (page_to_pfn (pages[j]) != next_pfn) +break ; + + len = (j - i) <<PAGE_SHIFT ; +if (iommu_map (domain, addr_hi, phys, len, prot)) + goto fail; + addr_hi += len; + i = j; + }
This loop is pretty miserable to read. Could we pass this off to ->map_sg and avoid the need to check that the pfns are contiguous?
Quoted 36 lines
drivers/iommu/dma-iommu.c new file +36
-0+return dev_dma_addr (dev, addr_lo); +fail: +iommu_unmap (domain, addr_lo, addr_hi - addr_lo); +__free_iova (iovad, iova); +return DMA_ERROR_CODE ; +} + +int iommu_dma_release_iova_mapping (struct device *dev,dma_addr_t iova, +size_t size) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +size_t offset =iova_offset (iovad, iova); +size_t len =iova_align (iovad, size + offset); + +iommu_unmap (dom->domain, iova - offset, len); +free_iova (iovad,iova_pfn (iovad, iova)); +return 0 ; +} + +struct page **iommu_dma_alloc_buffer (struct device *dev,size_t size, +gfp_t gfp,struct dma_attrs *attrs, +void (clear_buffer )(struct page *page,size_t size)) +{ +struct page **pages; +int count = size >>PAGE_SHIFT ; +int array_size = count *sizeof (struct page *); +int i =0 ; + +if (array_size <=PAGE_SIZE ) + pages =kzalloc (array_size,GFP_KERNEL ); +else + pages =vzalloc (array_size); +if (!pages) +return NULL ; +
drivers/iommu/dma-iommu.c line 154
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+if (dma_get_attr (DMA_ATTR_FORCE_CONTIGUOUS , attrs)) { +unsigned long order =get_order (size); +struct page *page; + + page =dma_alloc_from_contiguous (dev, count, order); +if (!page) + goto error; + +if (clear_buffer) +clear_buffer (page, size);
For cache-coherent systems, this is just a memset so perhaps we could default to that if no function pointer is passed? It would also be cool to do the highmem vs lowmem stuff in core code, but that's more fiddly than it looks and probably not worth the hassle.
drivers/iommu/dma-iommu.c line 428
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+struct iommu_dma_domain *iommu_dma_create_domain (const struct iommu_ops *ops, +dma_addr_t base,size_t size) +{ +struct iommu_dma_domain *dom; +struct iommu_domain *domain; +struct iova_domain *iovad; +struct iommu_domain_geometry *dg; +unsigned long order, base_pfn, end_pfn; + +pr_debug ("base=%pad\tsize=0x%zx\n" , &base, size);
We can lose the debug prints for upstream.
Quoted 14 lines
drivers/iommu/dma-iommu.c new file +14
-0+ dom =kzalloc (sizeof (*dom),GFP_KERNEL ); +if (!dom) +return NULL ; + +/* +* HACK: We'd like to ask the relevant IOMMU in ops for a suitable +* domain, but until that happens, bypass the bus nonsense and create +* one directly for this specific device/IOMMU combination... +*/ + domain =kzalloc (sizeof (*domain),GFP_KERNEL ); + +if (!domain) + goto out_free_dma_domain; + domain->ops = ops;
drivers/iommu/dma-iommu.c line 452
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+ +if (ops->domain_init (domain)) + goto out_free_iommu_domain; +/* +* ...and do the bare minimum to sanity-check that the domain allows +* at least some access to the device... +*/ + dg = &domain->geometry; +if (!(base <= dg->aperture_end && base + size > dg->aperture_start)) { +pr_warn ("DMA range outside IOMMU capability; is DT correct?\n" );
We probably shouldn't print things about DT in core (non-OF) code.
Quoted 55 lines
drivers/iommu/dma-iommu.c new file +55
-0+ goto out_free_iommu_domain; + } +/* ...then finally give it a kicking to make sure it fits */ + dg->aperture_start =max (base, dg->aperture_start); + dg->aperture_end =min (base + size -1 , dg->aperture_end); +/* +* Note that this almost certainly breaks the case where multiple +* devices with different DMA capabilities need to share a domain, +* but we don't have the necessary information to handle that here +* anyway - "proper" group and domain allocation needs to involve +* the IOMMU driver and a complete view of the bus. +*/ + + iovad =kzalloc (sizeof (*iovad),GFP_KERNEL ); +if (!iovad) + goto out_free_iommu_domain; + +/* Use the smallest supported page size for IOVA granularity */ + order =__ffs (ops->pgsize_bitmap); + base_pfn =max (dg->aperture_start >> order, (dma_addr_t )1 ); + end_pfn = dg->aperture_end >> order; +init_iova_domain (iovad,1UL << order, base_pfn, end_pfn); + + dom->domain = domain; + dom->iovad = iovad; +kref_init (&dom->kref); +pr_debug ("domain %p created\n" , dom); +return dom; + +out_free_iommu_domain: +kfree (domain); +out_free_dma_domain: +kfree (dom); +return NULL ; +} + +static void iommu_dma_free_domain (struct kref *kref) +{ +struct iommu_dma_domain *dom; + + dom = container_of(kref, struct iommu_dma_domain, kref); +put_iova_domain (dom->iovad); +iommu_domain_free (dom->domain); +kfree (dom); +pr_debug ("domain %p freed\n" , dom); +} + +void iommu_dma_release_domain (struct iommu_dma_domain *dom) +{ +kref_put (&dom->kref, iommu_dma_free_domain); +} + +struct iommu_domain *iommu_dma_raw_domain (struct iommu_dma_domain *dom) +{ +return dom ? dom->domain :NULL ;
drivers/iommu/dma-iommu.c line 517
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+} + +int iommu_dma_attach_device (struct device *dev,struct iommu_dma_domain *dom) +{ +int ret; + +kref_get (&dom->kref); + ret =iommu_attach_device (dom->domain, dev); +if (ret) +iommu_dma_release_domain (dom);
Shouldn't this be the responsibility of the caller?
Quoted 7 lines
drivers/iommu/dma-iommu.c new file +7
-0+else +set_dma_domain (dev, dom); +pr_debug ("%s%s attached to domain %p\n" ,dev_name (dev), + ret?" *not*" :"" , dom); +return ret; +} +
drivers/iommu/dma-iommu.c line 534
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+void iommu_dma_detach_device (struct device *dev) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); + +if (!dom) { +dev_warn (dev,"Not attached\n" ); +return ; + } +set_dma_domain (dev,NULL ); +iommu_detach_device (dom->domain, dev);
I think this means that we should be checking for NULL DMA domains in all of the IOVA calls, or can we assume our callers behave themselves?
drivers/iommu/dma-iommu.c line 546
Quoted 12 lines
drivers/iommu/dma-iommu.c new file +12
-0+iommu_dma_release_domain (dom); +pr_debug ("%s detached from domain %p\n" ,dev_name (dev), dom); +} + +int iommu_dma_supported (struct device *dev,u64 mask) +{ +/* +* This looks awful, but really it's reasonable to assume that if an +* IOMMU can't address everything that the CPU can, it probably isn't +* generic enough to be using this implementation in the first place. +*/ +return 1 ;
Hmm, perhaps, but I don't think it's *completely* unrealistic to have an IOMMU with more limited physical addressing caps than the CPU. Maybe we could have an IOMMU callback for querying the addressing capabilities?
It shouldn't block this series, I'm just interested in your thoughts on solving this in the future.
Will
Robin Murphy
maintainerRFC PATCH v2 1/3 iommu: implement common IOMMU ops for DMA mapping
Hi Will,
Thanks for the comments,
Quoted 29 lines · Will Deacon
Hi Robin,
Quoted 26 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.Whilst this RFC series is aimed at enabling arm64, once any remaining obvious issues in the common code are addressed we can complete the refactoring by porting
arch/armover for a merge-worthy series.Signed-off-by Robin Murphy <[email protected]>--- drivers/iommu/Kconfig | 7 + drivers/iommu/Makefile | 1 + drivers/iommu/dma-iommu.c | 552 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/dma-iommu.h | 94 ++++++++ 4 files changed, 654 insertions(+)create mode 100644
drivers/iommu/dma-iommu.c
create mode 100644include/linux/dma-iommu.hdrivers/iommu/Kconfig +0
-0@@ -20,6 +20,13 @@ config OF_IOMMU def_bool y depends on OF && IOMMU_API
Quoted 6 lines
drivers/iommu/Kconfig config OF_IOMMU · line 25
Quoted 3 lines
drivers/iommu/Kconfig +3
-0+# IOMMU-agnostic DMA-mapping layer +config IOMMU_DMA + def_bool nThis can just be `bool' (n is the default).
Good to know, fixed.
Quoted 60 lines
Quoted 60 lines
drivers/iommu/Kconfig +4
-0+ depends on NEED_SG_DMA_LENGTH + select IOMMU_API + select IOMMU_IOVA + config FSL_PAMU bool "Freescale IOMMU support" depends on PPC_E500MCdrivers/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_IOVA) += iova.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.odrivers/iommu/dma-iommu.c new file +35
-0@@ -0,0 +1,552 @@ +/* +* A fairly generic DMA-API to IOMMU-API glue layer. +* +* Copyright (C) 2014 ARM Ltd. +* +* based in part on arch/arm/mm/dma-mapping.c: +* Copyright (C) 2000-2004 Russell King +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define pr_fmt (fmt) "%s: " fmt, __func__ + +#include <linux/dma-contiguous.h> +#include <linux/dma-iommu.h> +#include <linux/iova.h> + +int iommu_dma_init (void ) +{ +return iommu_iova_cache_init (); +} + +struct iommu_dma_domain { +struct iommu_domain *domain; +struct iova_domain *iovad;
Quoted 9 lines
drivers/iommu/dma-iommu.c line 41
Quoted 6 lines
drivers/iommu/dma-iommu.c new file +6
-0+struct kref kref; +}; + +static inline dma_addr_t dev_dma_addr (struct device *dev,dma_addr_t addr) +{ +BUG_ON (addr < dev->dma_pfn_offset);Shouldn't we convert the
pfn_offsetto an address before this check?
Oops, yes indeed we should...
Quoted 13 lines
Quoted 13 lines
drivers/iommu/dma-iommu.c new file +13
-0+return addr - ((dma_addr_t )dev->dma_pfn_offset <<PAGE_SHIFT ); +} + +static int __dma_direction_to_prot (enum dma_data_direction dir,bool coherent) +{ +int prot = coherent ?IOMMU_CACHE :0 ; + +switch (dir) { +case DMA_BIDIRECTIONAL : +return prot |IOMMU_READ |IOMMU_WRITE ; +case DMA_TO_DEVICE : +return prot |IOMMU_READ ; +case DMA_FROM_DEVICE :
Quoted 8 lines
drivers/iommu/dma-iommu.c line 59
Quoted 5 lines
drivers/iommu/dma-iommu.c new file +5
-0+return prot |IOMMU_WRITE ; +default : +return 0 ; + } +}We could actually stick this into a header file as a generic helper.
arch/arm/already uses its own flavour, for example.
Well, it's already been said that the arch/arm implementation should go away as part of merging this ;) Having had a good look around there don't appear to be any other potential users to share it with.
Quoted 11 lines
drivers/iommu/dma-iommu.c line 68
Quoted 8 lines
drivers/iommu/dma-iommu.c new file +8
-0+static struct iova *__alloc_iova (struct device *dev,size_t size,bool coherent) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +unsigned long shift =iova_shift (iovad); +unsigned long length =iova_align (iovad, size) >> shift; +unsigned long limit_pfn = iovad->dma_32bit_pfn; +u64 dma_limit = coherent ? dev->coherent_dma_mask : *dev->dma_mask;Should we use
dma_get_maskin the non-coherent case in case the mask pointer is NULL?
Good point, fixed.
Quoted 3 lines
drivers/iommu/dma-iommu.c line 70
Quoted 1 line
drivers/iommu/dma-iommu.c new file +1
-0+ limit_pfn =min (limit_pfn, (unsigned long )(dma_limit >> shift));
min_tavoids the explicit cast.
Agreed, it does look a little nicer that way (although this is more of a nasty workaround for the mask not reflecting dma-ranges limits - hopefully that'll get sorted soon[1] so this can go back to just using the mask)
[1]:http://www.spinics.net/lists/arm-kernel/msg397604.html
Quoted 37 lines
Quoted 6 lines
drivers/iommu/dma-iommu.c new file +5
-0+/* Alignment should probably come from a domain/device attribute... */ +return alloc_iova (iovad, length, limit_pfn, false); +} + +/* drivers/iommu/dma-iommu.c line 85
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+* Create a mapping in device IO address space for specified pages +*/ +dma_addr_t iommu_dma_create_iova_mapping (struct device *dev, +struct page **pages,size_t size,bool coherent) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +struct iommu_domain *domain = dom->domain; +struct iova *iova; +unsigned int count =PAGE_ALIGN (size) >>PAGE_SHIFT ;Why not just make the size parameter npages instead?
Quoted 18 lines
drivers/iommu/dma-iommu.c new file +18
-0+dma_addr_t addr_lo, addr_hi; +int i, prot =__dma_direction_to_prot (DMA_BIDIRECTIONAL , coherent); + + iova =__alloc_iova (dev, size, coherent); +if (!iova) +return DMA_ERROR_CODE ; + + addr_hi = addr_lo =iova_dma_addr (iovad, iova); +for (i =0 ; i < count; ) { +unsigned int next_pfn =page_to_pfn (pages[i]) +1 ; +phys_addr_t phys =page_to_phys (pages[i]); +unsigned int len, j; + +for (j = i+1 ; j < count; j++, next_pfn++) +if (page_to_pfn (pages[j]) != next_pfn) +break ; + + len = (j - i) <<PAGE_SHIFT ;
Quoted 8 lines
drivers/iommu/dma-iommu.c line 108
Quoted 5 lines
drivers/iommu/dma-iommu.c new file +5
-0+if (iommu_map (domain, addr_hi, phys, len, prot)) + goto fail; + addr_hi += len; + i = j; + }This loop is pretty miserable to read. Could we pass this off to ->map_sg and avoid the need to check that the pfns are contiguous?
All part of the copy-paste magic ;) Y'know, other than the bit of extra work to set up the sg_table (and the possible hard size limit to that on some architectures), using scatterlists as the general primitive makes a lot of sense. With a full iommu_dma_alloc_attrs() implementation using sg_alloc_table_from_pages(), this function can end up mostly going away, and we get benefits...
Quoted 44 lines
Quoted 44 lines
drivers/iommu/dma-iommu.c new file +43
-0+return dev_dma_addr (dev, addr_lo); +fail: +iommu_unmap (domain, addr_lo, addr_hi - addr_lo); +__free_iova (iovad, iova); +return DMA_ERROR_CODE ; +} + +int iommu_dma_release_iova_mapping (struct device *dev,dma_addr_t iova, +size_t size) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +size_t offset =iova_offset (iovad, iova); +size_t len =iova_align (iovad, size + offset); + +iommu_unmap (dom->domain, iova - offset, len); +free_iova (iovad,iova_pfn (iovad, iova)); +return 0 ; +} + +struct page **iommu_dma_alloc_buffer (struct device *dev,size_t size, +gfp_t gfp,struct dma_attrs *attrs, +void (clear_buffer )(struct page *page,size_t size)) +{ +struct page **pages; +int count = size >>PAGE_SHIFT ; +int array_size = count *sizeof (struct page *); +int i =0 ; + +if (array_size <=PAGE_SIZE ) + pages =kzalloc (array_size,GFP_KERNEL ); +else + pages =vzalloc (array_size); +if (!pages) +return NULL ; + +if (dma_get_attr (DMA_ATTR_FORCE_CONTIGUOUS , attrs)) { +unsigned long order =get_order (size); +struct page *page; + + page =dma_alloc_from_contiguous (dev, count, order); +if (!page) + goto error;
Quoted 8 lines
drivers/iommu/dma-iommu.c line 154
Quoted 3 lines
drivers/iommu/dma-iommu.c new file +3
-0+ +if (clear_buffer) +clear_buffer (page, size);For cache-coherent systems, this is just a memset so perhaps we could default to that if no function pointer is passed? It would also be cool to do the highmem vs lowmem stuff in core code, but that's more fiddly than it looks and probably not worth the hassle.
...like being able to use sg_miter_* to magically memset everything here and have the architecture provide just the cache flush callback (if it wants to). I'll definitely investigate this as it might help solve some other issues too.
Quoted 31 lines
drivers/iommu/dma-iommu.c line 428
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+struct iommu_dma_domain *iommu_dma_create_domain (const struct iommu_ops *ops, +dma_addr_t base,size_t size) +{ +struct iommu_dma_domain *dom; +struct iommu_domain *domain; +struct iova_domain *iovad; +struct iommu_domain_geometry *dg; +unsigned long order, base_pfn, end_pfn; + +pr_debug ("base=%pad\tsize=0x%zx\n" , &base, size);We can lose the debug prints for upstream.
Quoted 18 lines
drivers/iommu/dma-iommu.c new file +18
-0+ dom =kzalloc (sizeof (*dom),GFP_KERNEL ); +if (!dom) +return NULL ; + +/* +* HACK: We'd like to ask the relevant IOMMU in ops for a suitable +* domain, but until that happens, bypass the bus nonsense and create +* one directly for this specific device/IOMMU combination... +*/ + domain =kzalloc (sizeof (*domain),GFP_KERNEL ); + +if (!domain) + goto out_free_dma_domain; + domain->ops = ops; + +if (ops->domain_init (domain)) + goto out_free_iommu_domain; +/*
Quoted 8 lines
drivers/iommu/dma-iommu.c line 452
Quoted 6 lines
drivers/iommu/dma-iommu.c new file +6
-0+* ...and do the bare minimum to sanity-check that the domain allows +* at least some access to the device... +*/ + dg = &domain->geometry; +if (!(base <= dg->aperture_end && base + size > dg->aperture_start)) { +pr_warn ("DMA range outside IOMMU capability; is DT correct?\n" );We probably shouldn't print things about DT in core (non-OF) code.
True. I'll change it for now, although I hope the default domain stuff will make most of this entirely redundant soon.
Quoted 59 lines
Quoted 59 lines
drivers/iommu/dma-iommu.c new file +59
-0+ goto out_free_iommu_domain; + } +/* ...then finally give it a kicking to make sure it fits */ + dg->aperture_start =max (base, dg->aperture_start); + dg->aperture_end =min (base + size -1 , dg->aperture_end); +/* +* Note that this almost certainly breaks the case where multiple +* devices with different DMA capabilities need to share a domain, +* but we don't have the necessary information to handle that here +* anyway - "proper" group and domain allocation needs to involve +* the IOMMU driver and a complete view of the bus. +*/ + + iovad =kzalloc (sizeof (*iovad),GFP_KERNEL ); +if (!iovad) + goto out_free_iommu_domain; + +/* Use the smallest supported page size for IOVA granularity */ + order =__ffs (ops->pgsize_bitmap); + base_pfn =max (dg->aperture_start >> order, (dma_addr_t )1 ); + end_pfn = dg->aperture_end >> order; +init_iova_domain (iovad,1UL << order, base_pfn, end_pfn); + + dom->domain = domain; + dom->iovad = iovad; +kref_init (&dom->kref); +pr_debug ("domain %p created\n" , dom); +return dom; + +out_free_iommu_domain: +kfree (domain); +out_free_dma_domain: +kfree (dom); +return NULL ; +} + +static void iommu_dma_free_domain (struct kref *kref) +{ +struct iommu_dma_domain *dom; + + dom = container_of(kref, struct iommu_dma_domain, kref); +put_iova_domain (dom->iovad); +iommu_domain_free (dom->domain); +kfree (dom); +pr_debug ("domain %p freed\n" , dom); +} + +void iommu_dma_release_domain (struct iommu_dma_domain *dom) +{ +kref_put (&dom->kref, iommu_dma_free_domain); +} + +struct iommu_domain *iommu_dma_raw_domain (struct iommu_dma_domain *dom) +{ +return dom ? dom->domain :NULL ; +} + +int iommu_dma_attach_device (struct device *dev,struct iommu_dma_domain *dom) +{
Quoted 8 lines
drivers/iommu/dma-iommu.c line 517
Quoted 6 lines
drivers/iommu/dma-iommu.c new file +6
-0+int ret; + +kref_get (&dom->kref); + ret =iommu_attach_device (dom->domain, dev); +if (ret) +iommu_dma_release_domain (dom);Shouldn't this be the responsibility of the caller?
It's the responsibility of the caller to release its reference by making a matching detach call on a successfully attached device. The original code only *takes* the reference on success - I just shuffled things around to avoid the race with someone else detaching the last device and taking down the domain before this iommu_attach_device() returns.
Quoted 12 lines
Quoted 12 lines
drivers/iommu/dma-iommu.c new file +12
-0+else +set_dma_domain (dev, dom); +pr_debug ("%s%s attached to domain %p\n" ,dev_name (dev), + ret?" *not*" :"" , dom); +return ret; +} + +void iommu_dma_detach_device (struct device *dev) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); + +if (!dom) {
Quoted 8 lines
drivers/iommu/dma-iommu.c line 534
Quoted 5 lines
drivers/iommu/dma-iommu.c new file +5
-0+dev_warn (dev,"Not attached\n" ); +return ; + } +set_dma_domain (dev,NULL ); +iommu_detach_device (dom->domain, dev);I think this means that we should be checking for NULL DMA domains in all of the IOVA calls, or can we assume our callers behave themselves?
This check is a hangover from the original code, which similarly never checks the validity of archdata.mapping elsewhere. I'm inclined to simply remove it and let broken callers blow up violently in iommu_detach_device() because I really can't think of a justifiable reason to let them off with a warning. IMO this is intended more for arch code than for any old driver to use willy-nilly, so they really should be getting it right.
Quoted 18 lines
drivers/iommu/dma-iommu.c line 546
Quoted 13 lines
drivers/iommu/dma-iommu.c new file +12
-0+iommu_dma_release_domain (dom); +pr_debug ("%s detached from domain %p\n" ,dev_name (dev), dom); +} + +int iommu_dma_supported (struct device *dev,u64 mask) +{ +/* +* This looks awful, but really it's reasonable to assume that if an +* IOMMU can't address everything that the CPU can, it probably isn't +* generic enough to be using this implementation in the first place. +*/ +return 1 ;Hmm, perhaps, but I don't think it's *completely* unrealistic to have an IOMMU with more limited physical addressing caps than the CPU. Maybe we could have an IOMMU callback for querying the addressing capabilities?
Quoted 2 lines
It shouldn't block this series, I'm just interested in your thoughts on solving this in the future.
I see potentially two places the relevant data could already exist:
* the DMA mask of the IOMMU device itself - accessible, but possibly not reliable if the IOMMU driver doesn't bother to set it correctly (e.g. ours). Also, really this should only represent the IOMMU's own DMA (i.e. PTW interface) capability which, sanity notwithstanding, could conceivably differ from that of the master interface.
* the output address size of the io_pgtable_config - not widely used yet, and not really something to be poking around in outside of the IOMMU driver itself.
Given that it's a fixed property of the hardware it seems like something we could reasonably express with e.g. a mask in iommu_ops, but strictly we'd still need to go digging around for the IOMMU device's dma_pfn_offset as well in case it's behind some wacky interconnect. Unless of course the PTW and master interfaces are hooked up to *different* wacky interconnects, in which case, urgh...
Robin.
RFC PATCH v2 1/3 iommu: implement common IOMMU ops for DMA mapping
In reply to Robin MurphyHello Robin,
I'm sorry for a delay, but I've just finished testing this patchser on Exynos5433
with Exynos DRM and other drivers.
Quoted 116 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.Whilst this RFC series is aimed at enabling arm64, once any remaining obvious issues in the common code are addressed we can complete the refactoring by porting
arch/armover for a merge-worthy series.Signed-off-by Robin Murphy <[email protected]>--- drivers/iommu/Kconfig | 7 + drivers/iommu/Makefile | 1 + drivers/iommu/dma-iommu.c | 552 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/dma-iommu.h | 94 ++++++++ 4 files changed, 654 insertions(+)create mode 100644
drivers/iommu/dma-iommu.c
create mode 100644include/linux/dma-iommu.hdrivers/iommu/Kconfig +7
-0@@ -20,6 +20,13 @@ config OF_IOMMU def_bool y depends on OF && IOMMU_API +# IOMMU-agnostic DMA-mapping layer +config IOMMU_DMA + def_bool n + depends on NEED_SG_DMA_LENGTH + select IOMMU_API + select IOMMU_IOVA + config FSL_PAMU bool "Freescale IOMMU support" depends on PPC_E500MCdrivers/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_IOVA) += iova.o obj-$(CONFIG_OF_IOMMU) += of_iommu.o obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o msm_iommu_dev.odrivers/iommu/dma-iommu.c new file +62
-0@@ -0,0 +1,552 @@ +/* +* A fairly generic DMA-API to IOMMU-API glue layer. +* +* Copyright (C) 2014 ARM Ltd. +* +* based in part on arch/arm/mm/dma-mapping.c: +* Copyright (C) 2000-2004 Russell King +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#define pr_fmt (fmt) "%s: " fmt, __func__ + +#include <linux/dma-contiguous.h> +#include <linux/dma-iommu.h> +#include <linux/iova.h> + +int iommu_dma_init (void ) +{ +return iommu_iova_cache_init (); +} + +struct iommu_dma_domain { +struct iommu_domain *domain; +struct iova_domain *iovad; +struct kref kref; +}; + +static inline dma_addr_t dev_dma_addr (struct device *dev,dma_addr_t addr) +{ +BUG_ON (addr < dev->dma_pfn_offset); +return addr - ((dma_addr_t )dev->dma_pfn_offset <<PAGE_SHIFT ); +} + +static int __dma_direction_to_prot (enum dma_data_direction dir,bool coherent) +{ +int prot = coherent ?IOMMU_CACHE :0 ; + +switch (dir) { +case DMA_BIDIRECTIONAL : +return prot |IOMMU_READ |IOMMU_WRITE ; +case DMA_TO_DEVICE : +return prot |IOMMU_READ ; +case DMA_FROM_DEVICE : +return prot |IOMMU_WRITE ; +default : +return 0 ; + } +} + +static struct iova *__alloc_iova (struct device *dev,size_t size,bool coherent) +{
drivers/iommu/dma-iommu.c line 72
Quoted 10 lines
drivers/iommu/dma-iommu.c new file +10
-0+struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +unsigned long shift =iova_shift (iovad); +unsigned long length =iova_align (iovad, size) >> shift; +unsigned long limit_pfn = iovad->dma_32bit_pfn; +u64 dma_limit = coherent ? dev->coherent_dma_mask : *dev->dma_mask; + + limit_pfn =min (limit_pfn, (unsigned long )(dma_limit >> shift)); +/* Alignment should probably come from a domain/device attribute... */ +return alloc_iova (iovad, length, limit_pfn, false);
DMA-mapping subsystem must provide IOVAs, which are at least size aligned, some drivers
depends on this feature (mainly because they are not able to specify alignment in other
way...). I've discussed this issue long time ago when CMA has been developed and the
conclusion was that size alignment is guaranteed by dma-mapping and drivers can rely on
it.
Quoted 570 lines
drivers/iommu/dma-iommu.c new file +480
-0+} + +/* +* Create a mapping in device IO address space for specified pages +*/ +dma_addr_t iommu_dma_create_iova_mapping (struct device *dev, +struct page **pages,size_t size,bool coherent) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +struct iommu_domain *domain = dom->domain; +struct iova *iova; +unsigned int count =PAGE_ALIGN (size) >>PAGE_SHIFT ; +dma_addr_t addr_lo, addr_hi; +int i, prot =__dma_direction_to_prot (DMA_BIDIRECTIONAL , coherent); + + iova =__alloc_iova (dev, size, coherent); +if (!iova) +return DMA_ERROR_CODE ; + + addr_hi = addr_lo =iova_dma_addr (iovad, iova); +for (i =0 ; i < count; ) { +unsigned int next_pfn =page_to_pfn (pages[i]) +1 ; +phys_addr_t phys =page_to_phys (pages[i]); +unsigned int len, j; + +for (j = i+1 ; j < count; j++, next_pfn++) +if (page_to_pfn (pages[j]) != next_pfn) +break ; + + len = (j - i) <<PAGE_SHIFT ; +if (iommu_map (domain, addr_hi, phys, len, prot)) + goto fail; + addr_hi += len; + i = j; + } +return dev_dma_addr (dev, addr_lo); +fail: +iommu_unmap (domain, addr_lo, addr_hi - addr_lo); +__free_iova (iovad, iova); +return DMA_ERROR_CODE ; +} + +int iommu_dma_release_iova_mapping (struct device *dev,dma_addr_t iova, +size_t size) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +size_t offset =iova_offset (iovad, iova); +size_t len =iova_align (iovad, size + offset); + +iommu_unmap (dom->domain, iova - offset, len); +free_iova (iovad,iova_pfn (iovad, iova)); +return 0 ; +} + +struct page **iommu_dma_alloc_buffer (struct device *dev,size_t size, +gfp_t gfp,struct dma_attrs *attrs, +void (clear_buffer )(struct page *page,size_t size)) +{ +struct page **pages; +int count = size >>PAGE_SHIFT ; +int array_size = count *sizeof (struct page *); +int i =0 ; + +if (array_size <=PAGE_SIZE ) + pages =kzalloc (array_size,GFP_KERNEL ); +else + pages =vzalloc (array_size); +if (!pages) +return NULL ; + +if (dma_get_attr (DMA_ATTR_FORCE_CONTIGUOUS , attrs)) { +unsigned long order =get_order (size); +struct page *page; + + page =dma_alloc_from_contiguous (dev, count, order); +if (!page) + goto error; + +if (clear_buffer) +clear_buffer (page, size); + +for (i =0 ; i < count; i++) + pages[i] = page + i; + +return pages; + } + +/* +* IOMMU can map any pages, so himem can also be used here +*/ + gfp |= __GFP_NOWARN | __GFP_HIGHMEM; + +while (count) { +int j, order =__fls (count); + + pages[i] =alloc_pages (gfp, order); +while (!pages[i] && order) + pages[i] =alloc_pages (gfp, --order); +if (!pages[i]) + goto error; + +if (order) { +split_page (pages[i], order); + j =1 << order; +while (--j) + pages[i + j] = pages[i] + j; + } + +if (clear_buffer) +clear_buffer (pages[i],PAGE_SIZE << order); + i +=1 << order; + count -=1 << order; + } + +return pages; +error: +while (i--) +if (pages[i]) +__free_pages (pages[i],0 ); +if (array_size <=PAGE_SIZE ) +kfree (pages); +else +vfree (pages); +return NULL ; +} + +int iommu_dma_free_buffer (struct device *dev,struct page **pages,size_t size, +struct dma_attrs *attrs) +{ +int count = size >>PAGE_SHIFT ; +int array_size = count *sizeof (struct page *); +int i; + +if (dma_get_attr (DMA_ATTR_FORCE_CONTIGUOUS , attrs)) { +dma_release_from_contiguous (dev, pages[0 ], count); + }else { +for (i =0 ; i < count; i++) +if (pages[i]) +__free_pages (pages[i],0 ); + } + +if (array_size <=PAGE_SIZE ) +kfree (pages); +else +vfree (pages); +return 0 ; +} + +static dma_addr_t __iommu_dma_map_page (struct device *dev,struct page *page, +unsigned long offset,size_t size,enum dma_data_direction dir, +bool coherent) +{ +dma_addr_t dma_addr; +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +phys_addr_t phys =page_to_phys (page) + offset; +size_t iova_off =iova_offset (iovad, phys); +size_t len =iova_align (iovad, size + iova_off); +int prot =__dma_direction_to_prot (dir, coherent); +struct iova *iova =__alloc_iova (dev, len, coherent); + +if (!iova) +return DMA_ERROR_CODE ; + + dma_addr =iova_dma_addr (iovad, iova); +if (iommu_map (dom->domain, dma_addr, phys - iova_off, len, prot)) { +__free_iova (iovad, iova); +return DMA_ERROR_CODE ; + } + +return dev_dma_addr (dev, dma_addr + iova_off); +} + +dma_addr_t iommu_dma_map_page (struct device *dev,struct page *page, +unsigned long offset,size_t size,enum dma_data_direction dir, +struct dma_attrs *attrs) +{ +return __iommu_dma_map_page (dev, page, offset, size, dir, false); +} + +dma_addr_t iommu_dma_coherent_map_page (struct device *dev,struct page *page, +unsigned long offset,size_t size,enum dma_data_direction dir, +struct dma_attrs *attrs) +{ +return __iommu_dma_map_page (dev, page, offset, size, dir, true); +} + +void iommu_dma_unmap_page (struct device *dev,dma_addr_t handle,size_t size, +enum dma_data_direction dir,struct dma_attrs *attrs) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +size_t offset =iova_offset (iovad, handle); +size_t len =iova_align (iovad, size + offset); +dma_addr_t iova = handle - offset; + +if (!iova) +return ; + +iommu_unmap (dom->domain, iova, len); +free_iova (iovad,iova_pfn (iovad, iova)); +} + +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)) + ) { +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; + + 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 ; + } +} + +static int __iommu_dma_map_sg (struct device *dev,struct scatterlist *sg, +int nents,enum dma_data_direction dir,struct dma_attrs *attrs, +bool coherent) +{ +struct iommu_dma_domain *dom =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, prot =__dma_direction_to_prot (dir, coherent); + +/* +* 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. +*/ +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; + + 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 ; +} + +int iommu_dma_map_sg (struct device *dev,struct scatterlist *sg,int nents, +enum dma_data_direction dir,struct dma_attrs *attrs) +{ +return __iommu_dma_map_sg (dev, sg, nents, dir, attrs, false); +} + +int iommu_dma_coherent_map_sg (struct device *dev,struct scatterlist *sg, +int nents,enum dma_data_direction dir,struct dma_attrs *attrs) +{ +return __iommu_dma_map_sg (dev, sg, nents, dir, attrs, true); +} + +void iommu_dma_unmap_sg (struct device *dev,struct scatterlist *sg,int nents, +enum dma_data_direction dir,struct dma_attrs *attrs) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); +struct iova_domain *iovad = dom->iovad; +struct scatterlist *s; +int i; +dma_addr_t iova =sg_dma_address (sg) & ~iova_mask (iovad); +size_t len =0 ; + +/* +* The scatterlist segments are mapped into contiguous IOVA space, +* so just add up the total length and unmap it in one go. +*/ +for_each_sg (sg, s, nents, i) + len +=sg_dma_len (s); + +iommu_unmap (dom->domain, iova, len); +free_iova (iovad,iova_pfn (iovad, iova)); +} + +struct iommu_dma_domain *iommu_dma_create_domain (const struct iommu_ops *ops, +dma_addr_t base,size_t size) +{ +struct iommu_dma_domain *dom; +struct iommu_domain *domain; +struct iova_domain *iovad; +struct iommu_domain_geometry *dg; +unsigned long order, base_pfn, end_pfn; + +pr_debug ("base=%pad\tsize=0x%zx\n" , &base, size); + dom =kzalloc (sizeof (*dom),GFP_KERNEL ); +if (!dom) +return NULL ; + +/* +* HACK: We'd like to ask the relevant IOMMU in ops for a suitable +* domain, but until that happens, bypass the bus nonsense and create +* one directly for this specific device/IOMMU combination... +*/ + domain =kzalloc (sizeof (*domain),GFP_KERNEL ); + +if (!domain) + goto out_free_dma_domain; + domain->ops = ops; + +if (ops->domain_init (domain)) + goto out_free_iommu_domain; +/* +* ...and do the bare minimum to sanity-check that the domain allows +* at least some access to the device... +*/ + dg = &domain->geometry; +if (!(base <= dg->aperture_end && base + size > dg->aperture_start)) { +pr_warn ("DMA range outside IOMMU capability; is DT correct?\n" ); + goto out_free_iommu_domain; + } +/* ...then finally give it a kicking to make sure it fits */ + dg->aperture_start =max (base, dg->aperture_start); + dg->aperture_end =min (base + size -1 , dg->aperture_end); +/* +* Note that this almost certainly breaks the case where multiple +* devices with different DMA capabilities need to share a domain, +* but we don't have the necessary information to handle that here +* anyway - "proper" group and domain allocation needs to involve +* the IOMMU driver and a complete view of the bus. +*/ + + iovad =kzalloc (sizeof (*iovad),GFP_KERNEL ); +if (!iovad) + goto out_free_iommu_domain; + +/* Use the smallest supported page size for IOVA granularity */ + order =__ffs (ops->pgsize_bitmap); + base_pfn =max (dg->aperture_start >> order, (dma_addr_t )1 ); + end_pfn = dg->aperture_end >> order; +init_iova_domain (iovad,1UL << order, base_pfn, end_pfn); + + dom->domain = domain; + dom->iovad = iovad; +kref_init (&dom->kref); +pr_debug ("domain %p created\n" , dom); +return dom; + +out_free_iommu_domain: +kfree (domain); +out_free_dma_domain: +kfree (dom); +return NULL ; +} + +static void iommu_dma_free_domain (struct kref *kref) +{ +struct iommu_dma_domain *dom; + + dom = container_of(kref, struct iommu_dma_domain, kref); +put_iova_domain (dom->iovad); +iommu_domain_free (dom->domain); +kfree (dom); +pr_debug ("domain %p freed\n" , dom); +} + +void iommu_dma_release_domain (struct iommu_dma_domain *dom) +{ +kref_put (&dom->kref, iommu_dma_free_domain); +} + +struct iommu_domain *iommu_dma_raw_domain (struct iommu_dma_domain *dom) +{ +return dom ? dom->domain :NULL ; +} + +int iommu_dma_attach_device (struct device *dev,struct iommu_dma_domain *dom) +{ +int ret; + +kref_get (&dom->kref); + ret =iommu_attach_device (dom->domain, dev); +if (ret) +iommu_dma_release_domain (dom); +else +set_dma_domain (dev, dom); +pr_debug ("%s%s attached to domain %p\n" ,dev_name (dev), + ret?" *not*" :"" , dom); +return ret; +} + +void iommu_dma_detach_device (struct device *dev) +{ +struct iommu_dma_domain *dom =get_dma_domain (dev); + +if (!dom) { +dev_warn (dev,"Not attached\n" ); +return ; + } +set_dma_domain (dev,NULL ); +iommu_detach_device (dom->domain, dev); +iommu_dma_release_domain (dom); +pr_debug ("%s detached from domain %p\n" ,dev_name (dev), dom); +} + +int iommu_dma_supported (struct device *dev,u64 mask) +{ +/* +* This looks awful, but really it's reasonable to assume that if an +* IOMMU can't address everything that the CPU can, it probably isn't +* generic enough to be using this implementation in the first place. +*/ +return 1 ; +} + +int iommu_dma_mapping_error (struct device *dev,dma_addr_t dma_addr) +{ +return dma_addr ==DMA_ERROR_CODE ; +}include/linux/dma-iommu.h new file +84
-0@@ -0,0 +1,94 @@ +/* +* Copyright (C) 2014 ARM Ltd. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef __DMA_IOMMU_H +#define __DMA_IOMMU_H + +#ifdef __KERNEL__ + +#include <linux/types.h> +#include <linux/iommu.h> + +#ifdef CONFIG_IOMMU_DMA + +int iommu_dma_init (void ); + + +struct iommu_dma_domain *iommu_dma_create_domain (const struct iommu_ops *ops, +dma_addr_t base,size_t size); +void iommu_dma_release_domain (struct iommu_dma_domain *dma_domain); + +struct iommu_domain *iommu_dma_raw_domain (struct iommu_dma_domain *dma_domain); + +int iommu_dma_attach_device (struct device *dev,struct iommu_dma_domain *domain); +void iommu_dma_detach_device (struct device *dev); + +/* +* Implementation of these is left to arch code - it can associate domains +* with devices however it likes, provided the lookup is efficient. +*/ +struct iommu_dma_domain *get_dma_domain (struct device *dev); +void set_dma_domain (struct device *dev,struct iommu_dma_domain *dma_domain); + + +dma_addr_t iommu_dma_create_iova_mapping (struct device *dev, +struct page **pages,size_t size,bool coherent); +int iommu_dma_release_iova_mapping (struct device *dev,dma_addr_t iova, +size_t size); + +struct page **iommu_dma_alloc_buffer (struct device *dev,size_t size, +gfp_t gfp,struct dma_attrs *attrs, +void (clear_buffer )(struct page *page,size_t size)); +int iommu_dma_free_buffer (struct device *dev,struct page **pages,size_t size, +struct dma_attrs *attrs); + +dma_addr_t iommu_dma_map_page (struct device *dev,struct page *page, +unsigned long offset,size_t size,enum dma_data_direction dir, +struct dma_attrs *attrs); +dma_addr_t iommu_dma_coherent_map_page (struct device *dev,struct page *page, +unsigned long offset,size_t size,enum dma_data_direction dir, +struct dma_attrs *attrs); +void iommu_dma_unmap_page (struct device *dev,dma_addr_t handle,size_t size, +enum dma_data_direction dir,struct dma_attrs *attrs); + +int iommu_dma_map_sg (struct device *dev,struct scatterlist *sg,int nents, +enum dma_data_direction dir,struct dma_attrs *attrs); +int iommu_dma_coherent_map_sg (struct device *dev,struct scatterlist *sg, +int nents,enum dma_data_direction dir, +struct dma_attrs *attrs); +void iommu_dma_unmap_sg (struct device *dev,struct scatterlist *sgl,int nents, +enum dma_data_direction dir,struct dma_attrs *attrs); + +int iommu_dma_supported (struct device *dev,u64 mask); +int iommu_dma_mapping_error (struct device *dev,dma_addr_t dma_addr); + +#else + +static inline int iommu_dma_init (void ) +{ +return 0 ; +} + +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev)
include/linux/dma-iommu.h line 94
Quoted 10 lines
include/linux/dma-iommu.h new file +10
-0+{ +return NULL ; +} + +void set_dma_domain (struct device *dev,struct iommu_dma_domain *dma_domain) { } + +#endif /* CONFIG_IOMMU_DMA */ + +#endif /* __KERNEL__ */ +#endif /* __DMA_IOMMU_H */
Best regards
Signature · 2 lines
Marek Szyprowski, PhD Samsung R&D Institute Poland
Robin Murphy
maintainerRFC PATCH v2 2/3 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.
--- arch/arm64/include/asm/device.h | 3 + arch/arm64/include/asm/dma-mapping.h | 17 ++ arch/arm64/mm/dma-mapping.c | 320 +++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+)
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 +17 -0
@@ -62,13 +62,30 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.h> +#ifdef CONFIG_IOMMU_DMA +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev) +{ +return dev->archdata.dma_domain; +} + +static inline void 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) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return DMA_ERROR_CODE ;return (dma_addr_t )paddr; }static inline phys_addr_t dma_to_phys (struct device *dev,dma_addr_t dev_addr) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return 0 ;return (phys_addr_t )dev_addr; }
arch/arm64/mm/dma-mapping.c +320 -0
335 lines not on this page · raw message
Signature · 1 line
1.9.1
RFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 2 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.
Is anybody looking at porting arch/arm/ over to this?
Quoted 26 lines
Signed-off-by Robin Murphy <[email protected]>--- arch/arm64/include/asm/device.h | 3 + arch/arm64/include/asm/dma-mapping.h | 17 ++ arch/arm64/mm/dma-mapping.c | 320 +++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+)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 +0
-0
arch/arm64/include/asm/dma-mapping.h static inline bool is_device_dma_coherent(struct device *dev) · line 69
Quoted 8 lines
arch/arm64/include/asm/dma-mapping.h +5
-0@@ -62,13 +62,30 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.h> +#ifdef CONFIG_IOMMU_DMA +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev) +{ +return dev->archdata.dma_domain; +}
You use this function in patch 1, so you probably need to reorder the series slightly.
Quoted 88 lines
arch/arm64/include/asm/dma-mapping.h +11
-0+static inline void 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) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return DMA_ERROR_CODE ;return (dma_addr_t )paddr; }static inline phys_addr_t dma_to_phys (struct device *dev,dma_addr_t dev_addr) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return 0 ;return (phys_addr_t )dev_addr; }arch/arm64/mm/dma-mapping.c +45
-0@@ -23,6 +23,7 @@#include <linux/genalloc.h> #include <linux/dma-mapping.h> #include <linux/dma-contiguous.h> +#include <linux/dma-iommu.h> #include <linux/vmalloc.h> #include <linux/swiotlb.h> @@ -426,6 +427,7 @@ static int __init arm64_dma_init(void) ret |=swiotlb_late_init (); ret |=atomic_pool_init (); + ret |=iommu_dma_init ();return ret; }@@ -439,3 +441,321 @@ static int __init dma_debug_do_init(void)return 0 ; }fs_initcall (dma_debug_do_init); + + +#ifdef CONFIG_IOMMU_DMA + +static struct page **__atomic_get_pages (void *addr) +{ +struct page *page; +phys_addr_t phys; + + phys =gen_pool_virt_to_phys (atomic_pool, (unsigned long )addr); + page =phys_to_page (phys); + +return (struct page **)page; +} + +static struct page **__iommu_get_pages (void *cpu_addr,struct dma_attrs *attrs) +{ +struct vm_struct *area; + +if (__in_atomic_pool (cpu_addr,PAGE_SIZE )) +return __atomic_get_pages (cpu_addr); + + area =find_vm_area (cpu_addr); +if (!area) +return NULL ; + +return area->pages; +} + +static void *__iommu_alloc_atomic (struct device *dev,size_t size, +dma_addr_t *handle,bool coherent) +{ +struct page *page; +void *addr; + + addr =__alloc_from_pool (size, &page); +if (!addr) +return NULL ; + + *handle =iommu_dma_create_iova_mapping (dev, &page, size, coherent); +if (*handle ==DMA_ERROR_CODE ) { +__free_from_pool (addr, size); +return NULL ;
arch/arm64/mm/dma-mapping.c static int __init dma_debug_do_init(void) · line 496
Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10
-0+ } +return addr; +} + +static void __iommu_free_atomic (struct device *dev,void *cpu_addr, +dma_addr_t handle,size_t size) +{ +iommu_dma_release_iova_mapping (dev, handle, size); +__free_from_pool (cpu_addr, size); +}
Up until this point, I don't get why things need to be arch-specific. Surely we should only care about cache and MMU management in the arch code?
Quoted 23 lines
arch/arm64/mm/dma-mapping.c +23
-0+static void __dma_clear_buffer (struct page *page,size_t size) +{ +void *ptr =page_address (page); + +memset (ptr,0 , size); +__dma_flush_range (ptr, ptr + size); +} + +static void *__iommu_alloc_attrs (struct device *dev,size_t size, +dma_addr_t *handle,gfp_t gfp, +struct dma_attrs *attrs) +{ +bool coherent =is_device_dma_coherent (dev); +pgprot_t prot = coherent ?__pgprot (PROT_NORMAL ) : +__pgprot (PROT_NORMAL_NC ); +struct page **pages; +void *addr =NULL ; + + *handle =DMA_ERROR_CODE ; + size =PAGE_ALIGN (size); + +if (!(gfp & __GFP_WAIT)) +return __iommu_alloc_atomic (dev, size, handle, coherent);
arch/arm64/mm/dma-mapping.c static int __init dma_debug_do_init(void) · line 530
Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10
-0+/* +* FIXME: This isn't even true any more! +* +* Following is a work-around (a.k.a. hack) to prevent pages +* with __GFP_COMP being passed to split_page() which cannot +* handle them. The real problem is that this flag probably +* should be 0 on ARM as it is not supported on this +* platform; see CONFIG_HUGETLBFS. +*/ + gfp &= ~(__GFP_COMP);
We should fix this... is it not just a case of calling split_huge_page for compond pages?
Will
Robin Murphy
maintainerRFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 5 lines · Will Deacon
Quoted 2 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.Is anybody looking at porting
arch/arm/over to this?
If not, future me from once-there-are-no-more-major-changes-to-patch-1 will be.
Quoted 30 lines
Quoted 30 lines
Signed-off-by Robin Murphy <[email protected]>--- arch/arm64/include/asm/device.h | 3 + arch/arm64/include/asm/dma-mapping.h | 17 ++ arch/arm64/mm/dma-mapping.c | 320 +++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+)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 +0
-0@@ -62,13 +62,30 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.h>
Quoted 8 lines
arch/arm64/include/asm/dma-mapping.h static inline bool is_device_dma_coherent(struct device *dev) · line 69
Quoted 5 lines
arch/arm64/include/asm/dma-mapping.h +5
-0+#ifdef CONFIG_IOMMU_DMA +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev) +{ +return dev->archdata.dma_domain; +}You use this function in patch 1, so you probably need to reorder the series slightly.
Good point. I need to introduce the dummy stubs unconditionally first, then configure them out here.
Quoted 93 lines
Quoted 93 lines
arch/arm64/include/asm/dma-mapping.h +11
-0+static inline void 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) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return DMA_ERROR_CODE ;return (dma_addr_t )paddr; }static inline phys_addr_t dma_to_phys (struct device *dev,dma_addr_t dev_addr) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return 0 ;return (phys_addr_t )dev_addr; }arch/arm64/mm/dma-mapping.c +50
-0@@ -23,6 +23,7 @@#include <linux/genalloc.h> #include <linux/dma-mapping.h> #include <linux/dma-contiguous.h> +#include <linux/dma-iommu.h> #include <linux/vmalloc.h> #include <linux/swiotlb.h> @@ -426,6 +427,7 @@ static int __init arm64_dma_init(void) ret |=swiotlb_late_init (); ret |=atomic_pool_init (); + ret |=iommu_dma_init ();return ret; }@@ -439,3 +441,321 @@ static int __init dma_debug_do_init(void)return 0 ; }fs_initcall (dma_debug_do_init); + + +#ifdef CONFIG_IOMMU_DMA + +static struct page **__atomic_get_pages (void *addr) +{ +struct page *page; +phys_addr_t phys; + + phys =gen_pool_virt_to_phys (atomic_pool, (unsigned long )addr); + page =phys_to_page (phys); + +return (struct page **)page; +} + +static struct page **__iommu_get_pages (void *cpu_addr,struct dma_attrs *attrs) +{ +struct vm_struct *area; + +if (__in_atomic_pool (cpu_addr,PAGE_SIZE )) +return __atomic_get_pages (cpu_addr); + + area =find_vm_area (cpu_addr); +if (!area) +return NULL ; + +return area->pages; +} + +static void *__iommu_alloc_atomic (struct device *dev,size_t size, +dma_addr_t *handle,bool coherent) +{ +struct page *page; +void *addr; + + addr =__alloc_from_pool (size, &page); +if (!addr) +return NULL ; + + *handle =iommu_dma_create_iova_mapping (dev, &page, size, coherent); +if (*handle ==DMA_ERROR_CODE ) { +__free_from_pool (addr, size); +return NULL ; + } +return addr; +} + +static void __iommu_free_atomic(struct device *dev, void *cpu_addr,
Quoted 8 lines
arch/arm64/mm/dma-mapping.c static int __init dma_debug_do_init(void) · line 496
Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5
-0+ dma_addr_t handle, size_t size) +{ +iommu_dma_release_iova_mapping (dev ,handle ,size ); +__free_from_pool (cpu_addr, size); +}Up until this point, I don't get why things need to be arch-specific. Surely we should only care about cache and MMU management in the arch code?
I agree, but the atomic pool implementation being private got in the way. However, the idea of replacing the _get_pages() stuff with scatterlists makes me think I can clean this up too.
Quoted 28 lines
Quoted 28 lines
arch/arm64/mm/dma-mapping.c +28
-0+static void __dma_clear_buffer (struct page *page,size_t size) +{ +void *ptr =page_address (page); + +memset (ptr,0 , size); +__dma_flush_range (ptr, ptr + size); +} + +static void *__iommu_alloc_attrs (struct device *dev,size_t size, +dma_addr_t *handle,gfp_t gfp, +struct dma_attrs *attrs) +{ +bool coherent =is_device_dma_coherent (dev); +pgprot_t prot = coherent ?__pgprot (PROT_NORMAL ) : +__pgprot (PROT_NORMAL_NC ); +struct page **pages; +void *addr =NULL ; + + *handle =DMA_ERROR_CODE ; + size =PAGE_ALIGN (size); + +if (!(gfp & __GFP_WAIT)) +return __iommu_alloc_atomic (dev, size, handle, coherent); +/* +* FIXME: This isn't even true any more! +* +* Following is a work-around (a.k.a. hack) to prevent pages +* with __GFP_COMP being passed to split_page() which cannot
Quoted 8 lines
arch/arm64/mm/dma-mapping.c static int __init dma_debug_do_init(void) · line 530
Quoted 5 lines
arch/arm64/mm/dma-mapping.c +5
-0+* handle them. The real problem is that this flag probably +* should be 0 on ARM as it is not supported on this +* platform; see CONFIG_HUGETLBFS. +*/ + gfp &= ~(__GFP_COMP);We should fix this... is it not just a case of calling
split_huge_pagefor compond pages?
I've been reading up on the matter and so far I'd concur, but I'm starting to wonder if we really need to do half of this at all. AFAICT the only real reason for splitting pages is so the mmap() implementation can use vm_insert_page() - if that was changed to use remap_pfn_range() instead would the need go away entirely? (SWIOTLB doesn't seem to worry about any of this stuff)
Robin.
RFC PATCH v2 2/3 arm64: add IOMMU dma_ops
In reply to Robin MurphyOn Fri, 2015-02-06 at 14:55 +0000, Robin Murphy wrote <...>
Quoted 17 lines
arch/arm64/include/asm/dma-mapping.h +9
-0@@ -62,13 +62,30 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.h> +#ifdef CONFIG_IOMMU_DMA +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev) +{ +return dev->archdata.dma_domain; +} + +static inline void set_dma_domain (struct device *dev, +struct iommu_dma_domain *dma_domain) +{
arch/arm64/include/asm/dma-mapping.h static inline bool is_device_dma_coherent(struct device *dev) · line 83
Quoted 10 lines
arch/arm64/include/asm/dma-mapping.h +6
-0+ dev->archdata.dma_domain = dma_domain; +} +#endif +static inline dma_addr_t phys_to_dma (struct device *dev,phys_addr_t paddr) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return DMA_ERROR_CODE ;return (dma_addr_t )paddr; }
Hi Robin,
Build fail if CONFIG_IOMMU_DMA is not enabled.
In file included from ../include/linux/dma-mapping.h:82:0,
from ../arch/arm64/kernel/asm-offsets.c:23:
../arch/arm64/include/asm/dma-mapping.h: In function 'phys_to_dma':../arch/arm64/include/asm/dma-mapping.h:81:2: error: implicit declaration of function 'get_dma_domain' [-Werror=implicit-function-declaration]
if (WARN_ON(dev && get_dma_domain(dev)))
^
Joe.C
Robin Murphy
maintainerRFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 35 lines · Yingjoe Chen
On Fri, 2015-02-06 at 14:55 +0000, Robin Murphy wrote <...>
Quoted 17 lines
arch/arm64/include/asm/dma-mapping.h +9
-0@@ -62,13 +62,30 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.h> +#ifdef CONFIG_IOMMU_DMA +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev) +{ +return dev->archdata.dma_domain; +} + +static inline void set_dma_domain (struct device *dev, +struct iommu_dma_domain *dma_domain) +{arch/arm64/include/asm/dma-mapping.h static inline bool is_device_dma_coherent(struct device *dev) · line 83
Quoted 10 lines
arch/arm64/include/asm/dma-mapping.h +6
-0+ dev->archdata.dma_domain = dma_domain; +} +#endif +static inline dma_addr_t phys_to_dma (struct device *dev,phys_addr_t paddr) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return DMA_ERROR_CODE ;return (dma_addr_t )paddr; }Hi Robin,
Build fail if
CONFIG_IOMMU_DMAis not enabled.
Quoted 8 lines
In file included from
../include/linux/dma-mapping.h:82:0,from ../arch/arm64/kernel/asm-offsets.c:23:
../arch/arm64/include/asm/dma-mapping.h: In function 'phys_to_dma':../arch/arm64/include/asm/dma-mapping.h:81:2: error: implicit declaration of function 'get_dma_domain' [-Werror=implicit-function-declaration]
if (WARN_ON(dev && get_dma_domain(dev)))
^Joe.C
Bah, how did I manage to make such a half-finished mess of the includes? Current fixup diff below.
Thanks,
Robin.
--->8---
(quoted diff) +0 -0
b/arch/arm64/include/asm/dma-mapping.h
index 5246d1a..208a268 100644
arch/arm64/include/asm/dma-mapping.h +0 -0
@@ -58,6 +58,7 @@ 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
arch/arm64/mm/dma-mapping.c +1 -1
@@ -23,7 +23,6 @@#include <linux/genalloc.h> #include <linux/dma-mapping.h> #include <linux/dma-contiguous.h> -#include <linux/dma-iommu.h> #include <linux/vmalloc.h> #include <linux/swiotlb.h>
@@ -438,6 +437,7 @@ fs_initcall(dma_debug_do_init);#ifdef CONFIG_IOMMU_DMA +#include <linux/iommu.h> static struct page **__atomic_get_pages (void *addr) {
drivers/iommu/Kconfig +1 -1
@@ -22,7 +22,7 @@ config OF_IOMMU # IOMMU-agnostic DMA-mapping layer config IOMMU_DMA - def_bool n + bool depends on NEED_SG_DMA_LENGTH select IOMMU_API select IOMMU_IOVA
drivers/iommu/dma-iommu.c +5 -2
@@ -23,6 +23,7 @@#include <linux/dma-contiguous.h> #include <linux/dma-iommu.h> +#include <linux/iommu.h> #include <linux/iova.h> int iommu_dma_init (void )
@@ -38,8 +39,10 @@ struct iommu_dma_domain {static inline dma_addr_t dev_dma_addr (struct device *dev,dma_addr_t addr) { -BUG_ON (addr < dev->dma_pfn_offset); -return addr - ((dma_addr_t )dev->dma_pfn_offset <<PAGE_SHIFT ); +dma_addr_t offset = (dma_addr_t )dev->dma_pfn_offset <<PAGE_SHIFT ; + +BUG_ON (addr < offset); +return addr - offset; } static int __dma_direction_to_prot(enum dma_data_direction dir, bool
coherent)
@@ -65,9 +68,9 @@ static struct iova *__alloc_iova(struct device *dev, size_t size, bool coherent)
unsigned long shift = iova_shift(iovad);
unsigned long length = iova_align(iovad, size) >> shift; unsigned long limit_pfn = iovad->dma_32bit_pfn;
- u64 dma_limit = coherent ? dev->coherent_dma_mask : *dev->dma_mask;
+ u64 dma_limit = coherent ? dev->coherent_dma_mask : dma_get_mask(dev);
- limit_pfn = min(limit_pfn, (unsigned long)(dma_limit >> shift));
+ limit_pfn = min_t(unsigned long, limit_pfn, dma_limit >> shift);
/* Alignment should probably come from a domain/device attribute... */ return alloc_iova(iovad, length, limit_pfn, false);
}
include/linux/dma-iommu.h +1 -4
@@ -18,8 +18,7 @@#ifdef __KERNEL__ -#include <linux/types.h> -#include <linux/iommu.h> +#include <linux/dma-mapping.h> #ifdef CONFIG_IOMMU_DMA
@@ -39,8 +38,9 @@ void iommu_dma_detach_device(struct device *dev);* Implementation of these is left to arch code - it can associate domains * with devices however it likes, provided the lookup is efficient. */ -struct iommu_dma_domain *get_dma_domain (struct device *dev); -void set_dma_domain(struct device *dev, struct iommu_dma_domain
*dma_domain);
+static inline struct iommu_dma_domain *get_dma_domain(struct device *dev); +static inline void set_dma_domain(struct device *dev,
+ struct iommu_dma_domain *dma_domain);
dma_addr_t iommu_dma_create_iova_mapping(struct device *dev,
@@ -86,7 +86,9 @@ static inline struct iommu_dma_domain *get_dma_domain(struct device *dev)
return NULL;
}
-void set_dma_domain(struct device *dev, struct iommu_dma_domain *dma_domain) { }
+static inline void set_dma_domain(struct device *dev,
+ struct iommu_dma_domain *dma_domain)
+{ }
#endif /* CONFIG_IOMMU_DMA */
RFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 43 lines · Robin Murphy
Quoted 42 lines · Yingjoe Chen
On Fri, 2015-02-06 at 14:55 +0000, Robin Murphy wrote <...>
Quoted 17 lines
arch/arm64/include/asm/dma-mapping.h +9
-0@@ -62,13 +62,30 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.h> +#ifdef CONFIG_IOMMU_DMA +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev) +{ +return dev->archdata.dma_domain; +} + +static inline void set_dma_domain (struct device *dev, +struct iommu_dma_domain *dma_domain) +{arch/arm64/include/asm/dma-mapping.h static inline bool is_device_dma_coherent(struct device *dev) · line 83
Quoted 10 lines
arch/arm64/include/asm/dma-mapping.h +6
-0+ dev->archdata.dma_domain = dma_domain; +} +#endif +static inline dma_addr_t phys_to_dma (struct device *dev,phys_addr_t paddr) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return DMA_ERROR_CODE ;return (dma_addr_t )paddr; }Hi Robin,
Build fail if
CONFIG_IOMMU_DMAis not enabled.In file included from
../include/linux/dma-mapping.h:82:0,from ../arch/arm64/kernel/asm-offsets.c:23:
../arch/arm64/include/asm/dma-mapping.h: In function 'phys_to_dma':../arch/arm64/include/asm/dma-mapping.h:81:2: error: implicit declaration of function 'get_dma_domain' [-Werror=implicit-function-declaration]
if (WARN_ON(dev && get_dma_domain(dev)))
^
Quoted 8 lines
Quoted 1 line
Joe.C
Bah, how did I manage to make such a half-finished mess of the includes? Current fixup diff below.
Thanks,
Robin.
Dear Robin,
We have test this patch on our mt8173, it also could work well.
And I have 2 questiones about how to use this iommu. 1)if We call "arch_setup_dma_ops" to create a iommu domain, then is
this function "bus_set_iommu" who also need "struct iommu_ops *" necessary to be called or not in your design?
2)int (*domain_init)(struct iommu_domain *domain);
About this function, it will alloc pagetable for the iommu
domain. And We expect the pagetable memory is uncacheable, so try to call "dma_alloc_coherent", unfortunately the "struct device *" can't be passed into this function. so is it possible if adding a parameter in this function.
Thanks.
Robin Murphy
maintainerRFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 52 lines · Yong Wu
Quoted 43 lines · Robin Murphy
Quoted 42 lines · Yingjoe Chen
On Fri, 2015-02-06 at 14:55 +0000, Robin Murphy wrote <...>
Quoted 17 lines
arch/arm64/include/asm/dma-mapping.h +9
-0@@ -62,13 +62,30 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.h> +#ifdef CONFIG_IOMMU_DMA +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev) +{ +return dev->archdata.dma_domain; +} + +static inline void set_dma_domain (struct device *dev, +struct iommu_dma_domain *dma_domain) +{arch/arm64/include/asm/dma-mapping.h static inline bool is_device_dma_coherent(struct device *dev) · line 83
Quoted 10 lines
arch/arm64/include/asm/dma-mapping.h +6
-0+ dev->archdata.dma_domain = dma_domain; +} +#endif +static inline dma_addr_t phys_to_dma (struct device *dev,phys_addr_t paddr) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return DMA_ERROR_CODE ;return (dma_addr_t )paddr; }Hi Robin,
Build fail if
CONFIG_IOMMU_DMAis not enabled.In file included from
../include/linux/dma-mapping.h:82:0,from ../arch/arm64/kernel/asm-offsets.c:23: ../arch/arm64/include/asm/dma-mapping.h: In function 'phys_to_dma': ../arch/arm64/include/asm/dma-mapping.h:81:2: error: implicit declaration of function 'get_dma_domain' [-Werror=implicit-function-declaration] if (WARN_ON(dev && get_dma_domain(dev))) ^Quoted 8 lines
Quoted 1 line
Joe.C
Bah, how did I manage to make such a half-finished mess of the includes? Current fixup diff below.
Thanks,
Robin.
Quoted 8 lines
Dear Robin,
We have test this patch on our mt8173, it also could work well.Tested-by Yong Wu <[email protected]>And I have 2 questiones about how to use this iommu. 1)if We call "
arch_setup_dma_ops" to create a iommu domain, then is
this function "bus_set_iommu" who also need "structiommu_ops*" necessary to be called or not in your design?
I've intentionally bypassed bus_set_iommu() because it doesn't necessarily work well for platform devices - I've got 7 separate IOMMUs here and I need to know which is which, so the notion of "the IOMMU for the platform bus" doesn't make a whole lot of sense. Thus iommu_dma_create_domain() creates the iommu_domain directly using the iommu_ops provided, because...
Quoted 6 lines
2)int (*domain_init)(struct
iommu_domain*domain);
About this function, it will alloc pagetable for the iommu
domain. And We expect the pagetable memory is uncacheable, so try to call "dma_alloc_coherent", unfortunately the "struct device *" can't be passed into this function. so is it possible if adding a parameter in this function.
...one of the ideas of the new of_iommu_configure framework is that iommu_ops structures can represent individual IOMMU devices if necessary. The ops->priv pointer was included for that purpose, but isn't very clean so Will has plans to remove it again - it's easy enough to achieve the same effect by having the driver embed the ops in its private instance data instead. I've done that with the ARM SMMU driver which has a similar issue of needing hardware details at domain_init() time (no patches ready yet but I have an iommu/dev branch on top of the iommu/dma branch with some current work-in-progress bits)
Thanks,
Robin.
RFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 17 lines · Robin Murphy
Quoted 6 lines
2)int (*domain_init)(struct
iommu_domain*domain);
About this function, it will alloc pagetable for the iommu
domain. And We expect the pagetable memory is uncacheable, so try to call "dma_alloc_coherent", unfortunately the "struct device *" can't be passed into this function. so is it possible if adding a parameter in this function....one of the ideas of the new
of_iommu_configureframework is thatiommu_opsstructures can represent individual IOMMU devices if necessary. Theops->privpointer was included for that purpose, but isn't very clean so Will has plans to remove it again - it's easy enough to achieve the same effect by having the driver embed the ops in its private instance data instead. I've done that with the ARM SMMU driver which has a similar issue of needing hardware details atdomain_init()time (no patches ready yet but I have an iommu/dev branch on top of the iommu/dma branch with some current work-in-progress bits)
Quoted 3 lines
Thanks,
Robin.
Dear Robin,
Thanks very much for your suggestion.
I have a more question: how to do cache maintenance in arm64.
a) "__dma_flush_range" can not be expected to used directly.
b) Following Documentation\DMA-API.txt, section: Streaming DMA
mappings. dma_map_single should be used, But I am not sure how to pass the first "struct device *".
We have a device which call arch_setup_dma_ops to create the
iommu domain, if we use this device, it will enter __iommu_map_page;
If we input NULL for dmap_map_single, it will assert atdma_capable in swiotlb_map_page...
And normally, we always need do cache maintenance only for some
bytes in the pagetable but not whole a page. Then is there a easy way to do the cache maintenance?
Robin Murphy
maintainerRFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 27 lines · Yong Wu
Quoted 17 lines · Robin Murphy
Quoted 6 lines
2)int (*domain_init)(struct
iommu_domain*domain);
About this function, it will alloc pagetable for the iommu
domain. And We expect the pagetable memory is uncacheable, so try to call "dma_alloc_coherent", unfortunately the "struct device *" can't be passed into this function. so is it possible if adding a parameter in this function....one of the ideas of the new
of_iommu_configureframework is thatiommu_opsstructures can represent individual IOMMU devices if necessary. Theops->privpointer was included for that purpose, but isn't very clean so Will has plans to remove it again - it's easy enough to achieve the same effect by having the driver embed the ops in its private instance data instead. I've done that with the ARM SMMU driver which has a similar issue of needing hardware details atdomain_init()time (no patches ready yet but I have an iommu/dev branch on top of the iommu/dma branch with some current work-in-progress bits)Quoted 3 lines
Thanks,
Robin.Dear Robin,
Thanks very much for your suggestion.I have a more question: how to do cache maintenance in arm64.
Quoted 8 lines
a) "
__dma_flush_range" can not be expected to used directly.
b) Following Documentation\DMA-API.txt, section: Streaming DMA
mappings.dma_map_singleshould be used, But I am not sure how to pass the first "struct device *".
We have a device which callarch_setup_dma_opsto create the
iommu domain, if we use this device, it will enter__iommu_map_page;
If we input NULL fordmap_map_single, it will assert atdma_capableinswiotlb_map_page...
Consider that the IOMMU's page table walker is a DMA master in its own right, and that is the device you're mapping the page tables for. Therefore your IOMMU driver needs to have access to the struct device of the IOMMU itself to use for the page-table-related mappings. Also, be sure to set the IOMMU's DMA mask correctly to prevent SWIOTLB bounce buffers being created in the process (which as I've found generally ends in disaster).
Quoted 3 lines
And normally, we always need do cache maintenance only for some
bytes in the pagetable but not whole a page. Then is there a easy way to do the cache maintenance?
For a noncoherent device, dma_map_single() will end up calling __dma_map_area() with the page offset and size of the original request, so the updated part gets flushed by VA, and the rest of the page isn't touched if it doesn't need to be. On the other hand if the page tables were allocated with dma_alloc_coherent() in the first place, then just calling dma_sync_single_for_device() for the updated region should suffice.
Robin.
RFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 37 lines · Robin Murphy
Quoted 27 lines · Yong Wu
Quoted 17 lines · Robin Murphy
Quoted 6 lines
2)int (*domain_init)(struct
iommu_domain*domain);
About this function, it will alloc pagetable for the iommu
domain. And We expect the pagetable memory is uncacheable, so try to call "dma_alloc_coherent", unfortunately the "struct device *" can't be passed into this function. so is it possible if adding a parameter in this function....one of the ideas of the new
of_iommu_configureframework is thatiommu_opsstructures can represent individual IOMMU devices if necessary. Theops->privpointer was included for that purpose, but isn't very clean so Will has plans to remove it again - it's easy enough to achieve the same effect by having the driver embed the ops in its private instance data instead. I've done that with the ARM SMMU driver which has a similar issue of needing hardware details atdomain_init()time (no patches ready yet but I have an iommu/dev branch on top of the iommu/dma branch with some current work-in-progress bits)Quoted 3 lines
Thanks,
Robin.Dear Robin,
Thanks very much for your suggestion.I have a more question: how to do cache maintenance in arm64.
Quoted 8 lines
a) "
__dma_flush_range" can not be expected to used directly.
b) Following Documentation\DMA-API.txt, section: Streaming DMA
mappings.dma_map_singleshould be used, But I am not sure how to pass the first "struct device *".
We have a device which callarch_setup_dma_opsto create the
iommu domain, if we use this device, it will enter__iommu_map_page;
If we input NULL fordmap_map_single, it will assert atdma_capableinswiotlb_map_page...
Quoted 1 line
Consider that the IOMMU's page table walker is a DMA master in its own
right, and that is the device you're mapping the page tables for. Therefore your IOMMU driver needs to have access to the struct device of the IOMMU itself to use for the page-table-related mappings. Also, be sure to set the IOMMU's DMA mask correctly to prevent SWIOTLB bounce buffers being created in the process (which as I've found generally ends in disaster).
Quoted 6 lines
Quoted 4 lines
And normally, we always need do cache maintenance only for some
bytes in the pagetable but not whole a page. Then is there a easy way to do the cache maintenance?For a noncoherent device,
dma_map_single()will end up calling
__dma_map_area() with the page offset and size of the original request, so the updated part gets flushed by VA, and the rest of the page isn't touched if it doesn't need to be. On the other hand if the page tables were allocated with dma_alloc_coherent() in the first place, then just calling dma_sync_single_for_device() for the updated region should suffice.
Quoted 1 line
Where exactly would you call the dma_unmap? It seems a bit strange to be repeatedly calling dma_map and never calling dma_unmap. I don't see it explicitly forbidden in the docs anywhere to do this but it seems like it would be violating the implicit handoff of dma_map/dma_unmap.
Quoted 2 lines
Robin.
Thanks,
Laura
Signature · 3 lines
Qualcomm Innovation Center, Inc. Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project
Robin Murphy
maintainerRFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Hi Laura,
On 05/03/15 00:19, Laura Abbott wrote:
[...]
Quoted 20 lines
Quoted 1 line
Consider that the IOMMU's page table walker is a DMA master in its own
right, and that is the device you're mapping the page tables for. Therefore your IOMMU driver needs to have access to the struct device of the IOMMU itself to use for the page-table-related mappings. Also, be sure to set the IOMMU's DMA mask correctly to prevent SWIOTLB bounce buffers being created in the process (which as I've found generally ends in disaster).
Quoted 6 lines
Quoted 4 lines
And normally, we always need do cache maintenance only for some
bytes in the pagetable but not whole a page. Then is there a easy way to do the cache maintenance?For a noncoherent device,
dma_map_single()will end up calling
__dma_map_area()with the page offset and size of the original request, so the updated part gets flushed by VA, and the rest of the page isn't touched if it doesn't need to be. On the other hand if the page tables were allocated withdma_alloc_coherent()in the first place, then just callingdma_sync_single_for_device()for the updated region should suffice.Quoted 1 line
Quoted 4 lines
Where exactly would you call the
dma_unmap? It seems a bit strange to be repeatedly callingdma_mapand never callingdma_unmap. I don't see it explicitly forbidden in the docs anywhere to do this but it seems like it would be violating the implicit handoff of dma_map/dma_unmap.
I think ideally you'd call dma_map_page when you first create the page table, dma_sync_single_for_device on any update, and dma_unmap_page when you tear it down, and you'd also use the appropriate DMA addresses everywhere instead of physical addresses.
I wouldn't compare that with what we do in the ARM SMMU driver, because that's a lot more hacky; there we're actually _relying_ on the mapping aspect of dma_map_page being a no-op so we just get the implicit sync part of it, thus we know an unmap would do absolutely nothing (since the SMMU doesn't write to the page tables we've no need to sync them back to the CPU), so we can get away with skipping it. Of course, as both Mitch and I have apparently discovered recently, things end up going wrong when that no-op assumption isn't true and bounce buffers happen...
Robin.
RFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Quoted 22 lines · Robin Murphy
Hi Laura,
On 05/03/15 00:19, Laura Abbott wrote:
[...]Quoted 18 lines
Quoted 1 line
Consider that the IOMMU's page table walker is a DMA master in its own
right, and that is the device you're mapping the page tables for. Therefore your IOMMU driver needs to have access to the struct device of the IOMMU itself to use for the page-table-related mappings. Also, be sure to set the IOMMU's DMA mask correctly to prevent SWIOTLB bounce buffers being created in the process (which as I've found generally ends in disaster).
Quoted 6 lines
Quoted 4 lines
And normally, we always need do cache maintenance only for some
bytes in the pagetable but not whole a page. Then is there a easy way to do the cache maintenance?For a noncoherent device,
dma_map_single()will end up calling
__dma_map_area()with the page offset and size of the original request, so the updated part gets flushed by VA, and the rest of the page isn't touched if it doesn't need to be. On the other hand if the page tables were allocated withdma_alloc_coherent()in the first place, then just callingdma_sync_single_for_device()for the updated region should suffice.
That's wrong. dma_sync_single_*() is not permitted to be called on coherently allocated memory. Where coherent memory needs to be remapped, dma_sync_single_*() will panic the kernel.
If it's in coherent memory, all you should need is the appropriate memory barrier to ensure that the DMA agent can see the writes.
Quoted 9 lines
Quoted 4 lines
Where exactly would you call the
dma_unmap? It seems a bit strange to be repeatedly callingdma_mapand never callingdma_unmap. I don't see it explicitly forbidden in the docs anywhere to do this but it seems like it would be violating the implicit handoff of dma_map/dma_unmap.I think ideally you'd call
dma_map_pagewhen you first create the page table,dma_sync_single_for_deviceon any update, anddma_unmap_pagewhen you tear it down, and you'd also use the appropriate DMA addresses everywhere instead of physical addresses.
No.
dma_map_page() ownership changes CPU->DMAdma_sync_single_for_cpu() ownership changes DMA->CPUdma_sync_single_for_device() ownership changes CPU->DMAdma_unmap_page() ownership changes DMA->CPU
It's invalid to miss out the pairing that give those ownership changes.
Signature · 2 lines
FTTC broadband for 0.8mile line: currently at 10.5Mbps down 400kbps up according to speedtest.net.
Robin Murphy
maintainerRFC PATCH v2 2/3 arm64: add IOMMU dma_ops
Hi Russell,
On 09/03/15 17:59, Russell King - ARM Linux wrote:
[...]
Quoted 7 lines
Quoted 6 lines
Quoted 6 lines
Quoted 1 line
For a noncoherent device,
dma_map_single()will end up calling
__dma_map_area()with the page offset and size of the original request, so the updated part gets flushed by VA, and the rest of the page isn't touched if it doesn't need to be. On the other hand if the page tables were allocated withdma_alloc_coherent()in the first place, then just callingdma_sync_single_for_device()for the updated region should suffice.
Quoted 6 lines
That's wrong. dma_sync_single_*() is not permitted to be called on coherently allocated memory. Where coherent memory needs to be remapped, dma_sync_single_*() will panic the kernel.
If it's in coherent memory, all you should need is the appropriate memory barrier to ensure that the DMA agent can see the writes.
You're quite right, that's the whole point of *coherent* allocations after all. I got my syncs and barriers muddled there.
Quoted 10 lines
Quoted 9 lines
Quoted 4 lines
Where exactly would you call the
dma_unmap? It seems a bit strange to be repeatedly callingdma_mapand never callingdma_unmap. I don't see it explicitly forbidden in the docs anywhere to do this but it seems like it would be violating the implicit handoff of dma_map/dma_unmap.I think ideally you'd call
dma_map_pagewhen you first create the page table,dma_sync_single_for_deviceon any update, anddma_unmap_pagewhen you tear it down, and you'd also use the appropriate DMA addresses everywhere instead of physical addresses.
Quoted 8 lines
No.
dma_map_page()ownership changesCPU->DMAdma_sync_single_for_cpu()ownership changesDMA->CPUdma_sync_single_for_device()ownership changesCPU->DMAdma_unmap_page()ownership changesDMA->CPUIt's invalid to miss out the pairing that give those ownership changes.
Thanks for the clarification - the wording in DMA-API.txt rather implies that in the DMA_TO_DEVICE case you only have to sync the updated data /after/ writing it. For the sake of purely getting pages flushed, would it be more reasonable then to call dma_map_single() followed immediately by dma_unmap_single_attrs() with DMA_ATTR_SKIP_CPU_SYNC? Since we know the IOMMU can never write back to memory (ones that can are a different issue) it would be nice to be able to skip the extra invalidations somehow, without too heinously abusing the API.
Robin.
Robin Murphy
maintainerRFC PATCH v2 2/3 arm64: add IOMMU dma_ops
On 09/03/15 20:09, Robin Murphy wrote:
[...]
Quoted 14 lines
Quoted 5 lines
Quoted 4 lines
I think ideally you'd call
dma_map_pagewhen you first create the page table,dma_sync_single_for_deviceon any update, anddma_unmap_pagewhen you tear it down, and you'd also use the appropriate DMA addresses everywhere instead of physical addresses.Quoted 8 lines
No.
dma_map_page()ownership changesCPU->DMAdma_sync_single_for_cpu()ownership changesDMA->CPUdma_sync_single_for_device()ownership changesCPU->DMAdma_unmap_page()ownership changesDMA->CPUIt's invalid to miss out the pairing that give those ownership changes.
Quoted 8 lines
Thanks for the clarification - the wording in DMA-API.txt rather implies that in the
DMA_TO_DEVICEcase you only have to sync the updated data /after/ writing it. For the sake of purely getting pages flushed, would it be more reasonable then to calldma_map_single()followed immediately bydma_unmap_single_attrs()withDMA_ATTR_SKIP_CPU_SYNC? Since we know the IOMMU can never write back to memory (ones that can are a different issue) it would be nice to be able to skip the extra invalidations somehow, without too heinously abusing the API.
Scratch that, I'm being a massive idiot (again). Of course the actual invalidations will only happen if they need to, based on the DMA direction. The overhead of dma_sync_*_for_cpu() and dma_unmap() is then only a handful of function calls, which is probably an acceptable price to pay for making sure things work as correctly as possible.
Quoted 8 lines
Robin.
_______________________________________________
iommu mailing list
iommu at lists.linux-foundation.org
https://lists.linuxfoundation.org/mailman/listinfo/iommu
RFC PATCH v2 2/3 arm64: add IOMMU dma_ops
In reply to Robin MurphyHello Robin,
Quoted 343 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.Signed-off-by Robin Murphy <[email protected]>--- arch/arm64/include/asm/device.h | 3 + arch/arm64/include/asm/dma-mapping.h | 17 ++ arch/arm64/mm/dma-mapping.c | 320 +++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+)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 +17
-0@@ -62,13 +62,30 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.h> +#ifdef CONFIG_IOMMU_DMA +static inline struct iommu_dma_domain *get_dma_domain (struct device *dev) +{ +return dev->archdata.dma_domain; +} + +static inline void 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) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return DMA_ERROR_CODE ;return (dma_addr_t )paddr; }static inline phys_addr_t dma_to_phys (struct device *dev,dma_addr_t dev_addr) { +if (WARN_ON (dev &&get_dma_domain (dev))) +return 0 ;return (phys_addr_t )dev_addr; }arch/arm64/mm/dma-mapping.c +262
-0@@ -23,6 +23,7 @@#include <linux/genalloc.h> #include <linux/dma-mapping.h> #include <linux/dma-contiguous.h> +#include <linux/dma-iommu.h> #include <linux/vmalloc.h> #include <linux/swiotlb.h> @@ -426,6 +427,7 @@ static int __init arm64_dma_init(void) ret |=swiotlb_late_init (); ret |=atomic_pool_init (); + ret |=iommu_dma_init ();return ret; }@@ -439,3 +441,321 @@ static int __init dma_debug_do_init(void)return 0 ; }fs_initcall (dma_debug_do_init); + + +#ifdef CONFIG_IOMMU_DMA + +static struct page **__atomic_get_pages (void *addr) +{ +struct page *page; +phys_addr_t phys; + + phys =gen_pool_virt_to_phys (atomic_pool, (unsigned long )addr); + page =phys_to_page (phys); + +return (struct page **)page; +} + +static struct page **__iommu_get_pages (void *cpu_addr,struct dma_attrs *attrs) +{ +struct vm_struct *area; + +if (__in_atomic_pool (cpu_addr,PAGE_SIZE )) +return __atomic_get_pages (cpu_addr); + + area =find_vm_area (cpu_addr); +if (!area) +return NULL ; + +return area->pages; +} + +static void *__iommu_alloc_atomic (struct device *dev,size_t size, +dma_addr_t *handle,bool coherent) +{ +struct page *page; +void *addr; + + addr =__alloc_from_pool (size, &page); +if (!addr) +return NULL ; + + *handle =iommu_dma_create_iova_mapping (dev, &page, size, coherent); +if (*handle ==DMA_ERROR_CODE ) { +__free_from_pool (addr, size); +return NULL ; + } +return addr; +} + +static void __iommu_free_atomic (struct device *dev,void *cpu_addr, +dma_addr_t handle,size_t size) +{ +iommu_dma_release_iova_mapping (dev, handle, size); +__free_from_pool (cpu_addr, size); +} + +static void __dma_clear_buffer (struct page *page,size_t size) +{ +void *ptr =page_address (page); + +memset (ptr,0 , size); +__dma_flush_range (ptr, ptr + size); +} + +static void *__iommu_alloc_attrs (struct device *dev,size_t size, +dma_addr_t *handle,gfp_t gfp, +struct dma_attrs *attrs) +{ +bool coherent =is_device_dma_coherent (dev); +pgprot_t prot = coherent ?__pgprot (PROT_NORMAL ) : +__pgprot (PROT_NORMAL_NC ); +struct page **pages; +void *addr =NULL ; + + *handle =DMA_ERROR_CODE ; + size =PAGE_ALIGN (size); + +if (!(gfp & __GFP_WAIT)) +return __iommu_alloc_atomic (dev, size, handle, coherent); +/* +* FIXME: This isn't even true any more! +* +* Following is a work-around (a.k.a. hack) to prevent pages +* with __GFP_COMP being passed to split_page() which cannot +* handle them. The real problem is that this flag probably +* should be 0 on ARM as it is not supported on this +* platform; see CONFIG_HUGETLBFS. +*/ + gfp &= ~(__GFP_COMP); + + pages =iommu_dma_alloc_buffer (dev, size, gfp, attrs, + __dma_clear_buffer); +if (!pages) +return NULL ; + + *handle =iommu_dma_create_iova_mapping (dev, pages, size, coherent); +if (*handle ==DMA_ERROR_CODE ) + goto err_mapping; + + addr =dma_common_pages_remap (pages, size,VM_USERMAP , +__get_dma_pgprot (attrs, prot, coherent), +__builtin_return_address (0 )); +if (addr) +return addr; + +iommu_dma_release_iova_mapping (dev, *handle, size); +err_mapping: +iommu_dma_free_buffer (dev, pages, size, attrs); +return NULL ; +} + +static void __iommu_free_attrs (struct device *dev,size_t size,void *cpu_addr, +dma_addr_t handle,struct dma_attrs *attrs) +{ +struct page **pages; + + size =PAGE_ALIGN (size); +if (__in_atomic_pool (cpu_addr, size)) { +__iommu_free_atomic (dev, cpu_addr, handle, size); +return ; + } + + pages =__iommu_get_pages (cpu_addr, attrs); +if (!pages) { +WARN (1 ,"trying to free invalid coherent area: %p\n" , cpu_addr); +return ; + } + +dma_common_free_remap (cpu_addr, size,VM_USERMAP ); + +iommu_dma_release_iova_mapping (dev, handle, size); +iommu_dma_free_buffer (dev, pages, size, attrs); +} + +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) +{ +unsigned long uaddr = vma->vm_start; +unsigned long usize = vma->vm_end - vma->vm_start; +struct page **pages =__iommu_get_pages (cpu_addr, attrs); +int ret; + +if (!pages) +return -ENXIO ; + + 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; + +do { + ret =vm_insert_page (vma, uaddr, *pages++); +if (ret) { +pr_err ("Remapping memory failed: %d\n" , ret); +return ret; + } + uaddr +=PAGE_SIZE ; + usize -=PAGE_SIZE ; + }while (usize >0 ); + +return 0 ; +} + +static void __iommu_sync_single_for_cpu (struct device *dev, +dma_addr_t dev_addr,size_t size, +enum dma_data_direction dir) +{ +if (!is_device_dma_coherent (dev) && (dev_addr !=DMA_ERROR_CODE )) { +struct iommu_dma_domain *dma_domain =get_dma_domain (dev); +struct iommu_domain *domain =iommu_dma_raw_domain (dma_domain); +phys_addr_t phys =iommu_iova_to_phys (domain, dev_addr); + +__dma_unmap_area (phys_to_virt (phys), size, dir); + } +} + +static void __iommu_sync_single_for_device (struct device *dev, +dma_addr_t dev_addr,size_t size, +enum dma_data_direction dir) +{ +if (!is_device_dma_coherent (dev) && (dev_addr !=DMA_ERROR_CODE )) { +struct iommu_dma_domain *dma_domain =get_dma_domain (dev); +struct iommu_domain *domain =iommu_dma_raw_domain (dma_domain); +phys_addr_t phys =iommu_iova_to_phys (domain, dev_addr); + +__dma_map_area (phys_to_virt (phys), size, dir); + } +} + +static dma_addr_t __iommu_map_page (struct device *dev,struct page *page, +unsigned long offset,size_t size, +enum dma_data_direction dir, +struct dma_attrs *attrs) +{ +dma_addr_t dev_addr; + +if (is_device_dma_coherent (dev)) +return iommu_dma_coherent_map_page (dev, page, offset, size, + dir, attrs); + + dev_addr =iommu_dma_map_page (dev, page, offset, size, dir, attrs); + +if (!dma_get_attr (DMA_ATTR_SKIP_CPU_SYNC , attrs)) +__iommu_sync_single_for_device (dev, dev_addr, size, dir); + +return dev_addr; +} + +static void __iommu_unmap_page (struct device *dev,dma_addr_t dev_addr, +size_t size,enum dma_data_direction dir, +struct dma_attrs *attrs) +{ +if (!dma_get_attr (DMA_ATTR_SKIP_CPU_SYNC , attrs)) +__iommu_sync_single_for_cpu (dev, dev_addr, size, dir); + +iommu_dma_unmap_page (dev, dev_addr, size, dir, attrs); +} + +static void __iommu_sync_sg_for_cpu (struct device *dev, +struct scatterlist *sgl,int nelems, +enum dma_data_direction dir) +{ +struct scatterlist *sg; +int i; + +if (is_device_dma_coherent (dev)) +return ; + +for_each_sg (sgl, sg, nelems, i) +__dma_unmap_area (sg_virt (sg), sg->length, dir); +} + +static void __iommu_sync_sg_for_device (struct device *dev, +struct scatterlist *sgl,int nelems, +enum dma_data_direction dir) +{ +struct scatterlist *sg; +int i; + +if (is_device_dma_coherent (dev)) +return ; + +for_each_sg (sgl, sg, nelems, i) +__dma_map_area (sg_virt (sg), sg->length, dir); +} + +static int __iommu_map_sg_attrs (struct device *dev,struct scatterlist *sgl, +int nelems,enum dma_data_direction dir, +struct dma_attrs *attrs) +{ +if (is_device_dma_coherent (dev)) +return iommu_dma_coherent_map_sg (dev, sgl, nelems, dir, attrs); + +if (!dma_get_attr (DMA_ATTR_SKIP_CPU_SYNC , attrs)) +__iommu_sync_sg_for_device (dev, sgl, nelems, dir); + +return iommu_dma_map_sg (dev, sgl, nelems, dir, attrs); +} + +static void __iommu_unmap_sg_attrs(struct device *dev,
arch/arm64/mm/dma-mapping.c static int __init dma_debug_do_init(void) · line 713
Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10
-0+ struct scatterlist *sgl, int nelems, + enum dma_data_direction dir, + struct dma_attrs *attrs) +{ + if (!dma_get_attr(DMA_ATTR_SKIP_CPU_SYNC, attrs)) + __iommu_sync_sg_for_cpu(dev, sgl, nelems, dir); + +iommu_dma_unmap_sg (dev ,sgl ,nelems ,dir ,attrs ); +} +
Could you also add the following function to make the implementation complete?
static int __iommu_get_sgtable(struct device *dev, struct sg_table *sgt,
void *cpu_addr, dma_addr_t dma_addr, size_t size, struct dma_attrs *attrs)
{
unsigned int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
struct page **pages = __iommu_get_pages(cpu_addr, attrs);
if (!pages)
return -ENXIO;
return sg_alloc_table_from_pages(sgt, pages, count, 0, size,
GFP_KERNEL);
}Quoted 38 lines
arch/arm64/mm/dma-mapping.c +38
-0+static struct dma_map_ops iommu_dma_ops = { + .alloc = __iommu_alloc_attrs, + .free = __iommu_free_attrs, + .mmap = __iommu_mmap_attrs, + .map_page = __iommu_map_page, + .unmap_page = __iommu_unmap_page, + .map_sg = __iommu_map_sg_attrs, + .unmap_sg = __iommu_unmap_sg_attrs, + .sync_single_for_cpu = __iommu_sync_single_for_cpu, + .sync_single_for_device = __iommu_sync_single_for_device, + .sync_sg_for_cpu = __iommu_sync_sg_for_cpu, + .sync_sg_for_device = __iommu_sync_sg_for_device, + .dma_supported = iommu_dma_supported, + .mapping_error = iommu_dma_mapping_error, +}; + +static void __iommu_setup_dma_ops (struct device *dev,u64 dma_base,u64 size, +const struct iommu_ops *ops) +{ +struct iommu_dma_domain *dma_domain; + +if (!ops) +return ; + + dma_domain =iommu_dma_create_domain (ops, dma_base, size); +if (!dma_domain) { +pr_warn ("Failed to create %llu-byte IOMMU mapping for device %s\n" , + size,dev_name (dev)); +return ; + } + +if (iommu_dma_attach_device (dev, dma_domain)) +pr_warn ("Failed to attach device %s to IOMMU mapping\n" , +dev_name (dev)); +else + dev->archdata.dma_ops = &iommu_dma_ops; + +/* drop the initial mapping refcount */
arch/arm64/mm/dma-mapping.c static int __init dma_debug_do_init(void) · line 761
Quoted 10 lines
arch/arm64/mm/dma-mapping.c +10
-0+ iommu_dma_release_domain(dma_domain); +} + +#else + +static void __iommu_setup_dma_ops (struct device *dev,u64 dma_base,u64 size, +struct iommu_ops *iommu) +{ } + +#endif /* CONFIG_IOMMU_DMA */
Best regards
Signature · 2 lines
Marek Szyprowski, PhD Samsung R&D Institute Poland
Robin Murphy
maintainerRFC PATCH v2 3/3 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
@@ -66,6 +66,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 */
@@ -63,6 +60,10 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.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 *get_dma_domain (struct device *dev) {return dev->archdata.dma_domain;
arch/arm64/mm/dma-mapping.c +15 -0
@@ -752,6 +752,14 @@ static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,iommu_dma_release_domain (dma_domain); } +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,
@@ -759,3 +767,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
RFC PATCH v2 3/3 arm64: hook up IOMMU dma_ops
Quoted 15 lines · Robin Murphy
With
iommu_dma_opsin place, hook them up to the configuration code, so IOMMU-fronted devices will get them automatically.Signed-off-by Robin Murphy <[email protected]>--- 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 +0
-0
arch/arm64/Kconfig config ARM64 · line 69
Quoted 4 lines
arch/arm64/Kconfig +1
-0@@ -66,6 +66,7 @@ config ARM64 select HAVE_PERF_USER_STACK_DUMP select HAVE_RCU_TABLE_FREE select HAVE_SYSCALL_TRACEPOINTS + select IOMMU_DMA if IOMMU_SUPPORT
IOMMU_SUPPORT is selected above, so "if IOMMU_SUPPORT" can be omitted. And I suggest putting "select IOMMU_DMA" next to "select IOMMU_SUPPORT".
Quoted 63 lines
arch/arm64/Kconfig +0
-0select IRQ_DOMAIN select MODULES_USE_ELF_RELA select NO_BOOTMEMarch/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 */ @@ -63,6 +60,10 @@ static inline bool is_device_dma_coherent(struct device *dev)#include <asm-generic/dma-mapping-common.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 *get_dma_domain (struct device *dev) {return dev->archdata.dma_domain;arch/arm64/mm/dma-mapping.c +15
-0@@ -752,6 +752,14 @@ static void __iommu_setup_dma_ops(struct device *dev, u64 dma_base, u64 size,iommu_dma_release_domain (dma_domain); } +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,@@ -759,3 +767,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); +}
Robin Murphy
maintainerRFC PATCH v2 3/3 arm64: hook up IOMMU dma_ops
Quoted 11 lines · leizhen
Quoted 10 lines · Robin Murphy
With
iommu_dma_opsin place, hook them up to the configuration code, so IOMMU-fronted devices will get them automatically.Signed-off-by Robin Murphy <[email protected]>--- 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(-)
Quoted 12 lines
arch/arm64/Kconfig config ARM64 · line 69
Quoted 9 lines
arch/arm64/Kconfig +1
-0@@ -66,6 +66,7 @@ config ARM64 select HAVE_PERF_USER_STACK_DUMP select HAVE_RCU_TABLE_FREE select HAVE_SYSCALL_TRACEPOINTS + select IOMMU_DMA if IOMMU_SUPPORT
IOMMU_SUPPORTis selected above, so "ifIOMMU_SUPPORT" can be omitted. And I suggest putting "selectIOMMU_DMA" next to "selectIOMMU_SUPPORT".
Not in this patch set it isn't ;)
Omitting the "if IOMMU_SUPPORT" at this point results in this for me:
*** Default configuration is based on 'defconfig'
warning: (ARM64) selects IOMMU_DMA which has unmet direct dependencies (IOMMU_SUPPORT && NEED_SG_DMA_LENGTH)
Furthermore, if IOMMU_SUPPORT is selected anywhere I think it should be in defconfig, rather than here - if the user wants to leave out all the IOMMU drivers, then they should be able to turn off IOMMU_SUPPORT as well (since it makes no sense on its own), at which point the architecture then needs to make sure that IOMMU_DMA is deselected. Unless there's a more elegant way to enforce that, I think this "select...if..." needs to stay as it is.
Robin.