From nobody Tue Apr 7 10:40:51 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1773450786; cv=none; d=zohomail.com; s=zohoarc; b=c9gx85XjCHgkgWDJ7modjr0ia/qwlgNJHqvf1vOLIz/u9UDHEEsZwVeQQZb8OPIDMZpt0HmqJzISKpMJItNSAkZh7bFZRK+kDotjtVybUafUFmHpCq23Fp5aw0i7yUhppc2tTInN2jMFNbhmNbQ3tzqmwYYQIKZ71bCJiZ6giMQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1773450786; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=0aLW/0hxdIc2xeObsl921VP8ly7B8yOSID9yBQ30DaU=; b=hKB4h1QqN+1b5y1udHq/3kZnqDCC07hKmqUVphuvRVPF4MXN8KYqcN6IIklsZ8XyWkyRQvt5mZXy38wfH/aM9Io51kOI6r4O0nBTwUJfTNvFGSku77uwgj8ivNe7bzgE1zENTNpVqW4B5k8wjoExbinONYsVfA0xUs1Cj0TuSVc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from=<15fengyuan@gmail.com> (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 177345078612153.353506966241866; Fri, 13 Mar 2026 18:13:06 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1w1DYa-00015A-2X; Fri, 13 Mar 2026 21:12:44 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from <15fengyuan@gmail.com>) id 1w1DYW-00012R-3K for qemu-devel@nongnu.org; Fri, 13 Mar 2026 21:12:40 -0400 Received: from mail-pj1-x102a.google.com ([2607:f8b0:4864:20::102a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from <15fengyuan@gmail.com>) id 1w1DYS-0005oG-Ie for qemu-devel@nongnu.org; Fri, 13 Mar 2026 21:12:39 -0400 Received: by mail-pj1-x102a.google.com with SMTP id 98e67ed59e1d1-359f35dfef6so1545850a91.2 for ; Fri, 13 Mar 2026 18:12:36 -0700 (PDT) Received: from orion-o6.tail020997.ts.net ([2408:8352:441:f661::1008]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-35a2454a7bdsm1248545a91.8.2026.03.13.18.12.30 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 13 Mar 2026 18:12:34 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1773450755; x=1774055555; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=0aLW/0hxdIc2xeObsl921VP8ly7B8yOSID9yBQ30DaU=; b=Cn54uoARV5nTFrFhqFRybZIdEuWJEnTk2FUmdQDqHQDgf93IOBUNa2cnj0KMDh1iGZ K1siTPy9BhfQlYz376UAKGqKi5gNhDJcYZzwW+pDmepL7wdq8WrngKXRTugMikJiiaWU 4CyCE7Kc0m4le3wDGa500YKym6ydZfn/CaRVZNYoSSBlg2hXvlusdRwqiBlpyWAU2Hw0 zaeDPjtyejFZfT/1Ji3fUGNMqMMCzp+1z461bCT3NXJZMDjAHvxu0RAQGDMNvmxw75f/ dy/l647g572dVgkxf/EqKNl/0/hM3lwcszRy4kdlcdw1Ol+dNhFJuHi1dOcC+eCU3Tm0 hVLQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773450755; x=1774055555; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=0aLW/0hxdIc2xeObsl921VP8ly7B8yOSID9yBQ30DaU=; b=AEw6Onb0MiTp1pkJW3/V/lY8ZqVyBrCf+lpbIYEvW3Rgzthv9TbcgPLoRpbRtHAxkW 7hlp7jGoi0AvTTl16lI9VwF3fxvSel/Jy+iH1ulddDnJLqgy7Zuz3FOPzJKFaNMAnaWr u4qB7vdwCJawKshzNabSBlsP+IEu1nrV3BURUmPYCYeh5iWI6GrVOP/SSQ9WvxB8z19y aulnJHga1Qqm68INdYnH2rF7mz3TE7fVXLFGNVf+AJVQIXJDQ9rLMkHmavqMlHshORIF bYHEL5kV/f3Ua0iXmZvQ7PPq3X+7VAlB6g3p3t6aDS1ysbshZ1XbtsBlukRePJsEgp5Z 94Zg== X-Gm-Message-State: AOJu0YyHmxtsrY+iwInccdX7kqFNbGIfH+Tn9hNW5Fmy+Pl83pLYBHp1 1GktildSNanV/QUj5aNe+86HXGJAxNtMYpsOeTgrZbkm5RbdGSZvwIBS X-Gm-Gg: ATEYQzxbobQs4VUSLu5PLrOAhwQINA6zvQNzr7xirFmR9JnEk9zTuzLhY9kROtEKcYy CX/nqWiHYfntve0zPrLzFnpo+nKxvJ9OKnkSvSfxn3T4Xa1Db4C67PHyqGKl7Hbn3SIRR6zoAsa I8cXhx4tG4+H9N6sH/SBtkTZw/snHM+AXHFlwKztFwUaU/b7vNoBwUla0JOYhfht0ANi7G3ol2B n1otqgOUlwcYkQH0COrIE7S672+h/xU8eeQO265x9StFF0jdrgS0YuThS5+lwQWvViD2HwBwig4 OdrcteJTR0WZUSlq91Y+AFDKFfOfZCIcB8NH9jeqN7zLxmkCLWAJRBw4MaGObApAWjILdtmP66Q AnkqnyXQ/DcF/Twybtl6HM0Lce3mA/oKjy2ceovM00Pg9GUHj4U2nrVIzHBPVy3M1qDYhrjmZSe VX5gyjzYn+9ZqU+DTpuUfPnPNCyPc= X-Received: by 2002:a17:90b:3908:b0:34c:6124:3616 with SMTP id 98e67ed59e1d1-35a22055899mr4544995a91.27.1773450754838; Fri, 13 Mar 2026 18:12:34 -0700 (PDT) From: Fengyuan Yu <15fengyuan@gmail.com> To: Tao Tang , "Michael S. Tsirkin" , Jason Wang , Yi Liu , =?UTF-8?q?Cl=C3=A9ment=20Mathieu--Drif?= , Paolo Bonzini , Zhao Liu , Fabiano Rosas , Laurent Vivier Cc: qemu-devel@nongnu.org, Chao Liu , Fengyuan Yu <15fengyuan@gmail.com> Subject: [PATCH v2 1/2] tests/qtest/libqos: Add Intel IOMMU helper library Date: Sat, 14 Mar 2026 09:11:36 +0800 Message-Id: <75efb1c5f4088f449754ac05d0aaff4e70e8329f.1773450180.git.15fengyuan@gmail.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: References: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2607:f8b0:4864:20::102a; envelope-from=15fengyuan@gmail.com; helo=mail-pj1-x102a.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1773450787358158500 Signed-off-by: Fengyuan Yu <15fengyuan@gmail.com> --- MAINTAINERS | 6 + tests/qtest/libqos/meson.build | 3 + tests/qtest/libqos/qos-intel-iommu.c | 454 +++++++++++++++++++++++++++ tests/qtest/libqos/qos-intel-iommu.h | 191 +++++++++++ 4 files changed, 654 insertions(+) create mode 100644 tests/qtest/libqos/qos-intel-iommu.c create mode 100644 tests/qtest/libqos/qos-intel-iommu.h diff --git a/MAINTAINERS b/MAINTAINERS index 247799c817..876e00ff77 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -151,6 +151,9 @@ F: target/i386/meson.build F: tools/i386/ F: tests/functional/i386/ F: tests/functional/x86_64/ +F: tests/qtest/intel-iommu-test.c +F: tests/qtest/libqos/qos-intel-iommu* +F: tests/qtest/iommu-intel-test.c =20 X86 VM file descriptor change on reset test M: Ani Sinha @@ -4026,6 +4029,9 @@ F: hw/i386/intel_iommu_accel.* F: include/hw/i386/intel_iommu.h F: tests/functional/x86_64/test_intel_iommu.py F: tests/qtest/intel-iommu-test.c +F: tests/qtest/libqos/qos-intel-iommu* +F: tests/qtest/iommu-intel-test.c + =20 AMD-Vi Emulation M: Alejandro Jimenez diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build index 4a69acad0d..96f2fc48b4 100644 --- a/tests/qtest/libqos/meson.build +++ b/tests/qtest/libqos/meson.build @@ -73,6 +73,9 @@ endif if config_all_devices.has_key('CONFIG_RISCV_IOMMU') libqos_srcs +=3D files('riscv-iommu.c', 'qos-riscv-iommu.c') endif +if config_all_devices.has_key('CONFIG_VTD') + libqos_srcs +=3D files('qos-intel-iommu.c') +endif if config_all_devices.has_key('CONFIG_TPCI200') libqos_srcs +=3D files('tpci200.c') endif diff --git a/tests/qtest/libqos/qos-intel-iommu.c b/tests/qtest/libqos/qos-= intel-iommu.c new file mode 100644 index 0000000000..b1baf5ea7c --- /dev/null +++ b/tests/qtest/libqos/qos-intel-iommu.c @@ -0,0 +1,454 @@ +/* + * QOS Intel IOMMU (VT-d) Module Implementation + * + * This module provides Intel IOMMU-specific helper functions for libqos t= ests. + * + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/i386/intel_iommu_internal.h" +#include "tests/qtest/libqos/pci.h" +#include "qos-iommu-testdev.h" +#include "qos-intel-iommu.h" + +#define QVTD_AW_48BIT_ENCODING 2 + +uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx) +{ + return ctx->config.expected_result; +} + +uint32_t qvtd_build_dma_attrs(void) +{ + /* + * VT-d obtains the Requester ID (Source ID) from PCI bus/devfn routing + * via pci_device_iommu_address_space(), not from DMA attributes. + * + * For scalable mode, QEMU maps MemTxAttrs.pid=3D=3D0 to PCI_NO_PASID, + * then remaps PCI_NO_PASID to PASID_0 when root_scalable is set. + * So returning 0 here implicitly selects PASID=3D0, which matches + * the PASID entry we configure in qvtd_build_pasid_table_entry(). + * + */ + return 0; +} + +static void qvtd_build_root_entry(QTestState *qts, uint8_t bus, + uint64_t context_table_ptr, + QVTDTransMode mode) +{ + uint64_t root_entry_addr =3D QVTD_ROOT_TABLE_BASE + + (bus * sizeof(VTDRootEntry)); + uint64_t lo, hi; + + if (qvtd_is_scalable(mode)) { + /* + * Scalable-mode Root Entry (Section 9.2): + * lo =3D Lower Context Table Pointer + LP (Lower Present) + * hi =3D Upper Context Table Pointer + UP (Upper Present) + * + * Lower table covers devfn 0-127, Upper covers devfn 128-255. + * Only lower half is needed for test device (devfn < 128). + */ + lo =3D (context_table_ptr & VTD_ROOT_ENTRY_CTP) | VTD_ROOT_ENTRY_P; + hi =3D 0; /* UP=3D0: upper context table not present */ + } else { + /* + * Legacy Root Entry (Section 9.1): + * lo =3D Context Table Pointer + Present + * hi =3D Reserved + */ + lo =3D (context_table_ptr & VTD_ROOT_ENTRY_CTP) | VTD_ROOT_ENTRY_P; + hi =3D 0; + } + + qtest_writeq(qts, root_entry_addr, lo); + qtest_writeq(qts, root_entry_addr + 8, hi); +} + +static void qvtd_build_context_entry(QTestState *qts, uint16_t sid, + QVTDTransMode mode, uint64_t ssptptr) +{ + uint8_t devfn =3D sid & 0xff; + uint64_t context_entry_addr =3D QVTD_CONTEXT_TABLE_BASE + + (devfn * VTD_CTX_ENTRY_LEGACY_SIZE); + uint64_t lo, hi; + + if (mode =3D=3D QVTD_TM_LEGACY_PT) { + /* + * Pass-through mode (Section 9.3): + * lo: P + FPD(=3D0, fault enabled) + TT(=3DPass-through) + * hi: DID + AW + */ + lo =3D VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_PASS_THROUGH; + hi =3D ((uint64_t)QVTD_DOMAIN_ID << 8) | QVTD_AW_48BIT_ENCODING; + } else { + /* + * Translated mode (Section 9.3): + * lo: P + FPD(=3D0, fault enabled) + TT(=3DMulti-level) + SSPTPTR + * hi: DID + AW(=3D48-bit, 4-level) + */ + lo =3D VTD_CONTEXT_ENTRY_P | VTD_CONTEXT_TT_MULTI_LEVEL | + (ssptptr & VTD_CONTEXT_ENTRY_SSPTPTR); + hi =3D ((uint64_t)QVTD_DOMAIN_ID << 8) | QVTD_AW_48BIT_ENCODING; + } + + qtest_writeq(qts, context_entry_addr, lo); + qtest_writeq(qts, context_entry_addr + 8, hi); +} + +static void qvtd_build_scalable_context_entry(QTestState *qts, uint16_t si= d) +{ + uint8_t devfn =3D sid & 0xff; + uint64_t ce_addr =3D QVTD_CONTEXT_TABLE_BASE + + (devfn * VTD_CTX_ENTRY_SCALABLE_SIZE); + + /* + * Scalable-Mode Context Entry (Section 9.4), 32 bytes =3D 4 qwords: + * + * val[0]: P + FPD(=3D0) + DTE(=3D0) + PASIDE(=3D0) + PRE(=3D0) + HPTE= (=3D0) + * + EPTR(=3D0) + PDTS(=3D0) + PASIDDIRPTR + * val[1]: RID_PASID(=3D0) + PDTTE(=3D0) + PRE(=3D0) + RID_CG(=3D0) + * val[2]: Reserved (must be 0) + * val[3]: Reserved (must be 0) + */ + qtest_writeq(qts, ce_addr, + (QVTD_PASID_DIR_BASE & VTD_PASID_DIR_BASE_ADDR_MASK) | + VTD_CONTEXT_ENTRY_P); + qtest_writeq(qts, ce_addr + 8, 0); + qtest_writeq(qts, ce_addr + 16, 0); + qtest_writeq(qts, ce_addr + 24, 0); +} + +static void qvtd_build_pasid_dir_entry(QTestState *qts) +{ + uint64_t addr =3D QVTD_PASID_DIR_BASE + + VTD_PASID_DIR_INDEX(0) * VTD_PASID_DIR_ENTRY_SIZE; + + /* + * PASID Directory Entry (Section 9.5): + * P + FPD(=3D0, fault enabled) + SMPTBLPTR + */ + qtest_writeq(qts, addr, + (QVTD_PASID_TABLE_BASE & VTD_PASID_TABLE_BASE_ADDR_MASK) | + VTD_PASID_ENTRY_P); +} + +static void qvtd_build_pasid_table_entry(QTestState *qts, QVTDTransMode mo= de, + uint64_t ptptr) +{ + uint64_t addr =3D QVTD_PASID_TABLE_BASE + + VTD_PASID_TABLE_INDEX(0) * VTD_PASID_ENTRY_SIZE; + uint64_t val0, val1, val2; + + /* + * Scalable-Mode PASID Table Entry (Section 9.6), 64 bytes =3D 8 qword= s: + * + * val[0]: P + FPD(=3D0) + AW + PGTT + SSADE(=3D0) + SSPTPTR + * val[1]: DID + PWSNP(=3D0) + PGSNP(=3D0) + * + CD(=3D0) + EMTE(=3D0) + PAT(=3D0): Memory Type, + * all Reserved(0) since QEMU ECAP.MTS=3D0 + * val[2]: SRE(=3D0) + FSPM(=3D0, 4-level) + WPE(=3D0) + IGN + EAFE(= =3D0) + FSPTPTR + * val[3]: Reserved (must be 0) + * val[4]: HPT fields, Reserved(0) since QEMU ECAP.HPTS=3D0 + * val[5]: HPT fields, Reserved(0) since QEMU ECAP.HPTS=3D0 + * val[6]: Reserved (must be 0) + * val[7]: Reserved (must be 0) + */ + switch (mode) { + case QVTD_TM_SCALABLE_PT: + val0 =3D VTD_PASID_ENTRY_P | + ((uint64_t)VTD_SM_PASID_ENTRY_PT << 6); + val1 =3D (uint64_t)QVTD_DOMAIN_ID; + val2 =3D 0; + break; + case QVTD_TM_SCALABLE_SLT: + val0 =3D VTD_PASID_ENTRY_P | + ((uint64_t)VTD_SM_PASID_ENTRY_SST << 6) | + ((uint64_t)QVTD_AW_48BIT_ENCODING << 2) | + (ptptr & VTD_SM_PASID_ENTRY_SSPTPTR); + val1 =3D (uint64_t)QVTD_DOMAIN_ID; + val2 =3D 0; + break; + case QVTD_TM_SCALABLE_FLT: + /* + * val[2] fields for FLT (Section 9.6): + * SRE(=3D0, user-level DMA only) + FSPM(=3D0, 4-level) + + * WPE(=3D0, no supervisor write-protect) + IGN + EAFE(=3D0) + FSP= TPTR + */ + val0 =3D VTD_PASID_ENTRY_P | + ((uint64_t)VTD_SM_PASID_ENTRY_FST << 6); + val1 =3D (uint64_t)QVTD_DOMAIN_ID; + val2 =3D ptptr & QVTD_SM_PASID_ENTRY_FSPTPTR; + break; + default: + g_assert_not_reached(); + } + + qtest_writeq(qts, addr, val0); + qtest_writeq(qts, addr + 8, val1); + qtest_writeq(qts, addr + 16, val2); + qtest_writeq(qts, addr + 24, 0); + qtest_writeq(qts, addr + 32, 0); + qtest_writeq(qts, addr + 40, 0); + qtest_writeq(qts, addr + 48, 0); + qtest_writeq(qts, addr + 56, 0); +} + +/* + * VT-d second-level paging helpers. + * 4-level, 48-bit address space, 9 bits per level index. + */ +static uint32_t qvtd_get_table_index(uint64_t iova, int level) +{ + int shift =3D VTD_PAGE_SHIFT + VTD_LEVEL_BITS * (level - 1); + + return (iova >> shift) & ((1u << VTD_LEVEL_BITS) - 1); +} + +static uint64_t qvtd_get_table_addr(uint64_t base, int level, uint64_t iov= a) +{ + return base + (qvtd_get_table_index(iova, level) * QVTD_PTE_SIZE); +} + +static uint64_t qvtd_get_pte_attrs(void) +{ + /* Second-level: R/W in every paging entry (Section 3.7.1) */ + return VTD_SS_R | VTD_SS_W; +} + +static uint64_t qvtd_get_fl_pte_attrs(bool is_leaf) +{ + /* First-level: x86 page table format (VT-d spec Section 9.9) */ + uint64_t attrs =3D VTD_FS_P | VTD_FS_RW | VTD_FS_US | VTD_FS_A; + + if (is_leaf) { + attrs |=3D VTD_FS_D; + } + return attrs; +} + +void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova, + QVTDTransMode mode) +{ + bool is_fl =3D (mode =3D=3D QVTD_TM_SCALABLE_FLT); + uint64_t non_leaf_attrs, leaf_attrs; + + if (is_fl) { + non_leaf_attrs =3D qvtd_get_fl_pte_attrs(false); + leaf_attrs =3D qvtd_get_fl_pte_attrs(true); + } else { + /* Second-level: all levels use identical R/W attrs (spec 3.7.1) */ + non_leaf_attrs =3D qvtd_get_pte_attrs(); + leaf_attrs =3D non_leaf_attrs; + } + + g_test_message("Page table setup: IOVA=3D0x%" PRIx64 + " PA=3D0x%" PRIx64 " %s", + (uint64_t)iova, (uint64_t)QVTD_PT_VAL, + is_fl ? "first-level" : "second-level"); + + /* PML4 (L4) -> PDPT (L3) -> PD (L2) -> PT (L1) -> PA */ + qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L4_BASE, 4, iova), + QVTD_PT_L3_BASE | non_leaf_attrs); + qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L3_BASE, 3, iova), + QVTD_PT_L2_BASE | non_leaf_attrs); + qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L2_BASE, 2, iova), + QVTD_PT_L1_BASE | non_leaf_attrs); + qtest_writeq(qts, qvtd_get_table_addr(QVTD_PT_L1_BASE, 1, iova), + (QVTD_PT_VAL & VTD_PAGE_MASK_4K) | leaf_attrs); +} + +void qvtd_program_regs(QTestState *qts, uint64_t iommu_base, + QVTDTransMode mode) +{ + uint32_t gcmd =3D 0; + uint64_t rtaddr =3D QVTD_ROOT_TABLE_BASE; + + /* Set SMT bit for scalable mode (VT-d spec Section 9.1) */ + if (qvtd_is_scalable(mode)) { + rtaddr |=3D VTD_RTADDR_SMT; + } + + /* Set Root Table Address */ + qtest_writeq(qts, iommu_base + DMAR_RTADDR_REG, rtaddr); + + /* Set Root Table Pointer and verify */ + gcmd |=3D VTD_GCMD_SRTP; + qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd); + g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_RTPS); + + /* Setup Invalidation Queue */ + qtest_writeq(qts, iommu_base + DMAR_IQA_REG, + QVTD_IQ_BASE | QVTD_IQ_QS); + qtest_writeq(qts, iommu_base + DMAR_IQH_REG, 0); + qtest_writeq(qts, iommu_base + DMAR_IQT_REG, 0); + + /* Enable Queued Invalidation and verify */ + gcmd |=3D VTD_GCMD_QIE; + qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd); + g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_QIES); + + /* Setup Fault Event MSI */ + qtest_writel(qts, iommu_base + DMAR_FECTL_REG, 0x0); + qtest_writel(qts, iommu_base + DMAR_FEDATA_REG, QVTD_FAULT_IRQ_DATA); + qtest_writel(qts, iommu_base + DMAR_FEADDR_REG, QVTD_FAULT_IRQ_ADDR); + + /* Enable translation and verify */ + gcmd |=3D VTD_GCMD_TE; + qtest_writel(qts, iommu_base + DMAR_GCMD_REG, gcmd); + g_assert(qtest_readl(qts, iommu_base + DMAR_GSTS_REG) & VTD_GSTS_TES); +} + +uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode, + uint16_t sid) +{ + uint8_t bus =3D (sid >> 8) & 0xff; + + g_test_message("Build translation: IOVA=3D0x%" PRIx64 " PA=3D0x%" PRIx= 64 + " mode=3D%d", + (uint64_t)QVTD_IOVA, (uint64_t)QVTD_PT_VAL, mode); + + /* Clear IOMMU structure regions to avoid stale entries */ + qtest_memset(qts, QVTD_ROOT_TABLE_BASE, 0, 0x1000); + qtest_memset(qts, QVTD_PT_L4_BASE, 0, 0x4000); + + if (qvtd_is_scalable(mode)) { + /* Scalable: 32B context entries need 8KB */ + qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 0x2000); + qtest_memset(qts, QVTD_PASID_DIR_BASE, 0, 0x1000); + qtest_memset(qts, QVTD_PASID_TABLE_BASE, 0, 0x1000); + } else { + qtest_memset(qts, QVTD_CONTEXT_TABLE_BASE, 0, 0x1000); + } + + qvtd_build_root_entry(qts, bus, QVTD_CONTEXT_TABLE_BASE, mode); + + if (qvtd_is_scalable(mode)) { + /* Scalable path: context -> PASID dir -> PASID entry -> page tabl= es */ + qvtd_build_scalable_context_entry(qts, sid); + qvtd_build_pasid_dir_entry(qts); + + if (mode =3D=3D QVTD_TM_SCALABLE_PT) { + qvtd_build_pasid_table_entry(qts, mode, 0); + } else { + qvtd_setup_translation_tables(qts, QVTD_IOVA, mode); + qvtd_build_pasid_table_entry(qts, mode, QVTD_PT_L4_BASE); + } + } else { + /* Legacy path */ + if (mode =3D=3D QVTD_TM_LEGACY_PT) { + qvtd_build_context_entry(qts, sid, mode, 0); + } else { + qvtd_setup_translation_tables(qts, QVTD_IOVA, mode); + qvtd_build_context_entry(qts, sid, mode, QVTD_PT_L4_BASE); + } + } + + return 0; +} + +uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx) +{ + uint32_t build_result; + + /* Build translation structures first */ + build_result =3D qvtd_build_translation(ctx->qts, ctx->config.trans_mo= de, + ctx->sid); + if (build_result !=3D 0) { + g_test_message("Build failed: mode=3D%u sid=3D%u status=3D0x%x", + ctx->config.trans_mode, ctx->sid, build_result); + ctx->trans_status =3D build_result; + return ctx->trans_status; + } + + /* Program IOMMU registers (sets root table pointer, enables translati= on) */ + qvtd_program_regs(ctx->qts, ctx->iommu_base, ctx->config.trans_mode); + + ctx->trans_status =3D 0; + return ctx->trans_status; +} + +static bool qvtd_validate_test_result(QVTDTestContext *ctx) +{ + uint32_t expected =3D qvtd_expected_dma_result(ctx); + + g_test_message("-> Validating result: expected=3D0x%x actual=3D0x%x", + expected, ctx->dma_result); + return (ctx->dma_result =3D=3D expected); +} + +static uint32_t qvtd_single_translation_setup(void *opaque) +{ + return qvtd_setup_and_enable_translation(opaque); +} + +static uint32_t qvtd_single_translation_attrs(void *opaque) +{ + return qvtd_build_dma_attrs(); +} + +static bool qvtd_single_translation_validate(void *opaque) +{ + return qvtd_validate_test_result(opaque); +} + +static void qvtd_single_translation_report(void *opaque, uint32_t dma_resu= lt) +{ + QVTDTestContext *ctx =3D opaque; + + if (dma_result !=3D 0) { + g_test_message("DMA failed: mode=3D%u result=3D0x%x", + ctx->config.trans_mode, dma_result); + } else { + g_test_message("-> DMA succeeded: mode=3D%u", + ctx->config.trans_mode); + } +} + +void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev, + QPCIBar bar, uint64_t iommu_base, + const QVTDTestConfig *cfg) +{ + QVTDTestContext ctx =3D { + .qts =3D qts, + .dev =3D dev, + .bar =3D bar, + .iommu_base =3D iommu_base, + .config =3D *cfg, + .sid =3D dev->devfn, + }; + + QOSIOMMUTestdevDmaCfg dma =3D { + .dev =3D dev, + .bar =3D bar, + .iova =3D QVTD_IOVA, + .gpa =3D cfg->dma_gpa, + .len =3D cfg->dma_len, + }; + + qtest_memset(qts, cfg->dma_gpa, 0x00, cfg->dma_len); + qos_iommu_testdev_single_translation(&dma, &ctx, + qvtd_single_translation_setup, + qvtd_single_translation_attrs, + qvtd_single_translation_validate, + qvtd_single_translation_report, + &ctx.dma_result); + + if (ctx.dma_result =3D=3D 0 && ctx.config.expected_result =3D=3D 0) { + g_autofree uint8_t *buf =3D NULL; + + buf =3D g_malloc(ctx.config.dma_len); + qtest_memread(ctx.qts, ctx.config.dma_gpa, buf, ctx.config.dma_len= ); + + for (int i =3D 0; i < ctx.config.dma_len; i++) { + uint8_t expected; + + expected =3D (ITD_DMA_WRITE_VAL >> ((i % 4) * 8)) & 0xff; + g_assert_cmpuint(buf[i], =3D=3D, expected); + } + } +} diff --git a/tests/qtest/libqos/qos-intel-iommu.h b/tests/qtest/libqos/qos-= intel-iommu.h new file mode 100644 index 0000000000..04450165af --- /dev/null +++ b/tests/qtest/libqos/qos-intel-iommu.h @@ -0,0 +1,191 @@ +/* + * QOS Intel IOMMU (VT-d) Module + * + * This module provides Intel IOMMU-specific helper functions for libqos t= ests, + * encapsulating VT-d setup, assertion, and cleanup operations. + * + * Copyright (c) 2026 Fengyuan Yu <15fengyuan@gmail.com> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QTEST_LIBQOS_INTEL_IOMMU_H +#define QTEST_LIBQOS_INTEL_IOMMU_H + +#include "hw/misc/iommu-testdev.h" +#include "hw/i386/intel_iommu_internal.h" + +/* + * Intel IOMMU MMIO register base. This is the standard Q35 IOMMU address. + */ +#define Q35_IOMMU_BASE 0xfed90000ULL + +/* + * Guest memory layout for IOMMU structures. + * All structures are placed in guest physical memory inside the 512MB RAM. + * Using 256MB mark (0x10000000) as base to ensure all structures fit in R= AM. + */ +#define QVTD_MEM_BASE 0x10000000ULL + +/* Root Entry Table: 256 entries * 16 bytes =3D 4KB */ +#define QVTD_ROOT_TABLE_BASE (QVTD_MEM_BASE + 0x00000000) + +/* Context Entry Table: 256 entries, 16B (legacy) or 32B (scalable) per en= try */ +#define QVTD_CONTEXT_TABLE_BASE (QVTD_MEM_BASE + 0x00001000) + +/* Page Tables: 4-level hierarchy for 48-bit address translation */ +#define QVTD_PT_L4_BASE (QVTD_MEM_BASE + 0x00010000) /* PML4 */ +#define QVTD_PT_L3_BASE (QVTD_MEM_BASE + 0x00011000) /* PDPT */ +#define QVTD_PT_L2_BASE (QVTD_MEM_BASE + 0x00012000) /* PD */ +#define QVTD_PT_L1_BASE (QVTD_MEM_BASE + 0x00013000) /* PT */ + +/* + * Invalidation Queue. + * IQA_REG bits[2:0] =3D QS, entries =3D 1 << (QS + 8), each entry 16 byte= s. + */ +#define QVTD_IQ_BASE (QVTD_MEM_BASE + 0x00020000) +#define QVTD_IQ_QS 0 /* QS=3D0 =E2=86=92 256 entries */ + +/* + * Fault Event MSI configuration. + */ +#define QVTD_FAULT_IRQ_ADDR 0xfee00000 /* APIC base */ +#define QVTD_FAULT_IRQ_DATA 0x0 + +/* Scalable mode PASID structures */ +#define QVTD_PASID_DIR_BASE (QVTD_MEM_BASE + 0x00030000) +#define QVTD_PASID_TABLE_BASE (QVTD_MEM_BASE + 0x00031000) + +/* Page table entry size (8 bytes per PTE) */ +#define QVTD_PTE_SIZE sizeof(uint64_t) + +/* FSPTPTR mask: same as VTD_SM_PASID_ENTRY_SSPTPTR, bits[63:12] */ +#define QVTD_SM_PASID_ENTRY_FSPTPTR VTD_SM_PASID_ENTRY_SSPTPTR + +/* Default Domain ID for single-domain tests */ +#define QVTD_DOMAIN_ID 0 + +/* Test IOVA and target physical address */ +#define QVTD_IOVA 0x0000000010200567ull +#define QVTD_PT_VAL (QVTD_MEM_BASE + 0x00100000) + +/* + * Translation modes supported by Intel IOMMU + */ +typedef enum QVTDTransMode { + QVTD_TM_LEGACY_PT, /* Legacy pass-through mode */ + QVTD_TM_LEGACY_TRANS, /* Legacy translated mode (4-level paging)= */ + QVTD_TM_SCALABLE_PT, /* Scalable pass-through mode */ + QVTD_TM_SCALABLE_SLT, /* Scalable Second Level Translation */ + QVTD_TM_SCALABLE_FLT, /* Scalable First Level Translation */ + QVTD_TM_SCALABLE_NESTED, /* Scalable Nested Translation */ +} QVTDTransMode; + +static inline bool qvtd_is_scalable(QVTDTransMode mode) +{ + return mode =3D=3D QVTD_TM_SCALABLE_PT || + mode =3D=3D QVTD_TM_SCALABLE_SLT || + mode =3D=3D QVTD_TM_SCALABLE_FLT; +} + +typedef struct QVTDTestConfig { + QVTDTransMode trans_mode; /* Translation mode */ + uint64_t dma_gpa; /* GPA for readback validation */ + uint32_t dma_len; /* DMA length for testing */ + uint32_t expected_result; /* Expected DMA result */ +} QVTDTestConfig; + +typedef struct QVTDTestContext { + QTestState *qts; /* QTest state handle */ + QPCIDevice *dev; /* PCI device handle */ + QPCIBar bar; /* PCI BAR for MMIO access */ + QVTDTestConfig config; /* Test configuration */ + uint64_t iommu_base; /* Intel IOMMU base address */ + uint32_t trans_status; /* Translation configuration status */ + uint32_t dma_result; /* DMA operation result */ + uint16_t sid; /* Source ID (bus:devfn) */ +} QVTDTestContext; + +/* + * qvtd_setup_and_enable_translation - Complete translation setup and enab= le + * + * @ctx: Test context containing configuration and device handles + * + * Returns: Translation status (0 =3D success, non-zero =3D error) + * + * This function performs the complete translation setup sequence: + * 1. Builds VT-d structures (root/context entry, page tables) + * 2. Programs IOMMU registers and enables translation + * 3. Returns configuration status + */ +uint32_t qvtd_setup_and_enable_translation(QVTDTestContext *ctx); + +/* + * qvtd_build_translation - Build Intel IOMMU translation structures + * + * @qts: QTest state handle + * @mode: Translation mode (pass-through or translated) + * @sid: Source ID (bus:devfn) + * + * Returns: Build status (0 =3D success, non-zero =3D error) + * + * Constructs all necessary VT-d translation structures in guest memory: + * - Root Entry for the device's bus + * - Context Entry for the device + * - Complete 4-level page table hierarchy (if translated mode) + */ +uint32_t qvtd_build_translation(QTestState *qts, QVTDTransMode mode, + uint16_t sid); + +/* + * qvtd_program_regs - Program Intel IOMMU registers and enable translation + * + * @qts: QTest state handle + * @iommu_base: IOMMU base address + * @mode: Translation mode (scalable modes set RTADDR SMT bit) + * + * Programs IOMMU registers with the following sequence: + * 1. Set root table pointer (SRTP), with SMT bit for scalable mode + * 2. Setup invalidation queue (QIE) + * 3. Configure fault event MSI + * 4. Enable translation (TE) + * + * Each step verifies completion via GSTS register read-back. + */ +void qvtd_program_regs(QTestState *qts, uint64_t iommu_base, + QVTDTransMode mode); + +/* + * qvtd_setup_translation_tables - Setup complete VT-d page table hierarchy + * + * @qts: QTest state handle + * @iova: Input Virtual Address to translate + * @mode: Translation mode + * + * This builds the 4-level page table structure for translating + * the given IOVA to PA through Intel VT-d. The structure is: + * - PML4 (Level 4): IOVA bits [47:39] + * - PDPT (Level 3): IOVA bits [38:30] + * - PD (Level 2): IOVA bits [29:21] + * - PT (Level 1): IOVA bits [20:12] + * - Page offset: IOVA bits [11:0] + * + * The function writes all necessary Page Table Entries (PTEs) to guest + * memory using qtest_writeq(), setting up the complete translation path + * that the VT-d hardware will traverse during DMA operations. + */ +void qvtd_setup_translation_tables(QTestState *qts, uint64_t iova, + QVTDTransMode mode); + +/* Calculate expected DMA result */ +uint32_t qvtd_expected_dma_result(QVTDTestContext *ctx); + +/* Build DMA attributes for Intel VT-d */ +uint32_t qvtd_build_dma_attrs(void); + +/* High-level test execution helpers */ +void qvtd_run_translation_case(QTestState *qts, QPCIDevice *dev, + QPCIBar bar, uint64_t iommu_base, + const QVTDTestConfig *cfg); + +#endif /* QTEST_LIBQOS_INTEL_IOMMU_H */ --=20 2.39.5