Add tests for the DMI register interface exercised in qtest mode
(no real CPU execution):

  data-rw, progbuf-rw         – DATA/PROGBUF read-write
  ro-registers                – read-only register enforcement
  abstractcs-w1c              – write-1-to-clear cmderr field
  hart-selection              – hartsel/hasel routing
  dmcontrol-warz              – write-any-read-zero fields
  hartreset                   – per-hart reset semantics
  ndmreset-gate               – ndmreset gating other operations
  optional-regs-absent        – unimplemented register reads
  deactivate                  – DM deactivation clears state
  ackhavereset                – havereset acknowledgment

Also expand the define/macro header to cover the full DMI
register map and rewrite the existing four basic tests to use
the new constants.

Signed-off-by: Chao Liu <[email protected]>
---
 tests/qtest/riscv-dm-test.c | 589 +++++++++++++++++++++++++++++++++---
 1 file changed, 539 insertions(+), 50 deletions(-)

diff --git a/tests/qtest/riscv-dm-test.c b/tests/qtest/riscv-dm-test.c
index c9e3c22a20..3a0dd1cbd4 100644
--- a/tests/qtest/riscv-dm-test.c
+++ b/tests/qtest/riscv-dm-test.c
@@ -9,43 +9,150 @@
 #include "qemu/osdep.h"
 #include "libqtest.h"
 
+/* Virt machine memory map base address for the Debug Module */
 #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
-
+/*
+ * ROM layout offsets (byte offsets within DM ROM).
+ * CPU ROM code writes to these addresses to signal state changes to the DM.
+ */
+#define ROM_HARTID      0x104
+#define ROM_GOING       0x108
+#define ROM_RESUME      0x10C
+#define ROM_EXCP        0x110
+#define ROM_DATA        0x3C0
+#define ROM_FLAGS       0x400
+#define ROM_ENTRY       0x800
+
+/* DMI register byte offsets (word address × 4) */
+#define A_DATA0         0x10
+#define A_DATA1         0x14
+#define A_DMCONTROL     0x40
+#define A_DMSTATUS      0x44
+#define A_HARTINFO      0x48
+#define A_HALTSUM1      0x4C
+#define A_HAWINDOWSEL   0x50
+#define A_HAWINDOW      0x54
+#define A_ABSTRACTCS    0x58
+#define A_COMMAND       0x5C
+#define A_ABSTRACTAUTO  0x60
+#define A_PROGBUF0      0x80
+#define A_PROGBUF1      0x84
+#define A_AUTHDATA      0xC0
+#define A_DMCS2         0xC8
+#define A_SBCS          0xE0
+#define A_SBADDRESS0    0xE4
+#define A_SBADDRESS1    0xE8
+#define A_SBADDRESS2    0xEC
+#define A_SBDATA0       0xF0
+#define A_SBDATA1       0xF4
+#define A_SBDATA2       0xF8
+#define A_SBDATA3       0xFC
+#define A_HALTSUM0      0x100
+
+/* DMCONTROL fields */
 #define DMCONTROL_DMACTIVE      (1u << 0)
+#define DMCONTROL_NDMRESET      (1u << 1)
+#define DMCONTROL_CLRRESETHALTREQ (1u << 2)
+#define DMCONTROL_SETRESETHALTREQ (1u << 3)
+#define DMCONTROL_HARTRESET     (1u << 29)
 #define DMCONTROL_HALTREQ       (1u << 31)
 #define DMCONTROL_RESUMEREQ     (1u << 30)
-
-#define DMSTATUS_VERSION_MASK   0xf
+#define DMCONTROL_ACKHAVERESET  (1u << 28)
+#define DMCONTROL_HASEL         (1u << 26)
+#define DMCONTROL_HARTSELHI_SHIFT   16
+#define DMCONTROL_HARTSELLO_SHIFT   6
+#define DMCONTROL_HARTSELLO_MASK    (0x3FFu << 6)
+
+/* DMSTATUS fields */
+#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_ANYUNAVAIL     (1u << 12)
+#define DMSTATUS_ALLUNAVAIL     (1u << 13)
+#define DMSTATUS_ANYNONEXISTENT (1u << 14)
+#define DMSTATUS_ALLNONEXISTENT (1u << 15)
 #define DMSTATUS_ANYRESUMEACK   (1u << 16)
 #define DMSTATUS_ALLRESUMEACK   (1u << 17)
 #define DMSTATUS_ANYHAVERESET   (1u << 18)
+#define DMSTATUS_ALLHAVERESET   (1u << 19)
 #define DMSTATUS_IMPEBREAK      (1u << 22)
+#define DMSTATUS_NDMRESETPENDING (1u << 24)
 
-#define HARTINFO_DATAADDR_MASK      0xfffu
+/* HARTINFO fields */
+#define HARTINFO_DATAADDR_MASK      0xFFFu
 #define HARTINFO_DATASIZE_SHIFT     12
-#define HARTINFO_DATASIZE_MASK      (0xfu << HARTINFO_DATASIZE_SHIFT)
+#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 HARTINFO_NSCRATCH_MASK      (0xFu << HARTINFO_NSCRATCH_SHIFT)
 
-#define ABSTRACTCS_DATACOUNT_MASK       0xf
+/* ABSTRACTCS fields */
+#define ABSTRACTCS_DATACOUNT_MASK       0xF
+#define ABSTRACTCS_CMDERR_MASK          (0x7u << 8)
+#define ABSTRACTCS_CMDERR_SHIFT         8
 #define ABSTRACTCS_BUSY                 (1u << 12)
+#define ABSTRACTCS_PROGBUFSIZE_MASK     (0x1Fu << 24)
 #define ABSTRACTCS_PROGBUFSIZE_SHIFT    24
-#define ABSTRACTCS_PROGBUFSIZE_MASK     (0x1fu << ABSTRACTCS_PROGBUFSIZE_SHIFT)
+
+/* SBCS fields */
+#define SBCS_SBERROR_MASK       (0x7u << 12)
+#define SBCS_SBBUSYERROR        (1u << 22)
+#define SBCS_SBVERSION_MASK     (0x7u << 29)
+#define SBCS_SBVERSION_SHIFT    29
+#define SBCS_SBASIZE_SHIFT      5
+#define SBCS_SBASIZE_MASK       (0x7Fu << SBCS_SBASIZE_SHIFT)
+#define SBCS_SBACCESS32         (1u << 2)
+
+/* Abstract command register number space */
+#define REGNO_GPR(n)    (0x1000 + (n))  /* x0..x31 */
+#define REGNO_FPR(n)    (0x1020 + (n))  /* f0..f31 */
+#define REGNO_CSR(n)    (n)             /* CSR direct */
+#define REGNO_DPC       0x7b1
+#define REGNO_DCSR      0x7b0
+
+/* DCSR fields */
+#define DCSR_STEP           (1u << 2)
+#define DCSR_CAUSE_SHIFT    6
+#define DCSR_CAUSE_MASK     (0x7u << DCSR_CAUSE_SHIFT)
+#define DCSR_CAUSE_EBREAK   1
+#define DCSR_CAUSE_TRIGGER  2
+#define DCSR_CAUSE_HALTREQ  3
+#define DCSR_CAUSE_STEP     4
+#define DCSR_CAUSE_RESET    5
+
+/* Trigger CSRs (abstract command register numbers) */
+#define REGNO_TSELECT       REGNO_CSR(0x7a0)
+#define REGNO_TDATA1        REGNO_CSR(0x7a1)
+#define REGNO_TDATA2        REGNO_CSR(0x7a2)
+
+/* mcontrol (type 2) tdata1 field helpers */
+#define TDATA1_TYPE2_TYPE       (2u << 28)
+#define TDATA1_TYPE2_DMODE      (1u << 27)
+#define TDATA1_TYPE2_ACTION_DBG (1u << 12)
+#define TDATA1_TYPE2_M          (1u << 6)
+#define TDATA1_TYPE2_S          (1u << 4)
+#define TDATA1_TYPE2_U          (1u << 3)
+#define TDATA1_TYPE2_EXEC       (1u << 2)
+#define TDATA1_TYPE2_STORE      (1u << 1)
+#define TDATA1_TYPE2_LOAD       (1u << 0)
+
+/* itrigger (type 3) tdata1 field helpers */
+#define TDATA1_ITRIGGER_TYPE        (3u << 28)
+#define TDATA1_ITRIGGER_ACTION_DBG  1u
+#define TDATA1_ITRIGGER_U           (1u << 6)
+#define TDATA1_ITRIGGER_S           (1u << 7)
+#define TDATA1_ITRIGGER_M           (1u << 9)
+#define TDATA1_ITRIGGER_COUNT(n)    (((n) & 0x3fffu) << 10)
+#define TDATA1_ITRIGGER_COUNT_MASK  TDATA1_ITRIGGER_COUNT(0x3fff)
+
+/* TCG test timing (wall-clock microseconds, CPU runs via MTTCG thread) */
+#define TCG_POLL_STEP_US    10000       /* 10ms per poll step */
+#define TCG_POLL_TIMEOUT_US 5000000     /* 5s max */
+#define TCG_BOOT_US         200000      /* 200ms boot */
 
 static uint32_t dm_read(QTestState *qts, uint32_t reg)
 {
@@ -62,118 +169,491 @@ static void dm_set_active(QTestState *qts)
     dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE);
 }
 
+/* Write to DM ROM area (simulates CPU writing from ROM code) */
 static void rom_write32(QTestState *qts, uint32_t offset, uint32_t val)
 {
     qtest_writel(qts, DM_BASE + offset, val);
 }
 
+/*
+ * Simulate the CPU executing the DM ROM halt entry code.
+ * In real hardware: CPU enters debug mode → jumps to ROM entry (0x800) →
+ * ROM code writes mhartid to HARTID offset → DM recognizes hart as halted.
+ */
 static void sim_cpu_halt_ack(QTestState *qts, uint32_t hartid)
 {
     rom_write32(qts, ROM_HARTID, hartid);
 }
 
+/*
+ * Simulate the CPU executing the DM ROM resume handler.
+ * In real hardware: ROM detects RESUME flag → writes hartid to RESUME offset
+ * → DM recognizes hart as resumed → CPU executes dret.
+ */
 static void sim_cpu_resume_ack(QTestState *qts, uint32_t hartid)
 {
     rom_write32(qts, ROM_RESUME, hartid);
 }
 
+
+/*
+ * Test: dmactive gate.
+ * When dmactive=0 (after reset), all non-DMCONTROL reads should return 0.
+ */
 static void test_dmactive_gate(void)
 {
     QTestState *qts = qtest_init("-machine virt");
 
+    /* DM starts inactive after reset */
     g_assert_cmpuint(dm_read(qts, A_DMCONTROL), ==, 0);
+
+    /* All other registers should return 0 when dmactive=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_HARTINFO), ==, 0);
     g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_PROGBUF0), ==, 0);
 
-    dm_write(qts, A_DATA0, 0xdeadbeef);
+    /* Writes to non-DMCONTROL registers should be ignored */
+    dm_write(qts, A_DATA0, 0xDEADBEEF);
+    /* Still inactive, so read should still return 0 */
     g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0);
 
+    /* Activate the DM */
     dm_set_active(qts);
     g_assert_cmpuint(dm_read(qts, A_DMCONTROL) & DMCONTROL_DMACTIVE, ==,
                      DMCONTROL_DMACTIVE);
+
+    /* Now DMSTATUS should be non-zero (version, authenticated, etc.) */
     g_assert_cmpuint(dm_read(qts, A_DMSTATUS), !=, 0);
 
     qtest_quit(qts);
 }
 
+/*
+ * Test: DMSTATUS register fields after activation.
+ */
 static void test_dmstatus(void)
 {
     QTestState *qts = qtest_init("-machine virt");
-    uint32_t status;
-
     dm_set_active(qts);
-    status = dm_read(qts, A_DMSTATUS);
 
+    uint32_t status = dm_read(qts, A_DMSTATUS);
+
+    /* Version should be 3 (v1.0 spec) */
     g_assert_cmpuint(status & DMSTATUS_VERSION_MASK, ==, 3);
+
+    /* Should be authenticated */
     g_assert_cmpuint(status & DMSTATUS_AUTHENTICATED, ==,
                      DMSTATUS_AUTHENTICATED);
-    g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==,
-                     DMSTATUS_ANYRUNNING);
-    g_assert_cmpuint(status & DMSTATUS_ALLRUNNING, ==,
-                     DMSTATUS_ALLRUNNING);
+
+    /* impebreak should be set (default property) */
+    g_assert_cmpuint(status & DMSTATUS_IMPEBREAK, ==, DMSTATUS_IMPEBREAK);
+
+    /* Hart 0 should be running (not halted) */
+    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_ALLHALTED, ==, 0);
+
+    /* Should have havereset after initial reset */
     g_assert_cmpuint(status & DMSTATUS_ANYHAVERESET, ==,
                      DMSTATUS_ANYHAVERESET);
-    g_assert_cmpuint(status & DMSTATUS_IMPEBREAK, ==, DMSTATUS_IMPEBREAK);
+    g_assert_cmpuint(status & DMSTATUS_ALLHAVERESET, ==,
+                     DMSTATUS_ALLHAVERESET);
+
+    /* Hart 0 should not be nonexistent or unavailable */
+    g_assert_cmpuint(status & DMSTATUS_ANYNONEXISTENT, ==, 0);
+    g_assert_cmpuint(status & DMSTATUS_ANYUNAVAIL, ==, 0);
 
     qtest_quit(qts);
 }
 
+/*
+ * Test: HARTINFO fields describe memory-mapped DATA registers.
+ */
 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);
+    uint32_t info = dm_read(qts, A_HARTINFO);
+    uint32_t datasize =
+        (info & HARTINFO_DATASIZE_MASK) >> HARTINFO_DATASIZE_SHIFT;
+    uint32_t nscratch =
+        (info & HARTINFO_NSCRATCH_MASK) >> HARTINFO_NSCRATCH_SHIFT;
+
     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);
+    g_assert_cmpuint(info & HARTINFO_DATAADDR_MASK, ==, ROM_DATA);
+    g_assert_cmpuint(datasize, ==, 2);
+    g_assert_cmpuint(nscratch, ==, 1);
 
     qtest_quit(qts);
 }
 
+/*
+ * Test: ABSTRACTCS config fields (datacount, progbufsize).
+ */
 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);
 
+    uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+
+    /* Default datacount = 2 */
     g_assert_cmpuint(acs & ABSTRACTCS_DATACOUNT_MASK, ==, 2);
-    g_assert_cmpuint((acs & ABSTRACTCS_PROGBUFSIZE_MASK) >>
-                     ABSTRACTCS_PROGBUFSIZE_SHIFT, ==, 8);
+
+    /* Default progbufsize = 8 */
+    uint32_t progbufsize =
+        (acs & ABSTRACTCS_PROGBUFSIZE_MASK) >> ABSTRACTCS_PROGBUFSIZE_SHIFT;
+    g_assert_cmpuint(progbufsize, ==, 8);
+
+    /* BUSY should be 0 */
     g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0);
 
+    /* CMDERR should be 0 */
+    g_assert_cmpuint(acs & ABSTRACTCS_CMDERR_MASK, ==, 0);
+
     qtest_quit(qts);
 }
 
-static void test_halt_resume(void)
+/*
+ * Test: DATA register read/write.
+ */
+static void test_data_rw(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    dm_set_active(qts);
+
+    /* Write to DATA0 and DATA1 */
+    dm_write(qts, A_DATA0, 0xCAFEBABE);
+    dm_write(qts, A_DATA1, 0x12345678);
+
+    g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0xCAFEBABE);
+    g_assert_cmpuint(dm_read(qts, A_DATA1), ==, 0x12345678);
+
+    /* Overwrite */
+    dm_write(qts, A_DATA0, 0x0);
+    g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0x0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: PROGBUF register read/write.
+ */
+static void test_progbuf_rw(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    dm_set_active(qts);
+
+    /* Write distinct values to progbuf0..3 */
+    for (int i = 0; i < 4; i++) {
+        dm_write(qts, A_PROGBUF0 + i * 4, 0xA0000000 | i);
+    }
+
+    for (int i = 0; i < 4; i++) {
+        g_assert_cmpuint(dm_read(qts, A_PROGBUF0 + i * 4), ==,
+                         0xA0000000 | (uint32_t)i);
+    }
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: Read-only register protection (DMSTATUS, HARTINFO, HALTSUM).
+ */
+static void test_ro_registers(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    dm_set_active(qts);
+
+    uint32_t orig_status = dm_read(qts, A_DMSTATUS);
+
+    /* Try to write DMSTATUS - should be ignored (RO) */
+    dm_write(qts, A_DMSTATUS, 0xFFFFFFFF);
+    g_assert_cmpuint(dm_read(qts, A_DMSTATUS), ==, orig_status);
+
+    /* Try to write HARTINFO - should be ignored (RO) */
+    uint32_t orig_hartinfo = dm_read(qts, A_HARTINFO);
+    dm_write(qts, A_HARTINFO, 0xFFFFFFFF);
+    g_assert_cmpuint(dm_read(qts, A_HARTINFO), ==, orig_hartinfo);
+
+    /* Try to write HALTSUM0 - should be ignored (RO) */
+    uint32_t orig_haltsum0 = dm_read(qts, A_HALTSUM0);
+    dm_write(qts, A_HALTSUM0, 0xFFFFFFFF);
+    g_assert_cmpuint(dm_read(qts, A_HALTSUM0), ==, orig_haltsum0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: W1C (Write-1-to-Clear) behavior on ABSTRACTCS.CMDERR.
+ */
+static void test_abstractcs_w1c(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    dm_set_active(qts);
+
+    /*
+     * CMDERR is 0 initially. To test W1C, we need to trigger an error.
+     * Issue an invalid command (cmdtype=0xFF) to a running hart.
+     * This should set CMDERR to HALTRESUME (4) since hart is not halted.
+     */
+    dm_write(qts, A_COMMAND, 0xFF000000);
+
+    uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+    uint32_t cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> 
ABSTRACTCS_CMDERR_SHIFT;
+    g_assert_cmpuint(cmderr, !=, 0);
+
+    /* Clear CMDERR by writing 1s to the CMDERR field */
+    dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+    acs = dm_read(qts, A_ABSTRACTCS);
+    cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+    g_assert_cmpuint(cmderr, ==, 0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: Hart selection via DMCONTROL.hartsel.
+ * Select a nonexistent hart and verify DMSTATUS reports it.
+ */
+static void test_hart_selection(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    dm_set_active(qts);
+
+    /* Default hart 0 is selected and should be running */
+    uint32_t status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, DMSTATUS_ANYRUNNING);
+    g_assert_cmpuint(status & DMSTATUS_ANYNONEXISTENT, ==, 0);
+
+    /* Select nonexistent hart 99 */
+    uint32_t ctl = DMCONTROL_DMACTIVE |
+                   (99u << DMCONTROL_HARTSELLO_SHIFT);
+    dm_write(qts, A_DMCONTROL, ctl);
+
+    status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status & DMSTATUS_ANYNONEXISTENT, ==,
+                     DMSTATUS_ANYNONEXISTENT);
+    g_assert_cmpuint(status & DMSTATUS_ALLNONEXISTENT, ==,
+                     DMSTATUS_ALLNONEXISTENT);
+    g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, 0);
+
+    /* Switch back to hart 0 */
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE);
+    status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, DMSTATUS_ANYRUNNING);
+    g_assert_cmpuint(status & DMSTATUS_ANYNONEXISTENT, ==, 0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: DMCONTROL WARZ (Write-Any-Read-Zero) fields.
+ * HALTREQ, RESUMEREQ, ACKHAVERESET etc. should not read back.
+ */
+static void test_dmcontrol_warz(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    dm_set_active(qts);
+
+    /* Write HALTREQ | DMACTIVE */
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+
+    /* HALTREQ should not read back */
+    uint32_t ctl = dm_read(qts, A_DMCONTROL);
+    g_assert_cmpuint(ctl & DMCONTROL_HALTREQ, ==, 0);
+
+    /* DMACTIVE should still be set */
+    g_assert_cmpuint(ctl & DMCONTROL_DMACTIVE, ==, DMCONTROL_DMACTIVE);
+
+    /* Write RESUMEREQ | DMACTIVE */
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_RESUMEREQ);
+    ctl = dm_read(qts, A_DMCONTROL);
+    g_assert_cmpuint(ctl & DMCONTROL_RESUMEREQ, ==, 0);
+
+    /* Write ACKHAVERESET | DMACTIVE */
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_ACKHAVERESET);
+    ctl = dm_read(qts, A_DMCONTROL);
+    g_assert_cmpuint(ctl & DMCONTROL_ACKHAVERESET, ==, 0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: HARTRESET is implemented and reads back while asserted.
+ */
+static void test_hartreset(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    uint32_t ctl, status;
+
+    dm_set_active(qts);
+
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HARTRESET);
+    ctl = dm_read(qts, A_DMCONTROL);
+    g_assert_cmpuint(ctl & DMCONTROL_HARTRESET, ==, DMCONTROL_HARTRESET);
+
+    status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status & DMSTATUS_ANYHAVERESET, ==,
+                     DMSTATUS_ANYHAVERESET);
+
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE);
+    ctl = dm_read(qts, A_DMCONTROL);
+    g_assert_cmpuint(ctl & DMCONTROL_HARTRESET, ==, 0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: NDMRESET gates non-DMCONTROL accesses and exposes only pending state.
+ */
+static void test_ndmreset_gate(void)
 {
     QTestState *qts = qtest_init("-machine virt");
     uint32_t status;
 
     dm_set_active(qts);
+    dm_write(qts, A_DATA0, 0xDEADBEEF);
+    g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0xDEADBEEF);
+
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_NDMRESET);
+
+    status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status, ==, DMSTATUS_NDMRESETPENDING);
+
+    dm_write(qts, A_DATA0, 0x11223344);
+    g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0);
+
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE);
+    status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status & DMSTATUS_NDMRESETPENDING, ==, 0);
+    g_assert_cmpuint(status & DMSTATUS_ANYHAVERESET, ==,
+                     DMSTATUS_ANYHAVERESET);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: Optional registers that are not implemented read as 0 and ignore
+ * writes.
+ */
+static void test_optional_regs_absent(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+
+    dm_set_active(qts);
+
+    dm_write(qts, A_AUTHDATA, 0xFFFFFFFF);
+    g_assert_cmpuint(dm_read(qts, A_AUTHDATA), ==, 0);
+
+    dm_write(qts, A_DMCS2, 0xFFFFFFFF);
+    g_assert_cmpuint(dm_read(qts, A_DMCS2), ==, 0);
+
+    dm_write(qts, A_SBADDRESS0, 0xFFFFFFFF);
+    dm_write(qts, A_SBADDRESS1, 0xFFFFFFFF);
+    dm_write(qts, A_SBADDRESS2, 0xFFFFFFFF);
+    dm_write(qts, A_SBDATA0, 0xAAAAAAAA);
+    dm_write(qts, A_SBDATA1, 0xBBBBBBBB);
+    dm_write(qts, A_SBDATA2, 0xCCCCCCCC);
+    dm_write(qts, A_SBDATA3, 0xDDDDDDDD);
+
+    g_assert_cmpuint(dm_read(qts, A_SBADDRESS0), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_SBADDRESS1), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_SBADDRESS2), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_SBDATA0), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_SBDATA1), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_SBDATA2), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_SBDATA3), ==, 0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: Deactivation resets all state.
+ */
+static void test_deactivate(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+
+    /* Activate and write some data */
+    dm_set_active(qts);
+    dm_write(qts, A_DATA0, 0xDEADBEEF);
+    g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0xDEADBEEF);
+
+    /* Deactivate: clear dmactive */
+    dm_write(qts, A_DMCONTROL, 0);
+
+    /* All registers should return 0 again */
+    g_assert_cmpuint(dm_read(qts, A_DMSTATUS), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0);
+    g_assert_cmpuint(dm_read(qts, A_ABSTRACTCS), ==, 0);
+
+    /* Re-activate: verify state was reset */
+    dm_set_active(qts);
+    g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0);
+
+    uint32_t status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status & DMSTATUS_VERSION_MASK, ==, 3);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: ACKHAVERESET clears havereset in DMSTATUS.
+ */
+static void test_ackhavereset(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    dm_set_active(qts);
+
+    /* After reset, havereset should be set */
+    uint32_t status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status & DMSTATUS_ANYHAVERESET, ==,
+                     DMSTATUS_ANYHAVERESET);
+
+    /* Acknowledge havereset */
+    dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_ACKHAVERESET);
+
+    status = dm_read(qts, A_DMSTATUS);
+    g_assert_cmpuint(status & DMSTATUS_ANYHAVERESET, ==, 0);
+    g_assert_cmpuint(status & DMSTATUS_ALLHAVERESET, ==, 0);
+
+    qtest_quit(qts);
+}
+
+/*
+ * Test: Halt and resume cycle via ROM simulation.
+ *
+ * Simulates the full halt/resume flow:
+ * 1. Debugger writes HALTREQ → DM signals halt IRQ
+ * 2. CPU enters debug mode → ROM entry code writes mhartid to HARTID offset
+ * 3. DM recognizes hart as halted (anyhalted/allhalted set)
+ * 4. Debugger writes RESUMEREQ → DM sets FLAGS to RESUME
+ * 5. CPU ROM resume handler writes hartid to RESUME offset → dret
+ * 6. DM recognizes hart as resumed (resumeack set, running)
+ */
+static void test_halt_resume(void)
+{
+    QTestState *qts = qtest_init("-machine virt");
+    dm_set_active(qts);
+
+    uint32_t status;
+
     status = dm_read(qts, A_DMSTATUS);
-    g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==,
-                     DMSTATUS_ANYRUNNING);
+    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_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);
 
@@ -185,10 +665,8 @@ static void test_halt_resume(void)
                      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_ANYRUNNING, ==, DMSTATUS_ANYRUNNING);
+    g_assert_cmpuint(status & DMSTATUS_ALLRUNNING, ==, DMSTATUS_ALLRUNNING);
     g_assert_cmpuint(status & DMSTATUS_ANYHALTED, ==, 0);
 
     qtest_quit(qts);
@@ -202,6 +680,17 @@ int main(int argc, char *argv[])
     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/data-rw", test_data_rw);
+    qtest_add_func("/riscv-dm/progbuf-rw", test_progbuf_rw);
+    qtest_add_func("/riscv-dm/ro-registers", test_ro_registers);
+    qtest_add_func("/riscv-dm/abstractcs-w1c", test_abstractcs_w1c);
+    qtest_add_func("/riscv-dm/hart-selection", test_hart_selection);
+    qtest_add_func("/riscv-dm/dmcontrol-warz", test_dmcontrol_warz);
+    qtest_add_func("/riscv-dm/hartreset", test_hartreset);
+    qtest_add_func("/riscv-dm/ndmreset-gate", test_ndmreset_gate);
+    qtest_add_func("/riscv-dm/optional-regs-absent", 
test_optional_regs_absent);
+    qtest_add_func("/riscv-dm/deactivate", test_deactivate);
+    qtest_add_func("/riscv-dm/ackhavereset", test_ackhavereset);
     qtest_add_func("/riscv-dm/halt-resume", test_halt_resume);
 
     return g_test_run();
-- 
2.53.0


Reply via email to