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 <[email protected]>
---
 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


Reply via email to