Optimizing iommu_[map/unmap] performance
Subsystems (2)
v2 cover Optimizing iommu_[map/unmap] performance
When unmapping a buffer from an IOMMU domain, the IOMMU framework unmaps the buffer at a granule of the largest page size that is supported by the IOMMU hardware and fits within the buffer. For every block that is unmapped, the IOMMU framework will call into the IOMMU driver, and then the io-pgtable framework to walk the page tables to find the entry that corresponds to the IOVA, and then unmaps the entry.
This can be suboptimal in scenarios where a buffer or a piece of a buffer can be split into several contiguous page blocks of the same size. For example, consider an IOMMU that supports 4 KB page blocks, 2 MB page blocks, and 1 GB page blocks, and a buffer that is 4 MB in size is being unmapped at IOVA 0. The current call-flow will result in 4 indirect calls, and 2 page table walks, to unmap 2 entries that are next to each other in the page-tables, when both entries could have been unmapped in one shot by clearing both page table entries in the same call.
The same optimization is applicable to mapping buffers as well, so these patches implement a set of callbacks called unmap_pages and map_pages to the io-pgtable code and IOMMU drivers which unmaps or maps an IOVA range that consists of a number of pages of the same page size that is supported by the IOMMU hardware, and allows for manipulating multiple page table entries in the same set of indirect calls. The reason for introducing these callbacks is to give other IOMMU drivers/io-pgtable formats time to change to using the new callbacks, so that the transition to using this approach can be done piecemeal.
Changes since V1:
* Implemented the map_pages() callbacks
* Integrated Will's patches into this series which address several concerns about how iommu_pgsize() partitioned a buffer (I made a minor change to the patch which changes iommu_pgsize() to use bitmaps by using the ULL variants of the bitops)
Any feedback is very much appreciated.
Isaac J. Manjarres (9):
iommu/io-pgtable: Introduce unmap_pages() as a page table op
iommu: Add an unmap_pages() op for IOMMU drivers iommu/io-pgtable: Introduce map_pages() as a page table op
iommu: Add a map_pages() op for IOMMU drivers
iommu: Add support for the map_pages() callback iommu/io-pgtable-arm: Implement arm_lpae_unmap_pages() iommu/io-pgtable-arm: Implement arm_lpae_map_pages() iommu/arm-smmu: Implement the unmap_pages() IOMMU driver callback iommu/arm-smmu: Implement the map_pages() IOMMU driver callback
Will Deacon (3):
iommu: Use bitmap to calculate page size in iommu_pgsize()
iommu: Split 'addr_merge' argument to iommu_pgsize() into separate
parts
iommu: Hook up '->unmap_pages' driver callback
drivers/iommu/arm/arm-smmu/arm-smmu.c | 38 +++++ drivers/iommu/io-pgtable-arm.c | 219 ++++++++++++++++++++++---- drivers/iommu/iommu.c | 128 +++++++++++---- include/linux/io-pgtable.h | 8 + include/linux/iommu.h | 9 ++ 5 files changed, 343 insertions(+), 59 deletions(-)
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 1/12 iommu/io-pgtable: Introduce unmap_pages() as a page table op
The io-pgtable code expects to operate on a single block or granule of memory that is supported by the IOMMU hardware when unmapping memory.
This means that when a large buffer that consists of multiple such blocks is unmapped, the io-pgtable code will walk the page tables to the correct level to unmap each block, even for blocks that are virtually contiguous and at the same level, which can incur an overhead in performance.
Introduce the unmap_pages() page table op to express to the io-pgtable code that it should unmap a number of blocks of the same size, instead of a single block. Doing so allows multiple blocks to be unmapped in one call to the io-pgtable code, reducing the number of page table walks, and indirect calls.
--- include/linux/io-pgtable.h | 4 ++++ 1 file changed, 4 insertions(+)
include/linux/io-pgtable.h +4 -0
@@ -144,6 +144,7 @@ struct io_pgtable_cfg {* * @map: Map a physically contiguous memory region. * @unmap: Unmap a physically contiguous memory region. +* @unmap_pages: Unmap a range of virtually contiguous pages of the same size. * @iova_to_phys: Translate iova to physical address. * * These functions map directly onto the iommu_ops member functions with
@@ -154,6 +155,9 @@ struct io_pgtable_ops { phys_addr_t paddr, size_t size, int prot, gfp_t gfp);size_t (*unmap)(struct io_pgtable_ops *ops,unsigned long iova,size_t size,struct iommu_iotlb_gather *gather); +size_t (*unmap_pages)(struct io_pgtable_ops *ops,unsigned long iova, +size_t pgsize,size_t pgcount, +struct iommu_iotlb_gather *gather);phys_addr_t (*iova_to_phys)(struct io_pgtable_ops *ops,unsigned long iova); };
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 2/12 iommu: Add an unmap_pages() op for IOMMU drivers
In reply to Isaac J. ManjarresAdd a callback for IOMMU drivers to provide a path for the IOMMU framework to call into an IOMMU driver, which can call into the io-pgtable code, to unmap a virtually contiguous range of pages of the same size.
For IOMMU drivers that do not specify an unmap_pages() callback, the existing logic of unmapping memory one page block at a time will be used.
--- include/linux/iommu.h | 4 ++++ 1 file changed, 4 insertions(+)
include/linux/iommu.h +4 -0
@@ -193,6 +193,7 @@ struct iommu_iotlb_gather {* @detach_dev: detach device from an iommu domain * @map: map a physically contiguous memory region to an iommu domain * @unmap: unmap a physically contiguous memory region from an iommu domain +* @unmap_pages: unmap a number of pages of the same size from an iommu domain * @flush_iotlb_all: Synchronously flush all hardware TLBs for this domain * @iotlb_sync_map: Sync mappings created recently using @map to the hardware * @iotlb_sync: Flush all queued ranges from the hardware TLBs and empty flush
@@ -245,6 +246,9 @@ struct iommu_ops { phys_addr_t paddr, size_t size, int prot, gfp_t gfp);size_t (*unmap)(struct iommu_domain *domain,unsigned long iova,size_t size,struct iommu_iotlb_gather *iotlb_gather); +size_t (*unmap_pages)(struct iommu_domain *domain,unsigned long iova, +size_t pgsize,size_t pgcount, +struct iommu_iotlb_gather *iotlb_gather);void (*flush_iotlb_all)(struct iommu_domain *domain);void (*iotlb_sync_map)(struct iommu_domain *domain,unsigned long iova,size_t size);
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 3/12 iommu/io-pgtable: Introduce map_pages() as a page table op
In reply to Isaac J. ManjarresMapping memory into io-pgtables follows the same semantics that unmapping memory used to follow (i.e. a buffer will be mapped one page block per call to the io-pgtable code). This means that it can be optimized in the same way that unmapping memory was, so add a map_pages() callback to the io-pgtable ops structure, so that a range of pages of the same size can be mapped within the same call.
--- include/linux/io-pgtable.h | 4 ++++ 1 file changed, 4 insertions(+)
include/linux/io-pgtable.h +4 -0
@@ -143,6 +143,7 @@ struct io_pgtable_cfg {* struct io_pgtable_ops - Page table manipulation API for IOMMU drivers. * * @map: Map a physically contiguous memory region. +* @map_pages: Map a physically contiguous range of pages of the same size. * @unmap: Unmap a physically contiguous memory region. * @unmap_pages: Unmap a range of virtually contiguous pages of the same size. * @iova_to_phys: Translate iova to physical address.
@@ -153,6 +154,9 @@ struct io_pgtable_cfg {struct io_pgtable_ops {int (*map)(struct io_pgtable_ops *ops,unsigned long iova,phys_addr_t paddr,size_t size,int prot,gfp_t gfp); +int (*map_pages)(struct io_pgtable_ops *ops,unsigned long iova, +phys_addr_t paddr,size_t pgsize,size_t pgcount, +int prot,gfp_t gfp,size_t *mapped);size_t (*unmap)(struct io_pgtable_ops *ops,unsigned long iova,size_t size,struct iommu_iotlb_gather *gather); size_t (*unmap_pages)(struct io_pgtable_ops *ops, unsigned long iova,
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 4/12 iommu: Add a map_pages() op for IOMMU drivers
In reply to Isaac J. ManjarresAdd a callback for IOMMU drivers to provide a path for the IOMMU framework to call into an IOMMU driver, which can call into the io-pgtable code, to map a physically contiguous rnage of pages of the same size.
For IOMMU drivers that do not specify a map_pages() callback, the existing logic of mapping memory one page block at a time will be used.
--- include/linux/iommu.h | 5 +++++ 1 file changed, 5 insertions(+)
include/linux/iommu.h +5 -0
@@ -192,6 +192,8 @@ struct iommu_iotlb_gather {* @attach_dev: attach device to an iommu domain * @detach_dev: detach device from an iommu domain * @map: map a physically contiguous memory region to an iommu domain +* @map_pages: map a physically contiguous set of pages of the same size to +* an iommu domain. * @unmap: unmap a physically contiguous memory region from an iommu domain * @unmap_pages: unmap a number of pages of the same size from an iommu domain * @flush_iotlb_all: Synchronously flush all hardware TLBs for this domain
@@ -244,6 +246,9 @@ struct iommu_ops {void (*detach_dev)(struct iommu_domain *domain,struct device *dev);int (*map)(struct iommu_domain *domain,unsigned long iova,phys_addr_t paddr,size_t size,int prot,gfp_t gfp); +int (*map_pages)(struct iommu_domain *domain,unsigned long iova, +phys_addr_t paddr,size_t pgsize,size_t pgcount, +int prot,gfp_t gfp,size_t *mapped);size_t (*unmap)(struct iommu_domain *domain,unsigned long iova,size_t size,struct iommu_iotlb_gather *iotlb_gather); size_t (*unmap_pages)(struct iommu_domain *domain, unsigned long iova,
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 5/12 iommu: Use bitmap to calculate page size in iommu_pgsize()
In reply to Isaac J. ManjarresFrom: Will Deacon <[email protected]>
Avoid the potential for shifting values by amounts greater than the width of their type by using a bitmap to compute page size in iommu_pgsize().
--- drivers/iommu/iommu.c | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-)
drivers/iommu/iommu.c +12 -19
@@ -8,6 +8,7 @@#include <linux/device.h> #include <linux/kernel.h> +#include <linux/bits.h> #include <linux/bug.h> #include <linux/types.h> #include <linux/init.h>
@@ -2360,30 +2361,22 @@ static size_t iommu_pgsize(struct iommu_domain *domain, unsigned long addr_merge, size_t size) {unsigned int pgsize_idx; +unsigned long pgsizes;size_t pgsize; -/* Max page size that still fits into 'size' */ - pgsize_idx =__fls (size); +/* Page sizes supported by the hardware and small enough for @size */ + pgsizes = domain->pgsize_bitmap &GENMASK_ULL (__fls (size),0 ); -/* need to consider alignment requirements ? */ -if (likely (addr_merge)) { -/* Max page size allowed by address */ -unsigned int align_pgsize_idx =__ffs (addr_merge); - pgsize_idx =min (pgsize_idx, align_pgsize_idx); - } - -/* build a mask of acceptable page sizes */ - pgsize = (1UL << (pgsize_idx +1 )) -1 ; - -/* throw away page sizes not supported by the hardware */ - pgsize &= domain->pgsize_bitmap; +/* Constrain the page sizes further based on the maximum alignment */ +if (likely (addr_merge)) + pgsizes &=GENMASK_ULL (__ffs (addr_merge),0 ); -/* make sure we're still sane */ -BUG_ON (!pgsize); +/* Make sure we have at least one suitable page size */ +BUG_ON (!pgsizes); -/* pick the biggest page */ - pgsize_idx =__fls (pgsize); - pgsize =1UL << pgsize_idx; +/* Pick the biggest page size remaining */ + pgsize_idx =__fls (pgsizes); + pgsize =BIT_ULL (pgsize_idx);return pgsize; }
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 6/12 iommu: Split 'addr_merge' argument to iommu_pgsize() into separate parts
In reply to Isaac J. ManjarresFrom: Will Deacon <[email protected]>
The 'addr_merge' parameter to iommu_pgsize() is a fabricated address intended to describe the alignment requirements to consider when choosing an appropriate page size. On the iommu_map() path, this address is the logical OR of the virtual and physical addresses.
Subsequent improvements to iommu_pgsize() will need to check the alignment of the virtual and physical components of 'addr_merge' independently, so pass them in as separate parameters and reconstruct 'addr_merge' locally.
No functional change.
--- drivers/iommu/iommu.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
drivers/iommu/iommu.c +6 -4
@@ -2357,12 +2357,13 @@ phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova) }EXPORT_SYMBOL_GPL (iommu_iova_to_phys); -static size_t iommu_pgsize (struct iommu_domain *domain, -unsigned long addr_merge,size_t size) +static size_t iommu_pgsize (struct iommu_domain *domain,unsigned long iova, +phys_addr_t paddr,size_t size) {unsigned int pgsize_idx;unsigned long pgsizes;size_t pgsize; +phys_addr_t addr_merge = paddr | iova;/* Page sizes supported by the hardware and small enough for @size */ pgsizes = domain->pgsize_bitmap &GENMASK_ULL (__fls (size),0 );
@@ -2415,7 +2416,7 @@ static int __iommu_map(struct iommu_domain *domain, unsigned long iova,pr_debug ("map: iova 0x%lx pa %pa size 0x%zx\n" , iova, &paddr, size);while (size) { -size_t pgsize =iommu_pgsize (domain, iova | paddr, size); +size_t pgsize =iommu_pgsize (domain, iova, paddr, size);pr_debug ("mapping: iova 0x%lx pa %pa pgsize 0x%zx\n" , iova, &paddr, pgsize);
@@ -2503,8 +2504,9 @@ static size_t __iommu_unmap(struct iommu_domain *domain,* or we hit an area that isn't mapped. */ while (unmapped < size) { -size_t pgsize =iommu_pgsize (domain, iova, size - unmapped); +size_t pgsize; + pgsize =iommu_pgsize (domain, iova, iova, size - unmapped); unmapped_page = ops->unmap (domain, iova, pgsize, iotlb_gather);if (!unmapped_page)break ;
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 7/12 iommu: Hook up '->unmap_pages' driver callback
In reply to Isaac J. ManjarresFrom: Will Deacon <[email protected]>
Extend iommu_pgsize() to populate an optional 'count' paramater so that we can direct unmapping operation to the ->unmap_pages callback if it has been provided by the driver.
--- drivers/iommu/iommu.c | 60 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 9 deletions(-)
drivers/iommu/iommu.c +51 -9
@@ -2358,11 +2358,11 @@ phys_addr_t iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova)EXPORT_SYMBOL_GPL (iommu_iova_to_phys);static size_t iommu_pgsize (struct iommu_domain *domain,unsigned long iova, -phys_addr_t paddr,size_t size) +phys_addr_t paddr,size_t size,size_t *count) { -unsigned int pgsize_idx; +unsigned int pgsize_idx, pgsize_idx_next;unsigned long pgsizes; -size_t pgsize; +size_t offset, pgsize, pgsize_next;phys_addr_t addr_merge = paddr | iova;/* Page sizes supported by the hardware and small enough for @size */
@@ -2378,7 +2378,37 @@ static size_t iommu_pgsize(struct iommu_domain *domain, unsigned long iova,/* Pick the biggest page size remaining */ pgsize_idx =__fls (pgsizes); pgsize =BIT_ULL (pgsize_idx); +if (!count) +return pgsize; + +/* Find the next biggest support page size, if it exists */ + pgsizes = domain->pgsize_bitmap & ~GENMASK (pgsize_idx,0 ); +if (!pgsizes) + goto out_set_count; + + pgsize_idx_next =__ffs (pgsizes); + pgsize_next =BIT (pgsize_idx_next); + +/* +* There's no point trying a bigger page size unless the virtual +* and physical addresses are similarly offset within the larger page. +*/ +if ((iova ^ paddr) & (pgsize_next -1 )) + goto out_set_count; + +/* Calculate the offset to the next page size alignment boundary */ + offset = pgsize_next - (addr_merge & (pgsize_next -1 )); + +/* +* If size is big enough to accomodate the larger page, reduce +* the number of smaller pages. +*/ +if (offset + pgsize_next <= size) + size = offset; + +out_set_count: + *count = size >> pgsize_idx;return pgsize; }
@@ -2416,7 +2446,7 @@ static int __iommu_map(struct iommu_domain *domain, unsigned long iova,pr_debug ("map: iova 0x%lx pa %pa size 0x%zx\n" , iova, &paddr, size);while (size) { -size_t pgsize =iommu_pgsize (domain, iova, paddr, size); +size_t pgsize =iommu_pgsize (domain, iova, paddr, size,NULL );pr_debug ("mapping: iova 0x%lx pa %pa pgsize 0x%zx\n" , iova, &paddr, pgsize);
@@ -2467,6 +2497,19 @@ int iommu_map_atomic(struct iommu_domain *domain, unsigned long iova, }EXPORT_SYMBOL_GPL (iommu_map_atomic); +static size_t __iommu_unmap_pages (struct iommu_domain *domain, +unsigned long iova,size_t size, +struct iommu_iotlb_gather *iotlb_gather) +{ +const struct iommu_ops *ops = domain->ops; +size_t pgsize, count; + + pgsize =iommu_pgsize (domain, iova, iova, size, &count); +return ops->unmap_pages ? + ops->unmap_pages (domain, iova, pgsize, count, iotlb_gather) : + ops->unmap (domain, iova, pgsize, iotlb_gather); +} +static size_t __iommu_unmap (struct iommu_domain *domain,unsigned long iova,size_t size,struct iommu_iotlb_gather *iotlb_gather)
@@ -2476,7 +2519,7 @@ static size_t __iommu_unmap(struct iommu_domain *domain,unsigned long orig_iova = iova;unsigned int min_pagesz; -if (unlikely (ops->unmap ==NULL || +if (unlikely ((ops->unmap ==NULL && ops->unmap_pages ==NULL ) || domain->pgsize_bitmap ==0UL ))return 0 ;
@@ -2504,10 +2547,9 @@ static size_t __iommu_unmap(struct iommu_domain *domain,* or we hit an area that isn't mapped. */ while (unmapped < size) { -size_t pgsize; - - pgsize =iommu_pgsize (domain, iova, iova, size - unmapped); - unmapped_page = ops->unmap (domain, iova, pgsize, iotlb_gather); + unmapped_page =__iommu_unmap_pages (domain, iova, + size - unmapped, + iotlb_gather);if (!unmapped_page)break ;
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
Re: [PATCH v2 07/12] iommu: Hook up '->unmap_pages' driver callback
Hi,
drivers/iommu/iommu.c static size_t __iommu_unmap(struct iommu_domain *domain, · line 2523
Quoted 10 lines · Isaac J. Manjarres
static
size_t__iommu_unmap(structiommu_domain*domain,
unsigned long iova,size_tsize, structiommu_iotlb_gather*iotlb_gather)drivers/iommu/iommu.c +1
-1@@ -2476,7 +2519,7 @@ static size_t __iommu_unmap(struct iommu_domain *domain,unsigned long orig_iova = iova;unsigned int min_pagesz; -if (unlikely (ops->unmap ==NULL || +if (unlikely ((ops->unmap ==NULL && ops->unmap_pages ==NULL ) || domain->pgsize_bitmap ==0UL ))
This change should also be applied to __iommu_map() path. And perhaps could be:
if (unlikely(!(ops->unmap || ops->unmap_pages) || !domain->pgsize_bitmap))
Best regards,
baolu
_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu
Re: [PATCH v2 07/12] iommu: Hook up '->unmap_pages' driver callback
Quoted 17 lines · Lu Baolu
Hi,
drivers/iommu/iommu.c static size_t __iommu_unmap(struct iommu_domain · line 2524
Quoted 10 lines · Isaac J. Manjarres
static
size_t__iommu_unmap(structiommu_domain*domain,
unsigned long iova,size_tsize, structiommu_iotlb_gather*iotlb_gather)drivers/iommu/iommu.c +1
-0@@ -2476,7 +2519,7 @@ static size_t __iommu_unmap(struct iommu_domain *domain, unsigned long orig_iova = iova;unsigned int min_pagesz; - if (unlikely(ops->unmap == NULL || + if (unlikely((ops->unmap == NULL && ops->unmap_pages == NULL) || domain->pgsize_bitmap == 0UL))This change should also be applied to
__iommu_map()path. And perhaps could be:
Quoted 3 lines
if (unlikely(!(ops->unmap || ops->unmap_pages) ||!domain->pgsize_bitmap))
Yep, that's correct. Thank you for spotting that; I've updated it in the latest series: https://lore.kernel.org/linux-iommu/[email protected]/T/#t.
Thanks,
Isaac
Quoted 7 lines
Best regards,
baolu_______________________________________________
linux-arm-kernel mailing list
[email protected]
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
_______________________________________________
iommu mailing list
[email protected]
https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 8/12 iommu: Add support for the map_pages() callback
In reply to Isaac J. ManjarresSince iommu_pgsize can calculate how many pages of the same size can be mapped/unmapped before the next largest page size boundary, add support for invoking an IOMMU driver's map_pages() callback, if it provides one.
--- drivers/iommu/iommu.c | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-)
drivers/iommu/iommu.c +34 -7
@@ -2412,6 +2412,30 @@ static size_t iommu_pgsize(struct iommu_domain *domain, unsigned long iova,return pgsize; } +static int __iommu_map_pages (struct iommu_domain *domain,unsigned long iova, +phys_addr_t paddr,size_t size,int prot, +gfp_t gfp,size_t *mapped) +{ +const struct iommu_ops *ops = domain->ops; +size_t pgsize, count; +int ret; + + pgsize =iommu_pgsize (domain, iova, paddr, size, &count); + +pr_debug ("mapping: iova 0x%lx pa %pa pgsize 0x%zx count %ld\n" , + iova, &paddr, pgsize, count); + +if (ops->map_pages) { + ret = ops->map_pages (domain, iova, paddr, pgsize, count, prot, + gfp, mapped); + }else { + ret = ops->map (domain, iova, paddr, pgsize, prot, gfp); + *mapped = ret ?0 : pgsize; + } + +return ret; +} +static int __iommu_map (struct iommu_domain *domain,unsigned long iova,phys_addr_t paddr,size_t size,int prot,gfp_t gfp) {
@@ -2446,18 +2470,21 @@ static int __iommu_map(struct iommu_domain *domain, unsigned long iova,pr_debug ("map: iova 0x%lx pa %pa size 0x%zx\n" , iova, &paddr, size);while (size) { -size_t pgsize =iommu_pgsize (domain, iova, paddr, size,NULL ); +size_t mapped =0 ; -pr_debug ("mapping: iova 0x%lx pa %pa pgsize 0x%zx\n" , - iova, &paddr, pgsize); - ret = ops->map (domain, iova, paddr, pgsize, prot, gfp); + ret =__iommu_map_pages (domain, iova, paddr, size, prot, gfp, + &mapped); +/* +* Some pages may have been mapped, even if an error occurred, +* so we should account for those so they can be unmapped. +*/ + size -= mapped;if (ret)break ; - iova += pgsize; - paddr += pgsize; - size -= pgsize; + iova += mapped; + paddr += mapped; }/* unroll mapping in case something went wrong */
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 9/12 iommu/io-pgtable-arm: Implement arm_lpae_unmap_pages()
In reply to Isaac J. ManjarresImplement the unmap_pages() callback for the ARM LPAE io-pgtable format.
--- drivers/iommu/io-pgtable-arm.c | 124 +++++++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 20 deletions(-)
drivers/iommu/io-pgtable-arm.c +104 -20
@@ -60,6 +60,14 @@/* Calculate the block/page mapping size at level l for pagetable in d. */ #define ARM_LPAE_BLOCK_SIZE (l,d) (1ULL << ARM_LPAE_LVL_SHIFT(l,d)) +/* +* Calculate the level that corresponds to the block/page mapping for pagetable +* in d. +*/ +#define ARM_LPAE_BLOCK_SIZE_LVL (s, d) \ + ((ARM_LPAE_MAX_LEVELS - \ + ((ilog2((s)) - ilog2(sizeof(arm_lpae_iopte))) / (d)->bits_per_level))) +/* Page table bits */ #define ARM_LPAE_PTE_TYPE_SHIFT 0#define ARM_LPAE_PTE_TYPE_MASK 0x3
@@ -248,10 +256,26 @@ static void __arm_lpae_set_pte(arm_lpae_iopte *ptep, arm_lpae_iopte pte,__arm_lpae_sync_pte (ptep, cfg); } +static void __arm_lpae_sync_ptes (arm_lpae_iopte *ptep,size_t num_ptes, +struct io_pgtable_cfg *cfg) +{ +dma_sync_single_for_device (cfg->iommu_dev,__arm_lpae_dma_addr (ptep), +sizeof (*ptep) * num_ptes,DMA_TO_DEVICE ); +} + +static void __arm_lpae_clear_ptes (arm_lpae_iopte *ptep,size_t num_ptes, +struct io_pgtable_cfg *cfg) +{ +memset (ptep,0 ,sizeof (*ptep) * num_ptes); + +if (!cfg->coherent_walk) +__arm_lpae_sync_ptes (ptep, num_ptes, cfg); +} +static size_t __arm_lpae_unmap (struct arm_lpae_io_pgtable *data,struct iommu_iotlb_gather *gather, -unsigned long iova,size_t size,int lvl, -arm_lpae_iopte *ptep); +unsigned long iova,size_t size,size_t pgcount, +int lvl,arm_lpae_iopte *ptep); static void __arm_lpae_init_pte(struct arm_lpae_io_pgtable *data, phys_addr_t paddr, arm_lpae_iopte prot,
@@ -289,7 +313,7 @@ static int arm_lpae_init_pte(struct arm_lpae_io_pgtable *data,size_t sz =ARM_LPAE_BLOCK_SIZE (lvl, data); tblp = ptep -ARM_LPAE_LVL_IDX (iova, lvl, data); -if (__arm_lpae_unmap (data,NULL , iova, sz, lvl, tblp) != sz) { +if (__arm_lpae_unmap (data,NULL , iova, sz,1 , lvl, tblp) != sz) {WARN_ON (1 );return -EINVAL ; }
@@ -516,14 +540,14 @@ static size_t arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data,struct iommu_iotlb_gather *gather, unsigned long iova, size_t size, arm_lpae_iopte blk_pte, int lvl, - arm_lpae_iopte *ptep) + arm_lpae_iopte *ptep, size_t pgcount) { struct io_pgtable_cfg *cfg = &data->iop.cfg;arm_lpae_iopte pte, *tablep;phys_addr_t blk_paddr;size_t tablesz =ARM_LPAE_GRANULE (data);size_t split_sz =ARM_LPAE_BLOCK_SIZE (lvl, data); -int i, unmap_idx =-1 ; +int i, unmap_idx_start =-1 ;if (WARN_ON (lvl ==ARM_LPAE_MAX_LEVELS ))return 0 ;
@@ -533,14 +557,14 @@ static size_t arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data,return 0 ;/* Bytes unmapped */ if (size == split_sz) - unmap_idx =ARM_LPAE_LVL_IDX (iova, lvl, data); + unmap_idx_start =ARM_LPAE_LVL_IDX (iova, lvl, data); blk_paddr =iopte_to_paddr (blk_pte, data); pte =iopte_prot (blk_pte);for (i =0 ; i < tablesz /sizeof (pte); i++, blk_paddr += split_sz) {/* Unmap! */ -if (i == unmap_idx) +if (i >= unmap_idx_start && i < (unmap_idx_start + pgcount))continue ;__arm_lpae_init_pte (data, blk_paddr, pte, lvl, &tablep[i]);
@@ -558,20 +582,24 @@ static size_t arm_lpae_split_blk_unmap(struct arm_lpae_io_pgtable *data,return 0 ; tablep =iopte_deref (pte, data); - }else if (unmap_idx >=0 ) { -io_pgtable_tlb_add_page (&data->iop, gather, iova, size); -return size; + }else if (unmap_idx_start >=0 ) { +for (i =0 ; i < pgcount; i++) { +io_pgtable_tlb_add_page (&data->iop, gather, iova, size); + iova += size; + } +return pgcount * size; } -return __arm_lpae_unmap (data, gather, iova, size, lvl, tablep); +return __arm_lpae_unmap (data, gather, iova, size, pgcount, lvl, tablep); }static size_t __arm_lpae_unmap (struct arm_lpae_io_pgtable *data,struct iommu_iotlb_gather *gather, -unsigned long iova,size_t size,int lvl, -arm_lpae_iopte *ptep) +unsigned long iova,size_t size,size_t pgcount, +int lvl,arm_lpae_iopte *ptep) {arm_lpae_iopte pte; +size_t i;struct io_pgtable *iop = &data->iop;/* Something went horribly wrong and we ran out of page table */
@@ -585,11 +613,11 @@ static size_t __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,/* If the size matches this level, we're in the right place */ if (size ==ARM_LPAE_BLOCK_SIZE (lvl, data)) { -__arm_lpae_set_pte (ptep,0 , &iop->cfg); +__arm_lpae_clear_ptes (ptep, pgcount, &iop->cfg);if (!iopte_leaf (pte, lvl, iop->fmt)) {/* Also flush any partial walks */ -io_pgtable_tlb_flush_walk (iop, iova, size, +io_pgtable_tlb_flush_walk (iop, iova, pgcount * size,ARM_LPAE_GRANULE (data)); ptep =iopte_deref (pte, data);__arm_lpae_free_pgtable (data, lvl +1 , ptep);
@@ -601,22 +629,25 @@ static size_t __arm_lpae_unmap(struct arm_lpae_io_pgtable *data,*/ smp_wmb (); }else { -io_pgtable_tlb_add_page (iop, gather, iova, size); +for (i =0 ; i < pgcount; i++) { +io_pgtable_tlb_add_page (iop, gather, iova, size); + iova += size; + } } -return size; +return pgcount * size; }else if (iopte_leaf (pte, lvl, iop->fmt)) {/* * Insert a table at the next level to map the old region, * minus the part we want to unmap */ return arm_lpae_split_blk_unmap (data, gather, iova, size, pte, - lvl +1 , ptep); + lvl +1 , ptep, pgcount); }/* Keep on walkin' */ ptep =iopte_deref (pte, data); -return __arm_lpae_unmap (data, gather, iova, size, lvl +1 , ptep); +return __arm_lpae_unmap (data, gather, iova, size, pgcount, lvl +1 , ptep); } static size_t arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova,
@@ -635,7 +666,59 @@ static size_t arm_lpae_unmap(struct io_pgtable_ops *ops, unsigned long iova,if (WARN_ON (iaext))return 0 ; -return __arm_lpae_unmap (data, gather, iova, size, data->start_level, ptep); +return __arm_lpae_unmap (data, gather, iova, size,1 , data->start_level, ptep); +} + +static size_t arm_lpae_unmap_pages (struct io_pgtable_ops *ops,unsigned long iova, +size_t pgsize,size_t pgcount, +struct iommu_iotlb_gather *gather) +{ +struct arm_lpae_io_pgtable *data =io_pgtable_ops_to_data (ops); +struct io_pgtable_cfg *cfg = &data->iop.cfg; +arm_lpae_iopte *ptep = data->pgd; +long iaext = (s64 )iova >> cfg->ias; +size_t unmapped =0 , unmapped_page; +int last_lvl; +size_t table_size, pages, tbl_offset, max_entries; + +if (WARN_ON (!pgsize || (pgsize & cfg->pgsize_bitmap) != pgsize || !pgcount)) +return 0 ; + +if (cfg->quirks &IO_PGTABLE_QUIRK_ARM_TTBR1 ) + iaext = ~iaext; +if (WARN_ON (iaext)) +return 0 ; + +/* +* Calculating the page table size here helps avoid situations where +* a page range that is being unmapped may be mapped at the same level +* but not mapped by the same tables. Allowing such a scenario to +* occur can complicate the logic in arm_lpae_split_blk_unmap(). +*/ + last_lvl =ARM_LPAE_BLOCK_SIZE_LVL (pgsize, data); + +if (last_lvl == data->start_level) + table_size =ARM_LPAE_PGD_SIZE (data); +else + table_size =ARM_LPAE_GRANULE (data); + + max_entries = table_size /sizeof (*ptep); + +while (pgcount) { + tbl_offset =ARM_LPAE_LVL_IDX (iova, last_lvl, data); + pages =min_t (size_t, pgcount, max_entries - tbl_offset); + unmapped_page =__arm_lpae_unmap (data, gather, iova, pgsize, + pages, data->start_level, + ptep); +if (!unmapped_page) +break ; + + unmapped += unmapped_page; + iova += unmapped_page; + pgcount -= pages; + } + +return unmapped; } static phys_addr_t arm_lpae_iova_to_phys(struct io_pgtable_ops *ops,
@@ -751,6 +834,7 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg) data->iop.ops = (struct io_pgtable_ops ) { .map = arm_lpae_map, .unmap = arm_lpae_unmap, + .unmap_pages = arm_lpae_unmap_pages, .iova_to_phys = arm_lpae_iova_to_phys, };
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 10/12 iommu/io-pgtable-arm: Implement arm_lpae_map_pages()
In reply to Isaac J. ManjarresImplement the map_pages() callback for the ARM LPAE io-pgtable format.
--- drivers/iommu/io-pgtable-arm.c | 95 +++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 7 deletions(-)
drivers/iommu/io-pgtable-arm.c +88 -7
@@ -355,20 +355,35 @@ static arm_lpae_iopte arm_lpae_install_table(arm_lpae_iopte *table, }static int __arm_lpae_map (struct arm_lpae_io_pgtable *data,unsigned long iova, -phys_addr_t paddr,size_t size,arm_lpae_iopte prot, -int lvl,arm_lpae_iopte *ptep,gfp_t gfp) +phys_addr_t paddr,size_t size,size_t pgcount, +arm_lpae_iopte prot,int lvl,arm_lpae_iopte *ptep, +gfp_t gfp,size_t *mapped) {arm_lpae_iopte *cptep, pte;size_t block_size =ARM_LPAE_BLOCK_SIZE (lvl, data);size_t tblsz =ARM_LPAE_GRANULE (data);struct io_pgtable_cfg *cfg = &data->iop.cfg; +int ret =0 ;/* Find our entry at the current level */ ptep +=ARM_LPAE_LVL_IDX (iova, lvl, data); -/* If we can install a leaf entry at this level, then do so */ -if (size == block_size) -return arm_lpae_init_pte (data, iova, paddr, prot, lvl, ptep); +/* If we can install leaf entries at this level, then do so */ +if (size == block_size) { +while (pgcount--) { + ret =arm_lpae_init_pte (data, iova, paddr, prot, lvl, ptep); +if (ret) +return ret; + + iova += size; + paddr += size; + ptep++; +if (mapped) + *mapped += size; + } + +return ret; + }/* We can't allocate tables at the final level */ if (WARN_ON (lvl >=ARM_LPAE_MAX_LEVELS -1 ))
@@ -397,7 +412,8 @@ static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, }/* Rinse, repeat */ -return __arm_lpae_map (data, iova, paddr, size, prot, lvl +1 , cptep, gfp); +return __arm_lpae_map (data, iova, paddr, size, pgcount, prot, lvl +1 , cptep, + gfp, mapped); } static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data,
@@ -487,7 +503,71 @@ static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova,return 0 ; prot =arm_lpae_prot_to_pte (data, iommu_prot); - ret =__arm_lpae_map (data, iova, paddr, size, prot, lvl, ptep, gfp); + ret =__arm_lpae_map (data, iova, paddr, size,1 , prot, lvl, ptep, gfp, +NULL ); +/* +* Synchronise all PTE updates for the new mapping before there's +* a chance for anything to kick off a table walk for the new iova. +*/ +wmb (); + +return ret; +} + +static int arm_lpae_map_pages (struct io_pgtable_ops *ops,unsigned long iova, +phys_addr_t paddr,size_t pgsize,size_t pgcount, +int iommu_prot,gfp_t gfp,size_t *mapped) +{ +struct arm_lpae_io_pgtable *data =io_pgtable_ops_to_data (ops); +struct io_pgtable_cfg *cfg = &data->iop.cfg; +arm_lpae_iopte *ptep = data->pgd; +int ret, lvl = data->start_level, last_lvl; +arm_lpae_iopte prot; +long iaext = (s64 )iova >> cfg->ias; +size_t table_size, pages, tbl_offset, max_entries; + +/* If no access, then nothing to do */ +if (!(iommu_prot & (IOMMU_READ |IOMMU_WRITE ))) +return 0 ; + +if (WARN_ON (!pgsize || (pgsize & cfg->pgsize_bitmap) != pgsize)) +return -EINVAL ; + +if (cfg->quirks &IO_PGTABLE_QUIRK_ARM_TTBR1 ) + iaext = ~iaext; +if (WARN_ON (iaext || paddr >> cfg->oas)) +return -ERANGE ; + + prot =arm_lpae_prot_to_pte (data, iommu_prot); + +/* +* Calculating the page table size here helps avoid situations where +* a page range that is being mapped may be mapped at the same level +* but not mapped by the same tables. Allowing such a scenario to +* occur can complicate the logic in __arm_lpae_map(). +*/ + last_lvl =ARM_LPAE_BLOCK_SIZE_LVL (pgsize, data); + +if (last_lvl == data->start_level) + table_size =ARM_LPAE_PGD_SIZE (data); +else + table_size =ARM_LPAE_GRANULE (data); + + max_entries = table_size /sizeof (*ptep); + +while (pgcount) { + tbl_offset =ARM_LPAE_LVL_IDX (iova, last_lvl, data); + pages =min_t (size_t, pgcount, max_entries - tbl_offset); + ret =__arm_lpae_map (data, iova, paddr, pgsize, pages, prot, + lvl, ptep, gfp, mapped); +if (ret) +break ; + + iova += pages * pgsize; + paddr += pages * pgsize; + pgcount -= pages; + } +/* * Synchronise all PTE updates for the new mapping before there's * a chance for anything to kick off a table walk for the new iova.
@@ -833,6 +913,7 @@ arm_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg) data->iop.ops = (struct io_pgtable_ops ) { .map = arm_lpae_map, + .map_pages = arm_lpae_map_pages, .unmap = arm_lpae_unmap, .unmap_pages = arm_lpae_unmap_pages, .iova_to_phys = arm_lpae_iova_to_phys,
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 11/12 iommu/arm-smmu: Implement the unmap_pages() IOMMU driver callback
In reply to Isaac J. ManjarresImplement the unmap_pages() callback for the ARM SMMU driver to allow calls from iommu_unmap to unmap multiple pages of the same size in one call.
--- drivers/iommu/arm/arm-smmu/arm-smmu.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
drivers/iommu/arm/arm-smmu/arm-smmu.c +19 -0
@@ -1225,6 +1225,24 @@ static size_t arm_smmu_unmap(struct iommu_domain *domain, unsigned long iova,return ret; } +static size_t arm_smmu_unmap_pages (struct iommu_domain *domain,unsigned long iova, +size_t pgsize,size_t pgcount, +struct iommu_iotlb_gather *iotlb_gather) +{ +struct io_pgtable_ops *ops =to_smmu_domain (domain)->pgtbl_ops; +struct arm_smmu_device *smmu =to_smmu_domain (domain)->smmu; +size_t ret; + +if (!ops) +return 0 ; + +arm_smmu_rpm_get (smmu); + ret = ops->unmap_pages (ops, iova, pgsize, pgcount, iotlb_gather); +arm_smmu_rpm_put (smmu); + +return ret; +} +static void arm_smmu_flush_iotlb_all (struct iommu_domain *domain) {struct arm_smmu_domain *smmu_domain =to_smmu_domain (domain);
@@ -1625,6 +1643,7 @@ static struct iommu_ops arm_smmu_ops = { .attach_dev = arm_smmu_attach_dev, .map = arm_smmu_map, .unmap = arm_smmu_unmap, + .unmap_pages = arm_smmu_unmap_pages, .flush_iotlb_all = arm_smmu_flush_iotlb_all, .iotlb_sync = arm_smmu_iotlb_sync, .iova_to_phys = arm_smmu_iova_to_phys,
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu
PATCH v2 12/12 iommu/arm-smmu: Implement the map_pages() IOMMU driver callback
In reply to Isaac J. ManjarresImplement the unmap_pages() callback for the ARM SMMU driver to allow calls from iommu_unmap to unmap multiple pages of the same size in one call.
--- drivers/iommu/arm/arm-smmu/arm-smmu.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+)
drivers/iommu/arm/arm-smmu/arm-smmu.c +19 -0
@@ -1208,6 +1208,24 @@ static int arm_smmu_map(struct iommu_domain *domain, unsigned long iova,return ret; } +static int arm_smmu_map_pages (struct iommu_domain *domain,unsigned long iova, +phys_addr_t paddr,size_t pgsize,size_t pgcount, +int prot,gfp_t gfp,size_t *mapped) +{ +struct io_pgtable_ops *ops =to_smmu_domain (domain)->pgtbl_ops; +struct arm_smmu_device *smmu =to_smmu_domain (domain)->smmu; +int ret; + +if (!ops) +return -ENODEV ; + +arm_smmu_rpm_get (smmu); + ret = ops->map_pages (ops, iova, paddr, pgsize, pgcount, prot, gfp, mapped); +arm_smmu_rpm_put (smmu); + +return ret; +} +static size_t arm_smmu_unmap (struct iommu_domain *domain,unsigned long iova,size_t size,struct iommu_iotlb_gather *gather) {
@@ -1642,6 +1660,7 @@ static struct iommu_ops arm_smmu_ops = { .domain_free = arm_smmu_domain_free, .attach_dev = arm_smmu_attach_dev, .map = arm_smmu_map, + .map_pages = arm_smmu_map_pages, .unmap = arm_smmu_unmap, .unmap_pages = arm_smmu_unmap_pages, .flush_iotlb_all = arm_smmu_flush_iotlb_all,
Signature · 7 lines
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project _______________________________________________ iommu mailing list [email protected] https://lists.linuxfoundation.org/mailman/listinfo/iommu