[PATCH 17/22] iommu/amd: Introduce vIOMMU Translation DTE

Suravee Suthikulpanit
2026-03-30T08:44:19Z

AMD vIOMMU requires hypervisor to setup a special DTE to store GPA->SPA
mapping for each VM. This is used to translate base addresses of guest
IOMMU data structures (i.g. Command Buffer, Event Log, PPR Log) and the
Completion-wait data store (as part of the Completion-wait command).

In Linux kernel, this is referred as vIOMMU Translation DTE (TransDTE),
which is unique per VM. (Note that all IOMMU instances within a PCI
segment share the same Device Table.) The device ID for the TransDTE is
referred to as TransDevID, which must be an unused PCI BDF and must be
unique for each VM.

Currently, TransDevID is specified by VMM, and passed to the IOMMU driver
via struct iommu_viommu_amd during vIOMMU initialization. The driver
programs the ID into the DeviceID field of the VFCntlMMIO Guest
Miscellaneous Control Register. The register is cleared during VM
destroy.

Signed-off-by: Suravee Suthikulpanit <suravee.suthikulpanit@amd.com>
---
drivers/iommu/amd/amd_iommu.h | 8 +++++
drivers/iommu/amd/amd_iommu_types.h | 2 ++
drivers/iommu/amd/iommu.c | 51 +++++++++++++++++++++++++++++
drivers/iommu/amd/iommufd.c | 8 +++++
drivers/iommu/amd/viommu.c | 3 ++
include/uapi/linux/iommufd.h | 2 ++
6 files changed, 74 insertions(+)

diff --git a/drivers/iommu/amd/amd_iommu.h b/drivers/iommu/amd/amd_iommu.h
index b5a54617a9a1..38694ebdb083 100644
--- a/drivers/iommu/amd/amd_iommu.h
+++ b/drivers/iommu/amd/amd_iommu.h
@@ -209,8 +209,16 @@ void amd_iommu_set_dte_v1(struct iommu_dev_data *dev_data,
void amd_iommu_update_dte(struct amd_iommu *iommu,
struct iommu_dev_data *dev_data,
struct dev_table_entry *new);
+
int amd_iommu_completion_wait(struct amd_iommu *iommu);

+void amd_iommu_set_translate_dte(struct amd_iommu *iommu, u16 gid,
+ struct protection_domain *pdom,
+ u32 devid);
+void amd_iommu_clear_translate_dte(struct amd_iommu *iommu, u16 gid, u32 devid);
+void amd_iommu_update_vfctrl_mmio_translate_devid(struct amd_iommu *iommu,
+ u16 gid, u32 trans_devid);
+
static inline void
amd_iommu_make_clear_dte(struct iommu_dev_data *dev_data, struct dev_table_entry *new)
{
diff --git a/drivers/iommu/amd/amd_iommu_types.h b/drivers/iommu/amd/amd_iommu_types.h
index c50ba5cda82d..2c4844b44caf 100644
--- a/drivers/iommu/amd/amd_iommu_types.h
+++ b/drivers/iommu/amd/amd_iommu_types.h
@@ -494,6 +494,7 @@ extern bool amdr_ivrs_remap_support;
/* VIOMMU stuff */
#define VIOMMU_VF_MMIO_ENTRY_SIZE 4096
#define VIOMMU_VFCTRL_MMIO_ENTRY_SIZE 64
+#define VIOMMU_VFCTRL_GUEST_MISC_CONTROL_OFFSET 0x10

#define VIOMMU_VF_MMIO_BASE(iommu, guestId) \
(iommu->vf_base + (guestId * VIOMMU_VF_MMIO_ENTRY_SIZE))
@@ -551,6 +552,7 @@ struct amd_iommu_viommu {

u64 *devid_table;
u64 *domid_table;
+ u16 trans_devid;
};

/*
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 8d8f4f374d5f..40a4dc219a84 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -3224,6 +3224,57 @@ static bool amd_iommu_enforce_cache_coherency(struct iommu_domain *domain)
return true;
}

+#if IS_ENABLED(CONFIG_AMD_IOMMU_IOMMUFD)
+
+void amd_iommu_update_vfctrl_mmio_translate_devid(struct amd_iommu *iommu,
+ u16 gid, u32 devid)
+{
+ writeq((devid & 0xFFFFULL) << 16,
+ VIOMMU_VFCTRL_MMIO_BASE(iommu, gid) +
+ VIOMMU_VFCTRL_GUEST_MISC_CONTROL_OFFSET);
+}
+
+void amd_iommu_set_translate_dte(struct amd_iommu *iommu, u16 gid,
+ struct protection_domain *pdom,
+ u32 devid)
+{
+ u64 tmp0 = 0ULL, tmp1 = 0ULL;
+ struct pt_iommu_amdv1_hw_info pt_info;
+ struct dev_table_entry *dev_table = get_dev_table(iommu);
+
+ pt_iommu_amdv1_hw_info(&pdom->amdv1, &pt_info);
+
+ pr_debug("%s: gid=%#x, iommu_devid=%#x, devid=%#x, host_pt_root=%#llx, mode=%#x\n",
+ __func__, gid, iommu->devid, devid, pt_info.host_pt_root, pt_info.mode);
+
+ /* Setup DTE for v1 page table at the offset specified by devid */
+ tmp0 |= FIELD_PREP(DTE_HOST_TRP, pt_info.host_pt_root >> 12);
+ tmp0 |= FIELD_PREP(DTE_MODE_MASK, pt_info.mode);
+ tmp0 |= (DTE_FLAG_IR | DTE_FLAG_IW | DTE_FLAG_TV | DTE_FLAG_V);
+ tmp1 |= FIELD_PREP(DTE_DOMID_MASK, pdom->id);
+
+ dev_table[devid].data[0] = tmp0;
+ dev_table[devid].data[1] = tmp1;
+
+ iommu_flush_dte(iommu, devid);
+ amd_iommu_completion_wait(iommu);
+}
+
+void amd_iommu_clear_translate_dte(struct amd_iommu *iommu, u16 gid, u32 devid)
+{
+ struct dev_table_entry *dev_table = get_dev_table(iommu);
+
+ pr_debug("%s: gid=%#x, iommu_devid=%#x, devid=%#x\n",
+ __func__, gid, iommu->devid, devid);
+
+ dev_table[devid].data[0] = 0ULL;
+ dev_table[devid].data[1] = 0ULL;
+
+ iommu_flush_dte(iommu, devid);
+ amd_iommu_completion_wait(iommu);
+}
+#endif /* CONFIG_AMD_IOMMU_IOMMUFD */
+
const struct iommu_ops amd_iommu_ops = {
.capable = amd_iommu_capable,
.hw_info = amd_iommufd_hw_info,
diff --git a/drivers/iommu/amd/iommufd.c b/drivers/iommu/amd/iommufd.c
index 685748a71b22..f83d69716eaa 100644
--- a/drivers/iommu/amd/iommufd.c
+++ b/drivers/iommu/amd/iommufd.c
@@ -83,6 +83,13 @@ int amd_iommufd_viommu_init(struct iommufd_viommu *viommu, struct iommu_domain *
/* Reset vIOMMU MMIOs to initialize the vIOMMU */
iommu_reset_vmmio(iommu, aviommu->gid);

+ amd_iommu_set_translate_dte(iommu, aviommu->gid, pdom, data.trans_devid);
+
+ /* Set translate devid in vfctrl mmio */
+ writeq((data.trans_devid & 0xFFFFULL) << 16,
+ VIOMMU_VFCTRL_MMIO_BASE(iommu, aviommu->gid) +
+ VIOMMU_VFCTRL_GUEST_MISC_CONTROL_OFFSET);
+
ret = amd_viommu_init_one(iommu, aviommu);
if (ret)
goto err_out;
@@ -93,6 +100,7 @@ int amd_iommufd_viommu_init(struct iommufd_viommu *viommu, struct iommu_domain *
if (ret)
goto free_mmap;

+ aviommu->trans_devid = data.trans_devid;
viommu->ops = &amd_viommu_ops;

spin_lock_irqsave(&pdom->lock, flags);
diff --git a/drivers/iommu/amd/viommu.c b/drivers/iommu/amd/viommu.c
index 28fe92ad9771..4626134893d6 100644
--- a/drivers/iommu/amd/viommu.c
+++ b/drivers/iommu/amd/viommu.c
@@ -428,6 +428,9 @@ void amd_viommu_uninit_one(struct amd_iommu *iommu, struct amd_iommu_viommu *avi
VIOMMU_DOMID_MAPPING_BASE,
VIOMMU_DOMID_MAPPING_ENTRY_SIZE,
aviommu->gid);
+
+ amd_iommu_update_vfctrl_mmio_translate_devid(iommu, aviommu->gid, 0);
+ amd_iommu_clear_translate_dte(iommu, aviommu->gid, aviommu->trans_devid);
viommu_clear_mapping(iommu, aviommu);
}

diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index eff5fbd2da6b..d9e5c5f0d997 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -1076,10 +1076,12 @@ struct iommu_viommu_tegra241_cmdqv {
/**
* struct iommu_viommu_amd - AMD vIOMMU Interface (IOMMU_VIOMMU_TYPE_AMD)
* @out_vfmmio_mmap_offset: (out) mmap offset for vIOMMU VF-MMIO
+ * @trans_devid: GPA->GVA translation device ID (host)
* @reserved: Must be zero
*/
struct iommu_viommu_amd {
__aligned_u64 out_vfmmio_mmap_offset;
+ __u32 trans_devid;
__u32 reserved; /* must be last */
};

--
2.34.1