diff --git a/config.yaml b/config.yaml index f5ab7dce..27ce25d6 100644 --- a/config.yaml +++ b/config.yaml @@ -128,6 +128,41 @@ patches: - hyperv/0005-x86-hyperv-take-vPCI-device-interrupts-through-Xen-w.patch - hyperv/0006-drivers-hv-refuse-to-balloon-when-nested-on-Xen.patch series: '6.18' +# Xen PV-IOMMU: let a PV guest program IOMMU contexts through the PV-IOMMU +# hypercall interface, so passed-through devices DMA through a real translation +# context instead of relying on an identity map. Includes the parallel +# io-pgtable used for iova_to_phys, the pcifront machine-BDF lookup the +# hypercalls need, and the PV MSI/MSI-X fixes that go with it. +# +# Pinned to the 6.18 series, not `lower`. iommu_domain_ops::attach_dev gained a +# third `struct iommu_domain *old` argument in 6.19, so this backport only +# compiles against 6.18.x. It is not enough to let the range run open-ended: +# `patch -p1` still applies these to 6.19 with fuzz and the build then fails on +# the incompatible function pointer, so the upper bound has to be explicit. A +# forward-port for 6.19+ wants the three-argument attach_dev and belongs in a +# separate patches/pv-iommu/ directory alongside this one. +# +# These files are trimmed relative to the edera/azenla/feat/pv-iommu branch they +# come from: commit 1 there also carries stray hunks belonging to two other +# series -- the VIRQ_HYPERV_* defines in include/xen/interface/xen.h and the +# XENMEM_get_mfn_pxms block in include/xen/interface/memory.h -- which this tree +# already supplies from patches/hyperv/ and +# 0002-xen-add-xen_mfn_to_node-to-resolve-a-foreign-frame-s.patch. Both are +# dropped here. If you regenerate these patches from that branch, drop them +# again or the 6.18 stack collides on those two headers. +- patches: + - pv-iommu-6.18/0001-iommu-xen-Add-Xen-PV-IOMMU-driver.patch + - pv-iommu-6.18/0002-iommu-xen-Allow-the-PV-IOMMU-driver-in-a-guest.patch + - pv-iommu-6.18/0003-iommu-xen-Give-the-identity-domain-a-type-and-ops.patch + - pv-iommu-6.18/0004-iommu-dma-Don-t-reserve-PCI-windows-under-a-paravirt.patch + - pv-iommu-6.18/0005-xen-pcifront-Let-callers-ask-for-a-device-s-machine-.patch + - pv-iommu-6.18/0006-iommu-xen-Default-a-PV-guest-to-the-identity-context.patch + - pv-iommu-6.18/0007-PCI-MSI-don-t-write-the-MSI-X-table-in-a-Xen-PV-gues.patch + - pv-iommu-6.18/0008-iommu-xen-batch-map-subops-over-contiguous-runs.patch + - pv-iommu-6.18/0009-iommu-xen-do-not-claim-the-IOMMU-itself.patch + - pv-iommu-6.18/0010-x86-xen-disable-the-right-interrupt-type-when-tearin.patch + - pv-iommu-6.18/0011-iommu-xen-flush-the-IOTLB-once-per-mapping-not-once-.patch + series: '6.18' - patch: 0001-feat-xen-deflate-balloon-via-oom-notifier.patch lower: '6.18' # virtio-gpu decides between DMA and guest-physical addresses on its own, diff --git a/configs/x86_64/host.config b/configs/x86_64/host.config index a1034917..cf84fa48 100644 --- a/configs/x86_64/host.config +++ b/configs/x86_64/host.config @@ -4859,6 +4859,7 @@ CONFIG_INTEL_IOMMU_FLOPPY_WA=y CONFIG_INTEL_IOMMU_PERF_EVENTS=y CONFIG_IRQ_REMAP=y CONFIG_HYPERV_IOMMU=y +CONFIG_XEN_IOMMU=y CONFIG_RPMSG=m diff --git a/configs/x86_64/zone.config b/configs/x86_64/zone.config index c8e23ef4..8c69d9bb 100644 --- a/configs/x86_64/zone.config +++ b/configs/x86_64/zone.config @@ -1032,6 +1032,7 @@ CONFIG_INTEL_IOMMU_DEFAULT_ON=y CONFIG_INTEL_IOMMU_SCALABLE_MODE_DEFAULT_ON=y CONFIG_INTEL_IOMMU_PERF_EVENTS=y CONFIG_IRQ_REMAP=y +CONFIG_XEN_IOMMU=y CONFIG_MEMORY=y diff --git a/patches/pv-iommu-6.18/0001-iommu-xen-Add-Xen-PV-IOMMU-driver.patch b/patches/pv-iommu-6.18/0001-iommu-xen-Add-Xen-PV-IOMMU-driver.patch new file mode 100644 index 00000000..10529830 --- /dev/null +++ b/patches/pv-iommu-6.18/0001-iommu-xen-Add-Xen-PV-IOMMU-driver.patch @@ -0,0 +1,1370 @@ +From 5bfc4264ba8f48808ec4f05763548e320374e07b Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Wed, 2 Sep 2026 00:38:42 -0700 +Subject: [PATCH 01/11] iommu/xen: Add Xen PV-IOMMU driver + +Port of Teddy Astie's RFC v3 driver for the Xen PV-IOMMU interface. Under +Xen the IOMMU belongs to the hypervisor, so a guest cannot use anything +that needs one -- VFIO, or DMA protection of its own. The PV-IOMMU +hypercall lets it manage IOMMU contexts through Xen instead, and this +driver presents that as a normal iommu_ops. + + https://patchwork.kernel.org/project/xen-devel/list/?series=906092 + +Changes needed against 6.18: + + - pgsize_bitmap moved from struct iommu_ops to struct iommu_domain, so + the mask the hypervisor reports is now stashed and applied to each + domain as it is allocated, including the identity domain. + - iommu_alloc_page()/iommu_free_page() are gone; the page table code uses + iommu_alloc_pages_sz()/iommu_free_pages(). + +CONFIG_XEN_IOMMU still depends on XEN_DOM0, matching the interface as +posted: the hypercall names devices by machine BDF, which a guest does not +see. +--- + arch/x86/include/asm/xen/hypercall.h | 6 + + drivers/iommu/Kconfig | 10 + + drivers/iommu/Makefile | 2 + + drivers/iommu/io-pgtable-xen.c | 370 ++++++++++++++++++++++ + drivers/iommu/io-pgtable.c | 3 + + drivers/iommu/xen-iommu.c | 446 +++++++++++++++++++++++++++ + include/linux/io-pgtable.h | 2 + + include/xen/interface/memory.h | 33 ++ + include/xen/interface/pv-iommu.h | 341 ++++++++++++++++++++ + include/xen/interface/xen.h | 1 + + 10 files changed, 1214 insertions(+) + create mode 100644 drivers/iommu/io-pgtable-xen.c + create mode 100644 drivers/iommu/xen-iommu.c + create mode 100644 include/xen/interface/pv-iommu.h + +diff --git a/arch/x86/include/asm/xen/hypercall.h b/arch/x86/include/asm/xen/hypercall.h +index a16d4631547c..fa7db4d3ba5c 100644 +--- a/arch/x86/include/asm/xen/hypercall.h ++++ b/arch/x86/include/asm/xen/hypercall.h +@@ -497,6 +497,12 @@ HYPERVISOR_xenpmu_op(unsigned int op, void *arg) + return _hypercall2(int, xenpmu_op, op, arg); + } + ++static inline long ++HYPERVISOR_iommu_op(unsigned int subop, void *arg) ++{ ++ return _hypercall2(int, iommu_op, subop, arg); ++} ++ + static inline int + HYPERVISOR_dm_op( + domid_t dom, unsigned int nr_bufs, struct xen_dm_op_buf *bufs) +diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig +index 70d29b14d851..8ab6cd68bdcc 100644 +--- a/drivers/iommu/Kconfig ++++ b/drivers/iommu/Kconfig +@@ -371,6 +371,16 @@ config VIRTIO_IOMMU + + Say Y here if you intend to run this kernel as a guest. + ++config XEN_IOMMU ++ bool "Xen IOMMU driver" ++ depends on XEN_DOM0 ++ select IOMMU_API ++ select IOMMU_IO_PGTABLE ++ help ++ Xen PV-IOMMU driver for Dom0. ++ ++ Say Y here if you intend to run this guest as Xen Dom0. ++ + config SPRD_IOMMU + tristate "Unisoc IOMMU Support" + depends on ARCH_SPRD || COMPILE_TEST +diff --git a/drivers/iommu/Makefile b/drivers/iommu/Makefile +index 355294fa9033..55954056cbb0 100644 +--- a/drivers/iommu/Makefile ++++ b/drivers/iommu/Makefile +@@ -13,6 +13,7 @@ obj-$(CONFIG_IOMMU_IO_PGTABLE) += io-pgtable.o + obj-$(CONFIG_IOMMU_IO_PGTABLE_ARMV7S) += io-pgtable-arm-v7s.o + obj-$(CONFIG_IOMMU_IO_PGTABLE_LPAE) += io-pgtable-arm.o + obj-$(CONFIG_IOMMU_IO_PGTABLE_DART) += io-pgtable-dart.o ++obj-$(CONFIG_XEN_IOMMU) += io-pgtable-xen.o + obj-$(CONFIG_IOMMU_IOVA) += iova.o + obj-$(CONFIG_OF_IOMMU) += of_iommu.o + obj-$(CONFIG_MSM_IOMMU) += msm_iommu.o +@@ -34,3 +35,4 @@ obj-$(CONFIG_IOMMU_SVA) += iommu-sva.o + obj-$(CONFIG_IOMMU_IOPF) += io-pgfault.o + obj-$(CONFIG_SPRD_IOMMU) += sprd-iommu.o + obj-$(CONFIG_APPLE_DART) += apple-dart.o ++obj-$(CONFIG_XEN_IOMMU) += xen-iommu.o +diff --git a/drivers/iommu/io-pgtable-xen.c b/drivers/iommu/io-pgtable-xen.c +new file mode 100644 +index 000000000000..37a8668a6771 +--- /dev/null ++++ b/drivers/iommu/io-pgtable-xen.c +@@ -0,0 +1,370 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Generic page table allocator for tracking purposes. ++ * Based on AMD IO pagetable allocator v2. ++ * ++ * Copyright (C) 2024 Vates SAS ++ * Author: Teddy Astie ++ */ ++ ++#define pr_fmt(fmt) "xen-iommu pg-table: " fmt ++#define dev_fmt(fmt) pr_fmt(fmt) ++ ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include "iommu-pages.h" ++ ++#include "xen/page.h" ++ ++#define IOMMU_PAGE_PRESENT BIT_ULL(0) /* Is present */ ++#define IOMMU_PAGE_HUGE BIT_ULL(1) /* Is hugepage */ ++#define MAX_PTRS_PER_PAGE 512 ++ ++#define IOMMU_PAGE_SIZE_2M BIT_ULL(21) ++#define IOMMU_PAGE_SIZE_1G BIT_ULL(30) ++ ++#define PM_ADDR_MASK 0x000ffffffffff000ULL ++#define XEN_IOMMU_PGSIZES (XEN_PAGE_SIZE | (1ULL << 21) | (1ULL << 30)) ++ ++#define PAGE_MODE_NONE 0x00 ++#define PAGE_MODE_1_LEVEL 0x01 ++#define PAGE_MODE_2_LEVEL 0x02 ++#define PAGE_MODE_3_LEVEL 0x03 ++#define PAGE_MODE_4_LEVEL 0x04 ++#define PAGE_MODE_5_LEVEL 0x05 ++ ++#define IOMMU_PTE_PR BIT(0) ++#define IOMMU_PTE_PRESENT(pte) ((pte) & IOMMU_PTE_PR) ++ ++#define PM_LEVEL_SHIFT(x) (12 + ((x) * 9)) ++#define PM_LEVEL_INDEX(x, a) (((a) >> PM_LEVEL_SHIFT((x))) & 0x1ffULL) ++ ++#define IOMMU_IN_ADDR_BIT_SIZE 52 ++#define IOMMU_OUT_ADDR_BIT_SIZE 52 ++ ++#define PAGE_SIZE_ALIGN(address, pagesize) \ ++ ((address) & ~((pagesize) - 1)) ++ ++#define io_pgtable_to_data(x) \ ++ container_of((x), struct xen_io_pgtable, iop) ++ ++#define io_pgtable_ops_to_data(x) \ ++ io_pgtable_to_data(io_pgtable_ops_to_pgtable(x)) ++ ++ ++struct xen_io_pgtable { ++ struct io_pgtable_cfg pgtbl_cfg; ++ struct io_pgtable iop; ++ u64 *pgd; /* pgtable pgd pointer */ ++}; ++ ++static inline bool is_large_pte(u64 pte) ++{ ++ return (pte & IOMMU_PAGE_HUGE); ++} ++ ++static inline u64 set_pgtable_attr(u64 *page) ++{ ++ return (virt_to_phys(page) | IOMMU_PAGE_PRESENT); ++} ++ ++static inline void *get_pgtable_pte(u64 pte) ++{ ++ return phys_to_virt(pte & PM_ADDR_MASK); ++} ++ ++static u64 set_pte_attr(u64 paddr, u64 pg_size) ++{ ++ u64 pte; ++ ++ pte = paddr & PM_ADDR_MASK; ++ pte |= IOMMU_PAGE_PRESENT; ++ ++ /* Large page */ ++ if (pg_size == IOMMU_PAGE_SIZE_1G || pg_size == IOMMU_PAGE_SIZE_2M) ++ pte |= IOMMU_PAGE_HUGE; ++ ++ return pte; ++} ++ ++static inline u64 get_alloc_page_size(u64 size) ++{ ++ if (size >= IOMMU_PAGE_SIZE_1G) ++ return IOMMU_PAGE_SIZE_1G; ++ ++ if (size >= IOMMU_PAGE_SIZE_2M) ++ return IOMMU_PAGE_SIZE_2M; ++ ++ return XEN_PAGE_SIZE; ++} ++ ++static inline int page_size_to_level(u64 pg_size) ++{ ++ if (pg_size == IOMMU_PAGE_SIZE_1G) ++ return PAGE_MODE_3_LEVEL; ++ if (pg_size == IOMMU_PAGE_SIZE_2M) ++ return PAGE_MODE_2_LEVEL; ++ ++ return PAGE_MODE_1_LEVEL; ++} ++ ++static void free_pgtable(u64 *pt, int level) ++{ ++ u64 *p; ++ int i; ++ ++ for (i = 0; i < MAX_PTRS_PER_PAGE; i++) { ++ /* PTE present? */ ++ if (!IOMMU_PTE_PRESENT(pt[i])) ++ continue; ++ ++ if (is_large_pte(pt[i])) ++ continue; ++ ++ /* ++ * Free the next level. No need to look at l1 tables here since ++ * they can only contain leaf PTEs; just free them directly. ++ */ ++ p = get_pgtable_pte(pt[i]); ++ if (level > 2) ++ free_pgtable(p, level - 1); ++ else ++ iommu_free_pages(p); ++ } ++ ++ iommu_free_pages(pt); ++} ++ ++/* Allocate page table */ ++static u64 *xen_alloc_pte(u64 *pgd, unsigned long iova, gfp_t gfp, ++ unsigned long pg_size, bool *updated) ++{ ++ u64 *pte, *page; ++ int level, end_level; ++ ++ level = PAGE_MODE_5_LEVEL - 1; ++ end_level = page_size_to_level(pg_size); ++ pte = &pgd[PM_LEVEL_INDEX(level, iova)]; ++ iova = PAGE_SIZE_ALIGN(iova, XEN_PAGE_SIZE); ++ ++ while (level >= end_level) { ++ u64 __pte, __npte; ++ ++ __pte = *pte; ++ ++ if (IOMMU_PTE_PRESENT(__pte) && is_large_pte(__pte)) { ++ /* Unmap large pte */ ++ cmpxchg64(pte, *pte, 0ULL); ++ *updated = true; ++ continue; ++ } ++ ++ if (!IOMMU_PTE_PRESENT(__pte)) { ++ page = iommu_alloc_pages_sz(gfp, SZ_4K); ++ if (!page) ++ return NULL; ++ ++ __npte = set_pgtable_attr(page); ++ /* pte could have been changed somewhere. */ ++ if (cmpxchg64(pte, __pte, __npte) != __pte) ++ iommu_free_pages(page); ++ else if (IOMMU_PTE_PRESENT(__pte)) ++ *updated = true; ++ ++ continue; ++ } ++ ++ level -= 1; ++ pte = get_pgtable_pte(__pte); ++ pte = &pte[PM_LEVEL_INDEX(level, iova)]; ++ } ++ ++ /* Tear down existing pte entries */ ++ if (IOMMU_PTE_PRESENT(*pte)) { ++ u64 *__pte; ++ ++ *updated = true; ++ __pte = get_pgtable_pte(*pte); ++ cmpxchg64(pte, *pte, 0ULL); ++ if (pg_size == IOMMU_PAGE_SIZE_1G) ++ free_pgtable(__pte, end_level - 1); ++ else if (pg_size == IOMMU_PAGE_SIZE_2M) ++ iommu_free_pages(__pte); ++ } ++ ++ return pte; ++} ++ ++/* ++ * This function checks if there is a PTE for a given dma address. ++ * If there is one, it returns the pointer to it. ++ */ ++static u64 *fetch_pte(struct xen_io_pgtable *pgtable, unsigned long iova, ++ unsigned long *page_size) ++{ ++ u64 *pte; ++ int level; ++ ++ level = PAGE_MODE_5_LEVEL - 1; ++ pte = &pgtable->pgd[PM_LEVEL_INDEX(level, iova)]; ++ /* Default page size is 4K */ ++ *page_size = XEN_PAGE_SIZE; ++ ++ while (level) { ++ /* Not present */ ++ if (!IOMMU_PTE_PRESENT(*pte)) ++ return NULL; ++ ++ /* Walk to the next level */ ++ pte = get_pgtable_pte(*pte); ++ pte = &pte[PM_LEVEL_INDEX(level - 1, iova)]; ++ ++ /* Large page */ ++ if (is_large_pte(*pte)) { ++ if (level == PAGE_MODE_3_LEVEL) ++ *page_size = IOMMU_PAGE_SIZE_1G; ++ else if (level == PAGE_MODE_2_LEVEL) ++ *page_size = IOMMU_PAGE_SIZE_2M; ++ else ++ return NULL; /* Wrongly set PSE bit in PTE */ ++ ++ break; ++ } ++ ++ level -= 1; ++ } ++ ++ return pte; ++} ++ ++static int iommu_xen_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) ++{ ++ struct xen_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); ++ struct io_pgtable_cfg *cfg = &pgtable->pgtbl_cfg; ++ u64 *pte; ++ unsigned long map_size; ++ unsigned long mapped_size = 0; ++ size_t size = pgcount << __ffs(pgsize); ++ int ret = 0; ++ bool updated = false; ++ ++ if (WARN_ON(!pgsize || (pgsize & cfg->pgsize_bitmap) != pgsize) || !pgcount) ++ return -EINVAL; ++ ++ while (mapped_size < size) { ++ map_size = get_alloc_page_size(pgsize); ++ pte = xen_alloc_pte(pgtable->pgd, iova, gfp, map_size, &updated); ++ if (!pte) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ *pte = set_pte_attr(paddr, map_size); ++ ++ iova += map_size; ++ paddr += map_size; ++ mapped_size += map_size; ++ } ++ ++out: ++ if (mapped) ++ *mapped += mapped_size; ++ ++ return ret; ++} ++ ++static unsigned long iommu_xen_unmap_pages(struct io_pgtable_ops *ops, ++ unsigned long iova, ++ size_t pgsize, size_t pgcount, ++ struct iommu_iotlb_gather *gather) ++{ ++ struct xen_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); ++ struct io_pgtable_cfg *cfg = &pgtable->iop.cfg; ++ unsigned long unmap_size; ++ unsigned long unmapped = 0; ++ size_t size = pgcount << __ffs(pgsize); ++ u64 *pte; ++ ++ if (WARN_ON(!pgsize || (pgsize & cfg->pgsize_bitmap) != pgsize || !pgcount)) ++ return 0; ++ ++ while (unmapped < size) { ++ pte = fetch_pte(pgtable, iova, &unmap_size); ++ if (!pte) ++ return unmapped; ++ ++ *pte = 0ULL; ++ ++ iova = (iova & ~(unmap_size - 1)) + unmap_size; ++ unmapped += unmap_size; ++ } ++ ++ return unmapped; ++} ++ ++static phys_addr_t iommu_xen_iova_to_phys(struct io_pgtable_ops *ops, unsigned long iova) ++{ ++ struct xen_io_pgtable *pgtable = io_pgtable_ops_to_data(ops); ++ unsigned long offset_mask, pte_pgsize; ++ u64 *pte, __pte; ++ ++ pte = fetch_pte(pgtable, iova, &pte_pgsize); ++ if (!pte || !IOMMU_PTE_PRESENT(*pte)) ++ return 0; ++ ++ offset_mask = pte_pgsize - 1; ++ __pte = *pte & PM_ADDR_MASK; ++ ++ return (__pte & ~offset_mask) | (iova & offset_mask); ++} ++ ++static void xen_free_pgtable(struct io_pgtable *iop) ++{ ++ struct xen_io_pgtable *pgtable = container_of(iop, struct xen_io_pgtable, iop); ++ ++ if (!pgtable || !pgtable->pgd) ++ return; ++ ++ /* Free page table */ ++ free_pgtable(pgtable->pgd, PAGE_MODE_5_LEVEL); ++ kfree(pgtable); ++} ++ ++static struct io_pgtable *xen_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie) ++{ ++ struct xen_io_pgtable *pgtable = kmalloc(sizeof(struct xen_io_pgtable), ++ GFP_KERNEL); ++ if (!pgtable) ++ return NULL; ++ ++ pgtable->pgd = iommu_alloc_pages_sz(GFP_KERNEL, SZ_4K); ++ if (!pgtable->pgd) { ++ kfree(pgtable); ++ return NULL; ++ } ++ ++ pgtable->iop.ops.map_pages = iommu_xen_map_pages; ++ pgtable->iop.ops.unmap_pages = iommu_xen_unmap_pages; ++ pgtable->iop.ops.iova_to_phys = iommu_xen_iova_to_phys; ++ ++ cfg->pgsize_bitmap = XEN_IOMMU_PGSIZES; ++ cfg->ias = IOMMU_IN_ADDR_BIT_SIZE; ++ cfg->oas = IOMMU_OUT_ADDR_BIT_SIZE; ++ ++ pgtable->pgtbl_cfg = *cfg; ++ ++ return &pgtable->iop; ++} ++ ++struct io_pgtable_init_fns io_pgtable_xen_init_fns = { ++ .alloc = xen_alloc_pgtable, ++ .free = xen_free_pgtable, ++}; +diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c +index 8841c1487f00..763f6c34db73 100644 +--- a/drivers/iommu/io-pgtable.c ++++ b/drivers/iommu/io-pgtable.c +@@ -32,6 +32,9 @@ io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = { + [AMD_IOMMU_V1] = &io_pgtable_amd_iommu_v1_init_fns, + [AMD_IOMMU_V2] = &io_pgtable_amd_iommu_v2_init_fns, + #endif ++#ifdef CONFIG_XEN_IOMMU ++ [XEN_IOMMU_GENERIC] = &io_pgtable_xen_init_fns, ++#endif + }; + + static int check_custom_allocator(enum io_pgtable_fmt fmt, +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +new file mode 100644 +index 000000000000..1ea66aeda096 +--- /dev/null ++++ b/drivers/iommu/xen-iommu.c +@@ -0,0 +1,446 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Xen PV-IOMMU driver. ++ * ++ * Copyright (C) 2024 Vates SAS ++ * ++ * Author: Teddy Astie ++ * ++ */ ++ ++#define pr_fmt(fmt) "xen-iommu: " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++MODULE_DESCRIPTION("Xen IOMMU driver"); ++MODULE_AUTHOR("Teddy Astie "); ++MODULE_LICENSE("GPL"); ++ ++#define MSI_RANGE_START (0xfee00000) ++#define MSI_RANGE_END (0xfeefffff) ++ ++struct xen_iommu_domain { ++ struct iommu_domain domain; ++ ++ u16 ctx_no; /* Xen PV-IOMMU context number */ ++ struct io_pgtable_ops *pgtable; /* Parralel page table for iova_to_phys */ ++}; ++ ++static struct iommu_device xen_iommu_device; ++static struct pv_iommu_capabilities caps; ++ ++static struct xen_iommu_domain xen_iommu_identity_domain; ++static unsigned long xen_iommu_pgsize_bitmap = XEN_PAGE_SIZE; ++static bool map_single_pages = false; ++ ++static inline struct xen_iommu_domain *to_xen_iommu_domain(struct iommu_domain *dom) ++{ ++ return container_of(dom, struct xen_iommu_domain, domain); ++} ++ ++static inline u64 addr_to_pfn(u64 addr) ++{ ++ return addr >> 12; ++} ++ ++static inline u64 pfn_to_addr(u64 pfn) ++{ ++ return pfn << 12; ++} ++ ++static bool xen_iommu_capable(struct device *dev, enum iommu_cap cap) ++{ ++ switch (cap) { ++ case IOMMU_CAP_CACHE_COHERENCY: ++ return true; ++ ++ default: ++ return false; ++ } ++} ++ ++static struct iommu_domain *xen_iommu_domain_alloc_paging(struct device *dev) ++{ ++ struct xen_iommu_domain *domain; ++ struct io_pgtable_cfg cfg = { .alloc = NULL, .free = NULL }; ++ struct io_pgtable_ops *pgtable; ++ int ret; ++ ++ struct pv_iommu_alloc alloc = { .alloc_flags = 0 }; ++ ++ domain = kzalloc(sizeof(*domain), GFP_KERNEL); ++ if (!domain) ++ return ERR_PTR(-ENOMEM); ++ ++ pgtable = alloc_io_pgtable_ops(XEN_IOMMU_GENERIC, &cfg, NULL); ++ if (!pgtable) { ++ kfree(domain); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ ret = HYPERVISOR_iommu_op(IOMMU_alloc_context, &alloc); ++ ++ if (ret) { ++ pr_err("Unable to create Xen IOMMU context (%d)", ret); ++ kfree(domain); ++ free_io_pgtable_ops(pgtable); ++ return ERR_PTR(ret); ++ } ++ ++ domain->ctx_no = alloc.ctx_no; ++ domain->pgtable = pgtable; ++ ++ domain->domain.pgsize_bitmap = xen_iommu_pgsize_bitmap; ++ domain->domain.geometry = (struct iommu_domain_geometry){ ++ .aperture_start = 0, ++ .aperture_end = caps.max_iova_addr, ++ .force_aperture = true, ++ }; ++ ++ return &domain->domain; ++} ++ ++static struct iommu_device *xen_iommu_probe_device(struct device *dev) ++{ ++ if (!dev_is_pci(dev)) ++ return ERR_PTR(-ENODEV); ++ ++ return &xen_iommu_device; ++} ++ ++static int xen_iommu_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) ++{ ++ int ret = 0; ++ size_t _mapped; /* for pgtable callback */ ++ struct xen_iommu_domain *dom = to_xen_iommu_domain(domain); ++ struct pv_iommu_map_pages map = { ++ .ctx_no = dom->ctx_no, ++ .pgsize = pgsize, ++ .map_flags = 0, ++ .mapped = 0, ++ }; ++ ++ /* NOTE: paddr is actually bound to pfn, not gfn */ ++ uint64_t pfn0 = addr_to_pfn(paddr); ++ uint64_t dfn0 = addr_to_pfn(iova); ++ ++ if (prot & IOMMU_READ) ++ map.map_flags |= IOMMU_MAP_readable; ++ ++ if (prot & IOMMU_WRITE) ++ map.map_flags |= IOMMU_MAP_writeable; ++ ++ if (prot & IOMMU_CACHE) ++ map.map_flags |= IOMMU_MAP_cache; ++ ++ if (map_single_pages) { ++ size_t i = 0; ++ map.nr_pages = 1; ++ ++ for (; i < pgcount; i++) { ++ map.gfn = pfn_to_gfn(pfn0 + i); ++ map.dfn = dfn0 + i; ++ map.nr_pages = 1; ++ ++ ret = HYPERVISOR_iommu_op(IOMMU_map_pages, &map); ++ ++ if (ret) ++ break; ++ } ++ } else { ++ map.nr_pages = pgcount; ++ map.gfn = pfn_to_gfn(pfn0); ++ map.dfn = dfn0; ++ ++ ret = HYPERVISOR_iommu_op(IOMMU_map_pages, &map); ++ } ++ ++ if (mapped) ++ *mapped = pgsize * map.mapped; ++ ++ dom->pgtable->map_pages(dom->pgtable, iova, paddr, pgsize, pgcount, ++ prot, gfp, &_mapped); ++ ++ return ret; ++} ++ ++static size_t xen_iommu_unmap_pages(struct iommu_domain *domain, unsigned long iova, ++ size_t pgsize, size_t pgcount, ++ struct iommu_iotlb_gather *iotlb_gather) ++{ ++ struct xen_iommu_domain *dom = to_xen_iommu_domain(domain); ++ struct pv_iommu_unmap_pages unmap = { ++ .ctx_no = dom->ctx_no, ++ .pgsize = pgsize, ++ .unmapped = 0, ++ .nr_pages = pgcount, ++ .dfn = addr_to_pfn(iova), ++ }; ++ ++ WARN_ON(HYPERVISOR_iommu_op(IOMMU_unmap_pages, &unmap)); ++ dom->pgtable->unmap_pages(dom->pgtable, iova, pgsize, pgcount, ++ iotlb_gather); ++ ++ return unmap.unmapped * pgsize; ++} ++ ++static int xen_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) ++{ ++ struct pci_dev *pdev; ++ struct xen_iommu_domain *dom = to_xen_iommu_domain(domain); ++ struct pv_iommu_reattach_device reattach = { ++ .ctx_no = dom->ctx_no, ++ .pasid = 0, ++ }; ++ ++ pdev = to_pci_dev(dev); ++ ++ reattach.dev.seg = pci_domain_nr(pdev->bus); ++ reattach.dev.bus = pdev->bus->number; ++ reattach.dev.devfn = pdev->devfn; ++ ++ return HYPERVISOR_iommu_op(IOMMU_reattach_device, &reattach); ++} ++ ++static void xen_iommu_free(struct iommu_domain *domain) ++{ ++ int ret; ++ struct xen_iommu_domain *dom = to_xen_iommu_domain(domain); ++ struct pv_iommu_free op = { ++ .ctx_no = dom->ctx_no, ++ .free_flags = 0, ++ }; ++ ++ ret = HYPERVISOR_iommu_op(IOMMU_free_context, &op); ++ ++ if (ret) ++ pr_err("Context %hu destruction failure\n", dom->ctx_no); ++ ++ free_io_pgtable_ops(dom->pgtable); ++ ++ kfree(domain); ++} ++ ++static phys_addr_t xen_iommu_iova_to_phys(struct iommu_domain *domain, dma_addr_t iova) ++{ ++ struct xen_iommu_domain *dom = to_xen_iommu_domain(domain); ++ ++ if (!dom->ctx_no) ++ /* If default domain is identity, phys_addr is iova. */ ++ return (caps.cap_flags & IOMMUCAP_default_identity) ? iova : 0; ++ ++ return dom->pgtable->iova_to_phys(dom->pgtable, iova); ++} ++ ++static void xen_iommu_get_resv_regions(struct device *dev, struct list_head *head) ++{ ++ struct iommu_resv_region *reg; ++ struct xen_reserved_device_memory *entries; ++ struct xen_reserved_device_memory_map map; ++ struct pci_dev *pdev; ++ int ret, i; ++ ++ pdev = to_pci_dev(dev); ++ ++ reg = iommu_alloc_resv_region(MSI_RANGE_START, ++ MSI_RANGE_END - MSI_RANGE_START + 1, ++ 0, IOMMU_RESV_MSI, GFP_KERNEL); ++ ++ if (!reg) ++ return; ++ ++ list_add_tail(®->list, head); ++ ++ /* Map xen-specific entries */ ++ ++ /* First, get number of entries to map */ ++ map.buffer = NULL; ++ map.nr_entries = 0; ++ map.flags = 0; ++ ++ map.dev.pci.seg = pci_domain_nr(pdev->bus); ++ map.dev.pci.bus = pdev->bus->number; ++ map.dev.pci.devfn = pdev->devfn; ++ ++ ret = HYPERVISOR_memory_op(XENMEM_reserved_device_memory_map, &map); ++ ++ if (ret == 0) ++ /* No reserved region, nothing to do */ ++ return; ++ ++ if (ret != -ENOBUFS) { ++ pr_err("Unable to get reserved region count (%d)\n", ret); ++ return; ++ } ++ ++ /* Assume a reasonable number of entries, otherwise, something is probably wrong */ ++ if (WARN_ON(map.nr_entries > 256)) ++ pr_warn("Xen reporting many reserved regions (%u)\n", map.nr_entries); ++ ++ /* And finally get actual mappings */ ++ entries = kcalloc(map.nr_entries, sizeof(struct xen_reserved_device_memory), ++ GFP_KERNEL); ++ ++ if (!entries) { ++ pr_err("No memory for map entries\n"); ++ return; ++ } ++ ++ map.buffer = entries; ++ ++ ret = HYPERVISOR_memory_op(XENMEM_reserved_device_memory_map, &map); ++ ++ if (ret != 0) { ++ pr_err("Unable to get reserved regions (%d)\n", ret); ++ kfree(entries); ++ return; ++ } ++ ++ for (i = 0; i < map.nr_entries; i++) { ++ struct xen_reserved_device_memory entry = entries[i]; ++ ++ reg = iommu_alloc_resv_region(pfn_to_addr(entry.start_pfn), ++ pfn_to_addr(entry.nr_pages), ++ 0, IOMMU_RESV_RESERVED, GFP_KERNEL); ++ ++ if (!reg) ++ break; ++ ++ list_add_tail(®->list, head); ++ } ++ ++ kfree(entries); ++} ++ ++static struct iommu_ops xen_iommu_ops = { ++ .identity_domain = &xen_iommu_identity_domain.domain, ++ .release_domain = &xen_iommu_identity_domain.domain, ++ .capable = xen_iommu_capable, ++ .domain_alloc_paging = xen_iommu_domain_alloc_paging, ++ .probe_device = xen_iommu_probe_device, ++ .device_group = pci_device_group, ++ .get_resv_regions = xen_iommu_get_resv_regions, ++ .default_domain_ops = &(const struct iommu_domain_ops) { ++ .map_pages = xen_iommu_map_pages, ++ .unmap_pages = xen_iommu_unmap_pages, ++ .attach_dev = xen_iommu_attach_dev, ++ .iova_to_phys = xen_iommu_iova_to_phys, ++ .free = xen_iommu_free, ++ }, ++}; ++ ++static int __init xen_iommu_init(void) ++{ ++ long ret; ++ ++ if (!xen_domain()) ++ return -ENODEV; ++ ++ /* Check if iommu_op is supported */ ++ if ((ret = HYPERVISOR_iommu_op(IOMMU_query_capabilities, &caps))) ++ { ++ pr_err("Unable to query capabilities (%ld)", ret); ++ return -ENODEV; /* No Xen IOMMU hardware */ ++ } ++ ++ /* If ctx_no is zero, it may be due to PV-IOMMU not being initialized. */ ++ if (!caps.max_ctx_no) ++ { ++ /* Try to initialize PV-IOMMU */ ++ struct pv_iommu_init init; ++ ++ pr_info("Got no usable context, try initializing PV-IOMMU\n"); ++ ++ /* FIXME: Don't hardcode this */ ++ init.max_ctx_no = 128; ++ init.arena_order = 12; ++ ++ pr_info("init.max_ctx_no=%hu\n", init.max_ctx_no); ++ pr_info("init.arena_order=%hu\n", init.arena_order); ++ ++ /* Try to initialize PV-IOMMU */ ++ ret = HYPERVISOR_iommu_op(IOMMU_init, &init); ++ ++ if (ret == -EACCES) { ++ /* PV-IOMMU being already initialized often means not allowed. */ ++ pr_warn("PV-IOMMU is already initialized, guest may not be allowed to use PV-IOMMU\n"); ++ return -EACCES; ++ } else if (ret) { ++ pr_err("PV-IOMMU initialization failure (%ld)", ret); ++ return ret; ++ } ++ ++ WARN_ON(HYPERVISOR_iommu_op(IOMMU_query_capabilities, &caps)); ++ } ++ ++ pr_info("Initialising Xen IOMMU driver\n"); ++ pr_info("max_ctx_no=%hu\n", caps.max_ctx_no); ++ pr_info("max_iova_addr=%llx\n", caps.max_iova_addr); ++ pr_info("pgsize_mask=%d\n", caps.pgsize_mask); ++ pr_info("default_identity=%c\n", (caps.cap_flags & IOMMUCAP_default_identity) ? 'y' : 'n'); ++ pr_info("cache=%c\n", (caps.cap_flags & IOMMUCAP_cache) ? 'y' : 'n'); ++ ++ if (caps.max_ctx_no == 0) { ++ pr_err("Unable to use IOMMU PV driver (no context available ?)\n"); ++ return -ENOTSUPP; /* Unable to use IOMMU PV ? */ ++ } ++ ++ xen_iommu_pgsize_bitmap = caps.pgsize_mask; ++ ++ if (xen_domain_type == XEN_PV_DOMAIN) ++ /* TODO: In PV domain, due to the existing pfn-gfn mapping we need to ++ * consider that under certains circonstances, we have : ++ * pfn_to_gfn(x + 1) != pfn_to_gfn(x) + 1 ++ * ++ * In these cases, we would want to separate the subop into several calls. ++ * (only doing the grouped operation when the mapping is actually contigous) ++ * Only map operation would be affected, as unmap actually uses dfn which ++ * doesn't have this kind of mapping. ++ * ++ * Force single-page operations to work arround this issue for now. ++ */ ++ map_single_pages = true; ++ ++ /* Initialize identity domain */ ++ xen_iommu_identity_domain.ctx_no = 0; ++ ++ xen_iommu_identity_domain.domain.pgsize_bitmap = xen_iommu_pgsize_bitmap; ++ xen_iommu_identity_domain.domain.geometry = (struct iommu_domain_geometry){ ++ .aperture_start = 0, ++ .aperture_end = caps.max_iova_addr, ++ .force_aperture = true, ++ }; ++ ++ ret = iommu_device_sysfs_add(&xen_iommu_device, NULL, NULL, "xen-iommu"); ++ if (ret) { ++ pr_err("Unable to add Xen IOMMU sysfs\n"); ++ return ret; ++ } ++ ++ ret = iommu_device_register(&xen_iommu_device, &xen_iommu_ops, NULL); ++ if (ret) { ++ pr_err("Unable to register Xen IOMMU device %ld\n", ret); ++ iommu_device_sysfs_remove(&xen_iommu_device); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++module_init(xen_iommu_init); +diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h +index 8a823c6f2b4a..7814114f0537 100644 +--- a/include/linux/io-pgtable.h ++++ b/include/linux/io-pgtable.h +@@ -19,6 +19,7 @@ enum io_pgtable_fmt { + AMD_IOMMU_V2, + APPLE_DART, + APPLE_DART2, ++ XEN_IOMMU_GENERIC, + IO_PGTABLE_NUM_FMTS, + }; + +@@ -325,5 +326,6 @@ extern struct io_pgtable_init_fns io_pgtable_arm_mali_lpae_init_fns; + extern struct io_pgtable_init_fns io_pgtable_amd_iommu_v1_init_fns; + extern struct io_pgtable_init_fns io_pgtable_amd_iommu_v2_init_fns; + extern struct io_pgtable_init_fns io_pgtable_apple_dart_init_fns; ++extern struct io_pgtable_init_fns io_pgtable_xen_init_fns; + + #endif /* __IO_PGTABLE_H */ +diff --git a/include/xen/interface/memory.h b/include/xen/interface/memory.h +index 1a371a825c55..c860acaf4b0e 100644 +--- a/include/xen/interface/memory.h ++++ b/include/xen/interface/memory.h +@@ -10,6 +10,7 @@ + #ifndef __XEN_PUBLIC_MEMORY_H__ + #define __XEN_PUBLIC_MEMORY_H__ + ++#include + #include + + /* +@@ -214,6 +215,38 @@ struct xen_add_to_physmap_range { + }; + DEFINE_GUEST_HANDLE_STRUCT(xen_add_to_physmap_range); + ++/* ++ * With some legacy devices, certain guest-physical addresses cannot safely ++ * be used for other purposes, e.g. to map guest RAM. This hypercall ++ * enumerates those regions so the toolstack can avoid using them. ++ */ ++#define XENMEM_reserved_device_memory_map 27 ++struct xen_reserved_device_memory { ++ xen_pfn_t start_pfn; ++ xen_ulong_t nr_pages; ++}; ++DEFINE_GUEST_HANDLE_STRUCT(xen_reserved_device_memory); ++ ++struct xen_reserved_device_memory_map { ++#define XENMEM_RDM_ALL 1 /* Request all regions (ignore dev union). */ ++ /* IN */ ++ uint32_t flags; ++ /* ++ * IN/OUT ++ * ++ * Gets set to the required number of entries when too low, ++ * signaled by error code -ERANGE. ++ */ ++ unsigned int nr_entries; ++ /* OUT */ ++ GUEST_HANDLE(xen_reserved_device_memory) buffer; ++ /* IN */ ++ union { ++ struct physdev_pci_device pci; ++ } dev; ++}; ++DEFINE_GUEST_HANDLE_STRUCT(xen_reserved_device_memory_map); ++ + /* + * Returns the pseudo-physical memory map as it was when the domain + * was started (specified by XENMEM_set_memory_map). +diff --git a/include/xen/interface/pv-iommu.h b/include/xen/interface/pv-iommu.h +new file mode 100644 +index 000000000000..a4a470319486 +--- /dev/null ++++ b/include/xen/interface/pv-iommu.h +@@ -0,0 +1,341 @@ ++/* SPDX-License-Identifier: MIT */ ++/** ++ * pv-iommu.h ++ * ++ * Paravirtualized IOMMU driver interface. ++ * ++ * Copyright (c) 2024 Teddy Astie ++ */ ++ ++#ifndef __XEN_PUBLIC_PV_IOMMU_H__ ++#define __XEN_PUBLIC_PV_IOMMU_H__ ++ ++#include "xen.h" ++#include "physdev.h" ++ ++#ifndef uint64_aligned_t ++#define uint64_aligned_t uint64_t ++#endif ++ ++#define IOMMU_DEFAULT_CONTEXT (0) ++ ++enum { ++ /* Basic cmd */ ++ IOMMU_noop = 0, ++ IOMMU_query_capabilities, ++ IOMMU_init, ++ IOMMU_alloc_context, ++ IOMMU_free_context, ++ IOMMU_reattach_device, ++ IOMMU_map_pages, ++ IOMMU_unmap_pages, ++ IOMMU_remote_cmd, ++ ++ /* Extended cmd */ ++ IOMMU_alloc_nested, /* if IOMMUCAP_nested */ ++ IOMMU_flush_nested, /* if IOMMUCAP_nested */ ++ IOMMU_attach_pasid, /* if IOMMUCAP_pasid */ ++ IOMMU_detach_pasid, /* if IOMMUCAP_pasid */ ++}; ++ ++/** ++ * Indicate if the default context is a identity mapping to domain memory. ++ * If not defined, default context blocks all DMA to domain memory. ++ */ ++#define IOMMUCAP_default_identity (1 << 0) ++ ++/** ++ * IOMMU_MAP_cache support. ++ */ ++#define IOMMUCAP_cache (1 << 1) ++ ++/** ++ * Support for IOMMU_alloc_nested. ++ */ ++#define IOMMUCAP_nested (1 << 2) ++ ++/** ++ * Support for IOMMU_attach_pasid and IOMMU_detach_pasid and pasid parameter in ++ * reattach_context. ++ */ ++#define IOMMUCAP_pasid (1 << 3) ++ ++/** ++ * Support for IOMMU_ALLOC_identity ++ */ ++#define IOMMUCAP_identity (1 << 4) ++ ++/** ++ * IOMMU_query_capabilities ++ * Query PV-IOMMU capabilities for this domain. ++ */ ++struct pv_iommu_capabilities { ++ /* ++ * OUT: Maximum device address (iova) that the guest can use for mappings. ++ */ ++ uint64_aligned_t max_iova_addr; ++ ++ /* OUT: IOMMU capabilities flags */ ++ uint32_t cap_flags; ++ ++ /* OUT: Mask of all supported page sizes. */ ++ uint32_t pgsize_mask; ++ ++ /* OUT: Maximum pasid (if IOMMUCAP_pasid) */ ++ uint32_t max_pasid; ++ ++ /* OUT: Maximum number of IOMMU context this domain can use. */ ++ uint16_t max_ctx_no; ++}; ++typedef struct pv_iommu_capabilities pv_iommu_capabilities_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_capabilities_t); ++ ++/** ++ * IOMMU_init ++ * Initialize PV-IOMMU for this domain. ++ * ++ * Fails with -EACCESS if PV-IOMMU is already initialized. ++ */ ++struct pv_iommu_init { ++ /* IN: Maximum number of IOMMU context this domain can use. */ ++ uint32_t max_ctx_no; ++ ++ /* IN: Arena size in pages (in power of two) */ ++ uint32_t arena_order; ++}; ++typedef struct pv_iommu_init pv_iommu_init_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_init_t); ++ ++/** ++ * Create a 1:1 identity mapped context to domain memory ++ * (needs IOMMUCAP_identity). ++ */ ++#define IOMMU_ALLOC_identity (1 << 0) ++ ++/** ++ * IOMMU_alloc_context ++ * Allocate an IOMMU context. ++ * Fails with -ENOSPC if no context number is available. ++ */ ++struct pv_iommu_alloc { ++ /* OUT: allocated IOMMU context number */ ++ uint16_t ctx_no; ++ ++ /* IN: allocation flags */ ++ uint32_t alloc_flags; ++}; ++typedef struct pv_iommu_alloc pv_iommu_alloc_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_alloc_t); ++ ++/** ++ * Move all devices to default context before freeing the context. ++ */ ++#define IOMMU_FREE_reattach_default (1 << 0) ++ ++/** ++ * IOMMU_free_context ++ * Destroy a IOMMU context. ++ * ++ * If IOMMU_FREE_reattach_default is specified, move all context devices to ++ * default context before destroying this context. ++ * ++ * If there are devices in the context and IOMMU_FREE_reattach_default is not ++ * specified, fail with -EBUSY. ++ * ++ * The default context can't be destroyed. ++ */ ++struct pv_iommu_free { ++ /* IN: IOMMU context number to free */ ++ uint16_t ctx_no; ++ ++ /* IN: Free operation specific flags */ ++ uint32_t free_flags; ++}; ++typedef struct pv_iommu_free pv_iommu_free_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_free_t); ++ ++/* Device has read access */ ++#define IOMMU_MAP_readable (1 << 0) ++ ++/* Device has write access */ ++#define IOMMU_MAP_writeable (1 << 1) ++ ++/* Enforce DMA coherency */ ++#define IOMMU_MAP_cache (1 << 2) ++ ++/** ++ * IOMMU_map_pages ++ * Map pages on a IOMMU context. ++ * ++ * pgsize must be supported by pgsize_mask. ++ * Fails with -EINVAL if mapping on top of another mapping. ++ * Report actually mapped page count in mapped field (regardless of failure). ++ */ ++struct pv_iommu_map_pages { ++ /* IN: IOMMU context number */ ++ uint16_t ctx_no; ++ ++ /* IN: Guest frame number */ ++ uint64_aligned_t gfn; ++ ++ /* IN: Device frame number */ ++ uint64_aligned_t dfn; ++ ++ /* IN: Map flags */ ++ uint32_t map_flags; ++ ++ /* IN: Size of pages to map */ ++ uint32_t pgsize; ++ ++ /* IN: Number of pages to map */ ++ uint32_t nr_pages; ++ ++ /* OUT: Number of pages actually mapped */ ++ uint32_t mapped; ++}; ++typedef struct pv_iommu_map_pages pv_iommu_map_pages_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_map_pages_t); ++ ++/** ++ * IOMMU_unmap_pages ++ * Unmap pages on a IOMMU context. ++ * ++ * pgsize must be supported by pgsize_mask. ++ * Report actually unmapped page count in mapped field (regardless of failure). ++ * Fails with -ENOENT when attempting to unmap a page without any mapping ++ */ ++struct pv_iommu_unmap_pages { ++ /* IN: IOMMU context number */ ++ uint16_t ctx_no; ++ ++ /* IN: Device frame number */ ++ uint64_aligned_t dfn; ++ ++ /* IN: Size of pages to unmap */ ++ uint32_t pgsize; ++ ++ /* IN: Number of pages to unmap */ ++ uint32_t nr_pages; ++ ++ /* OUT: Number of pages actually unmapped */ ++ uint32_t unmapped; ++}; ++typedef struct pv_iommu_unmap_pages pv_iommu_unmap_pages_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_unmap_pages_t); ++ ++/** ++ * IOMMU_reattach_device ++ * Reattach a device to another IOMMU context. ++ * Fails with -ENODEV if no such device exist. ++ */ ++struct pv_iommu_reattach_device { ++ /* IN: Target IOMMU context number */ ++ uint16_t ctx_no; ++ ++ /* IN: Physical device to move */ ++ struct physdev_pci_device dev; ++ ++ /* IN: PASID of the device (if IOMMUCAP_pasid) */ ++ uint32_t pasid; ++}; ++typedef struct pv_iommu_reattach_device pv_iommu_reattach_device_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_reattach_device_t); ++ ++ ++/** ++ * IOMMU_remote_cmd ++ * Do a PV-IOMMU operation on another domain. ++ * Current domain needs to be allowed to act on the target domain, otherwise ++ * fails with -EPERM. ++ */ ++struct pv_iommu_remote_cmd { ++ /* IN: Target domain to do the subop on */ ++ uint16_t domid; ++ ++ /* IN: Command to do on target domain. */ ++ uint16_t subop; ++ ++ /* INOUT: Command argument from current domain memory */ ++ GUEST_HANDLE(void) arg; ++}; ++typedef struct pv_iommu_remote_cmd pv_iommu_remote_cmd_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_remote_cmd_t); ++ ++/** ++ * IOMMU_alloc_nested ++ * Create a nested IOMMU context (needs IOMMUCAP_nested). ++ * ++ * This context uses a platform-specific page table from domain address space ++ * specified in pgtable_gfn and use it for nested translations. ++ * ++ * Explicit flushes needs to be submited with IOMMU_flush_nested on ++ * modification of the nested pagetable to ensure coherency between IOTLB and ++ * nested page table. ++ * ++ * This context can be destroyed using IOMMU_free_context. ++ * This context cannot be modified using map_pages, unmap_pages. ++ */ ++struct pv_iommu_alloc_nested { ++ /* OUT: allocated IOMMU context number */ ++ uint16_t ctx_no; ++ ++ /* IN: guest frame number of the nested page table */ ++ uint64_aligned_t pgtable_gfn; ++ ++ /* IN: nested mode flags */ ++ uint64_aligned_t nested_flags; ++}; ++typedef struct pv_iommu_alloc_nested pv_iommu_alloc_nested_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_alloc_nested_t); ++ ++/** ++ * IOMMU_flush_nested (needs IOMMUCAP_nested) ++ * Flush the IOTLB for nested translation. ++ */ ++struct pv_iommu_flush_nested { ++ /* TODO */ ++}; ++typedef struct pv_iommu_flush_nested pv_iommu_flush_nested_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_flush_nested_t); ++ ++/** ++ * IOMMU_attach_pasid (needs IOMMUCAP_pasid) ++ * Attach a new device-with-pasid to a IOMMU context. ++ * If a matching device-with-pasid already exists (globally), ++ * fail with -EEXIST. ++ * If pasid is 0, fails with -EINVAL. ++ * If physical device doesn't exist in domain, fail with -ENOENT. ++ */ ++struct pv_iommu_attach_pasid { ++ /* IN: IOMMU context to add the device-with-pasid in */ ++ uint16_t ctx_no; ++ ++ /* IN: Physical device */ ++ struct physdev_pci_device dev; ++ ++ /* IN: pasid of the device to attach */ ++ uint32_t pasid; ++}; ++typedef struct pv_iommu_attach_pasid pv_iommu_attach_pasid_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_attach_pasid_t); ++ ++/** ++ * IOMMU_detach_pasid (needs IOMMUCAP_pasid) ++ * detach a device-with-pasid. ++ * If the device-with-pasid doesn't exist or belong to the domain, ++ * fail with -ENOENT. ++ * If pasid is 0, fails with -EINVAL. ++ */ ++struct pv_iommu_detach_pasid { ++ /* IN: Physical device */ ++ struct physdev_pci_device dev; ++ ++ /* pasid of the device to detach */ ++ uint32_t pasid; ++}; ++typedef struct pv_iommu_detach_pasid pv_iommu_detach_pasid_t; ++DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_detach_pasid_t); ++ ++/* long do_iommu_op(int subop, XEN_GUEST_HANDLE_PARAM(void) arg) */ ++ ++#endif +\ No newline at end of file +diff --git a/include/xen/interface/xen.h b/include/xen/interface/xen.h +index 0ca23eca2a9c..8b1daf3fecc6 100644 +--- a/include/xen/interface/xen.h ++++ b/include/xen/interface/xen.h +@@ -65,6 +65,7 @@ + #define __HYPERVISOR_xc_reserved_op 39 /* reserved for XenClient */ + #define __HYPERVISOR_xenpmu_op 40 + #define __HYPERVISOR_dm_op 41 ++#define __HYPERVISOR_iommu_op 43 + + /* Architecture-specific hypercall definitions. */ + #define __HYPERVISOR_arch_0 48 +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0002-iommu-xen-Allow-the-PV-IOMMU-driver-in-a-guest.patch b/patches/pv-iommu-6.18/0002-iommu-xen-Allow-the-PV-IOMMU-driver-in-a-guest.patch new file mode 100644 index 00000000..6dcfadce --- /dev/null +++ b/patches/pv-iommu-6.18/0002-iommu-xen-Allow-the-PV-IOMMU-driver-in-a-guest.patch @@ -0,0 +1,69 @@ +From ba74c369c799713f6d47b9fee45ca3d16d09bb52 Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Wed, 2 Sep 2026 00:44:53 -0700 +Subject: [PATCH 02/11] iommu/xen: Allow the PV-IOMMU driver in a guest + +The driver itself was never Dom0-specific -- it only checks xen_domain() +and sends the SBDF it sees, which is what the hypervisor now translates. +The restriction lived entirely in Kconfig, so drop it to depends on XEN. + +Guests do need one adjustment. Dom0 sees the real topology, bridges +included, and Xen tracks all of it. A guest only gets the endpoints vPCI +assigned to it, sitting under an emulated bridge that has no device on the +Xen side, so don't claim bridges there; attaching a context to one can only +fail. + +A guest also has to call IOMMU_init before it has any contexts to allocate, +which the driver already does unconditionally. +--- + drivers/iommu/Kconfig | 8 +++++--- + drivers/iommu/xen-iommu.c | 10 ++++++++++ + 2 files changed, 15 insertions(+), 3 deletions(-) + +diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig +index 8ab6cd68bdcc..2e1e4a72670e 100644 +--- a/drivers/iommu/Kconfig ++++ b/drivers/iommu/Kconfig +@@ -373,13 +373,15 @@ config VIRTIO_IOMMU + + config XEN_IOMMU + bool "Xen IOMMU driver" +- depends on XEN_DOM0 ++ depends on XEN + select IOMMU_API + select IOMMU_IO_PGTABLE + help +- Xen PV-IOMMU driver for Dom0. ++ Xen PV-IOMMU driver. + +- Say Y here if you intend to run this guest as Xen Dom0. ++ Say Y here if you intend to run this kernel under Xen, either as ++ Dom0 or as a guest whose passed-through devices come through ++ vPCI. + + config SPRD_IOMMU + tristate "Unisoc IOMMU Support" +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +index 1ea66aeda096..35cbc7d7026b 100644 +--- a/drivers/iommu/xen-iommu.c ++++ b/drivers/iommu/xen-iommu.c +@@ -119,6 +119,16 @@ static struct iommu_device *xen_iommu_probe_device(struct device *dev) + if (!dev_is_pci(dev)) + return ERR_PTR(-ENODEV); + ++ /* ++ * Dom0 sees the real topology, bridges included, and Xen tracks all of ++ * it. A guest only gets the endpoints vPCI assigned to it; the bridge ++ * above them is emulated and has no device for Xen to attach a context ++ * to, so leave it alone rather than fail the attach later. ++ */ ++ if (!xen_initial_domain() && ++ to_pci_dev(dev)->hdr_type != PCI_HEADER_TYPE_NORMAL) ++ return ERR_PTR(-ENODEV); ++ + return &xen_iommu_device; + } + +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0003-iommu-xen-Give-the-identity-domain-a-type-and-ops.patch b/patches/pv-iommu-6.18/0003-iommu-xen-Give-the-identity-domain-a-type-and-ops.patch new file mode 100644 index 00000000..952fd10f --- /dev/null +++ b/patches/pv-iommu-6.18/0003-iommu-xen-Give-the-identity-domain-a-type-and-ops.patch @@ -0,0 +1,50 @@ +From 9d31f2f48b7b6fec0f2f006fd0998e8014e2d672 Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Wed, 2 Sep 2026 10:08:58 -0700 +Subject: [PATCH 03/11] iommu/xen: Give the identity domain a type and ops + +xen_iommu_identity_domain is statically allocated, and the core only fills +in type and ops for domains it allocated itself. Left at zero the type is +IOMMU_DOMAIN_BLOCKED, not identity, and the ops pointer is NULL, so +anything the core routes to this domain either does the wrong thing or +dereferences NULL. + +Name both explicitly. attach_dev is the same as for a paging domain -- the +identity domain is just context 0. +--- + drivers/iommu/xen-iommu.c | 19 ++++++++++++++++++- + 1 file changed, 18 insertions(+), 1 deletion(-) + +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +index 35cbc7d7026b..d43fd5a869a3 100644 +--- a/drivers/iommu/xen-iommu.c ++++ b/drivers/iommu/xen-iommu.c +@@ -43,7 +43,24 @@ struct xen_iommu_domain { + static struct iommu_device xen_iommu_device; + static struct pv_iommu_capabilities caps; + +-static struct xen_iommu_domain xen_iommu_identity_domain; ++static int xen_iommu_attach_dev(struct iommu_domain *domain, ++ struct device *dev); ++ ++static const struct iommu_domain_ops xen_iommu_identity_ops = { ++ .attach_dev = xen_iommu_attach_dev, ++}; ++ ++/* ++ * A statically allocated domain has to name its own type and ops; the core ++ * only fills those in for domains it allocated itself. Leaving type at 0 makes ++ * this an IOMMU_DOMAIN_BLOCKED domain with no ops at all. ++ */ ++static struct xen_iommu_domain xen_iommu_identity_domain = { ++ .domain = { ++ .type = IOMMU_DOMAIN_IDENTITY, ++ .ops = &xen_iommu_identity_ops, ++ }, ++}; + static unsigned long xen_iommu_pgsize_bitmap = XEN_PAGE_SIZE; + static bool map_single_pages = false; + +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0004-iommu-dma-Don-t-reserve-PCI-windows-under-a-paravirt.patch b/patches/pv-iommu-6.18/0004-iommu-dma-Don-t-reserve-PCI-windows-under-a-paravirt.patch new file mode 100644 index 00000000..51f1fd0a --- /dev/null +++ b/patches/pv-iommu-6.18/0004-iommu-dma-Don-t-reserve-PCI-windows-under-a-paravirt.patch @@ -0,0 +1,120 @@ +From 4b057c93075246242d30b29b3f8d854f07382c9c Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Wed, 2 Sep 2026 10:51:23 -0700 +Subject: [PATCH 04/11] iommu/dma: Don't reserve PCI windows under a + paravirtual IOMMU + +A PCI host bridge's memory windows are reserved in IOVA space so that an +IOVA cannot collide with an address the bridge would route to MMIO rather +than to memory. Under a paravirtual IOMMU that reasoning does not apply: +the IOVA space belongs to the hypervisor, is programmed by hypercall, and +is not the bridge's address space at all. + +Worse, the windows a Xen guest sees are invented. pcifront hands out +iomem_resource itself as the root bus window, and a PVH guest has no host +bridge _CRS so Linux falls back to its catch-all default, clipped only by +the guest's physical address width: + + pci_bus 0000:00: root bus resource [mem 0x00000000-0x3fffffffffff] + +Reserving that swallows the whole aperture the hypervisor reported, and +every IOVA allocation fails -- even for a single page: + + nvidia 0000:00:01.0: IOVA alloc failed: len=1 limit=7fffffffff shift=12 + granule=4096 start_pfn=1 aperture=0-7fffffffff + NVRM: GPU0 osIovaMap: failed to map allocation (status = 0x59) + +The device then never gets a DMA mapping and the driver cannot attach. This +is inert without an IOMMU, since there is no IOVA space to reserve, so it +only appears once a guest drives one. + +Let the driver owning the IOVA space opt out. Kept to a private header +rather than a flag in struct iommu_ops: adding a field there changes the +CRC of every exported symbol whose type graph reaches it, which breaks +every already-built out-of-tree module. +--- + drivers/iommu/dma-iommu.c | 14 +++++++++++++- + drivers/iommu/iommu-priv.h | 9 +++++++++ + drivers/iommu/xen-iommu.c | 7 +++++++ + 3 files changed, 29 insertions(+), 1 deletion(-) + +diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c +index b0dca7e7429a..3c2ad70d1c83 100644 +--- a/drivers/iommu/dma-iommu.c ++++ b/drivers/iommu/dma-iommu.c +@@ -28,6 +28,8 @@ + #include + #include + #include ++ ++#include "iommu-priv.h" + #include + #include + #include +@@ -558,7 +560,17 @@ static int iova_reserve_iommu_regions(struct device *dev, + LIST_HEAD(resv_regions); + int ret = 0; + +- if (dev_is_pci(dev)) { ++ /* ++ * A PCI host bridge's windows are reserved so that an IOVA cannot ++ * collide with an address the bridge would route to MMIO. Under a ++ * paravirtual IOMMU the IOVA space belongs to the hypervisor and is not ++ * the bridge's address space at all, and the windows a Xen guest sees ++ * are invented -- pcifront hands out iomem_resource itself, and a PVH ++ * guest with no host bridge _CRS gets Linux's catch-all default. There ++ * is nothing meaningful to reserve, and reserving it leaves no usable ++ * IOVA space whatsoever. ++ */ ++ if (dev_is_pci(dev) && !xen_iommu_manages_iova(dev)) { + ret = iova_reserve_pci_windows(to_pci_dev(dev), iovad); + if (ret) + return ret; +diff --git a/drivers/iommu/iommu-priv.h b/drivers/iommu/iommu-priv.h +index c95394cd03a7..292a73b1dd1d 100644 +--- a/drivers/iommu/iommu-priv.h ++++ b/drivers/iommu/iommu-priv.h +@@ -7,6 +7,15 @@ + #include + #include + ++#ifdef CONFIG_XEN_IOMMU ++bool xen_iommu_manages_iova(struct device *dev); ++#else ++static inline bool xen_iommu_manages_iova(struct device *dev) ++{ ++ return false; ++} ++#endif ++ + static inline const struct iommu_ops *dev_iommu_ops(struct device *dev) + { + /* +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +index d43fd5a869a3..56b36160b378 100644 +--- a/drivers/iommu/xen-iommu.c ++++ b/drivers/iommu/xen-iommu.c +@@ -13,6 +13,8 @@ + #include + #include + #include ++ ++#include "iommu-priv.h" + #include + #include + #include +@@ -372,6 +374,11 @@ static struct iommu_ops xen_iommu_ops = { + }, + }; + ++bool xen_iommu_manages_iova(struct device *dev) ++{ ++ return dev->iommu && dev_iommu_ops(dev) == &xen_iommu_ops; ++} ++ + static int __init xen_iommu_init(void) + { + long ret; +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0005-xen-pcifront-Let-callers-ask-for-a-device-s-machine-.patch b/patches/pv-iommu-6.18/0005-xen-pcifront-Let-callers-ask-for-a-device-s-machine-.patch new file mode 100644 index 00000000..ac176902 --- /dev/null +++ b/patches/pv-iommu-6.18/0005-xen-pcifront-Let-callers-ask-for-a-device-s-machine-.patch @@ -0,0 +1,195 @@ +From 28f6cdec0a2ecb54354a6dc1daa0807618623b3a Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Wed, 2 Sep 2026 11:26:57 -0700 +Subject: [PATCH 05/11] xen/pcifront: Let callers ask for a device's machine + BDF + +The BDF a PV guest sees for a passed-through device is invented by pciback +in the hardware domain and never reaches the hypervisor, so it cannot be +used to name the device in a hypercall. PV-IOMMU needs to. + +pciback already publishes both halves of the mapping in its xenstore +directory -- dev-N is the machine BDF, vdev-N the one this domain sees -- +and pcifront already reads vdev-N when detaching, so the mapping is right +there. Walk it and hand the machine BDF back. + +Use it in the PV-IOMMU driver for both reattach and the reserved-region +query. With vPCI the SBDF a guest sees is one Xen assigned and can +translate, so this only changes what a PV guest sends. Both end up naming a +device the hypervisor can find. +--- + drivers/iommu/xen-iommu.c | 33 +++++++++++++++++---- + drivers/pci/xen-pcifront.c | 59 ++++++++++++++++++++++++++++++++++++++ + include/xen/pci.h | 14 +++++++++ + 3 files changed, 100 insertions(+), 6 deletions(-) + +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +index 56b36160b378..7195bc76baa1 100644 +--- a/drivers/iommu/xen-iommu.c ++++ b/drivers/iommu/xen-iommu.c +@@ -14,6 +14,8 @@ + #include + #include + ++#include ++ + #include "iommu-priv.h" + #include + #include +@@ -133,6 +135,29 @@ static struct iommu_domain *xen_iommu_domain_alloc_paging(struct device *dev) + return &domain->domain; + } + ++/* ++ * Name a device the way the hypervisor knows it. With vPCI the SBDF this domain ++ * sees is one Xen assigned and can translate. A PV guest's comes from pciback ++ * instead and means nothing to Xen, so use the machine BDF pciback published ++ * alongside it. ++ */ ++static void xen_iommu_set_dev(struct physdev_pci_device *out, ++ struct pci_dev *pdev) ++{ ++ u32 sbdf; ++ ++ if (!pcifront_machine_sbdf(pdev, &sbdf)) { ++ out->seg = sbdf >> 16; ++ out->bus = (sbdf >> 8) & 0xff; ++ out->devfn = sbdf & 0xff; ++ return; ++ } ++ ++ out->seg = pci_domain_nr(pdev->bus); ++ out->bus = pdev->bus->number; ++ out->devfn = pdev->devfn; ++} ++ + static struct iommu_device *xen_iommu_probe_device(struct device *dev) + { + if (!dev_is_pci(dev)) +@@ -240,9 +265,7 @@ static int xen_iommu_attach_dev(struct iommu_domain *domain, struct device *dev) + + pdev = to_pci_dev(dev); + +- reattach.dev.seg = pci_domain_nr(pdev->bus); +- reattach.dev.bus = pdev->bus->number; +- reattach.dev.devfn = pdev->devfn; ++ xen_iommu_set_dev(&reattach.dev, pdev); + + return HYPERVISOR_iommu_op(IOMMU_reattach_device, &reattach); + } +@@ -303,9 +326,7 @@ static void xen_iommu_get_resv_regions(struct device *dev, struct list_head *hea + map.nr_entries = 0; + map.flags = 0; + +- map.dev.pci.seg = pci_domain_nr(pdev->bus); +- map.dev.pci.bus = pdev->bus->number; +- map.dev.pci.devfn = pdev->devfn; ++ xen_iommu_set_dev(&map.dev.pci, pdev); + + ret = HYPERVISOR_memory_op(XENMEM_reserved_device_memory_map, &map); + +diff --git a/drivers/pci/xen-pcifront.c b/drivers/pci/xen-pcifront.c +index 11636634ae51..2654ce573c01 100644 +--- a/drivers/pci/xen-pcifront.c ++++ b/drivers/pci/xen-pcifront.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -404,6 +405,64 @@ static int pcifront_claim_resource(struct pci_dev *dev, void *data) + return 0; + } + ++/* ++ * pciback names every assigned device twice in its xenstore directory: dev-N is ++ * the machine BDF and vdev-N is the BDF this domain sees. Only the former means ++ * anything to the hypervisor, so hand it back to callers that have to name a ++ * device to Xen -- the PV-IOMMU driver -- since the BDF a PV guest sees was ++ * invented in the backend and never reached Xen at all. ++ * ++ * Returns the machine SBDF packed as Xen expects it: seg << 16 | bus << 8 | ++ * devfn. ++ */ ++int pcifront_machine_sbdf(const struct pci_dev *dev, u32 *sbdf) ++{ ++ unsigned int domain, bus, slot, func; ++ struct pcifront_device *pdev; ++ int i, num_devs; ++ char str[64]; ++ ++ if (dev->bus->ops != &pcifront_bus_ops) ++ return -ENODEV; ++ ++ pdev = pcifront_get_pdev(dev->bus->sysdata); ++ if (!pdev) ++ return -ENODEV; ++ ++ if (xenbus_scanf(XBT_NIL, pdev->xdev->otherend, "num_devs", "%d", ++ &num_devs) != 1) ++ return -ENODEV; ++ ++ for (i = 0; i < num_devs; i++) { ++ if (snprintf(str, sizeof(str), "vdev-%d", i) >= sizeof(str) - 1) ++ return -ENODEV; ++ ++ if (xenbus_scanf(XBT_NIL, pdev->xdev->otherend, str, ++ "%x:%x:%x.%x", &domain, &bus, &slot, ++ &func) != 4) ++ continue; ++ ++ if (domain != pci_domain_nr(dev->bus) || ++ bus != dev->bus->number || ++ PCI_DEVFN(slot, func) != dev->devfn) ++ continue; ++ ++ if (snprintf(str, sizeof(str), "dev-%d", i) >= sizeof(str) - 1) ++ return -ENODEV; ++ ++ if (xenbus_scanf(XBT_NIL, pdev->xdev->otherend, str, ++ "%x:%x:%x.%x", &domain, &bus, &slot, ++ &func) != 4) ++ return -ENODEV; ++ ++ *sbdf = (domain << 16) | (bus << 8) | PCI_DEVFN(slot, func); ++ return 0; ++ } ++ ++ return -ENODEV; ++} ++EXPORT_SYMBOL_GPL(pcifront_machine_sbdf); ++ + static int pcifront_scan_bus(struct pcifront_device *pdev, + unsigned int domain, unsigned int bus, + struct pci_bus *b) +diff --git a/include/xen/pci.h b/include/xen/pci.h +index 424b8ea89ca8..b5efff694a03 100644 +--- a/include/xen/pci.h ++++ b/include/xen/pci.h +@@ -3,6 +3,20 @@ + #ifndef __XEN_PCI_H__ + #define __XEN_PCI_H__ + ++#include ++#include ++ ++struct pci_dev; ++ ++#if defined(CONFIG_XEN_PCIDEV_FRONTEND) ++int pcifront_machine_sbdf(const struct pci_dev *dev, u32 *sbdf); ++#else ++static inline int pcifront_machine_sbdf(const struct pci_dev *dev, u32 *sbdf) ++{ ++ return -ENODEV; ++} ++#endif ++ + #if defined(CONFIG_XEN_DOM0) + int xen_reset_device(const struct pci_dev *dev); + int xen_find_device_domain_owner(struct pci_dev *dev); +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0006-iommu-xen-Default-a-PV-guest-to-the-identity-context.patch b/patches/pv-iommu-6.18/0006-iommu-xen-Default-a-PV-guest-to-the-identity-context.patch new file mode 100644 index 00000000..4a44fe5f --- /dev/null +++ b/patches/pv-iommu-6.18/0006-iommu-xen-Default-a-PV-guest-to-the-identity-context.patch @@ -0,0 +1,69 @@ +From b5d8cdfb20894156c9da6642adfed05d371e9926 Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Wed, 2 Sep 2026 12:02:36 -0700 +Subject: [PATCH 06/11] iommu/xen: Default a PV guest to the identity context + +A PV guest reaches its devices through xen-swiotlb, which hands them +machine addresses. Attaching one to a translated IOMMU context as well +leaves two translations disagreeing about every address, and the DMA layer +rejects the combination outright: + + WARNING: CPU: 0 PID: 11 at kernel/dma/mapping.c:881 dma_supported+0x5e/0x80 + dma_set_mask+0x24/0xe0 + nv_set_dma_address_size+0x52/0x70 [nvidia] + nv_pci_probe+0x28f/0x1090 [nvidia] + +dma_supported() warns and returns false when a device both uses iommu-dma +and has dma_map_ops, which in a PV guest it always does. The driver's +dma_set_mask() then fails and every mapping after it is wrong; the GPU here +got as far as GspStatusQueueInit before returning NV_ERR_RESET_REQUIRED. + +Report IOMMU_DOMAIN_IDENTITY as the default domain type there, which is +context 0 and what the hypervisor already advertises as identity. Normal +DMA stays on xen-swiotlb and translated contexts can still be asked for +explicitly, which is what a guest wanting one for VFIO would do. + +Guests whose devices come through vPCI are unaffected; they have no +dma_map_ops of their own and use iommu-dma as before. +--- + drivers/iommu/xen-iommu.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +index 7195bc76baa1..f06dc957e8b0 100644 +--- a/drivers/iommu/xen-iommu.c ++++ b/drivers/iommu/xen-iommu.c +@@ -158,6 +158,22 @@ static void xen_iommu_set_dev(struct physdev_pci_device *out, + out->devfn = pdev->devfn; + } + ++/* ++ * A PV guest reaches its devices through xen-swiotlb, which hands them machine ++ * addresses. Putting a device in a translated context as well leaves two ++ * translations disagreeing about every address, and the DMA layer refuses the ++ * combination outright -- dma_supported() warns and fails once a device both ++ * uses iommu-dma and has dma_map_ops, which every PV guest does. Default to ++ * the identity context there and let translated ones be asked for explicitly. ++ */ ++static int xen_iommu_def_domain_type(struct device *dev) ++{ ++ if (xen_pv_domain()) ++ return IOMMU_DOMAIN_IDENTITY; ++ ++ return 0; ++} ++ + static struct iommu_device *xen_iommu_probe_device(struct device *dev) + { + if (!dev_is_pci(dev)) +@@ -386,6 +402,7 @@ static struct iommu_ops xen_iommu_ops = { + .probe_device = xen_iommu_probe_device, + .device_group = pci_device_group, + .get_resv_regions = xen_iommu_get_resv_regions, ++ .def_domain_type = xen_iommu_def_domain_type, + .default_domain_ops = &(const struct iommu_domain_ops) { + .map_pages = xen_iommu_map_pages, + .unmap_pages = xen_iommu_unmap_pages, +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0007-PCI-MSI-don-t-write-the-MSI-X-table-in-a-Xen-PV-gues.patch b/patches/pv-iommu-6.18/0007-PCI-MSI-don-t-write-the-MSI-X-table-in-a-Xen-PV-gues.patch new file mode 100644 index 00000000..c7871770 --- /dev/null +++ b/patches/pv-iommu-6.18/0007-PCI-MSI-don-t-write-the-MSI-X-table-in-a-Xen-PV-gues.patch @@ -0,0 +1,56 @@ +From 10aaa708318c723f223a7223c12ff61782d74ec9 Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Wed, 2 Sep 2026 20:52:03 -0700 +Subject: [PATCH 07/11] PCI/MSI: don't write the MSI-X table in a Xen PV guest + +A PV guest does not own the MSI-X table of a passed-through device. Xen +programs it when mapping the pirq and maps the table read-only, so the +mask, message and unmask writes in pci_write_msg_msix() fault. + +vfio-pci refreshes the cached message unconditionally before enabling a +vector, to undo a backdoor reset, which crashes a PV guest: + + BUG: unable to handle page fault for address: ffffc9004001d000 + #PF: supervisor write access in kernel mode + RIP: __pci_write_msi_msg+0x74/0x1f0 + vfio_msi_set_vector_signal+0x2c9/0x320 + +Return early as the is_virtual case already does; nothing in a PV guest +programs the table itself, the Xen MSI domain routes allocation through +PHYSDEVOP_map_pirq and Xen does the write. +--- + drivers/pci/msi/msi.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/drivers/pci/msi/msi.c b/drivers/pci/msi/msi.c +index 7e2332869306..c01f44195817 100644 +--- a/drivers/pci/msi/msi.c ++++ b/drivers/pci/msi/msi.c +@@ -12,6 +12,8 @@ + #include + #include + ++#include ++ + #include "../pci.h" + #include "msi.h" + +@@ -214,6 +216,15 @@ static inline void pci_write_msg_msix(struct msi_desc *desc, struct msi_msg *msg + + if (desc->pci.msi_attrib.is_virtual) + return; ++ ++ /* ++ * A PV guest never owns the MSI-X table. Xen programs it when mapping ++ * the pirq and maps it read-only, so the masking and message writes ++ * below would fault; vfio-pci refreshes the message unconditionally. ++ */ ++ if (xen_pv_domain()) ++ return; ++ + /* + * The specification mandates that the entry is masked + * when the message is modified: +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0008-iommu-xen-batch-map-subops-over-contiguous-runs.patch b/patches/pv-iommu-6.18/0008-iommu-xen-batch-map-subops-over-contiguous-runs.patch new file mode 100644 index 00000000..5264ab4e --- /dev/null +++ b/patches/pv-iommu-6.18/0008-iommu-xen-batch-map-subops-over-contiguous-runs.patch @@ -0,0 +1,104 @@ +From 50851dec86216ccead289dcc470674cea152d6d9 Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Thu, 3 Sep 2026 01:00:50 -0700 +Subject: [PATCH 08/11] iommu/xen: batch map subops over contiguous runs + +A PV domain took the single-page path unconditionally, one hypercall per +4K page, because pfn_to_gfn(x + 1) != pfn_to_gfn(x) + 1 there in general +and the batched path assumed the whole request was contiguous. Every +subop also carries an IOTLB flush on the hypervisor side, so a large +mapping costs a flush per page: a 4G premapping is a million hypercalls +and a million flushes, which no caller waits out. + +Walk the request and issue one subop per maximal run contiguous in both +dfn and gfn, as the TODO describing this asked for. Behaviour is +unchanged where pfn_to_gfn is the identity, the run then covering the +whole request. map_single_pages remains as a module parameter. +--- + drivers/iommu/xen-iommu.c | 48 +++++++++++++++++++++++---------------- + 1 file changed, 29 insertions(+), 19 deletions(-) + +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +index f06dc957e8b0..3a9bf55ecadb 100644 +--- a/drivers/iommu/xen-iommu.c ++++ b/drivers/iommu/xen-iommu.c +@@ -67,6 +67,9 @@ static struct xen_iommu_domain xen_iommu_identity_domain = { + }; + static unsigned long xen_iommu_pgsize_bitmap = XEN_PAGE_SIZE; + static bool map_single_pages = false; ++module_param(map_single_pages, bool, 0444); ++MODULE_PARM_DESC(map_single_pages, ++ "Issue one hypercall per page instead of per contiguous run"); + + static inline struct xen_iommu_domain *to_xen_iommu_domain(struct iommu_domain *dom) + { +@@ -221,7 +224,6 @@ static int xen_iommu_map_pages(struct iommu_domain *domain, unsigned long iova, + + if (map_single_pages) { + size_t i = 0; +- map.nr_pages = 1; + + for (; i < pgcount; i++) { + map.gfn = pfn_to_gfn(pfn0 + i); +@@ -234,11 +236,33 @@ static int xen_iommu_map_pages(struct iommu_domain *domain, unsigned long iova, + break; + } + } else { +- map.nr_pages = pgcount; +- map.gfn = pfn_to_gfn(pfn0); +- map.dfn = dfn0; ++ size_t done = 0; + +- ret = HYPERVISOR_iommu_op(IOMMU_map_pages, &map); ++ /* ++ * A subop covers a run of pages contiguous in both dfn and gfn. ++ * In a PV domain pfn_to_gfn(x + 1) != pfn_to_gfn(x) + 1 in ++ * general, so walk the request and issue one subop per maximal ++ * run rather than assuming the whole of it is contiguous. ++ */ ++ while (done < pgcount) { ++ uint64_t gfn0 = pfn_to_gfn(pfn0 + done); ++ size_t run = 1; ++ ++ while (done + run < pgcount && ++ pfn_to_gfn(pfn0 + done + run) == gfn0 + run) ++ run++; ++ ++ map.gfn = gfn0; ++ map.dfn = dfn0 + done; ++ map.nr_pages = run; ++ ++ ret = HYPERVISOR_iommu_op(IOMMU_map_pages, &map); ++ ++ if (ret) ++ break; ++ ++ done += run; ++ } + } + + if (mapped) +@@ -475,20 +499,6 @@ static int __init xen_iommu_init(void) + + xen_iommu_pgsize_bitmap = caps.pgsize_mask; + +- if (xen_domain_type == XEN_PV_DOMAIN) +- /* TODO: In PV domain, due to the existing pfn-gfn mapping we need to +- * consider that under certains circonstances, we have : +- * pfn_to_gfn(x + 1) != pfn_to_gfn(x) + 1 +- * +- * In these cases, we would want to separate the subop into several calls. +- * (only doing the grouped operation when the mapping is actually contigous) +- * Only map operation would be affected, as unmap actually uses dfn which +- * doesn't have this kind of mapping. +- * +- * Force single-page operations to work arround this issue for now. +- */ +- map_single_pages = true; +- + /* Initialize identity domain */ + xen_iommu_identity_domain.ctx_no = 0; + +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0009-iommu-xen-do-not-claim-the-IOMMU-itself.patch b/patches/pv-iommu-6.18/0009-iommu-xen-do-not-claim-the-IOMMU-itself.patch new file mode 100644 index 00000000..e418aaa9 --- /dev/null +++ b/patches/pv-iommu-6.18/0009-iommu-xen-do-not-claim-the-IOMMU-itself.patch @@ -0,0 +1,53 @@ +From 17bc7d257503a52576e685b5b57369efd4009b5b Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Thu, 3 Sep 2026 09:15:38 -0700 +Subject: [PATCH 09/11] iommu/xen: do not claim the IOMMU itself + +An IOMMU does not sit behind itself, so the hypervisor has no device for +it and reattach fails with -ENODEV. The IOMMU core does not allow a +driver to fail the first domain attach, so claiming it takes down +registration of the whole driver: + + WARNING at drivers/iommu/iommu.c:3037 iommu_setup_default_domain + iommu_device_register + xen_iommu_init + xen-iommu: Unable to register Xen IOMMU device -19 + +Only reachable in the initial domain, which sees the real topology; a +guest is never given the IOMMU. Skip PCI class 0806. +--- + drivers/iommu/xen-iommu.c | 11 +++++++++++ + 1 file changed, 11 insertions(+) + +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +index 3a9bf55ecadb..78ced18c4c90 100644 +--- a/drivers/iommu/xen-iommu.c ++++ b/drivers/iommu/xen-iommu.c +@@ -65,6 +65,9 @@ static struct xen_iommu_domain xen_iommu_identity_domain = { + .ops = &xen_iommu_identity_ops, + }, + }; ++/* PCI base class 08h, sub-class 06h: IOMMU. */ ++#define XEN_IOMMU_PCI_CLASS 0x0806 ++ + static unsigned long xen_iommu_pgsize_bitmap = XEN_PAGE_SIZE; + static bool map_single_pages = false; + module_param(map_single_pages, bool, 0444); +@@ -192,6 +195,14 @@ static struct iommu_device *xen_iommu_probe_device(struct device *dev) + to_pci_dev(dev)->hdr_type != PCI_HEADER_TYPE_NORMAL) + return ERR_PTR(-ENODEV); + ++ /* ++ * An IOMMU does not sit behind itself, so Xen has no device for it and ++ * a context can never be attached. Claiming it fails the first attach, ++ * which the IOMMU core treats as fatal to registering the driver. ++ */ ++ if ((to_pci_dev(dev)->class >> 8) == XEN_IOMMU_PCI_CLASS) ++ return ERR_PTR(-ENODEV); ++ + return &xen_iommu_device; + } + +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0010-x86-xen-disable-the-right-interrupt-type-when-tearin.patch b/patches/pv-iommu-6.18/0010-x86-xen-disable-the-right-interrupt-type-when-tearin.patch new file mode 100644 index 00000000..e0fcbf2b --- /dev/null +++ b/patches/pv-iommu-6.18/0010-x86-xen-disable-the-right-interrupt-type-when-tearin.patch @@ -0,0 +1,55 @@ +From 57110ab30a67bb0ed5b780b69f4d04e641ad763d Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Thu, 3 Sep 2026 10:53:59 -0700 +Subject: [PATCH 10/11] x86/xen: disable the right interrupt type when tearing + down MSI + +pci_disable_msix() clears msix_enabled in pci_msix_shutdown() before +pci_free_msi_irqs() reaches the domain teardown, so by the time +xen_pv_teardown_msi_irqs() runs the flag can no longer say which of the +two the device was using. It always took the MSI branch. + +The backend therefore never saw XEN_PCI_OP_disable_msix and left MSI-X +enabled on the real device, and a later guest asking to enable it got +-EALREADY from xen_pcibk_enable_msix(). Seen as a device usable exactly +once per assignment. + +Take the type from a descriptor instead. The descriptor lock is already +held by the caller. +--- + arch/x86/pci/xen.c | 19 ++++++++++++++++++- + 1 file changed, 18 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/pci/xen.c b/arch/x86/pci/xen.c +index b8755cde2419..de9a1c946a3f 100644 +--- a/arch/x86/pci/xen.c ++++ b/arch/x86/pci/xen.c +@@ -400,7 +400,24 @@ static void xen_teardown_msi_irqs(struct pci_dev *dev) + + static void xen_pv_teardown_msi_irqs(struct pci_dev *dev) + { +- if (dev->msix_enabled) ++ struct msi_desc *desc; ++ bool msix = false; ++ ++ /* ++ * pci_disable_msix() clears msix_enabled in pci_msix_shutdown() before ++ * freeing the irqs brings us here, so it cannot say which of the two ++ * the device was using. Ask a descriptor instead: getting this wrong ++ * leaves the backend with MSI-X still enabled on the real device, and ++ * the next attempt to enable it fails with -EALREADY. ++ * ++ * The descriptor lock is already held by pci_disable_msix(). ++ */ ++ msi_for_each_desc(desc, &dev->dev, MSI_DESC_ALL) { ++ msix = desc->pci.msi_attrib.is_msix; ++ break; ++ } ++ ++ if (msix) + xen_pci_frontend_disable_msix(dev); + else + xen_pci_frontend_disable_msi(dev); +-- +2.55.0 + diff --git a/patches/pv-iommu-6.18/0011-iommu-xen-flush-the-IOTLB-once-per-mapping-not-once-.patch b/patches/pv-iommu-6.18/0011-iommu-xen-flush-the-IOTLB-once-per-mapping-not-once-.patch new file mode 100644 index 00000000..8ab367cb --- /dev/null +++ b/patches/pv-iommu-6.18/0011-iommu-xen-flush-the-IOTLB-once-per-mapping-not-once-.patch @@ -0,0 +1,159 @@ +From 8c4bb578f2c5d44403928bc93f36f13d14c7d1e9 Mon Sep 17 00:00:00 2001 +From: Alex Zenla +Date: Thu, 3 Sep 2026 12:21:19 -0700 +Subject: [PATCH 11/11] iommu/xen: flush the IOTLB once per mapping, not once + per subop + +A map subop covers a run of frames contiguous in both dfn and gfn, and +in a PV domain gfns are machine frames, so a scattered buffer breaks +into runs of a few pages. Each subop then flushed, and against an +emulated IOMMU a flush is a command and a completion wait that costs far +more than the mapping does. Mapping several gigabytes took long enough +for callers to give up. + +The core already has somewhere to put this: ask the hypervisor to skip +the per-subop flush and issue one over the whole range from +iotlb_sync_map(). Gated on IOMMUCAP_deferred_flush so an older +hypervisor keeps flushing per subop rather than never flushing at all. +--- + drivers/iommu/xen-iommu.c | 31 +++++++++++++++++++++++++++++++ + include/xen/interface/pv-iommu.h | 32 ++++++++++++++++++++++++++++++++ + 2 files changed, 63 insertions(+) + +diff --git a/drivers/iommu/xen-iommu.c b/drivers/iommu/xen-iommu.c +index 78ced18c4c90..36ad0dbee42c 100644 +--- a/drivers/iommu/xen-iommu.c ++++ b/drivers/iommu/xen-iommu.c +@@ -69,6 +69,8 @@ static struct xen_iommu_domain xen_iommu_identity_domain = { + #define XEN_IOMMU_PCI_CLASS 0x0806 + + static unsigned long xen_iommu_pgsize_bitmap = XEN_PAGE_SIZE; ++static bool deferred_flush; ++ + static bool map_single_pages = false; + module_param(map_single_pages, bool, 0444); + MODULE_PARM_DESC(map_single_pages, +@@ -233,6 +235,14 @@ static int xen_iommu_map_pages(struct iommu_domain *domain, unsigned long iova, + if (prot & IOMMU_CACHE) + map.map_flags |= IOMMU_MAP_cache; + ++ /* ++ * A scattered buffer costs one subop per run of contiguous frames, and ++ * each one flushes. Leave the flush to iotlb_sync_map(), which pays it ++ * once for the whole mapping. ++ */ ++ if (deferred_flush) ++ map.map_flags |= IOMMU_MAP_no_flush; ++ + if (map_single_pages) { + size_t i = 0; + +@@ -285,6 +295,23 @@ static int xen_iommu_map_pages(struct iommu_domain *domain, unsigned long iova, + return ret; + } + ++static int xen_iommu_sync_map(struct iommu_domain *domain, unsigned long iova, ++ size_t size) ++{ ++ struct xen_iommu_domain *dom = to_xen_iommu_domain(domain); ++ struct pv_iommu_flush_pages flush = { ++ .ctx_no = dom->ctx_no, ++ .dfn = addr_to_pfn(iova), ++ .pgsize = PAGE_SIZE, ++ .nr_pages = size >> PAGE_SHIFT, ++ }; ++ ++ if (!deferred_flush) ++ return 0; ++ ++ return HYPERVISOR_iommu_op(IOMMU_flush_pages, &flush); ++} ++ + static size_t xen_iommu_unmap_pages(struct iommu_domain *domain, unsigned long iova, + size_t pgsize, size_t pgcount, + struct iommu_iotlb_gather *iotlb_gather) +@@ -440,6 +467,7 @@ static struct iommu_ops xen_iommu_ops = { + .def_domain_type = xen_iommu_def_domain_type, + .default_domain_ops = &(const struct iommu_domain_ops) { + .map_pages = xen_iommu_map_pages, ++ .iotlb_sync_map = xen_iommu_sync_map, + .unmap_pages = xen_iommu_unmap_pages, + .attach_dev = xen_iommu_attach_dev, + .iova_to_phys = xen_iommu_iova_to_phys, +@@ -503,6 +531,9 @@ static int __init xen_iommu_init(void) + pr_info("default_identity=%c\n", (caps.cap_flags & IOMMUCAP_default_identity) ? 'y' : 'n'); + pr_info("cache=%c\n", (caps.cap_flags & IOMMUCAP_cache) ? 'y' : 'n'); + ++ deferred_flush = caps.cap_flags & IOMMUCAP_deferred_flush; ++ pr_info("deferred_flush=%c\n", deferred_flush ? 'y' : 'n'); ++ + if (caps.max_ctx_no == 0) { + pr_err("Unable to use IOMMU PV driver (no context available ?)\n"); + return -ENOTSUPP; /* Unable to use IOMMU PV ? */ +diff --git a/include/xen/interface/pv-iommu.h b/include/xen/interface/pv-iommu.h +index a4a470319486..d4e58edd6e0d 100644 +--- a/include/xen/interface/pv-iommu.h ++++ b/include/xen/interface/pv-iommu.h +@@ -36,6 +36,7 @@ enum { + IOMMU_flush_nested, /* if IOMMUCAP_nested */ + IOMMU_attach_pasid, /* if IOMMUCAP_pasid */ + IOMMU_detach_pasid, /* if IOMMUCAP_pasid */ ++ IOMMU_flush_pages, /* if IOMMUCAP_deferred_flush */ + }; + + /** +@@ -65,6 +66,11 @@ enum { + */ + #define IOMMUCAP_identity (1 << 4) + ++/** ++ * Support for IOMMU_MAP_no_flush and IOMMU_flush_pages. ++ */ ++#define IOMMUCAP_deferred_flush (1 << 5) ++ + /** + * IOMMU_query_capabilities + * Query PV-IOMMU capabilities for this domain. +@@ -163,6 +169,12 @@ DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_free_t); + /* Enforce DMA coherency */ + #define IOMMU_MAP_cache (1 << 2) + ++/* ++ * Don't flush the IOTLB for this call. The caller must issue IOMMU_flush_pages ++ * over the range before a device uses it. ++ */ ++#define IOMMU_MAP_no_flush (1 << 3) ++ + /** + * IOMMU_map_pages + * Map pages on a IOMMU context. +@@ -193,6 +205,26 @@ struct pv_iommu_map_pages { + /* OUT: Number of pages actually mapped */ + uint32_t mapped; + }; ++ ++/** ++ * IOMMU_flush_pages ++ * Flush the IOTLB over a device frame range on a IOMMU context. ++ * ++ * Pairs with IOMMU_MAP_no_flush. ++ */ ++struct pv_iommu_flush_pages { ++ /* IN: IOMMU context number */ ++ uint16_t ctx_no; ++ ++ /* IN: Device frame number */ ++ uint64_aligned_t dfn; ++ ++ /* IN: Size of pages to flush */ ++ uint32_t pgsize; ++ ++ /* IN: Number of pages to flush */ ++ uint32_t nr_pages; ++}; + typedef struct pv_iommu_map_pages pv_iommu_map_pages_t; + DEFINE_GUEST_HANDLE_STRUCT(pv_iommu_map_pages_t); + +-- +2.55.0 +