[RFC PATCH v3 2/7] target/riscv: add sdext debug CSRs state

Chao Liu posted 7 patches 1 week, 3 days ago
There is a newer version of this series
[RFC PATCH v3 2/7] target/riscv: add sdext debug CSRs state
Posted by Chao Liu 1 week, 3 days ago
RISC-V Debug Specification:
https://github.com/riscv/riscv-debug-spec/releases/tag/1.0

Add architectural state for Sdext Debug Mode: debug_mode, dcsr, dpc
and dscratch0/1. Wire up CSR access for dcsr/dpc/dscratch and gate
them to Debug Mode (or host debugger access).

The Sdext is not fully implemented, so it is disabled by default.

Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
---
 target/riscv/cpu.c                |  10 +++
 target/riscv/cpu.h                |   4 +
 target/riscv/cpu_bits.h           |  33 ++++++++
 target/riscv/cpu_cfg_fields.h.inc |   1 +
 target/riscv/csr.c                | 126 ++++++++++++++++++++++++++++++
 target/riscv/machine.c            |  20 +++++
 6 files changed, 194 insertions(+)

diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 0ba98a62e4..ba8fd1557a 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -209,6 +209,7 @@ const RISCVIsaExtData isa_edata_arr[] = {
     ISA_EXT_DATA_ENTRY(zvkt, PRIV_VERSION_1_12_0, ext_zvkt),
     ISA_EXT_DATA_ENTRY(zhinx, PRIV_VERSION_1_12_0, ext_zhinx),
     ISA_EXT_DATA_ENTRY(zhinxmin, PRIV_VERSION_1_12_0, ext_zhinxmin),
+    ISA_EXT_DATA_ENTRY(sdext, PRIV_VERSION_1_12_0, ext_sdext),
     ISA_EXT_DATA_ENTRY(sdtrig, PRIV_VERSION_1_12_0, ext_sdtrig),
     ISA_EXT_DATA_ENTRY(shcounterenw, PRIV_VERSION_1_12_0, has_priv_1_12),
     ISA_EXT_DATA_ENTRY(sha, PRIV_VERSION_1_12_0, ext_sha),
@@ -779,6 +780,11 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
     /* Default NaN value: sign bit clear, frac msb set */
     set_float_default_nan_pattern(0b01000000, &env->fp_status);
     env->vill = true;
+    env->debug_mode = false;
+    env->dcsr = DCSR_DEBUGVER(4);
+    env->dpc = 0;
+    env->dscratch[0] = 0;
+    env->dscratch[1] = 0;
 
 #ifndef CONFIG_USER_ONLY
     if (cpu->cfg.ext_sdtrig) {
@@ -1131,6 +1137,9 @@ static void riscv_cpu_init(Object *obj)
      */
     cpu->cfg.ext_sdtrig = true;
 
+    /* sdext is not fully implemented, so it is disabled by default. */
+    cpu->cfg.ext_sdext = false;
+
     if (mcc->def->profile) {
         mcc->def->profile->enabled = true;
     }
@@ -1245,6 +1254,7 @@ const RISCVCPUMultiExtConfig riscv_cpu_extensions[] = {
     MULTI_EXT_CFG_BOOL("smcdeleg", ext_smcdeleg, false),
     MULTI_EXT_CFG_BOOL("sscsrind", ext_sscsrind, false),
     MULTI_EXT_CFG_BOOL("ssccfg", ext_ssccfg, false),
+    MULTI_EXT_CFG_BOOL("sdext", ext_sdext, false),
     MULTI_EXT_CFG_BOOL("sdtrig", ext_sdtrig, true),
     MULTI_EXT_CFG_BOOL("smctr", ext_smctr, false),
     MULTI_EXT_CFG_BOOL("ssctr", ext_ssctr, false),
diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 5c6824f2d3..a474494dff 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -476,6 +476,10 @@ struct CPUArchState {
 
     /* True if in debugger mode.  */
     bool debugger;
+    bool debug_mode;
+    target_ulong dcsr;
+    target_ulong dpc;
+    target_ulong dscratch[2];
 
     uint64_t mstateen[SMSTATEEN_MAX_COUNT];
     uint64_t hstateen[SMSTATEEN_MAX_COUNT];
diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h
index b62dd82fe7..bb59f7ff56 100644
--- a/target/riscv/cpu_bits.h
+++ b/target/riscv/cpu_bits.h
@@ -467,6 +467,39 @@
 #define CSR_DCSR            0x7b0
 #define CSR_DPC             0x7b1
 #define CSR_DSCRATCH        0x7b2
+#define CSR_DSCRATCH1       0x7b3
+
+/* DCSR fields */
+#define DCSR_XDEBUGVER_SHIFT    28
+#define DCSR_XDEBUGVER_MASK     (0xfu << DCSR_XDEBUGVER_SHIFT)
+#define DCSR_DEBUGVER(val)      ((target_ulong)(val) << DCSR_XDEBUGVER_SHIFT)
+#define DCSR_EXTCAUSE_SHIFT     24
+#define DCSR_EXTCAUSE_MASK      (0x7u << DCSR_EXTCAUSE_SHIFT)
+#define DCSR_CETRIG             BIT(19)
+#define DCSR_PELP               BIT(18)
+#define DCSR_EBREAKVS           BIT(17)
+#define DCSR_EBREAKVU           BIT(16)
+#define DCSR_EBREAKM            BIT(15)
+#define DCSR_EBREAKS            BIT(13)
+#define DCSR_EBREAKU            BIT(12)
+#define DCSR_STEPIE             BIT(11)
+#define DCSR_STOPCOUNT          BIT(10)
+#define DCSR_STOPTIME           BIT(9)
+#define DCSR_CAUSE_SHIFT        6
+#define DCSR_CAUSE_MASK         (0x7u << DCSR_CAUSE_SHIFT)
+#define DCSR_V                  BIT(5)
+#define DCSR_MPRVEN             BIT(4)
+#define DCSR_NMIP               BIT(3)
+#define DCSR_STEP               BIT(2)
+#define DCSR_PRV_MASK           0x3u
+
+#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
+#define DCSR_CAUSE_GROUP        6
+#define DCSR_CAUSE_OTHER        7
 
 /* Performance Counters */
 #define CSR_MHPMCOUNTER3    0xb03
diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc
index 492fdd1553..4b157ac920 100644
--- a/target/riscv/cpu_cfg_fields.h.inc
+++ b/target/riscv/cpu_cfg_fields.h.inc
@@ -46,6 +46,7 @@ BOOL_FIELD(ext_zilsd)
 BOOL_FIELD(ext_zimop)
 BOOL_FIELD(ext_zcmop)
 BOOL_FIELD(ext_ztso)
+BOOL_FIELD(ext_sdext)
 BOOL_FIELD(ext_sdtrig)
 BOOL_FIELD(ext_smstateen)
 BOOL_FIELD(ext_sstc)
diff --git a/target/riscv/csr.c b/target/riscv/csr.c
index 870fad87ac..3e38c943e0 100644
--- a/target/riscv/csr.c
+++ b/target/riscv/csr.c
@@ -3136,6 +3136,126 @@ static RISCVException write_mtval(CPURISCVState *env, int csrno,
     return RISCV_EXCP_NONE;
 }
 
+#if !defined(CONFIG_USER_ONLY)
+static RISCVException sdext(CPURISCVState *env, int csrno)
+{
+    if (!riscv_cpu_cfg(env)->ext_sdext) {
+        return RISCV_EXCP_ILLEGAL_INST;
+    }
+
+    if (!env->debug_mode && !env->debugger) {
+        return RISCV_EXCP_ILLEGAL_INST;
+    }
+
+    return RISCV_EXCP_NONE;
+}
+
+static target_ulong dcsr_visible_mask(CPURISCVState *env)
+{
+    target_ulong mask = (target_ulong)-1;
+    RISCVCPU *cpu = env_archcpu(env);
+
+    if (!riscv_has_ext(env, RVH)) {
+        mask &= ~(DCSR_EBREAKVS | DCSR_EBREAKVU | DCSR_V);
+    }
+    if (!riscv_has_ext(env, RVS)) {
+        mask &= ~DCSR_EBREAKS;
+    }
+    if (!riscv_has_ext(env, RVU)) {
+        mask &= ~DCSR_EBREAKU;
+    }
+    if (!cpu->cfg.ext_zicfilp) {
+        mask &= ~DCSR_PELP;
+    }
+    if (!cpu->cfg.ext_smdbltrp) {
+        mask &= ~DCSR_CETRIG;
+    }
+
+    return mask;
+}
+
+static RISCVException read_dcsr(CPURISCVState *env, int csrno,
+                                target_ulong *val)
+{
+    *val = env->dcsr & dcsr_visible_mask(env);
+    return RISCV_EXCP_NONE;
+}
+
+static target_ulong dcsr_writable_mask(CPURISCVState *env)
+{
+    target_ulong mask = DCSR_EBREAKM | DCSR_EBREAKS | DCSR_EBREAKU |
+                        DCSR_STEPIE | DCSR_STOPCOUNT | DCSR_STOPTIME |
+                        DCSR_STEP | DCSR_PRV_MASK;
+    RISCVCPU *cpu = env_archcpu(env);
+
+    mask |= DCSR_MPRVEN;
+
+    if (riscv_has_ext(env, RVH)) {
+        mask |= DCSR_EBREAKVS | DCSR_EBREAKVU | DCSR_V;
+    }
+    if (riscv_has_ext(env, RVS)) {
+        mask |= DCSR_EBREAKS;
+    }
+    if (riscv_has_ext(env, RVU)) {
+        mask |= DCSR_EBREAKU;
+    }
+    if (cpu->cfg.ext_zicfilp) {
+        mask |= DCSR_PELP;
+    }
+    if (cpu->cfg.ext_smdbltrp) {
+        mask |= DCSR_CETRIG;
+    }
+
+    return mask;
+}
+
+static RISCVException write_dcsr(CPURISCVState *env, int csrno,
+                                 target_ulong val, uintptr_t ra)
+{
+    target_ulong mask = dcsr_writable_mask(env);
+    target_ulong new_val = env->dcsr;
+
+    new_val &= ~mask;
+    new_val |= val & mask;
+    new_val &= ~DCSR_XDEBUGVER_MASK;
+    new_val |= DCSR_DEBUGVER(4);
+    env->dcsr = new_val;
+    return RISCV_EXCP_NONE;
+}
+
+static RISCVException read_dpc(CPURISCVState *env, int csrno,
+                               target_ulong *val)
+{
+    *val = env->dpc & get_xepc_mask(env);
+    return RISCV_EXCP_NONE;
+}
+
+static RISCVException write_dpc(CPURISCVState *env, int csrno,
+                                target_ulong val, uintptr_t ra)
+{
+    env->dpc = val & get_xepc_mask(env);
+    return RISCV_EXCP_NONE;
+}
+
+static RISCVException read_dscratch(CPURISCVState *env, int csrno,
+                                    target_ulong *val)
+{
+    int index = (csrno == CSR_DSCRATCH1) ? 1 : 0;
+
+    *val = env->dscratch[index];
+    return RISCV_EXCP_NONE;
+}
+
+static RISCVException write_dscratch(CPURISCVState *env, int csrno,
+                                     target_ulong val, uintptr_t ra)
+{
+    int index = (csrno == CSR_DSCRATCH1) ? 1 : 0;
+
+    env->dscratch[index] = val;
+    return RISCV_EXCP_NONE;
+}
+#endif /* !CONFIG_USER_ONLY */
+
 /* Execution environment configuration setup */
 static RISCVException read_menvcfg(CPURISCVState *env, int csrno,
                                    target_ulong *val)
@@ -6297,6 +6417,12 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = {
     [CSR_TDATA3]    =  { "tdata3",   debug, read_tdata,    write_tdata    },
     [CSR_TINFO]     =  { "tinfo",    debug, read_tinfo,    write_ignore   },
     [CSR_MCONTEXT]  =  { "mcontext", debug, read_mcontext, write_mcontext },
+#if !defined(CONFIG_USER_ONLY)
+    [CSR_DCSR]      =  { "dcsr",      sdext, read_dcsr,     write_dcsr },
+    [CSR_DPC]       =  { "dpc",       sdext, read_dpc,      write_dpc },
+    [CSR_DSCRATCH]  =  { "dscratch0", sdext, read_dscratch, write_dscratch },
+    [CSR_DSCRATCH1] =  { "dscratch1", sdext, read_dscratch, write_dscratch },
+#endif
 
     [CSR_MCTRCTL]    = { "mctrctl",    ctr_mmode,  NULL, NULL, rmw_xctrctl    },
     [CSR_SCTRCTL]    = { "sctrctl",    ctr_smode,  NULL, NULL, rmw_xctrctl    },
diff --git a/target/riscv/machine.c b/target/riscv/machine.c
index 62c51c8033..52264cf047 100644
--- a/target/riscv/machine.c
+++ b/target/riscv/machine.c
@@ -248,6 +248,25 @@ static const VMStateDescription vmstate_sdtrig = {
         VMSTATE_UINTTL_ARRAY(env.tdata1, RISCVCPU, RV_MAX_TRIGGERS),
         VMSTATE_UINTTL_ARRAY(env.tdata2, RISCVCPU, RV_MAX_TRIGGERS),
         VMSTATE_UINTTL_ARRAY(env.tdata3, RISCVCPU, RV_MAX_TRIGGERS),
+        VMSTATE_BOOL_V(env.debug_mode, RISCVCPU, 3),
+        VMSTATE_UINTTL_V(env.dcsr, RISCVCPU, 3),
+        VMSTATE_UINTTL_V(env.dpc, RISCVCPU, 3),
+        VMSTATE_UINTTL_ARRAY_V(env.dscratch, RISCVCPU, 2, 3),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_sdext = {
+    .name = "cpu/sdext",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = sdtrig_needed,
+    .post_load = sdtrig_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_BOOL_V(env.debug_mode, RISCVCPU, 3),
+        VMSTATE_UINTTL_V(env.dcsr, RISCVCPU, 3),
+        VMSTATE_UINTTL_V(env.dpc, RISCVCPU, 3),
+        VMSTATE_UINTTL_ARRAY_V(env.dscratch, RISCVCPU, 2, 3),
         VMSTATE_END_OF_LIST()
     }
 };
@@ -499,6 +518,7 @@ const VMStateDescription vmstate_riscv_cpu = {
         &vmstate_ctr,
         &vmstate_sstc,
         &vmstate_sdtrig,
+        &vmstate_sdext,
         NULL
     }
 };
-- 
2.52.0
Re: [RFC PATCH v3 2/7] target/riscv: add sdext debug CSRs state
Posted by Daniel Henrique Barboza 1 week, 2 days ago

On 1/27/2026 10:15 AM, Chao Liu wrote:
> RISC-V Debug Specification:
> https://github.com/riscv/riscv-debug-spec/releases/tag/1.0
> 
> Add architectural state for Sdext Debug Mode: debug_mode, dcsr, dpc
> and dscratch0/1. Wire up CSR access for dcsr/dpc/dscratch and gate
> them to Debug Mode (or host debugger access).
> 
> The Sdext is not fully implemented, so it is disabled by default.
> 
> Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
> ---
>   target/riscv/cpu.c                |  10 +++
>   target/riscv/cpu.h                |   4 +
>   target/riscv/cpu_bits.h           |  33 ++++++++
>   target/riscv/cpu_cfg_fields.h.inc |   1 +
>   target/riscv/csr.c                | 126 ++++++++++++++++++++++++++++++
>   target/riscv/machine.c            |  20 +++++
>   6 files changed, 194 insertions(+)
> 
> diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
> index 0ba98a62e4..ba8fd1557a 100644
> --- a/target/riscv/cpu.c
> +++ b/target/riscv/cpu.c
> @@ -209,6 +209,7 @@ const RISCVIsaExtData isa_edata_arr[] = {
>       ISA_EXT_DATA_ENTRY(zvkt, PRIV_VERSION_1_12_0, ext_zvkt),
>       ISA_EXT_DATA_ENTRY(zhinx, PRIV_VERSION_1_12_0, ext_zhinx),
>       ISA_EXT_DATA_ENTRY(zhinxmin, PRIV_VERSION_1_12_0, ext_zhinxmin),
> +    ISA_EXT_DATA_ENTRY(sdext, PRIV_VERSION_1_12_0, ext_sdext),
>       ISA_EXT_DATA_ENTRY(sdtrig, PRIV_VERSION_1_12_0, ext_sdtrig),
>       ISA_EXT_DATA_ENTRY(shcounterenw, PRIV_VERSION_1_12_0, has_priv_1_12),
>       ISA_EXT_DATA_ENTRY(sha, PRIV_VERSION_1_12_0, ext_sha),
> @@ -779,6 +780,11 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
>       /* Default NaN value: sign bit clear, frac msb set */
>       set_float_default_nan_pattern(0b01000000, &env->fp_status);
>       env->vill = true;
> +    env->debug_mode = false;
> +    env->dcsr = DCSR_DEBUGVER(4);
> +    env->dpc = 0;
> +    env->dscratch[0] = 0;
> +    env->dscratch[1] = 0;
>   
>   #ifndef CONFIG_USER_ONLY
>       if (cpu->cfg.ext_sdtrig) {
> @@ -1131,6 +1137,9 @@ static void riscv_cpu_init(Object *obj)
>        */
>       cpu->cfg.ext_sdtrig = true;
>   
> +    /* sdext is not fully implemented, so it is disabled by default. */
> +    cpu->cfg.ext_sdext = false;
> +
>       if (mcc->def->profile) {
>           mcc->def->profile->enabled = true;
>       }
> @@ -1245,6 +1254,7 @@ const RISCVCPUMultiExtConfig riscv_cpu_extensions[] = {
>       MULTI_EXT_CFG_BOOL("smcdeleg", ext_smcdeleg, false),
>       MULTI_EXT_CFG_BOOL("sscsrind", ext_sscsrind, false),
>       MULTI_EXT_CFG_BOOL("ssccfg", ext_ssccfg, false),
> +    MULTI_EXT_CFG_BOOL("sdext", ext_sdext, false),
>       MULTI_EXT_CFG_BOOL("sdtrig", ext_sdtrig, true),
>       MULTI_EXT_CFG_BOOL("smctr", ext_smctr, false),
>       MULTI_EXT_CFG_BOOL("ssctr", ext_ssctr, false),
> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
> index 5c6824f2d3..a474494dff 100644
> --- a/target/riscv/cpu.h
> +++ b/target/riscv/cpu.h
> @@ -476,6 +476,10 @@ struct CPUArchState {
>   
>       /* True if in debugger mode.  */
>       bool debugger;
> +    bool debug_mode;
> +    target_ulong dcsr;
> +    target_ulong dpc;
> +    target_ulong dscratch[2];

This patch (and I believe almost every single other patch that follows) 
won't build in the linux-user target. It will throw errors like this:

../target/riscv/cpu_helper.c: In function ‘riscv_cpu_enter_debug_mode’: 
  
../target/riscv/cpu_helper.c:154:8: error: ‘CPURISCVState’ {aka ‘struct 
CPUArchState’} has no member named ‘debug_mode’       154 | 
env->debug_mode = true; 
                                              |        ^~ 
  
                    ../target/riscv/cpu_helper.c:155:10: error: 
‘CPURISCVState’ {aka ‘struct CPUArchState’} has no member named ‘dpc’; 
did you mean ‘pc’? 
                                                               155 | 
env->dpc = pc & get_xepc_mask(env); 
                                              |          ^~~


The reason is that the flags you're declaring up above exist only in the 
system emulation mode. If you double check target/riscv/cpu.h you'll 
notice that the flags you're adding are inside a big ifdef around line 270:

#ifndef CONFIG_USER_ONLY
     /* This contains QEMU specific information about the virt state. */


So debug_mode, dscr, dpc and dscratch aren't available for user mode. 
Note that this is correct - user mode can't deal with sdext or any other 
debug trigger extension. But you'll have to gate all logic that uses 
those flags in #ifndef CONFIG_USER_ONLY blocks.


For example, in patch 3:


+void riscv_cpu_enter_debug_mode(CPURISCVState *env, target_ulong pc,
+                                uint32_t cause)
+{
+#ifndef CONFIG_USER_ONLY
+    if (!riscv_sdext_enabled(env)) {
+        return;
+    }
+#endif
+    env->debug_mode = true;
+    env->dpc = pc & get_xepc_mask(env);
+    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;
+    if (env->virt_enabled && riscv_has_ext(env, RVH)) {
+        env->dcsr |= DCSR_V;
+    }
+#ifndef CONFIG_USER_ONLY
+    if (env_archcpu(env)->cfg.ext_zicfilp) {
+        if (env->elp) {
+            env->dcsr |= DCSR_PELP;
+        } else {
+            env->dcsr &= ~DCSR_PELP;
+        }
+        env->elp = false;
+    }
+#endif


We want the whole function body inside an "#ifndef CONFIG_USER_ONLY" 
because everything being done is unavailable in that mode.

You can use the build target 'riscv64-linux-user' to build qemu-riscv64 
(i.e. user mode) to check if the patches will break it by accident. I 
usually build all riscv targets to catch this type of build errors:


./configure 
--target-list=riscv64-softmmu,riscv64-linux-user,riscv32-softmmu,riscv32-linux-user 
  (...)



Thanks,
Daniel




>   
>       uint64_t mstateen[SMSTATEEN_MAX_COUNT];
>       uint64_t hstateen[SMSTATEEN_MAX_COUNT];
> diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h
> index b62dd82fe7..bb59f7ff56 100644
> --- a/target/riscv/cpu_bits.h
> +++ b/target/riscv/cpu_bits.h
> @@ -467,6 +467,39 @@
>   #define CSR_DCSR            0x7b0
>   #define CSR_DPC             0x7b1
>   #define CSR_DSCRATCH        0x7b2
> +#define CSR_DSCRATCH1       0x7b3
> +
> +/* DCSR fields */
> +#define DCSR_XDEBUGVER_SHIFT    28
> +#define DCSR_XDEBUGVER_MASK     (0xfu << DCSR_XDEBUGVER_SHIFT)
> +#define DCSR_DEBUGVER(val)      ((target_ulong)(val) << DCSR_XDEBUGVER_SHIFT)
> +#define DCSR_EXTCAUSE_SHIFT     24
> +#define DCSR_EXTCAUSE_MASK      (0x7u << DCSR_EXTCAUSE_SHIFT)
> +#define DCSR_CETRIG             BIT(19)
> +#define DCSR_PELP               BIT(18)
> +#define DCSR_EBREAKVS           BIT(17)
> +#define DCSR_EBREAKVU           BIT(16)
> +#define DCSR_EBREAKM            BIT(15)
> +#define DCSR_EBREAKS            BIT(13)
> +#define DCSR_EBREAKU            BIT(12)
> +#define DCSR_STEPIE             BIT(11)
> +#define DCSR_STOPCOUNT          BIT(10)
> +#define DCSR_STOPTIME           BIT(9)
> +#define DCSR_CAUSE_SHIFT        6
> +#define DCSR_CAUSE_MASK         (0x7u << DCSR_CAUSE_SHIFT)
> +#define DCSR_V                  BIT(5)
> +#define DCSR_MPRVEN             BIT(4)
> +#define DCSR_NMIP               BIT(3)
> +#define DCSR_STEP               BIT(2)
> +#define DCSR_PRV_MASK           0x3u
> +
> +#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
> +#define DCSR_CAUSE_GROUP        6
> +#define DCSR_CAUSE_OTHER        7
>   
>   /* Performance Counters */
>   #define CSR_MHPMCOUNTER3    0xb03
> diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/cpu_cfg_fields.h.inc
> index 492fdd1553..4b157ac920 100644
> --- a/target/riscv/cpu_cfg_fields.h.inc
> +++ b/target/riscv/cpu_cfg_fields.h.inc
> @@ -46,6 +46,7 @@ BOOL_FIELD(ext_zilsd)
>   BOOL_FIELD(ext_zimop)
>   BOOL_FIELD(ext_zcmop)
>   BOOL_FIELD(ext_ztso)
> +BOOL_FIELD(ext_sdext)
>   BOOL_FIELD(ext_sdtrig)
>   BOOL_FIELD(ext_smstateen)
>   BOOL_FIELD(ext_sstc)
> diff --git a/target/riscv/csr.c b/target/riscv/csr.c
> index 870fad87ac..3e38c943e0 100644
> --- a/target/riscv/csr.c
> +++ b/target/riscv/csr.c
> @@ -3136,6 +3136,126 @@ static RISCVException write_mtval(CPURISCVState *env, int csrno,
>       return RISCV_EXCP_NONE;
>   }
>   
> +#if !defined(CONFIG_USER_ONLY)
> +static RISCVException sdext(CPURISCVState *env, int csrno)
> +{
> +    if (!riscv_cpu_cfg(env)->ext_sdext) {
> +        return RISCV_EXCP_ILLEGAL_INST;
> +    }
> +
> +    if (!env->debug_mode && !env->debugger) {
> +        return RISCV_EXCP_ILLEGAL_INST;
> +    }
> +
> +    return RISCV_EXCP_NONE;
> +}
> +
> +static target_ulong dcsr_visible_mask(CPURISCVState *env)
> +{
> +    target_ulong mask = (target_ulong)-1;
> +    RISCVCPU *cpu = env_archcpu(env);
> +
> +    if (!riscv_has_ext(env, RVH)) {
> +        mask &= ~(DCSR_EBREAKVS | DCSR_EBREAKVU | DCSR_V);
> +    }
> +    if (!riscv_has_ext(env, RVS)) {
> +        mask &= ~DCSR_EBREAKS;
> +    }
> +    if (!riscv_has_ext(env, RVU)) {
> +        mask &= ~DCSR_EBREAKU;
> +    }
> +    if (!cpu->cfg.ext_zicfilp) {
> +        mask &= ~DCSR_PELP;
> +    }
> +    if (!cpu->cfg.ext_smdbltrp) {
> +        mask &= ~DCSR_CETRIG;
> +    }
> +
> +    return mask;
> +}
> +
> +static RISCVException read_dcsr(CPURISCVState *env, int csrno,
> +                                target_ulong *val)
> +{
> +    *val = env->dcsr & dcsr_visible_mask(env);
> +    return RISCV_EXCP_NONE;
> +}
> +
> +static target_ulong dcsr_writable_mask(CPURISCVState *env)
> +{
> +    target_ulong mask = DCSR_EBREAKM | DCSR_EBREAKS | DCSR_EBREAKU |
> +                        DCSR_STEPIE | DCSR_STOPCOUNT | DCSR_STOPTIME |
> +                        DCSR_STEP | DCSR_PRV_MASK;
> +    RISCVCPU *cpu = env_archcpu(env);
> +
> +    mask |= DCSR_MPRVEN;
> +
> +    if (riscv_has_ext(env, RVH)) {
> +        mask |= DCSR_EBREAKVS | DCSR_EBREAKVU | DCSR_V;
> +    }
> +    if (riscv_has_ext(env, RVS)) {
> +        mask |= DCSR_EBREAKS;
> +    }
> +    if (riscv_has_ext(env, RVU)) {
> +        mask |= DCSR_EBREAKU;
> +    }
> +    if (cpu->cfg.ext_zicfilp) {
> +        mask |= DCSR_PELP;
> +    }
> +    if (cpu->cfg.ext_smdbltrp) {
> +        mask |= DCSR_CETRIG;
> +    }
> +
> +    return mask;
> +}
> +
> +static RISCVException write_dcsr(CPURISCVState *env, int csrno,
> +                                 target_ulong val, uintptr_t ra)
> +{
> +    target_ulong mask = dcsr_writable_mask(env);
> +    target_ulong new_val = env->dcsr;
> +
> +    new_val &= ~mask;
> +    new_val |= val & mask;
> +    new_val &= ~DCSR_XDEBUGVER_MASK;
> +    new_val |= DCSR_DEBUGVER(4);
> +    env->dcsr = new_val;
> +    return RISCV_EXCP_NONE;
> +}
> +
> +static RISCVException read_dpc(CPURISCVState *env, int csrno,
> +                               target_ulong *val)
> +{
> +    *val = env->dpc & get_xepc_mask(env);
> +    return RISCV_EXCP_NONE;
> +}
> +
> +static RISCVException write_dpc(CPURISCVState *env, int csrno,
> +                                target_ulong val, uintptr_t ra)
> +{
> +    env->dpc = val & get_xepc_mask(env);
> +    return RISCV_EXCP_NONE;
> +}
> +
> +static RISCVException read_dscratch(CPURISCVState *env, int csrno,
> +                                    target_ulong *val)
> +{
> +    int index = (csrno == CSR_DSCRATCH1) ? 1 : 0;
> +
> +    *val = env->dscratch[index];
> +    return RISCV_EXCP_NONE;
> +}
> +
> +static RISCVException write_dscratch(CPURISCVState *env, int csrno,
> +                                     target_ulong val, uintptr_t ra)
> +{
> +    int index = (csrno == CSR_DSCRATCH1) ? 1 : 0;
> +
> +    env->dscratch[index] = val;
> +    return RISCV_EXCP_NONE;
> +}
> +#endif /* !CONFIG_USER_ONLY */
> +
>   /* Execution environment configuration setup */
>   static RISCVException read_menvcfg(CPURISCVState *env, int csrno,
>                                      target_ulong *val)
> @@ -6297,6 +6417,12 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = {
>       [CSR_TDATA3]    =  { "tdata3",   debug, read_tdata,    write_tdata    },
>       [CSR_TINFO]     =  { "tinfo",    debug, read_tinfo,    write_ignore   },
>       [CSR_MCONTEXT]  =  { "mcontext", debug, read_mcontext, write_mcontext },
> +#if !defined(CONFIG_USER_ONLY)
> +    [CSR_DCSR]      =  { "dcsr",      sdext, read_dcsr,     write_dcsr },
> +    [CSR_DPC]       =  { "dpc",       sdext, read_dpc,      write_dpc },
> +    [CSR_DSCRATCH]  =  { "dscratch0", sdext, read_dscratch, write_dscratch },
> +    [CSR_DSCRATCH1] =  { "dscratch1", sdext, read_dscratch, write_dscratch },
> +#endif
>   
>       [CSR_MCTRCTL]    = { "mctrctl",    ctr_mmode,  NULL, NULL, rmw_xctrctl    },
>       [CSR_SCTRCTL]    = { "sctrctl",    ctr_smode,  NULL, NULL, rmw_xctrctl    },
> diff --git a/target/riscv/machine.c b/target/riscv/machine.c
> index 62c51c8033..52264cf047 100644
> --- a/target/riscv/machine.c
> +++ b/target/riscv/machine.c
> @@ -248,6 +248,25 @@ static const VMStateDescription vmstate_sdtrig = {
>           VMSTATE_UINTTL_ARRAY(env.tdata1, RISCVCPU, RV_MAX_TRIGGERS),
>           VMSTATE_UINTTL_ARRAY(env.tdata2, RISCVCPU, RV_MAX_TRIGGERS),
>           VMSTATE_UINTTL_ARRAY(env.tdata3, RISCVCPU, RV_MAX_TRIGGERS),
> +        VMSTATE_BOOL_V(env.debug_mode, RISCVCPU, 3),
> +        VMSTATE_UINTTL_V(env.dcsr, RISCVCPU, 3),
> +        VMSTATE_UINTTL_V(env.dpc, RISCVCPU, 3),
> +        VMSTATE_UINTTL_ARRAY_V(env.dscratch, RISCVCPU, 2, 3),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static const VMStateDescription vmstate_sdext = {
> +    .name = "cpu/sdext",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .needed = sdtrig_needed,
> +    .post_load = sdtrig_post_load,
> +    .fields = (const VMStateField[]) {
> +        VMSTATE_BOOL_V(env.debug_mode, RISCVCPU, 3),
> +        VMSTATE_UINTTL_V(env.dcsr, RISCVCPU, 3),
> +        VMSTATE_UINTTL_V(env.dpc, RISCVCPU, 3),
> +        VMSTATE_UINTTL_ARRAY_V(env.dscratch, RISCVCPU, 2, 3),
>           VMSTATE_END_OF_LIST()
>       }
>   };
> @@ -499,6 +518,7 @@ const VMStateDescription vmstate_riscv_cpu = {
>           &vmstate_ctr,
>           &vmstate_sstc,
>           &vmstate_sdtrig,
> +        &vmstate_sdext,
>           NULL
>       }
>   };


Re: [RFC PATCH v3 2/7] target/riscv: add sdext debug CSRs state
Posted by Chao Liu 1 week ago
Hi Daniel,

On 1/29/2026 3:42 AM, Daniel Henrique Barboza wrote:
> 
> 
> On 1/27/2026 10:15 AM, Chao Liu wrote:
>> RISC-V Debug Specification:
>> https://github.com/riscv/riscv-debug-spec/releases/tag/1.0
>>
>> Add architectural state for Sdext Debug Mode: debug_mode, dcsr, dpc
>> and dscratch0/1. Wire up CSR access for dcsr/dpc/dscratch and gate
>> them to Debug Mode (or host debugger access).
>>
>> The Sdext is not fully implemented, so it is disabled by default.
>>
>> Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
>> ---
>>   target/riscv/cpu.c                |  10 +++
>>   target/riscv/cpu.h                |   4 +
>>   target/riscv/cpu_bits.h           |  33 ++++++++
>>   target/riscv/cpu_cfg_fields.h.inc |   1 +
>>   target/riscv/csr.c                | 126 ++++++++++++++++++++++++++++++
>>   target/riscv/machine.c            |  20 +++++
>>   6 files changed, 194 insertions(+)
>>
>> diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
>> index 0ba98a62e4..ba8fd1557a 100644
>> --- a/target/riscv/cpu.c
>> +++ b/target/riscv/cpu.c
>> @@ -209,6 +209,7 @@ const RISCVIsaExtData isa_edata_arr[] = {
>>       ISA_EXT_DATA_ENTRY(zvkt, PRIV_VERSION_1_12_0, ext_zvkt),
>>       ISA_EXT_DATA_ENTRY(zhinx, PRIV_VERSION_1_12_0, ext_zhinx),
>>       ISA_EXT_DATA_ENTRY(zhinxmin, PRIV_VERSION_1_12_0, ext_zhinxmin),
>> +    ISA_EXT_DATA_ENTRY(sdext, PRIV_VERSION_1_12_0, ext_sdext),
>>       ISA_EXT_DATA_ENTRY(sdtrig, PRIV_VERSION_1_12_0, ext_sdtrig),
>>       ISA_EXT_DATA_ENTRY(shcounterenw, PRIV_VERSION_1_12_0, has_priv_1_12),
>>       ISA_EXT_DATA_ENTRY(sha, PRIV_VERSION_1_12_0, ext_sha),
>> @@ -779,6 +780,11 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType
>> type)
>>       /* Default NaN value: sign bit clear, frac msb set */
>>       set_float_default_nan_pattern(0b01000000, &env->fp_status);
>>       env->vill = true;
>> +    env->debug_mode = false;
>> +    env->dcsr = DCSR_DEBUGVER(4);
>> +    env->dpc = 0;
>> +    env->dscratch[0] = 0;
>> +    env->dscratch[1] = 0;
>>     #ifndef CONFIG_USER_ONLY
>>       if (cpu->cfg.ext_sdtrig) {
>> @@ -1131,6 +1137,9 @@ static void riscv_cpu_init(Object *obj)
>>        */
>>       cpu->cfg.ext_sdtrig = true;
>>   +    /* sdext is not fully implemented, so it is disabled by default. */
>> +    cpu->cfg.ext_sdext = false;
>> +
>>       if (mcc->def->profile) {
>>           mcc->def->profile->enabled = true;
>>       }
>> @@ -1245,6 +1254,7 @@ const RISCVCPUMultiExtConfig riscv_cpu_extensions[] = {
>>       MULTI_EXT_CFG_BOOL("smcdeleg", ext_smcdeleg, false),
>>       MULTI_EXT_CFG_BOOL("sscsrind", ext_sscsrind, false),
>>       MULTI_EXT_CFG_BOOL("ssccfg", ext_ssccfg, false),
>> +    MULTI_EXT_CFG_BOOL("sdext", ext_sdext, false),
>>       MULTI_EXT_CFG_BOOL("sdtrig", ext_sdtrig, true),
>>       MULTI_EXT_CFG_BOOL("smctr", ext_smctr, false),
>>       MULTI_EXT_CFG_BOOL("ssctr", ext_ssctr, false),
>> diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
>> index 5c6824f2d3..a474494dff 100644
>> --- a/target/riscv/cpu.h
>> +++ b/target/riscv/cpu.h
>> @@ -476,6 +476,10 @@ struct CPUArchState {
>>         /* True if in debugger mode.  */
>>       bool debugger;
>> +    bool debug_mode;
>> +    target_ulong dcsr;
>> +    target_ulong dpc;
>> +    target_ulong dscratch[2];
> 
> This patch (and I believe almost every single other patch that follows) won't
> build in the linux-user target. It will throw errors like this:
> 
> ../target/riscv/cpu_helper.c: In function ‘riscv_cpu_enter_debug_mode’:  
> ../target/riscv/cpu_helper.c:154:8: error: ‘CPURISCVState’ {aka ‘struct
> CPUArchState’} has no member named ‘debug_mode’       154 | env->debug_mode =
> true;                                              |        ^~  
>                    ../target/riscv/cpu_helper.c:155:10: error:
> ‘CPURISCVState’ {aka ‘struct CPUArchState’} has no member named ‘dpc’; did you
> mean ‘pc’?                                                               155 |
> env->dpc = pc & get_xepc_mask(env);                                             
> |          ^~~
> 
> 
> The reason is that the flags you're declaring up above exist only in the system
> emulation mode. If you double check target/riscv/cpu.h you'll notice that the
> flags you're adding are inside a big ifdef around line 270:
> 
> #ifndef CONFIG_USER_ONLY
>     /* This contains QEMU specific information about the virt state. */
> 
> 
> So debug_mode, dscr, dpc and dscratch aren't available for user mode. Note that
> this is correct - user mode can't deal with sdext or any other debug trigger
> extension. But you'll have to gate all logic that uses those flags in #ifndef
> CONFIG_USER_ONLY blocks.
> 
> 
> For example, in patch 3:
> 
> 
> +void riscv_cpu_enter_debug_mode(CPURISCVState *env, target_ulong pc,
> +                                uint32_t cause)
> +{
> +#ifndef CONFIG_USER_ONLY
> +    if (!riscv_sdext_enabled(env)) {
> +        return;
> +    }
> +#endif
> +    env->debug_mode = true;
> +    env->dpc = pc & get_xepc_mask(env);
> +    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;
> +    if (env->virt_enabled && riscv_has_ext(env, RVH)) {
> +        env->dcsr |= DCSR_V;
> +    }
> +#ifndef CONFIG_USER_ONLY
> +    if (env_archcpu(env)->cfg.ext_zicfilp) {
> +        if (env->elp) {
> +            env->dcsr |= DCSR_PELP;
> +        } else {
> +            env->dcsr &= ~DCSR_PELP;
> +        }
> +        env->elp = false;
> +    }
> +#endif
> 
> 
> We want the whole function body inside an "#ifndef CONFIG_USER_ONLY" because
> everything being done is unavailable in that mode.
> 
> You can use the build target 'riscv64-linux-user' to build qemu-riscv64 (i.e.
> user mode) to check if the patches will break it by accident. I usually build
> all riscv targets to catch this type of build errors:
> 
> 
> ./configure --target-list=riscv64-softmmu,riscv64-linux-user,riscv32-
> softmmu,riscv32-linux-user  (...)
> 
> 
Thanks for catching this! You're right - I missed the linux-user build
target entirely.

I'll fix this in v4 by moving debug_mode/dcsr/dpc/dscratch fields inside
the #ifndef CONFIG_USER_ONLY block in cpu.h, and wrapping all code using
these fields with proper guards.

Thanks,
Chao
> 
> Thanks,
> Daniel
> 
> 
> 
> 
>>         uint64_t mstateen[SMSTATEEN_MAX_COUNT];
>>       uint64_t hstateen[SMSTATEEN_MAX_COUNT];
>> diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h
>> index b62dd82fe7..bb59f7ff56 100644
>> --- a/target/riscv/cpu_bits.h
>> +++ b/target/riscv/cpu_bits.h
>> @@ -467,6 +467,39 @@
>>   #define CSR_DCSR            0x7b0
>>   #define CSR_DPC             0x7b1
>>   #define CSR_DSCRATCH        0x7b2
>> +#define CSR_DSCRATCH1       0x7b3
>> +
>> +/* DCSR fields */
>> +#define DCSR_XDEBUGVER_SHIFT    28
>> +#define DCSR_XDEBUGVER_MASK     (0xfu << DCSR_XDEBUGVER_SHIFT)
>> +#define DCSR_DEBUGVER(val)      ((target_ulong)(val) << DCSR_XDEBUGVER_SHIFT)
>> +#define DCSR_EXTCAUSE_SHIFT     24
>> +#define DCSR_EXTCAUSE_MASK      (0x7u << DCSR_EXTCAUSE_SHIFT)
>> +#define DCSR_CETRIG             BIT(19)
>> +#define DCSR_PELP               BIT(18)
>> +#define DCSR_EBREAKVS           BIT(17)
>> +#define DCSR_EBREAKVU           BIT(16)
>> +#define DCSR_EBREAKM            BIT(15)
>> +#define DCSR_EBREAKS            BIT(13)
>> +#define DCSR_EBREAKU            BIT(12)
>> +#define DCSR_STEPIE             BIT(11)
>> +#define DCSR_STOPCOUNT          BIT(10)
>> +#define DCSR_STOPTIME           BIT(9)
>> +#define DCSR_CAUSE_SHIFT        6
>> +#define DCSR_CAUSE_MASK         (0x7u << DCSR_CAUSE_SHIFT)
>> +#define DCSR_V                  BIT(5)
>> +#define DCSR_MPRVEN             BIT(4)
>> +#define DCSR_NMIP               BIT(3)
>> +#define DCSR_STEP               BIT(2)
>> +#define DCSR_PRV_MASK           0x3u
>> +
>> +#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
>> +#define DCSR_CAUSE_GROUP        6
>> +#define DCSR_CAUSE_OTHER        7
>>     /* Performance Counters */
>>   #define CSR_MHPMCOUNTER3    0xb03
>> diff --git a/target/riscv/cpu_cfg_fields.h.inc b/target/riscv/
>> cpu_cfg_fields.h.inc
>> index 492fdd1553..4b157ac920 100644
>> --- a/target/riscv/cpu_cfg_fields.h.inc
>> +++ b/target/riscv/cpu_cfg_fields.h.inc
>> @@ -46,6 +46,7 @@ BOOL_FIELD(ext_zilsd)
>>   BOOL_FIELD(ext_zimop)
>>   BOOL_FIELD(ext_zcmop)
>>   BOOL_FIELD(ext_ztso)
>> +BOOL_FIELD(ext_sdext)
>>   BOOL_FIELD(ext_sdtrig)
>>   BOOL_FIELD(ext_smstateen)
>>   BOOL_FIELD(ext_sstc)
>> diff --git a/target/riscv/csr.c b/target/riscv/csr.c
>> index 870fad87ac..3e38c943e0 100644
>> --- a/target/riscv/csr.c
>> +++ b/target/riscv/csr.c
>> @@ -3136,6 +3136,126 @@ static RISCVException write_mtval(CPURISCVState *env,
>> int csrno,
>>       return RISCV_EXCP_NONE;
>>   }
>>   +#if !defined(CONFIG_USER_ONLY)
>> +static RISCVException sdext(CPURISCVState *env, int csrno)
>> +{
>> +    if (!riscv_cpu_cfg(env)->ext_sdext) {
>> +        return RISCV_EXCP_ILLEGAL_INST;
>> +    }
>> +
>> +    if (!env->debug_mode && !env->debugger) {
>> +        return RISCV_EXCP_ILLEGAL_INST;
>> +    }
>> +
>> +    return RISCV_EXCP_NONE;
>> +}
>> +
>> +static target_ulong dcsr_visible_mask(CPURISCVState *env)
>> +{
>> +    target_ulong mask = (target_ulong)-1;
>> +    RISCVCPU *cpu = env_archcpu(env);
>> +
>> +    if (!riscv_has_ext(env, RVH)) {
>> +        mask &= ~(DCSR_EBREAKVS | DCSR_EBREAKVU | DCSR_V);
>> +    }
>> +    if (!riscv_has_ext(env, RVS)) {
>> +        mask &= ~DCSR_EBREAKS;
>> +    }
>> +    if (!riscv_has_ext(env, RVU)) {
>> +        mask &= ~DCSR_EBREAKU;
>> +    }
>> +    if (!cpu->cfg.ext_zicfilp) {
>> +        mask &= ~DCSR_PELP;
>> +    }
>> +    if (!cpu->cfg.ext_smdbltrp) {
>> +        mask &= ~DCSR_CETRIG;
>> +    }
>> +
>> +    return mask;
>> +}
>> +
>> +static RISCVException read_dcsr(CPURISCVState *env, int csrno,
>> +                                target_ulong *val)
>> +{
>> +    *val = env->dcsr & dcsr_visible_mask(env);
>> +    return RISCV_EXCP_NONE;
>> +}
>> +
>> +static target_ulong dcsr_writable_mask(CPURISCVState *env)
>> +{
>> +    target_ulong mask = DCSR_EBREAKM | DCSR_EBREAKS | DCSR_EBREAKU |
>> +                        DCSR_STEPIE | DCSR_STOPCOUNT | DCSR_STOPTIME |
>> +                        DCSR_STEP | DCSR_PRV_MASK;
>> +    RISCVCPU *cpu = env_archcpu(env);
>> +
>> +    mask |= DCSR_MPRVEN;
>> +
>> +    if (riscv_has_ext(env, RVH)) {
>> +        mask |= DCSR_EBREAKVS | DCSR_EBREAKVU | DCSR_V;
>> +    }
>> +    if (riscv_has_ext(env, RVS)) {
>> +        mask |= DCSR_EBREAKS;
>> +    }
>> +    if (riscv_has_ext(env, RVU)) {
>> +        mask |= DCSR_EBREAKU;
>> +    }
>> +    if (cpu->cfg.ext_zicfilp) {
>> +        mask |= DCSR_PELP;
>> +    }
>> +    if (cpu->cfg.ext_smdbltrp) {
>> +        mask |= DCSR_CETRIG;
>> +    }
>> +
>> +    return mask;
>> +}
>> +
>> +static RISCVException write_dcsr(CPURISCVState *env, int csrno,
>> +                                 target_ulong val, uintptr_t ra)
>> +{
>> +    target_ulong mask = dcsr_writable_mask(env);
>> +    target_ulong new_val = env->dcsr;
>> +
>> +    new_val &= ~mask;
>> +    new_val |= val & mask;
>> +    new_val &= ~DCSR_XDEBUGVER_MASK;
>> +    new_val |= DCSR_DEBUGVER(4);
>> +    env->dcsr = new_val;
>> +    return RISCV_EXCP_NONE;
>> +}
>> +
>> +static RISCVException read_dpc(CPURISCVState *env, int csrno,
>> +                               target_ulong *val)
>> +{
>> +    *val = env->dpc & get_xepc_mask(env);
>> +    return RISCV_EXCP_NONE;
>> +}
>> +
>> +static RISCVException write_dpc(CPURISCVState *env, int csrno,
>> +                                target_ulong val, uintptr_t ra)
>> +{
>> +    env->dpc = val & get_xepc_mask(env);
>> +    return RISCV_EXCP_NONE;
>> +}
>> +
>> +static RISCVException read_dscratch(CPURISCVState *env, int csrno,
>> +                                    target_ulong *val)
>> +{
>> +    int index = (csrno == CSR_DSCRATCH1) ? 1 : 0;
>> +
>> +    *val = env->dscratch[index];
>> +    return RISCV_EXCP_NONE;
>> +}
>> +
>> +static RISCVException write_dscratch(CPURISCVState *env, int csrno,
>> +                                     target_ulong val, uintptr_t ra)
>> +{
>> +    int index = (csrno == CSR_DSCRATCH1) ? 1 : 0;
>> +
>> +    env->dscratch[index] = val;
>> +    return RISCV_EXCP_NONE;
>> +}
>> +#endif /* !CONFIG_USER_ONLY */
>> +
>>   /* Execution environment configuration setup */
>>   static RISCVException read_menvcfg(CPURISCVState *env, int csrno,
>>                                      target_ulong *val)
>> @@ -6297,6 +6417,12 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = {
>>       [CSR_TDATA3]    =  { "tdata3",   debug, read_tdata,    write_tdata    },
>>       [CSR_TINFO]     =  { "tinfo",    debug, read_tinfo,    write_ignore   },
>>       [CSR_MCONTEXT]  =  { "mcontext", debug, read_mcontext, write_mcontext },
>> +#if !defined(CONFIG_USER_ONLY)
>> +    [CSR_DCSR]      =  { "dcsr",      sdext, read_dcsr,     write_dcsr },
>> +    [CSR_DPC]       =  { "dpc",       sdext, read_dpc,      write_dpc },
>> +    [CSR_DSCRATCH]  =  { "dscratch0", sdext, read_dscratch, write_dscratch },
>> +    [CSR_DSCRATCH1] =  { "dscratch1", sdext, read_dscratch, write_dscratch },
>> +#endif
>>         [CSR_MCTRCTL]    = { "mctrctl",    ctr_mmode,  NULL, NULL,
>> rmw_xctrctl    },
>>       [CSR_SCTRCTL]    = { "sctrctl",    ctr_smode,  NULL, NULL,
>> rmw_xctrctl    },
>> diff --git a/target/riscv/machine.c b/target/riscv/machine.c
>> index 62c51c8033..52264cf047 100644
>> --- a/target/riscv/machine.c
>> +++ b/target/riscv/machine.c
>> @@ -248,6 +248,25 @@ static const VMStateDescription vmstate_sdtrig = {
>>           VMSTATE_UINTTL_ARRAY(env.tdata1, RISCVCPU, RV_MAX_TRIGGERS),
>>           VMSTATE_UINTTL_ARRAY(env.tdata2, RISCVCPU, RV_MAX_TRIGGERS),
>>           VMSTATE_UINTTL_ARRAY(env.tdata3, RISCVCPU, RV_MAX_TRIGGERS),
>> +        VMSTATE_BOOL_V(env.debug_mode, RISCVCPU, 3),
>> +        VMSTATE_UINTTL_V(env.dcsr, RISCVCPU, 3),
>> +        VMSTATE_UINTTL_V(env.dpc, RISCVCPU, 3),
>> +        VMSTATE_UINTTL_ARRAY_V(env.dscratch, RISCVCPU, 2, 3),
>> +        VMSTATE_END_OF_LIST()
>> +    }
>> +};
>> +
>> +static const VMStateDescription vmstate_sdext = {
>> +    .name = "cpu/sdext",
>> +    .version_id = 1,
>> +    .minimum_version_id = 1,
>> +    .needed = sdtrig_needed,
>> +    .post_load = sdtrig_post_load,
>> +    .fields = (const VMStateField[]) {
>> +        VMSTATE_BOOL_V(env.debug_mode, RISCVCPU, 3),
>> +        VMSTATE_UINTTL_V(env.dcsr, RISCVCPU, 3),
>> +        VMSTATE_UINTTL_V(env.dpc, RISCVCPU, 3),
>> +        VMSTATE_UINTTL_ARRAY_V(env.dscratch, RISCVCPU, 2, 3),
>>           VMSTATE_END_OF_LIST()
>>       }
>>   };
>> @@ -499,6 +518,7 @@ const VMStateDescription vmstate_riscv_cpu = {
>>           &vmstate_ctr,
>>           &vmstate_sstc,
>>           &vmstate_sdtrig,
>> +        &vmstate_sdext,
>>           NULL
>>       }
>>   };
>