@samitouri / QOSamiQemu / commits / 57a06593bd

tests/qtest/libqos: Add Intel IOMMU helper library

Introduce a libqos helper module for Intel IOMMU (VT-d) bare-metal testing via iommu-testdev. The helper provides routines to: - Build Legacy-mode structures: Root Entry Tables, Context Entry Tables, and 4-level page tables for 48-bit address translation - Build Scalable-mode structures: Scalable Context Entries, PASID Directory Entries, PASID Table Entries, and 4-level page tables for both second-level and first-level translation - Program VT-d registers (Root Table Address, Invalidation Queue, Fault Event MSI, Global Command) following the VT-d specification, with GSTS read-back verification for each step - Execute DMA translations through iommu-testdev and verify results by reading back guest memory The module supports all major VT-d translation modes through the QVTDTransMode enum: - Legacy pass-through - Legacy translated with 4-level paging - Scalable pass-through - Scalable Second-Level Translation - Scalable First-Level Translation Reviewed-by: Chao Liu <chao.liu.zevorn@gmail.com> Signed-off-by: Fengyuan Yu <15fengyuan@gmail.com> Reviewed-by: Fabiano Rosas <farosas@suse.de> Reviewed-by: Tao Tang <tangtao1634@phytium.com.cn> Link: https://lore.kernel.org/qemu-devel/c4f7bf5d7985891a2db291193669ebe15dd2ba15.1774421649.git.15fengyuan@gmail.com Signed-off-by: Fabiano Rosas <farosas@suse.de>

Fengyuan Yu committed Mar 25, 2026 at 15:09 UTC 57a06593bdc7de1c45b84fb40917129d583028cc
4 files changed +643
MAINTAINERS
+1
@@ -3597,6 +3597,7 @@ S: Maintained
3597 F: tests/qtest/libqos/qos-iommu*
3598 F: tests/qtest/libqos/qos-smmuv3*
3599 F: tests/qtest/libqos/qos-riscv-iommu*
3600 +F: tests/qtest/libqos/qos-intel-iommu*
3601
3602 Device Fuzzing
3603 M: Alexander Bulekov <alxndr@bu.edu>
tests/qtest/libqos/meson.build
+3
@@ -73,6 +73,9 @@ endif
73 if config_all_devices.has_key('CONFIG_RISCV_IOMMU')
74 libqos_srcs += files('riscv-iommu.c', 'qos-riscv-iommu.c')
75 endif
76 +if config_all_devices.has_key('CONFIG_VTD')
77 + libqos_srcs += files('qos-intel-iommu.c')
78 +endif
79 if config_all_devices.has_key('CONFIG_TPCI200')
80 libqos_srcs += files('tpci200.c')
81 endif
tests/qtest/libqos/qos-intel-iommu.c new
+454
@@ -0,0 +1,454 @@
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 +}
tests/qtest/libqos/qos-intel-iommu.h new
+185
@@ -0,0 +1,185 @@
1 +/*
2 + * QOS Intel IOMMU (VT-d) Module
3 + *
4 + * This module provides Intel IOMMU-specific helper functions for libqos tests,
5 + * encapsulating VT-d setup, assertion, and cleanup operations.
6 + *
7 + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com>
8 + *
9 + * SPDX-License-Identifier: GPL-2.0-or-later
10 + */
11 +
12 +#ifndef QTEST_LIBQOS_INTEL_IOMMU_H
13 +#define QTEST_LIBQOS_INTEL_IOMMU_H
14 +
15 +#include "hw/misc/iommu-testdev.h"
16 +#include "hw/i386/intel_iommu_internal.h"
17 +
18 +/*
19 + * Guest memory layout for IOMMU structures.
20 + * All structures are placed in guest physical memory inside the 512MB RAM.
21 + * Using 256MB mark (0x10000000) as base to ensure all structures fit in RAM.
22 + */
23 +#define QVTD_MEM_BASE 0x10000000ULL
24 +
25 +/* Root Entry Table: 256 entries * 16 bytes = 4KB */
26 +#define QVTD_ROOT_TABLE_BASE (QVTD_MEM_BASE + 0x00000000)
27 +
28 +/* Context Entry Table: 256 entries, 16B (legacy) or 32B (scalable) per entry */
29 +#define QVTD_CONTEXT_TABLE_BASE (QVTD_MEM_BASE + 0x00001000)
30 +
31 +/* Page Tables: 4-level hierarchy for 48-bit address translation */
32 +#define QVTD_PT_L4_BASE (QVTD_MEM_BASE + 0x00010000) /* PML4 */
33 +#define QVTD_PT_L3_BASE (QVTD_MEM_BASE + 0x00011000) /* PDPT */
34 +#define QVTD_PT_L2_BASE (QVTD_MEM_BASE + 0x00012000) /* PD */
35 +#define QVTD_PT_L1_BASE (QVTD_MEM_BASE + 0x00013000) /* PT */
36 +
37 +/*
38 + * Invalidation Queue.
39 + * IQA_REG bits[2:0] = QS, entries = 1 << (QS + 8), each entry 16 bytes.
40 + */
41 +#define QVTD_IQ_BASE (QVTD_MEM_BASE + 0x00020000)
42 +#define QVTD_IQ_QS 0 /* QS=0 → 256 entries */
43 +
44 +/*
45 + * Fault Event MSI configuration.
46 + */
47 +#define QVTD_FAULT_IRQ_ADDR 0xfee00000 /* APIC base */
48 +#define QVTD_FAULT_IRQ_DATA 0x0
49 +
50 +/* Scalable mode PASID structures */
51 +#define QVTD_PASID_DIR_BASE (QVTD_MEM_BASE + 0x00030000)
52 +#define QVTD_PASID_TABLE_BASE (QVTD_MEM_BASE + 0x00031000)
53 +
54 +/* Page table entry size (8 bytes per PTE) */
55 +#define QVTD_PTE_SIZE sizeof(uint64_t)
56 +
57 +/* FSPTPTR mask: same as VTD_SM_PASID_ENTRY_SSPTPTR, bits[63:12] */
58 +#define QVTD_SM_PASID_ENTRY_FSPTPTR VTD_SM_PASID_ENTRY_SSPTPTR
59 +
60 +/* Default Domain ID for single-domain tests */
61 +#define QVTD_DOMAIN_ID 0
62 +
63 +/* Test IOVA and target physical address */
64 +#define QVTD_IOVA 0x0000000010200567ull
65 +#define QVTD_PT_VAL (QVTD_MEM_BASE + 0x00100000)
66 +
67 +/*
68 + * Translation modes supported by Intel IOMMU
69 + */
70 +typedef enum QVTDTransMode {
71 + QVTD_TM_LEGACY_PT, /* Legacy pass-through mode */
72 + QVTD_TM_LEGACY_TRANS, /* Legacy translated mode (4-level paging) */
73 + QVTD_TM_SCALABLE_PT, /* Scalable pass-through mode */
74 + QVTD_TM_SCALABLE_SLT, /* Scalable Second Level Translation */
75 + QVTD_TM_SCALABLE_FLT, /* Scalable First Level Translation */
76 +} QVTDTransMode;
77 +
78 +static inline bool qvtd_is_scalable(QVTDTransMode mode)
79 +{
80 + return mode == QVTD_TM_SCALABLE_PT ||
81 + mode == QVTD_TM_SCALABLE_SLT ||
82 + mode == QVTD_TM_SCALABLE_FLT;
83 +}
84 +
85 +typedef struct QVTDTestConfig {
86 + QVTDTransMode trans_mode; /* Translation mode */
87 + uint64_t dma_gpa; /* GPA for readback validation */
88 + uint32_t dma_len; /* DMA length for testing */
89 + uint32_t expected_result; /* Expected DMA result */
90 +} QVTDTestConfig;
91 +
92 +typedef struct QVTDTestContext {
93 + QTestState *qts; /* QTest state handle */
94 + QPCIDevice *dev; /* PCI device handle */
95 + QPCIBar bar; /* PCI BAR for MMIO access */
96 + QVTDTestConfig config; /* Test configuration */
97 + uint64_t iommu_base; /* Intel IOMMU base address */
98 + uint32_t trans_status; /* Translation configuration status */
99 + uint32_t dma_result; /* DMA operation result */
100 + uint16_t sid; /* Source ID (bus:devfn) */
101 +} QVTDTestContext;
102 +
103 +/*
104 + * qvtd_setup_and_enable_translation - Complete translation setup and enable
105 + *
106 + * @ctx: Test context containing configuration and device handles
107 + *
108 + * Returns: Translation status (0 = success, non-zero = error)
109 + *
110 + * This function performs the complete translation setup sequence:
111 + * 1. Builds VT-d structures (root/context entry, page tables)
112 + * 2. Programs IOMMU registers and enables translation
113 + * 3. Returns configuration status
114 + */
115 +uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx);
116 +
117 +/*
118 + * qvtd_build_translation - Build Intel IOMMU translation structures
119 + *
120 + * @qts: QTest state handle
121 + * @mode: Translation mode (pass-through or translated)
122 + * @sid: Source ID (bus:devfn)
123 + *
124 + * Returns: Build status (0 = success, non-zero = error)
125 + *
126 + * Constructs all necessary VT-d translation structures in guest memory:
127 + * - Root Entry for the device's bus
128 + * - Context Entry for the device
129 + * - Complete 4-level page table hierarchy (if translated mode)
130 + */
131 +uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode,
132 + uint16_t sid);
133 +
134 +/*
135 + * qvtd_program_regs - Program Intel IOMMU registers and enable translation
136 + *
137 + * @qts: QTest state handle
138 + * @iommu_base: IOMMU base address
139 + * @mode: Translation mode (scalable modes set RTADDR SMT bit)
140 + *
141 + * Programs IOMMU registers with the following sequence:
142 + * 1. Set root table pointer (SRTP), with SMT bit for scalable mode
143 + * 2. Setup invalidation queue (QIE)
144 + * 3. Configure fault event MSI
145 + * 4. Enable translation (TE)
146 + *
147 + * Each step verifies completion via GSTS register read-back.
148 + */
149 +void qvtd_program_regs(QTestState *qts, uint64_t iommu_base,
150 + QVTDTransMode mode);
151 +
152 +/*
153 + * qvtd_setup_translation_tables - Setup complete VT-d page table hierarchy
154 + *
155 + * @qts: QTest state handle
156 + * @iova: Input Virtual Address to translate
157 + * @mode: Translation mode
158 + *
159 + * This builds the 4-level page table structure for translating
160 + * the given IOVA to PA through Intel VT-d. The structure is:
161 + * - PML4 (Level 4): IOVA bits [47:39]
162 + * - PDPT (Level 3): IOVA bits [38:30]
163 + * - PD (Level 2): IOVA bits [29:21]
164 + * - PT (Level 1): IOVA bits [20:12]
165 + * - Page offset: IOVA bits [11:0]
166 + *
167 + * The function writes all necessary Page Table Entries (PTEs) to guest
168 + * memory using qtest_writeq(), setting up the complete translation path
169 + * that the VT-d hardware will traverse during DMA operations.
170 + */
171 +void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova,
172 + QVTDTransMode mode);
173 +
174 +/* Calculate expected DMA result */
175 +uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx);
176 +
177 +/* Build DMA attributes for Intel VT-d */
178 +uint32_t qvtd_build_dma_attrs(void);
179 +
180 +/* High-level test execution helpers */
181 +void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev,
182 + QPCIBar bar, uint64_t iommu_base,
183 + const QVTDTestConfig *cfg);
184 +
185 +#endif /* QTEST_LIBQOS_INTEL_IOMMU_H */