Generic IOMMU pooled allocator
Subsystems (2)
RFC PATCH v9 1/3 Break up monolithic iommu table/lock into finer graularity pools and lock
In reply to Sowmini VaradhanInvestigation of multithreaded iperf experiments on an ethernet interface show the iommu->lock as the hottest lock identified by lockstat, with something of the order of 21M contentions out of 27M acquisitions, and an average wait time of 26 us for the lock. This is not efficient. A more scalable design is to follow the ppc model, where the iommu_map_table has multiple pools, each stretching over a segment of the map, and with a separate lock for each pool. This model allows for better parallelization of the iommu map search.
This patch adds the iommu range alloc/free function infrastructure.
---
v2 changes:
- incorporate David Miller editorial comments: sparc specific fields moved from iommu-common into sparc's iommu_64.h
- make the npools value an input parameter, for the case when the iommu map size is not very large
- cookie_to_index mapping, and optimizations for span-boundary check, for use case such as LDC.
v3: eliminate iommu_sparc, rearrange the ->demap indirection to
be invoked under the pool lock.
v4: David Miller review changes:
- s/IOMMU_ERROR_CODE/DMA_ERROR_CODE
- page_table_map_base and page_table_shift are unsigned long, not u32.
v5: Feedback from [email protected] and [email protected]
- removed ->cookie_to_index and ->demap indirection: caller should invoke these as needed before calling into the generic allocator
v6: Benh/DaveM discussion eliminationg iommu_tbl_ops, but retaining flush_all
optimization.
v7: one-time initialization of pool_hash from iommu_tbl_pool_init()
v8: Benh code review comments.
v9: More Benh code review comments, added dma_mask, align_order logic to
iommu_tbl_range_alloc.
include/linux/iommu-common.h | 51 ++++++++ lib/Makefile | 2 +- lib/iommu-common.c | 266 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 318 insertions(+), 1 deletions(-) create mode 100644 include/linux/iommu-common.h create mode 100644 lib/iommu-common.c
include/linux/iommu-common.h new file +51 -0
@@ -0,0 +1,51 @@ +#ifndef _LINUX_IOMMU_COMMON_H +#define _LINUX_IOMMU_COMMON_H + +#include <linux/spinlock_types.h> +#include <linux/device.h> +#include <asm/page.h> + +#define IOMMU_POOL_HASHBITS 4 +#define IOMMU_NR_POOLS (1 << IOMMU_POOL_HASHBITS) + +struct iommu_pool { +unsigned long start; +unsigned long end; +unsigned long hint; +spinlock_t lock; +}; + +struct iommu_map_table { +unsigned long table_map_base; +unsigned long table_shift; +unsigned long nr_pools; +void (*lazy_flush)(struct iommu_map_table *); +unsigned long poolsize; +struct iommu_pool pools[IOMMU_NR_POOLS ]; +u32 flags; +#define IOMMU_HAS_LARGE_POOL 0x00000001 +#define IOMMU_NO_SPAN_BOUND 0x00000002 +#define IOMMU_NEED_FLUSH 0x00000004 +struct iommu_pool large_pool; +unsigned long *map; +}; + +extern void iommu_tbl_pool_init (struct iommu_map_table *iommu, +unsigned long num_entries, +u32 table_shift, +void (*lazy_flush)(struct iommu_map_table *), +bool large_pool,u32 npools, +bool skip_span_boundary_check); + +extern unsigned long iommu_tbl_range_alloc (struct device *dev, +struct iommu_map_table *iommu, +unsigned long npages, +unsigned long *handle, +unsigned long mask, +unsigned int align_order); + +extern void iommu_tbl_range_free (struct iommu_map_table *iommu, +u64 dma_addr,unsigned long npages, +unsigned long entry); + +#endif
lib/Makefile +1 -1
@@ -106,7 +106,7 @@ obj-$(CONFIG_AUDIT_GENERIC) += audit.o obj-$(CONFIG_AUDIT_COMPAT_GENERIC) += compat_audit.o obj-$(CONFIG_SWIOTLB) += swiotlb.o -obj-$(CONFIG_IOMMU_HELPER) += iommu-helper.o +obj-$(CONFIG_IOMMU_HELPER) += iommu-helper.o iommu-common.o obj-$(CONFIG_FAULT_INJECTION) += fault-inject.o obj-$(CONFIG_NOTIFIER_ERROR_INJECTION) += notifier-error-inject.o obj-$(CONFIG_CPU_NOTIFIER_ERROR_INJECT) += cpu-notifier-error-inject.o
lib/iommu-common.c new file +266 -0
266 lines not on this page · raw message
Signature · 1 line
1.7.1
RFC PATCH v9 2/3 sparc: Make sparc64 use scalable lib/iommu-common.c functions
In reply to Sowmini VaradhanIn iperf experiments running linux as the Tx side (TCP client) with 10 threads results in a severe performance drop when TSO is disabled, indicating a weakness in the software that can be avoided by using the scalable IOMMU arena DMA allocation.
Baseline numbers before this patch:
with default settings (TSO enabled) : 9-9.5 Gbps
Disable TSO using ethtool- drops badly: 2-3 Gbps.
After this patch, iperf client with 10 threads, can give a throughput of at least 8.5 Gbps, even when TSO is disabled.
---
v2: moved sparc specific fileds into iommu_sparc
v3: converted all sparc users of iommu, so lot of cleanup and streamlining
v4: David Miller review change:
- s/IOMMU_ERROR_CODE/DMA_ERROR_CODE
- reverts pci_impl.h (now that all iommu usage has been converted)
v5: benh/aik feedback modifies the function signatures: pass in
modified args to iommmu_tbl_pool_init() and iommu_tbl_range_free()
v6: removed iommu_tbl_ops. Pass flush_all as function pointer to
iommu_tbl_pool_init
v7: move pool_hash initialization to iommu_tbl_pool_init()
v8: BenH comments
v9: more BenH comments
arch/sparc/include/asm/iommu_64.h | 7 +- arch/sparc/kernel/iommu.c | 172 +++++++++-------------------------- arch/sparc/kernel/iommu_common.h | 8 -- arch/sparc/kernel/pci_sun4v.c | 183 +++++++++++++++++-------------------- 4 files changed, 128 insertions(+), 242 deletions(-)
arch/sparc/include/asm/iommu_64.h +3 -4
@@ -16,6 +16,7 @@#define IOPTE_WRITE 0x0000000000000002UL#define IOMMU_NUM_CTXS 4096 +#include <linux/iommu-common.h> struct iommu_arena {unsigned long *map;
@@ -24,11 +25,10 @@ struct iommu_arena { };struct iommu { +struct iommu_map_table tbl;spinlock_t lock; -struct iommu_arena arena; -void (*flush_all)(struct iommu *); +u32 dma_addr_mask;iopte_t *page_table; -u32 page_table_map_base;unsigned long iommu_control;unsigned long iommu_tsbbase;unsigned long iommu_flush;
@@ -40,7 +40,6 @@ struct iommu {unsigned long dummy_page_pa;unsigned long ctx_lowest_free;DECLARE_BITMAP (ctx_bitmap,IOMMU_NUM_CTXS ); -u32 dma_addr_mask; };struct strbuf {
arch/sparc/kernel/iommu.c +43 -129
342 lines not on this page · raw message
arch/sparc/kernel/iommu_common.h +0 -8
@@ -48,12 +48,4 @@ static inline int is_span_boundary(unsigned long entry, return iommu_is_span_boundary(entry, nr, shift, boundary_size); } -unsigned long iommu_range_alloc (struct device *dev, -struct iommu *iommu, -unsigned long npages, -unsigned long *handle); -void iommu_range_free (struct iommu *iommu, -dma_addr_t dma_addr, -unsigned long npages); -#endif /* _IOMMU_COMMON_H */
arch/sparc/kernel/pci_sun4v.c +82 -101
343 lines not on this page · raw message
Signature · 1 line
1.7.1
RFC PATCH v9 3/3 sparc: Make LDC use common iommu poll management functions
In reply to Sowmini VaradhanNote that this conversion is only being done to consolidate the code and ensure that the common code provides the sufficient abstraction. It is not expected to result in any noticeable performance improvement, as there is typically one ldc_iommu per vnet_port, and each one has 8k entries, with a typical request for 1-4 pages. Thus LDC uses npools == 1.
---
v3: added this file to be a consumer of the common iommu library
v4: removed ->cookie_to_index and ->demap from iommu_tbl_ops and instead
inline these calls into ldc before calling into iommu-common
v6: remove iommu_tbl_ops
v7: move pool_hash initialization to iommu_tbl_pool_init
v8: BenH comments
v9: More BenH comments
arch/sparc/kernel/ldc.c | 153 ++++++++++++++++++++--------------------------- 1 files changed, 65 insertions(+), 88 deletions(-)
arch/sparc/kernel/ldc.c +65 -88
@@ -15,6 +15,7 @@#include <linux/list.h> #include <linux/init.h> #include <linux/bitmap.h> +#include <linux/iommu-common.h> #include <asm/hypervisor.h> #include <asm/iommu.h>
@@ -27,6 +28,10 @@#define DRV_MODULE_VERSION "1.1"#define DRV_MODULE_RELDATE "July 22, 2008" +#define COOKIE_PGSZ_CODE 0xf000000000000000ULL +#define COOKIE_PGSZ_CODE_SHIFT 60ULL + +static char version[] =DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n" ;#define LDC_PACKET_SIZE 64
@@ -98,10 +103,10 @@ static const struct ldc_mode_ops stream_ops;int ldom_domaining_enabled;struct ldc_iommu { -/* Protects arena alloc/free. */ +/* Protects ldc_unmap. */ spinlock_t lock; -struct iommu_arena arena;struct ldc_mtable_entry *page_table; +struct iommu_map_table iommu_map_table; };struct ldc_channel {
@@ -998,31 +1003,59 @@ static void free_queue(unsigned long num_entries, struct ldc_packet *q)free_pages ((unsigned long )q, order); } +static unsigned long ldc_cookie_to_index (u64 cookie,void *arg) +{ +u64 szcode = cookie >>COOKIE_PGSZ_CODE_SHIFT ; +/* struct ldc_iommu *ldc_iommu = (struct ldc_iommu *)arg; */ + + cookie &= ~COOKIE_PGSZ_CODE ; + +return (cookie >> (13ULL + (szcode *3ULL ))); +} + +static void ldc_demap (struct ldc_iommu *iommu,unsigned long id,u64 cookie, +unsigned long entry,unsigned long npages) +{ +struct ldc_mtable_entry *base; +unsigned long i, shift; + + shift = (cookie >>COOKIE_PGSZ_CODE_SHIFT ) *3 ; + base = iommu->page_table + entry; +for (i =0 ; i < npages; i++) { +if (base->cookie) +sun4v_ldc_revoke (id, cookie + (i << shift), + base->cookie); + base->mte =0 ; + } +} +/* XXX Make this configurable... XXX */ #define LDC_IOTABLE_SIZE (8 * 1024) -static int ldc_iommu_init (struct ldc_channel *lp) +static int ldc_iommu_init (const char *name,struct ldc_channel *lp) {unsigned long sz, num_tsb_entries, tsbsize, order; -struct ldc_iommu *iommu = &lp->iommu; +struct ldc_iommu *ldc_iommu = &lp->iommu; +struct iommu_map_table *iommu = &ldc_iommu->iommu_map_table;struct ldc_mtable_entry *table;unsigned long hv_err;int err; num_tsb_entries =LDC_IOTABLE_SIZE ; tsbsize = num_tsb_entries *sizeof (struct ldc_mtable_entry ); - -spin_lock_init (&iommu->lock); +spin_lock_init (&ldc_iommu->lock); sz = num_tsb_entries /8 ; sz = (sz +7UL ) & ~7UL ; - iommu->arena.map =kzalloc (sz,GFP_KERNEL ); -if (!iommu->arena.map) { + iommu->map =kzalloc (sz,GFP_KERNEL ); +if (!iommu->map) { printk(KERN_ERR PFX "Alloc of arena map failed, sz=%lu\n", sz);return -ENOMEM ; } - - iommu->arena.limit = num_tsb_entries; +iommu_tbl_pool_init (iommu, num_tsb_entries,PAGE_SHIFT , +NULL , false/* no large pool */ , +1 /* npools */ , + true/* skip span boundary check */ ); order =get_order (tsbsize);
@@ -1037,7 +1070,7 @@ static int ldc_iommu_init(struct ldc_channel *lp)memset (table,0 ,PAGE_SIZE << order); - iommu->page_table = table; + ldc_iommu->page_table = table; hv_err =sun4v_ldc_set_map_table (lp->id,__pa (table), num_tsb_entries);
@@ -1049,31 +1082,32 @@ static int ldc_iommu_init(struct ldc_channel *lp) out_free_table:free_pages ((unsigned long ) table, order); - iommu->page_table =NULL ; + ldc_iommu->page_table =NULL ; out_free_map: -kfree (iommu->arena.map); - iommu->arena.map =NULL ; +kfree (iommu->map); + iommu->map =NULL ;return err; }static void ldc_iommu_release (struct ldc_channel *lp) { -struct ldc_iommu *iommu = &lp->iommu; +struct ldc_iommu *ldc_iommu = &lp->iommu; +struct iommu_map_table *iommu = &ldc_iommu->iommu_map_table;unsigned long num_tsb_entries, tsbsize, order; (void )sun4v_ldc_set_map_table (lp->id,0 ,0 ); - num_tsb_entries = iommu->arena.limit; + num_tsb_entries = iommu->poolsize * iommu->nr_pools; tsbsize = num_tsb_entries *sizeof (struct ldc_mtable_entry ); order =get_order (tsbsize); -free_pages ((unsigned long ) iommu->page_table, order); - iommu->page_table =NULL ; +free_pages ((unsigned long ) ldc_iommu->page_table, order); + ldc_iommu->page_table =NULL ; -kfree (iommu->arena.map); - iommu->arena.map =NULL ; +kfree (iommu->map); + iommu->map =NULL ; } struct ldc_channel *ldc_alloc(unsigned long id,
@@ -1140,7 +1174,7 @@ struct ldc_channel *ldc_alloc(unsigned long id, lp->id = id; - err =ldc_iommu_init (lp); + err =ldc_iommu_init (name, lp);if (err) goto out_free_ldc;
@@ -1885,40 +1919,6 @@ int ldc_read(struct ldc_channel *lp, void *buf, unsigned int size) }EXPORT_SYMBOL (ldc_read); -static long arena_alloc (struct ldc_iommu *iommu,unsigned long npages) -{ -struct iommu_arena *arena = &iommu->arena; -unsigned long n, start, end, limit; -int pass; - - limit = arena->limit; - start = arena->hint; - pass =0 ; - -again: - n =bitmap_find_next_zero_area (arena->map, limit, start, npages,0 ); - end = n + npages; -if (unlikely (end >= limit)) { -if (likely (pass <1 )) { - limit = start; - start =0 ; - pass++; - goto again; - }else { -/* Scanned the whole thing, give up. */ -return -1 ; - } - } -bitmap_set (arena->map, n, npages); - - arena->hint = end; - -return n; -} - -#define COOKIE_PGSZ_CODE 0xf000000000000000ULL -#define COOKIE_PGSZ_CODE_SHIFT 60ULL -static u64 pagesize_code (void ) {switch (PAGE_SIZE ) {
@@ -1945,23 +1945,14 @@ static u64 make_cookie(u64 index, u64 pgsz_code, u64 page_offset) page_offset); } -static u64 cookie_to_index (u64 cookie,unsigned long *shift) -{ -u64 szcode = cookie >>COOKIE_PGSZ_CODE_SHIFT ; - - cookie &= ~COOKIE_PGSZ_CODE ; - - *shift = szcode *3 ; - -return (cookie >> (13ULL + (szcode *3ULL ))); -}static struct ldc_mtable_entry *alloc_npages (struct ldc_iommu *iommu,unsigned long npages) {long entry; - entry =arena_alloc (iommu, npages); + entry =iommu_tbl_range_alloc (NULL , &iommu->iommu_map_table, + npages,NULL , (unsigned long )-1 ,0 );if (unlikely (entry <0 ))return NULL ;
@@ -2090,7 +2081,7 @@ int ldc_map_sg(struct ldc_channel *lp, struct ldc_trans_cookie *cookies, int ncookies, unsigned int map_perm) { - unsigned long i, npages, flags; + unsigned long i, npages;struct ldc_mtable_entry *base;struct cookie_state state;struct ldc_iommu *iommu;
@@ -2109,9 +2100,7 @@ int ldc_map_sg(struct ldc_channel *lp, iommu = &lp->iommu; -spin_lock_irqsave (&iommu->lock, flags); base =alloc_npages (iommu, npages); -spin_unlock_irqrestore (&iommu->lock, flags);if (!base)return -ENOMEM ;
@@ -2136,7 +2125,7 @@ int ldc_map_single(struct ldc_channel *lp, struct ldc_trans_cookie *cookies, int ncookies, unsigned int map_perm) { - unsigned long npages, pa, flags; + unsigned long npages, pa;struct ldc_mtable_entry *base;struct cookie_state state;struct ldc_iommu *iommu;
@@ -2152,9 +2141,7 @@ int ldc_map_single(struct ldc_channel *lp, iommu = &lp->iommu; -spin_lock_irqsave (&iommu->lock, flags); base =alloc_npages (iommu, npages); -spin_unlock_irqrestore (&iommu->lock, flags);if (!base)return -ENOMEM ;
@@ -2172,35 +2159,25 @@ int ldc_map_single(struct ldc_channel *lp, }EXPORT_SYMBOL (ldc_map_single); +static void free_npages (unsigned long id,struct ldc_iommu *iommu,u64 cookie,u64 size) { -struct iommu_arena *arena = &iommu->arena; -unsigned long i, shift, index, npages; -struct ldc_mtable_entry *base; +unsigned long npages, entry; npages =PAGE_ALIGN (((cookie & ~PAGE_MASK ) + size)) >>PAGE_SHIFT ; - index =cookie_to_index (cookie, &shift); - base = iommu->page_table + index; -BUG_ON (index > arena->limit || - (index + npages) > arena->limit); - -for (i =0 ; i < npages; i++) { -if (base->cookie) -sun4v_ldc_revoke (id, cookie + (i << shift), - base->cookie); - base->mte =0 ; -__clear_bit (index + i, arena->map); - } + entry =ldc_cookie_to_index (cookie, iommu); +ldc_demap (iommu, id, cookie, entry, npages); +iommu_tbl_range_free (&iommu->iommu_map_table, cookie, npages, entry); }void ldc_unmap (struct ldc_channel *lp,struct ldc_trans_cookie *cookies,int ncookies) {struct ldc_iommu *iommu = &lp->iommu; -unsigned long flags;int i; +unsigned long flags;spin_lock_irqsave (&iommu->lock, flags);for (i =0 ; i < ncookies; i++) {
Signature · 1 line
1.7.1
v9 cover Generic IOMMU pooled allocator
Addresses latest BenH comments: need_flush checks, add support for dma mask and align_order.
Sowmini Varadhan (3):
Break up monolithic iommu table/lock into finer graularity pools and
lock
Make sparc64 use scalable lib/iommu-common.c functions Make LDC use common iommu poll management functions
arch/sparc/include/asm/iommu_64.h | 7 +- arch/sparc/kernel/iommu.c | 172 ++++++------------------ arch/sparc/kernel/iommu_common.h | 8 - arch/sparc/kernel/ldc.c | 153 +++++++++------------ arch/sparc/kernel/pci_sun4v.c | 183 ++++++++++++-------------- include/linux/iommu-common.h | 51 +++++++ lib/Makefile | 2 +- lib/iommu-common.c | 266 +++++++++++++++++++++++++++++++++++++ 8 files changed, 511 insertions(+), 331 deletions(-) create mode 100644 include/linux/iommu-common.h create mode 100644 lib/iommu-common.c
Re: [PATCHv9 RFC 1/3] Break up monolithic iommu table/lock into finer graularity pools and lock
In reply to Sowmini VaradhanQuoted 11 lines · Sowmini Varadhan
(quoted diff) +2
-0+ if (likely(pass = 0)) { + /* First failure, rescan from the beginning.*/
lib/iommu-common.c new file +7
-0+ pool->hint = pool->start; +set_flush (iommu); + pass++; + goto again; + }else if (!largealloc && pass <= iommu->nr_pools) { +spin_unlock (&(pool->lock)); + pool_nr = (pool_nr + 1) & (iommu->nr_pools -1);
lib/iommu-common.c line 194
Quoted 6 lines
lib/iommu-common.c new file +6
-0+ pool = &(iommu->pools[pool_nr]); +spin_lock (&(pool->lock)); + pool->hint = pool->start; +set_flush (iommu); + pass++; + goto again;
So you decided to keep the logic here that updates the hint instead of just getting rid of need_flush alltogether ?
Out of curiosity, what's the rationale ? Did you find a reason why resetting the hint in those two cases (rather than just setting "start" appropriately) is actually useful ?
Cheers,
Ben.
Re: [PATCHv9 RFC 1/3] Break up monolithic iommu table/lock into finer graularity pools and lock
Quoted 7 lines · Benjamin Herrenschmidt
So you decided to keep the logic here that updates the hint instead of just getting rid of
need_flushalltogether ?Out of curiosity, what's the rationale ? Did you find a reason why resetting the hint in those two cases (rather than just setting "start" appropriately) is actually useful ?
To be honest, I actually did not want to poke at this too much, given that both ppc and sparc seem to have essentially the same logic.
But my gut feeling is that if the pool->hint hasn't resulted in a succesful area_alloc, we might as well reset it for the next iommu*range_alloc-er, because it's likely that the next alloc will also need to wrap and search anyway..
--Sowmini
Re: [PATCHv9 RFC 1/3] Break up monolithic iommu table/lock into finer graularity pools and lock
In reply to Sowmini VaradhanQuoted 10 lines · Sowmini Varadhan
Investigation of multithreaded iperf experiments on an ethernet interface show the
iommu->lockas the hottest lock identified by lockstat, with something of the order of 21M contentions out of 27M acquisitions, and an average wait time of 26 us for the lock. This is not efficient. A more scalable design is to follow the ppc model, where theiommu_map_tablehas multiple pools, each stretching over a segment of the map, and with a separate lock for each pool. This model allows for better parallelization of the iommu map search.This patch adds the iommu range alloc/free function infrastructure.
Sorry for the delay, I'm swamped with "stuff" at the moment, I wanted to try actually porting powerpc over and testing but didn't get a chance.
I'm happy with your last version, feel free to add my
I'll port powerpc over when I get a chance and if I find a need to tweak something at that point, I'll send a separate patch on top of yours.
Cheers,
Ben.
Quoted 379 lines
Signed-off-by Sowmini Varadhan <[email protected]>---
v2 changes:
- incorporate David Miller editorial comments: sparc specific fields moved from iommu-common into sparc'siommu_64.h
- make the npools value an input parameter, for the case when the iommu map size is not very large
-cookie_to_indexmapping, and optimizations for span-boundary check, for use case such as LDC.
v3: eliminateiommu_sparc, rearrange the ->demap indirection to
be invoked under the pool lock.v4: David Miller review changes:
- s/IOMMU_ERROR_CODE/DMA_ERROR_CODE
-page_table_map_baseandpage_table_shiftare unsigned long, not u32.v5: Feedback from [email protected] and [email protected]
- removed ->cookie_to_index and ->demap indirection: caller should invoke these as needed before calling into the generic allocatorv6: Benh/DaveM discussion eliminationg
iommu_tbl_ops, but retainingflush_all
optimization.v7: one-time initialization of
pool_hashfromiommu_tbl_pool_init()v8: Benh code review comments.
v9: More Benh code review comments, added
dma_mask,align_orderlogic to
iommu_tbl_range_alloc.include/linux/iommu-common.h | 51 ++++++++ lib/Makefile | 2 +- lib/iommu-common.c | 266 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 318 insertions(+), 1 deletions(-) create mode 100644 include/linux/iommu-common.h create mode 100644 lib/iommu-common.cinclude/linux/iommu-common.h new file +51
-0@@ -0,0 +1,51 @@ +#ifndef _LINUX_IOMMU_COMMON_H +#define _LINUX_IOMMU_COMMON_H + +#include <linux/spinlock_types.h> +#include <linux/device.h> +#include <asm/page.h> + +#define IOMMU_POOL_HASHBITS 4 +#define IOMMU_NR_POOLS (1 << IOMMU_POOL_HASHBITS) + +struct iommu_pool { +unsigned long start; +unsigned long end; +unsigned long hint; +spinlock_t lock; +}; + +struct iommu_map_table { +unsigned long table_map_base; +unsigned long table_shift; +unsigned long nr_pools; +void (*lazy_flush)(struct iommu_map_table *); +unsigned long poolsize; +struct iommu_pool pools[IOMMU_NR_POOLS ]; +u32 flags; +#define IOMMU_HAS_LARGE_POOL 0x00000001 +#define IOMMU_NO_SPAN_BOUND 0x00000002 +#define IOMMU_NEED_FLUSH 0x00000004 +struct iommu_pool large_pool; +unsigned long *map; +}; + +extern void iommu_tbl_pool_init (struct iommu_map_table *iommu, +unsigned long num_entries, +u32 table_shift, +void (*lazy_flush)(struct iommu_map_table *), +bool large_pool,u32 npools, +bool skip_span_boundary_check); + +extern unsigned long iommu_tbl_range_alloc (struct device *dev, +struct iommu_map_table *iommu, +unsigned long npages, +unsigned long *handle, +unsigned long mask, +unsigned int align_order); + +extern void iommu_tbl_range_free (struct iommu_map_table *iommu, +u64 dma_addr,unsigned long npages, +unsigned long entry); + +#endif lib/Makefile +1
-1@@ -106,7 +106,7 @@ obj-$(CONFIG_AUDIT_GENERIC) += audit.o obj-$(CONFIG_AUDIT_COMPAT_GENERIC) += compat_audit.o obj-$(CONFIG_SWIOTLB) += swiotlb.o -obj-$(CONFIG_IOMMU_HELPER) += iommu-helper.o +obj-$(CONFIG_IOMMU_HELPER) += iommu-helper.o iommu-common.o obj-$(CONFIG_FAULT_INJECTION) += fault-inject.o obj-$(CONFIG_NOTIFIER_ERROR_INJECTION) += notifier-error-inject.o obj-$(CONFIG_CPU_NOTIFIER_ERROR_INJECT) += cpu-notifier-error-inject.olib/iommu-common.c new file +266
-0@@ -0,0 +1,266 @@ +/* +* IOMMU mmap management and range allocation functions. +* Based almost entirely upon the powerpc iommu allocator. +*/ + +#include <linux/export.h> +#include <linux/bitmap.h> +#include <linux/bug.h> +#include <linux/iommu-helper.h> +#include <linux/iommu-common.h> +#include <linux/dma-mapping.h> +#include <linux/hash.h> + +unsigned long iommu_large_alloc =15 ; + +static DEFINE_PER_CPU(unsigned int, iommu_pool_hash); + +static inline bool need_flush (struct iommu_map_table *iommu) +{ +return (iommu->lazy_flush !=NULL && + (iommu->flags &IOMMU_NEED_FLUSH ) !=0 ); +} + +static inline void set_flush (struct iommu_map_table *iommu) +{ + iommu->flags |=IOMMU_NEED_FLUSH ; +} + +static inline void clear_flush (struct iommu_map_table *iommu) +{ + iommu->flags &= ~IOMMU_NEED_FLUSH ; +} + +static void setup_iommu_pool_hash (void ) +{ +unsigned int i; +static bool do_once; + +if (do_once) +return ; + do_once = true; +for_each_possible_cpu (i) +per_cpu (iommu_pool_hash, i) =hash_32 (i,IOMMU_POOL_HASHBITS ); +} + +/* +* Initialize iommu_pool entries for the iommu_map_table. `num_entries' +* is the number of table entries. If `large_pool' is set to true, +* the top 1/4 of the table will be set aside for pool allocations +* of more than iommu_large_alloc pages. +*/ +extern void iommu_tbl_pool_init (struct iommu_map_table *iommu, +unsigned long num_entries, +u32 table_shift, +void (*lazy_flush)(struct iommu_map_table *), +bool large_pool,u32 npools, +bool skip_span_boundary_check) +{ +unsigned int start, i; +struct iommu_pool *p = &(iommu->large_pool); + +setup_iommu_pool_hash (); +if (npools =0 ) + iommu->nr_pools =IOMMU_NR_POOLS ; +else + iommu->nr_pools = npools; +BUG_ON (npools >IOMMU_NR_POOLS ); + + iommu->table_shift = table_shift; + iommu->lazy_flush = lazy_flush; + start =0 ; +if (skip_span_boundary_check) + iommu->flags |=IOMMU_NO_SPAN_BOUND ; +if (large_pool) + iommu->flags |=IOMMU_HAS_LARGE_POOL ; + +if (!large_pool) + iommu->poolsize = num_entries/iommu->nr_pools; +else + iommu->poolsize = (num_entries *3 /4 )/iommu->nr_pools; +for (i =0 ; i < iommu->nr_pools; i++) { +spin_lock_init (&(iommu->pools[i].lock)); + iommu->pools[i].start = start; + iommu->pools[i].hint = start; + start += iommu->poolsize;/* start for next pool */ + iommu->pools[i].end = start -1 ; + } +if (!large_pool) +return ; +/* initialize large_pool */ +spin_lock_init (&(p->lock)); + p->start = start; + p->hint = p->start; + p->end = num_entries; +} +EXPORT_SYMBOL (iommu_tbl_pool_init); + +unsigned long iommu_tbl_range_alloc (struct device *dev, +struct iommu_map_table *iommu, +unsigned long npages, +unsigned long *handle, +unsigned long mask, +unsigned int align_order) +{ +unsigned int pool_hash =__this_cpu_read (iommu_pool_hash); +unsigned long n, end, start, limit, boundary_size; +struct iommu_pool *pool; +int pass =0 ; +unsigned int pool_nr; +unsigned int npools = iommu->nr_pools; +unsigned long flags; +bool large_pool = ((iommu->flags &IOMMU_HAS_LARGE_POOL ) !=0 ); +bool largealloc = (large_pool && npages > iommu_large_alloc); +unsigned long shift; +unsigned long align_mask =0 ; + +if (align_order >0 ) + align_mask =0xffffffffffffffffl >> (64 - align_order); + +/* Sanity check */ +if (unlikely (npages =0 )) { +WARN_ON_ONCE (1 ); +return DMA_ERROR_CODE ; + } + +if (largealloc) { + pool = &(iommu->large_pool); + pool_nr =0 ;/* to keep compiler happy */ + }else { +/* pick out pool_nr */ + pool_nr = pool_hash & (npools -1 ); + pool = &(iommu->pools[pool_nr]); + } +spin_lock_irqsave (&pool->lock, flags); + + again: +if (pass =0 && handle && *handle && + (*handle >= pool->start) && (*handle < pool->end)) + start = *handle; +else + start = pool->hint; + + limit = pool->end; + +/* The case below can happen if we have a small segment appended +* to a large, or when the previous alloc was at the very end of +* the available space. If so, go back to the beginning. If a +* flush is needed, it will get done based on the return value +* from iommu_area_alloc() below. +*/ +if (start >= limit) + start = pool->start; + shift = iommu->table_map_base >> iommu->table_shift; +if (limit + shift > mask) { + limit = mask - shift +1 ; +/* If we're constrained on address range, first try +* at the masked hint to avoid O(n) search complexity, +* but on second pass, start at 0 in pool 0. +*/ +if ((start & mask) >= limit || pass >0 ) { +spin_unlock (&(pool->lock)); + pool = &(iommu->pools[0 ]); +spin_lock (&(pool->lock)); + start = pool->start; + }else { + start &= mask; + } + } + +if (dev) + boundary_size =ALIGN (dma_get_seg_boundary (dev) +1 , +1 << iommu->table_shift); +else + boundary_size =ALIGN (1UL <<32 ,1 << iommu->table_shift); + + boundary_size = boundary_size >> iommu->table_shift; +/* +* if the skip_span_boundary_check had been set during init, we set +* things up so that iommu_is_span_boundary() merely checks if the +* (index + npages) < num_tsb_entries +*/ +if ((iommu->flags &IOMMU_NO_SPAN_BOUND ) !=0 ) { + shift =0 ; + boundary_size = iommu->poolsize * iommu->nr_pools; + } + n =iommu_area_alloc (iommu->map, limit, start, npages, shift, + boundary_size, align_mask); +if (n =-1 ) { +if (likely (pass =0 )) { +/* First failure, rescan from the beginning. */ + pool->hint = pool->start; +set_flush (iommu); + pass++; + goto again; + }else if (!largealloc && pass <= iommu->nr_pools) { +spin_unlock (&(pool->lock)); + pool_nr = (pool_nr +1 ) & (iommu->nr_pools -1 ); + pool = &(iommu->pools[pool_nr]); +spin_lock (&(pool->lock)); + pool->hint = pool->start; +set_flush (iommu); + pass++; + goto again; + }else { +/* give up */ + n =DMA_ERROR_CODE ; + goto bail; + } + } +if (n < pool->hint ||need_flush (iommu)) { +clear_flush (iommu); + iommu->lazy_flush (iommu); + } + + end = n + npages; + pool->hint = end; + +/* Update handle for SG allocations */ +if (handle) + *handle = end; +bail: +spin_unlock_irqrestore (&(pool->lock), flags); + +return n; +} +EXPORT_SYMBOL (iommu_tbl_range_alloc); + +static struct iommu_pool *get_pool (struct iommu_map_table *tbl, +unsigned long entry) +{ +struct iommu_pool *p; +unsigned long largepool_start = tbl->large_pool.start; +bool large_pool = ((tbl->flags &IOMMU_HAS_LARGE_POOL ) !=0 ); + +/* The large pool is the last pool at the top of the table */ +if (large_pool && entry >= largepool_start) { + p = &tbl->large_pool; + }else { +unsigned int pool_nr = entry / tbl->poolsize; + +BUG_ON (pool_nr >= tbl->nr_pools); + p = &tbl->pools[pool_nr]; + } +return p; +} + +/* Caller supplies the index of the entry into the iommu map table +* itself when the mapping from dma_addr to the entry is not the +* default addr->entry mapping below. +*/ +void iommu_tbl_range_free (struct iommu_map_table *iommu,u64 dma_addr, +unsigned long npages,unsigned long entry) +{ +struct iommu_pool *pool; +unsigned long flags; +unsigned long shift = iommu->table_shift; + +if (entry =DMA_ERROR_CODE )/* use default addr->entry mapping */ + entry = (dma_addr - iommu->table_map_base) >> shift; + pool =get_pool (iommu, entry); + +spin_lock_irqsave (&(pool->lock), flags); +bitmap_clear (iommu->map, entry, npages); +spin_unlock_irqrestore (&(pool->lock), flags); +} +EXPORT_SYMBOL (iommu_tbl_range_free);
Re: [PATCHv9 RFC 1/3] Break up monolithic iommu table/lock into finer graularity pools and lock
Quoted 4 lines · Benjamin Herrenschmidt
I'm happy with your last version, feel free to add my
sounds good, I'll do this and rese a non-RFC version today.
Thanks for all the feedback - it was very useful to me, and I'm much happier with the end-result.
--Sowmini