Merge pull request #857 from riscv/power_dance2

When dcsr.ebreak* might be cleared, halt the target and set it again
This commit is contained in:
Tim Newsome 2023-06-21 09:32:23 -07:00 committed by GitHub
commit 1bcabbebb7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 32 deletions

View File

@ -579,7 +579,7 @@ static bool usb_get_response(uint32_t *total_bytes_read, uint32_t timeout)
static bool usb_send_command(uint16_t size)
{
int written;
int written = 0;
bool success = true;
/* Check the packet length */

View File

@ -44,7 +44,6 @@ static int riscv013_halt_prep(struct target *target);
static int riscv013_halt_go(struct target *target);
static int riscv013_resume_go(struct target *target);
static int riscv013_step_current_hart(struct target *target);
static int riscv013_on_halt(struct target *target);
static int riscv013_on_step(struct target *target);
static int riscv013_resume_prep(struct target *target);
static enum riscv_halt_reason riscv013_halt_reason(struct target *target);
@ -68,6 +67,13 @@ static int read_memory(struct target *target, target_addr_t address,
static int write_memory(struct target *target, target_addr_t address,
uint32_t size, uint32_t count, const uint8_t *buffer);
typedef enum {
HALT_GROUP,
RESUME_GROUP
} grouptype_t;
static int set_group(struct target *target, bool *supported, unsigned int group,
grouptype_t grouptype);
/**
* Since almost everything can be accomplish by scanning the dbus register, all
* functions here assume dbus is already selected. The exception are functions
@ -199,6 +205,13 @@ typedef struct {
/* This target was selected using hasel. */
bool selected;
/* When false, we need to set dcsr.ebreak*, halting the target if that's
* necessary. */
bool dcsr_ebreak_is_set;
/* This hart was placed into a halt group in examine(). */
bool haltgroup_supported;
} riscv013_info_t;
static LIST_HEAD(dm_list);
@ -475,6 +488,7 @@ static dmi_status_t dmi_scan(struct target *target, uint32_t *address_in,
if (r->reset_delays_wait >= 0) {
r->reset_delays_wait--;
if (r->reset_delays_wait < 0) {
LOG_TARGET_DEBUG(target, "reset_delays_wait done");
info->dmi_busy_delay = 0;
info->ac_busy_delay = 0;
}
@ -1556,27 +1570,95 @@ static int wait_for_authbusy(struct target *target, uint32_t *dmstatus)
return ERROR_OK;
}
static int update_dcsr(struct target *target, bool step)
static int set_dcsr_ebreak(struct target *target, bool step)
{
LOG_TARGET_DEBUG(target, "Set dcsr.ebreak*");
if (dm013_select_target(target) != ERROR_OK)
return ERROR_FAIL;
riscv_reg_t dcsr;
RISCV013_INFO(info);
riscv_reg_t original_dcsr, dcsr;
/* We want to twiddle some bits in the debug CSR so debugging works. */
int result = register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
if (result != ERROR_OK)
return result;
if (riscv_get_register(target, &dcsr, GDB_REGNO_DCSR) != ERROR_OK)
return ERROR_FAIL;
original_dcsr = dcsr;
dcsr = set_field(dcsr, CSR_DCSR_STEP, step);
dcsr = set_field(dcsr, CSR_DCSR_EBREAKM, riscv_ebreakm);
dcsr = set_field(dcsr, CSR_DCSR_EBREAKS, riscv_ebreaks);
dcsr = set_field(dcsr, CSR_DCSR_EBREAKU, riscv_ebreaku);
dcsr = set_field(dcsr, CSR_DCSR_EBREAKVS, riscv_ebreaku);
dcsr = set_field(dcsr, CSR_DCSR_EBREAKVU, riscv_ebreaku);
if (riscv_set_register(target, GDB_REGNO_DCSR, dcsr) != ERROR_OK)
dcsr = set_field(dcsr, CSR_DCSR_EBREAKS, riscv_ebreaks && riscv_supports_extension(target, 'S'));
dcsr = set_field(dcsr, CSR_DCSR_EBREAKU, riscv_ebreaku && riscv_supports_extension(target, 'U'));
dcsr = set_field(dcsr, CSR_DCSR_EBREAKVS, riscv_ebreaku && riscv_supports_extension(target, 'H'));
dcsr = set_field(dcsr, CSR_DCSR_EBREAKVU, riscv_ebreaku && riscv_supports_extension(target, 'H'));
if (dcsr != original_dcsr &&
riscv_set_register(target, GDB_REGNO_DCSR, dcsr) != ERROR_OK)
return ERROR_FAIL;
info->dcsr_ebreak_is_set = true;
return ERROR_OK;
}
static int halt_set_dcsr_ebreak(struct target *target)
{
RISCV_INFO(r);
RISCV013_INFO(info);
LOG_TARGET_DEBUG(target, "Halt to set DCSR.ebreak*");
/* Remove this hart from the halt group. This won't work on all targets
* because the debug spec allows halt groups to be hard-coded, but I
* haven't actually encountered those in the wild yet.
*
* There is a possible race condition when another hart halts, and
* this one is expected to also halt because it's supposed to be in the
* same halt group. Or when this hart is halted when that happens.
*
* A better solution might be to leave the halt groups alone, and track
* why we're halting when a halt occurs. When there are halt groups,
* that leads to extra halting if not all harts need to set dcsr.ebreak
* at the same time. It also makes for more complicated code.
*
* The perfect solution would be Quick Access, but I'm not aware of any
* hardware that implements it.
*
* We don't need a perfect solution, because we only get here when a
* hart spontaneously resets, or when it powers down and back up again.
* Those are both relatively rare. (At least I hope so. Maybe some
* design just powers each hart down for 90ms out of every 100ms)
*/
if (info->haltgroup_supported) {
bool supported;
if (set_group(target, &supported, 0, HALT_GROUP) != ERROR_OK)
return ERROR_FAIL;
if (!supported)
LOG_TARGET_ERROR(target, "Couldn't place hart in halt group 0. "
"Some harts may be unexpectedly halted.");
}
int result = ERROR_OK;
r->prepped = true;
if (riscv013_halt_go(target) != ERROR_OK ||
set_dcsr_ebreak(target, false) != ERROR_OK ||
riscv013_step_or_resume_current_hart(target, false) != ERROR_OK) {
result = ERROR_FAIL;
} else {
target->state = TARGET_RUNNING;
target->debug_reason = DBG_REASON_NOTHALTED;
}
/* Add it back to the halt group. */
if (info->haltgroup_supported) {
bool supported;
if (set_group(target, &supported, target->smp, HALT_GROUP) != ERROR_OK)
return ERROR_FAIL;
if (!supported)
LOG_TARGET_ERROR(target, "Couldn't place hart back in halt group %d. "
"Some harts may be unexpectedly halted.", target->smp);
}
return result;
}
/*** OpenOCD target functions. ***/
static void deinit_target(struct target *target)
@ -1591,22 +1673,20 @@ static void deinit_target(struct target *target)
info->version_specific = NULL;
}
typedef enum {
HALTGROUP,
RESUMEGROUP
} grouptype_t;
static int set_group(struct target *target, bool *supported, unsigned group, grouptype_t grouptype)
static int set_group(struct target *target, bool *supported, unsigned int group,
grouptype_t grouptype)
{
uint32_t write_val = DM_DMCS2_HGWRITE;
assert(group <= 31);
write_val = set_field(write_val, DM_DMCS2_GROUP, group);
write_val = set_field(write_val, DM_DMCS2_GROUPTYPE, (grouptype == HALTGROUP) ? 0 : 1);
write_val = set_field(write_val, DM_DMCS2_GROUPTYPE, (grouptype == HALT_GROUP) ? 0 : 1);
if (dmi_write(target, DM_DMCS2, write_val) != ERROR_OK)
return ERROR_FAIL;
uint32_t read_val;
if (dmi_read(target, &read_val, DM_DMCS2) != ERROR_OK)
return ERROR_FAIL;
*supported = get_field(read_val, DM_DMCS2_GROUP) == group;
if (supported)
*supported = (get_field(read_val, DM_DMCS2_GROUP) == group);
return ERROR_OK;
}
@ -1866,7 +1946,7 @@ static int examine(struct target *target)
if (riscv_init_registers(target) != ERROR_OK)
return ERROR_FAIL;
if (update_dcsr(target, false) != ERROR_OK)
if (set_dcsr_ebreak(target, false) != ERROR_OK)
return ERROR_FAIL;
target->state = saved_tgt_state;
@ -1880,9 +1960,9 @@ static int examine(struct target *target)
if (target->smp) {
bool haltgroup_supported;
if (set_group(target, &haltgroup_supported, target->smp, HALTGROUP) != ERROR_OK)
if (set_group(target, &haltgroup_supported, target->smp, HALT_GROUP) != ERROR_OK)
return ERROR_FAIL;
if (haltgroup_supported)
if (info->haltgroup_supported)
LOG_INFO("Core %d made part of halt group %d.", target->coreid,
target->smp);
else
@ -2429,6 +2509,7 @@ static int riscv013_get_hart_state(struct target *target, enum riscv_hart_state
return ERROR_FAIL;
if (get_field(dmstatus, DM_DMSTATUS_ANYHAVERESET)) {
LOG_TARGET_INFO(target, "Hart unexpectedly reset!");
info->dcsr_ebreak_is_set = false;
/* TODO: Can we make this more obvious to eg. a gdb user? */
uint32_t dmcontrol = DM_DMCONTROL_DMACTIVE |
DM_DMCONTROL_ACKHAVERESET;
@ -2462,6 +2543,24 @@ static int riscv013_get_hart_state(struct target *target, enum riscv_hart_state
return ERROR_FAIL;
}
static int handle_became_unavailable(struct target *target,
enum riscv_hart_state previous_riscv_state)
{
RISCV013_INFO(info);
info->dcsr_ebreak_is_set = false;
return ERROR_OK;
}
static int tick(struct target *target)
{
RISCV013_INFO(info);
if (!info->dcsr_ebreak_is_set &&
target->state == TARGET_RUNNING &&
target_was_examined(target))
return halt_set_dcsr_ebreak(target);
return ERROR_OK;
}
static int init_target(struct command_context *cmd_ctx,
struct target *target)
{
@ -2476,7 +2575,6 @@ static int init_target(struct command_context *cmd_ctx,
generic_info->get_hart_state = &riscv013_get_hart_state;
generic_info->resume_go = &riscv013_resume_go;
generic_info->step_current_hart = &riscv013_step_current_hart;
generic_info->on_halt = &riscv013_on_halt;
generic_info->resume_prep = &riscv013_resume_prep;
generic_info->halt_prep = &riscv013_halt_prep;
generic_info->halt_go = &riscv013_halt_go;
@ -2498,6 +2596,10 @@ static int init_target(struct command_context *cmd_ctx,
generic_info->hart_count = &riscv013_hart_count;
generic_info->data_bits = &riscv013_data_bits;
generic_info->print_info = &riscv013_print_info;
generic_info->handle_became_unavailable = &handle_became_unavailable;
generic_info->tick = &tick;
if (!generic_info->version_specific) {
generic_info->version_specific = calloc(1, sizeof(riscv013_info_t));
if (!generic_info->version_specific)
@ -2615,6 +2717,7 @@ static int deassert_reset(struct target *target)
target->state = TARGET_RUNNING;
target->debug_reason = DBG_REASON_NOTHALTED;
}
info->dcsr_ebreak_is_set = false;
/* Ack reset and clear DM_DMCONTROL_HALTREQ if previously set */
control = 0;
@ -4502,11 +4605,6 @@ static int riscv013_on_step(struct target *target)
return riscv013_on_step_or_resume(target, true);
}
static int riscv013_on_halt(struct target *target)
{
return ERROR_OK;
}
static enum riscv_halt_reason riscv013_halt_reason(struct target *target)
{
riscv_reg_t dcsr;
@ -4630,7 +4728,7 @@ static int riscv013_on_step_or_resume(struct target *target, bool step)
if (maybe_execute_fence_i(target) != ERROR_OK)
return ERROR_FAIL;
if (update_dcsr(target, step) != ERROR_OK)
if (set_dcsr_ebreak(target, step) != ERROR_OK)
return ERROR_FAIL;
if (riscv_flush_registers(target) != ERROR_OK)

View File

@ -2752,7 +2752,9 @@ static int riscv_poll_hart(struct target *target, enum riscv_next_action *next_a
}
}
r->on_halt(target);
if (r->handle_became_halted &&
r->handle_became_halted(target, previous_riscv_state) != ERROR_OK)
return ERROR_FAIL;
/* We shouldn't do the callbacks yet. What if
* there are multiple harts that halted at the
@ -2771,12 +2773,18 @@ static int riscv_poll_hart(struct target *target, enum riscv_next_action *next_a
LOG_TARGET_DEBUG(target, " triggered running");
target->state = TARGET_RUNNING;
target->debug_reason = DBG_REASON_NOTHALTED;
if (r->handle_became_running &&
r->handle_became_running(target, previous_riscv_state) != ERROR_OK)
return ERROR_FAIL;
break;
case RISCV_STATE_UNAVAILABLE:
LOG_TARGET_DEBUG(target, " became unavailable");
LOG_TARGET_INFO(target, "became unavailable.");
target->state = TARGET_UNAVAILABLE;
if (r->handle_became_unavailable &&
r->handle_became_unavailable(target, previous_riscv_state) != ERROR_OK)
return ERROR_FAIL;
break;
case RISCV_STATE_NON_EXISTENT:
@ -2926,6 +2934,17 @@ int riscv_openocd_poll(struct target *target)
}
}
/* Call tick() for every hart. What happens in tick() is opaque to this
* layer. The reason it's outside the previous loop is that at this point
* the state of every hart has settled, so any side effects happening in
* tick() won't affect the delicate poll() code. */
foreach_smp_target(entry, targets) {
struct target *t = entry->target;
struct riscv_info *info = riscv_info(t);
if (info->tick && info->tick(t) != ERROR_OK)
return ERROR_FAIL;
}
/* Sample memory if any target is running. */
foreach_smp_target(entry, targets) {
struct target *t = entry->target;
@ -4538,7 +4557,6 @@ static int riscv_step_rtos_hart(struct target *target)
r->on_step(target);
if (r->step_current_hart(target) != ERROR_OK)
return ERROR_FAIL;
r->on_halt(target);
if (target->state != TARGET_HALTED) {
LOG_ERROR("Hart was not halted after single step!");
return ERROR_FAIL;

View File

@ -193,7 +193,21 @@ struct riscv_info {
* was resumed. */
int (*resume_go)(struct target *target);
int (*step_current_hart)(struct target *target);
int (*on_halt)(struct target *target);
/* These get called from riscv_poll_hart(), which is a house of cards
* together with openocd_poll(), so be careful not to upset things too
* much. */
int (*handle_became_halted)(struct target *target,
enum riscv_hart_state previous_riscv_state);
int (*handle_became_running)(struct target *target,
enum riscv_hart_state previous_riscv_state);
int (*handle_became_unavailable)(struct target *target,
enum riscv_hart_state previous_riscv_state);
/* Called periodically (no guarantees about frequency), while there's
* nothing else going on. */
int (*tick)(struct target *target);
/* Get this target as ready as possible to resume, without actually
* resuming. */
int (*resume_prep)(struct target *target);