master
c 639 lines 20.5 KB
Raw
1 /*
2 * QOS Intel IOMMU (VT-d) Module Implementation
3 *
4 * This module provides Intel IOMMU-specific helper functions for libqos tests.
5 *
6 * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11 #include "qemu/osdep.h"
12 #include "hw/i386/intel_iommu_internal.h"
13 #include "tests/qtest/libqos/pci.h"
14 #include "tests/qtest/libqos/pci-pc.h"
15 #include "qos-iommu-testdev.h"
16 #include "qos-intel-iommu.h"
17
18 /* Bounded poll for the invalidation-wait Status Write. */
19 #define QVTD_INV_WAIT_POLL_MAX_ITERS 1000
20 #define QVTD_INV_WAIT_POLL_INTERVAL_US 1000
21
22 #define QVTD_AW_48BIT_ENCODING 2
23
24 uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx)
25 {
26 return ctx->config.expected_result;
27 }
28
29 uint32_t qvtd_build_dma_attrs(void)
30 {
31 /*
32 * VT-d obtains the Requester ID (Source ID) from PCI bus/devfn routing
33 * via pci_device_iommu_address_space(), not from DMA attributes.
34 *
35 * For scalable mode, iommu-testdev does not set MemTxAttrs.pid,
36 * so the device's VTDAddressSpace has pasid=PCI_NO_PASID.
37 * vtd_do_iommu_translate() remaps PCI_NO_PASID to PASID_0
38 * when root_scalable is set, which matches the PASID=0 entry
39 * we configure in qvtd_build_pasid_table_entry().
40 */
41 return 0;
42 }
43
44 static void qvtd_build_root_entry(QTestState *qts, uint8_t bus,
45 uint64_t context_table_ptr,
46 QVTDTransMode mode)
47 {
48 uint64_t root_entry_addr = QVTD_ROOT_TABLE_BASE +
49 (bus * sizeof(VTDRootEntry));
50 uint64_t lo, hi;
51
52 if (qvtd_is_scalable(mode)) {
53 /*
54 * Scalable-mode Root Entry (Section 9.2):
55 * lo = Lower Context Table Pointer + LP (Lower Present)
56 * hi = Upper Context Table Pointer + UP (Upper Present)
57 *
58 * Lower table covers devfn 0-127, Upper covers devfn 128-255.
59 * Only lower half is needed for test device (devfn < 128).
60 */
61 lo = (context_table_ptr & VTD_ROOT_ENTRY_CTP) | VTD_ROOT_ENTRY_P;
62 hi = 0; /* UP=0: upper context table not present */
63 } else {
64 /*
65 * Legacy Root Entry (Section 9.1):
66 * lo = Context Table Pointer + Present
67 * hi = Reserved
68 */
69 lo = (context_table_ptr & VTD_ROOT_ENTRY_CTP) | VTD_ROOT_ENTRY_P;
70 hi = 0;
71 }
72
73 qtest_writeq(qts, root_entry_addr, lo);
74 qtest_writeq(qts, root_entry_addr + 8, hi);
75 }
76
77 static void qvtd_build_context_entry(QTestState *qts, uint16_t sid,
78 QVTDTransMode mode, uint64_t ssptptr)
79 {
80 uint8_t devfn = sid & 0xff;
81 uint64_t context_entry_addr = QVTD_CONTEXT_TABLE_BASE +
82 (devfn * VTD_CTX_ENTRY_LEGACY_SIZE);
83 uint64_t lo, hi;
84
85 if (mode == QVTD_TM_LEGACY_PT) {
86 /*
87 * Pass-through mode (Section 9.3):
88 * lo: P + FPD(=0, fault enabled) + TT(=Pass-through)
89 * hi: DID + AW
90 */
91 lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_PASS_THROUGH;
92 hi = ((uint64_t)QVTD_DOMAIN_ID << 8) | QVTD_AW_48BIT_ENCODING;
93 } else {
94 /*
95 * Translated mode (Section 9.3):
96 * lo: P + FPD(=0, fault enabled) + TT(=Multi-level) + SSPTPTR
97 * hi: DID + AW(=48-bit, 4-level)
98 */
99 lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_MULTI_LEVEL |
100 (ssptptr & VTD_CONTEXT_ENTRY_SSPTPTR);
101 hi = ((uint64_t)QVTD_DOMAIN_ID << 8) | QVTD_AW_48BIT_ENCODING;
102 }
103
104 qtest_writeq(qts, context_entry_addr, lo);
105 qtest_writeq(qts, context_entry_addr + 8, hi);
106 }
107
108 static void qvtd_build_scalable_context_entry(QTestState *qts, uint16_t sid)
109 {
110 uint8_t devfn = sid & 0xff;
111 uint64_t ce_addr = QVTD_CONTEXT_TABLE_BASE +
112 (devfn * VTD_CTX_ENTRY_SCALABLE_SIZE);
113
114 /*
115 * Scalable-Mode Context Entry (Section 9.4), 32 bytes = 4 qwords:
116 *
117 * val[0]: P + FPD(=0) + DTE(=0) + PASIDE(=0) + PRE(=0) + HPTE(=0)
118 * + EPTR(=0) + PDTS(=0) + PASIDDIRPTR
119 * val[1]: RID_PASID(=0) + PDTTE(=0) + PRE(=0) + RID_CG(=0)
120 * val[2]: Reserved (must be 0)
121 * val[3]: Reserved (must be 0)
122 */
123 qtest_writeq(qts, ce_addr,
124 (QVTD_PASID_DIR_BASE & VTD_PASID_DIR_BASE_ADDR_MASK) |
125 VTD_CONTEXT_ENTRY_P);
126 qtest_writeq(qts, ce_addr + 8, 0);
127 qtest_writeq(qts, ce_addr + 16, 0);
128 qtest_writeq(qts, ce_addr + 24, 0);
129 }
130
131 static void qvtd_build_pasid_dir_entry(QTestState *qts)
132 {
133 uint64_t addr = QVTD_PASID_DIR_BASE +
134 VTD_PASID_DIR_INDEX(0) * VTD_PASID_DIR_ENTRY_SIZE;
135
136 /*
137 * PASID Directory Entry (Section 9.5):
138 * P + FPD(=0, fault enabled) + SMPTBLPTR
139 */
140 qtest_writeq(qts, addr,
141 (QVTD_PASID_TABLE_BASE & VTD_PASID_TABLE_BASE_ADDR_MASK) |
142 VTD_PASID_ENTRY_P);
143 }
144
145 static void qvtd_build_pasid_table_entry(QTestState *qts, QVTDTransMode mode,
146 uint64_t ptptr)
147 {
148 uint64_t addr = QVTD_PASID_TABLE_BASE +
149 VTD_PASID_TABLE_INDEX(0) * VTD_PASID_ENTRY_SIZE;
150 uint64_t val0, val1, val2;
151
152 /*
153 * Scalable-Mode PASID Table Entry (Section 9.6), 64 bytes = 8 qwords:
154 *
155 * val[0]: P + FPD(=0) + AW + PGTT + SSADE(=0) + SSPTPTR
156 * val[1]: DID + PWSNP(=0) + PGSNP(=0)
157 * + CD(=0) + EMTE(=0) + PAT(=0): Memory Type,
158 * all Reserved(0) since QEMU ECAP.MTS=0
159 * val[2]: SRE(=0) + FSPM(=0, 4-level) + WPE(=0) + IGN + EAFE(=0) + FSPTPTR
160 * val[3]: Reserved (must be 0)
161 * val[4]: HPT fields, Reserved(0) since QEMU ECAP.HPTS=0
162 * val[5]: HPT fields, Reserved(0) since QEMU ECAP.HPTS=0
163 * val[6]: Reserved (must be 0)
164 * val[7]: Reserved (must be 0)
165 */
166 switch (mode) {
167 case QVTD_TM_SCALABLE_PT:
168 val0 = VTD_PASID_ENTRY_P |
169 ((uint64_t)VTD_SM_PASID_ENTRY_PT << 6);
170 val1 = (uint64_t)QVTD_DOMAIN_ID;
171 val2 = 0;
172 break;
173 case QVTD_TM_SCALABLE_SLT:
174 val0 = VTD_PASID_ENTRY_P |
175 ((uint64_t)VTD_SM_PASID_ENTRY_SST << 6) |
176 ((uint64_t)QVTD_AW_48BIT_ENCODING << 2) |
177 (ptptr & VTD_SM_PASID_ENTRY_SSPTPTR);
178 val1 = (uint64_t)QVTD_DOMAIN_ID;
179 val2 = 0;
180 break;
181 case QVTD_TM_SCALABLE_FLT:
182 /*
183 * val[2] fields for FLT (Section 9.6):
184 * SRE(=0, user-level DMA only) + FSPM(=0, 4-level) +
185 * WPE(=0, no supervisor write-protect) + IGN + EAFE(=0) + FSPTPTR
186 */
187 val0 = VTD_PASID_ENTRY_P |
188 ((uint64_t)VTD_SM_PASID_ENTRY_FST << 6);
189 val1 = (uint64_t)QVTD_DOMAIN_ID;
190 val2 = ptptr & QVTD_SM_PASID_ENTRY_FSPTPTR;
191 break;
192 default:
193 g_assert_not_reached();
194 }
195
196 qtest_writeq(qts, addr, val0);
197 qtest_writeq(qts, addr + 8, val1);
198 qtest_writeq(qts, addr + 16, val2);
199 qtest_writeq(qts, addr + 24, 0);
200 qtest_writeq(qts, addr + 32, 0);
201 qtest_writeq(qts, addr + 40, 0);
202 qtest_writeq(qts, addr + 48, 0);
203 qtest_writeq(qts, addr + 56, 0);
204 }
205
206 /*
207 * VT-d second-level paging helpers.
208 * 4-level, 48-bit address space, 9 bits per level index.
209 */
210 static uint32_t qvtd_get_table_index(uint64_t iova, int level)
211 {
212 int shift = VTD_PAGE_SHIFT + VTD_LEVEL_BITS * (level - 1);
213
214 return (iova >> shift) & ((1u << VTD_LEVEL_BITS) - 1);
215 }
216
217 static uint64_t qvtd_get_table_addr(uint64_t base, int level, uint64_t iova)
218 {
219 return base + (qvtd_get_table_index(iova, level) * QVTD_PTE_SIZE);
220 }
221
222 static uint64_t qvtd_get_pte_attrs(void)
223 {
224 /* Second-level: R/W in every paging entry (Section 3.7.1) */
225 return VTD_SS_R | VTD_SS_W;
226 }
227
228 static uint64_t qvtd_get_fl_pte_attrs(bool is_leaf)
229 {
230 /* First-level: x86 page table format (VT-d spec Section 9.9) */
231 uint64_t attrs = VTD_FS_P | VTD_FS_RW | VTD_FS_US | VTD_FS_A;
232
233 if (is_leaf) {
234 attrs |= VTD_FS_D;
235 }
236 return attrs;
237 }
238
239 uint64_t qvtd_leaf_pte_addr(uint64_t iova)
240 {
241 return qvtd_get_table_addr(QVTD_PT_L1_BASE, 1, iova);
242 }
243
244 uint64_t qvtd_make_leaf_pte(uint64_t pa, QVTDTransMode mode)
245 {
246 uint64_t attrs;
247
248 /*
249 * Reuse the same leaf attributes qvtd_setup_translation_tables() writes,
250 * so a rewritten PTE stays consistent with the initial mapping. US=1 in
251 * the first-level format is required because the PASID entry runs with
252 * SRE=0, which makes every DMA appear as a user-mode access
253 * (VT-d 3.6.2 / 9.9).
254 */
255 if (mode == QVTD_TM_SCALABLE_FLT) {
256 attrs = qvtd_get_fl_pte_attrs(true);
257 } else {
258 attrs = qvtd_get_pte_attrs();
259 }
260 return (pa & VTD_PAGE_MASK_4K) | attrs;
261 }
262
263 void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
264 QVTDTransMode mode)
265 {
266 bool is_fl = (mode == QVTD_TM_SCALABLE_FLT);
267 uint64_t non_leaf_attrs, leaf_attrs;
268
269 if (is_fl) {
270 non_leaf_attrs = qvtd_get_fl_pte_attrs(false);
271 leaf_attrs = qvtd_get_fl_pte_attrs(true);
272 } else {
273 /* Second-level: all levels use identical R/W attrs (spec 3.7.1) */
274 non_leaf_attrs = qvtd_get_pte_attrs();
275 leaf_attrs = non_leaf_attrs;
276 }
277
278 g_test_message("Page table setup: IOVA=0x%" PRIx64
279 " PA=0x%" PRIx64 " %s",
280 (uint64_t)iova, (uint64_t)QVTD_PT_VAL,
281 is_fl ? "first-level" : "second-level");
282
283 /* PML4 (L4) -> PDPT (L3) -> PD (L2) -> PT (L1) -> PA */
284 qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L4_BASE, 4, iova),
285 QVTD_PT_L3_BASE | non_leaf_attrs);
286 qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L3_BASE, 3, iova),
287 QVTD_PT_L2_BASE | non_leaf_attrs);
288 qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L2_BASE, 2, iova),
289 QVTD_PT_L1_BASE | non_leaf_attrs);
290 qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L1_BASE, 1, iova),
291 (QVTD_PT_VAL & VTD_PAGE_MASK_4K) | leaf_attrs);
292 }
293
294 void qvtd_program_regs(QTestState *qts, uint64_t iommu_base,
295 QVTDTransMode mode)
296 {
297 uint32_t gcmd = 0;
298 uint64_t rtaddr = QVTD_ROOT_TABLE_BASE;
299
300 /* Set SMT bit for scalable mode (VT-d spec Section 9.1) */
301 if (qvtd_is_scalable(mode)) {
302 rtaddr |= VTD_RTADDR_SMT;
303 }
304
305 /* Set Root Table Address */
306 qtest_writeq(qts, iommu_base + DMAR_RTADDR_REG, rtaddr);
307
308 /* Set Root Table Pointer and verify */
309 gcmd |= VTD_GCMD_SRTP;
310 qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
311 g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_RTPS);
312
313 /* Setup Invalidation Queue */
314 qtest_writeq(qts, iommu_base + DMAR_IQA_REG,
315 QVTD_IQ_BASE | QVTD_IQ_QS);
316 qtest_writeq(qts, iommu_base + DMAR_IQH_REG, 0);
317 qtest_writeq(qts, iommu_base + DMAR_IQT_REG, 0);
318
319 /* Enable Queued Invalidation and verify */
320 gcmd |= VTD_GCMD_QIE;
321 qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
322 g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_QIES);
323
324 /* Setup Fault Event MSI */
325 qtest_writel(qts, iommu_base + DMAR_FECTL_REG, 0x0);
326 qtest_writel(qts, iommu_base + DMAR_FEDATA_REG, QVTD_FAULT_IRQ_DATA);
327 qtest_writel(qts, iommu_base + DMAR_FEADDR_REG, QVTD_FAULT_IRQ_ADDR);
328
329 /* Enable translation and verify */
330 gcmd |= VTD_GCMD_TE;
331 qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
332 g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_TES);
333 }
334
335 uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
336 uint16_t sid)
337 {
338 uint8_t bus = (sid >> 8) & 0xff;
339
340 g_test_message("Build translation: IOVA=0x%" PRIx64 " PA=0x%" PRIx64
341 " mode=%d",
342 (uint64_t)QVTD_IOVA, (uint64_t)QVTD_PT_VAL, mode);
343
344 /* Clear IOMMU structure regions to avoid stale entries */
345 qtest_memset(qts, QVTD_ROOT_TABLE_BASE, 0, 0x1000);
346 qtest_memset(qts, QVTD_PT_L4_BASE, 0, 0x4000);
347
348 if (qvtd_is_scalable(mode)) {
349 /* Scalable: 32B context entries need 8KB */
350 qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 0x2000);
351 qtest_memset(qts, QVTD_PASID_DIR_BASE, 0, 0x1000);
352 qtest_memset(qts, QVTD_PASID_TABLE_BASE, 0, 0x1000);
353 } else {
354 qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 0x1000);
355 }
356
357 qvtd_build_root_entry(qts, bus, QVTD_CONTEXT_TABLE_BASE, mode);
358
359 if (qvtd_is_scalable(mode)) {
360 /* Scalable path: context -> PASID dir -> PASID entry -> page tables */
361 qvtd_build_scalable_context_entry(qts, sid);
362 qvtd_build_pasid_dir_entry(qts);
363
364 if (mode == QVTD_TM_SCALABLE_PT) {
365 qvtd_build_pasid_table_entry(qts, mode, 0);
366 } else {
367 qvtd_setup_translation_tables(qts, QVTD_IOVA, mode);
368 qvtd_build_pasid_table_entry(qts, mode, QVTD_PT_L4_BASE);
369 }
370 } else {
371 /* Legacy path */
372 if (mode == QVTD_TM_LEGACY_PT) {
373 qvtd_build_context_entry(qts, sid, mode, 0);
374 } else {
375 qvtd_setup_translation_tables(qts, QVTD_IOVA, mode);
376 qvtd_build_context_entry(qts, sid, mode, QVTD_PT_L4_BASE);
377 }
378 }
379
380 return 0;
381 }
382
383 uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx)
384 {
385 uint32_t build_result;
386
387 /* Build translation structures first */
388 build_result = qvtd_build_translation(ctx->qts, ctx->config.trans_mode,
389 ctx->sid);
390 if (build_result != 0) {
391 g_test_message("Build failed: mode=%u sid=%u status=0x%x",
392 ctx->config.trans_mode, ctx->sid, build_result);
393 ctx->trans_status = build_result;
394 return ctx->trans_status;
395 }
396
397 /* Program IOMMU registers (sets root table pointer, enables translation) */
398 qvtd_program_regs(ctx->qts, ctx->iommu_base, ctx->config.trans_mode);
399
400 ctx->trans_status = 0;
401 return ctx->trans_status;
402 }
403
404 static bool qvtd_validate_test_result(QVTDTestContext *ctx)
405 {
406 uint32_t expected = qvtd_expected_dma_result(ctx);
407
408 g_test_message("-> Validating result: expected=0x%x actual=0x%x",
409 expected, ctx->dma_result);
410 return (ctx->dma_result == expected);
411 }
412
413 static uint32_t qvtd_single_translation_setup(void *opaque)
414 {
415 return qvtd_setup_and_enable_translation(opaque);
416 }
417
418 static uint32_t qvtd_single_translation_attrs(void *opaque)
419 {
420 return qvtd_build_dma_attrs();
421 }
422
423 static bool qvtd_single_translation_validate(void *opaque)
424 {
425 return qvtd_validate_test_result(opaque);
426 }
427
428 static void qvtd_single_translation_report(void *opaque, uint32_t dma_result)
429 {
430 QVTDTestContext *ctx = opaque;
431
432 if (dma_result != 0) {
433 g_test_message("DMA failed: mode=%u result=0x%x",
434 ctx->config.trans_mode, dma_result);
435 } else {
436 g_test_message("-> DMA succeeded: mode=%u",
437 ctx->config.trans_mode);
438 }
439 }
440
441 void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
442 QPCIBar bar, uint64_t iommu_base,
443 const QVTDTestConfig *cfg)
444 {
445 QVTDTestContext ctx = {
446 .qts = qts,
447 .dev = dev,
448 .bar = bar,
449 .iommu_base = iommu_base,
450 .config = *cfg,
451 .sid = dev->devfn,
452 };
453
454 QOSIOMMUTestdevDmaCfg dma = {
455 .dev = dev,
456 .bar = bar,
457 .iova = QVTD_IOVA,
458 .gpa = cfg->dma_gpa,
459 .len = cfg->dma_len,
460 };
461
462 qtest_memset(qts, cfg->dma_gpa, 0x00, cfg->dma_len);
463 qos_iommu_testdev_single_translation(&dma, &ctx,
464 qvtd_single_translation_setup,
465 qvtd_single_translation_attrs,
466 qvtd_single_translation_validate,
467 qvtd_single_translation_report,
468 &ctx.dma_result);
469
470 if (ctx.dma_result == 0 && ctx.config.expected_result == 0) {
471 g_autofree uint8_t *buf = NULL;
472
473 buf = g_malloc(ctx.config.dma_len);
474 qtest_memread(ctx.qts, ctx.config.dma_gpa, buf, ctx.config.dma_len);
475
476 for (int i = 0; i < ctx.config.dma_len; i++) {
477 uint8_t expected;
478
479 expected = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
480 g_assert_cmpuint(buf[i], ==, expected);
481 }
482 }
483 }
484
485 const char *qvtd_iommu_args(QVTDTransMode mode)
486 {
487 switch (mode) {
488 case QVTD_TM_SCALABLE_FLT:
489 return "-device intel-iommu,scalable-mode=on,fsts=on ";
490 case QVTD_TM_SCALABLE_PT:
491 case QVTD_TM_SCALABLE_SLT:
492 return "-device intel-iommu,scalable-mode=on ";
493 default:
494 return "-device intel-iommu ";
495 }
496 }
497
498 bool qvtd_check_caps(QTestState *qts, QVTDTransMode mode)
499 {
500 uint64_t ecap = qtest_readq(qts,
501 Q35_HOST_BRIDGE_IOMMU_ADDR + DMAR_ECAP_REG);
502
503 /* All scalable modes require SMTS */
504 if (qvtd_is_scalable(mode) && !(ecap & VTD_ECAP_SMTS)) {
505 g_test_skip("ECAP.SMTS not supported");
506 return false;
507 }
508
509 switch (mode) {
510 case QVTD_TM_SCALABLE_PT:
511 if (!(ecap & VTD_ECAP_PT)) {
512 g_test_skip("ECAP.PT not supported");
513 return false;
514 }
515 break;
516 case QVTD_TM_SCALABLE_SLT:
517 if (!(ecap & VTD_ECAP_SSTS)) {
518 g_test_skip("ECAP.SSTS not supported");
519 return false;
520 }
521 break;
522 case QVTD_TM_SCALABLE_FLT:
523 if (!(ecap & VTD_ECAP_FSTS)) {
524 g_test_skip("ECAP.FSTS not supported");
525 return false;
526 }
527 break;
528 default:
529 break;
530 }
531
532 return true;
533 }
534
535 static void qvtd_save_pci_dev(QPCIDevice *dev, int devfn, void *data)
536 {
537 QPCIDevice **pdev = (QPCIDevice **)data;
538
539 *pdev = dev;
540 }
541
542 QPCIDevice *qvtd_setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus,
543 QPCIBar *bar)
544 {
545 QPCIDevice *dev = NULL;
546
547 *pcibus = qpci_new_pc(qts, NULL);
548 g_assert(*pcibus != NULL);
549
550 qpci_device_foreach(*pcibus, IOMMU_TESTDEV_VENDOR_ID,
551 IOMMU_TESTDEV_DEVICE_ID, qvtd_save_pci_dev, &dev);
552
553 g_assert(dev);
554 qpci_device_enable(dev);
555 *bar = qpci_iomap(dev, 0, NULL);
556 g_assert_false(bar->is_io);
557
558 return dev;
559 }
560
561 /*
562 * Write a 128-bit invalidation descriptor at the current tail and advance
563 * IQT_REG. Internal helper for the IOTLB / wait variants below.
564 */
565 static uint32_t qvtd_submit_inv_desc(QTestState *qts, uint64_t iommu_base,
566 uint64_t desc_lo, uint64_t desc_hi,
567 uint32_t tail)
568 {
569 uint64_t desc_addr = QVTD_IQ_BASE + (uint64_t)tail * QVTD_IQ_DESC_SIZE;
570
571 qtest_writeq(qts, desc_addr, desc_lo);
572 qtest_writeq(qts, desc_addr + 8, desc_hi);
573 tail++;
574
575 qtest_writeq(qts, iommu_base + DMAR_IQT_REG,
576 (uint64_t)tail << QVTD_IQT_SHIFT);
577 return tail;
578 }
579
580 uint32_t qvtd_submit_inv_wait_and_poll(QTestState *qts, uint64_t iommu_base,
581 uint32_t tail)
582 {
583 uint64_t lo, hi;
584 uint32_t status = 0;
585 int i;
586
587 qtest_writel(qts, QVTD_INV_WAIT_ADDR, 0);
588
589 lo = VTD_INV_DESC_WAIT | VTD_INV_DESC_WAIT_SW |
590 ((uint64_t)QVTD_INV_WAIT_DATA << VTD_INV_DESC_WAIT_DATA_SHIFT);
591 hi = QVTD_INV_WAIT_ADDR;
592
593 tail = qvtd_submit_inv_desc(qts, iommu_base, lo, hi, tail);
594
595 for (i = 0; i < QVTD_INV_WAIT_POLL_MAX_ITERS; i++) {
596 status = qtest_readl(qts, QVTD_INV_WAIT_ADDR);
597 if (status == QVTD_INV_WAIT_DATA) {
598 return tail;
599 }
600 g_usleep(QVTD_INV_WAIT_POLL_INTERVAL_US);
601 }
602
603 g_assert_cmphex(status, ==, QVTD_INV_WAIT_DATA);
604 return tail;
605 }
606
607 uint32_t qvtd_submit_iotlb_global_inv(QTestState *qts, uint64_t iommu_base,
608 uint32_t tail)
609 {
610 uint64_t lo = VTD_INV_DESC_IOTLB | VTD_INV_DESC_IOTLB_GLOBAL;
611
612 return qvtd_submit_inv_desc(qts, iommu_base, lo, 0, tail);
613 }
614
615 uint32_t qvtd_submit_iotlb_domain_inv(QTestState *qts, uint64_t iommu_base,
616 uint16_t domain_id, uint32_t tail)
617 {
618 uint64_t lo = VTD_INV_DESC_IOTLB | VTD_INV_DESC_IOTLB_DOMAIN |
619 ((uint64_t)domain_id << 16);
620
621 return qvtd_submit_inv_desc(qts, iommu_base, lo, 0, tail);
622 }
623
624 uint32_t qvtd_submit_iotlb_page_inv(QTestState *qts, uint64_t iommu_base,
625 uint16_t domain_id, uint64_t addr,
626 uint8_t am, uint32_t tail)
627 {
628 uint64_t lo = VTD_INV_DESC_IOTLB | VTD_INV_DESC_IOTLB_PAGE |
629 ((uint64_t)domain_id << 16);
630 /*
631 * AM selects the invalidation range per VT-d 6.5.2.4:
632 * am=0 → 4 KB, am=9 → 2 MB, am=18 → 1 GB.
633 * IH (hi[6]) is left clear, requesting full invalidation
634 * including non-leaf paging-structure caches.
635 */
636 uint64_t hi = (addr & ~0xfffULL) | (am & 0x3fULL);
637
638 return qvtd_submit_inv_desc(qts, iommu_base, lo, hi, tail);
639 }