[PATCH v2 12/26] iommu/amd: Add per-VM private IPA alloc/map helpers
From: Suravee Suthikulpanit
Date: Thu May 28 2026 - 01:19:56 EST
Guest device ID and guest domain ID tables use dedicated slots in
the vIOMMU private address (IPA) region, indexed by guest ID (GID).
Add alloc_private_vm_region() and free_private_vm_region() to
allocate backing pages, map them through viommu_pdom, flush the
private VM mapping, and tear down on VM destroy.
Export amd_iommu_iotlb_sync() and use it for nested domain
iotlb_sync so nested attach paths can flush gathered IOTLB state.
Signed-off-by: Suravee Suthikulpanit <suravee.suthikulpanit@xxxxxxx>
---
drivers/iommu/amd/amd_iommu.h | 2 ++
drivers/iommu/amd/iommu.c | 4 +--
drivers/iommu/amd/nested.c | 1 +
drivers/iommu/amd/viommu.c | 59 +++++++++++++++++++++++++++++++++++
4 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h
index 7c725e032d20..304cb9093249 100644
---
drivers/iommu/amd/amd_iommu.h | 2 ++
drivers/iommu/amd/iommu.c | 4 +--
drivers/iommu/amd/nested.c | 1 +
drivers/iommu/amd/viommu.c | 55 +++++++++++++++++++++++++++++++++++
4 files changed, 60 insertions(+), 2 deletions(-)
diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h
index 279f458becda..1d8727c16840 100644
--- a/drivers/iommu/amd/amd_iommu.h
+++ b/drivers/iommu/amd/amd_iommu.h
@@ -105,6 +105,8 @@ void amd_iommu_domain_flush_pages(struct protection_domain *domain,
void amd_iommu_dev_flush_pasid_pages(struct iommu_dev_data *dev_data,
ioasid_t pasid, u64 address, size_t size);
+void amd_iommu_iotlb_sync(struct iommu_domain *domain,
+ struct iommu_iotlb_gather *gather);
int amd_iommu_flush_private_vm_region(struct amd_iommu *iommu, struct protection_domain *pdom,
u64 address, size_t size);
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 8b441f68bc47..e0433e65cfa5 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -2729,8 +2729,8 @@ static void amd_iommu_flush_iotlb_all(struct iommu_domain *domain)
spin_unlock_irqrestore(&dom->lock, flags);
}
-static void amd_iommu_iotlb_sync(struct iommu_domain *domain,
- struct iommu_iotlb_gather *gather)
+void amd_iommu_iotlb_sync(struct iommu_domain *domain,
+ struct iommu_iotlb_gather *gather)
{
struct protection_domain *dom = to_pdomain(domain);
unsigned long flags;
diff --git a/drivers/iommu/amd/nested.c b/drivers/iommu/amd/nested.c
index 5b902598e68a..15dc57cf7c5f 100644
--- a/drivers/iommu/amd/nested.c
+++ b/drivers/iommu/amd/nested.c
@@ -291,4 +291,5 @@ static void nested_domain_free(struct iommu_domain *dom)
static const struct iommu_domain_ops nested_domain_ops = {
.attach_dev = nested_attach_device,
.free = nested_domain_free,
+ .iotlb_sync = amd_iommu_iotlb_sync,
};
diff --git a/drivers/iommu/amd/viommu.c b/drivers/iommu/amd/viommu.c
index 90ed2eb92aeb..6dcb02b12a28 100644
--- a/drivers/iommu/amd/viommu.c
+++ b/drivers/iommu/amd/viommu.c
@@ -297,3 +297,58 @@ int __init amd_viommu_init(struct amd_iommu *iommu)
return 0;
}
+
+static int __maybe_unused alloc_private_vm_region(struct amd_iommu *iommu, u64 **entry,
+ u64 base, size_t size, u16 gid)
+{
+ int ret;
+ u64 addr = base + (gid * size);
+ int nid = iommu && iommu->dev ? dev_to_node(&iommu->dev->dev) : NUMA_NO_NODE;
+
+ *entry = (void *)iommu_alloc_pages_node_sz(nid, GFP_KERNEL | __GFP_ZERO, size);
+ if (!*entry)
+ return -ENOMEM;
+
+ ret = set_memory_uc((unsigned long)*entry, size >> PAGE_SHIFT);
+ if (ret)
+ goto err_out;
+
+ pr_debug("%s: entry=%#llx(%#llx), addr=%#llx, size=%#lx\n", __func__,
+ (unsigned long long)*entry, iommu_virt_to_phys(*entry), addr, size);
+
+ ret = iommu_map(&iommu->viommu_pdom->domain, addr,
+ iommu_virt_to_phys(*entry), size,
+ IOMMU_PROT_IR | IOMMU_PROT_IW, GFP_KERNEL);
+ if (ret)
+ goto cleanup_mem_attr;
+
+ return amd_iommu_flush_private_vm_region(iommu, iommu->viommu_pdom, addr, size);
+cleanup_mem_attr:
+ set_memory_wb((unsigned long)*entry, size >> PAGE_SHIFT);
+err_out:
+ iommu_free_pages(*entry);
+ *entry = NULL;
+ return ret;
+}
+
+static void __maybe_unused free_private_vm_region(struct amd_iommu *iommu, u64 **entry,
+ u64 base, size_t size, u16 gid)
+{
+ size_t unmapped;
+ u64 addr = base + (gid * size);
+
+ pr_debug("%s: entry=%#llx(%#llx), base=%#llx, addr=%#llx, size=%#lx\n",
+ __func__, (unsigned long long)*entry,
+ iommu_virt_to_phys(*entry), base, addr, size);
+
+ if (!iommu || !iommu->viommu_pdom)
+ return;
+
+ unmapped = iommu_unmap(&iommu->viommu_pdom->domain, addr, size);
+ if (unmapped != size)
+ pr_warn("%s: unmapped %#zx of %#lx at %#llx\n", __func__, unmapped, size, addr);
+
+ set_memory_wb((unsigned long)*entry, size >> PAGE_SHIFT);
+ iommu_free_pages(*entry);
+ *entry = NULL;
+}
--
2.34.1