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


Reply via email to