From nobody Mon Feb 9 16:01:06 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=linaro.org ARC-Seal: i=1; a=rsa-sha256; t=1768934484; cv=none; d=zohomail.com; s=zohoarc; b=WB+55WV8Qb23tRbhFJlvT1sTehQ7l0v4IjNMgnwNJNuoG8m2CBZbASzsnB/eXOtCrayGBAbRlojZlXV3gmQQM1inWvrhqzD9Y3BDQFVUJfNCUoQ1i54txKa2swudxlsuWUa6WNiJQFJTkhPpxELlaJJC7j390pjPhwtMKkZvQKM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1768934484; h=Content-Type:Content-Transfer-Encoding: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:Cc; bh=qeRefDc4UWGe3k05tsHGAgyGgQvBhPXs0rdg1kfPac8=; b=jfUSNBytaytabvun9qxAVzx57WosrWC3wPr9l+dge7HTuJMhL+vEsioR6pmQYbxd4jtljtBxOJDLF+0+oY7cSPu/tTvP9LxE8kU1go9IHxrfdBDtyzIOg5WAFZ+dQBZ0nfN1/KMxVHMRohi0hY3mY/QULw0JeVkX15rpWxFZ4Ok= 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= (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 1768934484731837.8976028228836; Tue, 20 Jan 2026 10:41:24 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1viGeT-0000sR-Ew; Tue, 20 Jan 2026 13:40:29 -0500 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 ) id 1viGe0-0008MF-Vy for qemu-devel@nongnu.org; Tue, 20 Jan 2026 13:40:05 -0500 Received: from mail-wm1-x32c.google.com ([2a00:1450:4864:20::32c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1viGdx-00064U-Ct for qemu-devel@nongnu.org; Tue, 20 Jan 2026 13:40:00 -0500 Received: by mail-wm1-x32c.google.com with SMTP id 5b1f17b1804b1-47edffe5540so49507345e9.0 for ; Tue, 20 Jan 2026 10:39:57 -0800 (PST) Received: from localhost.localdomain (88-187-86-199.subs.proxad.net. [88.187.86.199]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-47f4289b789sm317460355e9.1.2026.01.20.10.39.53 for (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Tue, 20 Jan 2026 10:39:54 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1768934395; x=1769539195; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:from:to:cc:subject:date:message-id :reply-to; bh=qeRefDc4UWGe3k05tsHGAgyGgQvBhPXs0rdg1kfPac8=; b=gSZZK+NXhj+1I1CYa+sVoeeTC1EcoMlZ2uuLXq0bPpxlCZc7a5ZqcL8ho5jUN5+XZ1 GZKKt26DczkQXT4UiScHc3AjnSg/uEvbY4W/Z9mUAUZO5xrX1SUUjRFzRxID0LTS1LAt qoIcz6WubvNaRUmNzoMtsk1GgLDvOkWrU8buo5aWNpWiBjp4uem5xKGl30PEu+izx68M /V0H5WVJFENMcr6EnmEFNz6g5wHDCcGRZNJVvYMgv4crG42J5s5sJBaiPZz0CeD1slCw +3bOymxfM/YZMD9OYkU/zYJ3//BhwxSERRHFLXcPZhtQYpB3z7XM9u+sv04tkJBhUzNy Od9A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768934395; x=1769539195; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=qeRefDc4UWGe3k05tsHGAgyGgQvBhPXs0rdg1kfPac8=; b=Ss8SYkQlWMK4YFl+Re94unCO4g8bEMR2DQFXR4xVhZ7JLtBesjb/eqDPUJIuz/WQBF diqCnPVvfbs8GkCvv1h0xvBUPGj+AkU+yVolP5FeAajAK07yWx7gNGc6AAdK0QFtnDES OLESPFr+3vhJouBDWMfbu5ncNT7u55iK2aeY/p5njNwlR7iwEd6T3bX8dXzxNJJITD9L 3jCsDpqscl/AnN/2mdKsaisOZW0Xl1uHJpsdYGnn+xP4oRRYL55iQ5GWsf0yWsr+Jgnh oYCX1AW29v7exWP907iVWIi+xw41QYIO5bwGyCvXyLJttYTodfVbRBioKVJJ11oV46+T SxhA== X-Gm-Message-State: AOJu0YxhKszqIFv+P4ble5tk7X5iUjacfBTWIcmTZtSwnBL/J3YUG+9B lbFJmed4j22eLKHt2iAke3FVrU+Il2T/NHN0/0K/NA9ctUumMstdwQ1mOek5RtLOy7Wu4+z6onZ p7k6iLF0= X-Gm-Gg: AY/fxX6YPZ13LfRiFzuAtdcxk1Mjg6JrDboE4EBhI6o+Fgcz0qBle0yWozgU+4NLAMS VdRiHA1YX4miSppsLL2Q1qU7lDfGpw2LKkDqEBB0VGl6KcqonRS0iXkD5wOzlnSbxXtM8dEpi6X fiEcpW9oIvHnpvKm5INQ3eYDGJBDfpP6Mr5/4NPGm0dejATgEBKFfWy3WtK2eU0c273rSOawBOb F4v43zoqIJyQpWGLXUi7R1Wt3XpI19wu9ZSm3rpFcbm34CYMUahBJ9GxdZh7qAuqvPkcfq9vYmO qR5kh/WNuzN1HKIByBVLwYQ8j7085SK1zPhJzsPAyXBbmviFY3pUtNcUKgQKk7RLk/l+2S87z3M d4qAJfXYBkM5PreD1mYBI00pIX8qFy5p8E78R1/Ca9DnE+8raNIoU6QGhQXkX1e831UTB2qRIg+ YOsuNCQdQwe5nNNkek3rO9yHQG4XrO+cJ8J2eFufA93Xcx09Ovdf5kpf4FcIGJJYaYCVjwX0Y= X-Received: by 2002:a05:600c:8b75:b0:477:9574:d641 with SMTP id 5b1f17b1804b1-4803e7e8531mr41469055e9.22.1768934394817; Tue, 20 Jan 2026 10:39:54 -0800 (PST) From: =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= To: qemu-devel@nongnu.org Subject: [PULL 07/29] tests/qtest/libqos: Add SMMUv3 helper library Date: Tue, 20 Jan 2026 19:38:40 +0100 Message-ID: <20260120183902.73845-8-philmd@linaro.org> X-Mailer: git-send-email 2.52.0 In-Reply-To: <20260120183902.73845-1-philmd@linaro.org> References: <20260120183902.73845-1-philmd@linaro.org> 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 (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=2a00:1450:4864:20::32c; envelope-from=philmd@linaro.org; helo=mail-wm1-x32c.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, 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 @linaro.org) X-ZM-MESSAGEID: 1768934487143154100 From: Tao Tang Introduce qos-smmuv3, a reusable library for SMMUv3-related qtest operations. This module encapsulates common tasks like: - SMMUv3 initialization (enabling, configuring command/event queues) - Stream Table Entry (STE) and Context Descriptor (CD) setup - Multi-level page table construction (L0-L3 for 4KB granules) - Support for Stage 1, Stage 2, and nested translation modes - Could be easily extended to support multi-space testing infrastructure (Non-Secure, Secure, Root, Realm) The library provides high-level abstractions that allow test code to focus on IOMMU behavior validation rather than low-level register manipulation and page table encoding. Key features include: - Provide memory allocation for translation structures with proper alignment - Helper functions to build valid STEs/CDs for different translation scenarios - Page table walkers that handle address offset calculations per security space This infrastructure is designed to be used by iommu-testdev-based tests and future SMMUv3 test suites, reducing code duplication and improving test maintainability. Signed-off-by: Tao Tang Reviewed-by: Pierrick Bouvier Reviewed-by: Fabiano Rosas Message-ID: <20260119161112.3841386-8-tangtao1634@phytium.com.cn> Signed-off-by: Philippe Mathieu-Daud=C3=A9 --- MAINTAINERS | 1 + tests/qtest/libqos/qos-smmuv3.h | 242 ++++++++++++ tests/qtest/libqos/qos-smmuv3.c | 629 ++++++++++++++++++++++++++++++++ tests/qtest/libqos/meson.build | 1 + 4 files changed, 873 insertions(+) create mode 100644 tests/qtest/libqos/qos-smmuv3.h create mode 100644 tests/qtest/libqos/qos-smmuv3.c diff --git a/MAINTAINERS b/MAINTAINERS index cc219e39b98..dc31be033ee 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3582,6 +3582,7 @@ QTest IOMMU helpers M: Tao Tang S: Maintained F: tests/qtest/libqos/qos-iommu* +F: tests/qtest/libqos/qos-smmuv3* =20 Device Fuzzing M: Alexander Bulekov diff --git a/tests/qtest/libqos/qos-smmuv3.h b/tests/qtest/libqos/qos-smmuv= 3.h new file mode 100644 index 00000000000..4a3c43f765c --- /dev/null +++ b/tests/qtest/libqos/qos-smmuv3.h @@ -0,0 +1,242 @@ +/* + * QOS SMMUv3 Module + * + * This module provides SMMUv3-specific helper functions for libqos tests, + * encapsulating SMMUv3 setup, and assertions. + * + * Copyright (c) 2026 Phytium Technology + * + * Author: + * Tao Tang + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QTEST_LIBQOS_SMMUV3_H +#define QTEST_LIBQOS_SMMUV3_H + +#include "hw/misc/iommu-testdev.h" + +/* + * SMMU MMIO register base for virt machine-wide SMMU. This does not + * apply to user-creatable device such as -device arm-smmuv3. + */ +#define VIRT_SMMU_BASE 0x0000000009050000ull + +/* SMMU queue and table base addresses */ +#define QSMMU_CMDQ_BASE_ADDR 0x000000000e16b000ull +#define QSMMU_EVENTQ_BASE_ADDR 0x000000000e170000ull + +/* + * Translation tables and descriptors for a mapping of + * - IOVA (Stage 1 only or nested translation stage) + * - IPA (Stage 2 only) + * to GPA. + * + * The translation is based on the Arm architecture with the following + * prerequisites: + * - Granule size: 4KB pages. + * - Page table levels: 4 levels (L0, L1, L2, L3), starting at level 0. + * - IOVA size: The walk resolves a IOVA: 0x8080604567 + * - Address space: The 4-level lookup with 4KB granules supports up to a + * 48-bit (256TB) virtual address space. Each level uses a 9-bit index + * (512 entries per table). The breakdown is: + * - L0 index: IOVA bits [47:39] + * - L1 index: IOVA bits [38:30] + * - L2 index: IOVA bits [29:21] + * - L3 index: IOVA bits [20:12] + * - Page offset: IOVA bits [11:0] + * + * NOTE: All physical addresses defined here (QSMMU_VTTB, table addresses,= etc.) + * appear to be within a secure RAM region. In practice, an offset is added + * to these values to place them in non-secure RAM. For example, when runn= ing + * in a virt machine type, the RAM base address (e.g., 0x40000000) is adde= d to + * these constants. + */ +#define QSMMU_IOVA 0x0000008080604567ull +#define QSMMU_VTTB 0x000000000e4d0000ull +#define QSMMU_STR_TAB_BASE 0x000000000e179000ull +#define QSMMU_CD_GPA (QSMMU_STR_TAB_BASE - 0x40ull) + + +#define QSMMU_L0_PTE_VAL 0x000000000e4d1000ull +#define QSMMU_L1_PTE_VAL 0x000000000e4d2000ull +#define QSMMU_L2_PTE_VAL 0x000000000e4d3000ull +#define QSMMU_L3_PTE_VAL 0x000000000ecba000ull + +#define QSMMU_NON_LEAF_PTE_MASK 0x8000000000000003ull +#define QSMMU_LEAF_PTE_RO_MASK 0x04000000000007e3ull +#define QSMMU_LEAF_PTE_RW_MASK 0x0400000000000763ull +#define QSMMU_PTE_MASK 0x0000fffffffff000ull + +/* + * Address-space base offsets for test tables. + * - Non-Secure uses a fixed offset, keeping internal layout identical. + * + * Note: Future spaces (e.g. Secure/Realm/Root) are not implemented here. + * When needed, introduce new offsets and reuse the helpers below so relat= ive + * layout stays identical across spaces. + */ +#define QSMMU_SPACE_OFFS_NS 0x0000000040000000ull + +typedef enum QSMMUSecSID { + QSMMU_SEC_SID_NONSECURE =3D 0, +} QSMMUSecSID; + +typedef enum QSMMUSpace { + QSMMU_SPACE_NONSECURE =3D 1, +} QSMMUSpace; + +typedef enum QSMMUTransMode { + QSMMU_TM_S1_ONLY =3D 0, + QSMMU_TM_S2_ONLY =3D 1, + QSMMU_TM_NESTED =3D 2, +} QSMMUTransMode; + +typedef struct QSMMUTestConfig { + QSMMUTransMode trans_mode; /* Translation mode (S1, S2, Nested)= */ + QSMMUSecSID sec_sid; /* SEC_SID of test device */ + uint64_t dma_gpa; /* GPA for readback validation */ + uint32_t dma_len; /* DMA length for testing */ + uint32_t expected_result; /* Expected DMA result for validatio= n */ +} QSMMUTestConfig; + +typedef struct QSMMUTestContext { + QTestState *qts; /* QTest state handle */ + QPCIDevice *dev; /* PCI device handle */ + QPCIBar bar; /* PCI BAR for MMIO access */ + QSMMUTestConfig config; /* Test configuration */ + uint64_t smmu_base; /* SMMU base address */ + uint32_t trans_status; /* Translation configuration status */ + uint32_t dma_result; /* DMA operation result */ + uint32_t sid; /* Stream ID for the test */ + QSMMUSpace tx_space; /* Cached transaction space */ +} QSMMUTestContext; + +/* Convert SEC_SID to corresponding Security Space */ +QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid); + +/* Get base offset of the specific Security space */ +uint64_t qsmmu_space_offset(QSMMUSpace sp); + +uint32_t qsmmu_build_dma_attrs(QSMMUSpace space); + +/* + * qsmmu_setup_and_enable_translation - Complete translation setup and ena= ble + * + * @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 all required SMMU structures (STE, CD, page tables) + * 2. Programs SMMU registers for the appropriate security space + * 3. Returns configuration status + */ +uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx); + +/* + * qsmmu_build_translation - Build SMMU translation structures + * + * @qts: QTest state handle + * @mode: Translation mode (S1_ONLY, S2_ONLY, NESTED) + * @tx_space: Transaction security space + * @sid: Stream ID + * + * Returns: Build status (0 =3D success, non-zero =3D error) + * + * Constructs all necessary SMMU translation structures in guest memory + * using the fixed QSMMU_IOVA constant: + * - Stream Table Entry (STE) for the given SID + * - Context Descriptor (CD) if Stage 1 translation is involved + * - Complete page table hierarchy based on translation mode + * + * The structures are written to security-space-specific memory regions. + */ +uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode, + QSMMUSpace tx_space, uint32_t sid); + +/* + * qsmmu_bank_base - Get SMMU control bank base address + * + * @base: SMMU base address + * @sp: Security space + * + * Returns: Bank base address for the given security space + * + * Maps security space to the corresponding SMMU control register bank. + * Currently only Non-Secure bank is supported. + */ +uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp); + +/* + * qsmmu_program_bank - Program SMMU control bank registers + * + * @qts: QTest state handle + * @bank_base: SMMU bank base address + * @sp: Security space + * + * Programs a specific SMMU control bank with minimal configuration: + * - Global Bypass Attribute (GBPA) + * - Control registers (CR0, CR1) + * - Command queue (base, producer, consumer) + * - Event queue (base, producer, consumer) + * - Stream table configuration (base, format) + * + * Addresses are adjusted based on security space offset. + */ +void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp= ); + +/* + * qsmmu_program_regs - Program all required SMMU register banks + * + * @qts: QTest state handle + * @smmu_base: SMMU base address + * @space: Target security space + * + * Programs SMMU registers for the requested security space which is calle= d in + * qsmmu_setup_and_enable_translation. Always programs Non-Secure bank fir= st, + * then the target space if different. + */ +void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace sp= ace); + +/* qsmmu_expected_dma_result - Calculate expected DMA result */ +uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx); + +/* + * qsmmu_setup_translation_tables - Setup complete SMMU page table hierarc= hy + * + * @qts: QTest state handle + * @iova: Input Virtual Address or IPA to translate + * @space: Security space (NONSECURE, SECURE, REALM, ROOT) + * @is_cd: Whether translating CD address (vs regular IOVA) + * @mode: Translation mode (S1_ONLY, S2_ONLY, NESTED) + * + * This function builds the complete page table structure for translating + * the given IOVA through the SMMU. The structure varies based on mode: + * + * - S1_ONLY: Single Stage 1 walk (IOVA -> PA) + * - S2_ONLY: Single Stage 2 walk (IPA -> PA) + * - NESTED: Stage 1 walk (IOVA -> IPA) with nested S2 walks for each + * S1 table access, plus final S2 walk for the result IPA + * + * For nested mode, this creates a complex hierarchy: + * - 4 Stage 1 levels (L0-L3), each requiring a 4-level Stage 2 walk + * - 1 final Stage 2 walk for the resulting IPA + * + * The function writes all necessary Page Table Entries (PTEs) to guest + * memory using qtest_writeq(), setting up the complete translation path + * that the SMMU hardware will traverse during DMA operations. + */ +void qsmmu_setup_translation_tables(QTestState *qts, + uint64_t iova, + QSMMUSpace space, + bool is_cd, + QSMMUTransMode mode); + +/* High-level test execution helpers */ +void qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev, + QPCIBar bar, uint64_t smmu_base, + const QSMMUTestConfig *cfg); + +#endif /* QTEST_LIBQOS_SMMUV3_H */ diff --git a/tests/qtest/libqos/qos-smmuv3.c b/tests/qtest/libqos/qos-smmuv= 3.c new file mode 100644 index 00000000000..710a04d98da --- /dev/null +++ b/tests/qtest/libqos/qos-smmuv3.c @@ -0,0 +1,629 @@ +/* + * QOS SMMUv3 Module + * + * This module provides SMMUv3-specific helper functions for libqos tests, + * encapsulating SMMUv3 setup, and assertions. + * + * Copyright (c) 2026 Phytium Technology + * + * Author: + * Tao Tang + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/arm/smmuv3-common.h" +#include "tests/qtest/libqos/pci.h" +#include "qos-iommu-testdev.h" +#include "qos-smmuv3.h" + +#define QSMMU_STE_S2T0SZ_VAL 0x14 + +/* Apply space offset to address */ +static inline uint64_t qsmmu_apply_space_offs(QSMMUSpace sp, + uint64_t address) +{ + return address + qsmmu_space_offset(sp); +} + +uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx) +{ + /* Currently only non-secure space is supported. */ + if (ctx->tx_space !=3D QSMMU_SPACE_NONSECURE) { + return ITD_DMA_ERR_TX_FAIL; + } + return ctx->config.expected_result; +} + +uint32_t qsmmu_build_dma_attrs(QSMMUSpace space) +{ + uint32_t attrs =3D 0; + switch (space) { + case QSMMU_SPACE_NONSECURE: + /* Non-secure: secure=3D0, space=3D1, space_valid=3D1 */ + attrs =3D ITD_ATTRS_SET_SECURE(attrs, 0); + attrs =3D ITD_ATTRS_SET_SPACE(attrs, QSMMU_SPACE_NONSECURE); + attrs =3D ITD_ATTRS_SET_SPACE_VALID(attrs, 1); + break; + default: + g_assert_not_reached(); + } + + return attrs; +} + +uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx) +{ + uint32_t build_result; + + /* Build page tables and SMMU structures first */ + build_result =3D qsmmu_build_translation( + ctx->qts, ctx->config.trans_mode, + ctx->tx_space, 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 SMMU registers for the appropriate security space */ + qsmmu_program_regs(ctx->qts, ctx->smmu_base, ctx->tx_space); + + ctx->trans_status =3D 0; + return ctx->trans_status; +} + +static bool qsmmu_validate_test_result(QSMMUTestContext *ctx) +{ + uint32_t expected =3D qsmmu_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); +} + +QSMMUSpace qsmmu_sec_sid_to_space(QSMMUSecSID sec_sid) +{ + switch (sec_sid) { + case QSMMU_SEC_SID_NONSECURE: + return QSMMU_SPACE_NONSECURE; + default: + g_assert_not_reached(); + } +} + +uint64_t qsmmu_space_offset(QSMMUSpace sp) +{ + switch (sp) { + case QSMMU_SPACE_NONSECURE: + return QSMMU_SPACE_OFFS_NS; + default: + g_assert_not_reached(); + } +} + +static uint32_t qsmmu_single_translation_setup(void *opaque) +{ + return qsmmu_setup_and_enable_translation(opaque); +} + +static uint32_t qsmmu_single_translation_attrs(void *opaque) +{ + QSMMUTestContext *ctx =3D opaque; + + return qsmmu_build_dma_attrs(ctx->tx_space); +} + +static bool qsmmu_single_translation_validate(void *opaque) +{ + return qsmmu_validate_test_result(opaque); +} + +static void qsmmu_single_translation_report(void *opaque, + uint32_t dma_result) +{ + QSMMUTestContext *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 qsmmu_run_translation_case(QTestState *qts, QPCIDevice *dev, + QPCIBar bar, uint64_t smmu_base, + const QSMMUTestConfig *cfg) +{ + QSMMUTestContext ctx =3D { + .qts =3D qts, + .dev =3D dev, + .bar =3D bar, + .smmu_base =3D smmu_base, + .config =3D *cfg, + .sid =3D dev->devfn, + .tx_space =3D qsmmu_sec_sid_to_space(cfg->sec_sid), + }; + + QOSIOMMUTestdevDmaCfg dma =3D { + .dev =3D dev, + .bar =3D bar, + .iova =3D QSMMU_IOVA, + .gpa =3D ctx.config.dma_gpa, + .len =3D ctx.config.dma_len, + }; + + qtest_memset(qts, cfg->dma_gpa, 0x00, cfg->dma_len); + qos_iommu_testdev_single_translation(&dma, &ctx, + qsmmu_single_translation_setup, + qsmmu_single_translation_attrs, + qsmmu_single_translation_validate, + qsmmu_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); + } + } +} + +uint32_t qsmmu_build_translation(QTestState *qts, QSMMUTransMode mode, + QSMMUSpace tx_space, uint32_t sid) +{ + uint64_t ste_addr, ste_addr_real, cd_addr_real; + uint64_t cd_ttb, vttb, vttb_real; + uint8_t nscfg0, nscfg1; + QSMMUSpace build_space; + size_t ste_cd_entry_bytes =3D sizeof(STE); + STE ste; + CD cd; + + build_space =3D tx_space; + if (build_space !=3D QSMMU_SPACE_NONSECURE) { + return 0xdeadbeafu; + } + + /* Build STE image */ + memset(&ste, 0, sizeof(ste)); + switch (mode) { + case QSMMU_TM_S1_ONLY: + STE_SET_CONFIG(&ste, 0x5); + break; + case QSMMU_TM_S2_ONLY: + STE_SET_CONFIG(&ste, 0x6); + break; + case QSMMU_TM_NESTED: + default: + STE_SET_CONFIG(&ste, 0x7); + break; + } + + STE_SET_VALID(&ste, 1); + STE_SET_S2T0SZ(&ste, QSMMU_STE_S2T0SZ_VAL); + STE_SET_S2SL0(&ste, 0x2); + STE_SET_S2TG(&ste, 0); + STE_SET_S2PS(&ste, 0x5); + STE_SET_S2AA64(&ste, 1); + STE_SET_S2ENDI(&ste, 0); + STE_SET_S2AFFD(&ste, 0); + + /* + * The consistent policy also extends to pointer fetches. For cases th= at + * require reading STE.S1ContextPtr or STE.S2TTB, we still follow the = same + * policy: + * - The PA space security attribute of the address pointed to + * (e.g., the CD or S2L1 table) must also match the input 'SEC_SID'. + */ + cd_addr_real =3D qsmmu_apply_space_offs(build_space, QSMMU_CD_GPA); + STE_SET_CTXPTR(&ste, cd_addr_real); + + vttb =3D QSMMU_VTTB; + vttb_real =3D qsmmu_apply_space_offs(build_space, vttb); + STE_SET_S2TTB(&ste, vttb_real); + + ste_addr =3D sid * ste_cd_entry_bytes + QSMMU_STR_TAB_BASE; + ste_addr_real =3D qsmmu_apply_space_offs(build_space, ste_addr); + + /* Write STE to memory */ + for (int i =3D 0; i < ARRAY_SIZE(ste.word); i++) { + qtest_writel(qts, ste_addr_real + i * 4, ste.word[i]); + } + + switch (tx_space) { + case QSMMU_SPACE_NONSECURE: + nscfg0 =3D 0x1; + nscfg1 =3D 0x1; + break; + default: + g_assert_not_reached(); + } + /* Build CD image for S1 path if needed */ + if (mode !=3D QSMMU_TM_S2_ONLY) { + memset(&cd, 0, sizeof(cd)); + + CD_SET_ASID(&cd, 0x1e20); + CD_SET_AARCH64(&cd, 1); + CD_SET_VALID(&cd, 1); + CD_SET_A(&cd, 1); + CD_SET_S(&cd, 0); + CD_SET_HD(&cd, 0); + CD_SET_HA(&cd, 0); + CD_SET_IPS(&cd, 0x4); + CD_SET_TBI(&cd, 0x0); + CD_SET_AFFD(&cd, 0x0); + CD_SET_EPD(&cd, 0, 0x0); + CD_SET_EPD(&cd, 1, 0x1); + CD_SET_TSZ(&cd, 0, 0x10); + CD_SET_TG(&cd, 0, 0x0); + CD_SET_ENDI(&cd, 0x0); + + CD_SET_NSCFG(&cd, 0, nscfg0); + CD_SET_NSCFG(&cd, 1, nscfg1); + CD_SET_R(&cd, 0x1); + cd_ttb =3D vttb_real; + CD_SET_TTB(&cd, 0, cd_ttb); + + for (int i =3D 0; i < ARRAY_SIZE(cd.word); i++) { + /* TODO: Maybe need more work to write to secure RAM in future= */ + qtest_writel(qts, cd_addr_real + i * 4, cd.word[i]); + g_assert_cmpint(qtest_readl(qts, cd_addr_real + i * 4), =3D=3D, + cd.word[i]); + } + } + + qsmmu_setup_translation_tables(qts, QSMMU_IOVA, build_space, + false, mode); + /* Nested extras: CD S2 tables */ + if (mode =3D=3D QSMMU_TM_NESTED) { + /* + * Extra Stage 2 page tables is needed if + * SMMUTranslationClass =3D=3D SMMU_CLASS_CD + * as smmuv3_do_translate would translate an IPA of the CD to the = final + * output CD after a Stage 2 translation. + */ + qsmmu_setup_translation_tables(qts, cd_addr_real, build_space, + true, mode); + } + + return 0; +} + +uint64_t qsmmu_bank_base(uint64_t base, QSMMUSpace sp) +{ + switch (sp) { + case QSMMU_SPACE_NONSECURE: + return base; + default: + g_assert_not_reached(); + } +} + +void qsmmu_program_bank(QTestState *qts, uint64_t bank_base, QSMMUSpace sp) +{ + uint64_t cmdq_base, eventq_base, strtab_base; + + qtest_writel(qts, bank_base + A_GBPA, 0x80000000); /* UPDATE */ + qtest_writel(qts, bank_base + A_CR0, 0x0); /* Disable */ + qtest_writel(qts, bank_base + A_CR1, 0x0d75); /* Config */ + + /* CMDQ_BASE: add address-space offset*/ + cmdq_base =3D qsmmu_apply_space_offs(sp, QSMMU_CMDQ_BASE_ADDR); + cmdq_base |=3D 0x0a; /* Size and valid bits */ + qtest_writeq(qts, bank_base + A_CMDQ_BASE, cmdq_base); + + qtest_writel(qts, bank_base + A_CMDQ_CONS, 0x0); + qtest_writel(qts, bank_base + A_CMDQ_PROD, 0x0); + + /* EVENTQ_BASE: add address-space offset */ + eventq_base =3D qsmmu_apply_space_offs(sp, QSMMU_EVENTQ_BASE_ADDR); + eventq_base |=3D 0x0a; /* Size and valid bits */ + qtest_writeq(qts, bank_base + A_EVENTQ_BASE, eventq_base); + + qtest_writel(qts, bank_base + A_EVENTQ_PROD, 0x0); + qtest_writel(qts, bank_base + A_EVENTQ_CONS, 0x0); + + /* STRTAB_BASE_CFG: linear stream table, LOG2SIZE=3D5 */ + qtest_writel(qts, bank_base + A_STRTAB_BASE_CFG, 0x5); + + /* STRTAB_BASE: add address-space offset */ + strtab_base =3D qsmmu_apply_space_offs(sp, QSMMU_STR_TAB_BASE); + qtest_writeq(qts, bank_base + A_STRTAB_BASE, strtab_base); + + /* CR0: Enable SMMU with appropriate flags */ + qtest_writel(qts, bank_base + A_CR0, 0xd); +} + +void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace sp= ace) +{ + uint64_t sp_base; + /* Always program Non-Secure bank first */ + uint64_t ns_base =3D qsmmu_bank_base(smmu_base, QSMMU_SPACE_NONSECURE); + qsmmu_program_bank(qts, ns_base, QSMMU_SPACE_NONSECURE); + + /* Program the requested space if different from Non-Secure */ + sp_base =3D qsmmu_bank_base(smmu_base, space); + if (sp_base !=3D ns_base) { + qsmmu_program_bank(qts, sp_base, space); + } +} + +static uint32_t qsmmu_get_table_index(uint64_t addr, int level) +{ + switch (level) { + case 0: + return (addr >> 39) & 0x1ff; + case 1: + return (addr >> 30) & 0x1ff; + case 2: + return (addr >> 21) & 0x1ff; + case 3: + return (addr >> 12) & 0x1ff; + default: + g_assert_not_reached(); + } +} + +static uint64_t qsmmu_get_table_addr(uint64_t base, int level, uint64_t io= va) +{ + uint32_t index =3D qsmmu_get_table_index(iova, level); + return (base & QSMMU_PTE_MASK) + (index * 8); +} + +/* + * qsmmu_get_pte_attrs - Calculate the S1 leaf PTE value + * + * IOMMU need to set different attributes for PTEs based on the translatio= n mode + */ +static uint64_t qsmmu_get_pte_attrs(QSMMUTransMode mode, bool is_leaf, + QSMMUSpace space) +{ + uint64_t rw_mask =3D QSMMU_LEAF_PTE_RW_MASK; + uint64_t ro_mask =3D QSMMU_LEAF_PTE_RO_MASK; + uint64_t non_leaf_mask =3D QSMMU_NON_LEAF_PTE_MASK; + + switch (space) { + case QSMMU_SPACE_NONSECURE: + break; + default: + g_assert_not_reached(); + } + + if (!is_leaf) { + return non_leaf_mask; + } + + /* For leaf PTE */ + if (mode =3D=3D QSMMU_TM_NESTED || mode =3D=3D QSMMU_TM_S1_ONLY) { + return rw_mask; + } + + return ro_mask; +} + +/* + * qsmmu_setup_s2_walk_for_ipa - Setup Stage 2 page table walk for an IPA + * + * @qts: QTest state handle + * @space: Security space + * @ipa: Intermediate Physical Address to translate + * @s2_vttb: Stage 2 VTTB (page table base) + * @mode: Translation mode + * @is_final: Whether this is the final S2 walk (not nested within S1) + * + * Calculates and writes a 4-level Stage 2 page table walk for the given I= PA. + * This function dynamically generates and writes all page table entries + * (L0-L3) to guest memory based on the input IPA and configuration. + */ +static void qsmmu_setup_s2_walk_for_ipa(QTestState *qts, + QSMMUSpace space, + uint64_t ipa, + uint64_t s2_vttb, + QSMMUTransMode mode, + bool is_final) +{ + uint64_t all_s2_l0_pte_val; + uint64_t all_s2_l1_pte_val; + uint64_t all_s2_l2_pte_val; + uint64_t all_s2_l3_pte_val; + uint64_t s2_l0_addr, s2_l1_addr, s2_l2_addr, s2_l3_addr; + + /* Shared intermediate PTE values for all S2 walks */ + all_s2_l0_pte_val =3D qsmmu_apply_space_offs( + space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space)); + all_s2_l1_pte_val =3D qsmmu_apply_space_offs( + space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space)); + all_s2_l2_pte_val =3D qsmmu_apply_space_offs( + space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space)); + + /* Stage 2 Level 0 */ + s2_l0_addr =3D qsmmu_get_table_addr(s2_vttb, 0, ipa); + qtest_writeq(qts, s2_l0_addr, all_s2_l0_pte_val); + + /* Stage 2 Level 1 */ + s2_l1_addr =3D qsmmu_get_table_addr(all_s2_l0_pte_val, 1, ipa); + qtest_writeq(qts, s2_l1_addr, all_s2_l1_pte_val); + + /* Stage 2 Level 2 */ + s2_l2_addr =3D qsmmu_get_table_addr(all_s2_l1_pte_val, 2, ipa); + qtest_writeq(qts, s2_l2_addr, all_s2_l2_pte_val); + + /* Stage 2 Level 3 (leaf) */ + s2_l3_addr =3D qsmmu_get_table_addr(all_s2_l2_pte_val, 3, ipa); + + /* + * Stage 2 L3 PTE attributes depend on the context: + * - For nested S1 table address translations (!is_final): + * Use LEAF attrs (0x763) because these PTEs map S1 table pages dire= ctly + * - For final S2 walk (is_final): + * Use TABLE attrs (0x7e3) for the final IPA=E2=86=92PA mapping + */ + if (!is_final) { + all_s2_l3_pte_val =3D + (ipa & QSMMU_PTE_MASK) | + qsmmu_get_pte_attrs(QSMMU_TM_NESTED, true, space); + } else { + all_s2_l3_pte_val =3D + (ipa & QSMMU_PTE_MASK) | + qsmmu_get_pte_attrs(QSMMU_TM_S2_ONLY, true, space); + } + + qtest_writeq(qts, s2_l3_addr, all_s2_l3_pte_val); +} + +/* + * qsmmu_setup_s1_level_with_nested_s2 - Setup S1 level with nested S2 walk + * + * @qts: QTest state handle + * @space: Security space + * @s1_level: Stage 1 level (0-3) + * @s1_pte_addr: Stage 1 PTE address (as IPA) + * @s1_pte_val: Stage 1 PTE value to write + * @s2_vttb: Stage 2 VTTB for nested translation + * @mode: Translation mode + * + * For nested translation, each S1 table access requires a full S2 walk + * to translate the S1 table's IPA to PA. This function performs the nested + * S2 walk and writes the S1 PTE value to guest memory. + */ +static void qsmmu_setup_s1_level_with_nested_s2(QTestState *qts, + QSMMUSpace space, + int s1_level, + uint64_t s1_pte_addr, + uint64_t s1_pte_val, + uint64_t s2_vttb, + QSMMUTransMode mode) +{ + /* + * Perform nested S2 walk to translate S1 table IPA to PA. + * This is always needed for S1_ONLY/S2_ONLY/NESTED modes because: + * - S1_ONLY: Needs S2 tables for "IPA as PA" mapping (for testing) + * - S2_ONLY: Needs S2 tables for direct translation + * - NESTED: Needs S2 tables for nested translation + */ + qsmmu_setup_s2_walk_for_ipa(qts, space, s1_pte_addr, + s2_vttb, mode, false); + + /* Write the S1 PTE value */ + qtest_writeq(qts, s1_pte_addr, s1_pte_val); +} + +/* + * qsmmu_setup_translation_tables - Setup SMMU translation tables + * + * The 'SEC_SID' represents the input security state of the device/transac= tion, + * whether it's a static Secure state or a dynamically-switched Realm stat= e. + * SEC_SID has been converted to the corresponding Security Space (QSMMUSp= ace) + * before calling this function. + * + * In a real SMMU translation, this input security state does not unilater= ally + * determine the output Physical Address (PA) space. The output PA space is + * ultimately determined by attributes encountered during the page table w= alk, + * such as NSCFG and NSTable. + * + * However, for the specific context of testing the SMMU with the iommu-te= stdev, + * and to simplify the future support for Secure and Realm states, we adop= t a + * consistent policy: + * + * - We always ensure that the page table attributes (e.g., nscfg, nstable) + * *match* the input 'SEC_SID' of the test case. + * + * For example: If 'SEC_SID' is Non-Secure, the corresponding nscfg and ns= table + * attributes in the translation tables will always be set to 1. + * + */ +void qsmmu_setup_translation_tables(QTestState *qts, + uint64_t iova, + QSMMUSpace space, + bool is_cd, + QSMMUTransMode mode) +{ + uint64_t all_s2_l0_pte_val, all_s2_l1_pte_val, all_s2_l2_pte_val; + uint64_t s1_vttb, s2_vttb, s1_leaf_pte_val; + uint64_t l0_addr, l1_addr, l2_addr, l3_addr; + + g_test_message("Begin of construction: IOVA=3D0x%" PRIx64 + " mode=3D%d is_building_CD=3D%s =3D=3D=3D", + iova, mode, is_cd ? "yes" : "no"); + + /* Initialize shared S2 PTE values used across all walks */ + all_s2_l0_pte_val =3D qsmmu_apply_space_offs( + space, QSMMU_L0_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space)); + all_s2_l1_pte_val =3D qsmmu_apply_space_offs( + space, QSMMU_L1_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space)); + all_s2_l2_pte_val =3D qsmmu_apply_space_offs( + space, QSMMU_L2_PTE_VAL | qsmmu_get_pte_attrs(mode, false, space)); + + /* Both S1 and S2 share the same VTTB base */ + s1_vttb =3D qsmmu_apply_space_offs(space, QSMMU_VTTB & QSMMU_PTE_MASK); + s2_vttb =3D s1_vttb; + + if (!is_cd) { + /* + * Setup Stage 1 page tables with nested Stage 2 walks. + * For each S1 level (L0-L3), we need to: + * 1. Calculate S1 PTE address (as IPA) + * 2. Perform nested S2 walk to translate that IPA to PA + * 3. Write the S1 PTE value + */ + + /* Stage 1 Level 0 */ + l0_addr =3D qsmmu_get_table_addr(s1_vttb, 0, iova); + qsmmu_setup_s1_level_with_nested_s2(qts, space, 0, l0_addr, + all_s2_l0_pte_val, s2_vttb, mo= de); + + /* Stage 1 Level 1 */ + l1_addr =3D qsmmu_get_table_addr(all_s2_l0_pte_val & QSMMU_PTE_MAS= K, + 1, iova); + qsmmu_setup_s1_level_with_nested_s2(qts, space, 1, l1_addr, + all_s2_l1_pte_val, s2_vttb, mo= de); + + /* Stage 1 Level 2 */ + l2_addr =3D qsmmu_get_table_addr(all_s2_l1_pte_val & QSMMU_PTE_MAS= K, + 2, iova); + qsmmu_setup_s1_level_with_nested_s2(qts, space, 2, l2_addr, + all_s2_l2_pte_val, s2_vttb, mo= de); + + /* Stage 1 Level 3 (leaf) */ + l3_addr =3D qsmmu_get_table_addr(all_s2_l2_pte_val & QSMMU_PTE_MAS= K, + 3, iova); + + s1_leaf_pte_val =3D qsmmu_apply_space_offs( + space, QSMMU_L3_PTE_VAL | qsmmu_get_pte_attrs(mode, true, spac= e) + ); + + qsmmu_setup_s1_level_with_nested_s2(qts, space, 3, l3_addr, + s1_leaf_pte_val, s2_vttb, mode= ); + } else { + /* + * For CD address translation, we start directly with the IPA. + */ + s1_leaf_pte_val =3D iova | qsmmu_get_pte_attrs(QSMMU_TM_NESTED, + false, space); + } + + /* + * Final Stage 2 walk: Translate the result from Stage 1. + * - For S1_ONLY: This is skipped in hardware but we set it up for tes= ting + * - For S2_ONLY: This is the only walk + * - For NESTED: This translates the IPA from S1 to final PA + * - For CD address (is_cd=3Dtrue): This is a table address, use !is_f= inal + */ + qsmmu_setup_s2_walk_for_ipa(qts, space, s1_leaf_pte_val, s2_vttb, + mode, !is_cd); + + /* Calculate and log final translated PA */ + g_test_message("End of construction: PA=3D0x%llx =3D=3D=3D", + (s1_leaf_pte_val & QSMMU_PTE_MASK) + (iova & 0xfff)); +} diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build index 9805d63a290..b4daec808fe 100644 --- a/tests/qtest/libqos/meson.build +++ b/tests/qtest/libqos/meson.build @@ -63,6 +63,7 @@ libqos_srcs =3D files( =20 # SMMU: 'qos-iommu-testdev.c', + 'qos-smmuv3.c', ) =20 if have_virtfs --=20 2.52.0