From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 7A62DCED617 for ; Tue, 18 Nov 2025 11:32:58 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1vLJwr-0004DY-PH; Tue, 18 Nov 2025 06:32:37 -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 1vLJwq-0004Cy-0V for qemu-arm@nongnu.org; Tue, 18 Nov 2025 06:32:36 -0500 Received: from mail-wm1-x329.google.com ([2a00:1450:4864:20::329]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1vLJwl-0006Jl-MQ for qemu-arm@nongnu.org; Tue, 18 Nov 2025 06:32:35 -0500 Received: by mail-wm1-x329.google.com with SMTP id 5b1f17b1804b1-4779aa4f928so32546345e9.1 for ; Tue, 18 Nov 2025 03:32:30 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1763465549; x=1764070349; darn=nongnu.org; h=content-transfer-encoding:mime-version:message-id:date:user-agent :references:in-reply-to:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=nRNKsPuWfZ28g5e4fKUsruNkhjYsfRzvMV/7yCawOjo=; b=oURw5Lv4+C8TKxWUeQvi+mG4HkWuyRNxTufrvunozogputTY0J9+CmQr3IX9DfBBOu jTCBBqmHYDnHNUOOyvwNZKmYx0XkSML5Tle4tfZz6jYJqGvRh8Ru06BccIRX6qgR8etR /fuH/SxNKS20F/hBvEBjafQt1IHDgm5udBVI5dPV9GBS40yIn683toq7PVvTRPGcCsF6 4CQ5tWwSvmMwANzX3QjK7jFOwEPVSESOMp1Zm5PzJCr3mDerYD90Phsg6DU4jskp37T8 VVvsC+vq8wXSnCqrpGD+2G72PE07PptKo3mhhlK3xqknNHU36SUHOaVru+LpW9qO1dsp llkQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1763465549; x=1764070349; h=content-transfer-encoding:mime-version:message-id:date:user-agent :references:in-reply-to:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=nRNKsPuWfZ28g5e4fKUsruNkhjYsfRzvMV/7yCawOjo=; b=d24c5H6kC2uQVppTppG/lu82aXzaSitvjBfly12IADGEXwMDWEd3uvlhbg97nwgGFj 8vrVZXn8qTpBG8l97vohXq6nxpFXD4YcPuZ8iVKcg00RwjvzJ/wdUEqpWMsvyEPxcK7E nzedpz5qGhzG562H0EVLPvg1iVhJ+PNLBLv7mayQ+m2rajW9xpLVs7FhhqYdYe8TnfzH GJ1Bk1z6IaFH6MJXkHx1aB7zSGxoMLnLabXtjjuEUsT6/Ood8XKl/COxGPD/TzYKtlbY aCJsnl2DyqXoGgJ07xKyY+SIDhNBCaS+kv01SxUAeXml9QT4NSKj/0JwbblFFmPjgj6p XBxQ== X-Forwarded-Encrypted: i=1; AJvYcCUKZjDVROHSa2ec1nbduKQ5i0kDJ+xCliQbAcjlQbYH06rTAZxQ6965r9jVBxp/YjDH01aJ3p6cpg==@nongnu.org X-Gm-Message-State: AOJu0YyLMbOLezZopXX1/Zwxz4BdlTFJnVsFSFdPLCMnOl6gcZ/48pex tTs5d11SddUCuHb6KtXQAbgAaim+u2eKXOR5xq36x4rjV4EVLr8E2qMn7grEUoZiaso= X-Gm-Gg: ASbGnctI6qt6+8wbXUAPVaxvaMNnBmdVutMArWktcZqi73XafZlfHjYSvL6WwSkJjwN HMjbX5XmefUhaYPUpd2qk7KHDp1cDh0STGf2GSz0TbPQX9sDjqD/eZ37nzJKgHIdSHfVr82ZJQA jyszD3ErPy3T5zXTg6MPGWT3xILxNvgAfqsQyMNH79sbUaADG14b73b/QKd9wTnK1PnvcIj2qXH YWeosTbh73546yR4HWke/V6k0En6xsSiAZ7upsQXERipMnKvYWV4BhW2jDTjFZaKgp43uHqzQoS lXQLrsV2jF4WK+m/hW8h87c5VhVzxG7EryCrR6OQwkMeMob3ZQCnFI67UN9XQSb0ig7U32kbEv4 H7zYPIicC7E7jA6ZiYyLDdwgPxSrkhJbmWcsrnej2ErEAmh+7IaaZ44VGwrZY39y+Ag9ft4tk14 sW X-Google-Smtp-Source: AGHT+IGbn01bpzidm3vzU/gALnvsftRYR4yF73Ka1Ms9wouHI/z33jSJKDmh/HdWXxAzSPZYtM863g== X-Received: by 2002:a05:600c:3542:b0:471:115e:87bd with SMTP id 5b1f17b1804b1-4778fe7d0ecmr149548325e9.26.1763465548954; Tue, 18 Nov 2025 03:32:28 -0800 (PST) Received: from draig.lan ([185.126.160.19]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-4779973ddcfsm181140965e9.15.2025.11.18.03.32.27 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 18 Nov 2025 03:32:28 -0800 (PST) Received: from draig (localhost [IPv6:::1]) by draig.lan (Postfix) with ESMTP id 39D935F886; Tue, 18 Nov 2025 11:32:27 +0000 (GMT) From: =?utf-8?Q?Alex_Benn=C3=A9e?= To: Tao Tang Cc: Paolo Bonzini , Fabiano Rosas , Laurent Vivier , Eric Auger , Peter Maydell , qemu-devel@nongnu.org, qemu-arm@nongnu.org, Chen Baozi , Pierrick Bouvier , Philippe =?utf-8?Q?Mathieu-Daud?= =?utf-8?Q?=C3=A9?= , Jean-Philippe Brucker , Mostafa Saleh Subject: Re: [RFC v3 2/3] tests/qtest: add libqos SMMUv3 helper library In-Reply-To: <20251112162152.447327-3-tangtao1634@phytium.com.cn> (Tao Tang's message of "Thu, 13 Nov 2025 00:21:51 +0800") References: <20251112162152.447327-1-tangtao1634@phytium.com.cn> <20251112162152.447327-3-tangtao1634@phytium.com.cn> User-Agent: mu4e 1.12.14-pre3; emacs 30.1 Date: Tue, 18 Nov 2025 11:32:27 +0000 Message-ID: <87zf8jk244.fsf@draig.linaro.org> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Received-SPF: pass client-ip=2a00:1450:4864:20::329; envelope-from=alex.bennee@linaro.org; helo=mail-wm1-x329.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-arm@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-arm-bounces+qemu-arm=archiver.kernel.org@nongnu.org Sender: qemu-arm-bounces+qemu-arm=archiver.kernel.org@nongnu.org Tao Tang writes: > 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: > > - Automatic 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 > - Command queue management for SMMU configuration commands > > 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 > --- > tests/qtest/libqos/meson.build | 3 + > tests/qtest/libqos/qos-smmuv3.c | 920 ++++++++++++++++++++++++++++++++ > tests/qtest/libqos/qos-smmuv3.h | 291 ++++++++++ > 3 files changed, 1214 insertions(+) > create mode 100644 tests/qtest/libqos/qos-smmuv3.c > create mode 100644 tests/qtest/libqos/qos-smmuv3.h > > diff --git a/tests/qtest/libqos/meson.build b/tests/qtest/libqos/meson.bu= ild > index 1ddaf7b095..8d6758ec2b 100644 > --- a/tests/qtest/libqos/meson.build > +++ b/tests/qtest/libqos/meson.build > @@ -60,6 +60,9 @@ libqos_srcs =3D files( > 'x86_64_pc-machine.c', > 'riscv-virt-machine.c', > 'loongarch-virt-machine.c', > + > + # SMMU: > + 'qos-smmuv3.c', > ) >=20=20 > if have_virtfs > diff --git a/tests/qtest/libqos/qos-smmuv3.c b/tests/qtest/libqos/qos-smm= uv3.c > new file mode 100644 > index 0000000000..1b97b8b5e6 > --- /dev/null > +++ b/tests/qtest/libqos/qos-smmuv3.c > @@ -0,0 +1,920 @@ > +/* > + * QOS SMMUv3 Module > + * > + * This module provides SMMUv3-specific helper functions for libqos test= s, > + * encapsulating SMMUv3 setup, assertion, and cleanup operations. > + * > + * Copyright (c) 2025 Phytium Technology > + * > + * Author: > + * Tao Tang > + * > + * SPDX-License-Identifier: GPL-2.0-or-later > + */ > + > +#include "qemu/osdep.h" > +#include "tests/qtest/libqos/pci.h" > +#include "hw/misc/iommu-testdev.h" > +#include "qos-smmuv3.h" > + > +/* STE/CD field setting macros */ > +#define QSMMU_STE_OR_CD_ENTRY_BYTES 64 > +#define QSMMU_STE_S2T0SZ_VAL 0x14 > + > +#define QSMMU_STE_SET_VALID(ste, val) \ > + ((ste)->word[0] =3D ((ste)->word[0] & ~(0x1 << 0)) | = \ > + (((val) & 0x1) << 0)) > +#define QSMMU_STE_SET_CONFIG(ste, val) \ > + ((ste)->word[0] =3D ((ste)->word[0] & ~(0x7 << 1)) | = \ > + (((val) & 0x7) << 1)) > +#define QSMMU_STE_SET_S1FMT(ste, val) \ > + ((ste)->word[0] =3D ((ste)->word[0] & ~(0x3 << 4)) | = \ > + (((val) & 0x3) << 4)) > + These macros are basically re-inventing what we have in include/hw/registerfields.h so instead you would have something like: REG32(STE, 0) FIELD(STE, VALID, 0, 1) FIELD(STE, CONFIG, 1, 7) FIELD(STE, S1FMT, 4, 2) etc However as these are mirroring smmuv3-internal.h why aren't we using those? The fuller solution would be to update smmuv3-internal to used the REG/FIELD macros rather than doing it by hand. Although the register field API existed then we weren't so keen to standardise all this boilerplate back then. > +#define QSMMU_STE_SET_CTXPTR(ste, val) do { \ > + (ste)->word[0] =3D ((ste)->word[0] & 0x0000003fu) | = \ > + ((uint32_t)(val) & 0xffffffc0u); \ > + (ste)->word[1] =3D ((ste)->word[1] & 0xffff0000u) | = \ > + ((uint32_t)(((uint64_t)(val)) >> 32) & \ > + 0x0000ffffu); \ > +} while (0) > + > +#define QSMMU_STE_SET_S1CDMAX(ste, val) \ > + ((ste)->word[1] =3D ((ste)->word[1] & ~(0x1f << 27)) | = \ > + (((val) & 0x1f) << 27)) > +#define QSMMU_STE_SET_S1STALLD(ste, val) \ > + ((ste)->word[2] =3D ((ste)->word[2] & ~(0x1 << 27)) | = \ > + (((val) & 0x1) << 27)) > +#define QSMMU_STE_SET_EATS(ste, val) \ > + ((ste)->word[2] =3D ((ste)->word[2] & ~(0x3 << 28)) | = \ > + (((val) & 0x3) << 28)) > +#define QSMMU_STE_SET_STRW(ste, val) \ > + ((ste)->word[2] =3D ((ste)->word[2] & ~(0x3 << 30)) | = \ > + (((val) & 0x3) << 30)) > +#define QSMMU_STE_SET_NSCFG(ste, val) \ > + ((ste)->word[2] =3D ((ste)->word[2] & ~(0x3 << 14)) | = \ > + (((val) & 0x3) << 14)) > +#define QSMMU_STE_SET_S2VMID(ste, val) \ > + ((ste)->word[4] =3D ((ste)->word[4] & ~0xffff) | ((val) & 0xffff)) > +#define QSMMU_STE_SET_S2T0SZ(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~0x3f) | ((val) & 0x3f)) > +#define QSMMU_STE_SET_S2SL0(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x3 << 6)) | = \ > + (((val) & 0x3) << 6)) > +#define QSMMU_STE_SET_S2TG(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x3 << 14)) | = \ > + (((val) & 0x3) << 14)) > +#define QSMMU_STE_SET_S2PS(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x7 << 16)) | = \ > + (((val) & 0x7) << 16)) > +#define QSMMU_STE_SET_S2AA64(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x1 << 19)) | = \ > + (((val) & 0x1) << 19)) > +#define QSMMU_STE_SET_S2ENDI(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x1 << 20)) | = \ > + (((val) & 0x1) << 20)) > +#define QSMMU_STE_SET_S2AFFD(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x1 << 21)) | = \ > + (((val) & 0x1) << 21)) > +#define QSMMU_STE_SET_S2HD(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x1 << 23)) | = \ > + (((val) & 0x1) << 23)) > +#define QSMMU_STE_SET_S2HA(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x1 << 24)) | = \ > + (((val) & 0x1) << 24)) > +#define QSMMU_STE_SET_S2S(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x1 << 25)) | = \ > + (((val) & 0x1) << 25)) > +#define QSMMU_STE_SET_S2R(ste, val) \ > + ((ste)->word[5] =3D ((ste)->word[5] & ~(0x1 << 26)) | = \ > + (((val) & 0x1) << 26)) > + > +#define QSMMU_STE_SET_S2TTB(ste, val) do { \ > + (ste)->word[6] =3D ((ste)->word[6] & 0x0000000fu) | = \ > + ((uint32_t)(val) & 0xfffffff0u); \ > + (ste)->word[7] =3D ((ste)->word[7] & 0xfff00000u) | = \ > + ((uint32_t)(((uint64_t)(val)) >> 32) & \ > + 0x000fffffu); \ > +} while (0) > + > +/* CD field setting macros */ > +#define QSMMU_CD_SET_VALID(cd, val) \ > + ((cd)->word[0] =3D ((cd)->word[0] & ~(0x1 << 31)) | = \ > + (((val) & 0x1) << 31)) > +#define QSMMU_CD_SET_TSZ(cd, sel, val) \ > + ((cd)->word[0] =3D ((cd)->word[0] & = \ > + ~(0x3f << ((sel) * 16 + 0))) | \ > + (((val) & 0x3f) << ((sel) * 16 + 0))) > +#define QSMMU_CD_SET_TG(cd, sel, val) \ > + ((cd)->word[0] =3D ((cd)->word[0] & = \ > + ~(0x3 << ((sel) * 16 + 6))) | \ > + (((val) & 0x3) << ((sel) * 16 + 6))) > +#define QSMMU_CD_SET_EPD(cd, sel, val) \ > + ((cd)->word[0] =3D ((cd)->word[0] & = \ > + ~(0x1 << ((sel) * 16 + 14))) | \ > + (((val) & 0x1) << ((sel) * 16 + 14))) > +#define QSMMU_CD_SET_ENDI(cd, val) \ > + ((cd)->word[0] =3D ((cd)->word[0] & ~(0x1 << 15)) | = \ > + (((val) & 0x1) << 15)) > +#define QSMMU_CD_SET_IPS(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x7 << 0)) | = \ > + (((val) & 0x7) << 0)) > +#define QSMMU_CD_SET_AFFD(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x1 << 3)) | = \ > + (((val) & 0x1) << 3)) > +#define QSMMU_CD_SET_HD(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x1 << 10)) | = \ > + (((val) & 0x1) << 10)) > +#define QSMMU_CD_SET_HA(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x1 << 11)) | = \ > + (((val) & 0x1) << 11)) > +#define QSMMU_CD_SET_TTB(cd, sel, val) do { \ > + (cd)->word[(sel) * 2 + 2] =3D = \ > + ((cd)->word[(sel) * 2 + 2] & 0x0000000f) | \ > + ((val) & 0xfffffff0); \ > + (cd)->word[(sel) * 2 + 3] =3D = \ > + ((cd)->word[(sel) * 2 + 3] & 0xfff80000) | \ > + ((((uint64_t)(val)) >> 32) & 0x0007ffff); \ > +} while (0) > +#define QSMMU_CD_SET_HAD(cd, sel, val) \ > + ((cd)->word[(sel) * 2 + 2] =3D = \ > + ((cd)->word[(sel) * 2 + 2] & ~(0x1 << 1)) | \ > + (((val) & 0x1) << 1)) > +#define QSMMU_CD_SET_MAIR0(cd, val) ((cd)->word[6] =3D (val)) > +#define QSMMU_CD_SET_MAIR1(cd, val) ((cd)->word[7] =3D (val)) > +#define QSMMU_CD_SET_TCR_T0SZ(cd, val) \ > + ((cd)->word[4] =3D ((cd)->word[4] & ~0x3f) | ((val) & 0x3f)) > +#define QSMMU_CD_SET_ASID(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0xffff << 16)) | = \ > + (((val) & 0xffff) << 16)) > +#define QSMMU_CD_SET_S(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x1 << 12)) | = \ > + (((val) & 0x1) << 12)) > +#define QSMMU_CD_SET_R(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x1 << 13)) | = \ > + (((val) & 0x1) << 13)) > +#define QSMMU_CD_SET_A(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x1 << 14)) | = \ > + (((val) & 0x1) << 14)) > +#define QSMMU_CD_SET_AARCH64(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x1 << 9)) | = \ > + (((val) & 0x1) << 9)) > +#define QSMMU_CD_SET_TBI(cd, val) \ > + ((cd)->word[1] =3D ((cd)->word[1] & ~(0x3 << 6)) | = \ > + (((val) & 0x3) << 6)) > +#define QSMMU_CD_SET_NSCFG0(cd, val) \ > + ((cd)->word[2] =3D ((cd)->word[2] & ~(0x1 << 0)) | = \ > + (((val) & 0x1) << 0)) > +#define QSMMU_CD_SET_NSCFG1(cd, val) \ > + ((cd)->word[4] =3D ((cd)->word[4] & ~(0x1 << 0)) | = \ > + (((val) & 0x1) << 0)) > + > + > +/* STE and CD image structures */ > +typedef struct { > + uint32_t word[8]; > +} STEImg; > + > +typedef struct { > + uint32_t word[8]; > +} CDImg; > + again we are duplicating smmuv3-internal here. > +/* 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 0; > +} > + > +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 */ > + attrs =3D ITD_ATTRS_SET_SECURE(attrs, 0); > + attrs =3D ITD_ATTRS_SET_SPACE(attrs, QSMMU_SPACE_NONSECURE); > + break; > + default: > + g_assert_not_reached(); > + } > + > + return attrs; > +} > + > +uint32_t qsmmu_setup_and_enable_translation(QSMMUTestContext *ctx) > +{ > + uint32_t build_result; > + > + /* Trigger configuration */ > + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_TRANS_DBELL, 0x2); > + > + /* 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); > + > + /* Read configuration status */ > + ctx->trans_status =3D qpci_io_readl(ctx->dev, ctx->bar, > + ITD_REG_TRANS_STATUS); > + > + return ctx->trans_status; > +} > + > +uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx) > +{ > + uint32_t result, attrs_val; > + int i; > + > + /* Program DMA parameters */ > + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_LO, > + (uint32_t)ctx->config.dma_iova); > + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_GVA_HI, > + (uint32_t)(ctx->config.dma_iova >> 32)); > + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_LEN, > + ctx->config.dma_len); > + > + /* > + * Build and write DMA attributes based on device security state. > + * > + * We only support Non-secure state for now. But in future, this can= be > + * extended to support static Secure state or dynamic Realm state as= well. > + */ > + attrs_val =3D qsmmu_build_dma_attrs(QSMMU_SPACE_NONSECURE); > + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_ATTRS, attrs_val); > + > + /* Flip status */ > + qpci_io_writel(ctx->dev, ctx->bar, ITD_REG_DMA_DBELL, 1); > + > + /* Trigger DMA by reading ID register */ > + qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_TRIGGERING); > + > + /* Poll for DMA completion */ > + for (i =3D 0; i < 1000; i++) { > + result =3D qpci_io_readl(ctx->dev, ctx->bar, ITD_REG_DMA_RESULT); > + if (result !=3D ITD_DMA_RESULT_BUSY) { > + ctx->dma_result =3D result; > + break; > + } > + g_usleep(1000); > + } > + > + /* Fallback for timeout */ > + if (ctx->dma_result =3D=3D ITD_DMA_RESULT_BUSY) { > + ctx->dma_result =3D ITD_DMA_ERR_TX_FAIL; > + } > + > + return ctx->dma_result; > +} > + > +static void qsmmu_push_cfgi_cmd(QTestState *qts, uint64_t smmu_base, > + QSMMUSpace bank_sp, uint32_t type, > + uint32_t sid, bool ssec) > +{ > + hwaddr bank_off; > + uint32_t new_prod, base_lo, base_hi, log2size, prod; > + uint32_t index_mask, slot, words[4]; > + uint64_t base, qbase, entry_pa; > + int i; > + > + /* Only non-secure commands are supported for now */ > + g_assert_false(ssec); > + > + bank_off =3D 0; > + > + /* Read CMDQ_BASE register */ > + base_lo =3D qtest_readl(qts, smmu_base + bank_off + > + QSMMU_REG_CMDQ_BASE); > + base_hi =3D qtest_readl(qts, smmu_base + bank_off + > + QSMMU_REG_CMDQ_BASE + 4); > + base =3D ((uint64_t)base_hi << 32) | base_lo; > + log2size =3D base & 0x1f; > + qbase =3D base & QSMMU_BASE_ADDR_MASK; > + > + /* Read CMDQ_PROD register */ > + prod =3D qtest_readl(qts, smmu_base + bank_off + > + QSMMU_REG_CMDQ_PROD); > + index_mask =3D (1u << log2size) - 1u; > + slot =3D prod & index_mask; > + entry_pa =3D qbase + (uint64_t)slot * 16u; > + > + /* Prepare command words */ > + memset(words, 0, sizeof(words)); > + words[0] =3D (type & 0xff) | (ssec ? (1u << 10) : 0u); > + words[1] =3D sid; > + > + /* Write command to the command queue */ > + for (i =3D 0; i < 4; i++) { > + qtest_writel(qts, entry_pa + i * 4, words[i]); > + } > + > + /* Update PROD to trigger command handler */ > + new_prod =3D (prod + 1) & ((1u << (log2size + 1)) - 1u); > + qtest_writel(qts, smmu_base + bank_off + QSMMU_REG_CMDQ_PROD, new_pr= od); > +} > + > +void qsmmu_cleanup_translation(QSMMUTestContext *ctx) > +{ > + static const QSMMUSpace spaces[] =3D { QSMMU_SPACE_NONSECURE }; > + uint32_t sid; > + uint64_t ste_addr, ste_addr_real, cd_addr_real; > + QSMMUSpace build_space; > + int idx, i; > + > + sid =3D ctx->sid; > + ste_addr =3D sid * QSMMU_STE_OR_CD_ENTRY_BYTES + QSMMU_STR_TAB_BASE; > + > + /* Clear page table entries and configuration structures */ > + for (idx =3D 0; idx < ARRAY_SIZE(spaces); idx++) { > + build_space =3D spaces[idx]; > + > + ste_addr_real =3D qsmmu_apply_space_offs(build_space, ste_addr); > + /* Clear STE (8 words) */ > + for (i =3D 0; i < 8; i++) { > + qtest_writel(ctx->qts, ste_addr_real + i * 4, 0); > + } > + > + cd_addr_real =3D qsmmu_apply_space_offs(build_space, QSMMU_CD_GP= A); > + /* Clear CD (8 words) */ > + for (i =3D 0; i < 8; i++) { > + qtest_writel(ctx->qts, cd_addr_real + i * 4, 0); > + g_assert_cmpint(qtest_readl(ctx->qts, cd_addr_real + i * 4),= =3D=3D, 0); > + } > + } > + > + /* Invalidate SMMU caches via configuration invalidation commands */ > + if (ctx->smmu_base) { > + /* Issue cache invalidation commands to SMMU */ > + qsmmu_push_cfgi_cmd(ctx->qts, ctx->smmu_base, QSMMU_SPACE_NONSEC= URE, > + QSMMU_CMD_CFGI_STE, sid, false); > + qsmmu_push_cfgi_cmd(ctx->qts, ctx->smmu_base, QSMMU_SPACE_NONSEC= URE, > + QSMMU_CMD_CFGI_CD, sid, false); > + qsmmu_push_cfgi_cmd(ctx->qts, ctx->smmu_base, QSMMU_SPACE_NONSEC= URE, > + QSMMU_CMD_TLBI_NSNH_ALL, sid, false); > + } > +} > + > +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; > + case QSMMU_SEC_SID_SECURE: > + return QSMMU_SPACE_SECURE; > + case QSMMU_SEC_SID_REALM: > + return QSMMU_SPACE_REALM; > + case QSMMU_SEC_SID_ROOT: > + return QSMMU_SPACE_ROOT; > + 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(); > + } > +} > + > +void qsmmu_single_translation(QSMMUTestContext *ctx) > +{ > + uint32_t config_result; > + uint32_t dma_result; > + bool test_passed; > + > + /* Configure SMMU translation */ > + config_result =3D qsmmu_setup_and_enable_translation(ctx); > + if (config_result !=3D 0) { > + g_test_message("Configuration failed: mode=3D%u status=3D0x%x", > + ctx->config.trans_mode, config_result); > + return; > + } > + > + /* Trigger DMA operation */ > + dma_result =3D qsmmu_trigger_dma(ctx); > + 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); > + } > + > + /* Validate test result */ > + test_passed =3D qsmmu_validate_test_result(ctx); > + g_assert_true(test_passed); > + > + /* Clean up translation state to prepare for the next test */ > + qsmmu_cleanup_translation(ctx); > +} > + > +void qsmmu_translation_batch(const QSMMUTestConfig *configs, > + size_t count, > + QTestState *qts, > + QPCIDevice *dev, > + QPCIBar bar, > + uint64_t smmu_base) > +{ > + int i; > + /* Initialize test memory */ > + for (i =3D 0; i < count; i++) { > + qtest_memset(qts, configs[i].dma_iova, 0x00, configs[i].dma_len); > + } > + /* Execute each test configuration */ > + for (i =3D 0; i < count; i++) { > + QSMMUTestContext ctx =3D { > + .qts =3D qts, > + .dev =3D dev, > + .bar =3D bar, > + .smmu_base =3D smmu_base, > + .config =3D configs[i], > + .trans_status =3D 0, > + .dma_result =3D 0, > + .sid =3D dev->devfn, > + .tx_space =3D qsmmu_sec_sid_to_space(configs[i].sec_sid), > + }; > + > + qsmmu_single_translation(&ctx); > + g_test_message("--> Test %d completed: mode=3D%u sec_sid=3D%u " > + "status=3D0x%x result=3D0x%x", i, configs[i].tran= s_mode, > + configs[i].sec_sid, ctx.trans_status, ctx.dma_res= ult); > + } > +} > + > +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; > + STEImg ste; > + CDImg cd; > + int i; > + > + build_space =3D tx_space; > + /* Only Non-Secure space is supported */ > + if (build_space !=3D QSMMU_SPACE_NONSECURE) { > + return 0xdeadbeafu; > + } > + > + /* Build STE image */ > + memset(&ste, 0, sizeof(ste)); > + switch (mode) { > + case QSMMU_TM_S1_ONLY: > + QSMMU_STE_SET_CONFIG(&ste, 0x5); > + break; > + case QSMMU_TM_S2_ONLY: > + QSMMU_STE_SET_CONFIG(&ste, 0x6); > + break; > + case QSMMU_TM_NESTED: > + default: > + QSMMU_STE_SET_CONFIG(&ste, 0x7); > + break; > + } > + > + QSMMU_STE_SET_VALID(&ste, 1); > + QSMMU_STE_SET_S2T0SZ(&ste, QSMMU_STE_S2T0SZ_VAL); > + QSMMU_STE_SET_S2SL0(&ste, 0x2); > + QSMMU_STE_SET_S2TG(&ste, 0); > + QSMMU_STE_SET_S2PS(&ste, 0x5); > + QSMMU_STE_SET_S2AA64(&ste, 1); > + QSMMU_STE_SET_S2ENDI(&ste, 0); > + QSMMU_STE_SET_S2AFFD(&ste, 0); > + > + /* > + * The consistent policy also extends to pointer fetches. For cases = that > + * require reading STE.S1ContextPtr or STE.S2TTB, we still follow th= e 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); > + QSMMU_STE_SET_CTXPTR(&ste, cd_addr_real); > + > + vttb =3D QSMMU_VTTB; > + vttb_real =3D qsmmu_apply_space_offs(build_space, vttb); > + QSMMU_STE_SET_S2TTB(&ste, vttb_real); > + > + ste_addr =3D sid * QSMMU_STE_OR_CD_ENTRY_BYTES + QSMMU_STR_TAB_BASE; > + ste_addr_real =3D qsmmu_apply_space_offs(build_space, ste_addr); > + > + /* Write STE to memory */ > + for (i =3D 0; i < 8; 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)); > + > + QSMMU_CD_SET_ASID(&cd, 0x1e20); > + QSMMU_CD_SET_AARCH64(&cd, 1); > + QSMMU_CD_SET_VALID(&cd, 1); > + QSMMU_CD_SET_A(&cd, 1); > + QSMMU_CD_SET_S(&cd, 0); > + QSMMU_CD_SET_HD(&cd, 0); > + QSMMU_CD_SET_HA(&cd, 0); > + QSMMU_CD_SET_IPS(&cd, 0x4); > + QSMMU_CD_SET_TBI(&cd, 0x0); > + QSMMU_CD_SET_AFFD(&cd, 0x0); > + QSMMU_CD_SET_EPD(&cd, 0, 0x0); > + QSMMU_CD_SET_EPD(&cd, 1, 0x1); > + QSMMU_CD_SET_TSZ(&cd, 0, 0x10); > + QSMMU_CD_SET_TG(&cd, 0, 0x0); > + QSMMU_CD_SET_ENDI(&cd, 0x0); > + > + QSMMU_CD_SET_NSCFG0(&cd, nscfg0); > + QSMMU_CD_SET_NSCFG1(&cd, nscfg1); > + QSMMU_CD_SET_R(&cd, 0x1); > + cd_ttb =3D vttb_real; > + QSMMU_CD_SET_TTB(&cd, 0, cd_ttb); > + > + for (i =3D 0; i < 8; i++) { > + /* TODO: Maybe need more work to write to secure RAM in futu= re */ > + 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_OR_IPA, 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 th= e 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 + QSMMU_REG_GBPA, 0x80000000); /* UPDAT= E */ > + qtest_writel(qts, bank_base + QSMMU_REG_CR0, 0x0); /* Disab= le */ > + qtest_writel(qts, bank_base + QSMMU_REG_CR1, 0x0d75); /* Confi= g */ > + > + /* 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 + QSMMU_REG_CMDQ_BASE, cmdq_base); > + > + qtest_writel(qts, bank_base + QSMMU_REG_CMDQ_CONS, 0x0); > + qtest_writel(qts, bank_base + QSMMU_REG_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 + QSMMU_REG_EVENTQ_BASE, eventq_base); > + > + qtest_writel(qts, bank_base + QSMMU_REG_EVENTQ_PROD, 0x0); > + qtest_writel(qts, bank_base + QSMMU_REG_EVENTQ_CONS, 0x0); > + > + /* STRTAB_BASE_CFG: linear stream table, LOG2SIZE=3D5 */ > + qtest_writel(qts, bank_base + QSMMU_REG_STRTAB_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 + QSMMU_REG_STRTAB_BASE, strtab_base); > + > + /* CR0: Enable SMMU with appropriate flags */ > + qtest_writel(qts, bank_base + QSMMU_REG_CR0, 0xd); > +} > + > +void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace = space) > +{ > + uint64_t sp_base; > + /* Always program Non-Secure bank first */ > + uint64_t ns_base =3D qsmmu_bank_base(smmu_base, QSMMU_SPACE_NONSECUR= E); > + 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 = iova) > +{ > + 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 translat= ion 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= IPA. > + * 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 di= rectly > + * - 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 w= alk > + * > + * @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 nes= ted > + * 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/trans= action, > + * whether it's a static Secure state or a dynamically-switched Realm st= ate. > + * SEC_SID has been converted to the corresponding SEcurity Space (QSMMU= Space) > + * before calling this function. > + * > + * In a real SMMU translation, this input security state does not unilat= erally > + * determine the output Physical Address (PA) space. The output PA space= is > + * ultimately determined by attributes encountered during the page table= walk, > + * such as NSCFG and NSTable. > + * > + * However, for the specific context of testing the SMMU with the iommu-= testdev, > + * and to simplify the future support for Secure and Realm states, we ad= opt a > + * consistent policy: > + * > + * - We always ensure that the page table attributes (e.g., nscfg, nstab= le) > + * *match* the input 'SEC_SID' of the test case. > + * > + * For example: If 'SEC_SID' is Non-Secure, the corresponding nscfg and = nstable > + * 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%lx mode=3D%d is_bui= lding_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_MAS= K); > + 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, = mode); > + > + /* Stage 1 Level 1 */ > + l1_addr =3D qsmmu_get_table_addr(all_s2_l0_pte_val & QSMMU_PTE_M= ASK, > + 1, iova); > + qsmmu_setup_s1_level_with_nested_s2(qts, space, 1, l1_addr, > + all_s2_l1_pte_val, s2_vttb, = mode); > + > + /* Stage 1 Level 2 */ > + l2_addr =3D qsmmu_get_table_addr(all_s2_l1_pte_val & QSMMU_PTE_M= ASK, > + 2, iova); > + qsmmu_setup_s1_level_with_nested_s2(qts, space, 2, l2_addr, > + all_s2_l2_pte_val, s2_vttb, = mode); > + > + /* Stage 1 Level 3 (leaf) */ > + l3_addr =3D qsmmu_get_table_addr(all_s2_l2_pte_val & QSMMU_PTE_M= ASK, > + 3, iova); > + > + s1_leaf_pte_val =3D qsmmu_apply_space_offs( > + space, QSMMU_L3_PTE_VAL | qsmmu_get_pte_attrs(mode, true, sp= ace) > + ); > + > + qsmmu_setup_s1_level_with_nested_s2(qts, space, 3, l3_addr, > + s1_leaf_pte_val, s2_vttb, mo= de); > + } 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 t= esting > + * - 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= _final > + */ > + 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/qos-smmuv3.h b/tests/qtest/libqos/qos-smm= uv3.h > new file mode 100644 > index 0000000000..366da774eb > --- /dev/null > +++ b/tests/qtest/libqos/qos-smmuv3.h > @@ -0,0 +1,291 @@ > +/* > + * QOS SMMUv3 Module > + * > + * This module provides SMMUv3-specific helper functions for libqos test= s, > + * encapsulating SMMUv3 setup, assertion, and cleanup operations. > + * > + * Copyright (c) 2025 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" > + > +#define VIRT_SMMU_BASE 0x0000000009050000ull > + > +/* SMMU command type */ > +#define QSMMU_CMD_CFGI_STE 0x03 > +#define QSMMU_CMD_CFGI_CD 0x05 > +#define QSMMU_CMD_TLBI_NSNH_ALL 0x30 > + > +/* SMMU register offsets */ > +#define QSMMU_REG_GBPA 0x0044 > +#define QSMMU_REG_CR0 0x0020 > +#define QSMMU_REG_CR1 0x0028 > +#define QSMMU_REG_CMDQ_BASE 0x0090 > +#define QSMMU_REG_CMDQ_CONS 0x009c > +#define QSMMU_REG_CMDQ_PROD 0x0098 > +#define QSMMU_REG_EVENTQ_BASE 0x00a0 > +#define QSMMU_REG_EVENTQ_PROD 0x00a8 > +#define QSMMU_REG_EVENTQ_CONS 0x00ac > +#define QSMMU_REG_STRTAB_CFG 0x0088 > +#define QSMMU_REG_STRTAB_BASE 0x0080 > + > +#define QSMMU_BASE_ADDR_MASK 0xfffffffffffc0 > + > +/* 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) > + * - 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 addresse= s, etc.) > + * appear to be within a secure RAM region. In practice, an offset is ad= ded > + * to these values to place them in non-secure RAM. For example, when ru= nning > + * in a virt machine type, the RAM base address (e.g., 0x40000000) is ad= ded to > + * these constants. > + */ > +#define QSMMU_IOVA_OR_IPA 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 > + * relative layout stays identical across spaces. > + */ > +#define QSMMU_SPACE_OFFS_NS 0x0000000040000000ull > + > +typedef enum QSMMUSecSID { > + QSMMU_SEC_SID_NONSECURE =3D 0, > + QSMMU_SEC_SID_SECURE =3D 1, > + QSMMU_SEC_SID_REALM =3D 2, > + QSMMU_SEC_SID_ROOT =3D 3, > +} QSMMUSecSID; > + > +typedef enum QSMMUSpace { > + QSMMU_SPACE_SECURE =3D 0, > + QSMMU_SPACE_NONSECURE =3D 1, > + QSMMU_SPACE_ROOT =3D 2, > + QSMMU_SPACE_REALM =3D 3, > +} 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, Neste= d) */ > + QSMMUSecSID sec_sid; /* SEC_SID of test device */ > + uint64_t dma_iova; /* DMA IOVA address for testing */ > + uint32_t dma_len; /* DMA length for testing */ > + uint32_t expected_result; /* Expected DMA result for validat= ion */ > +} 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 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. Triggers configuration request via ITD_REG_TRANS_DBELL > + * 2. Builds all required SMMU structures (STE, CD, page tables) > + * 3. Programs SMMU registers for the appropriate security space > + * 4. Reads back and 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: > + * - 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 cal= led in > + * qsmmu_setup_and_enable_translation. Always programs Non-Secure bank f= irst, > + * then the target space if different. > + */ > +void qsmmu_program_regs(QTestState *qts, uint64_t smmu_base, QSMMUSpace = space); > + > +uint32_t qsmmu_trigger_dma(QSMMUTestContext *ctx); > + > +/* > + * qsmmu_cleanup_translation - Clean up translation configuration > + * > + * @ctx: Test context containing configuration and device handles > + * > + * Clears all translation structures and invalidates SMMU caches: > + * - Clears STE and CD entries > + * - Issues SMMU invalidation commands (CFGI_STE, CFGI_CD, TLBI_NSNH_ALL) > + */ > +void qsmmu_cleanup_translation(QSMMUTestContext *ctx); > + > +/* qsmmu_expected_dma_result - Calculate expected DMA result */ > +uint32_t qsmmu_expected_dma_result(QSMMUTestContext *ctx); > + > +/* > + * qsmmu_validate_test_result - Validate actual VS expected test result > + * > + * @ctx: Test context containing actual and expected results > + * > + * Returns: true if test passed (actual =3D=3D expected), false otherwise > + * > + * Compares the actual DMA result with the expected result and logs > + * the comparison for debugging purposes. > + */ > +bool qsmmu_validate_test_result(QSMMUTestContext *ctx); > + > +/* > + * qsmmu_setup_translation_tables - Setup complete SMMU page table hiera= rchy > + * > + * @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 functions */ > + > +void qsmmu_single_translation(QSMMUTestContext *ctx); > +void qsmmu_translation_batch(const QSMMUTestConfig *configs, size_t coun= t, > + QTestState *qts, QPCIDevice *dev, > + QPCIBar bar, uint64_t smmu_base); > + > +#endif /* QTEST_LIBQOS_SMMUV3_H */ --=20 Alex Benn=C3=A9e Virtualisation Tech Lead @ Linaro