* [PATCH v1 00/28] RISC-V Debug Module support
@ 2026-03-08 7:17 Chao Liu
2026-03-08 7:17 ` [PATCH v1 01/28] target/riscv: track pending Debug Module halt requests Chao Liu
` (27 more replies)
0 siblings, 28 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
This series adds a RISC-V Debug Module model and wires it into
target/riscv and the virt machine.
It depends on two earlier patch sets:
- Max's Zvfbfa v5 series, which introduces the EXT_TB flags
mechanism reused here;
- the Sdext debug extension v5 series, which adds the base
debug-mode support that this series builds on.
The architectural reference for this work is the RISC-V Debug
Specification v1.0 (ratified 2025-02-21):
https://github.com/riscv/riscv-debug-spec/releases/tag/1.0
The implementation takes design cues from spike's debug module.
The focus is the Debug Module itself and the execution-based
debug flow around it. In practice, this means:
- target/riscv gains the plumbing needed to keep a hart in Debug
Mode while it executes from a DM ROM, and to route haltreq,
reset-haltreq, single-step completion, ebreak, exceptions, and
trigger-based debug entry through that ROM flow;
- hw/riscv gains a QOM Debug Module device with a DMI-style MMIO
window, a ROM work area, per-hart state tracking, run-control,
abstract register access, and system bus access;
- virt wires CPUs to the DM and exposes a stable layout for the
DMI window and ROM alias;
- qtest adds both protocol-level coverage and TCG-backed
execution tests for the full bring-up path on rv32 and rv64.
The series intentionally focuses on board-visible DM behavior.
It does not add a JTAG DTM frontend, authentication, or other
transport-side pieces. The result is a usable DM model for virt,
with enough target-side support to exercise the main spec-driven
debug flows and enough qtest coverage to keep the behavior
regression-resistant.
Current implementation status:
+-------------------+---------+---------+---------------------------+
| Area | Req/Opt | Status | Notes |
+-------------------+---------+---------+---------------------------+
| Debug entry | req | done | haltreq, reset-haltreq, |
| | | | ebreak, step, exceptions, |
| | | | triggers |
+-------------------+---------+---------+---------------------------+
| DM device | req | done | riscv-dm QOM, DMI MMIO, |
| | | | ROM work area, reset |
+-------------------+---------+---------+---------------------------+
| Hart state | req | done | halted, resumeack, |
| | | | havereset, haltsum0/1, |
| | | | hart selection |
+-------------------+---------+---------+---------------------------+
| Run-control | req | done | haltreq, resumereq, |
| | | | hartreset, ndmreset, |
| | | | reset-haltreq |
+-------------------+---------+---------+---------------------------+
| Abstract cmds | req | done | Access Register |
| | | | (GPR/CSR/FPR), DATA, |
| | | | PROGBUF, ABSTRACTAUTO |
+-------------------+---------+---------+---------------------------+
| SBA | opt | done | SBCS, SBADDRESS, SBDATA, |
| | | | sticky errors, auto-inc |
+-------------------+---------+---------+---------------------------+
| virt wiring | -- | done | DM address map, CPU |
| | | | hookup, ROM propagation |
+-------------------+---------+---------+---------------------------+
| ndmreset scope | req | partial | resets harts only, not |
| | | | other devices |
+-------------------+---------+---------+---------------------------+
| haltsum2/3 | opt | stub | present but read zero; |
| | | | only for >1024 harts |
+-------------------+---------+---------+---------------------------+
| Quick Access | opt | no | cmdtype=1 |
+-------------------+---------+---------+---------------------------+
| Access Memory | opt | no | cmdtype=2 |
+-------------------+---------+---------+---------------------------+
| DTM / JTAG | opt | no | transport-side, outside |
| | | | this series |
+-------------------+---------+---------+---------------------------+
| Authentication | opt | no | authdata absent; |
| | | | authenticated=1 |
+-------------------+---------+---------+---------------------------+
| Hart/resume | opt | no | dmcs2 absent, no group |
| groups | | | halt/resume |
+-------------------+---------+---------+---------------------------+
| External triggers | opt | no | DM ext-trigger I/O not |
| | | | wired |
+-------------------+---------+---------+---------------------------+
| keepalive | opt | no | set/clrkeepalive ignored |
+-------------------+---------+---------+---------------------------+
| ackunavail / | opt | no | sticky unavail not |
| stickyunavail | | | tracked |
+-------------------+---------+---------+---------------------------+
| confstrptr | opt | stub | always 0 |
+-------------------+---------+---------+---------------------------+
| Multi-DM/nextdm | opt | stub | always 0, single DM only |
+-------------------+---------+---------+---------------------------+
| 128-bit access | opt | no | aarsize=4, sbaccess128 |
+-------------------+---------+---------+---------------------------+
| Custom registers | opt | no | regno 0xC000-0xFFFF |
+-------------------+---------+---------+---------------------------+
| Cmds on running | opt | no | abstract cmds require |
| harts | | | halted hart |
+-------------------+---------+---------+---------------------------+
| Sub-word reg | opt | no | aarsize 8/16-bit not |
| access | | | supported |
+-------------------+---------+---------+---------------------------+
The main design choice is to follow the execution-based Debug
Module model described by the spec: the hart stays in Debug Mode,
enters a ROM park loop, and the DM coordinates state changes and
abstract command execution through mailbox words and generated
ROM snippets. This keeps the DM behavior explicit in the machine
model and avoids inventing a separate QEMU-only debug protocol.
The patches are split into three layers:
- patches 1-14 build the target/riscv side of the ROM-based
Debug Mode flow and make trigger-driven stops preserve the
right cause information;
- patches 15-23 add the Debug Module device model, per-hart
state tracking, run-control, register infrastructure, abstract
commands, system bus access, and virt integration;
- patches 24-28 add qtest coverage for the DMI register surface
and the TCG execution paths.
This series is also expected to help advance rvsp-ref machine
support, since Sdext is a required instruction extension for
rvsp-soc.
Thanks Tao Tang for running CI on this series:
https://gitlab.com/TaoTang/qemu/-/pipelines/2370872174
The resulting series has been validated with:
- `TMPDIR=/tmp QTEST_QEMU_BINARY=./build/qemu-system-riscv32 \
./build/tests/qtest/riscv-dm-test`
- `TMPDIR=/tmp QTEST_QEMU_BINARY=./build/qemu-system-riscv64 \
./build/tests/qtest/riscv-dm-test`
Thanks,
Chao
Chao Liu (28):
target/riscv: track pending Debug Module halt requests
target/riscv: keep Debug Mode active when a DM ROM is present
target/riscv: enter Debug Mode from pending DM halt requests
target/riscv: route in-debug exceptions back to the DM ROM
target/riscv: re-enter the DM ROM after single-step completion
target/riscv: let ebreak enter the DM ROM
target/riscv: restart the DM ROM on debug-mode ebreak
target/riscv: track the exact breakpoint trigger hit
target/riscv: track the exact watchpoint trigger hit
target/riscv: send trigger debug-mode actions to the DM ROM
target/riscv: suppress itrigger TB state in Debug Mode
target/riscv: tighten itrigger privilege and count checks
target/riscv: allow debug-mode actions for itrigger
target/riscv: stop consuming itrigger counts in Debug Mode
hw/riscv: add an initial Debug Module device model
hw/riscv/dm: track per-hart debug state
hw/riscv/dm: add hart run-control operations
hw/riscv/dm: queue reset halts from the reset work item
hw/riscv/dm: add DMI register declarations and ROM entry program
hw/riscv/dm: add register handlers and update state management
hw/riscv/dm: add abstract command execution
hw/riscv/dm: add system bus access
hw/riscv/virt: integrate the Debug Module
tests/qtest: add initial RISC-V Debug Module tests
tests/qtest: extend DM register semantics coverage
tests/qtest: add DM abstract command and halt/resume tests
tests/qtest: add DM TCG halt and register access tests
tests/qtest: add DM TCG single-step and trigger tests
MAINTAINERS | 1 +
hw/riscv/Kconfig | 5 +
hw/riscv/dm.c | 2017 +++++++++++++++++++++++++++++++++++
hw/riscv/meson.build | 2 +
hw/riscv/trace-events | 14 +
hw/riscv/virt.c | 21 +-
include/hw/riscv/dm.h | 139 +++
target/riscv/cpu.c | 68 ++
target/riscv/cpu.h | 8 +
target/riscv/cpu_bits.h | 1 +
target/riscv/cpu_helper.c | 60 +-
target/riscv/debug.c | 249 ++---
target/riscv/machine.c | 2 +
target/riscv/op_helper.c | 15 +
target/riscv/tcg/tcg-cpu.c | 12 +-
tests/qtest/meson.build | 7 +-
tests/qtest/riscv-dm-test.c | 1914 +++++++++++++++++++++++++++++++++
17 files changed, 4410 insertions(+), 125 deletions(-)
create mode 100644 hw/riscv/dm.c
create mode 100644 include/hw/riscv/dm.h
create mode 100644 tests/qtest/riscv-dm-test.c
--
2.53.0
^ permalink raw reply [flat|nested] 29+ messages in thread
* [PATCH v1 01/28] target/riscv: track pending Debug Module halt requests
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 02/28] target/riscv: keep Debug Mode active when a DM ROM is present Chao Liu
` (26 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Track a pending board-level Debug Module halt request in the CPU
state so machine code can queue haltreq and reset-haltreq before
the hart reaches the normal debug entry path.
Add the CPU-side helper, the GPIO input, and the migration state
needed to preserve the pending request and its debug cause across
machine resets and VM state transfers. Later patches will route
this state through the regular interrupt and Debug Mode entry
flow.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/cpu.c | 68 +++++++++++++++++++++++++++++++++++++++++
target/riscv/cpu.h | 5 +++
target/riscv/cpu_bits.h | 1 +
target/riscv/machine.c | 2 ++
4 files changed, 76 insertions(+)
diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 470c2d5b39..fce778a67d 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -784,6 +784,8 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
#ifndef CONFIG_USER_ONLY
env->debug_mode = false;
+ env->dm_halt_request = false;
+ env->dm_halt_cause = DCSR_CAUSE_HALTREQ;
env->dcsr = DCSR_DEBUGVER(4);
env->dpc = 0;
env->dscratch[0] = 0;
@@ -1086,6 +1088,70 @@ static void riscv_cpu_set_nmi(void *opaque, int irq, int level)
{
riscv_cpu_set_rnmi(RISCV_CPU(opaque), irq, level);
}
+
+/*
+ * Debug cause priority (higher to lower), per Debug Spec v1.0 Table 4.2:
+ * reset-haltreq, halt-group, haltreq, trigger, ebreak, step.
+ */
+static int riscv_debug_cause_priority(uint32_t cause)
+{
+ switch (cause & 0x7) {
+ case DCSR_CAUSE_RESET:
+ return 0;
+ case DCSR_CAUSE_GROUP:
+ return 1;
+ case DCSR_CAUSE_HALTREQ:
+ return 2;
+ case DCSR_CAUSE_TRIGGER:
+ return 3;
+ case DCSR_CAUSE_EBREAK:
+ return 4;
+ case DCSR_CAUSE_STEP:
+ return 5;
+ case DCSR_CAUSE_OTHER:
+ return 6;
+ default:
+ return 7;
+ }
+}
+
+static bool riscv_debug_cause_is_higher(uint32_t new_cause,
+ uint32_t current_cause)
+{
+ return riscv_debug_cause_priority(new_cause) <
+ riscv_debug_cause_priority(current_cause);
+}
+
+void riscv_cpu_request_dm_halt(RISCVCPU *cpu, uint32_t cause)
+{
+ CPURISCVState *env = &cpu->env;
+ CPUState *cs = CPU(cpu);
+
+ if (!riscv_cpu_cfg(env)->ext_sdext) {
+ return;
+ }
+
+ if (env->dm_halt_request &&
+ !riscv_debug_cause_is_higher(cause, env->dm_halt_cause)) {
+ return;
+ }
+
+ env->dm_halt_request = true;
+ env->dm_halt_cause = cause & 0x7;
+ cpu_interrupt(cs, CPU_INTERRUPT_DM_HALT);
+}
+
+static void riscv_cpu_dm_halt_req(void *opaque, int irq, int level)
+{
+ RISCVCPU *cpu = RISCV_CPU(opaque);
+ CPURISCVState *env = &cpu->env;
+
+ qemu_log_mask(CPU_LOG_INT, "dm_halt_req: level=%d ext_sdext=%d\n",
+ level, riscv_cpu_cfg(env)->ext_sdext);
+ if (level) {
+ riscv_cpu_request_dm_halt(cpu, DCSR_CAUSE_HALTREQ);
+ }
+}
#endif /* CONFIG_USER_ONLY */
static bool riscv_cpu_is_dynamic(Object *cpu_obj)
@@ -1106,6 +1172,8 @@ static void riscv_cpu_init(Object *obj)
IRQ_LOCAL_MAX + IRQ_LOCAL_GUEST_MAX);
qdev_init_gpio_in_named(DEVICE(cpu), riscv_cpu_set_nmi,
"riscv.cpu.rnmi", RNMI_MAX);
+ qdev_init_gpio_in_named(DEVICE(cpu), riscv_cpu_dm_halt_req,
+ "dm-halt-req", 1);
#endif /* CONFIG_USER_ONLY */
general_user_opts = g_hash_table_new(g_str_hash, g_str_equal);
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 0d6b70c9f0..6fb255c7c3 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -481,6 +481,10 @@ struct CPUArchState {
target_ulong dpc;
target_ulong dscratch[2];
+ /* Pending Debug Module halt request from the board-level controller. */
+ bool dm_halt_request;
+ uint8_t dm_halt_cause;
+
uint64_t mstateen[SMSTATEEN_MAX_COUNT];
uint64_t hstateen[SMSTATEEN_MAX_COUNT];
uint64_t sstateen[SMSTATEEN_MAX_COUNT];
@@ -642,6 +646,7 @@ void riscv_cpu_swap_hypervisor_regs(CPURISCVState *env);
int riscv_cpu_claim_interrupts(RISCVCPU *cpu, uint64_t interrupts);
uint64_t riscv_cpu_update_mip(CPURISCVState *env, uint64_t mask,
uint64_t value);
+void riscv_cpu_request_dm_halt(RISCVCPU *cpu, uint32_t cause);
void riscv_cpu_set_rnmi(RISCVCPU *cpu, uint32_t irq, bool level);
void riscv_cpu_interrupt(CPURISCVState *env);
#define BOOL_TO_MASK(x) (-!!(x)) /* helper for riscv_cpu_update_mip value */
diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h
index bb59f7ff56..01ec2a69c4 100644
--- a/target/riscv/cpu_bits.h
+++ b/target/riscv/cpu_bits.h
@@ -1177,6 +1177,7 @@ typedef enum CTRType {
/* RISC-V-specific interrupt pending bits. */
#define CPU_INTERRUPT_RNMI CPU_INTERRUPT_TGT_EXT_0
+#define CPU_INTERRUPT_DM_HALT CPU_INTERRUPT_TGT_INT_0
/* JVT CSR bits */
#define JVT_MODE 0x3F
diff --git a/target/riscv/machine.c b/target/riscv/machine.c
index ddd0569d9e..e811abc868 100644
--- a/target/riscv/machine.c
+++ b/target/riscv/machine.c
@@ -263,6 +263,8 @@ static const VMStateDescription vmstate_sdext = {
VMSTATE_UINTTL(env.dcsr, RISCVCPU),
VMSTATE_UINTTL(env.dpc, RISCVCPU),
VMSTATE_UINTTL_ARRAY(env.dscratch, RISCVCPU, 2),
+ VMSTATE_BOOL(env.dm_halt_request, RISCVCPU),
+ VMSTATE_UINT8(env.dm_halt_cause, RISCVCPU),
VMSTATE_END_OF_LIST()
}
};
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 02/28] target/riscv: keep Debug Mode active when a DM ROM is present
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
2026-03-08 7:17 ` [PATCH v1 01/28] target/riscv: track pending Debug Module halt requests Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 03/28] target/riscv: enter Debug Mode from pending DM halt requests Chao Liu
` (25 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Record whether a hart has a Debug Module ROM entry point and keep
the CPU in Debug Mode while that ROM is active, instead of always
returning to dpc on the next exec loop entry.
This updates Debug Mode entry so dpc follows the architectural
cause rules, switches ROM and Program Buffer execution to M-mode,
and lets later DM patches hand control to the ROM park loop.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/cpu.h | 4 +++-
target/riscv/cpu_helper.c | 27 ++++++++++++++++++++++++++-
target/riscv/tcg/tcg-cpu.c | 10 ++++++++++
3 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 6fb255c7c3..0bc14f6953 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -481,9 +481,11 @@ struct CPUArchState {
target_ulong dpc;
target_ulong dscratch[2];
- /* Pending Debug Module halt request from the board-level controller. */
+ /* DM halt request (set by external Debug Module GPIO) */
bool dm_halt_request;
uint8_t dm_halt_cause;
+ bool dm_rom_present;
+ uint64_t dm_halt_addr;
uint64_t mstateen[SMSTATEEN_MAX_COUNT];
uint64_t hstateen[SMSTATEEN_MAX_COUNT];
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index a0874f4e23..714dcb446e 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -141,6 +141,24 @@ static bool riscv_sdext_enabled(CPURISCVState *env)
{
return riscv_cpu_cfg(env)->ext_sdext;
}
+
+/*
+ * Debug Spec v1.0 Table 9:
+ * - ebreak: dpc = address of the ebreak instruction.
+ * - step/trigger/haltreq (and reset/group/other): dpc = next instruction
+ * to execute when Debug Mode was entered.
+ */
+static target_ulong riscv_debug_dpc_on_entry(CPURISCVState *env,
+ target_ulong trap_pc,
+ uint32_t cause)
+{
+ switch (cause & 0x7) {
+ case DCSR_CAUSE_EBREAK:
+ return trap_pc & get_xepc_mask(env);
+ default:
+ return env->pc & get_xepc_mask(env);
+ }
+}
#endif
void riscv_cpu_enter_debug_mode(CPURISCVState *env, target_ulong pc,
@@ -152,7 +170,7 @@ void riscv_cpu_enter_debug_mode(CPURISCVState *env, target_ulong pc,
}
env->debug_mode = true;
- env->dpc = pc & get_xepc_mask(env);
+ env->dpc = riscv_debug_dpc_on_entry(env, pc, cause);
env->dcsr &= ~(DCSR_CAUSE_MASK | DCSR_PRV_MASK | DCSR_V);
env->dcsr |= ((target_ulong)(cause & 0x7)) << DCSR_CAUSE_SHIFT;
env->dcsr |= env->priv & DCSR_PRV_MASK;
@@ -168,6 +186,13 @@ void riscv_cpu_enter_debug_mode(CPURISCVState *env, target_ulong pc,
}
env->elp = false;
}
+
+ /*
+ * Per RISC-V Debug Spec v1.0 Section 4.1:
+ * "All operations are executed with machine mode privilege."
+ * Switch to M-mode so ROM/progbuf fetches use physical addressing.
+ */
+ riscv_cpu_set_mode(env, PRV_M, false);
#endif
}
diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
index 53d862080c..4674ff3e3c 100644
--- a/target/riscv/tcg/tcg-cpu.c
+++ b/target/riscv/tcg/tcg-cpu.c
@@ -280,6 +280,16 @@ static void riscv_cpu_exec_enter(CPUState *cs)
if (!cpu->cfg.ext_sdext || !env->debug_mode) {
return;
}
+
+ /*
+ * When a DM ROM is present, the CPU must stay in debug mode and
+ * execute ROM code. DRET will leave debug mode later. Without a DM
+ * ROM, leave debug mode immediately (legacy shortcut).
+ */
+ if (env->dm_rom_present) {
+ return;
+ }
+
target_ulong pc = env->dpc;
riscv_cpu_leave_debug_mode(env);
env->pc = pc;
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 03/28] target/riscv: enter Debug Mode from pending DM halt requests
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
2026-03-08 7:17 ` [PATCH v1 01/28] target/riscv: track pending Debug Module halt requests Chao Liu
2026-03-08 7:17 ` [PATCH v1 02/28] target/riscv: keep Debug Mode active when a DM ROM is present Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 04/28] target/riscv: route in-debug exceptions back to the DM ROM Chao Liu
` (24 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Consume pending Debug Module halt requests from the regular CPU
execution path and enter Debug Mode with the recorded debug cause
before normal interrupt delivery.
This routes haltreq and reset-haltreq through the same entry path
used by other debug stops, clears the pending request once it is
taken, and jumps to the Debug Module ROM when the hart is wired
to one.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/cpu_helper.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index 714dcb446e..e477016d4a 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -655,12 +655,31 @@ static int riscv_cpu_local_irq_pending(CPURISCVState *env)
bool riscv_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
{
- uint32_t mask = CPU_INTERRUPT_HARD | CPU_INTERRUPT_RNMI;
+ uint32_t mask = CPU_INTERRUPT_HARD | CPU_INTERRUPT_RNMI |
+ CPU_INTERRUPT_DM_HALT;
if (interrupt_request & mask) {
RISCVCPU *cpu = RISCV_CPU(cs);
CPURISCVState *env = &cpu->env;
+ /* DM halt request: enter debug mode before checking regular IRQs */
+ if (env->dm_halt_request && !env->debug_mode) {
+ uint32_t halt_cause = env->dm_halt_cause;
+
+ qemu_log_mask(CPU_LOG_INT,
+ "exec_interrupt: dm_halt cause=%u pc=0x%" PRIx64
+ " halt_addr=0x%" PRIx64 "\n",
+ halt_cause,
+ (uint64_t)env->pc,
+ (uint64_t)env->dm_halt_addr);
+ env->dm_halt_request = false;
+ env->dm_halt_cause = DCSR_CAUSE_HALTREQ;
+ cpu_reset_interrupt(cs, CPU_INTERRUPT_DM_HALT);
+ riscv_cpu_enter_debug_mode(env, env->pc, halt_cause);
+ env->pc = env->dm_halt_addr;
+ return true;
+ }
+
if (cpu->cfg.ext_sdext && !env->debug_mode &&
(env->dcsr & DCSR_STEP) && !(env->dcsr & DCSR_STEPIE)) {
return false;
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 04/28] target/riscv: route in-debug exceptions back to the DM ROM
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (2 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 03/28] target/riscv: enter Debug Mode from pending DM halt requests Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 05/28] target/riscv: re-enter the DM ROM after single-step completion Chao Liu
` (23 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
When a hart is already executing from the Debug Module ROM,
redirect synchronous exceptions to the ROM exception entry
instead of taking the normal trap path.
This keeps the hart inside the Debug Module state machine and
lets the ROM report command faults through its mailbox protocol.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/cpu_helper.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c
index e477016d4a..81b74f7e4a 100644
--- a/target/riscv/cpu_helper.c
+++ b/target/riscv/cpu_helper.c
@@ -142,6 +142,9 @@ static bool riscv_sdext_enabled(CPURISCVState *env)
return riscv_cpu_cfg(env)->ext_sdext;
}
+/* DM ROM entry window offsets: exception vector is entry + 0x10 (0x810). */
+#define RISCV_DEBUG_ROM_EXCEPTION_OFS 0x10
+
/*
* Debug Spec v1.0 Table 9:
* - ebreak: dpc = address of the ebreak instruction.
@@ -2326,6 +2329,15 @@ void riscv_cpu_do_interrupt(CPUState *cs)
return;
}
+ if (env->debug_mode && env->dm_rom_present) {
+ /*
+ * Exceptions taken while already in Debug Mode are handled by the
+ * DM ROM exception entry (0x810 in the backing ROM).
+ */
+ env->pc = env->dm_halt_addr + RISCV_DEBUG_ROM_EXCEPTION_OFS;
+ return;
+ }
+
if (!async) {
/* set tval to badaddr for traps with address information */
switch (cause) {
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 05/28] target/riscv: re-enter the DM ROM after single-step completion
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (3 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 04/28] target/riscv: route in-debug exceptions back to the DM ROM Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 06/28] target/riscv: let ebreak enter the DM ROM Chao Liu
` (22 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
When single-step finishes on a hart backed by a Debug Module ROM,
restart execution at the ROM entry instead of forcing the legacy
EXCP_DEBUG stop path.
This lets the park loop observe step completion through the
normal ROM flow and keeps single-step behavior aligned with the
Debug Module run-control model.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/op_helper.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c
index e7878d7aa4..945688eca8 100644
--- a/target/riscv/op_helper.c
+++ b/target/riscv/op_helper.c
@@ -481,6 +481,10 @@ void helper_sdext_step(CPURISCVState *env)
}
riscv_cpu_enter_debug_mode(env, env->pc, DCSR_CAUSE_STEP);
+ if (env->dm_rom_present) {
+ env->pc = env->dm_halt_addr;
+ cpu_loop_exit(cs);
+ }
cs->exception_index = EXCP_DEBUG;
cpu_loop_exit_restore(cs, GETPC());
#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 06/28] target/riscv: let ebreak enter the DM ROM
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (4 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 05/28] target/riscv: re-enter the DM ROM after single-step completion Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 07/28] target/riscv: restart the DM ROM on debug-mode ebreak Chao Liu
` (21 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
When ebreak is configured to enter Debug Mode and a Debug Module
ROM is present, restart execution from the ROM halt entry
instead of reporting a legacy EXCP_DEBUG stop.
This makes ebreak-driven debug entry follow the same ROM-based
flow as haltreq and single-step stops.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/op_helper.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c
index 945688eca8..7f2769f444 100644
--- a/target/riscv/op_helper.c
+++ b/target/riscv/op_helper.c
@@ -517,6 +517,10 @@ void helper_sdext_ebreak(CPURISCVState *env, target_ulong pc)
if (enter_debug) {
riscv_cpu_enter_debug_mode(env, pc, DCSR_CAUSE_EBREAK);
+ if (env->dm_rom_present) {
+ env->pc = env->dm_halt_addr;
+ cpu_loop_exit(cs);
+ }
cs->exception_index = EXCP_DEBUG;
cpu_loop_exit_restore(cs, GETPC());
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 07/28] target/riscv: restart the DM ROM on debug-mode ebreak
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (5 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 06/28] target/riscv: let ebreak enter the DM ROM Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 08/28] target/riscv: track the exact breakpoint trigger hit Chao Liu
` (20 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
If ebreak executes while the hart is already in Debug Mode,
restart the Debug Module ROM from its halt entry rather than
falling back to the normal breakpoint exception path.
This matches the ROM-based debug flow used by hardware debuggers
that place an ebreak in Program Buffer code.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/op_helper.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c
index 7f2769f444..406fe2ecd0 100644
--- a/target/riscv/op_helper.c
+++ b/target/riscv/op_helper.c
@@ -495,6 +495,13 @@ void helper_sdext_ebreak(CPURISCVState *env, target_ulong pc)
CPUState *cs = env_cpu(env);
bool enter_debug = false;
+ /* ebreak in debug mode: re-enter DM ROM at halt address */
+ if (riscv_cpu_cfg(env)->ext_sdext &&
+ env->debug_mode && env->dm_rom_present) {
+ env->pc = env->dm_halt_addr;
+ cpu_loop_exit(cs);
+ }
+
if (riscv_cpu_cfg(env)->ext_sdext && !env->debug_mode) {
if (env->virt_enabled) {
if (env->priv == PRV_S) {
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 08/28] target/riscv: track the exact breakpoint trigger hit
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (6 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 07/28] target/riscv: restart the DM ROM on debug-mode ebreak Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 09/28] target/riscv: track the exact watchpoint " Chao Liu
` (19 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Store the exact breakpoint trigger index in CPU state when a
trigger-backed CPU breakpoint matches so later debug handling can
distinguish which slot fired.
This simplifies the post-hit path because the expensive matching
work has already been done when the trigger was programmed into a
QEMU breakpoint object. By the time debug handling runs, the
matching breakpoint pointer already identifies the trigger slot,
so there is no need to re-decode the execute bits or compare the
program counter against tdata2 again. The remaining dynamic
checks are the common privilege and textra conditions, which are
still revalidated before the trigger action is taken.
This avoids collapsing all breakpoint hits into a generic stop
and lets ROM-backed Debug Mode entry preserve the original
trigger action and metadata.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/cpu.h | 1 +
target/riscv/debug.c | 81 +++++++++++++++++++++-----------------------
2 files changed, 40 insertions(+), 42 deletions(-)
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 0bc14f6953..65c7b66596 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -452,6 +452,7 @@ struct CPUArchState {
struct CPUWatchpoint *cpu_watchpoint[RV_MAX_TRIGGERS];
QEMUTimer *itrigger_timer[RV_MAX_TRIGGERS];
int64_t last_icount;
+ int pending_trigger_hit;
bool itrigger_enabled;
/* machine specific rdtime callback */
diff --git a/target/riscv/debug.c b/target/riscv/debug.c
index 6c69c2f796..ef2fafcbef 100644
--- a/target/riscv/debug.c
+++ b/target/riscv/debug.c
@@ -558,6 +558,34 @@ static void type2_breakpoint_remove(CPURISCVState *env, target_ulong index)
}
}
+static int riscv_debug_find_breakpoint_trigger(CPUState *cs)
+{
+ RISCVCPU *cpu = RISCV_CPU(cs);
+ CPURISCVState *env = &cpu->env;
+ CPUBreakpoint *bp;
+
+ QTAILQ_FOREACH(bp, &cs->breakpoints, entry) {
+ if (!(bp->flags & BP_CPU) || bp->pc != env->pc) {
+ continue;
+ }
+
+ for (int i = 0; i < RV_MAX_TRIGGERS; i++) {
+ int trigger_type = get_trigger_type(env, i);
+
+ if (!trigger_common_match(env, trigger_type, i)) {
+ continue;
+ }
+
+ if (bp == env->cpu_breakpoint[i]) {
+ env->badaddr = bp->pc;
+ return i;
+ }
+ }
+ }
+
+ return -1;
+}
+
static void type2_reg_write(CPURISCVState *env, target_ulong index,
int tdata_index, target_ulong val)
{
@@ -975,6 +1003,9 @@ void riscv_cpu_debug_excp_handler(CPUState *cs)
{
RISCVCPU *cpu = RISCV_CPU(cs);
CPURISCVState *env = &cpu->env;
+ int hit = env->pending_trigger_hit;
+
+ env->pending_trigger_hit = -1;
/* Triggers must not match or fire while in Debug Mode. */
if (env->debug_mode) {
@@ -986,8 +1017,11 @@ void riscv_cpu_debug_excp_handler(CPUState *cs)
do_trigger_action(env, DBG_ACTION_BP);
}
} else {
- if (cpu_breakpoint_test(cs, env->pc, BP_CPU)) {
- do_trigger_action(env, DBG_ACTION_BP);
+ if (hit < 0) {
+ hit = riscv_debug_find_breakpoint_trigger(cs);
+ }
+ if (hit >= 0) {
+ do_trigger_action(env, hit);
}
}
}
@@ -996,47 +1030,9 @@ bool riscv_cpu_debug_check_breakpoint(CPUState *cs)
{
RISCVCPU *cpu = RISCV_CPU(cs);
CPURISCVState *env = &cpu->env;
- CPUBreakpoint *bp;
- target_ulong ctrl;
- target_ulong pc;
- int trigger_type;
- int i;
-
- QTAILQ_FOREACH(bp, &cs->breakpoints, entry) {
- for (i = 0; i < RV_MAX_TRIGGERS; i++) {
- trigger_type = get_trigger_type(env, i);
- if (!trigger_common_match(env, trigger_type, i)) {
- continue;
- }
-
- switch (trigger_type) {
- case TRIGGER_TYPE_AD_MATCH:
- ctrl = env->tdata1[i];
- pc = env->tdata2[i];
-
- if ((ctrl & TYPE2_EXEC) && (bp->pc == pc)) {
- env->badaddr = pc;
- return true;
- }
- break;
- case TRIGGER_TYPE_AD_MATCH6:
- ctrl = env->tdata1[i];
- pc = env->tdata2[i];
-
- if ((ctrl & TYPE6_EXEC) && (bp->pc == pc)) {
- env->badaddr = pc;
- return true;
- }
- break;
- default:
- /* other trigger types are not supported or irrelevant */
- break;
- }
- }
- }
-
- return false;
+ env->pending_trigger_hit = riscv_debug_find_breakpoint_trigger(cs);
+ return env->pending_trigger_hit >= 0;
}
bool riscv_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp)
@@ -1137,4 +1133,5 @@ void riscv_trigger_reset_hold(CPURISCVState *env)
}
env->mcontext = 0;
+ env->pending_trigger_hit = -1;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 09/28] target/riscv: track the exact watchpoint trigger hit
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (7 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 08/28] target/riscv: track the exact breakpoint trigger hit Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 10/28] target/riscv: send trigger debug-mode actions to the DM ROM Chao Liu
` (18 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Extend trigger-hit tracking to CPU watchpoints so load and store
triggers preserve the exact matching trigger index across
exception delivery.
This simplifies the watchpoint hit path for the same reason as
the breakpoint case: the trigger programming code has already
expanded the load/store bits, size, and address into the concrete
QEMU watchpoint object stored in env->cpu_watchpoint[i]. When
QEMU reports a watchpoint hit, matching that object back to the
trigger slot is enough; there is no need to repeat the address
and access-type comparisons in the debug exception handler. The
remaining dynamic privilege and textra conditions are still
revalidated before the trigger action is taken.
This gives watchpoint-based debug entry the same precise trigger
information that breakpoint triggers now carry in CPU state.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/debug.c | 103 +++++++++++++++++--------------------------
1 file changed, 40 insertions(+), 63 deletions(-)
diff --git a/target/riscv/debug.c b/target/riscv/debug.c
index ef2fafcbef..eb03f306b1 100644
--- a/target/riscv/debug.c
+++ b/target/riscv/debug.c
@@ -586,6 +586,29 @@ static int riscv_debug_find_breakpoint_trigger(CPUState *cs)
return -1;
}
+static int riscv_debug_find_watchpoint_trigger(CPUState *cs, CPUWatchpoint *wp)
+{
+ RISCVCPU *cpu = RISCV_CPU(cs);
+ CPURISCVState *env = &cpu->env;
+
+ for (int i = 0; i < RV_MAX_TRIGGERS; i++) {
+ int trigger_type = get_trigger_type(env, i);
+
+ if (!trigger_common_match(env, trigger_type, i)) {
+ continue;
+ }
+
+ if (wp != env->cpu_watchpoint[i]) {
+ continue;
+ }
+
+ env->badaddr = wp->hitaddr;
+ return i;
+ }
+
+ return -1;
+}
+
static void type2_reg_write(CPURISCVState *env, target_ulong index,
int tdata_index, target_ulong val)
{
@@ -1012,17 +1035,21 @@ void riscv_cpu_debug_excp_handler(CPUState *cs)
return;
}
- if (cs->watchpoint_hit) {
- if (cs->watchpoint_hit->flags & BP_CPU) {
- do_trigger_action(env, DBG_ACTION_BP);
- }
- } else {
- if (hit < 0) {
- hit = riscv_debug_find_breakpoint_trigger(cs);
- }
- if (hit >= 0) {
- do_trigger_action(env, hit);
- }
+ if (hit < 0 && cs->watchpoint_hit && (cs->watchpoint_hit->flags & BP_CPU)) {
+ hit = riscv_debug_find_watchpoint_trigger(cs, cs->watchpoint_hit);
+ }
+
+ if (cs->watchpoint_hit &&
+ (hit >= 0 || (cs->watchpoint_hit->flags & BP_CPU))) {
+ cs->watchpoint_hit = NULL;
+ }
+
+ if (hit < 0) {
+ hit = riscv_debug_find_breakpoint_trigger(cs);
+ }
+
+ if (hit >= 0) {
+ do_trigger_action(env, hit);
}
}
@@ -1039,59 +1066,9 @@ bool riscv_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp)
{
RISCVCPU *cpu = RISCV_CPU(cs);
CPURISCVState *env = &cpu->env;
- target_ulong ctrl;
- target_ulong addr;
- int trigger_type;
- int flags;
- int i;
-
- for (i = 0; i < RV_MAX_TRIGGERS; i++) {
- trigger_type = get_trigger_type(env, i);
-
- if (!trigger_common_match(env, trigger_type, i)) {
- continue;
- }
-
- switch (trigger_type) {
- case TRIGGER_TYPE_AD_MATCH:
- ctrl = env->tdata1[i];
- addr = env->tdata2[i];
- flags = 0;
-
- if (ctrl & TYPE2_LOAD) {
- flags |= BP_MEM_READ;
- }
- if (ctrl & TYPE2_STORE) {
- flags |= BP_MEM_WRITE;
- }
- if ((wp->flags & flags) && (wp->vaddr == addr)) {
- return true;
- }
- break;
- case TRIGGER_TYPE_AD_MATCH6:
- ctrl = env->tdata1[i];
- addr = env->tdata2[i];
- flags = 0;
-
- if (ctrl & TYPE6_LOAD) {
- flags |= BP_MEM_READ;
- }
- if (ctrl & TYPE6_STORE) {
- flags |= BP_MEM_WRITE;
- }
-
- if ((wp->flags & flags) && (wp->vaddr == addr)) {
- return true;
- }
- break;
- default:
- /* other trigger types are not supported */
- break;
- }
- }
-
- return false;
+ env->pending_trigger_hit = riscv_debug_find_watchpoint_trigger(cs, wp);
+ return env->pending_trigger_hit >= 0;
}
void riscv_trigger_realize(CPURISCVState *env)
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 10/28] target/riscv: send trigger debug-mode actions to the DM ROM
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (8 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 09/28] target/riscv: track the exact watchpoint " Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 11/28] target/riscv: suppress itrigger TB state in Debug Mode Chao Liu
` (17 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
When a trigger configured for debug-mode action fires on a hart
with a Debug Module ROM, redirect execution to the ROM entry and
suppress the legacy EXCP_DEBUG stop notification.
This lets breakpoint, load, and store trigger actions feed the
same ROM park loop that handles the other Debug Module entry
causes.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/debug.c | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/target/riscv/debug.c b/target/riscv/debug.c
index eb03f306b1..00fd75f15f 100644
--- a/target/riscv/debug.c
+++ b/target/riscv/debug.c
@@ -298,9 +298,20 @@ static void do_trigger_action(CPURISCVState *env, target_ulong trigger_index)
riscv_raise_exception(env, RISCV_EXCP_BREAKPOINT, 0);
}
riscv_cpu_enter_debug_mode(env, env->pc, DCSR_CAUSE_TRIGGER);
+ if (env->dm_rom_present) {
+ /*
+ * DM ROM present: redirect to ROM entry and restart the exec
+ * loop. Clear exception_index so EXCP_DEBUG does not propagate
+ * to the MTTCG layer as a spurious GDB stop event.
+ */
+ env->pc = env->dm_halt_addr;
+ cs->exception_index = -1;
+ cpu_loop_exit(cs);
+ }
/*
- * If this came from the Trigger Module's CPU breakpoint/watchpoint,
- * we're already returning via EXCP_DEBUG. Otherwise, stop now.
+ * No DM ROM (gdbstub path): let EXCP_DEBUG propagate so the gdb
+ * stub stops the CPU. riscv_cpu_exec_enter leaves debug mode via
+ * the legacy shortcut on next exec entry.
*/
if (cs->exception_index != EXCP_DEBUG) {
cs->exception_index = EXCP_DEBUG;
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 11/28] target/riscv: suppress itrigger TB state in Debug Mode
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (9 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 10/28] target/riscv: send trigger debug-mode actions to the DM ROM Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 12/28] target/riscv: tighten itrigger privilege and count checks Chao Liu
` (16 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Do not expose the native itrigger translation-block state while
a hart is already in Debug Mode, because trigger matching is
architecturally disabled there.
Hiding the TB flag avoids stale itrigger state leaking into ROM
execution and keeps translated code aligned with the active
debug context.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/tcg/tcg-cpu.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
index 4674ff3e3c..43d764fd59 100644
--- a/target/riscv/tcg/tcg-cpu.c
+++ b/target/riscv/tcg/tcg-cpu.c
@@ -180,7 +180,7 @@ static TCGTBCPUState riscv_get_tb_cpu_state(CPUState *cs)
? EXT_STATUS_DIRTY : EXT_STATUS_DISABLED;
}
- if (cpu->cfg.ext_sdtrig && !icount_enabled()) {
+ if (cpu->cfg.ext_sdtrig && !env->debug_mode && !icount_enabled()) {
flags = FIELD_DP32(flags, TB_FLAGS, ITRIGGER, env->itrigger_enabled);
}
#endif
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 12/28] target/riscv: tighten itrigger privilege and count checks
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (10 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 11/28] target/riscv: suppress itrigger TB state in Debug Mode Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 13/28] target/riscv: allow debug-mode actions for itrigger Chao Liu
` (15 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Decode itrigger privilege enables directly from the mode bits and
only advertise itrigger activity while an instruction-count
trigger still has a non-zero count.
This removes a looser check in the debug helper paths and keeps
the exposed itrigger state aligned with the conditions under
which the trigger can actually fire.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/debug.c | 35 +++++++++++++++++++++--------------
1 file changed, 21 insertions(+), 14 deletions(-)
diff --git a/target/riscv/debug.c b/target/riscv/debug.c
index 00fd75f15f..f3d6eaeb1e 100644
--- a/target/riscv/debug.c
+++ b/target/riscv/debug.c
@@ -795,30 +795,37 @@ itrigger_set_count(CPURISCVState *env, int index, int value)
static bool check_itrigger_priv(CPURISCVState *env, int index)
{
target_ulong tdata1 = env->tdata1[index];
+
if (env->virt_enabled) {
- /* check VU/VS bit against current privilege level */
- return (get_field(tdata1, ITRIGGER_VS) == env->priv) ||
- (get_field(tdata1, ITRIGGER_VU) == env->priv);
- } else {
- /* check U/S/M bit against current privilege level */
- return (get_field(tdata1, ITRIGGER_M) == env->priv) ||
- (get_field(tdata1, ITRIGGER_S) == env->priv) ||
- (get_field(tdata1, ITRIGGER_U) == env->priv);
+ switch (env->priv) {
+ case PRV_S:
+ return !!(tdata1 & ITRIGGER_VS);
+ case PRV_U:
+ return !!(tdata1 & ITRIGGER_VU);
+ default:
+ return false;
+ }
+ }
+
+ switch (env->priv) {
+ case PRV_M:
+ return !!(tdata1 & ITRIGGER_M);
+ case PRV_S:
+ return !!(tdata1 & ITRIGGER_S);
+ case PRV_U:
+ return !!(tdata1 & ITRIGGER_U);
+ default:
+ return false;
}
}
bool riscv_itrigger_enabled(CPURISCVState *env)
{
- int count;
for (int i = 0; i < RV_MAX_TRIGGERS; i++) {
if (get_trigger_type(env, i) != TRIGGER_TYPE_INST_CNT) {
continue;
}
- if (check_itrigger_priv(env, i)) {
- continue;
- }
- count = itrigger_get_count(env, i);
- if (!count) {
+ if (!itrigger_get_count(env, i)) {
continue;
}
return true;
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 13/28] target/riscv: allow debug-mode actions for itrigger
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (11 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 12/28] target/riscv: tighten itrigger privilege and count checks Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 14/28] target/riscv: stop consuming itrigger counts in Debug Mode Chao Liu
` (14 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Accept action=debug-mode in itrigger tdata1 when Sdext is enabled
and reject unsupported action encodings explicitly.
This extends the instruction-count trigger path to the same
debug-entry mode used by other trigger types and keeps invalid
encodings from being silently accepted.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/debug.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/target/riscv/debug.c b/target/riscv/debug.c
index f3d6eaeb1e..4dbcf289f2 100644
--- a/target/riscv/debug.c
+++ b/target/riscv/debug.c
@@ -916,15 +916,27 @@ static target_ulong itrigger_validate(CPURISCVState *env,
target_ulong ctrl)
{
target_ulong val;
+ uint32_t action;
/* validate the generic part first */
val = tdata1_validate(env, ctrl, TRIGGER_TYPE_INST_CNT);
- /* validate unimplemented (always zero) bits */
- warn_always_zero_bit(ctrl, ITRIGGER_ACTION, "action");
warn_always_zero_bit(ctrl, ITRIGGER_HIT, "hit");
warn_always_zero_bit(ctrl, ITRIGGER_PENDING, "pending");
+ action = ctrl & ITRIGGER_ACTION;
+ if (action == DBG_ACTION_DBG_MODE) {
+ if (env_archcpu(env)->cfg.ext_sdext) {
+ val |= action;
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "trigger action=debug mode requires Sdext\n");
+ }
+ } else if (action != DBG_ACTION_BP) {
+ qemu_log_mask(LOG_UNIMP, "trigger action: %u is not supported\n",
+ action);
+ }
+
/* keep the mode and attribute bits */
val |= ctrl & (ITRIGGER_VU | ITRIGGER_VS | ITRIGGER_U | ITRIGGER_S |
ITRIGGER_M | ITRIGGER_COUNT);
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 14/28] target/riscv: stop consuming itrigger counts in Debug Mode
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (12 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 13/28] target/riscv: allow debug-mode actions for itrigger Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 15/28] hw/riscv: add an initial Debug Module device model Chao Liu
` (13 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Do not decrement instruction-count triggers while the hart is
already in Debug Mode, and store the decremented count before
testing whether it reached zero.
This matches the architectural rule that triggers do not fire in
Debug Mode and avoids losing a count update when the trigger
transitions to its firing state.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
target/riscv/debug.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/target/riscv/debug.c b/target/riscv/debug.c
index 4dbcf289f2..ae9b1cd42a 100644
--- a/target/riscv/debug.c
+++ b/target/riscv/debug.c
@@ -837,6 +837,11 @@ bool riscv_itrigger_enabled(CPURISCVState *env)
void helper_itrigger_match(CPURISCVState *env)
{
int count;
+
+ if (env->debug_mode) {
+ return;
+ }
+
for (int i = 0; i < RV_MAX_TRIGGERS; i++) {
if (get_trigger_type(env, i) != TRIGGER_TYPE_INST_CNT) {
continue;
@@ -848,7 +853,9 @@ void helper_itrigger_match(CPURISCVState *env)
if (!count) {
continue;
}
- itrigger_set_count(env, i, count--);
+
+ count--;
+ itrigger_set_count(env, i, count);
if (!count) {
env->itrigger_enabled = riscv_itrigger_enabled(env);
do_trigger_action(env, i);
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 15/28] hw/riscv: add an initial Debug Module device model
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (13 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 14/28] target/riscv: stop consuming itrigger counts in Debug Mode Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 16/28] hw/riscv/dm: track per-hart debug state Chao Liu
` (12 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Add the initial QOM model for the RISC-V Debug Module and wire it
into Kconfig and Meson.
This introduces the public DM header, the riscv-dm SysBus device
type, the basic dmcontrol, dmstatus, hartinfo, and abstractcs
register state, and the backing storage for the ROM work area.
It also creates separate MMIO regions for the DMI window and the
ROM entry alias used by later patches.
At this stage the device provides reset values, simple ROM
storage, the create helper, and stub CPU callbacks. Follow-on
patches will add hart state tracking, run-control, abstract
commands, and system bus access.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/Kconfig | 5 +
hw/riscv/dm.c | 252 ++++++++++++++++++++++++++++++++++++++++++
hw/riscv/meson.build | 2 +
include/hw/riscv/dm.h | 139 +++++++++++++++++++++++
4 files changed, 398 insertions(+)
create mode 100644 hw/riscv/dm.c
create mode 100644 include/hw/riscv/dm.h
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 0222c93f87..72bcaa0028 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -1,3 +1,7 @@
+config RISCV_DM
+ bool
+ select REGISTER
+
config RISCV_IOMMU
bool
@@ -68,6 +72,7 @@ config RISCV_VIRT
select PLATFORM_BUS
select ACPI
select ACPI_PCI
+ select RISCV_DM
config SHAKTI_C
bool
diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c
new file mode 100644
index 0000000000..9ac5f2cfac
--- /dev/null
+++ b/hw/riscv/dm.c
@@ -0,0 +1,252 @@
+/*
+ * RISC-V Debug Module v1.0
+ *
+ * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
+ *
+ * Based on the RISC-V Debug Specification v1.0 (ratified 2025-02-21)
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/riscv/dm.h"
+#include "migration/vmstate.h"
+
+REG32(DMCONTROL, 0x40)
+ FIELD(DMCONTROL, DMACTIVE, 0, 1)
+
+REG32(DMSTATUS, 0x44)
+ FIELD(DMSTATUS, VERSION, 0, 4)
+ FIELD(DMSTATUS, AUTHENTICATED, 7, 1)
+
+REG32(HARTINFO, 0x48)
+
+REG32(ABSTRACTCS, 0x58)
+ FIELD(ABSTRACTCS, DATACOUNT, 0, 4)
+ FIELD(ABSTRACTCS, PROGBUFSIZE, 24, 5)
+
+static RegisterAccessInfo riscv_dm_regs_info[] = {
+ { .name = "DMCONTROL", .addr = A_DMCONTROL, },
+ { .name = "DMSTATUS", .addr = A_DMSTATUS, .ro = 0xffffffff, },
+ { .name = "HARTINFO", .addr = A_HARTINFO, .ro = 0xffffffff, },
+ { .name = "ABSTRACTCS", .addr = A_ABSTRACTCS, .ro = 0xffffffff, },
+};
+
+static uint64_t riscv_dm_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return register_read_memory(opaque, addr, size);
+}
+
+static void riscv_dm_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ register_write_memory(opaque, addr, value, size);
+}
+
+static const MemoryRegionOps riscv_dm_ops = {
+ .read = riscv_dm_read,
+ .write = riscv_dm_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+/*
+ * ROM device read callback.
+ *
+ * In ROMD mode (the default) reads go directly to the RAM backing store and
+ * this callback is never invoked. It is kept as a fallback for correctness
+ * should ROMD mode ever be disabled at runtime.
+ */
+static uint64_t dm_rom_read(void *opaque, hwaddr offset, unsigned size)
+{
+ RISCVDMState *s = opaque;
+
+ return ldn_le_p(s->rom_ptr + offset, size);
+}
+
+static void dm_rom_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ RISCVDMState *s = opaque;
+
+ stn_le_p(s->rom_ptr + offset, size, value);
+}
+
+static const MemoryRegionOps dm_rom_ops = {
+ .read = dm_rom_read,
+ .write = dm_rom_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static bool dm_rom_realize(RISCVDMState *s, Error **errp)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(s);
+
+ if (!memory_region_init_rom_device(&s->rom_mr, OBJECT(s), &dm_rom_ops, s,
+ "riscv-dm.rom", RISCV_DM_SIZE,
+ errp)) {
+ return false;
+ }
+
+ s->rom_ptr = memory_region_get_ram_ptr(&s->rom_mr);
+
+ memory_region_init_alias(&s->rom_work_alias_mr, OBJECT(s),
+ "riscv-dm.rom-work", &s->rom_mr,
+ RISCV_DM_ROM_WORK_BASE, RISCV_DM_ROM_WORK_SIZE);
+ memory_region_init_alias(&s->rom_entry_alias_mr, OBJECT(s),
+ "riscv-dm.rom-entry", &s->rom_mr,
+ RISCV_DM_ROM_ENTRY, RISCV_DM_ROM_ENTRY_SIZE);
+
+ sysbus_init_mmio(sbd, &s->rom_work_alias_mr);
+ sysbus_init_mmio(sbd, &s->rom_entry_alias_mr);
+ return true;
+}
+
+void riscv_dm_hart_halted(RISCVDMState *s, uint32_t hartsel)
+{
+ (void)s;
+ (void)hartsel;
+}
+
+void riscv_dm_hart_resumed(RISCVDMState *s, uint32_t hartsel)
+{
+ (void)s;
+ (void)hartsel;
+}
+
+void riscv_dm_abstracts_done(RISCVDMState *s, uint32_t hartsel)
+{
+ (void)s;
+ (void)hartsel;
+}
+
+void riscv_dm_abstracts_exception(RISCVDMState *s, uint32_t hartsel)
+{
+ (void)s;
+ (void)hartsel;
+}
+
+static void dm_debug_reset(RISCVDMState *s)
+{
+ memset(s->regs, 0, sizeof(s->regs));
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, VERSION, 3);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, AUTHENTICATED, 1);
+ ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, DATACOUNT, s->num_abstract_data);
+ ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, PROGBUFSIZE, s->progbuf_size);
+
+ if (s->rom_ptr) {
+ memset(s->rom_ptr, 0, RISCV_DM_SIZE);
+ }
+}
+
+static void riscv_dm_init(Object *obj)
+{
+ RISCVDMState *s = RISCV_DM(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ s->reg_array = register_init_block32(DEVICE(obj), riscv_dm_regs_info,
+ ARRAY_SIZE(riscv_dm_regs_info),
+ s->regs_info, s->regs, &riscv_dm_ops,
+ false, RISCV_DM_REG_SIZE);
+
+ sysbus_init_mmio(sbd, &s->reg_array->mem);
+}
+
+static void riscv_dm_realize(DeviceState *dev, Error **errp)
+{
+ RISCVDMState *s = RISCV_DM(dev);
+
+ if (s->num_harts > RISCV_DM_MAX_HARTS) {
+ error_setg(errp, "riscv-dm: num-harts %u exceeds maximum %d",
+ s->num_harts, RISCV_DM_MAX_HARTS);
+ return;
+ }
+
+ if (!dm_rom_realize(s, errp)) {
+ return;
+ }
+
+ dm_debug_reset(s);
+}
+
+static void riscv_dm_reset_hold(Object *obj, ResetType type)
+{
+ (void)type;
+ dm_debug_reset(RISCV_DM(obj));
+}
+
+static const Property riscv_dm_props[] = {
+ DEFINE_PROP_UINT32("num-harts", RISCVDMState, num_harts, 1),
+ DEFINE_PROP_UINT32("datacount", RISCVDMState, num_abstract_data, 2),
+ DEFINE_PROP_UINT32("progbufsize", RISCVDMState, progbuf_size, 8),
+ DEFINE_PROP_BOOL("impebreak", RISCVDMState, impebreak, true),
+ DEFINE_PROP_UINT32("nscratch", RISCVDMState, nscratch, 1),
+ DEFINE_PROP_UINT32("sba-addr-width", RISCVDMState, sba_addr_width, 0),
+};
+
+static const VMStateDescription vmstate_riscv_dm = {
+ .name = TYPE_RISCV_DM,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, RISCVDMState, RISCV_DM_R_MAX),
+ VMSTATE_BOOL(dm_active, RISCVDMState),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static void riscv_dm_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = riscv_dm_realize;
+ dc->vmsd = &vmstate_riscv_dm;
+ device_class_set_props(dc, riscv_dm_props);
+ rc->phases.hold = riscv_dm_reset_hold;
+}
+
+static const TypeInfo riscv_dm_info = {
+ .name = TYPE_RISCV_DM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RISCVDMState),
+ .instance_init = riscv_dm_init,
+ .class_init = riscv_dm_class_init,
+};
+
+static void riscv_dm_register_types(void)
+{
+ type_register_static(&riscv_dm_info);
+}
+
+type_init(riscv_dm_register_types)
+
+DeviceState *riscv_dm_create(MemoryRegion *sys_mem, hwaddr base,
+ uint32_t num_harts)
+{
+ DeviceState *dev = qdev_new(TYPE_RISCV_DM);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ qdev_prop_set_uint32(dev, "num-harts", num_harts);
+ sysbus_realize_and_unref(sbd, &error_fatal);
+
+ /* MMIO region 0: DMI register file */
+ memory_region_add_subregion(sys_mem, base,
+ sysbus_mmio_get_region(sbd, 0));
+ /* MMIO region 1: low debug work area (mailbox, cmd, data, flags) */
+ memory_region_add_subregion(sys_mem, base + RISCV_DM_ROM_WORK_BASE,
+ sysbus_mmio_get_region(sbd, 1));
+ /* MMIO region 2: debug ROM entry vector */
+ memory_region_add_subregion(sys_mem, base + RISCV_DM_ROM_ENTRY,
+ sysbus_mmio_get_region(sbd, 2));
+ return dev;
+}
diff --git a/hw/riscv/meson.build b/hw/riscv/meson.build
index 533472e22a..2e98bfbe9a 100644
--- a/hw/riscv/meson.build
+++ b/hw/riscv/meson.build
@@ -18,4 +18,6 @@ riscv_ss.add(when: 'CONFIG_XIANGSHAN_KUNMINGHU', if_true: files('xiangshan_kmh.c
riscv_ss.add(when: 'CONFIG_RISCV_MIPS_CPS', if_true: files('cps.c'))
riscv_ss.add(when: 'CONFIG_MIPS_BOSTON_AIA', if_true: files('boston-aia.c'))
+riscv_ss.add(when: 'CONFIG_RISCV_DM', if_true: files('dm.c'))
+
hw_arch += {'riscv': riscv_ss}
diff --git a/include/hw/riscv/dm.h b/include/hw/riscv/dm.h
new file mode 100644
index 0000000000..652a1f53dd
--- /dev/null
+++ b/include/hw/riscv/dm.h
@@ -0,0 +1,139 @@
+/*
+ * RISC-V Debug Module v1.0
+ *
+ * Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
+ *
+ * Based on the RISC-V Debug Specification v1.0 (ratified 2025-02-21)
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_RISCV_DM_H
+#define HW_RISCV_DM_H
+
+#include "hw/core/sysbus.h"
+#include "hw/core/register.h"
+
+/* Debug memory layout constants (byte offsets within DM backing store) */
+#define RISCV_DM_SIZE 0x1000
+
+/*
+ * The DMI register window occupies 0x0..0x103.
+ * Place CPU<->DM mailbox words right above it.
+ */
+#define RISCV_DM_ROM_HARTID 0x104
+#define RISCV_DM_ROM_GOING 0x108
+#define RISCV_DM_ROM_RESUME 0x10C
+#define RISCV_DM_ROM_EXCP 0x110
+#define RISCV_DM_ROM_WHERETO 0x300
+#define RISCV_DM_ROM_CMD 0x338
+#define RISCV_DM_ROM_PROGBUF 0x360
+#define RISCV_DM_ROM_DATA 0x3C0
+#define RISCV_DM_ROM_FLAGS 0x400
+#define RISCV_DM_ROM_ENTRY 0x800
+#define RISCV_DM_ROM_WORK_BASE RISCV_DM_ROM_HARTID
+#define RISCV_DM_ROM_WORK_SIZE (RISCV_DM_ROM_ENTRY - RISCV_DM_ROM_WORK_BASE)
+#define RISCV_DM_ROM_ENTRY_SIZE (RISCV_DM_SIZE - RISCV_DM_ROM_ENTRY)
+
+/*
+ * Maximum harts addressable by the ROM entry loop.
+ * The ROM uses `lbu s0, 0x400(s0)` with a 7-bit masked mhartid,
+ * giving 128 directly-addressable harts per DM instance.
+ */
+#define RISCV_DM_MAX_HARTS 128
+#define RISCV_DM_HAWINDOW_SIZE 32
+
+/* Register space: 0x00 - 0x100, word-addressed */
+#define RISCV_DM_REG_SIZE 0x104
+#define RISCV_DM_R_MAX (RISCV_DM_REG_SIZE / 4)
+
+/* Hart flag values written into ROM FLAGS area */
+enum RISCVDMHartFlag {
+ RISCV_DM_FLAG_CLEAR = 0,
+ RISCV_DM_FLAG_GOING = 1,
+ RISCV_DM_FLAG_RESUME = 2,
+};
+
+/* Abstract command error codes (CMDERR field) */
+enum RISCVDMCmdErr {
+ RISCV_DM_CMDERR_NONE = 0,
+ RISCV_DM_CMDERR_BUSY = 1,
+ RISCV_DM_CMDERR_NOTSUP = 2,
+ RISCV_DM_CMDERR_EXCEPTION = 3,
+ RISCV_DM_CMDERR_HALTRESUME = 4,
+ RISCV_DM_CMDERR_BUS = 5,
+ RISCV_DM_CMDERR_OTHER = 7,
+};
+
+/* Abstract command types */
+enum RISCVDMCmdType {
+ RISCV_DM_CMD_ACCESS_REG = 0,
+ RISCV_DM_CMD_QUICK_ACCESS = 1,
+ RISCV_DM_CMD_ACCESS_MEM = 2,
+};
+
+/* Abstract register number ranges */
+#define RISCV_DM_REGNO_CSR_START 0x0000
+#define RISCV_DM_REGNO_CSR_END 0x0FFF
+#define RISCV_DM_REGNO_GPR_START 0x1000
+#define RISCV_DM_REGNO_GPR_END 0x101F
+#define RISCV_DM_REGNO_FPR_START 0x1020
+#define RISCV_DM_REGNO_FPR_END 0x103F
+
+#define TYPE_RISCV_DM "riscv-dm"
+OBJECT_DECLARE_SIMPLE_TYPE(RISCVDMState, RISCV_DM)
+
+struct RISCVDMState {
+ SysBusDevice parent_obj;
+
+ /* register.h framework */
+ RegisterInfoArray *reg_array;
+ uint32_t regs[RISCV_DM_R_MAX];
+ RegisterInfo regs_info[RISCV_DM_R_MAX];
+
+ /* DM backing store (rom_device) and exported aliases */
+ MemoryRegion rom_mr;
+ MemoryRegion rom_work_alias_mr;
+ MemoryRegion rom_entry_alias_mr;
+ uint8_t *rom_ptr;
+
+ /* Per-hart state */
+ uint32_t num_harts;
+ qemu_irq *halt_irqs;
+ bool *hart_halted;
+ bool *hart_resumeack;
+ bool *hart_havereset;
+ bool *hart_resethaltreq;
+
+ /* DM active state (from DMCONTROL.dmactive) */
+ bool dm_active;
+
+ /* Hart array mask window */
+ uint32_t hawindow[RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE];
+
+ /* Last executed command (stored for autoexec) */
+ uint32_t last_cmd;
+
+ /* QOM properties */
+ uint32_t num_abstract_data; /* datacount: default 2 */
+ uint32_t progbuf_size; /* progbufsize: default 8 */
+ bool impebreak; /* implicit ebreak */
+ uint32_t nscratch; /* dscratch count: default 1 */
+ uint32_t sba_addr_width; /* SBA address bits: default 0 (disabled) */
+};
+
+/*
+ * CPU-side callbacks: called from ROM write handler or CPU debug logic.
+ * These update per-hart state in the DM.
+ */
+void riscv_dm_hart_halted(RISCVDMState *s, uint32_t hartsel);
+void riscv_dm_hart_resumed(RISCVDMState *s, uint32_t hartsel);
+void riscv_dm_abstracts_done(RISCVDMState *s, uint32_t hartsel);
+void riscv_dm_abstracts_exception(RISCVDMState *s, uint32_t hartsel);
+
+/* Convenience: create, configure, realize, and map the DM device.
+ * @base is the start of the DM address space (size RISCV_DM_SIZE). */
+DeviceState *riscv_dm_create(MemoryRegion *sys_mem, hwaddr base,
+ uint32_t num_harts);
+
+#endif /* HW_RISCV_DM_H */
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 16/28] hw/riscv/dm: track per-hart debug state
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (14 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 15/28] hw/riscv: add an initial Debug Module device model Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 17/28] hw/riscv/dm: add hart run-control operations Chao Liu
` (11 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Add per-hart arrays for tracking halt, resume-acknowledge,
have-reset and reset-halt-request state. The initial DM reset
handler clears all arrays consistently using a for loop.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/dm.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 183 insertions(+), 10 deletions(-)
diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c
index 9ac5f2cfac..fad8e2b2ce 100644
--- a/hw/riscv/dm.c
+++ b/hw/riscv/dm.c
@@ -16,32 +16,148 @@
REG32(DMCONTROL, 0x40)
FIELD(DMCONTROL, DMACTIVE, 0, 1)
+ FIELD(DMCONTROL, HARTSELLO, 6, 10)
+ FIELD(DMCONTROL, HARTSELHI, 16, 10)
+ FIELD(DMCONTROL, HASEL, 26, 1)
REG32(DMSTATUS, 0x44)
FIELD(DMSTATUS, VERSION, 0, 4)
+ FIELD(DMSTATUS, HASRESETHALTREQ, 5, 1)
FIELD(DMSTATUS, AUTHENTICATED, 7, 1)
+ FIELD(DMSTATUS, ANYHALTED, 8, 1)
+ FIELD(DMSTATUS, ALLHALTED, 9, 1)
+ FIELD(DMSTATUS, ANYRUNNING, 10, 1)
+ FIELD(DMSTATUS, ALLRUNNING, 11, 1)
+ FIELD(DMSTATUS, ANYNONEXISTENT, 14, 1)
+ FIELD(DMSTATUS, ALLNONEXISTENT, 15, 1)
+ FIELD(DMSTATUS, ANYRESUMEACK, 16, 1)
+ FIELD(DMSTATUS, ALLRESUMEACK, 17, 1)
+ FIELD(DMSTATUS, ANYHAVERESET, 18, 1)
+ FIELD(DMSTATUS, ALLHAVERESET, 19, 1)
+ FIELD(DMSTATUS, IMPEBREAK, 22, 1)
REG32(HARTINFO, 0x48)
+ FIELD(HARTINFO, DATAADDR, 0, 12)
+ FIELD(HARTINFO, DATASIZE, 12, 4)
+ FIELD(HARTINFO, DATAACCESS, 16, 1)
+ FIELD(HARTINFO, NSCRATCH, 20, 4)
REG32(ABSTRACTCS, 0x58)
FIELD(ABSTRACTCS, DATACOUNT, 0, 4)
+ FIELD(ABSTRACTCS, CMDERR, 8, 3)
+ FIELD(ABSTRACTCS, BUSY, 12, 1)
FIELD(ABSTRACTCS, PROGBUFSIZE, 24, 5)
+static inline uint32_t dm_get_hartsel(RISCVDMState *s)
+{
+ uint32_t hi = ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELHI);
+ uint32_t lo = ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELLO);
+
+ return (hi << 10) | lo;
+}
+
+static inline bool dm_hart_valid(RISCVDMState *s, uint32_t hartsel)
+{
+ return hartsel < s->num_harts;
+}
+
+static void dm_status_refresh(RISCVDMState *s)
+{
+ uint32_t hartsel = dm_get_hartsel(s);
+ bool valid = dm_hart_valid(s, hartsel);
+ bool halted = valid && s->hart_halted[hartsel];
+ bool running = valid && !s->hart_halted[hartsel];
+ bool resumeack = valid && s->hart_resumeack[hartsel];
+ bool havereset = valid && s->hart_havereset[hartsel];
+
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYNONEXISTENT, !valid);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLNONEXISTENT, !valid);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHALTED, halted);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHALTED, halted);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRUNNING, running);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRUNNING, running);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRESUMEACK, resumeack);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRESUMEACK, resumeack);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHAVERESET, havereset);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHAVERESET, havereset);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, HASRESETHALTREQ, 1);
+}
+
+static void dm_debug_reset(RISCVDMState *s);
+
+static uint64_t dm_dmcontrol_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ uint32_t val = val64;
+
+ if (!FIELD_EX32(val, DMCONTROL, DMACTIVE)) {
+ dm_debug_reset(s);
+ return 0;
+ }
+
+ s->dm_active = true;
+
+ val = FIELD_DP32(val, DMCONTROL, DMACTIVE, 1);
+ s->regs[R_DMCONTROL] = val;
+ dm_status_refresh(s);
+ return val;
+}
+
+static uint64_t dm_dmstatus_post_read(RegisterInfo *reg, uint64_t val)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ dm_status_refresh(s);
+ return val;
+}
+
+static uint64_t dm_hartinfo_post_read(RegisterInfo *reg, uint64_t val)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ uint32_t v = val;
+
+ v = FIELD_DP32(v, HARTINFO, DATAADDR, RISCV_DM_ROM_DATA);
+ v = FIELD_DP32(v, HARTINFO, DATASIZE, s->num_abstract_data);
+ v = FIELD_DP32(v, HARTINFO, DATAACCESS, 1);
+ v = FIELD_DP32(v, HARTINFO, NSCRATCH, s->nscratch);
+ return v;
+}
+
static RegisterAccessInfo riscv_dm_regs_info[] = {
- { .name = "DMCONTROL", .addr = A_DMCONTROL, },
- { .name = "DMSTATUS", .addr = A_DMSTATUS, .ro = 0xffffffff, },
- { .name = "HARTINFO", .addr = A_HARTINFO, .ro = 0xffffffff, },
- { .name = "ABSTRACTCS", .addr = A_ABSTRACTCS, .ro = 0xffffffff, },
+ { .name = "DMCONTROL", .addr = A_DMCONTROL,
+ .pre_write = dm_dmcontrol_pre_write, },
+ { .name = "DMSTATUS", .addr = A_DMSTATUS,
+ .ro = 0xffffffff,
+ .post_read = dm_dmstatus_post_read, },
+ { .name = "HARTINFO", .addr = A_HARTINFO,
+ .ro = 0xffffffff,
+ .post_read = dm_hartinfo_post_read, },
+ { .name = "ABSTRACTCS", .addr = A_ABSTRACTCS,
+ .ro = 0xffffffff, },
};
static uint64_t riscv_dm_read(void *opaque, hwaddr addr, unsigned size)
{
+ RegisterInfoArray *reg_array = opaque;
+ RISCVDMState *s = RISCV_DM(reg_array->r[0]->opaque);
+
+ if (!s->dm_active && addr != A_DMCONTROL) {
+ return 0;
+ }
+
return register_read_memory(opaque, addr, size);
}
static void riscv_dm_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
+ RegisterInfoArray *reg_array = opaque;
+ RISCVDMState *s = RISCV_DM(reg_array->r[0]->opaque);
+
+ if (!s->dm_active && addr != A_DMCONTROL) {
+ return;
+ }
+
register_write_memory(opaque, addr, value, size);
}
@@ -75,6 +191,20 @@ static void dm_rom_write(void *opaque, hwaddr offset,
RISCVDMState *s = opaque;
stn_le_p(s->rom_ptr + offset, size, value);
+
+ if (offset == RISCV_DM_ROM_HARTID && size == 4) {
+ riscv_dm_hart_halted(s, value);
+ return;
+ }
+
+ if (offset == RISCV_DM_ROM_RESUME && size == 4) {
+ riscv_dm_hart_resumed(s, value);
+ return;
+ }
+
+ if (offset == RISCV_DM_ROM_EXCP && size == 4) {
+ riscv_dm_abstracts_exception(s, dm_get_hartsel(s));
+ }
}
static const MemoryRegionOps dm_rom_ops = {
@@ -113,26 +243,38 @@ static bool dm_rom_realize(RISCVDMState *s, Error **errp)
void riscv_dm_hart_halted(RISCVDMState *s, uint32_t hartsel)
{
- (void)s;
- (void)hartsel;
+ if (!dm_hart_valid(s, hartsel)) {
+ return;
+ }
+
+ s->hart_halted[hartsel] = true;
+ riscv_dm_abstracts_done(s, hartsel);
+ dm_status_refresh(s);
}
void riscv_dm_hart_resumed(RISCVDMState *s, uint32_t hartsel)
{
- (void)s;
- (void)hartsel;
+ if (!dm_hart_valid(s, hartsel)) {
+ return;
+ }
+
+ s->hart_halted[hartsel] = false;
+ s->hart_resumeack[hartsel] = true;
+ dm_status_refresh(s);
}
void riscv_dm_abstracts_done(RISCVDMState *s, uint32_t hartsel)
{
- (void)s;
(void)hartsel;
+ ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, BUSY, 0);
}
void riscv_dm_abstracts_exception(RISCVDMState *s, uint32_t hartsel)
{
- (void)s;
(void)hartsel;
+ ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, BUSY, 0);
+ ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, CMDERR,
+ RISCV_DM_CMDERR_EXCEPTION);
}
static void dm_debug_reset(RISCVDMState *s)
@@ -140,12 +282,23 @@ static void dm_debug_reset(RISCVDMState *s)
memset(s->regs, 0, sizeof(s->regs));
ARRAY_FIELD_DP32(s->regs, DMSTATUS, VERSION, 3);
ARRAY_FIELD_DP32(s->regs, DMSTATUS, AUTHENTICATED, 1);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, IMPEBREAK, s->impebreak);
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, DATACOUNT, s->num_abstract_data);
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, PROGBUFSIZE, s->progbuf_size);
+ if (s->hart_halted) {
+ memset(s->hart_halted, 0, s->num_harts * sizeof(bool));
+ memset(s->hart_resumeack, 0, s->num_harts * sizeof(bool));
+ memset(s->hart_havereset, 1, s->num_harts * sizeof(bool));
+ }
+
+ s->dm_active = false;
+
if (s->rom_ptr) {
memset(s->rom_ptr, 0, RISCV_DM_SIZE);
}
+
+ dm_status_refresh(s);
}
static void riscv_dm_init(Object *obj)
@@ -171,6 +324,10 @@ static void riscv_dm_realize(DeviceState *dev, Error **errp)
return;
}
+ s->hart_halted = g_new0(bool, s->num_harts);
+ s->hart_resumeack = g_new0(bool, s->num_harts);
+ s->hart_havereset = g_new0(bool, s->num_harts);
+
if (!dm_rom_realize(s, errp)) {
return;
}
@@ -184,6 +341,15 @@ static void riscv_dm_reset_hold(Object *obj, ResetType type)
dm_debug_reset(RISCV_DM(obj));
}
+static void riscv_dm_unrealize(DeviceState *dev)
+{
+ RISCVDMState *s = RISCV_DM(dev);
+
+ g_free(s->hart_halted);
+ g_free(s->hart_resumeack);
+ g_free(s->hart_havereset);
+}
+
static const Property riscv_dm_props[] = {
DEFINE_PROP_UINT32("num-harts", RISCVDMState, num_harts, 1),
DEFINE_PROP_UINT32("datacount", RISCVDMState, num_abstract_data, 2),
@@ -200,6 +366,12 @@ static const VMStateDescription vmstate_riscv_dm = {
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(regs, RISCVDMState, RISCV_DM_R_MAX),
VMSTATE_BOOL(dm_active, RISCVDMState),
+ VMSTATE_VARRAY_UINT32(hart_halted, RISCVDMState, num_harts,
+ 0, vmstate_info_bool, bool),
+ VMSTATE_VARRAY_UINT32(hart_resumeack, RISCVDMState, num_harts,
+ 0, vmstate_info_bool, bool),
+ VMSTATE_VARRAY_UINT32(hart_havereset, RISCVDMState, num_harts,
+ 0, vmstate_info_bool, bool),
VMSTATE_END_OF_LIST(),
}
};
@@ -210,6 +382,7 @@ static void riscv_dm_class_init(ObjectClass *klass, const void *data)
ResettableClass *rc = RESETTABLE_CLASS(klass);
dc->realize = riscv_dm_realize;
+ dc->unrealize = riscv_dm_unrealize;
dc->vmsd = &vmstate_riscv_dm;
device_class_set_props(dc, riscv_dm_props);
rc->phases.hold = riscv_dm_reset_hold;
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 17/28] hw/riscv/dm: add hart run-control operations
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (15 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 16/28] hw/riscv/dm: track per-hart debug state Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 18/28] hw/riscv/dm: queue reset halts from the reset work item Chao Liu
` (10 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Implement the dmcontrol operations that act on the selected hart
set and expose the matching summary registers.
This adds haltreq, resumereq, hartreset, ndmreset, and
reset-haltreq handling, collects selected harts from hartsel,
hasel, and hawindow, and computes haltsum0 and haltsum1 from the
tracked hart state. It also feeds GOING, RESUME, halt, and
resume acknowledgements from the ROM mailbox back into the state
machine and adds tracepoints for the new transitions.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/dm.c | 506 +++++++++++++++++++++++++++++++++++++++---
hw/riscv/trace-events | 14 ++
2 files changed, 485 insertions(+), 35 deletions(-)
diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c
index fad8e2b2ce..d55b5b827d 100644
--- a/hw/riscv/dm.c
+++ b/hw/riscv/dm.c
@@ -10,15 +10,28 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
+#include "hw/core/cpu.h"
+#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
#include "hw/riscv/dm.h"
+#include "exec/cpu-common.h"
#include "migration/vmstate.h"
+#include "system/tcg.h"
+#include "target/riscv/cpu.h"
+#include "trace.h"
REG32(DMCONTROL, 0x40)
FIELD(DMCONTROL, DMACTIVE, 0, 1)
+ FIELD(DMCONTROL, NDMRESET, 1, 1)
+ FIELD(DMCONTROL, CLRRESETHALTREQ, 2, 1)
+ FIELD(DMCONTROL, SETRESETHALTREQ, 3, 1)
FIELD(DMCONTROL, HARTSELLO, 6, 10)
FIELD(DMCONTROL, HARTSELHI, 16, 10)
FIELD(DMCONTROL, HASEL, 26, 1)
+ FIELD(DMCONTROL, ACKHAVERESET, 28, 1)
+ FIELD(DMCONTROL, HARTRESET, 29, 1)
+ FIELD(DMCONTROL, RESUMEREQ, 30, 1)
+ FIELD(DMCONTROL, HALTREQ, 31, 1)
REG32(DMSTATUS, 0x44)
FIELD(DMSTATUS, VERSION, 0, 4)
@@ -28,6 +41,8 @@ REG32(DMSTATUS, 0x44)
FIELD(DMSTATUS, ALLHALTED, 9, 1)
FIELD(DMSTATUS, ANYRUNNING, 10, 1)
FIELD(DMSTATUS, ALLRUNNING, 11, 1)
+ FIELD(DMSTATUS, ANYUNAVAIL, 12, 1)
+ FIELD(DMSTATUS, ALLUNAVAIL, 13, 1)
FIELD(DMSTATUS, ANYNONEXISTENT, 14, 1)
FIELD(DMSTATUS, ALLNONEXISTENT, 15, 1)
FIELD(DMSTATUS, ANYRESUMEACK, 16, 1)
@@ -35,6 +50,7 @@ REG32(DMSTATUS, 0x44)
FIELD(DMSTATUS, ANYHAVERESET, 18, 1)
FIELD(DMSTATUS, ALLHAVERESET, 19, 1)
FIELD(DMSTATUS, IMPEBREAK, 22, 1)
+ FIELD(DMSTATUS, NDMRESETPENDING, 24, 1)
REG32(HARTINFO, 0x48)
FIELD(HARTINFO, DATAADDR, 0, 12)
@@ -42,12 +58,28 @@ REG32(HARTINFO, 0x48)
FIELD(HARTINFO, DATAACCESS, 16, 1)
FIELD(HARTINFO, NSCRATCH, 20, 4)
+REG32(HALTSUM1, 0x4c)
+
+REG32(HAWINDOWSEL, 0x50)
+ FIELD(HAWINDOWSEL, HAWINDOWSEL, 0, 15)
+
+REG32(HAWINDOW, 0x54)
+
REG32(ABSTRACTCS, 0x58)
FIELD(ABSTRACTCS, DATACOUNT, 0, 4)
FIELD(ABSTRACTCS, CMDERR, 8, 3)
FIELD(ABSTRACTCS, BUSY, 12, 1)
FIELD(ABSTRACTCS, PROGBUFSIZE, 24, 5)
+REG32(HALTSUM0, 0x100)
+
+typedef struct DMHartSelection {
+ int all[RISCV_DM_HAWINDOW_SIZE + 1];
+ int all_count;
+ int harts[RISCV_DM_HAWINDOW_SIZE + 1];
+ int valid_count;
+} DMHartSelection;
+
static inline uint32_t dm_get_hartsel(RISCVDMState *s)
{
uint32_t hi = ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELHI);
@@ -61,46 +93,306 @@ static inline bool dm_hart_valid(RISCVDMState *s, uint32_t hartsel)
return hartsel < s->num_harts;
}
-static void dm_status_refresh(RISCVDMState *s)
+static inline uint32_t dm_rom_read32(RISCVDMState *s, uint32_t offset)
+{
+ return ldl_le_p(s->rom_ptr + offset);
+}
+
+static inline void dm_rom_write32(RISCVDMState *s, uint32_t offset,
+ uint32_t val)
+{
+ stl_le_p(s->rom_ptr + offset, val);
+}
+
+static inline void dm_rom_write8(RISCVDMState *s, uint32_t offset, uint8_t val)
+{
+ s->rom_ptr[offset] = val;
+}
+
+static void dm_selection_add(RISCVDMState *s, DMHartSelection *sel, int hartsel)
+{
+ for (int i = 0; i < sel->all_count; i++) {
+ if (sel->all[i] == hartsel) {
+ return;
+ }
+ }
+
+ sel->all[sel->all_count++] = hartsel;
+ if (dm_hart_valid(s, hartsel)) {
+ sel->harts[sel->valid_count++] = hartsel;
+ }
+}
+
+static void dm_collect_selected_harts(RISCVDMState *s, DMHartSelection *sel)
{
uint32_t hartsel = dm_get_hartsel(s);
- bool valid = dm_hart_valid(s, hartsel);
- bool halted = valid && s->hart_halted[hartsel];
- bool running = valid && !s->hart_halted[hartsel];
- bool resumeack = valid && s->hart_resumeack[hartsel];
- bool havereset = valid && s->hart_havereset[hartsel];
-
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYNONEXISTENT, !valid);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLNONEXISTENT, !valid);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHALTED, halted);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHALTED, halted);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRUNNING, running);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRUNNING, running);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRESUMEACK, resumeack);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRESUMEACK, resumeack);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHAVERESET, havereset);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHAVERESET, havereset);
+ uint32_t wsel;
+ uint32_t window;
+ uint32_t base;
+
+ memset(sel, 0, sizeof(*sel));
+ dm_selection_add(s, sel, hartsel);
+
+ if (!ARRAY_FIELD_EX32(s->regs, DMCONTROL, HASEL)) {
+ return;
+ }
+
+ wsel = ARRAY_FIELD_EX32(s->regs, HAWINDOWSEL, HAWINDOWSEL);
+ if (wsel >= RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE) {
+ return;
+ }
+
+ window = s->hawindow[wsel];
+ base = wsel * RISCV_DM_HAWINDOW_SIZE;
+ for (int i = 0; i < RISCV_DM_HAWINDOW_SIZE; i++) {
+ if ((window >> i) & 1) {
+ dm_selection_add(s, sel, base + i);
+ }
+ }
+}
+
+static inline bool dm_ndmreset_active(RISCVDMState *s)
+{
+ return ARRAY_FIELD_EX32(s->regs, DMCONTROL, NDMRESET);
+}
+
+static bool dm_reg_present(RISCVDMState *s, hwaddr addr)
+{
+ switch (addr) {
+ case A_HALTSUM1:
+ return s->num_harts >= 33;
+ default:
+ return true;
+ }
+}
+
+static void dm_status_refresh(RISCVDMState *s)
+{
+ DMHartSelection sel;
+ bool anyhalted = false;
+ bool allhalted = true;
+ bool anyrunning = false;
+ bool allrunning = true;
+ bool anyunavail = false;
+ bool allunavail = true;
+ bool anyresumeack = false;
+ bool allresumeack = true;
+ bool anyhavereset = false;
+ bool allhavereset = true;
+ bool anynonexistent;
+ bool allnonexistent;
+ bool reset_unavail = dm_ndmreset_active(s) ||
+ ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTRESET);
+
+ dm_collect_selected_harts(s, &sel);
+
+ anynonexistent = sel.all_count > sel.valid_count;
+ allnonexistent = sel.valid_count == 0 && sel.all_count > 0;
+
+ if (sel.valid_count == 0) {
+ allhalted = false;
+ allrunning = false;
+ allunavail = false;
+ allresumeack = false;
+ allhavereset = false;
+ }
+
+ for (int i = 0; i < sel.valid_count; i++) {
+ int h = sel.harts[i];
+ bool halted = s->hart_halted[h];
+ bool resumeack = s->hart_resumeack[h];
+ bool havereset = s->hart_havereset[h];
+
+ if (reset_unavail) {
+ anyunavail = true;
+ allhalted = false;
+ allrunning = false;
+ } else {
+ anyhalted |= halted;
+ allhalted &= halted;
+ anyrunning |= !halted;
+ allrunning &= !halted;
+ }
+
+ allunavail &= reset_unavail;
+ anyresumeack |= resumeack;
+ allresumeack &= resumeack;
+ anyhavereset |= havereset;
+ allhavereset &= havereset;
+ }
+
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHALTED, anyhalted);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHALTED, allhalted);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRUNNING, anyrunning);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRUNNING, allrunning);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYUNAVAIL, anyunavail);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLUNAVAIL, allunavail);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYNONEXISTENT, anynonexistent);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLNONEXISTENT, allnonexistent);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRESUMEACK, anyresumeack);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRESUMEACK, allresumeack);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHAVERESET, anyhavereset);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHAVERESET, allhavereset);
ARRAY_FIELD_DP32(s->regs, DMSTATUS, HASRESETHALTREQ, 1);
}
static void dm_debug_reset(RISCVDMState *s);
+static void dm_cpu_reset_on_cpu(CPUState *cpu, run_on_cpu_data data)
+{
+ (void)data;
+ cpu_reset(cpu);
+}
+
+static void dm_note_hart_reset(RISCVDMState *s, uint32_t hartsel)
+{
+ CPUState *cs = qemu_get_cpu(hartsel);
+
+ s->hart_halted[hartsel] = false;
+ s->hart_resumeack[hartsel] = false;
+ s->hart_havereset[hartsel] = true;
+ dm_rom_write8(s, RISCV_DM_ROM_FLAGS + hartsel, RISCV_DM_FLAG_CLEAR);
+
+ if (cs && s->hart_resethaltreq[hartsel]) {
+ riscv_cpu_request_dm_halt(RISCV_CPU(cs), DCSR_CAUSE_RESET);
+ }
+}
+
+static void dm_reset_hart(RISCVDMState *s, uint32_t hartsel)
+{
+ CPUState *cs;
+
+ if (!dm_hart_valid(s, hartsel)) {
+ return;
+ }
+
+ cs = qemu_get_cpu(hartsel);
+ if (cs && tcg_enabled()) {
+ run_on_cpu(cs, dm_cpu_reset_on_cpu, RUN_ON_CPU_NULL);
+ }
+
+ dm_note_hart_reset(s, hartsel);
+}
+
+static void dm_reset_selected_harts(RISCVDMState *s, DMHartSelection *sel)
+{
+ for (int i = 0; i < sel->valid_count; i++) {
+ dm_reset_hart(s, sel->harts[i]);
+ }
+}
+
+static void dm_reset_all_harts(RISCVDMState *s)
+{
+ for (uint32_t hartsel = 0; hartsel < s->num_harts; hartsel++) {
+ dm_reset_hart(s, hartsel);
+ }
+}
+
static uint64_t dm_dmcontrol_pre_write(RegisterInfo *reg, uint64_t val64)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
uint32_t val = val64;
+ uint32_t cur_ctl = s->regs[R_DMCONTROL];
+ bool busy = ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY);
+ bool ndmreset_was = FIELD_EX32(cur_ctl, DMCONTROL, NDMRESET);
+ bool hartreset_was = FIELD_EX32(cur_ctl, DMCONTROL, HARTRESET);
+ bool dmactive = FIELD_EX32(val, DMCONTROL, DMACTIVE);
+ DMHartSelection sel;
+ uint32_t stored = 0;
+
+ trace_riscv_dm_control_write(s->regs[R_DMCONTROL], val, busy);
+
+ if (busy) {
+ val = FIELD_DP32(val, DMCONTROL, HARTSELHI,
+ ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELHI));
+ val = FIELD_DP32(val, DMCONTROL, HARTSELLO,
+ ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELLO));
+ val = FIELD_DP32(val, DMCONTROL, HASEL,
+ ARRAY_FIELD_EX32(s->regs, DMCONTROL, HASEL));
+ val = FIELD_DP32(val, DMCONTROL, HALTREQ, 0);
+ val = FIELD_DP32(val, DMCONTROL, RESUMEREQ, 0);
+ val = FIELD_DP32(val, DMCONTROL, ACKHAVERESET, 0);
+ }
- if (!FIELD_EX32(val, DMCONTROL, DMACTIVE)) {
+ if (!dmactive) {
dm_debug_reset(s);
return 0;
}
- s->dm_active = true;
-
- val = FIELD_DP32(val, DMCONTROL, DMACTIVE, 1);
s->regs[R_DMCONTROL] = val;
+ dm_collect_selected_harts(s, &sel);
+
+ if (FIELD_EX32(val, DMCONTROL, NDMRESET) && !ndmreset_was) {
+ dm_reset_all_harts(s);
+ }
+
+ if (FIELD_EX32(val, DMCONTROL, HARTRESET) && !hartreset_was) {
+ dm_reset_selected_harts(s, &sel);
+ }
+
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, NDMRESETPENDING,
+ FIELD_EX32(val, DMCONTROL, NDMRESET));
+
+ if (!busy && FIELD_EX32(val, DMCONTROL, ACKHAVERESET)) {
+ for (int i = 0; i < sel.valid_count; i++) {
+ s->hart_havereset[sel.harts[i]] = false;
+ }
+ }
+
+ if (!busy && FIELD_EX32(val, DMCONTROL, SETRESETHALTREQ)) {
+ for (int i = 0; i < sel.valid_count; i++) {
+ s->hart_resethaltreq[sel.harts[i]] = true;
+ }
+ }
+
+ if (!busy && FIELD_EX32(val, DMCONTROL, CLRRESETHALTREQ)) {
+ for (int i = 0; i < sel.valid_count; i++) {
+ s->hart_resethaltreq[sel.harts[i]] = false;
+ }
+ }
+
+ if (!busy && FIELD_EX32(val, DMCONTROL, RESUMEREQ) &&
+ !FIELD_EX32(val, DMCONTROL, HALTREQ)) {
+ for (int i = 0; i < sel.valid_count; i++) {
+ int h = sel.harts[i];
+
+ s->hart_resumeack[h] = false;
+ dm_rom_write8(s, RISCV_DM_ROM_FLAGS + h, RISCV_DM_FLAG_RESUME);
+ trace_riscv_dm_control_resume(h);
+ }
+ }
+
+ if (!busy) {
+ if (FIELD_EX32(val, DMCONTROL, HALTREQ)) {
+ for (int i = 0; i < sel.valid_count; i++) {
+ int h = sel.harts[i];
+
+ dm_rom_write8(s, RISCV_DM_ROM_FLAGS + h, RISCV_DM_FLAG_CLEAR);
+ qemu_set_irq(s->halt_irqs[h], 1);
+ trace_riscv_dm_control_halt(h);
+ }
+ } else {
+ for (int i = 0; i < sel.valid_count; i++) {
+ qemu_set_irq(s->halt_irqs[sel.harts[i]], 0);
+ }
+ }
+ }
+
+ stored = FIELD_DP32(stored, DMCONTROL, DMACTIVE, 1);
+ stored = FIELD_DP32(stored, DMCONTROL, NDMRESET,
+ FIELD_EX32(val, DMCONTROL, NDMRESET));
+ stored = FIELD_DP32(stored, DMCONTROL, HARTSELLO,
+ FIELD_EX32(val, DMCONTROL, HARTSELLO));
+ stored = FIELD_DP32(stored, DMCONTROL, HARTSELHI,
+ FIELD_EX32(val, DMCONTROL, HARTSELHI));
+ stored = FIELD_DP32(stored, DMCONTROL, HASEL,
+ FIELD_EX32(val, DMCONTROL, HASEL));
+ stored = FIELD_DP32(stored, DMCONTROL, HARTRESET,
+ FIELD_EX32(val, DMCONTROL, HARTRESET));
+
+ s->dm_active = true;
dm_status_refresh(s);
- return val;
+ return stored;
}
static uint64_t dm_dmstatus_post_read(RegisterInfo *reg, uint64_t val)
@@ -108,7 +400,7 @@ static uint64_t dm_dmstatus_post_read(RegisterInfo *reg, uint64_t val)
RISCVDMState *s = RISCV_DM(reg->opaque);
dm_status_refresh(s);
- return val;
+ return s->regs[R_DMSTATUS];
}
static uint64_t dm_hartinfo_post_read(RegisterInfo *reg, uint64_t val)
@@ -123,6 +415,72 @@ static uint64_t dm_hartinfo_post_read(RegisterInfo *reg, uint64_t val)
return v;
}
+static uint64_t dm_hawindowsel_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ uint32_t max_sel = RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE;
+ uint32_t val = val64 & R_HAWINDOWSEL_HAWINDOWSEL_MASK;
+
+ (void)reg;
+ if (val >= max_sel) {
+ val = max_sel - 1;
+ }
+ return val;
+}
+
+static uint64_t dm_hawindow_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ uint32_t wsel = ARRAY_FIELD_EX32(s->regs, HAWINDOWSEL, HAWINDOWSEL);
+
+ if (wsel < RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE) {
+ s->hawindow[wsel] = val64;
+ }
+ return (uint32_t)val64;
+}
+
+static uint64_t dm_hawindow_post_read(RegisterInfo *reg, uint64_t val)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ uint32_t wsel = ARRAY_FIELD_EX32(s->regs, HAWINDOWSEL, HAWINDOWSEL);
+
+ (void)val;
+ if (wsel < RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE) {
+ return s->hawindow[wsel];
+ }
+ return 0;
+}
+
+static uint64_t dm_haltsum0_post_read(RegisterInfo *reg, uint64_t val)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ uint32_t sum = 0;
+ uint32_t base = 0;
+ uint32_t limit = MIN(s->num_harts, 32u);
+
+ (void)val;
+ for (uint32_t h = base; h < limit; h++) {
+ if (s->hart_halted[h]) {
+ sum |= 1u << (h - base);
+ }
+ }
+ return sum;
+}
+
+static uint64_t dm_haltsum1_post_read(RegisterInfo *reg, uint64_t val)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ uint32_t sum = 0;
+
+ (void)reg;
+ (void)val;
+ for (uint32_t h = 0; h < s->num_harts; h++) {
+ if (s->hart_halted[h]) {
+ sum |= 1u << (h / 32);
+ }
+ }
+ return sum;
+}
+
static RegisterAccessInfo riscv_dm_regs_info[] = {
{ .name = "DMCONTROL", .addr = A_DMCONTROL,
.pre_write = dm_dmcontrol_pre_write, },
@@ -132,8 +490,19 @@ static RegisterAccessInfo riscv_dm_regs_info[] = {
{ .name = "HARTINFO", .addr = A_HARTINFO,
.ro = 0xffffffff,
.post_read = dm_hartinfo_post_read, },
+ { .name = "HALTSUM1", .addr = A_HALTSUM1,
+ .ro = 0xffffffff,
+ .post_read = dm_haltsum1_post_read, },
+ { .name = "HAWINDOWSEL", .addr = A_HAWINDOWSEL,
+ .pre_write = dm_hawindowsel_pre_write, },
+ { .name = "HAWINDOW", .addr = A_HAWINDOW,
+ .pre_write = dm_hawindow_pre_write,
+ .post_read = dm_hawindow_post_read, },
{ .name = "ABSTRACTCS", .addr = A_ABSTRACTCS,
.ro = 0xffffffff, },
+ { .name = "HALTSUM0", .addr = A_HALTSUM0,
+ .ro = 0xffffffff,
+ .post_read = dm_haltsum0_post_read, },
};
static uint64_t riscv_dm_read(void *opaque, hwaddr addr, unsigned size)
@@ -145,6 +514,19 @@ static uint64_t riscv_dm_read(void *opaque, hwaddr addr, unsigned size)
return 0;
}
+ if (dm_ndmreset_active(s)) {
+ if (addr == A_DMSTATUS) {
+ return s->regs[R_DMSTATUS] & R_DMSTATUS_NDMRESETPENDING_MASK;
+ }
+ if (addr != A_DMCONTROL) {
+ return 0;
+ }
+ }
+
+ if (!dm_reg_present(s, addr)) {
+ return 0;
+ }
+
return register_read_memory(opaque, addr, size);
}
@@ -158,6 +540,14 @@ static void riscv_dm_write(void *opaque, hwaddr addr,
return;
}
+ if (dm_ndmreset_active(s) && addr != A_DMCONTROL) {
+ return;
+ }
+
+ if (!dm_reg_present(s, addr)) {
+ return;
+ }
+
register_write_memory(opaque, addr, value, size);
}
@@ -181,29 +571,54 @@ static const MemoryRegionOps riscv_dm_ops = {
static uint64_t dm_rom_read(void *opaque, hwaddr offset, unsigned size)
{
RISCVDMState *s = opaque;
+ uint64_t ret;
- return ldn_le_p(s->rom_ptr + offset, size);
+ ret = ldn_le_p(s->rom_ptr + offset, size);
+ trace_riscv_dm_rom_access(offset, ret, size, false);
+ return ret;
}
static void dm_rom_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
RISCVDMState *s = opaque;
+ uint32_t hartsel;
stn_le_p(s->rom_ptr + offset, size, value);
+ trace_riscv_dm_rom_access(offset, value, size, true);
+
if (offset == RISCV_DM_ROM_HARTID && size == 4) {
- riscv_dm_hart_halted(s, value);
+ hartsel = value;
+ if (dm_hart_valid(s, hartsel)) {
+ riscv_dm_hart_halted(s, hartsel);
+ }
+ return;
+ }
+
+ if (offset == RISCV_DM_ROM_GOING && size == 4) {
+ hartsel = dm_rom_read32(s, RISCV_DM_ROM_HARTID);
+ if (dm_hart_valid(s, hartsel)) {
+ dm_rom_write8(s, RISCV_DM_ROM_FLAGS + hartsel,
+ RISCV_DM_FLAG_CLEAR);
+ trace_riscv_dm_going(hartsel);
+ }
return;
}
if (offset == RISCV_DM_ROM_RESUME && size == 4) {
- riscv_dm_hart_resumed(s, value);
+ hartsel = value;
+ if (dm_hart_valid(s, hartsel)) {
+ riscv_dm_hart_resumed(s, hartsel);
+ }
return;
}
if (offset == RISCV_DM_ROM_EXCP && size == 4) {
- riscv_dm_abstracts_exception(s, dm_get_hartsel(s));
+ hartsel = dm_rom_read32(s, RISCV_DM_ROM_HARTID);
+ if (dm_hart_valid(s, hartsel)) {
+ riscv_dm_abstracts_exception(s, hartsel);
+ }
}
}
@@ -250,6 +665,7 @@ void riscv_dm_hart_halted(RISCVDMState *s, uint32_t hartsel)
s->hart_halted[hartsel] = true;
riscv_dm_abstracts_done(s, hartsel);
dm_status_refresh(s);
+ trace_riscv_dm_hart_halted(hartsel);
}
void riscv_dm_hart_resumed(RISCVDMState *s, uint32_t hartsel)
@@ -260,21 +676,23 @@ void riscv_dm_hart_resumed(RISCVDMState *s, uint32_t hartsel)
s->hart_halted[hartsel] = false;
s->hart_resumeack[hartsel] = true;
+ dm_rom_write8(s, RISCV_DM_ROM_FLAGS + hartsel, RISCV_DM_FLAG_CLEAR);
dm_status_refresh(s);
+ trace_riscv_dm_hart_resumed(hartsel);
}
void riscv_dm_abstracts_done(RISCVDMState *s, uint32_t hartsel)
{
- (void)hartsel;
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, BUSY, 0);
+ trace_riscv_dm_abstract_cmd_complete(hartsel);
}
void riscv_dm_abstracts_exception(RISCVDMState *s, uint32_t hartsel)
{
- (void)hartsel;
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, BUSY, 0);
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, CMDERR,
RISCV_DM_CMDERR_EXCEPTION);
+ trace_riscv_dm_abstract_cmd_exception(hartsel);
}
static void dm_debug_reset(RISCVDMState *s)
@@ -287,11 +705,15 @@ static void dm_debug_reset(RISCVDMState *s)
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, PROGBUFSIZE, s->progbuf_size);
if (s->hart_halted) {
- memset(s->hart_halted, 0, s->num_harts * sizeof(bool));
- memset(s->hart_resumeack, 0, s->num_harts * sizeof(bool));
- memset(s->hart_havereset, 1, s->num_harts * sizeof(bool));
+ for (uint32_t i = 0; i < s->num_harts; i++) {
+ s->hart_halted[i] = false;
+ s->hart_resumeack[i] = false;
+ s->hart_havereset[i] = true;
+ s->hart_resethaltreq[i] = false;
+ }
}
+ memset(s->hawindow, 0, sizeof(s->hawindow));
s->dm_active = false;
if (s->rom_ptr) {
@@ -324,11 +746,21 @@ static void riscv_dm_realize(DeviceState *dev, Error **errp)
return;
}
- s->hart_halted = g_new0(bool, s->num_harts);
- s->hart_resumeack = g_new0(bool, s->num_harts);
- s->hart_havereset = g_new0(bool, s->num_harts);
+ if (s->num_harts > 0) {
+ s->hart_halted = g_new0(bool, s->num_harts);
+ s->hart_resumeack = g_new0(bool, s->num_harts);
+ s->hart_havereset = g_new0(bool, s->num_harts);
+ s->hart_resethaltreq = g_new0(bool, s->num_harts);
+ s->halt_irqs = g_new(qemu_irq, s->num_harts);
+ qdev_init_gpio_out(dev, s->halt_irqs, s->num_harts);
+ }
if (!dm_rom_realize(s, errp)) {
+ g_free(s->hart_halted);
+ g_free(s->hart_resumeack);
+ g_free(s->hart_havereset);
+ g_free(s->hart_resethaltreq);
+ g_free(s->halt_irqs);
return;
}
@@ -348,6 +780,8 @@ static void riscv_dm_unrealize(DeviceState *dev)
g_free(s->hart_halted);
g_free(s->hart_resumeack);
g_free(s->hart_havereset);
+ g_free(s->hart_resethaltreq);
+ g_free(s->halt_irqs);
}
static const Property riscv_dm_props[] = {
@@ -372,6 +806,8 @@ static const VMStateDescription vmstate_riscv_dm = {
0, vmstate_info_bool, bool),
VMSTATE_VARRAY_UINT32(hart_havereset, RISCVDMState, num_harts,
0, vmstate_info_bool, bool),
+ VMSTATE_VARRAY_UINT32(hart_resethaltreq, RISCVDMState, num_harts,
+ 0, vmstate_info_bool, bool),
VMSTATE_END_OF_LIST(),
}
};
diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events
index b50b14a654..020e26607f 100644
--- a/hw/riscv/trace-events
+++ b/hw/riscv/trace-events
@@ -1,5 +1,19 @@
# See documentation at docs/devel/tracing.rst
+# dm.c
+riscv_dm_control_write(uint32_t old_val, uint32_t new_val, bool busy) "old=0x%08x new=0x%08x busy=%d"
+riscv_dm_control_halt(uint32_t hartsel) "hartsel=%u"
+riscv_dm_control_resume(uint32_t hartsel) "hartsel=%u"
+riscv_dm_hart_halted(uint32_t hartsel) "hartsel=%u"
+riscv_dm_hart_resumed(uint32_t hartsel) "hartsel=%u"
+riscv_dm_abstract_cmd_submit(uint32_t cmdtype, uint32_t regno, bool transfer, bool write, bool aarpostinc, bool postexec) "cmdtype=%u regno=0x%04x transfer=%d write=%d aarpostinc=%d postexec=%d"
+riscv_dm_abstract_cmd_complete(uint32_t hartsel) "hartsel=%u"
+riscv_dm_abstract_cmd_exception(uint32_t hartsel) "hartsel=%u"
+riscv_dm_abstract_cmd_rejected(const char *reason, uint32_t cmderr) "reason=%s cmderr=%u"
+riscv_dm_sba_access(uint64_t addr, uint32_t data, int bytes, bool is_write) "addr=0x%"PRIx64" data=0x%08x bytes=%d write=%d"
+riscv_dm_rom_access(uint64_t offset, uint64_t value, unsigned size, bool is_write) "offset=0x%"PRIx64" value=0x%"PRIx64" size=%u write=%d"
+riscv_dm_going(uint32_t hartsel) "hartsel=%u"
+
# riscv-iommu.c
riscv_iommu_new(const char *id, unsigned b, unsigned d, unsigned f) "%s: device attached %04x:%02x.%d"
riscv_iommu_flt(const char *id, unsigned b, unsigned d, unsigned f, uint64_t reason, uint64_t iova) "%s: fault %04x:%02x.%u reason: 0x%"PRIx64" iova: 0x%"PRIx64
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 18/28] hw/riscv/dm: queue reset halts from the reset work item
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (16 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 17/28] hw/riscv/dm: add hart run-control operations Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 19/28] hw/riscv/dm: add DMI register declarations and ROM entry program Chao Liu
` (9 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Queue reset-halt requests from the CPU reset work item so the
halt is requested after the hart reset has completed, not before
it starts.
Keeping the ordering in one place avoids racing the pending halt
against the code that clears hart state and ROM mailbox flags,
and it works the same way for both TCG and non-TCG reset paths.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/dm.c | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c
index d55b5b827d..760cae27e2 100644
--- a/hw/riscv/dm.c
+++ b/hw/riscv/dm.c
@@ -240,22 +240,20 @@ static void dm_debug_reset(RISCVDMState *s);
static void dm_cpu_reset_on_cpu(CPUState *cpu, run_on_cpu_data data)
{
- (void)data;
+ bool request_reset_halt = data.host_int;
+
cpu_reset(cpu);
+ if (request_reset_halt) {
+ riscv_cpu_request_dm_halt(RISCV_CPU(cpu), DCSR_CAUSE_RESET);
+ }
}
static void dm_note_hart_reset(RISCVDMState *s, uint32_t hartsel)
{
- CPUState *cs = qemu_get_cpu(hartsel);
-
s->hart_halted[hartsel] = false;
s->hart_resumeack[hartsel] = false;
s->hart_havereset[hartsel] = true;
dm_rom_write8(s, RISCV_DM_ROM_FLAGS + hartsel, RISCV_DM_FLAG_CLEAR);
-
- if (cs && s->hart_resethaltreq[hartsel]) {
- riscv_cpu_request_dm_halt(RISCV_CPU(cs), DCSR_CAUSE_RESET);
- }
}
static void dm_reset_hart(RISCVDMState *s, uint32_t hartsel)
@@ -268,10 +266,15 @@ static void dm_reset_hart(RISCVDMState *s, uint32_t hartsel)
cs = qemu_get_cpu(hartsel);
if (cs && tcg_enabled()) {
- run_on_cpu(cs, dm_cpu_reset_on_cpu, RUN_ON_CPU_NULL);
+ run_on_cpu(cs, dm_cpu_reset_on_cpu,
+ RUN_ON_CPU_HOST_INT(s->hart_resethaltreq[hartsel]));
}
dm_note_hart_reset(s, hartsel);
+
+ if (cs && !tcg_enabled() && s->hart_resethaltreq[hartsel]) {
+ riscv_cpu_request_dm_halt(RISCV_CPU(cs), DCSR_CAUSE_RESET);
+ }
}
static void dm_reset_selected_harts(RISCVDMState *s, DMHartSelection *sel)
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 19/28] hw/riscv/dm: add DMI register declarations and ROM entry program
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (17 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 18/28] hw/riscv/dm: queue reset halts from the reset work item Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 20/28] hw/riscv/dm: add register handlers and update state management Chao Liu
` (8 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Add the complete set of DMI register field declarations per the
Debug Specification v1.0 (DATA0-11, COMMAND, ABSTRACTAUTO,
PROGBUF0-15, SBCS, SBADDRESS/DATA, etc.) and the instruction
builder macros used to generate abstract command sequences.
Lay down the ROM entry program (park loop, resume, exception
vectors) and the ROM state management helpers (flush, sync,
reset). Update dm_debug_reset() to use the new infrastructure.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/dm.c | 423 ++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 373 insertions(+), 50 deletions(-)
diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c
index 760cae27e2..427507e77e 100644
--- a/hw/riscv/dm.c
+++ b/hw/riscv/dm.c
@@ -4,61 +4,85 @@
* Copyright (c) 2025 Chao Liu <chao.liu.zevorn@gmail.com>
*
* Based on the RISC-V Debug Specification v1.0 (ratified 2025-02-21)
+ * Uses the QEMU register.h framework for declarative register definitions.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
+#include "qemu/log.h"
#include "qapi/error.h"
#include "hw/core/cpu.h"
-#include "hw/core/irq.h"
#include "hw/core/qdev-properties.h"
+#include "hw/core/irq.h"
#include "hw/riscv/dm.h"
#include "exec/cpu-common.h"
#include "migration/vmstate.h"
+#include "exec/translation-block.h"
#include "system/tcg.h"
#include "target/riscv/cpu.h"
#include "trace.h"
+/* Addresses = DMI word address × 4 (byte offsets). */
+
+REG32(DATA0, 0x10)
+REG32(DATA1, 0x14)
+REG32(DATA2, 0x18)
+REG32(DATA3, 0x1C)
+REG32(DATA4, 0x20)
+REG32(DATA5, 0x24)
+REG32(DATA6, 0x28)
+REG32(DATA7, 0x2C)
+REG32(DATA8, 0x30)
+REG32(DATA9, 0x34)
+REG32(DATA10, 0x38)
+REG32(DATA11, 0x3C)
+
REG32(DMCONTROL, 0x40)
- FIELD(DMCONTROL, DMACTIVE, 0, 1)
- FIELD(DMCONTROL, NDMRESET, 1, 1)
- FIELD(DMCONTROL, CLRRESETHALTREQ, 2, 1)
- FIELD(DMCONTROL, SETRESETHALTREQ, 3, 1)
- FIELD(DMCONTROL, HARTSELLO, 6, 10)
- FIELD(DMCONTROL, HARTSELHI, 16, 10)
- FIELD(DMCONTROL, HASEL, 26, 1)
- FIELD(DMCONTROL, ACKHAVERESET, 28, 1)
- FIELD(DMCONTROL, HARTRESET, 29, 1)
- FIELD(DMCONTROL, RESUMEREQ, 30, 1)
- FIELD(DMCONTROL, HALTREQ, 31, 1)
+ FIELD(DMCONTROL, DMACTIVE, 0, 1)
+ FIELD(DMCONTROL, NDMRESET, 1, 1)
+ FIELD(DMCONTROL, CLRRESETHALTREQ, 2, 1)
+ FIELD(DMCONTROL, SETRESETHALTREQ, 3, 1)
+ FIELD(DMCONTROL, CLRKEEPALIVE, 4, 1)
+ FIELD(DMCONTROL, SETKEEPALIVE, 5, 1)
+ FIELD(DMCONTROL, HARTSELLO, 6, 10)
+ FIELD(DMCONTROL, HARTSELHI, 16, 10)
+ FIELD(DMCONTROL, HASEL, 26, 1)
+ FIELD(DMCONTROL, ACKUNAVAIL, 27, 1)
+ FIELD(DMCONTROL, ACKHAVERESET, 28, 1)
+ FIELD(DMCONTROL, HARTRESET, 29, 1)
+ FIELD(DMCONTROL, RESUMEREQ, 30, 1)
+ FIELD(DMCONTROL, HALTREQ, 31, 1)
REG32(DMSTATUS, 0x44)
- FIELD(DMSTATUS, VERSION, 0, 4)
- FIELD(DMSTATUS, HASRESETHALTREQ, 5, 1)
- FIELD(DMSTATUS, AUTHENTICATED, 7, 1)
- FIELD(DMSTATUS, ANYHALTED, 8, 1)
- FIELD(DMSTATUS, ALLHALTED, 9, 1)
- FIELD(DMSTATUS, ANYRUNNING, 10, 1)
- FIELD(DMSTATUS, ALLRUNNING, 11, 1)
- FIELD(DMSTATUS, ANYUNAVAIL, 12, 1)
- FIELD(DMSTATUS, ALLUNAVAIL, 13, 1)
- FIELD(DMSTATUS, ANYNONEXISTENT, 14, 1)
- FIELD(DMSTATUS, ALLNONEXISTENT, 15, 1)
- FIELD(DMSTATUS, ANYRESUMEACK, 16, 1)
- FIELD(DMSTATUS, ALLRESUMEACK, 17, 1)
- FIELD(DMSTATUS, ANYHAVERESET, 18, 1)
- FIELD(DMSTATUS, ALLHAVERESET, 19, 1)
- FIELD(DMSTATUS, IMPEBREAK, 22, 1)
+ FIELD(DMSTATUS, VERSION, 0, 4)
+ FIELD(DMSTATUS, CONFSTRPTRVALID, 4, 1)
+ FIELD(DMSTATUS, HASRESETHALTREQ, 5, 1)
+ FIELD(DMSTATUS, AUTHBUSY, 6, 1)
+ FIELD(DMSTATUS, AUTHENTICATED, 7, 1)
+ FIELD(DMSTATUS, ANYHALTED, 8, 1)
+ FIELD(DMSTATUS, ALLHALTED, 9, 1)
+ FIELD(DMSTATUS, ANYRUNNING, 10, 1)
+ FIELD(DMSTATUS, ALLRUNNING, 11, 1)
+ FIELD(DMSTATUS, ANYUNAVAIL, 12, 1)
+ FIELD(DMSTATUS, ALLUNAVAIL, 13, 1)
+ FIELD(DMSTATUS, ANYNONEXISTENT, 14, 1)
+ FIELD(DMSTATUS, ALLNONEXISTENT, 15, 1)
+ FIELD(DMSTATUS, ANYRESUMEACK, 16, 1)
+ FIELD(DMSTATUS, ALLRESUMEACK, 17, 1)
+ FIELD(DMSTATUS, ANYHAVERESET, 18, 1)
+ FIELD(DMSTATUS, ALLHAVERESET, 19, 1)
+ FIELD(DMSTATUS, IMPEBREAK, 22, 1)
+ FIELD(DMSTATUS, STICKYUNAVAIL, 23, 1)
FIELD(DMSTATUS, NDMRESETPENDING, 24, 1)
REG32(HARTINFO, 0x48)
- FIELD(HARTINFO, DATAADDR, 0, 12)
- FIELD(HARTINFO, DATASIZE, 12, 4)
- FIELD(HARTINFO, DATAACCESS, 16, 1)
- FIELD(HARTINFO, NSCRATCH, 20, 4)
+ FIELD(HARTINFO, DATAADDR, 0, 12)
+ FIELD(HARTINFO, DATASIZE, 12, 4)
+ FIELD(HARTINFO, DATAACCESS, 16, 1)
+ FIELD(HARTINFO, NSCRATCH, 20, 4)
-REG32(HALTSUM1, 0x4c)
+REG32(HALTSUM1, 0x4C)
REG32(HAWINDOWSEL, 0x50)
FIELD(HAWINDOWSEL, HAWINDOWSEL, 0, 15)
@@ -66,13 +90,144 @@ REG32(HAWINDOWSEL, 0x50)
REG32(HAWINDOW, 0x54)
REG32(ABSTRACTCS, 0x58)
- FIELD(ABSTRACTCS, DATACOUNT, 0, 4)
- FIELD(ABSTRACTCS, CMDERR, 8, 3)
- FIELD(ABSTRACTCS, BUSY, 12, 1)
+ FIELD(ABSTRACTCS, DATACOUNT, 0, 4)
+ FIELD(ABSTRACTCS, CMDERR, 8, 3)
+ FIELD(ABSTRACTCS, BUSY, 12, 1)
FIELD(ABSTRACTCS, PROGBUFSIZE, 24, 5)
+REG32(COMMAND, 0x5C)
+ FIELD(COMMAND, REGNO, 0, 16)
+ FIELD(COMMAND, WRITE, 16, 1)
+ FIELD(COMMAND, TRANSFER, 17, 1)
+ FIELD(COMMAND, POSTEXEC, 18, 1)
+ FIELD(COMMAND, AARPOSTINCREMENT, 19, 1)
+ FIELD(COMMAND, AARSIZE, 20, 3)
+ FIELD(COMMAND, CMDTYPE, 24, 8)
+
+REG32(ABSTRACTAUTO, 0x60)
+ FIELD(ABSTRACTAUTO, AUTOEXECDATA, 0, 12)
+ FIELD(ABSTRACTAUTO, AUTOEXECPROGBUF, 16, 16)
+
+REG32(CONFSTRPTR0, 0x64)
+REG32(CONFSTRPTR1, 0x68)
+REG32(CONFSTRPTR2, 0x6C)
+REG32(CONFSTRPTR3, 0x70)
+
+REG32(NEXTDM, 0x74)
+
+REG32(PROGBUF0, 0x80)
+REG32(PROGBUF1, 0x84)
+REG32(PROGBUF2, 0x88)
+REG32(PROGBUF3, 0x8C)
+REG32(PROGBUF4, 0x90)
+REG32(PROGBUF5, 0x94)
+REG32(PROGBUF6, 0x98)
+REG32(PROGBUF7, 0x9C)
+REG32(PROGBUF8, 0xA0)
+REG32(PROGBUF9, 0xA4)
+REG32(PROGBUF10, 0xA8)
+REG32(PROGBUF11, 0xAC)
+REG32(PROGBUF12, 0xB0)
+REG32(PROGBUF13, 0xB4)
+REG32(PROGBUF14, 0xB8)
+REG32(PROGBUF15, 0xBC)
+
+REG32(AUTHDATA, 0xC0)
+
+REG32(DMCS2, 0xC8)
+ FIELD(DMCS2, HGSELECT, 0, 1)
+ FIELD(DMCS2, HGWRITE, 1, 1)
+ FIELD(DMCS2, GROUP, 2, 5)
+ FIELD(DMCS2, DMEXTTRIGGER, 7, 4)
+ FIELD(DMCS2, GROUPTYPE, 11, 1)
+
+REG32(HALTSUM2, 0xD0)
+REG32(HALTSUM3, 0xD4)
+
+REG32(SBADDRESS3, 0xDC)
+
+REG32(SBCS, 0xE0)
+ FIELD(SBCS, SBACCESS8, 0, 1)
+ FIELD(SBCS, SBACCESS16, 1, 1)
+ FIELD(SBCS, SBACCESS32, 2, 1)
+ FIELD(SBCS, SBACCESS64, 3, 1)
+ FIELD(SBCS, SBACCESS128, 4, 1)
+ FIELD(SBCS, SBASIZE, 5, 7)
+ FIELD(SBCS, SBERROR, 12, 3)
+ FIELD(SBCS, SBREADONDATA, 15, 1)
+ FIELD(SBCS, SBAUTOINCREMENT, 16, 1)
+ FIELD(SBCS, SBACCESS, 17, 3)
+ FIELD(SBCS, SBREADONADDR, 20, 1)
+ FIELD(SBCS, SBBUSY, 21, 1)
+ FIELD(SBCS, SBBUSYERROR, 22, 1)
+ FIELD(SBCS, SBVERSION, 29, 3)
+
+REG32(SBADDRESS0, 0xE4)
+REG32(SBADDRESS1, 0xE8)
+REG32(SBADDRESS2, 0xEC)
+
+REG32(SBDATA0, 0xF0)
+REG32(SBDATA1, 0xF4)
+REG32(SBDATA2, 0xF8)
+REG32(SBDATA3, 0xFC)
+
REG32(HALTSUM0, 0x100)
+
+/* Minimal instruction builders used by abstract commands and ROM mailboxes. */
+#define DM_I(opcode, funct3, rd, rs1, imm) \
+ (((((uint32_t)(imm)) & 0xfffu) << 20) | \
+ (((uint32_t)(rs1)) << 15) | (((uint32_t)(funct3)) << 12) | \
+ (((uint32_t)(rd)) << 7) | ((uint32_t)(opcode)))
+
+#define DM_S(opcode, funct3, rs1, rs2, imm) \
+ (((((((uint32_t)(imm)) >> 5) & 0x7fu) << 25) | \
+ (((uint32_t)(rs2)) << 20) | (((uint32_t)(rs1)) << 15) | \
+ (((uint32_t)(funct3)) << 12) | ((((uint32_t)(imm)) & 0x1fu) << 7) | \
+ ((uint32_t)(opcode))))
+
+#define DM_LOAD(rd, rs1, offset, size) \
+ DM_I(0x03u, size, rd, rs1, offset)
+
+#define DM_STORE(rs2, rs1, offset, size) \
+ DM_S(0x23u, size, rs1, rs2, offset)
+
+#define DM_FP_LOAD(rd, rs1, offset, size) \
+ DM_I(0x07u, size, rd, rs1, offset)
+
+#define DM_FP_STORE(rs2, rs1, offset, size) \
+ DM_S(0x27u, size, rs1, rs2, offset)
+
+#define DM_LBU(rd, rs1, offset) \
+ DM_LOAD(rd, rs1, offset, 4u)
+
+#define DM_DATA_LOAD(rd, size) \
+ DM_LOAD(rd, 0u, RISCV_DM_ROM_DATA, size)
+
+#define DM_DATA_STORE(rs2, size) \
+ DM_STORE(rs2, 0u, RISCV_DM_ROM_DATA, size)
+
+#define DM_DATA_FP_LOAD(rd, size) \
+ DM_FP_LOAD(rd, 0u, RISCV_DM_ROM_DATA, size)
+
+#define DM_DATA_FP_STORE(rs2, size) \
+ DM_FP_STORE(rs2, 0u, RISCV_DM_ROM_DATA, size)
+
+/* csrr rd=s0, csr=regno */
+#define DM_CSRR(regno) \
+ (0x2473u | ((uint32_t)(regno) << 20))
+
+/* csrw csr=regno, rs1=s0 */
+#define DM_CSRW(regno) \
+ (0x41073u | ((uint32_t)(regno) << 20))
+
+/* jal x0, imm (imm is in units of 2 bytes, encoded in J-format) */
+#define DM_JAL(imm) \
+ (0x6fu | ((uint32_t)(imm) << 21))
+
+#define DM_NOP 0x13u
+#define DM_EBREAK 0x100073u
+
typedef struct DMHartSelection {
int all[RISCV_DM_HAWINDOW_SIZE + 1];
int all_count;
@@ -109,6 +264,103 @@ static inline void dm_rom_write8(RISCVDMState *s, uint32_t offset, uint8_t val)
s->rom_ptr[offset] = val;
}
+static inline uint8_t dm_rom_read8(RISCVDMState *s, uint32_t offset)
+{
+ return s->rom_ptr[offset];
+}
+
+static void dm_sync_data_to_rom(RISCVDMState *s, int data_index)
+{
+ uint32_t val = s->regs[R_DATA0 + data_index];
+ dm_rom_write32(s, RISCV_DM_ROM_DATA + data_index * 4, val);
+}
+
+static void dm_sync_progbuf_to_rom(RISCVDMState *s, int progbuf_index)
+{
+ uint32_t val = s->regs[R_PROGBUF0 + progbuf_index];
+ dm_rom_write32(s, RISCV_DM_ROM_PROGBUF + progbuf_index * 4, val);
+}
+
+static void dm_flush_cmd_space(RISCVDMState *s)
+{
+ for (int i = 0; i < 8; i++) {
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + i * 4, DM_NOP);
+ }
+ /* Restore s0 from dscratch0 */
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 8 * 4, DM_CSRR(0x7b2));
+ /* ebreak */
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 9 * 4, DM_EBREAK);
+ /* Default whereto: jump to CMD space from the ROM dispatcher. */
+ dm_rom_write32(s, RISCV_DM_ROM_WHERETO, DM_JAL(0x1C));
+}
+
+static void dm_invalidate_dynamic_code(RISCVDMState *s)
+{
+ if (tcg_enabled()) {
+ ram_addr_t base = memory_region_get_ram_addr(&s->rom_mr);
+
+ tb_invalidate_phys_range(NULL, base + RISCV_DM_ROM_WHERETO,
+ base + RISCV_DM_ROM_DATA - 1);
+ }
+}
+
+static void dm_update_impebreak(RISCVDMState *s)
+{
+ hwaddr ebreak_addr = RISCV_DM_ROM_PROGBUF + s->progbuf_size * 4;
+
+ if (ebreak_addr + 4 > RISCV_DM_ROM_DATA) {
+ return;
+ }
+
+ dm_rom_write32(s, ebreak_addr, s->impebreak ? DM_EBREAK : DM_NOP);
+}
+
+static void dm_reset_rom_state(RISCVDMState *s)
+{
+ if (!s->rom_ptr) {
+ return;
+ }
+
+ memset(s->rom_ptr + RISCV_DM_ROM_WORK_BASE, 0, RISCV_DM_ROM_WORK_SIZE);
+
+ dm_flush_cmd_space(s);
+
+ for (uint32_t i = 0; i < s->num_abstract_data; i++) {
+ dm_sync_data_to_rom(s, i);
+ }
+ for (uint32_t i = 0; i < s->progbuf_size; i++) {
+ dm_sync_progbuf_to_rom(s, i);
+ }
+
+ dm_update_impebreak(s);
+ dm_invalidate_dynamic_code(s);
+}
+
+static inline void dm_set_cmderr(RISCVDMState *s, uint32_t err)
+{
+ uint32_t cur = ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR);
+ if (cur == RISCV_DM_CMDERR_NONE) {
+ ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, CMDERR, err);
+ }
+}
+
+static bool dm_abstract_cmd_completed(RISCVDMState *s, uint32_t hartsel)
+{
+ uint32_t selected = dm_get_hartsel(s);
+ uint8_t flags;
+
+ if (!ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY) || selected != hartsel) {
+ return false;
+ }
+
+ /*
+ * The hart only completes an execution-based abstract command after it has
+ * consumed GO and returned to the park loop.
+ */
+ flags = dm_rom_read8(s, RISCV_DM_ROM_FLAGS + hartsel);
+ return !(flags & RISCV_DM_FLAG_GOING);
+}
+
static void dm_selection_add(RISCVDMState *s, DMHartSelection *sel, int hartsel)
{
for (int i = 0; i < sel->all_count; i++) {
@@ -637,6 +889,65 @@ static const MemoryRegionOps dm_rom_ops = {
static bool dm_rom_realize(RISCVDMState *s, Error **errp)
{
+ /*
+ * ROM program at 0x800:
+ * - entry: jump to _entry
+ * - resume: jump to _resume
+ * - exception: jump to _exception
+ * - _entry: fence, save s0, poll FLAGS loop
+ * - _exception: write EXCP, restore s0, ebreak
+ * - going: write GOING, restore s0, jump to whereto
+ * - _resume: write RESUME hartid, restore s0, dret
+ */
+ static const uint32_t rom_code_entry[] = {
+ /* 0x800 <entry>: */
+ 0x0180006f, /* j 818 <_entry> */
+ 0x00000013, /* nop */
+
+ /* 0x808 <resume>: */
+ 0x0600006f, /* j 868 <_resume> */
+ 0x00000013, /* nop */
+
+ /* 0x810 <exception>: */
+ 0x0400006f, /* j 850 <_exception> */
+ 0x00000013, /* nop */
+
+ /* 0x818 <_entry>: */
+ 0x0ff0000f, /* fence */
+ 0x7b241073, /* csrw dscratch0, s0 */
+
+ /* 0x820 <entry_loop>: */
+ 0xf1402473, /* csrr s0, mhartid */
+ 0x07f47413, /* andi s0, s0, 127 */
+ DM_STORE(8, 0u, RISCV_DM_ROM_HARTID, 2u), /* sw s0, HARTID(zero) */
+ DM_LBU(8, 8, RISCV_DM_ROM_FLAGS), /* lbu s0, FLAGS(s0) */
+ 0x00147413, /* andi s0, s0, 1 */
+ 0x02041463, /* bnez s0, 85c <going> */
+ 0xf1402473, /* csrr s0, mhartid */
+ 0x07f47413, /* andi s0, s0, 127 */
+ DM_LBU(8, 8, RISCV_DM_ROM_FLAGS), /* lbu s0, FLAGS(s0) */
+ 0x00247413, /* andi s0, s0, 2 */
+ 0xfc0410e3, /* bnez s0, 808 <resume> */
+ 0xfd5ff06f, /* j 820 <entry_loop> */
+
+ /* 0x850 <_exception>: */
+ DM_STORE(0, 0u, RISCV_DM_ROM_EXCP, 2u), /* sw zero, EXCP(zero) */
+ 0x7b202473, /* csrr s0, dscratch0 */
+ 0x00100073, /* ebreak */
+
+ /* 0x85c <going>: */
+ DM_STORE(0, 0u, RISCV_DM_ROM_GOING, 2u), /* sw zero, GOING(zero) */
+ 0x7b202473, /* csrr s0, dscratch0 */
+ 0xa9dff06f, /* j 300 <whereto> */
+
+ /* 0x868 <_resume>: */
+ 0xf1402473, /* csrr s0, mhartid */
+ 0x07f47413, /* andi s0, s0, 127 */
+ DM_STORE(8, 0u, RISCV_DM_ROM_RESUME, 2u), /* sw s0, RESUME(zero) */
+ 0x7b202473, /* csrr s0, dscratch0 */
+ 0x7b200073, /* dret */
+ };
+
SysBusDevice *sbd = SYS_BUS_DEVICE(s);
if (!memory_region_init_rom_device(&s->rom_mr, OBJECT(s), &dm_rom_ops, s,
@@ -644,9 +955,11 @@ static bool dm_rom_realize(RISCVDMState *s, Error **errp)
errp)) {
return false;
}
-
s->rom_ptr = memory_region_get_ram_ptr(&s->rom_mr);
+ memcpy(s->rom_ptr + RISCV_DM_ROM_ENTRY, rom_code_entry,
+ sizeof(rom_code_entry));
+
memory_region_init_alias(&s->rom_work_alias_mr, OBJECT(s),
"riscv-dm.rom-work", &s->rom_mr,
RISCV_DM_ROM_WORK_BASE, RISCV_DM_ROM_WORK_SIZE);
@@ -656,6 +969,7 @@ static bool dm_rom_realize(RISCVDMState *s, Error **errp)
sysbus_init_mmio(sbd, &s->rom_work_alias_mr);
sysbus_init_mmio(sbd, &s->rom_entry_alias_mr);
+
return true;
}
@@ -666,7 +980,11 @@ void riscv_dm_hart_halted(RISCVDMState *s, uint32_t hartsel)
}
s->hart_halted[hartsel] = true;
- riscv_dm_abstracts_done(s, hartsel);
+
+ if (dm_abstract_cmd_completed(s, hartsel)) {
+ riscv_dm_abstracts_done(s, hartsel);
+ }
+
dm_status_refresh(s);
trace_riscv_dm_hart_halted(hartsel);
}
@@ -692,22 +1010,29 @@ void riscv_dm_abstracts_done(RISCVDMState *s, uint32_t hartsel)
void riscv_dm_abstracts_exception(RISCVDMState *s, uint32_t hartsel)
{
- ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, BUSY, 0);
- ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, CMDERR,
- RISCV_DM_CMDERR_EXCEPTION);
+ dm_set_cmderr(s, RISCV_DM_CMDERR_EXCEPTION);
trace_riscv_dm_abstract_cmd_exception(hartsel);
}
static void dm_debug_reset(RISCVDMState *s)
{
- memset(s->regs, 0, sizeof(s->regs));
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, VERSION, 3);
+ s->dm_active = false;
+
+ /* Reset all registers via framework */
+ for (unsigned int i = 0; i < ARRAY_SIZE(s->regs_info); i++) {
+ register_reset(&s->regs_info[i]);
+ }
+
+ /* Set config-dependent reset values */
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, VERSION, 3); /* v1.0 */
ARRAY_FIELD_DP32(s->regs, DMSTATUS, AUTHENTICATED, 1);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, IMPEBREAK, s->impebreak);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, IMPEBREAK, s->impebreak ? 1 : 0);
+
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, DATACOUNT, s->num_abstract_data);
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, PROGBUFSIZE, s->progbuf_size);
- if (s->hart_halted) {
+ /* Reset per-hart state */
+ if (s->hart_resumeack && s->num_harts > 0) {
for (uint32_t i = 0; i < s->num_harts; i++) {
s->hart_halted[i] = false;
s->hart_resumeack[i] = false;
@@ -717,12 +1042,9 @@ static void dm_debug_reset(RISCVDMState *s)
}
memset(s->hawindow, 0, sizeof(s->hawindow));
- s->dm_active = false;
-
- if (s->rom_ptr) {
- memset(s->rom_ptr, 0, RISCV_DM_SIZE);
- }
+ s->last_cmd = 0;
+ dm_reset_rom_state(s);
dm_status_refresh(s);
}
@@ -803,6 +1125,7 @@ static const VMStateDescription vmstate_riscv_dm = {
.fields = (const VMStateField[]) {
VMSTATE_UINT32_ARRAY(regs, RISCVDMState, RISCV_DM_R_MAX),
VMSTATE_BOOL(dm_active, RISCVDMState),
+ VMSTATE_UINT32(last_cmd, RISCVDMState),
VMSTATE_VARRAY_UINT32(hart_halted, RISCVDMState, num_harts,
0, vmstate_info_bool, bool),
VMSTATE_VARRAY_UINT32(hart_resumeack, RISCVDMState, num_harts,
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 20/28] hw/riscv/dm: add register handlers and update state management
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (18 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 19/28] hw/riscv/dm: add DMI register declarations and ROM entry program Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 21/28] hw/riscv/dm: add abstract command execution Chao Liu
` (7 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Wire up pre_write/post_write/post_read callbacks for the newly
declared registers (DATA, PROGBUF, COMMAND, ABSTRACTAUTO,
ABSTRACTCS W1C) and expand the register_info table.
Refactor dm_dmcontrol_pre_write for clarity, fix haltsum0/1 to
use hartsel-based windowing, expand dm_reg_present for all
optional registers, add DATA area remap in ROM read/write, and
add realize-time validation for datacount and progbufsize.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/dm.c | 772 +++++++++++++++++++++++++++++++++++++-------------
1 file changed, 571 insertions(+), 201 deletions(-)
diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c
index 427507e77e..169863c54c 100644
--- a/hw/riscv/dm.c
+++ b/hw/riscv/dm.c
@@ -228,6 +228,7 @@ REG32(HALTSUM0, 0x100)
#define DM_NOP 0x13u
#define DM_EBREAK 0x100073u
+
typedef struct DMHartSelection {
int all[RISCV_DM_HAWINDOW_SIZE + 1];
int all_count;
@@ -235,19 +236,6 @@ typedef struct DMHartSelection {
int valid_count;
} DMHartSelection;
-static inline uint32_t dm_get_hartsel(RISCVDMState *s)
-{
- uint32_t hi = ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELHI);
- uint32_t lo = ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELLO);
-
- return (hi << 10) | lo;
-}
-
-static inline bool dm_hart_valid(RISCVDMState *s, uint32_t hartsel)
-{
- return hartsel < s->num_harts;
-}
-
static inline uint32_t dm_rom_read32(RISCVDMState *s, uint32_t offset)
{
return ldl_le_p(s->rom_ptr + offset);
@@ -294,54 +282,58 @@ static void dm_flush_cmd_space(RISCVDMState *s)
dm_rom_write32(s, RISCV_DM_ROM_WHERETO, DM_JAL(0x1C));
}
-static void dm_invalidate_dynamic_code(RISCVDMState *s)
+static inline uint32_t dm_get_hartsel(RISCVDMState *s)
{
- if (tcg_enabled()) {
- ram_addr_t base = memory_region_get_ram_addr(&s->rom_mr);
-
- tb_invalidate_phys_range(NULL, base + RISCV_DM_ROM_WHERETO,
- base + RISCV_DM_ROM_DATA - 1);
- }
+ uint32_t hi = ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELHI);
+ uint32_t lo = ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELLO);
+ return (hi << 10) | lo;
}
-static void dm_update_impebreak(RISCVDMState *s)
+static inline bool dm_hart_valid(RISCVDMState *s, uint32_t hartsel)
{
- hwaddr ebreak_addr = RISCV_DM_ROM_PROGBUF + s->progbuf_size * 4;
+ return hartsel < s->num_harts;
+}
- if (ebreak_addr + 4 > RISCV_DM_ROM_DATA) {
- return;
+static void dm_selection_add(RISCVDMState *s, DMHartSelection *sel, int hartsel)
+{
+ for (int i = 0; i < sel->all_count; i++) {
+ if (sel->all[i] == hartsel) {
+ return;
+ }
+ }
+ sel->all[sel->all_count++] = hartsel;
+ if (dm_hart_valid(s, hartsel)) {
+ sel->harts[sel->valid_count++] = hartsel;
}
-
- dm_rom_write32(s, ebreak_addr, s->impebreak ? DM_EBREAK : DM_NOP);
}
-static void dm_reset_rom_state(RISCVDMState *s)
+static void dm_collect_selected_harts(RISCVDMState *s, DMHartSelection *sel)
{
- if (!s->rom_ptr) {
- return;
- }
+ uint32_t hartsel = dm_get_hartsel(s);
- memset(s->rom_ptr + RISCV_DM_ROM_WORK_BASE, 0, RISCV_DM_ROM_WORK_SIZE);
+ memset(sel, 0, sizeof(*sel));
+ dm_selection_add(s, sel, hartsel);
- dm_flush_cmd_space(s);
+ if (!ARRAY_FIELD_EX32(s->regs, DMCONTROL, HASEL)) {
+ return;
+ }
- for (uint32_t i = 0; i < s->num_abstract_data; i++) {
- dm_sync_data_to_rom(s, i);
+ uint32_t wsel = ARRAY_FIELD_EX32(s->regs, HAWINDOWSEL, HAWINDOWSEL);
+ if (wsel >= RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE) {
+ return;
}
- for (uint32_t i = 0; i < s->progbuf_size; i++) {
- dm_sync_progbuf_to_rom(s, i);
+ uint32_t window = s->hawindow[wsel];
+ uint32_t base = wsel * RISCV_DM_HAWINDOW_SIZE;
+ for (int i = 0; i < RISCV_DM_HAWINDOW_SIZE; i++) {
+ if ((window >> i) & 1) {
+ dm_selection_add(s, sel, base + i);
+ }
}
-
- dm_update_impebreak(s);
- dm_invalidate_dynamic_code(s);
}
-static inline void dm_set_cmderr(RISCVDMState *s, uint32_t err)
+static inline bool dm_ndmreset_active(RISCVDMState *s)
{
- uint32_t cur = ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR);
- if (cur == RISCV_DM_CMDERR_NONE) {
- ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, CMDERR, err);
- }
+ return ARRAY_FIELD_EX32(s->regs, DMCONTROL, NDMRESET);
}
static bool dm_abstract_cmd_completed(RISCVDMState *s, uint32_t hartsel)
@@ -361,135 +353,137 @@ static bool dm_abstract_cmd_completed(RISCVDMState *s, uint32_t hartsel)
return !(flags & RISCV_DM_FLAG_GOING);
}
-static void dm_selection_add(RISCVDMState *s, DMHartSelection *sel, int hartsel)
+static void dm_invalidate_dynamic_code(RISCVDMState *s)
{
- for (int i = 0; i < sel->all_count; i++) {
- if (sel->all[i] == hartsel) {
- return;
- }
- }
+ if (tcg_enabled()) {
+ ram_addr_t base = memory_region_get_ram_addr(&s->rom_mr);
- sel->all[sel->all_count++] = hartsel;
- if (dm_hart_valid(s, hartsel)) {
- sel->harts[sel->valid_count++] = hartsel;
+ tb_invalidate_phys_range(NULL, base + RISCV_DM_ROM_WHERETO,
+ base + RISCV_DM_ROM_DATA - 1);
}
}
-static void dm_collect_selected_harts(RISCVDMState *s, DMHartSelection *sel)
+static bool dm_data_reg_present(RISCVDMState *s, hwaddr addr)
{
- uint32_t hartsel = dm_get_hartsel(s);
- uint32_t wsel;
- uint32_t window;
- uint32_t base;
+ unsigned int index = (addr - A_DATA0) / 4;
- memset(sel, 0, sizeof(*sel));
- dm_selection_add(s, sel, hartsel);
+ return index < s->num_abstract_data;
+}
- if (!ARRAY_FIELD_EX32(s->regs, DMCONTROL, HASEL)) {
- return;
- }
+static bool dm_progbuf_reg_present(RISCVDMState *s, hwaddr addr)
+{
+ unsigned int index = (addr - A_PROGBUF0) / 4;
- wsel = ARRAY_FIELD_EX32(s->regs, HAWINDOWSEL, HAWINDOWSEL);
- if (wsel >= RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE) {
- return;
- }
+ return index < s->progbuf_size;
+}
- window = s->hawindow[wsel];
- base = wsel * RISCV_DM_HAWINDOW_SIZE;
- for (int i = 0; i < RISCV_DM_HAWINDOW_SIZE; i++) {
- if ((window >> i) & 1) {
- dm_selection_add(s, sel, base + i);
- }
+static bool dm_sba_addr_reg_present(RISCVDMState *s, hwaddr addr)
+{
+ switch (addr) {
+ case A_SBADDRESS0:
+ return s->sba_addr_width > 0;
+ case A_SBADDRESS1:
+ return s->sba_addr_width > 32;
+ case A_SBADDRESS2:
+ return s->sba_addr_width > 64;
+ case A_SBADDRESS3:
+ return s->sba_addr_width > 96;
+ default:
+ return false;
}
}
-static inline bool dm_ndmreset_active(RISCVDMState *s)
+static bool dm_sba_data_reg_present(RISCVDMState *s, hwaddr addr)
{
- return ARRAY_FIELD_EX32(s->regs, DMCONTROL, NDMRESET);
+ bool sbdata0 = ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS8) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS16) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS32) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS64) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS128);
+ bool sbdata1 = ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS64) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS128);
+ bool sbdata23 = ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS128);
+
+ switch (addr) {
+ case A_SBDATA0:
+ return sbdata0;
+ case A_SBDATA1:
+ return sbdata1;
+ case A_SBDATA2:
+ case A_SBDATA3:
+ return sbdata23;
+ default:
+ return false;
+ }
}
static bool dm_reg_present(RISCVDMState *s, hwaddr addr)
{
+ if (addr >= A_DATA0 && addr <= A_DATA11) {
+ return dm_data_reg_present(s, addr);
+ }
+
+ if (addr >= A_PROGBUF0 && addr <= A_PROGBUF15) {
+ return dm_progbuf_reg_present(s, addr);
+ }
+
switch (addr) {
+ case A_AUTHDATA:
+ case A_DMCS2:
+ return false;
case A_HALTSUM1:
return s->num_harts >= 33;
+ case A_HALTSUM2:
+ return s->num_harts >= 1025;
+ case A_HALTSUM3:
+ return s->num_harts >= 32769;
+ case A_SBADDRESS0:
+ case A_SBADDRESS1:
+ case A_SBADDRESS2:
+ case A_SBADDRESS3:
+ return dm_sba_addr_reg_present(s, addr);
+ case A_SBDATA0:
+ case A_SBDATA1:
+ case A_SBDATA2:
+ case A_SBDATA3:
+ return dm_sba_data_reg_present(s, addr);
default:
return true;
}
}
-static void dm_status_refresh(RISCVDMState *s)
+static void dm_update_impebreak(RISCVDMState *s)
{
- DMHartSelection sel;
- bool anyhalted = false;
- bool allhalted = true;
- bool anyrunning = false;
- bool allrunning = true;
- bool anyunavail = false;
- bool allunavail = true;
- bool anyresumeack = false;
- bool allresumeack = true;
- bool anyhavereset = false;
- bool allhavereset = true;
- bool anynonexistent;
- bool allnonexistent;
- bool reset_unavail = dm_ndmreset_active(s) ||
- ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTRESET);
+ hwaddr ebreak_addr = RISCV_DM_ROM_PROGBUF + s->progbuf_size * 4;
- dm_collect_selected_harts(s, &sel);
+ if (ebreak_addr + 4 > RISCV_DM_ROM_DATA) {
+ return;
+ }
- anynonexistent = sel.all_count > sel.valid_count;
- allnonexistent = sel.valid_count == 0 && sel.all_count > 0;
+ dm_rom_write32(s, ebreak_addr, s->impebreak ? DM_EBREAK : DM_NOP);
+}
- if (sel.valid_count == 0) {
- allhalted = false;
- allrunning = false;
- allunavail = false;
- allresumeack = false;
- allhavereset = false;
+static void dm_reset_rom_state(RISCVDMState *s)
+{
+ if (!s->rom_ptr) {
+ return;
}
- for (int i = 0; i < sel.valid_count; i++) {
- int h = sel.harts[i];
- bool halted = s->hart_halted[h];
- bool resumeack = s->hart_resumeack[h];
- bool havereset = s->hart_havereset[h];
+ memset(s->rom_ptr + RISCV_DM_ROM_WORK_BASE, 0, RISCV_DM_ROM_WORK_SIZE);
- if (reset_unavail) {
- anyunavail = true;
- allhalted = false;
- allrunning = false;
- } else {
- anyhalted |= halted;
- allhalted &= halted;
- anyrunning |= !halted;
- allrunning &= !halted;
- }
+ dm_flush_cmd_space(s);
- allunavail &= reset_unavail;
- anyresumeack |= resumeack;
- allresumeack &= resumeack;
- anyhavereset |= havereset;
- allhavereset &= havereset;
+ for (uint32_t i = 0; i < s->num_abstract_data; i++) {
+ dm_sync_data_to_rom(s, i);
+ }
+ for (uint32_t i = 0; i < s->progbuf_size; i++) {
+ dm_sync_progbuf_to_rom(s, i);
}
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHALTED, anyhalted);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHALTED, allhalted);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRUNNING, anyrunning);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRUNNING, allrunning);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYUNAVAIL, anyunavail);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLUNAVAIL, allunavail);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYNONEXISTENT, anynonexistent);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLNONEXISTENT, allnonexistent);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRESUMEACK, anyresumeack);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRESUMEACK, allresumeack);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHAVERESET, anyhavereset);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHAVERESET, allhavereset);
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, HASRESETHALTREQ, 1);
+ dm_update_impebreak(s);
+ dm_invalidate_dynamic_code(s);
}
-static void dm_debug_reset(RISCVDMState *s);
-
static void dm_cpu_reset_on_cpu(CPUState *cpu, run_on_cpu_data data)
{
bool request_reset_halt = data.host_int;
@@ -543,20 +537,93 @@ static void dm_reset_all_harts(RISCVDMState *s)
}
}
+
+static inline void dm_set_cmderr(RISCVDMState *s, uint32_t err)
+{
+ uint32_t cur = ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR);
+ if (cur == RISCV_DM_CMDERR_NONE) {
+ ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, CMDERR, err);
+ }
+}
+
+static void dm_status_refresh(RISCVDMState *s)
+{
+ DMHartSelection sel;
+ bool anyhalted = false, allhalted = true;
+ bool anyrunning = false, allrunning = true;
+ bool anyunavail = false, allunavail = true;
+ bool anyresumeack = false, allresumeack = true;
+ bool anyhavereset = false, allhavereset = true;
+ bool anynonexistent = false, allnonexistent = false;
+ bool reset_unavail = dm_ndmreset_active(s) ||
+ ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTRESET);
+
+ dm_collect_selected_harts(s, &sel);
+
+ anynonexistent = (sel.all_count > sel.valid_count);
+ allnonexistent = (sel.valid_count == 0 && sel.all_count > 0);
+
+ if (sel.valid_count == 0) {
+ allhalted = false;
+ allrunning = false;
+ allunavail = false;
+ allresumeack = false;
+ allhavereset = false;
+ }
+
+ for (int i = 0; i < sel.valid_count; i++) {
+ int h = sel.harts[i];
+ bool halted = s->hart_halted[h];
+ bool resumeack = s->hart_resumeack[h];
+ bool havereset = s->hart_havereset[h];
+
+ if (reset_unavail) {
+ anyunavail = true;
+ allhalted = false;
+ allrunning = false;
+ } else {
+ anyhalted |= halted;
+ allhalted &= halted;
+ anyrunning |= !halted;
+ allrunning &= !halted;
+ }
+
+ allunavail &= reset_unavail;
+ anyresumeack |= resumeack;
+ allresumeack &= resumeack;
+ anyhavereset |= havereset;
+ allhavereset &= havereset;
+ }
+
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHALTED, anyhalted);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHALTED, allhalted);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRUNNING, anyrunning);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRUNNING, allrunning);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYUNAVAIL, anyunavail);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLUNAVAIL, allunavail);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYNONEXISTENT, anynonexistent);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLNONEXISTENT, allnonexistent);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYRESUMEACK, anyresumeack);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLRESUMEACK, allresumeack);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ANYHAVERESET, anyhavereset);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, ALLHAVERESET, allhavereset);
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, HASRESETHALTREQ, 1);
+}
+
+
+static void dm_debug_reset(RISCVDMState *s);
static uint64_t dm_dmcontrol_pre_write(RegisterInfo *reg, uint64_t val64)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
- uint32_t val = val64;
+ uint32_t val = (uint32_t)val64;
uint32_t cur_ctl = s->regs[R_DMCONTROL];
bool busy = ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY);
bool ndmreset_was = FIELD_EX32(cur_ctl, DMCONTROL, NDMRESET);
bool hartreset_was = FIELD_EX32(cur_ctl, DMCONTROL, HARTRESET);
- bool dmactive = FIELD_EX32(val, DMCONTROL, DMACTIVE);
- DMHartSelection sel;
- uint32_t stored = 0;
trace_riscv_dm_control_write(s->regs[R_DMCONTROL], val, busy);
+ /* If busy, preserve hart selection and suppress run-control */
if (busy) {
val = FIELD_DP32(val, DMCONTROL, HARTSELHI,
ARRAY_FIELD_EX32(s->regs, DMCONTROL, HARTSELHI));
@@ -569,25 +636,34 @@ static uint64_t dm_dmcontrol_pre_write(RegisterInfo *reg, uint64_t val64)
val = FIELD_DP32(val, DMCONTROL, ACKHAVERESET, 0);
}
+ bool dmactive = FIELD_EX32(val, DMCONTROL, DMACTIVE);
+
if (!dmactive) {
+ /* dmactive=0: reset the DM */
dm_debug_reset(s);
+ /* Return the reset value (only dmactive=0) */
return 0;
}
+ /* Store first so helpers see updated fields */
s->regs[R_DMCONTROL] = val;
+
+ DMHartSelection sel;
dm_collect_selected_harts(s, &sel);
- if (FIELD_EX32(val, DMCONTROL, NDMRESET) && !ndmreset_was) {
+ bool ndmreset = FIELD_EX32(val, DMCONTROL, NDMRESET);
+ bool hartreset = FIELD_EX32(val, DMCONTROL, HARTRESET);
+
+ if (ndmreset && !ndmreset_was) {
dm_reset_all_harts(s);
}
-
- if (FIELD_EX32(val, DMCONTROL, HARTRESET) && !hartreset_was) {
+ if (hartreset && !hartreset_was) {
dm_reset_selected_harts(s, &sel);
}
- ARRAY_FIELD_DP32(s->regs, DMSTATUS, NDMRESETPENDING,
- FIELD_EX32(val, DMCONTROL, NDMRESET));
+ ARRAY_FIELD_DP32(s->regs, DMSTATUS, NDMRESETPENDING, ndmreset);
+ /* ACKHAVERESET: clear havereset for selected harts */
if (!busy && FIELD_EX32(val, DMCONTROL, ACKHAVERESET)) {
for (int i = 0; i < sel.valid_count; i++) {
s->hart_havereset[sel.harts[i]] = false;
@@ -606,22 +682,24 @@ static uint64_t dm_dmcontrol_pre_write(RegisterInfo *reg, uint64_t val64)
}
}
- if (!busy && FIELD_EX32(val, DMCONTROL, RESUMEREQ) &&
- !FIELD_EX32(val, DMCONTROL, HALTREQ)) {
+ /* RESUMEREQ */
+ bool resumereq = FIELD_EX32(val, DMCONTROL, RESUMEREQ);
+ bool haltreq = FIELD_EX32(val, DMCONTROL, HALTREQ);
+
+ if (!busy && resumereq && !haltreq) {
for (int i = 0; i < sel.valid_count; i++) {
int h = sel.harts[i];
-
s->hart_resumeack[h] = false;
dm_rom_write8(s, RISCV_DM_ROM_FLAGS + h, RISCV_DM_FLAG_RESUME);
trace_riscv_dm_control_resume(h);
}
}
+ /* HALTREQ */
if (!busy) {
- if (FIELD_EX32(val, DMCONTROL, HALTREQ)) {
+ if (haltreq) {
for (int i = 0; i < sel.valid_count; i++) {
int h = sel.harts[i];
-
dm_rom_write8(s, RISCV_DM_ROM_FLAGS + h, RISCV_DM_FLAG_CLEAR);
qemu_set_irq(s->halt_irqs[h], 1);
trace_riscv_dm_control_halt(h);
@@ -633,6 +711,12 @@ static uint64_t dm_dmcontrol_pre_write(RegisterInfo *reg, uint64_t val64)
}
}
+ /*
+ * Build the stored value: only keep fields with readable semantics.
+ * WARZ fields (haltreq, resumereq, ackhavereset, setresethaltreq,
+ * clrresethaltreq, setkeepalive, clrkeepalive, ackunavail) read as 0.
+ */
+ uint32_t stored = 0;
stored = FIELD_DP32(stored, DMCONTROL, DMACTIVE, 1);
stored = FIELD_DP32(stored, DMCONTROL, NDMRESET,
FIELD_EX32(val, DMCONTROL, NDMRESET));
@@ -643,17 +727,17 @@ static uint64_t dm_dmcontrol_pre_write(RegisterInfo *reg, uint64_t val64)
stored = FIELD_DP32(stored, DMCONTROL, HASEL,
FIELD_EX32(val, DMCONTROL, HASEL));
stored = FIELD_DP32(stored, DMCONTROL, HARTRESET,
- FIELD_EX32(val, DMCONTROL, HARTRESET));
+ hartreset);
s->dm_active = true;
dm_status_refresh(s);
+
return stored;
}
static uint64_t dm_dmstatus_post_read(RegisterInfo *reg, uint64_t val)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
-
dm_status_refresh(s);
return s->regs[R_DMSTATUS];
}
@@ -661,8 +745,7 @@ static uint64_t dm_dmstatus_post_read(RegisterInfo *reg, uint64_t val)
static uint64_t dm_hartinfo_post_read(RegisterInfo *reg, uint64_t val)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
- uint32_t v = val;
-
+ uint32_t v = 0;
v = FIELD_DP32(v, HARTINFO, DATAADDR, RISCV_DM_ROM_DATA);
v = FIELD_DP32(v, HARTINFO, DATASIZE, s->num_abstract_data);
v = FIELD_DP32(v, HARTINFO, DATAACCESS, 1);
@@ -673,9 +756,7 @@ static uint64_t dm_hartinfo_post_read(RegisterInfo *reg, uint64_t val)
static uint64_t dm_hawindowsel_pre_write(RegisterInfo *reg, uint64_t val64)
{
uint32_t max_sel = RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE;
- uint32_t val = val64 & R_HAWINDOWSEL_HAWINDOWSEL_MASK;
-
- (void)reg;
+ uint32_t val = (uint32_t)val64 & R_HAWINDOWSEL_HAWINDOWSEL_MASK;
if (val >= max_sel) {
val = max_sel - 1;
}
@@ -686,9 +767,8 @@ static uint64_t dm_hawindow_pre_write(RegisterInfo *reg, uint64_t val64)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
uint32_t wsel = ARRAY_FIELD_EX32(s->regs, HAWINDOWSEL, HAWINDOWSEL);
-
if (wsel < RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE) {
- s->hawindow[wsel] = val64;
+ s->hawindow[wsel] = (uint32_t)val64;
}
return (uint32_t)val64;
}
@@ -697,69 +777,312 @@ static uint64_t dm_hawindow_post_read(RegisterInfo *reg, uint64_t val)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
uint32_t wsel = ARRAY_FIELD_EX32(s->regs, HAWINDOWSEL, HAWINDOWSEL);
-
- (void)val;
if (wsel < RISCV_DM_MAX_HARTS / RISCV_DM_HAWINDOW_SIZE) {
return s->hawindow[wsel];
}
return 0;
}
+static uint64_t dm_abstractcs_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY)) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_BUSY);
+ return s->regs[R_ABSTRACTCS];
+ }
+
+ /* W1C on CMDERR */
+ uint32_t w1c_bits = FIELD_EX32((uint32_t)val64, ABSTRACTCS, CMDERR);
+ uint32_t cur = s->regs[R_ABSTRACTCS];
+ uint32_t cmderr = FIELD_EX32(cur, ABSTRACTCS, CMDERR);
+ cmderr &= ~w1c_bits;
+ cur = FIELD_DP32(cur, ABSTRACTCS, CMDERR, cmderr);
+
+ return cur;
+}
+
+static uint64_t dm_command_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ /* Stub: abstract command execution added in a follow-on patch. */
+ s->last_cmd = (uint32_t)val64;
+ return s->regs[R_COMMAND];
+}
+
+static uint64_t dm_command_post_read(RegisterInfo *reg, uint64_t val)
+{
+ return 0;
+}
+
+static uint64_t dm_abstractauto_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY)) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_BUSY);
+ return s->regs[R_ABSTRACTAUTO];
+ }
+
+ /* Stub: autoexec trigger logic added in a follow-on patch. */
+ uint32_t data_count = MIN(s->num_abstract_data, 12);
+ uint32_t pbuf_size = MIN(s->progbuf_size, 16);
+ uint32_t data_mask = data_count ? ((1u << data_count) - 1u) : 0;
+ uint32_t pbuf_mask = pbuf_size ? ((1u << pbuf_size) - 1u) : 0;
+ uint32_t mask = data_mask | (pbuf_mask << 16);
+
+ return (uint32_t)val64 & mask;
+}
+
+static uint64_t dm_data_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY)) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_BUSY);
+ return s->regs[reg->access->addr / 4];
+ }
+ return (uint32_t)val64;
+}
+
+static void dm_data_post_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ int index = (reg->access->addr - A_DATA0) / 4;
+
+ dm_sync_data_to_rom(s, index);
+}
+
+static uint64_t dm_data_post_read(RegisterInfo *reg, uint64_t val)
+{
+ return val;
+}
+
+static uint64_t dm_progbuf_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY)) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_BUSY);
+ return s->regs[reg->access->addr / 4];
+ }
+ return (uint32_t)val64;
+}
+
+static void dm_progbuf_post_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ int index = (reg->access->addr - A_PROGBUF0) / 4;
+
+ dm_sync_progbuf_to_rom(s, index);
+}
+
+static uint64_t dm_progbuf_post_read(RegisterInfo *reg, uint64_t val)
+{
+ return val;
+}
+
static uint64_t dm_haltsum0_post_read(RegisterInfo *reg, uint64_t val)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
- uint32_t sum = 0;
- uint32_t base = 0;
- uint32_t limit = MIN(s->num_harts, 32u);
-
- (void)val;
- for (uint32_t h = base; h < limit; h++) {
- if (s->hart_halted[h]) {
- sum |= 1u << (h - base);
+ uint32_t result = 0;
+ uint32_t base = dm_get_hartsel(s) & ~0x1fu;
+
+ for (uint32_t i = 0; i < 32; i++) {
+ uint32_t h = base + i;
+ if (h < s->num_harts && s->hart_halted[h]) {
+ result |= (1u << i);
}
}
- return sum;
+ return result;
}
static uint64_t dm_haltsum1_post_read(RegisterInfo *reg, uint64_t val)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
- uint32_t sum = 0;
-
- (void)reg;
- (void)val;
- for (uint32_t h = 0; h < s->num_harts; h++) {
- if (s->hart_halted[h]) {
- sum |= 1u << (h / 32);
+ uint32_t result = 0;
+ uint32_t base = dm_get_hartsel(s) & ~0x3ffu;
+
+ for (uint32_t g = 0; g < 32; g++) {
+ for (uint32_t i = 0; i < 32; i++) {
+ uint32_t h = base + g * 32 + i;
+ if (h < s->num_harts && s->hart_halted[h]) {
+ result |= (1u << g);
+ break;
+ }
}
}
- return sum;
+ return result;
}
+
static RegisterAccessInfo riscv_dm_regs_info[] = {
+ { .name = "DATA0", .addr = A_DATA0, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA1", .addr = A_DATA1, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA2", .addr = A_DATA2, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA3", .addr = A_DATA3, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA4", .addr = A_DATA4, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA5", .addr = A_DATA5, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA6", .addr = A_DATA6, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA7", .addr = A_DATA7, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA8", .addr = A_DATA8, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA9", .addr = A_DATA9, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA10", .addr = A_DATA10, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+ { .name = "DATA11", .addr = A_DATA11, .pre_write = dm_data_pre_write,
+ .post_write = dm_data_post_write, .post_read = dm_data_post_read, },
+
{ .name = "DMCONTROL", .addr = A_DMCONTROL,
.pre_write = dm_dmcontrol_pre_write, },
+
{ .name = "DMSTATUS", .addr = A_DMSTATUS,
- .ro = 0xffffffff,
+ .ro = 0xFFFFFFFF,
.post_read = dm_dmstatus_post_read, },
+
{ .name = "HARTINFO", .addr = A_HARTINFO,
- .ro = 0xffffffff,
+ .ro = 0xFFFFFFFF,
.post_read = dm_hartinfo_post_read, },
+
{ .name = "HALTSUM1", .addr = A_HALTSUM1,
- .ro = 0xffffffff,
+ .ro = 0xFFFFFFFF,
.post_read = dm_haltsum1_post_read, },
+
{ .name = "HAWINDOWSEL", .addr = A_HAWINDOWSEL,
.pre_write = dm_hawindowsel_pre_write, },
+
{ .name = "HAWINDOW", .addr = A_HAWINDOW,
.pre_write = dm_hawindow_pre_write,
.post_read = dm_hawindow_post_read, },
+
{ .name = "ABSTRACTCS", .addr = A_ABSTRACTCS,
- .ro = 0xffffffff, },
+ .ro = R_ABSTRACTCS_DATACOUNT_MASK | R_ABSTRACTCS_BUSY_MASK |
+ R_ABSTRACTCS_PROGBUFSIZE_MASK,
+ .pre_write = dm_abstractcs_pre_write, },
+
+ { .name = "COMMAND", .addr = A_COMMAND,
+ .pre_write = dm_command_pre_write,
+ .post_read = dm_command_post_read, },
+
+ { .name = "ABSTRACTAUTO", .addr = A_ABSTRACTAUTO,
+ .pre_write = dm_abstractauto_pre_write, },
+
+ { .name = "CONFSTRPTR0", .addr = A_CONFSTRPTR0, .ro = 0xFFFFFFFF, },
+ { .name = "CONFSTRPTR1", .addr = A_CONFSTRPTR1, .ro = 0xFFFFFFFF, },
+ { .name = "CONFSTRPTR2", .addr = A_CONFSTRPTR2, .ro = 0xFFFFFFFF, },
+ { .name = "CONFSTRPTR3", .addr = A_CONFSTRPTR3, .ro = 0xFFFFFFFF, },
+
+ { .name = "NEXTDM", .addr = A_NEXTDM, .ro = 0xFFFFFFFF, },
+
+ { .name = "PROGBUF0", .addr = A_PROGBUF0,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF1", .addr = A_PROGBUF1,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF2", .addr = A_PROGBUF2,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF3", .addr = A_PROGBUF3,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF4", .addr = A_PROGBUF4,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF5", .addr = A_PROGBUF5,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF6", .addr = A_PROGBUF6,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF7", .addr = A_PROGBUF7,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF8", .addr = A_PROGBUF8,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF9", .addr = A_PROGBUF9,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF10", .addr = A_PROGBUF10,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF11", .addr = A_PROGBUF11,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF12", .addr = A_PROGBUF12,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF13", .addr = A_PROGBUF13,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF14", .addr = A_PROGBUF14,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+ { .name = "PROGBUF15", .addr = A_PROGBUF15,
+ .pre_write = dm_progbuf_pre_write,
+ .post_write = dm_progbuf_post_write,
+ .post_read = dm_progbuf_post_read, },
+
+ { .name = "AUTHDATA", .addr = A_AUTHDATA, },
+
+ { .name = "DMCS2", .addr = A_DMCS2, },
+
+ { .name = "HALTSUM2", .addr = A_HALTSUM2, .ro = 0xFFFFFFFF, },
+ { .name = "HALTSUM3", .addr = A_HALTSUM3, .ro = 0xFFFFFFFF, },
+
+ { .name = "SBADDRESS3", .addr = A_SBADDRESS3, },
+
+ { .name = "SBCS", .addr = A_SBCS,
+ .ro = R_SBCS_SBACCESS8_MASK | R_SBCS_SBACCESS16_MASK |
+ R_SBCS_SBACCESS32_MASK | R_SBCS_SBACCESS64_MASK |
+ R_SBCS_SBACCESS128_MASK | R_SBCS_SBASIZE_MASK |
+ R_SBCS_SBBUSY_MASK | R_SBCS_SBVERSION_MASK, },
+
+ { .name = "SBADDRESS0", .addr = A_SBADDRESS0, },
+
+ { .name = "SBADDRESS1", .addr = A_SBADDRESS1, },
+
+ { .name = "SBADDRESS2", .addr = A_SBADDRESS2, },
+
+ { .name = "SBDATA0", .addr = A_SBDATA0, },
+
+ { .name = "SBDATA1", .addr = A_SBDATA1, },
+
+ { .name = "SBDATA2", .addr = A_SBDATA2, },
+ { .name = "SBDATA3", .addr = A_SBDATA3, },
+
{ .name = "HALTSUM0", .addr = A_HALTSUM0,
- .ro = 0xffffffff,
+ .ro = 0xFFFFFFFF,
.post_read = dm_haltsum0_post_read, },
};
+
static uint64_t riscv_dm_read(void *opaque, hwaddr addr, unsigned size)
{
RegisterInfoArray *reg_array = opaque;
@@ -829,6 +1152,16 @@ static uint64_t dm_rom_read(void *opaque, hwaddr offset, unsigned size)
uint64_t ret;
ret = ldn_le_p(s->rom_ptr + offset, size);
+
+ /* DATA area remap: reads from ROM DATA area return register values */
+ if (offset >= RISCV_DM_ROM_DATA &&
+ offset < RISCV_DM_ROM_DATA + s->num_abstract_data * 4) {
+ int idx = (offset - RISCV_DM_ROM_DATA) / 4;
+ if (size == 4) {
+ ret = s->regs[R_DATA0 + idx];
+ }
+ }
+
trace_riscv_dm_rom_access(offset, ret, size, false);
return ret;
}
@@ -837,54 +1170,59 @@ static void dm_rom_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
RISCVDMState *s = opaque;
- uint32_t hartsel;
stn_le_p(s->rom_ptr + offset, size, value);
trace_riscv_dm_rom_access(offset, value, size, true);
+ /* CPU wrote to HARTID register → hart has halted */
if (offset == RISCV_DM_ROM_HARTID && size == 4) {
- hartsel = value;
+ uint32_t hartsel = (uint32_t)value;
if (dm_hart_valid(s, hartsel)) {
riscv_dm_hart_halted(s, hartsel);
}
- return;
}
+ /* CPU wrote GOING acknowledgment */
if (offset == RISCV_DM_ROM_GOING && size == 4) {
- hartsel = dm_rom_read32(s, RISCV_DM_ROM_HARTID);
+ uint32_t hartsel = dm_rom_read32(s, RISCV_DM_ROM_HARTID);
if (dm_hart_valid(s, hartsel)) {
dm_rom_write8(s, RISCV_DM_ROM_FLAGS + hartsel,
RISCV_DM_FLAG_CLEAR);
trace_riscv_dm_going(hartsel);
}
- return;
}
+ /* CPU wrote RESUME acknowledgment */
if (offset == RISCV_DM_ROM_RESUME && size == 4) {
- hartsel = value;
+ uint32_t hartsel = (uint32_t)value;
if (dm_hart_valid(s, hartsel)) {
riscv_dm_hart_resumed(s, hartsel);
}
- return;
}
+ /* CPU wrote EXCEPTION */
if (offset == RISCV_DM_ROM_EXCP && size == 4) {
- hartsel = dm_rom_read32(s, RISCV_DM_ROM_HARTID);
+ uint32_t hartsel = dm_rom_read32(s, RISCV_DM_ROM_HARTID);
if (dm_hart_valid(s, hartsel)) {
riscv_dm_abstracts_exception(s, hartsel);
}
}
+
+ /* DATA area remap: writes to ROM DATA area update registers */
+ if (offset >= RISCV_DM_ROM_DATA &&
+ offset < RISCV_DM_ROM_DATA + s->num_abstract_data * 4 && size == 4) {
+ int idx = (offset - RISCV_DM_ROM_DATA) / 4;
+ s->regs[R_DATA0 + idx] = (uint32_t)value;
+ }
}
static const MemoryRegionOps dm_rom_ops = {
.read = dm_rom_read,
.write = dm_rom_write,
.endianness = DEVICE_LITTLE_ENDIAN,
- .impl = {
- .min_access_size = 1,
- .max_access_size = 4,
- },
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 4,
};
static bool dm_rom_realize(RISCVDMState *s, Error **errp)
@@ -967,12 +1305,18 @@ static bool dm_rom_realize(RISCVDMState *s, Error **errp)
"riscv-dm.rom-entry", &s->rom_mr,
RISCV_DM_ROM_ENTRY, RISCV_DM_ROM_ENTRY_SIZE);
+ /*
+ * Expose debug backing store in two non-overlapping physical windows:
+ * - work area at low addresses (mailbox/data/progbuf/flags)
+ * - ROM entry vector at VIRT_DM_ROM base
+ */
sysbus_init_mmio(sbd, &s->rom_work_alias_mr);
sysbus_init_mmio(sbd, &s->rom_entry_alias_mr);
return true;
}
+
void riscv_dm_hart_halted(RISCVDMState *s, uint32_t hartsel)
{
if (!dm_hart_valid(s, hartsel)) {
@@ -998,6 +1342,7 @@ void riscv_dm_hart_resumed(RISCVDMState *s, uint32_t hartsel)
s->hart_halted[hartsel] = false;
s->hart_resumeack[hartsel] = true;
dm_rom_write8(s, RISCV_DM_ROM_FLAGS + hartsel, RISCV_DM_FLAG_CLEAR);
+
dm_status_refresh(s);
trace_riscv_dm_hart_resumed(hartsel);
}
@@ -1014,6 +1359,7 @@ void riscv_dm_abstracts_exception(RISCVDMState *s, uint32_t hartsel)
trace_riscv_dm_abstract_cmd_exception(hartsel);
}
+
static void dm_debug_reset(RISCVDMState *s)
{
s->dm_active = false;
@@ -1048,15 +1394,18 @@ static void dm_debug_reset(RISCVDMState *s)
dm_status_refresh(s);
}
+
static void riscv_dm_init(Object *obj)
{
RISCVDMState *s = RISCV_DM(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
- s->reg_array = register_init_block32(DEVICE(obj), riscv_dm_regs_info,
- ARRAY_SIZE(riscv_dm_regs_info),
- s->regs_info, s->regs, &riscv_dm_ops,
- false, RISCV_DM_REG_SIZE);
+ s->reg_array =
+ register_init_block32(DEVICE(obj), riscv_dm_regs_info,
+ ARRAY_SIZE(riscv_dm_regs_info),
+ s->regs_info, s->regs,
+ &riscv_dm_ops, false,
+ RISCV_DM_REG_SIZE);
sysbus_init_mmio(sbd, &s->reg_array->mem);
}
@@ -1071,6 +1420,25 @@ static void riscv_dm_realize(DeviceState *dev, Error **errp)
return;
}
+ if (s->num_abstract_data == 0 || s->num_abstract_data > 12) {
+ error_setg(errp, "riscv-dm: datacount %u must be in range 1..12",
+ s->num_abstract_data);
+ return;
+ }
+
+ if (s->progbuf_size > 16) {
+ error_setg(errp, "riscv-dm: progbufsize %u exceeds maximum 16",
+ s->progbuf_size);
+ return;
+ }
+
+ if (s->progbuf_size == 1 && !s->impebreak) {
+ error_setg(errp,
+ "riscv-dm: progbufsize 1 requires impebreak to be enabled");
+ return;
+ }
+
+ /* Allocate per-hart state */
if (s->num_harts > 0) {
s->hart_halted = g_new0(bool, s->num_harts);
s->hart_resumeack = g_new0(bool, s->num_harts);
@@ -1089,19 +1457,19 @@ static void riscv_dm_realize(DeviceState *dev, Error **errp)
return;
}
+ /* Apply initial reset */
dm_debug_reset(s);
}
static void riscv_dm_reset_hold(Object *obj, ResetType type)
{
- (void)type;
- dm_debug_reset(RISCV_DM(obj));
+ RISCVDMState *s = RISCV_DM(obj);
+ dm_debug_reset(s);
}
static void riscv_dm_unrealize(DeviceState *dev)
{
RISCVDMState *s = RISCV_DM(dev);
-
g_free(s->hart_halted);
g_free(s->hart_resumeack);
g_free(s->hart_havereset);
@@ -1109,6 +1477,7 @@ static void riscv_dm_unrealize(DeviceState *dev)
g_free(s->halt_irqs);
}
+
static const Property riscv_dm_props[] = {
DEFINE_PROP_UINT32("num-harts", RISCVDMState, num_harts, 1),
DEFINE_PROP_UINT32("datacount", RISCVDMState, num_abstract_data, 2),
@@ -1183,5 +1552,6 @@ DeviceState *riscv_dm_create(MemoryRegion *sys_mem, hwaddr base,
/* MMIO region 2: debug ROM entry vector */
memory_region_add_subregion(sys_mem, base + RISCV_DM_ROM_ENTRY,
sysbus_mmio_get_region(sbd, 2));
+
return dev;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 21/28] hw/riscv/dm: add abstract command execution
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (19 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 20/28] hw/riscv/dm: add register handlers and update state management Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 22/28] hw/riscv/dm: add system bus access Chao Liu
` (6 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Implement execution-based abstract commands following the pattern
described in the RISC-V Debug Specification Appendix A.2.
The Access Register command generates RISC-V instructions into
the ROM command space for CSR (0x0000-0x0FFF), GPR (0x1000-0x101F),
and FPR (0x1020-0x103F) transfers. The hart executes these
instructions, and the DM detects completion when the hart returns
to the park loop.
This also adds the COMMAND and ABSTRACTAUTO register handlers, the
autoexec mechanism that re-executes the last command on DATA or
PROGBUF access, and the CMDERR sticky error reporting.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/dm.c | 231 +++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 228 insertions(+), 3 deletions(-)
diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c
index 169863c54c..818e48629f 100644
--- a/hw/riscv/dm.c
+++ b/hw/riscv/dm.c
@@ -336,6 +336,18 @@ static inline bool dm_ndmreset_active(RISCVDMState *s)
return ARRAY_FIELD_EX32(s->regs, DMCONTROL, NDMRESET);
}
+static RISCVCPU *dm_get_cpu(RISCVDMState *s, uint32_t hartsel)
+{
+ CPUState *cs;
+
+ if (!dm_hart_valid(s, hartsel)) {
+ return NULL;
+ }
+
+ cs = qemu_get_cpu(hartsel);
+ return cs ? RISCV_CPU(cs) : NULL;
+}
+
static bool dm_abstract_cmd_completed(RISCVDMState *s, uint32_t hartsel)
{
uint32_t selected = dm_get_hartsel(s);
@@ -545,6 +557,185 @@ static inline void dm_set_cmderr(RISCVDMState *s, uint32_t err)
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, CMDERR, err);
}
}
+static void dm_execute_access_register(RISCVDMState *s, uint32_t command)
+{
+ uint32_t regno = FIELD_EX32(command, COMMAND, REGNO);
+ bool write = FIELD_EX32(command, COMMAND, WRITE);
+ bool transfer = FIELD_EX32(command, COMMAND, TRANSFER);
+ bool postexec = FIELD_EX32(command, COMMAND, POSTEXEC);
+ bool aarpostinc = FIELD_EX32(command, COMMAND, AARPOSTINCREMENT);
+ uint32_t aarsize = FIELD_EX32(command, COMMAND, AARSIZE);
+
+ uint32_t hartsel = dm_get_hartsel(s);
+ RISCVCPU *cpu = dm_get_cpu(s, hartsel);
+ bool csr_hit = (regno <= RISCV_DM_REGNO_CSR_END);
+ bool gpr_hit = (regno >= RISCV_DM_REGNO_GPR_START &&
+ regno <= RISCV_DM_REGNO_GPR_END);
+ bool fpr_hit = (regno >= RISCV_DM_REGNO_FPR_START &&
+ regno <= RISCV_DM_REGNO_FPR_END);
+ uint32_t max_aarsize = 0;
+
+ /* Hart must be halted */
+ if (!dm_hart_valid(s, hartsel) || !s->hart_halted[hartsel]) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_HALTRESUME);
+ trace_riscv_dm_abstract_cmd_rejected("hart_not_halted",
+ ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR));
+ return;
+ }
+
+ if (!cpu) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_HALTRESUME);
+ trace_riscv_dm_abstract_cmd_rejected("hart_unavailable",
+ ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR));
+ return;
+ }
+
+ dm_flush_cmd_space(s);
+
+ if (transfer) {
+ if (csr_hit || gpr_hit) {
+ max_aarsize = riscv_cpu_mxl(&cpu->env) == MXL_RV32 ? 2 : 3;
+ } else if (fpr_hit) {
+ if (riscv_has_ext(&cpu->env, RVD)) {
+ max_aarsize = 3;
+ } else if (riscv_has_ext(&cpu->env, RVF)) {
+ max_aarsize = 2;
+ }
+ }
+
+ if ((csr_hit || gpr_hit || fpr_hit) &&
+ (aarsize < 2 || aarsize > max_aarsize)) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_NOTSUP);
+ trace_riscv_dm_abstract_cmd_rejected("unsupported_aarsize",
+ ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR));
+ return;
+ }
+
+ if (!write) {
+ /* Read: copy register -> data */
+ if (csr_hit) {
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 2 * 4,
+ DM_CSRR(regno & 0xFFF));
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 3 * 4,
+ DM_DATA_STORE(8, aarsize));
+ } else if (gpr_hit) {
+ if ((regno & 0x1F) == 8) {
+ /*
+ * GPR 8 (s0) is used as a scratch register by the ROM.
+ * Read the architected x8 value from dscratch0 first.
+ */
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 2 * 4,
+ DM_CSRR(0x7b2));
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 3 * 4,
+ DM_DATA_STORE(8, aarsize));
+ } else {
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 2 * 4,
+ DM_DATA_STORE(regno & 0x1F, aarsize));
+ }
+ } else if (fpr_hit) {
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 2 * 4,
+ DM_DATA_FP_STORE(regno & 0x1F, aarsize));
+ } else {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_EXCEPTION);
+ trace_riscv_dm_abstract_cmd_rejected("unsupported_regno_read",
+ ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR));
+ return;
+ }
+ } else {
+ /* Write: copy data -> register */
+ if (csr_hit) {
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 2 * 4,
+ DM_DATA_LOAD(8, aarsize));
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 3 * 4,
+ DM_CSRW(regno & 0xFFF));
+ } else if (gpr_hit) {
+ if ((regno & 0x1F) == 8) {
+ /* GPR 8 (s0) is saved in dscratch0 */
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 2 * 4,
+ DM_DATA_LOAD(8, aarsize));
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 3 * 4,
+ DM_CSRW(0x7b2));
+ } else {
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 2 * 4,
+ DM_DATA_LOAD(regno & 0x1F, aarsize));
+ }
+ } else if (fpr_hit) {
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 2 * 4,
+ DM_DATA_FP_LOAD(regno & 0x1F, aarsize));
+ } else {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_EXCEPTION);
+ trace_riscv_dm_abstract_cmd_rejected("unsupported_regno_write",
+ ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR));
+ return;
+ }
+ }
+ }
+
+ /* Post-increment */
+ if (aarpostinc && transfer) {
+ s->last_cmd = FIELD_DP32(command, COMMAND, REGNO, regno + 1);
+ }
+
+ if (postexec) {
+ if (s->progbuf_size == 0) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_NOTSUP);
+ trace_riscv_dm_abstract_cmd_rejected("postexec_without_progbuf",
+ ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR));
+ return;
+ }
+
+ /* Restore s0 first, then continue with Program Buffer. */
+ dm_rom_write32(s, RISCV_DM_ROM_CMD + 9 * 4, DM_JAL(2));
+ }
+
+ dm_invalidate_dynamic_code(s);
+
+ /* Set BUSY */
+ ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, BUSY, 1);
+
+ /* Set FLAGS to going and signal hart */
+ dm_rom_write8(s, RISCV_DM_ROM_FLAGS + hartsel, RISCV_DM_FLAG_GOING);
+
+ trace_riscv_dm_abstract_cmd_submit(
+ FIELD_EX32(command, COMMAND, CMDTYPE), regno, transfer,
+ write, aarpostinc, postexec);
+}
+
+static bool dm_execute_abstract_cmd(RISCVDMState *s, uint32_t command)
+{
+ uint32_t cmderr = ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR);
+
+ /* If cmderr is latched, reject silently */
+ if (cmderr != RISCV_DM_CMDERR_NONE) {
+ trace_riscv_dm_abstract_cmd_rejected("cmderr_latched", cmderr);
+ return false;
+ }
+
+ /* If busy, set cmderr=busy */
+ if (ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY)) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_BUSY);
+ trace_riscv_dm_abstract_cmd_rejected("command_while_busy",
+ ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR));
+ return false;
+ }
+
+ uint32_t cmdtype = FIELD_EX32(command, COMMAND, CMDTYPE);
+
+ switch (cmdtype) {
+ case RISCV_DM_CMD_ACCESS_REG:
+ dm_execute_access_register(s, command);
+ break;
+ default:
+ dm_set_cmderr(s, RISCV_DM_CMDERR_NOTSUP);
+ trace_riscv_dm_abstract_cmd_rejected("unsupported_cmdtype",
+ ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, CMDERR));
+ break;
+ }
+
+ return true;
+}
+
+
static void dm_status_refresh(RISCVDMState *s)
{
@@ -805,9 +996,13 @@ static uint64_t dm_abstractcs_pre_write(RegisterInfo *reg, uint64_t val64)
static uint64_t dm_command_pre_write(RegisterInfo *reg, uint64_t val64)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
+ uint32_t command = (uint32_t)val64;
- /* Stub: abstract command execution added in a follow-on patch. */
- s->last_cmd = (uint32_t)val64;
+ if (dm_execute_abstract_cmd(s, command)) {
+ s->last_cmd = command;
+ }
+
+ /* COMMAND is WARZ: keep internal state, but reads always return 0. */
return s->regs[R_COMMAND];
}
@@ -825,7 +1020,6 @@ static uint64_t dm_abstractauto_pre_write(RegisterInfo *reg, uint64_t val64)
return s->regs[R_ABSTRACTAUTO];
}
- /* Stub: autoexec trigger logic added in a follow-on patch. */
uint32_t data_count = MIN(s->num_abstract_data, 12);
uint32_t pbuf_size = MIN(s->progbuf_size, 16);
uint32_t data_mask = data_count ? ((1u << data_count) - 1u) : 0;
@@ -835,6 +1029,27 @@ static uint64_t dm_abstractauto_pre_write(RegisterInfo *reg, uint64_t val64)
return (uint32_t)val64 & mask;
}
+static void dm_check_autoexec(RISCVDMState *s, bool is_data, int index)
+{
+ if (ARRAY_FIELD_EX32(s->regs, ABSTRACTCS, BUSY)) {
+ dm_set_cmderr(s, RISCV_DM_CMDERR_BUSY);
+ return;
+ }
+
+ uint32_t autoexec = s->regs[R_ABSTRACTAUTO];
+ bool trigger;
+
+ if (is_data) {
+ trigger = (autoexec >> index) & 1;
+ } else {
+ trigger = (autoexec >> (16 + index)) & 1;
+ }
+
+ if (trigger) {
+ dm_execute_abstract_cmd(s, s->last_cmd);
+ }
+}
+
static uint64_t dm_data_pre_write(RegisterInfo *reg, uint64_t val64)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
@@ -852,10 +1067,15 @@ static void dm_data_post_write(RegisterInfo *reg, uint64_t val64)
int index = (reg->access->addr - A_DATA0) / 4;
dm_sync_data_to_rom(s, index);
+ dm_check_autoexec(s, true, index);
}
static uint64_t dm_data_post_read(RegisterInfo *reg, uint64_t val)
{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ int index = (reg->access->addr - A_DATA0) / 4;
+
+ dm_check_autoexec(s, true, index);
return val;
}
@@ -876,10 +1096,15 @@ static void dm_progbuf_post_write(RegisterInfo *reg, uint64_t val64)
int index = (reg->access->addr - A_PROGBUF0) / 4;
dm_sync_progbuf_to_rom(s, index);
+ dm_check_autoexec(s, false, index);
}
static uint64_t dm_progbuf_post_read(RegisterInfo *reg, uint64_t val)
{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ int index = (reg->access->addr - A_PROGBUF0) / 4;
+
+ dm_check_autoexec(s, false, index);
return val;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 22/28] hw/riscv/dm: add system bus access
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (20 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 21/28] hw/riscv/dm: add abstract command execution Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 23/28] hw/riscv/virt: integrate the Debug Module Chao Liu
` (5 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Implement the optional System Bus Access (SBA) register set
that allows the debugger to read and write system memory
without involving a hart.
This adds SBCS, SBADDRESS0-3, and SBDATA0-3 register handlers
with sticky error reporting, sbreadonaddr, sbreadondata, and
sbautoincrement support. The SBA path supports 8/16/32/64-bit
access widths and sets the SBA capability bits during DM reset
based on the configured sba-addr-width property.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/dm.c | 249 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 242 insertions(+), 7 deletions(-)
diff --git a/hw/riscv/dm.c b/hw/riscv/dm.c
index 818e48629f..bcaaffd02b 100644
--- a/hw/riscv/dm.c
+++ b/hw/riscv/dm.c
@@ -19,6 +19,7 @@
#include "exec/cpu-common.h"
#include "migration/vmstate.h"
#include "exec/translation-block.h"
+#include "system/address-spaces.h"
#include "system/tcg.h"
#include "target/riscv/cpu.h"
#include "trace.h"
@@ -736,6 +737,95 @@ static bool dm_execute_abstract_cmd(RISCVDMState *s, uint32_t command)
}
+static int dm_sba_access_bytes(uint32_t sbaccess)
+{
+ if (sbaccess <= 4) {
+ return 1 << sbaccess;
+ }
+ return 0;
+}
+
+static bool dm_sba_access_supported(RISCVDMState *s, uint32_t sbaccess)
+{
+ switch (sbaccess) {
+ case 0: return ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS8);
+ case 1: return ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS16);
+ case 2: return ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS32);
+ case 3: return ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS64);
+ case 4: return ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS128);
+ default: return false;
+ }
+}
+
+static void dm_sba_execute(RISCVDMState *s, bool is_write)
+{
+ uint32_t sbaccess = ARRAY_FIELD_EX32(s->regs, SBCS, SBACCESS);
+ int access_bytes = dm_sba_access_bytes(sbaccess);
+ uint8_t buf[16] = { 0 };
+ MemTxResult result;
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSYERROR) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBERROR)) {
+ return;
+ }
+
+ if (!access_bytes || !dm_sba_access_supported(s, sbaccess)) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBERROR, 4); /* other error */
+ return;
+ }
+
+ if (s->regs[R_SBADDRESS2] || s->regs[R_SBADDRESS3]) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBERROR, 2);
+ return;
+ }
+
+ hwaddr lo = s->regs[R_SBADDRESS0];
+ hwaddr hi = s->regs[R_SBADDRESS1];
+ hwaddr addr = (hi << 32) | lo;
+
+ if (addr & (access_bytes - 1)) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBERROR, 3); /* alignment */
+ return;
+ }
+
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSY, 1);
+ for (int i = 0; i < 4; i++) {
+ stl_le_p(buf + i * 4, s->regs[R_SBDATA0 + i]);
+ }
+
+ if (is_write) {
+ result = address_space_rw(&address_space_memory, addr,
+ MEMTXATTRS_UNSPECIFIED, buf,
+ access_bytes, true);
+ } else {
+ result = address_space_rw(&address_space_memory, addr,
+ MEMTXATTRS_UNSPECIFIED, buf,
+ access_bytes, false);
+ if (result == MEMTX_OK) {
+ for (int i = 0; i < 4; i++) {
+ s->regs[R_SBDATA0 + i] = ldl_le_p(buf + i * 4);
+ }
+ }
+ }
+
+ if (result != MEMTX_OK) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBERROR,
+ result == MEMTX_DECODE_ERROR ? 2 : 7);
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSY, 0);
+ return;
+ }
+
+ trace_riscv_dm_sba_access(addr, s->regs[R_SBDATA0], access_bytes, is_write);
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBAUTOINCREMENT)) {
+ addr += access_bytes;
+ s->regs[R_SBADDRESS0] = (uint32_t)addr;
+ s->regs[R_SBADDRESS1] = addr >> 32;
+ }
+
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSY, 0);
+}
+
static void dm_status_refresh(RISCVDMState *s)
{
@@ -1108,6 +1198,129 @@ static uint64_t dm_progbuf_post_read(RegisterInfo *reg, uint64_t val)
return val;
}
+static uint64_t dm_sbcs_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+ uint32_t val = (uint32_t)val64;
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSY)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "riscv-dm: write to sbcs while busy\n");
+ return s->regs[R_SBCS];
+ }
+
+ uint32_t cur = s->regs[R_SBCS];
+
+ /* W1C: sberror and sbbusyerror */
+ uint32_t sberror_clr = FIELD_EX32(val, SBCS, SBERROR);
+ uint32_t sberror_cur = FIELD_EX32(cur, SBCS, SBERROR);
+ cur = FIELD_DP32(cur, SBCS, SBERROR, sberror_cur & ~sberror_clr);
+
+ uint32_t busyerr_clr = FIELD_EX32(val, SBCS, SBBUSYERROR);
+ uint32_t busyerr_cur = FIELD_EX32(cur, SBCS, SBBUSYERROR);
+ cur = FIELD_DP32(cur, SBCS, SBBUSYERROR, busyerr_cur & ~busyerr_clr);
+
+ /* Writable fields */
+ cur = FIELD_DP32(cur, SBCS, SBREADONADDR,
+ FIELD_EX32(val, SBCS, SBREADONADDR));
+ cur = FIELD_DP32(cur, SBCS, SBACCESS,
+ FIELD_EX32(val, SBCS, SBACCESS));
+ cur = FIELD_DP32(cur, SBCS, SBAUTOINCREMENT,
+ FIELD_EX32(val, SBCS, SBAUTOINCREMENT));
+ cur = FIELD_DP32(cur, SBCS, SBREADONDATA,
+ FIELD_EX32(val, SBCS, SBREADONDATA));
+
+ return cur;
+}
+
+static void dm_sbaddress0_post_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSY)) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSYERROR, 1);
+ return;
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBREADONADDR) &&
+ !ARRAY_FIELD_EX32(s->regs, SBCS, SBERROR) &&
+ !ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSYERROR)) {
+ dm_sba_execute(s, false);
+ }
+}
+
+static uint64_t dm_sbaddress1_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSY)) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSYERROR, 1);
+ return s->regs[R_SBADDRESS1];
+ }
+ return (uint32_t)val64;
+}
+
+static void dm_sbdata0_post_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSY)) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSYERROR, 1);
+ return;
+ }
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSYERROR) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBERROR)) {
+ return;
+ }
+
+ dm_sba_execute(s, true);
+}
+
+static uint64_t dm_sbdata0_post_read(RegisterInfo *reg, uint64_t val)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSY)) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSYERROR, 1);
+ return val;
+ }
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSYERROR) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBERROR)) {
+ return val;
+ }
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBREADONDATA)) {
+ dm_sba_execute(s, false);
+ return val;
+ }
+ return val;
+}
+
+static uint64_t dm_sbdata1_pre_write(RegisterInfo *reg, uint64_t val64)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSY)) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSYERROR, 1);
+ return s->regs[R_SBDATA1];
+ }
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSYERROR) ||
+ ARRAY_FIELD_EX32(s->regs, SBCS, SBERROR)) {
+ return s->regs[R_SBDATA1];
+ }
+ return (uint32_t)val64;
+}
+
+static uint64_t dm_sbdata_hi_post_read(RegisterInfo *reg, uint64_t val)
+{
+ RISCVDMState *s = RISCV_DM(reg->opaque);
+
+ if (ARRAY_FIELD_EX32(s->regs, SBCS, SBBUSY)) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBBUSYERROR, 1);
+ }
+ return val;
+}
+
static uint64_t dm_haltsum0_post_read(RegisterInfo *reg, uint64_t val)
{
RISCVDMState *s = RISCV_DM(reg->opaque);
@@ -1287,20 +1500,31 @@ static RegisterAccessInfo riscv_dm_regs_info[] = {
.ro = R_SBCS_SBACCESS8_MASK | R_SBCS_SBACCESS16_MASK |
R_SBCS_SBACCESS32_MASK | R_SBCS_SBACCESS64_MASK |
R_SBCS_SBACCESS128_MASK | R_SBCS_SBASIZE_MASK |
- R_SBCS_SBBUSY_MASK | R_SBCS_SBVERSION_MASK, },
+ R_SBCS_SBBUSY_MASK | R_SBCS_SBVERSION_MASK,
+ .pre_write = dm_sbcs_pre_write, },
- { .name = "SBADDRESS0", .addr = A_SBADDRESS0, },
+ { .name = "SBADDRESS0", .addr = A_SBADDRESS0,
+ .post_write = dm_sbaddress0_post_write, },
- { .name = "SBADDRESS1", .addr = A_SBADDRESS1, },
+ { .name = "SBADDRESS1", .addr = A_SBADDRESS1,
+ .pre_write = dm_sbaddress1_pre_write, },
{ .name = "SBADDRESS2", .addr = A_SBADDRESS2, },
- { .name = "SBDATA0", .addr = A_SBDATA0, },
+ { .name = "SBDATA0", .addr = A_SBDATA0,
+ .post_write = dm_sbdata0_post_write,
+ .post_read = dm_sbdata0_post_read, },
- { .name = "SBDATA1", .addr = A_SBDATA1, },
+ { .name = "SBDATA1", .addr = A_SBDATA1,
+ .pre_write = dm_sbdata1_pre_write,
+ .post_read = dm_sbdata_hi_post_read, },
- { .name = "SBDATA2", .addr = A_SBDATA2, },
- { .name = "SBDATA3", .addr = A_SBDATA3, },
+ { .name = "SBDATA2", .addr = A_SBDATA2,
+ .pre_write = dm_sbdata1_pre_write,
+ .post_read = dm_sbdata_hi_post_read, },
+ { .name = "SBDATA3", .addr = A_SBDATA3,
+ .pre_write = dm_sbdata1_pre_write,
+ .post_read = dm_sbdata_hi_post_read, },
{ .name = "HALTSUM0", .addr = A_HALTSUM0,
.ro = 0xFFFFFFFF,
@@ -1602,6 +1826,17 @@ static void dm_debug_reset(RISCVDMState *s)
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, DATACOUNT, s->num_abstract_data);
ARRAY_FIELD_DP32(s->regs, ABSTRACTCS, PROGBUFSIZE, s->progbuf_size);
+ /* SBA capabilities */
+ if (s->sba_addr_width > 0) {
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBACCESS8, 1);
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBACCESS16, 1);
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBACCESS32, 1);
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBACCESS64, 1);
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBASIZE, s->sba_addr_width);
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBACCESS, 2); /* default 32-bit */
+ ARRAY_FIELD_DP32(s->regs, SBCS, SBVERSION, 1);
+ }
+
/* Reset per-hart state */
if (s->hart_resumeack && s->num_harts > 0) {
for (uint32_t i = 0; i < s->num_harts; i++) {
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 23/28] hw/riscv/virt: integrate the Debug Module
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (21 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 22/28] hw/riscv/dm: add system bus access Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 24/28] tests/qtest: add initial RISC-V Debug Module tests Chao Liu
` (4 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Expand the VIRT_DEBUG memory map entry from 0x100 to
RISCV_DM_SIZE (0x1000 = 4 KiB) to accommodate the full
Debug Module address space: DMI registers, work area, and ROM
entry vector.
Create the Debug Module device via riscv_dm_create() and wire
each hart's halt-request GPIO to it. Set dm_halt_addr so the
CPU enters the DM ROM entry point on debug-mode entry.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
hw/riscv/virt.c | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index bbce2fb667..0a7e29a743 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -58,6 +58,7 @@
#include "qapi/qapi-visit-common.h"
#include "hw/virtio/virtio-iommu.h"
#include "hw/uefi/var-service-api.h"
+#include "hw/riscv/dm.h"
/* KVM AIA only supports APLIC MSI. APLIC Wired is always emulated by QEMU. */
static bool virt_use_kvm_aia_aplic_imsic(RISCVVirtAIAType aia_type)
@@ -80,7 +81,7 @@ static bool virt_aclint_allowed(void)
}
static const MemMapEntry virt_memmap[] = {
- [VIRT_DEBUG] = { 0x0, 0x100 },
+ [VIRT_DEBUG] = { 0x0, RISCV_DM_SIZE },
[VIRT_MROM] = { 0x1000, 0xf000 },
[VIRT_TEST] = { 0x100000, 0x1000 },
[VIRT_RTC] = { 0x101000, 0x1000 },
@@ -1702,6 +1703,24 @@ static void virt_machine_init(MachineState *machine)
create_platform_bus(s, mmio_irqchip);
+ /* Create Debug Module and connect each hart's halt-request IRQ */
+ int total_harts = machine->smp.cpus;
+ DeviceState *dm = riscv_dm_create(system_memory,
+ s->memmap[VIRT_DEBUG].base,
+ total_harts);
+
+ for (int h = 0; h < total_harts; h++) {
+ CPUState *cs = qemu_get_cpu(h);
+ RISCVCPU *rcpu = RISCV_CPU(cs);
+
+ qdev_connect_gpio_out(dm, h,
+ qdev_get_gpio_in_named(DEVICE(cs),
+ "dm-halt-req", 0));
+ rcpu->env.dm_rom_present = true;
+ rcpu->env.dm_halt_addr = s->memmap[VIRT_DEBUG].base +
+ RISCV_DM_ROM_ENTRY;
+ }
+
serial_mm_init(system_memory, s->memmap[VIRT_UART0].base,
0, qdev_get_gpio_in(mmio_irqchip, UART0_IRQ), 399193,
serial_hd(0), DEVICE_LITTLE_ENDIAN);
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 24/28] tests/qtest: add initial RISC-V Debug Module tests
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (22 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 23/28] hw/riscv/virt: integrate the Debug Module Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 25/28] tests/qtest: extend DM register semantics coverage Chao Liu
` (3 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
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 <chao.liu.zevorn@gmail.com>
---
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 <christoph.muellner@vrull.eu>
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 <chao.liu.zevorn@gmail.com>
+ *
+ * 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
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 25/28] tests/qtest: extend DM register semantics coverage
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (23 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 24/28] tests/qtest: add initial RISC-V Debug Module tests Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 26/28] tests/qtest: add DM abstract command and halt/resume tests Chao Liu
` (2 subsequent siblings)
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
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 <chao.liu.zevorn@gmail.com>
---
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
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 26/28] tests/qtest: add DM abstract command and halt/resume tests
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (24 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 25/28] tests/qtest: extend DM register semantics coverage Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 27/28] tests/qtest: add DM TCG halt and register access tests Chao Liu
2026-03-08 7:17 ` [PATCH v1 28/28] tests/qtest: add DM TCG single-step and trigger tests Chao Liu
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Add the ROM simulation helpers (sim_cpu_going_ack,
sim_cpu_exception) and tests that exercise the abstract command
interface and hart-window registers in qtest mode:
abstractauto – auto-execute on data/progbuf write
command-not-halted – command rejected when hart running
invalid-regno-exception – out-of-range register access
abstract-cmd-flow – SMP abstract read/write cycle
abstract-cmd-exception – exception during abstract command
hawindow – hart array window register
haltsum0-window – haltsum0 with 64-hart SMP
haltsum1-window – haltsum1 with 64-hart SMP
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
tests/qtest/riscv-dm-test.c | 315 ++++++++++++++++++++++++++++++++++++
1 file changed, 315 insertions(+)
diff --git a/tests/qtest/riscv-dm-test.c b/tests/qtest/riscv-dm-test.c
index 3a0dd1cbd4..1b1ab02284 100644
--- a/tests/qtest/riscv-dm-test.c
+++ b/tests/qtest/riscv-dm-test.c
@@ -195,6 +195,24 @@ static void sim_cpu_resume_ack(QTestState *qts, uint32_t hartid)
rom_write32(qts, ROM_RESUME, hartid);
}
+/*
+ * Simulate the CPU executing the GOING acknowledgment.
+ * ROM code: detects GOING flag → writes 0 to GOING offset → jumps to cmd.
+ */
+static void sim_cpu_going_ack(QTestState *qts)
+{
+ rom_write32(qts, ROM_GOING, 0);
+}
+
+/*
+ * Simulate CPU hitting exception during abstract command.
+ * ROM exception handler writes 0 to EXCP offset.
+ */
+static void sim_cpu_exception(QTestState *qts)
+{
+ rom_write32(qts, ROM_EXCP, 0);
+}
+
/*
* Test: dmactive gate.
@@ -626,6 +644,293 @@ static void test_ackhavereset(void)
qtest_quit(qts);
}
+/*
+ * Test: Unsupported register numbers report CMDERR=EXCEPTION.
+ */
+static void test_invalid_regno_exception(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ uint32_t cmd, acs, cmderr;
+
+ dm_set_active(qts);
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+ sim_cpu_halt_ack(qts, 0);
+
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ cmd = (0u << 24) | (1u << 17) | (2u << 20) | 0x1040u;
+ dm_write(qts, A_COMMAND, cmd);
+
+ acs = dm_read(qts, A_ABSTRACTCS);
+ cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 3);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: ABSTRACTAUTO read/write.
+ */
+static void test_abstractauto(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ dm_set_active(qts);
+
+ /* Write autoexec pattern */
+ dm_write(qts, A_ABSTRACTAUTO, 0x00030003);
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTAUTO), ==, 0x00030003);
+
+ /* Clear */
+ dm_write(qts, A_ABSTRACTAUTO, 0);
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTAUTO), ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: COMMAND write when hart is not halted should set CMDERR.
+ */
+static void test_command_not_halted(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ dm_set_active(qts);
+
+ /* Clear any existing CMDERR */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ /*
+ * Issue Access Register command (cmdtype=0, transfer=1, regno=0x1000)
+ * to a running hart. Should fail with HALTRESUME error (4).
+ */
+ uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */
+ (1u << 17) | /* transfer = 1 */
+ (2u << 20) | /* aarsize = 32-bit */
+ 0x1000; /* regno = x0 */
+ dm_write(qts, A_COMMAND, cmd);
+
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ uint32_t cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ /* HALTRESUME error = 4 */
+ g_assert_cmpuint(cmderr, ==, 4);
+
+ /* Clear CMDERR */
+ 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: Abstract command execution flow (Access Register).
+ *
+ * With hart halted, issue an Access Register command:
+ * 1. Hart must be halted first
+ * 2. Write COMMAND → DM sets BUSY=1, writes instructions to CMD space,
+ * sets FLAGS=GOING
+ * 3. Hart can keep reporting HALTED in the park loop before consuming GO
+ * 4. CPU ROM detects GOING → writes to GOING offset (ack) → jumps to cmd
+ * 5. CPU executes cmd → hits ebreak → re-enters ROM → writes HARTID
+ * 6. DM only completes after the selected hart returns to the park loop
+ */
+static void test_abstract_cmd_flow(void)
+{
+ QTestState *qts = qtest_init("-machine virt -smp 2");
+ dm_set_active(qts);
+
+ /* Halt both harts first. */
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+ sim_cpu_halt_ack(qts, 0);
+ sim_cpu_halt_ack(qts, 1);
+
+ uint32_t status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+
+ /* Clear any latched CMDERR */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ /*
+ * Issue Access Register command:
+ * cmdtype=0 (Access Register), transfer=1, write=0 (read),
+ * aarsize=2 (32-bit), regno=0x1000 (x0)
+ */
+ uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */
+ (1u << 17) | /* transfer = 1 */
+ (2u << 20) | /* aarsize = 32-bit */
+ 0x1000; /* regno = x0 */
+ dm_write(qts, A_COMMAND, cmd);
+
+ /* BUSY should be set */
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, ABSTRACTCS_BUSY);
+
+ /*
+ * A different halted hart must not complete the command, and the selected
+ * hart must not complete it before GO has been consumed.
+ */
+ sim_cpu_halt_ack(qts, 1);
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTCS) & ABSTRACTCS_BUSY, ==,
+ ABSTRACTCS_BUSY);
+ sim_cpu_halt_ack(qts, 0);
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTCS) & ABSTRACTCS_BUSY, ==,
+ ABSTRACTCS_BUSY);
+
+ /* Simulate CPU: ROM detects GOING flag → writes GOING ack. */
+ sim_cpu_going_ack(qts);
+
+ /* Simulate CPU: execute cmd, hit ebreak, then re-enter ROM. */
+ sim_cpu_halt_ack(qts, 0);
+
+ /* BUSY should be cleared, no error */
+ acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0);
+ uint32_t cmderr =
+ (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: Abstract command exception path.
+ *
+ * When CPU hits an exception during abstract cmd execution:
+ * 1. ROM exception handler writes to EXCP offset
+ * 2. DM latches CMDERR=EXCEPTION(3) and stays busy until the hart parks again
+ * 3. CPU ebreak → re-enters ROM → writes HARTID (re-halt)
+ */
+static void test_abstract_cmd_exception(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ dm_set_active(qts);
+
+ /* Halt hart 0 */
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+ sim_cpu_halt_ack(qts, 0);
+
+ /* Clear CMDERR */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ /* Issue Access Register command */
+ uint32_t cmd = (0u << 24) | (1u << 17) | (2u << 20) | 0x1000;
+ dm_write(qts, A_COMMAND, cmd);
+
+ g_assert_cmpuint(dm_read(qts, A_ABSTRACTCS) & ABSTRACTCS_BUSY, ==,
+ ABSTRACTCS_BUSY);
+
+ /* CPU acknowledges going */
+ sim_cpu_going_ack(qts);
+
+ /* CPU hits exception during cmd execution → ROM exception handler */
+ sim_cpu_exception(qts);
+
+ /* BUSY stays set until the hart re-enters the park loop. */
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, ABSTRACTCS_BUSY);
+ uint32_t cmderr =
+ (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 3); /* EXCEPTION */
+
+ /* CPU ebreak in exception handler → re-enters ROM → writes HARTID */
+ sim_cpu_halt_ack(qts, 0);
+
+ acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0);
+
+ /* Hart should still be halted */
+ uint32_t st = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(st & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+
+ /* Clear CMDERR for cleanup */
+ 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: HAWINDOWSEL and HAWINDOW read/write.
+ */
+static void test_hawindow(void)
+{
+ QTestState *qts = qtest_init("-machine virt");
+ dm_set_active(qts);
+
+ /* Select window 0 */
+ dm_write(qts, A_HAWINDOWSEL, 0);
+ g_assert_cmpuint(dm_read(qts, A_HAWINDOWSEL), ==, 0);
+
+ /* Write a mask to HAWINDOW */
+ dm_write(qts, A_HAWINDOW, 0x5);
+ g_assert_cmpuint(dm_read(qts, A_HAWINDOW), ==, 0x5);
+
+ /* Clear */
+ dm_write(qts, A_HAWINDOW, 0);
+ g_assert_cmpuint(dm_read(qts, A_HAWINDOW), ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: HALTSUM0 window follows hartsel[19:5].
+ */
+static void test_haltsum0_window(void)
+{
+ QTestState *qts = qtest_init("-machine virt -smp 64");
+ dm_set_active(qts);
+
+ /* Mark hart 40 halted. */
+ sim_cpu_halt_ack(qts, 40);
+
+ /* Window base 0: hart 40 is out of range 0..31. */
+ dm_write(qts, A_DMCONTROL,
+ DMCONTROL_DMACTIVE | (0u << DMCONTROL_HARTSELLO_SHIFT));
+ g_assert_cmpuint(dm_read(qts, A_HALTSUM0), ==, 0);
+
+ /* Window base 32: hart 40 maps to bit 8. */
+ dm_write(qts, A_DMCONTROL,
+ DMCONTROL_DMACTIVE | (32u << DMCONTROL_HARTSELLO_SHIFT));
+ g_assert_cmpuint(dm_read(qts, A_HALTSUM0), ==, (1u << 8));
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: HALTSUM1 window follows hartsel[19:10].
+ */
+static void test_haltsum1_window(void)
+{
+ QTestState *qts = qtest_init("-machine virt -smp 64");
+
+ dm_set_active(qts);
+
+ sim_cpu_halt_ack(qts, 33);
+
+ g_assert_cmpuint(dm_read(qts, A_HALTSUM1), ==, (1u << 1));
+
+ dm_write(qts, A_DMCONTROL,
+ DMCONTROL_DMACTIVE | (1u << DMCONTROL_HARTSELHI_SHIFT));
+ g_assert_cmpuint(dm_read(qts, A_HALTSUM1), ==, 0);
+
+ qtest_quit(qts);
+}
+
+/* TCG-mode tests: real CPU execution. */
+
+/*
+ * Test: Halt CPU with TCG and verify DPC/DCSR.
+ *
+ * With real CPU execution:
+ * 1. Boot CPU, let it run briefly
+ * 2. Send HALTREQ → CPU enters debug mode
+ * 3. Read DCSR via abstract command → verify cause = HALTREQ (3)
+ * 4. Read DPC via abstract command → verify it's a valid address
+ * 5. Resume CPU, then re-halt to verify the cycle works
+ */
+
/*
* Test: Halt and resume cycle via ROM simulation.
*
@@ -691,7 +996,17 @@ int main(int argc, char *argv[])
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/abstractauto", test_abstractauto);
+ qtest_add_func("/riscv-dm/command-not-halted", test_command_not_halted);
+ qtest_add_func("/riscv-dm/invalid-regno-exception",
+ test_invalid_regno_exception);
+ qtest_add_func("/riscv-dm/hawindow", test_hawindow);
+ qtest_add_func("/riscv-dm/haltsum0-window", test_haltsum0_window);
+ qtest_add_func("/riscv-dm/haltsum1-window", test_haltsum1_window);
qtest_add_func("/riscv-dm/halt-resume", test_halt_resume);
+ qtest_add_func("/riscv-dm/abstract-cmd-flow", test_abstract_cmd_flow);
+ qtest_add_func("/riscv-dm/abstract-cmd-exception",
+ test_abstract_cmd_exception);
return g_test_run();
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 27/28] tests/qtest: add DM TCG halt and register access tests
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (25 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 26/28] tests/qtest: add DM abstract command and halt/resume tests Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
2026-03-08 7:17 ` [PATCH v1 28/28] tests/qtest: add DM TCG single-step and trigger tests Chao Liu
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Add TCG-mode helper functions that boot a real CPU and interact
with the Debug Module through clock stepping, then add the first
batch of TCG tests:
tcg/halt-dpc – halt via HALTREQ, verify DPC/DCSR
tcg/resethaltreq-priority – resethaltreq takes effect on reset
tcg/gpr-rw – read/write GPRs via abstract cmds
tcg/fpr-rw – read/write FPRs via abstract cmds
rv32-gpr64-notsup – 64-bit GPR access rejected on RV32
tcg/command-ignored-on-cmderr – sticky cmderr blocks new cmds
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
tests/qtest/riscv-dm-test.c | 355 ++++++++++++++++++++++++++++++++++++
1 file changed, 355 insertions(+)
diff --git a/tests/qtest/riscv-dm-test.c b/tests/qtest/riscv-dm-test.c
index 1b1ab02284..2e7c29ed7c 100644
--- a/tests/qtest/riscv-dm-test.c
+++ b/tests/qtest/riscv-dm-test.c
@@ -213,6 +213,139 @@ static void sim_cpu_exception(QTestState *qts)
rom_write32(qts, ROM_EXCP, 0);
}
+/* TCG-mode helpers: real CPU execution with clock stepping. */
+
+static QTestState *tcg_dm_init(void)
+{
+ const char *arch = qtest_get_arch();
+ const char *cpu_type = strstr(arch, "64") ? "rv64" : "rv32";
+ g_autofree char *args = g_strdup_printf(
+ "-machine virt -accel tcg -smp 1 -cpu %s,sdext=true", cpu_type);
+ QTestState *qts = qtest_init(args);
+
+ /* Check VM status */
+ QDict *resp = qtest_qmp(qts, "{'execute': 'query-status'}");
+ const QDict *ret = qdict_get_qdict(resp, "return");
+ bool running = qdict_get_bool(ret, "running");
+ const char *vm_status = qdict_get_str(ret, "status");
+ g_test_message("VM status: %s running=%d", vm_status, running);
+ qobject_unref(resp);
+
+ if (!running) {
+ resp = qtest_qmp(qts, "{'execute': 'cont'}");
+ g_test_message("Sent cont");
+ qobject_unref(resp);
+ }
+
+ /* Let the CPU boot via MTTCG thread */
+ g_usleep(TCG_BOOT_US);
+
+ dm_set_active(qts);
+ return qts;
+}
+
+static bool tcg_wait_for_halt(QTestState *qts)
+{
+ for (int t = 0; t < TCG_POLL_TIMEOUT_US; t += TCG_POLL_STEP_US) {
+ g_usleep(TCG_POLL_STEP_US);
+ uint32_t st = dm_read(qts, A_DMSTATUS);
+ if (t < TCG_POLL_STEP_US * 5) {
+ g_test_message("halt poll: DMSTATUS=0x%08x", st);
+ }
+ if (st & DMSTATUS_ALLHALTED) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool tcg_wait_for_cmd_done(QTestState *qts)
+{
+ for (int t = 0; t < TCG_POLL_TIMEOUT_US; t += TCG_POLL_STEP_US) {
+ g_usleep(TCG_POLL_STEP_US);
+ if (!(dm_read(qts, A_ABSTRACTCS) & ABSTRACTCS_BUSY)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool tcg_halt_hart(QTestState *qts)
+{
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+ return tcg_wait_for_halt(qts);
+}
+
+static bool tcg_resume_hart(QTestState *qts)
+{
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_RESUMEREQ);
+ for (int t = 0; t < TCG_POLL_TIMEOUT_US; t += TCG_POLL_STEP_US) {
+ g_usleep(TCG_POLL_STEP_US);
+ if (dm_read(qts, A_DMSTATUS) & DMSTATUS_ALLRESUMEACK) {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+/*
+ * Read a register via Access Register abstract command.
+ * Returns true on success, false on error or timeout.
+ */
+static bool tcg_abstract_read_reg(QTestState *qts, uint32_t regno,
+ uint32_t *val)
+{
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */
+ (1u << 17) | /* transfer = 1 */
+ (2u << 20) | /* aarsize = 32-bit */
+ (regno & 0xFFFF);
+ dm_write(qts, A_COMMAND, cmd);
+
+ if (!tcg_wait_for_cmd_done(qts)) {
+ return false;
+ }
+
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ if (acs & ABSTRACTCS_CMDERR_MASK) {
+ return false;
+ }
+
+ *val = dm_read(qts, A_DATA0);
+ return true;
+}
+
+/*
+ * Write a register via Access Register abstract command.
+ * Returns true on success, false on error or timeout.
+ */
+static bool tcg_abstract_write_reg(QTestState *qts, uint32_t regno,
+ uint32_t val)
+{
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+ dm_write(qts, A_DATA0, val);
+
+ uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */
+ (1u << 17) | /* transfer = 1 */
+ (1u << 16) | /* write = 1 */
+ (2u << 20) | /* aarsize = 32-bit */
+ (regno & 0xFFFF);
+ dm_write(qts, A_COMMAND, cmd);
+
+ if (!tcg_wait_for_cmd_done(qts)) {
+ return false;
+ }
+
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ return !(acs & ABSTRACTCS_CMDERR_MASK);
+}
+
+/*
+ * Write a 64-bit register via Access Register abstract command (aarsize=3).
+ * DATA1 holds high 32 bits, DATA0 holds low 32 bits.
+ */
/*
* Test: dmactive gate.
@@ -930,6 +1063,218 @@ static void test_haltsum1_window(void)
* 4. Read DPC via abstract command → verify it's a valid address
* 5. Resume CPU, then re-halt to verify the cycle works
*/
+static void test_tcg_halt_dpc(void)
+{
+ QTestState *qts = tcg_dm_init();
+
+ /* Halt the CPU */
+ g_assert_true(tcg_halt_hart(qts));
+
+ uint32_t status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+ g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, 0);
+
+ /* Read DCSR: verify cause = HALTREQ (3) */
+ uint32_t dcsr;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ uint32_t cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT;
+ g_assert_cmpuint(cause, ==, DCSR_CAUSE_HALTREQ);
+
+ /* Read DPC: implementation dependent, but it must stay aligned. */
+ uint32_t dpc;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &dpc));
+ g_assert_cmpuint(dpc & 1u, ==, 0);
+
+ /* Resume the CPU */
+ g_assert_true(tcg_resume_hart(qts));
+ status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLRESUMEACK, ==,
+ DMSTATUS_ALLRESUMEACK);
+
+ /* Let it run a bit, then re-halt */
+ g_usleep(TCG_BOOT_US);
+
+ g_assert_true(tcg_halt_hart(qts));
+
+ /* DPC after re-halt is also execution dependent; only assert alignment */
+ uint32_t dpc2;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &dpc2));
+ g_assert_cmpuint(dpc2 & 1u, ==, 0);
+
+ /* DCSR cause should be HALTREQ again */
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT;
+ g_assert_cmpuint(cause, ==, DCSR_CAUSE_HALTREQ);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: reset-haltreq and haltreq asserted together.
+ *
+ * Priority per Debug Spec v1.0 Table 4.2 requires reset-haltreq (cause=5)
+ * to win over haltreq (cause=3).
+ */
+static void test_tcg_resethaltreq_priority(void)
+{
+ QTestState *qts = tcg_dm_init();
+ uint32_t status, dcsr, cause;
+
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_SETRESETHALTREQ);
+
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE |
+ DMCONTROL_HARTRESET | DMCONTROL_HALTREQ);
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+
+ g_assert_true(tcg_wait_for_halt(qts));
+ status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT;
+ g_assert_cmpuint(cause, ==, DCSR_CAUSE_RESET);
+
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE |
+ DMCONTROL_CLRRESETHALTREQ | DMCONTROL_HALTREQ);
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: Read/Write all 32 GPR registers via abstract commands (TCG).
+ *
+ * For each GPR x0..x31:
+ * - x0: hardwired to 0, read should return 0
+ * - x1..x31: write a test pattern, read back and verify
+ */
+static void test_tcg_gpr_rw(void)
+{
+ QTestState *qts = tcg_dm_init();
+
+ g_assert_true(tcg_halt_hart(qts));
+
+ /* x0 is hardwired to 0 */
+ uint32_t val;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_GPR(0), &val));
+ g_assert_cmpuint(val, ==, 0);
+
+ /* x1..x31: write test patterns and read back */
+ for (int i = 1; i < 32; i++) {
+ uint32_t pattern = 0xA5000000u | (uint32_t)i;
+
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_GPR(i), pattern));
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_GPR(i), &val));
+ g_assert_cmpuint(val, ==, pattern);
+ }
+
+ /* Verify x0 is still 0 after all writes */
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_GPR(0), &val));
+ g_assert_cmpuint(val, ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: Read/Write all 32 FPR registers via abstract commands (TCG).
+ *
+ * For each FPR f0..f31:
+ * - Write a 32-bit test pattern (single-precision view)
+ * - Read back and verify
+ */
+static void test_tcg_fpr_rw(void)
+{
+ QTestState *qts = tcg_dm_init();
+
+ g_assert_true(tcg_halt_hart(qts));
+
+ uint32_t val;
+
+ for (int i = 0; i < 32; i++) {
+ uint32_t pattern = 0x3F800000u + (uint32_t)i; /* 1.0f + i */
+
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_FPR(i), pattern));
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_FPR(i), &val));
+ g_assert_cmpuint(val, ==, pattern);
+ }
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: RV32 rejects 64-bit GPR abstract accesses at command decode time.
+ */
+static void test_rv32_gpr64_notsup(void)
+{
+ QTestState *qts;
+ uint32_t acs;
+ const char *arch = qtest_get_arch();
+
+ if (!strstr(arch, "32")) {
+ g_test_skip("RV32-only");
+ return;
+ }
+
+ qts = qtest_init("-machine virt");
+
+ dm_set_active(qts);
+ dm_write(qts, A_DMCONTROL, DMCONTROL_DMACTIVE | DMCONTROL_HALTREQ);
+ sim_cpu_halt_ack(qts, 0);
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ dm_write(qts, A_COMMAND,
+ (0u << 24) | (1u << 17) | (3u << 20) | REGNO_GPR(1));
+
+ acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0);
+ g_assert_cmpuint((acs & ABSTRACTCS_CMDERR_MASK) >>
+ ABSTRACTCS_CMDERR_SHIFT, ==, 2);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: COMMAND writes are ignored while CMDERR is non-zero.
+ */
+static void test_tcg_command_ignored_on_cmderr(void)
+{
+ QTestState *qts = tcg_dm_init();
+
+ g_assert_true(tcg_halt_hart(qts));
+
+ /* Baseline last command: read x0, result must be 0. */
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+ dm_write(qts, A_COMMAND,
+ (0u << 24) | (1u << 17) | (2u << 20) | REGNO_GPR(0));
+ g_assert_true(tcg_wait_for_cmd_done(qts));
+ g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0);
+
+ /* Latch cmderr with an unsupported command. */
+ dm_write(qts, A_COMMAND, 0xFF000000);
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint((acs & ABSTRACTCS_CMDERR_MASK) >>
+ ABSTRACTCS_CMDERR_SHIFT, ==, 2);
+
+ /*
+ * This command must be ignored because cmderr != 0.
+ * If it incorrectly overwrites last_cmd, later autoexec will read DCSR.
+ */
+ dm_write(qts, A_COMMAND,
+ (0u << 24) | (1u << 17) | (2u << 20) | REGNO_DCSR);
+
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+ dm_write(qts, A_ABSTRACTAUTO, 0x1); /* autoexec on data0 */
+ dm_write(qts, A_DATA0, 0xDEADBEEF); /* triggers autoexec of last_cmd */
+
+ g_assert_true(tcg_wait_for_cmd_done(qts));
+ acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint((acs & ABSTRACTCS_CMDERR_MASK) >>
+ ABSTRACTCS_CMDERR_SHIFT, ==, 2);
+ g_assert_cmpuint(dm_read(qts, A_DATA0), ==, 0xDEADBEEF);
+
+ qtest_quit(qts);
+}
+
/*
* Test: Halt and resume cycle via ROM simulation.
@@ -1008,5 +1353,15 @@ int main(int argc, char *argv[])
qtest_add_func("/riscv-dm/abstract-cmd-exception",
test_abstract_cmd_exception);
+ /* TCG-mode tests: real CPU execution */
+ qtest_add_func("/riscv-dm/tcg/halt-dpc", test_tcg_halt_dpc);
+ qtest_add_func("/riscv-dm/tcg/resethaltreq-priority",
+ test_tcg_resethaltreq_priority);
+ qtest_add_func("/riscv-dm/tcg/gpr-rw", test_tcg_gpr_rw);
+ qtest_add_func("/riscv-dm/tcg/fpr-rw", test_tcg_fpr_rw);
+ qtest_add_func("/riscv-dm/rv32-gpr64-notsup", test_rv32_gpr64_notsup);
+ qtest_add_func("/riscv-dm/tcg/command-ignored-on-cmderr",
+ test_tcg_command_ignored_on_cmderr);
+
return g_test_run();
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
* [PATCH v1 28/28] tests/qtest: add DM TCG single-step and trigger tests
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
` (26 preceding siblings ...)
2026-03-08 7:17 ` [PATCH v1 27/28] tests/qtest: add DM TCG halt and register access tests Chao Liu
@ 2026-03-08 7:17 ` Chao Liu
27 siblings, 0 replies; 29+ messages in thread
From: Chao Liu @ 2026-03-08 7:17 UTC (permalink / raw)
To: Paolo Bonzini, Palmer Dabbelt, Alistair Francis, Weiwei Li,
Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, Fabiano Rosas,
Laurent Vivier
Cc: tangtao1634, qemu-devel, qemu-riscv
Add the remaining TCG-mode helpers (64-bit register access,
resume-and-run, is_rv64) and the second batch of TCG tests:
tcg/single-step – single-step via DCSR.step
tcg/postexec-impebreak – postexec with implicit ebreak
tcg/postexec-exception – abstract cmd exception path
tcg/trigger-debug-mode – mcontrol6 breakpoint fires
tcg/load-trigger-debug-mode – load watchpoint enters debug
tcg/store-trigger-debug-mode – store watchpoint enters debug
tcg/itrigger-debug-mode – instruction trigger enters debug
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
tests/qtest/riscv-dm-test.c | 547 ++++++++++++++++++++++++++++++++++++
1 file changed, 547 insertions(+)
diff --git a/tests/qtest/riscv-dm-test.c b/tests/qtest/riscv-dm-test.c
index 2e7c29ed7c..b2fb47e8f0 100644
--- a/tests/qtest/riscv-dm-test.c
+++ b/tests/qtest/riscv-dm-test.c
@@ -288,6 +288,15 @@ static bool tcg_resume_hart(QTestState *qts)
return false;
}
+static bool tcg_resume_hart_and_run(QTestState *qts)
+{
+ if (!tcg_resume_hart(qts)) {
+ return false;
+ }
+
+ g_usleep(TCG_BOOT_US);
+ return true;
+}
/*
* Read a register via Access Register abstract command.
@@ -346,6 +355,62 @@ static bool tcg_abstract_write_reg(QTestState *qts, uint32_t regno,
* Write a 64-bit register via Access Register abstract command (aarsize=3).
* DATA1 holds high 32 bits, DATA0 holds low 32 bits.
*/
+static bool tcg_abstract_write_reg64(QTestState *qts, uint32_t regno,
+ uint64_t val)
+{
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+ dm_write(qts, A_DATA0, (uint32_t)val);
+ dm_write(qts, A_DATA1, (uint32_t)(val >> 32));
+
+ uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */
+ (1u << 17) | /* transfer = 1 */
+ (1u << 16) | /* write = 1 */
+ (3u << 20) | /* aarsize = 64-bit */
+ (regno & 0xFFFF);
+ dm_write(qts, A_COMMAND, cmd);
+
+ if (!tcg_wait_for_cmd_done(qts)) {
+ return false;
+ }
+
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ return !(acs & ABSTRACTCS_CMDERR_MASK);
+}
+
+/*
+ * Read a 64-bit register via Access Register abstract command (aarsize=3).
+ * DATA0 holds low 32 bits, DATA1 holds high 32 bits.
+ */
+static bool tcg_abstract_read_reg64(QTestState *qts, uint32_t regno,
+ uint64_t *val)
+{
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ uint32_t cmd = (0u << 24) | /* cmdtype = Access Register */
+ (1u << 17) | /* transfer = 1 */
+ (3u << 20) | /* aarsize = 64-bit */
+ (regno & 0xFFFF);
+ dm_write(qts, A_COMMAND, cmd);
+
+ if (!tcg_wait_for_cmd_done(qts)) {
+ return false;
+ }
+
+ uint32_t acs = dm_read(qts, A_ABSTRACTCS);
+ if (acs & ABSTRACTCS_CMDERR_MASK) {
+ return false;
+ }
+
+ uint32_t lo = dm_read(qts, A_DATA0);
+ uint32_t hi = dm_read(qts, A_DATA1);
+ *val = ((uint64_t)hi << 32) | lo;
+ return true;
+}
+
+static bool is_rv64(void)
+{
+ return strstr(qtest_get_arch(), "64") != NULL;
+}
/*
* Test: dmactive gate.
@@ -1275,6 +1340,475 @@ static void test_tcg_command_ignored_on_cmderr(void)
qtest_quit(qts);
}
+/*
+ * Test: Single-step via DM.
+ *
+ * Verifies the Sdext single-step mechanism end-to-end:
+ * 1. Halt the hart via HALTREQ
+ * 2. Read DPC (save initial PC)
+ * 3. Set DCSR.step = 1 via abstract command
+ * 4. Resume the hart → CPU executes one instruction → re-enters debug mode
+ * 5. Verify DCSR.cause = STEP (4)
+ * 6. Verify DPC advanced from the initial value
+ * 7. Step again to confirm repeatability
+ * 8. Clear DCSR.step, resume, verify hart stays running
+ */
+static void test_tcg_single_step(void)
+{
+ QTestState *qts = tcg_dm_init();
+
+ /* Step 1: Halt the hart */
+ g_assert_true(tcg_halt_hart(qts));
+ uint32_t status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+
+ /* Step 2: Read initial DPC */
+ uint32_t dpc0;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &dpc0));
+ g_test_message("initial DPC = 0x%08x", dpc0);
+
+ /* Step 3: Set DCSR.step = 1 */
+ uint32_t dcsr;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ g_test_message("DCSR before step = 0x%08x", dcsr);
+ dcsr |= DCSR_STEP;
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_DCSR, dcsr));
+
+ /* Verify step bit is set */
+ uint32_t dcsr_check;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr_check));
+ g_assert_cmpuint(dcsr_check & DCSR_STEP, ==, DCSR_STEP);
+
+ /* Step 4: Resume → hart executes one instruction → re-halts */
+ g_assert_true(tcg_resume_hart_and_run(qts));
+ g_assert_true(tcg_wait_for_halt(qts));
+
+ /* Step 5: Verify DCSR.cause = STEP (4) */
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ uint32_t cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT;
+ g_test_message("DCSR after step = 0x%08x, cause = %u", dcsr, cause);
+ g_assert_cmpuint(cause, ==, DCSR_CAUSE_STEP);
+
+ /* Step 6: Verify DPC advanced */
+ uint32_t dpc1;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &dpc1));
+ g_test_message("DPC after step = 0x%08x", dpc1);
+ g_assert_cmpuint(dpc1, !=, dpc0);
+
+ /* Step 7: Step again to confirm repeatability */
+ uint32_t dpc_prev = dpc1;
+ g_assert_true(tcg_resume_hart_and_run(qts));
+ g_assert_true(tcg_wait_for_halt(qts));
+
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT;
+ g_assert_cmpuint(cause, ==, DCSR_CAUSE_STEP);
+
+ uint32_t dpc2;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &dpc2));
+ g_test_message("DPC after 2nd step = 0x%08x", dpc2);
+ g_assert_cmpuint(dpc2, !=, dpc_prev);
+
+ /* Step 8: Clear step bit, resume, verify hart stays running */
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ dcsr &= ~DCSR_STEP;
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_DCSR, dcsr));
+
+ g_assert_true(tcg_resume_hart(qts));
+
+ /* Give CPU time to run freely, then verify it's still running */
+ g_usleep(TCG_BOOT_US);
+ status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, DMSTATUS_ANYRUNNING);
+ g_assert_cmpuint(status & DMSTATUS_ANYHALTED, ==, 0);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: POSTEXEC completes via the implicit ebreak after Program Buffer.
+ */
+static void test_tcg_postexec_impebreak(void)
+{
+ QTestState *qts = tcg_dm_init();
+ uint32_t cmd, acs, status;
+
+ g_assert_true(tcg_halt_hart(qts));
+
+ for (int i = 0; i < 8; i++) {
+ dm_write(qts, A_PROGBUF0 + i * 4, 0x00000013);
+ }
+
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ cmd = (0u << 24) | (1u << 18);
+ dm_write(qts, A_COMMAND, cmd);
+
+ g_assert_true(tcg_wait_for_cmd_done(qts));
+
+ acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0);
+ g_assert_cmpuint(acs & ABSTRACTCS_CMDERR_MASK, ==, 0);
+
+ status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: POSTEXEC illegal instruction in Program Buffer.
+ *
+ * The hart is in Debug Mode and executes Program Buffer from DM ROM.
+ * If the first progbuf instruction is illegal, CPU should vector to the
+ * DM ROM exception entry and DM should latch CMDERR=EXCEPTION.
+ */
+static void test_tcg_postexec_exception(void)
+{
+ QTestState *qts = tcg_dm_init();
+ uint32_t cmd, acs, cmderr, status;
+
+ g_assert_true(tcg_halt_hart(qts));
+
+ /* 0x00000000 is an illegal instruction encoding. */
+ dm_write(qts, A_PROGBUF0, 0x00000000);
+ for (int i = 1; i < 8; i++) {
+ dm_write(qts, A_PROGBUF0 + i * 4, 0x00000013);
+ }
+
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ cmd = (0u << 24) | (1u << 18);
+ dm_write(qts, A_COMMAND, cmd);
+
+ g_assert_true(tcg_wait_for_cmd_done(qts));
+
+ acs = dm_read(qts, A_ABSTRACTCS);
+ g_assert_cmpuint(acs & ABSTRACTCS_BUSY, ==, 0);
+ cmderr = (acs & ABSTRACTCS_CMDERR_MASK) >> ABSTRACTCS_CMDERR_SHIFT;
+ g_assert_cmpuint(cmderr, ==, 3);
+
+ status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+
+ dm_write(qts, A_ABSTRACTCS, ABSTRACTCS_CMDERR_MASK);
+
+ qtest_quit(qts);
+}
+
+/*
+ * Test: Trigger with action=debug_mode halts the hart via DM.
+ *
+ * Configures an mcontrol (type 2) trigger on the current DPC address with
+ * action=1 (enter debug mode). After resume the CPU hits the trigger and
+ * re-enters debug mode. Verifies DCSR.cause = TRIGGER and DPC matches.
+ */
+static void test_tcg_trigger_debug_mode(void)
+{
+ QTestState *qts = tcg_dm_init();
+ bool rv64 = is_rv64();
+ const uint64_t code_addr = 0x80400000ULL;
+ const uint32_t nop_insn = 0x00000013;
+ const uint32_t loop_insn = 0x0000006f;
+
+ /* Halt the hart */
+ g_assert_true(tcg_halt_hart(qts));
+
+ /*
+ * Use a fixed code location so the test does not depend on where the CPU
+ * happened to be halted during boot.
+ */
+ qtest_writel(qts, code_addr, nop_insn);
+ qtest_writel(qts, code_addr + 4, loop_insn);
+
+ uint64_t dpc = code_addr;
+ if (rv64) {
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_DPC, dpc));
+ } else {
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_DPC, (uint32_t)dpc));
+ }
+ g_test_message("DPC before trigger = 0x%lx", (unsigned long)dpc);
+
+ /* Select trigger 1 to verify non-zero trigger index handling */
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TSELECT, 1));
+
+ /*
+ * Configure mcontrol type 2 trigger:
+ * - type=2, action=1 (DBG_ACTION_DBG_MODE)
+ * - match on EXEC in M/S/U modes
+ * - exact address match (match=0)
+ *
+ * On RV64 the type field is at bits[63:60] and tdata2 is 64-bit,
+ * so both require 64-bit abstract commands to avoid sign-extension.
+ */
+ uint64_t tdata1_lo = TDATA1_TYPE2_ACTION_DBG |
+ TDATA1_TYPE2_M | TDATA1_TYPE2_S | TDATA1_TYPE2_U |
+ TDATA1_TYPE2_EXEC;
+ if (rv64) {
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_TDATA2, dpc));
+ uint64_t tdata1_64 = (2ULL << 60) | tdata1_lo;
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_TDATA1, tdata1_64));
+ } else {
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TDATA2, (uint32_t)dpc));
+ uint32_t tdata1 = TDATA1_TYPE2_TYPE | (uint32_t)tdata1_lo;
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TDATA1, tdata1));
+ }
+
+ /* Verify trigger configuration was accepted (low 32 bits) */
+ uint32_t td1_rb;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_TDATA1, &td1_rb));
+ g_test_message("tdata1 readback low = 0x%08x", td1_rb);
+ g_assert_cmpuint(td1_rb & TDATA1_TYPE2_EXEC, ==, TDATA1_TYPE2_EXEC);
+
+ /* Resume -> CPU tries to execute at DPC -> trigger fires -> debug mode */
+ g_assert_true(tcg_resume_hart_and_run(qts));
+ g_assert_true(tcg_wait_for_halt(qts));
+
+ /* Verify DCSR.cause = TRIGGER (2) */
+ uint32_t dcsr;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ uint32_t cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT;
+ g_test_message("DCSR after trigger = 0x%08x, cause = %u", dcsr, cause);
+ g_assert_cmpuint(cause, ==, DCSR_CAUSE_TRIGGER);
+
+ /* Verify DPC is the trigger address */
+ uint64_t dpc_after;
+ if (rv64) {
+ g_assert_true(tcg_abstract_read_reg64(qts, REGNO_DPC, &dpc_after));
+ } else {
+ uint32_t tmp;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &tmp));
+ dpc_after = tmp;
+ }
+ g_test_message("DPC after trigger = 0x%lx", (unsigned long)dpc_after);
+ g_assert_cmpuint(dpc_after, ==, dpc);
+
+ /* Disable trigger: write tdata1 with type=2 but no EXEC/LOAD/STORE bits */
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TSELECT, 1));
+ if (rv64) {
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_TDATA1,
+ 2ULL << 60));
+ } else {
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TDATA1,
+ TDATA1_TYPE2_TYPE));
+ }
+
+ /*
+ * Advance DPC past the trigger address so resume doesn't re-trigger.
+ * The loop instruction keeps the hart running at a stable address.
+ */
+ if (rv64) {
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_DPC, dpc + 4));
+ } else {
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_DPC,
+ (uint32_t)(dpc + 4)));
+ }
+
+ /* Resume and verify hart stays running */
+ g_assert_true(tcg_resume_hart(qts));
+ g_usleep(TCG_BOOT_US);
+ uint32_t status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, DMSTATUS_ANYRUNNING);
+ g_assert_cmpuint(status & DMSTATUS_ANYHALTED, ==, 0);
+
+ qtest_quit(qts);
+}
+
+
+static void test_tcg_watchpoint_trigger_debug_mode(bool store)
+{
+ QTestState *qts = tcg_dm_init();
+ bool rv64 = is_rv64();
+ const uint64_t code_addr = 0x80400000ULL;
+ const uint64_t data_addr = 0x80401000ULL;
+ const uint32_t load_insn = 0x0002a383; /* lw x7, 0(x5) */
+ const uint32_t store_insn = 0x0062a023; /* sw x6, 0(x5) */
+ const uint32_t loop_insn = 0x0000006f; /* j . */
+ const uint32_t data_init = 0x11223344;
+ const uint32_t store_val = 0x55667788;
+ const uint32_t trigger_idx = store ? 3 : 2;
+ uint32_t dcsr, cause, status;
+ uint64_t tdata1_lo = TDATA1_TYPE2_ACTION_DBG |
+ TDATA1_TYPE2_M | TDATA1_TYPE2_S | TDATA1_TYPE2_U |
+ (store ? TDATA1_TYPE2_STORE : TDATA1_TYPE2_LOAD);
+
+ g_assert_true(tcg_halt_hart(qts));
+
+ qtest_writel(qts, code_addr, store ? store_insn : load_insn);
+ qtest_writel(qts, code_addr + 4, loop_insn);
+ qtest_writel(qts, data_addr, data_init);
+
+ if (rv64) {
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_GPR(5), data_addr));
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_GPR(6), store_val));
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_GPR(7), 0));
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_DPC, code_addr));
+ } else {
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_GPR(5),
+ (uint32_t)data_addr));
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_GPR(6), store_val));
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_GPR(7), 0));
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_DPC,
+ (uint32_t)code_addr));
+ }
+
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TSELECT, trigger_idx));
+ if (rv64) {
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_TDATA2, data_addr));
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_TDATA1,
+ (2ULL << 60) | tdata1_lo));
+ } else {
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TDATA2,
+ (uint32_t)data_addr));
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TDATA1,
+ TDATA1_TYPE2_TYPE |
+ (uint32_t)tdata1_lo));
+ }
+
+ g_assert_true(tcg_resume_hart_and_run(qts));
+ g_assert_true(tcg_wait_for_halt(qts));
+
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT;
+ g_assert_cmpuint(cause, ==, DCSR_CAUSE_TRIGGER);
+
+ if (rv64) {
+ uint64_t dpc;
+ g_assert_true(tcg_abstract_read_reg64(qts, REGNO_DPC, &dpc));
+ g_assert_cmpuint(dpc, ==, code_addr);
+ } else {
+ uint32_t dpc;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &dpc));
+ g_assert_cmpuint(dpc, ==, (uint32_t)code_addr);
+ }
+
+ if (store) {
+ g_assert_cmpuint(qtest_readl(qts, data_addr), ==, data_init);
+ }
+
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TSELECT, trigger_idx));
+ if (rv64) {
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_TDATA1, 2ULL << 60));
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_DPC, code_addr));
+ } else {
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TDATA1,
+ TDATA1_TYPE2_TYPE));
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_DPC,
+ (uint32_t)code_addr));
+ }
+
+ g_assert_true(tcg_resume_hart(qts));
+ g_usleep(TCG_BOOT_US);
+
+ if (store) {
+ g_assert_cmpuint(qtest_readl(qts, data_addr), ==, store_val);
+ status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, DMSTATUS_ANYRUNNING);
+ } else {
+ uint64_t load_val;
+
+ g_assert_true(tcg_halt_hart(qts));
+ if (rv64) {
+ g_assert_true(tcg_abstract_read_reg64(qts, REGNO_GPR(7),
+ &load_val));
+ } else {
+ uint32_t tmp;
+
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_GPR(7), &tmp));
+ load_val = tmp;
+ }
+ g_assert_cmpuint(load_val, ==, data_init);
+ status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ALLHALTED, ==, DMSTATUS_ALLHALTED);
+ }
+
+ qtest_quit(qts);
+}
+
+static void test_tcg_load_trigger_debug_mode(void)
+{
+ test_tcg_watchpoint_trigger_debug_mode(false);
+}
+
+static void test_tcg_store_trigger_debug_mode(void)
+{
+ test_tcg_watchpoint_trigger_debug_mode(true);
+}
+
+/*
+ * Test: itrigger with action=debug_mode enters Debug Mode.
+ *
+ * Configures an itrigger with count=1 so the hart executes one instruction,
+ * re-enters debug mode with cause=TRIGGER, and clears the count.
+ */
+static void test_tcg_itrigger_debug_mode(void)
+{
+ QTestState *qts = tcg_dm_init();
+ bool rv64 = is_rv64();
+ uint64_t dpc;
+ uint64_t dpc_after;
+ uint64_t tdata1;
+ uint32_t dcsr;
+ uint32_t status;
+ uint32_t cause;
+
+ g_assert_true(tcg_halt_hart(qts));
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TSELECT, 1));
+
+ if (rv64) {
+ g_assert_true(tcg_abstract_read_reg64(qts, REGNO_DPC, &dpc));
+ tdata1 = (3ULL << 60) |
+ TDATA1_ITRIGGER_ACTION_DBG |
+ TDATA1_ITRIGGER_U |
+ TDATA1_ITRIGGER_S |
+ TDATA1_ITRIGGER_M |
+ TDATA1_ITRIGGER_COUNT(1);
+ g_assert_true(tcg_abstract_write_reg64(qts, REGNO_TDATA1, tdata1));
+ } else {
+ uint32_t tmp;
+
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &tmp));
+ dpc = tmp;
+ tdata1 = TDATA1_ITRIGGER_TYPE |
+ TDATA1_ITRIGGER_ACTION_DBG |
+ TDATA1_ITRIGGER_U |
+ TDATA1_ITRIGGER_S |
+ TDATA1_ITRIGGER_M |
+ TDATA1_ITRIGGER_COUNT(1);
+ g_assert_true(tcg_abstract_write_reg(qts, REGNO_TDATA1,
+ (uint32_t)tdata1));
+ }
+
+ g_assert_true(tcg_resume_hart(qts));
+ g_assert_true(tcg_wait_for_halt(qts));
+
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DCSR, &dcsr));
+ cause = (dcsr & DCSR_CAUSE_MASK) >> DCSR_CAUSE_SHIFT;
+ g_assert_cmpuint(cause, ==, DCSR_CAUSE_TRIGGER);
+
+ if (rv64) {
+ g_assert_true(tcg_abstract_read_reg64(qts, REGNO_DPC, &dpc_after));
+ g_assert_true(tcg_abstract_read_reg64(qts, REGNO_TDATA1, &tdata1));
+ } else {
+ uint32_t tmp;
+
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_DPC, &tmp));
+ dpc_after = tmp;
+ g_assert_true(tcg_abstract_read_reg(qts, REGNO_TDATA1, &tmp));
+ tdata1 = tmp;
+ }
+
+ g_assert_cmpuint(dpc_after, !=, dpc);
+ g_assert_cmpuint(tdata1 & TDATA1_ITRIGGER_COUNT_MASK, ==, 0);
+
+ g_assert_true(tcg_resume_hart(qts));
+ g_usleep(TCG_BOOT_US);
+ status = dm_read(qts, A_DMSTATUS);
+ g_assert_cmpuint(status & DMSTATUS_ANYRUNNING, ==, DMSTATUS_ANYRUNNING);
+ g_assert_cmpuint(status & DMSTATUS_ANYHALTED, ==, 0);
+
+ qtest_quit(qts);
+}
/*
* Test: Halt and resume cycle via ROM simulation.
@@ -1362,6 +1896,19 @@ int main(int argc, char *argv[])
qtest_add_func("/riscv-dm/rv32-gpr64-notsup", test_rv32_gpr64_notsup);
qtest_add_func("/riscv-dm/tcg/command-ignored-on-cmderr",
test_tcg_command_ignored_on_cmderr);
+ qtest_add_func("/riscv-dm/tcg/single-step", test_tcg_single_step);
+ qtest_add_func("/riscv-dm/tcg/postexec-impebreak",
+ test_tcg_postexec_impebreak);
+ qtest_add_func("/riscv-dm/tcg/postexec-exception",
+ test_tcg_postexec_exception);
+ qtest_add_func("/riscv-dm/tcg/trigger-debug-mode",
+ test_tcg_trigger_debug_mode);
+ qtest_add_func("/riscv-dm/tcg/load-trigger-debug-mode",
+ test_tcg_load_trigger_debug_mode);
+ qtest_add_func("/riscv-dm/tcg/store-trigger-debug-mode",
+ test_tcg_store_trigger_debug_mode);
+ qtest_add_func("/riscv-dm/tcg/itrigger-debug-mode",
+ test_tcg_itrigger_debug_mode);
return g_test_run();
}
--
2.53.0
^ permalink raw reply related [flat|nested] 29+ messages in thread
end of thread, other threads:[~2026-03-08 7:23 UTC | newest]
Thread overview: 29+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-08 7:17 [PATCH v1 00/28] RISC-V Debug Module support Chao Liu
2026-03-08 7:17 ` [PATCH v1 01/28] target/riscv: track pending Debug Module halt requests Chao Liu
2026-03-08 7:17 ` [PATCH v1 02/28] target/riscv: keep Debug Mode active when a DM ROM is present Chao Liu
2026-03-08 7:17 ` [PATCH v1 03/28] target/riscv: enter Debug Mode from pending DM halt requests Chao Liu
2026-03-08 7:17 ` [PATCH v1 04/28] target/riscv: route in-debug exceptions back to the DM ROM Chao Liu
2026-03-08 7:17 ` [PATCH v1 05/28] target/riscv: re-enter the DM ROM after single-step completion Chao Liu
2026-03-08 7:17 ` [PATCH v1 06/28] target/riscv: let ebreak enter the DM ROM Chao Liu
2026-03-08 7:17 ` [PATCH v1 07/28] target/riscv: restart the DM ROM on debug-mode ebreak Chao Liu
2026-03-08 7:17 ` [PATCH v1 08/28] target/riscv: track the exact breakpoint trigger hit Chao Liu
2026-03-08 7:17 ` [PATCH v1 09/28] target/riscv: track the exact watchpoint " Chao Liu
2026-03-08 7:17 ` [PATCH v1 10/28] target/riscv: send trigger debug-mode actions to the DM ROM Chao Liu
2026-03-08 7:17 ` [PATCH v1 11/28] target/riscv: suppress itrigger TB state in Debug Mode Chao Liu
2026-03-08 7:17 ` [PATCH v1 12/28] target/riscv: tighten itrigger privilege and count checks Chao Liu
2026-03-08 7:17 ` [PATCH v1 13/28] target/riscv: allow debug-mode actions for itrigger Chao Liu
2026-03-08 7:17 ` [PATCH v1 14/28] target/riscv: stop consuming itrigger counts in Debug Mode Chao Liu
2026-03-08 7:17 ` [PATCH v1 15/28] hw/riscv: add an initial Debug Module device model Chao Liu
2026-03-08 7:17 ` [PATCH v1 16/28] hw/riscv/dm: track per-hart debug state Chao Liu
2026-03-08 7:17 ` [PATCH v1 17/28] hw/riscv/dm: add hart run-control operations Chao Liu
2026-03-08 7:17 ` [PATCH v1 18/28] hw/riscv/dm: queue reset halts from the reset work item Chao Liu
2026-03-08 7:17 ` [PATCH v1 19/28] hw/riscv/dm: add DMI register declarations and ROM entry program Chao Liu
2026-03-08 7:17 ` [PATCH v1 20/28] hw/riscv/dm: add register handlers and update state management Chao Liu
2026-03-08 7:17 ` [PATCH v1 21/28] hw/riscv/dm: add abstract command execution Chao Liu
2026-03-08 7:17 ` [PATCH v1 22/28] hw/riscv/dm: add system bus access Chao Liu
2026-03-08 7:17 ` [PATCH v1 23/28] hw/riscv/virt: integrate the Debug Module Chao Liu
2026-03-08 7:17 ` [PATCH v1 24/28] tests/qtest: add initial RISC-V Debug Module tests Chao Liu
2026-03-08 7:17 ` [PATCH v1 25/28] tests/qtest: extend DM register semantics coverage Chao Liu
2026-03-08 7:17 ` [PATCH v1 26/28] tests/qtest: add DM abstract command and halt/resume tests Chao Liu
2026-03-08 7:17 ` [PATCH v1 27/28] tests/qtest: add DM TCG halt and register access tests Chao Liu
2026-03-08 7:17 ` [PATCH v1 28/28] tests/qtest: add DM TCG single-step and trigger tests Chao Liu
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox