Add a q35-only test path for x86 secure attrs by introducing an optional test-only RAM region that is mapped only into the SMM address space.
The new qtest-x86-attrs-test enables this region with `-global mch.x-smm-test-ram=on` and verifies that accesses with the `secure` attribute reach the SMM-only region, while default accesses do not. This provides the x86 cross-verification that qtest-attrs-test does not cover, where normal RAM is visible from both the default and SMM address spaces. This is a NOT-MERGE commit. Signed-off-by: Tao Tang <[email protected]> --- hw/pci-host/q35.c | 27 +++++ include/hw/pci-host/q35.h | 8 ++ tests/qtest/meson.build | 1 + tests/qtest/qtest-x86-attrs-test.c | 170 +++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 tests/qtest/qtest-x86-attrs-test.c diff --git a/hw/pci-host/q35.c b/hw/pci-host/q35.c index f4556ad03a0..dfdfcde7cd1 100644 --- a/hw/pci-host/q35.c +++ b/hw/pci-host/q35.c @@ -635,12 +635,19 @@ static void mch_realize(PCIDevice *d, Error **errp) int i; MCHPCIState *mch = MCH_PCI_DEVICE(d); + ERRP_GUARD(); + if (mch->ext_tseg_mbytes > MCH_HOST_BRIDGE_EXT_TSEG_MBYTES_MAX) { error_setg(errp, "invalid extended-tseg-mbytes value: %" PRIu16, mch->ext_tseg_mbytes); return; } + if (mch->enable_smm_test_ram && !mch->has_smm_ranges) { + error_setg(errp, "x-smm-test-ram requires SMM support"); + return; + } + /* setup pci memory mapping */ pc_pci_as_mapping_init(mch->system_memory, mch->pci_address_space); @@ -671,6 +678,24 @@ static void mch_realize(PCIDevice *d, Error **errp) if (mch->has_smm_ranges) { mch_init_smram_regions(mch); + + if (mch->enable_smm_test_ram) { + /* + * This is a QEMU-specific, test-only region. It is mapped only + * into mch->smram so qtest can verify that x86 secure attrs + * select the SMM address space rather than the default one. + */ + memory_region_init_ram(&mch->smm_test_ram, OBJECT(mch), + "smm-test-ram", + MCH_HOST_BRIDGE_SMM_TEST_RAM_SIZE, errp); + if (*errp) { + return; + } + memory_region_add_subregion(&mch->smram, + MCH_HOST_BRIDGE_SMM_TEST_RAM_BASE, + &mch->smm_test_ram); + } + object_property_add_const_link(qdev_get_machine(), "smram", OBJECT(&mch->smram)); } @@ -680,6 +705,8 @@ static const Property mch_props[] = { DEFINE_PROP_UINT16("extended-tseg-mbytes", MCHPCIState, ext_tseg_mbytes, 64), DEFINE_PROP_BOOL("smbase-smram", MCHPCIState, has_smram_at_smbase, true), + DEFINE_PROP_BOOL("x-smm-test-ram", MCHPCIState, enable_smm_test_ram, + false), }; static void mch_class_init(ObjectClass *klass, const void *data) diff --git a/include/hw/pci-host/q35.h b/include/hw/pci-host/q35.h index f31a71010b6..5ba7541d321 100644 --- a/include/hw/pci-host/q35.h +++ b/include/hw/pci-host/q35.h @@ -49,8 +49,10 @@ struct MCHPCIState { MemoryRegion smram, low_smram, high_smram; MemoryRegion tseg_blackhole, tseg_window; MemoryRegion smbase_blackhole, smbase_window; + MemoryRegion smm_test_ram; bool has_smram_at_smbase; bool has_smm_ranges; + bool enable_smm_test_ram; Range pci_hole; uint64_t below_4g_mem_size; uint64_t above_4g_mem_size; @@ -99,6 +101,12 @@ struct Q35PCIHost { #define MCH_HOST_BRIDGE_PCIEXBAR_SIZE 8 /* 64bit register */ #define MCH_HOST_BRIDGE_PCIEXBAR_DEFAULT 0xb0000000 #define MCH_HOST_BRIDGE_PCIEXBAR_MAX (0x10000000) /* 256M */ +/* + * Optional qtest-only RAM window used to expose an address that exists only + * in the SMM address space, so x86 secure attrs can be cross-checked. + */ +#define MCH_HOST_BRIDGE_SMM_TEST_RAM_BASE 0xfef00000 +#define MCH_HOST_BRIDGE_SMM_TEST_RAM_SIZE (64 * KiB) #define MCH_HOST_BRIDGE_PCIEXBAR_ADMSK Q35_MASK(64, 35, 28) #define MCH_HOST_BRIDGE_PCIEXBAR_128ADMSK ((uint64_t)(1 << 27)) #define MCH_HOST_BRIDGE_PCIEXBAR_64ADMSK ((uint64_t)(1 << 26)) diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index fd59050fd27..b8eaa1180fa 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -121,6 +121,7 @@ qtests_i386 = \ 'cpu-plug-test', 'migration-test', 'qtest-attrs-test', + 'qtest-x86-attrs-test', ] if dbus_display and config_all_devices.has_key('CONFIG_VGA') diff --git a/tests/qtest/qtest-x86-attrs-test.c b/tests/qtest/qtest-x86-attrs-test.c new file mode 100644 index 00000000000..068ee8b7d37 --- /dev/null +++ b/tests/qtest/qtest-x86-attrs-test.c @@ -0,0 +1,170 @@ +/* + * QTest for x86 memory access with transaction attributes + * + * Verify q35 SMM address-space access with the secure attribute. + * + * Copyright (c) 2026 Phytium Technology + * + * Author: + * Tao Tang <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/cutils.h" +#include "libqtest.h" + +#define TEST_ADDR_OFFSET_NS 0x1000ULL +#define TEST_X86_BASE 0x0ULL +#define TEST_X86_SMM_BASE 0xfef00000ULL + +#define TEST_ADDR_X86 (TEST_X86_BASE + TEST_ADDR_OFFSET_NS) + +#define X86_MACHINE_ARGS "-machine q35,smm=on -m 1G -accel tcg " \ + "-global mch.x-smm-test-ram=on" + +static void assert_default_scalar_read_isolated(QTestState *qts, uint64_t addr, + char **before, + uint8_t secure_value) +{ + g_auto(GStrv) after = NULL; + uint64_t value; + int ret; + + after = qtest_raw_cmd(qts, "readb 0x%" PRIx64 "\n", addr); + + if (g_strcmp0(before[0], "ERR") == 0) { + g_assert_cmpstr(after[0], ==, "ERR"); + return; + } + + g_assert_cmpstr(before[0], ==, "OK"); + g_assert_nonnull(before[1]); + g_assert_cmpstr(after[0], ==, "OK"); + g_assert_nonnull(after[1]); + g_assert_cmpstr(after[1], ==, before[1]); + + ret = qemu_strtou64(after[1], NULL, 0, &value); + g_assert_cmpint(ret, ==, 0); + g_assert_cmpuint(value, !=, secure_value); +} + +static void assert_default_bulk_read_isolated(QTestState *qts, uint64_t addr, + char **before, + const uint8_t *expected, + size_t len) +{ + g_auto(GStrv) after = NULL; + g_autofree gchar *expected_b64 = NULL; + + expected_b64 = g_base64_encode(expected, len); + after = qtest_raw_cmd(qts, "b64read 0x%" PRIx64 " 0x%zx\n", addr, len); + + if (g_strcmp0(before[0], "ERR") == 0) { + g_assert_cmpstr(after[0], ==, "ERR"); + return; + } + + g_assert_cmpstr(before[0], ==, "OK"); + g_assert_nonnull(before[1]); + g_assert_cmpstr(after[0], ==, "OK"); + g_assert_nonnull(after[1]); + g_assert_cmpstr(after[1], ==, before[1]); + g_assert_cmpstr(after[1], !=, expected_b64); +} + +static void test_x86_scalar_attrs(void) +{ + QTestState *qts; + g_auto(GStrv) before = NULL; + uint8_t val; + + if (!qtest_has_machine("q35")) { + g_test_skip("q35 machine not available"); + return; + } + + qts = qtest_init(X86_MACHINE_ARGS); + + qtest_writeb_attrs(qts, TEST_ADDR_X86, 0x11, NULL); + val = qtest_readb_attrs(qts, TEST_ADDR_X86, NULL); + g_assert_cmpuint(val, ==, 0x11); + + qtest_writeb_attrs(qts, TEST_ADDR_X86 + 0x1, 0x22, "secure"); + val = qtest_readb_attrs(qts, TEST_ADDR_X86 + 0x1, "secure"); + g_assert_cmpuint(val, ==, 0x22); + + before = qtest_raw_cmd(qts, "readb 0x%" PRIx64 "\n", + (uint64_t)(TEST_X86_SMM_BASE + 0x2)); + qtest_writeb_attrs(qts, TEST_X86_SMM_BASE + 0x2, 0x33, "secure"); + val = qtest_readb_attrs(qts, TEST_X86_SMM_BASE + 0x2, "secure"); + g_assert_cmpuint(val, ==, 0x33); + assert_default_scalar_read_isolated(qts, TEST_X86_SMM_BASE + 0x2, + before, 0x33); + + qtest_quit(qts); +} + +static void test_x86_bulk_attrs(void) +{ + QTestState *qts; + g_auto(GStrv) before = NULL; + uint8_t wbuf[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + uint8_t rbuf[8]; + size_t i; + + if (!qtest_has_machine("q35")) { + g_test_skip("q35 machine not available"); + return; + } + + qts = qtest_init(X86_MACHINE_ARGS); + + qtest_memwrite_attrs(qts, TEST_ADDR_X86 + 0x100, wbuf, sizeof(wbuf), NULL); + qtest_memread_attrs(qts, TEST_ADDR_X86 + 0x100, rbuf, sizeof(rbuf), NULL); + g_assert(memcmp(wbuf, rbuf, sizeof(wbuf)) == 0); + + qtest_memwrite_attrs(qts, TEST_ADDR_X86 + 0x180, + wbuf, sizeof(wbuf), "secure"); + qtest_memread_attrs(qts, TEST_ADDR_X86 + 0x180, + rbuf, sizeof(rbuf), "secure"); + g_assert(memcmp(wbuf, rbuf, sizeof(wbuf)) == 0); + + before = qtest_raw_cmd(qts, "b64read 0x%" PRIx64 " 0x%zx\n", + (uint64_t)(TEST_X86_SMM_BASE + 0x100), + sizeof(wbuf)); + qtest_memwrite_attrs(qts, TEST_X86_SMM_BASE + 0x100, + wbuf, sizeof(wbuf), "secure"); + qtest_memread_attrs(qts, TEST_X86_SMM_BASE + 0x100, + rbuf, sizeof(rbuf), "secure"); + g_assert(memcmp(wbuf, rbuf, sizeof(wbuf)) == 0); + assert_default_bulk_read_isolated(qts, TEST_X86_SMM_BASE + 0x100, before, + wbuf, sizeof(wbuf)); + + qtest_memset_attrs(qts, TEST_X86_SMM_BASE + 0x120, + 0x5a, sizeof(rbuf), "secure"); + qtest_memread_attrs(qts, TEST_X86_SMM_BASE + 0x120, + rbuf, sizeof(rbuf), "secure"); + for (i = 0; i < sizeof(rbuf); i++) { + g_assert_cmpuint(rbuf[i], ==, 0x5a); + } + + qtest_bufwrite_attrs(qts, TEST_X86_SMM_BASE + 0x200, + wbuf, sizeof(wbuf), "secure"); + qtest_bufread_attrs(qts, TEST_X86_SMM_BASE + 0x200, + rbuf, sizeof(rbuf), "secure"); + g_assert(memcmp(wbuf, rbuf, sizeof(wbuf)) == 0); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/qtest/x86/attrs/scalar", test_x86_scalar_attrs); + qtest_add_func("/qtest/x86/attrs/bulk", test_x86_bulk_attrs); + + return g_test_run(); +} -- 2.34.1
