aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Newsome <tim@sifive.com>2022-11-18 15:57:17 -0800
committerTim Newsome <tim@sifive.com>2023-01-02 13:54:35 -0800
commit0b022a349e41a054bfda66b975769bbfa5e60c0b (patch)
tree897ddf2e9041058bda2a9be3e2421cac725de0c9
parent2258a59a0f1baa376972818b35530e50645f7c8f (diff)
downloadriscv-openocd-0b022a349e41a054bfda66b975769bbfa5e60c0b.zip
riscv-openocd-0b022a349e41a054bfda66b975769bbfa5e60c0b.tar.gz
riscv-openocd-0b022a349e41a054bfda66b975769bbfa5e60c0b.tar.bz2
target/riscv: Add `riscv itrigger` command.
This lets the user set an itrigger trigger, which doesn't fit in the normal breakpoint abstraction. This implementation only allows control of a single itrigger. Hardware could support more than one, and that may be useful to catch different interrupts in different execution modes. But it would make the code/UI more complex and it feels like an unlikely use case. Change-Id: I76c88636ee73d4bd298b2bd1435cb5d052e86c91 Signed-off-by: Tim Newsome <tim@sifive.com>
-rw-r--r--doc/openocd.texi22
-rw-r--r--src/target/riscv/riscv.c142
-rw-r--r--src/target/riscv/riscv.h6
3 files changed, 163 insertions, 7 deletions
diff --git a/doc/openocd.texi b/doc/openocd.texi
index e671b80..b5a16a9 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -10716,6 +10716,28 @@ 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 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
diff --git a/src/target/riscv/riscv.c b/src/target/riscv/riscv.c
index 5a59d5a..6cd3a01 100644
--- a/src/target/riscv/riscv.c
+++ b/src/target/riscv/riscv.c
@@ -506,6 +506,17 @@ static int find_trigger(struct target *target, int type, bool chained, int *idx)
return ERROR_FAIL;
}
+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, int idx, riscv_reg_t tdata1, riscv_reg_t tdata2,
riscv_reg_t tdata1_ignore_mask)
{
@@ -661,6 +672,38 @@ 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 idx, 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;
+
+ 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_t6(struct target *target, struct trigger *trigger)
{
int idx, ret;
@@ -937,7 +980,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 +994,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 +1029,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 +1078,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;
@@ -1080,6 +1123,9 @@ static int riscv_hit_trigger_hit_bit(struct target *target, uint32_t *unique_id)
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;
default:
LOG_DEBUG("trigger %d has unknown type %d", i, type);
continue;
@@ -3173,6 +3219,81 @@ 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(handle_repeat_read)
{
struct target *target = get_current_target(CMD_CTX);
@@ -3590,6 +3711,13 @@ 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 = "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
};
@@ -4133,6 +4261,10 @@ int riscv_enumerate_triggers(struct target *target)
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;
}
r->trigger_tinfo[t] = 1 << type;
}
diff --git a/src/target/riscv/riscv.h b/src/target/riscv/riscv.h
index 657dafb..3709c39 100644
--- a/src/target/riscv/riscv.h
+++ b/src/target/riscv/riscv.h
@@ -124,8 +124,10 @@ 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
+ * >= 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];