@samitouri / QOSamiQemu / commits / 3c774326fe

tests/qtest/libqos: add Intel IOMMU invalidation helpers

Add the building blocks a queued-invalidation test needs on top of the existing translation helpers: - qvtd_leaf_pte_addr() / qvtd_make_leaf_pte() let a test locate and rewrite the leaf PTE built by qvtd_setup_translation_tables() without re-deriving the page-table index or leaf attributes by hand. The attributes reuse qvtd_get_fl_pte_attrs()/qvtd_get_pte_attrs() so the first- and second-level leaf formats stay defined in one place. - qvtd_submit_iotlb_global_inv() / _domain_inv() / _page_inv() write an IOTLB Invalidation Descriptor (global / domain-selective / page-selective) into the Invalidation Queue and advance IQT_REG. - qvtd_submit_inv_wait_and_poll() submits an Invalidation Wait Descriptor with Status Write and polls the status word with a bounded retry loop, asserting on timeout. No caller yet; used by the IOTLB invalidation test that follows. Signed-off-by: Junjie Cao <junjie.cao@intel.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-ID: <20260703072200.463082-3-junjie.cao@intel.com>

Junjie Cao committed Jul 3, 2026 at 15:21 UTC 3c774326fe1ee8182666b00748609a7751b9d0b6
2 files changed +159
tests/qtest/libqos/qos-intel-iommu.c
+108
@@ -15,6 +15,10 @@
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)
@@ -232,6 +236,30 @@ static uint64_t qvtd_get_fl_pte_attrs(bool is_leaf)
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 {
@@ -529,3 +557,83 @@ QPCIDevice *qvtd_setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus,
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 +}
tests/qtest/libqos/qos-intel-iommu.h
+51
@@ -40,6 +40,16 @@
40 */
41 #define QVTD_IQ_BASE (QVTD_MEM_BASE + 0x00020000)
42 #define QVTD_IQ_QS 0 /* QS=0 → 256 entries */
43 +#define QVTD_IQ_DESC_SIZE 16 /* 128-bit descriptor (iq_dw=0) */
44 +#define QVTD_IQT_SHIFT 4 /* IQT_REG[18:4] is the tail index */
45 +
46 +/*
47 + * Invalidation Wait Descriptor with Status Write (VT-d 6.5.2.8).
48 + * The IOMMU writes QVTD_INV_WAIT_DATA to QVTD_INV_WAIT_ADDR after all
49 + * preceding invalidation descriptors complete.
50 + */
51 +#define QVTD_INV_WAIT_ADDR (QVTD_MEM_BASE + 0x00040000)
52 +#define QVTD_INV_WAIT_DATA 0x1u
53
54 /*
55 * Fault Event MSI configuration.
@@ -205,4 +215,45 @@ bool qvtd_check_caps(QTestState *qts, QVTDTransMode mode);
215 QPCIDevice *qvtd_setup_qtest_pci_device(QTestState *qts, QPCIBus **pcibus,
216 QPCIBar *bar);
217
218 +/*
219 + * qvtd_leaf_pte_addr - Address of the leaf (L1) PTE for @iova, assuming
220 + * the hierarchy was built by
221 + * qvtd_setup_translation_tables() with 4 KB leaves.
222 + */
223 +uint64_t qvtd_leaf_pte_addr(uint64_t iova);
224 +
225 +/*
226 + * qvtd_make_leaf_pte - Build a leaf PTE value mapping @pa, for the
227 + * 4-level / 4 KB-leaf layout used by
228 + * qvtd_setup_translation_tables().
229 + * Selects FLT or SLT/legacy attributes from @mode.
230 + */
231 +uint64_t qvtd_make_leaf_pte(uint64_t pa, QVTDTransMode mode);
232 +
233 +/*
234 + * qvtd_submit_inv_wait_and_poll - Submit an Invalidation Wait Descriptor
235 + * with Status Write and poll until the
236 + * IOMMU writes the expected status data.
237 + *
238 + * Asserts on timeout. Returns the new tail index.
239 + */
240 +uint32_t qvtd_submit_inv_wait_and_poll(QTestState *qts, uint64_t iommu_base,
241 + uint32_t tail);
242 +
243 +/*
244 + * qvtd_submit_iotlb_global_inv - global IOTLB invalidation.
245 + * qvtd_submit_iotlb_domain_inv - domain-selective IOTLB invalidation.
246 + * qvtd_submit_iotlb_page_inv - page-selective IOTLB invalidation;
247 + * @addr must be 4 KB aligned. @am
248 + * selects the address mask per VT-d
249 + * 6.5.2.4 (am=0 → single 4 KB page).
250 + */
251 +uint32_t qvtd_submit_iotlb_global_inv(QTestState *qts, uint64_t iommu_base,
252 + uint32_t tail);
253 +uint32_t qvtd_submit_iotlb_domain_inv(QTestState *qts, uint64_t iommu_base,
254 + uint16_t domain_id, uint32_t tail);
255 +uint32_t qvtd_submit_iotlb_page_inv(QTestState *qts, uint64_t iommu_base,
256 + uint16_t domain_id, uint64_t addr,
257 + uint8_t am, uint32_t tail);
258 +
259 #endif /* QTEST_LIBQOS_INTEL_IOMMU_H */