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