Share trigger code between 0.11 and 0.13 code.
The actual implementation of triggers didn't change between those two versions, so there's no need to duplicate the code. In the process, I also fixed a minor multicore bug where tselect didn't always get written on all harts.print_port
parent
cc2c2e7a65
commit
d60dbd60e8
|
@ -171,7 +171,6 @@ enum {
|
||||||
REG_COUNT
|
REG_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MAX_HWBPS 16
|
|
||||||
#define DRAM_CACHE_SIZE 16
|
#define DRAM_CACHE_SIZE 16
|
||||||
|
|
||||||
struct trigger {
|
struct trigger {
|
||||||
|
@ -196,7 +195,6 @@ typedef struct {
|
||||||
unsigned int dramsize;
|
unsigned int dramsize;
|
||||||
uint64_t dcsr;
|
uint64_t dcsr;
|
||||||
uint64_t dpc;
|
uint64_t dpc;
|
||||||
uint64_t misa;
|
|
||||||
uint64_t tselect;
|
uint64_t tselect;
|
||||||
bool tselect_dirty;
|
bool tselect_dirty;
|
||||||
/* The value that mstatus actually has on the target right now. This is not
|
/* The value that mstatus actually has on the target right now. This is not
|
||||||
|
@ -212,10 +210,6 @@ typedef struct {
|
||||||
/* Single buffer that contains all register values. */
|
/* Single buffer that contains all register values. */
|
||||||
void *reg_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
|
// Number of run-test/idle cycles the target requests we do after each dbus
|
||||||
// access.
|
// access.
|
||||||
unsigned int dtmcontrol_idle;
|
unsigned int dtmcontrol_idle;
|
||||||
|
@ -1519,9 +1513,6 @@ static int init_target(struct command_context *cmd_ctx,
|
||||||
reg_name += strlen(reg_name) + 1;
|
reg_name += strlen(reg_name) + 1;
|
||||||
assert(reg_name < info->reg_names + REG_COUNT * max_reg_name_len);
|
assert(reg_name < info->reg_names + REG_COUNT * max_reg_name_len);
|
||||||
}
|
}
|
||||||
update_reg_list(target);
|
|
||||||
|
|
||||||
memset(info->trigger_unique_id, 0xff, sizeof(info->trigger_unique_id));
|
|
||||||
|
|
||||||
return ERROR_OK;
|
return ERROR_OK;
|
||||||
}
|
}
|
||||||
|
@ -1534,306 +1525,6 @@ static void deinit_target(struct target *target)
|
||||||
info->version_specific = NULL;
|
info->version_specific = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int maybe_add_trigger_t1(struct target *target, struct trigger *trigger,
|
|
||||||
uint64_t tdata1)
|
|
||||||
{
|
|
||||||
riscv011_info_t *info = get_info(target);
|
|
||||||
|
|
||||||
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, !!(info->misa & (1 << ('U' - 'A'))));
|
|
||||||
tdata1 = set_field(tdata1, bpcontrol_s, !!(info->misa & (1 << ('S' - 'A'))));
|
|
||||||
tdata1 = set_field(tdata1, bpcontrol_h, !!(info->misa & (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
|
|
||||||
|
|
||||||
write_csr(target, CSR_TDATA1, tdata1);
|
|
||||||
|
|
||||||
uint64_t tdata1_rb;
|
|
||||||
read_csr(target, &tdata1_rb, CSR_TDATA1);
|
|
||||||
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);
|
|
||||||
write_csr(target, CSR_TDATA1, 0);
|
|
||||||
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_csr(target, CSR_TDATA2, trigger->address);
|
|
||||||
|
|
||||||
return ERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int maybe_add_trigger_t2(struct target *target, struct trigger *trigger,
|
|
||||||
uint64_t tdata1)
|
|
||||||
{
|
|
||||||
riscv011_info_t *info = get_info(target);
|
|
||||||
// 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 (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;
|
|
||||||
|
|
||||||
write_csr(target, CSR_TDATA1, tdata1);
|
|
||||||
|
|
||||||
uint64_t tdata1_rb;
|
|
||||||
read_csr(target, &tdata1_rb, CSR_TDATA1);
|
|
||||||
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);
|
|
||||||
write_csr(target, CSR_TDATA1, 0);
|
|
||||||
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_csr(target, CSR_TDATA2, trigger->address);
|
|
||||||
|
|
||||||
return ERROR_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int add_trigger(struct target *target, struct trigger *trigger)
|
|
||||||
{
|
|
||||||
riscv011_info_t *info = get_info(target);
|
|
||||||
RISCV_INFO(r);
|
|
||||||
|
|
||||||
maybe_read_tselect(target);
|
|
||||||
|
|
||||||
unsigned int i;
|
|
||||||
for (i = 0; i < r->trigger_count[0]; i++) {
|
|
||||||
if (info->trigger_unique_id[i] != -1) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_csr(target, CSR_TSELECT, i);
|
|
||||||
|
|
||||||
uint64_t tdata1;
|
|
||||||
read_csr(target, &tdata1, CSR_TDATA1);
|
|
||||||
int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
|
|
||||||
|
|
||||||
int result;
|
|
||||||
switch (type) {
|
|
||||||
case 1:
|
|
||||||
result = maybe_add_trigger_t1(target, trigger, tdata1);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
result = maybe_add_trigger_t2(target, trigger, tdata1);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LOG_DEBUG("trigger %d has unknown type %d", i, type);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != ERROR_OK) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_DEBUG("Using resource %d for bp %d", i,
|
|
||||||
trigger->unique_id);
|
|
||||||
info->trigger_unique_id[i] = trigger->unique_id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (i >= r->trigger_count[0]) {
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
RISCV_INFO(r);
|
|
||||||
riscv011_info_t *info = get_info(target);
|
|
||||||
|
|
||||||
maybe_read_tselect(target);
|
|
||||||
|
|
||||||
unsigned int i;
|
|
||||||
for (i = 0; i < r->trigger_count[0]; i++) {
|
|
||||||
if (info->trigger_unique_id[i] == trigger->unique_id) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (i >= r->trigger_count[0]) {
|
|
||||||
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);
|
|
||||||
write_csr(target, CSR_TSELECT, i);
|
|
||||||
write_csr(target, CSR_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 strict_step(struct target *target, bool announce)
|
static int strict_step(struct target *target, bool announce)
|
||||||
{
|
{
|
||||||
riscv011_info_t *info = get_info(target);
|
riscv011_info_t *info = get_info(target);
|
||||||
|
@ -1842,13 +1533,13 @@ static int strict_step(struct target *target, bool announce)
|
||||||
|
|
||||||
struct breakpoint *breakpoint = target->breakpoints;
|
struct breakpoint *breakpoint = target->breakpoints;
|
||||||
while (breakpoint) {
|
while (breakpoint) {
|
||||||
remove_breakpoint(target, breakpoint);
|
riscv_remove_breakpoint(target, breakpoint);
|
||||||
breakpoint = breakpoint->next;
|
breakpoint = breakpoint->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct watchpoint *watchpoint = target->watchpoints;
|
struct watchpoint *watchpoint = target->watchpoints;
|
||||||
while (watchpoint) {
|
while (watchpoint) {
|
||||||
remove_watchpoint(target, watchpoint);
|
riscv_remove_watchpoint(target, watchpoint);
|
||||||
watchpoint = watchpoint->next;
|
watchpoint = watchpoint->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1858,13 +1549,13 @@ static int strict_step(struct target *target, bool announce)
|
||||||
|
|
||||||
breakpoint = target->breakpoints;
|
breakpoint = target->breakpoints;
|
||||||
while (breakpoint) {
|
while (breakpoint) {
|
||||||
add_breakpoint(target, breakpoint);
|
riscv_add_breakpoint(target, breakpoint);
|
||||||
breakpoint = breakpoint->next;
|
breakpoint = breakpoint->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
watchpoint = target->watchpoints;
|
watchpoint = target->watchpoints;
|
||||||
while (watchpoint) {
|
while (watchpoint) {
|
||||||
add_watchpoint(target, watchpoint);
|
riscv_add_watchpoint(target, watchpoint);
|
||||||
watchpoint = watchpoint->next;
|
watchpoint = watchpoint->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1962,6 +1653,9 @@ static int examine(struct target *target)
|
||||||
return ERROR_FAIL;
|
return ERROR_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pretend this is a 32-bit system until we have found out the true value.
|
||||||
|
r->xlen[0] = 32;
|
||||||
|
|
||||||
// Figure out XLEN, and test writing all of Debug RAM while we're at it.
|
// Figure out XLEN, and test writing all of Debug RAM while we're at it.
|
||||||
cache_set32(target, 0, xori(S1, ZERO, -1));
|
cache_set32(target, 0, xori(S1, ZERO, -1));
|
||||||
// 0xffffffff 0xffffffff:ffffffff 0xffffffff:ffffffff:ffffffff:ffffffff
|
// 0xffffffff 0xffffffff:ffffffff 0xffffffff:ffffffff:ffffffff:ffffffff
|
||||||
|
@ -2007,12 +1701,14 @@ static int examine(struct target *target)
|
||||||
// Update register list to match discovered XLEN.
|
// Update register list to match discovered XLEN.
|
||||||
update_reg_list(target);
|
update_reg_list(target);
|
||||||
|
|
||||||
if (read_csr(target, &info->misa, CSR_MISA) != ERROR_OK) {
|
if (read_csr(target, &r->misa, CSR_MISA) != ERROR_OK) {
|
||||||
LOG_WARNING("Failed to read misa at 0x%x.", CSR_MISA);
|
const unsigned old_csr_misa = 0xf10;
|
||||||
if (read_csr(target, &info->misa, 0xf10) != ERROR_OK) {
|
LOG_WARNING("Failed to read misa at 0x%x; trying 0x%x.", CSR_MISA,
|
||||||
|
old_csr_misa);
|
||||||
|
if (read_csr(target, &r->misa, old_csr_misa) != ERROR_OK) {
|
||||||
// Maybe this is an old core that still has $misa at the old
|
// Maybe this is an old core that still has $misa at the old
|
||||||
// address.
|
// address.
|
||||||
LOG_ERROR("Failed to read misa at 0x%x.", 0xf10);
|
LOG_ERROR("Failed to read misa at 0x%x.", old_csr_misa);
|
||||||
return ERROR_FAIL;
|
return ERROR_FAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2028,7 +1724,8 @@ static int examine(struct target *target)
|
||||||
riscv_set_current_hartid(target, 0);
|
riscv_set_current_hartid(target, 0);
|
||||||
for (size_t i = 0; i < 32; ++i)
|
for (size_t i = 0; i < 32; ++i)
|
||||||
reg_cache_set(target, i, -1);
|
reg_cache_set(target, i, -1);
|
||||||
LOG_INFO("Examined RISCV core; XLEN=%d, misa=0x%" PRIx64, riscv_xlen(target), info->misa);
|
LOG_INFO("Examined RISCV core; XLEN=%d, misa=0x%" PRIx64,
|
||||||
|
riscv_xlen(target), r->misa);
|
||||||
|
|
||||||
return ERROR_OK;
|
return ERROR_OK;
|
||||||
}
|
}
|
||||||
|
@ -2699,11 +2396,5 @@ struct target_type riscv011_target =
|
||||||
.read_memory = read_memory,
|
.read_memory = read_memory,
|
||||||
.write_memory = write_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,
|
.arch_state = arch_state,
|
||||||
};
|
};
|
||||||
|
|
|
@ -123,8 +123,6 @@ typedef enum slot {
|
||||||
|
|
||||||
#define WALL_CLOCK_TIMEOUT 2
|
#define WALL_CLOCK_TIMEOUT 2
|
||||||
|
|
||||||
#define MAX_HWBPS 16
|
|
||||||
|
|
||||||
struct trigger {
|
struct trigger {
|
||||||
uint64_t address;
|
uint64_t address;
|
||||||
uint32_t length;
|
uint32_t length;
|
||||||
|
@ -149,7 +147,6 @@ typedef struct {
|
||||||
unsigned progsize;
|
unsigned progsize;
|
||||||
/* Number of Program Buffer registers. */
|
/* Number of Program Buffer registers. */
|
||||||
/* Number of words in Debug RAM. */
|
/* Number of words in Debug RAM. */
|
||||||
uint64_t misa;
|
|
||||||
uint64_t tselect;
|
uint64_t tselect;
|
||||||
bool tselect_dirty;
|
bool tselect_dirty;
|
||||||
/* The value that mstatus actually has on the target right now. This is not
|
/* The value that mstatus actually has on the target right now. This is not
|
||||||
|
@ -163,10 +160,6 @@ typedef struct {
|
||||||
/* Single buffer that contains all register values. */
|
/* Single buffer that contains all register values. */
|
||||||
void *reg_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
|
// Number of run-test/idle cycles the target requests we do after each dbus
|
||||||
// access.
|
// access.
|
||||||
unsigned int dtmcontrol_idle;
|
unsigned int dtmcontrol_idle;
|
||||||
|
@ -298,7 +291,7 @@ static int register_get(struct reg *reg);
|
||||||
|
|
||||||
bool supports_extension(struct target *target, char letter)
|
bool supports_extension(struct target *target, char letter)
|
||||||
{
|
{
|
||||||
riscv013_info_t *info = get_info(target);
|
RISCV_INFO(r);
|
||||||
unsigned num;
|
unsigned num;
|
||||||
if (letter >= 'a' && letter <= 'z') {
|
if (letter >= 'a' && letter <= 'z') {
|
||||||
num = letter - 'a';
|
num = letter - 'a';
|
||||||
|
@ -307,7 +300,7 @@ bool supports_extension(struct target *target, char letter)
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return info->misa & (1 << num);
|
return r->misa & (1 << num);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void select_dmi(struct target *target)
|
static void select_dmi(struct target *target)
|
||||||
|
@ -844,20 +837,6 @@ static int register_read_direct(struct target *target, uint64_t *value, uint32_t
|
||||||
return ERROR_OK;
|
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. ***/
|
/*** OpenOCD target functions. ***/
|
||||||
|
|
||||||
static int register_get(struct reg *reg)
|
static int register_get(struct reg *reg)
|
||||||
|
@ -900,7 +879,6 @@ static int init_target(struct command_context *cmd_ctx,
|
||||||
LOG_DEBUG("init");
|
LOG_DEBUG("init");
|
||||||
riscv_info_t *generic_info = (riscv_info_t *) target->arch_info;
|
riscv_info_t *generic_info = (riscv_info_t *) target->arch_info;
|
||||||
|
|
||||||
riscv_info_init(target, generic_info);
|
|
||||||
generic_info->get_register = &riscv013_get_register;
|
generic_info->get_register = &riscv013_get_register;
|
||||||
generic_info->set_register = &riscv013_set_register;
|
generic_info->set_register = &riscv013_set_register;
|
||||||
generic_info->select_current_hart = &riscv013_select_current_hart;
|
generic_info->select_current_hart = &riscv013_select_current_hart;
|
||||||
|
@ -983,11 +961,6 @@ static int init_target(struct command_context *cmd_ctx,
|
||||||
reg_name += strlen(reg_name) + 1;
|
reg_name += strlen(reg_name) + 1;
|
||||||
assert(reg_name < info->reg_names + GDB_REGNO_COUNT * max_reg_name_len);
|
assert(reg_name < info->reg_names + 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;
|
return ERROR_OK;
|
||||||
}
|
}
|
||||||
|
@ -1000,269 +973,6 @@ static void deinit_target(struct target *target)
|
||||||
info->version_specific = NULL;
|
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) {
|
|
||||||
if (!riscv_hart_enabled(target, hartid))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if (!riscv_hart_enabled(target, hartid))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
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)
|
static int examine(struct target *target)
|
||||||
{
|
{
|
||||||
// Don't need to select dbus, since the first thing we do is read dtmcontrol.
|
// Don't need to select dbus, since the first thing we do is read dtmcontrol.
|
||||||
|
@ -1939,12 +1649,6 @@ struct target_type riscv013_target =
|
||||||
.read_memory = read_memory,
|
.read_memory = read_memory,
|
||||||
.write_memory = write_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,
|
.arch_state = arch_state,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -248,6 +248,7 @@ static int riscv_init_target(struct command_context *cmd_ctx,
|
||||||
if (!target->arch_info)
|
if (!target->arch_info)
|
||||||
return ERROR_FAIL;
|
return ERROR_FAIL;
|
||||||
riscv_info_t *info = (riscv_info_t *) target->arch_info;
|
riscv_info_t *info = (riscv_info_t *) target->arch_info;
|
||||||
|
riscv_info_init(target, info);
|
||||||
info->cmd_ctx = cmd_ctx;
|
info->cmd_ctx = cmd_ctx;
|
||||||
|
|
||||||
select_dtmcontrol.num_bits = target->tap->ir_length;
|
select_dtmcontrol.num_bits = target->tap->ir_length;
|
||||||
|
@ -273,32 +274,329 @@ static int oldriscv_halt(struct target *target)
|
||||||
return tt->halt(target);
|
return tt->halt(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int riscv_add_breakpoint(struct target *target,
|
static void trigger_from_breakpoint(struct trigger *trigger,
|
||||||
struct breakpoint *breakpoint)
|
const struct breakpoint *breakpoint)
|
||||||
{
|
{
|
||||||
struct target_type *tt = get_target_type(target);
|
trigger->address = breakpoint->address;
|
||||||
return tt->add_breakpoint(target, breakpoint);
|
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 riscv_remove_breakpoint(struct target *target,
|
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 & (1 << ('U' - 'A'))));
|
||||||
|
tdata1 = set_field(tdata1, bpcontrol_s, !!(r->misa & (1 << ('S' - 'A'))));
|
||||||
|
tdata1 = set_field(tdata1, bpcontrol_h, !!(r->misa & (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 = riscv_get_register_on_hart(target, hartid,
|
||||||
|
GDB_REGNO_TDATA1);
|
||||||
|
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 & (1 << ('H' - 'A')))
|
||||||
|
tdata1 |= MCONTROL_H;
|
||||||
|
if (r->misa & (1 << ('S' - 'A')))
|
||||||
|
tdata1 |= MCONTROL_S;
|
||||||
|
if (r->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;
|
||||||
|
|
||||||
|
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TDATA1, tdata1);
|
||||||
|
|
||||||
|
uint64_t tdata1_rb = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TDATA1);
|
||||||
|
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);
|
||||||
|
|
||||||
|
riscv_reg_t tselect[RISCV_MAX_HARTS];
|
||||||
|
|
||||||
|
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
|
||||||
|
if (!riscv_hart_enabled(target, hartid))
|
||||||
|
continue;
|
||||||
|
tselect[hartid] = riscv_get_register_on_hart(target, hartid,
|
||||||
|
GDB_REGNO_TSELECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; i < r->trigger_count[0]; i++) {
|
||||||
|
if (r->trigger_unique_id[i] != -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
riscv_set_register_on_hart(target, 0, GDB_REGNO_TSELECT, i);
|
||||||
|
|
||||||
|
uint64_t tdata1 = riscv_get_register_on_hart(target, 0, GDB_REGNO_TDATA1);
|
||||||
|
int type = get_field(tdata1, MCONTROL_TYPE(riscv_xlen(target)));
|
||||||
|
|
||||||
|
int result = ERROR_OK;
|
||||||
|
for (int hartid = 0; hartid < riscv_count_harts(target); ++hartid) {
|
||||||
|
if (!riscv_hart_enabled(target, hartid))
|
||||||
|
continue;
|
||||||
|
if (hartid > 0) {
|
||||||
|
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 resource %d for bp %d", i,
|
||||||
|
trigger->unique_id);
|
||||||
|
r->trigger_unique_id[i] = trigger->unique_id;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int hartid = 0; 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[0]) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
unsigned int i;
|
||||||
|
for (i = 0; i < r->trigger_count[0]; i++) {
|
||||||
|
if (r->trigger_unique_id[i] == trigger->unique_id) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i >= r->trigger_count[0]) {
|
||||||
|
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) {
|
||||||
|
if (!riscv_hart_enabled(target, hartid))
|
||||||
|
continue;
|
||||||
|
riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid, GDB_REGNO_TSELECT);
|
||||||
|
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)
|
struct breakpoint *breakpoint)
|
||||||
{
|
{
|
||||||
struct target_type *tt = get_target_type(target);
|
if (breakpoint->type == BKPT_SOFT) {
|
||||||
return tt->remove_breakpoint(target, breakpoint);
|
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 riscv_add_watchpoint(struct target *target,
|
static void trigger_from_watchpoint(struct trigger *trigger,
|
||||||
struct watchpoint *watchpoint)
|
const struct watchpoint *watchpoint)
|
||||||
{
|
{
|
||||||
struct target_type *tt = get_target_type(target);
|
trigger->address = watchpoint->address;
|
||||||
return tt->add_watchpoint(target, watchpoint);
|
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 riscv_remove_watchpoint(struct target *target,
|
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 watchpoint *watchpoint)
|
||||||
{
|
{
|
||||||
struct target_type *tt = get_target_type(target);
|
struct trigger trigger;
|
||||||
return tt->remove_watchpoint(target, watchpoint);
|
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,
|
static int oldriscv_step(struct target *target, int current, uint32_t address,
|
||||||
|
@ -880,6 +1178,8 @@ void riscv_info_init(struct target *target, riscv_info_t *r)
|
||||||
r->registers_initialized = false;
|
r->registers_initialized = false;
|
||||||
r->current_hartid = target->coreid;
|
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) {
|
for (size_t h = 0; h < RISCV_MAX_HARTS; ++h) {
|
||||||
r->xlen[h] = -1;
|
r->xlen[h] = -1;
|
||||||
r->debug_buffer_addr[h] = -1;
|
r->debug_buffer_addr[h] = -1;
|
||||||
|
@ -1033,7 +1333,6 @@ void riscv_set_current_hartid(struct target *target, int hartid)
|
||||||
&& (!riscv_rtos_enabled(target) || (previous_hartid == hartid))
|
&& (!riscv_rtos_enabled(target) || (previous_hartid == hartid))
|
||||||
&& target->reg_cache->reg_list[GDB_REGNO_XPR0].size == (unsigned)riscv_xlen(target)
|
&& target->reg_cache->reg_list[GDB_REGNO_XPR0].size == (unsigned)riscv_xlen(target)
|
||||||
&& (!riscv_rtos_enabled(target) || (r->rtos_hartid != -1))) {
|
&& (!riscv_rtos_enabled(target) || (r->rtos_hartid != -1))) {
|
||||||
LOG_DEBUG("registers already initialized, skipping");
|
|
||||||
return;
|
return;
|
||||||
} else
|
} else
|
||||||
LOG_DEBUG("Initializing registers: xlen=%d", riscv_xlen(target));
|
LOG_DEBUG("Initializing registers: xlen=%d", riscv_xlen(target));
|
||||||
|
@ -1107,7 +1406,7 @@ void riscv_set_register_on_hart(struct target *target, int hartid,
|
||||||
enum gdb_regno regid, uint64_t value)
|
enum gdb_regno regid, uint64_t value)
|
||||||
{
|
{
|
||||||
RISCV_INFO(r);
|
RISCV_INFO(r);
|
||||||
LOG_DEBUG("[%d] reg[%d] <- %" PRIx64, hartid, regid, value);
|
LOG_DEBUG("[%d] reg[0x%x] <- %" PRIx64, hartid, regid, value);
|
||||||
assert(r->set_register);
|
assert(r->set_register);
|
||||||
return r->set_register(target, hartid, regid, value);
|
return r->set_register(target, hartid, regid, value);
|
||||||
}
|
}
|
||||||
|
@ -1120,13 +1419,15 @@ riscv_reg_t riscv_get_register(struct target *target, enum gdb_regno r)
|
||||||
uint64_t riscv_get_register_on_hart(struct target *target, int hartid, enum gdb_regno regid)
|
uint64_t riscv_get_register_on_hart(struct target *target, int hartid, enum gdb_regno regid)
|
||||||
{
|
{
|
||||||
RISCV_INFO(r);
|
RISCV_INFO(r);
|
||||||
LOG_DEBUG("reading register %d on hart %d", regid, hartid);
|
uint64_t value = r->get_register(target, hartid, regid);
|
||||||
return r->get_register(target, hartid, regid);
|
LOG_DEBUG("[%d] reg[0x%x] = %" PRIx64, hartid, regid, value);
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool riscv_is_halted(struct target *target)
|
bool riscv_is_halted(struct target *target)
|
||||||
{
|
{
|
||||||
RISCV_INFO(r);
|
RISCV_INFO(r);
|
||||||
|
assert(r->is_halted);
|
||||||
return r->is_halted(target);
|
return r->is_halted(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1261,16 +1562,19 @@ int riscv_enumerate_triggers(struct target *target)
|
||||||
if (!riscv_hart_enabled(target, hartid))
|
if (!riscv_hart_enabled(target, hartid))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
riscv_reg_t tselect = riscv_get_register_on_hart(target, hartid,
|
||||||
|
GDB_REGNO_TSELECT);
|
||||||
|
|
||||||
for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) {
|
for (unsigned t = 0; t < RISCV_MAX_TRIGGERS; ++t) {
|
||||||
r->trigger_count[hartid] = t;
|
r->trigger_count[hartid] = t;
|
||||||
|
|
||||||
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t);
|
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, t);
|
||||||
uint64_t tselect = riscv_get_register_on_hart(target, hartid,
|
uint64_t tselect_rb = riscv_get_register_on_hart(target, hartid,
|
||||||
GDB_REGNO_TSELECT);
|
GDB_REGNO_TSELECT);
|
||||||
// Mask off the top bit, which is used as tdrmode in old
|
// Mask off the top bit, which is used as tdrmode in old
|
||||||
// implementations.
|
// implementations.
|
||||||
tselect &= ~(1ULL << (riscv_xlen(target)-1));
|
tselect_rb &= ~(1ULL << (riscv_xlen(target)-1));
|
||||||
if (tselect != t)
|
if (tselect_rb != t)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
uint64_t tdata1 = riscv_get_register_on_hart(target, hartid,
|
uint64_t tdata1 = riscv_get_register_on_hart(target, hartid,
|
||||||
|
@ -1290,6 +1594,8 @@ int riscv_enumerate_triggers(struct target *target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
riscv_set_register_on_hart(target, hartid, GDB_REGNO_TSELECT, tselect);
|
||||||
|
|
||||||
LOG_DEBUG("[%d] Found %d triggers", hartid, r->trigger_count[hartid]);
|
LOG_DEBUG("[%d] Found %d triggers", hartid, r->trigger_count[hartid]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ struct riscv_program;
|
||||||
#define RISCV_MAX_HARTS 32
|
#define RISCV_MAX_HARTS 32
|
||||||
#define RISCV_MAX_REGISTERS 5000
|
#define RISCV_MAX_REGISTERS 5000
|
||||||
#define RISCV_MAX_TRIGGERS 32
|
#define RISCV_MAX_TRIGGERS 32
|
||||||
|
#define RISCV_MAX_HWBPS 16
|
||||||
|
|
||||||
extern struct target_type riscv011_target;
|
extern struct target_type riscv011_target;
|
||||||
extern struct target_type riscv013_target;
|
extern struct target_type riscv013_target;
|
||||||
|
@ -30,6 +31,9 @@ enum riscv_halt_reason {
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned dtm_version;
|
unsigned dtm_version;
|
||||||
|
|
||||||
|
riscv_reg_t misa;
|
||||||
|
|
||||||
struct command_context *cmd_ctx;
|
struct command_context *cmd_ctx;
|
||||||
void *version_specific;
|
void *version_specific;
|
||||||
|
|
||||||
|
@ -59,6 +63,10 @@ typedef struct {
|
||||||
/* The number of triggers per hart. */
|
/* The number of triggers per hart. */
|
||||||
unsigned trigger_count[RISCV_MAX_HARTS];
|
unsigned trigger_count[RISCV_MAX_HARTS];
|
||||||
|
|
||||||
|
/* 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[RISCV_MAX_HWBPS];
|
||||||
|
|
||||||
/* The address of the debug RAM buffer. */
|
/* The address of the debug RAM buffer. */
|
||||||
riscv_addr_t debug_buffer_addr[RISCV_MAX_HARTS];
|
riscv_addr_t debug_buffer_addr[RISCV_MAX_HARTS];
|
||||||
|
|
||||||
|
@ -221,4 +229,11 @@ bool riscv_hart_enabled(struct target *target, int hartid);
|
||||||
|
|
||||||
int riscv_enumerate_triggers(struct target *target);
|
int riscv_enumerate_triggers(struct target *target);
|
||||||
|
|
||||||
|
int riscv_add_breakpoint(struct target *target, struct breakpoint *breakpoint);
|
||||||
|
int riscv_remove_breakpoint(struct target *target,
|
||||||
|
struct breakpoint *breakpoint);
|
||||||
|
int riscv_add_watchpoint(struct target *target, struct watchpoint *watchpoint);
|
||||||
|
int riscv_remove_watchpoint(struct target *target,
|
||||||
|
struct watchpoint *watchpoint);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue