Lots of RISC-V improvements.
[fw/openocd] / src / target / riscv / riscv.c
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];
        }