master
c 529 lines 17.8 KB
Raw
1 /*
2 * Intel IOMMU acceleration with nested translation
3 *
4 * Copyright (C) 2026 Intel Corporation.
5 *
6 * Authors: Zhenzhong Duan <zhenzhong.duan@intel.com>
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11 #include "qemu/osdep.h"
12 #include "system/iommufd.h"
13 #include "intel_iommu_internal.h"
14 #include "intel_iommu_accel.h"
15 #include "hw/core/iommu.h"
16 #include "hw/pci/pci_bus.h"
17 #include "trace.h"
18
19 bool vtd_check_hiod_accel(IntelIOMMUState *s, VTDHostIOMMUDevice *vtd_hiod,
20 Error **errp)
21 {
22 HostIOMMUDevice *hiod = vtd_hiod->hiod;
23 struct HostIOMMUDeviceCaps *caps = &hiod->caps;
24 struct iommu_hw_info_vtd *vtd = &caps->vendor_caps.vtd;
25 uint8_t hpasid = VTD_ECAP_GET_PSS(vtd->ecap_reg) + 1;
26 PCIBus *bus = vtd_hiod->bus;
27 PCIDevice *pdev = bus->devices[vtd_hiod->devfn];
28
29 if (!object_dynamic_cast(OBJECT(hiod), TYPE_HOST_IOMMU_DEVICE_IOMMUFD)) {
30 error_setg(errp, "Need IOMMUFD backend when fsts=on");
31 return false;
32 }
33
34 if (caps->type != IOMMU_HW_INFO_TYPE_INTEL_VTD) {
35 error_setg(errp, "Incompatible host platform IOMMU type %d",
36 caps->type);
37 return false;
38 }
39
40 if (s->fs1gp && !(vtd->cap_reg & VTD_CAP_FS1GP)) {
41 error_setg(errp,
42 "First stage 1GB large page is unsupported by host IOMMU");
43 return false;
44 }
45
46 /* Only do the check when host device support PASIDs */
47 if (caps->max_pasid_log2 && s->pasid > hpasid) {
48 error_setg(errp, "PASID bits size %d > host IOMMU PASID bits size %d",
49 s->pasid, hpasid);
50 return false;
51 }
52
53 if (pci_device_get_iommu_bus_devfn(pdev, &bus, NULL, NULL)) {
54 error_setg(errp, "Host device downstream to a PCI bridge is "
55 "unsupported when fsts=on");
56 return false;
57 }
58
59 return true;
60 }
61
62 VTDHostIOMMUDevice *vtd_find_hiod_iommufd(VTDAddressSpace *as)
63 {
64 IntelIOMMUState *s = as->iommu_state;
65 struct vtd_as_key key = {
66 .bus = as->bus,
67 .devfn = as->devfn,
68 };
69 VTDHostIOMMUDevice *vtd_hiod = g_hash_table_lookup(s->vtd_host_iommu_dev,
70 &key);
71
72 if (vtd_hiod && vtd_hiod->hiod &&
73 object_dynamic_cast(OBJECT(vtd_hiod->hiod),
74 TYPE_HOST_IOMMU_DEVICE_IOMMUFD)) {
75 return vtd_hiod;
76 }
77 return NULL;
78 }
79
80 static bool vtd_create_fs_hwpt(VTDHostIOMMUDevice *vtd_hiod,
81 VTDPASIDEntry *pe, uint32_t *fs_hwpt_id,
82 Error **errp)
83 {
84 HostIOMMUDeviceIOMMUFD *hiodi = HOST_IOMMU_DEVICE_IOMMUFD(vtd_hiod->hiod);
85 struct iommu_hwpt_vtd_s1 vtd = {};
86 uint32_t flags = vtd_hiod->iommu_state->pasid ? IOMMU_HWPT_ALLOC_PASID : 0;
87
88 vtd.flags = (VTD_SM_PASID_ENTRY_SRE(pe) ? IOMMU_VTD_S1_SRE : 0) |
89 (VTD_SM_PASID_ENTRY_WPE(pe) ? IOMMU_VTD_S1_WPE : 0) |
90 (VTD_SM_PASID_ENTRY_EAFE(pe) ? IOMMU_VTD_S1_EAFE : 0);
91 vtd.addr_width = vtd_pe_get_fs_aw(pe);
92 vtd.pgtbl_addr = (uint64_t)vtd_pe_get_fspt_base(pe);
93
94 return iommufd_backend_alloc_hwpt(hiodi->iommufd, hiodi->devid,
95 hiodi->hwpt_id, flags,
96 IOMMU_HWPT_DATA_VTD_S1, sizeof(vtd), &vtd,
97 fs_hwpt_id, errp);
98 }
99
100 static void vtd_destroy_old_fs_hwpt(VTDAccelPASIDCacheEntry *vtd_pce)
101 {
102 HostIOMMUDeviceIOMMUFD *hiodi =
103 HOST_IOMMU_DEVICE_IOMMUFD(vtd_pce->vtd_hiod->hiod);
104
105 if (!vtd_pce->fs_hwpt_id) {
106 return;
107 }
108 iommufd_backend_free_id(hiodi->iommufd, vtd_pce->fs_hwpt_id);
109 vtd_pce->fs_hwpt_id = 0;
110 }
111
112 static bool vtd_device_attach_iommufd(VTDAccelPASIDCacheEntry *vtd_pce,
113 Error **errp)
114 {
115 VTDHostIOMMUDevice *vtd_hiod = vtd_pce->vtd_hiod;
116 HostIOMMUDeviceIOMMUFD *hiodi = HOST_IOMMU_DEVICE_IOMMUFD(vtd_hiod->hiod);
117 VTDPASIDEntry *pe = &vtd_pce->pasid_entry;
118 uint32_t hwpt_id = hiodi->hwpt_id, pasid = vtd_pce->pasid;
119 bool ret;
120
121 /*
122 * We can get here only if fsts=on, the supported PGTT is FST or PT.
123 * Catch invalid PGTT when processing invalidation request to avoid
124 * attaching to wrong hwpt.
125 */
126 if (!vtd_pe_pgtt_is_fst(pe) && !vtd_pe_pgtt_is_pt(pe)) {
127 error_setg(errp, "Invalid PGTT type %d",
128 (uint8_t)VTD_SM_PASID_ENTRY_PGTT(pe));
129 return false;
130 }
131
132 if (vtd_pe_pgtt_is_fst(pe)) {
133 if (!vtd_create_fs_hwpt(vtd_hiod, pe, &hwpt_id, errp)) {
134 return false;
135 }
136 }
137
138 ret = host_iommu_device_iommufd_attach_hwpt(hiodi, pasid, hwpt_id, errp);
139 trace_vtd_device_attach_hwpt(hiodi->devid, pasid, hwpt_id, ret);
140 if (ret) {
141 /* Destroy old fs_hwpt if it's a replacement */
142 vtd_destroy_old_fs_hwpt(vtd_pce);
143 if (vtd_pe_pgtt_is_fst(pe)) {
144 vtd_pce->fs_hwpt_id = hwpt_id;
145 }
146 } else if (vtd_pe_pgtt_is_fst(pe)) {
147 iommufd_backend_free_id(hiodi->iommufd, hwpt_id);
148 }
149
150 return ret;
151 }
152
153 static bool vtd_device_detach_iommufd(VTDAccelPASIDCacheEntry *vtd_pce,
154 Error **errp)
155 {
156 VTDHostIOMMUDevice *vtd_hiod = vtd_pce->vtd_hiod;
157 HostIOMMUDeviceIOMMUFD *hiodi = HOST_IOMMU_DEVICE_IOMMUFD(vtd_hiod->hiod);
158
159 IntelIOMMUState *s = vtd_hiod->iommu_state;
160 uint32_t pasid = vtd_pce->pasid;
161 bool ret;
162
163 if (pasid != IOMMU_NO_PASID || (s->dmar_enabled && s->root_scalable)) {
164 ret = host_iommu_device_iommufd_detach_hwpt(hiodi, pasid, errp);
165 trace_vtd_device_detach_hwpt(hiodi->devid, pasid, ret);
166 } else {
167 /*
168 * If DMAR remapping is disabled or guest switches to legacy mode,
169 * we fallback to the default HWPT which contains shadow page table.
170 * So guest DMA could still work.
171 */
172 ret = host_iommu_device_iommufd_attach_hwpt(hiodi, IOMMU_NO_PASID,
173 hiodi->hwpt_id, errp);
174 trace_vtd_device_reattach_def_hwpt(hiodi->devid, IOMMU_NO_PASID,
175 hiodi->hwpt_id, ret);
176 }
177
178 if (ret) {
179 vtd_destroy_old_fs_hwpt(vtd_pce);
180 }
181
182 return ret;
183 }
184
185 /*
186 * This function is a loop function for the s->vtd_host_iommu_dev
187 * and vtd_hiod->pasid_cache_list lists with VTDPIOTLBInvInfo as
188 * execution filter. It propagates the piotlb invalidation to host.
189 */
190 static void vtd_flush_host_piotlb_locked(VTDAccelPASIDCacheEntry *vtd_pce,
191 VTDPIOTLBInvInfo *piotlb_info)
192 {
193 VTDHostIOMMUDevice *vtd_hiod = vtd_pce->vtd_hiod;
194 VTDPASIDEntry *pe = &vtd_pce->pasid_entry;
195 uint16_t did;
196
197 /* Nothing to do if there is no first stage HWPT attached */
198 if (!vtd_pe_pgtt_is_fst(pe)) {
199 return;
200 }
201
202 did = VTD_SM_PASID_ENTRY_DID(pe);
203
204 if (piotlb_info->domain_id == did && piotlb_info->pasid == vtd_pce->pasid) {
205 HostIOMMUDeviceIOMMUFD *hiodi =
206 HOST_IOMMU_DEVICE_IOMMUFD(vtd_hiod->hiod);
207 uint32_t entry_num = 1; /* Only implement one request for simplicity */
208 Error *local_err = NULL;
209 struct iommu_hwpt_vtd_s1_invalidate *cache = piotlb_info->inv_data;
210
211 if (!iommufd_backend_invalidate_cache(hiodi->iommufd,
212 vtd_pce->fs_hwpt_id,
213 IOMMU_HWPT_INVALIDATE_DATA_VTD_S1,
214 sizeof(*cache), &entry_num, cache,
215 &local_err)) {
216 /* Something wrong in kernel, but trying to continue */
217 error_report_err(local_err);
218 }
219 }
220 }
221
222 void vtd_flush_host_piotlb_all_locked(IntelIOMMUState *s, uint16_t domain_id,
223 uint32_t pasid, hwaddr addr,
224 uint64_t npages, bool ih)
225 {
226 struct iommu_hwpt_vtd_s1_invalidate cache_info = { 0 };
227 VTDPIOTLBInvInfo piotlb_info;
228 VTDHostIOMMUDevice *vtd_hiod;
229 GHashTableIter hiod_it;
230
231 cache_info.addr = addr;
232 cache_info.npages = npages;
233 cache_info.flags = ih ? IOMMU_VTD_INV_FLAGS_LEAF : 0;
234
235 piotlb_info.domain_id = domain_id;
236 piotlb_info.pasid = pasid;
237 piotlb_info.inv_data = &cache_info;
238
239 /*
240 * Go through each vtd_pce in vtd_hiod->pasid_cache_list for each host
241 * device, find out affected host device pasid which need host piotlb
242 * invalidation. Piotlb invalidation should check pasid cache per
243 * architecture point of view.
244 */
245 g_hash_table_iter_init(&hiod_it, s->vtd_host_iommu_dev);
246 while (g_hash_table_iter_next(&hiod_it, NULL, (void **)&vtd_hiod)) {
247 VTDAccelPASIDCacheEntry *vtd_pce;
248
249 QLIST_FOREACH(vtd_pce, &vtd_hiod->pasid_cache_list, next) {
250 vtd_flush_host_piotlb_locked(vtd_pce, &piotlb_info);
251 }
252 }
253 }
254
255 static void vtd_accel_fill_pc(VTDHostIOMMUDevice *vtd_hiod, uint32_t pasid,
256 VTDPASIDEntry *pe)
257 {
258 VTDAccelPASIDCacheEntry *vtd_pce;
259 Error *local_err = NULL;
260
261 QLIST_FOREACH(vtd_pce, &vtd_hiod->pasid_cache_list, next) {
262 if (vtd_pce->pasid == pasid) {
263 if (vtd_pasid_entry_compare(pe, &vtd_pce->pasid_entry)) {
264 vtd_pce->pasid_entry = *pe;
265
266 if (!vtd_device_attach_iommufd(vtd_pce, &local_err)) {
267 error_reportf_err(local_err, "%s",
268 "Replacing HWPT attachment failed: ");
269 }
270 }
271 return;
272 }
273 }
274
275 vtd_pce = g_malloc0(sizeof(VTDAccelPASIDCacheEntry));
276 vtd_pce->vtd_hiod = vtd_hiod;
277 vtd_pce->pasid = pasid;
278 vtd_pce->pasid_entry = *pe;
279 QLIST_INSERT_HEAD(&vtd_hiod->pasid_cache_list, vtd_pce, next);
280
281 if (!vtd_device_attach_iommufd(vtd_pce, &local_err)) {
282 error_reportf_err(local_err, "%s", "Attaching to HWPT failed: ");
283 }
284 }
285
286 static void vtd_accel_delete_pc(VTDAccelPASIDCacheEntry *vtd_pce,
287 VTDPASIDCacheInfo *pc_info)
288 {
289 Error *local_err = NULL;
290
291 if (!vtd_device_detach_iommufd(vtd_pce, &local_err)) {
292 error_reportf_err(local_err, "%s", "Detaching from HWPT failed: ");
293 }
294
295 QLIST_REMOVE(vtd_pce, next);
296 g_free(vtd_pce);
297
298 if (pc_info->type == VTD_INV_DESC_PASIDC_G_PASID_SI) {
299 pc_info->accel_pce_deleted = true;
300 }
301 }
302
303 static void
304 vtd_accel_pasid_cache_invalidate_one(VTDAccelPASIDCacheEntry *vtd_pce,
305 VTDPASIDCacheInfo *pc_info)
306 {
307 VTDHostIOMMUDevice *vtd_hiod = vtd_pce->vtd_hiod;
308 VTDPASIDEntry pe;
309 uint16_t did;
310
311 /*
312 * VTD_INV_DESC_PASIDC_G_DSI and VTD_INV_DESC_PASIDC_G_PASID_SI require
313 * DID check. If DID doesn't match the value in cache or memory, then
314 * it's not a pasid entry we want to invalidate.
315 */
316 switch (pc_info->type) {
317 case VTD_INV_DESC_PASIDC_G_PASID_SI:
318 if (pc_info->pasid != vtd_pce->pasid) {
319 return;
320 }
321 /* Fall through */
322 case VTD_INV_DESC_PASIDC_G_DSI:
323 did = VTD_SM_PASID_ENTRY_DID(&vtd_pce->pasid_entry);
324 if (pc_info->did != did) {
325 return;
326 }
327 }
328
329 if (vtd_dev_get_pe_from_pasid(vtd_hiod->iommu_state, vtd_hiod->bus,
330 vtd_hiod->devfn, vtd_pce->pasid, &pe)) {
331 /*
332 * No valid pasid entry in guest memory. e.g. pasid entry was modified
333 * to be either all-zero or non-present. Either case means existing
334 * pasid cache should be invalidated.
335 */
336 vtd_accel_delete_pc(vtd_pce, pc_info);
337 }
338 }
339
340 static void vtd_accel_pasid_cache_invalidate(VTDHostIOMMUDevice *vtd_hiod,
341 VTDPASIDCacheInfo *pc_info)
342 {
343 VTDAccelPASIDCacheEntry *vtd_pce, *next;
344
345 QLIST_FOREACH_SAFE(vtd_pce, &vtd_hiod->pasid_cache_list, next, next) {
346 vtd_accel_pasid_cache_invalidate_one(vtd_pce, pc_info);
347 }
348 }
349
350 /*
351 * This function walks over PASID range within [start, end) in a single
352 * PASID table for entries matching @info type/did, then create
353 * VTDAccelPASIDCacheEntry if not exist yet.
354 */
355 static void vtd_sm_pasid_table_walk_one(VTDHostIOMMUDevice *vtd_hiod,
356 dma_addr_t pt_base, int start, int end,
357 VTDPASIDCacheInfo *info)
358 {
359 IntelIOMMUState *s = vtd_hiod->iommu_state;
360 VTDPASIDEntry pe;
361 int pasid;
362
363 for (pasid = start; pasid < end; pasid++) {
364 if (vtd_get_pe_in_pasid_leaf_table(s, pasid, pt_base, &pe) ||
365 !vtd_pe_present(&pe)) {
366 continue;
367 }
368
369 if ((info->type == VTD_INV_DESC_PASIDC_G_DSI ||
370 info->type == VTD_INV_DESC_PASIDC_G_PASID_SI) &&
371 (info->did != VTD_SM_PASID_ENTRY_DID(&pe))) {
372 /*
373 * VTD_PASID_CACHE_DOMSI and VTD_PASID_CACHE_PASIDSI
374 * requires domain id check. If domain id check fail,
375 * go to next pasid.
376 */
377 continue;
378 }
379
380 vtd_accel_fill_pc(vtd_hiod, pasid, &pe);
381 }
382 }
383
384 /*
385 * In VT-d scalable mode translation, PASID dir + PASID table is used.
386 * This function aims at looping over a range of PASIDs in the given
387 * two level table to identify the pasid config in guest.
388 */
389 static void vtd_sm_pasid_table_walk(VTDHostIOMMUDevice *vtd_hiod,
390 dma_addr_t pdt_base, int start, int end,
391 VTDPASIDCacheInfo *info)
392 {
393 VTDPASIDDirEntry pdire;
394 int pasid = start;
395 int pasid_next;
396 dma_addr_t pt_base;
397
398 while (pasid < end) {
399 pasid_next = (pasid + VTD_PASID_TABLE_ENTRY_NUM) &
400 ~(VTD_PASID_TABLE_ENTRY_NUM - 1);
401 pasid_next = pasid_next < end ? pasid_next : end;
402
403 if (!vtd_get_pdire_from_pdir_table(pdt_base, pasid, &pdire)
404 && vtd_pdire_present(&pdire)) {
405 pt_base = pdire.val & VTD_PASID_TABLE_BASE_ADDR_MASK;
406 vtd_sm_pasid_table_walk_one(vtd_hiod, pt_base, pasid, pasid_next,
407 info);
408 }
409 pasid = pasid_next;
410 }
411 }
412
413 static void vtd_accel_replay_pasid_bind_for_dev(VTDHostIOMMUDevice *vtd_hiod,
414 int start, int end,
415 VTDPASIDCacheInfo *pc_info)
416 {
417 IntelIOMMUState *s = vtd_hiod->iommu_state;
418 VTDContextEntry ce;
419 int dev_max_pasid = 1 << vtd_hiod->hiod->caps.max_pasid_log2;
420
421 if (!vtd_dev_to_context_entry(s, pci_bus_num(vtd_hiod->bus),
422 vtd_hiod->devfn, &ce)) {
423 VTDPASIDCacheInfo walk_info = *pc_info;
424 uint32_t ce_max_pasid = vtd_sm_ce_get_pdt_entry_num(&ce) *
425 VTD_PASID_TABLE_ENTRY_NUM;
426
427 end = MIN(end, MIN(dev_max_pasid, ce_max_pasid));
428
429 vtd_sm_pasid_table_walk(vtd_hiod, VTD_CE_GET_PASID_DIR_TABLE(&ce),
430 start, end, &walk_info);
431 }
432 }
433
434 /*
435 * This function replays the guest pasid bindings by walking the two level
436 * guest PASID table. For each valid pasid entry, it creates an entry
437 * VTDAccelPASIDCacheEntry dynamically if not exist yet. This entry holds
438 * info specific to a pasid
439 */
440 void vtd_accel_pasid_cache_sync(IntelIOMMUState *s, VTDPASIDCacheInfo *pc_info)
441 {
442 int start = IOMMU_NO_PASID, end = 1 << s->pasid;
443 VTDHostIOMMUDevice *vtd_hiod;
444 GHashTableIter hiod_it;
445
446 if (!s->fsts) {
447 return;
448 }
449
450 switch (pc_info->type) {
451 case VTD_INV_DESC_PASIDC_G_PASID_SI:
452 start = pc_info->pasid;
453 end = pc_info->pasid + 1;
454 /* fall through */
455 case VTD_INV_DESC_PASIDC_G_DSI:
456 /*
457 * loop all assigned devices, do domain id check in
458 * vtd_sm_pasid_table_walk_one() after get pasid entry.
459 */
460 break;
461 case VTD_INV_DESC_PASIDC_G_GLOBAL:
462 /* loop all assigned devices */
463 break;
464 default:
465 g_assert_not_reached();
466 }
467
468 /*
469 * Loop all the vtd_hiod instances to sync the "pasid cache" per the
470 * guest pasid configuration.
471 *
472 * VTD translation callback never accesses vtd_hiod and its corresponding
473 * cached pasid entry, so no iommu lock needed here.
474 */
475 g_hash_table_iter_init(&hiod_it, s->vtd_host_iommu_dev);
476 while (g_hash_table_iter_next(&hiod_it, NULL, (void **)&vtd_hiod)) {
477 if (!object_dynamic_cast(OBJECT(vtd_hiod->hiod),
478 TYPE_HOST_IOMMU_DEVICE_IOMMUFD)) {
479 continue;
480 }
481
482 /*
483 * The replay path inevitably needs to iterate through existing
484 * PASID cache entries. Since cached PASID entries that are marked
485 * for removal don't need to be iterated, we intentionally handle
486 * removals before additions to optimize the replay process.
487 */
488 vtd_accel_pasid_cache_invalidate(vtd_hiod, pc_info);
489
490 if (pc_info->accel_pce_deleted) {
491 pc_info->accel_pce_deleted = false;
492 } else {
493 vtd_accel_replay_pasid_bind_for_dev(vtd_hiod, start, end, pc_info);
494 }
495 }
496 }
497
498 /* Fake a global pasid cache invalidation to remove all pasid cache entries */
499 void vtd_accel_pasid_cache_reset(IntelIOMMUState *s)
500 {
501 VTDPASIDCacheInfo pc_info = { .type = VTD_INV_DESC_PASIDC_G_GLOBAL };
502 VTDHostIOMMUDevice *vtd_hiod;
503 GHashTableIter hiod_it;
504
505 g_hash_table_iter_init(&hiod_it, s->vtd_host_iommu_dev);
506 while (g_hash_table_iter_next(&hiod_it, NULL, (void **)&vtd_hiod)) {
507 vtd_accel_pasid_cache_invalidate(vtd_hiod, &pc_info);
508 }
509 }
510
511 static uint64_t vtd_get_host_iommu_quirks(uint32_t type,
512 void *caps, uint32_t size)
513 {
514 struct iommu_hw_info_vtd *vtd = caps;
515 uint64_t quirks = 0;
516
517 if (type == IOMMU_HW_INFO_TYPE_INTEL_VTD &&
518 sizeof(struct iommu_hw_info_vtd) <= size &&
519 vtd->flags & IOMMU_HW_INFO_VTD_ERRATA_772415_SPR17) {
520 quirks |= HOST_IOMMU_QUIRK_NESTING_PARENT_BYPASS_RO;
521 }
522
523 return quirks;
524 }
525
526 void vtd_iommu_ops_update_accel(PCIIOMMUOps *ops)
527 {
528 ops->get_host_iommu_quirks = vtd_get_host_iommu_quirks;
529 }