From nobody Sun Feb 8 04:13:01 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=1769681002; cv=none; d=zohomail.com; s=zohoarc; b=S+NKEzAttFCwUFK138n9WbjEZ8Ly9ZOuSRY0JOF3G9auacjIJYFF3HComlUV+hg/CDL8frPq9BfweGbbUscSrhYSwk58tWO1EZDUFGMhkHGNg68kKxTD8XusQWZYtnLty/jcD9qFEjB1MHK8WD4VYK5qxt2MSxrKXxLbbXWFGSc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1769681002; h=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=N2ZFoycNlXEyzShTEShZ12s5drXHD5ztaB7ZC4XNTqY=; b=LKqjq3pbghLK1+3TtdW5HXBRQQsK2od3sah+QNyXLjELpe0FCzFozppd9Nx+dk0uYv7QlleoN9kfxGtc1IGJ4Lthig3KpkpWvMrC1LuXRWnvqReXXI0dqRyI7qLZ8Dg7Cp2MTTRGQYLNWbJndKIgB7Lh0q3PCuPeP/gCe2D7q9I= 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 1769681002154879.6513773262178; Thu, 29 Jan 2026 02:03:22 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1vlOWB-0004u6-5J; Thu, 29 Jan 2026 04:40:51 -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 1vlOW9-0004sF-8w for qemu-devel@nongnu.org; Thu, 29 Jan 2026 04:40:49 -0500 Received: from mail-pj1-x1042.google.com ([2607:f8b0:4864:20::1042]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1vlOW6-0003Z0-FK for qemu-devel@nongnu.org; Thu, 29 Jan 2026 04:40:49 -0500 Received: by mail-pj1-x1042.google.com with SMTP id 98e67ed59e1d1-34c21417781so393480a91.3 for ; Thu, 29 Jan 2026 01:40:46 -0800 (PST) Received: from ZEVORN-PC.bbrouter ([183.195.20.6]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3540f3cc7c6sm4871990a91.10.2026.01.29.01.40.41 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 01:40:44 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769679645; x=1770284445; 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=N2ZFoycNlXEyzShTEShZ12s5drXHD5ztaB7ZC4XNTqY=; b=bmZuzgLHSKFDgm0xz+xNQF2xGzp2lHi5P+tUoX67rCnsR9vVkgFrwa4kW3Il+9f2yR uSFn3CCK0hA+Fy2vFplfvab0A6vw53pTc8NfRgAjpR8Yy8aqqHbTbvdrJ4L7w+QPABOz NnFYK7nCVOTsbzTC3bc407h2Uyoq4xvKvZ3bHP9LBrazI5/LJ/Z5dpTwHJr1F6wD2qFJ ++LG9VNdUIQ6Cwn760e4rDnIebbiA7nW31TwdexiqiLSCoQNEZsqE2sSP434RnaLzLil O/c9SEqdDyHvKtuh3QM5Ayn7QO5t1xg6NRk+Lf+1gIB2loDAPY8pCRf1ppFoHPGs79yC 5tUg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769679645; x=1770284445; 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=N2ZFoycNlXEyzShTEShZ12s5drXHD5ztaB7ZC4XNTqY=; b=wta0J0RsxQTP0bGkDLkXwGsN167ATZPfPjtW4uNkbJxrnyjH4pruDl3puYsdpsijBl 6qarsZMLctdwBsECHFs+A7Y3GJn1L05H4v8ntPACFbyI3rf6tYkseBJVQLoH3NI/RcQh xJvTDh805K62h9UQ623rBg6KJgKpE0qOo/bju1nd0wEtE/Ph8wjWvUuLGZg8cvGCowKI OoIfu3XjH2p2MFozD7pGbBMdDk02NFyxc6boCq3AsK87eJxzsg4WPPnJcWh0mCZZ+fEd 1rf4bGlcvVg8uVf3hmgn1QNWx2pgT82UYGKds01UsO3ezIX11swIkF6HkJIdvFjGb+dY 8+gA== X-Gm-Message-State: AOJu0YxZQhAq22jaB/Wsh6nIrDkOwG44nMaO3M2maZYnB/g7YoMXGpa0 dGtlNIg/sCY1X2Ku5kgnMh+eZHyveBo9TqYYfR9BKBI/2dqZnEHpiuRU X-Gm-Gg: AZuq6aKYb+qwj1W12G2XWRufRWLy9nWLe+zQURdeydUa0slskBX4nDltCznbccPzhiR 2LKC2C2s/3utB5ENad89p2AZyC46XaEJqxDzHfhKP9bfSDQVGxnJ06Y4IRvz9hie5+lhCIxSMgv T+58WIaVj1GM9NM2Bi+j+x5pj9WzlQVuzvlAROYdeq+G0gw+DzeOTtJyj5aYB8K2uB3KKU8TxKI OvjI42txc442BX6W5nWV0a7P4HKe+POk6pqqBEbZEnplcqdAAR18OEuKWQafIZ9KBtAWMdccbpj cxP5FWxDNhD7BZ8TYhsXU/UXiRQFtKh1uHj7IvyaSdFw8DAAoE2ylOaumIGkTBWGdRfyojh0UIf wZNrFBaZOhxVFLjkj3o5x8CaeDHY6hMXdnR/w48TZMvt0T1xRRWdBelaEqwmiQmIJjj8MGK8RDl IaOKSl4dYJHBkxgKHAngtVh2Sf0aUMD3kTnP1vsux3RbL15NvkUrOI+HgmoSuALwkAozY= X-Received: by 2002:a17:90b:1e51:b0:32e:749d:fcb7 with SMTP id 98e67ed59e1d1-353fecdb174mr7534950a91.13.1769679645029; Thu, 29 Jan 2026 01:40:45 -0800 (PST) From: Chao Liu To: Alistair Francis , Daniel Henrique Barboza , Palmer Dabbelt , Weiwei Li , Liu Zhiwei , Fabiano Rosas , Laurent Vivier , Paolo Bonzini , Tao Tang Cc: qemu-devel@nongnu.org, qemu-riscv@nongnu.org, hust-os-kernel-patches@googlegroups.com, Chao Liu Subject: [RFC PATCH v2 1/2] tests/qtest/libqos: Add RISC-V IOMMU helper library Date: Thu, 29 Jan 2026 17:39:50 +0800 Message-ID: X-Mailer: git-send-email 2.52.0 In-Reply-To: References: MIME-Version: 1.0 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::1042; envelope-from=chao.liu.zevorn@gmail.com; helo=mail-pj1-x1042.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: 1769681005621154100 Content-Type: text/plain; charset="utf-8" Introduce a libqos helper module for RISC-V IOMMU testing with iommu-testdev. The helper provides routines to: - Build device contexts (DC) and 3-level page tables for SV39/SV39x4 - Program command queue (CQ), fault queue (FQ), and DDTP registers following the RISC-V IOMMU specification - Execute DMA translations and verify results The current implementation supports SV39 for S-stage and SV39x4 for G-stage translation. Support for SV48/SV48x4/SV57/SV57x4 can be added in future patches. Signed-off-by: Chao Liu Reviewed-by: Tao Tang --- MAINTAINERS | 1 + tests/qtest/libqos/meson.build | 2 +- tests/qtest/libqos/qos-riscv-iommu.c | 405 +++++++++++++++++++++++++++ tests/qtest/libqos/qos-riscv-iommu.h | 164 +++++++++++ 4 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/libqos/qos-riscv-iommu.c create mode 100644 tests/qtest/libqos/qos-riscv-iommu.h diff --git a/MAINTAINERS b/MAINTAINERS index dccdf47888..830f56376b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3584,6 +3584,7 @@ M: Tao Tang S: Maintained F: tests/qtest/libqos/qos-iommu* F: tests/qtest/libqos/qos-smmuv3* +F: tests/qtest/libqos/qos-riscv-iommu* =20 Device Fuzzing M: Alexander Bulekov diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.build index b4daec808f..4a69acad0d 100644 --- a/tests/qtest/libqos/meson.build +++ b/tests/qtest/libqos/meson.build @@ -71,7 +71,7 @@ if have_virtfs endif =20 if config_all_devices.has_key('CONFIG_RISCV_IOMMU') - libqos_srcs +=3D files('riscv-iommu.c') + libqos_srcs +=3D files('riscv-iommu.c', 'qos-riscv-iommu.c') endif if config_all_devices.has_key('CONFIG_TPCI200') libqos_srcs +=3D files('tpci200.c') diff --git a/tests/qtest/libqos/qos-riscv-iommu.c b/tests/qtest/libqos/qos-= riscv-iommu.c new file mode 100644 index 0000000000..6c60889eeb --- /dev/null +++ b/tests/qtest/libqos/qos-riscv-iommu.c @@ -0,0 +1,405 @@ +/* + * QOS RISC-V IOMMU Module + * + * This module provides RISC-V IOMMU-specific helper functions for libqos = tests, + * encapsulating RISC-V IOMMU setup, and assertions. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/bitops.h" +#include "hw/riscv/riscv-iommu-bits.h" +#include "tests/qtest/libqos/pci.h" +#include "qos-iommu-testdev.h" +#include "qos-riscv-iommu.h" + +/* Apply space offset to address */ +static inline uint64_t qriommu_apply_space_offs(uint64_t address) +{ + return address + QRIOMMU_SPACE_OFFS; +} + +static uint64_t qriommu_encode_pte(uint64_t pa, uint64_t attrs) +{ + return ((pa >> 12) << 10) | attrs; +} + +static void qriommu_wait_for_queue_active(QTestState *qts, uint64_t iommu_= base, + uint32_t queue_csr, uint32_t on_= bit) +{ + guint64 timeout_us =3D 2 * 1000 * 1000; + gint64 start_time =3D g_get_monotonic_time(); + uint32_t reg; + + for (;;) { + qtest_clock_step(qts, 100); + + reg =3D qtest_readl(qts, iommu_base + queue_csr); + if (reg & on_bit) { + return; + } + g_assert(g_get_monotonic_time() - start_time <=3D timeout_us); + } +} + +uint32_t qriommu_expected_dma_result(QRIOMMUTestContext *ctx) +{ + return ctx->config.expected_result; +} + +uint32_t qriommu_build_dma_attrs(void) +{ + /* RISC-V IOMMU uses standard AXI attributes */ + return 0; +} + +uint32_t qriommu_setup_and_enable_translation(QRIOMMUTestContext *ctx) +{ + uint32_t build_result; + + /* Build page tables and RISC-V IOMMU structures first */ + build_result =3D qriommu_build_translation( + ctx->qts, ctx->config.trans_mode, + ctx->device_id); + if (build_result !=3D 0) { + g_test_message("Build failed: mode=3D%u device_id=3D%u status=3D0x= %x", + ctx->config.trans_mode, ctx->device_id, build_resul= t); + ctx->trans_status =3D build_result; + return ctx->trans_status; + } + + /* Program RISC-V IOMMU registers */ + qriommu_program_regs(ctx->qts, ctx->iommu_base); + + ctx->trans_status =3D 0; + return ctx->trans_status; +} + +static bool qriommu_validate_test_result(QRIOMMUTestContext *ctx) +{ + uint32_t expected =3D qriommu_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 qriommu_single_translation_setup(void *opaque) +{ + return qriommu_setup_and_enable_translation(opaque); +} + +static uint32_t qriommu_single_translation_attrs(void *opaque) +{ + return qriommu_build_dma_attrs(); +} + +static bool qriommu_single_translation_validate(void *opaque) +{ + return qriommu_validate_test_result(opaque); +} + +static void qriommu_single_translation_report(void *opaque, + uint32_t dma_result) +{ + QRIOMMUTestContext *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 qriommu_run_translation_case(QTestState *qts, QPCIDevice *dev, + QPCIBar bar, uint64_t iommu_base, + const QRIOMMUTestConfig *cfg) +{ + QRIOMMUTestContext ctx =3D { + .qts =3D qts, + .dev =3D dev, + .bar =3D bar, + .iommu_base =3D iommu_base, + .config =3D *cfg, + .device_id =3D dev->devfn, + }; + + QOSIOMMUTestdevDmaCfg dma =3D { + .dev =3D dev, + .bar =3D bar, + .iova =3D QRIOMMU_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, + qriommu_single_translation_setup, + qriommu_single_translation_attrs, + qriommu_single_translation_valida= te, + qriommu_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 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); + } + } +} + +static uint32_t qriommu_get_table_index(uint64_t addr, int level) +{ + /* SV39: 39-bit virtual address, 3-level page table */ + switch (level) { + case 0: + return (addr >> 30) & 0x1ff; /* L0: bits [38:30] */ + case 1: + return (addr >> 21) & 0x1ff; /* L1: bits [29:21] */ + case 2: + return (addr >> 12) & 0x1ff; /* L2: bits [20:12] */ + default: + g_assert_not_reached(); + } +} + +static uint64_t qriommu_get_table_addr(uint64_t base, int level, uint64_t = iova) +{ + uint32_t index =3D qriommu_get_table_index(iova, level); + return (base & QRIOMMU_PTE_PPN_MASK) + (index * 8); +} + +static void qriommu_map_leaf(QTestState *qts, uint64_t root_pa, + uint64_t l0_pa, uint64_t l1_pa, + uint64_t l0_pte_val, uint64_t l1_pte_val, + uint64_t va, uint64_t pa, uint64_t leaf_attrs) +{ + uint64_t l0_addr =3D qriommu_get_table_addr(root_pa, 0, va); + uint64_t l1_addr =3D qriommu_get_table_addr(l0_pa, 1, va); + uint64_t l2_addr =3D qriommu_get_table_addr(l1_pa, 2, va); + + qtest_writeq(qts, l0_addr, l0_pte_val); + qtest_writeq(qts, l1_addr, l1_pte_val); + qtest_writeq(qts, l2_addr, qriommu_encode_pte(pa, leaf_attrs)); +} + +static uint64_t qriommu_get_pte_attrs(bool is_leaf) +{ + if (!is_leaf) { + return QRIOMMU_NON_LEAF_PTE_MASK; + } + + /* For leaf PTE, set RWX permissions */ + return QRIOMMU_LEAF_PTE_RW_MASK; +} + +void qriommu_setup_translation_tables(QTestState *qts, + uint64_t iova, + QRIOMMUTransMode mode) +{ + uint64_t s_root =3D 0, s_l0_pte_val =3D 0, s_l1_pte_val =3D 0; + uint64_t s_l0_addr =3D 0, s_l1_addr =3D 0, s_l2_addr =3D 0, s_l2_pte_v= al =3D 0; + uint64_t s_l0_pa =3D 0, s_l1_pa =3D 0; + uint64_t s_l2_pa =3D qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); + uint64_t s_l0_pa_real =3D 0, s_l1_pa_real =3D 0; + uint64_t s_l2_pa_real =3D qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); + uint64_t non_leaf_attrs =3D qriommu_get_pte_attrs(false); + uint64_t leaf_attrs =3D qriommu_get_pte_attrs(true); + + if (mode !=3D QRIOMMU_TM_G_STAGE_ONLY) { + /* Setup S-stage 3-level page tables (SV39) */ + s_l0_pa =3D qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL); + s_l1_pa =3D qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL); + s_root =3D qriommu_apply_space_offs( + QRIOMMU_IOHGATP & QRIOMMU_PTE_PPN_MASK); + s_l2_pa =3D qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); + + s_l0_pa_real =3D s_l0_pa; + s_l1_pa_real =3D s_l1_pa; + s_l2_pa_real =3D s_l2_pa; + + if (mode =3D=3D QRIOMMU_TM_NESTED) { + s_l0_pa =3D QRIOMMU_L0_PTE_VAL; + s_l1_pa =3D QRIOMMU_L1_PTE_VAL; + s_l2_pa =3D QRIOMMU_L2_PTE_VAL; + + s_l0_pa_real =3D qriommu_apply_space_offs(QRIOMMU_L0_PTE_VAL); + s_l1_pa_real =3D qriommu_apply_space_offs(QRIOMMU_L1_PTE_VAL); + s_l2_pa_real =3D qriommu_apply_space_offs(QRIOMMU_L2_PTE_VAL); + } + + s_l0_pte_val =3D qriommu_encode_pte(s_l0_pa, non_leaf_attrs); + s_l1_pte_val =3D qriommu_encode_pte(s_l1_pa, non_leaf_attrs); + + s_l0_addr =3D qriommu_get_table_addr(s_root, 0, iova); + qtest_writeq(qts, s_l0_addr, s_l0_pte_val); + + s_l1_addr =3D qriommu_get_table_addr(s_l0_pa_real, 1, iova); + qtest_writeq(qts, s_l1_addr, s_l1_pte_val); + + s_l2_addr =3D qriommu_get_table_addr(s_l1_pa_real, 2, iova); + s_l2_pte_val =3D qriommu_encode_pte(s_l2_pa, leaf_attrs); + qtest_writeq(qts, s_l2_addr, s_l2_pte_val); + } + + if (mode =3D=3D QRIOMMU_TM_G_STAGE_ONLY || mode =3D=3D QRIOMMU_TM_NEST= ED) { + uint64_t g_root; + uint64_t g_l0_pa; + uint64_t g_l1_pa; + uint64_t g_l0_pte_val; + uint64_t g_l1_pte_val; + + g_root =3D qriommu_apply_space_offs( + QRIOMMU_G_IOHGATP & QRIOMMU_PTE_PPN_MASK); + g_l0_pa =3D qriommu_apply_space_offs(QRIOMMU_G_L0_PTE_VAL); + g_l1_pa =3D qriommu_apply_space_offs(QRIOMMU_G_L1_PTE_VAL); + g_l0_pte_val =3D qriommu_encode_pte(g_l0_pa, non_leaf_attrs); + g_l1_pte_val =3D qriommu_encode_pte(g_l1_pa, non_leaf_attrs); + + if (mode =3D=3D QRIOMMU_TM_G_STAGE_ONLY) { + qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa, + g_l0_pte_val, g_l1_pte_val, + iova, s_l2_pa_real, leaf_attrs); + } else { + qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa, + g_l0_pte_val, g_l1_pte_val, + QRIOMMU_IOHGATP, s_root, leaf_attrs); + qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa, + g_l0_pte_val, g_l1_pte_val, + QRIOMMU_L0_PTE_VAL, s_l0_pa_real, leaf_attrs); + qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa, + g_l0_pte_val, g_l1_pte_val, + QRIOMMU_L1_PTE_VAL, s_l1_pa_real, leaf_attrs); + qriommu_map_leaf(qts, g_root, g_l0_pa, g_l1_pa, + g_l0_pte_val, g_l1_pte_val, + QRIOMMU_L2_PTE_VAL, s_l2_pa_real, leaf_attrs); + } + } +} + +uint32_t qriommu_build_translation(QTestState *qts, QRIOMMUTransMode mode, + uint32_t device_id) +{ + uint64_t dc_addr, dc_addr_real; + struct riscv_iommu_dc dc; + uint64_t iohgatp; + + qtest_memset(qts, qriommu_apply_space_offs(QRIOMMU_DDT_BASE), 0, 0x100= 0); + + dc_addr =3D device_id * sizeof(struct riscv_iommu_dc) + QRIOMMU_DC_BAS= E; + dc_addr_real =3D qriommu_apply_space_offs(dc_addr); + + /* Build Device Context (DC) */ + memset(&dc, 0, sizeof(dc)); + + switch (mode) { + case QRIOMMU_TM_BARE: + /* Pass-through mode: tc.V=3D1, no FSC/IOHGATP */ + dc.tc =3D RISCV_IOMMU_DC_TC_V; + break; + + case QRIOMMU_TM_S_STAGE_ONLY: + /* S-stage only: tc.V=3D1, set FSC */ + dc.tc =3D RISCV_IOMMU_DC_TC_V; + iohgatp =3D qriommu_apply_space_offs(QRIOMMU_IOHGATP); + /* FSC mode: SV39 (mode=3D8) */ + dc.fsc =3D (iohgatp >> 12) | (8ull << 60); + break; + + case QRIOMMU_TM_G_STAGE_ONLY: + /* G-stage only: tc.V=3D1, set IOHGATP */ + dc.tc =3D RISCV_IOMMU_DC_TC_V; + iohgatp =3D qriommu_apply_space_offs(QRIOMMU_G_IOHGATP); + /* IOHGATP mode: SV39x4 (mode=3D8) */ + dc.iohgatp =3D (iohgatp >> 12) | (8ull << 60); + break; + + case QRIOMMU_TM_NESTED: + /* Nested: tc.V=3D1, set both FSC and IOHGATP */ + dc.tc =3D RISCV_IOMMU_DC_TC_V; + /* FSC mode: SV39 (mode=3D8) */ + dc.fsc =3D (QRIOMMU_IOHGATP >> 12) | (8ull << 60); + /* IOHGATP mode: SV39x4 (mode=3D8) */ + iohgatp =3D qriommu_apply_space_offs(QRIOMMU_G_IOHGATP); + dc.iohgatp =3D (iohgatp >> 12) | (8ull << 60); + break; + + default: + g_assert_not_reached(); + } + + /* Write DC to memory */ + qtest_writeq(qts, dc_addr_real + 0, dc.tc); + qtest_writeq(qts, dc_addr_real + 8, dc.iohgatp); + qtest_writeq(qts, dc_addr_real + 16, dc.ta); + qtest_writeq(qts, dc_addr_real + 24, dc.fsc); + qtest_writeq(qts, dc_addr_real + 32, dc.msiptp); + qtest_writeq(qts, dc_addr_real + 40, dc.msi_addr_mask); + qtest_writeq(qts, dc_addr_real + 48, dc.msi_addr_pattern); + qtest_writeq(qts, dc_addr_real + 56, dc._reserved); + + /* Setup translation tables if not in BARE mode */ + if (mode !=3D QRIOMMU_TM_BARE) { + qriommu_setup_translation_tables(qts, QRIOMMU_IOVA, mode); + } + + return 0; +} + +void qriommu_program_regs(QTestState *qts, uint64_t iommu_base) +{ + uint64_t ddtp, cqb, fqb; + uint64_t cq_base, fq_base; + uint64_t cq_align, fq_align; + uint32_t cq_entries =3D QRIOMMU_QUEUE_ENTRIES; + uint32_t fq_entries =3D QRIOMMU_QUEUE_ENTRIES; + uint32_t cq_log2sz =3D ctz32(cq_entries) - 1; + uint32_t fq_log2sz =3D ctz32(fq_entries) - 1; + + cq_base =3D qriommu_apply_space_offs(QRIOMMU_CQ_BASE_ADDR); + fq_base =3D qriommu_apply_space_offs(QRIOMMU_FQ_BASE_ADDR); + + cq_align =3D MAX(0x1000ull, (uint64_t)cq_entries * QRIOMMU_CQ_ENTRY_SI= ZE); + fq_align =3D MAX(0x1000ull, (uint64_t)fq_entries * QRIOMMU_FQ_ENTRY_SI= ZE); + g_assert((cq_base & (cq_align - 1)) =3D=3D 0); + g_assert((fq_base & (fq_align - 1)) =3D=3D 0); + + /* Setup Command Queue */ + cqb =3D (cq_base >> 12) << 10 | cq_log2sz; + qtest_writeq(qts, iommu_base + RISCV_IOMMU_REG_CQB, cqb); + qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_CQH, 0); + qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_CQT, 0); + qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_CQCSR, + RISCV_IOMMU_CQCSR_CQEN); + qriommu_wait_for_queue_active(qts, iommu_base, RISCV_IOMMU_REG_CQCSR, + RISCV_IOMMU_CQCSR_CQON); + + /* Setup Fault Queue */ + fqb =3D (fq_base >> 12) << 10 | fq_log2sz; + qtest_writeq(qts, iommu_base + RISCV_IOMMU_REG_FQB, fqb); + qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_FQH, 0); + qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_FQT, 0); + qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_FQCSR, + RISCV_IOMMU_FQCSR_FQEN); + qriommu_wait_for_queue_active(qts, iommu_base, RISCV_IOMMU_REG_FQCSR, + RISCV_IOMMU_FQCSR_FQON); + + /* Set Device Directory Table Pointer (DDTP) */ + ddtp =3D qriommu_apply_space_offs(QRIOMMU_DDT_BASE); + g_assert((ddtp & 0xfff) =3D=3D 0); + ddtp =3D ((ddtp >> 12) << 10) | RISCV_IOMMU_DDTP_MODE_1LVL; + qtest_writeq(qts, iommu_base + RISCV_IOMMU_REG_DDTP, ddtp); + g_assert((qtest_readq(qts, iommu_base + RISCV_IOMMU_REG_DDTP) & + (RISCV_IOMMU_DDTP_PPN | RISCV_IOMMU_DDTP_MODE)) =3D=3D + (ddtp & (RISCV_IOMMU_DDTP_PPN | RISCV_IOMMU_DDTP_MODE))); +} diff --git a/tests/qtest/libqos/qos-riscv-iommu.h b/tests/qtest/libqos/qos-= riscv-iommu.h new file mode 100644 index 0000000000..90e69a5d73 --- /dev/null +++ b/tests/qtest/libqos/qos-riscv-iommu.h @@ -0,0 +1,164 @@ +/* + * QOS RISC-V IOMMU Module + * + * This module provides RISC-V IOMMU-specific helper functions for libqos = tests, + * encapsulating RISC-V IOMMU setup, and assertions. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QTEST_LIBQOS_RISCV_IOMMU_H +#define QTEST_LIBQOS_RISCV_IOMMU_H + +#include "hw/misc/iommu-testdev.h" + +/* RISC-V IOMMU MMIO register base for virt machine */ +#define VIRT_RISCV_IOMMU_BASE 0x0000000003010000ull + +/* RISC-V IOMMU queue and table base addresses */ +#define QRIOMMU_CQ_BASE_ADDR 0x000000000e160000ull +#define QRIOMMU_FQ_BASE_ADDR 0x000000000e170000ull + +/* RISC-V IOMMU queue sizing */ +#define QRIOMMU_QUEUE_ENTRIES 1024 +#define QRIOMMU_CQ_ENTRY_SIZE 16 +#define QRIOMMU_FQ_ENTRY_SIZE 32 + +/* + * Translation tables and descriptors for RISC-V IOMMU. + * Similar to ARM SMMUv3, but using RISC-V IOMMU terminology: + * - Device Context (DC) instead of STE + * - First-stage context (FSC) for S-stage translation + * - IOHGATP for G-stage translation + * + * Granule size: 4KB pages + * Page table levels: 3 levels for SV39 (L0, L1, L2) + * IOVA size: 39-bit virtual address space + */ +#define QRIOMMU_IOVA 0x0000000080604567ull +#define QRIOMMU_IOHGATP 0x0000000000010000ull +#define QRIOMMU_DDT_BASE 0x0000000000014000ull +#define QRIOMMU_DC_BASE (QRIOMMU_DDT_BASE) + +#define QRIOMMU_L0_PTE_VAL 0x0000000000011000ull +#define QRIOMMU_L1_PTE_VAL 0x0000000000012000ull +#define QRIOMMU_L2_PTE_VAL 0x0000000000013000ull + +#define QRIOMMU_G_IOHGATP 0x0000000000020000ull +#define QRIOMMU_G_L0_PTE_VAL 0x0000000000021000ull +#define QRIOMMU_G_L1_PTE_VAL 0x0000000000022000ull + +/* + * PTE masks for RISC-V IOMMU page tables. + * Values match PTE_V, PTE_R, PTE_W, PTE_A, PTE_D in target/riscv/cpu_bits= .h + */ +#define QRIOMMU_NON_LEAF_PTE_MASK 0x001 /* PTE_V */ +#define QRIOMMU_LEAF_PTE_RW_MASK 0x0c7 /* V|R|W|A|D */ +#define QRIOMMU_PTE_PPN_MASK 0x003ffffffffffc00ull + +/* Address-space base offset for test tables */ +#define QRIOMMU_SPACE_OFFS 0x0000000080000000ull + +typedef enum QRIOMMUTransMode { + QRIOMMU_TM_BARE =3D 0, /* No translation (pass-through) */ + QRIOMMU_TM_S_STAGE_ONLY =3D 1, /* First-stage only (S-stage) */ + QRIOMMU_TM_G_STAGE_ONLY =3D 2, /* Second-stage only (G-stage) */ + QRIOMMU_TM_NESTED =3D 3, /* Nested translation (S + G) */ +} QRIOMMUTransMode; + +typedef struct QRIOMMUTestConfig { + QRIOMMUTransMode 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 */ +} QRIOMMUTestConfig; + +typedef struct QRIOMMUTestContext { + QTestState *qts; /* QTest state handle */ + QPCIDevice *dev; /* PCI device handle */ + QPCIBar bar; /* PCI BAR for MMIO access */ + QRIOMMUTestConfig config; /* Test configuration */ + uint64_t iommu_base; /* RISC-V IOMMU base address */ + uint32_t trans_status; /* Translation configuration status */ + uint32_t dma_result; /* DMA operation result */ + uint32_t device_id; /* Device ID for the test */ +} QRIOMMUTestContext; + +/* + * qriommu_setup_and_enable_translation - Complete translation setup and e= nable + * + * @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 RISC-V IOMMU structures (DC, page tables) + * 2. Programs RISC-V IOMMU registers + * 3. Returns configuration status + */ +uint32_t qriommu_setup_and_enable_translation(QRIOMMUTestContext *ctx); + +/* + * qriommu_build_translation - Build RISC-V IOMMU translation structures + * + * @qts: QTest state handle + * @mode: Translation mode (BARE, S_STAGE_ONLY, G_STAGE_ONLY, NESTED) + * @device_id: Device ID + * + * Returns: Build status (0 =3D success, non-zero =3D error) + * + * Constructs all necessary RISC-V IOMMU translation structures in guest m= emory: + * - Device Context (DC) for the given device ID + * - First-stage context (FSC) if S-stage translation is involved + * - Complete page table hierarchy based on translation mode + */ +uint32_t qriommu_build_translation(QTestState *qts, QRIOMMUTransMode mode, + uint32_t device_id); + +/* + * qriommu_program_regs - Program all required RISC-V IOMMU registers + * + * @qts: QTest state handle + * @iommu_base: RISC-V IOMMU base address + * + * Programs RISC-V IOMMU registers: + * - Device Directory Table Pointer (DDTP) + * - Command queue (base, head, tail) + * - Fault queue (base, head, tail) + * - Control and status registers + */ +void qriommu_program_regs(QTestState *qts, uint64_t iommu_base); + +/* + * qriommu_setup_translation_tables - Setup RISC-V IOMMU page table hierar= chy + * + * @qts: QTest state handle + * @iova: Input Virtual Address to translate + * @mode: Translation mode + * + * This function builds the complete page table structure for translating + * the given IOVA through the RISC-V IOMMU. The structure varies based on = mode: + * + * - BARE: No translation (pass-through) + * - S_STAGE_ONLY: Single S-stage walk (IOVA -> PA) + * - G_STAGE_ONLY: Single G-stage walk (IPA -> PA) + * - NESTED: S-stage walk (IOVA -> IPA) + G-stage walk (IPA -> PA) + */ +void qriommu_setup_translation_tables(QTestState *qts, + uint64_t iova, + QRIOMMUTransMode mode); + +/* High-level test execution helpers */ +void qriommu_run_translation_case(QTestState *qts, QPCIDevice *dev, + QPCIBar bar, uint64_t iommu_base, + const QRIOMMUTestConfig *cfg); + +/* Calculate expected DMA result */ +uint32_t qriommu_expected_dma_result(QRIOMMUTestContext *ctx); + +/* Build DMA attributes for RISC-V IOMMU */ +uint32_t qriommu_build_dma_attrs(void); + +#endif /* QTEST_LIBQOS_RISCV_IOMMU_H */ --=20 2.52.0 From nobody Sun Feb 8 04:13:01 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=1769684288; cv=none; d=zohomail.com; s=zohoarc; b=I4oNlI1qZC73uzOyyykpfYSCI4H3dzGyl9+DSeKMbTpwvunClqIhc6yOA8JOJMYOPaDWuR0S3Dz6bPvXLKOpK4i35d+pYqotXdudtU7AjrsUElyM4k0OBzD9vEsSEaVTuS1VkL+eYD65ULNbfIuyWSVGd+H0QSrgLI3h4t/uK10= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1769684288; h=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=hMVb9EtRJrCK4HmaBQXFutC5uXT4ZYxQiQYd+xiewGk=; b=CMKWsPaOGksSiNNeXTkeafpoT/RGBH2j+VnKao9T4wvklcqcsq9BEgmWmL+FyjB8x2K4IwLJQdSx3HZCU6o1a3jP+B6S+ssWoEFy/cbw8L4xBUCiTVgFRUyyfI8AFd5hyJo7x7dBFZsxsUQe6mwVv+sXAI3zLRyGo4+hSfAuRAw= 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 1769684288171833.919240996486; Thu, 29 Jan 2026 02:58:08 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1vlOWF-00053h-9Q; Thu, 29 Jan 2026 04:40:55 -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 1vlOWC-0004vb-93 for qemu-devel@nongnu.org; Thu, 29 Jan 2026 04:40:52 -0500 Received: from mail-pg1-x544.google.com ([2607:f8b0:4864:20::544]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1vlOWA-0003ZU-0o for qemu-devel@nongnu.org; Thu, 29 Jan 2026 04:40:51 -0500 Received: by mail-pg1-x544.google.com with SMTP id 41be03b00d2f7-c2af7d09533so460015a12.1 for ; Thu, 29 Jan 2026 01:40:49 -0800 (PST) Received: from ZEVORN-PC.bbrouter ([183.195.20.6]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3540f3cc7c6sm4871990a91.10.2026.01.29.01.40.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 01:40:48 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769679649; x=1770284449; 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=hMVb9EtRJrCK4HmaBQXFutC5uXT4ZYxQiQYd+xiewGk=; b=ZQrM/aMNy24ZZpdUTIlcPGaYry3rMwbO8sW1Zql/0ql+CgrPAI+xlL4JcwMRUQ03Br 5lGOh8l71uOfIcybmV+zAujmXvSM7yz258kTZDc2UxF5Y9P6Avm74jZc6LzfKkZ151Qj QfvF/PfSoiWXbHLpMv3Ul3W+SMIsyRJQlkQjZl0qg25W+8JndAsH1PLQMxYYp9Wr0CDX f4Hzib4fvo+2VQlPsS7AKRoOele1EPK2AuHPnCAqA0OeWgByfkWDGUuv9g97UweR2iYt tAISieoJeNL3VayVVE/FhQJXXqpxbS5DQFZyjfR17mV01TP1G/T26VFviZElj2uDPA2R tW0A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769679649; x=1770284449; 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=hMVb9EtRJrCK4HmaBQXFutC5uXT4ZYxQiQYd+xiewGk=; b=GeC1AJ2a6Da6WUd9OZXmO5irCbh5QdLjRhT/dph8aakEY9hkSPvg0jJu1iC2Ju0IVz hau14QhdCq5eRZFBHfDYzOaO5604G9s/ifkAwYzV0va42CpSCgW4Hdymmg8/xIIRgb/7 +B/4gAHizWigLKlNxhl518o+xwmB2KXwjf7rrQ/jqmKkZO/o5bN0mdSXbB4lZmohf4Ry /HcTfmfS74QVPzNM+s97Vtq6aAGVkdUBh8Akg3551cEFiK69YMWn/ESVEdLJnAcXREV/ 3lasfvvGCzIgu/+21StBE9UlJRsNgmmlcGWSxfCAayQGdy/taavJwXFfe5Cx0EcdKshV 9xFg== X-Gm-Message-State: AOJu0YxB/a7RG8JywPMVGvtpKSsaYTcX8oOwmchQNRfH0EB/7d4JWVIn 8qqaXqtjfb9hSiSn498wRWixZsdyjgnUT189o15K1+5+qV1ivx04ojas X-Gm-Gg: AZuq6aIMeiwfHYNS2eOuuEdAJ2vNbEk8qWvhRRQiPSTAXGWPS5CdlX/ac2LCRO2CeYT THPgnS4hvWZ67e5XH9iuYOo4mDT99TtynflnuV1KpM0lzT3qO0yoZoyjVpH3MXTycIzDehmX+hU jIU9ycYmcfXNQs5y8HNMR2gAa+nug0Y1SSsOZXriQnzisrSrRMjxBLFLl+90SuWNKg1AZM32NR0 BPvMrhl1z5f6wvbjjEoa3rNTnYKxisR4vJ17178EjCAVW8PgM8Je9IRoVKXbt67C2cqTAFuc2/h QyvY+j29XZ/TcS+Pnb+UJrX1+O5veLrebk9qb7+K19XzdCC7FFBdP/K8rqk1gQp8YxF09o60JAV i7erDBikLH2uxcGgRyMW9nMCTLjzrL3lphkJQrUJuScl/g7khmnVZp3xSIT83LkrEYg7Eu+No1o +t3bE6L/DeiKTnwkYT3d/sBsDIIuYyOJemMN0LFtGHPw6rk9D+VRJrnGyE X-Received: by 2002:a17:90a:d448:b0:349:3fe8:e7df with SMTP id 98e67ed59e1d1-353fed84605mr6389819a91.22.1769679648618; Thu, 29 Jan 2026 01:40:48 -0800 (PST) From: Chao Liu To: Alistair Francis , Daniel Henrique Barboza , Palmer Dabbelt , Weiwei Li , Liu Zhiwei , Fabiano Rosas , Laurent Vivier , Paolo Bonzini , Tao Tang Cc: qemu-devel@nongnu.org, qemu-riscv@nongnu.org, hust-os-kernel-patches@googlegroups.com, Chao Liu Subject: [RFC PATCH v2 2/2] tests/qtest: Add RISC-V IOMMU bare-metal test Date: Thu, 29 Jan 2026 17:39:51 +0800 Message-ID: <7cc1e085432336463dd19e11212cda516b97a33e.1769678103.git.chao.liu.zevorn@gmail.com> X-Mailer: git-send-email 2.52.0 In-Reply-To: References: MIME-Version: 1.0 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::544; envelope-from=chao.liu.zevorn@gmail.com; helo=mail-pg1-x544.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: 1769684291595158500 Content-Type: text/plain; charset="utf-8" Add a qtest suite for the RISC-V IOMMU PCI device on the virt machine. The test exercises bare, S-stage, G-stage, and nested translation paths using iommu-testdev and the qos-riscv-iommu helpers. The test validates: - Device context (DC) configuration - SV39 page table walks for S-stage translation - SV39x4 page table walks for G-stage translation - Nested translation combining both stages - FCTL register constraints This provides regression coverage for the RISC-V IOMMU implementation without requiring a full guest OS boot. Signed-off-by: Chao Liu Reviewed-by: Fabiano Rosas Reviewed-by: Tao Tang --- MAINTAINERS | 1 + tests/qtest/iommu-riscv-test.c | 279 +++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 5 +- 3 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/iommu-riscv-test.c diff --git a/MAINTAINERS b/MAINTAINERS index 830f56376b..73daaad841 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -349,6 +349,7 @@ F: common-user/host/riscv* F: tests/functional/riscv32 F: tests/functional/riscv64 F: tests/tcg/riscv64/ +F: tests/qtest/iommu-riscv-test.c =20 RISC-V XThead* extensions M: Christoph Muellner diff --git a/tests/qtest/iommu-riscv-test.c b/tests/qtest/iommu-riscv-test.c new file mode 100644 index 0000000000..5a86b18db9 --- /dev/null +++ b/tests/qtest/iommu-riscv-test.c @@ -0,0 +1,279 @@ +/* + * QTest testcase for RISC-V IOMMU with iommu-testdev + * + * This QTest file is used to test the RISC-V IOMMU with iommu-testdev so = that + * we can test RISC-V IOMMU without any guest kernel or firmware. + * + * Copyright (c) 2026 Chao Liu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "libqos/pci.h" +#include "libqos/generic-pcihost.h" +#include "hw/pci/pci_regs.h" +#include "hw/misc/iommu-testdev.h" +#include "hw/riscv/riscv-iommu-bits.h" +#include "libqos/qos-riscv-iommu.h" +#include "libqos/riscv-iommu.h" + +#define DMA_LEN 4 + +/* RISC-V virt machine PCI configuration */ +#define RISCV_GPEX_PIO_BASE 0x3000000 +#define RISCV_BUS_PIO_LIMIT 0x10000 +#define RISCV_BUS_MMIO_ALLOC_PTR 0x40000000 +#define RISCV_BUS_MMIO_LIMIT 0x80000000 +#define RISCV_ECAM_ALLOC_PTR 0x30000000 + +typedef struct RiscvIommuTestState { + QTestState *qts; + QGenericPCIBus gbus; + QPCIDevice *iommu_dev; + QPCIDevice *testdev; + QPCIBar testdev_bar; + uint64_t iommu_base; +} RiscvIommuTestState; + +static void riscv_config_qpci_bus(QGenericPCIBus *qpci) +{ + qpci->gpex_pio_base =3D RISCV_GPEX_PIO_BASE; + qpci->bus.pio_limit =3D RISCV_BUS_PIO_LIMIT; + qpci->bus.mmio_alloc_ptr =3D RISCV_BUS_MMIO_ALLOC_PTR; + qpci->bus.mmio_limit =3D RISCV_BUS_MMIO_LIMIT; + qpci->ecam_alloc_ptr =3D RISCV_ECAM_ALLOC_PTR; +} + +static uint64_t riscv_iommu_expected_gpa(uint64_t iova) +{ + return QRIOMMU_SPACE_OFFS + QRIOMMU_L2_PTE_VAL + (iova & 0xfff); +} + +static void save_fn(QPCIDevice *dev, int devfn, void *data) +{ + QPCIDevice **pdev =3D (QPCIDevice **) data; + uint16_t vendor =3D qpci_config_readw(dev, 0); + uint16_t device =3D qpci_config_readw(dev, 2); + + g_test_message("Found PCI device: vendor=3D0x%04x device=3D0x%04x devf= n=3D0x%02x", + vendor, device, devfn); + + if (!*pdev) { + *pdev =3D dev; + } +} + +static QPCIDevice *find_riscv_iommu_pci(QGenericPCIBus *gbus, + uint64_t *iommu_base) +{ + QPCIDevice *iommu_dev =3D NULL; + QPCIBar iommu_bar; + + g_test_message("Searching for riscv-iommu-pci " + "(vendor=3D0x%04x, device=3D0x%04x)", + RISCV_IOMMU_PCI_VENDOR_ID, RISCV_IOMMU_PCI_DEVICE_ID); + + qpci_device_foreach(&gbus->bus, RISCV_IOMMU_PCI_VENDOR_ID, + RISCV_IOMMU_PCI_DEVICE_ID, save_fn, &iommu_dev); + + if (!iommu_dev) { + g_test_message("riscv-iommu-pci device not found!"); + return NULL; + } + + g_test_message("Found riscv-iommu-pci at devfn=3D0x%02x", iommu_dev->d= evfn); + + qpci_device_enable(iommu_dev); + iommu_bar =3D qpci_iomap(iommu_dev, 0, NULL); + g_assert_false(iommu_bar.is_io); + + *iommu_base =3D iommu_bar.addr; + g_test_message("RISC-V IOMMU MMIO base address: 0x%" PRIx64, *iommu_ba= se); + + return iommu_dev; +} + +static QPCIDevice *find_iommu_testdev(QGenericPCIBus *gbus, QPCIBar *bar) +{ + QPCIDevice *dev =3D NULL; + + g_test_message("Searching for iommu-testdev (vendor=3D0x%04x, device= =3D0x%04x)", + IOMMU_TESTDEV_VENDOR_ID, IOMMU_TESTDEV_DEVICE_ID); + + qpci_device_foreach(&gbus->bus, IOMMU_TESTDEV_VENDOR_ID, + IOMMU_TESTDEV_DEVICE_ID, save_fn, &dev); + g_assert(dev); + + qpci_device_enable(dev); + *bar =3D qpci_iomap(dev, 0, NULL); + g_assert_false(bar->is_io); + + return dev; +} + +static bool riscv_iommu_test_setup(RiscvIommuTestState *state) +{ + if (!qtest_has_machine("virt")) { + g_test_skip("virt machine not available"); + return false; + } + + state->qts =3D qtest_init("-machine virt,acpi=3Doff " + "-cpu max -smp 1 -m 512 -net none " + "-device riscv-iommu-pci " + "-device iommu-testdev"); + + qpci_init_generic(&state->gbus, state->qts, NULL, false); + riscv_config_qpci_bus(&state->gbus); + + state->iommu_dev =3D find_riscv_iommu_pci(&state->gbus, &state->iommu_= base); + g_assert(state->iommu_dev); + + state->testdev =3D find_iommu_testdev(&state->gbus, &state->testdev_ba= r); + g_assert(state->testdev); + + return true; +} + +static void riscv_iommu_test_teardown(RiscvIommuTestState *state) +{ + qtest_quit(state->qts); +} + +static uint64_t riscv_iommu_check(QTestState *qts, uint64_t iommu_base, + QRIOMMUTransMode mode) +{ + uint64_t cap; + uint64_t ddtp; + uint32_t cqcsr; + uint32_t fqcsr; + uint32_t pqcsr; + uint32_t fctl; + uint32_t fctl_mask; + uint32_t fctl_desired; + uint32_t igs; + + cap =3D qtest_readq(qts, iommu_base + RISCV_IOMMU_REG_CAP); + g_assert_cmpuint((uint32_t)(cap & RISCV_IOMMU_CAP_VERSION), =3D=3D, + RISCV_IOMMU_SPEC_DOT_VER); + + fctl =3D qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_FCTL); + igs =3D (cap & RISCV_IOMMU_CAP_IGS) >> 28; + g_assert_cmpuint(igs, <=3D, RISCV_IOMMU_CAP_IGS_BOTH); + + fctl_mask =3D RISCV_IOMMU_FCTL_BE | RISCV_IOMMU_FCTL_WSI | + RISCV_IOMMU_FCTL_GXL; + fctl_desired =3D fctl & ~fctl_mask; + if (igs =3D=3D RISCV_IOMMU_CAP_IGS_WSI) { + fctl_desired |=3D RISCV_IOMMU_FCTL_WSI; + } + + if ((fctl & fctl_mask) !=3D (fctl_desired & fctl_mask)) { + ddtp =3D qtest_readq(qts, iommu_base + RISCV_IOMMU_REG_DDTP); + cqcsr =3D qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_CQCSR); + fqcsr =3D qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_FQCSR); + pqcsr =3D qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_PQCSR); + + g_assert_cmpuint((uint32_t)(ddtp & RISCV_IOMMU_DDTP_MODE), =3D=3D, + RISCV_IOMMU_DDTP_MODE_OFF); + g_assert_cmpuint(cqcsr & RISCV_IOMMU_CQCSR_CQON, =3D=3D, 0); + g_assert_cmpuint(fqcsr & RISCV_IOMMU_FQCSR_FQON, =3D=3D, 0); + g_assert_cmpuint(pqcsr & RISCV_IOMMU_PQCSR_PQON, =3D=3D, 0); + + qtest_writel(qts, iommu_base + RISCV_IOMMU_REG_FCTL, fctl_desired); + fctl =3D qtest_readl(qts, iommu_base + RISCV_IOMMU_REG_FCTL); + } + + g_assert_cmpuint(fctl & fctl_mask, =3D=3D, fctl_desired & fctl_mask); + + if (mode =3D=3D QRIOMMU_TM_S_STAGE_ONLY || mode =3D=3D QRIOMMU_TM_NEST= ED) { + g_assert((cap & RISCV_IOMMU_CAP_SV39) !=3D 0); + } + if (mode =3D=3D QRIOMMU_TM_G_STAGE_ONLY || mode =3D=3D QRIOMMU_TM_NEST= ED) { + g_assert((cap & RISCV_IOMMU_CAP_SV39X4) !=3D 0); + g_assert_cmpuint(fctl & RISCV_IOMMU_FCTL_GXL, =3D=3D, 0); + } + + return cap; +} + +static void run_riscv_iommu_translation(const QRIOMMUTestConfig *cfg) +{ + RiscvIommuTestState state =3D { 0 }; + + if (!riscv_iommu_test_setup(&state)) { + return; + } + + riscv_iommu_check(state.qts, state.iommu_base, cfg->trans_mode); + + g_test_message("### RISC-V IOMMU translation mode=3D%d ###", + cfg->trans_mode); + qriommu_run_translation_case(state.qts, state.testdev, state.testdev_b= ar, + state.iommu_base, cfg); + riscv_iommu_test_teardown(&state); +} + +static void test_riscv_iommu_bare(void) +{ + QRIOMMUTestConfig cfg =3D { + .trans_mode =3D QRIOMMU_TM_BARE, + .dma_gpa =3D QRIOMMU_IOVA, + .dma_len =3D DMA_LEN, + .expected_result =3D 0, + }; + + run_riscv_iommu_translation(&cfg); +} + +static void test_riscv_iommu_s_stage_only(void) +{ + QRIOMMUTestConfig cfg =3D { + .trans_mode =3D QRIOMMU_TM_S_STAGE_ONLY, + .dma_gpa =3D riscv_iommu_expected_gpa(QRIOMMU_IOVA), + .dma_len =3D DMA_LEN, + .expected_result =3D 0, + }; + + run_riscv_iommu_translation(&cfg); +} + +static void test_riscv_iommu_g_stage_only(void) +{ + QRIOMMUTestConfig cfg =3D { + .trans_mode =3D QRIOMMU_TM_G_STAGE_ONLY, + .dma_gpa =3D riscv_iommu_expected_gpa(QRIOMMU_IOVA), + .dma_len =3D DMA_LEN, + .expected_result =3D 0, + }; + + run_riscv_iommu_translation(&cfg); +} + +static void test_riscv_iommu_nested(void) +{ + QRIOMMUTestConfig cfg =3D { + .trans_mode =3D QRIOMMU_TM_NESTED, + .dma_gpa =3D riscv_iommu_expected_gpa(QRIOMMU_IOVA), + .dma_len =3D DMA_LEN, + .expected_result =3D 0, + }; + + run_riscv_iommu_translation(&cfg); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + qtest_add_func("/iommu-testdev/translation/bare", + test_riscv_iommu_bare); + qtest_add_func("/iommu-testdev/translation/s-stage-only", + test_riscv_iommu_s_stage_only); + qtest_add_func("/iommu-testdev/translation/g-stage-only", + test_riscv_iommu_g_stage_only); + qtest_add_func("/iommu-testdev/translation/ns-nested", + test_riscv_iommu_nested); + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index dfb83650c6..25fdbc7980 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -287,7 +287,10 @@ qtests_riscv32 =3D \ (config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watc= hdog-test'] : []) =20 qtests_riscv64 =3D ['riscv-csr-test'] + \ - (unpack_edk2_blobs ? ['bios-tables-test'] : []) + (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ + (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and + config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? + ['iommu-riscv-test'] : []) =20 qos_test_ss =3D ss.source_set() qos_test_ss.add( --=20 2.52.0