Lots of RISC-V improvements.
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);