Add the first qtest coverage for the virt Debug Module model. The new riscv-dm-test binary checks dmactive gating, dmstatus, hartinfo, and abstractcs reset values, then drives a basic halt/resume cycle by writing the ROM mailbox words used by the CPU park loop. Register the test for both riscv32 and riscv64 when CONFIG_RISCV_DM is enabled.
Signed-off-by: Chao Liu <[email protected]> --- MAINTAINERS | 1 + tests/qtest/meson.build | 7 +- tests/qtest/riscv-dm-test.c | 208 ++++++++++++++++++++++++++++++++++++ 3 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 tests/qtest/riscv-dm-test.c diff --git a/MAINTAINERS b/MAINTAINERS index d8f326c8b2..faaa7114ec 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -361,6 +361,7 @@ F: tests/functional/riscv32 F: tests/functional/riscv64 F: tests/tcg/riscv64/ F: tests/qtest/iommu-riscv-test.c +F: tests/qtest/riscv-dm-test.c RISC-V XThead* extensions M: Christoph Muellner <[email protected]> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index ba9f59d2f8..3bb9e3cc16 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -282,13 +282,16 @@ qtests_s390x = \ 'migration-test'] qtests_riscv32 = \ - (config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : []) + (config_all_devices.has_key('CONFIG_SIFIVE_E_AON') ? ['sifive-e-aon-watchdog-test'] : []) + \ + (config_all_devices.has_key('CONFIG_RISCV_DM') ? ['riscv-dm-test'] : []) qtests_riscv64 = ['riscv-csr-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'] : []) + ['iommu-riscv-test'] : []) + \ + (config_all_devices.has_key('CONFIG_RISCV_DM') ? + ['riscv-dm-test'] : []) qos_test_ss = ss.source_set() qos_test_ss.add( diff --git a/tests/qtest/riscv-dm-test.c b/tests/qtest/riscv-dm-test.c new file mode 100644 index 0000000000..c9e3c22a20 --- /dev/null +++ b/tests/qtest/riscv-dm-test.c @@ -0,0 +1,208 @@ +/* + * QTest for RISC-V Debug Module v1.0 + * + * Copyright (c) 2025 Chao Liu <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" + +#define DM_BASE 0x0 + +#define ROM_HARTID 0x104 +#define ROM_RESUME 0x10c + +#define A_DATA0 0x10 +#define A_DMCONTROL 0x40 +#define A_DMSTATUS 0x44 +#define A_HARTINFO 0x48 +#define A_ABSTRACTCS 0x58 + +#define DMCONTROL_DMACTIVE (1u << 0) +#define DMCONTROL_HALTREQ (1u << 31) +#define DMCONTROL_RESUMEREQ (1u << 30) + +#define DMSTATUS_VERSION_MASK 0xf +#define DMSTATUS_AUTHENTICATED (1u << 7) +#define DMSTATUS_ANYHALTED (1u << 8) +#define DMSTATUS_ALLHALTED (1u << 9) +#define DMSTATUS_ANYRUNNING (1u << 10) +#define DMSTATUS_ALLRUNNING (1u << 11) +#define DMSTATUS_ANYRESUMEACK (1u << 16) +#define DMSTATUS_ALLRESUMEACK (1u << 17) +#define DMSTATUS_ANYHAVERESET (1u << 18) +#define DMSTATUS_IMPEBREAK (1u << 22) + +#define HARTINFO_DATAADDR_MASK 0xfffu +#define HARTINFO_DATASIZE_SHIFT 12 +#define HARTINFO_DATASIZE_MASK (0xfu << HARTINFO_DATASIZE_SHIFT) +#define HARTINFO_DATAACCESS (1u << 16) +#define HARTINFO_NSCRATCH_SHIFT 20 +#define HARTINFO_NSCRATCH_MASK (0xfu << HARTINFO_NSCRATCH_SHIFT) + +#define ABSTRACTCS_DATACOUNT_MASK 0xf +#define ABSTRACTCS_BUSY (1u << 12) +#define ABSTRACTCS_PROGBUFSIZE_SHIFT 24 +#define ABSTRACTCS_PROGBUFSIZE_MASK (0x1fu << ABSTRACTCS_PROGBUFSIZE_SHIFT) + +static uint32_t dm_read(QTestState *qts, uint32_t reg) +{ + return qtest_readl(qts, DM_BASE + reg); +} + +static void dm_write(QTestState *qts, uint32_t reg, uint32_t val) +{ + qtest_writel(qts, DM_BASE + reg, val); +} + +static void dm_set_active(QTestState *qts) +{ + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE); +} + +static void rom_write32(QTestState *qts, uint32_t offset, uint32_t val) +{ + qtest_writel(qts, DM_BASE + offset, val); +} + +static void sim_cpu_halt_ack(QTestState *qts, uint32_t hartid) +{ + rom_write32(qts, ROM_HARTID, hartid); +} + +static void sim_cpu_resume_ack(QTestState *qts, uint32_t hartid) +{ + rom_write32(qts, ROM_RESUME, hartid); +} + +static void test_dmactive_gate(void) +{ + QTestState *qts = qtest_init("-machine virt"); + + g_assert_cmpuint(dm_read(qts, A_DMCONTROL), ==, 0); + g_assert_cmpuint(dm_read(qts, A_DMSTATUS), ==, 0); + g_assert_cmpuint(dm_read(qts, A_HARTINFO), ==, 0); + g_assert_cmpuint(dm_read(qts, A_ABSTRACTCS), ==, 0); + g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0); + + dm_write(qts, A_DATA0, 0xdeadbeef); + g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0); + + dm_set_active(qts); + g_assert_cmpuint(dm_read(qts, A_DMCONTROL) & DMCONTROL_DMACTIVE, ==, + DMCONTROL_DMACTIVE); + g_assert_cmpuint(dm_read(qts, A_DMSTATUS), !=, 0); + + qtest_quit(qts); +} + +static void test_dmstatus(void) +{ + QTestState *qts = qtest_init("-machine virt"); + uint32_t status; + + dm_set_active(qts); + status = dm_read(qts, A_DMSTATUS); + + g_assert_cmpuint(status & DMSTATUS_VERSION_MASK, ==, 3); + g_assert_cmpuint(status & DMSTATUS_AUTHENTICATED, ==, + DMSTATUS_AUTHENTICATED); + g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, + DMSTATUS_ANYRUNNING); + g_assert_cmpuint(status & DMSTATUS_ALLRUNNING, ==, + DMSTATUS_ALLRUNNING); + g_assert_cmpuint(status & DMSTATUS_ANYHALTED, ==, 0); + g_assert_cmpuint(status & DMSTATUS_ANYHAVERESET, ==, + DMSTATUS_ANYHAVERESET); + g_assert_cmpuint(status & DMSTATUS_IMPEBREAK, ==, DMSTATUS_IMPEBREAK); + + qtest_quit(qts); +} + +static void test_hartinfo(void) +{ + QTestState *qts = qtest_init("-machine virt"); + uint32_t info; + + dm_set_active(qts); + info = dm_read(qts, A_HARTINFO); + + g_assert_cmpuint(info & HARTINFO_DATAADDR_MASK, ==, 0x3c0); + g_assert_cmpuint(info & HARTINFO_DATAACCESS, ==, HARTINFO_DATAACCESS); + g_assert_cmpuint((info & HARTINFO_DATASIZE_MASK) >> + HARTINFO_DATASIZE_SHIFT, ==, 2); + g_assert_cmpuint((info & HARTINFO_NSCRATCH_MASK) >> + HARTINFO_NSCRATCH_SHIFT, ==, 1); + + qtest_quit(qts); +} + +static void test_abstractcs_config(void) +{ + QTestState *qts = qtest_init("-machine virt"); + uint32_t acs; + + dm_set_active(qts); + acs = dm_read(qts, A_ABSTRACTCS); + + g_assert_cmpuint(acs & ABSTRACTCS_DATACOUNT_MASK, ==, 2); + g_assert_cmpuint((acs & ABSTRACTCS_PROGBUFSIZE_MASK) >> + ABSTRACTCS_PROGBUFSIZE_SHIFT, ==, 8); + g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0); + + qtest_quit(qts); +} + +static void test_halt_resume(void) +{ + QTestState *qts = qtest_init("-machine virt"); + uint32_t status; + + dm_set_active(qts); + status = dm_read(qts, A_DMSTATUS); + g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, + DMSTATUS_ANYRUNNING); + g_assert_cmpuint(status & DMSTATUS_ANYHALTED, ==, 0); + + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ); + sim_cpu_halt_ack(qts, 0); + + status = dm_read(qts, A_DMSTATUS); + g_assert_cmpuint(status & DMSTATUS_ANYHALTED, ==, + DMSTATUS_ANYHALTED); + g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, + DMSTATUS_ALLHALTED); + g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, 0); + g_assert_cmpuint(status & DMSTATUS_ALLRUNNING, ==, 0); + + dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_RESUMEREQ); + sim_cpu_resume_ack(qts, 0); + + status = dm_read(qts, A_DMSTATUS); + g_assert_cmpuint(status & DMSTATUS_ANYRESUMEACK, ==, + DMSTATUS_ANYRESUMEACK); + g_assert_cmpuint(status & DMSTATUS_ALLRESUMEACK, ==, + DMSTATUS_ALLRESUMEACK); + g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, + DMSTATUS_ANYRUNNING); + g_assert_cmpuint(status & DMSTATUS_ALLRUNNING, ==, + DMSTATUS_ALLRUNNING); + g_assert_cmpuint(status & DMSTATUS_ANYHALTED, ==, 0); + + qtest_quit(qts); +} + +int main(int argc, char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/riscv-dm/dmactive-gate", test_dmactive_gate); + qtest_add_func("/riscv-dm/dmstatus", test_dmstatus); + qtest_add_func("/riscv-dm/hartinfo", test_hartinfo); + qtest_add_func("/riscv-dm/abstractcs-config", test_abstractcs_config); + qtest_add_func("/riscv-dm/halt-resume", test_halt_resume); + + return g_test_run(); +} -- 2.53.0
