Add command to expose custom registers (#293)
* Added `riscv expose_custom` command. Seems to work for reading. I need to do some more testing for writes, as well as minor cleanup. Change-Id: I85d5d00897d5da4add4a6643b538be37d31a016f * Conform to OpenOCD style. Change-Id: I40a316f06f418d2b63d9e11aea03ef51da8d8faf * Free all the memory allocated by register init. Change-Id: I04e35ab54613f99708cee85e41fef989079adefc * Properly document `riscv expose_custom`. Change-Id: Id78a02b7a00c161df80f11b521a306e0cf3d7478
This commit is contained in:
parent
074b4fabed
commit
b4b2ec7d2d
|
@ -8998,6 +8998,14 @@ command can be used if OpenOCD gets this wrong, or a target implements custom
|
|||
CSRs.
|
||||
@end deffn
|
||||
|
||||
@deffn Command {riscv expose_csrs} n0[-m0][,n1[-m1]]...
|
||||
The RISC-V Debug Specification allows targets to expose custom registers
|
||||
through abstract commands. (See Section 3.5.1.1 in that document.) This command
|
||||
configures a list of inclusive ranges of those registers to expose. Number 0
|
||||
indicates the first custom register, whose abstract command number is 0xc000.
|
||||
This command must be executed before `init`.
|
||||
@end deffn
|
||||
|
||||
@deffn Command {riscv set_command_timeout_sec} [seconds]
|
||||
Set the wall-clock timeout (in seconds) for individual commands. The default
|
||||
should work fine for all but the slowest targets (eg. simulators).
|
||||
|
|
|
@ -745,8 +745,8 @@ static int write_abstract_arg(struct target *target, unsigned index,
|
|||
/**
|
||||
* @size in bits
|
||||
*/
|
||||
static uint32_t access_register_command(uint32_t number, unsigned size,
|
||||
uint32_t flags)
|
||||
static uint32_t access_register_command(struct target *target, uint32_t number,
|
||||
unsigned size, uint32_t flags)
|
||||
{
|
||||
uint32_t command = set_field(0, DMI_COMMAND_CMDTYPE, 0);
|
||||
switch (size) {
|
||||
|
@ -769,8 +769,13 @@ static uint32_t access_register_command(uint32_t number, unsigned size,
|
|||
} 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);
|
||||
} else if (number >= GDB_REGNO_COUNT) {
|
||||
/* Custom register. */
|
||||
assert(target->reg_cache->reg_list[number].arch_info);
|
||||
riscv_reg_info_t *reg_info = target->reg_cache->reg_list[number].arch_info;
|
||||
assert(reg_info);
|
||||
command = set_field(command, AC_ACCESS_REGISTER_REGNO,
|
||||
0xc000 + reg_info->custom_number);
|
||||
}
|
||||
|
||||
command |= flags;
|
||||
|
@ -790,7 +795,7 @@ static int register_read_abstract(struct target *target, uint64_t *value,
|
|||
!info->abstract_read_csr_supported)
|
||||
return ERROR_FAIL;
|
||||
|
||||
uint32_t command = access_register_command(number, size,
|
||||
uint32_t command = access_register_command(target, number, size,
|
||||
AC_ACCESS_REGISTER_TRANSFER);
|
||||
|
||||
int result = execute_abstract_command(target, command);
|
||||
|
@ -825,7 +830,7 @@ static int register_write_abstract(struct target *target, uint32_t number,
|
|||
!info->abstract_write_csr_supported)
|
||||
return ERROR_FAIL;
|
||||
|
||||
uint32_t command = access_register_command(number, size,
|
||||
uint32_t command = access_register_command(target, number, size,
|
||||
AC_ACCESS_REGISTER_TRANSFER |
|
||||
AC_ACCESS_REGISTER_WRITE);
|
||||
|
||||
|
@ -1320,6 +1325,7 @@ 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);
|
||||
/* TODO: free register arch_info */
|
||||
info->version_specific = NULL;
|
||||
}
|
||||
|
||||
|
@ -2072,9 +2078,9 @@ static int read_memory_progbuf(struct target *target, target_addr_t address,
|
|||
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);
|
||||
uint32_t command = access_register_command(target, 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;
|
||||
|
@ -2560,7 +2566,8 @@ static int write_memory_progbuf(struct target *target, target_addr_t address,
|
|||
|
||||
/* Write and execute command that moves value into S1 and
|
||||
* executes program buffer. */
|
||||
uint32_t command = access_register_command(GDB_REGNO_S1, 32,
|
||||
uint32_t command = access_register_command(target,
|
||||
GDB_REGNO_S1, 32,
|
||||
AC_ACCESS_REGISTER_POSTEXEC |
|
||||
AC_ACCESS_REGISTER_TRANSFER |
|
||||
AC_ACCESS_REGISTER_WRITE);
|
||||
|
|
|
@ -187,13 +187,17 @@ int riscv_reset_timeout_sec = DEFAULT_RESET_TIMEOUT_SEC;
|
|||
|
||||
bool riscv_prefer_sba;
|
||||
|
||||
typedef struct {
|
||||
uint16_t low, high;
|
||||
} range_t;
|
||||
|
||||
/* In addition to the ones in the standard spec, we'll also expose additional
|
||||
* CSRs in this list.
|
||||
* The list is either NULL, or a series of ranges (inclusive), terminated with
|
||||
* 1,0. */
|
||||
struct {
|
||||
uint16_t low, high;
|
||||
} *expose_csr;
|
||||
range_t *expose_csr;
|
||||
/* Same, but for custom registers. */
|
||||
range_t *expose_custom;
|
||||
|
||||
static uint32_t dtmcontrol_scan(struct target *target, uint32_t out)
|
||||
{
|
||||
|
@ -272,8 +276,16 @@ static void riscv_deinit_target(struct target *target)
|
|||
if (tt) {
|
||||
tt->deinit_target(target);
|
||||
riscv_info_t *info = (riscv_info_t *) target->arch_info;
|
||||
free(info->reg_names);
|
||||
free(info);
|
||||
}
|
||||
/* Free the shared structure use for most registers. */
|
||||
free(target->reg_cache->reg_list[0].arch_info);
|
||||
/* Free the ones we allocated separately. */
|
||||
for (unsigned i = GDB_REGNO_COUNT; i < target->reg_cache->num_regs; i++)
|
||||
free(target->reg_cache->reg_list[i].arch_info);
|
||||
free(target->reg_cache->reg_list);
|
||||
free(target->reg_cache);
|
||||
target->arch_info = NULL;
|
||||
}
|
||||
|
||||
|
@ -884,7 +896,7 @@ static int riscv_get_gdb_reg_list(struct target *target,
|
|||
*reg_list_size = 32;
|
||||
break;
|
||||
case REG_CLASS_ALL:
|
||||
*reg_list_size = GDB_REGNO_COUNT;
|
||||
*reg_list_size = target->reg_cache->num_regs;
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR("Unsupported reg_class: %d", reg_class);
|
||||
|
@ -1334,20 +1346,15 @@ void parse_error(const char *string, char c, unsigned position)
|
|||
LOG_ERROR("%s", buf);
|
||||
}
|
||||
|
||||
COMMAND_HANDLER(riscv_set_expose_csrs)
|
||||
int parse_ranges(range_t **ranges, const char **argv)
|
||||
{
|
||||
if (CMD_ARGC != 1) {
|
||||
LOG_ERROR("Command takes exactly 1 parameter");
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
}
|
||||
|
||||
for (unsigned pass = 0; pass < 2; pass++) {
|
||||
unsigned range = 0;
|
||||
unsigned low = 0;
|
||||
bool parse_low = true;
|
||||
unsigned high = 0;
|
||||
for (unsigned i = 0; i == 0 || CMD_ARGV[0][i-1]; i++) {
|
||||
char c = CMD_ARGV[0][i];
|
||||
for (unsigned i = 0; i == 0 || argv[0][i-1]; i++) {
|
||||
char c = argv[0][i];
|
||||
if (isspace(c)) {
|
||||
/* Ignore whitespace. */
|
||||
continue;
|
||||
|
@ -1361,13 +1368,13 @@ COMMAND_HANDLER(riscv_set_expose_csrs)
|
|||
parse_low = false;
|
||||
} else if (c == ',' || c == 0) {
|
||||
if (pass == 1) {
|
||||
expose_csr[range].low = low;
|
||||
expose_csr[range].high = low;
|
||||
(*ranges)[range].low = low;
|
||||
(*ranges)[range].high = low;
|
||||
}
|
||||
low = 0;
|
||||
range++;
|
||||
} else {
|
||||
parse_error(CMD_ARGV[0], c, i);
|
||||
parse_error(argv[0], c, i);
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
}
|
||||
|
||||
|
@ -1378,31 +1385,52 @@ COMMAND_HANDLER(riscv_set_expose_csrs)
|
|||
} else if (c == ',' || c == 0) {
|
||||
parse_low = true;
|
||||
if (pass == 1) {
|
||||
expose_csr[range].low = low;
|
||||
expose_csr[range].high = high;
|
||||
(*ranges)[range].low = low;
|
||||
(*ranges)[range].high = high;
|
||||
}
|
||||
low = 0;
|
||||
high = 0;
|
||||
range++;
|
||||
} else {
|
||||
parse_error(CMD_ARGV[0], c, i);
|
||||
parse_error(argv[0], c, i);
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pass == 0) {
|
||||
if (expose_csr)
|
||||
free(expose_csr);
|
||||
expose_csr = calloc(range + 2, sizeof(*expose_csr));
|
||||
if (*ranges)
|
||||
free(*ranges);
|
||||
*ranges = calloc(range + 2, sizeof(range_t));
|
||||
} else {
|
||||
expose_csr[range].low = 1;
|
||||
expose_csr[range].high = 0;
|
||||
(*ranges)[range].low = 1;
|
||||
(*ranges)[range].high = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
|
||||
COMMAND_HANDLER(riscv_set_expose_csrs)
|
||||
{
|
||||
if (CMD_ARGC != 1) {
|
||||
LOG_ERROR("Command takes exactly 1 parameter");
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
}
|
||||
|
||||
return parse_ranges(&expose_csr, CMD_ARGV);
|
||||
}
|
||||
|
||||
COMMAND_HANDLER(riscv_set_expose_custom)
|
||||
{
|
||||
if (CMD_ARGC != 1) {
|
||||
LOG_ERROR("Command takes exactly 1 parameter");
|
||||
return ERROR_COMMAND_SYNTAX_ERROR;
|
||||
}
|
||||
|
||||
return parse_ranges(&expose_custom, CMD_ARGV);
|
||||
}
|
||||
|
||||
COMMAND_HANDLER(riscv_authdata_read)
|
||||
{
|
||||
if (CMD_ARGC != 0) {
|
||||
|
@ -1542,6 +1570,15 @@ static const struct command_registration riscv_exec_command_handlers[] = {
|
|||
"addition to the standard ones. This must be executed before "
|
||||
"`init`."
|
||||
},
|
||||
{
|
||||
.name = "expose_custom",
|
||||
.handler = riscv_set_expose_custom,
|
||||
.mode = COMMAND_ANY,
|
||||
.usage = "riscv expose_custom n0[-m0][,n1[-m1]]...",
|
||||
.help = "Configure a list of inclusive ranges for custom registers to "
|
||||
"expose. custom0 is accessed as abstract register number 0xc000, "
|
||||
"etc. This must be executed before `init`."
|
||||
},
|
||||
{
|
||||
.name = "authdata_read",
|
||||
.handler = riscv_authdata_read,
|
||||
|
@ -1850,7 +1887,7 @@ void riscv_invalidate_register_cache(struct target *target)
|
|||
RISCV_INFO(r);
|
||||
|
||||
register_cache_invalidate(target->reg_cache);
|
||||
for (size_t i = 0; i < GDB_REGNO_COUNT; ++i) {
|
||||
for (size_t i = 0; i < target->reg_cache->num_regs; ++i) {
|
||||
struct reg *reg = &target->reg_cache->reg_list[i];
|
||||
reg->valid = false;
|
||||
}
|
||||
|
@ -2123,7 +2160,8 @@ const char *gdb_regno_name(enum gdb_regno regno)
|
|||
|
||||
static int register_get(struct reg *reg)
|
||||
{
|
||||
struct target *target = (struct target *) reg->arch_info;
|
||||
riscv_reg_info_t *reg_info = reg->arch_info;
|
||||
struct target *target = reg_info->target;
|
||||
uint64_t value;
|
||||
int result = riscv_get_register(target, &value, reg->number);
|
||||
if (result != ERROR_OK)
|
||||
|
@ -2134,7 +2172,8 @@ static int register_get(struct reg *reg)
|
|||
|
||||
static int register_set(struct reg *reg, uint8_t *buf)
|
||||
{
|
||||
struct target *target = (struct target *) reg->arch_info;
|
||||
riscv_reg_info_t *reg_info = reg->arch_info;
|
||||
struct target *target = reg_info->target;
|
||||
|
||||
uint64_t value = buf_get_u64(buf, 0, reg->size);
|
||||
|
||||
|
@ -2176,12 +2215,26 @@ int riscv_init_registers(struct target *target)
|
|||
target->reg_cache->name = "RISC-V Registers";
|
||||
target->reg_cache->num_regs = GDB_REGNO_COUNT;
|
||||
|
||||
target->reg_cache->reg_list = calloc(GDB_REGNO_COUNT, sizeof(struct reg));
|
||||
if (expose_custom) {
|
||||
for (unsigned i = 0; expose_custom[i].low <= expose_custom[i].high; i++) {
|
||||
for (unsigned number = expose_custom[i].low;
|
||||
number <= expose_custom[i].high;
|
||||
number++)
|
||||
target->reg_cache->num_regs++;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("create register cache for %d registers",
|
||||
target->reg_cache->num_regs);
|
||||
|
||||
target->reg_cache->reg_list =
|
||||
calloc(target->reg_cache->num_regs, sizeof(struct reg));
|
||||
|
||||
const unsigned int max_reg_name_len = 12;
|
||||
if (info->reg_names)
|
||||
free(info->reg_names);
|
||||
info->reg_names = calloc(1, GDB_REGNO_COUNT * max_reg_name_len);
|
||||
info->reg_names =
|
||||
calloc(target->reg_cache->num_regs, max_reg_name_len);
|
||||
char *reg_name = info->reg_names;
|
||||
|
||||
static struct reg_feature feature_cpu = {
|
||||
|
@ -2196,6 +2249,9 @@ int riscv_init_registers(struct target *target)
|
|||
static struct reg_feature feature_virtual = {
|
||||
.name = "org.gnu.gdb.riscv.virtual"
|
||||
};
|
||||
static struct reg_feature feature_custom = {
|
||||
.name = "org.gnu.gdb.riscv.custom"
|
||||
};
|
||||
|
||||
static struct reg_data_type type_ieee_single = {
|
||||
.type = REG_TYPE_IEEE_SINGLE,
|
||||
|
@ -2214,18 +2270,24 @@ int riscv_init_registers(struct target *target)
|
|||
qsort(csr_info, DIM(csr_info), sizeof(*csr_info), cmp_csr_info);
|
||||
unsigned csr_info_index = 0;
|
||||
|
||||
/* When gdb request register N, gdb_get_register_packet() assumes that this
|
||||
unsigned custom_range_index = 0;
|
||||
int custom_within_range = 0;
|
||||
|
||||
riscv_reg_info_t *shared_reg_info = calloc(1, sizeof(riscv_reg_info_t));
|
||||
shared_reg_info->target = target;
|
||||
|
||||
/* When gdb requests register N, gdb_get_register_packet() assumes that this
|
||||
* is register at index N in reg_list. So if there are certain registers
|
||||
* that don't exist, we need to leave holes in the list (or renumber, but
|
||||
* it would be nice not to have yet another set of numbers to translate
|
||||
* between). */
|
||||
for (uint32_t number = 0; number < GDB_REGNO_COUNT; number++) {
|
||||
for (uint32_t number = 0; number < target->reg_cache->num_regs; number++) {
|
||||
struct reg *r = &target->reg_cache->reg_list[number];
|
||||
r->dirty = false;
|
||||
r->valid = false;
|
||||
r->exist = true;
|
||||
r->type = &riscv_reg_arch_type;
|
||||
r->arch_info = target;
|
||||
r->arch_info = shared_reg_info;
|
||||
r->number = number;
|
||||
r->size = riscv_xlen(target);
|
||||
/* r->size is set in riscv_invalidate_register_cache, maybe because the
|
||||
|
@ -2585,11 +2647,35 @@ int riscv_init_registers(struct target *target)
|
|||
r->group = "general";
|
||||
r->feature = &feature_virtual;
|
||||
r->size = 8;
|
||||
|
||||
} else {
|
||||
/* Custom registers. */
|
||||
assert(expose_custom);
|
||||
|
||||
range_t *range = &expose_custom[custom_range_index];
|
||||
assert(range->low <= range->high);
|
||||
unsigned custom_number = range->low + custom_within_range;
|
||||
|
||||
r->group = "custom";
|
||||
r->feature = &feature_custom;
|
||||
r->arch_info = calloc(1, sizeof(riscv_reg_info_t));
|
||||
assert(r->arch_info);
|
||||
((riscv_reg_info_t *) r->arch_info)->target = target;
|
||||
((riscv_reg_info_t *) r->arch_info)->custom_number = custom_number;
|
||||
sprintf(reg_name, "custom%d", custom_number);
|
||||
|
||||
custom_within_range++;
|
||||
if (custom_within_range > range->high - range->low) {
|
||||
custom_within_range = 0;
|
||||
custom_range_index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (reg_name[0])
|
||||
r->name = reg_name;
|
||||
reg_name += strlen(reg_name) + 1;
|
||||
assert(reg_name < info->reg_names + GDB_REGNO_COUNT * max_reg_name_len);
|
||||
assert(reg_name < info->reg_names + target->reg_cache->num_regs *
|
||||
max_reg_name_len);
|
||||
r->value = &info->reg_cache_values[number];
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,11 @@ enum riscv_halt_reason {
|
|||
RISCV_HALT_ERROR
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
struct target *target;
|
||||
unsigned custom_number;
|
||||
} riscv_reg_info_t;
|
||||
|
||||
typedef struct {
|
||||
unsigned dtm_version;
|
||||
|
||||
|
|
Loading…
Reference in New Issue