Merge pull request #777 from riscv/itrigger

target/riscv: Add `riscv` `itrigger` and `etrigger` commands.
This commit is contained in:
Tim Newsome 2023-01-04 10:31:55 -08:00 committed by GitHub
commit 43ea20dfbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 326 additions and 22 deletions

View File

@ -10716,6 +10716,43 @@ Perform a 32-bit DMI read at address, returning the value.
Perform a 32-bit DMI write of value at address.
@end deffn
@subsection RISC-V Trigger Commands
The RISC-V Debug Specification defines several trigger types that don't map
cleanly onto OpenOCD's notion of hardware breakpoints. These commands let you
set those triggers directly. (It's also possible to do so by writing the
appropriate CSRs.)
@deffn {Command} {riscv etrigger set} [@option{m}] [@option{s}] [@option{u}] [@option{vs}] [@option{vu}] exception_codes
Set an exception trigger (type 5) on the current target, which halts the target when it
fires. @option{m}, @option{s}, @option{u}, @option{vs}, and @option{vu} control
which execution modes the trigger fires in. @var{exception_codes} is a bit
field, where each bit corresponds to an exception code in mcause (defined in the
RISC-V Privileged Spec). The etrigger will fire on the exceptions whose bits are
set in @var{exception_codes}.
For details on this trigger type, see the RISC-V Debug Specification.
@end deffn
@deffn {Command} {riscv etrigger clear}
Clear the type 5 trigger that was set using @command{riscv etrigger set}.
@end deffn
@deffn {Command} {riscv itrigger set} [@option{m}] [@option{s}] [@option{u}] [@option{vs}] [@option{vu}] [@option{nmi}] mie_bits
Set an interrupt trigger (type 4) on the current target, which halts the target when it
fires. @option{m}, @option{s}, @option{u}, @option{vs}, and @option{vu} control
which execution modes the trigger fires in. If [@option{nmi}] is passed then
the trigger will fire on non-maskable interrupts in those modes. @var{mie_bits}
controls which interrupts the trigger fires on, using the same bit assignments
as in the mie CSR (defined in the RISC-V Privileged Spec).
For details on this trigger type, see the RISC-V Debug Specification.
@end deffn
@deffn {Command} {riscv itrigger clear}
Clear the type 4 trigger that was set using @command{riscv itrigger set}.
@end deffn
@section ARC Architecture
@cindex ARC

View File

@ -476,7 +476,7 @@ static bool can_use_napot_match(struct trigger *trigger, riscv_reg_t *tdata2)
return false;
}
static int find_trigger(struct target *target, int type, bool chained, int *idx)
static int find_trigger(struct target *target, int type, bool chained, unsigned int *idx)
{
RISCV_INFO(r);
@ -506,10 +506,21 @@ static int find_trigger(struct target *target, int type, bool chained, int *idx)
return ERROR_FAIL;
}
static int set_trigger(struct target *target, int idx, riscv_reg_t tdata1, riscv_reg_t tdata2,
static int find_first_trigger_by_id(struct target *target, int unique_id)
{
RISCV_INFO(r);
for (unsigned i = 0; i < r->trigger_count; i++) {
if (r->trigger_unique_id[i] == unique_id)
return i;
}
return -1;
}
static int set_trigger(struct target *target, unsigned int idx, riscv_reg_t tdata1, riscv_reg_t tdata2,
riscv_reg_t tdata1_ignore_mask)
{
riscv_reg_t tdata1_rb;
riscv_reg_t tdata1_rb, tdata2_rb;
if (riscv_set_register(target, GDB_REGNO_TSELECT, idx) != ERROR_OK)
return ERROR_FAIL;
if (riscv_set_register(target, GDB_REGNO_TDATA1, tdata1) != ERROR_OK)
@ -517,22 +528,32 @@ static int set_trigger(struct target *target, int idx, riscv_reg_t tdata1, riscv
if (riscv_get_register(target, &tdata1_rb, GDB_REGNO_TDATA1) != ERROR_OK)
return ERROR_FAIL;
if ((tdata1 & ~tdata1_ignore_mask) != (tdata1_rb & ~tdata1_ignore_mask)) {
LOG_DEBUG("Trigger doesn't support what we need; After writing 0x%"
LOG_TARGET_DEBUG(target,
"Trigger %u doesn't support what we need; After writing 0x%"
PRIx64 " to tdata1 it contains 0x%" PRIx64
"; tdata1_ignore_mask=0x%" PRIx64,
tdata1, tdata1_rb, tdata1_ignore_mask);
idx, tdata1, tdata1_rb, tdata1_ignore_mask);
riscv_set_register(target, GDB_REGNO_TDATA1, 0);
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
}
LOG_DEBUG("tdata1=0x%" PRIx64, tdata1_rb);
if (riscv_set_register(target, GDB_REGNO_TDATA2, tdata2) != ERROR_OK)
return ERROR_FAIL;
if (riscv_get_register(target, &tdata2_rb, GDB_REGNO_TDATA2) != ERROR_OK)
return ERROR_FAIL;
if (tdata2 != tdata2_rb) {
LOG_TARGET_DEBUG(target,
"Trigger %u doesn't support what we need; wrote 0x%"
PRIx64 " to tdata2 but read back 0x%" PRIx64,
idx, tdata2, tdata2_rb);
riscv_set_register(target, GDB_REGNO_TDATA1, 0);
return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
}
return ERROR_OK;
}
static int maybe_add_trigger_t1(struct target *target, struct trigger *trigger)
{
int idx, ret;
int ret;
riscv_reg_t tdata1, tdata2;
RISCV_INFO(r);
@ -547,6 +568,7 @@ static int maybe_add_trigger_t1(struct target *target, struct trigger *trigger)
const uint32_t bpcontrol_bpmatch = 0xf << 7;
const uint32_t bpcontrol_bpaction = 0xff << 11;
unsigned int idx;
ret = find_trigger(target, CSR_TDATA1_TYPE_LEGACY, false, &idx);
if (ret != ERROR_OK)
return ret;
@ -578,7 +600,7 @@ static int maybe_add_trigger_t1(struct target *target, struct trigger *trigger)
static int maybe_add_trigger_t2(struct target *target, struct trigger *trigger)
{
int idx, ret;
int ret;
riscv_reg_t tdata1, tdata2;
RISCV_INFO(r);
@ -601,6 +623,7 @@ static int maybe_add_trigger_t2(struct target *target, struct trigger *trigger)
if (!can_use_napot_match(trigger, &tdata2))
goto MATCH_GE_LT;
unsigned int idx;
ret = find_trigger(target, CSR_TDATA1_TYPE_MCONTROL, false, &idx);
if (ret != ERROR_OK)
return ret;
@ -661,9 +684,74 @@ MATCH_EQUAL:
return ERROR_OK;
}
static int maybe_add_trigger_t4(struct target *target, bool vs, bool vu,
bool nmi, bool m, bool s, bool u, riscv_reg_t interrupts,
int unique_id)
{
int ret;
riscv_reg_t tdata1, tdata2;
RISCV_INFO(r);
tdata1 = 0;
tdata1 = set_field(tdata1, CSR_ITRIGGER_TYPE(riscv_xlen(target)), CSR_TDATA1_TYPE_ITRIGGER);
tdata1 = set_field(tdata1, CSR_ITRIGGER_DMODE(riscv_xlen(target)), 1);
tdata1 = set_field(tdata1, CSR_ITRIGGER_ACTION, CSR_ITRIGGER_ACTION_DEBUG_MODE);
tdata1 = set_field(tdata1, CSR_ITRIGGER_VS, vs);
tdata1 = set_field(tdata1, CSR_ITRIGGER_VU, vu);
tdata1 = set_field(tdata1, CSR_ITRIGGER_NMI, nmi);
tdata1 = set_field(tdata1, CSR_ITRIGGER_M, m);
tdata1 = set_field(tdata1, CSR_ITRIGGER_S, s);
tdata1 = set_field(tdata1, CSR_ITRIGGER_U, u);
tdata2 = interrupts;
unsigned int idx;
ret = find_trigger(target, CSR_TDATA1_TYPE_ITRIGGER, false, &idx);
if (ret != ERROR_OK)
return ret;
ret = set_trigger(target, idx, tdata1, tdata2, 0);
if (ret != ERROR_OK)
return ret;
r->trigger_unique_id[idx] = unique_id;
return ERROR_OK;
}
static int maybe_add_trigger_t5(struct target *target, bool vs, bool vu,
bool m, bool s, bool u, riscv_reg_t exception_codes,
int unique_id)
{
int ret;
riscv_reg_t tdata1, tdata2;
RISCV_INFO(r);
tdata1 = 0;
tdata1 = set_field(tdata1, CSR_ETRIGGER_TYPE(riscv_xlen(target)), CSR_TDATA1_TYPE_ETRIGGER);
tdata1 = set_field(tdata1, CSR_ETRIGGER_DMODE(riscv_xlen(target)), 1);
tdata1 = set_field(tdata1, CSR_ETRIGGER_ACTION, CSR_ETRIGGER_ACTION_DEBUG_MODE);
tdata1 = set_field(tdata1, CSR_ETRIGGER_VS, vs);
tdata1 = set_field(tdata1, CSR_ETRIGGER_VU, vu);
tdata1 = set_field(tdata1, CSR_ETRIGGER_M, m);
tdata1 = set_field(tdata1, CSR_ETRIGGER_S, s);
tdata1 = set_field(tdata1, CSR_ETRIGGER_U, u);
tdata2 = exception_codes;
unsigned int idx;
ret = find_trigger(target, CSR_TDATA1_TYPE_ETRIGGER, false, &idx);
if (ret != ERROR_OK)
return ret;
ret = set_trigger(target, idx, tdata1, tdata2, 0);
if (ret != ERROR_OK)
return ret;
r->trigger_unique_id[idx] = unique_id;
return ERROR_OK;
}
static int maybe_add_trigger_t6(struct target *target, struct trigger *trigger)
{
int idx, ret;
int ret;
riscv_reg_t tdata1, tdata2;
RISCV_INFO(r);
@ -685,6 +773,7 @@ static int maybe_add_trigger_t6(struct target *target, struct trigger *trigger)
if (!can_use_napot_match(trigger, &tdata2))
goto MATCH_GE_LT;
unsigned int idx;
ret = find_trigger(target, CSR_TDATA1_TYPE_MCONTROL6, false, &idx);
if (ret != ERROR_OK)
return ret;
@ -937,7 +1026,7 @@ int riscv_add_breakpoint(struct target *target, struct breakpoint *breakpoint)
return ERROR_OK;
}
static int remove_trigger(struct target *target, struct trigger *trigger)
static int remove_trigger(struct target *target, int unique_id)
{
RISCV_INFO(r);
@ -951,12 +1040,12 @@ static int remove_trigger(struct target *target, struct trigger *trigger)
bool done = false;
for (unsigned int i = 0; i < r->trigger_count; i++) {
if (r->trigger_unique_id[i] == trigger->unique_id) {
if (r->trigger_unique_id[i] == unique_id) {
riscv_set_register(target, GDB_REGNO_TSELECT, i);
riscv_set_register(target, GDB_REGNO_TDATA1, 0);
r->trigger_unique_id[i] = -1;
LOG_TARGET_DEBUG(target, "Stop using resource %d for bp %d",
i, trigger->unique_id);
i, unique_id);
done = true;
}
}
@ -986,7 +1075,7 @@ int riscv_remove_breakpoint(struct target *target,
} else if (breakpoint->type == BKPT_HARD) {
struct trigger trigger;
trigger_from_breakpoint(&trigger, breakpoint);
int result = remove_trigger(target, &trigger);
int result = remove_trigger(target, trigger.unique_id);
if (result != ERROR_OK)
return result;
@ -1035,7 +1124,7 @@ int riscv_remove_watchpoint(struct target *target,
struct trigger trigger;
trigger_from_watchpoint(&trigger, watchpoint);
int result = remove_trigger(target, &trigger);
int result = remove_trigger(target, trigger.unique_id);
if (result != ERROR_OK)
return result;
watchpoint->is_set = false;
@ -1071,15 +1160,21 @@ static int riscv_hit_trigger_hit_bit(struct target *target, uint32_t *unique_id)
uint64_t hit_mask = 0;
switch (type) {
case 1:
case CSR_TDATA1_TYPE_LEGACY:
/* Doesn't support hit bit. */
break;
case 2:
case CSR_TDATA1_TYPE_MCONTROL:
hit_mask = CSR_MCONTROL_HIT;
break;
case 6:
case CSR_TDATA1_TYPE_MCONTROL6:
hit_mask = CSR_MCONTROL6_HIT;
break;
case CSR_TDATA1_TYPE_ITRIGGER:
hit_mask = CSR_ITRIGGER_HIT(riscv_xlen(target));
break;
case CSR_TDATA1_TYPE_ETRIGGER:
hit_mask = CSR_ETRIGGER_HIT(riscv_xlen(target));
break;
default:
LOG_DEBUG("trigger %d has unknown type %d", i, type);
continue;
@ -3144,6 +3239,153 @@ COMMAND_HANDLER(riscv_set_ebreaku)
return ERROR_OK;
}
COMMAND_HANDLER(riscv_itrigger)
{
if (CMD_ARGC < 1) {
LOG_ERROR("Command takes at least 1 parameter");
return ERROR_COMMAND_SYNTAX_ERROR;
}
struct target *target = get_current_target(CMD_CTX);
const int ITRIGGER_UNIQUE_ID = -CSR_TDATA1_TYPE_ITRIGGER;
if (riscv_enumerate_triggers(target) != ERROR_OK)
return ERROR_FAIL;
if (!strcmp(CMD_ARGV[0], "set")) {
if (find_first_trigger_by_id(target, ITRIGGER_UNIQUE_ID) >= 0) {
LOG_TARGET_ERROR(target, "An itrigger is already set, and OpenOCD "
"doesn't support setting more than one at a time.");
return ERROR_FAIL;
}
bool vs = false;
bool vu = false;
bool nmi = false;
bool m = false;
bool s = false;
bool u = false;
riscv_reg_t interrupts = 0;
for (unsigned int i = 1; i < CMD_ARGC; i++) {
if (!strcmp(CMD_ARGV[i], "vs"))
vs = true;
else if (!strcmp(CMD_ARGV[i], "vu"))
vu = true;
else if (!strcmp(CMD_ARGV[i], "nmi"))
nmi = true;
else if (!strcmp(CMD_ARGV[i], "m"))
m = true;
else if (!strcmp(CMD_ARGV[i], "s"))
s = true;
else if (!strcmp(CMD_ARGV[i], "u"))
u = true;
else
COMMAND_PARSE_NUMBER(u64, CMD_ARGV[i], interrupts);
}
if (!nmi && interrupts == 0) {
LOG_ERROR("Doesn't make sense to set itrigger with "
"mie_bits=0 and without nmi.");
return ERROR_FAIL;
} else if (!vs && !vu && !m && !s && !u) {
LOG_ERROR("Doesn't make sense to set itrigger without at "
"least one of vs, vu, m, s, or u.");
return ERROR_FAIL;
}
int result = maybe_add_trigger_t4(target, vs, vu, nmi, m, s, u, interrupts, ITRIGGER_UNIQUE_ID);
if (result != ERROR_OK)
LOG_TARGET_ERROR(target, "Failed to set requested itrigger.");
return result;
} else if (!strcmp(CMD_ARGV[0], "clear")) {
if (CMD_ARGC != 1) {
LOG_ERROR("clear command takes no extra arguments.");
return ERROR_COMMAND_SYNTAX_ERROR;
}
if (find_first_trigger_by_id(target, ITRIGGER_UNIQUE_ID) < 0) {
LOG_TARGET_ERROR(target, "No itrigger is set. Nothing to clear.");
return ERROR_FAIL;
}
return remove_trigger(target, ITRIGGER_UNIQUE_ID);
} else {
LOG_ERROR("First argument must be either 'set' or 'clear'.");
return ERROR_COMMAND_SYNTAX_ERROR;
}
return ERROR_OK;
}
COMMAND_HANDLER(riscv_etrigger)
{
if (CMD_ARGC < 1) {
LOG_ERROR("Command takes at least 1 parameter");
return ERROR_COMMAND_SYNTAX_ERROR;
}
struct target *target = get_current_target(CMD_CTX);
const int ETRIGGER_UNIQUE_ID = -CSR_TDATA1_TYPE_ETRIGGER;
if (riscv_enumerate_triggers(target) != ERROR_OK)
return ERROR_FAIL;
if (!strcmp(CMD_ARGV[0], "set")) {
if (find_first_trigger_by_id(target, ETRIGGER_UNIQUE_ID) >= 0) {
LOG_TARGET_ERROR(target, "An etrigger is already set, and OpenOCD "
"doesn't support setting more than one at a time.");
return ERROR_FAIL;
}
bool vs = false;
bool vu = false;
bool m = false;
bool s = false;
bool u = false;
riscv_reg_t exception_codes = 0;
for (unsigned int i = 1; i < CMD_ARGC; i++) {
if (!strcmp(CMD_ARGV[i], "vs"))
vs = true;
else if (!strcmp(CMD_ARGV[i], "vu"))
vu = true;
else if (!strcmp(CMD_ARGV[i], "m"))
m = true;
else if (!strcmp(CMD_ARGV[i], "s"))
s = true;
else if (!strcmp(CMD_ARGV[i], "u"))
u = true;
else
COMMAND_PARSE_NUMBER(u64, CMD_ARGV[i], exception_codes);
}
if (exception_codes == 0) {
LOG_ERROR("Doesn't make sense to set etrigger with "
"exception_codes=0.");
return ERROR_FAIL;
} else if (!vs && !vu && !m && !s && !u) {
LOG_ERROR("Doesn't make sense to set etrigger without at "
"least one of vs, vu, m, s, or u.");
return ERROR_FAIL;
}
int result = maybe_add_trigger_t5(target, vs, vu, m, s, u, exception_codes, ETRIGGER_UNIQUE_ID);
if (result != ERROR_OK)
LOG_TARGET_ERROR(target, "Failed to set requested etrigger.");
return result;
} else if (!strcmp(CMD_ARGV[0], "clear")) {
if (CMD_ARGC != 1) {
LOG_ERROR("clear command takes no extra arguments.");
return ERROR_COMMAND_SYNTAX_ERROR;
}
if (find_first_trigger_by_id(target, ETRIGGER_UNIQUE_ID) < 0) {
LOG_TARGET_ERROR(target, "No etrigger is set. Nothing to clear.");
return ERROR_FAIL;
}
return remove_trigger(target, ETRIGGER_UNIQUE_ID);
} else {
LOG_ERROR("First argument must be either 'set' or 'clear'.");
return ERROR_COMMAND_SYNTAX_ERROR;
}
return ERROR_OK;
}
COMMAND_HANDLER(handle_repeat_read)
{
struct target *target = get_current_target(CMD_CTX);
@ -3548,6 +3790,20 @@ static const struct command_registration riscv_exec_command_handlers[] = {
.help = "Control dcsr.ebreaku. When off, U-mode ebreak instructions "
"don't trap to OpenOCD. Defaults to on."
},
{
.name = "etrigger",
.handler = riscv_etrigger,
.mode = COMMAND_EXEC,
.usage = "set [vs] [vu] [m] [s] [u] <exception_codes>|clear",
.help = "Set or clear a single exception trigger."
},
{
.name = "itrigger",
.handler = riscv_itrigger,
.mode = COMMAND_EXEC,
.usage = "set [vs] [vu] [nmi] [m] [s] [u] <mie_bits>|clear",
.help = "Set or clear a single interrupt trigger."
},
COMMAND_REGISTRATION_DONE
};
@ -4078,19 +4334,27 @@ int riscv_enumerate_triggers(struct target *target)
if (type == 0)
break;
switch (type) {
case 1:
case CSR_TDATA1_TYPE_LEGACY:
/* On these older cores we don't support software using
* triggers. */
riscv_set_register(target, GDB_REGNO_TDATA1, 0);
break;
case 2:
case CSR_TDATA1_TYPE_MCONTROL:
if (tdata1 & CSR_MCONTROL_DMODE(riscv_xlen(target)))
riscv_set_register(target, GDB_REGNO_TDATA1, 0);
break;
case 6:
case CSR_TDATA1_TYPE_MCONTROL6:
if (tdata1 & CSR_MCONTROL6_DMODE(riscv_xlen(target)))
riscv_set_register(target, GDB_REGNO_TDATA1, 0);
break;
case CSR_TDATA1_TYPE_ITRIGGER:
if (tdata1 & CSR_ITRIGGER_DMODE(riscv_xlen(target)))
riscv_set_register(target, GDB_REGNO_TDATA1, 0);
break;
case CSR_TDATA1_TYPE_ETRIGGER:
if (tdata1 & CSR_ETRIGGER_DMODE(riscv_xlen(target)))
riscv_set_register(target, GDB_REGNO_TDATA1, 0);
break;
}
r->trigger_tinfo[t] = 1 << type;
}

View File

@ -124,8 +124,11 @@ typedef struct {
/* record the tinfo of each trigger */
unsigned int trigger_tinfo[RISCV_MAX_TRIGGERS];
/* For each physical trigger, contains -1 if the hwbp is available, or the
* unique_id of the breakpoint/watchpoint that is using it.
/* For each physical trigger contains:
* -1: the hwbp is available
* -4: The trigger is used by the itrigger command
* -5: The trigger is used by the etrigger command
* >= 0: unique_id of the breakpoint/watchpoint that is using it.
* Note that in RTOS mode the triggers are the same across all harts the
* target controls, while otherwise only a single hart is controlled. */
int trigger_unique_id[RISCV_MAX_HWBPS];