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 "qos-iommu-testdev.h"
15
+#include "qos-intel-iommu.h"
16
+
17
+#define QVTD_AW_48BIT_ENCODING 2
18
+
19
+uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx)
20
+{
21
+ return ctx->config.expected_result;
22
+}
23
+
24
+uint32_t qvtd_build_dma_attrs(void)
25
+{
26
+ /*
27
+ * VT-d obtains the Requester ID (Source ID) from PCI bus/devfn routing
28
+ * via pci_device_iommu_address_space(), not from DMA attributes.
29
+ *
30
+ * For scalable mode, iommu-testdev does not set MemTxAttrs.pid,
31
+ * so the device's VTDAddressSpace has pasid=PCI_NO_PASID.
32
+ * vtd_do_iommu_translate() remaps PCI_NO_PASID to PASID_0
33
+ * when root_scalable is set, which matches the PASID=0 entry
34
+ * we configure in qvtd_build_pasid_table_entry().
35
+ */
36
+ return 0;
37
+}
38
+
39
+static void qvtd_build_root_entry(QTestState *qts, uint8_t bus,
40
+ uint64_t context_table_ptr,
41
+ QVTDTransMode mode)
42
+{
43
+ uint64_t root_entry_addr = QVTD_ROOT_TABLE_BASE +
44
+ (bus * sizeof(VTDRootEntry));
45
+ uint64_t lo, hi;
46
+
47
+ if (qvtd_is_scalable(mode)) {
48
+ /*
49
+ * Scalable-mode Root Entry (Section 9.2):
50
+ * lo = Lower Context Table Pointer + LP (Lower Present)
51
+ * hi = Upper Context Table Pointer + UP (Upper Present)
52
+ *
53
+ * Lower table covers devfn 0-127, Upper covers devfn 128-255.
54
+ * Only lower half is needed for test device (devfn < 128).
55
+ */
56
+ lo = (context_table_ptr & VTD_ROOT_ENTRY_CTP) | VTD_ROOT_ENTRY_P;
57
+ hi = 0; /* UP=0: upper context table not present */
58
+ } else {
59
+ /*
60
+ * Legacy Root Entry (Section 9.1):
61
+ * lo = Context Table Pointer + Present
62
+ * hi = Reserved
63
+ */
64
+ lo = (context_table_ptr & VTD_ROOT_ENTRY_CTP) | VTD_ROOT_ENTRY_P;
65
+ hi = 0;
66
+ }
67
+
68
+ qtest_writeq(qts, root_entry_addr, lo);
69
+ qtest_writeq(qts, root_entry_addr + 8, hi);
70
+}
71
+
72
+static void qvtd_build_context_entry(QTestState *qts, uint16_t sid,
73
+ QVTDTransMode mode, uint64_t ssptptr)
74
+{
75
+ uint8_t devfn = sid & 0xff;
76
+ uint64_t context_entry_addr = QVTD_CONTEXT_TABLE_BASE +
77
+ (devfn * VTD_CTX_ENTRY_LEGACY_SIZE);
78
+ uint64_t lo, hi;
79
+
80
+ if (mode == QVTD_TM_LEGACY_PT) {
81
+ /*
82
+ * Pass-through mode (Section 9.3):
83
+ * lo: P + FPD(=0, fault enabled) + TT(=Pass-through)
84
+ * hi: DID + AW
85
+ */
86
+ lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_PASS_THROUGH;
87
+ hi = ((uint64_t)QVTD_DOMAIN_ID << 8) | QVTD_AW_48BIT_ENCODING;
88
+ } else {
89
+ /*
90
+ * Translated mode (Section 9.3):
91
+ * lo: P + FPD(=0, fault enabled) + TT(=Multi-level) + SSPTPTR
92
+ * hi: DID + AW(=48-bit, 4-level)
93
+ */
94
+ lo = VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_MULTI_LEVEL |
95
+ (ssptptr & VTD_CONTEXT_ENTRY_SSPTPTR);
96
+ hi = ((uint64_t)QVTD_DOMAIN_ID << 8) | QVTD_AW_48BIT_ENCODING;
97
+ }
98
+
99
+ qtest_writeq(qts, context_entry_addr, lo);
100
+ qtest_writeq(qts, context_entry_addr + 8, hi);
101
+}
102
+
103
+static void qvtd_build_scalable_context_entry(QTestState *qts, uint16_t sid)
104
+{
105
+ uint8_t devfn = sid & 0xff;
106
+ uint64_t ce_addr = QVTD_CONTEXT_TABLE_BASE +
107
+ (devfn * VTD_CTX_ENTRY_SCALABLE_SIZE);
108
+
109
+ /*
110
+ * Scalable-Mode Context Entry (Section 9.4), 32 bytes = 4 qwords:
111
+ *
112
+ * val[0]: P + FPD(=0) + DTE(=0) + PASIDE(=0) + PRE(=0) + HPTE(=0)
113
+ * + EPTR(=0) + PDTS(=0) + PASIDDIRPTR
114
+ * val[1]: RID_PASID(=0) + PDTTE(=0) + PRE(=0) + RID_CG(=0)
115
+ * val[2]: Reserved (must be 0)
116
+ * val[3]: Reserved (must be 0)
117
+ */
118
+ qtest_writeq(qts, ce_addr,
119
+ (QVTD_PASID_DIR_BASE & VTD_PASID_DIR_BASE_ADDR_MASK) |
120
+ VTD_CONTEXT_ENTRY_P);
121
+ qtest_writeq(qts, ce_addr + 8, 0);
122
+ qtest_writeq(qts, ce_addr + 16, 0);
123
+ qtest_writeq(qts, ce_addr + 24, 0);
124
+}
125
+
126
+static void qvtd_build_pasid_dir_entry(QTestState *qts)
127
+{
128
+ uint64_t addr = QVTD_PASID_DIR_BASE +
129
+ VTD_PASID_DIR_INDEX(0) * VTD_PASID_DIR_ENTRY_SIZE;
130
+
131
+ /*
132
+ * PASID Directory Entry (Section 9.5):
133
+ * P + FPD(=0, fault enabled) + SMPTBLPTR
134
+ */
135
+ qtest_writeq(qts, addr,
136
+ (QVTD_PASID_TABLE_BASE & VTD_PASID_TABLE_BASE_ADDR_MASK) |
137
+ VTD_PASID_ENTRY_P);
138
+}
139
+
140
+static void qvtd_build_pasid_table_entry(QTestState *qts, QVTDTransMode mode,
141
+ uint64_t ptptr)
142
+{
143
+ uint64_t addr = QVTD_PASID_TABLE_BASE +
144
+ VTD_PASID_TABLE_INDEX(0) * VTD_PASID_ENTRY_SIZE;
145
+ uint64_t val0, val1, val2;
146
+
147
+ /*
148
+ * Scalable-Mode PASID Table Entry (Section 9.6), 64 bytes = 8 qwords:
149
+ *
150
+ * val[0]: P + FPD(=0) + AW + PGTT + SSADE(=0) + SSPTPTR
151
+ * val[1]: DID + PWSNP(=0) + PGSNP(=0)
152
+ * + CD(=0) + EMTE(=0) + PAT(=0): Memory Type,
153
+ * all Reserved(0) since QEMU ECAP.MTS=0
154
+ * val[2]: SRE(=0) + FSPM(=0, 4-level) + WPE(=0) + IGN + EAFE(=0) + FSPTPTR
155
+ * val[3]: Reserved (must be 0)
156
+ * val[4]: HPT fields, Reserved(0) since QEMU ECAP.HPTS=0
157
+ * val[5]: HPT fields, Reserved(0) since QEMU ECAP.HPTS=0
158
+ * val[6]: Reserved (must be 0)
159
+ * val[7]: Reserved (must be 0)
160
+ */
161
+ switch (mode) {
162
+ case QVTD_TM_SCALABLE_PT:
163
+ val0 = VTD_PASID_ENTRY_P |
164
+ ((uint64_t)VTD_SM_PASID_ENTRY_PT << 6);
165
+ val1 = (uint64_t)QVTD_DOMAIN_ID;
166
+ val2 = 0;
167
+ break;
168
+ case QVTD_TM_SCALABLE_SLT:
169
+ val0 = VTD_PASID_ENTRY_P |
170
+ ((uint64_t)VTD_SM_PASID_ENTRY_SST << 6) |
171
+ ((uint64_t)QVTD_AW_48BIT_ENCODING << 2) |
172
+ (ptptr & VTD_SM_PASID_ENTRY_SSPTPTR);
173
+ val1 = (uint64_t)QVTD_DOMAIN_ID;
174
+ val2 = 0;
175
+ break;
176
+ case QVTD_TM_SCALABLE_FLT:
177
+ /*
178
+ * val[2] fields for FLT (Section 9.6):
179
+ * SRE(=0, user-level DMA only) + FSPM(=0, 4-level) +
180
+ * WPE(=0, no supervisor write-protect) + IGN + EAFE(=0) + FSPTPTR
181
+ */
182
+ val0 = VTD_PASID_ENTRY_P |
183
+ ((uint64_t)VTD_SM_PASID_ENTRY_FST << 6);
184
+ val1 = (uint64_t)QVTD_DOMAIN_ID;
185
+ val2 = ptptr & QVTD_SM_PASID_ENTRY_FSPTPTR;
186
+ break;
187
+ default:
188
+ g_assert_not_reached();
189
+ }
190
+
191
+ qtest_writeq(qts, addr, val0);
192
+ qtest_writeq(qts, addr + 8, val1);
193
+ qtest_writeq(qts, addr + 16, val2);
194
+ qtest_writeq(qts, addr + 24, 0);
195
+ qtest_writeq(qts, addr + 32, 0);
196
+ qtest_writeq(qts, addr + 40, 0);
197
+ qtest_writeq(qts, addr + 48, 0);
198
+ qtest_writeq(qts, addr + 56, 0);
199
+}
200
+
201
+/*
202
+ * VT-d second-level paging helpers.
203
+ * 4-level, 48-bit address space, 9 bits per level index.
204
+ */
205
+static uint32_t qvtd_get_table_index(uint64_t iova, int level)
206
+{
207
+ int shift = VTD_PAGE_SHIFT + VTD_LEVEL_BITS * (level - 1);
208
+
209
+ return (iova >> shift) & ((1u << VTD_LEVEL_BITS) - 1);
210
+}
211
+
212
+static uint64_t qvtd_get_table_addr(uint64_t base, int level, uint64_t iova)
213
+{
214
+ return base + (qvtd_get_table_index(iova, level) * QVTD_PTE_SIZE);
215
+}
216
+
217
+static uint64_t qvtd_get_pte_attrs(void)
218
+{
219
+ /* Second-level: R/W in every paging entry (Section 3.7.1) */
220
+ return VTD_SS_R | VTD_SS_W;
221
+}
222
+
223
+static uint64_t qvtd_get_fl_pte_attrs(bool is_leaf)
224
+{
225
+ /* First-level: x86 page table format (VT-d spec Section 9.9) */
226
+ uint64_t attrs = VTD_FS_P | VTD_FS_RW | VTD_FS_US | VTD_FS_A;
227
+
228
+ if (is_leaf) {
229
+ attrs |= VTD_FS_D;
230
+ }
231
+ return attrs;
232
+}
233
+
234
+void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
235
+ QVTDTransMode mode)
236
+{
237
+ bool is_fl = (mode == QVTD_TM_SCALABLE_FLT);
238
+ uint64_t non_leaf_attrs, leaf_attrs;
239
+
240
+ if (is_fl) {
241
+ non_leaf_attrs = qvtd_get_fl_pte_attrs(false);
242
+ leaf_attrs = qvtd_get_fl_pte_attrs(true);
243
+ } else {
244
+ /* Second-level: all levels use identical R/W attrs (spec 3.7.1) */
245
+ non_leaf_attrs = qvtd_get_pte_attrs();
246
+ leaf_attrs = non_leaf_attrs;
247
+ }
248
+
249
+ g_test_message("Page table setup: IOVA=0x%" PRIx64
250
+ " PA=0x%" PRIx64 " %s",
251
+ (uint64_t)iova, (uint64_t)QVTD_PT_VAL,
252
+ is_fl ? "first-level" : "second-level");
253
+
254
+ /* PML4 (L4) -> PDPT (L3) -> PD (L2) -> PT (L1) -> PA */
255
+ qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L4_BASE, 4, iova),
256
+ QVTD_PT_L3_BASE | non_leaf_attrs);
257
+ qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L3_BASE, 3, iova),
258
+ QVTD_PT_L2_BASE | non_leaf_attrs);
259
+ qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L2_BASE, 2, iova),
260
+ QVTD_PT_L1_BASE | non_leaf_attrs);
261
+ qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L1_BASE, 1, iova),
262
+ (QVTD_PT_VAL & VTD_PAGE_MASK_4K) | leaf_attrs);
263
+}
264
+
265
+void qvtd_program_regs(QTestState *qts, uint64_t iommu_base,
266
+ QVTDTransMode mode)
267
+{
268
+ uint32_t gcmd = 0;
269
+ uint64_t rtaddr = QVTD_ROOT_TABLE_BASE;
270
+
271
+ /* Set SMT bit for scalable mode (VT-d spec Section 9.1) */
272
+ if (qvtd_is_scalable(mode)) {
273
+ rtaddr |= VTD_RTADDR_SMT;
274
+ }
275
+
276
+ /* Set Root Table Address */
277
+ qtest_writeq(qts, iommu_base + DMAR_RTADDR_REG, rtaddr);
278
+
279
+ /* Set Root Table Pointer and verify */
280
+ gcmd |= VTD_GCMD_SRTP;
281
+ qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
282
+ g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_RTPS);
283
+
284
+ /* Setup Invalidation Queue */
285
+ qtest_writeq(qts, iommu_base + DMAR_IQA_REG,
286
+ QVTD_IQ_BASE | QVTD_IQ_QS);
287
+ qtest_writeq(qts, iommu_base + DMAR_IQH_REG, 0);
288
+ qtest_writeq(qts, iommu_base + DMAR_IQT_REG, 0);
289
+
290
+ /* Enable Queued Invalidation and verify */
291
+ gcmd |= VTD_GCMD_QIE;
292
+ qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
293
+ g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_QIES);
294
+
295
+ /* Setup Fault Event MSI */
296
+ qtest_writel(qts, iommu_base + DMAR_FECTL_REG, 0x0);
297
+ qtest_writel(qts, iommu_base + DMAR_FEDATA_REG, QVTD_FAULT_IRQ_DATA);
298
+ qtest_writel(qts, iommu_base + DMAR_FEADDR_REG, QVTD_FAULT_IRQ_ADDR);
299
+
300
+ /* Enable translation and verify */
301
+ gcmd |= VTD_GCMD_TE;
302
+ qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd);
303
+ g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_TES);
304
+}
305
+
306
+uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
307
+ uint16_t sid)
308
+{
309
+ uint8_t bus = (sid >> 8) & 0xff;
310
+
311
+ g_test_message("Build translation: IOVA=0x%" PRIx64 " PA=0x%" PRIx64
312
+ " mode=%d",
313
+ (uint64_t)QVTD_IOVA, (uint64_t)QVTD_PT_VAL, mode);
314
+
315
+ /* Clear IOMMU structure regions to avoid stale entries */
316
+ qtest_memset(qts, QVTD_ROOT_TABLE_BASE, 0, 0x1000);
317
+ qtest_memset(qts, QVTD_PT_L4_BASE, 0, 0x4000);
318
+
319
+ if (qvtd_is_scalable(mode)) {
320
+ /* Scalable: 32B context entries need 8KB */
321
+ qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 0x2000);
322
+ qtest_memset(qts, QVTD_PASID_DIR_BASE, 0, 0x1000);
323
+ qtest_memset(qts, QVTD_PASID_TABLE_BASE, 0, 0x1000);
324
+ } else {
325
+ qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 0x1000);
326
+ }
327
+
328
+ qvtd_build_root_entry(qts, bus, QVTD_CONTEXT_TABLE_BASE, mode);
329
+
330
+ if (qvtd_is_scalable(mode)) {
331
+ /* Scalable path: context -> PASID dir -> PASID entry -> page tables */
332
+ qvtd_build_scalable_context_entry(qts, sid);
333
+ qvtd_build_pasid_dir_entry(qts);
334
+
335
+ if (mode == QVTD_TM_SCALABLE_PT) {
336
+ qvtd_build_pasid_table_entry(qts, mode, 0);
337
+ } else {
338
+ qvtd_setup_translation_tables(qts, QVTD_IOVA, mode);
339
+ qvtd_build_pasid_table_entry(qts, mode, QVTD_PT_L4_BASE);
340
+ }
341
+ } else {
342
+ /* Legacy path */
343
+ if (mode == QVTD_TM_LEGACY_PT) {
344
+ qvtd_build_context_entry(qts, sid, mode, 0);
345
+ } else {
346
+ qvtd_setup_translation_tables(qts, QVTD_IOVA, mode);
347
+ qvtd_build_context_entry(qts, sid, mode, QVTD_PT_L4_BASE);
348
+ }
349
+ }
350
+
351
+ return 0;
352
+}
353
+
354
+uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx)
355
+{
356
+ uint32_t build_result;
357
+
358
+ /* Build translation structures first */
359
+ build_result = qvtd_build_translation(ctx->qts, ctx->config.trans_mode,
360
+ ctx->sid);
361
+ if (build_result != 0) {
362
+ g_test_message("Build failed: mode=%u sid=%u status=0x%x",
363
+ ctx->config.trans_mode, ctx->sid, build_result);
364
+ ctx->trans_status = build_result;
365
+ return ctx->trans_status;
366
+ }
367
+
368
+ /* Program IOMMU registers (sets root table pointer, enables translation) */
369
+ qvtd_program_regs(ctx->qts, ctx->iommu_base, ctx->config.trans_mode);
370
+
371
+ ctx->trans_status = 0;
372
+ return ctx->trans_status;
373
+}
374
+
375
+static bool qvtd_validate_test_result(QVTDTestContext *ctx)
376
+{
377
+ uint32_t expected = qvtd_expected_dma_result(ctx);
378
+
379
+ g_test_message("-> Validating result: expected=0x%x actual=0x%x",
380
+ expected, ctx->dma_result);
381
+ return (ctx->dma_result == expected);
382
+}
383
+
384
+static uint32_t qvtd_single_translation_setup(void *opaque)
385
+{
386
+ return qvtd_setup_and_enable_translation(opaque);
387
+}
388
+
389
+static uint32_t qvtd_single_translation_attrs(void *opaque)
390
+{
391
+ return qvtd_build_dma_attrs();
392
+}
393
+
394
+static bool qvtd_single_translation_validate(void *opaque)
395
+{
396
+ return qvtd_validate_test_result(opaque);
397
+}
398
+
399
+static void qvtd_single_translation_report(void *opaque, uint32_t dma_result)
400
+{
401
+ QVTDTestContext *ctx = opaque;
402
+
403
+ if (dma_result != 0) {
404
+ g_test_message("DMA failed: mode=%u result=0x%x",
405
+ ctx->config.trans_mode, dma_result);
406
+ } else {
407
+ g_test_message("-> DMA succeeded: mode=%u",
408
+ ctx->config.trans_mode);
409
+ }
410
+}
411
+
412
+void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
413
+ QPCIBar bar, uint64_t iommu_base,
414
+ const QVTDTestConfig *cfg)
415
+{
416
+ QVTDTestContext ctx = {
417
+ .qts = qts,
418
+ .dev = dev,
419
+ .bar = bar,
420
+ .iommu_base = iommu_base,
421
+ .config = *cfg,
422
+ .sid = dev->devfn,
423
+ };
424
+
425
+ QOSIOMMUTestdevDmaCfg dma = {
426
+ .dev = dev,
427
+ .bar = bar,
428
+ .iova = QVTD_IOVA,
429
+ .gpa = cfg->dma_gpa,
430
+ .len = cfg->dma_len,
431
+ };
432
+
433
+ qtest_memset(qts, cfg->dma_gpa, 0x00, cfg->dma_len);
434
+ qos_iommu_testdev_single_translation(&dma, &ctx,
435
+ qvtd_single_translation_setup,
436
+ qvtd_single_translation_attrs,
437
+ qvtd_single_translation_validate,
438
+ qvtd_single_translation_report,
439
+ &ctx.dma_result);
440
+
441
+ if (ctx.dma_result == 0 && ctx.config.expected_result == 0) {
442
+ g_autofree uint8_t *buf = NULL;
443
+
444
+ buf = g_malloc(ctx.config.dma_len);
445
+ qtest_memread(ctx.qts, ctx.config.dma_gpa, buf, ctx.config.dma_len);
446
+
447
+ for (int i = 0; i < ctx.config.dma_len; i++) {
448
+ uint8_t expected;
449
+
450
+ expected = (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff;
451
+ g_assert_cmpuint(buf[i], ==, expected);
452
+ }
453
+ }
454
+}