@samitouri / QOSamiQemu / commits / c93b82e7dd

intel_iommu_accel: Handle PASID entry addition for pc_inv_dsc request

Structure VTDAddressSpace includes some elements suitable for emulated device and passthrough device without PASID, e.g., address space, different memory regions, etc, it is also protected by vtd iommu lock, all these are useless and become a burden for passthrough device with PASID. When there are lots of PASIDs used in one device, the AS and MRs are all registered to memory core and impact the whole system performance. So instead of using VTDAddressSpace to cache pasid entry for each pasid of a passthrough device, we define a light weight structure VTDAccelPASIDCacheEntry with only necessary elements for each pasid. We will use this struct as a parameter to conduct binding/unbinding to nested hwpt and to record the current bound nested hwpt. It's also designed to support IOMMU_NO_PASID. VTDAccelPASIDCacheEntry is designed to only be used in intel_iommu_accel.c, similarly VTDPASIDCacheEntry should only be used in hw/i386/intel_iommu.c When guest creates new PASID entries, QEMU will capture the pc_inv_dsc (pasid cache invalidation) request, walk through each pasid in each passthrough device for valid pasid entries, create a new VTDAccelPASIDCacheEntry if not existing yet. IOMMU_NO_PASID of passthrough device still need to register MRs in case guest does not operate in scalable mode. So for IOMMU_NO_PASID, we have both VTDPASIDCacheEntry and VTDAccelPASIDCacheEntry. Co-developed-by: Yi Liu <yi.l.liu@intel.com> Signed-off-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> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-Id: <20260527054658.1021096-12-zhenzhong.duan@intel.com>

Zhenzhong Duan committed May 27, 2026 at 01:46 UTC c93b82e7dde2e9c26ab743e80d19050bde988fdf
4 files changed +180
hw/i386/intel_iommu.c
+3
@@ -3181,6 +3181,8 @@ static void vtd_pasid_cache_sync(IntelIOMMUState *s, VTDPASIDCacheInfo *pc_info)
3181 g_hash_table_foreach(s->vtd_address_spaces, vtd_pasid_cache_sync_locked,
3182 pc_info);
3183 vtd_iommu_unlock(s);
3184 +
3185 + vtd_accel_pasid_cache_sync(s, pc_info);
3186 }
3187
3188 static void vtd_replay_pasid_bindings_all(IntelIOMMUState *s)
@@ -4751,6 +4753,7 @@ static bool vtd_dev_set_iommu_device(PCIBus *bus, void *opaque, int devfn,
4753 vtd_hiod->devfn = (uint8_t)devfn;
4754 vtd_hiod->iommu_state = s;
4755 vtd_hiod->hiod = hiod;
4756 + QLIST_INIT(&vtd_hiod->pasid_cache_list);
4757
4758 if (!vtd_check_hiod(s, vtd_hiod, errp)) {
4759 g_free(vtd_hiod);
hw/i386/intel_iommu_accel.c
+156
@@ -259,6 +259,162 @@ void vtd_flush_host_piotlb_all_locked(IntelIOMMUState *s, uint16_t domain_id,
259 vtd_flush_host_piotlb_locked, &piotlb_info);
260 }
261
262 +static void vtd_accel_fill_pc(VTDHostIOMMUDevice *vtd_hiod, uint32_t pasid,
263 + VTDPASIDEntry *pe)
264 +{
265 + VTDAccelPASIDCacheEntry *vtd_pce;
266 +
267 + QLIST_FOREACH(vtd_pce, &vtd_hiod->pasid_cache_list, next) {
268 + if (vtd_pce->pasid == pasid) {
269 + if (vtd_pasid_entry_compare(pe, &vtd_pce->pasid_entry)) {
270 + vtd_pce->pasid_entry = *pe;
271 + }
272 + return;
273 + }
274 + }
275 +
276 + vtd_pce = g_malloc0(sizeof(VTDAccelPASIDCacheEntry));
277 + vtd_pce->vtd_hiod = vtd_hiod;
278 + vtd_pce->pasid = pasid;
279 + vtd_pce->pasid_entry = *pe;
280 + QLIST_INSERT_HEAD(&vtd_hiod->pasid_cache_list, vtd_pce, next);
281 +}
282 +
283 +/*
284 + * This function walks over PASID range within [start, end) in a single
285 + * PASID table for entries matching @info type/did, then create
286 + * VTDAccelPASIDCacheEntry if not exist yet.
287 + */
288 +static void vtd_sm_pasid_table_walk_one(VTDHostIOMMUDevice *vtd_hiod,
289 + dma_addr_t pt_base, int start, int end,
290 + VTDPASIDCacheInfo *info)
291 +{
292 + IntelIOMMUState *s = vtd_hiod->iommu_state;
293 + VTDPASIDEntry pe;
294 + int pasid;
295 +
296 + for (pasid = start; pasid < end; pasid++) {
297 + if (vtd_get_pe_in_pasid_leaf_table(s, pasid, pt_base, &pe) ||
298 + !vtd_pe_present(&pe)) {
299 + continue;
300 + }
301 +
302 + if ((info->type == VTD_INV_DESC_PASIDC_G_DSI ||
303 + info->type == VTD_INV_DESC_PASIDC_G_PASID_SI) &&
304 + (info->did != VTD_SM_PASID_ENTRY_DID(&pe))) {
305 + /*
306 + * VTD_PASID_CACHE_DOMSI and VTD_PASID_CACHE_PASIDSI
307 + * requires domain id check. If domain id check fail,
308 + * go to next pasid.
309 + */
310 + continue;
311 + }
312 +
313 + vtd_accel_fill_pc(vtd_hiod, pasid, &pe);
314 + }
315 +}
316 +
317 +/*
318 + * In VT-d scalable mode translation, PASID dir + PASID table is used.
319 + * This function aims at looping over a range of PASIDs in the given
320 + * two level table to identify the pasid config in guest.
321 + */
322 +static void vtd_sm_pasid_table_walk(VTDHostIOMMUDevice *vtd_hiod,
323 + dma_addr_t pdt_base, int start, int end,
324 + VTDPASIDCacheInfo *info)
325 +{
326 + VTDPASIDDirEntry pdire;
327 + int pasid = start;
328 + int pasid_next;
329 + dma_addr_t pt_base;
330 +
331 + while (pasid < end) {
332 + pasid_next = (pasid + VTD_PASID_TABLE_ENTRY_NUM) &
333 + ~(VTD_PASID_TABLE_ENTRY_NUM - 1);
334 + pasid_next = pasid_next < end ? pasid_next : end;
335 +
336 + if (!vtd_get_pdire_from_pdir_table(pdt_base, pasid, &pdire)
337 + && vtd_pdire_present(&pdire)) {
338 + pt_base = pdire.val & VTD_PASID_TABLE_BASE_ADDR_MASK;
339 + vtd_sm_pasid_table_walk_one(vtd_hiod, pt_base, pasid, pasid_next,
340 + info);
341 + }
342 + pasid = pasid_next;
343 + }
344 +}
345 +
346 +static void vtd_accel_replay_pasid_bind_for_dev(VTDHostIOMMUDevice *vtd_hiod,
347 + int start, int end,
348 + VTDPASIDCacheInfo *pc_info)
349 +{
350 + IntelIOMMUState *s = vtd_hiod->iommu_state;
351 + VTDContextEntry ce;
352 + int dev_max_pasid = 1 << vtd_hiod->hiod->caps.max_pasid_log2;
353 +
354 + if (!vtd_dev_to_context_entry(s, pci_bus_num(vtd_hiod->bus),
355 + vtd_hiod->devfn, &ce)) {
356 + VTDPASIDCacheInfo walk_info = *pc_info;
357 + uint32_t ce_max_pasid = vtd_sm_ce_get_pdt_entry_num(&ce) *
358 + VTD_PASID_TABLE_ENTRY_NUM;
359 +
360 + end = MIN(end, MIN(dev_max_pasid, ce_max_pasid));
361 +
362 + vtd_sm_pasid_table_walk(vtd_hiod, VTD_CE_GET_PASID_DIR_TABLE(&ce),
363 + start, end, &walk_info);
364 + }
365 +}
366 +
367 +/*
368 + * This function replays the guest pasid bindings by walking the two level
369 + * guest PASID table. For each valid pasid entry, it creates an entry
370 + * VTDAccelPASIDCacheEntry dynamically if not exist yet. This entry holds
371 + * info specific to a pasid
372 + */
373 +void vtd_accel_pasid_cache_sync(IntelIOMMUState *s, VTDPASIDCacheInfo *pc_info)
374 +{
375 + int start = IOMMU_NO_PASID, end = 1 << s->pasid;
376 + VTDHostIOMMUDevice *vtd_hiod;
377 + GHashTableIter hiod_it;
378 +
379 + if (!s->fsts) {
380 + return;
381 + }
382 +
383 + switch (pc_info->type) {
384 + case VTD_INV_DESC_PASIDC_G_PASID_SI:
385 + start = pc_info->pasid;
386 + end = pc_info->pasid + 1;
387 + /* fall through */
388 + case VTD_INV_DESC_PASIDC_G_DSI:
389 + /*
390 + * loop all assigned devices, do domain id check in
391 + * vtd_sm_pasid_table_walk_one() after get pasid entry.
392 + */
393 + break;
394 + case VTD_INV_DESC_PASIDC_G_GLOBAL:
395 + /* loop all assigned devices */
396 + break;
397 + default:
398 + g_assert_not_reached();
399 + }
400 +
401 + /*
402 + * Loop all the vtd_hiod instances to sync the "pasid cache" per the
403 + * guest pasid configuration.
404 + *
405 + * VTD translation callback never accesses vtd_hiod and its corresponding
406 + * cached pasid entry, so no iommu lock needed here.
407 + */
408 + g_hash_table_iter_init(&hiod_it, s->vtd_host_iommu_dev);
409 + while (g_hash_table_iter_next(&hiod_it, NULL, (void **)&vtd_hiod)) {
410 + if (!object_dynamic_cast(OBJECT(vtd_hiod->hiod),
411 + TYPE_HOST_IOMMU_DEVICE_IOMMUFD)) {
412 + continue;
413 + }
414 + vtd_accel_replay_pasid_bind_for_dev(vtd_hiod, start, end, pc_info);
415 + }
416 +}
417 +
418 static uint64_t vtd_get_host_iommu_quirks(uint32_t type,
419 void *caps, uint32_t size)
420 {
hw/i386/intel_iommu_accel.h
+13
@@ -12,6 +12,13 @@
12 #define HW_I386_INTEL_IOMMU_ACCEL_H
13 #include CONFIG_DEVICES
14
15 +typedef struct VTDAccelPASIDCacheEntry {
16 + VTDHostIOMMUDevice *vtd_hiod;
17 + VTDPASIDEntry pasid_entry;
18 + uint32_t pasid;
19 + QLIST_ENTRY(VTDAccelPASIDCacheEntry) next;
20 +} VTDAccelPASIDCacheEntry;
21 +
22 #ifdef CONFIG_VTD_ACCEL
23 bool vtd_check_hiod_accel(IntelIOMMUState *s, VTDHostIOMMUDevice *vtd_hiod,
24 Error **errp);
@@ -20,6 +27,7 @@ bool vtd_propagate_guest_pasid(VTDAddressSpace *vtd_as, Error **errp);
27 void vtd_flush_host_piotlb_all_locked(IntelIOMMUState *s, uint16_t domain_id,
28 uint32_t pasid, hwaddr addr,
29 uint64_t npages, bool ih);
30 +void vtd_accel_pasid_cache_sync(IntelIOMMUState *s, VTDPASIDCacheInfo *pc_info);
31 void vtd_iommu_ops_update_accel(PCIIOMMUOps *ops);
32 #else
33 static inline bool vtd_check_hiod_accel(IntelIOMMUState *s,
@@ -49,6 +57,11 @@ static inline void vtd_flush_host_piotlb_all_locked(IntelIOMMUState *s,
57 {
58 }
59
60 +static inline void vtd_accel_pasid_cache_sync(IntelIOMMUState *s,
61 + VTDPASIDCacheInfo *pc_info)
62 +{
63 +}
64 +
65 static inline void vtd_iommu_ops_update_accel(PCIIOMMUOps *ops)
66 {
67 }
hw/i386/intel_iommu_internal.h
+8
@@ -615,6 +615,7 @@ typedef struct VTDRootEntry VTDRootEntry;
615 #define VTD_CTX_ENTRY_LEGACY_SIZE 16
616 #define VTD_CTX_ENTRY_SCALABLE_SIZE 32
617
618 +#define VTD_SM_CONTEXT_ENTRY_PDTS(x) extract64((x)->val[0], 9, 3)
619 #define VTD_SM_CONTEXT_ENTRY_RSVD_VAL0(aw) (0x1e0ULL | ~VTD_HAW_MASK(aw))
620 #define VTD_SM_CONTEXT_ENTRY_RSVD_VAL1 0xffffffffffe00000ULL
621 #define VTD_SM_CONTEXT_ENTRY_PRE 0x10ULL
@@ -645,6 +646,7 @@ typedef struct VTDPIOTLBInvInfo {
646 #define VTD_PASID_DIR_BITS_MASK (0x3fffULL)
647 #define VTD_PASID_DIR_INDEX(pasid) (((pasid) >> 6) & VTD_PASID_DIR_BITS_MASK)
648 #define VTD_PASID_DIR_FPD (1ULL << 1) /* Fault Processing Disable */
649 +#define VTD_PASID_TABLE_ENTRY_NUM (1ULL << 6)
650 #define VTD_PASID_TABLE_BITS_MASK (0x3fULL)
651 #define VTD_PASID_TABLE_INDEX(pasid) ((pasid) & VTD_PASID_TABLE_BITS_MASK)
652 #define VTD_PASID_ENTRY_FPD (1ULL << 1) /* Fault Processing Disable */
@@ -710,6 +712,7 @@ typedef struct VTDHostIOMMUDevice {
712 PCIBus *bus;
713 uint8_t devfn;
714 HostIOMMUDevice *hiod;
715 + QLIST_HEAD(, VTDAccelPASIDCacheEntry) pasid_cache_list;
716 } VTDHostIOMMUDevice;
717
718 /*
@@ -767,6 +770,11 @@ static inline int vtd_pasid_entry_compare(VTDPASIDEntry *p1, VTDPASIDEntry *p2)
770 return memcmp(p1, p2, sizeof(*p1));
771 }
772
773 +static inline uint32_t vtd_sm_ce_get_pdt_entry_num(VTDContextEntry *ce)
774 +{
775 + return 1U << (VTD_SM_CONTEXT_ENTRY_PDTS(ce) + 7);
776 +}
777 +
778 int vtd_get_pdire_from_pdir_table(dma_addr_t pasid_dir_base, uint32_t pasid,
779 VTDPASIDDirEntry *pdire);
780 int vtd_get_pe_in_pasid_leaf_table(IntelIOMMUState *s, uint32_t pasid,