When guest deletes PASID entries, QEMU will capture the pasid cache
invalidation request, walk through pasid_cache_list in each passthrough
device to find stale VTDAccelPASIDCacheEntry and delete them.
Co-developed-by: Yi Liu <yi.l.liu@intel.com>
Signed-off-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Tested-by: Xudong Hao <xudong.hao@intel.com>
---
hw/i386/intel_iommu_accel.c | 80 +++++++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
diff --git a/hw/i386/intel_iommu_accel.c b/hw/i386/intel_iommu_accel.c
index 46250aa75c..b951d90135 100644
--- a/hw/i386/intel_iommu_accel.c
+++ b/hw/i386/intel_iommu_accel.c
@@ -16,6 +16,28 @@
#include "hw/pci/pci_bus.h"
#include "trace.h"
+static int vtd_hiod_get_pe_from_pasid(VTDAccelPASIDCacheEntry *vtd_pce,
+ VTDPASIDEntry *pe)
+{
+ VTDHostIOMMUDevice *vtd_hiod = vtd_pce->vtd_hiod;
+ IntelIOMMUState *s = vtd_hiod->iommu_state;
+ uint32_t pasid = vtd_pce->pasid;
+ VTDContextEntry ce;
+ int ret;
+
+ if (!s->dmar_enabled || !s->root_scalable) {
+ return -VTD_FR_RTADDR_INV_TTM;
+ }
+
+ ret = vtd_dev_to_context_entry(s, pci_bus_num(vtd_hiod->bus),
+ vtd_hiod->devfn, &ce);
+ if (ret) {
+ return ret;
+ }
+
+ return vtd_ce_get_pasid_entry(s, &ce, pe, pasid);
+}
+
bool vtd_check_hiod_accel(IntelIOMMUState *s, VTDHostIOMMUDevice *vtd_hiod,
Error **errp)
{
@@ -280,6 +302,57 @@ static void vtd_accel_fill_pc(VTDHostIOMMUDevice *vtd_hiod, uint32_t pasid,
QLIST_INSERT_HEAD(&vtd_hiod->pasid_cache_list, vtd_pce, next);
}
+static void vtd_accel_delete_pc(VTDAccelPASIDCacheEntry *vtd_pce)
+{
+ QLIST_REMOVE(vtd_pce, next);
+ g_free(vtd_pce);
+}
+
+static void
+vtd_accel_pasid_cache_invalidate_one(VTDAccelPASIDCacheEntry *vtd_pce,
+ VTDPASIDCacheInfo *pc_info)
+{
+ VTDPASIDEntry pe;
+ uint16_t did;
+
+ /*
+ * VTD_INV_DESC_PASIDC_G_DSI and VTD_INV_DESC_PASIDC_G_PASID_SI require
+ * DID check. If DID doesn't match the value in cache or memory, then
+ * it's not a pasid entry we want to invalidate.
+ */
+ switch (pc_info->type) {
+ case VTD_INV_DESC_PASIDC_G_PASID_SI:
+ if (pc_info->pasid != vtd_pce->pasid) {
+ return;
+ }
+ /* Fall through */
+ case VTD_INV_DESC_PASIDC_G_DSI:
+ did = VTD_SM_PASID_ENTRY_DID(&vtd_pce->pasid_entry);
+ if (pc_info->did != did) {
+ return;
+ }
+ }
+
+ if (vtd_hiod_get_pe_from_pasid(vtd_pce, &pe)) {
+ /*
+ * No valid pasid entry in guest memory. e.g. pasid entry was modified
+ * to be either all-zero or non-present. Either case means existing
+ * pasid cache should be invalidated.
+ */
+ vtd_accel_delete_pc(vtd_pce);
+ }
+}
+
+static void vtd_accel_pasid_cache_invalidate(VTDHostIOMMUDevice *vtd_hiod,
+ VTDPASIDCacheInfo *pc_info)
+{
+ VTDAccelPASIDCacheEntry *vtd_pce, *next;
+
+ QLIST_FOREACH_SAFE(vtd_pce, &vtd_hiod->pasid_cache_list, next, next) {
+ vtd_accel_pasid_cache_invalidate_one(vtd_pce, pc_info);
+ }
+}
+
/*
* This function walks over PASID range within [start, end) in a single
* PASID table for entries matching @info type/did, then create
@@ -422,6 +495,13 @@ void vtd_accel_pasid_cache_sync(IntelIOMMUState *s, VTDPASIDCacheInfo *pc_info)
TYPE_HOST_IOMMU_DEVICE_IOMMUFD)) {
continue;
}
+
+ /*
+ * PASID entry removal is handled before addition intentionally,
+ * because it's unnecessary to iterate on an entry that will be
+ * removed.
+ */
+ vtd_accel_pasid_cache_invalidate(vtd_hiod, pc_info);
vtd_accel_replay_pasid_bind_for_dev(vtd_hiod, start, end, pc_info);
}
}
--
2.47.3