2196 lines
64 KiB
C
2196 lines
64 KiB
C
/*
|
|
* Support for RISC-V, debug version 0.13, which is currently (2/4/17) the
|
|
* latest draft.
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "target/target.h"
|
|
#include "target/algorithm.h"
|
|
#include "target/target_type.h"
|
|
#include "log.h"
|
|
#include "jtag/jtag.h"
|
|
#include "target/register.h"
|
|
#include "target/breakpoints.h"
|
|
#include "helper/time_support.h"
|
|
#include "riscv.h"
|
|
#include "rtos/riscv_debug.h"
|
|
#include "debug_defines.h"
|
|
#include "rtos/rtos.h"
|
|
#include "program.h"
|
|
#include "asm.h"
|
|
#include "batch.h"
|
|
|
|
#define DMI_DATA1 (DMI_DATA0 + 1)
|
|
#define DMI_PROGBUF1 (DMI_PROGBUF0 + 1)
|
|
|
|
static int riscv013_on_step_or_resume(struct target *target, bool step);
|
|
static int riscv013_step_or_resume_current_hart(struct target *target, bool step);
|
|
static void riscv013_clear_abstract_error(struct target *target);
|
|
|
|
/* Implementations of the functions in riscv_info_t. */
|
|
static int riscv013_get_register(struct target *target,
|
|
riscv_reg_t *value, int hid, int rid);
|
|
static int riscv013_set_register(struct target *target, int hartid, int regid, uint64_t value);
|
|
static void riscv013_select_current_hart(struct target *target);
|
|
static int riscv013_halt_current_hart(struct target *target);
|
|
static int riscv013_resume_current_hart(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_on_resume(struct target *target);
|
|
static bool riscv013_is_halted(struct target *target);
|
|
static enum riscv_halt_reason riscv013_halt_reason(struct target *target);
|
|
static int riscv013_write_debug_buffer(struct target *target, unsigned index,
|
|
riscv_insn_t d);
|
|
static riscv_insn_t riscv013_read_debug_buffer(struct target *target, unsigned
|
|
index);
|
|
static int riscv013_execute_debug_buffer(struct target *target);
|
|
static void riscv013_fill_dmi_write_u64(struct target *target, char *buf, int a, uint64_t d);
|
|
static void riscv013_fill_dmi_read_u64(struct target *target, char *buf, int a);
|
|
static int riscv013_dmi_write_u64_bits(struct target *target);
|
|
static void riscv013_fill_dmi_nop_u64(struct target *target, char *buf);
|
|
static int register_read_direct(struct target *target, uint64_t *value, uint32_t number);
|
|
static int register_write_direct(struct target *target, unsigned number,
|
|
uint64_t value);
|
|
static int read_memory(struct target *target, target_addr_t address,
|
|
uint32_t size, uint32_t count, uint8_t *buffer);
|
|
static int write_memory(struct target *target, target_addr_t address,
|
|
uint32_t size, uint32_t count, const uint8_t *buffer);
|
|
|
|
/**
|
|
* Since almost everything can be accomplish by scanning the dbus register, all
|
|
* functions here assume dbus is already selected. The exception are functions
|
|
* called directly by OpenOCD, which can't assume anything about what's
|
|
* currently in IR. They should set IR to dbus explicitly.
|
|
*/
|
|
|
|
#define get_field(reg, mask) (((reg) & (mask)) / ((mask) & ~((mask) << 1)))
|
|
#define set_field(reg, mask, val) (((reg) & ~(mask)) | (((val) * ((mask) & ~((mask) << 1))) & (mask)))
|
|
|
|
#define DIM(x) (sizeof(x)/sizeof(*x))
|
|
|
|
#define CSR_DCSR_CAUSE_SWBP 1
|
|
#define CSR_DCSR_CAUSE_TRIGGER 2
|
|
#define CSR_DCSR_CAUSE_DEBUGINT 3
|
|
#define CSR_DCSR_CAUSE_STEP 4
|
|
#define CSR_DCSR_CAUSE_HALT 5
|
|
|
|
#define RISCV013_INFO(r) riscv013_info_t *r = get_info(target)
|
|
|
|
/*** JTAG registers. ***/
|
|
|
|
typedef enum {
|
|
DMI_OP_NOP = 0,
|
|
DMI_OP_READ = 1,
|
|
DMI_OP_WRITE = 2
|
|
} dmi_op_t;
|
|
typedef enum {
|
|
DMI_STATUS_SUCCESS = 0,
|
|
DMI_STATUS_FAILED = 2,
|
|
DMI_STATUS_BUSY = 3
|
|
} dmi_status_t;
|
|
|
|
typedef enum {
|
|
RE_OK,
|
|
RE_FAIL,
|
|
RE_AGAIN
|
|
} riscv_error_t;
|
|
|
|
typedef enum slot {
|
|
SLOT0,
|
|
SLOT1,
|
|
SLOT_LAST,
|
|
} slot_t;
|
|
|
|
/*** Debug Bus registers. ***/
|
|
|
|
#define CMDERR_NONE 0
|
|
#define CMDERR_BUSY 1
|
|
#define CMDERR_NOT_SUPPORTED 2
|
|
#define CMDERR_EXCEPTION 3
|
|
#define CMDERR_HALT_RESUME 4
|
|
#define CMDERR_OTHER 7
|
|
|
|
/*** Info about the core being debugged. ***/
|
|
|
|
struct trigger {
|
|
uint64_t address;
|
|
uint32_t length;
|
|
uint64_t mask;
|
|
uint64_t value;
|
|
bool read, write, execute;
|
|
int unique_id;
|
|
};
|
|
|
|
typedef enum {
|
|
YNM_MAYBE,
|
|
YNM_YES,
|
|
YNM_NO
|
|
} yes_no_maybe_t;
|
|
|
|
typedef struct {
|
|
/* Number of address bits in the dbus register. */
|
|
unsigned abits;
|
|
/* Number of abstract command data registers. */
|
|
unsigned datacount;
|
|
/* Number of words in the Program Buffer. */
|
|
unsigned progbufsize;
|
|
|
|
yes_no_maybe_t progbuf_writable;
|
|
/* We only need the address so that we know the alignment of the buffer. */
|
|
riscv_addr_t progbuf_address;
|
|
|
|
/* Number of run-test/idle cycles the target requests we do after each dbus
|
|
* access. */
|
|
unsigned int dtmcontrol_idle;
|
|
|
|
/* This value is incremented every time a dbus access comes back as "busy".
|
|
* It's used to determine how many run-test/idle cycles to feed the target
|
|
* in between accesses. */
|
|
unsigned int dmi_busy_delay;
|
|
|
|
/* This value is increased every time we tried to execute two commands
|
|
* consecutively, and the second one failed because the previous hadn't
|
|
* completed yet. It's used to add extra run-test/idle cycles after
|
|
* starting a command, so we don't have to waste time checking for busy to
|
|
* go low. */
|
|
unsigned int ac_busy_delay;
|
|
|
|
bool need_strict_step;
|
|
|
|
bool abstract_read_csr_supported;
|
|
bool abstract_write_csr_supported;
|
|
bool abstract_read_fpr_supported;
|
|
bool abstract_write_fpr_supported;
|
|
|
|
/* When a function returns some error due to a failure indicated by the
|
|
* target in cmderr, the caller can look here to see what that error was.
|
|
* (Compare with errno.) */
|
|
uint8_t cmderr;
|
|
|
|
/* Some fields from hartinfo. */
|
|
uint8_t datasize;
|
|
uint8_t dataaccess;
|
|
int16_t dataaddr;
|
|
|
|
/* The width of the hartsel field. */
|
|
unsigned hartsellen;
|
|
} riscv013_info_t;
|
|
|
|
static riscv013_info_t *get_info(const struct target *target)
|
|
{
|
|
riscv_info_t *info = (riscv_info_t *) target->arch_info;
|
|
return (riscv013_info_t *) info->version_specific;
|
|
}
|
|
|
|
static uint32_t hartsel_mask(const struct target *target)
|
|
{
|
|
RISCV013_INFO(info);
|
|
return ((1L<<info->hartsellen)-1) << DMI_DMCONTROL_HARTSEL_OFFSET;
|
|
}
|
|
|
|
static void decode_dmi(char *text, unsigned address, unsigned data)
|
|
{
|
|
static const struct {
|
|
unsigned address;
|
|
uint64_t mask;
|
|
const char *name;
|
|
} description[] = {
|
|
{ DMI_DMCONTROL, DMI_DMCONTROL_HALTREQ, "haltreq" },
|
|
{ DMI_DMCONTROL, DMI_DMCONTROL_RESUMEREQ, "resumereq" },
|
|
{ DMI_DMCONTROL, DMI_DMCONTROL_HARTRESET, "hartreset" },
|
|
{ DMI_DMCONTROL, DMI_DMCONTROL_HASEL, "hasel" },
|
|
{ DMI_DMCONTROL, ((1L<<10)-1) << DMI_DMCONTROL_HARTSEL_OFFSET, "hartsel" },
|
|
{ DMI_DMCONTROL, DMI_DMCONTROL_NDMRESET, "ndmreset" },
|
|
{ DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE, "dmactive" },
|
|
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_IMPEBREAK, "impebreak" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ALLRESUMEACK, "allresumeack" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ANYRESUMEACK, "anyresumeack" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ALLNONEXISTENT, "allnonexistent" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ANYNONEXISTENT, "anynonexistent" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ALLUNAVAIL, "allunavail" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ANYUNAVAIL, "anyunavail" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ALLRUNNING, "allrunning" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ANYRUNNING, "anyrunning" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ALLHALTED, "allhalted" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_ANYHALTED, "anyhalted" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_AUTHENTICATED, "authenticated" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_AUTHBUSY, "authbusy" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_DEVTREEVALID, "devtreevalid" },
|
|
{ DMI_DMSTATUS, DMI_DMSTATUS_VERSION, "version" },
|
|
|
|
{ DMI_ABSTRACTCS, DMI_ABSTRACTCS_PROGBUFSIZE, "progbufsize" },
|
|
{ DMI_ABSTRACTCS, DMI_ABSTRACTCS_BUSY, "busy" },
|
|
{ DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR, "cmderr" },
|
|
{ DMI_ABSTRACTCS, DMI_ABSTRACTCS_DATACOUNT, "datacount" },
|
|
|
|
{ DMI_COMMAND, DMI_COMMAND_CMDTYPE, "cmdtype" },
|
|
};
|
|
|
|
text[0] = 0;
|
|
for (unsigned i = 0; i < DIM(description); i++) {
|
|
if (description[i].address == address) {
|
|
uint64_t mask = description[i].mask;
|
|
unsigned value = get_field(data, mask);
|
|
if (value) {
|
|
if (i > 0)
|
|
*(text++) = ' ';
|
|
if (mask & (mask >> 1)) {
|
|
/* If the field is more than 1 bit wide. */
|
|
sprintf(text, "%s=%d", description[i].name, value);
|
|
} else {
|
|
strcpy(text, description[i].name);
|
|
}
|
|
text += strlen(text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void dump_field(const struct scan_field *field)
|
|
{
|
|
static const char * const op_string[] = {"-", "r", "w", "?"};
|
|
static const char * const status_string[] = {"+", "?", "F", "b"};
|
|
|
|
if (debug_level < LOG_LVL_DEBUG)
|
|
return;
|
|
|
|
uint64_t out = buf_get_u64(field->out_value, 0, field->num_bits);
|
|
unsigned int out_op = get_field(out, DTM_DMI_OP);
|
|
unsigned int out_data = get_field(out, DTM_DMI_DATA);
|
|
unsigned int out_address = out >> DTM_DMI_ADDRESS_OFFSET;
|
|
|
|
uint64_t in = buf_get_u64(field->in_value, 0, field->num_bits);
|
|
unsigned int in_op = get_field(in, DTM_DMI_OP);
|
|
unsigned int in_data = get_field(in, DTM_DMI_DATA);
|
|
unsigned int in_address = in >> DTM_DMI_ADDRESS_OFFSET;
|
|
|
|
log_printf_lf(LOG_LVL_DEBUG,
|
|
__FILE__, __LINE__, "scan",
|
|
"%db %s %08x @%02x -> %s %08x @%02x",
|
|
field->num_bits,
|
|
op_string[out_op], out_data, out_address,
|
|
status_string[in_op], in_data, in_address);
|
|
|
|
char out_text[500];
|
|
char in_text[500];
|
|
decode_dmi(out_text, out_address, out_data);
|
|
decode_dmi(in_text, in_address, in_data);
|
|
if (in_text[0] || out_text[0]) {
|
|
log_printf_lf(LOG_LVL_DEBUG, __FILE__, __LINE__, "scan", "%s -> %s",
|
|
out_text, in_text);
|
|
}
|
|
}
|
|
|
|
/*** Utility functions. ***/
|
|
|
|
static void select_dmi(struct target *target)
|
|
{
|
|
static uint8_t ir_dmi[1] = {DTM_DMI};
|
|
struct scan_field field = {
|
|
.num_bits = target->tap->ir_length,
|
|
.out_value = ir_dmi,
|
|
.in_value = NULL,
|
|
.check_value = NULL,
|
|
.check_mask = NULL
|
|
};
|
|
|
|
jtag_add_ir_scan(target->tap, &field, TAP_IDLE);
|
|
}
|
|
|
|
static uint32_t dtmcontrol_scan(struct target *target, uint32_t out)
|
|
{
|
|
struct scan_field field;
|
|
uint8_t in_value[4];
|
|
uint8_t out_value[4];
|
|
|
|
buf_set_u32(out_value, 0, 32, out);
|
|
|
|
jtag_add_ir_scan(target->tap, &select_dtmcontrol, TAP_IDLE);
|
|
|
|
field.num_bits = 32;
|
|
field.out_value = out_value;
|
|
field.in_value = in_value;
|
|
jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);
|
|
|
|
/* Always return to dmi. */
|
|
select_dmi(target);
|
|
|
|
int retval = jtag_execute_queue();
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("failed jtag scan: %d", retval);
|
|
return retval;
|
|
}
|
|
|
|
uint32_t in = buf_get_u32(field.in_value, 0, 32);
|
|
LOG_DEBUG("DTMCS: 0x%x -> 0x%x", out, in);
|
|
|
|
return in;
|
|
}
|
|
|
|
static void increase_dmi_busy_delay(struct target *target)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
info->dmi_busy_delay += info->dmi_busy_delay / 10 + 1;
|
|
LOG_DEBUG("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
|
|
info->dtmcontrol_idle, info->dmi_busy_delay,
|
|
info->ac_busy_delay);
|
|
|
|
dtmcontrol_scan(target, DTM_DTMCS_DMIRESET);
|
|
}
|
|
|
|
/**
|
|
* exec: If this is set, assume the scan results in an execution, so more
|
|
* run-test/idle cycles may be required.
|
|
*/
|
|
static dmi_status_t dmi_scan(struct target *target, uint16_t *address_in,
|
|
uint32_t *data_in, dmi_op_t op, uint16_t address_out, uint32_t data_out,
|
|
bool exec)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
uint8_t in[8] = {0};
|
|
uint8_t out[8];
|
|
struct scan_field field = {
|
|
.num_bits = info->abits + DTM_DMI_OP_LENGTH + DTM_DMI_DATA_LENGTH,
|
|
.out_value = out,
|
|
.in_value = in
|
|
};
|
|
|
|
assert(info->abits != 0);
|
|
|
|
buf_set_u32(out, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, op);
|
|
buf_set_u32(out, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, data_out);
|
|
buf_set_u32(out, DTM_DMI_ADDRESS_OFFSET, info->abits, address_out);
|
|
|
|
/* Assume dbus is already selected. */
|
|
jtag_add_dr_scan(target->tap, 1, &field, TAP_IDLE);
|
|
|
|
int idle_count = info->dmi_busy_delay;
|
|
if (exec)
|
|
idle_count += info->ac_busy_delay;
|
|
|
|
if (idle_count)
|
|
jtag_add_runtest(idle_count, TAP_IDLE);
|
|
|
|
int retval = jtag_execute_queue();
|
|
if (retval != ERROR_OK) {
|
|
LOG_ERROR("dmi_scan failed jtag scan");
|
|
return DMI_STATUS_FAILED;
|
|
}
|
|
|
|
if (data_in)
|
|
*data_in = buf_get_u32(in, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH);
|
|
|
|
if (address_in)
|
|
*address_in = buf_get_u32(in, DTM_DMI_ADDRESS_OFFSET, info->abits);
|
|
|
|
dump_field(&field);
|
|
|
|
return buf_get_u32(in, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH);
|
|
}
|
|
|
|
static uint32_t dmi_read(struct target *target, uint16_t address)
|
|
{
|
|
select_dmi(target);
|
|
|
|
dmi_status_t status;
|
|
uint16_t address_in;
|
|
|
|
unsigned i = 0;
|
|
|
|
/* This first loop ensures that the read request was actually sent
|
|
* to the target. Note that if for some reason this stays busy,
|
|
* it is actually due to the previous dmi_read or dmi_write. */
|
|
for (i = 0; i < 256; i++) {
|
|
status = dmi_scan(target, NULL, NULL, DMI_OP_READ, address, 0,
|
|
false);
|
|
if (status == DMI_STATUS_BUSY) {
|
|
increase_dmi_busy_delay(target);
|
|
} else if (status == DMI_STATUS_SUCCESS) {
|
|
break;
|
|
} else {
|
|
LOG_ERROR("failed read from 0x%x, status=%d", address, status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (status != DMI_STATUS_SUCCESS) {
|
|
LOG_ERROR("Failed read from 0x%x; status=%d", address, status);
|
|
return ~0;
|
|
}
|
|
|
|
/* This second loop ensures that we got the read
|
|
* data back. Note that NOP can result in a 'busy' result as well, but
|
|
* that would be noticed on the next DMI access we do. */
|
|
uint32_t value;
|
|
for (i = 0; i < 256; i++) {
|
|
status = dmi_scan(target, &address_in, &value, DMI_OP_NOP, address, 0,
|
|
false);
|
|
if (status == DMI_STATUS_BUSY) {
|
|
increase_dmi_busy_delay(target);
|
|
} else if (status == DMI_STATUS_SUCCESS) {
|
|
break;
|
|
} else {
|
|
LOG_ERROR("failed read (NOP) at 0x%x, status=%d", address, status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (status != DMI_STATUS_SUCCESS) {
|
|
if (status == DMI_STATUS_FAILED) {
|
|
LOG_ERROR("Failed read (NOP) from 0x%x; status=%d", address, status);
|
|
} else {
|
|
LOG_ERROR("Failed read (NOP) from 0x%x; value=0x%x, status=%d",
|
|
address, value, status);
|
|
}
|
|
return ~0;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
static int dmi_write(struct target *target, uint16_t address, uint64_t value)
|
|
{
|
|
select_dmi(target);
|
|
dmi_status_t status = DMI_STATUS_BUSY;
|
|
unsigned i = 0;
|
|
|
|
/* The first loop ensures that we successfully sent the write request. */
|
|
for (i = 0; i < 256; i++) {
|
|
status = dmi_scan(target, NULL, NULL, DMI_OP_WRITE, address, value,
|
|
address == DMI_COMMAND);
|
|
if (status == DMI_STATUS_BUSY) {
|
|
increase_dmi_busy_delay(target);
|
|
} else if (status == DMI_STATUS_SUCCESS) {
|
|
break;
|
|
} else {
|
|
LOG_ERROR("failed write to 0x%x, status=%d", address, status);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (status != DMI_STATUS_SUCCESS) {
|
|
LOG_ERROR("Failed write to 0x%x;, status=%d",
|
|
address, status);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
/* The second loop isn't strictly necessary, but would ensure that the
|
|
* write is complete/ has no non-busy errors before returning from this
|
|
* function. */
|
|
for (i = 0; i < 256; i++) {
|
|
status = dmi_scan(target, NULL, NULL, DMI_OP_NOP, address, 0,
|
|
false);
|
|
if (status == DMI_STATUS_BUSY) {
|
|
increase_dmi_busy_delay(target);
|
|
} else if (status == DMI_STATUS_SUCCESS) {
|
|
break;
|
|
} else {
|
|
LOG_ERROR("failed write (NOP) at 0x%x, status=%d", address, status);
|
|
break;
|
|
}
|
|
}
|
|
if (status != DMI_STATUS_SUCCESS) {
|
|
LOG_ERROR("failed to write (NOP) 0x%" PRIx64 " to 0x%x; status=%d", value, address, status);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static void increase_ac_busy_delay(struct target *target)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
info->ac_busy_delay += info->ac_busy_delay / 10 + 1;
|
|
LOG_DEBUG("dtmcontrol_idle=%d, dmi_busy_delay=%d, ac_busy_delay=%d",
|
|
info->dtmcontrol_idle, info->dmi_busy_delay,
|
|
info->ac_busy_delay);
|
|
}
|
|
|
|
uint32_t abstract_register_size(unsigned width)
|
|
{
|
|
switch (width) {
|
|
case 32:
|
|
return set_field(0, AC_ACCESS_REGISTER_SIZE, 2);
|
|
case 64:
|
|
return set_field(0, AC_ACCESS_REGISTER_SIZE, 3);
|
|
break;
|
|
case 128:
|
|
return set_field(0, AC_ACCESS_REGISTER_SIZE, 4);
|
|
break;
|
|
default:
|
|
LOG_ERROR("Unsupported register width: %d", width);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int wait_for_idle(struct target *target, uint32_t *abstractcs)
|
|
{
|
|
RISCV013_INFO(info);
|
|
time_t start = time(NULL);
|
|
while (1) {
|
|
*abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
|
|
if (get_field(*abstractcs, DMI_ABSTRACTCS_BUSY) == 0)
|
|
return ERROR_OK;
|
|
|
|
if (time(NULL) - start > riscv_command_timeout_sec) {
|
|
info->cmderr = get_field(*abstractcs, DMI_ABSTRACTCS_CMDERR);
|
|
if (info->cmderr != CMDERR_NONE) {
|
|
const char *errors[8] = {
|
|
"none",
|
|
"busy",
|
|
"not supported",
|
|
"exception",
|
|
"halt/resume",
|
|
"reserved",
|
|
"reserved",
|
|
"other" };
|
|
|
|
LOG_ERROR("Abstract command ended in error '%s' (abstractcs=0x%x)",
|
|
errors[info->cmderr], *abstractcs);
|
|
}
|
|
|
|
LOG_ERROR("Timed out after %ds waiting for busy to go low (abstractcs=0x%x). "
|
|
"Increase the timeout with riscv set_command_timeout_sec.",
|
|
riscv_command_timeout_sec,
|
|
*abstractcs);
|
|
return ERROR_FAIL;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int execute_abstract_command(struct target *target, uint32_t command)
|
|
{
|
|
RISCV013_INFO(info);
|
|
LOG_DEBUG("command=0x%x", command);
|
|
dmi_write(target, DMI_COMMAND, command);
|
|
|
|
{
|
|
uint32_t abstractcs = 0;
|
|
wait_for_idle(target, &abstractcs);
|
|
}
|
|
|
|
uint32_t cs = dmi_read(target, DMI_ABSTRACTCS);
|
|
info->cmderr = get_field(cs, DMI_ABSTRACTCS_CMDERR);
|
|
if (info->cmderr != 0) {
|
|
LOG_DEBUG("command 0x%x failed; abstractcs=0x%x", command, cs);
|
|
/* Clear the error. */
|
|
dmi_write(target, DMI_ABSTRACTCS, set_field(0, DMI_ABSTRACTCS_CMDERR,
|
|
info->cmderr));
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static riscv_reg_t read_abstract_arg(struct target *target, unsigned index)
|
|
{
|
|
riscv_reg_t value = 0;
|
|
unsigned xlen = riscv_xlen(target);
|
|
unsigned offset = index * xlen / 32;
|
|
switch (xlen) {
|
|
default:
|
|
LOG_ERROR("Unsupported xlen: %d", xlen);
|
|
return ~0;
|
|
case 64:
|
|
value |= ((uint64_t) dmi_read(target, DMI_DATA0 + offset + 1)) << 32;
|
|
case 32:
|
|
value |= dmi_read(target, DMI_DATA0 + offset);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
static int write_abstract_arg(struct target *target, unsigned index,
|
|
riscv_reg_t value)
|
|
{
|
|
unsigned xlen = riscv_xlen(target);
|
|
unsigned offset = index * xlen / 32;
|
|
switch (xlen) {
|
|
default:
|
|
LOG_ERROR("Unsupported xlen: %d", xlen);
|
|
return ~0;
|
|
case 64:
|
|
dmi_write(target, DMI_DATA0 + offset + 1, value >> 32);
|
|
case 32:
|
|
dmi_write(target, DMI_DATA0 + offset, value);
|
|
}
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* @size in bits
|
|
*/
|
|
static uint32_t access_register_command(uint32_t number, unsigned size,
|
|
uint32_t flags)
|
|
{
|
|
uint32_t command = set_field(0, DMI_COMMAND_CMDTYPE, 0);
|
|
switch (size) {
|
|
case 32:
|
|
command = set_field(command, AC_ACCESS_REGISTER_SIZE, 2);
|
|
break;
|
|
case 64:
|
|
command = set_field(command, AC_ACCESS_REGISTER_SIZE, 3);
|
|
break;
|
|
default:
|
|
assert(0);
|
|
}
|
|
|
|
if (number <= GDB_REGNO_XPR31) {
|
|
command = set_field(command, AC_ACCESS_REGISTER_REGNO,
|
|
0x1000 + number - GDB_REGNO_ZERO);
|
|
} else if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
|
command = set_field(command, AC_ACCESS_REGISTER_REGNO,
|
|
0x1020 + number - GDB_REGNO_FPR0);
|
|
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
|
command = set_field(command, AC_ACCESS_REGISTER_REGNO,
|
|
number - GDB_REGNO_CSR0);
|
|
} else {
|
|
assert(0);
|
|
}
|
|
|
|
command |= flags;
|
|
|
|
return command;
|
|
}
|
|
|
|
static int register_read_abstract(struct target *target, uint64_t *value,
|
|
uint32_t number, unsigned size)
|
|
{
|
|
RISCV013_INFO(info);
|
|
|
|
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
|
|
!info->abstract_read_fpr_supported)
|
|
return ERROR_FAIL;
|
|
if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095 &&
|
|
!info->abstract_read_csr_supported)
|
|
return ERROR_FAIL;
|
|
|
|
uint32_t command = access_register_command(number, size,
|
|
AC_ACCESS_REGISTER_TRANSFER);
|
|
|
|
int result = execute_abstract_command(target, command);
|
|
if (result != ERROR_OK) {
|
|
if (info->cmderr == CMDERR_NOT_SUPPORTED) {
|
|
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
|
info->abstract_read_fpr_supported = false;
|
|
LOG_INFO("Disabling abstract command reads from FPRs.");
|
|
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
|
info->abstract_read_csr_supported = false;
|
|
LOG_INFO("Disabling abstract command reads from CSRs.");
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
if (value)
|
|
*value = read_abstract_arg(target, 0);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int register_write_abstract(struct target *target, uint32_t number,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
RISCV013_INFO(info);
|
|
|
|
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
|
|
!info->abstract_write_fpr_supported)
|
|
return ERROR_FAIL;
|
|
if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095 &&
|
|
!info->abstract_write_csr_supported)
|
|
return ERROR_FAIL;
|
|
|
|
uint32_t command = access_register_command(number, size,
|
|
AC_ACCESS_REGISTER_TRANSFER |
|
|
AC_ACCESS_REGISTER_WRITE);
|
|
|
|
if (write_abstract_arg(target, 0, value) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
int result = execute_abstract_command(target, command);
|
|
if (result != ERROR_OK) {
|
|
if (info->cmderr == CMDERR_NOT_SUPPORTED) {
|
|
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
|
info->abstract_write_fpr_supported = false;
|
|
LOG_INFO("Disabling abstract command writes to FPRs.");
|
|
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
|
info->abstract_write_csr_supported = false;
|
|
LOG_INFO("Disabling abstract command writes to CSRs.");
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int examine_progbuf(struct target *target)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
|
|
if (info->progbuf_writable != YNM_MAYBE)
|
|
return ERROR_OK;
|
|
|
|
/* Figure out if progbuf is writable. */
|
|
|
|
if (info->progbufsize < 1) {
|
|
info->progbuf_writable = YNM_NO;
|
|
LOG_INFO("No program buffer present.");
|
|
return ERROR_OK;
|
|
}
|
|
|
|
uint64_t s0;
|
|
if (register_read_direct(target, &s0, GDB_REGNO_S0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_program_insert(&program, auipc(S0));
|
|
if (riscv_program_exec(&program, target) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (register_read_direct(target, &info->progbuf_address, GDB_REGNO_S0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
riscv_program_init(&program, target);
|
|
riscv_program_insert(&program, sw(S0, S0, 0));
|
|
int result = riscv_program_exec(&program, target);
|
|
|
|
if (register_write_direct(target, GDB_REGNO_S0, s0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (result != ERROR_OK) {
|
|
/* This program might have failed if the program buffer is not
|
|
* writable. */
|
|
info->progbuf_writable = YNM_NO;
|
|
return ERROR_OK;
|
|
}
|
|
|
|
uint32_t written = dmi_read(target, DMI_PROGBUF0);
|
|
if (written == (uint32_t) info->progbuf_address) {
|
|
LOG_INFO("progbuf is writable at 0x%" TARGET_PRIxADDR,
|
|
info->progbuf_address);
|
|
info->progbuf_writable = YNM_YES;
|
|
|
|
} else {
|
|
LOG_INFO("progbuf is not writeable at 0x%" TARGET_PRIxADDR,
|
|
info->progbuf_address);
|
|
info->progbuf_writable = YNM_NO;
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
typedef enum {
|
|
SPACE_DMI_DATA,
|
|
SPACE_DMI_PROGBUF,
|
|
SPACE_DMI_RAM
|
|
} memory_space_t;
|
|
|
|
typedef struct {
|
|
/* How can the debugger access this memory? */
|
|
memory_space_t memory_space;
|
|
/* Memory address to access the scratch memory from the hart. */
|
|
riscv_addr_t hart_address;
|
|
/* Memory address to access the scratch memory from the debugger. */
|
|
riscv_addr_t debug_address;
|
|
} scratch_mem_t;
|
|
|
|
/**
|
|
* Find some scratch memory to be used with the given program.
|
|
*/
|
|
static int scratch_find(struct target *target,
|
|
scratch_mem_t *scratch,
|
|
struct riscv_program *program,
|
|
unsigned size_bytes)
|
|
{
|
|
riscv013_info_t *info = get_info(target);
|
|
|
|
riscv_addr_t alignment = 1;
|
|
while (alignment < size_bytes)
|
|
alignment *= 2;
|
|
|
|
if (info->dataaccess == 1) {
|
|
/* Sign extend dataaddr. */
|
|
scratch->hart_address = info->dataaddr;
|
|
if (info->dataaddr & (1<<11))
|
|
scratch->hart_address |= 0xfffffffffffff000ULL;
|
|
/* Align. */
|
|
scratch->hart_address = (scratch->hart_address + alignment - 1) & ~(alignment - 1);
|
|
|
|
if ((size_bytes + scratch->hart_address - info->dataaddr + 3) / 4 >=
|
|
info->datasize) {
|
|
scratch->memory_space = SPACE_DMI_DATA;
|
|
scratch->debug_address = (scratch->hart_address - info->dataaddr) / 4;
|
|
return ERROR_OK;
|
|
}
|
|
}
|
|
|
|
if (examine_progbuf(target) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
/* Allow for ebreak at the end of the program. */
|
|
unsigned program_size = (program->instruction_count + 1) * 4;
|
|
scratch->hart_address = (info->progbuf_address + program_size + alignment - 1) &
|
|
~(alignment - 1);
|
|
if ((size_bytes + scratch->hart_address - info->progbuf_address + 3) / 4 >=
|
|
info->progbufsize) {
|
|
scratch->memory_space = SPACE_DMI_PROGBUF;
|
|
scratch->debug_address = (scratch->hart_address - info->progbuf_address) / 4;
|
|
return ERROR_OK;
|
|
}
|
|
|
|
if (riscv_use_scratch_ram) {
|
|
scratch->hart_address = (riscv_scratch_ram_address + alignment - 1) &
|
|
~(alignment - 1);
|
|
scratch->memory_space = SPACE_DMI_RAM;
|
|
scratch->debug_address = scratch->hart_address;
|
|
return ERROR_OK;
|
|
}
|
|
|
|
LOG_ERROR("Couldn't find %d bytes of scratch RAM to use. Please configure "
|
|
"an address with 'riscv set_scratch_ram'.", size_bytes);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
static int scratch_read64(struct target *target, scratch_mem_t *scratch,
|
|
uint64_t *value)
|
|
{
|
|
switch (scratch->memory_space) {
|
|
case SPACE_DMI_DATA:
|
|
*value = dmi_read(target, DMI_DATA0 + scratch->debug_address);
|
|
*value |= ((uint64_t) dmi_read(target, DMI_DATA1 +
|
|
scratch->debug_address)) << 32;
|
|
break;
|
|
case SPACE_DMI_PROGBUF:
|
|
*value = dmi_read(target, DMI_PROGBUF0 + scratch->debug_address);
|
|
*value |= ((uint64_t) dmi_read(target, DMI_PROGBUF1 +
|
|
scratch->debug_address)) << 32;
|
|
break;
|
|
case SPACE_DMI_RAM:
|
|
{
|
|
uint8_t buffer[8];
|
|
if (read_memory(target, scratch->debug_address, 4, 2, buffer) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
*value = buffer[0] |
|
|
(((uint64_t) buffer[1]) << 8) |
|
|
(((uint64_t) buffer[2]) << 16) |
|
|
(((uint64_t) buffer[3]) << 24) |
|
|
(((uint64_t) buffer[4]) << 32) |
|
|
(((uint64_t) buffer[5]) << 40) |
|
|
(((uint64_t) buffer[6]) << 48) |
|
|
(((uint64_t) buffer[7]) << 56);
|
|
}
|
|
break;
|
|
}
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int scratch_write64(struct target *target, scratch_mem_t *scratch,
|
|
uint64_t value)
|
|
{
|
|
switch (scratch->memory_space) {
|
|
case SPACE_DMI_DATA:
|
|
dmi_write(target, DMI_DATA0 + scratch->debug_address, value);
|
|
dmi_write(target, DMI_DATA1 + scratch->debug_address, value >> 32);
|
|
break;
|
|
case SPACE_DMI_PROGBUF:
|
|
dmi_write(target, DMI_PROGBUF0 + scratch->debug_address, value);
|
|
dmi_write(target, DMI_PROGBUF1 + scratch->debug_address, value >> 32);
|
|
break;
|
|
case SPACE_DMI_RAM:
|
|
{
|
|
uint8_t buffer[8] = {
|
|
value,
|
|
value >> 8,
|
|
value >> 16,
|
|
value >> 24,
|
|
value >> 32,
|
|
value >> 40,
|
|
value >> 48,
|
|
value >> 56
|
|
};
|
|
if (write_memory(target, scratch->debug_address, 4, 2, buffer) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
}
|
|
break;
|
|
}
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int register_write_direct(struct target *target, unsigned number,
|
|
uint64_t value)
|
|
{
|
|
LOG_DEBUG("[%d] reg[0x%x] <- 0x%" PRIx64, riscv_current_hartid(target),
|
|
number, value);
|
|
|
|
int result = register_write_abstract(target, number, value,
|
|
riscv_xlen(target));
|
|
if (result == ERROR_OK)
|
|
return ERROR_OK;
|
|
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
|
|
uint64_t s0;
|
|
if (register_read_direct(target, &s0, GDB_REGNO_S0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
|
|
riscv_supports_extension(target, 'D') &&
|
|
riscv_xlen(target) < 64) {
|
|
/* There are no instructions to move all the bits from a register, so
|
|
* we need to use some scratch RAM. */
|
|
riscv_program_insert(&program, fld(number - GDB_REGNO_FPR0, S0, 0));
|
|
|
|
scratch_mem_t scratch;
|
|
if (scratch_find(target, &scratch, &program, 8) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (register_write_direct(target, GDB_REGNO_S0, scratch.hart_address)
|
|
!= ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (scratch_write64(target, &scratch, value) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
} else {
|
|
if (register_write_direct(target, GDB_REGNO_S0, value) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
|
if (riscv_supports_extension(target, 'D'))
|
|
riscv_program_insert(&program, fmv_d_x(number - GDB_REGNO_FPR0, S0));
|
|
else
|
|
riscv_program_insert(&program, fmv_w_x(number - GDB_REGNO_FPR0, S0));
|
|
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
|
riscv_program_csrw(&program, S0, number);
|
|
} else {
|
|
LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
|
|
return ERROR_FAIL;
|
|
}
|
|
}
|
|
|
|
int exec_out = riscv_program_exec(&program, target);
|
|
|
|
/* Restore S0. */
|
|
if (register_write_direct(target, GDB_REGNO_S0, s0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
return exec_out;
|
|
}
|
|
|
|
/** Actually read registers from the target right now. */
|
|
static int register_read_direct(struct target *target, uint64_t *value, uint32_t number)
|
|
{
|
|
int result = register_read_abstract(target, value, number,
|
|
riscv_xlen(target));
|
|
|
|
if (result != ERROR_OK) {
|
|
assert(number != GDB_REGNO_S0);
|
|
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
|
|
scratch_mem_t scratch;
|
|
bool use_scratch = false;
|
|
|
|
uint64_t s0;
|
|
if (register_read_direct(target, &s0, GDB_REGNO_S0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
/* Write program to move data into s0. */
|
|
|
|
uint64_t mstatus;
|
|
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) {
|
|
if (register_read_direct(target, &mstatus, GDB_REGNO_MSTATUS) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
if ((mstatus & MSTATUS_FS) == 0)
|
|
if (register_write_direct(target, GDB_REGNO_MSTATUS,
|
|
set_field(mstatus, MSTATUS_FS, 1)) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (riscv_supports_extension(target, 'D') && riscv_xlen(target) < 64) {
|
|
/* There are no instructions to move all the bits from a
|
|
* register, so we need to use some scratch RAM. */
|
|
riscv_program_insert(&program, fsd(number - GDB_REGNO_FPR0, S0,
|
|
0));
|
|
|
|
if (scratch_find(target, &scratch, &program, 8) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
use_scratch = true;
|
|
|
|
if (register_write_direct(target, GDB_REGNO_S0,
|
|
scratch.hart_address) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
} else if (riscv_supports_extension(target, 'D')) {
|
|
riscv_program_insert(&program, fmv_x_d(S0, number - GDB_REGNO_FPR0));
|
|
} else {
|
|
riscv_program_insert(&program, fmv_x_w(S0, number - GDB_REGNO_FPR0));
|
|
}
|
|
} else if (number >= GDB_REGNO_CSR0 && number <= GDB_REGNO_CSR4095) {
|
|
riscv_program_csrr(&program, S0, number);
|
|
} else {
|
|
LOG_ERROR("Unsupported register (enum gdb_regno)(%d)", number);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
/* Execute program. */
|
|
result = riscv_program_exec(&program, target);
|
|
|
|
if (use_scratch) {
|
|
if (scratch_read64(target, &scratch, value) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
} else {
|
|
/* Read S0 */
|
|
if (register_read_direct(target, value, GDB_REGNO_S0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 &&
|
|
(mstatus & MSTATUS_FS) == 0)
|
|
if (register_write_direct(target, GDB_REGNO_MSTATUS, mstatus) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
/* Restore S0. */
|
|
if (register_write_direct(target, GDB_REGNO_S0, s0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
if (result == ERROR_OK) {
|
|
LOG_DEBUG("[%d] reg[0x%x] = 0x%" PRIx64, riscv_current_hartid(target),
|
|
number, *value);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*** OpenOCD target functions. ***/
|
|
|
|
static int init_target(struct command_context *cmd_ctx,
|
|
struct target *target)
|
|
{
|
|
LOG_DEBUG("init");
|
|
riscv_info_t *generic_info = (riscv_info_t *) target->arch_info;
|
|
|
|
generic_info->get_register = &riscv013_get_register;
|
|
generic_info->set_register = &riscv013_set_register;
|
|
generic_info->select_current_hart = &riscv013_select_current_hart;
|
|
generic_info->is_halted = &riscv013_is_halted;
|
|
generic_info->halt_current_hart = &riscv013_halt_current_hart;
|
|
generic_info->resume_current_hart = &riscv013_resume_current_hart;
|
|
generic_info->step_current_hart = &riscv013_step_current_hart;
|
|
generic_info->on_halt = &riscv013_on_halt;
|
|
generic_info->on_resume = &riscv013_on_resume;
|
|
generic_info->on_step = &riscv013_on_step;
|
|
generic_info->halt_reason = &riscv013_halt_reason;
|
|
generic_info->read_debug_buffer = &riscv013_read_debug_buffer;
|
|
generic_info->write_debug_buffer = &riscv013_write_debug_buffer;
|
|
generic_info->execute_debug_buffer = &riscv013_execute_debug_buffer;
|
|
generic_info->fill_dmi_write_u64 = &riscv013_fill_dmi_write_u64;
|
|
generic_info->fill_dmi_read_u64 = &riscv013_fill_dmi_read_u64;
|
|
generic_info->fill_dmi_nop_u64 = &riscv013_fill_dmi_nop_u64;
|
|
generic_info->dmi_write_u64_bits = &riscv013_dmi_write_u64_bits;
|
|
generic_info->version_specific = calloc(1, sizeof(riscv013_info_t));
|
|
if (!generic_info->version_specific)
|
|
return ERROR_FAIL;
|
|
riscv013_info_t *info = get_info(target);
|
|
|
|
info->progbufsize = -1;
|
|
|
|
info->dmi_busy_delay = 0;
|
|
info->ac_busy_delay = 0;
|
|
|
|
/* Assume all these abstract commands are supported until we learn
|
|
* otherwise.
|
|
* TODO: The spec allows eg. one CSR to be able to be accessed abstractly
|
|
* while another one isn't. We don't track that this closely here, but in
|
|
* the future we probably should. */
|
|
info->abstract_read_csr_supported = true;
|
|
info->abstract_write_csr_supported = true;
|
|
info->abstract_read_fpr_supported = true;
|
|
info->abstract_write_fpr_supported = true;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static void deinit_target(struct target *target)
|
|
{
|
|
LOG_DEBUG("riscv_deinit_target()");
|
|
riscv_info_t *info = (riscv_info_t *) target->arch_info;
|
|
free(info->version_specific);
|
|
info->version_specific = NULL;
|
|
}
|
|
|
|
static int examine(struct target *target)
|
|
{
|
|
/* Don't need to select dbus, since the first thing we do is read dtmcontrol. */
|
|
|
|
uint32_t dtmcontrol = dtmcontrol_scan(target, 0);
|
|
LOG_DEBUG("dtmcontrol=0x%x", dtmcontrol);
|
|
LOG_DEBUG(" dmireset=%d", get_field(dtmcontrol, DTM_DTMCS_DMIRESET));
|
|
LOG_DEBUG(" idle=%d", get_field(dtmcontrol, DTM_DTMCS_IDLE));
|
|
LOG_DEBUG(" dmistat=%d", get_field(dtmcontrol, DTM_DTMCS_DMISTAT));
|
|
LOG_DEBUG(" abits=%d", get_field(dtmcontrol, DTM_DTMCS_ABITS));
|
|
LOG_DEBUG(" version=%d", get_field(dtmcontrol, DTM_DTMCS_VERSION));
|
|
if (dtmcontrol == 0) {
|
|
LOG_ERROR("dtmcontrol is 0. Check JTAG connectivity/board power.");
|
|
return ERROR_FAIL;
|
|
}
|
|
if (get_field(dtmcontrol, DTM_DTMCS_VERSION) != 1) {
|
|
LOG_ERROR("Unsupported DTM version %d. (dtmcontrol=0x%x)",
|
|
get_field(dtmcontrol, DTM_DTMCS_VERSION), dtmcontrol);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
riscv013_info_t *info = get_info(target);
|
|
info->abits = get_field(dtmcontrol, DTM_DTMCS_ABITS);
|
|
info->dtmcontrol_idle = get_field(dtmcontrol, DTM_DTMCS_IDLE);
|
|
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(dmstatus, DMI_DMSTATUS_VERSION) != 2) {
|
|
LOG_ERROR("OpenOCD only supports Debug Module version 2, not %d "
|
|
"(dmstatus=0x%x)", get_field(dmstatus, DMI_DMSTATUS_VERSION), dmstatus);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
/* Reset the Debug Module. */
|
|
dmi_write(target, DMI_DMCONTROL, 0);
|
|
dmi_write(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE);
|
|
|
|
uint32_t max_hartsel_mask = ((1L<<10)-1) << DMI_DMCONTROL_HARTSEL_OFFSET;
|
|
dmi_write(target, DMI_DMCONTROL, max_hartsel_mask | DMI_DMCONTROL_DMACTIVE);
|
|
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
|
|
if (!get_field(dmcontrol, DMI_DMCONTROL_DMACTIVE)) {
|
|
LOG_ERROR("Debug Module did not become active. dmcontrol=0x%x",
|
|
dmcontrol);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
uint32_t hartsel = get_field(dmcontrol, max_hartsel_mask);
|
|
info->hartsellen = 0;
|
|
while (hartsel & 1) {
|
|
info->hartsellen++;
|
|
hartsel >>= 1;
|
|
}
|
|
LOG_DEBUG("hartsellen=%d", info->hartsellen);
|
|
|
|
uint32_t hartinfo = dmi_read(target, DMI_HARTINFO);
|
|
|
|
info->datasize = get_field(hartinfo, DMI_HARTINFO_DATASIZE);
|
|
info->dataaccess = get_field(hartinfo, DMI_HARTINFO_DATAACCESS);
|
|
info->dataaddr = get_field(hartinfo, DMI_HARTINFO_DATAADDR);
|
|
|
|
if (!get_field(dmstatus, DMI_DMSTATUS_AUTHENTICATED)) {
|
|
LOG_ERROR("Authentication required by RISC-V core but not "
|
|
"supported by OpenOCD. dmcontrol=0x%x", dmcontrol);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
/* Check that abstract data registers are accessible. */
|
|
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
info->datacount = get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT);
|
|
info->progbufsize = get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE);
|
|
|
|
RISCV_INFO(r);
|
|
r->impebreak = get_field(dmstatus, DMI_DMSTATUS_IMPEBREAK);
|
|
|
|
/* Before doing anything else we must first enumerate the harts. */
|
|
|
|
/* Don't call any riscv_* functions until after we've counted the number of
|
|
* cores and initialized registers. */
|
|
for (int i = 0; i < MIN(RISCV_MAX_HARTS, 1 << info->hartsellen); ++i) {
|
|
if (!riscv_rtos_enabled(target) && i != target->coreid)
|
|
continue;
|
|
|
|
r->current_hartid = i;
|
|
riscv013_select_current_hart(target);
|
|
|
|
uint32_t s = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(s, DMI_DMSTATUS_ANYNONEXISTENT))
|
|
break;
|
|
r->hart_count = i + 1;
|
|
|
|
if (!riscv_is_halted(target)) {
|
|
if (riscv013_halt_current_hart(target) != ERROR_OK) {
|
|
LOG_ERROR("Fatal: Hart %d failed to halt during examine()", i);
|
|
return ERROR_FAIL;
|
|
}
|
|
}
|
|
|
|
/* Without knowing anything else we can at least mess with the
|
|
* program buffer. */
|
|
r->debug_buffer_size[i] = info->progbufsize;
|
|
|
|
int result = register_read_abstract(target, NULL, GDB_REGNO_S0, 64);
|
|
if (result == ERROR_OK)
|
|
r->xlen[i] = 64;
|
|
else
|
|
r->xlen[i] = 32;
|
|
|
|
if (register_read_direct(target, &r->misa, GDB_REGNO_MISA)) {
|
|
LOG_ERROR("Fatal: Failed to read MISA from hart %d.", i);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
/* Now init registers based on what we discovered. */
|
|
if (riscv_init_registers(target) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
/* Display this as early as possible to help people who are using
|
|
* really slow simulators. */
|
|
LOG_DEBUG(" hart %d: XLEN=%d, misa=0x%" PRIx64, i, r->xlen[i],
|
|
r->misa);
|
|
}
|
|
|
|
LOG_DEBUG("Enumerated %d harts", r->hart_count);
|
|
|
|
if (r->hart_count == 0) {
|
|
LOG_ERROR("No harts found!");
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
/* Then we check the number of triggers availiable to each hart. */
|
|
riscv_enumerate_triggers(target);
|
|
|
|
/* Resumes all the harts, so the debugger can later pause them. */
|
|
/* TODO: Only do this if the harts were halted to start with. */
|
|
riscv_resume_all_harts(target);
|
|
target->state = TARGET_RUNNING;
|
|
|
|
target_set_examined(target);
|
|
|
|
if (target->rtos)
|
|
riscv_update_threads(target->rtos);
|
|
|
|
/* Some regression suites rely on seeing 'Examined RISC-V core' to know
|
|
* when they can connect with gdb/telnet.
|
|
* We will need to update those suites if we want to change that text. */
|
|
LOG_INFO("Examined RISC-V core; found %d harts",
|
|
riscv_count_harts(target));
|
|
for (int i = 0; i < riscv_count_harts(target); ++i) {
|
|
if (riscv_hart_enabled(target, i)) {
|
|
LOG_INFO(" hart %d: XLEN=%d, %d triggers", i, r->xlen[i],
|
|
r->trigger_count[i]);
|
|
} else {
|
|
LOG_INFO(" hart %d: currently disabled", i);
|
|
}
|
|
}
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int assert_reset(struct target *target)
|
|
{
|
|
RISCV_INFO(r);
|
|
|
|
select_dmi(target);
|
|
|
|
uint32_t control_base = set_field(0, DMI_DMCONTROL_DMACTIVE, 1);
|
|
|
|
if (target->rtos) {
|
|
/* There's only one target, and OpenOCD thinks each hart is a thread.
|
|
* We must reset them all. */
|
|
|
|
/* TODO: Try to use hasel in dmcontrol */
|
|
|
|
/* Set haltreq/resumereq for each hart. */
|
|
uint32_t control = control_base;
|
|
for (int i = 0; i < riscv_count_harts(target); ++i) {
|
|
if (!riscv_hart_enabled(target, i))
|
|
continue;
|
|
|
|
control = set_field(control_base, hartsel_mask(target), i);
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ,
|
|
target->reset_halt ? 1 : 0);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
}
|
|
/* Assert ndmreset */
|
|
control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
|
|
} else {
|
|
/* Reset just this hart. */
|
|
uint32_t control = set_field(control_base, hartsel_mask(target),
|
|
r->current_hartid);
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ,
|
|
target->reset_halt ? 1 : 0);
|
|
control = set_field(control, DMI_DMCONTROL_HARTRESET, 1);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
|
|
/* Read back to check if hartreset is supported. */
|
|
uint32_t rb = dmi_read(target, DMI_DMCONTROL);
|
|
if (!get_field(rb, DMI_DMCONTROL_HARTRESET)) {
|
|
/* Use ndmreset instead. That will reset the entire device, but
|
|
* that's probably what OpenOCD wants anyway. */
|
|
control = set_field(control, DMI_DMCONTROL_HARTRESET, 0);
|
|
control = set_field(control, DMI_DMCONTROL_NDMRESET, 1);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
}
|
|
}
|
|
|
|
target->state = TARGET_RESET;
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int deassert_reset(struct target *target)
|
|
{
|
|
RISCV_INFO(r);
|
|
RISCV013_INFO(info);
|
|
select_dmi(target);
|
|
|
|
LOG_DEBUG("%d", r->current_hartid);
|
|
|
|
/* Clear the reset, but make sure haltreq is still set */
|
|
uint32_t control = 0;
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ, target->reset_halt ? 1 : 0);
|
|
control = set_field(control, hartsel_mask(target), r->current_hartid);
|
|
control = set_field(control, DMI_DMCONTROL_DMACTIVE, 1);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
|
|
uint32_t dmstatus;
|
|
int dmi_busy_delay = info->dmi_busy_delay;
|
|
time_t start = time(NULL);
|
|
|
|
if (target->reset_halt) {
|
|
LOG_DEBUG("Waiting for hart to be halted.");
|
|
do {
|
|
dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
if (time(NULL) - start > riscv_reset_timeout_sec) {
|
|
LOG_ERROR("Hart didn't halt coming out of reset in %ds; "
|
|
"dmstatus=0x%x; "
|
|
"Increase the timeout with riscv set_reset_timeout_sec.",
|
|
riscv_reset_timeout_sec, dmstatus);
|
|
return ERROR_FAIL;
|
|
}
|
|
target->state = TARGET_HALTED;
|
|
} while (get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0);
|
|
|
|
control = set_field(control, DMI_DMCONTROL_HALTREQ, 0);
|
|
dmi_write(target, DMI_DMCONTROL, control);
|
|
|
|
} else {
|
|
LOG_DEBUG("Waiting for hart to be running.");
|
|
do {
|
|
dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ANYHALTED) ||
|
|
get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL)) {
|
|
LOG_ERROR("Unexpected hart status during reset. dmstatus=0x%x",
|
|
dmstatus);
|
|
return ERROR_FAIL;
|
|
}
|
|
if (time(NULL) - start > riscv_reset_timeout_sec) {
|
|
LOG_ERROR("Hart didn't run coming out of reset in %ds; "
|
|
"dmstatus=0x%x; "
|
|
"Increase the timeout with riscv set_reset_timeout_sec.",
|
|
riscv_reset_timeout_sec, dmstatus);
|
|
return ERROR_FAIL;
|
|
}
|
|
} while (get_field(dmstatus, DMI_DMSTATUS_ALLRUNNING) == 0);
|
|
target->state = TARGET_RUNNING;
|
|
}
|
|
info->dmi_busy_delay = dmi_busy_delay;
|
|
return ERROR_OK;
|
|
}
|
|
|
|
/**
|
|
* @size in bytes
|
|
*/
|
|
static void write_to_buf(uint8_t *buffer, uint64_t value, unsigned size)
|
|
{
|
|
switch (size) {
|
|
case 8:
|
|
buffer[7] = value >> 56;
|
|
buffer[6] = value >> 48;
|
|
buffer[5] = value >> 40;
|
|
buffer[4] = value >> 32;
|
|
case 4:
|
|
buffer[3] = value >> 24;
|
|
buffer[2] = value >> 16;
|
|
case 2:
|
|
buffer[1] = value >> 8;
|
|
case 1:
|
|
buffer[0] = value;
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
static int execute_fence(struct target *target)
|
|
{
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_program_fence(&program);
|
|
int result = riscv_program_exec(&program, target);
|
|
if (result != ERROR_OK)
|
|
LOG_ERROR("Unable to execute fence");
|
|
return result;
|
|
}
|
|
|
|
static void log_memory_access(target_addr_t address, uint64_t value,
|
|
unsigned size_bytes, bool read)
|
|
{
|
|
if (debug_level < LOG_LVL_DEBUG)
|
|
return;
|
|
|
|
char fmt[80];
|
|
sprintf(fmt, "M[0x%" TARGET_PRIxADDR "] %ss 0x%%0%d" PRIx64,
|
|
address, read ? "read" : "write", size_bytes * 2);
|
|
value &= (((uint64_t) 0x1) << (size_bytes * 8)) - 1;
|
|
LOG_DEBUG(fmt, value);
|
|
}
|
|
|
|
/**
|
|
* Read the requested memory, taking care to execute every read exactly once,
|
|
* even if cmderr=busy is encountered.
|
|
*/
|
|
static int read_memory(struct target *target, target_addr_t address,
|
|
uint32_t size, uint32_t count, uint8_t *buffer)
|
|
{
|
|
RISCV013_INFO(info);
|
|
|
|
int result = ERROR_OK;
|
|
|
|
LOG_DEBUG("reading %d words of %d bytes from 0x%" TARGET_PRIxADDR, count,
|
|
size, address);
|
|
|
|
select_dmi(target);
|
|
|
|
/* s0 holds the next address to write to
|
|
* s1 holds the next data value to write
|
|
*/
|
|
uint64_t s0, s1;
|
|
if (register_read_direct(target, &s0, GDB_REGNO_S0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
if (register_read_direct(target, &s1, GDB_REGNO_S1) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (execute_fence(target) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
/* Write the program (load, increment) */
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
switch (size) {
|
|
case 1:
|
|
riscv_program_lbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
case 2:
|
|
riscv_program_lhr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
case 4:
|
|
riscv_program_lwr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
default:
|
|
LOG_ERROR("Unsupported size: %d", size);
|
|
return ERROR_FAIL;
|
|
}
|
|
riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
|
|
|
|
if (riscv_program_ebreak(&program) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
riscv_program_write(&program);
|
|
|
|
/* Write address to S0, and execute buffer. */
|
|
result = register_write_direct(target, GDB_REGNO_S0, address);
|
|
if (result != ERROR_OK)
|
|
goto error;
|
|
uint32_t command = access_register_command(GDB_REGNO_S1, riscv_xlen(target),
|
|
AC_ACCESS_REGISTER_TRANSFER |
|
|
AC_ACCESS_REGISTER_POSTEXEC);
|
|
result = execute_abstract_command(target, command);
|
|
if (result != ERROR_OK)
|
|
goto error;
|
|
|
|
/* First read has just triggered. Result is in s1. */
|
|
|
|
dmi_write(target, DMI_ABSTRACTAUTO,
|
|
1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);
|
|
|
|
/* read_addr is the next address that the hart will read from, which is the
|
|
* value in s0. */
|
|
riscv_addr_t read_addr = address + size;
|
|
/* The next address that we need to receive data for. */
|
|
riscv_addr_t receive_addr = address;
|
|
riscv_addr_t fin_addr = address + (count * size);
|
|
unsigned skip = 1;
|
|
while (read_addr < fin_addr) {
|
|
LOG_DEBUG("read_addr=0x%" PRIx64 ", receive_addr=0x%" PRIx64
|
|
", fin_addr=0x%" PRIx64, read_addr, receive_addr, fin_addr);
|
|
/* The pipeline looks like this:
|
|
* memory -> s1 -> dm_data0 -> debugger
|
|
* It advances every time the debugger reads dmdata0.
|
|
* So at any time the debugger has just read mem[s0 - 3*size],
|
|
* dm_data0 contains mem[s0 - 2*size]
|
|
* s1 contains mem[s0-size] */
|
|
|
|
LOG_DEBUG("creating burst to read from 0x%" TARGET_PRIxADDR
|
|
" up to 0x%" TARGET_PRIxADDR, read_addr, fin_addr);
|
|
assert(read_addr >= address && read_addr < fin_addr);
|
|
struct riscv_batch *batch = riscv_batch_alloc(target, 32,
|
|
info->dmi_busy_delay + info->ac_busy_delay);
|
|
|
|
size_t reads = 0;
|
|
for (riscv_addr_t addr = read_addr; addr < fin_addr; addr += size) {
|
|
riscv_batch_add_dmi_read(batch, DMI_DATA0);
|
|
|
|
reads++;
|
|
if (riscv_batch_full(batch))
|
|
break;
|
|
}
|
|
|
|
riscv_batch_run(batch);
|
|
|
|
/* Wait for the target to finish performing the last abstract command,
|
|
* and update our copy of cmderr. */
|
|
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
|
|
abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);
|
|
|
|
unsigned cmderr = info->cmderr;
|
|
riscv_addr_t next_read_addr;
|
|
uint32_t dmi_data0 = -1;
|
|
switch (info->cmderr) {
|
|
case CMDERR_NONE:
|
|
LOG_DEBUG("successful (partial?) memory read");
|
|
next_read_addr = read_addr + reads * size;
|
|
break;
|
|
case CMDERR_BUSY:
|
|
LOG_DEBUG("memory read resulted in busy response");
|
|
|
|
/*
|
|
* If you want to exercise this code path, apply the following patch to spike:
|
|
--- a/riscv/debug_module.cc
|
|
+++ b/riscv/debug_module.cc
|
|
@@ -1,3 +1,5 @@
|
|
+#include <unistd.h>
|
|
+
|
|
#include <cassert>
|
|
|
|
#include "debug_module.h"
|
|
@@ -398,6 +400,15 @@ bool debug_module_t::perform_abstract_command()
|
|
// Since the next instruction is what we will use, just use nother NOP
|
|
// to get there.
|
|
write32(debug_abstract, 1, addi(ZERO, ZERO, 0));
|
|
+
|
|
+ if (abstractauto.autoexecdata &&
|
|
+ program_buffer[0] == 0x83 &&
|
|
+ program_buffer[1] == 0x24 &&
|
|
+ program_buffer[2] == 0x04 &&
|
|
+ program_buffer[3] == 0 &&
|
|
+ rand() < RAND_MAX / 10) {
|
|
+ usleep(1000000);
|
|
+ }
|
|
} else {
|
|
write32(debug_abstract, 1, ebreak());
|
|
}
|
|
*/
|
|
increase_ac_busy_delay(target);
|
|
riscv013_clear_abstract_error(target);
|
|
|
|
dmi_write(target, DMI_ABSTRACTAUTO, 0);
|
|
|
|
/* This is definitely a good version of the value that we
|
|
* attempted to read when we discovered that the target was
|
|
* busy. */
|
|
dmi_data0 = dmi_read(target, DMI_DATA0);
|
|
|
|
/* Clobbers DMI_DATA0. */
|
|
result = register_read_direct(target, &next_read_addr,
|
|
GDB_REGNO_S0);
|
|
if (result != ERROR_OK) {
|
|
riscv_batch_free(batch);
|
|
goto error;
|
|
}
|
|
/* Restore the command, and execute it.
|
|
* Now DMI_DATA0 contains the next value just as it would if no
|
|
* error had occurred. */
|
|
dmi_write(target, DMI_COMMAND, command);
|
|
|
|
dmi_write(target, DMI_ABSTRACTAUTO,
|
|
1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);
|
|
break;
|
|
default:
|
|
LOG_ERROR("error when reading memory, abstractcs=0x%08lx", (long)abstractcs);
|
|
riscv013_clear_abstract_error(target);
|
|
riscv_batch_free(batch);
|
|
result = ERROR_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
/* Now read whatever we got out of the batch. */
|
|
for (size_t i = 0; i < reads; i++) {
|
|
if (read_addr >= next_read_addr)
|
|
break;
|
|
|
|
read_addr += size;
|
|
|
|
if (skip > 0) {
|
|
skip--;
|
|
continue;
|
|
}
|
|
|
|
riscv_addr_t offset = receive_addr - address;
|
|
uint64_t dmi_out = riscv_batch_get_dmi_read(batch, i);
|
|
uint32_t value = get_field(dmi_out, DTM_DMI_DATA);
|
|
write_to_buf(buffer + offset, value, size);
|
|
log_memory_access(receive_addr, value, size, true);
|
|
|
|
receive_addr += size;
|
|
}
|
|
riscv_batch_free(batch);
|
|
|
|
if (cmderr == CMDERR_BUSY) {
|
|
riscv_addr_t offset = receive_addr - address;
|
|
write_to_buf(buffer + offset, dmi_data0, size);
|
|
log_memory_access(receive_addr, dmi_data0, size, true);
|
|
read_addr += size;
|
|
receive_addr += size;
|
|
}
|
|
}
|
|
|
|
dmi_write(target, DMI_ABSTRACTAUTO, 0);
|
|
|
|
if (count > 1) {
|
|
/* Read the penultimate word. */
|
|
uint64_t value = dmi_read(target, DMI_DATA0);
|
|
write_to_buf(buffer + receive_addr - address, value, size);
|
|
log_memory_access(receive_addr, value, size, true);
|
|
receive_addr += size;
|
|
}
|
|
|
|
/* Read the last word. */
|
|
uint64_t value;
|
|
result = register_read_direct(target, &value, GDB_REGNO_S1);
|
|
if (result != ERROR_OK)
|
|
goto error;
|
|
write_to_buf(buffer + receive_addr - address, value, size);
|
|
log_memory_access(receive_addr, value, size, true);
|
|
|
|
riscv_set_register(target, GDB_REGNO_S0, s0);
|
|
riscv_set_register(target, GDB_REGNO_S1, s1);
|
|
return ERROR_OK;
|
|
|
|
error:
|
|
dmi_write(target, DMI_ABSTRACTAUTO, 0);
|
|
|
|
riscv_set_register(target, GDB_REGNO_S0, s0);
|
|
riscv_set_register(target, GDB_REGNO_S1, s1);
|
|
return result;
|
|
}
|
|
|
|
static int write_memory(struct target *target, target_addr_t address,
|
|
uint32_t size, uint32_t count, const uint8_t *buffer)
|
|
{
|
|
RISCV013_INFO(info);
|
|
|
|
LOG_DEBUG("writing %d words of %d bytes to 0x%08lx", count, size, (long)address);
|
|
|
|
select_dmi(target);
|
|
|
|
/* s0 holds the next address to write to
|
|
* s1 holds the next data value to write
|
|
*/
|
|
|
|
int result = ERROR_OK;
|
|
uint64_t s0, s1;
|
|
if (register_read_direct(target, &s0, GDB_REGNO_S0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
if (register_read_direct(target, &s1, GDB_REGNO_S1) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
/* Write the program (store, increment) */
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
|
|
switch (size) {
|
|
case 1:
|
|
riscv_program_sbr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
case 2:
|
|
riscv_program_shr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
case 4:
|
|
riscv_program_swr(&program, GDB_REGNO_S1, GDB_REGNO_S0, 0);
|
|
break;
|
|
default:
|
|
LOG_ERROR("Unsupported size: %d", size);
|
|
result = ERROR_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, size);
|
|
|
|
result = riscv_program_ebreak(&program);
|
|
if (result != ERROR_OK)
|
|
goto error;
|
|
riscv_program_write(&program);
|
|
|
|
riscv_addr_t cur_addr = address;
|
|
riscv_addr_t fin_addr = address + (count * size);
|
|
bool setup_needed = true;
|
|
LOG_DEBUG("writing until final address 0x%016" PRIx64, fin_addr);
|
|
while (cur_addr < fin_addr) {
|
|
LOG_DEBUG("transferring burst starting at address 0x%016" PRIx64,
|
|
cur_addr);
|
|
|
|
struct riscv_batch *batch = riscv_batch_alloc(
|
|
target,
|
|
32,
|
|
info->dmi_busy_delay + info->ac_busy_delay);
|
|
|
|
/* To write another word, we put it in S1 and execute the program. */
|
|
unsigned start = (cur_addr - address) / size;
|
|
for (unsigned i = start; i < count; ++i) {
|
|
unsigned offset = size*i;
|
|
const uint8_t *t_buffer = buffer + offset;
|
|
|
|
uint32_t value;
|
|
switch (size) {
|
|
case 1:
|
|
value = t_buffer[0];
|
|
break;
|
|
case 2:
|
|
value = t_buffer[0]
|
|
| ((uint32_t) t_buffer[1] << 8);
|
|
break;
|
|
case 4:
|
|
value = t_buffer[0]
|
|
| ((uint32_t) t_buffer[1] << 8)
|
|
| ((uint32_t) t_buffer[2] << 16)
|
|
| ((uint32_t) t_buffer[3] << 24);
|
|
break;
|
|
default:
|
|
LOG_ERROR("unsupported access size: %d", size);
|
|
riscv_batch_free(batch);
|
|
result = ERROR_FAIL;
|
|
goto error;
|
|
}
|
|
|
|
log_memory_access(address + offset, value, size, false);
|
|
cur_addr += size;
|
|
|
|
if (setup_needed) {
|
|
result = register_write_direct(target, GDB_REGNO_S0,
|
|
address + offset);
|
|
if (result != ERROR_OK) {
|
|
riscv_batch_free(batch);
|
|
goto error;
|
|
}
|
|
|
|
/* Write value. */
|
|
dmi_write(target, DMI_DATA0, value);
|
|
|
|
/* Write and execute command that moves value into S1 and
|
|
* executes program buffer. */
|
|
uint32_t command = access_register_command(GDB_REGNO_S1, 32,
|
|
AC_ACCESS_REGISTER_POSTEXEC |
|
|
AC_ACCESS_REGISTER_TRANSFER |
|
|
AC_ACCESS_REGISTER_WRITE);
|
|
result = execute_abstract_command(target, command);
|
|
if (result != ERROR_OK) {
|
|
riscv_batch_free(batch);
|
|
goto error;
|
|
}
|
|
|
|
/* Turn on autoexec */
|
|
dmi_write(target, DMI_ABSTRACTAUTO,
|
|
1 << DMI_ABSTRACTAUTO_AUTOEXECDATA_OFFSET);
|
|
|
|
setup_needed = false;
|
|
} else {
|
|
riscv_batch_add_dmi_write(batch, DMI_DATA0, value);
|
|
if (riscv_batch_full(batch))
|
|
break;
|
|
}
|
|
}
|
|
|
|
result = riscv_batch_run(batch);
|
|
riscv_batch_free(batch);
|
|
if (result != ERROR_OK)
|
|
goto error;
|
|
|
|
/* Note that if the scan resulted in a Busy DMI response, it
|
|
* is this read to abstractcs that will cause the dmi_busy_delay
|
|
* to be incremented if necessary. */
|
|
|
|
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY))
|
|
abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
info->cmderr = get_field(abstractcs, DMI_ABSTRACTCS_CMDERR);
|
|
switch (info->cmderr) {
|
|
case CMDERR_NONE:
|
|
LOG_DEBUG("successful (partial?) memory write");
|
|
break;
|
|
case CMDERR_BUSY:
|
|
LOG_DEBUG("memory write resulted in busy response");
|
|
riscv013_clear_abstract_error(target);
|
|
increase_ac_busy_delay(target);
|
|
|
|
dmi_write(target, DMI_ABSTRACTAUTO, 0);
|
|
result = register_read_direct(target, &cur_addr, GDB_REGNO_S0);
|
|
if (result != ERROR_OK)
|
|
goto error;
|
|
setup_needed = true;
|
|
break;
|
|
|
|
default:
|
|
LOG_ERROR("error when writing memory, abstractcs=0x%08lx", (long)abstractcs);
|
|
riscv013_clear_abstract_error(target);
|
|
result = ERROR_FAIL;
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
error:
|
|
dmi_write(target, DMI_ABSTRACTAUTO, 0);
|
|
|
|
if (register_write_direct(target, GDB_REGNO_S1, s1) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
if (register_write_direct(target, GDB_REGNO_S0, s0) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
if (execute_fence(target) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
return result;
|
|
}
|
|
|
|
static int arch_state(struct target *target)
|
|
{
|
|
return ERROR_OK;
|
|
}
|
|
|
|
struct target_type riscv013_target = {
|
|
.name = "riscv",
|
|
|
|
.init_target = init_target,
|
|
.deinit_target = deinit_target,
|
|
.examine = examine,
|
|
|
|
.poll = &riscv_openocd_poll,
|
|
.halt = &riscv_openocd_halt,
|
|
.resume = &riscv_openocd_resume,
|
|
.step = &riscv_openocd_step,
|
|
|
|
.assert_reset = assert_reset,
|
|
.deassert_reset = deassert_reset,
|
|
|
|
.read_memory = read_memory,
|
|
.write_memory = write_memory,
|
|
|
|
.arch_state = arch_state,
|
|
};
|
|
|
|
/*** 0.13-specific implementations of various RISC-V helper functions. ***/
|
|
static int riscv013_get_register(struct target *target,
|
|
riscv_reg_t *value, int hid, int rid)
|
|
{
|
|
LOG_DEBUG("reading register %s on hart %d", gdb_regno_name(rid), hid);
|
|
|
|
riscv_set_current_hartid(target, hid);
|
|
|
|
int result = ERROR_OK;
|
|
if (rid <= GDB_REGNO_XPR31) {
|
|
result = register_read_direct(target, value, rid);
|
|
} else if (rid == GDB_REGNO_PC) {
|
|
result = register_read_direct(target, value, GDB_REGNO_DPC);
|
|
LOG_DEBUG("read PC from DPC: 0x%016" PRIx64, *value);
|
|
} else if (rid == GDB_REGNO_PRIV) {
|
|
uint64_t dcsr;
|
|
result = register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
|
|
buf_set_u64((unsigned char *)value, 0, 8, get_field(dcsr, CSR_DCSR_PRV));
|
|
} else {
|
|
result = register_read_direct(target, value, rid);
|
|
if (result != ERROR_OK) {
|
|
LOG_ERROR("Unable to read %s", gdb_regno_name(rid));
|
|
*value = -1;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static int riscv013_set_register(struct target *target, int hid, int rid, uint64_t value)
|
|
{
|
|
LOG_DEBUG("writing 0x%" PRIx64 " to register %s on hart %d", value,
|
|
gdb_regno_name(rid), hid);
|
|
|
|
riscv_set_current_hartid(target, hid);
|
|
|
|
if (rid <= GDB_REGNO_XPR31) {
|
|
return register_write_direct(target, rid, value);
|
|
} else if (rid == GDB_REGNO_PC) {
|
|
LOG_DEBUG("writing PC to DPC: 0x%016" PRIx64, value);
|
|
register_write_direct(target, GDB_REGNO_DPC, value);
|
|
uint64_t actual_value;
|
|
register_read_direct(target, &actual_value, GDB_REGNO_DPC);
|
|
LOG_DEBUG(" actual DPC written: 0x%016" PRIx64, actual_value);
|
|
if (value != actual_value) {
|
|
LOG_ERROR("Written PC (0x%" PRIx64 ") does not match read back "
|
|
"value (0x%" PRIx64 ")", value, actual_value);
|
|
return ERROR_FAIL;
|
|
}
|
|
} else if (rid == GDB_REGNO_PRIV) {
|
|
uint64_t dcsr;
|
|
register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
|
|
dcsr = set_field(dcsr, CSR_DCSR_PRV, value);
|
|
return register_write_direct(target, GDB_REGNO_DCSR, dcsr);
|
|
} else {
|
|
return register_write_direct(target, rid, value);
|
|
}
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static void riscv013_select_current_hart(struct target *target)
|
|
{
|
|
RISCV_INFO(r);
|
|
|
|
uint64_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
dmcontrol = set_field(dmcontrol, hartsel_mask(target), r->current_hartid);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
}
|
|
|
|
static int riscv013_halt_current_hart(struct target *target)
|
|
{
|
|
RISCV_INFO(r);
|
|
LOG_DEBUG("halting hart %d", r->current_hartid);
|
|
if (riscv_is_halted(target))
|
|
LOG_ERROR("Hart %d is already halted!", r->current_hartid);
|
|
|
|
/* Issue the halt command, and then wait for the current hart to halt. */
|
|
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 1);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
for (size_t i = 0; i < 256; ++i)
|
|
if (riscv_is_halted(target))
|
|
break;
|
|
|
|
if (!riscv_is_halted(target)) {
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
|
|
LOG_ERROR("unable to halt hart %d", r->current_hartid);
|
|
LOG_ERROR(" dmcontrol=0x%08x", dmcontrol);
|
|
LOG_ERROR(" dmstatus =0x%08x", dmstatus);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_HALTREQ, 0);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
|
|
return ERROR_OK;
|
|
}
|
|
|
|
static int riscv013_resume_current_hart(struct target *target)
|
|
{
|
|
return riscv013_step_or_resume_current_hart(target, false);
|
|
}
|
|
|
|
static int riscv013_step_current_hart(struct target *target)
|
|
{
|
|
return riscv013_step_or_resume_current_hart(target, true);
|
|
}
|
|
|
|
static int riscv013_on_resume(struct target *target)
|
|
{
|
|
return riscv013_on_step_or_resume(target, false);
|
|
}
|
|
|
|
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 bool riscv013_is_halted(struct target *target)
|
|
{
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ANYUNAVAIL))
|
|
LOG_ERROR("hart %d is unavailiable", riscv_current_hartid(target));
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ANYNONEXISTENT))
|
|
LOG_ERROR("hart %d doesn't exist", riscv_current_hartid(target));
|
|
return get_field(dmstatus, DMI_DMSTATUS_ALLHALTED);
|
|
}
|
|
|
|
static enum riscv_halt_reason riscv013_halt_reason(struct target *target)
|
|
{
|
|
riscv_reg_t dcsr;
|
|
int result = register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
|
|
if (result != ERROR_OK)
|
|
return RISCV_HALT_UNKNOWN;
|
|
|
|
switch (get_field(dcsr, CSR_DCSR_CAUSE)) {
|
|
case CSR_DCSR_CAUSE_SWBP:
|
|
case CSR_DCSR_CAUSE_TRIGGER:
|
|
return RISCV_HALT_BREAKPOINT;
|
|
case CSR_DCSR_CAUSE_STEP:
|
|
return RISCV_HALT_SINGLESTEP;
|
|
case CSR_DCSR_CAUSE_DEBUGINT:
|
|
case CSR_DCSR_CAUSE_HALT:
|
|
return RISCV_HALT_INTERRUPT;
|
|
}
|
|
|
|
LOG_ERROR("Unknown DCSR cause field: %x", (int)get_field(dcsr, CSR_DCSR_CAUSE));
|
|
LOG_ERROR(" dcsr=0x%016lx", (long)dcsr);
|
|
return RISCV_HALT_UNKNOWN;
|
|
}
|
|
|
|
int riscv013_write_debug_buffer(struct target *target, unsigned index, riscv_insn_t data)
|
|
{
|
|
return dmi_write(target, DMI_PROGBUF0 + index, data);
|
|
}
|
|
|
|
riscv_insn_t riscv013_read_debug_buffer(struct target *target, unsigned index)
|
|
{
|
|
return dmi_read(target, DMI_PROGBUF0 + index);
|
|
}
|
|
|
|
int riscv013_execute_debug_buffer(struct target *target)
|
|
{
|
|
uint32_t run_program = 0;
|
|
run_program = set_field(run_program, AC_ACCESS_REGISTER_SIZE, 2);
|
|
run_program = set_field(run_program, AC_ACCESS_REGISTER_POSTEXEC, 1);
|
|
run_program = set_field(run_program, AC_ACCESS_REGISTER_TRANSFER, 0);
|
|
run_program = set_field(run_program, AC_ACCESS_REGISTER_REGNO, 0x1000);
|
|
|
|
return execute_abstract_command(target, run_program);
|
|
}
|
|
|
|
void riscv013_fill_dmi_write_u64(struct target *target, char *buf, int a, uint64_t d)
|
|
{
|
|
RISCV013_INFO(info);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, DMI_OP_WRITE);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, d);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, a);
|
|
}
|
|
|
|
void riscv013_fill_dmi_read_u64(struct target *target, char *buf, int a)
|
|
{
|
|
RISCV013_INFO(info);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, DMI_OP_READ);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, 0);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, a);
|
|
}
|
|
|
|
void riscv013_fill_dmi_nop_u64(struct target *target, char *buf)
|
|
{
|
|
RISCV013_INFO(info);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_OP_OFFSET, DTM_DMI_OP_LENGTH, DMI_OP_NOP);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_DATA_OFFSET, DTM_DMI_DATA_LENGTH, 0);
|
|
buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, 0);
|
|
}
|
|
|
|
int riscv013_dmi_write_u64_bits(struct target *target)
|
|
{
|
|
RISCV013_INFO(info);
|
|
return info->abits + DTM_DMI_DATA_LENGTH + DTM_DMI_OP_LENGTH;
|
|
}
|
|
|
|
/* Helper Functions. */
|
|
static int riscv013_on_step_or_resume(struct target *target, bool step)
|
|
{
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_program_fence_i(&program);
|
|
if (riscv_program_exec(&program, target) != ERROR_OK)
|
|
LOG_ERROR("Unable to execute fence.i");
|
|
|
|
/* We want to twiddle some bits in the debug CSR so debugging works. */
|
|
riscv_reg_t dcsr;
|
|
int result = register_read_direct(target, &dcsr, GDB_REGNO_DCSR);
|
|
if (result != ERROR_OK)
|
|
return result;
|
|
dcsr = set_field(dcsr, CSR_DCSR_STEP, step);
|
|
dcsr = set_field(dcsr, CSR_DCSR_EBREAKM, 1);
|
|
dcsr = set_field(dcsr, CSR_DCSR_EBREAKS, 1);
|
|
dcsr = set_field(dcsr, CSR_DCSR_EBREAKU, 1);
|
|
return riscv_set_register(target, GDB_REGNO_DCSR, dcsr);
|
|
}
|
|
|
|
static int riscv013_step_or_resume_current_hart(struct target *target, bool step)
|
|
{
|
|
RISCV_INFO(r);
|
|
LOG_DEBUG("resuming hart %d (for step?=%d)", r->current_hartid, step);
|
|
if (!riscv_is_halted(target)) {
|
|
LOG_ERROR("Hart %d is not halted!", r->current_hartid);
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
struct riscv_program program;
|
|
riscv_program_init(&program, target);
|
|
riscv_program_fence_i(&program);
|
|
if (riscv_program_exec(&program, target) != ERROR_OK)
|
|
return ERROR_FAIL;
|
|
|
|
/* Issue the resume command, and then wait for the current hart to resume. */
|
|
uint32_t dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 1);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
|
|
for (size_t i = 0; i < 256; ++i) {
|
|
usleep(10);
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
if (get_field(dmstatus, DMI_DMSTATUS_ALLRESUMEACK) == 0)
|
|
continue;
|
|
if (step && get_field(dmstatus, DMI_DMSTATUS_ALLHALTED) == 0)
|
|
continue;
|
|
|
|
dmcontrol = set_field(dmcontrol, DMI_DMCONTROL_RESUMEREQ, 0);
|
|
dmi_write(target, DMI_DMCONTROL, dmcontrol);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
uint32_t dmstatus = dmi_read(target, DMI_DMSTATUS);
|
|
dmcontrol = dmi_read(target, DMI_DMCONTROL);
|
|
LOG_ERROR("unable to resume hart %d", r->current_hartid);
|
|
LOG_ERROR(" dmcontrol=0x%08x", dmcontrol);
|
|
LOG_ERROR(" dmstatus =0x%08x", dmstatus);
|
|
|
|
if (step) {
|
|
LOG_ERROR(" was stepping, halting");
|
|
riscv013_halt_current_hart(target);
|
|
return ERROR_OK;
|
|
}
|
|
|
|
return ERROR_FAIL;
|
|
}
|
|
|
|
void riscv013_clear_abstract_error(struct target *target)
|
|
{
|
|
/* Wait for busy to go away. */
|
|
time_t start = time(NULL);
|
|
uint32_t abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
while (get_field(abstractcs, DMI_ABSTRACTCS_BUSY)) {
|
|
abstractcs = dmi_read(target, DMI_ABSTRACTCS);
|
|
|
|
if (time(NULL) - start > riscv_command_timeout_sec) {
|
|
LOG_ERROR("abstractcs.busy is not going low after %d seconds "
|
|
"(abstractcs=0x%x). The target is either really slow or "
|
|
"broken. You could increase the timeout with riscv "
|
|
"set_reset_timeout_sec.",
|
|
riscv_command_timeout_sec, abstractcs);
|
|
break;
|
|
}
|
|
}
|
|
/* Clear the error status. */
|
|
dmi_write(target, DMI_ABSTRACTCS, abstractcs & DMI_ABSTRACTCS_CMDERR);
|
|
}
|