Add RISC-V support.
[openocd.git] / src / target / riscv / riscv.c
diff --git a/src/target/riscv/riscv.c b/src/target/riscv/riscv.c
new file mode 100644 (file)
index 0000000..8d76c4a
--- /dev/null
@@ -0,0 +1,2512 @@
+#include <assert.h>
+#include <stdlib.h>
+#include <time.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "target/target.h"
+#include "target/algorithm.h"
+#include "target/target_type.h"
+#include "log.h"
+#include "jtag/jtag.h"
+#include "target/register.h"
+#include "target/breakpoints.h"
+#include "helper/time_support.h"
+#include "riscv.h"
+#include "gdb_regs.h"
+#include "rtos/rtos.h"
+
+/**
+ * Since almost everything can be accomplish by scanning the dbus register, all
+ * functions here assume dbus is already selected. The exception are functions
+ * called directly by OpenOCD, which can't assume anything about what's
+ * currently in IR. They should set IR to dbus explicitly.
+ */
+
+/**
+ * Code structure
+ *
+ * At the bottom of the stack are the OpenOCD JTAG functions:
+ *             jtag_add_[id]r_scan
+ *             jtag_execute_query
+ *             jtag_add_runtest
+ *
+ * There are a few functions to just instantly shift a register and get its
+ * value:
+ *             dtmcontrol_scan
+ *             idcode_scan
+ *             dbus_scan
+ *
+ * Because doing one scan and waiting for the result is slow, most functions
+ * batch up a bunch of dbus writes and then execute them all at once. They use
+ * the scans "class" for this:
+ *             scans_new
+ *             scans_delete
+ *             scans_execute
+ *             scans_add_...
+ * Usually you new(), call a bunch of add functions, then execute() and look
+ * at the results by calling scans_get...()
+ *
+ * Optimized functions will directly use the scans class above, but slightly
+ * lazier code will use the cache functions that in turn use the scans
+ * functions:
+ *             cache_get...
+ *             cache_set...
+ *             cache_write
+ * cache_set... update a local structure, which is then synced to the target
+ * with cache_write(). Only Debug RAM words that are actually changed are sent
+ * to the target. Afterwards use cache_get... to read results.
+ */
+
+#define get_field(reg, mask) (((reg) & (mask)) / ((mask) & ~((mask) << 1)))
+#define set_field(reg, mask, val) (((reg) & ~(mask)) | (((val) * ((mask) & ~((mask) << 1))) & (mask)))
+
+#define DIM(x)         (sizeof(x)/sizeof(*x))
+
+/* Constants for legacy SiFive hardware breakpoints. */
+#define CSR_BPCONTROL_X                        (1<<0)
+#define CSR_BPCONTROL_W                        (1<<1)
+#define CSR_BPCONTROL_R                        (1<<2)
+#define CSR_BPCONTROL_U                        (1<<3)
+#define CSR_BPCONTROL_S                        (1<<4)
+#define CSR_BPCONTROL_H                        (1<<5)
+#define CSR_BPCONTROL_M                        (1<<6)
+#define CSR_BPCONTROL_BPMATCH  (0xf<<7)
+#define CSR_BPCONTROL_BPACTION (0xff<<11)
+
+#define DEBUG_ROM_START         0x800
+#define DEBUG_ROM_RESUME       (DEBUG_ROM_START + 4)
+#define DEBUG_ROM_EXCEPTION    (DEBUG_ROM_START + 8)
+#define DEBUG_RAM_START         0x400
+
+#define SETHALTNOT                             0x10c
+
+/*** JTAG registers. ***/
+
+#define DTMCONTROL                                     0x10
+#define DTMCONTROL_DBUS_RESET          (1<<16)
+#define DTMCONTROL_IDLE                                (7<<10)
+#define DTMCONTROL_ADDRBITS                    (0xf<<4)
+#define DTMCONTROL_VERSION                     (0xf)
+
+#define DBUS                                           0x11
+#define DBUS_OP_START                          0
+#define DBUS_OP_SIZE                           2
+typedef enum {
+       DBUS_OP_NOP = 0,
+       DBUS_OP_READ = 1,
+       DBUS_OP_WRITE = 2
+} dbus_op_t;
+typedef enum {
+       DBUS_STATUS_SUCCESS = 0,
+       DBUS_STATUS_FAILED = 2,
+       DBUS_STATUS_BUSY = 3
+} dbus_status_t;
+#define DBUS_DATA_START                                2
+#define DBUS_DATA_SIZE                         34
+#define DBUS_ADDRESS_START                     36
+
+typedef enum {
+       RE_OK,
+       RE_FAIL,
+       RE_AGAIN
+} riscv_error_t;
+
+typedef enum slot {
+       SLOT0,
+       SLOT1,
+       SLOT_LAST,
+} slot_t;
+
+/*** Debug Bus registers. ***/
+
+#define DMCONTROL                              0x10
+#define DMCONTROL_INTERRUPT            (((uint64_t)1)<<33)
+#define DMCONTROL_HALTNOT              (((uint64_t)1)<<32)
+#define DMCONTROL_BUSERROR             (7<<19)
+#define DMCONTROL_SERIAL               (3<<16)
+#define DMCONTROL_AUTOINCREMENT        (1<<15)
+#define DMCONTROL_ACCESS               (7<<12)
+#define DMCONTROL_HARTID               (0x3ff<<2)
+#define DMCONTROL_NDRESET              (1<<1)
+#define DMCONTROL_FULLRESET            1
+
+#define DMINFO                                 0x11
+#define DMINFO_ABUSSIZE                        (0x7fU<<25)
+#define DMINFO_SERIALCOUNT             (0xf<<21)
+#define DMINFO_ACCESS128               (1<<20)
+#define DMINFO_ACCESS64                        (1<<19)
+#define DMINFO_ACCESS32                        (1<<18)
+#define DMINFO_ACCESS16                        (1<<17)
+#define DMINFO_ACCESS8                 (1<<16)
+#define DMINFO_DRAMSIZE                        (0x3f<<10)
+#define DMINFO_AUTHENTICATED   (1<<5)
+#define DMINFO_AUTHBUSY                        (1<<4)
+#define DMINFO_AUTHTYPE                        (3<<2)
+#define DMINFO_VERSION                 3
+
+/*** Info about the core being debugged. ***/
+
+#define DBUS_ADDRESS_UNKNOWN   0xffff
+
+#define MAX_HWBPS                      16
+#define DRAM_CACHE_SIZE                16
+
+uint8_t ir_dtmcontrol[1] = {DTMCONTROL};
+struct scan_field select_dtmcontrol = {
+       .in_value = NULL,
+       .out_value = ir_dtmcontrol
+};
+uint8_t ir_dbus[1] = {DBUS};
+struct scan_field select_dbus = {
+       .in_value = NULL,
+       .out_value = ir_dbus
+};
+uint8_t ir_idcode[1] = {0x1};
+struct scan_field select_idcode = {
+       .in_value = NULL,
+       .out_value = ir_idcode
+};
+
+struct trigger {
+       uint64_t address;
+       uint32_t length;
+       uint64_t mask;
+       uint64_t value;
+       bool read, write, execute;
+       int unique_id;
+};
+
+/* Wall-clock timeout for a command/access. Settable via RISC-V Target commands.*/
+int riscv_command_timeout_sec = DEFAULT_COMMAND_TIMEOUT_SEC;
+
+/* Wall-clock timeout after reset. Settable via RISC-V Target commands.*/
+int riscv_reset_timeout_sec = DEFAULT_RESET_TIMEOUT_SEC;
+
+bool riscv_prefer_sba;
+
+/* 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;
+
+static uint32_t dtmcontrol_scan(struct target *target, uint32_t out)
+{
+       struct scan_field field;
+       uint8_t in_value[4];
+       uint8_t out_value[4];
+
+       buf_set_u32(out_value, 0, 32, out);
+
+       jtag_add_ir_scan(target->tap, &select_dtmcontrol, TAP_IDLE);
+
+       field.num_bits = 32;
+       field.out_value = out_value;
+       field.in_value = in_value;
+       jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);
+
+       /* Always return to dbus. */
+       jtag_add_ir_scan(target->tap, &select_dbus, TAP_IDLE);
+
+       int retval = jtag_execute_queue();
+       if (retval != ERROR_OK) {
+               LOG_ERROR("failed jtag scan: %d", retval);
+               return retval;
+       }
+
+       uint32_t in = buf_get_u32(field.in_value, 0, 32);
+       LOG_DEBUG("DTMCONTROL: 0x%x -> 0x%x", out, in);
+
+       return in;
+}
+
+static struct target_type *get_target_type(struct target *target)
+{
+       riscv_info_t *info = (riscv_info_t *) target->arch_info;
+
+       if (!info) {
+               LOG_ERROR("Target has not been initialized");
+               return NULL;
+       }
+
+       switch (info->dtm_version) {
+               case 0:
+                       return &riscv011_target;
+               case 1:
+                       return &riscv013_target;
+               default:
+                       LOG_ERROR("Unsupported DTM version: %d", info->dtm_version);
+                       return NULL;
+       }
+}
+
+static int riscv_init_target(struct command_context *cmd_ctx,
+               struct target *target)
+{
+       LOG_DEBUG("riscv_init_target()");
+       target->arch_info = calloc(1, sizeof(riscv_info_t));
+       if (!target->arch_info)
+               return ERROR_FAIL;
+       riscv_info_t *info = (riscv_info_t *) target->arch_info;
+       riscv_info_init(target, info);
+       info->cmd_ctx = cmd_ctx;
+
+       select_dtmcontrol.num_bits = target->tap->ir_length;
+       select_dbus.num_bits = target->tap->ir_length;
+       select_idcode.num_bits = target->tap->ir_length;
+
+       riscv_semihosting_init(target);
+
+       return ERROR_OK;
+}
+
+static void riscv_deinit_target(struct target *target)
+{
+       LOG_DEBUG("riscv_deinit_target()");
+       struct target_type *tt = get_target_type(target);
+       if (tt) {
+               tt->deinit_target(target);
+               riscv_info_t *info = (riscv_info_t *) target->arch_info;
+               free(info);
+       }
+       target->arch_info = NULL;
+}
+
+static int oldriscv_halt(struct target *target)
+{
+       struct target_type *tt = get_target_type(target);
+       return tt->halt(target);
+}
+
+static void trigger_from_breakpoint(struct trigger *trigger,
+               const struct breakpoint *breakpoint)
+{
+       trigger->address = breakpoint->address;
+       trigger->length = breakpoint->length;
+       trigger->mask = ~0LL;
+       trigger->read = false;
+       trigger->write = false;
+       trigger->execute = true;
+       /* unique_id is unique across both breakpoints and watchpoints. */
+       trigger->unique_id = breakpoint->unique_id;
+}
+
+static int maybe_add_trigger_t1(struct target *target, unsigned hartid,
+               struct trigger *trigger, uint64_t tdata1)
+{
+       RISCV_INFO(r);
+
+       const uint32_t bpcontrol_x = 1<<0;
+       const uint32_t bpcontrol_w = 1<<1;
+       const uint32_t bpcontrol_r = 1<<2;
+       const uint32_t bpcontrol_u = 1<<3;
+       const uint32_t bpcontrol_s = 1<<4;
+       const uint32_t bpcontrol_h = 1<<5;
+       const uint32_t bpcontrol_m = 1<<6;
+       const uint32_t bpcontrol_bpmatch = 0xf << 7;
+       const uint32_t bpcontrol_bpaction = 0xff << 11;
+
+       if (tdata1 & (bpcontrol_r | bpcontrol_w | bpcontrol_x)) {
+               /* Trigger is already in use, presumably by user code. */
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       }
+
+       tdata1 = set_field(tdata1, bpcontrol_r, trigger->read);
+       tdata1 = set_field(tdata1, bpcontrol_w, trigger->write);
+       tdata1 = set_field(tdata1, bpcontrol_x, trigger->execute);
+       tdata1 = set_field(tdata1, bpcontrol_u,
+                       !!(r->misa[hartid] & (1 << ('U' - 'A'))));
+       tdata1 = set_field(tdata1, bpcontrol_s,
+                       !!(r->misa[hartid] & (1 << ('S' - 'A'))));
+       tdata1 = set_field(tdata1, bpcontrol_h,
+                       !!(r->misa[hartid] & (1 << ('H' - 'A'))));
+       tdata1 |= bpcontrol_m;
+       tdata1 = set_field(tdata1, bpcontrol_bpmatch, 0); /* exact match */
+       tdata1 = set_field(tdata1, bpcontrol_bpaction, 0); /* cause bp exception */
+
+       riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, tdata1);
+
+       riscv_reg_t tdata1_rb;
+       if (riscv_get_register_on_hart(target, &tdata1_rb, hartid,
+                               GDB_REGNO_TDATA1) != ERROR_OK)
+               return ERROR_FAIL;
+       LOG_DEBUG("tdata1=0x%" PRIx64, tdata1_rb);
+
+       if (tdata1 != tdata1_rb) {
+               LOG_DEBUG("Trigger doesn't support what we need; After writing 0x%"
+                               PRIx64 " to tdata1 it contains 0x%" PRIx64,
+                               tdata1, tdata1_rb);
+               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       }
+
+       riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA2, trigger->address);
+
+       return ERROR_OK;
+}
+
+static int maybe_add_trigger_t2(struct target *target, unsigned hartid,
+               struct trigger *trigger, uint64_t tdata1)
+{
+       RISCV_INFO(r);
+
+       /* tselect is already set */
+       if (tdata1 & (MCONTROL_EXECUTE | MCONTROL_STORE | MCONTROL_LOAD)) {
+               /* Trigger is already in use, presumably by user code. */
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       }
+
+       /* address/data match trigger */
+       tdata1 |= MCONTROL_DMODE(riscv_xlen(target));
+       tdata1 = set_field(tdata1, MCONTROL_ACTION,
+                       MCONTROL_ACTION_DEBUG_MODE);
+       tdata1 = set_field(tdata1, MCONTROL_MATCH, MCONTROL_MATCH_EQUAL);
+       tdata1 |= MCONTROL_M;
+       if (r->misa[hartid] & (1 << ('H' - 'A')))
+               tdata1 |= MCONTROL_H;
+       if (r->misa[hartid] & (1 << ('S' - 'A')))
+               tdata1 |= MCONTROL_S;
+       if (r->misa[hartid] & (1 << ('U' - 'A')))
+               tdata1 |= MCONTROL_U;
+
+       if (trigger->execute)
+               tdata1 |= MCONTROL_EXECUTE;
+       if (trigger->read)
+               tdata1 |= MCONTROL_LOAD;
+       if (trigger->write)
+               tdata1 |= MCONTROL_STORE;
+
+       riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, tdata1);
+
+       uint64_t tdata1_rb;
+       int result = riscv_get_register_on_hart(target, &tdata1_rb, hartid, GDB_REGNO_TDATA1);
+       if (result != ERROR_OK)
+               return result;
+       LOG_DEBUG("tdata1=0x%" PRIx64, tdata1_rb);
+
+       if (tdata1 != tdata1_rb) {
+               LOG_DEBUG("Trigger doesn't support what we need; After writing 0x%"
+                               PRIx64 " to tdata1 it contains 0x%" PRIx64,
+                               tdata1, tdata1_rb);
+               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       }
+
+       riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA2, trigger->address);
+
+       return ERROR_OK;
+}
+
+static int add_trigger(struct target *target, struct trigger *trigger)
+{
+       RISCV_INFO(r);
+
+       if (riscv_enumerate_triggers(target) != ERROR_OK)
+               return ERROR_FAIL;
+
+       /* In RTOS mode, we need to set the same trigger in the same slot on every
+        * hart, to keep up the illusion that each hart is a thread running on the
+        * same core. */
+
+       /* Otherwise, we just set the trigger on the one hart this target deals
+        * with. */
+
+       riscv_reg_t tselect[RISCV_MAX_HARTS];
+
+       int first_hart = -1;
+       for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
+               if (!riscv_hart_enabled(target, hartid))
+                       continue;
+               if (first_hart < 0)
+                       first_hart = hartid;
+               int result = riscv_get_register_on_hart(target, &tselect[hartid],
+                               hartid, GDB_REGNO_TSELECT);
+               if (result != ERROR_OK)
+                       return result;
+       }
+       assert(first_hart >= 0);
+
+       unsigned int i;
+       for (i = 0; i < r->trigger_count[first_hart]; i++) {
+               if (r->trigger_unique_id[i] != -1)
+                       continue;
+
+               riscv_set_register_on_hart(target, first_hart, GDB_REGNO_TSELECT, i);
+
+               uint64_t tdata1;
+               int result = riscv_get_register_on_hart(target, &tdata1, first_hart,
+                               GDB_REGNO_TDATA1);
+               if (result != ERROR_OK)
+                       return result;
+               int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
+
+               result = ERROR_OK;
+               for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
+                       if (!riscv_hart_enabled(target, hartid))
+                               continue;
+                       if (hartid > first_hart)
+                               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, i);
+                       switch (type) {
+                               case 1:
+                                       result = maybe_add_trigger_t1(target, hartid, trigger, tdata1);
+                                       break;
+                               case 2:
+                                       result = maybe_add_trigger_t2(target, hartid, trigger, tdata1);
+                                       break;
+                               default:
+                                       LOG_DEBUG("trigger %d has unknown type %d", i, type);
+                                       continue;
+                       }
+
+                       if (result != ERROR_OK)
+                               continue;
+               }
+
+               if (result != ERROR_OK)
+                       continue;
+
+               LOG_DEBUG("Using trigger %d (type %d) for bp %d", i, type,
+                               trigger->unique_id);
+               r->trigger_unique_id[i] = trigger->unique_id;
+               break;
+       }
+
+       for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
+               if (!riscv_hart_enabled(target, hartid))
+                       continue;
+               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT,
+                               tselect[hartid]);
+       }
+
+       if (i >= r->trigger_count[first_hart]) {
+               LOG_ERROR("Couldn't find an available hardware trigger.");
+               return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+       }
+
+       return ERROR_OK;
+}
+
+int riscv_add_breakpoint(struct target *target, struct breakpoint *breakpoint)
+{
+       if (breakpoint->type == BKPT_SOFT) {
+               if (target_read_memory(target, breakpoint->address, breakpoint->length, 1,
+                                       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());
+               if (retval != ERROR_OK) {
+                       LOG_ERROR("Failed to write %d-byte breakpoint instruction at 0x%"
+                                       TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
+                       return ERROR_FAIL;
+               }
+
+       } else if (breakpoint->type == BKPT_HARD) {
+               struct trigger trigger;
+               trigger_from_breakpoint(&trigger, breakpoint);
+               int 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;
+}
+
+static int remove_trigger(struct target *target, struct trigger *trigger)
+{
+       RISCV_INFO(r);
+
+       if (riscv_enumerate_triggers(target) != ERROR_OK)
+               return ERROR_FAIL;
+
+       int first_hart = -1;
+       for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
+               if (!riscv_hart_enabled(target, hartid))
+                       continue;
+               if (first_hart < 0) {
+                       first_hart = hartid;
+                       break;
+               }
+       }
+       assert(first_hart >= 0);
+
+       unsigned int i;
+       for (i = 0; i < r->trigger_count[first_hart]; i++) {
+               if (r->trigger_unique_id[i] == trigger->unique_id)
+                       break;
+       }
+       if (i >= r->trigger_count[first_hart]) {
+               LOG_ERROR("Couldn't find the hardware resources used by hardware "
+                               "trigger.");
+               return ERROR_FAIL;
+       }
+       LOG_DEBUG("Stop using resource %d for bp %d", i, trigger->unique_id);
+       for (int hartid = first_hart; hartid < riscv_count_harts(target); ++hartid) {
+               if (!riscv_hart_enabled(target, hartid))
+                       continue;
+               riscv_reg_t tselect;
+               int result = riscv_get_register_on_hart(target, &tselect, hartid, GDB_REGNO_TSELECT);
+               if (result != ERROR_OK)
+                       return result;
+               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, i);
+               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
+               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
+       }
+       r->trigger_unique_id[i] = -1;
+
+       return ERROR_OK;
+}
+
+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,
+                                       breakpoint->orig_instr) != ERROR_OK) {
+                       LOG_ERROR("Failed to restore instruction for %d-byte breakpoint at "
+                                       "0x%" TARGET_PRIxADDR, breakpoint->length, breakpoint->address);
+                       return ERROR_FAIL;
+               }
+
+       } else if (breakpoint->type == BKPT_HARD) {
+               struct trigger trigger;
+               trigger_from_breakpoint(&trigger, breakpoint);
+               int result = remove_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 = false;
+
+       return ERROR_OK;
+}
+
+static void trigger_from_watchpoint(struct trigger *trigger,
+               const struct watchpoint *watchpoint)
+{
+       trigger->address = watchpoint->address;
+       trigger->length = watchpoint->length;
+       trigger->mask = watchpoint->mask;
+       trigger->value = watchpoint->value;
+       trigger->read = (watchpoint->rw == WPT_READ || watchpoint->rw == WPT_ACCESS);
+       trigger->write = (watchpoint->rw == WPT_WRITE || watchpoint->rw == WPT_ACCESS);
+       trigger->execute = false;
+       /* unique_id is unique across both breakpoints and watchpoints. */
+       trigger->unique_id = watchpoint->unique_id;
+}
+
+int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint)
+{
+       struct trigger trigger;
+       trigger_from_watchpoint(&trigger, watchpoint);
+
+       int result = add_trigger(target, &trigger);
+       if (result != ERROR_OK)
+               return result;
+       watchpoint->set = true;
+
+       return ERROR_OK;
+}
+
+int riscv_remove_watchpoint(struct target *target,
+               struct watchpoint *watchpoint)
+{
+       struct trigger trigger;
+       trigger_from_watchpoint(&trigger, watchpoint);
+
+       int result = remove_trigger(target, &trigger);
+       if (result != ERROR_OK)
+               return result;
+       watchpoint->set = false;
+
+       return ERROR_OK;
+}
+
+static int oldriscv_step(struct target *target, int current, uint32_t address,
+               int handle_breakpoints)
+{
+       struct target_type *tt = get_target_type(target);
+       return tt->step(target, current, address, handle_breakpoints);
+}
+
+static int old_or_new_riscv_step(
+               struct target *target,
+               int current,
+               target_addr_t address,
+               int handle_breakpoints
+){
+       RISCV_INFO(r);
+       LOG_DEBUG("handle_breakpoints=%d", handle_breakpoints);
+       if (r->is_halted == NULL)
+               return oldriscv_step(target, current, address, handle_breakpoints);
+       else
+               return riscv_openocd_step(target, current, address, handle_breakpoints);
+}
+
+
+static int riscv_examine(struct target *target)
+{
+       LOG_DEBUG("riscv_examine()");
+       if (target_was_examined(target)) {
+               LOG_DEBUG("Target was already examined.");
+               return ERROR_OK;
+       }
+
+       /* Don't need to select dbus, since the first thing we do is read dtmcontrol. */
+
+       riscv_info_t *info = (riscv_info_t *) target->arch_info;
+       uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
+       LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
+       info->dtm_version = get_field(dtmcontrol, DTMCONTROL_VERSION);
+       LOG_DEBUG("  version=0x%x", info->dtm_version);
+
+       struct target_type *tt = get_target_type(target);
+       if (tt == NULL)
+               return ERROR_FAIL;
+
+       int result = tt->init_target(info->cmd_ctx, target);
+       if (result != ERROR_OK)
+               return result;
+
+       return tt->examine(target);
+}
+
+static int oldriscv_poll(struct target *target)
+{
+       struct target_type *tt = get_target_type(target);
+       return tt->poll(target);
+}
+
+static int old_or_new_riscv_poll(struct target *target)
+{
+       RISCV_INFO(r);
+       if (r->is_halted == NULL)
+               return oldriscv_poll(target);
+       else
+               return riscv_openocd_poll(target);
+}
+
+static int old_or_new_riscv_halt(struct target *target)
+{
+       RISCV_INFO(r);
+       if (r->is_halted == NULL)
+               return oldriscv_halt(target);
+       else
+               return riscv_openocd_halt(target);
+}
+
+static int riscv_assert_reset(struct target *target)
+{
+       struct target_type *tt = get_target_type(target);
+       return tt->assert_reset(target);
+}
+
+static int riscv_deassert_reset(struct target *target)
+{
+       LOG_DEBUG("RISCV DEASSERT RESET");
+       struct target_type *tt = get_target_type(target);
+       return tt->deassert_reset(target);
+}
+
+
+static int oldriscv_resume(struct target *target, int current, uint32_t address,
+               int handle_breakpoints, int debug_execution)
+{
+       struct target_type *tt = get_target_type(target);
+       return tt->resume(target, current, address, handle_breakpoints,
+                       debug_execution);
+}
+
+static int old_or_new_riscv_resume(
+               struct target *target,
+               int current,
+               target_addr_t address,
+               int handle_breakpoints,
+               int debug_execution
+){
+       RISCV_INFO(r);
+       LOG_DEBUG("handle_breakpoints=%d", handle_breakpoints);
+       if (r->is_halted == NULL)
+               return oldriscv_resume(target, current, address, handle_breakpoints, debug_execution);
+       else
+               return riscv_openocd_resume(target, current, address, handle_breakpoints, debug_execution);
+}
+
+static int riscv_select_current_hart(struct target *target)
+{
+       RISCV_INFO(r);
+       if (r->rtos_hartid != -1 && riscv_rtos_enabled(target))
+               return riscv_set_current_hartid(target, r->rtos_hartid);
+       else
+               return riscv_set_current_hartid(target, target->coreid);
+}
+
+static int riscv_read_memory(struct target *target, target_addr_t address,
+               uint32_t size, uint32_t count, uint8_t *buffer)
+{
+       if (riscv_select_current_hart(target) != ERROR_OK)
+               return ERROR_FAIL;
+       struct target_type *tt = get_target_type(target);
+       return tt->read_memory(target, address, size, count, buffer);
+}
+
+static int riscv_write_memory(struct target *target, target_addr_t address,
+               uint32_t size, uint32_t count, const uint8_t *buffer)
+{
+       if (riscv_select_current_hart(target) != ERROR_OK)
+               return ERROR_FAIL;
+       struct target_type *tt = get_target_type(target);
+       return tt->write_memory(target, address, size, count, buffer);
+}
+
+static int riscv_get_gdb_reg_list(struct target *target,
+               struct reg **reg_list[], int *reg_list_size,
+               enum target_register_class reg_class)
+{
+       RISCV_INFO(r);
+       LOG_DEBUG("reg_class=%d", reg_class);
+       LOG_DEBUG("rtos_hartid=%d current_hartid=%d", r->rtos_hartid, r->current_hartid);
+
+       if (!target->reg_cache) {
+               LOG_ERROR("Target not initialized. Return ERROR_FAIL.");
+               return ERROR_FAIL;
+       }
+
+       if (riscv_select_current_hart(target) != ERROR_OK)
+               return ERROR_FAIL;
+
+       switch (reg_class) {
+               case REG_CLASS_GENERAL:
+                       *reg_list_size = 32;
+                       break;
+               case REG_CLASS_ALL:
+                       *reg_list_size = GDB_REGNO_COUNT;
+                       break;
+               default:
+                       LOG_ERROR("Unsupported reg_class: %d", reg_class);
+                       return ERROR_FAIL;
+       }
+
+       *reg_list = calloc(*reg_list_size, sizeof(struct reg *));
+       if (!*reg_list)
+               return ERROR_FAIL;
+
+       for (int i = 0; i < *reg_list_size; i++) {
+               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];
+       }
+
+       return ERROR_OK;
+}
+
+static int riscv_arch_state(struct target *target)
+{
+       struct target_type *tt = get_target_type(target);
+       return tt->arch_state(target);
+}
+
+/* Algorithm must end with a software breakpoint instruction. */
+static int riscv_run_algorithm(struct target *target, int num_mem_params,
+               struct mem_param *mem_params, int num_reg_params,
+               struct reg_param *reg_params, target_addr_t entry_point,
+               target_addr_t exit_point, int timeout_ms, void *arch_info)
+{
+       riscv_info_t *info = (riscv_info_t *) target->arch_info;
+
+       if (num_mem_params > 0) {
+               LOG_ERROR("Memory parameters are not supported for RISC-V algorithms.");
+               return ERROR_FAIL;
+       }
+
+       if (target->state != TARGET_HALTED) {
+               LOG_WARNING("target not halted");
+               return ERROR_TARGET_NOT_HALTED;
+       }
+
+       /* Save registers */
+       struct reg *reg_pc = register_get_by_name(target->reg_cache, "pc", 1);
+       if (!reg_pc || reg_pc->type->get(reg_pc) != ERROR_OK)
+               return ERROR_FAIL;
+       uint64_t saved_pc = buf_get_u64(reg_pc->value, 0, reg_pc->size);
+
+       uint64_t saved_regs[32];
+       for (int i = 0; i < num_reg_params; i++) {
+               LOG_DEBUG("save %s", reg_params[i].reg_name);
+               struct reg *r = register_get_by_name(target->reg_cache, reg_params[i].reg_name, 0);
+               if (!r) {
+                       LOG_ERROR("Couldn't find register named '%s'", reg_params[i].reg_name);
+                       return ERROR_FAIL;
+               }
+
+               if (r->size != reg_params[i].size) {
+                       LOG_ERROR("Register %s is %d bits instead of %d bits.",
+                                       reg_params[i].reg_name, r->size, reg_params[i].size);
+                       return ERROR_FAIL;
+               }
+
+               if (r->number > GDB_REGNO_XPR31) {
+                       LOG_ERROR("Only GPRs can be use as argument registers.");
+                       return ERROR_FAIL;
+               }
+
+               if (r->type->get(r) != ERROR_OK)
+                       return ERROR_FAIL;
+               saved_regs[r->number] = buf_get_u64(r->value, 0, r->size);
+               if (r->type->set(r, reg_params[i].value) != ERROR_OK)
+                       return ERROR_FAIL;
+       }
+
+
+       /* Disable Interrupts before attempting to run the algorithm. */
+       uint64_t current_mstatus;
+       uint8_t mstatus_bytes[8];
+
+       LOG_DEBUG("Disabling Interrupts");
+       struct reg *reg_mstatus = register_get_by_name(target->reg_cache,
+                       "mstatus", 1);
+       if (!reg_mstatus) {
+               LOG_ERROR("Couldn't find mstatus!");
+               return ERROR_FAIL;
+       }
+
+       reg_mstatus->type->get(reg_mstatus);
+       current_mstatus = buf_get_u64(reg_mstatus->value, 0, reg_mstatus->size);
+       uint64_t ie_mask = MSTATUS_MIE | MSTATUS_HIE | MSTATUS_SIE | MSTATUS_UIE;
+       buf_set_u64(mstatus_bytes, 0, info->xlen[0], set_field(current_mstatus,
+                               ie_mask, 0));
+
+       reg_mstatus->type->set(reg_mstatus, mstatus_bytes);
+
+       /* Run algorithm */
+       LOG_DEBUG("resume at 0x%" TARGET_PRIxADDR, entry_point);
+       if (oldriscv_resume(target, 0, entry_point, 0, 0) != ERROR_OK)
+               return ERROR_FAIL;
+
+       int64_t start = timeval_ms();
+       while (target->state != TARGET_HALTED) {
+               LOG_DEBUG("poll()");
+               int64_t now = timeval_ms();
+               if (now - start > timeout_ms) {
+                       LOG_ERROR("Algorithm timed out after %d ms.", timeout_ms);
+                       LOG_ERROR("  now   = 0x%08x", (uint32_t) now);
+                       LOG_ERROR("  start = 0x%08x", (uint32_t) start);
+                       oldriscv_halt(target);
+                       old_or_new_riscv_poll(target);
+                       return ERROR_TARGET_TIMEOUT;
+               }
+
+               int result = old_or_new_riscv_poll(target);
+               if (result != ERROR_OK)
+                       return result;
+       }
+
+       if (reg_pc->type->get(reg_pc) != ERROR_OK)
+               return ERROR_FAIL;
+       uint64_t final_pc = buf_get_u64(reg_pc->value, 0, reg_pc->size);
+       if (final_pc != exit_point) {
+               LOG_ERROR("PC ended up at 0x%" PRIx64 " instead of 0x%"
+                               TARGET_PRIxADDR, final_pc, exit_point);
+               return ERROR_FAIL;
+       }
+
+       /* Restore Interrupts */
+       LOG_DEBUG("Restoring Interrupts");
+       buf_set_u64(mstatus_bytes, 0, info->xlen[0], current_mstatus);
+       reg_mstatus->type->set(reg_mstatus, mstatus_bytes);
+
+       /* Restore registers */
+       uint8_t buf[8];
+       buf_set_u64(buf, 0, info->xlen[0], saved_pc);
+       if (reg_pc->type->set(reg_pc, buf) != ERROR_OK)
+               return ERROR_FAIL;
+
+       for (int i = 0; i < num_reg_params; i++) {
+               LOG_DEBUG("restore %s", reg_params[i].reg_name);
+               struct reg *r = register_get_by_name(target->reg_cache, reg_params[i].reg_name, 0);
+               buf_set_u64(buf, 0, info->xlen[0], saved_regs[r->number]);
+               if (r->type->set(r, buf) != ERROR_OK)
+                       return ERROR_FAIL;
+       }
+
+       return ERROR_OK;
+}
+
+/* Should run code on the target to perform CRC of
+memory. Not yet implemented.
+*/
+
+static int riscv_checksum_memory(struct target *target,
+               target_addr_t address, uint32_t count,
+               uint32_t *checksum)
+{
+       *checksum = 0xFFFFFFFF;
+       return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+}
+
+/*** OpenOCD Helper Functions ***/
+
+enum riscv_poll_hart {
+       RPH_NO_CHANGE,
+       RPH_DISCOVERED_HALTED,
+       RPH_DISCOVERED_RUNNING,
+       RPH_ERROR
+};
+static enum riscv_poll_hart riscv_poll_hart(struct target *target, int hartid)
+{
+       RISCV_INFO(r);
+       if (riscv_set_current_hartid(target, hartid) != ERROR_OK)
+               return RPH_ERROR;
+
+       LOG_DEBUG("polling hart %d, target->state=%d", hartid, target->state);
+
+       /* If OpenOCD thinks we're running but this hart is halted then it's time
+        * to raise an event. */
+       bool halted = riscv_is_halted(target);
+       if (target->state != TARGET_HALTED && halted) {
+               LOG_DEBUG("  triggered a halt");
+               r->on_halt(target);
+               return RPH_DISCOVERED_HALTED;
+       } else if (target->state != TARGET_RUNNING && !halted) {
+               LOG_DEBUG("  triggered running");
+               target->state = TARGET_RUNNING;
+               return RPH_DISCOVERED_RUNNING;
+       }
+
+       return RPH_NO_CHANGE;
+}
+
+/*** OpenOCD Interface ***/
+int riscv_openocd_poll(struct target *target)
+{
+       LOG_DEBUG("polling all harts");
+       int halted_hart = -1;
+       if (riscv_rtos_enabled(target)) {
+               /* Check every hart for an event. */
+               for (int i = 0; i < riscv_count_harts(target); ++i) {
+                       enum riscv_poll_hart out = riscv_poll_hart(target, i);
+                       switch (out) {
+                       case RPH_NO_CHANGE:
+                       case RPH_DISCOVERED_RUNNING:
+                               continue;
+                       case RPH_DISCOVERED_HALTED:
+                               halted_hart = i;
+                               break;
+                       case RPH_ERROR:
+                               return ERROR_FAIL;
+                       }
+               }
+               if (halted_hart == -1) {
+                       LOG_DEBUG("  no harts just halted, target->state=%d", target->state);
+                       return ERROR_OK;
+               }
+               LOG_DEBUG("  hart %d halted", halted_hart);
+
+               /* If we're here then at least one hart triggered.  That means
+                * we want to go and halt _every_ hart in the system, as that's
+                * the invariant we hold here.  Some harts might have already
+                * halted (as we're either in single-step mode or they also
+                * triggered a breakpoint), so don't attempt to halt those
+                * harts. */
+               for (int i = 0; i < riscv_count_harts(target); ++i)
+                       riscv_halt_one_hart(target, i);
+       } else {
+               enum riscv_poll_hart out = riscv_poll_hart(target,
+                               riscv_current_hartid(target));
+               if (out == RPH_NO_CHANGE || out == RPH_DISCOVERED_RUNNING)
+                       return ERROR_OK;
+               else if (out == RPH_ERROR)
+                       return ERROR_FAIL;
+
+               halted_hart = riscv_current_hartid(target);
+               LOG_DEBUG("  hart %d halted", halted_hart);
+       }
+
+       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:
+               return ERROR_FAIL;
+       }
+
+       if (riscv_rtos_enabled(target)) {
+               target->rtos->current_threadid = halted_hart + 1;
+               target->rtos->current_thread = halted_hart + 1;
+       }
+
+       target->state = TARGET_HALTED;
+
+       if (target->debug_reason == DBG_REASON_BREAKPOINT) {
+               int retval;
+               if (riscv_semihosting(target, &retval) != 0)
+                       return retval;
+       }
+
+       target_call_event_callbacks(target, TARGET_EVENT_HALTED);
+       return ERROR_OK;
+}
+
+int riscv_openocd_halt(struct target *target)
+{
+       RISCV_INFO(r);
+
+       LOG_DEBUG("halting all harts");
+
+       int out = riscv_halt_all_harts(target);
+       if (out != ERROR_OK) {
+               LOG_ERROR("Unable to halt all harts");
+               return out;
+       }
+
+       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;
+       }
+
+       target->state = TARGET_HALTED;
+       target->debug_reason = DBG_REASON_DBGRQ;
+       target_call_event_callbacks(target, TARGET_EVENT_HALTED);
+       return out;
+}
+
+int riscv_openocd_resume(
+               struct target *target,
+               int current,
+               target_addr_t address,
+               int handle_breakpoints,
+               int debug_execution)
+{
+       LOG_DEBUG("debug_reason=%d", target->debug_reason);
+
+       if (!current)
+               riscv_set_register(target, GDB_REGNO_PC, address);
+
+       if (target->debug_reason == DBG_REASON_WATCHPOINT) {
+               /* To be able to run off a trigger, disable all the triggers, step, and
+                * then resume as usual. */
+               struct watchpoint *watchpoint = target->watchpoints;
+               bool trigger_temporarily_cleared[RISCV_MAX_HWBPS] = {0};
+
+               int i = 0;
+               int result = ERROR_OK;
+               while (watchpoint && result == ERROR_OK) {
+                       LOG_DEBUG("watchpoint %d: set=%d", i, watchpoint->set);
+                       trigger_temporarily_cleared[i] = watchpoint->set;
+                       if (watchpoint->set)
+                               result = riscv_remove_watchpoint(target, watchpoint);
+                       watchpoint = watchpoint->next;
+                       i++;
+               }
+
+               if (result == ERROR_OK)
+                       result = riscv_step_rtos_hart(target);
+
+               watchpoint = target->watchpoints;
+               i = 0;
+               while (watchpoint) {
+                       LOG_DEBUG("watchpoint %d: cleared=%d", i, trigger_temporarily_cleared[i]);
+                       if (trigger_temporarily_cleared[i]) {
+                               if (result == ERROR_OK)
+                                       result = riscv_add_watchpoint(target, watchpoint);
+                               else
+                                       riscv_add_watchpoint(target, watchpoint);
+                       }
+                       watchpoint = watchpoint->next;
+                       i++;
+               }
+
+               if (result != ERROR_OK)
+                       return result;
+       }
+
+       int out = riscv_resume_all_harts(target);
+       if (out != ERROR_OK) {
+               LOG_ERROR("unable to resume all harts");
+               return out;
+       }
+
+       register_cache_invalidate(target->reg_cache);
+       target->state = TARGET_RUNNING;
+       target_call_event_callbacks(target, TARGET_EVENT_RESUMED);
+       return out;
+}
+
+int riscv_openocd_step(
+               struct target *target,
+               int current,
+               target_addr_t address,
+               int handle_breakpoints
+) {
+       LOG_DEBUG("stepping rtos hart");
+
+       if (!current)
+               riscv_set_register(target, GDB_REGNO_PC, address);
+
+       int out = riscv_step_rtos_hart(target);
+       if (out != ERROR_OK) {
+               LOG_ERROR("unable to step rtos hart");
+               return out;
+       }
+
+       register_cache_invalidate(target->reg_cache);
+       target->state = TARGET_RUNNING;
+       target_call_event_callbacks(target, TARGET_EVENT_RESUMED);
+       target->state = TARGET_HALTED;
+       target->debug_reason = DBG_REASON_SINGLESTEP;
+       target_call_event_callbacks(target, TARGET_EVENT_HALTED);
+       return out;
+}
+
+/* Command Handlers */
+COMMAND_HANDLER(riscv_set_command_timeout_sec)
+{
+       if (CMD_ARGC != 1) {
+               LOG_ERROR("Command takes exactly 1 parameter");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+       int timeout = atoi(CMD_ARGV[0]);
+       if (timeout <= 0) {
+               LOG_ERROR("%s is not a valid integer argument for command.", CMD_ARGV[0]);
+               return ERROR_FAIL;
+       }
+
+       riscv_command_timeout_sec = timeout;
+
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(riscv_set_reset_timeout_sec)
+{
+       if (CMD_ARGC != 1) {
+               LOG_ERROR("Command takes exactly 1 parameter");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+       int timeout = atoi(CMD_ARGV[0]);
+       if (timeout <= 0) {
+               LOG_ERROR("%s is not a valid integer argument for command.", CMD_ARGV[0]);
+               return ERROR_FAIL;
+       }
+
+       riscv_reset_timeout_sec = timeout;
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(riscv_set_prefer_sba)
+{
+       if (CMD_ARGC != 1) {
+               LOG_ERROR("Command takes exactly 1 parameter");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+       COMMAND_PARSE_ON_OFF(CMD_ARGV[0], riscv_prefer_sba);
+       return ERROR_OK;
+}
+
+void parse_error(const char *string, char c, unsigned position)
+{
+       char buf[position+2];
+       for (unsigned i = 0; i < position; i++)
+               buf[i] = ' ';
+       buf[position] = '^';
+       buf[position + 1] = 0;
+
+       LOG_ERROR("Parse error at character %c in:", c);
+       LOG_ERROR("%s", string);
+       LOG_ERROR("%s", buf);
+}
+
+COMMAND_HANDLER(riscv_set_expose_csrs)
+{
+       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];
+                       if (isspace(c)) {
+                               /* Ignore whitespace. */
+                               continue;
+                       }
+
+                       if (parse_low) {
+                               if (isdigit(c)) {
+                                       low *= 10;
+                                       low += c - '0';
+                               } else if (c == '-') {
+                                       parse_low = false;
+                               } else if (c == ',' || c == 0) {
+                                       if (pass == 1) {
+                                               expose_csr[range].low = low;
+                                               expose_csr[range].high = low;
+                                       }
+                                       low = 0;
+                                       range++;
+                               } else {
+                                       parse_error(CMD_ARGV[0], c, i);
+                                       return ERROR_COMMAND_SYNTAX_ERROR;
+                               }
+
+                       } else {
+                               if (isdigit(c)) {
+                                       high *= 10;
+                                       high += c - '0';
+                               } else if (c == ',' || c == 0) {
+                                       parse_low = true;
+                                       if (pass == 1) {
+                                               expose_csr[range].low = low;
+                                               expose_csr[range].high = high;
+                                       }
+                                       low = 0;
+                                       high = 0;
+                                       range++;
+                               } else {
+                                       parse_error(CMD_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));
+               } else {
+                       expose_csr[range].low = 1;
+                       expose_csr[range].high = 0;
+               }
+       }
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(riscv_authdata_read)
+{
+       if (CMD_ARGC != 0) {
+               LOG_ERROR("Command takes no parameters");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       struct target *target = get_current_target(CMD_CTX);
+       if (!target) {
+               LOG_ERROR("target is NULL!");
+               return ERROR_FAIL;
+       }
+
+       RISCV_INFO(r);
+       if (!r) {
+               LOG_ERROR("riscv_info is NULL!");
+               return ERROR_FAIL;
+       }
+
+       if (r->authdata_read) {
+               uint32_t value;
+               if (r->authdata_read(target, &value) != ERROR_OK)
+                       return ERROR_FAIL;
+               command_print(CMD_CTX, "0x%" PRIx32, value);
+               return ERROR_OK;
+       } else {
+               LOG_ERROR("authdata_read is not implemented for this target.");
+               return ERROR_FAIL;
+       }
+}
+
+COMMAND_HANDLER(riscv_authdata_write)
+{
+       if (CMD_ARGC != 1) {
+               LOG_ERROR("Command takes exactly 1 argument");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       struct target *target = get_current_target(CMD_CTX);
+       RISCV_INFO(r);
+
+       uint32_t value;
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], value);
+
+       if (r->authdata_write) {
+               return r->authdata_write(target, value);
+       } else {
+               LOG_ERROR("authdata_write is not implemented for this target.");
+               return ERROR_FAIL;
+       }
+}
+
+COMMAND_HANDLER(riscv_dmi_read)
+{
+       if (CMD_ARGC != 1) {
+               LOG_ERROR("Command takes 1 parameter");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       struct target *target = get_current_target(CMD_CTX);
+       if (!target) {
+               LOG_ERROR("target is NULL!");
+               return ERROR_FAIL;
+       }
+
+       RISCV_INFO(r);
+       if (!r) {
+               LOG_ERROR("riscv_info is NULL!");
+               return ERROR_FAIL;
+       }
+
+       if (r->dmi_read) {
+               uint32_t address, value;
+               COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], address);
+               if (r->dmi_read(target, &value, address) != ERROR_OK)
+                       return ERROR_FAIL;
+               command_print(CMD_CTX, "0x%" PRIx32, value);
+               return ERROR_OK;
+       } else {
+               LOG_ERROR("dmi_read is not implemented for this target.");
+               return ERROR_FAIL;
+       }
+}
+
+
+COMMAND_HANDLER(riscv_dmi_write)
+{
+       if (CMD_ARGC != 2) {
+               LOG_ERROR("Command takes exactly 2 arguments");
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+
+       struct target *target = get_current_target(CMD_CTX);
+       RISCV_INFO(r);
+
+       uint32_t address, value;
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[0], address);
+       COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], value);
+
+       if (r->dmi_write) {
+               return r->dmi_write(target, address, value);
+       } else {
+               LOG_ERROR("dmi_write is not implemented for this target.");
+               return ERROR_FAIL;
+       }
+}
+
+static const struct command_registration riscv_exec_command_handlers[] = {
+       {
+               .name = "set_command_timeout_sec",
+               .handler = riscv_set_command_timeout_sec,
+               .mode = COMMAND_ANY,
+               .usage = "riscv set_command_timeout_sec [sec]",
+               .help = "Set the wall-clock timeout (in seconds) for individual commands"
+       },
+       {
+               .name = "set_reset_timeout_sec",
+               .handler = riscv_set_reset_timeout_sec,
+               .mode = COMMAND_ANY,
+               .usage = "riscv set_reset_timeout_sec [sec]",
+               .help = "Set the wall-clock timeout (in seconds) after reset is deasserted"
+       },
+       {
+               .name = "set_prefer_sba",
+               .handler = riscv_set_prefer_sba,
+               .mode = COMMAND_ANY,
+               .usage = "riscv set_prefer_sba on|off",
+               .help = "When on, prefer to use System Bus Access to access memory. "
+                       "When off, prefer to use the Program Buffer to access memory."
+       },
+       {
+               .name = "expose_csrs",
+               .handler = riscv_set_expose_csrs,
+               .mode = COMMAND_ANY,
+               .usage = "riscv expose_csrs n0[-m0][,n1[-m1]]...",
+               .help = "Configure a list of inclusive ranges for CSRs to expose in "
+                               "addition to the standard ones. This must be executed before "
+                               "`init`."
+       },
+       {
+               .name = "authdata_read",
+               .handler = riscv_authdata_read,
+               .mode = COMMAND_ANY,
+               .usage = "riscv authdata_read",
+               .help = "Return the 32-bit value read from authdata."
+       },
+       {
+               .name = "authdata_write",
+               .handler = riscv_authdata_write,
+               .mode = COMMAND_ANY,
+               .usage = "riscv authdata_write value",
+               .help = "Write the 32-bit value to authdata."
+       },
+       {
+               .name = "dmi_read",
+               .handler = riscv_dmi_read,
+               .mode = COMMAND_ANY,
+               .usage = "riscv dmi_read address",
+               .help = "Perform a 32-bit DMI read at address, returning the value."
+       },
+       {
+               .name = "dmi_write",
+               .handler = riscv_dmi_write,
+               .mode = COMMAND_ANY,
+               .usage = "riscv dmi_write address value",
+               .help = "Perform a 32-bit DMI write of value at address."
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+extern __COMMAND_HANDLER(handle_common_semihosting_command);
+extern __COMMAND_HANDLER(handle_common_semihosting_fileio_command);
+extern __COMMAND_HANDLER(handle_common_semihosting_resumable_exit_command);
+extern __COMMAND_HANDLER(handle_common_semihosting_cmdline);
+
+/*
+ * To be noted that RISC-V targets use the same semihosting commands as
+ * ARM targets.
+ *
+ * The main reason is compatibility with existing tools. For example the
+ * Eclipse OpenOCD/SEGGER J-Link/QEMU plug-ins have several widgets to
+ * configure semihosting, which generate commands like `arm semihosting
+ * enable`.
+ * A secondary reason is the fact that the protocol used is exactly the
+ * one specified by ARM. If RISC-V will ever define its own semihosting
+ * protocol, then a command like `riscv semihosting enable` will make
+ * sense, but for now all semihosting commands are prefixed with `arm`.
+ */
+static const struct command_registration arm_exec_command_handlers[] = {
+       {
+               "semihosting",
+               .handler = handle_common_semihosting_command,
+               .mode = COMMAND_EXEC,
+               .usage = "['enable'|'disable']",
+               .help = "activate support for semihosting operations",
+       },
+       {
+               "semihosting_cmdline",
+               .handler = handle_common_semihosting_cmdline,
+               .mode = COMMAND_EXEC,
+               .usage = "arguments",
+               .help = "command line arguments to be passed to program",
+       },
+       {
+               "semihosting_fileio",
+               .handler = handle_common_semihosting_fileio_command,
+               .mode = COMMAND_EXEC,
+               .usage = "['enable'|'disable']",
+               .help = "activate support for semihosting fileio operations",
+       },
+       {
+               "semihosting_resexit",
+               .handler = handle_common_semihosting_resumable_exit_command,
+               .mode = COMMAND_EXEC,
+               .usage = "['enable'|'disable']",
+               .help = "activate support for semihosting resumable exit",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+const struct command_registration riscv_command_handlers[] = {
+       {
+               .name = "riscv",
+               .mode = COMMAND_ANY,
+               .help = "RISC-V Command Group",
+               .usage = "",
+               .chain = riscv_exec_command_handlers
+       },
+       {
+               .name = "arm",
+               .mode = COMMAND_ANY,
+               .help = "ARM Command Group",
+               .usage = "",
+               .chain = arm_exec_command_handlers
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+struct target_type riscv_target = {
+       .name = "riscv",
+
+       .init_target = riscv_init_target,
+       .deinit_target = riscv_deinit_target,
+       .examine = riscv_examine,
+
+       /* poll current target status */
+       .poll = old_or_new_riscv_poll,
+
+       .halt = old_or_new_riscv_halt,
+       .resume = old_or_new_riscv_resume,
+       .step = old_or_new_riscv_step,
+
+       .assert_reset = riscv_assert_reset,
+       .deassert_reset = riscv_deassert_reset,
+
+       .read_memory = riscv_read_memory,
+       .write_memory = riscv_write_memory,
+
+       .checksum_memory = riscv_checksum_memory,
+
+       .get_gdb_reg_list = riscv_get_gdb_reg_list,
+
+       .add_breakpoint = riscv_add_breakpoint,
+       .remove_breakpoint = riscv_remove_breakpoint,
+
+       .add_watchpoint = riscv_add_watchpoint,
+       .remove_watchpoint = riscv_remove_watchpoint,
+
+       .arch_state = riscv_arch_state,
+
+       .run_algorithm = riscv_run_algorithm,
+
+       .commands = riscv_command_handlers
+};
+
+/*** RISC-V Interface ***/
+
+void riscv_info_init(struct target *target, riscv_info_t *r)
+{
+       memset(r, 0, sizeof(*r));
+       r->dtm_version = 1;
+       r->registers_initialized = false;
+       r->current_hartid = target->coreid;
+
+       memset(r->trigger_unique_id, 0xff, sizeof(r->trigger_unique_id));
+
+       for (size_t h = 0; h < RISCV_MAX_HARTS; ++h) {
+               r->xlen[h] = -1;
+
+               for (size_t e = 0; e < RISCV_MAX_REGISTERS; ++e)
+                       r->valid_saved_registers[h][e] = false;
+       }
+}
+
+int riscv_halt_all_harts(struct target *target)
+{
+       for (int i = 0; i < riscv_count_harts(target); ++i) {
+               if (!riscv_hart_enabled(target, i))
+                       continue;
+
+               riscv_halt_one_hart(target, i);
+       }
+
+       return ERROR_OK;
+}
+
+int riscv_halt_one_hart(struct target *target, int hartid)
+{
+       RISCV_INFO(r);
+       LOG_DEBUG("halting hart %d", hartid);
+       if (riscv_set_current_hartid(target, hartid) != ERROR_OK)
+               return ERROR_FAIL;
+       if (riscv_is_halted(target)) {
+               LOG_DEBUG("  hart %d requested halt, but was already halted", hartid);
+               return ERROR_OK;
+       }
+
+       return r->halt_current_hart(target);
+}
+
+int riscv_resume_all_harts(struct target *target)
+{
+       for (int i = 0; i < riscv_count_harts(target); ++i) {
+               if (!riscv_hart_enabled(target, i))
+                       continue;
+
+               riscv_resume_one_hart(target, i);
+       }
+
+       riscv_invalidate_register_cache(target);
+       return ERROR_OK;
+}
+
+int riscv_resume_one_hart(struct target *target, int hartid)
+{
+       RISCV_INFO(r);
+       LOG_DEBUG("resuming hart %d", hartid);
+       if (riscv_set_current_hartid(target, hartid) != ERROR_OK)
+               return ERROR_FAIL;
+       if (!riscv_is_halted(target)) {
+               LOG_DEBUG("  hart %d requested resume, but was already resumed", hartid);
+               return ERROR_OK;
+       }
+
+       r->on_resume(target);
+       return r->resume_current_hart(target);
+}
+
+int riscv_step_rtos_hart(struct target *target)
+{
+       RISCV_INFO(r);
+       int hartid = r->current_hartid;
+       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.");
+                       hartid = 0;
+               }
+       }
+       if (riscv_set_current_hartid(target, hartid) != ERROR_OK)
+               return ERROR_FAIL;
+       LOG_DEBUG("stepping hart %d", hartid);
+
+       if (!riscv_is_halted(target)) {
+               LOG_ERROR("Hart isn't halted before single step!");
+               return ERROR_FAIL;
+       }
+       riscv_invalidate_register_cache(target);
+       r->on_step(target);
+       if (r->step_current_hart(target) != ERROR_OK)
+               return ERROR_FAIL;
+       riscv_invalidate_register_cache(target);
+       r->on_halt(target);
+       if (!riscv_is_halted(target)) {
+               LOG_ERROR("Hart was not halted after single step!");
+               return ERROR_FAIL;
+       }
+       return ERROR_OK;
+}
+
+bool riscv_supports_extension(struct target *target, int hartid, char letter)
+{
+       RISCV_INFO(r);
+       unsigned num;
+       if (letter >= 'a' && letter <= 'z')
+               num = letter - 'a';
+       else if (letter >= 'A' && letter <= 'Z')
+               num = letter - 'A';
+       else
+               return false;
+       return r->misa[hartid] & (1 << num);
+}
+
+int riscv_xlen(const struct target *target)
+{
+       return riscv_xlen_of_hart(target, riscv_current_hartid(target));
+}
+
+int riscv_xlen_of_hart(const struct target *target, int hartid)
+{
+       RISCV_INFO(r);
+       assert(r->xlen[hartid] != -1);
+       return r->xlen[hartid];
+}
+
+bool riscv_rtos_enabled(const struct target *target)
+{
+       return target->rtos != NULL;
+}
+
+int riscv_set_current_hartid(struct target *target, int hartid)
+{
+       RISCV_INFO(r);
+       if (!r->select_current_hart)
+               return ERROR_OK;
+
+       int previous_hartid = riscv_current_hartid(target);
+       r->current_hartid = hartid;
+       assert(riscv_hart_enabled(target, hartid));
+       LOG_DEBUG("setting hartid to %d, was %d", hartid, previous_hartid);
+       if (r->select_current_hart(target) != ERROR_OK)
+               return ERROR_FAIL;
+
+       /* 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;
+
+       /* 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;
+}
+
+void riscv_invalidate_register_cache(struct target *target)
+{
+       RISCV_INFO(r);
+
+       register_cache_invalidate(target->reg_cache);
+       for (size_t i = 0; i < GDB_REGNO_COUNT; ++i) {
+               struct reg *reg = &target->reg_cache->reg_list[i];
+               reg->valid = false;
+       }
+
+       r->registers_initialized = true;
+}
+
+int riscv_current_hartid(const struct target *target)
+{
+       RISCV_INFO(r);
+       return r->current_hartid;
+}
+
+void riscv_set_all_rtos_harts(struct target *target)
+{
+       RISCV_INFO(r);
+       r->rtos_hartid = -1;
+}
+
+void riscv_set_rtos_hartid(struct target *target, int hartid)
+{
+       LOG_DEBUG("setting RTOS hartid %d", hartid);
+       RISCV_INFO(r);
+       r->rtos_hartid = hartid;
+}
+
+int riscv_count_harts(struct target *target)
+{
+       if (target == NULL)
+               return 1;
+       RISCV_INFO(r);
+       if (r == NULL)
+               return 1;
+       return r->hart_count;
+}
+
+bool riscv_has_register(struct target *target, int hartid, int regid)
+{
+       return 1;
+}
+
+/**
+ * This function is called when the debug user wants to change the value of a
+ * register. The new value may be cached, and may not be written until the hart
+ * is resumed. */
+int riscv_set_register(struct target *target, enum gdb_regno r, riscv_reg_t v)
+{
+       return riscv_set_register_on_hart(target, riscv_current_hartid(target), r, v);
+}
+
+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);
+       assert(r->set_register);
+       return r->set_register(target, hartid, regid, value);
+}
+
+int riscv_get_register(struct target *target, riscv_reg_t *value,
+               enum gdb_regno r)
+{
+       return riscv_get_register_on_hart(target, value,
+                       riscv_current_hartid(target), r);
+}
+
+int riscv_get_register_on_hart(struct target *target, riscv_reg_t *value,
+               int hartid, enum gdb_regno regid)
+{
+       RISCV_INFO(r);
+       int result = r->get_register(target, value, hartid, regid);
+       LOG_DEBUG("[%d] %s: %" PRIx64, hartid, gdb_regno_name(regid), *value);
+       return result;
+}
+
+bool riscv_is_halted(struct target *target)
+{
+       RISCV_INFO(r);
+       assert(r->is_halted);
+       return r->is_halted(target);
+}
+
+enum riscv_halt_reason riscv_halt_reason(struct target *target, int hartid)
+{
+       RISCV_INFO(r);
+       if (riscv_set_current_hartid(target, hartid) != ERROR_OK)
+               return RISCV_HALT_ERROR;
+       if (!riscv_is_halted(target)) {
+               LOG_ERROR("Hart is not halted!");
+               return RISCV_HALT_UNKNOWN;
+       }
+       return r->halt_reason(target);
+}
+
+size_t riscv_debug_buffer_size(struct target *target)
+{
+       RISCV_INFO(r);
+       return r->debug_buffer_size[riscv_current_hartid(target)];
+}
+
+int riscv_write_debug_buffer(struct target *target, int index, riscv_insn_t insn)
+{
+       RISCV_INFO(r);
+       r->write_debug_buffer(target, index, insn);
+       return ERROR_OK;
+}
+
+riscv_insn_t riscv_read_debug_buffer(struct target *target, int index)
+{
+       RISCV_INFO(r);
+       return r->read_debug_buffer(target, index);
+}
+
+int riscv_execute_debug_buffer(struct target *target)
+{
+       RISCV_INFO(r);
+       return r->execute_debug_buffer(target);
+}
+
+void riscv_fill_dmi_write_u64(struct target *target, char *buf, int a, uint64_t d)
+{
+       RISCV_INFO(r);
+       r->fill_dmi_write_u64(target, buf, a, d);
+}
+
+void riscv_fill_dmi_read_u64(struct target *target, char *buf, int a)
+{
+       RISCV_INFO(r);
+       r->fill_dmi_read_u64(target, buf, a);
+}
+
+void riscv_fill_dmi_nop_u64(struct target *target, char *buf)
+{
+       RISCV_INFO(r);
+       r->fill_dmi_nop_u64(target, buf);
+}
+
+int riscv_dmi_write_u64_bits(struct target *target)
+{
+       RISCV_INFO(r);
+       return r->dmi_write_u64_bits(target);
+}
+
+bool riscv_hart_enabled(struct target *target, int hartid)
+{
+       /* FIXME: Add a hart mask to the RTOS. */
+       if (riscv_rtos_enabled(target))
+               return hartid < riscv_count_harts(target);
+
+       return hartid == target->coreid;
+}
+
+/**
+ * Count triggers, and initialize trigger_count for each hart.
+ * trigger_count is initialized even if this function fails to discover
+ * something.
+ * Disable any hardware triggers that have dmode set. We can't have set them
+ * ourselves. Maybe they're left over from some killed debug session.
+ * */
+int riscv_enumerate_triggers(struct target *target)
+{
+       RISCV_INFO(r);
+
+       if (r->triggers_enumerated)
+               return ERROR_OK;
+
+       r->triggers_enumerated = true;  /* At the very least we tried. */
+
+       for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
+               if (!riscv_hart_enabled(target, hartid))
+                       continue;
+
+               riscv_reg_t tselect;
+               int result = riscv_get_register_on_hart(target, &tselect, hartid,
+                               GDB_REGNO_TSELECT);
+               if (result != ERROR_OK)
+                       return result;
+
+               for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) {
+                       r->trigger_count[hartid] = t;
+
+                       riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t);
+                       uint64_t tselect_rb;
+                       result = riscv_get_register_on_hart(target, &tselect_rb, hartid,
+                                       GDB_REGNO_TSELECT);
+                       if (result != ERROR_OK)
+                               return result;
+                       /* Mask off the top bit, which is used as tdrmode in old
+                        * implementations. */
+                       tselect_rb &= ~(1ULL << (riscv_xlen(target)-1));
+                       if (tselect_rb != t)
+                               break;
+                       uint64_t tdata1;
+                       result = riscv_get_register_on_hart(target, &tdata1, hartid,
+                                       GDB_REGNO_TDATA1);
+                       if (result != ERROR_OK)
+                               return result;
+
+                       int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
+                       switch (type) {
+                               case 1:
+                                       /* On these older cores we don't support software using
+                                        * triggers. */
+                                       riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
+                                       break;
+                               case 2:
+                                       if (tdata1 & MCONTROL_DMODE(riscv_xlen(target)))
+                                               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, 0);
+                                       break;
+                       }
+               }
+
+               riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
+
+               LOG_INFO("[%d] Found %d triggers", hartid, r->trigger_count[hartid]);
+       }
+
+       return ERROR_OK;
+}
+
+const char *gdb_regno_name(enum gdb_regno regno)
+{
+       static char buf[32];
+
+       switch (regno) {
+               case GDB_REGNO_ZERO:
+                       return "zero";
+               case GDB_REGNO_S0:
+                       return "s0";
+               case GDB_REGNO_S1:
+                       return "s1";
+               case GDB_REGNO_PC:
+                       return "pc";
+               case GDB_REGNO_FPR0:
+                       return "fpr0";
+               case GDB_REGNO_FPR31:
+                       return "fpr31";
+               case GDB_REGNO_CSR0:
+                       return "csr0";
+               case GDB_REGNO_TSELECT:
+                       return "tselect";
+               case GDB_REGNO_TDATA1:
+                       return "tdata1";
+               case GDB_REGNO_TDATA2:
+                       return "tdata2";
+               case GDB_REGNO_MISA:
+                       return "misa";
+               case GDB_REGNO_DPC:
+                       return "dpc";
+               case GDB_REGNO_DCSR:
+                       return "dcsr";
+               case GDB_REGNO_DSCRATCH:
+                       return "dscratch";
+               case GDB_REGNO_MSTATUS:
+                       return "mstatus";
+               case GDB_REGNO_PRIV:
+                       return "priv";
+               default:
+                       if (regno <= GDB_REGNO_XPR31)
+                               sprintf(buf, "x%d", regno - GDB_REGNO_ZERO);
+                       else if (regno >= GDB_REGNO_CSR0 && regno <= GDB_REGNO_CSR4095)
+                               sprintf(buf, "csr%d", regno - GDB_REGNO_CSR0);
+                       else if (regno >= GDB_REGNO_FPR0 && regno <= GDB_REGNO_FPR31)
+                               sprintf(buf, "f%d", regno - GDB_REGNO_FPR0);
+                       else
+                               sprintf(buf, "gdb_regno_%d", regno);
+                       return buf;
+       }
+}
+
+static int register_get(struct reg *reg)
+{
+       struct target *target = (struct target *) reg->arch_info;
+       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);
+       return ERROR_OK;
+}
+
+static int register_set(struct reg *reg, uint8_t *buf)
+{
+       struct target *target = (struct target *) reg->arch_info;
+
+       uint64_t value = buf_get_u64(buf, 0, reg->size);
+
+       LOG_DEBUG("write 0x%" PRIx64 " to %s", value, reg->name);
+       struct reg *r = &target->reg_cache->reg_list[reg->number];
+       r->valid = true;
+       memcpy(r->value, buf, (r->size + 7) / 8);
+
+       riscv_set_register(target, reg->number, value);
+       return ERROR_OK;
+}
+
+static struct reg_arch_type riscv_reg_arch_type = {
+       .get = register_get,
+       .set = register_set
+};
+
+struct csr_info {
+       unsigned number;
+       const char *name;
+};
+
+static int cmp_csr_info(const void *p1, const void *p2)
+{
+       return (int) (((struct csr_info *)p1)->number) - (int) (((struct csr_info *)p2)->number);
+}
+
+int riscv_init_registers(struct target *target)
+{
+       RISCV_INFO(info);
+
+       if (target->reg_cache) {
+               if (target->reg_cache->reg_list)
+                       free(target->reg_cache->reg_list);
+               free(target->reg_cache);
+       }
+
+       target->reg_cache = calloc(1, sizeof(*target->reg_cache));
+       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));
+
+       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);
+       char *reg_name = info->reg_names;
+
+       static struct reg_feature feature_cpu = {
+               .name = "org.gnu.gdb.riscv.cpu"
+       };
+       static struct reg_feature feature_fpu = {
+               .name = "org.gnu.gdb.riscv.fpu"
+       };
+       static struct reg_feature feature_csr = {
+               .name = "org.gnu.gdb.riscv.csr"
+       };
+       static struct reg_feature feature_virtual = {
+               .name = "org.gnu.gdb.riscv.virtual"
+       };
+
+       static struct reg_data_type type_ieee_single = {
+               .type = REG_TYPE_IEEE_SINGLE,
+               .id = "ieee_single"
+       };
+       static struct reg_data_type type_ieee_double = {
+               .type = REG_TYPE_IEEE_DOUBLE,
+               .id = "ieee_double"
+       };
+       struct csr_info csr_info[] = {
+#define DECLARE_CSR(name, number) { number, #name },
+#include "encoding.h"
+#undef DECLARE_CSR
+       };
+       /* encoding.h does not contain the registers in sorted order. */
+       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
+        * 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++) {
+               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->number = number;
+               r->size = riscv_xlen(target);
+               /* r->size is set in riscv_invalidate_register_cache, maybe because the
+                * target is in theory allowed to change XLEN on us. But I expect a lot
+                * of other things to break in that case as well. */
+               if (number <= GDB_REGNO_XPR31) {
+                       r->caller_save = true;
+                       switch (number) {
+                               case GDB_REGNO_ZERO:
+                                       r->name = "zero";
+                                       break;
+                               case GDB_REGNO_RA:
+                                       r->name = "ra";
+                                       break;
+                               case GDB_REGNO_SP:
+                                       r->name = "sp";
+                                       break;
+                               case GDB_REGNO_GP:
+                                       r->name = "gp";
+                                       break;
+                               case GDB_REGNO_TP:
+                                       r->name = "tp";
+                                       break;
+                               case GDB_REGNO_T0:
+                                       r->name = "t0";
+                                       break;
+                               case GDB_REGNO_T1:
+                                       r->name = "t1";
+                                       break;
+                               case GDB_REGNO_T2:
+                                       r->name = "t2";
+                                       break;
+                               case GDB_REGNO_FP:
+                                       r->name = "fp";
+                                       break;
+                               case GDB_REGNO_S1:
+                                       r->name = "s1";
+                                       break;
+                               case GDB_REGNO_A0:
+                                       r->name = "a0";
+                                       break;
+                               case GDB_REGNO_A1:
+                                       r->name = "a1";
+                                       break;
+                               case GDB_REGNO_A2:
+                                       r->name = "a2";
+                                       break;
+                               case GDB_REGNO_A3:
+                                       r->name = "a3";
+                                       break;
+                               case GDB_REGNO_A4:
+                                       r->name = "a4";
+                                       break;
+                               case GDB_REGNO_A5:
+                                       r->name = "a5";
+                                       break;
+                               case GDB_REGNO_A6:
+                                       r->name = "a6";
+                                       break;
+                               case GDB_REGNO_A7:
+                                       r->name = "a7";
+                                       break;
+                               case GDB_REGNO_S2:
+                                       r->name = "s2";
+                                       break;
+                               case GDB_REGNO_S3:
+                                       r->name = "s3";
+                                       break;
+                               case GDB_REGNO_S4:
+                                       r->name = "s4";
+                                       break;
+                               case GDB_REGNO_S5:
+                                       r->name = "s5";
+                                       break;
+                               case GDB_REGNO_S6:
+                                       r->name = "s6";
+                                       break;
+                               case GDB_REGNO_S7:
+                                       r->name = "s7";
+                                       break;
+                               case GDB_REGNO_S8:
+                                       r->name = "s8";
+                                       break;
+                               case GDB_REGNO_S9:
+                                       r->name = "s9";
+                                       break;
+                               case GDB_REGNO_S10:
+                                       r->name = "s10";
+                                       break;
+                               case GDB_REGNO_S11:
+                                       r->name = "s11";
+                                       break;
+                               case GDB_REGNO_T3:
+                                       r->name = "t3";
+                                       break;
+                               case GDB_REGNO_T4:
+                                       r->name = "t4";
+                                       break;
+                               case GDB_REGNO_T5:
+                                       r->name = "t5";
+                                       break;
+                               case GDB_REGNO_T6:
+                                       r->name = "t6";
+                                       break;
+                       }
+                       r->group = "general";
+                       r->feature = &feature_cpu;
+               } else if (number == GDB_REGNO_PC) {
+                       r->caller_save = true;
+                       sprintf(reg_name, "pc");
+                       r->group = "general";
+                       r->feature = &feature_cpu;
+               } else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
+                       r->caller_save = true;
+                       if (riscv_supports_extension(target, riscv_current_hartid(target),
+                                               'D')) {
+                               r->reg_data_type = &type_ieee_double;
+                               r->size = 64;
+                       } else if (riscv_supports_extension(target,
+                                               riscv_current_hartid(target), 'F')) {
+                               r->reg_data_type = &type_ieee_single;
+                               r->size = 32;
+                       } else {
+                               r->exist = false;
+                       }
+                       switch (number) {
+                               case GDB_REGNO_FT0:
+                                       r->name = "ft0";
+                                       break;
+                               case GDB_REGNO_FT1:
+                                       r->name = "ft1";
+                                       break;
+                               case GDB_REGNO_FT2:
+                                       r->name = "ft2";
+                                       break;
+                               case GDB_REGNO_FT3:
+                                       r->name = "ft3";
+                                       break;
+                               case GDB_REGNO_FT4:
+                                       r->name = "ft4";
+                                       break;
+                               case GDB_REGNO_FT5:
+                                       r->name = "ft5";
+                                       break;
+                               case GDB_REGNO_FT6:
+                                       r->name = "ft6";
+                                       break;
+                               case GDB_REGNO_FT7:
+                                       r->name = "ft7";
+                                       break;
+                               case GDB_REGNO_FS0:
+                                       r->name = "fs0";
+                                       break;
+                               case GDB_REGNO_FS1:
+                                       r->name = "fs1";
+                                       break;
+                               case GDB_REGNO_FA0:
+                                       r->name = "fa0";
+                                       break;
+                               case GDB_REGNO_FA1:
+                                       r->name = "fa1";
+                                       break;
+                               case GDB_REGNO_FA2:
+                                       r->name = "fa2";
+                                       break;
+                               case GDB_REGNO_FA3:
+                                       r->name = "fa3";
+                                       break;
+                               case GDB_REGNO_FA4:
+                                       r->name = "fa4";
+                                       break;
+                               case GDB_REGNO_FA5:
+                                       r->name = "fa5";
+                                       break;
+                               case GDB_REGNO_FA6:
+                                       r->name = "fa6";
+                                       break;
+                               case GDB_REGNO_FA7:
+                                       r->name = "fa7";
+                                       break;
+                               case GDB_REGNO_FS2:
+                                       r->name = "fs2";
+                                       break;
+                               case GDB_REGNO_FS3:
+                                       r->name = "fs3";
+                                       break;
+                               case GDB_REGNO_FS4:
+                                       r->name = "fs4";
+                                       break;
+                               case GDB_REGNO_FS5:
+                                       r->name = "fs5";
+                                       break;
+                               case GDB_REGNO_FS6:
+                                       r->name = "fs6";
+                                       break;
+                               case GDB_REGNO_FS7:
+                                       r->name = "fs7";
+                                       break;
+                               case GDB_REGNO_FS8:
+                                       r->name = "fs8";
+                                       break;
+                               case GDB_REGNO_FS9:
+                                       r->name = "fs9";
+                                       break;
+                               case GDB_REGNO_FS10:
+                                       r->name = "fs10";
+                                       break;
+                               case GDB_REGNO_FS11:
+                                       r->name = "fs11";
+                                       break;
+                               case GDB_REGNO_FT8:
+                                       r->name = "ft8";
+                                       break;
+                               case GDB_REGNO_FT9:
+                                       r->name = "ft9";
+                                       break;
+                               case GDB_REGNO_FT10:
+                                       r->name = "ft10";
+                                       break;
+                               case GDB_REGNO_FT11:
+                                       r->name = "ft11";
+                                       break;
+                       }
+                       r->group = "float";
+                       r->feature = &feature_fpu;
+               } else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
+                       r->group = "csr";
+                       r->feature = &feature_csr;
+                       unsigned csr_number = number - GDB_REGNO_CSR0;
+
+                       while (csr_info[csr_info_index].number < csr_number &&
+                                       csr_info_index < DIM(csr_info) - 1) {
+                               csr_info_index++;
+                       }
+                       if (csr_info[csr_info_index].number == csr_number) {
+                               r->name = csr_info[csr_info_index].name;
+                       } else {
+                               sprintf(reg_name, "csr%d", csr_number);
+                               /* Assume unnamed registers don't exist, unless we have some
+                                * configuration that tells us otherwise. That's important
+                                * because eg. Eclipse crashes if a target has too many
+                                * registers, and apparently has no way of only showing a
+                                * subset of registers in any case. */
+                               r->exist = false;
+                       }
+
+                       switch (csr_number) {
+                               case CSR_FFLAGS:
+                               case CSR_FRM:
+                               case CSR_FCSR:
+                                       r->exist = riscv_supports_extension(target,
+                                                       riscv_current_hartid(target), 'F');
+                                       r->group = "float";
+                                       r->feature = &feature_fpu;
+                                       break;
+                               case CSR_SSTATUS:
+                               case CSR_STVEC:
+                               case CSR_SIP:
+                               case CSR_SIE:
+                               case CSR_SCOUNTEREN:
+                               case CSR_SSCRATCH:
+                               case CSR_SEPC:
+                               case CSR_SCAUSE:
+                               case CSR_STVAL:
+                               case CSR_SATP:
+                                       r->exist = riscv_supports_extension(target,
+                                                       riscv_current_hartid(target), 'S');
+                                       break;
+                               case CSR_MEDELEG:
+                               case CSR_MIDELEG:
+                                       /* "In systems with only M-mode, or with both M-mode and
+                                        * U-mode but without U-mode trap support, the medeleg and
+                                        * mideleg registers should not exist." */
+                                       r->exist = riscv_supports_extension(target, riscv_current_hartid(target), 'S') ||
+                                               riscv_supports_extension(target, riscv_current_hartid(target), 'N');
+                                       break;
+
+                               case CSR_CYCLEH:
+                               case CSR_TIMEH:
+                               case CSR_INSTRETH:
+                               case CSR_HPMCOUNTER3H:
+                               case CSR_HPMCOUNTER4H:
+                               case CSR_HPMCOUNTER5H:
+                               case CSR_HPMCOUNTER6H:
+                               case CSR_HPMCOUNTER7H:
+                               case CSR_HPMCOUNTER8H:
+                               case CSR_HPMCOUNTER9H:
+                               case CSR_HPMCOUNTER10H:
+                               case CSR_HPMCOUNTER11H:
+                               case CSR_HPMCOUNTER12H:
+                               case CSR_HPMCOUNTER13H:
+                               case CSR_HPMCOUNTER14H:
+                               case CSR_HPMCOUNTER15H:
+                               case CSR_HPMCOUNTER16H:
+                               case CSR_HPMCOUNTER17H:
+                               case CSR_HPMCOUNTER18H:
+                               case CSR_HPMCOUNTER19H:
+                               case CSR_HPMCOUNTER20H:
+                               case CSR_HPMCOUNTER21H:
+                               case CSR_HPMCOUNTER22H:
+                               case CSR_HPMCOUNTER23H:
+                               case CSR_HPMCOUNTER24H:
+                               case CSR_HPMCOUNTER25H:
+                               case CSR_HPMCOUNTER26H:
+                               case CSR_HPMCOUNTER27H:
+                               case CSR_HPMCOUNTER28H:
+                               case CSR_HPMCOUNTER29H:
+                               case CSR_HPMCOUNTER30H:
+                               case CSR_HPMCOUNTER31H:
+                               case CSR_MCYCLEH:
+                               case CSR_MINSTRETH:
+                               case CSR_MHPMCOUNTER3H:
+                               case CSR_MHPMCOUNTER4H:
+                               case CSR_MHPMCOUNTER5H:
+                               case CSR_MHPMCOUNTER6H:
+                               case CSR_MHPMCOUNTER7H:
+                               case CSR_MHPMCOUNTER8H:
+                               case CSR_MHPMCOUNTER9H:
+                               case CSR_MHPMCOUNTER10H:
+                               case CSR_MHPMCOUNTER11H:
+                               case CSR_MHPMCOUNTER12H:
+                               case CSR_MHPMCOUNTER13H:
+                               case CSR_MHPMCOUNTER14H:
+                               case CSR_MHPMCOUNTER15H:
+                               case CSR_MHPMCOUNTER16H:
+                               case CSR_MHPMCOUNTER17H:
+                               case CSR_MHPMCOUNTER18H:
+                               case CSR_MHPMCOUNTER19H:
+                               case CSR_MHPMCOUNTER20H:
+                               case CSR_MHPMCOUNTER21H:
+                               case CSR_MHPMCOUNTER22H:
+                               case CSR_MHPMCOUNTER23H:
+                               case CSR_MHPMCOUNTER24H:
+                               case CSR_MHPMCOUNTER25H:
+                               case CSR_MHPMCOUNTER26H:
+                               case CSR_MHPMCOUNTER27H:
+                               case CSR_MHPMCOUNTER28H:
+                               case CSR_MHPMCOUNTER29H:
+                               case CSR_MHPMCOUNTER30H:
+                               case CSR_MHPMCOUNTER31H:
+                                       r->exist = riscv_xlen(target) == 32;
+                                       break;
+                       }
+
+                       if (!r->exist && expose_csr) {
+                               for (unsigned i = 0; expose_csr[i].low <= expose_csr[i].high; i++) {
+                                       if (csr_number >= expose_csr[i].low && csr_number <= expose_csr[i].high) {
+                                               LOG_INFO("Exposing additional CSR %d", csr_number);
+                                               r->exist = true;
+                                               break;
+                                       }
+                               }
+                       }
+
+               } else if (number == GDB_REGNO_PRIV) {
+                       sprintf(reg_name, "priv");
+                       r->group = "general";
+                       r->feature = &feature_virtual;
+                       r->size = 8;
+               }
+               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);
+               r->value = &info->reg_cache_values[number];
+       }
+
+       return ERROR_OK;
+}

Linking to existing account procedure

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

SSH host keys fingerprints

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