Message ID | 1501081804-4882-7-git-send-email-olekstysh@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 26/07/17 16:10, Oleksandr Tyshchenko wrote: > From: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com> > > This is the PoC how to optimize page table deallocation sequence > by splitting it into separate chunks. > Use iommu_pt_cleanup_list to queue pages that need to be handled and > freed next time. Use free_page_table platform callback to dequeue > pages. The page allocation/deallocation definitely need to be split in chunk and allow voluntary preemption. Otherwise you may end up hit the RCU sched on the toolstack domain. > > Signed-off-by: Oleksandr Tyshchenko <oleksandr_tyshchenko@epam.com> > CC: Julien Grall <julien.grall@arm.com> > CC: Stefano Stabellini <sstabellini@kernel.org> > --- > xen/drivers/passthrough/arm/io-pgtable-arm.c | 94 +++++++++++++++++++++++++--- > xen/drivers/passthrough/arm/io-pgtable.c | 5 +- > xen/drivers/passthrough/arm/io-pgtable.h | 4 +- > xen/drivers/passthrough/arm/ipmmu-vmsa.c | 33 ++++++++-- > 4 files changed, 119 insertions(+), 17 deletions(-) > > diff --git a/xen/drivers/passthrough/arm/io-pgtable-arm.c b/xen/drivers/passthrough/arm/io-pgtable-arm.c > index c98caa3..7673fda 100644 > --- a/xen/drivers/passthrough/arm/io-pgtable-arm.c > +++ b/xen/drivers/passthrough/arm/io-pgtable-arm.c > @@ -254,6 +254,10 @@ struct arm_lpae_io_pgtable { > > /* Xen: We deal with domain pages. */ > struct page_info *pgd; > + /* Xen: To indicate that deallocation sequence is in progress. */ > + bool_t cleanup; > + /* Xen: To count allocated domain pages. */ > + unsigned int page_count; > }; > > typedef u64 arm_lpae_iopte; > @@ -329,7 +333,7 @@ static void __arm_lpae_set_pte(arm_lpae_iopte *ptep, arm_lpae_iopte pte, > #endif > > static struct page_info *__arm_lpae_alloc_pages(size_t size, gfp_t gfp, > - struct io_pgtable_cfg *cfg) > + struct arm_lpae_io_pgtable *data) > { > struct page_info *pages; > unsigned int order = get_order_from_bytes(size); > @@ -342,15 +346,21 @@ static struct page_info *__arm_lpae_alloc_pages(size_t size, gfp_t gfp, > for (i = 0; i < (1 << order); i ++) > clear_and_clean_page(pages + i); > > + data->page_count += (1<<order); > + > return pages; > } > > static void __arm_lpae_free_pages(struct page_info *pages, size_t size, > - struct io_pgtable_cfg *cfg) > + struct arm_lpae_io_pgtable *data) > { > unsigned int order = get_order_from_bytes(size); > > + BUG_ON((int)data->page_count <= 0); > + > free_domheap_pages(pages, order); > + > + data->page_count -= (1<<order); > } > > static void __arm_lpae_set_pte(arm_lpae_iopte *ptep, arm_lpae_iopte pte, > @@ -434,7 +444,7 @@ static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, > pte = *ptep; > if (!pte) { > page = __arm_lpae_alloc_pages(ARM_LPAE_GRANULE(data), > - GFP_ATOMIC, cfg); > + GFP_ATOMIC, data); > if (!page) > return -ENOMEM; > > @@ -526,6 +536,46 @@ static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova, > return ret; > } > > +static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, > + struct page_info *page); > + > +/* > + * TODO: We have reused unused at the moment "page->pad" variable for > + * storing "data" pointer we need during deallocation sequence. The current > + * free_page_table platform callback carries the only one "page" argument. > + * To perform required calculations with the current (generic) allocator > + * implementation we are highly interested in the following fields: > + * - data->levels > + * - data->pg_shift > + * - data->pgd_size > + * But, this necessity might be avoided if we integrate allocator code with > + * IPMMU-VMSA driver. And these variables will turn into the > + * corresponding #define-s. > + */ > +static void __arm_lpae_free_next_pgtable(struct arm_lpae_io_pgtable *data, > + int lvl, struct page_info *page) > +{ > + if (!data->cleanup) { > + /* > + * We are here during normal page table maintenance. Just call > + * __arm_lpae_free_pgtable(), what we actually had to call. > + */ > + __arm_lpae_free_pgtable(data, lvl, page); > + } else { > + /* > + * The page table deallocation sequence is in progress. Use some fields > + * in struct page_info to pass arguments we will need during handling > + * this page back. Queue page to list. > + */ > + PFN_ORDER(page) = lvl; > + page->pad = (u64)&data->iop.ops; > + > + spin_lock(&iommu_pt_cleanup_lock); > + page_list_add_tail(page, &iommu_pt_cleanup_list); > + spin_unlock(&iommu_pt_cleanup_lock); > + } > +} > + > /* Xen: We deal with domain pages. */ > static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, > struct page_info *page) > @@ -553,19 +603,41 @@ static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, > if (!pte || iopte_leaf(pte, lvl)) > continue; > > - __arm_lpae_free_pgtable(data, lvl + 1, iopte_deref(pte, data)); > + __arm_lpae_free_next_pgtable(data, lvl + 1, iopte_deref(pte, data)); > } > > unmap_domain_page(start); > - __arm_lpae_free_pages(page, table_size, &data->iop.cfg); > + __arm_lpae_free_pages(page, table_size, data); > } > > -static void arm_lpae_free_pgtable(struct io_pgtable *iop) > +/* > + * We added extra "page" argument since we want to know what page is processed > + * at the moment and should be freed. > + * */ > +static void arm_lpae_free_pgtable(struct io_pgtable *iop, struct page_info *page) > { > struct arm_lpae_io_pgtable *data = io_pgtable_to_data(iop); > + int lvl; > > - __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd); > - kfree(data); > + if (!data->cleanup) { > + /* Start page table deallocation sequence from the first level. */ > + data->cleanup = true; > + lvl = ARM_LPAE_START_LVL(data); > + } else { > + /* Retrieve the level to continue deallocation sequence from. */ > + lvl = PFN_ORDER(page); > + PFN_ORDER(page) = 0; > + page->pad = 0; > + } > + > + __arm_lpae_free_pgtable(data, lvl, page); > + > + /* > + * Seems, we have already deallocated all pages, so it is time > + * to release unfreed resource. > + */ > + if (!data->page_count) > + kfree(data); > } > > /* Xen: We deal with domain pages. */ > @@ -889,8 +961,12 @@ arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) > cfg->arm_lpae_s1_cfg.mair[0] = reg; > cfg->arm_lpae_s1_cfg.mair[1] = 0; > > + /* Just to be sure */ > + data->cleanup = false; > + data->page_count = 0; > + > /* Looking good; allocate a pgd */ > - data->pgd = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg); > + data->pgd = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, data); > if (!data->pgd) > goto out_free_data; > > diff --git a/xen/drivers/passthrough/arm/io-pgtable.c b/xen/drivers/passthrough/arm/io-pgtable.c > index bfc7020..e25d731 100644 > --- a/xen/drivers/passthrough/arm/io-pgtable.c > +++ b/xen/drivers/passthrough/arm/io-pgtable.c > @@ -77,7 +77,7 @@ struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, > * It is the IOMMU driver's responsibility to ensure that the page table > * is no longer accessible to the walker by this point. > */ > -void free_io_pgtable_ops(struct io_pgtable_ops *ops) > +void free_io_pgtable_ops(struct io_pgtable_ops *ops, struct page_info *page) > { > struct io_pgtable *iop; > > @@ -86,5 +86,6 @@ void free_io_pgtable_ops(struct io_pgtable_ops *ops) > > iop = container_of(ops, struct io_pgtable, ops); > io_pgtable_tlb_flush_all(iop); > - io_pgtable_init_table[iop->fmt]->free(iop); > + iop->cookie = NULL; > + io_pgtable_init_table[iop->fmt]->free(iop, page); > } > diff --git a/xen/drivers/passthrough/arm/io-pgtable.h b/xen/drivers/passthrough/arm/io-pgtable.h > index fb81fcf..df0e21b 100644 > --- a/xen/drivers/passthrough/arm/io-pgtable.h > +++ b/xen/drivers/passthrough/arm/io-pgtable.h > @@ -144,7 +144,7 @@ struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, > * > * @ops: The ops returned from alloc_io_pgtable_ops. > */ > -void free_io_pgtable_ops(struct io_pgtable_ops *ops); > +void free_io_pgtable_ops(struct io_pgtable_ops *ops, struct page_info *page); > > > /* > @@ -201,7 +201,7 @@ static inline void io_pgtable_tlb_sync(struct io_pgtable *iop) > */ > struct io_pgtable_init_fns { > struct io_pgtable *(*alloc)(struct io_pgtable_cfg *cfg, void *cookie); > - void (*free)(struct io_pgtable *iop); > + void (*free)(struct io_pgtable *iop, struct page_info *page); > }; > > extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns; > diff --git a/xen/drivers/passthrough/arm/ipmmu-vmsa.c b/xen/drivers/passthrough/arm/ipmmu-vmsa.c > index e54b507..2a04800 100644 > --- a/xen/drivers/passthrough/arm/ipmmu-vmsa.c > +++ b/xen/drivers/passthrough/arm/ipmmu-vmsa.c > @@ -708,8 +708,8 @@ static void ipmmu_tlb_flush_all(void *cookie) > { > struct ipmmu_vmsa_domain *domain = cookie; > > - /* Xen: Just return if context_id has non-existent value */ > - if (domain->context_id >= domain->root->num_ctx) > + /* Xen: Just return if context is absent or context_id has non-existent value */ > + if (!domain || domain->context_id >= domain->root->num_ctx) > return; > > ipmmu_tlb_invalidate(domain); > @@ -796,7 +796,9 @@ static int ipmmu_domain_init_context(struct ipmmu_vmsa_domain *domain) > */ > ret = ipmmu_domain_allocate_context(domain->root, domain); > if (ret < 0) { > - free_io_pgtable_ops(domain->iop); > + /* Pass root page table for this domain as an argument. */ > + free_io_pgtable_ops(domain->iop, > + maddr_to_page(domain->cfg.arm_lpae_s1_cfg.ttbr[0])); > return ret; > } > > @@ -2193,7 +2195,12 @@ static void ipmmu_vmsa_destroy_domain(struct iommu_domain *io_domain) > * been detached. > */ > ipmmu_domain_destroy_context(domain); > - free_io_pgtable_ops(domain->iop); > + /* > + * Pass root page table for this domain as an argument. > + * This call will lead to start deallocation sequence. > + */ > + free_io_pgtable_ops(domain->iop, > + maddr_to_page(domain->cfg.arm_lpae_s1_cfg.ttbr[0])); > } > > kfree(domain); > @@ -2383,6 +2390,17 @@ static int ipmmu_vmsa_domain_init(struct domain *d, bool use_iommu) > return 0; > } > > +/* > + * Seems, there is one more page we need to process. So, retrieve > + * the pointer and go on deallocation sequence. > + */ > +static void ipmmu_vmsa_free_page_table(struct page_info *page) > +{ > + struct io_pgtable_ops *ops = (struct io_pgtable_ops *)page->pad; > + > + free_io_pgtable_ops(ops, page); > +} > + > static void __hwdom_init ipmmu_vmsa_hwdom_init(struct domain *d) > { > } > @@ -2404,6 +2422,12 @@ static void ipmmu_vmsa_domain_teardown(struct domain *d) > ASSERT(list_empty(&xen_domain->contexts)); > xfree(xen_domain); > dom_iommu(d)->arch.priv = NULL; > + /* > + * After this point we have all domain resources deallocated, except > + * page table which we will deallocate asynchronously. The IOMMU code > + * provides us with iommu_pt_cleanup_list and free_page_table platform > + * callback what we actually going to use. > + */ > } > > static int __must_check ipmmu_vmsa_map_pages(struct domain *d, > @@ -2462,6 +2486,7 @@ static void ipmmu_vmsa_dump_p2m_table(struct domain *d) > static const struct iommu_ops ipmmu_vmsa_iommu_ops = { > .init = ipmmu_vmsa_domain_init, > .hwdom_init = ipmmu_vmsa_hwdom_init, > + .free_page_table = ipmmu_vmsa_free_page_table, > .teardown = ipmmu_vmsa_domain_teardown, > .iotlb_flush = ipmmu_vmsa_iotlb_flush, > .assign_device = ipmmu_vmsa_assign_dev, >
diff --git a/xen/drivers/passthrough/arm/io-pgtable-arm.c b/xen/drivers/passthrough/arm/io-pgtable-arm.c index c98caa3..7673fda 100644 --- a/xen/drivers/passthrough/arm/io-pgtable-arm.c +++ b/xen/drivers/passthrough/arm/io-pgtable-arm.c @@ -254,6 +254,10 @@ struct arm_lpae_io_pgtable { /* Xen: We deal with domain pages. */ struct page_info *pgd; + /* Xen: To indicate that deallocation sequence is in progress. */ + bool_t cleanup; + /* Xen: To count allocated domain pages. */ + unsigned int page_count; }; typedef u64 arm_lpae_iopte; @@ -329,7 +333,7 @@ static void __arm_lpae_set_pte(arm_lpae_iopte *ptep, arm_lpae_iopte pte, #endif static struct page_info *__arm_lpae_alloc_pages(size_t size, gfp_t gfp, - struct io_pgtable_cfg *cfg) + struct arm_lpae_io_pgtable *data) { struct page_info *pages; unsigned int order = get_order_from_bytes(size); @@ -342,15 +346,21 @@ static struct page_info *__arm_lpae_alloc_pages(size_t size, gfp_t gfp, for (i = 0; i < (1 << order); i ++) clear_and_clean_page(pages + i); + data->page_count += (1<<order); + return pages; } static void __arm_lpae_free_pages(struct page_info *pages, size_t size, - struct io_pgtable_cfg *cfg) + struct arm_lpae_io_pgtable *data) { unsigned int order = get_order_from_bytes(size); + BUG_ON((int)data->page_count <= 0); + free_domheap_pages(pages, order); + + data->page_count -= (1<<order); } static void __arm_lpae_set_pte(arm_lpae_iopte *ptep, arm_lpae_iopte pte, @@ -434,7 +444,7 @@ static int __arm_lpae_map(struct arm_lpae_io_pgtable *data, unsigned long iova, pte = *ptep; if (!pte) { page = __arm_lpae_alloc_pages(ARM_LPAE_GRANULE(data), - GFP_ATOMIC, cfg); + GFP_ATOMIC, data); if (!page) return -ENOMEM; @@ -526,6 +536,46 @@ static int arm_lpae_map(struct io_pgtable_ops *ops, unsigned long iova, return ret; } +static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, + struct page_info *page); + +/* + * TODO: We have reused unused at the moment "page->pad" variable for + * storing "data" pointer we need during deallocation sequence. The current + * free_page_table platform callback carries the only one "page" argument. + * To perform required calculations with the current (generic) allocator + * implementation we are highly interested in the following fields: + * - data->levels + * - data->pg_shift + * - data->pgd_size + * But, this necessity might be avoided if we integrate allocator code with + * IPMMU-VMSA driver. And these variables will turn into the + * corresponding #define-s. + */ +static void __arm_lpae_free_next_pgtable(struct arm_lpae_io_pgtable *data, + int lvl, struct page_info *page) +{ + if (!data->cleanup) { + /* + * We are here during normal page table maintenance. Just call + * __arm_lpae_free_pgtable(), what we actually had to call. + */ + __arm_lpae_free_pgtable(data, lvl, page); + } else { + /* + * The page table deallocation sequence is in progress. Use some fields + * in struct page_info to pass arguments we will need during handling + * this page back. Queue page to list. + */ + PFN_ORDER(page) = lvl; + page->pad = (u64)&data->iop.ops; + + spin_lock(&iommu_pt_cleanup_lock); + page_list_add_tail(page, &iommu_pt_cleanup_list); + spin_unlock(&iommu_pt_cleanup_lock); + } +} + /* Xen: We deal with domain pages. */ static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, struct page_info *page) @@ -553,19 +603,41 @@ static void __arm_lpae_free_pgtable(struct arm_lpae_io_pgtable *data, int lvl, if (!pte || iopte_leaf(pte, lvl)) continue; - __arm_lpae_free_pgtable(data, lvl + 1, iopte_deref(pte, data)); + __arm_lpae_free_next_pgtable(data, lvl + 1, iopte_deref(pte, data)); } unmap_domain_page(start); - __arm_lpae_free_pages(page, table_size, &data->iop.cfg); + __arm_lpae_free_pages(page, table_size, data); } -static void arm_lpae_free_pgtable(struct io_pgtable *iop) +/* + * We added extra "page" argument since we want to know what page is processed + * at the moment and should be freed. + * */ +static void arm_lpae_free_pgtable(struct io_pgtable *iop, struct page_info *page) { struct arm_lpae_io_pgtable *data = io_pgtable_to_data(iop); + int lvl; - __arm_lpae_free_pgtable(data, ARM_LPAE_START_LVL(data), data->pgd); - kfree(data); + if (!data->cleanup) { + /* Start page table deallocation sequence from the first level. */ + data->cleanup = true; + lvl = ARM_LPAE_START_LVL(data); + } else { + /* Retrieve the level to continue deallocation sequence from. */ + lvl = PFN_ORDER(page); + PFN_ORDER(page) = 0; + page->pad = 0; + } + + __arm_lpae_free_pgtable(data, lvl, page); + + /* + * Seems, we have already deallocated all pages, so it is time + * to release unfreed resource. + */ + if (!data->page_count) + kfree(data); } /* Xen: We deal with domain pages. */ @@ -889,8 +961,12 @@ arm_64_lpae_alloc_pgtable_s1(struct io_pgtable_cfg *cfg, void *cookie) cfg->arm_lpae_s1_cfg.mair[0] = reg; cfg->arm_lpae_s1_cfg.mair[1] = 0; + /* Just to be sure */ + data->cleanup = false; + data->page_count = 0; + /* Looking good; allocate a pgd */ - data->pgd = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, cfg); + data->pgd = __arm_lpae_alloc_pages(data->pgd_size, GFP_KERNEL, data); if (!data->pgd) goto out_free_data; diff --git a/xen/drivers/passthrough/arm/io-pgtable.c b/xen/drivers/passthrough/arm/io-pgtable.c index bfc7020..e25d731 100644 --- a/xen/drivers/passthrough/arm/io-pgtable.c +++ b/xen/drivers/passthrough/arm/io-pgtable.c @@ -77,7 +77,7 @@ struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, * It is the IOMMU driver's responsibility to ensure that the page table * is no longer accessible to the walker by this point. */ -void free_io_pgtable_ops(struct io_pgtable_ops *ops) +void free_io_pgtable_ops(struct io_pgtable_ops *ops, struct page_info *page) { struct io_pgtable *iop; @@ -86,5 +86,6 @@ void free_io_pgtable_ops(struct io_pgtable_ops *ops) iop = container_of(ops, struct io_pgtable, ops); io_pgtable_tlb_flush_all(iop); - io_pgtable_init_table[iop->fmt]->free(iop); + iop->cookie = NULL; + io_pgtable_init_table[iop->fmt]->free(iop, page); } diff --git a/xen/drivers/passthrough/arm/io-pgtable.h b/xen/drivers/passthrough/arm/io-pgtable.h index fb81fcf..df0e21b 100644 --- a/xen/drivers/passthrough/arm/io-pgtable.h +++ b/xen/drivers/passthrough/arm/io-pgtable.h @@ -144,7 +144,7 @@ struct io_pgtable_ops *alloc_io_pgtable_ops(enum io_pgtable_fmt fmt, * * @ops: The ops returned from alloc_io_pgtable_ops. */ -void free_io_pgtable_ops(struct io_pgtable_ops *ops); +void free_io_pgtable_ops(struct io_pgtable_ops *ops, struct page_info *page); /* @@ -201,7 +201,7 @@ static inline void io_pgtable_tlb_sync(struct io_pgtable *iop) */ struct io_pgtable_init_fns { struct io_pgtable *(*alloc)(struct io_pgtable_cfg *cfg, void *cookie); - void (*free)(struct io_pgtable *iop); + void (*free)(struct io_pgtable *iop, struct page_info *page); }; extern struct io_pgtable_init_fns io_pgtable_arm_32_lpae_s1_init_fns; diff --git a/xen/drivers/passthrough/arm/ipmmu-vmsa.c b/xen/drivers/passthrough/arm/ipmmu-vmsa.c index e54b507..2a04800 100644 --- a/xen/drivers/passthrough/arm/ipmmu-vmsa.c +++ b/xen/drivers/passthrough/arm/ipmmu-vmsa.c @@ -708,8 +708,8 @@ static void ipmmu_tlb_flush_all(void *cookie) { struct ipmmu_vmsa_domain *domain = cookie; - /* Xen: Just return if context_id has non-existent value */ - if (domain->context_id >= domain->root->num_ctx) + /* Xen: Just return if context is absent or context_id has non-existent value */ + if (!domain || domain->context_id >= domain->root->num_ctx) return; ipmmu_tlb_invalidate(domain); @@ -796,7 +796,9 @@ static int ipmmu_domain_init_context(struct ipmmu_vmsa_domain *domain) */ ret = ipmmu_domain_allocate_context(domain->root, domain); if (ret < 0) { - free_io_pgtable_ops(domain->iop); + /* Pass root page table for this domain as an argument. */ + free_io_pgtable_ops(domain->iop, + maddr_to_page(domain->cfg.arm_lpae_s1_cfg.ttbr[0])); return ret; } @@ -2193,7 +2195,12 @@ static void ipmmu_vmsa_destroy_domain(struct iommu_domain *io_domain) * been detached. */ ipmmu_domain_destroy_context(domain); - free_io_pgtable_ops(domain->iop); + /* + * Pass root page table for this domain as an argument. + * This call will lead to start deallocation sequence. + */ + free_io_pgtable_ops(domain->iop, + maddr_to_page(domain->cfg.arm_lpae_s1_cfg.ttbr[0])); } kfree(domain); @@ -2383,6 +2390,17 @@ static int ipmmu_vmsa_domain_init(struct domain *d, bool use_iommu) return 0; } +/* + * Seems, there is one more page we need to process. So, retrieve + * the pointer and go on deallocation sequence. + */ +static void ipmmu_vmsa_free_page_table(struct page_info *page) +{ + struct io_pgtable_ops *ops = (struct io_pgtable_ops *)page->pad; + + free_io_pgtable_ops(ops, page); +} + static void __hwdom_init ipmmu_vmsa_hwdom_init(struct domain *d) { } @@ -2404,6 +2422,12 @@ static void ipmmu_vmsa_domain_teardown(struct domain *d) ASSERT(list_empty(&xen_domain->contexts)); xfree(xen_domain); dom_iommu(d)->arch.priv = NULL; + /* + * After this point we have all domain resources deallocated, except + * page table which we will deallocate asynchronously. The IOMMU code + * provides us with iommu_pt_cleanup_list and free_page_table platform + * callback what we actually going to use. + */ } static int __must_check ipmmu_vmsa_map_pages(struct domain *d, @@ -2462,6 +2486,7 @@ static void ipmmu_vmsa_dump_p2m_table(struct domain *d) static const struct iommu_ops ipmmu_vmsa_iommu_ops = { .init = ipmmu_vmsa_domain_init, .hwdom_init = ipmmu_vmsa_hwdom_init, + .free_page_table = ipmmu_vmsa_free_page_table, .teardown = ipmmu_vmsa_domain_teardown, .iotlb_flush = ipmmu_vmsa_iotlb_flush, .assign_device = ipmmu_vmsa_assign_dev,