master
c 346 lines 12.3 KB
Raw
1 /*
2 * QTest for Intel IOMMU (VT-d) IOTLB invalidation via Invalidation Queue
3 *
4 * Validates that IOTLB invalidation descriptors submitted through the
5 * queued invalidation interface correctly flush cached translations,
6 * forcing the IOMMU to re-walk page tables on subsequent DMA.
7 *
8 * Copyright (c) 2026 Intel Corporation.
9 *
10 * Author: Junjie Cao <junjie.cao@intel.com>
11 *
12 * SPDX-License-Identifier: GPL-2.0-or-later
13 */
14
15 #include "qemu/osdep.h"
16 #include "libqtest.h"
17 #include "libqos/pci.h"
18 #include "libqos/pci-pc.h"
19 #include "hw/i386/intel_iommu_internal.h"
20 #include "hw/misc/iommu-testdev.h"
21 #include "libqos/qos-intel-iommu.h"
22 #include "libqos/qos-iommu-testdev.h"
23
24 #define DMA_LEN 4
25
26 /*
27 * Second DMA target page, chosen to fall well outside any address used by
28 * qos-intel-iommu's fixed structure layout.
29 */
30 #define QVTD_PT_VAL_B (QVTD_MEM_BASE + 0x00200000)
31
32 /*
33 * A second IOVA/target page for the page-selectivity test. QVTD_IOVA_2 is
34 * QVTD_IOVA + 4K: it shares the L4/L3/L2 walk built by
35 * qvtd_build_translation() and differs only in the leaf (L1) slot, so mapping
36 * it costs one extra leaf PTE. QVTD_PT_VAL_2 is its distinct target page.
37 */
38 #define QVTD_IOVA_2 (QVTD_IOVA + 0x1000)
39 #define QVTD_PT_VAL_2 (QVTD_MEM_BASE + 0x00300000)
40
41 typedef enum {
42 IOTLB_INV_GLOBAL,
43 IOTLB_INV_DOMAIN,
44 IOTLB_INV_PAGE,
45 } IOTLBInvGranularity;
46
47 /*
48 * Core invalidation test, parameterized by translation mode and
49 * invalidation granularity.
50 *
51 * The iommu-testdev device performs DMA writes via the IOMMU (using the
52 * IOVA) and verifies by reading back from the expected GPA directly. If
53 * the IOTLB is stale, the DMA write lands at the old PA while readback
54 * uses the GPA we supply, causing a mismatch (ITD_DMA_ERR_MISMATCH).
55 *
56 * Test sequence:
57 * 1. Setup translation: IOVA -> PA_A
58 * 2. DMA(gpa=PA_A) -> success (IOTLB populates cache)
59 * 3. Modify PTE: IOVA -> PA_B (no invalidation)
60 * 4. DMA(gpa=PA_B) -> MISMATCH (stale IOTLB directs write to PA_A)
61 * 5. Issue IOTLB invalidation + wait
62 * 6. DMA(gpa=PA_B) -> success (cache flushed, fresh page walk)
63 *
64 * Phase 4 depends on QEMU's IOTLB caching the Phase 1 translation; if a
65 * future change makes IOTLB caching lazy this assertion would no longer
66 * exercise the stale-cache path.
67 */
68 static void run_iotlb_inv_test(QVTDTransMode mode, IOTLBInvGranularity gran)
69 {
70 QTestState *qts;
71 QPCIBus *pcibus;
72 QPCIDevice *dev;
73 QPCIBar bar;
74 uint32_t tail = 0;
75 uint32_t result;
76 uint64_t pa_a, pa_b;
77
78 if (!qtest_has_machine("q35")) {
79 g_test_skip("q35 machine not available");
80 return;
81 }
82
83 qts = qtest_initf("-machine q35 -smp 1 -m 512 -net none "
84 "%s -device iommu-testdev",
85 qvtd_iommu_args(mode));
86
87 if (!qvtd_check_caps(qts, mode)) {
88 qtest_quit(qts);
89 return;
90 }
91
92 dev = qvtd_setup_qtest_pci_device(qts, &pcibus, &bar);
93
94 /*
95 * The IOMMU translates an IOVA to a page base, then the page offset
96 * from the IOVA is added. So GPA = page_base + (IOVA & 0xfff).
97 */
98 pa_a = (QVTD_PT_VAL & VTD_PAGE_MASK_4K) + (QVTD_IOVA & 0xfff);
99 pa_b = (QVTD_PT_VAL_B & VTD_PAGE_MASK_4K) + (QVTD_IOVA & 0xfff);
100
101 /* --- Phase 1: Setup and initial DMA (populates IOTLB) --- */
102 qvtd_build_translation(qts, mode, dev->devfn);
103 qvtd_program_regs(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, mode);
104
105 qtest_memset(qts, pa_a, 0, DMA_LEN);
106 qtest_memset(qts, pa_b, 0, DMA_LEN);
107
108 result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_a,
109 DMA_LEN, 0);
110 g_assert_cmpuint(result, ==, 0);
111
112 /* --- Phase 2: Modify PTE without invalidation -> stale IOTLB --- */
113 qtest_writeq(qts, qvtd_leaf_pte_addr(QVTD_IOVA),
114 qvtd_make_leaf_pte(QVTD_PT_VAL_B & VTD_PAGE_MASK_4K, mode));
115 qtest_memset(qts, pa_a, 0, DMA_LEN);
116 qtest_memset(qts, pa_b, 0, DMA_LEN);
117
118 result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_b,
119 DMA_LEN, 0);
120 g_assert_cmpuint(result, ==, ITD_DMA_ERR_MISMATCH);
121
122 /* --- Phase 3: Invalidate IOTLB -> fresh page walk succeeds --- */
123 switch (gran) {
124 case IOTLB_INV_GLOBAL:
125 tail = qvtd_submit_iotlb_global_inv(qts, Q35_HOST_BRIDGE_IOMMU_ADDR,
126 tail);
127 break;
128 case IOTLB_INV_DOMAIN:
129 tail = qvtd_submit_iotlb_domain_inv(qts, Q35_HOST_BRIDGE_IOMMU_ADDR,
130 QVTD_DOMAIN_ID, tail);
131 break;
132 case IOTLB_INV_PAGE:
133 tail = qvtd_submit_iotlb_page_inv(qts, Q35_HOST_BRIDGE_IOMMU_ADDR,
134 QVTD_DOMAIN_ID, QVTD_IOVA, 0,
135 tail);
136 break;
137 }
138 tail = qvtd_submit_inv_wait_and_poll(qts, Q35_HOST_BRIDGE_IOMMU_ADDR,
139 tail);
140
141 qtest_memset(qts, pa_a, 0, DMA_LEN);
142 qtest_memset(qts, pa_b, 0, DMA_LEN);
143
144 result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_b,
145 DMA_LEN, 0);
146 g_assert_cmpuint(result, ==, 0);
147
148 g_free(dev);
149 qpci_free_pc(pcibus);
150 qtest_quit(qts);
151 }
152
153 /*
154 * Page-selectivity test: verify that a page-selective invalidation flushes
155 * the named page and touches other cached pages only as far as the model
156 * intends. run_iotlb_inv_test() caches a single entry, so it cannot tell a
157 * page-selective flush from a domain-wide or global one; this test caches two
158 * pages in the same domain and checks the second one's fate.
159 *
160 * The expected fate of the second page depends on the translation level:
161 *
162 * - second-level (legacy / scalable-slt): a page-selective descriptor
163 * evicts only the matching gfn, so IOVA_2 survives.
164 * - first-level (scalable-flt): QEMU invalidates all first-stage entries of
165 * the domain on a page-selective descriptor (vtd_hash_remove_by_page()
166 * returns true for any pgtt==FST entry of the domain, matching the VT-d
167 * spec for first-stage IOTLB invalidation), so IOVA_2 is flushed too.
168 *
169 * Method: map IOVA -> PA_A and IOVA_2 -> PA_A_2, DMA both to populate two
170 * IOTLB entries, rewrite both leaf PTEs to PA_B* without invalidating, then
171 * page-invalidate IOVA only. IOVA always re-walks to PA_B. For IOVA_2 we
172 * verify the DMA against its *original* page PA_A_2: if the entry survived,
173 * the stale cache still serves PA_A_2 (success); if it was flushed, the fresh
174 * walk reaches PA_B_2 and mismatches PA_A_2. So a survived entry gives
175 * success and a flushed entry gives MISMATCH, and we assert whichever the
176 * mode requires -- catching both an over-matching second-level flush and a
177 * regression that stopped flushing first-stage entries domain-wide.
178 */
179 static void run_page_selectivity_test(QVTDTransMode mode)
180 {
181 QTestState *qts;
182 QPCIBus *pcibus;
183 QPCIDevice *dev;
184 QPCIBar bar;
185 uint32_t tail = 0;
186 uint32_t result;
187 uint64_t pa_a, pa_b, pa_a2, pa_b2;
188 bool fl_domain_wide = (mode == QVTD_TM_SCALABLE_FLT);
189
190 if (!qtest_has_machine("q35")) {
191 g_test_skip("q35 machine not available");
192 return;
193 }
194
195 qts = qtest_initf("-machine q35 -smp 1 -m 512 -net none "
196 "%s -device iommu-testdev",
197 qvtd_iommu_args(mode));
198
199 if (!qvtd_check_caps(qts, mode)) {
200 qtest_quit(qts);
201 return;
202 }
203
204 dev = qvtd_setup_qtest_pci_device(qts, &pcibus, &bar);
205
206 pa_a = (QVTD_PT_VAL & VTD_PAGE_MASK_4K) + (QVTD_IOVA & 0xfff);
207 pa_b = (QVTD_PT_VAL_B & VTD_PAGE_MASK_4K) + (QVTD_IOVA & 0xfff);
208 pa_a2 = (QVTD_PT_VAL_2 & VTD_PAGE_MASK_4K) + (QVTD_IOVA_2 & 0xfff);
209 pa_b2 = (QVTD_PT_VAL_B & VTD_PAGE_MASK_4K) + (QVTD_IOVA_2 & 0xfff);
210
211 /* --- Setup: IOVA -> PA_A (built by helper) and IOVA_2 -> PA_A_2 --- */
212 qvtd_build_translation(qts, mode, dev->devfn);
213 qtest_writeq(qts, qvtd_leaf_pte_addr(QVTD_IOVA_2),
214 qvtd_make_leaf_pte(QVTD_PT_VAL_2 & VTD_PAGE_MASK_4K, mode));
215 qvtd_program_regs(qts, Q35_HOST_BRIDGE_IOMMU_ADDR, mode);
216
217 /* Populate both IOTLB entries. */
218 qtest_memset(qts, pa_a, 0, DMA_LEN);
219 qtest_memset(qts, pa_a2, 0, DMA_LEN);
220 result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_a,
221 DMA_LEN, 0);
222 g_assert_cmpuint(result, ==, 0);
223 result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA_2, pa_a2,
224 DMA_LEN, 0);
225 g_assert_cmpuint(result, ==, 0);
226
227 /* Rewrite both leaf PTEs to PA_B* without invalidating. */
228 qtest_writeq(qts, qvtd_leaf_pte_addr(QVTD_IOVA),
229 qvtd_make_leaf_pte(QVTD_PT_VAL_B & VTD_PAGE_MASK_4K, mode));
230 qtest_writeq(qts, qvtd_leaf_pte_addr(QVTD_IOVA_2),
231 qvtd_make_leaf_pte(QVTD_PT_VAL_B & VTD_PAGE_MASK_4K, mode));
232
233 /* Page-selective invalidation of IOVA only. */
234 tail = qvtd_submit_iotlb_page_inv(qts, Q35_HOST_BRIDGE_IOMMU_ADDR,
235 QVTD_DOMAIN_ID, QVTD_IOVA, 0, tail);
236 tail = qvtd_submit_inv_wait_and_poll(qts, Q35_HOST_BRIDGE_IOMMU_ADDR,
237 tail);
238
239 /* IOVA was flushed: fresh walk reaches PA_B. */
240 qtest_memset(qts, pa_a, 0, DMA_LEN);
241 qtest_memset(qts, pa_b, 0, DMA_LEN);
242 result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA, pa_b,
243 DMA_LEN, 0);
244 g_assert_cmpuint(result, ==, 0);
245
246 /*
247 * IOVA_2's fate, verified against its original page PA_A_2:
248 * - second-level: entry survives, stale cache serves PA_A_2 -> success;
249 * - first-level: entry was flushed domain-wide, fresh walk reaches
250 * PA_B_2 -> MISMATCH against PA_A_2.
251 */
252 qtest_memset(qts, pa_a2, 0, DMA_LEN);
253 qtest_memset(qts, pa_b2, 0, DMA_LEN);
254 result = qos_iommu_testdev_trigger_dma(dev, bar, QVTD_IOVA_2, pa_a2,
255 DMA_LEN, 0);
256 if (fl_domain_wide) {
257 g_assert_cmpuint(result, ==, ITD_DMA_ERR_MISMATCH);
258 } else {
259 g_assert_cmpuint(result, ==, 0);
260 }
261
262 g_free(dev);
263 qpci_free_pc(pcibus);
264 qtest_quit(qts);
265 }
266
267 /*
268 * scalable-flt is covered here even though, per the VT-d spec, first-level
269 * mappings are invalidated with the PASID-based descriptor
270 * (VTD_INV_DESC_PIOTLB). QEMU keeps first- and second-level mappings in a
271 * single IOTLB that the legacy VTD_INV_DESC_IOTLB descriptor flushes for
272 * every level, so this test drives that descriptor across all modes.
273 * PASID-selective (PIOTLB) invalidation is a separate path, left for a
274 * follow-up.
275 */
276 static const struct {
277 const char *name;
278 QVTDTransMode mode;
279 } trans_modes[] = {
280 { "legacy", QVTD_TM_LEGACY_TRANS },
281 { "scalable-slt", QVTD_TM_SCALABLE_SLT },
282 { "scalable-flt", QVTD_TM_SCALABLE_FLT },
283 };
284
285 static const struct {
286 const char *name;
287 IOTLBInvGranularity gran;
288 } granularities[] = {
289 { "global", IOTLB_INV_GLOBAL },
290 { "domain", IOTLB_INV_DOMAIN },
291 { "page", IOTLB_INV_PAGE },
292 };
293
294 typedef struct {
295 QVTDTransMode mode;
296 IOTLBInvGranularity gran;
297 } TestCase;
298
299 static void test_iotlb_inv(const void *opaque)
300 {
301 const TestCase *tc = opaque;
302
303 run_iotlb_inv_test(tc->mode, tc->gran);
304 }
305
306 static void test_page_selectivity(const void *opaque)
307 {
308 const QVTDTransMode *mode = opaque;
309
310 run_page_selectivity_test(*mode);
311 }
312
313 int main(int argc, char **argv)
314 {
315 g_test_init(&argc, &argv, NULL);
316
317 for (size_t m = 0; m < ARRAY_SIZE(trans_modes); m++) {
318 for (size_t g = 0; g < ARRAY_SIZE(granularities); g++) {
319 TestCase *tc = g_new(TestCase, 1);
320 char *path;
321
322 tc->mode = trans_modes[m].mode;
323 tc->gran = granularities[g].gran;
324
325 path = g_strdup_printf("/iommu-testdev/intel/iotlb-inv/%s-%s",
326 granularities[g].name,
327 trans_modes[m].name);
328 qtest_add_data_func_full(path, tc, test_iotlb_inv, g_free);
329 g_free(path);
330 }
331 }
332
333 for (size_t m = 0; m < ARRAY_SIZE(trans_modes); m++) {
334 QVTDTransMode *mode = g_new(QVTDTransMode, 1);
335 char *path;
336
337 *mode = trans_modes[m].mode;
338 path = g_strdup_printf(
339 "/iommu-testdev/intel/iotlb-inv/page-selective/%s",
340 trans_modes[m].name);
341 qtest_add_data_func_full(path, mode, test_page_selectivity, g_free);
342 g_free(path);
343 }
344
345 return g_test_run();
346 }