2112 lines
64 KiB
C
2112 lines
64 KiB
C
/*
|
|
* Support for RISC-V, debug version 0.13, which is currently (2/4/17) the
|
|
* latest draft.
|
|
*/
|
|
|
|
#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 "rtos/riscv_debug.h"
|
|
#include "debug_defines.h"
|
|
#include "rtos/rtos.h"
|
|
#include "program.h"
|
|
#include "asm.h"
|
|
#include "batch.h"
|
|
|
|
#define DMI_DATA1 (DMI_DATA0 + 1)
|
|
|
|
static void riscv013_on_step_or_resume(struct target *target, bool step);
|
|
static void riscv013_step_or_resume_current_hart(struct target *target, bool step);
|
|
static riscv_addr_t riscv013_progbuf_addr(struct target *target);
|
|
static riscv_addr_t riscv013_progbuf_size(struct target *target);
|
|
static riscv_addr_t riscv013_data_size(struct target *target);
|
|
static riscv_addr_t riscv013_data_addr(struct target *target);
|
|
static void riscv013_set_autoexec(struct target *target, unsigned index,
|
|
bool enabled);
|
|
static int riscv013_debug_buffer_register(struct target *target, riscv_addr_t addr);
|
|
static void riscv013_clear_abstract_error(struct target *target);
|
|
|
|
/* Implementations of the functions in riscv_info_t. */
|
|
static riscv_reg_t riscv013_get_register(struct target *target, int hartid, int regid);
|
|
static void riscv013_set_register(struct target *target, int hartid, int regid, uint64_t value);
|
|
static void riscv013_select_current_hart(struct target *target);
|
|
static void riscv013_halt_current_hart(struct target *target);
|
|
static void riscv013_resume_current_hart(struct target *target);
|
|
static void riscv013_step_current_hart(struct target *target);
|
|
static void riscv013_on_halt(struct target *target);
|
|
static void riscv013_on_step(struct target *target);
|
|
static void riscv013_on_resume(struct target *target);
|
|
static bool riscv013_is_halted(struct target *target);
|
|
static enum riscv_halt_reason riscv013_halt_reason(struct target *target);
|
|
static void riscv013_debug_buffer_enter(struct target *target, struct riscv_program *p);
|
|
static void riscv013_debug_buffer_leave(struct target *target, struct riscv_program *p);
|
|
static void riscv013_write_debug_buffer(struct target *target, unsigned index,
|
|
riscv_insn_t d);
|
|
static riscv_insn_t riscv013_read_debug_buffer(struct target *target, unsigned
|
|
index);
|
|
static int riscv013_execute_debug_buffer(struct target *target);
|
|
static void riscv013_fill_dmi_write_u64(struct target *target, char *buf, int a, uint64_t d);
|
|
static void riscv013_fill_dmi_read_u64(struct target *target, char *buf, int a);
|
|
static int riscv013_dmi_write_u64_bits(struct target *target);
|
|
static void riscv013_fill_dmi_nop_u64(struct target *target, char *buf);
|
|
static void riscv013_reset_current_hart(struct target *target);
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
|
|
#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))
|
|
|
|
#define CSR_DCSR_CAUSE_SWBP 1
|
|
#define CSR_DCSR_CAUSE_TRIGGER 2
|
|
#define CSR_DCSR_CAUSE_DEBUGINT 3
|
|
#define CSR_DCSR_CAUSE_STEP 4
|
|
#define CSR_DCSR_CAUSE_HALT 5
|
|
|
|
#define RISCV013_INFO(r) riscv013_info_t *r = get_info(target)
|
|
|
|
/*** JTAG registers. ***/
|
|
|
|
typedef enum {
|
|
DMI_OP_NOP = 0,
|
|
DMI_OP_READ = 1,
|
|
DMI_OP_WRITE = 2
|
|
} dmi_op_t;
|
|
typedef enum {
|
|
DMI_STATUS_SUCCESS = 0,
|
|
DMI_STATUS_FAILED = 2,
|
|
DMI_STATUS_BUSY = 3
|
|
} dmi_status_t;
|
|
|
|
typedef enum {
|
|
RE_OK,
|
|
RE_FAIL,
|
|
RE_AGAIN
|
|
} riscv_error_t;
|
|
|
|
typedef enum slot {
|
|
SLOT0,
|
|
SLOT1,
|
|
SLOT_LAST,
|
|
} slot_t;
|
|
|
|
/*** Debug Bus registers. ***/
|
|
|
|
#define CMDERR_NONE 0
|
|
#define CMDERR_BUSY 1
|
|
#define CMDERR_NOT_SUPPORTED 2
|
|
#define CMDERR_EXCEPTION 3
|
|
#define CMDERR_HALT_RESUME 4
|
|
#define CMDERR_OTHER 7
|
|
|
|
/*** Info about the core being debugged. ***/
|
|
|
|
#define WALL_CLOCK_TIMEOUT 2
|
|
|
|
#define MAX_HWBPS 16
|
|
|
|
struct trigger {
|
|
uint64_t address;
|
|
uint32_t length;
|
|
uint64_t mask;
|
|
uint64_t value;
|
|
bool read, write, execute;
|
|
int unique_id;
|
|
};
|
|
|
|
struct memory_cache_line {
|
|
uint32_t data;
|
|
bool valid;
|
|
bool dirty;
|
|
};
|
|
|
|
typedef struct {
|
|
/* Number of address bits in the dbus register. */
|
|
unsigned abits;
|
|
/* Number of abstract command data registers. */
|
|
unsigned datacount;
|
|
/* Number of words in the Program Buffer. */
|
|
unsigned progsize;
|
|
/* Number of Program Buffer registers. */
|
|
/* Number of words in Debug RAM. */
|
|
uint64_t misa;
|
|
uint64_t tselect;
|
|
bool tselect_dirty;
|
|
/* The value that mstatus actually has on the target right now. This is not
|
|
* the value we present to the user. That one may be stored in the
|
|
* reg_cache. */
|
|
uint64_t mstatus_actual;
|
|
|
|
/* Single buffer that contains all register names, instead of calling
|
|
* malloc for each register. Needs to be freed when reg_list is freed. */
|
|
char *reg_names;
|
|
/* Single buffer that contains all register values. */
|
|
void *reg_values;
|
|
|
|
// For each physical trigger, contains -1 if the hwbp is available, or the
|
|
// unique_id of the breakpoint/watchpoint that is using it.
|
|
int trigger_unique_id[MAX_HWBPS];
|
|
|
|
// Number of run-test/idle cycles the target requests we do after each dbus
|
|
// access.
|
|
unsigned int dtmcontrol_idle;
|
|
|
|
// This value is incremented every time a dbus access comes back as "busy".
|
|
// It's used to determine how many run-test/idle cycles to feed the target
|
|
// in between accesses.
|
|
unsigned int dmi_busy_delay;
|
|
|
|
// This value is increased every time we tried to execute two commands
|
|
// consecutively, and the second one failed because the previous hadn't
|
|
// completed yet. It's used to add extra run-test/idle cycles after
|
|
// starting a command, so we don't have to waste time checking for busy to
|
|
// go low.
|
|
unsigned int ac_busy_delay;
|
|
|
|
bool need_strict_step;
|
|
|
|
// Some memoized values
|
|
int progbuf_size, progbuf_addr, data_addr, data_size;
|
|
} riscv013_info_t;
|
|
|
|
static void decode_dmi(char *text, unsigned address, unsigned data)
|
|
{
|
|
text[0] = 0;
|
|
switch (address) {
|
|
case DMI_DMSTATUS:
|
|
if (get_field(data, DMI_DMSTATUS_ALLRESUMEACK)) {
|
|
strcat(text, " allresumeack");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ANYRESUMEACK)) {
|
|
strcat(text, " anyresumeack");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ALLNONEXISTENT)) {
|
|
strcat(text, " allnonexistent");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ANYNONEXISTENT)) {
|
|
strcat(text, " anynonexistent");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ALLUNAVAIL)) {
|
|
strcat(text, " allunavail");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ANYUNAVAIL)) {
|
|
strcat(text, " anyunavail");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ALLRUNNING)) {
|
|
strcat(text, " allrunning");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ANYRUNNING)) {
|
|
strcat(text, " anyrunning");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ALLHALTED)) {
|
|
strcat(text, " allhalted");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_ANYHALTED)) {
|
|
strcat(text, " anyhalted");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_AUTHENTICATED)) {
|
|
strcat(text, " authenticated");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_AUTHBUSY)) {
|
|
strcat(text, " authbusy");
|
|
}
|
|
if (get_field(data, DMI_DMSTATUS_CFGSTRVALID)) {
|
|
strcat(text, " cfgstrvalid");
|
|
}
|
|
sprintf(text + strlen(text), " version=%d", get_field(data,
|
|
DMI_DMSTATUS_VERSION));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void dump_field(const struct scan_field *field)
|
|
{
|
|
static const char *op_string[] = {"-", "r", "w", "?"};
|
|
static const char *status_string[] = {"+", "?", "F", "b"};
|
|
|
|
if (debug_level < LOG_LVL_DEBUG)
|
|
return;
|
|
|
|
uint64_t out = buf_get_u64(field->out_value, 0, field->num_bits);
|
|
unsigned int out_op = get_field(out, DTM_DMI_OP);
|
|
unsigned int out_data = get_field(out, DTM_DMI_DATA);
|
|
unsigned int out_address = out >> DTM_DMI_ADDRESS_OFFSET;
|
|
|
|
uint64_t in = buf_get_u64(field->in_value, 0, field->num_bits);
|
|
unsigned int in_op = get_field(in, DTM_DMI_OP);
|
|
unsigned int in_data = get_field(in, DTM_DMI_DATA);
|
|
unsigned int in_address = in >> DTM_DMI_ADDRESS_OFFSET;
|
|
|
|
log_printf_lf(LOG_LVL_DEBUG,
|
|
__FILE__, __LINE__, "scan",
|
|
"%db %s %08x @%02x -> %s %08x @%02x",
|
|
field->num_bits,
|
|
op_string[out_op], out_data, out_address,
|
|
status_string[in_op], in_data, in_address);
|
|
|
|
char out_text[500];
|
|
char in_text[500];
|
|
decode_dmi(out_text, out_address, out_data);
|
|
decode_dmi(in_text, in_address, in_data);
|
|
if (in_text[0] || out_text[0]) {
|
|
log_printf_lf(LOG_LVL_DEBUG, __FILE__, __LINE__, "scan", "%s -> %s",
|
|
out_text, in_text);
|
|
}
|
|
}
|
|
|
|
static riscv013_info_t *get_info(const struct target *target)
|
|
{
|
|
riscv_info_t *info = (riscv_info_t *) target->arch_info;
|
|
return (riscv013_info_t *) info->version_specific;
|
|
}
|
|
|
|
/*** Necessary prototypes. ***/
|
|
|
|
static int register_get(struct reg *reg);
|
|
|
|
/*** Utility functions. ***/
|
|
|
|
bool supports_extension(struct target *target, char letter)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
unsigned num;
|
|
if (letter >= 'a' && letter <= 'z') {
|
|
num = letter - 'a';
|
|
} else if (letter >= 'A' && letter <= 'Z') {
|
|
num = letter - 'A';
|
|
} else {
|
|
return false;
|
|
}
|
|
return info->misa & (1 << num);
|
|
}
|
|
|
|
static void select_dmi(struct target *target)
|
|
{
|
|
static uint8_t ir_dmi[1] = {DTM_DMI};
|
|
struct scan_field field = {
|
|
.num_bits = target->tap->ir_length,
|
|
.out_value = ir_dmi,
|
|
.in_value = NULL,
|
|
.check_value = NULL,
|
|
.check_mask = NULL
|
|
};
|
|
|
|
jtag_add_ir_scan(target->tap, &field, TAP_IDLE);
|
|
}
|
|
|
|
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 dmi. */
|
|
select_dmi(target);
|
|
|
|
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("DTMCS: 0x%x -> 0x%x", out, in);
|
|
|
|
return in;
|
|
}
|
|
|
|
static void increase_dmi_busy_delay(struct target *target)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
info->dmi_busy_delay += info->dmi_busy_delay / 10 + 1;
|
|
LOG_INFO("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
|
|
info->dtmcontrol_idle, info->dmi_busy_delay,
|
|
info->ac_busy_delay);
|
|
|
|
dtmcontrol_scan(target, DTM_DTMCS_DMIRESET);
|
|
}
|
|
|
|
static void increase_ac_busy_delay(struct target *target)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
info->ac_busy_delay += info->ac_busy_delay / 10 + 1;
|
|
LOG_INFO("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
|
|
info->dtmcontrol_idle, info->dmi_busy_delay,
|
|
info->ac_busy_delay);
|
|
|
|
dtmcontrol_scan(target, DTM_DTMCS_DMIRESET);
|
|
}
|
|
|
|
/**
|
|
* exec: If this is set, assume the scan results in an execution, so more
|
|
* run-test/idle cycles may be required.
|
|
*/
|
|
static dmi_status_t dmi_scan(struct target *target, uint16_t *address_in,
|
|
uint64_t *data_in, dmi_op_t op, uint16_t address_out, uint64_t data_out,
|
|
bool exec)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
uint8_t in[8] = {0};
|
|
uint8_t out[8];
|
|
struct scan_field field = {
|
|
.num_bits = info->abits + DTM_DMI_OP_LENGTH + DTM_DMI_DATA_LENGTH,
|
|
.out_value = out,
|
|
.in_value = in
|
|
};
|
|
|
|
assert(info->abits != 0);
|
|
|
|
buf_set_u64(out, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, op);
|
|
buf_set_u64(out, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, data_out);
|
|
buf_set_u64(out, DTM_DMI_ADDRESS_OFFSET, info->abits, address_out);
|
|
|
|
/* Assume dbus is already selected. */
|
|
jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);
|
|
|
|
int idle_count = info->dmi_busy_delay;
|
|
if (exec)
|
|
idle_count += info->ac_busy_delay;
|
|
|
|
if (idle_count) {
|
|
jtag_add_runtest(idle_count, TAP_IDLE);
|
|
}
|
|
|
|
int retval = jtag_execute_queue();
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("dmi_scan failed jtag scan");
|
|
return DMI_STATUS_FAILED;
|
|
}
|
|
|
|
if (data_in) {
|
|
*data_in = buf_get_u64(in, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH);
|
|
}
|
|
|
|
if (address_in) {
|
|
*address_in = buf_get_u32(in, DTM_DMI_ADDRESS_OFFSET, info->abits);
|
|
}
|
|
|
|
dump_field(&field);
|
|
|
|
return buf_get_u32(in, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
|
|
}
|
|
|
|
static uint64_t dmi_read(struct target *target, uint16_t address)
|
|
{
|
|
select_dmi(target);
|
|
|
|
uint64_t value;
|
|
dmi_status_t status;
|
|
uint16_t address_in;
|
|
|
|
unsigned i = 0;
|
|
|
|
// This first loop ensures that the read request was actually sent
|
|
// to the target. Note that if for some reason this stays busy,
|
|
// it is actually due to the Previous dmi_read or dmi_write.
|
|
for (i = 0; i < 256; i++) {
|
|
status = dmi_scan(target, NULL, NULL, DMI_OP_READ, address, 0,
|
|
false);
|
|
if (status == DMI_STATUS_BUSY) {
|
|
increase_dmi_busy_delay(target);
|
|
} else if (status == DMI_STATUS_SUCCESS) {
|
|
break;
|
|
} else {
|
|
LOG_ERROR("failed read from 0x%x, status=%d", address, status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (status != DMI_STATUS_SUCCESS) {
|
|
LOG_ERROR("Failed read from 0x%x; value=0x%" PRIx64 ", status=%d",
|
|
address, value, status);
|
|
abort();
|
|
}
|
|
|
|
// This second loop ensures that we got the read
|
|
// data back. Note that NOP can result in a 'busy' result as well, but
|
|
// that would be noticed on the next DMI access we do.
|
|
for (i = 0; i < 256; i++) {
|
|
status = dmi_scan(target, &address_in, &value, DMI_OP_NOP, address, 0,
|
|
false);
|
|
if (status == DMI_STATUS_BUSY) {
|
|
increase_dmi_busy_delay(target);
|
|
} else if (status == DMI_STATUS_SUCCESS) {
|
|
break;
|
|
} else {
|
|
LOG_ERROR("failed read (NOP) at 0x%x, status=%d\n", address, status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (status != DMI_STATUS_SUCCESS) {
|
|
LOG_ERROR("Failed read (NOP) from 0x%x; value=0x%" PRIx64 ", status=%d",
|
|
address, value, status);
|
|
abort();
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static void dmi_write(struct target *target, uint16_t address, uint64_t value)
|
|
{
|
|
select_dmi(target);
|
|
dmi_status_t status = DMI_STATUS_BUSY;
|
|
unsigned i = 0;
|
|
|
|
// The first loop ensures that we successfully sent the write request.
|
|
for (i = 0; i < 256; i++) {
|
|
status = dmi_scan(target, NULL, NULL, DMI_OP_WRITE, address, value,
|
|
address == DMI_COMMAND);
|
|
if (status == DMI_STATUS_BUSY) {
|
|
increase_dmi_busy_delay(target);
|
|
} else if (status == DMI_STATUS_SUCCESS) {
|
|
break;
|
|
} else {
|
|
LOG_ERROR("failed write to 0x%x, status=%d\n", address, status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (status != DMI_STATUS_SUCCESS) {
|
|
LOG_ERROR("Failed write to 0x%x;, status=%d\n",
|
|
address, status);
|
|
abort();
|
|
}
|
|
|
|
// The second loop isn't strictly necessary, but would ensure that
|
|
// the write is complete/ has no non-busy errors before returning from this function.
|
|
for (i = 0; i < 256; i++) {
|
|
status = dmi_scan(target, NULL, NULL, DMI_OP_NOP, address, 0,
|
|
false);
|
|
if (status == DMI_STATUS_BUSY) {
|
|
increase_dmi_busy_delay(target);
|
|
} else if (status == DMI_STATUS_SUCCESS) {
|
|
break;
|
|
} else {
|
|
LOG_ERROR("failed write (NOP) at 0x%x, status=%d\n", address, status);
|
|
break;
|
|
}
|
|
}
|
|
if (status != DMI_STATUS_SUCCESS) {
|
|
LOG_ERROR("failed to write (NOP) 0x%" PRIx64 " to 0x%x; status=%d\n", value, address, status);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
uint32_t abstract_register_size(unsigned width)
|
|
{
|
|
switch (width) {
|
|
case 32:
|
|
return set_field(0, AC_ACCESS_REGISTER_SIZE, 2);
|
|
case 64:
|
|
return set_field(0, AC_ACCESS_REGISTER_SIZE, 3);
|
|
break;
|
|
case 128:
|
|
return set_field(0, AC_ACCESS_REGISTER_SIZE, 4);
|
|
break;
|
|
default:
|
|
LOG_ERROR("Unsupported register width: %d", width);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int wait_for_idle(struct target *target, uint32_t *abstractcs)
|
|
{
|
|
time_t start = time(NULL);
|
|
while (1) {
|
|
*abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
|
|
if (get_field(*abstractcs, DMI_ABSTRACTCS_BUSY) == 0) {
|
|
return ERROR_OK;
|
|
}
|
|
|
|
if (time(NULL) - start > WALL_CLOCK_TIMEOUT) {
|
|
if (get_field(*abstractcs, DMI_ABSTRACTCS_CMDERR) != CMDERR_NONE) {
|
|
const char *errors[8] = {
|
|
"none",
|
|
"busy",
|
|
"not supported",
|
|
"exception",
|
|
"halt/resume",
|
|
"reserved",
|
|
"reserved",
|
|
"other" };
|
|
|
|
LOG_ERROR("Abstract command ended in error '%s' (abstractcs=0x%x)",
|
|
errors[get_field(*abstractcs, DMI_ABSTRACTCS_CMDERR)],
|
|
*abstractcs);
|
|
}
|
|
|
|
LOG_ERROR("Timed out waiting for busy to go low. (abstractcs=0x%x)",
|
|
*abstractcs);
|
|
return ERROR_FAIL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int register_read_direct(struct target *target, uint64_t *value, uint32_t number);
|
|
|
|
static int register_write_direct(struct target *target, unsigned number,
|
|
uint64_t value)
|
|
{
|
|
struct riscv_program program;
|
|
|
|
LOG_DEBUG("[%d] reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
|
|
number, value);
|
|
|
|
riscv_program_init(&program, target);
|
|
|
|
riscv_addr_t input = riscv_program_alloc_d(&program);
|
|
riscv_program_write_ram(&program, input + 4, value >> 32);
|
|
riscv_program_write_ram(&program, input, value);
|
|
|
|
assert(GDB_REGNO_XPR0 == 0);
|
|
if (number <= GDB_REGNO_XPR31) {
|
|
riscv_program_lx(&program, number, input);
|
|
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
|
riscv_program_fld(&program, number, input);
|
|
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
|
enum gdb_regno temp = riscv_program_gettemp(&program);
|
|
riscv_program_lx(&program, temp, input);
|
|
riscv_program_csrw(&program, temp, number);
|
|
} else {
|
|
LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
|
|
abort();
|
|
}
|
|
|
|
int exec_out = riscv_program_exec(&program, target);
|
|
if (exec_out != ERROR_OK) {
|
|
riscv013_clear_abstract_error(target);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/** Actually read registers from the target right now. */
|
|
static int register_read_direct(struct target *target, uint64_t *value, uint32_t number)
|
|
{
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_addr_t output = riscv_program_alloc_d(&program);
|
|
riscv_program_write_ram(&program, output + 4, 0);
|
|
riscv_program_write_ram(&program, output, 0);
|
|
|
|
assert(GDB_REGNO_XPR0 == 0);
|
|
if (number <= GDB_REGNO_XPR31) {
|
|
riscv_program_sx(&program, number, output);
|
|
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
|
riscv_program_fsd(&program, number, output);
|
|
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
|
LOG_DEBUG("reading CSR index=0x%03x", number - GDB_REGNO_CSR0);
|
|
enum gdb_regno temp = riscv_program_gettemp(&program);
|
|
riscv_program_csrr(&program, temp, number);
|
|
riscv_program_sx(&program, temp, output);
|
|
} else {
|
|
LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
|
|
abort();
|
|
}
|
|
|
|
int exec_out = riscv_program_exec(&program, target);
|
|
if (exec_out != ERROR_OK) {
|
|
riscv013_clear_abstract_error(target);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
*value = 0;
|
|
*value |= ((uint64_t)(riscv_program_read_ram(&program, output + 4))) << 32;
|
|
*value |= riscv_program_read_ram(&program, output);
|
|
LOG_DEBUG("[%d] reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
|
|
number, *value);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int maybe_read_tselect(struct target *target)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
|
|
if (info->tselect_dirty) {
|
|
int result = register_read_direct(target, &info->tselect, GDB_REGNO_TSELECT);
|
|
if (result != ERROR_OK)
|
|
return result;
|
|
info->tselect_dirty = false;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/*** OpenOCD target functions. ***/
|
|
|
|
static int register_get(struct reg *reg)
|
|
{
|
|
struct target *target = (struct target *) reg->arch_info;
|
|
uint64_t value = riscv_get_register(target, reg->number);
|
|
buf_set_u64(reg->value, 0, 64, value);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int register_write(struct target *target, unsigned int number,
|
|
uint64_t value)
|
|
{
|
|
riscv_set_register(target, number, 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, riscv_xlen(target));
|
|
|
|
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);
|
|
|
|
return register_write(target, reg->number, value);
|
|
}
|
|
|
|
static struct reg_arch_type riscv_reg_arch_type = {
|
|
.get = register_get,
|
|
.set = register_set
|
|
};
|
|
|
|
static int init_target(struct command_context *cmd_ctx,
|
|
struct target *target)
|
|
{
|
|
LOG_DEBUG("init");
|
|
riscv_info_t *generic_info = (riscv_info_t *) target->arch_info;
|
|
|
|
riscv_info_init(generic_info);
|
|
generic_info->get_register = &riscv013_get_register;
|
|
generic_info->set_register = &riscv013_set_register;
|
|
generic_info->select_current_hart = &riscv013_select_current_hart;
|
|
generic_info->is_halted = &riscv013_is_halted;
|
|
generic_info->halt_current_hart = &riscv013_halt_current_hart;
|
|
generic_info->resume_current_hart = &riscv013_resume_current_hart;
|
|
generic_info->step_current_hart = &riscv013_step_current_hart;
|
|
generic_info->on_halt = &riscv013_on_halt;
|
|
generic_info->on_resume = &riscv013_on_resume;
|
|
generic_info->on_step = &riscv013_on_step;
|
|
generic_info->halt_reason = &riscv013_halt_reason;
|
|
generic_info->debug_buffer_enter = &riscv013_debug_buffer_enter;
|
|
generic_info->debug_buffer_leave = &riscv013_debug_buffer_leave;
|
|
generic_info->read_debug_buffer = &riscv013_read_debug_buffer;
|
|
generic_info->write_debug_buffer = &riscv013_write_debug_buffer;
|
|
generic_info->execute_debug_buffer = &riscv013_execute_debug_buffer;
|
|
generic_info->fill_dmi_write_u64 = &riscv013_fill_dmi_write_u64;
|
|
generic_info->fill_dmi_read_u64 = &riscv013_fill_dmi_read_u64;
|
|
generic_info->fill_dmi_nop_u64 = &riscv013_fill_dmi_nop_u64;
|
|
generic_info->dmi_write_u64_bits = &riscv013_dmi_write_u64_bits;
|
|
generic_info->reset_current_hart = &riscv013_reset_current_hart;
|
|
|
|
generic_info->version_specific = calloc(1, sizeof(riscv013_info_t));
|
|
if (!generic_info->version_specific)
|
|
return ERROR_FAIL;
|
|
riscv013_info_t *info = get_info(target);
|
|
|
|
info->progbuf_size = -1;
|
|
info->progbuf_addr = -1;
|
|
info->data_size = -1;
|
|
info->data_addr = -1;
|
|
|
|
info->dmi_busy_delay = 0;
|
|
info->ac_busy_delay = 0;
|
|
|
|
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;
|
|
info->reg_names = calloc(1, GDB_REGNO_COUNT * max_reg_name_len);
|
|
char *reg_name = info->reg_names;
|
|
info->reg_values = NULL;
|
|
|
|
for (unsigned int i = 0; i < GDB_REGNO_COUNT; i++) {
|
|
struct reg *r = &target->reg_cache->reg_list[i];
|
|
r->number = i;
|
|
r->caller_save = true;
|
|
r->dirty = false;
|
|
r->valid = false;
|
|
r->exist = true;
|
|
r->type = &riscv_reg_arch_type;
|
|
r->arch_info = target;
|
|
if (i <= GDB_REGNO_XPR31) {
|
|
sprintf(reg_name, "x%d", i);
|
|
} else if (i == GDB_REGNO_PC) {
|
|
sprintf(reg_name, "pc");
|
|
} else if (i >= GDB_REGNO_FPR0 && i <= GDB_REGNO_FPR31) {
|
|
sprintf(reg_name, "f%d", i - GDB_REGNO_FPR0);
|
|
} else if (i >= GDB_REGNO_CSR0 && i <= GDB_REGNO_CSR4095) {
|
|
sprintf(reg_name, "csr%d", i - GDB_REGNO_CSR0);
|
|
} else if (i == GDB_REGNO_PRIV) {
|
|
sprintf(reg_name, "priv");
|
|
}
|
|
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);
|
|
}
|
|
#if 0
|
|
update_reg_list(target);
|
|
#endif
|
|
|
|
memset(info->trigger_unique_id, 0xff, sizeof(info->trigger_unique_id));
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static void deinit_target(struct target *target)
|
|
{
|
|
LOG_DEBUG("riscv_deinit_target()");
|
|
riscv_info_t *info = (riscv_info_t *) target->arch_info;
|
|
free(info->version_specific);
|
|
info->version_specific = NULL;
|
|
}
|
|
|
|
static int add_trigger(struct target *target, struct trigger *trigger)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
|
|
// While we're using threads to fake harts, both gdb and OpenOCD assume
|
|
// that hardware breakpoints are shared among threads. Make this true by
|
|
// setting the same breakpoints on all harts.
|
|
|
|
// Assume that all triggers are configured the same on all harts.
|
|
riscv_set_current_hartid(target, 0);
|
|
|
|
maybe_read_tselect(target);
|
|
|
|
int i;
|
|
for (i = 0; i < riscv_count_triggers(target); i++) {
|
|
if (info->trigger_unique_id[i] != -1) {
|
|
continue;
|
|
}
|
|
|
|
register_write_direct(target, GDB_REGNO_TSELECT, i);
|
|
|
|
uint64_t tdata1;
|
|
register_read_direct(target, &tdata1, GDB_REGNO_TDATA1);
|
|
int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
|
|
|
|
if (type != 2) {
|
|
continue;
|
|
}
|
|
|
|
if (tdata1 & (MCONTROL_EXECUTE | MCONTROL_STORE | MCONTROL_LOAD)) {
|
|
// Trigger is already in use, presumably by user code.
|
|
continue;
|
|
}
|
|
|
|
uint64_t tdata1_rb;
|
|
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
|
|
riscv_set_current_hartid(target, hartid);
|
|
|
|
if (hartid > 0) {
|
|
register_write_direct(target, GDB_REGNO_TSELECT, i);
|
|
}
|
|
|
|
// 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 (info->misa & (1 << ('H' - 'A')))
|
|
tdata1 |= MCONTROL_H;
|
|
if (info->misa & (1 << ('S' - 'A')))
|
|
tdata1 |= MCONTROL_S;
|
|
if (info->misa & (1 << ('U' - 'A')))
|
|
tdata1 |= MCONTROL_U;
|
|
|
|
if (trigger->execute)
|
|
tdata1 |= MCONTROL_EXECUTE;
|
|
if (trigger->read)
|
|
tdata1 |= MCONTROL_LOAD;
|
|
if (trigger->write)
|
|
tdata1 |= MCONTROL_STORE;
|
|
|
|
register_write_direct(target, GDB_REGNO_TDATA1, tdata1);
|
|
|
|
register_read_direct(target, &tdata1_rb, GDB_REGNO_TDATA1);
|
|
LOG_DEBUG("tdata1=0x%" PRIx64, tdata1_rb);
|
|
|
|
if (tdata1 != tdata1_rb) {
|
|
LOG_DEBUG("Trigger %d doesn't support what we need; After writing 0x%"
|
|
PRIx64 " to tdata1 it contains 0x%" PRIx64,
|
|
i, tdata1, tdata1_rb);
|
|
register_write_direct(target, GDB_REGNO_TDATA1, 0);
|
|
if (hartid > 0) {
|
|
LOG_ERROR("Setting hardware breakpoints requires "
|
|
"homogeneous harts.");
|
|
return ERROR_FAIL;
|
|
}
|
|
break;
|
|
}
|
|
|
|
register_write_direct(target, GDB_REGNO_TDATA2, trigger->address);
|
|
}
|
|
|
|
if (tdata1 != tdata1_rb) {
|
|
continue;
|
|
}
|
|
|
|
LOG_DEBUG("Using resource %d for bp %d", i,
|
|
trigger->unique_id);
|
|
info->trigger_unique_id[i] = trigger->unique_id;
|
|
break;
|
|
}
|
|
if (i >= riscv_count_triggers(target)) {
|
|
LOG_ERROR("Couldn't find an available hardware trigger.");
|
|
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int remove_trigger(struct target *target, struct trigger *trigger)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
|
|
// Assume that all triggers are configured the same on all harts.
|
|
riscv_set_current_hartid(target, 0);
|
|
|
|
maybe_read_tselect(target);
|
|
|
|
int i;
|
|
for (i = 0; i < riscv_count_triggers(target); i++) {
|
|
if (info->trigger_unique_id[i] == trigger->unique_id) {
|
|
break;
|
|
}
|
|
}
|
|
if (i >= riscv_count_triggers(target)) {
|
|
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 = 0; hartid < riscv_count_harts(target); ++hartid) {
|
|
riscv_set_current_hartid(target, hartid);
|
|
register_write_direct(target, GDB_REGNO_TSELECT, i);
|
|
register_write_direct(target, GDB_REGNO_TDATA1, 0);
|
|
}
|
|
info->trigger_unique_id[i] = -1;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
static int 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_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 int 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;
|
|
}
|
|
|
|
static int 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 examine(struct target *target)
|
|
{
|
|
// Don't need to select dbus, since the first thing we do is read dtmcontrol.
|
|
|
|
uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
|
|
LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
|
|
LOG_DEBUG(" dmireset=%d", get_field(dtmcontrol, DTM_DTMCS_DMIRESET));
|
|
LOG_DEBUG(" idle=%d", get_field(dtmcontrol, DTM_DTMCS_IDLE));
|
|
LOG_DEBUG(" dmistat=%d", get_field(dtmcontrol, DTM_DTMCS_DMISTAT));
|
|
LOG_DEBUG(" abits=%d", get_field(dtmcontrol, DTM_DTMCS_ABITS));
|
|
LOG_DEBUG(" version=%d", get_field(dtmcontrol, DTM_DTMCS_VERSION));
|
|
if (dtmcontrol == 0) {
|
|
LOG_ERROR("dtmcontrol is 0. Check JTAG connectivity/board power.");
|
|
return ERROR_FAIL;
|
|
}
|
|
if (get_field(dtmcontrol, DTM_DTMCS_VERSION) != 1) {
|
|
LOG_ERROR("Unsupported DTM version %d. (dtmcontrol=0x%x)",
|
|
get_field(dtmcontrol, DTM_DTMCS_VERSION), dtmcontrol);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
riscv013_info_t *info = get_info(target);
|
|
info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
|
|
info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
|
|
|
|
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(dmstatus, DMI_DMSTATUS_VERSION) != 2) {
|
|
LOG_ERROR("OpenOCD only supports Debug Module version 2, not %d "
|
|
"(dmstatus=0x%x)", get_field(dmstatus, DMI_DMSTATUS_VERSION), dmstatus);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
// Reset the Debug Module.
|
|
dmi_write(target, DMI_DMCONTROL, 0);
|
|
dmi_write(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE);
|
|
dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
|
|
LOG_DEBUG("dmcontrol: 0x%08x", dmcontrol);
|
|
LOG_DEBUG("dmstatus: 0x%08x", dmstatus);
|
|
|
|
if (!get_field(dmcontrol, DMI_DMCONTROL_DMACTIVE)) {
|
|
LOG_ERROR("Debug Module did not become active. dmcontrol=0x%x",
|
|
dmcontrol);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
if (!get_field(dmstatus, DMI_DMSTATUS_AUTHENTICATED)) {
|
|
LOG_ERROR("Authentication required by RISC-V core but not "
|
|
"supported by OpenOCD. dmcontrol=0x%x", dmcontrol);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL)) {
|
|
LOG_ERROR("The hart is unavailable.");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ANYNONEXISTENT)) {
|
|
LOG_ERROR("The hart doesn't exist.");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
// Check that abstract data registers are accessible.
|
|
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
info->datacount = get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT);
|
|
info->progsize = get_field(abstractcs, DMI_ABSTRACTCS_PROGSIZE);
|
|
|
|
/* Before doing anything else we must first enumerate the harts. */
|
|
RISCV_INFO(r);
|
|
if (riscv_rtos_enabled(target)) {
|
|
for (int i = 0; i < RISCV_MAX_HARTS; ++i) {
|
|
riscv_set_current_hartid(target, i);
|
|
uint32_t s = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(s, DMI_DMSTATUS_ANYNONEXISTENT))
|
|
break;
|
|
r->hart_count = i + 1;
|
|
}
|
|
} else {
|
|
r->hart_count = 1;
|
|
}
|
|
|
|
LOG_DEBUG("Enumerated %d harts", r->hart_count);
|
|
|
|
/* Halt every hart so we can probe them. */
|
|
riscv_halt_all_harts(target);
|
|
|
|
/* Find the address of the program buffer, which must be done without
|
|
* knowing anything about the target. */
|
|
for (int i = 0; i < riscv_count_harts(target); ++i) {
|
|
riscv_set_current_hartid(target, i);
|
|
|
|
/* Without knowing anything else we can at least mess with the
|
|
* program buffer. */
|
|
r->debug_buffer_size[i] = riscv013_progbuf_size(target);
|
|
|
|
/* Guess this is a 32-bit system, we're probing it. */
|
|
r->xlen[i] = 32;
|
|
|
|
/* First find the low 32 bits of the program buffer. This is
|
|
* used to check for alignment. */
|
|
struct riscv_program program32;
|
|
riscv_program_init(&program32, target);
|
|
riscv_program_csrrw(&program32, GDB_REGNO_S0, GDB_REGNO_S0, GDB_REGNO_DSCRATCH);
|
|
riscv_program_insert(&program32, auipc(GDB_REGNO_S0));
|
|
riscv_program_insert(&program32, sw(GDB_REGNO_S0, GDB_REGNO_S0, -4));
|
|
riscv_program_csrrw(&program32, GDB_REGNO_S0, GDB_REGNO_S0, GDB_REGNO_DSCRATCH);
|
|
riscv_program_fence(&program32);
|
|
riscv_program_exec(&program32, target);
|
|
|
|
riscv_addr_t progbuf_addr = dmi_read(target, DMI_PROGBUF0) - 4;
|
|
if (get_field(dmi_read(target, DMI_ABSTRACTCS), DMI_ABSTRACTCS_CMDERR) != 0) {
|
|
LOG_ERROR("Unable to find the address of the program buffer on hart %d", i);
|
|
r->xlen[i] = -1;
|
|
continue;
|
|
}
|
|
r->debug_buffer_addr[i] = progbuf_addr;
|
|
|
|
/* Check to see if the core can execute 64 bit instructions.
|
|
* In order to make this work we first need to */
|
|
int offset = (progbuf_addr % 8 == 0) ? -4 : 0;
|
|
|
|
struct riscv_program program64;
|
|
riscv_program_init(&program64, target);
|
|
riscv_program_csrrw(&program64, GDB_REGNO_S0, GDB_REGNO_S0, GDB_REGNO_DSCRATCH);
|
|
riscv_program_insert(&program64, auipc(GDB_REGNO_S0));
|
|
riscv_program_insert(&program64, sd(GDB_REGNO_S0, GDB_REGNO_S0, offset));
|
|
riscv_program_csrrw(&program64, GDB_REGNO_S0, GDB_REGNO_S0, GDB_REGNO_DSCRATCH);
|
|
riscv_program_fence(&program64);
|
|
riscv_program_exec(&program64, target);
|
|
|
|
if (get_field(dmi_read(target, DMI_ABSTRACTCS), DMI_ABSTRACTCS_CMDERR) == 0) {
|
|
r->debug_buffer_addr[i] =
|
|
(dmi_read(target, DMI_PROGBUF0 + (8 + offset) / 4) << 32)
|
|
+ dmi_read(target, DMI_PROGBUF0 + (4 + offset) / 4)
|
|
- 4;
|
|
r->xlen[i] = 64;
|
|
}
|
|
|
|
/* Display this as early as possible to help people who are using
|
|
* really slow simulators. */
|
|
LOG_DEBUG(" hart %d: XLEN=%d, program buffer at 0x%" PRIx64, i,
|
|
r->xlen[i], r->debug_buffer_addr[i]);
|
|
|
|
if (riscv_program_gah(&program64, r->debug_buffer_addr[i])) {
|
|
LOG_ERROR("This implementation will not work with hart %d with debug_buffer_addr of 0x%lx\n", i,
|
|
(long)r->debug_buffer_addr[i]);
|
|
abort();
|
|
}
|
|
|
|
/* Check to see if we can use the data words as an extended
|
|
* program buffer or not. */
|
|
if (r->debug_buffer_addr[i] + (4 * r->debug_buffer_size[i]) == riscv013_data_addr(target)) {
|
|
r->debug_buffer_size[i] += riscv013_data_size(target);
|
|
LOG_DEBUG("extending the debug buffer using data words, total size %d", r->debug_buffer_size[i]);
|
|
}
|
|
}
|
|
|
|
/* Then we check the number of triggers availiable to each hart. */
|
|
for (int i = 0; i < riscv_count_harts(target); ++i) {
|
|
for (uint32_t t = 0; t < RISCV_MAX_TRIGGERS; ++t) {
|
|
riscv_set_current_hartid(target, i);
|
|
|
|
r->trigger_count[i] = t;
|
|
register_write_direct(target, GDB_REGNO_TSELECT, t);
|
|
uint64_t tselect = t+1;
|
|
register_read_direct(target, &tselect, GDB_REGNO_TSELECT);
|
|
if (tselect != t)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Resumes all the harts, so the debugger can later pause them. */
|
|
riscv_resume_all_harts(target);
|
|
target_set_examined(target);
|
|
|
|
if (target->rtos) {
|
|
riscv_update_threads(target->rtos);
|
|
}
|
|
|
|
// Some regression suites rely on seeing 'Examined RISC-V core' to know
|
|
// when they can connect with gdb/telnet.
|
|
// We will need to update those suites if we want to change that text.
|
|
LOG_INFO("Examined RISC-V core; found %d harts",
|
|
riscv_count_harts(target));
|
|
for (int i = 0; i < riscv_count_harts(target); ++i) {
|
|
LOG_INFO(" hart %d: XLEN=%d, program buffer at 0x%" PRIx64
|
|
", %d triggers", i, r->xlen[i], r->debug_buffer_addr[i],
|
|
r->trigger_count[i]);
|
|
}
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int assert_reset(struct target *target)
|
|
{
|
|
/*FIXME -- this only works for single-hart.*/
|
|
RISCV_INFO(r);
|
|
assert(r->current_hartid == 0);
|
|
|
|
select_dmi(target);
|
|
LOG_DEBUG("ASSERTING NDRESET");
|
|
uint32_t control = dmi_read(target, DMI_DMCONTROL);
|
|
control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
|
|
if (target->reset_halt) {
|
|
LOG_DEBUG("TARGET RESET HALT SET, ensuring halt is set during reset.");
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ, 1);
|
|
} else {
|
|
LOG_DEBUG("TARGET RESET HALT NOT SET");
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ, 0);
|
|
}
|
|
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int deassert_reset(struct target *target)
|
|
{
|
|
RISCV_INFO(r);
|
|
RISCV013_INFO(info);
|
|
select_dmi(target);
|
|
|
|
/*FIXME -- this only works for Single Hart*/
|
|
assert(r->current_hartid == 0);
|
|
|
|
/*FIXME -- is there bookkeeping we need to do here*/
|
|
|
|
uint32_t control = dmi_read(target, DMI_DMCONTROL);
|
|
|
|
// Clear the reset, but make sure haltreq is still set
|
|
if (target->reset_halt) {
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ, 1);
|
|
}
|
|
|
|
control = set_field(control, DMI_DMCONTROL_NDMRESET, 0);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
|
|
uint32_t status;
|
|
int dmi_busy_delay = info->dmi_busy_delay;
|
|
if (target->reset_halt) {
|
|
LOG_DEBUG("DEASSERTING RESET, waiting for hart to be halted.");
|
|
do {
|
|
status = dmi_read(target, DMI_DMSTATUS);
|
|
} while (get_field(status, DMI_DMSTATUS_ALLHALTED) == 0);
|
|
} else {
|
|
LOG_DEBUG("DEASSERTING RESET, waiting for hart to be running.");
|
|
do {
|
|
status = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(status, DMI_DMSTATUS_ANYHALTED) ||
|
|
get_field(status, DMI_DMSTATUS_ANYUNAVAIL)) {
|
|
LOG_ERROR("Unexpected hart status during reset.");
|
|
abort();
|
|
}
|
|
} while (get_field(status, DMI_DMSTATUS_ALLRUNNING) == 0);
|
|
}
|
|
info->dmi_busy_delay = dmi_busy_delay;
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int read_memory(struct target *target, target_addr_t address,
|
|
uint32_t size, uint32_t count, uint8_t *buffer)
|
|
{
|
|
RISCV013_INFO(info);
|
|
|
|
LOG_DEBUG("reading %d words of %d bytes from 0x%" TARGET_PRIxADDR, count,
|
|
size, address);
|
|
|
|
select_dmi(target);
|
|
/* There was a bug in the memory system and only accesses from hart 0 actually
|
|
* worked correctly. This should be obselete now. -palmer */
|
|
riscv_set_current_hartid(target, 0);
|
|
|
|
/* This program uses two temporary registers. A word of data and the
|
|
* associated address are stored at some location in memory. The
|
|
* program loads the word from that address and then increments the
|
|
* address. The debugger is expected to pull the memory word-by-word
|
|
* from the chip with AUTOEXEC set in order to trigger program
|
|
* execution on every word. */
|
|
uint64_t s0 = riscv_get_register(target, GDB_REGNO_S0);
|
|
uint64_t s1 = riscv_get_register(target, GDB_REGNO_S1);
|
|
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_addr_t r_data = riscv_program_alloc_w(&program);
|
|
riscv_addr_t r_addr = riscv_program_alloc_x(&program);
|
|
riscv_program_fence(&program);
|
|
riscv_program_lx(&program, GDB_REGNO_S0, r_addr);
|
|
riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
|
|
switch (size) {
|
|
case 1:
|
|
riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
case 2:
|
|
riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
case 4:
|
|
riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
default:
|
|
LOG_ERROR("Unsupported size: %d", size);
|
|
return ERROR_FAIL;
|
|
}
|
|
riscv_program_sw(&program, GDB_REGNO_S1, r_data);
|
|
riscv_program_sx(&program, GDB_REGNO_S0, r_addr);
|
|
|
|
/* The first round through the program's execution we use the regular
|
|
* program execution mechanism. */
|
|
switch (riscv_xlen(target)) {
|
|
case 64:
|
|
riscv_program_write_ram(&program, r_addr + 4, (((riscv_addr_t) address) - size) >> 32);
|
|
case 32:
|
|
riscv_program_write_ram(&program, r_addr, ((riscv_addr_t) address) - size);
|
|
break;
|
|
default:
|
|
LOG_ERROR("unknown XLEN %d", riscv_xlen(target));
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
if (riscv_program_exec(&program, target) != ERROR_OK) {
|
|
uint32_t acs = dmi_read(target, DMI_ABSTRACTCS);
|
|
LOG_ERROR("failed to execute program, abstractcs=0x%08x", acs);
|
|
riscv013_clear_abstract_error(target);
|
|
riscv_set_register(target, GDB_REGNO_S0, s0);
|
|
riscv_set_register(target, GDB_REGNO_S1, s1);
|
|
LOG_ERROR(" exiting with ERROR_FAIL");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
uint32_t value = riscv_program_read_ram(&program, r_data);
|
|
LOG_DEBUG("M[0x%" TARGET_PRIxADDR "] reads 0x%08lx", address, (long)value);
|
|
switch (size) {
|
|
case 1:
|
|
buffer[0] = value;
|
|
break;
|
|
case 2:
|
|
buffer[0] = value;
|
|
buffer[1] = value >> 8;
|
|
break;
|
|
case 4:
|
|
buffer[0] = value;
|
|
buffer[1] = value >> 8;
|
|
buffer[2] = value >> 16;
|
|
buffer[3] = value >> 24;
|
|
break;
|
|
default:
|
|
LOG_ERROR("unsupported access size: %d", size);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
/* The rest of this program is designed to be fast so it reads various
|
|
* DMI registers directly. */
|
|
int d_data = (r_data - riscv_debug_buffer_addr(target)) / 4;
|
|
int d_addr = (r_addr - riscv_debug_buffer_addr(target)) / 4;
|
|
|
|
riscv013_set_autoexec(target, d_data, 1);
|
|
|
|
/* Copying memory might fail because we're going too quickly, in which
|
|
* case we need to back off a bit and try again. There's two
|
|
* termination conditions to this loop: a non-BUSY error message, or
|
|
* the data was all copied. */
|
|
riscv_addr_t cur_addr = 0xbadbeef;
|
|
riscv_addr_t fin_addr = address + (count * size);
|
|
riscv_addr_t prev_addr = ((riscv_addr_t) address) - size;
|
|
LOG_DEBUG("writing until final address 0x%" PRIx64, fin_addr);
|
|
while (count > 1 && (cur_addr = riscv_read_debug_buffer_x(target, d_addr)) < fin_addr) {
|
|
LOG_DEBUG("transferring burst starting at address 0x%" TARGET_PRIxADDR
|
|
" (previous burst was 0x%" TARGET_PRIxADDR ")", cur_addr,
|
|
prev_addr);
|
|
assert(prev_addr < cur_addr);
|
|
prev_addr = cur_addr;
|
|
riscv_addr_t start = (cur_addr - address) / size;
|
|
assert (cur_addr >= address);
|
|
struct riscv_batch *batch = riscv_batch_alloc(
|
|
target,
|
|
32,
|
|
info->dmi_busy_delay + info->ac_busy_delay);
|
|
|
|
size_t reads = 0;
|
|
size_t rereads = reads;
|
|
for (riscv_addr_t i = start; i < count; ++i) {
|
|
size_t index =
|
|
riscv_batch_add_dmi_read(
|
|
batch,
|
|
riscv013_debug_buffer_register(target, r_data));
|
|
assert(index == reads);
|
|
reads++;
|
|
if (riscv_batch_full(batch))
|
|
break;
|
|
}
|
|
|
|
riscv_batch_run(batch);
|
|
|
|
for (size_t i = start; i < start + reads; ++i) {
|
|
riscv_addr_t offset = size*i;
|
|
riscv_addr_t t_addr = address + offset;
|
|
uint8_t *t_buffer = buffer + offset;
|
|
|
|
uint64_t dmi_out = riscv_batch_get_dmi_read(batch, rereads);
|
|
value = get_field(dmi_out, DTM_DMI_DATA);
|
|
rereads++;
|
|
|
|
switch (size) {
|
|
case 1:
|
|
t_buffer[0] = value;
|
|
break;
|
|
case 2:
|
|
t_buffer[0] = value;
|
|
t_buffer[1] = value >> 8;
|
|
break;
|
|
case 4:
|
|
t_buffer[0] = value;
|
|
t_buffer[1] = value >> 8;
|
|
t_buffer[2] = value >> 16;
|
|
t_buffer[3] = value >> 24;
|
|
break;
|
|
default:
|
|
LOG_ERROR("unsupported access size: %d", size);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
LOG_DEBUG("M[0x%08lx] reads 0x%08lx", (long)t_addr, (long)value);
|
|
}
|
|
|
|
riscv_batch_free(batch);
|
|
|
|
// Note that if the scan resulted in a Busy DMI response, it
|
|
// is this read to abstractcs that will cause the dmi_busy_delay
|
|
// to be incremented if necessary. The loop condition above
|
|
// catches the case where no writes went through at all.
|
|
|
|
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
|
|
abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
switch (get_field(abstractcs, DMI_ABSTRACTCS_CMDERR)) {
|
|
case CMDERR_NONE:
|
|
LOG_DEBUG("successful (partial?) memory write");
|
|
break;
|
|
case CMDERR_BUSY:
|
|
LOG_DEBUG("memory write resulted in busy response");
|
|
riscv013_clear_abstract_error(target);
|
|
increase_ac_busy_delay(target);
|
|
break;
|
|
default:
|
|
LOG_ERROR("error when writing memory, abstractcs=0x%08lx", (long)abstractcs);
|
|
riscv013_set_autoexec(target, d_data, 0);
|
|
riscv_set_register(target, GDB_REGNO_S0, s0);
|
|
riscv_set_register(target, GDB_REGNO_S1, s1);
|
|
riscv013_clear_abstract_error(target);
|
|
return ERROR_FAIL;
|
|
}
|
|
}
|
|
|
|
riscv013_set_autoexec(target, d_data, 0);
|
|
riscv_set_register(target, GDB_REGNO_S0, s0);
|
|
riscv_set_register(target, GDB_REGNO_S1, s1);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int write_memory(struct target *target, target_addr_t address,
|
|
uint32_t size, uint32_t count, const uint8_t *buffer)
|
|
{
|
|
RISCV013_INFO(info);
|
|
|
|
LOG_DEBUG("writing %d words of %d bytes to 0x%08lx", count, size, (long)address);
|
|
|
|
select_dmi(target);
|
|
/* There was a bug in the memory system and only accesses from hart 0 actually
|
|
* worked correctly. This should be obselete now. -palmer */
|
|
riscv_set_current_hartid(target, 0);
|
|
|
|
/* This program uses two temporary registers. A word of data and the
|
|
* associated address are stored at some location in memory. The
|
|
* program stores the word to that address and then increments the
|
|
* address. The debugger is expected to feed the memory word-by-word
|
|
* into the chip with AUTOEXEC set in order to trigger program
|
|
* execution on every word. */
|
|
uint64_t s0 = riscv_get_register(target, GDB_REGNO_S0);
|
|
uint64_t s1 = riscv_get_register(target, GDB_REGNO_S1);
|
|
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_addr_t r_data = riscv_program_alloc_w(&program);
|
|
riscv_addr_t r_addr = riscv_program_alloc_x(&program);
|
|
riscv_program_fence(&program);
|
|
riscv_program_lx(&program, GDB_REGNO_S0, r_addr);
|
|
riscv_program_lw(&program, GDB_REGNO_S1, r_data);
|
|
|
|
switch (size) {
|
|
case 1:
|
|
riscv_program_sbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
case 2:
|
|
riscv_program_shr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
case 4:
|
|
riscv_program_swr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
default:
|
|
LOG_ERROR("Unsupported size: %d", size);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
|
|
riscv_program_sx(&program, GDB_REGNO_S0, r_addr);
|
|
|
|
/* The first round through the program's execution we use the regular
|
|
* program execution mechanism. */
|
|
uint32_t value;
|
|
switch (size) {
|
|
case 1:
|
|
value = buffer[0];
|
|
break;
|
|
case 2:
|
|
value = buffer[0]
|
|
| ((uint32_t) buffer[1] << 8);
|
|
break;
|
|
case 4:
|
|
value = buffer[0]
|
|
| ((uint32_t) buffer[1] << 8)
|
|
| ((uint32_t) buffer[2] << 16)
|
|
| ((uint32_t) buffer[3] << 24);
|
|
break;
|
|
default:
|
|
LOG_ERROR("unsupported access size: %d", size);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
switch (riscv_xlen(target)) {
|
|
case 64:
|
|
riscv_program_write_ram(&program, r_addr + 4, (uint64_t)address >> 32);
|
|
case 32:
|
|
riscv_program_write_ram(&program, r_addr, address);
|
|
break;
|
|
default:
|
|
LOG_ERROR("unknown XLEN %d", riscv_xlen(target));
|
|
return ERROR_FAIL;
|
|
}
|
|
riscv_program_write_ram(&program, r_data, value);
|
|
|
|
LOG_DEBUG("M[0x%08lx] writes 0x%08lx", (long)address, (long)value);
|
|
|
|
if (riscv_program_exec(&program, target) != ERROR_OK) {
|
|
uint32_t acs = dmi_read(target, DMI_ABSTRACTCS);
|
|
LOG_ERROR("failed to execute program, abstractcs=0x%08x", acs);
|
|
riscv013_clear_abstract_error(target);
|
|
riscv_set_register(target, GDB_REGNO_S0, s0);
|
|
riscv_set_register(target, GDB_REGNO_S1, s1);
|
|
LOG_ERROR(" exiting with ERROR_FAIL");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
/* The rest of this program is designed to be fast so it reads various
|
|
* DMI registers directly. */
|
|
int d_data = (r_data - riscv_debug_buffer_addr(target)) / 4;
|
|
int d_addr = (r_addr - riscv_debug_buffer_addr(target)) / 4;
|
|
|
|
riscv013_set_autoexec(target, d_data, 1);
|
|
|
|
/* Copying memory might fail because we're going too quickly, in which
|
|
* case we need to back off a bit and try again. There's two
|
|
* termination conditions to this loop: a non-BUSY error message, or
|
|
* the data was all copied. */
|
|
riscv_addr_t cur_addr = 0xbadbeef;
|
|
riscv_addr_t fin_addr = address + (count * size);
|
|
LOG_DEBUG("writing until final address 0x%016" PRIx64, fin_addr);
|
|
while ((cur_addr = riscv_read_debug_buffer_x(target, d_addr)) < fin_addr) {
|
|
LOG_DEBUG("transferring burst starting at address 0x%016" PRIx64,
|
|
cur_addr);
|
|
riscv_addr_t start = (cur_addr - address) / size;
|
|
assert (cur_addr > address);
|
|
struct riscv_batch *batch = riscv_batch_alloc(
|
|
target,
|
|
32,
|
|
info->dmi_busy_delay + info->ac_busy_delay);
|
|
|
|
for (riscv_addr_t i = start; i < count; ++i) {
|
|
riscv_addr_t offset = size*i;
|
|
riscv_addr_t t_addr = address + offset;
|
|
const uint8_t *t_buffer = buffer + offset;
|
|
|
|
switch (size) {
|
|
case 1:
|
|
value = t_buffer[0];
|
|
break;
|
|
case 2:
|
|
value = t_buffer[0]
|
|
| ((uint32_t) t_buffer[1] << 8);
|
|
break;
|
|
case 4:
|
|
value = t_buffer[0]
|
|
| ((uint32_t) t_buffer[1] << 8)
|
|
| ((uint32_t) t_buffer[2] << 16)
|
|
| ((uint32_t) t_buffer[3] << 24);
|
|
break;
|
|
default:
|
|
LOG_ERROR("unsupported access size: %d", size);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
LOG_DEBUG("M[0x%08lx] writes 0x%08lx", (long)t_addr, (long)value);
|
|
|
|
riscv_batch_add_dmi_write(
|
|
batch,
|
|
riscv013_debug_buffer_register(target, r_data),
|
|
value);
|
|
if (riscv_batch_full(batch))
|
|
break;
|
|
}
|
|
|
|
riscv_batch_run(batch);
|
|
riscv_batch_free(batch);
|
|
|
|
// Note that if the scan resulted in a Busy DMI response, it
|
|
// is this read to abstractcs that will cause the dmi_busy_delay
|
|
// to be incremented if necessary. The loop condition above
|
|
// catches the case where no writes went through at all.
|
|
|
|
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
|
|
abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
switch (get_field(abstractcs, DMI_ABSTRACTCS_CMDERR)) {
|
|
case CMDERR_NONE:
|
|
LOG_DEBUG("successful (partial?) memory write");
|
|
break;
|
|
case CMDERR_BUSY:
|
|
LOG_DEBUG("memory write resulted in busy response");
|
|
riscv013_clear_abstract_error(target);
|
|
increase_ac_busy_delay(target);
|
|
break;
|
|
default:
|
|
LOG_ERROR("error when writing memory, abstractcs=0x%08lx", (long)abstractcs);
|
|
riscv013_set_autoexec(target, d_data, 0);
|
|
riscv013_clear_abstract_error(target);
|
|
riscv_set_register(target, GDB_REGNO_S0, s0);
|
|
riscv_set_register(target, GDB_REGNO_S1, s1);
|
|
return ERROR_FAIL;
|
|
}
|
|
}
|
|
|
|
riscv013_set_autoexec(target, d_data, 0);
|
|
riscv_set_register(target, GDB_REGNO_S0, s0);
|
|
riscv_set_register(target, GDB_REGNO_S1, s1);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int arch_state(struct target *target)
|
|
{
|
|
return ERROR_OK;
|
|
}
|
|
|
|
struct target_type riscv013_target =
|
|
{
|
|
.name = "riscv",
|
|
|
|
.init_target = init_target,
|
|
.deinit_target = deinit_target,
|
|
.examine = examine,
|
|
|
|
.poll = &riscv_openocd_poll,
|
|
.halt = &riscv_openocd_halt,
|
|
.resume = &riscv_openocd_resume,
|
|
.step = &riscv_openocd_step,
|
|
|
|
.assert_reset = assert_reset,
|
|
.deassert_reset = deassert_reset,
|
|
|
|
.read_memory = read_memory,
|
|
.write_memory = write_memory,
|
|
|
|
.add_breakpoint = add_breakpoint,
|
|
.remove_breakpoint = remove_breakpoint,
|
|
|
|
.add_watchpoint = add_watchpoint,
|
|
.remove_watchpoint = remove_watchpoint,
|
|
|
|
.arch_state = arch_state,
|
|
};
|
|
|
|
/*** 0.13-specific implementations of various RISC-V hepler functions. ***/
|
|
static riscv_reg_t riscv013_get_register(struct target *target, int hid, int rid)
|
|
{
|
|
LOG_DEBUG("reading register 0x%08x on hart %d", rid, hid);
|
|
|
|
riscv_set_current_hartid(target, hid);
|
|
|
|
uint64_t out;
|
|
riscv013_info_t *info = get_info(target);
|
|
|
|
if (rid <= GDB_REGNO_XPR31) {
|
|
register_read_direct(target, &out, rid);
|
|
} else if (rid == GDB_REGNO_PC) {
|
|
register_read_direct(target, &out, GDB_REGNO_DPC);
|
|
LOG_DEBUG("read PC from DPC: 0x%016" PRIx64, out);
|
|
} else if (rid == GDB_REGNO_PRIV) {
|
|
uint64_t dcsr;
|
|
register_read_direct(target, &dcsr, CSR_DCSR);
|
|
buf_set_u64((unsigned char *)&out, 0, 8, get_field(dcsr, CSR_DCSR_PRV));
|
|
} else {
|
|
int result = register_read_direct(target, &out, rid);
|
|
if (result != ERROR_OK) {
|
|
LOG_ERROR("Unable to read register %d", rid);
|
|
out = -1;
|
|
}
|
|
|
|
if (rid == GDB_REGNO_MSTATUS)
|
|
info->mstatus_actual = out;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
static void riscv013_set_register(struct target *target, int hid, int rid, uint64_t value)
|
|
{
|
|
LOG_DEBUG("writing register 0x%08x on hart %d", rid, hid);
|
|
|
|
riscv_set_current_hartid(target, hid);
|
|
|
|
if (rid <= GDB_REGNO_XPR31) {
|
|
register_write_direct(target, rid, value);
|
|
} else if (rid == GDB_REGNO_PC) {
|
|
LOG_DEBUG("writing PC to DPC: 0x%016" PRIx64, value);
|
|
register_write_direct(target, GDB_REGNO_DPC, value);
|
|
uint64_t actual_value;
|
|
register_read_direct(target, &actual_value, GDB_REGNO_DPC);
|
|
LOG_DEBUG(" actual DPC written: 0x%016" PRIx64, actual_value);
|
|
assert(value == actual_value);
|
|
} else if (rid == GDB_REGNO_PRIV) {
|
|
uint64_t dcsr;
|
|
register_read_direct(target, &dcsr, CSR_DCSR);
|
|
dcsr = set_field(dcsr, CSR_DCSR_PRV, value);
|
|
register_write_direct(target, CSR_DCSR, dcsr);
|
|
} else {
|
|
register_write_direct(target, rid, value);
|
|
}
|
|
}
|
|
|
|
static void riscv013_select_current_hart(struct target *target)
|
|
{
|
|
RISCV_INFO(r);
|
|
|
|
uint64_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HARTSEL, r->current_hartid);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
}
|
|
|
|
static void riscv013_halt_current_hart(struct target *target)
|
|
{
|
|
RISCV_INFO(r);
|
|
LOG_DEBUG("halting hart %d", r->current_hartid);
|
|
assert(!riscv_is_halted(target));
|
|
|
|
/* Issue the halt command, and then wait for the current hart to halt. */
|
|
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 1);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
for (size_t i = 0; i < 256; ++i)
|
|
if (riscv_is_halted(target))
|
|
break;
|
|
|
|
if (!riscv_is_halted(target)) {
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
|
|
LOG_ERROR("unable to halt hart %d", r->current_hartid);
|
|
LOG_ERROR(" dmcontrol=0x%08x", dmcontrol);
|
|
LOG_ERROR(" dmstatus =0x%08x", dmstatus);
|
|
abort();
|
|
}
|
|
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 0);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
}
|
|
|
|
static void riscv013_resume_current_hart(struct target *target)
|
|
{
|
|
return riscv013_step_or_resume_current_hart(target, false);
|
|
}
|
|
|
|
static void riscv013_step_current_hart(struct target *target)
|
|
{
|
|
return riscv013_step_or_resume_current_hart(target, true);
|
|
}
|
|
|
|
static void riscv013_on_resume(struct target *target)
|
|
{
|
|
return riscv013_on_step_or_resume(target, false);
|
|
}
|
|
|
|
static void riscv013_on_step(struct target *target)
|
|
{
|
|
return riscv013_on_step_or_resume(target, true);
|
|
}
|
|
|
|
static void riscv013_on_halt(struct target *target)
|
|
{
|
|
}
|
|
|
|
static bool riscv013_is_halted(struct target *target)
|
|
{
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL))
|
|
LOG_ERROR("hart %d is unavailiable", riscv_current_hartid(target));
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ANYNONEXISTENT))
|
|
LOG_ERROR("hart %d doesn't exist", riscv_current_hartid(target));
|
|
return get_field(dmstatus, DMI_DMSTATUS_ALLHALTED);
|
|
}
|
|
|
|
static enum riscv_halt_reason riscv013_halt_reason(struct target *target)
|
|
{
|
|
uint64_t dcsr = riscv_get_register(target, GDB_REGNO_DCSR);
|
|
switch (get_field(dcsr, CSR_DCSR_CAUSE)) {
|
|
case CSR_DCSR_CAUSE_SWBP:
|
|
case CSR_DCSR_CAUSE_TRIGGER:
|
|
return RISCV_HALT_BREAKPOINT;
|
|
case CSR_DCSR_CAUSE_STEP:
|
|
return RISCV_HALT_SINGLESTEP;
|
|
case CSR_DCSR_CAUSE_DEBUGINT:
|
|
case CSR_DCSR_CAUSE_HALT:
|
|
return RISCV_HALT_INTERRUPT;
|
|
}
|
|
|
|
LOG_ERROR("Unknown DCSR cause field: %x", (int)get_field(dcsr, CSR_DCSR_CAUSE));
|
|
LOG_ERROR(" dcsr=0x%016lx", (long)dcsr);
|
|
abort();
|
|
}
|
|
|
|
void riscv013_debug_buffer_enter(struct target *target, struct riscv_program *program)
|
|
{
|
|
}
|
|
|
|
void riscv013_debug_buffer_leave(struct target *target, struct riscv_program *program)
|
|
{
|
|
}
|
|
|
|
void riscv013_write_debug_buffer(struct target *target, unsigned index, riscv_insn_t data)
|
|
{
|
|
if (index >= riscv013_progbuf_size(target))
|
|
return dmi_write(target, DMI_DATA0 + index - riscv013_progbuf_size(target), data);
|
|
return dmi_write(target, DMI_PROGBUF0 + index, data);
|
|
}
|
|
|
|
riscv_insn_t riscv013_read_debug_buffer(struct target *target, unsigned index)
|
|
{
|
|
if (index >= riscv013_progbuf_size(target))
|
|
return dmi_read(target, DMI_DATA0 + index - riscv013_progbuf_size(target));
|
|
return dmi_read(target, DMI_PROGBUF0 + index);
|
|
}
|
|
|
|
int riscv013_execute_debug_buffer(struct target *target)
|
|
{
|
|
riscv013_clear_abstract_error(target);
|
|
|
|
uint32_t run_program = 0;
|
|
run_program = set_field(run_program, AC_ACCESS_REGISTER_SIZE, 2);
|
|
run_program = set_field(run_program, AC_ACCESS_REGISTER_POSTEXEC, 1);
|
|
run_program = set_field(run_program, AC_ACCESS_REGISTER_TRANSFER, 0);
|
|
run_program = set_field(run_program, AC_ACCESS_REGISTER_REGNO, 0x1000);
|
|
dmi_write(target, DMI_COMMAND, run_program);
|
|
|
|
{
|
|
uint32_t dmstatus = 0;
|
|
wait_for_idle(target, &dmstatus);
|
|
}
|
|
|
|
uint32_t cs = dmi_read(target, DMI_ABSTRACTCS);
|
|
if (get_field(cs, DMI_ABSTRACTCS_CMDERR) != 0) {
|
|
LOG_ERROR("unable to execute program: (abstractcs=0x%08x)", cs);
|
|
dmi_read(target, DMI_DMSTATUS);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
void riscv013_fill_dmi_write_u64(struct target *target, char *buf, int a, uint64_t d)
|
|
{
|
|
RISCV013_INFO(info);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, DMI_OP_WRITE);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, d);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, a);
|
|
}
|
|
|
|
void riscv013_fill_dmi_read_u64(struct target *target, char *buf, int a)
|
|
{
|
|
RISCV013_INFO(info);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, DMI_OP_READ);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, 0);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, a);
|
|
}
|
|
|
|
void riscv013_fill_dmi_nop_u64(struct target *target, char *buf)
|
|
{
|
|
RISCV013_INFO(info);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, DMI_OP_NOP);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, 0);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, 0);
|
|
}
|
|
|
|
int riscv013_dmi_write_u64_bits(struct target *target)
|
|
{
|
|
RISCV013_INFO(info);
|
|
return info->abits + DTM_DMI_DATA_LENGTH + DTM_DMI_OP_LENGTH;
|
|
}
|
|
|
|
void riscv013_reset_current_hart(struct target *target)
|
|
{
|
|
select_dmi(target);
|
|
uint32_t control = dmi_read(target, DMI_DMCONTROL);
|
|
control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ, 1);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
|
|
control = set_field(control, DMI_DMCONTROL_NDMRESET, 0);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
|
|
while (get_field(dmi_read(target, DMI_DMSTATUS), DMI_DMSTATUS_ALLHALTED) == 0);
|
|
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ, 0);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
}
|
|
|
|
/* Helper Functions. */
|
|
static void riscv013_on_step_or_resume(struct target *target, bool step)
|
|
{
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_program_fence_i(&program);
|
|
if (riscv_program_exec(&program, target) != ERROR_OK)
|
|
LOG_ERROR("Unable to execute fence.i");
|
|
|
|
/* We want to twiddle some bits in the debug CSR so debugging works. */
|
|
uint64_t dcsr = riscv_get_register(target, GDB_REGNO_DCSR);
|
|
dcsr = set_field(dcsr, CSR_DCSR_STEP, step);
|
|
dcsr = set_field(dcsr, CSR_DCSR_EBREAKM, 1);
|
|
dcsr = set_field(dcsr, CSR_DCSR_EBREAKH, 1);
|
|
dcsr = set_field(dcsr, CSR_DCSR_EBREAKS, 1);
|
|
dcsr = set_field(dcsr, CSR_DCSR_EBREAKU, 1);
|
|
riscv_set_register(target, GDB_REGNO_DCSR, dcsr);
|
|
}
|
|
|
|
static void riscv013_step_or_resume_current_hart(struct target *target, bool step)
|
|
{
|
|
RISCV_INFO(r);
|
|
LOG_DEBUG("resuming hart %d (for step?=%d)", r->current_hartid, step);
|
|
assert(riscv_is_halted(target));
|
|
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_program_fence_i(&program);
|
|
if (riscv_program_exec(&program, target) != ERROR_OK)
|
|
abort();
|
|
|
|
/* Issue the resume command, and then wait for the current hart to resume. */
|
|
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 1);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
|
|
for (size_t i = 0; i < 256; ++i) {
|
|
usleep(10);
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ALLRESUMEACK) == 0)
|
|
continue;
|
|
if (step && get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0)
|
|
continue;
|
|
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 0);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
return;
|
|
}
|
|
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
LOG_ERROR("unable to resume hart %d", r->current_hartid);
|
|
LOG_ERROR(" dmcontrol=0x%08x", dmcontrol);
|
|
LOG_ERROR(" dmstatus =0x%08x", dmstatus);
|
|
|
|
if (step) {
|
|
LOG_ERROR(" was stepping, halting");
|
|
riscv013_halt_current_hart(target);
|
|
return;
|
|
}
|
|
|
|
abort();
|
|
}
|
|
|
|
riscv_addr_t riscv013_progbuf_addr(struct target *target)
|
|
{
|
|
RISCV013_INFO(info);
|
|
assert(info->progbuf_addr != -1);
|
|
return info->progbuf_addr;
|
|
}
|
|
|
|
riscv_addr_t riscv013_progbuf_size(struct target *target)
|
|
{
|
|
RISCV013_INFO(info);
|
|
if (info->progbuf_size == -1) {
|
|
uint32_t acs = dmi_read(target, DMI_ABSTRACTCS);
|
|
info->progbuf_size = get_field(acs, DMI_ABSTRACTCS_PROGSIZE);
|
|
}
|
|
return info->progbuf_size;
|
|
}
|
|
|
|
riscv_addr_t riscv013_data_size(struct target *target)
|
|
{
|
|
RISCV013_INFO(info);
|
|
if (info->data_size == -1) {
|
|
uint32_t acs = dmi_read(target, DMI_HARTINFO);
|
|
info->data_size = get_field(acs, DMI_HARTINFO_DATASIZE);
|
|
}
|
|
return info->data_size;
|
|
}
|
|
|
|
riscv_addr_t riscv013_data_addr(struct target *target)
|
|
{
|
|
RISCV013_INFO(info);
|
|
if (info->data_addr == -1) {
|
|
uint32_t acs = dmi_read(target, DMI_HARTINFO);
|
|
info->data_addr = get_field(acs, DMI_HARTINFO_DATAACCESS) ? get_field(acs, DMI_HARTINFO_DATAADDR) : 0;
|
|
}
|
|
return info->data_addr;
|
|
}
|
|
|
|
void riscv013_set_autoexec(struct target *target, unsigned index, bool enabled)
|
|
{
|
|
if (index >= riscv013_progbuf_size(target)) {
|
|
LOG_DEBUG("setting bit %d in AUTOEXECDATA to %d", index, enabled);
|
|
uint32_t aa = dmi_read(target, DMI_ABSTRACTAUTO);
|
|
uint32_t aa_aed = get_field(aa, DMI_ABSTRACTAUTO_AUTOEXECDATA);
|
|
aa_aed &= ~(1 << (index - riscv013_progbuf_size(target)));
|
|
aa_aed |= (enabled << (index - riscv013_progbuf_size(target)));
|
|
aa = set_field(aa, DMI_ABSTRACTAUTO_AUTOEXECDATA, aa_aed);
|
|
dmi_write(target, DMI_ABSTRACTAUTO, aa);
|
|
} else {
|
|
LOG_DEBUG("setting bit %d in AUTOEXECPROGBUF to %d", index, enabled);
|
|
uint32_t aa = dmi_read(target, DMI_ABSTRACTAUTO);
|
|
uint32_t aa_aed = get_field(aa, DMI_ABSTRACTAUTO_AUTOEXECPROGBUF);
|
|
aa_aed &= ~(1 << index);
|
|
aa_aed |= (enabled << index);
|
|
aa = set_field(aa, DMI_ABSTRACTAUTO_AUTOEXECPROGBUF, aa_aed);
|
|
dmi_write(target, DMI_ABSTRACTAUTO, aa);
|
|
}
|
|
}
|
|
|
|
int riscv013_debug_buffer_register(struct target *target, riscv_addr_t addr)
|
|
{
|
|
if (addr >= riscv013_data_addr(target))
|
|
return DMI_DATA0 + (addr - riscv013_data_addr(target)) / 4;
|
|
else
|
|
return DMI_PROGBUF0 + (addr - riscv013_progbuf_addr(target)) / 4;
|
|
}
|
|
|
|
void riscv013_clear_abstract_error(struct target *target)
|
|
{
|
|
uint32_t acs = dmi_read(target, DMI_ABSTRACTCS);
|
|
dmi_write(target, DMI_ABSTRACTCS, acs);
|
|
}
|