Lots of RISC-V improvements. 22/4922/3
authorTim Newsome <tim@sifive.com>
Fri, 15 Feb 2019 20:08:51 +0000 (12:08 -0800)
committerMatthias Welwarsky <matthias@welwarsky.de>
Wed, 27 Mar 2019 08:53:09 +0000 (08:53 +0000)
This represents months of continuing RISC-V work, with too many changes
to list individually. Some improvements:
* Fixed memory leaks.
* Better handling of dbus timeouts.
* Add `riscv expose_custom` command.
* Somewhat deal with cache coherency.
* Deal with more timeouts during block memory accesses.
* Basic debug compliance test.
* Tell gdb which watchpoint hit.
* SMP support for use with -rtos hwthread
* Add `riscv set_ir`

Change-Id: Ica507ee2a57eaf51b578ab1d9b7de71512fdf47f
Signed-off-by: Tim Newsome <tim@sifive.com>
Reviewed-on: http://openocd.zylin.com/4922
Tested-by: jenkins
Reviewed-by: Philipp Guehring <pg@futureware.at>
Reviewed-by: Liviu Ionescu <ilg@livius.net>
Reviewed-by: Matthias Welwarsky <matthias@welwarsky.de>
doc/openocd.texi
src/target/riscv/batch.c
src/target/riscv/opcodes.h
src/target/riscv/program.h
src/target/riscv/riscv-011.c
src/target/riscv/riscv-013.c
src/target/riscv/riscv.c
src/target/riscv/riscv.h

index bbb907558950b58bd80f7905c866632f6f82f1fb..a17173ce8838ea434ddd227c1be15f680140993b 100644 (file)
@@ -9466,6 +9466,14 @@ command can be used if OpenOCD gets this wrong, or a target implements custom
 CSRs.
 @end deffn
 
+@deffn Command {riscv expose_custom} n0[-m0][,n1[-m1]]...
+The RISC-V Debug Specification allows targets to expose custom registers
+through abstract commands. (See Section 3.5.1.1 in that document.) This command
+configures a list of inclusive ranges of those registers to expose. Number 0
+indicates the first custom register, whose abstract command number is 0xc000.
+This command must be executed before `init`.
+@end deffn
+
 @deffn Command {riscv set_command_timeout_sec} [seconds]
 Set the wall-clock timeout (in seconds) for individual commands. The default
 should work fine for all but the slowest targets (eg. simulators).
@@ -9486,6 +9494,17 @@ When on, prefer to use System Bus Access to access memory.  When off, prefer to
 use the Program Buffer to access memory.
 @end deffn
 
+@deffn Command {riscv set_ir} (@option{idcode}|@option{dtmcs}|@option{dmi}) [value]
+Set the IR value for the specified JTAG register.  This is useful, for
+example, when using the existing JTAG interface on a Xilinx FPGA by
+way of BSCANE2 primitives that only permit a limited selection of IR
+values.
+
+When utilizing version 0.11 of the RISC-V Debug Specification,
+@option{dtmcs} and @option{dmi} set the IR values for the DTMCONTROL
+and DBUS registers, respectively.
+@end deffn
+
 @subsection RISC-V Authentication Commands
 
 The following commands can be used to authenticate to a RISC-V system. Eg.  a
index 9327cb38babdea7bb6ddadb80fed40a9179501c3..d041ed119205d282fa0ea6fd52a17e9af93ebfcc 100644 (file)
@@ -9,23 +9,20 @@
 #define get_field(reg, mask) (((reg) & (mask)) / ((mask) & ~((mask) << 1)))
 #define set_field(reg, mask, val) (((reg) & ~(mask)) | (((val) * ((mask) & ~((mask) << 1))) & (mask)))
 
-static void dump_field(const struct scan_field *field);
+static void dump_field(int idle, const struct scan_field *field);
 
 struct riscv_batch *riscv_batch_alloc(struct target *target, size_t scans, size_t idle)
 {
        scans += 4;
-       struct riscv_batch *out = malloc(sizeof(*out));
-       memset(out, 0, sizeof(*out));
+       struct riscv_batch *out = calloc(1, sizeof(*out));
        out->target = target;
        out->allocated_scans = scans;
-       out->used_scans = 0;
        out->idle_count = idle;
        out->data_out = malloc(sizeof(*out->data_out) * (scans) * sizeof(uint64_t));
        out->data_in  = malloc(sizeof(*out->data_in)  * (scans) * sizeof(uint64_t));
        out->fields = malloc(sizeof(*out->fields) * (scans));
        out->last_scan = RISCV_SCAN_TYPE_INVALID;
        out->read_keys = malloc(sizeof(*out->read_keys) * (scans));
-       out->read_keys_used = 0;
        return out;
 }
 
@@ -51,7 +48,6 @@ int riscv_batch_run(struct riscv_batch *batch)
 
        keep_alive();
 
-       LOG_DEBUG("running a batch of %ld scans", (long)batch->used_scans);
        riscv_batch_add_nop(batch);
 
        for (size_t i = 0; i < batch->used_scans; ++i) {
@@ -60,14 +56,13 @@ int riscv_batch_run(struct riscv_batch *batch)
                        jtag_add_runtest(batch->idle_count, TAP_IDLE);
        }
 
-       LOG_DEBUG("executing queue");
        if (jtag_execute_queue() != ERROR_OK) {
                LOG_ERROR("Unable to execute JTAG queue");
                return ERROR_FAIL;
        }
 
        for (size_t i = 0; i < batch->used_scans; ++i)
-               dump_field(batch->fields + i);
+               dump_field(batch->idle_count, batch->fields + i);
 
        return ERROR_OK;
 }
@@ -98,13 +93,10 @@ size_t riscv_batch_add_dmi_read(struct riscv_batch *batch, unsigned address)
        batch->used_scans++;
 
        /* FIXME We get the read response back on the next scan.  For now I'm
-        * just sticking a NOP in there, but this should be coelesced away. */
+        * just sticking a NOP in there, but this should be coalesced away. */
        riscv_batch_add_nop(batch);
 
        batch->read_keys[batch->read_keys_used] = batch->used_scans - 1;
-       LOG_DEBUG("read key %u for batch 0x%p is %u (0x%p)",
-                       (unsigned) batch->read_keys_used, batch, (unsigned) (batch->used_scans - 1),
-                       batch->data_in + sizeof(uint64_t) * (batch->used_scans + 1));
        return batch->read_keys_used++;
 }
 
@@ -135,10 +127,9 @@ void riscv_batch_add_nop(struct riscv_batch *batch)
        riscv_fill_dmi_nop_u64(batch->target, (char *)field->in_value);
        batch->last_scan = RISCV_SCAN_TYPE_NOP;
        batch->used_scans++;
-       LOG_DEBUG("  added NOP with in_value=0x%p", field->in_value);
 }
 
-void dump_field(const struct scan_field *field)
+void dump_field(int idle, const struct scan_field *field)
 {
        static const char * const op_string[] = {"-", "r", "w", "?"};
        static const char * const status_string[] = {"+", "?", "F", "b"};
@@ -160,13 +151,13 @@ void dump_field(const struct scan_field *field)
 
                log_printf_lf(LOG_LVL_DEBUG,
                                __FILE__, __LINE__, __PRETTY_FUNCTION__,
-                               "%db %s %08x @%02x -> %s %08x @%02x",
-                               field->num_bits,
+                               "%db %di %s %08x @%02x -> %s %08x @%02x",
+                               field->num_bits, idle,
                                op_string[out_op], out_data, out_address,
                                status_string[in_op], in_data, in_address);
        } else {
                log_printf_lf(LOG_LVL_DEBUG,
-                               __FILE__, __LINE__, __PRETTY_FUNCTION__, "%db %s %08x @%02x -> ?",
-                               field->num_bits, op_string[out_op], out_data, out_address);
+                               __FILE__, __LINE__, __PRETTY_FUNCTION__, "%db %di %s %08x @%02x -> ?",
+                               field->num_bits, idle, op_string[out_op], out_data, out_address);
        }
 }
index dd51c809d58a894ab2bfc31b12e8ea1bfc6984d5..de85aadd8e08c6d4448fd11620c1ee90189f9ee2 100644 (file)
@@ -224,6 +224,9 @@ static uint32_t ebreak_c(void)
        return MATCH_C_EBREAK;
 }
 
+static uint32_t wfi(void) __attribute__ ((unused));
+static uint32_t wfi(void) { return MATCH_WFI; }
+
 static uint32_t fence_i(void) __attribute__ ((unused));
 static uint32_t fence_i(void)
 {
index d641be1be1fae04ff083d2b45d6013c003922acf..310460c2816a385be00a0ef1df16fed17db3f841 100644 (file)
@@ -52,8 +52,8 @@ int riscv_program_insert(struct riscv_program *p, riscv_insn_t i);
  * memory. */
 int riscv_program_save_to_dscratch(struct riscv_program *p, enum gdb_regno to_save);
 
-/* Helpers to assembly various instructions.  Return 0 on success.  These might
- * assembly into a multi-instruction sequence that overwrites some other
+/* Helpers to assemble various instructions.  Return 0 on success.  These might
+ * assemble into a multi-instruction sequence that overwrites some other
  * register, but those will be properly saved and restored. */
 int riscv_program_lwr(struct riscv_program *p, enum gdb_regno d, enum gdb_regno a, int o);
 int riscv_program_lhr(struct riscv_program *p, enum gdb_regno d, enum gdb_regno a, int o);
index bd3f159fbeebd213683cb8eefc68867f8f695579..eded86246cc21f8c51d104e059051a4fe497412d 100644 (file)
@@ -358,6 +358,15 @@ static void add_dbus_scan(const struct target *target, struct scan_field *field,
                uint16_t address, uint64_t data)
 {
        riscv011_info_t *info = get_info(target);
+       RISCV_INFO(r);
+
+       if (r->reset_delays_wait >= 0) {
+               r->reset_delays_wait--;
+               if (r->reset_delays_wait < 0) {
+                       info->dbus_busy_delay = 0;
+                       info->interrupt_high_delay = 0;
+               }
+       }
 
        field->num_bits = info->addrbits + DBUS_OP_SIZE + DBUS_DATA_SIZE;
        field->in_value = in_value;
@@ -1408,12 +1417,6 @@ static int strict_step(struct target *target, bool announce)
 
        LOG_DEBUG("enter");
 
-       struct breakpoint *breakpoint = target->breakpoints;
-       while (breakpoint) {
-               riscv_remove_breakpoint(target, breakpoint);
-               breakpoint = breakpoint->next;
-       }
-
        struct watchpoint *watchpoint = target->watchpoints;
        while (watchpoint) {
                riscv_remove_watchpoint(target, watchpoint);
@@ -1424,12 +1427,6 @@ static int strict_step(struct target *target, bool announce)
        if (result != ERROR_OK)
                return result;
 
-       breakpoint = target->breakpoints;
-       while (breakpoint) {
-               riscv_add_breakpoint(target, breakpoint);
-               breakpoint = breakpoint->next;
-       }
-
        watchpoint = target->watchpoints;
        while (watchpoint) {
                riscv_add_watchpoint(target, watchpoint);
@@ -1463,7 +1460,7 @@ static int step(struct target *target, int current, target_addr_t address,
                if (result != ERROR_OK)
                        return result;
        } else {
-               return resume(target, 0, true);
+               return full_step(target, false);
        }
 
        return ERROR_OK;
@@ -1676,7 +1673,7 @@ static riscv_error_t handle_halt_routine(struct target *target)
                                break;
                        default:
                                LOG_ERROR("Got invalid bus access status: %d", status);
-                               return ERROR_FAIL;
+                               goto error;
                }
                if (data & DMCONTROL_INTERRUPT) {
                        interrupt_set++;
@@ -1850,7 +1847,7 @@ static int handle_halt(struct target *target, bool announce)
                        target->debug_reason = DBG_REASON_BREAKPOINT;
                        break;
                case DCSR_CAUSE_HWBP:
-                       target->debug_reason = DBG_REASON_WPTANDBKPT;
+                       target->debug_reason = DBG_REASON_WATCHPOINT;
                        /* If we halted because of a data trigger, gdb doesn't know to do
                         * the disable-breakpoints-step-enable-breakpoints dance. */
                        info->need_strict_step = true;
index 4acd4275470b89aad1f473717191e86596227484..5683e5a3f6af9d76b5bd54ca2f6167ffea7596b3 100644 (file)
@@ -64,6 +64,13 @@ static int read_memory(struct target *target, target_addr_t address,
                uint32_t size, uint32_t count, uint8_t *buffer);
 static int write_memory(struct target *target, target_addr_t address,
                uint32_t size, uint32_t count, const uint8_t *buffer);
+static int riscv013_test_sba_config_reg(struct target *target, target_addr_t legal_address,
+               uint32_t num_words, target_addr_t illegal_address, bool run_sbbusyerror_test);
+void write_memory_sba_simple(struct target *target, target_addr_t addr, uint32_t* write_data,
+               uint32_t write_size, uint32_t sbcs);
+void read_memory_sba_simple(struct target *target, target_addr_t addr,
+               uint32_t *rd_buf, uint32_t read_size, uint32_t sbcs);
+static int     riscv013_test_compliance(struct target *target);
 
 /**
  * Since almost everything can be accomplish by scanning the dbus register, all
@@ -169,7 +176,7 @@ typedef struct {
 
        /* Number of run-test/idle cycles the target requests we do after each dbus
         * access. */
-       unsigned int dtmcontrol_idle;
+       unsigned int dtmcs_idle;
 
        /* This value is incremented every time a dbus access comes back as "busy".
         * It's used to determine how many run-test/idle cycles to feed the target
@@ -187,8 +194,6 @@ typedef struct {
         * go low. */
        unsigned int ac_busy_delay;
 
-       bool need_strict_step;
-
        bool abstract_read_csr_supported;
        bool abstract_write_csr_supported;
        bool abstract_read_fpr_supported;
@@ -351,7 +356,7 @@ static void decode_dmi(char *text, unsigned address, unsigned data)
        }
 }
 
-static void dump_field(const struct scan_field *field)
+static void dump_field(int idle, const struct scan_field *field)
 {
        static const char * const op_string[] = {"-", "r", "w", "?"};
        static const char * const status_string[] = {"+", "?", "F", "b"};
@@ -371,8 +376,8 @@ static void dump_field(const struct scan_field *field)
 
        log_printf_lf(LOG_LVL_DEBUG,
                        __FILE__, __LINE__, "scan",
-                       "%db %s %08x @%02x -> %s %08x @%02x",
-                       field->num_bits,
+                       "%db %di %s %08x @%02x -> %s %08x @%02x",
+                       field->num_bits, idle,
                        op_string[out_op], out_data, out_address,
                        status_string[in_op], in_data, in_address);
 
@@ -390,16 +395,7 @@ static void dump_field(const struct scan_field *field)
 
 static void select_dmi(struct target *target)
 {
-       static uint8_t ir_dmi[1] = {DTM_DMI};
-       struct scan_field field = {
-               .num_bits = target->tap->ir_length,
-               .out_value = ir_dmi,
-               .in_value = NULL,
-               .check_value = NULL,
-               .check_mask = NULL
-       };
-
-       jtag_add_ir_scan(target->tap, &field, TAP_IDLE);
+       jtag_add_ir_scan(target->tap, &select_dbus, TAP_IDLE);
 }
 
 static uint32_t dtmcontrol_scan(struct target *target, uint32_t out)
@@ -436,8 +432,8 @@ static void increase_dmi_busy_delay(struct target *target)
 {
        riscv013_info_t *info = get_info(target);
        info->dmi_busy_delay += info->dmi_busy_delay / 10 + 1;
-       LOG_DEBUG("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
-                       info->dtmcontrol_idle, info->dmi_busy_delay,
+       LOG_DEBUG("dtmcs_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
+                       info->dtmcs_idle, info->dmi_busy_delay,
                        info->ac_busy_delay);
 
        dtmcontrol_scan(target, DTM_DTMCS_DMIRESET);
@@ -452,14 +448,27 @@ static dmi_status_t dmi_scan(struct target *target, uint32_t *address_in,
                bool exec)
 {
        riscv013_info_t *info = get_info(target);
-       uint8_t in[8] = {0};
-       uint8_t out[8];
+       RISCV_INFO(r);
+       unsigned num_bits = info->abits + DTM_DMI_OP_LENGTH + DTM_DMI_DATA_LENGTH;
+       size_t num_bytes = (num_bits + 7) / 8;
+       uint8_t in[num_bytes];
+       uint8_t out[num_bytes];
        struct scan_field field = {
-               .num_bits = info->abits + DTM_DMI_OP_LENGTH + DTM_DMI_DATA_LENGTH,
+               .num_bits = num_bits,
                .out_value = out,
                .in_value = in
        };
 
+       if (r->reset_delays_wait >= 0) {
+               r->reset_delays_wait--;
+               if (r->reset_delays_wait < 0) {
+                       info->dmi_busy_delay = 0;
+                       info->ac_busy_delay = 0;
+               }
+       }
+
+       memset(in, 0, num_bytes);
+
        assert(info->abits != 0);
 
        buf_set_u32(out, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, op);
@@ -488,19 +497,25 @@ static dmi_status_t dmi_scan(struct target *target, uint32_t *address_in,
        if (address_in)
                *address_in = buf_get_u32(in, DTM_DMI_ADDRESS_OFFSET, info->abits);
 
-       dump_field(&field);
+       dump_field(idle_count, &field);
 
        return buf_get_u32(in, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
 }
 
-static int dmi_op_timeout(struct target *target, uint32_t *data_in, int dmi_op,
-               uint32_t address, uint32_t data_out, int timeout_sec)
+/* If dmi_busy_encountered is non-NULL, this function will use it to tell the
+ * caller whether DMI was ever busy during this call. */
+static int dmi_op_timeout(struct target *target, uint32_t *data_in,
+               bool *dmi_busy_encountered, int dmi_op, uint32_t address,
+               uint32_t data_out, int timeout_sec, bool exec)
 {
        select_dmi(target);
 
        dmi_status_t status;
        uint32_t address_in;
 
+       if (dmi_busy_encountered)
+               *dmi_busy_encountered = false;
+
        const char *op_name;
        switch (dmi_op) {
                case DMI_OP_NOP:
@@ -522,9 +537,11 @@ static int dmi_op_timeout(struct target *target, uint32_t *data_in, int dmi_op,
         * stays busy, it is actually due to the previous access. */
        while (1) {
                status = dmi_scan(target, NULL, NULL, dmi_op, address, data_out,
-                               false);
+                               exec);
                if (status == DMI_STATUS_BUSY) {
                        increase_dmi_busy_delay(target);
+                       if (dmi_busy_encountered)
+                               *dmi_busy_encountered = true;
                } else if (status == DMI_STATUS_SUCCESS) {
                        break;
                } else {
@@ -573,11 +590,12 @@ static int dmi_op_timeout(struct target *target, uint32_t *data_in, int dmi_op,
        return ERROR_OK;
 }
 
-static int dmi_op(struct target *target, uint32_t *data_in, int dmi_op,
-               uint32_t address, uint32_t data_out)
+static int dmi_op(struct target *target, uint32_t *data_in,
+               bool *dmi_busy_encountered, int dmi_op, uint32_t address,
+               uint32_t data_out, bool exec)
 {
-       int result = dmi_op_timeout(target, data_in, dmi_op, address, data_out,
-                       riscv_command_timeout_sec);
+       int result = dmi_op_timeout(target, data_in, dmi_busy_encountered, dmi_op,
+                       address, data_out, riscv_command_timeout_sec, exec);
        if (result == ERROR_TIMEOUT_REACHED) {
                LOG_ERROR("DMI operation didn't complete in %d seconds. The target is "
                                "either really slow or broken. You could increase the "
@@ -590,19 +608,29 @@ static int dmi_op(struct target *target, uint32_t *data_in, int dmi_op,
 
 static int dmi_read(struct target *target, uint32_t *value, uint32_t address)
 {
-       return dmi_op(target, value, DMI_OP_READ, address, 0);
+       return dmi_op(target, value, NULL, DMI_OP_READ, address, 0, false);
+}
+
+static int dmi_read_exec(struct target *target, uint32_t *value, uint32_t address)
+{
+       return dmi_op(target, value, NULL, DMI_OP_READ, address, 0, true);
 }
 
 static int dmi_write(struct target *target, uint32_t address, uint32_t value)
 {
-       return dmi_op(target, NULL, DMI_OP_WRITE, address, value);
+       return dmi_op(target, NULL, NULL, DMI_OP_WRITE, address, value, false);
+}
+
+static int dmi_write_exec(struct target *target, uint32_t address, uint32_t value)
+{
+       return dmi_op(target, NULL, NULL, DMI_OP_WRITE, address, value, true);
 }
 
 int dmstatus_read_timeout(struct target *target, uint32_t *dmstatus,
                bool authenticated, unsigned timeout_sec)
 {
-       int result = dmi_op_timeout(target, dmstatus, DMI_OP_READ, DMI_DMSTATUS, 0,
-                       timeout_sec);
+       int result = dmi_op_timeout(target, dmstatus, NULL, DMI_OP_READ,
+                       DMI_DMSTATUS, 0, timeout_sec, false);
        if (result != ERROR_OK)
                return result;
        if (authenticated && !get_field(*dmstatus, DMI_DMSTATUS_AUTHENTICATED)) {
@@ -625,8 +653,8 @@ static void increase_ac_busy_delay(struct target *target)
 {
        riscv013_info_t *info = get_info(target);
        info->ac_busy_delay += info->ac_busy_delay / 10 + 1;
-       LOG_DEBUG("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
-                       info->dtmcontrol_idle, info->dmi_busy_delay,
+       LOG_DEBUG("dtmcs_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
+                       info->dtmcs_idle, info->dmi_busy_delay,
                        info->ac_busy_delay);
 }
 
@@ -687,8 +715,25 @@ static int wait_for_idle(struct target *target, uint32_t *abstractcs)
 static int execute_abstract_command(struct target *target, uint32_t command)
 {
        RISCV013_INFO(info);
-       LOG_DEBUG("command=0x%x", command);
-       dmi_write(target, DMI_COMMAND, command);
+       if (debug_level >= LOG_LVL_DEBUG) {
+               switch (get_field(command, DMI_COMMAND_CMDTYPE)) {
+                       case 0:
+                               LOG_DEBUG("command=0x%x; access register, size=%d, postexec=%d, "
+                                               "transfer=%d, write=%d, regno=0x%x",
+                                               command,
+                                               8 << get_field(command, AC_ACCESS_REGISTER_SIZE),
+                                               get_field(command, AC_ACCESS_REGISTER_POSTEXEC),
+                                               get_field(command, AC_ACCESS_REGISTER_TRANSFER),
+                                               get_field(command, AC_ACCESS_REGISTER_WRITE),
+                                               get_field(command, AC_ACCESS_REGISTER_REGNO));
+                               break;
+                       default:
+                               LOG_DEBUG("command=0x%x", command);
+                               break;
+               }
+       }
+
+       dmi_write_exec(target, DMI_COMMAND, command);
 
        uint32_t abstractcs = 0;
        wait_for_idle(target, &abstractcs);
@@ -744,10 +789,10 @@ static int write_abstract_arg(struct target *target, unsigned index,
 }
 
 /**
- * @size in bits
+ * @par size in bits
  */
-static uint32_t access_register_command(uint32_t number, unsigned size,
-               uint32_t flags)
+static uint32_t access_register_command(struct target *target, uint32_t number,
+               unsigned size, uint32_t flags)
 {
        uint32_t command = set_field(0, DMI_COMMAND_CMDTYPE, 0);
        switch (size) {
@@ -770,8 +815,13 @@ static uint32_t access_register_command(uint32_t number, unsigned size,
        } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
                command = set_field(command, AC_ACCESS_REGISTER_REGNO,
                                number - GDB_REGNO_CSR0);
-       } else {
-               assert(0);
+       } else if (number >= GDB_REGNO_COUNT) {
+               /* Custom register. */
+               assert(target->reg_cache->reg_list[number].arch_info);
+               riscv_reg_info_t *reg_info = target->reg_cache->reg_list[number].arch_info;
+               assert(reg_info);
+               command = set_field(command, AC_ACCESS_REGISTER_REGNO,
+                               0xc000 + reg_info->custom_number);
        }
 
        command |= flags;
@@ -791,7 +841,7 @@ static int register_read_abstract(struct target *target, uint64_t *value,
                        !info->abstract_read_csr_supported)
                return ERROR_FAIL;
 
-       uint32_t command = access_register_command(number, size,
+       uint32_t command = access_register_command(target, number, size,
                        AC_ACCESS_REGISTER_TRANSFER);
 
        int result = execute_abstract_command(target, command);
@@ -826,7 +876,7 @@ static int register_write_abstract(struct target *target, uint32_t number,
                        !info->abstract_write_csr_supported)
                return ERROR_FAIL;
 
-       uint32_t command = access_register_command(number, size,
+       uint32_t command = access_register_command(target, number, size,
                        AC_ACCESS_REGISTER_TRANSFER |
                        AC_ACCESS_REGISTER_WRITE);
 
@@ -1087,7 +1137,7 @@ static int register_write_direct(struct target *target, unsigned number,
        RISCV013_INFO(info);
        RISCV_INFO(r);
 
-       LOG_DEBUG("[%d] reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
+       LOG_DEBUG("{%d} reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
                        number, value);
 
        int result = register_write_abstract(target, number, value,
@@ -1095,7 +1145,6 @@ static int register_write_direct(struct target *target, unsigned number,
        if (result == ERROR_OK && target->reg_cache) {
                struct reg *reg = &target->reg_cache->reg_list[number];
                buf_set_u64(reg->value, 0, reg->size, value);
-               reg->valid = true;
        }
        if (result == ERROR_OK || info->progbufsize + r->impebreak < 2 ||
                        !riscv_is_halted(target))
@@ -1154,7 +1203,6 @@ static int register_write_direct(struct target *target, unsigned number,
        if (exec_out == ERROR_OK && target->reg_cache) {
                struct reg *reg = &target->reg_cache->reg_list[number];
                buf_set_u64(reg->value, 0, reg->size, value);
-               reg->valid = true;
        }
 
        if (use_scratch)
@@ -1174,24 +1222,12 @@ static int register_read(struct target *target, uint64_t *value, uint32_t number
                *value = 0;
                return ERROR_OK;
        }
-       if (target->reg_cache &&
-                       (number <= GDB_REGNO_XPR31 ||
-                        (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31))) {
-               /* Only check the cache for registers that we know won't spontaneously
-                * change. */
-               struct reg *reg = &target->reg_cache->reg_list[number];
-               if (reg && reg->valid) {
-                       *value = buf_get_u64(reg->value, 0, reg->size);
-                       return ERROR_OK;
-               }
-       }
        int result = register_read_direct(target, value, number);
        if (result != ERROR_OK)
                return ERROR_FAIL;
        if (target->reg_cache) {
                struct reg *reg = &target->reg_cache->reg_list[number];
                buf_set_u64(reg->value, 0, reg->size, *value);
-               reg->valid = true;
        }
        return ERROR_OK;
 }
@@ -1284,7 +1320,7 @@ static int register_read_direct(struct target *target, uint64_t *value, uint32_t
        }
 
        if (result == ERROR_OK) {
-               LOG_DEBUG("[%d] reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
+               LOG_DEBUG("{%d} reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
                                number, *value);
        }
 
@@ -1321,6 +1357,7 @@ static void deinit_target(struct target *target)
        LOG_DEBUG("riscv_deinit_target()");
        riscv_info_t *info = (riscv_info_t *) target->arch_info;
        free(info->version_specific);
+       /* TODO: free register arch_info */
        info->version_specific = NULL;
 }
 
@@ -1347,17 +1384,7 @@ static int examine(struct target *target)
 
        riscv013_info_t *info = get_info(target);
        info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
-       info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
-
-       uint32_t dmstatus;
-       if (dmstatus_read(target, &dmstatus, false) != ERROR_OK)
-               return ERROR_FAIL;
-       LOG_DEBUG("dmstatus:  0x%08x", dmstatus);
-       if (get_field(dmstatus, DMI_DMSTATUS_VERSION) != 2) {
-               LOG_ERROR("OpenOCD only supports Debug Module version 2, not %d "
-                               "(dmstatus=0x%x)", get_field(dmstatus, DMI_DMSTATUS_VERSION), dmstatus);
-               return ERROR_FAIL;
-       }
+       info->dtmcs_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
 
        /* Reset the Debug Module. */
        dm013_info_t *dm = get_dm(target);
@@ -1379,6 +1406,16 @@ static int examine(struct target *target)
                return ERROR_FAIL;
        }
 
+       uint32_t dmstatus;
+       if (dmstatus_read(target, &dmstatus, false) != ERROR_OK)
+               return ERROR_FAIL;
+       LOG_DEBUG("dmstatus:  0x%08x", dmstatus);
+       if (get_field(dmstatus, DMI_DMSTATUS_VERSION) != 2) {
+               LOG_ERROR("OpenOCD only supports Debug Module version 2, not %d "
+                               "(dmstatus=0x%x)", get_field(dmstatus, DMI_DMSTATUS_VERSION), dmstatus);
+               return ERROR_FAIL;
+       }
+
        uint32_t hartsel =
                (get_field(dmcontrol, DMI_DMCONTROL_HARTSELHI) <<
                 DMI_DMCONTROL_HARTSELLO_LENGTH) |
@@ -1454,7 +1491,8 @@ static int examine(struct target *target)
                        dmi_write(target, DMI_DMCONTROL,
                                        set_hartsel(DMI_DMCONTROL_DMACTIVE | DMI_DMCONTROL_ACKHAVERESET, i));
 
-               if (!riscv_is_halted(target)) {
+               bool halted = riscv_is_halted(target);
+               if (!halted) {
                        if (riscv013_halt_current_hart(target) != ERROR_OK) {
                                LOG_ERROR("Fatal: Hart %d failed to halt during examine()", i);
                                return ERROR_FAIL;
@@ -1484,6 +1522,9 @@ static int examine(struct target *target)
                 * really slow simulators. */
                LOG_DEBUG(" hart %d: XLEN=%d, misa=0x%" PRIx64, i, r->xlen[i],
                                r->misa[i]);
+
+               if (!halted)
+                       riscv013_resume_current_hart(target);
        }
 
        LOG_DEBUG("Enumerated %d harts", r->hart_count);
@@ -1493,11 +1534,6 @@ static int examine(struct target *target)
                return ERROR_FAIL;
        }
 
-       /* Resumes all the harts, so the debugger can later pause them. */
-       /* TODO: Only do this if the harts were halted to start with. */
-       riscv_resume_all_harts(target);
-       target->state = TARGET_RUNNING;
-
        target_set_examined(target);
 
        /* Some regression suites rely on seeing 'Examined RISC-V core' to know
@@ -1579,6 +1615,8 @@ static int init_target(struct command_context *cmd_ctx,
        generic_info->authdata_write = &riscv013_authdata_write;
        generic_info->dmi_read = &dmi_read;
        generic_info->dmi_write = &dmi_write;
+       generic_info->test_sba_config_reg = &riscv013_test_sba_config_reg;
+       generic_info->test_compliance = &riscv013_test_compliance;
        generic_info->version_specific = calloc(1, sizeof(riscv013_info_t));
        if (!generic_info->version_specific)
                return ERROR_FAIL;
@@ -1722,7 +1760,7 @@ static int deassert_reset(struct target *target)
 }
 
 /**
- * @size in bytes
+ * @par size in bytes
  */
 static void write_to_buf(uint8_t *buffer, uint64_t value, unsigned size)
 {
@@ -1750,13 +1788,38 @@ static void write_to_buf(uint8_t *buffer, uint64_t value, unsigned size)
 
 static int execute_fence(struct target *target)
 {
-       struct riscv_program program;
-       riscv_program_init(&program, target);
-       riscv_program_fence(&program);
-       int result = riscv_program_exec(&program, target);
-       if (result != ERROR_OK)
-               LOG_ERROR("Unable to execute fence");
-       return result;
+       int old_hartid = riscv_current_hartid(target);
+
+       /* FIXME: For non-coherent systems we need to flush the caches right
+        * here, but there's no ISA-defined way of doing that. */
+       {
+               struct riscv_program program;
+               riscv_program_init(&program, target);
+               riscv_program_fence_i(&program);
+               riscv_program_fence(&program);
+               int result = riscv_program_exec(&program, target);
+               if (result != ERROR_OK)
+                       LOG_DEBUG("Unable to execute pre-fence");
+       }
+
+       for (int i = 0; i < riscv_count_harts(target); ++i) {
+               if (!riscv_hart_enabled(target, i))
+                       continue;
+
+               riscv_set_current_hartid(target, i);
+
+               struct riscv_program program;
+               riscv_program_init(&program, target);
+               riscv_program_fence_i(&program);
+               riscv_program_fence(&program);
+               int result = riscv_program_exec(&program, target);
+               if (result != ERROR_OK)
+                       LOG_DEBUG("Unable to execute fence on hart %d", i);
+       }
+
+       riscv_set_current_hartid(target, old_hartid);
+
+       return ERROR_OK;
 }
 
 static void log_memory_access(target_addr_t address, uint64_t value,
@@ -2015,89 +2078,76 @@ static int read_memory_bus_v1(struct target *target, target_addr_t address,
        return ERROR_OK;
 }
 
+static int batch_run(const struct target *target, struct riscv_batch *batch)
+{
+       RISCV013_INFO(info);
+       RISCV_INFO(r);
+       if (r->reset_delays_wait >= 0) {
+               r->reset_delays_wait -= batch->used_scans;
+               if (r->reset_delays_wait <= 0) {
+                       batch->idle_count = 0;
+                       info->dmi_busy_delay = 0;
+                       info->ac_busy_delay = 0;
+               }
+       }
+       return riscv_batch_run(batch);
+}
+
 /**
  * Read the requested memory, taking care to execute every read exactly once,
  * even if cmderr=busy is encountered.
  */
-static int read_memory_progbuf(struct target *target, target_addr_t address,
+static int read_memory_progbuf_inner(struct target *target, target_addr_t address,
                uint32_t size, uint32_t count, uint8_t *buffer)
 {
        RISCV013_INFO(info);
 
        int result = ERROR_OK;
 
-       LOG_DEBUG("reading %d words of %d bytes from 0x%" TARGET_PRIxADDR, count,
-                       size, address);
-
-       select_dmi(target);
-
-       /* s0 holds the next address to write to
-        * s1 holds the next data value to write
-        */
-       uint64_t s0, s1;
-       if (register_read(target, &s0, GDB_REGNO_S0) != ERROR_OK)
-               return ERROR_FAIL;
-       if (register_read(target, &s1, GDB_REGNO_S1) != ERROR_OK)
+       /* Write address to S0, and execute buffer. */
+       result = register_write_direct(target, GDB_REGNO_S0, address);
+       if (result != ERROR_OK)
+               goto error;
+       uint32_t command = access_register_command(target, GDB_REGNO_S1,
+                       riscv_xlen(target),
+                       AC_ACCESS_REGISTER_TRANSFER | AC_ACCESS_REGISTER_POSTEXEC);
+       if (execute_abstract_command(target, command) != ERROR_OK)
                return ERROR_FAIL;
 
-       if (execute_fence(target) != ERROR_OK)
-               return ERROR_FAIL;
+       /* First read has just triggered. Result is in s1. */
 
-       /* Write the program (load, increment) */
-       struct riscv_program program;
-       riscv_program_init(&program, target);
-       switch (size) {
-               case 1:
-                       riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
-                       break;
-               case 2:
-                       riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
-                       break;
-               case 4:
-                       riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
-                       break;
-               default:
-                       LOG_ERROR("Unsupported size: %d", size);
+       if (count == 1) {
+               uint64_t value;
+               if (register_read_direct(target, &value, GDB_REGNO_S1) != ERROR_OK)
                        return ERROR_FAIL;
+               write_to_buf(buffer, value, size);
+               log_memory_access(address, value, size, true);
+               return ERROR_OK;
        }
-       riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
 
-       if (riscv_program_ebreak(&program) != ERROR_OK)
-               return ERROR_FAIL;
-       riscv_program_write(&program);
-
-       /* Write address to S0, and execute buffer. */
-       result = register_write_direct(target, GDB_REGNO_S0, address);
-       if (result != ERROR_OK)
+       if (dmi_write(target, DMI_ABSTRACTAUTO,
+                       1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET) != ERROR_OK)
                goto error;
-       uint32_t command = access_register_command(GDB_REGNO_S1, riscv_xlen(target),
-                               AC_ACCESS_REGISTER_TRANSFER |
-                               AC_ACCESS_REGISTER_POSTEXEC);
-       result = execute_abstract_command(target, command);
-       if (result != ERROR_OK)
+       /* Read garbage from dmi_data0, which triggers another execution of the
+        * program. Now dmi_data0 contains the first good result, and s1 the next
+        * memory value. */
+       if (dmi_read_exec(target, NULL, DMI_DATA0) != ERROR_OK)
                goto error;
 
-       /* First read has just triggered. Result is in s1. */
-
-       dmi_write(target, DMI_ABSTRACTAUTO,
-                       1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);
-
        /* read_addr is the next address that the hart will read from, which is the
         * value in s0. */
-       riscv_addr_t read_addr = address + size;
-       /* The next address that we need to receive data for. */
-       riscv_addr_t receive_addr = address;
+       riscv_addr_t read_addr = address + 2 * size;
        riscv_addr_t fin_addr = address + (count * size);
-       unsigned skip = 1;
        while (read_addr < fin_addr) {
-               LOG_DEBUG("read_addr=0x%" PRIx64 ", receive_addr=0x%" PRIx64
-                               ", fin_addr=0x%" PRIx64, read_addr, receive_addr, fin_addr);
+               LOG_DEBUG("read_addr=0x%" PRIx64 ", fin_addr=0x%" PRIx64, read_addr,
+                               fin_addr);
                /* The pipeline looks like this:
                 * memory -> s1 -> dm_data0 -> debugger
-                * It advances every time the debugger reads dmdata0.
-                * So at any time the debugger has just read mem[s0 - 3*size],
-                * dm_data0 contains mem[s0 - 2*size]
-                * s1 contains mem[s0-size] */
+                * Right now:
+                * s0 contains read_addr
+                * s1 contains mem[read_addr-size]
+                * dm_data0 contains[read_addr-size*2]
+                */
 
                LOG_DEBUG("creating burst to read from 0x%" PRIx64
                                " up to 0x%" PRIx64, read_addr, fin_addr);
@@ -2114,10 +2164,11 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                                break;
                }
 
-               riscv_batch_run(batch);
+               batch_run(target, batch);
 
                /* Wait for the target to finish performing the last abstract command,
-                * and update our copy of cmderr. */
+                * and update our copy of cmderr. If we see that DMI is busy here,
+                * dmi_busy_delay will be incremented. */
                uint32_t abstractcs;
                if (dmi_read(target, &abstractcs, DMI_ABSTRACTCS) != ERROR_OK)
                        return ERROR_FAIL;
@@ -2126,9 +2177,8 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                                return ERROR_FAIL;
                info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);
 
-               unsigned cmderr = info->cmderr;
                riscv_addr_t next_read_addr;
-               uint32_t dmi_data0 = -1;
+               unsigned ignore_last = 0;
                switch (info->cmderr) {
                        case CMDERR_NONE:
                                LOG_DEBUG("successful (partial?) memory read");
@@ -2137,38 +2187,12 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                        case CMDERR_BUSY:
                                LOG_DEBUG("memory read resulted in busy response");
 
-                               /*
-                                * If you want to exercise this code path, apply the following patch to spike:
---- a/riscv/debug_module.cc
-+++ b/riscv/debug_module.cc
-@@ -1,3 +1,5 @@
-+#include <unistd.h>
-+
- #include <cassert>
-
- #include "debug_module.h"
-@@ -398,6 +400,15 @@ bool debug_module_t::perform_abstract_command()
-       // Since the next instruction is what we will use, just use nother NOP
-       // to get there.
-       write32(debug_abstract, 1, addi(ZERO, ZERO, 0));
-+
-+      if (abstractauto.autoexecdata &&
-+          program_buffer[0] == 0x83 &&
-+          program_buffer[1] == 0x24 &&
-+          program_buffer[2] == 0x04 &&
-+          program_buffer[3] == 0 &&
-+          rand() < RAND_MAX / 10) {
-+        usleep(1000000);
-+      }
-     } else {
-       write32(debug_abstract, 1, ebreak());
-     }
-                                */
                                increase_ac_busy_delay(target);
                                riscv013_clear_abstract_error(target);
 
                                dmi_write(target, DMI_ABSTRACTAUTO, 0);
 
+                               uint32_t dmi_data0;
                                /* This is definitely a good version of the value that we
                                 * attempted to read when we discovered that the target was
                                 * busy. */
@@ -2177,23 +2201,30 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                                        goto error;
                                }
 
-                               /* Clobbers DMI_DATA0. */
+                               /* See how far we got, clobbering dmi_data0. */
                                result = register_read_direct(target, &next_read_addr,
                                                GDB_REGNO_S0);
                                if (result != ERROR_OK) {
                                        riscv_batch_free(batch);
                                        goto error;
                                }
+                               write_to_buf(buffer + next_read_addr - 2 * size - address, dmi_data0, size);
+                               log_memory_access(next_read_addr - 2 * size, dmi_data0, size, true);
+
                                /* Restore the command, and execute it.
                                 * Now DMI_DATA0 contains the next value just as it would if no
                                 * error had occurred. */
-                               dmi_write(target, DMI_COMMAND, command);
+                               dmi_write_exec(target, DMI_COMMAND, command);
+                               next_read_addr += size;
 
                                dmi_write(target, DMI_ABSTRACTAUTO,
                                                1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);
+
+                               ignore_last = 1;
+
                                break;
                        default:
-                               LOG_ERROR("error when reading memory, abstractcs=0x%08lx", (long)abstractcs);
+                               LOG_DEBUG("error when reading memory, abstractcs=0x%08lx", (long)abstractcs);
                                riscv013_clear_abstract_error(target);
                                riscv_batch_free(batch);
                                result = ERROR_FAIL;
@@ -2201,34 +2232,43 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                }
 
                /* Now read whatever we got out of the batch. */
+               dmi_status_t status = DMI_STATUS_SUCCESS;
                for (size_t i = 0; i < reads; i++) {
-                       if (read_addr >= next_read_addr)
-                               break;
-
-                       read_addr += size;
-
-                       if (skip > 0) {
-                               skip--;
+                       riscv_addr_t receive_addr = read_addr + (i-2) * size;
+                       assert(receive_addr < address + size * count);
+                       if (receive_addr < address)
                                continue;
-                       }
+                       if (receive_addr > next_read_addr - (3 + ignore_last) * size)
+                               break;
 
-                       riscv_addr_t offset = receive_addr - address;
                        uint64_t dmi_out = riscv_batch_get_dmi_read(batch, i);
+                       status = get_field(dmi_out, DTM_DMI_OP);
+                       if (status != DMI_STATUS_SUCCESS) {
+                               /* If we're here because of busy count, dmi_busy_delay will
+                                * already have been increased and busy state will have been
+                                * cleared in dmi_read(). */
+                               /* In at least some implementations, we issue a read, and then
+                                * can get busy back when we try to scan out the read result,
+                                * and the actual read value is lost forever. Since this is
+                                * rare in any case, we return error here and rely on our
+                                * caller to reread the entire block. */
+                               LOG_WARNING("Batch memory read encountered DMI error %d. "
+                                               "Falling back on slower reads.", status);
+                               riscv_batch_free(batch);
+                               result = ERROR_FAIL;
+                               goto error;
+                       }
                        uint32_t value = get_field(dmi_out, DTM_DMI_DATA);
+                       riscv_addr_t offset = receive_addr - address;
                        write_to_buf(buffer + offset, value, size);
                        log_memory_access(receive_addr, value, size, true);
 
                        receive_addr += size;
                }
-               riscv_batch_free(batch);
 
-               if (cmderr == CMDERR_BUSY) {
-                       riscv_addr_t offset = receive_addr - address;
-                       write_to_buf(buffer + offset, dmi_data0, size);
-                       log_memory_access(receive_addr, dmi_data0, size, true);
-                       read_addr += size;
-                       receive_addr += size;
-               }
+               read_addr = next_read_addr;
+
+               riscv_batch_free(batch);
        }
 
        dmi_write(target, DMI_ABSTRACTAUTO, 0);
@@ -2237,10 +2277,9 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
                /* Read the penultimate word. */
                uint32_t value;
                if (dmi_read(target, &value, DMI_DATA0) != ERROR_OK)
-                       goto error;
-               write_to_buf(buffer + receive_addr - address, value, size);
-               log_memory_access(receive_addr, value, size, true);
-               receive_addr += size;
+                       return ERROR_FAIL;
+               write_to_buf(buffer + size * (count-2), value, size);
+               log_memory_access(address + size * (count-2), value, size, true);
        }
 
        /* Read the last word. */
@@ -2248,16 +2287,95 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
        result = register_read_direct(target, &value, GDB_REGNO_S1);
        if (result != ERROR_OK)
                goto error;
-       write_to_buf(buffer + receive_addr - address, value, size);
-       log_memory_access(receive_addr, value, size, true);
+       write_to_buf(buffer + size * (count-1), value, size);
+       log_memory_access(address + size * (count-1), value, size, true);
 
-       riscv_set_register(target, GDB_REGNO_S0, s0);
-       riscv_set_register(target, GDB_REGNO_S1, s1);
        return ERROR_OK;
 
 error:
        dmi_write(target, DMI_ABSTRACTAUTO, 0);
 
+       return result;
+}
+
+/**
+ * Read the requested memory, silently handling memory access errors.
+ */
+static int read_memory_progbuf(struct target *target, target_addr_t address,
+               uint32_t size, uint32_t count, uint8_t *buffer)
+{
+       int result = ERROR_OK;
+
+       LOG_DEBUG("reading %d words of %d bytes from 0x%" TARGET_PRIxADDR, count,
+                       size, address);
+
+       select_dmi(target);
+
+       memset(buffer, 0, count*size);
+
+       /* s0 holds the next address to write to
+        * s1 holds the next data value to write
+        */
+       uint64_t s0, s1;
+       if (register_read(target, &s0, GDB_REGNO_S0) != ERROR_OK)
+               return ERROR_FAIL;
+       if (register_read(target, &s1, GDB_REGNO_S1) != ERROR_OK)
+               return ERROR_FAIL;
+
+       if (execute_fence(target) != ERROR_OK)
+               return ERROR_FAIL;
+
+       /* Write the program (load, increment) */
+       struct riscv_program program;
+       riscv_program_init(&program, target);
+       switch (size) {
+               case 1:
+                       riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
+                       break;
+               case 2:
+                       riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
+                       break;
+               case 4:
+                       riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
+                       break;
+               default:
+                       LOG_ERROR("Unsupported size: %d", size);
+                       return ERROR_FAIL;
+       }
+       riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
+
+       if (riscv_program_ebreak(&program) != ERROR_OK)
+               return ERROR_FAIL;
+       riscv_program_write(&program);
+
+       result = read_memory_progbuf_inner(target, address, size, count, buffer);
+
+       if (result != ERROR_OK) {
+               /* The full read did not succeed, so we will try to read each word individually. */
+               /* This will not be fast, but reading outside actual memory is a special case anyway. */
+               /* It will make the toolchain happier, especially Eclipse Memory View as it reads ahead. */
+               target_addr_t address_i = address;
+               uint32_t size_i = size;
+               uint32_t count_i = 1;
+               uint8_t *buffer_i = buffer;
+
+               for (uint32_t i = 0; i < count; i++, address_i += size_i, buffer_i += size_i) {
+                       /* TODO: This is much slower than it needs to be because we end up
+                        * writing the address to read for every word we read. */
+                       result = read_memory_progbuf_inner(target, address_i, size_i, count_i, buffer_i);
+
+                       /* The read of a single word failed, so we will just return 0 for that instead */
+                       if (result != ERROR_OK) {
+                               LOG_DEBUG("error reading single word of %d bytes from 0x%" TARGET_PRIxADDR,
+                                               size_i, address_i);
+
+                               uint64_t value_i = 0;
+                               write_to_buf(buffer_i, value_i, size_i);
+                       }
+               }
+               result = ERROR_OK;
+       }
+
        riscv_set_register(target, GDB_REGNO_S0, s0);
        riscv_set_register(target, GDB_REGNO_S1, s1);
        return result;
@@ -2558,7 +2676,8 @@ static int write_memory_progbuf(struct target *target, target_addr_t address,
 
                                /* Write and execute command that moves value into S1 and
                                 * executes program buffer. */
-                               uint32_t command = access_register_command(GDB_REGNO_S1, 32,
+                               uint32_t command = access_register_command(target,
+                                               GDB_REGNO_S1, 32,
                                                AC_ACCESS_REGISTER_POSTEXEC |
                                                AC_ACCESS_REGISTER_TRANSFER |
                                                AC_ACCESS_REGISTER_WRITE);
@@ -2580,7 +2699,7 @@ static int write_memory_progbuf(struct target *target, target_addr_t address,
                        }
                }
 
-               result = riscv_batch_run(batch);
+               result = batch_run(target, batch);
                riscv_batch_free(batch);
                if (result != ERROR_OK)
                        goto error;
@@ -2590,33 +2709,34 @@ static int write_memory_progbuf(struct target *target, target_addr_t address,
                 * to be incremented if necessary. */
 
                uint32_t abstractcs;
-               if (dmi_read(target, &abstractcs, DMI_ABSTRACTCS) != ERROR_OK)
+               bool dmi_busy_encountered;
+               if (dmi_op(target, &abstractcs, &dmi_busy_encountered, DMI_OP_READ,
+                                       DMI_ABSTRACTCS, 0, false) != ERROR_OK)
                        goto error;
                while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
                        if (dmi_read(target, &abstractcs, DMI_ABSTRACTCS) != ERROR_OK)
                                return ERROR_FAIL;
                info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);
-               switch (info->cmderr) {
-                       case CMDERR_NONE:
-                               LOG_DEBUG("successful (partial?) memory write");
-                               break;
-                       case CMDERR_BUSY:
-                               LOG_DEBUG("memory write resulted in busy response");
-                               riscv013_clear_abstract_error(target);
-                               increase_ac_busy_delay(target);
-
-                               dmi_write(target, DMI_ABSTRACTAUTO, 0);
-                               result = register_read_direct(target, &cur_addr, GDB_REGNO_S0);
-                               if (result != ERROR_OK)
-                                       goto error;
-                               setup_needed = true;
-                               break;
-
-                       default:
-                               LOG_ERROR("error when writing memory, abstractcs=0x%08lx", (long)abstractcs);
-                               riscv013_clear_abstract_error(target);
-                               result = ERROR_FAIL;
+               if (info->cmderr == CMDERR_NONE && !dmi_busy_encountered) {
+                       LOG_DEBUG("successful (partial?) memory write");
+               } else if (info->cmderr == CMDERR_BUSY || dmi_busy_encountered) {
+                       if (info->cmderr == CMDERR_BUSY)
+                               LOG_DEBUG("Memory write resulted in abstract command busy response.");
+                       else if (dmi_busy_encountered)
+                               LOG_DEBUG("Memory write resulted in DMI busy response.");
+                       riscv013_clear_abstract_error(target);
+                       increase_ac_busy_delay(target);
+
+                       dmi_write(target, DMI_ABSTRACTAUTO, 0);
+                       result = register_read_direct(target, &cur_addr, GDB_REGNO_S0);
+                       if (result != ERROR_OK)
                                goto error;
+                       setup_needed = true;
+               } else {
+                       LOG_ERROR("error when writing memory, abstractcs=0x%08lx", (long)abstractcs);
+                       riscv013_clear_abstract_error(target);
+                       result = ERROR_FAIL;
+                       goto error;
                }
        }
 
@@ -2696,7 +2816,7 @@ static int riscv013_get_register(struct target *target,
        int result = ERROR_OK;
        if (rid == GDB_REGNO_PC) {
                result = register_read(target, value, GDB_REGNO_DPC);
-               LOG_DEBUG("read PC from DPC: 0x%016" PRIx64, *value);
+               LOG_DEBUG("read PC from DPC: 0x%" PRIx64, *value);
        } else if (rid == GDB_REGNO_PRIV) {
                uint64_t dcsr;
                result = register_read(target, &dcsr, GDB_REGNO_DCSR);
@@ -2720,7 +2840,7 @@ static int riscv013_set_register(struct target *target, int hid, int rid, uint64
        if (rid <= GDB_REGNO_XPR31) {
                return register_write_direct(target, rid, value);
        } else if (rid == GDB_REGNO_PC) {
-               LOG_DEBUG("writing PC to DPC: 0x%016" PRIx64, value);
+               LOG_DEBUG("writing PC to DPC: 0x%" PRIx64, value);
                register_write_direct(target, GDB_REGNO_DPC, value);
                uint64_t actual_value;
                register_read_direct(target, &actual_value, GDB_REGNO_DPC);
@@ -2864,6 +2984,7 @@ static enum riscv_halt_reason riscv013_halt_reason(struct target *target)
                 * already set when we connected. Force enumeration now, which has the
                 * side effect of clearing any triggers we did not set. */
                riscv_enumerate_triggers(target);
+               LOG_DEBUG("{%d} halted because of trigger", target->coreid);
                return RISCV_HALT_TRIGGER;
        case CSR_DCSR_CAUSE_STEP:
                return RISCV_HALT_SINGLESTEP;
@@ -2924,6 +3045,357 @@ void riscv013_fill_dmi_nop_u64(struct target *target, char *buf)
        buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, 0);
 }
 
+/* Helper function for riscv013_test_sba_config_reg */
+static int get_max_sbaccess(struct target *target)
+{
+       RISCV013_INFO(info);
+
+       uint32_t sbaccess128 = get_field(info->sbcs, DMI_SBCS_SBACCESS128);
+       uint32_t sbaccess64 = get_field(info->sbcs, DMI_SBCS_SBACCESS64);
+       uint32_t sbaccess32 = get_field(info->sbcs, DMI_SBCS_SBACCESS32);
+       uint32_t sbaccess16 = get_field(info->sbcs, DMI_SBCS_SBACCESS16);
+       uint32_t sbaccess8 = get_field(info->sbcs, DMI_SBCS_SBACCESS8);
+
+       if (sbaccess128)
+               return 4;
+       else if (sbaccess64)
+               return 3;
+       else if (sbaccess32)
+               return 2;
+       else if (sbaccess16)
+               return 1;
+       else if (sbaccess8)
+               return 0;
+       else
+               return -1;
+}
+
+static uint32_t get_num_sbdata_regs(struct target *target)
+{
+       RISCV013_INFO(info);
+
+       uint32_t sbaccess128 = get_field(info->sbcs, DMI_SBCS_SBACCESS128);
+       uint32_t sbaccess64 = get_field(info->sbcs, DMI_SBCS_SBACCESS64);
+       uint32_t sbaccess32 = get_field(info->sbcs, DMI_SBCS_SBACCESS32);
+
+       if (sbaccess128)
+               return 4;
+       else if (sbaccess64)
+               return 2;
+       else if (sbaccess32)
+               return 1;
+       else
+               return 0;
+}
+
+static int riscv013_test_sba_config_reg(struct target *target,
+               target_addr_t legal_address, uint32_t num_words,
+               target_addr_t illegal_address, bool run_sbbusyerror_test)
+{
+       LOG_INFO("Testing System Bus Access as defined by RISC-V Debug Spec v0.13");
+
+       uint32_t tests_failed = 0;
+
+       uint32_t rd_val;
+       uint32_t sbcs_orig;
+       dmi_read(target, &sbcs_orig, DMI_SBCS);
+
+       uint32_t sbcs = sbcs_orig;
+       bool test_passed;
+
+       int max_sbaccess = get_max_sbaccess(target);
+
+       if (max_sbaccess == -1) {
+               LOG_ERROR("System Bus Access not supported in this config.");
+               return ERROR_FAIL;
+       }
+
+       if (get_field(sbcs, DMI_SBCS_SBVERSION) != 1) {
+               LOG_ERROR("System Bus Access unsupported SBVERSION (%d). Only version 1 is supported.",
+                               get_field(sbcs, DMI_SBCS_SBVERSION));
+               return ERROR_FAIL;
+       }
+
+       uint32_t num_sbdata_regs = get_num_sbdata_regs(target);
+
+       uint32_t rd_buf[num_sbdata_regs];
+
+       /* Test 1: Simple write/read test */
+       test_passed = true;
+       sbcs = set_field(sbcs_orig, DMI_SBCS_SBAUTOINCREMENT, 0);
+       dmi_write(target, DMI_SBCS, sbcs);
+
+       uint32_t test_patterns[4] = {0xdeadbeef, 0xfeedbabe, 0x12345678, 0x08675309};
+       for (uint32_t sbaccess = 0; sbaccess <= (uint32_t)max_sbaccess; sbaccess++) {
+               sbcs = set_field(sbcs, DMI_SBCS_SBACCESS, sbaccess);
+               dmi_write(target, DMI_SBCS, sbcs);
+
+               uint32_t compare_mask = (sbaccess == 0) ? 0xff : (sbaccess == 1) ? 0xffff : 0xffffffff;
+
+               for (uint32_t i = 0; i < num_words; i++) {
+                       uint32_t addr = legal_address + (i << sbaccess);
+                       uint32_t wr_data[num_sbdata_regs];
+                       for (uint32_t j = 0; j < num_sbdata_regs; j++)
+                               wr_data[j] = test_patterns[j] + i;
+                       write_memory_sba_simple(target, addr, wr_data, num_sbdata_regs, sbcs);
+               }
+
+               for (uint32_t i = 0; i < num_words; i++) {
+                       uint32_t addr = legal_address + (i << sbaccess);
+                       read_memory_sba_simple(target, addr, rd_buf, num_sbdata_regs, sbcs);
+                       for (uint32_t j = 0; j < num_sbdata_regs; j++) {
+                               if (((test_patterns[j]+i)&compare_mask) != (rd_buf[j]&compare_mask)) {
+                                       LOG_ERROR("System Bus Access Test 1: Error reading non-autoincremented address %x,"
+                                                       "expected val = %x, read val = %x", addr, test_patterns[j]+i, rd_buf[j]);
+                                       test_passed = false;
+                                       tests_failed++;
+                               }
+                       }
+               }
+       }
+       if (test_passed)
+               LOG_INFO("System Bus Access Test 1: Simple write/read test PASSED.");
+
+       /* Test 2: Address autoincrement test */
+       target_addr_t curr_addr;
+       target_addr_t prev_addr;
+       test_passed = true;
+       sbcs = set_field(sbcs_orig, DMI_SBCS_SBAUTOINCREMENT, 1);
+       dmi_write(target, DMI_SBCS, sbcs);
+
+       for (uint32_t sbaccess = 0; sbaccess <= (uint32_t)max_sbaccess; sbaccess++) {
+               sbcs = set_field(sbcs, DMI_SBCS_SBACCESS, sbaccess);
+               dmi_write(target, DMI_SBCS, sbcs);
+
+               dmi_write(target, DMI_SBADDRESS0, legal_address);
+               read_sbcs_nonbusy(target, &sbcs);
+               curr_addr = legal_address;
+               for (uint32_t i = 0; i < num_words; i++) {
+                       prev_addr = curr_addr;
+                       read_sbcs_nonbusy(target, &sbcs);
+                       curr_addr = sb_read_address(target);
+                       if ((curr_addr - prev_addr != (uint32_t)(1 << sbaccess)) && (i != 0)) {
+                               LOG_ERROR("System Bus Access Test 2: Error with address auto-increment, sbaccess = %x.", sbaccess);
+                               test_passed = false;
+                               tests_failed++;
+                       }
+                       dmi_write(target, DMI_SBDATA0, i);
+               }
+
+               read_sbcs_nonbusy(target, &sbcs);
+
+               dmi_write(target, DMI_SBADDRESS0, legal_address);
+
+               uint32_t val;
+               sbcs = set_field(sbcs, DMI_SBCS_SBREADONDATA, 1);
+               dmi_write(target, DMI_SBCS, sbcs);
+               dmi_read(target, &val, DMI_SBDATA0); /* Dummy read to trigger first system bus read */
+               curr_addr = legal_address;
+               for (uint32_t i = 0; i < num_words; i++) {
+                       prev_addr = curr_addr;
+                       read_sbcs_nonbusy(target, &sbcs);
+                       curr_addr = sb_read_address(target);
+                       if ((curr_addr - prev_addr != (uint32_t)(1 << sbaccess)) && (i != 0)) {
+                               LOG_ERROR("System Bus Access Test 2: Error with address auto-increment, sbaccess = %x", sbaccess);
+                               test_passed = false;
+                               tests_failed++;
+                       }
+                       dmi_read(target, &val, DMI_SBDATA0);
+                       read_sbcs_nonbusy(target, &sbcs);
+                       if (i != val) {
+                               LOG_ERROR("System Bus Access Test 2: Error reading auto-incremented address,"
+                                               "expected val = %x, read val = %x.", i, val);
+                               test_passed = false;
+                               tests_failed++;
+                       }
+               }
+       }
+       if (test_passed)
+               LOG_INFO("System Bus Access Test 2: Address auto-increment test PASSED.");
+
+       /* Test 3: Read from illegal address */
+       read_memory_sba_simple(target, illegal_address, rd_buf, 1, sbcs_orig);
+
+       dmi_read(target, &rd_val, DMI_SBCS);
+       if (get_field(rd_val, DMI_SBCS_SBERROR) == 2) {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 2);
+               dmi_write(target, DMI_SBCS, sbcs);
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBERROR) == 0)
+                       LOG_INFO("System Bus Access Test 3: Illegal address read test PASSED.");
+               else
+                       LOG_ERROR("System Bus Access Test 3: Illegal address read test FAILED, unable to clear to 0.");
+       } else {
+               LOG_ERROR("System Bus Access Test 3: Illegal address read test FAILED, unable to set error code.");
+       }
+
+       /* Test 4: Write to illegal address */
+       write_memory_sba_simple(target, illegal_address, test_patterns, 1, sbcs_orig);
+
+       dmi_read(target, &rd_val, DMI_SBCS);
+       if (get_field(rd_val, DMI_SBCS_SBERROR) == 2) {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 2);
+               dmi_write(target, DMI_SBCS, sbcs);
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBERROR) == 0)
+                       LOG_INFO("System Bus Access Test 4: Illegal address write test PASSED.");
+               else {
+                       LOG_ERROR("System Bus Access Test 4: Illegal address write test FAILED, unable to clear to 0.");
+                       tests_failed++;
+               }
+       } else {
+               LOG_ERROR("System Bus Access Test 4: Illegal address write test FAILED, unable to set error code.");
+               tests_failed++;
+       }
+
+       /* Test 5: Write with unsupported sbaccess size */
+       uint32_t sbaccess128 = get_field(sbcs_orig, DMI_SBCS_SBACCESS128);
+
+       if (sbaccess128) {
+               LOG_INFO("System Bus Access Test 5: SBCS sbaccess error test PASSED, all sbaccess sizes supported.");
+       } else {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBACCESS, 4);
+
+               write_memory_sba_simple(target, legal_address, test_patterns, 1, sbcs);
+
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBERROR) == 4) {
+                       sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 4);
+                       dmi_write(target, DMI_SBCS, sbcs);
+                       dmi_read(target, &rd_val, DMI_SBCS);
+                       if (get_field(rd_val, DMI_SBCS_SBERROR) == 0)
+                               LOG_INFO("System Bus Access Test 5: SBCS sbaccess error test PASSED.");
+                       else {
+                               LOG_ERROR("System Bus Access Test 5: SBCS sbaccess error test FAILED, unable to clear to 0.");
+                               tests_failed++;
+                       }
+               } else {
+                       LOG_ERROR("System Bus Access Test 5: SBCS sbaccess error test FAILED, unable to set error code.");
+                       tests_failed++;
+               }
+       }
+
+       /* Test 6: Write to misaligned address */
+       sbcs = set_field(sbcs_orig, DMI_SBCS_SBACCESS, 1);
+
+       write_memory_sba_simple(target, legal_address+1, test_patterns, 1, sbcs);
+
+       dmi_read(target, &rd_val, DMI_SBCS);
+       if (get_field(rd_val, DMI_SBCS_SBERROR) == 3) {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 3);
+               dmi_write(target, DMI_SBCS, sbcs);
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBERROR) == 0)
+                       LOG_INFO("System Bus Access Test 6: SBCS address alignment error test PASSED");
+               else {
+                       LOG_ERROR("System Bus Access Test 6: SBCS address alignment error test FAILED, unable to clear to 0.");
+                       tests_failed++;
+               }
+       } else {
+               LOG_ERROR("System Bus Access Test 6: SBCS address alignment error test FAILED, unable to set error code.");
+               tests_failed++;
+       }
+
+       /* Test 7: Set sbbusyerror, only run this case in simulation as it is likely
+        * impossible to hit otherwise */
+       if (run_sbbusyerror_test) {
+               sbcs = set_field(sbcs_orig, DMI_SBCS_SBREADONADDR, 1);
+               dmi_write(target, DMI_SBCS, sbcs);
+
+               for (int i = 0; i < 16; i++)
+                       dmi_write(target, DMI_SBDATA0, 0xdeadbeef);
+
+               for (int i = 0; i < 16; i++)
+                       dmi_write(target, DMI_SBADDRESS0, legal_address);
+
+               dmi_read(target, &rd_val, DMI_SBCS);
+               if (get_field(rd_val, DMI_SBCS_SBBUSYERROR)) {
+                       sbcs = set_field(sbcs_orig, DMI_SBCS_SBBUSYERROR, 1);
+                       dmi_write(target, DMI_SBCS, sbcs);
+                       dmi_read(target, &rd_val, DMI_SBCS);
+                       if (get_field(rd_val, DMI_SBCS_SBBUSYERROR) == 0)
+                               LOG_INFO("System Bus Access Test 7: SBCS sbbusyerror test PASSED.");
+                       else {
+                               LOG_ERROR("System Bus Access Test 7: SBCS sbbusyerror test FAILED, unable to clear to 0.");
+                               tests_failed++;
+                       }
+               } else {
+                       LOG_ERROR("System Bus Access Test 7: SBCS sbbusyerror test FAILED, unable to set error code.");
+                       tests_failed++;
+               }
+       }
+
+       if (tests_failed == 0) {
+               LOG_INFO("ALL TESTS PASSED");
+               return ERROR_OK;
+       } else {
+               LOG_ERROR("%d TESTS FAILED", tests_failed);
+               return ERROR_FAIL;
+       }
+
+}
+
+void write_memory_sba_simple(struct target *target, target_addr_t addr,
+               uint32_t *write_data, uint32_t write_size, uint32_t sbcs)
+{
+       RISCV013_INFO(info);
+
+       uint32_t rd_sbcs;
+       uint32_t masked_addr;
+
+       uint32_t sba_size = get_field(info->sbcs, DMI_SBCS_SBASIZE);
+
+       read_sbcs_nonbusy(target, &rd_sbcs);
+
+       uint32_t sbcs_no_readonaddr = set_field(sbcs, DMI_SBCS_SBREADONADDR, 0);
+       dmi_write(target, DMI_SBCS, sbcs_no_readonaddr);
+
+       for (uint32_t i = 0; i < sba_size/32; i++) {
+               masked_addr = (addr >> 32*i) & 0xffffffff;
+
+               if (i != 3)
+                       dmi_write(target, DMI_SBADDRESS0+i, masked_addr);
+               else
+                       dmi_write(target, DMI_SBADDRESS3, masked_addr);
+       }
+
+       /* Write SBDATA registers starting with highest address, since write to
+        * SBDATA0 triggers write */
+       for (int i = write_size-1; i >= 0; i--)
+               dmi_write(target, DMI_SBDATA0+i, write_data[i]);
+}
+
+void read_memory_sba_simple(struct target *target, target_addr_t addr,
+               uint32_t *rd_buf, uint32_t read_size, uint32_t sbcs)
+{
+       RISCV013_INFO(info);
+
+       uint32_t rd_sbcs;
+       uint32_t masked_addr;
+
+       uint32_t sba_size = get_field(info->sbcs, DMI_SBCS_SBASIZE);
+
+       read_sbcs_nonbusy(target, &rd_sbcs);
+
+       uint32_t sbcs_readonaddr = set_field(sbcs, DMI_SBCS_SBREADONADDR, 1);
+       dmi_write(target, DMI_SBCS, sbcs_readonaddr);
+
+       /* Write addresses starting with highest address register */
+       for (int i = sba_size/32-1; i >= 0; i--) {
+               masked_addr = (addr >> 32*i) & 0xffffffff;
+
+               if (i != 3)
+                       dmi_write(target, DMI_SBADDRESS0+i, masked_addr);
+               else
+                       dmi_write(target, DMI_SBADDRESS3, masked_addr);
+       }
+
+       read_sbcs_nonbusy(target, &rd_sbcs);
+
+       for (uint32_t i = 0; i < read_size; i++)
+               dmi_read(target, &(rd_buf[i]), DMI_SBDATA0+i);
+}
+
 int riscv013_dmi_write_u64_bits(struct target *target)
 {
        RISCV013_INFO(info);
@@ -2934,16 +3406,8 @@ static int maybe_execute_fence_i(struct target *target)
 {
        RISCV013_INFO(info);
        RISCV_INFO(r);
-       if (info->progbufsize + r->impebreak >= 2) {
-               struct riscv_program program;
-               riscv_program_init(&program, target);
-               if (riscv_program_fence_i(&program) != ERROR_OK)
-                       return ERROR_FAIL;
-               if (riscv_program_exec(&program, target) != ERROR_OK) {
-                       LOG_ERROR("Failed to execute fence.i");
-                       return ERROR_FAIL;
-               }
-       }
+       if (info->progbufsize + r->impebreak >= 3)
+               return execute_fence(target);
        return ERROR_OK;
 }
 
@@ -3034,3 +3498,459 @@ void riscv013_clear_abstract_error(struct target *target)
        /* Clear the error status. */
        dmi_write(target, DMI_ABSTRACTCS, abstractcs & DMI_ABSTRACTCS_CMDERR);
 }
+
+#define COMPLIANCE_TEST(b, message) \
+{                                   \
+       int pass = 0;               \
+       if (b) {                    \
+               pass = 1;           \
+               passed_tests++;     \
+       }                           \
+       LOG_INFO("%s test %d (%s)\n", (pass) ? "PASSED" : "FAILED",  total_tests, message); \
+       assert(pass);               \
+       total_tests++;              \
+}
+
+#define COMPLIANCE_MUST_PASS(b) COMPLIANCE_TEST(ERROR_OK == (b), "Regular calls must return ERROR_OK")
+
+#define COMPLIANCE_READ(target, addr, value) COMPLIANCE_MUST_PASS(dmi_read(target, addr, value))
+#define COMPLIANCE_WRITE(target, addr, value) COMPLIANCE_MUST_PASS(dmi_write(target, addr, value))
+
+#define COMPLIANCE_CHECK_RO(target, addr)                               \
+{                                                                       \
+       uint32_t orig;                                                      \
+       uint32_t inverse;                                                   \
+       COMPLIANCE_READ(target, &orig, addr);                               \
+       COMPLIANCE_WRITE(target, addr, ~orig);                              \
+       COMPLIANCE_READ(target, &inverse, addr);                            \
+       COMPLIANCE_TEST(orig == inverse, "Register must be read-only");     \
+}
+
+int riscv013_test_compliance(struct target *target)
+{
+       LOG_INFO("Testing Compliance against RISC-V Debug Spec v0.13");
+
+       if (!riscv_rtos_enabled(target)) {
+               LOG_ERROR("Please run with -rtos riscv to run compliance test.");
+               return ERROR_FAIL;
+       }
+
+       int total_tests = 0;
+       int passed_tests = 0;
+
+       uint32_t dmcontrol_orig = DMI_DMCONTROL_DMACTIVE;
+       uint32_t dmcontrol;
+       uint32_t testvar;
+       uint32_t testvar_read;
+       riscv_reg_t value;
+       RISCV013_INFO(info);
+
+       /* All the bits of HARTSEL are covered by the examine sequence. */
+
+       /* hartreset */
+       /* This field is optional. Either we can read and write it to 1/0,
+       or it is tied to 0. This check doesn't really do anything, but
+       it does attempt to set the bit to 1 and then back to 0, which needs to
+       work if its implemented. */
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HARTRESET, 1));
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HARTRESET, 0));
+       COMPLIANCE_READ(target, &dmcontrol, DMI_DMCONTROL);
+       COMPLIANCE_TEST((get_field(dmcontrol, DMI_DMCONTROL_HARTRESET) == 0),
+                       "DMCONTROL.hartreset can be 0 or RW.");
+
+       /* hasel */
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HASEL, 1));
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HASEL, 0));
+       COMPLIANCE_READ(target, &dmcontrol, DMI_DMCONTROL);
+       COMPLIANCE_TEST((get_field(dmcontrol, DMI_DMCONTROL_HASEL) == 0),
+                       "DMCONTROL.hasel can be 0 or RW.");
+       /* TODO: test that hamask registers exist if hasel does. */
+
+       /* haltreq */
+       COMPLIANCE_MUST_PASS(riscv_halt_all_harts(target));
+       /* This bit is not actually readable according to the spec, so nothing to check.*/
+
+       /* DMSTATUS */
+       COMPLIANCE_CHECK_RO(target, DMI_DMSTATUS);
+
+       /* resumereq */
+       /* This bit is not actually readable according to the spec, so nothing to check.*/
+       COMPLIANCE_MUST_PASS(riscv_resume_all_harts(target));
+
+       /* Halt all harts again so the test can continue.*/
+       COMPLIANCE_MUST_PASS(riscv_halt_all_harts(target));
+
+       /* HARTINFO: Read-Only. This is per-hart, so need to adjust hartsel. */
+       uint32_t hartinfo;
+       COMPLIANCE_READ(target, &hartinfo, DMI_HARTINFO);
+       for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) {
+               COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel));
+
+               COMPLIANCE_CHECK_RO(target, DMI_HARTINFO);
+
+               /* $dscratch CSRs */
+               uint32_t nscratch = get_field(hartinfo, DMI_HARTINFO_NSCRATCH);
+               for (unsigned int d = 0; d < nscratch; d++) {
+                       riscv_reg_t testval, testval_read;
+                       /* Because DSCRATCH is not guaranteed to last across PB executions, need to put
+                       this all into one PB execution. Which may not be possible on all implementations.*/
+                       if (info->progbufsize >= 5) {
+                               for (testval = 0x0011223300112233;
+                                                testval != 0xDEAD;
+                                                testval = testval == 0x0011223300112233 ? ~testval : 0xDEAD) {
+                                       COMPLIANCE_TEST(register_write_direct(target, GDB_REGNO_S0, testval) == ERROR_OK,
+                                                       "Need to be able to write S0 in order to test DSCRATCH.");
+                                       struct riscv_program program32;
+                                       riscv_program_init(&program32, target);
+                                       riscv_program_csrw(&program32, GDB_REGNO_S0, GDB_REGNO_DSCRATCH + d);
+                                       riscv_program_csrr(&program32, GDB_REGNO_S1, GDB_REGNO_DSCRATCH + d);
+                                       riscv_program_fence(&program32);
+                                       riscv_program_ebreak(&program32);
+                                       COMPLIANCE_TEST(riscv_program_exec(&program32, target) == ERROR_OK,
+                                                       "Accessing DSCRATCH with program buffer should succeed.");
+                                       COMPLIANCE_TEST(register_read_direct(target, &testval_read, GDB_REGNO_S1) == ERROR_OK,
+                                                       "Need to be able to read S1 in order to test DSCRATCH.");
+                                       if (riscv_xlen(target) > 32) {
+                                               COMPLIANCE_TEST(testval == testval_read,
+                                                               "All DSCRATCH registers in HARTINFO must be R/W.");
+                                       } else {
+                                               COMPLIANCE_TEST(testval_read == (testval & 0xFFFFFFFF),
+                                                               "All DSCRATCH registers in HARTINFO must be R/W.");
+                                       }
+                               }
+                       }
+               }
+               /* TODO: dataaccess */
+               if (get_field(hartinfo, DMI_HARTINFO_DATAACCESS)) {
+                       /* TODO: Shadowed in memory map. */
+                       /* TODO: datasize */
+                       /* TODO: dataaddr */
+               } else {
+                       /* TODO: Shadowed in CSRs. */
+                       /* TODO: datasize */
+                       /* TODO: dataaddr */
+               }
+
+       }
+
+       /* HALTSUM -- TODO: More than 32 harts. Would need to loop over this to set hartsel */
+       /* TODO: HALTSUM2, HALTSUM3 */
+       /* HALTSUM0 */
+       uint32_t expected_haltsum0 = 0;
+       for (int i = 0; i < MIN(riscv_count_harts(target), 32); i++)
+               expected_haltsum0 |= (1 << i);
+
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM0);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum0,
+                       "HALTSUM0 should report summary of up to 32 halted harts");
+
+       COMPLIANCE_WRITE(target, DMI_HALTSUM0, 0xffffffff);
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM0);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum0, "HALTSUM0 should be R/O");
+
+       COMPLIANCE_WRITE(target, DMI_HALTSUM0, 0x0);
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM0);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum0, "HALTSUM0 should be R/O");
+
+       /* HALTSUM1 */
+       uint32_t expected_haltsum1 = 0;
+       for (int i = 0; i < MIN(riscv_count_harts(target), 1024); i += 32)
+               expected_haltsum1 |= (1 << (i/32));
+
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM1);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum1,
+                       "HALTSUM1 should report summary of up to 1024 halted harts");
+
+       COMPLIANCE_WRITE(target, DMI_HALTSUM1, 0xffffffff);
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM1);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum1, "HALTSUM1 should be R/O");
+
+       COMPLIANCE_WRITE(target, DMI_HALTSUM1, 0x0);
+       COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM1);
+       COMPLIANCE_TEST(testvar_read == expected_haltsum1, "HALTSUM1 should be R/O");
+
+       /* TODO: HAWINDOWSEL */
+
+       /* TODO: HAWINDOW */
+
+       /* ABSTRACTCS */
+
+       uint32_t abstractcs;
+       COMPLIANCE_READ(target, &abstractcs, DMI_ABSTRACTCS);
+
+       /* Check that all reported Data Words are really R/W */
+       for (int invert = 0; invert < 2; invert++) {
+               for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+                       testvar = (i + 1) * 0x11111111;
+                       if (invert)
+                               testvar = ~testvar;
+                       COMPLIANCE_WRITE(target, DMI_DATA0 + i, testvar);
+               }
+               for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+                       testvar = (i + 1) * 0x11111111;
+                       if (invert)
+                               testvar = ~testvar;
+                       COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i);
+                       COMPLIANCE_TEST(testvar_read == testvar, "All reported DATA words must be R/W");
+               }
+       }
+
+       /* Check that all reported ProgBuf words are really R/W */
+       for (int invert = 0; invert < 2; invert++) {
+               for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+                       testvar = (i + 1) * 0x11111111;
+                       if (invert)
+                               testvar = ~testvar;
+                       COMPLIANCE_WRITE(target, DMI_PROGBUF0 + i, testvar);
+               }
+               for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+                       testvar = (i + 1) * 0x11111111;
+                       if (invert)
+                               testvar = ~testvar;
+                       COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i);
+                       COMPLIANCE_TEST(testvar_read == testvar, "All reported PROGBUF words must be R/W");
+               }
+       }
+
+       /* TODO: Cause and clear all error types */
+
+       /* COMMAND
+       According to the spec, this register is only W, so can't really check the read result.
+       But at any rate, this is not legal and should cause an error. */
+       COMPLIANCE_WRITE(target, DMI_COMMAND, 0xAAAAAAAA);
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+       COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, \
+                       "Illegal COMMAND should result in UNSUPPORTED");
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR);
+
+       COMPLIANCE_WRITE(target, DMI_COMMAND, 0x55555555);
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+       COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, \
+                       "Illegal COMMAND should result in UNSUPPORTED");
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR);
+
+       /* Basic Abstract Commands */
+       for (unsigned int i = 1; i < 32; i = i << 1) {
+               riscv_reg_t testval =   i | ((i + 1ULL) << 32);
+               riscv_reg_t testval_read;
+               COMPLIANCE_TEST(ERROR_OK == register_write_direct(target, GDB_REGNO_ZERO + i, testval),
+                               "GPR Writes should be supported.");
+               COMPLIANCE_MUST_PASS(write_abstract_arg(target, 0, 0xDEADBEEFDEADBEEF, 64));
+               COMPLIANCE_TEST(ERROR_OK == register_read_direct(target, &testval_read, GDB_REGNO_ZERO + i),
+                               "GPR Reads should be supported.");
+               if (riscv_xlen(target) > 32) {
+                       /* Dummy comment to satisfy linter, since removing the brances here doesn't actually compile. */
+                       COMPLIANCE_TEST(testval == testval_read, "GPR Reads and writes should be supported.");
+               } else {
+                       /* Dummy comment to satisfy linter, since removing the brances here doesn't actually compile. */
+                       COMPLIANCE_TEST((testval & 0xFFFFFFFF) == testval_read, "GPR Reads and writes should be supported.");
+               }
+       }
+
+       /* ABSTRACTAUTO
+       See which bits are actually writable */
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0xFFFFFFFF);
+       uint32_t abstractauto;
+       uint32_t busy;
+       COMPLIANCE_READ(target, &abstractauto, DMI_ABSTRACTAUTO);
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0x0);
+       if (abstractauto > 0) {
+               /* This mechanism only works when you have a reasonable sized progbuf, which is not
+               a true compliance requirement. */
+               if (info->progbufsize >= 3) {
+
+                       testvar = 0;
+                       COMPLIANCE_TEST(ERROR_OK == register_write_direct(target, GDB_REGNO_S0, 0),
+                                       "Need to be able to write S0 to test ABSTRACTAUTO");
+                       struct riscv_program program;
+                       COMPLIANCE_MUST_PASS(riscv_program_init(&program, target));
+                       /* This is also testing that WFI() is a NOP during debug mode. */
+                       COMPLIANCE_MUST_PASS(riscv_program_insert(&program, wfi()));
+                       COMPLIANCE_MUST_PASS(riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, 1));
+                       COMPLIANCE_MUST_PASS(riscv_program_ebreak(&program));
+                       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0x0);
+                       COMPLIANCE_MUST_PASS(riscv_program_exec(&program, target));
+                       testvar++;
+                       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0xFFFFFFFF);
+                       COMPLIANCE_READ(target, &abstractauto, DMI_ABSTRACTAUTO);
+                       uint32_t autoexec_data = get_field(abstractauto, DMI_ABSTRACTAUTO_AUTOEXECDATA);
+                       uint32_t autoexec_progbuf = get_field(abstractauto, DMI_ABSTRACTAUTO_AUTOEXECPROGBUF);
+                       for (unsigned int i = 0; i < 12; i++) {
+                               COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i);
+                               do {
+                                       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+                                       busy = get_field(testvar_read, DMI_ABSTRACTCS_BUSY);
+                               } while (busy);
+                               if (autoexec_data & (1 << i)) {
+                                       COMPLIANCE_TEST(i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT),
+                                                       "AUTOEXEC may be writable up to DATACOUNT bits.");
+                                       testvar++;
+                               }
+                       }
+                       for (unsigned int i = 0; i < 16; i++) {
+                               COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i);
+                               do {
+                                       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+                                       busy = get_field(testvar_read, DMI_ABSTRACTCS_BUSY);
+                               } while (busy);
+                               if (autoexec_progbuf & (1 << i)) {
+                                       COMPLIANCE_TEST(i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE),
+                                                       "AUTOEXEC may be writable up to PROGBUFSIZE bits.");
+                                       testvar++;
+                               }
+                       }
+
+                       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0);
+                       COMPLIANCE_TEST(ERROR_OK == register_read_direct(target, &value, GDB_REGNO_S0),
+                                       "Need to be able to read S0 to test ABSTRACTAUTO");
+
+                       COMPLIANCE_TEST(testvar == value,
+                                       "ABSTRACTAUTO should cause COMMAND to run the expected number of times.");
+               }
+       }
+
+       /* Single-Step each hart. */
+       for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) {
+               COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel));
+               COMPLIANCE_MUST_PASS(riscv013_on_step(target));
+               COMPLIANCE_MUST_PASS(riscv013_step_current_hart(target));
+               COMPLIANCE_TEST(riscv_halt_reason(target, hartsel) == RISCV_HALT_SINGLESTEP,
+                               "Single Step should result in SINGLESTEP");
+       }
+
+       /* Core Register Tests */
+       uint64_t bogus_dpc = 0xdeadbeef;
+       for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) {
+               COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel));
+
+               /* DCSR Tests */
+               COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DCSR, 0x0));
+               COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DCSR));
+               COMPLIANCE_TEST(value != 0,     "Not all bits in DCSR are writable by Debugger");
+               COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DCSR, 0xFFFFFFFF));
+               COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DCSR));
+               COMPLIANCE_TEST(value != 0,     "At least some bits in DCSR must be 1");
+
+               /* DPC. Note that DPC is sign-extended. */
+               riscv_reg_t dpcmask = 0xFFFFFFFCUL;
+               riscv_reg_t dpc;
+
+               if (riscv_xlen(target) > 32)
+                       dpcmask |= (0xFFFFFFFFULL << 32);
+
+               if (riscv_supports_extension(target, riscv_current_hartid(target), 'C'))
+                       dpcmask |= 0x2;
+
+               COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DPC, dpcmask));
+               COMPLIANCE_MUST_PASS(register_read_direct(target, &dpc, GDB_REGNO_DPC));
+               COMPLIANCE_TEST(dpcmask == dpc,
+                               "DPC must be sign-extended to XLEN and writable to all-1s (except the least significant bits)");
+               COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DPC, 0));
+               COMPLIANCE_MUST_PASS(register_read_direct(target, &dpc, GDB_REGNO_DPC));
+               COMPLIANCE_TEST(dpc == 0, "DPC must be writable to 0.");
+               if (hartsel == 0)
+                       bogus_dpc = dpc; /* For a later test step */
+       }
+
+       /* NDMRESET
+       Asserting non-debug module reset should not reset Debug Module state.
+       But it should reset Hart State, e.g. DPC should get a different value.
+       Also make sure that DCSR reports cause of 'HALT' even though previously we single-stepped.
+       */
+
+       /* Write some registers. They should not be impacted by ndmreset. */
+       COMPLIANCE_WRITE(target, DMI_COMMAND, 0xFFFFFFFF);
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+               testvar = (i + 1) * 0x11111111;
+               COMPLIANCE_WRITE(target, DMI_PROGBUF0 + i, testvar);
+       }
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+               testvar = (i + 1) * 0x11111111;
+               COMPLIANCE_WRITE(target, DMI_DATA0 + i, testvar);
+       }
+
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0xFFFFFFFF);
+       COMPLIANCE_READ(target, &abstractauto, DMI_ABSTRACTAUTO);
+
+       /* Pulse reset. */
+       target->reset_halt = true;
+       COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, 0));
+       COMPLIANCE_TEST(ERROR_OK == assert_reset(target), "Must be able to assert NDMRESET");
+       COMPLIANCE_TEST(ERROR_OK == deassert_reset(target), "Must be able to deassert NDMRESET");
+
+       /* Verify that most stuff is not affected by ndmreset. */
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+       COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR)  == CMDERR_NOT_SUPPORTED,
+                       "NDMRESET should not affect DMI_ABSTRACTCS");
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTAUTO);
+       COMPLIANCE_TEST(testvar_read == abstractauto, "NDMRESET should not affect DMI_ABSTRACTAUTO");
+
+       /* Clean up to avoid future test failures */
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR);
+       COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0);
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+               testvar = (i + 1) * 0x11111111;
+               COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i);
+               COMPLIANCE_TEST(testvar_read == testvar, "PROGBUF words must not be affected by NDMRESET");
+       }
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+               testvar = (i + 1) * 0x11111111;
+               COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i);
+               COMPLIANCE_TEST(testvar_read == testvar, "DATA words must not be affected by NDMRESET");
+       }
+
+       /* Verify that DPC *is* affected by ndmreset. Since we don't know what it *should* be,
+       just verify that at least it's not the bogus value anymore. */
+
+       COMPLIANCE_TEST(bogus_dpc != 0xdeadbeef, "BOGUS DPC should have been set somehow (bug in compliance test)");
+       COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DPC));
+       COMPLIANCE_TEST(bogus_dpc != value, "NDMRESET should move DPC to reset value.");
+
+       COMPLIANCE_TEST(riscv_halt_reason(target, 0) == RISCV_HALT_INTERRUPT,
+                       "After NDMRESET halt, DCSR should report cause of halt");
+
+       /* DMACTIVE -- deasserting DMACTIVE should reset all the above values. */
+
+       /* Toggle dmactive */
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, 0);
+       COMPLIANCE_WRITE(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE);
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS);
+       COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR)  == 0, "ABSTRACTCS.cmderr should reset to 0");
+       COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTAUTO);
+       COMPLIANCE_TEST(testvar_read == 0, "ABSTRACTAUTO should reset to 0");
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) {
+               COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i);
+               COMPLIANCE_TEST(testvar_read == 0, "PROGBUF words should reset to 0");
+       }
+
+       for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) {
+               COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i);
+               COMPLIANCE_TEST(testvar_read == 0, "DATA words should reset to 0");
+       }
+
+       /*
+       * TODO:
+       * DCSR.cause priorities
+       * DCSR.stoptime/stopcycle
+       * DCSR.stepie
+       * DCSR.ebreak
+       * DCSR.prv
+       */
+
+       /* Halt every hart for any follow-up tests*/
+       COMPLIANCE_MUST_PASS(riscv_halt_all_harts(target));
+
+       uint32_t failed_tests = total_tests - passed_tests;
+       if (total_tests == passed_tests) {
+               LOG_INFO("ALL TESTS PASSED\n");
+               return ERROR_OK;
+       } else {
+               LOG_INFO("%d TESTS FAILED\n", failed_tests);
+               return ERROR_FAIL;
+       }
+}
index 9a6b9389af62ee99966375c0cb9b5cb047a87c20..7bae39034690d8408ab18510a867309a658599fb 100644 (file)
@@ -154,17 +154,17 @@ typedef enum slot {
 #define MAX_HWBPS                      16
 #define DRAM_CACHE_SIZE                16
 
-uint8_t ir_dtmcontrol[1] = {DTMCONTROL};
+uint8_t ir_dtmcontrol[4] = {DTMCONTROL};
 struct scan_field select_dtmcontrol = {
        .in_value = NULL,
        .out_value = ir_dtmcontrol
 };
-uint8_t ir_dbus[1] = {DBUS};
+uint8_t ir_dbus[4] = {DBUS};
 struct scan_field select_dbus = {
        .in_value = NULL,
        .out_value = ir_dbus
 };
-uint8_t ir_idcode[1] = {0x1};
+uint8_t ir_idcode[4] = {0x1};
 struct scan_field select_idcode = {
        .in_value = NULL,
        .out_value = ir_idcode
@@ -187,13 +187,17 @@ int riscv_reset_timeout_sec = DEFAULT_RESET_TIMEOUT_SEC;
 
 bool riscv_prefer_sba;
 
+typedef struct {
+       uint16_t low, high;
+} range_t;
+
 /* In addition to the ones in the standard spec, we'll also expose additional
  * CSRs in this list.
  * The list is either NULL, or a series of ranges (inclusive), terminated with
  * 1,0. */
-struct {
-       uint16_t low, high;
-} *expose_csr;
+range_t *expose_csr;
+/* Same, but for custom registers. */
+range_t *expose_custom;
 
 static uint32_t dtmcontrol_scan(struct target *target, uint32_t out)
 {
@@ -262,6 +266,8 @@ static int riscv_init_target(struct command_context *cmd_ctx,
 
        riscv_semihosting_init(target);
 
+       target->debug_reason = DBG_REASON_DBGRQ;
+
        return ERROR_OK;
 }
 
@@ -272,8 +278,21 @@ static void riscv_deinit_target(struct target *target)
        if (tt) {
                tt->deinit_target(target);
                riscv_info_t *info = (riscv_info_t *) target->arch_info;
+               free(info->reg_names);
                free(info);
        }
+       /* Free the shared structure use for most registers. */
+       if (target->reg_cache) {
+               if (target->reg_cache->reg_list) {
+                       if (target->reg_cache->reg_list[0].arch_info)
+                               free(target->reg_cache->reg_list[0].arch_info);
+                       /* Free the ones we allocated separately. */
+                       for (unsigned i = GDB_REGNO_COUNT; i < target->reg_cache->num_regs; i++)
+                               free(target->reg_cache->reg_list[i].arch_info);
+                       free(target->reg_cache->reg_list);
+               }
+               free(target->reg_cache);
+       }
        target->arch_info = NULL;
 }
 
@@ -470,8 +489,8 @@ static int add_trigger(struct target *target, struct trigger *trigger)
                if (result != ERROR_OK)
                        continue;
 
-               LOG_DEBUG("Using trigger %d (type %d) for bp %d", i, type,
-                               trigger->unique_id);
+               LOG_DEBUG("[%d] Using trigger %d (type %d) for bp %d", target->coreid,
+                               i, type, trigger->unique_id);
                r->trigger_unique_id[i] = trigger->unique_id;
                break;
        }
@@ -493,19 +512,31 @@ static int add_trigger(struct target *target, struct trigger *trigger)
 
 int riscv_add_breakpoint(struct target *target, struct breakpoint *breakpoint)
 {
+       LOG_DEBUG("[%d] @0x%" TARGET_PRIxADDR, target->coreid, breakpoint->address);
+       assert(breakpoint);
        if (breakpoint->type == BKPT_SOFT) {
-               if (target_read_memory(target, breakpoint->address, breakpoint->length, 1,
+               /** @todo check RVC for size/alignment */
+               if (!(breakpoint->length == 4 || breakpoint->length == 2)) {
+                       LOG_ERROR("Invalid breakpoint length %d", breakpoint->length);
+                       return ERROR_FAIL;
+               }
+
+               if (0 != (breakpoint->address % 2)) {
+                       LOG_ERROR("Invalid breakpoint alignment for address 0x%" TARGET_PRIxADDR, breakpoint->address);
+                       return ERROR_FAIL;
+               }
+
+               if (target_read_memory(target, breakpoint->address, 2, breakpoint->length / 2,
                                        breakpoint->orig_instr) != ERROR_OK) {
                        LOG_ERROR("Failed to read original instruction at 0x%" TARGET_PRIxADDR,
                                        breakpoint->address);
                        return ERROR_FAIL;
                }
 
-               int retval;
-               if (breakpoint->length == 4)
-                       retval = target_write_u32(target, breakpoint->address, ebreak());
-               else
-                       retval = target_write_u16(target, breakpoint->address, ebreak_c());
+               uint8_t buff[4];
+               buf_set_u32(buff, 0, breakpoint->length * CHAR_BIT, breakpoint->length == 4 ? ebreak() : ebreak_c());
+               int const retval = target_write_memory(target, breakpoint->address, 2, breakpoint->length / 2, buff);
+
                if (retval != ERROR_OK) {
                        LOG_ERROR("Failed to write %d-byte breakpoint instruction at 0x%"
                                        TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
@@ -515,17 +546,15 @@ int riscv_add_breakpoint(struct target *target, struct breakpoint *breakpoint)
        } else if (breakpoint->type == BKPT_HARD) {
                struct trigger trigger;
                trigger_from_breakpoint(&trigger, breakpoint);
-               int result = add_trigger(target, &trigger);
+               int const result = add_trigger(target, &trigger);
                if (result != ERROR_OK)
                        return result;
-
        } else {
                LOG_INFO("OpenOCD only supports hardware and software breakpoints.");
                return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
        }
 
        breakpoint->set = true;
-
        return ERROR_OK;
 }
 
@@ -557,7 +586,8 @@ static int remove_trigger(struct target *target, struct trigger *trigger)
                                "trigger.");
                return ERROR_FAIL;
        }
-       LOG_DEBUG("Stop using resource %d for bp %d", i, trigger->unique_id);
+       LOG_DEBUG("[%d] Stop using resource %d for bp %d", target->coreid, i,
+                       trigger->unique_id);
        for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
                if (!riscv_hart_enabled(target, hartid))
                        continue;
@@ -578,7 +608,7 @@ int riscv_remove_breakpoint(struct target *target,
                struct breakpoint *breakpoint)
 {
        if (breakpoint->type == BKPT_SOFT) {
-               if (target_write_memory(target, breakpoint->address, breakpoint->length, 1,
+               if (target_write_memory(target, breakpoint->address, 2, breakpoint->length / 2,
                                        breakpoint->orig_instr) != ERROR_OK) {
                        LOG_ERROR("Failed to restore instruction for %d-byte breakpoint at "
                                        "0x%" TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
@@ -632,6 +662,8 @@ int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint)
 int riscv_remove_watchpoint(struct target *target,
                struct watchpoint *watchpoint)
 {
+       LOG_DEBUG("[%d] @0x%" TARGET_PRIxADDR, target->coreid, watchpoint->address);
+
        struct trigger trigger;
        trigger_from_watchpoint(&trigger, watchpoint);
 
@@ -643,6 +675,89 @@ int riscv_remove_watchpoint(struct target *target,
        return ERROR_OK;
 }
 
+/* Sets *hit_watchpoint to the first watchpoint identified as causing the
+ * current halt.
+ *
+ * The GDB server uses this information to tell GDB what data address has
+ * been hit, which enables GDB to print the hit variable along with its old
+ * and new value. */
+int riscv_hit_watchpoint(struct target *target, struct watchpoint **hit_watchpoint)
+{
+       struct watchpoint *wp = target->watchpoints;
+
+       LOG_DEBUG("Current hartid = %d", riscv_current_hartid(target));
+
+       /*TODO instead of disassembling the instruction that we think caused the
+        * trigger, check the hit bit of each watchpoint first. The hit bit is
+        * simpler and more reliable to check but as it is optional and relatively
+        * new, not all hardware will implement it  */
+       riscv_reg_t dpc;
+       riscv_get_register(target, &dpc, GDB_REGNO_DPC);
+       const uint8_t length = 4;
+       LOG_DEBUG("dpc is 0x%" PRIx64, dpc);
+
+       /* fetch the instruction at dpc */
+       uint8_t buffer[length];
+       if (target_read_buffer(target, dpc, length, buffer) != ERROR_OK) {
+               LOG_ERROR("Failed to read instruction at dpc 0x%" PRIx64, dpc);
+               return ERROR_FAIL;
+       }
+
+       uint32_t instruction = 0;
+
+       for (int i = 0; i < length; i++) {
+               LOG_DEBUG("Next byte is %x", buffer[i]);
+               instruction += (buffer[i] << 8 * i);
+       }
+       LOG_DEBUG("Full instruction is %x", instruction);
+
+       /* find out which memory address is accessed by the instruction at dpc */
+       /* opcode is first 7 bits of the instruction */
+       uint8_t opcode = instruction & 0x7F;
+       uint32_t rs1;
+       int16_t imm;
+       riscv_reg_t mem_addr;
+
+       if (opcode == MATCH_LB || opcode == MATCH_SB) {
+               rs1 = (instruction & 0xf8000) >> 15;
+               riscv_get_register(target, &mem_addr, rs1);
+
+               if (opcode == MATCH_SB) {
+                       LOG_DEBUG("%x is store instruction", instruction);
+                       imm = ((instruction & 0xf80) >> 7) | ((instruction & 0xfe000000) >> 20);
+               } else {
+                       LOG_DEBUG("%x is load instruction", instruction);
+                       imm = (instruction & 0xfff00000) >> 20;
+               }
+               /* sign extend 12-bit imm to 16-bits */
+               if (imm & (1 << 11))
+                       imm |= 0xf000;
+               mem_addr += imm;
+               LOG_DEBUG("memory address=0x%" PRIx64, mem_addr);
+       } else {
+               LOG_DEBUG("%x is not a RV32I load or store", instruction);
+               return ERROR_FAIL;
+       }
+
+       while (wp) {
+               /*TODO support length/mask */
+               if (wp->address == mem_addr) {
+                       *hit_watchpoint = wp;
+                       LOG_DEBUG("Hit address=%" TARGET_PRIxADDR, wp->address);
+                       return ERROR_OK;
+               }
+               wp = wp->next;
+       }
+
+       /* No match found - either we hit a watchpoint caused by an instruction that
+        * this function does not yet disassemble, or we hit a breakpoint.
+        *
+        * OpenOCD will behave as if this function had never been implemented i.e.
+        * report the halt to GDB with no address information. */
+       return ERROR_FAIL;
+}
+
+
 static int oldriscv_step(struct target *target, int current, uint32_t address,
                int handle_breakpoints)
 {
@@ -718,13 +833,15 @@ static int old_or_new_riscv_halt(struct target *target)
 
 static int riscv_assert_reset(struct target *target)
 {
+       LOG_DEBUG("[%d]", target->coreid);
        struct target_type *tt = get_target_type(target);
+       riscv_invalidate_register_cache(target);
        return tt->assert_reset(target);
 }
 
 static int riscv_deassert_reset(struct target *target)
 {
-       LOG_DEBUG("RISCV DEASSERT RESET");
+       LOG_DEBUG("[%d]", target->coreid);
        struct target_type *tt = get_target_type(target);
        return tt->deassert_reset(target);
 }
@@ -745,8 +862,28 @@ static int old_or_new_riscv_resume(
                int handle_breakpoints,
                int debug_execution
 ){
-       RISCV_INFO(r);
        LOG_DEBUG("handle_breakpoints=%d", handle_breakpoints);
+       if (target->smp) {
+               struct target_list *targets = target->head;
+               int result = ERROR_OK;
+               while (targets) {
+                       struct target *t = targets->target;
+                       riscv_info_t *r = riscv_info(t);
+                       if (r->is_halted == NULL) {
+                               if (oldriscv_resume(t, current, address, handle_breakpoints,
+                                                       debug_execution) != ERROR_OK)
+                                       result = ERROR_FAIL;
+                       } else {
+                               if (riscv_openocd_resume(t, current, address,
+                                                       handle_breakpoints, debug_execution) != ERROR_OK)
+                                       result = ERROR_FAIL;
+                       }
+                       targets = targets->next;
+               }
+               return result;
+       }
+
+       RISCV_INFO(r);
        if (r->is_halted == NULL)
                return oldriscv_resume(target, current, address, handle_breakpoints, debug_execution);
        else
@@ -756,9 +893,11 @@ static int old_or_new_riscv_resume(
 static int riscv_select_current_hart(struct target *target)
 {
        RISCV_INFO(r);
-       if (r->rtos_hartid != -1 && riscv_rtos_enabled(target))
+       if (riscv_rtos_enabled(target)) {
+               if (r->rtos_hartid == -1)
+                       r->rtos_hartid = target->rtos->current_threadid - 1;
                return riscv_set_current_hartid(target, r->rtos_hartid);
-       else
+       else
                return riscv_set_current_hartid(target, target->coreid);
 }
 
@@ -780,13 +919,13 @@ static int riscv_write_memory(struct target *target, target_addr_t address,
        return tt->write_memory(target, address, size, count, buffer);
 }
 
-static int riscv_get_gdb_reg_list(struct target *target,
+static int riscv_get_gdb_reg_list_internal(struct target *target,
                struct reg **reg_list[], int *reg_list_size,
-               enum target_register_class reg_class)
+               enum target_register_class reg_class, bool read)
 {
        RISCV_INFO(r);
-       LOG_DEBUG("reg_class=%d", reg_class);
-       LOG_DEBUG("rtos_hartid=%d current_hartid=%d", r->rtos_hartid, r->current_hartid);
+       LOG_DEBUG("rtos_hartid=%d, current_hartid=%d, reg_class=%d, read=%d",
+                       r->rtos_hartid, r->current_hartid, reg_class, read);
 
        if (!target->reg_cache) {
                LOG_ERROR("Target not initialized. Return ERROR_FAIL.");
@@ -798,10 +937,10 @@ static int riscv_get_gdb_reg_list(struct target *target,
 
        switch (reg_class) {
                case REG_CLASS_GENERAL:
-                       *reg_list_size = 32;
+                       *reg_list_size = 33;
                        break;
                case REG_CLASS_ALL:
-                       *reg_list_size = GDB_REGNO_COUNT;
+                       *reg_list_size = target->reg_cache->num_regs;
                        break;
                default:
                        LOG_ERROR("Unsupported reg_class: %d", reg_class);
@@ -816,11 +955,28 @@ static int riscv_get_gdb_reg_list(struct target *target,
                assert(!target->reg_cache->reg_list[i].valid ||
                                target->reg_cache->reg_list[i].size > 0);
                (*reg_list)[i] = &target->reg_cache->reg_list[i];
+               if (read && !target->reg_cache->reg_list[i].valid) {
+                       if (target->reg_cache->reg_list[i].type->get(
+                                               &target->reg_cache->reg_list[i]) != ERROR_OK)
+                               /* This function is called when first connecting to gdb,
+                                * resulting in an attempt to read all kinds of registers which
+                                * probably will fail. Ignore these failures, and when
+                                * encountered stop reading to save time. */
+                               read = false;
+               }
        }
 
        return ERROR_OK;
 }
 
+static int riscv_get_gdb_reg_list(struct target *target,
+               struct reg **reg_list[], int *reg_list_size,
+               enum target_register_class reg_class)
+{
+       return riscv_get_gdb_reg_list_internal(target, reg_list, reg_list_size,
+                       reg_class, true);
+}
+
 static int riscv_arch_state(struct target *target)
 {
        struct target_type *tt = get_target_type(target);
@@ -853,7 +1009,7 @@ static int riscv_run_algorithm(struct target *target, int num_mem_params,
 
        uint64_t saved_regs[32];
        for (int i = 0; i < num_reg_params; i++) {
-               if (mem_params[i].direction == PARAM_IN)
+               if (reg_params[i].direction == PARAM_IN)
                        continue;
 
                LOG_DEBUG("save %s", reg_params[i].reg_name);
@@ -1000,6 +1156,30 @@ static enum riscv_poll_hart riscv_poll_hart(struct target *target, int hartid)
        return RPH_NO_CHANGE;
 }
 
+int set_debug_reason(struct target *target, int hartid)
+{
+       switch (riscv_halt_reason(target, hartid)) {
+               case RISCV_HALT_BREAKPOINT:
+                       target->debug_reason = DBG_REASON_BREAKPOINT;
+                       break;
+               case RISCV_HALT_TRIGGER:
+                       target->debug_reason = DBG_REASON_WATCHPOINT;
+                       break;
+               case RISCV_HALT_INTERRUPT:
+                       target->debug_reason = DBG_REASON_DBGRQ;
+                       break;
+               case RISCV_HALT_SINGLESTEP:
+                       target->debug_reason = DBG_REASON_SINGLESTEP;
+                       break;
+               case RISCV_HALT_UNKNOWN:
+                       target->debug_reason = DBG_REASON_UNDEFINED;
+                       break;
+               case RISCV_HALT_ERROR:
+                       return ERROR_FAIL;
+       }
+       return ERROR_OK;
+}
+
 /*** OpenOCD Interface ***/
 int riscv_openocd_poll(struct target *target)
 {
@@ -1034,6 +1214,64 @@ int riscv_openocd_poll(struct target *target)
                 * harts. */
                for (int i = 0; i < riscv_count_harts(target); ++i)
                        riscv_halt_one_hart(target, i);
+
+       } else if (target->smp) {
+               bool halt_discovered = false;
+               bool newly_halted[128] = {0};
+               unsigned i = 0;
+               for (struct target_list *list = target->head; list != NULL;
+                               list = list->next, i++) {
+                       struct target *t = list->target;
+                       riscv_info_t *r = riscv_info(t);
+                       assert(i < DIM(newly_halted));
+                       enum riscv_poll_hart out = riscv_poll_hart(t, r->current_hartid);
+                       switch (out) {
+                               case RPH_NO_CHANGE:
+                                       break;
+                               case RPH_DISCOVERED_RUNNING:
+                                       t->state = TARGET_RUNNING;
+                                       break;
+                               case RPH_DISCOVERED_HALTED:
+                                       halt_discovered = true;
+                                       newly_halted[i] = true;
+                                       t->state = TARGET_HALTED;
+                                       if (set_debug_reason(t, r->current_hartid) != ERROR_OK)
+                                               return ERROR_FAIL;
+                                       break;
+                               case RPH_ERROR:
+                                       return ERROR_FAIL;
+                       }
+               }
+
+               if (halt_discovered) {
+                       LOG_DEBUG("Halt other targets in this SMP group.");
+                       i = 0;
+                       for (struct target_list *list = target->head; list != NULL;
+                                       list = list->next, i++) {
+                               struct target *t = list->target;
+                               riscv_info_t *r = riscv_info(t);
+                               if (t->state != TARGET_HALTED) {
+                                       if (riscv_halt_one_hart(t, r->current_hartid) != ERROR_OK)
+                                               return ERROR_FAIL;
+                                       t->state = TARGET_HALTED;
+                                       if (set_debug_reason(t, r->current_hartid) != ERROR_OK)
+                                               return ERROR_FAIL;
+                                       newly_halted[i] = true;
+                               }
+                       }
+
+                       /* Now that we have all our ducks in a row, tell the higher layers
+                        * what just happened. */
+                       i = 0;
+                       for (struct target_list *list = target->head; list != NULL;
+                                       list = list->next, i++) {
+                               struct target *t = list->target;
+                               if (newly_halted[i])
+                                       target_call_event_callbacks(t, TARGET_EVENT_HALTED);
+                       }
+               }
+               return ERROR_OK;
+
        } else {
                enum riscv_poll_hart out = riscv_poll_hart(target,
                                riscv_current_hartid(target));
@@ -1047,29 +1285,13 @@ int riscv_openocd_poll(struct target *target)
        }
 
        target->state = TARGET_HALTED;
-       switch (riscv_halt_reason(target, halted_hart)) {
-       case RISCV_HALT_BREAKPOINT:
-               target->debug_reason = DBG_REASON_BREAKPOINT;
-               break;
-       case RISCV_HALT_TRIGGER:
-               target->debug_reason = DBG_REASON_WATCHPOINT;
-               break;
-       case RISCV_HALT_INTERRUPT:
-               target->debug_reason = DBG_REASON_DBGRQ;
-               break;
-       case RISCV_HALT_SINGLESTEP:
-               target->debug_reason = DBG_REASON_SINGLESTEP;
-               break;
-       case RISCV_HALT_UNKNOWN:
-               target->debug_reason = DBG_REASON_UNDEFINED;
-               break;
-       case RISCV_HALT_ERROR:
+       if (set_debug_reason(target, halted_hart) != ERROR_OK)
                return ERROR_FAIL;
-       }
 
        if (riscv_rtos_enabled(target)) {
                target->rtos->current_threadid = halted_hart + 1;
                target->rtos->current_thread = halted_hart + 1;
+               riscv_set_rtos_hartid(target, halted_hart);
        }
 
        target->state = TARGET_HALTED;
@@ -1087,25 +1309,39 @@ int riscv_openocd_poll(struct target *target)
 int riscv_openocd_halt(struct target *target)
 {
        RISCV_INFO(r);
+       int result;
 
-       LOG_DEBUG("halting all harts");
+       LOG_DEBUG("[%d] halting all harts", target->coreid);
 
-       int out = riscv_halt_all_harts(target);
-       if (out != ERROR_OK) {
-               LOG_ERROR("Unable to halt all harts");
-               return out;
+       if (target->smp) {
+               LOG_DEBUG("Halt other targets in this SMP group.");
+               struct target_list *targets = target->head;
+               result = ERROR_OK;
+               while (targets) {
+                       struct target *t = targets->target;
+                       targets = targets->next;
+                       if (t->state != TARGET_HALTED) {
+                               if (riscv_halt_all_harts(t) != ERROR_OK)
+                                       result = ERROR_FAIL;
+                       }
+               }
+       } else {
+               result = riscv_halt_all_harts(target);
        }
 
-       register_cache_invalidate(target->reg_cache);
        if (riscv_rtos_enabled(target)) {
-               target->rtos->current_threadid = r->rtos_hartid + 1;
-               target->rtos->current_thread = r->rtos_hartid + 1;
+               if (r->rtos_hartid != -1) {
+                       LOG_DEBUG("halt requested on RTOS hartid %d", r->rtos_hartid);
+                       target->rtos->current_threadid = r->rtos_hartid + 1;
+                       target->rtos->current_thread = r->rtos_hartid + 1;
+               } else
+                       LOG_DEBUG("halt requested, but no known RTOS hartid");
        }
 
        target->state = TARGET_HALTED;
        target->debug_reason = DBG_REASON_DBGRQ;
        target_call_event_callbacks(target, TARGET_EVENT_HALTED);
-       return out;
+       return result;
 }
 
 int riscv_openocd_resume(
@@ -1230,6 +1466,25 @@ COMMAND_HANDLER(riscv_set_reset_timeout_sec)
        return ERROR_OK;
 }
 
+COMMAND_HANDLER(riscv_test_compliance) {
+
+       struct target *target = get_current_target(CMD_CTX);
+
+       RISCV_INFO(r);
+
+       if (CMD_ARGC > 0) {
+               LOG_ERROR("Command does not take any parameters.");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       if (r->test_compliance) {
+               return r->test_compliance(target);
+       } else {
+               LOG_ERROR("This target does not support this command (may implement an older version of the spec).");
+               return ERROR_FAIL;
+       }
+}
+
 COMMAND_HANDLER(riscv_set_prefer_sba)
 {
        if (CMD_ARGC != 1) {
@@ -1253,20 +1508,15 @@ void parse_error(const char *string, char c, unsigned position)
        LOG_ERROR("%s", buf);
 }
 
-COMMAND_HANDLER(riscv_set_expose_csrs)
+int parse_ranges(range_t **ranges, const char **argv)
 {
-       if (CMD_ARGC != 1) {
-               LOG_ERROR("Command takes exactly 1 parameter");
-               return ERROR_COMMAND_SYNTAX_ERROR;
-       }
-
        for (unsigned pass = 0; pass < 2; pass++) {
                unsigned range = 0;
                unsigned low = 0;
                bool parse_low = true;
                unsigned high = 0;
-               for (unsigned i = 0; i == 0 || CMD_ARGV[0][i-1]; i++) {
-                       char c = CMD_ARGV[0][i];
+               for (unsigned i = 0; i == 0 || argv[0][i-1]; i++) {
+                       char c = argv[0][i];
                        if (isspace(c)) {
                                /* Ignore whitespace. */
                                continue;
@@ -1280,13 +1530,13 @@ COMMAND_HANDLER(riscv_set_expose_csrs)
                                        parse_low = false;
                                } else if (c == ',' || c == 0) {
                                        if (pass == 1) {
-                                               expose_csr[range].low = low;
-                                               expose_csr[range].high = low;
+                                               (*ranges)[range].low = low;
+                                               (*ranges)[range].high = low;
                                        }
                                        low = 0;
                                        range++;
                                } else {
-                                       parse_error(CMD_ARGV[0], c, i);
+                                       parse_error(argv[0], c, i);
                                        return ERROR_COMMAND_SYNTAX_ERROR;
                                }
 
@@ -1297,31 +1547,52 @@ COMMAND_HANDLER(riscv_set_expose_csrs)
                                } else if (c == ',' || c == 0) {
                                        parse_low = true;
                                        if (pass == 1) {
-                                               expose_csr[range].low = low;
-                                               expose_csr[range].high = high;
+                                               (*ranges)[range].low = low;
+                                               (*ranges)[range].high = high;
                                        }
                                        low = 0;
                                        high = 0;
                                        range++;
                                } else {
-                                       parse_error(CMD_ARGV[0], c, i);
+                                       parse_error(argv[0], c, i);
                                        return ERROR_COMMAND_SYNTAX_ERROR;
                                }
                        }
                }
 
                if (pass == 0) {
-                       if (expose_csr)
-                               free(expose_csr);
-                       expose_csr = calloc(range + 2, sizeof(*expose_csr));
+                       if (*ranges)
+                               free(*ranges);
+                       *ranges = calloc(range + 2, sizeof(range_t));
                } else {
-                       expose_csr[range].low = 1;
-                       expose_csr[range].high = 0;
+                       (*ranges)[range].low = 1;
+                       (*ranges)[range].high = 0;
                }
        }
+
        return ERROR_OK;
 }
 
+COMMAND_HANDLER(riscv_set_expose_csrs)
+{
+       if (CMD_ARGC != 1) {
+               LOG_ERROR("Command takes exactly 1 parameter");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       return parse_ranges(&expose_csr, CMD_ARGV);
+}
+
+COMMAND_HANDLER(riscv_set_expose_custom)
+{
+       if (CMD_ARGC != 1) {
+               LOG_ERROR("Command takes exactly 1 parameter");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       return parse_ranges(&expose_custom, CMD_ARGV);
+}
+
 COMMAND_HANDLER(riscv_authdata_read)
 {
        if (CMD_ARGC != 0) {
@@ -1429,7 +1700,85 @@ COMMAND_HANDLER(riscv_dmi_write)
        }
 }
 
+COMMAND_HANDLER(riscv_test_sba_config_reg)
+{
+       if (CMD_ARGC != 4) {
+               LOG_ERROR("Command takes exactly 4 arguments");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       struct target *target = get_current_target(CMD_CTX);
+       RISCV_INFO(r);
+
+       target_addr_t legal_address;
+       uint32_t num_words;
+       target_addr_t illegal_address;
+       bool run_sbbusyerror_test;
+
+       COMMAND_PARSE_NUMBER(target_addr, CMD_ARGV[0], legal_address);
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], num_words);
+       COMMAND_PARSE_NUMBER(target_addr, CMD_ARGV[2], illegal_address);
+       COMMAND_PARSE_ON_OFF(CMD_ARGV[3], run_sbbusyerror_test);
+
+       if (r->test_sba_config_reg) {
+               return r->test_sba_config_reg(target, legal_address, num_words,
+                               illegal_address, run_sbbusyerror_test);
+       } else {
+               LOG_ERROR("test_sba_config_reg is not implemented for this target.");
+               return ERROR_FAIL;
+       }
+}
+
+COMMAND_HANDLER(riscv_reset_delays)
+{
+       int wait = 0;
+
+       if (CMD_ARGC > 1) {
+               LOG_ERROR("Command takes at most one argument");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       if (CMD_ARGC == 1)
+               COMMAND_PARSE_NUMBER(int, CMD_ARGV[0], wait);
+
+       struct target *target = get_current_target(CMD_CTX);
+       RISCV_INFO(r);
+       r->reset_delays_wait = wait;
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(riscv_set_ir)
+{
+       if (CMD_ARGC != 2) {
+               LOG_ERROR("Command takes exactly 2 arguments");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       uint32_t value;
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], value);
+
+       if (!strcmp(CMD_ARGV[0], "idcode")) {
+               buf_set_u32(ir_idcode, 0, 32, value);
+               return ERROR_OK;
+       } else if (!strcmp(CMD_ARGV[0], "dtmcs")) {
+               buf_set_u32(ir_dtmcontrol, 0, 32, value);
+               return ERROR_OK;
+       } else if (!strcmp(CMD_ARGV[0], "dmi")) {
+               buf_set_u32(ir_dbus, 0, 32, value);
+               return ERROR_OK;
+       } else {
+               return ERROR_FAIL;
+       }
+}
+
 static const struct command_registration riscv_exec_command_handlers[] = {
+       {
+               .name = "test_compliance",
+               .handler = riscv_test_compliance,
+               .mode = COMMAND_EXEC,
+               .usage = "riscv test_compliance",
+               .help = "Runs a basic compliance test suite against the RISC-V Debug Spec."
+       },
        {
                .name = "set_command_timeout_sec",
                .handler = riscv_set_command_timeout_sec,
@@ -1461,6 +1810,15 @@ static const struct command_registration riscv_exec_command_handlers[] = {
                                "addition to the standard ones. This must be executed before "
                                "`init`."
        },
+       {
+               .name = "expose_custom",
+               .handler = riscv_set_expose_custom,
+               .mode = COMMAND_ANY,
+               .usage = "riscv expose_custom n0[-m0][,n1[-m1]]...",
+               .help = "Configure a list of inclusive ranges for custom registers to "
+                       "expose. custom0 is accessed as abstract register number 0xc000, "
+                       "etc. This must be executed before `init`."
+       },
        {
                .name = "authdata_read",
                .handler = riscv_authdata_read,
@@ -1489,6 +1847,36 @@ static const struct command_registration riscv_exec_command_handlers[] = {
                .usage = "riscv dmi_write address value",
                .help = "Perform a 32-bit DMI write of value at address."
        },
+       {
+               .name = "test_sba_config_reg",
+               .handler = riscv_test_sba_config_reg,
+               .mode = COMMAND_ANY,
+               .usage = "riscv test_sba_config_reg legal_address num_words"
+                       "illegal_address run_sbbusyerror_test[on/off]",
+               .help = "Perform a series of tests on the SBCS register."
+                       "Inputs are a legal, 128-byte aligned address and a number of words to"
+                       "read/write starting at that address (i.e., address range [legal address,"
+                       "legal_address+word_size*num_words) must be legally readable/writable)"
+                       ", an illegal, 128-byte aligned address for error flag/handling cases,"
+                       "and whether sbbusyerror test should be run."
+       },
+       {
+               .name = "reset_delays",
+               .handler = riscv_reset_delays,
+               .mode = COMMAND_ANY,
+               .usage = "reset_delays [wait]",
+               .help = "OpenOCD learns how many Run-Test/Idle cycles are required "
+                       "between scans to avoid encountering the target being busy. This "
+                       "command resets those learned values after `wait` scans. It's only "
+                       "useful for testing OpenOCD itself."
+       },
+       {
+               .name = "set_ir",
+               .handler = riscv_set_ir,
+               .mode = COMMAND_ANY,
+               .usage = "riscv set_ir_idcode [idcode|dtmcs|dmi] value",
+               .help = "Set IR value for specified JTAG register."
+       },
        COMMAND_REGISTRATION_DONE
 };
 
@@ -1594,6 +1982,7 @@ struct target_type riscv_target = {
 
        .add_watchpoint = riscv_add_watchpoint,
        .remove_watchpoint = riscv_remove_watchpoint,
+       .hit_watchpoint = riscv_hit_watchpoint,
 
        .arch_state = riscv_arch_state,
 
@@ -1632,6 +2021,8 @@ int riscv_halt_all_harts(struct target *target)
                riscv_halt_one_hart(target, i);
        }
 
+       riscv_invalidate_register_cache(target);
+
        return ERROR_OK;
 }
 
@@ -1646,7 +2037,9 @@ int riscv_halt_one_hart(struct target *target, int hartid)
                return ERROR_OK;
        }
 
-       return r->halt_current_hart(target);
+       int result = r->halt_current_hart(target);
+       register_cache_invalidate(target->reg_cache);
+       return result;
 }
 
 int riscv_resume_all_harts(struct target *target)
@@ -1684,7 +2077,7 @@ int riscv_step_rtos_hart(struct target *target)
        if (riscv_rtos_enabled(target)) {
                hartid = r->rtos_hartid;
                if (hartid == -1) {
-                       LOG_USER("GDB has asked me to step \"any\" thread, so I'm stepping hart 0.");
+                       LOG_DEBUG("GDB has asked me to step \"any\" thread, so I'm stepping hart 0.");
                        hartid = 0;
                }
        }
@@ -1734,9 +2127,10 @@ int riscv_xlen_of_hart(const struct target *target, int hartid)
        return r->xlen[hartid];
 }
 
+extern struct rtos_type riscv_rtos;
 bool riscv_rtos_enabled(const struct target *target)
 {
-       return target->rtos != NULL;
+       return false;
 }
 
 int riscv_set_current_hartid(struct target *target, int hartid)
@@ -1754,19 +2148,9 @@ int riscv_set_current_hartid(struct target *target, int hartid)
 
        /* This might get called during init, in which case we shouldn't be
         * setting up the register cache. */
-       if (!target_was_examined(target))
-               return ERROR_OK;
+       if (target_was_examined(target) && riscv_rtos_enabled(target))
+               riscv_invalidate_register_cache(target);
 
-       /* Avoid invalidating the register cache all the time. */
-       if (r->registers_initialized
-                       && (!riscv_rtos_enabled(target) || (previous_hartid == hartid))
-                       && target->reg_cache->reg_list[GDB_REGNO_ZERO].size == (unsigned)riscv_xlen(target)
-                       && (!riscv_rtos_enabled(target) || (r->rtos_hartid != -1))) {
-               return ERROR_OK;
-       } else
-               LOG_DEBUG("Initializing registers: xlen=%d", riscv_xlen(target));
-
-       riscv_invalidate_register_cache(target);
        return ERROR_OK;
 }
 
@@ -1774,8 +2158,9 @@ void riscv_invalidate_register_cache(struct target *target)
 {
        RISCV_INFO(r);
 
+       LOG_DEBUG("[%d]", target->coreid);
        register_cache_invalidate(target->reg_cache);
-       for (size_t i = 0; i < GDB_REGNO_COUNT; ++i) {
+       for (size_t i = 0; i < target->reg_cache->num_regs; ++i) {
                struct reg *reg = &target->reg_cache->reg_list[i];
                reg->valid = false;
        }
@@ -1830,7 +2215,7 @@ int riscv_set_register_on_hart(struct target *target, int hartid,
                enum gdb_regno regid, uint64_t value)
 {
        RISCV_INFO(r);
-       LOG_DEBUG("[%d] %s <- %" PRIx64, hartid, gdb_regno_name(regid), value);
+       LOG_DEBUG("{%d} %s <- %" PRIx64, hartid, gdb_regno_name(regid), value);
        assert(r->set_register);
        return r->set_register(target, hartid, regid, value);
 }
@@ -1846,8 +2231,17 @@ int riscv_get_register_on_hart(struct target *target, riscv_reg_t *value,
                int hartid, enum gdb_regno regid)
 {
        RISCV_INFO(r);
+
+       struct reg *reg = &target->reg_cache->reg_list[regid];
+
+       if (reg && reg->valid && hartid == riscv_current_hartid(target)) {
+               *value = buf_get_u64(reg->value, 0, reg->size);
+               return ERROR_OK;
+       }
+
        int result = r->get_register(target, value, hartid, regid);
-       LOG_DEBUG("[%d] %s: %" PRIx64, hartid, gdb_regno_name(regid), *value);
+
+       LOG_DEBUG("{%d} %s: %" PRIx64, hartid, gdb_regno_name(regid), *value);
        return result;
 }
 
@@ -2048,24 +2442,42 @@ const char *gdb_regno_name(enum gdb_regno regno)
 
 static int register_get(struct reg *reg)
 {
-       struct target *target = (struct target *) reg->arch_info;
+       riscv_reg_info_t *reg_info = reg->arch_info;
+       struct target *target = reg_info->target;
        uint64_t value;
        int result = riscv_get_register(target, &value, reg->number);
        if (result != ERROR_OK)
                return result;
        buf_set_u64(reg->value, 0, reg->size, value);
+       /* CSRs (and possibly other extension) registers may change value at any
+        * time. */
+       if (reg->number <= GDB_REGNO_XPR31 ||
+                       (reg->number >= GDB_REGNO_FPR0 && reg->number <= GDB_REGNO_FPR31) ||
+                       reg->number == GDB_REGNO_PC)
+               reg->valid = true;
+       LOG_DEBUG("[%d]{%d} read 0x%" PRIx64 " from %s (valid=%d)",
+                       target->coreid, riscv_current_hartid(target), value, reg->name,
+                       reg->valid);
        return ERROR_OK;
 }
 
 static int register_set(struct reg *reg, uint8_t *buf)
 {
-       struct target *target = (struct target *) reg->arch_info;
+       riscv_reg_info_t *reg_info = reg->arch_info;
+       struct target *target = reg_info->target;
 
        uint64_t value = buf_get_u64(buf, 0, reg->size);
 
-       LOG_DEBUG("write 0x%" PRIx64 " to %s", value, reg->name);
+       LOG_DEBUG("[%d]{%d} write 0x%" PRIx64 " to %s (valid=%d)",
+                       target->coreid, riscv_current_hartid(target), value, reg->name,
+                       reg->valid);
        struct reg *r = &target->reg_cache->reg_list[reg->number];
-       r->valid = true;
+       /* CSRs (and possibly other extension) registers may change value at any
+        * time. */
+       if (reg->number <= GDB_REGNO_XPR31 ||
+                       (reg->number >= GDB_REGNO_FPR0 && reg->number <= GDB_REGNO_FPR31) ||
+                       reg->number == GDB_REGNO_PC)
+               r->valid = true;
        memcpy(r->value, buf, (r->size + 7) / 8);
 
        riscv_set_register(target, reg->number, value);
@@ -2101,12 +2513,26 @@ int riscv_init_registers(struct target *target)
        target->reg_cache->name = "RISC-V Registers";
        target->reg_cache->num_regs = GDB_REGNO_COUNT;
 
-       target->reg_cache->reg_list = calloc(GDB_REGNO_COUNT, sizeof(struct reg));
+       if (expose_custom) {
+               for (unsigned i = 0; expose_custom[i].low <= expose_custom[i].high; i++) {
+                       for (unsigned number = expose_custom[i].low;
+                                       number <= expose_custom[i].high;
+                                       number++)
+                               target->reg_cache->num_regs++;
+               }
+       }
+
+       LOG_DEBUG("create register cache for %d registers",
+                       target->reg_cache->num_regs);
+
+       target->reg_cache->reg_list =
+               calloc(target->reg_cache->num_regs, sizeof(struct reg));
 
        const unsigned int max_reg_name_len = 12;
        if (info->reg_names)
                free(info->reg_names);
-       info->reg_names = calloc(1, GDB_REGNO_COUNT * max_reg_name_len);
+       info->reg_names =
+               calloc(target->reg_cache->num_regs, max_reg_name_len);
        char *reg_name = info->reg_names;
 
        static struct reg_feature feature_cpu = {
@@ -2121,6 +2547,9 @@ int riscv_init_registers(struct target *target)
        static struct reg_feature feature_virtual = {
                .name = "org.gnu.gdb.riscv.virtual"
        };
+       static struct reg_feature feature_custom = {
+               .name = "org.gnu.gdb.riscv.custom"
+       };
 
        static struct reg_data_type type_ieee_single = {
                .type = REG_TYPE_IEEE_SINGLE,
@@ -2139,18 +2568,24 @@ int riscv_init_registers(struct target *target)
        qsort(csr_info, DIM(csr_info), sizeof(*csr_info), cmp_csr_info);
        unsigned csr_info_index = 0;
 
-       /* When gdb request register N, gdb_get_register_packet() assumes that this
+       unsigned custom_range_index = 0;
+       int custom_within_range = 0;
+
+       riscv_reg_info_t *shared_reg_info = calloc(1, sizeof(riscv_reg_info_t));
+       shared_reg_info->target = target;
+
+       /* When gdb requests register N, gdb_get_register_packet() assumes that this
         * is register at index N in reg_list. So if there are certain registers
         * that don't exist, we need to leave holes in the list (or renumber, but
         * it would be nice not to have yet another set of numbers to translate
         * between). */
-       for (uint32_t number = 0; number < GDB_REGNO_COUNT; number++) {
+       for (uint32_t number = 0; number < target->reg_cache->num_regs; number++) {
                struct reg *r = &target->reg_cache->reg_list[number];
                r->dirty = false;
                r->valid = false;
                r->exist = true;
                r->type = &riscv_reg_arch_type;
-               r->arch_info = target;
+               r->arch_info = shared_reg_info;
                r->number = number;
                r->size = riscv_xlen(target);
                /* r->size is set in riscv_invalidate_register_cache, maybe because the
@@ -2510,11 +2945,35 @@ int riscv_init_registers(struct target *target)
                        r->group = "general";
                        r->feature = &feature_virtual;
                        r->size = 8;
+
+               } else {
+                       /* Custom registers. */
+                       assert(expose_custom);
+
+                       range_t *range = &expose_custom[custom_range_index];
+                       assert(range->low <= range->high);
+                       unsigned custom_number = range->low + custom_within_range;
+
+                       r->group = "custom";
+                       r->feature = &feature_custom;
+                       r->arch_info = calloc(1, sizeof(riscv_reg_info_t));
+                       assert(r->arch_info);
+                       ((riscv_reg_info_t *) r->arch_info)->target = target;
+                       ((riscv_reg_info_t *) r->arch_info)->custom_number = custom_number;
+                       sprintf(reg_name, "custom%d", custom_number);
+
+                       custom_within_range++;
+                       if (custom_within_range > range->high - range->low) {
+                               custom_within_range = 0;
+                               custom_range_index++;
+                       }
                }
+
                if (reg_name[0])
                        r->name = reg_name;
                reg_name += strlen(reg_name) + 1;
-               assert(reg_name < info->reg_names + GDB_REGNO_COUNT * max_reg_name_len);
+               assert(reg_name < info->reg_names + target->reg_cache->num_regs *
+                               max_reg_name_len);
                r->value = &info->reg_cache_values[number];
        }
 
index 31f3cf63c47dd36ad03d52091f871e555ed97a7d..59414fc0886a0510e862ad6abc83127fadc0ef50 100644 (file)
@@ -35,6 +35,11 @@ enum riscv_halt_reason {
        RISCV_HALT_ERROR
 };
 
+typedef struct {
+       struct target *target;
+       unsigned custom_number;
+} riscv_reg_info_t;
+
 typedef struct {
        unsigned dtm_version;
 
@@ -91,6 +96,10 @@ typedef struct {
 
        bool triggers_enumerated;
 
+       /* Decremented every scan, and when it reaches 0 we clear the learned
+        * delays, causing them to be relearned. Used for testing. */
+       int reset_delays_wait;
+
        /* Helper functions that target the various RISC-V debug spec
         * implementations. */
        int (*get_register)(struct target *target,
@@ -120,6 +129,11 @@ typedef struct {
 
        int (*dmi_read)(struct target *target, uint32_t *value, uint32_t address);
        int (*dmi_write)(struct target *target, uint32_t address, uint32_t value);
+
+       int (*test_sba_config_reg)(struct target *target, target_addr_t legal_address,
+                       uint32_t num_words, target_addr_t illegal_address, bool run_sbbusyerror_test);
+
+       int (*test_compliance)(struct target *target);
 } riscv_info_t;
 
 /* Wall-clock timeout for a command/access. Settable via RISC-V Target commands.*/
@@ -137,11 +151,11 @@ static inline riscv_info_t *riscv_info(const struct target *target)
 { return target->arch_info; }
 #define RISCV_INFO(R) riscv_info_t *R = riscv_info(target);
 
-extern uint8_t ir_dtmcontrol[1];
+extern uint8_t ir_dtmcontrol[4];
 extern struct scan_field select_dtmcontrol;
-extern uint8_t ir_dbus[1];
+extern uint8_t ir_dbus[4];
 extern struct scan_field select_dbus;
-extern uint8_t ir_idcode[1];
+extern uint8_t ir_idcode[4];
 extern struct scan_field select_idcode;
 
 /*** OpenOCD Interface */
@@ -253,6 +267,7 @@ int riscv_remove_breakpoint(struct target *target,
 int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint);
 int riscv_remove_watchpoint(struct target *target,
                struct watchpoint *watchpoint);
+int riscv_hit_watchpoint(struct target *target, struct watchpoint **hit_wp_address);
 
 int riscv_init_registers(struct target *target);
 

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)