| 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 | #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. |
| 56 | */ |
| 57 | #define QVTD_FAULT_IRQ_ADDR 0xfee00000 /* APIC base */ |
| 58 | #define QVTD_FAULT_IRQ_DATA 0x0 |
| 59 | |
| 60 | /* Scalable mode PASID structures */ |
| 61 | #define QVTD_PASID_DIR_BASE (QVTD_MEM_BASE + 0x00030000) |
| 62 | #define QVTD_PASID_TABLE_BASE (QVTD_MEM_BASE + 0x00031000) |
| 63 | |
| 64 | /* Page table entry size (8 bytes per PTE) */ |
| 65 | #define QVTD_PTE_SIZE sizeof(uint64_t) |
| 66 | |
| 67 | /* FSPTPTR mask: same as VTD_SM_PASID_ENTRY_SSPTPTR, bits[63:12] */ |
| 68 | #define QVTD_SM_PASID_ENTRY_FSPTPTR VTD_SM_PASID_ENTRY_SSPTPTR |
| 69 | |
| 70 | /* Default Domain ID for single-domain tests */ |
| 71 | #define QVTD_DOMAIN_ID 0 |
| 72 | |
| 73 | /* Test IOVA and target physical address */ |
| 74 | #define QVTD_IOVA 0x0000000010200567ull |
| 75 | #define QVTD_PT_VAL (QVTD_MEM_BASE + 0x00100000) |
| 76 | |
| 77 | /* |
| 78 | * Translation modes supported by Intel IOMMU |
| 79 | */ |
| 80 | typedef enum QVTDTransMode { |
| 81 | QVTD_TM_LEGACY_PT, /* Legacy pass-through mode */ |
| 82 | QVTD_TM_LEGACY_TRANS, /* Legacy translated mode (4-level paging) */ |
| 83 | QVTD_TM_SCALABLE_PT, /* Scalable pass-through mode */ |
| 84 | QVTD_TM_SCALABLE_SLT, /* Scalable Second Level Translation */ |
| 85 | QVTD_TM_SCALABLE_FLT, /* Scalable First Level Translation */ |
| 86 | } QVTDTransMode; |
| 87 | |
| 88 | static inline bool qvtd_is_scalable(QVTDTransMode mode) |
| 89 | { |
| 90 | return mode == QVTD_TM_SCALABLE_PT || |
| 91 | mode == QVTD_TM_SCALABLE_SLT || |
| 92 | mode == QVTD_TM_SCALABLE_FLT; |
| 93 | } |
| 94 | |
| 95 | typedef struct QVTDTestConfig { |
| 96 | QVTDTransMode trans_mode; /* Translation mode */ |
| 97 | uint64_t dma_gpa; /* GPA for readback validation */ |
| 98 | uint32_t dma_len; /* DMA length for testing */ |
| 99 | uint32_t expected_result; /* Expected DMA result */ |
| 100 | } QVTDTestConfig; |
| 101 | |
| 102 | typedef struct QVTDTestContext { |
| 103 | QTestState *qts; /* QTest state handle */ |
| 104 | QPCIDevice *dev; /* PCI device handle */ |
| 105 | QPCIBar bar; /* PCI BAR for MMIO access */ |
| 106 | QVTDTestConfig config; /* Test configuration */ |
| 107 | uint64_t iommu_base; /* Intel IOMMU base address */ |
| 108 | uint32_t trans_status; /* Translation configuration status */ |
| 109 | uint32_t dma_result; /* DMA operation result */ |
| 110 | uint16_t sid; /* Source ID (bus:devfn) */ |
| 111 | } QVTDTestContext; |
| 112 | |
| 113 | /* |
| 114 | * qvtd_setup_and_enable_translation - Complete translation setup and enable |
| 115 | * |
| 116 | * @ctx: Test context containing configuration and device handles |
| 117 | * |
| 118 | * Returns: Translation status (0 = success, non-zero = error) |
| 119 | * |
| 120 | * This function performs the complete translation setup sequence: |
| 121 | * 1. Builds VT-d structures (root/context entry, page tables) |
| 122 | * 2. Programs IOMMU registers and enables translation |
| 123 | * 3. Returns configuration status |
| 124 | */ |
| 125 | uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx); |
| 126 | |
| 127 | /* |
| 128 | * qvtd_build_translation - Build Intel IOMMU translation structures |
| 129 | * |
| 130 | * @qts: QTest state handle |
| 131 | * @mode: Translation mode (pass-through or translated) |
| 132 | * @sid: Source ID (bus:devfn) |
| 133 | * |
| 134 | * Returns: Build status (0 = success, non-zero = error) |
| 135 | * |
| 136 | * Constructs all necessary VT-d translation structures in guest memory: |
| 137 | * - Root Entry for the device's bus |
| 138 | * - Context Entry for the device |
| 139 | * - Complete 4-level page table hierarchy (if translated mode) |
| 140 | */ |
| 141 | uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode, |
| 142 | uint16_t sid); |
| 143 | |
| 144 | /* |
| 145 | * qvtd_program_regs - Program Intel IOMMU registers and enable translation |
| 146 | * |
| 147 | * @qts: QTest state handle |
| 148 | * @iommu_base: IOMMU base address |
| 149 | * @mode: Translation mode (scalable modes set RTADDR SMT bit) |
| 150 | * |
| 151 | * Programs IOMMU registers with the following sequence: |
| 152 | * 1. Set root table pointer (SRTP), with SMT bit for scalable mode |
| 153 | * 2. Setup invalidation queue (QIE) |
| 154 | * 3. Configure fault event MSI |
| 155 | * 4. Enable translation (TE) |
| 156 | * |
| 157 | * Each step verifies completion via GSTS register read-back. |
| 158 | */ |
| 159 | void qvtd_program_regs(QTestState *qts, uint64_t iommu_base, |
| 160 | QVTDTransMode mode); |
| 161 | |
| 162 | /* |
| 163 | * qvtd_setup_translation_tables - Setup complete VT-d page table hierarchy |
| 164 | * |
| 165 | * @qts: QTest state handle |
| 166 | * @iova: Input Virtual Address to translate |
| 167 | * @mode: Translation mode |
| 168 | * |
| 169 | * This builds the 4-level page table structure for translating |
| 170 | * the given IOVA to PA through Intel VT-d. The structure is: |
| 171 | * - PML4 (Level 4): IOVA bits [47:39] |
| 172 | * - PDPT (Level 3): IOVA bits [38:30] |
| 173 | * - PD (Level 2): IOVA bits [29:21] |
| 174 | * - PT (Level 1): IOVA bits [20:12] |
| 175 | * - Page offset: IOVA bits [11:0] |
| 176 | * |
| 177 | * The function writes all necessary Page Table Entries (PTEs) to guest |
| 178 | * memory using qtest_writeq(), setting up the complete translation path |
| 179 | * that the VT-d hardware will traverse during DMA operations. |
| 180 | */ |
| 181 | void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova, |
| 182 | QVTDTransMode mode); |
| 183 | |
| 184 | /* Calculate expected DMA result */ |
| 185 | uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx); |
| 186 | |
| 187 | /* Build DMA attributes for Intel VT-d */ |
| 188 | uint32_t qvtd_build_dma_attrs(void); |
| 189 | |
| 190 | /* High-level test execution helpers */ |
| 191 | void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev, |
| 192 | QPCIBar bar, uint64_t iommu_base, |
| 193 | const QVTDTestConfig *cfg); |
| 194 | |
| 195 | /* |
| 196 | * qvtd_iommu_args - Build the -device intel-iommu command-line fragment |
| 197 | * for the requested translation mode. |
| 198 | */ |
| 199 | const char *qvtd_iommu_args(QVTDTransMode mode); |
| 200 | |
| 201 | /* |
| 202 | * qvtd_check_caps - Check whether the running QEMU exposes the ECAP bits |
| 203 | * required by @mode. Calls g_test_skip() and returns |
| 204 | * false when a required capability is missing. |
| 205 | */ |
| 206 | bool qvtd_check_caps(QTestState *qts, QVTDTransMode mode); |
| 207 | |
| 208 | /* |
| 209 | * qvtd_setup_qtest_pci_device - Create a QPCIBus, locate the iommu-testdev |
| 210 | * PCI function, enable it and map BAR0. |
| 211 | * |
| 212 | * On success, *pcibus and *bar are populated and the returned QPCIDevice |
| 213 | * must be released by the caller via g_free(). |
| 214 | */ |
| 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 */ |