target/riscv: access registers via `reg->type`

* `int riscv_reg_get()` and `int riscv_reg_set()` are implemented in
  terms of `reg->type->get/set` instead of the other way around. This
  makes it easier to support custom behavior for some registers.
* Cacheability is determined by `reg->type` instead of
  `riscv_reg_impl_gdb_regno_cacheable()`.
* Issues with redirection of `priv` -> `dcsr` and `pc` -> `dpc` are
  addressed at the "topmost" level.
    - `priv` and `pc` are alvais invalid.
    - Fixed some issues, e.g. the first `pc` write printed-out an
      uninitialized value:
```
> reg pc 0
pc (/64): 0x000075da6b33db20
```

Change-Id: I514547f455d62b289fb5dee62753bf5d9aa3b8ae
Signed-off-by: Evgeniy Naydanov <evgeniy.naydanov@syntacore.com>
This commit is contained in:
Evgeniy Naydanov 2024-09-10 14:37:23 +03:00
parent 1c6b7fa2c9
commit a54d86f3d0
5 changed files with 477 additions and 297 deletions

View File

@ -5064,19 +5064,8 @@ struct target_type riscv013_target = {
int riscv013_get_register(struct target *target, int riscv013_get_register(struct target *target,
riscv_reg_t *value, enum gdb_regno rid) riscv_reg_t *value, enum gdb_regno rid)
{ {
/* It would be beneficial to move this redirection to the assert(rid != GDB_REGNO_PC && "'pc' should be read through 'dpc'");
* version-independent section, but there is a conflict: assert(rid != GDB_REGNO_PRIV && "'priv' should be read through 'dcsr'");
* `dcsr[5]` is `dcsr.v` in current spec, but it is `dcsr.debugint` in 0.11.
*/
if (rid == GDB_REGNO_PRIV) {
uint64_t dcsr;
if (riscv_reg_get(target, &dcsr, GDB_REGNO_DCSR) != ERROR_OK)
return ERROR_FAIL;
*value = set_field(0, VIRT_PRIV_V, get_field(dcsr, CSR_DCSR_V));
*value = set_field(*value, VIRT_PRIV_PRV, get_field(dcsr, CSR_DCSR_PRV));
return ERROR_OK;
}
LOG_TARGET_DEBUG(target, "reading register %s", riscv_reg_gdb_regno_name(target, rid)); LOG_TARGET_DEBUG(target, "reading register %s", riscv_reg_gdb_regno_name(target, rid));
if (dm013_select_target(target) != ERROR_OK) if (dm013_select_target(target) != ERROR_OK)
@ -5093,6 +5082,8 @@ int riscv013_get_register(struct target *target,
int riscv013_set_register(struct target *target, enum gdb_regno rid, int riscv013_set_register(struct target *target, enum gdb_regno rid,
riscv_reg_t value) riscv_reg_t value)
{ {
assert(rid != GDB_REGNO_PC && "'pc' should be written through 'dpc'");
assert(rid != GDB_REGNO_PRIV && "'priv' should be written through 'dcsr'");
LOG_TARGET_DEBUG(target, "writing 0x%" PRIx64 " to register %s", LOG_TARGET_DEBUG(target, "writing 0x%" PRIx64 " to register %s",
value, riscv_reg_gdb_regno_name(target, rid)); value, riscv_reg_gdb_regno_name(target, rid));

View File

@ -13,74 +13,452 @@
#include "debug_defines.h" #include "debug_defines.h"
#include <helper/time_support.h> #include <helper/time_support.h>
static int riscv013_reg_get(struct reg *reg) /* Start of register fetch/send definitions.
* - "reg_fetcher" (named "fetch_*") -- loads the value from target into reg
* cache entry. Modifyes only "reg->value".
* - "reg_sender" (named "send_*") -- stores the value from "buf" to the
* target. Does not modify "reg" at all.
*/
typedef int (*reg_fetcher)(struct reg *reg);
typedef int (*reg_sender)(const struct reg *reg, const uint8_t *buf);
/* TODO: Introduce separate "fetch_*" and "send_*" functions for register
* classes (xregs, fregs, csrs).
*/
static int fetch_riscv013_reg(struct reg *reg)
{ {
struct target *target = riscv_reg_impl_get_target(reg); riscv_reg_t value;
int res = riscv013_get_register(riscv_reg_impl_get_target(reg),
&value, reg->number);
if (res != ERROR_OK)
return res;
buf_set_u64(reg->value, 0, reg->size, value);
return res;
}
/* TODO: Hack to deal with gdb that thinks these registers still exist. */ static int send_riscv013_reg(const struct reg *reg, const uint8_t *buf)
if (reg->number > GDB_REGNO_XPR15 && reg->number <= GDB_REGNO_XPR31 && {
riscv_supports_extension(target, 'E')) { assert(reg->size <= 64);
buf_set_u64(reg->value, 0, reg->size, 0); const riscv_reg_t value = buf_get_u64(buf, 0, reg->size);
return ERROR_OK; return riscv013_set_register(riscv_reg_impl_get_target(reg),
reg->number, value);
}
/* TODO: Inline "riscv013_*_register_buf" into "vreg" accessors.
*/
static int fetch_vreg(struct reg *reg)
{
return riscv013_get_register_buf(riscv_reg_impl_get_target(reg),
reg->value, reg->number);
}
static int send_vreg(const struct reg *reg, const uint8_t *buf)
{
return riscv013_set_register_buf(riscv_reg_impl_get_target(reg),
reg->number, reg->value);
}
/* End of register fetch/send definitions. */
/* Start of cache entry utils. */
static void set_cache_value(struct reg *reg, const uint8_t *buf)
{
assert(reg);
assert(reg->value);
assert(reg->size);
buf_cpy(buf, reg->value, reg->size);
}
/**
* Note: there are only 3 states a cahce entry can be in:
* |: state :|: "reg->valid" :|: "reg->dirty" :|
* | "invalid" | false | false |
* | "valid" | true | false |
* | "dirty" | true | true |
*
* The state "reg->valid == dirty && reg->valid == false" should be unreacheable.
* To achieve this, "reg->valid" and "reg->dirty" are not assigned directly
* outside of "mark_<state>()" functions.
*/
static void mark_invalid(struct reg *reg)
{
assert(riscv_reg_impl_is_initialized(reg));
assert(reg->exist);
reg->valid = false;
reg->dirty = false;
}
static void mark_clean(struct reg *reg)
{
assert(riscv_reg_impl_is_initialized(reg));
assert(reg->exist);
assert(riscv_reg_impl_get_target(reg)->state == TARGET_HALTED);
reg->valid = true;
reg->dirty = false;
}
static void mark_dirty(struct reg *reg)
{
assert(riscv_reg_impl_is_initialized(reg));
assert(reg->exist);
assert(riscv_reg_impl_get_target(reg)->state == TARGET_HALTED);
reg->valid = true;
reg->dirty = true;
}
static int access_prelude(const struct reg *reg, bool write)
{
assert(riscv_reg_impl_is_initialized(reg));
const struct target * const target = riscv_reg_impl_get_target(reg);
if (!reg->exist) {
LOG_TARGET_DEBUG(target, "Register %s does not exist.",
reg->name);
return ERROR_FAIL;
} }
LOG_TARGET_DEBUG(target, "%s %s (valid=%s, dirty=%s).",
write ? "Writing" : "Reading", reg->name,
reg->valid ? "true" : "false",
reg->dirty ? "true" : "false");
return ERROR_OK;
}
if (reg->number >= GDB_REGNO_V0 && reg->number <= GDB_REGNO_V31) { static void access_epilogue(const struct reg *reg, bool write)
if (riscv013_get_register_buf(target, reg->value, reg->number) != ERROR_OK) {
return ERROR_FAIL; assert(riscv_reg_impl_is_initialized(reg));
assert(reg->exist);
reg->valid = riscv_reg_impl_gdb_regno_cacheable(reg->number, /* is write? */ false); if (debug_level < LOG_LVL_DEBUG)
} else { return;
uint64_t value;
int result = riscv_reg_get(target, &value, reg->number);
if (result != ERROR_OK)
return result;
buf_set_u64(reg->value, 0, reg->size, value);
}
char *str = buf_to_hex_str(reg->value, reg->size); char *str = buf_to_hex_str(reg->value, reg->size);
LOG_TARGET_DEBUG(target, "Read 0x%s from %s (valid=%d).", str, reg->name, LOG_TARGET_DEBUG(riscv_reg_impl_get_target(reg),
reg->valid); "%s 0x%s %s %s (valid=%s, dirty=%s).",
write ? "Wrote" : "Read", str,
write ? "to" : "from", reg->name,
reg->valid ? "true" : "false",
reg->dirty ? "true" : "false");
free(str); free(str);
return ERROR_OK;
} }
static int riscv013_reg_set(struct reg *reg, uint8_t *buf) static int noncaching_get(struct reg *reg, reg_fetcher fetch_reg)
{ {
struct target *target = riscv_reg_impl_get_target(reg); assert(riscv_reg_impl_is_initialized(reg));
int res = access_prelude(reg, /*write*/ false);
char *str = buf_to_hex_str(buf, reg->size); if (res != ERROR_OK)
LOG_TARGET_DEBUG(target, "Write 0x%s to %s (valid=%d).", str, reg->name, return res;
reg->valid); res = fetch_reg(reg);
free(str); mark_invalid(reg);
if (res != ERROR_OK)
/* TODO: Hack to deal with gdb that thinks these registers still exist. */ return res;
if (reg->number > GDB_REGNO_XPR15 && reg->number <= GDB_REGNO_XPR31 && access_epilogue(reg, /*write*/ false);
riscv_supports_extension(target, 'E') &&
buf_get_u64(buf, 0, reg->size) == 0)
return ERROR_OK;
if (reg->number >= GDB_REGNO_V0 && reg->number <= GDB_REGNO_V31) {
if (riscv013_set_register_buf(target, reg->number, buf) != ERROR_OK)
return ERROR_FAIL;
memcpy(reg->value, buf, DIV_ROUND_UP(reg->size, 8));
reg->valid = riscv_reg_impl_gdb_regno_cacheable(reg->number, /* is write? */ true);
} else {
const riscv_reg_t value = buf_get_u64(buf, 0, reg->size);
if (riscv_reg_set(target, reg->number, value) != ERROR_OK)
return ERROR_FAIL;
}
return ERROR_OK; return ERROR_OK;
} }
static int caching_get(struct reg *reg, reg_fetcher fetch_reg)
{
assert(riscv_reg_impl_is_initialized(reg));
int res = access_prelude(reg, /*write*/ false);
if (res != ERROR_OK)
return res;
if (reg->valid) {
LOG_TARGET_DEBUG(riscv_reg_impl_get_target(reg),
"Reading %s from cache.", reg->name);
return ERROR_OK;
}
res = fetch_reg(reg);
if (res != ERROR_OK) {
mark_invalid(reg);
return res;
}
if (riscv_reg_impl_get_target(reg)->state == TARGET_HALTED)
mark_clean(reg);
else
mark_invalid(reg);
access_epilogue(reg, /*write*/ false);
return ERROR_OK;
}
static int noncaching_set(struct reg *reg, const uint8_t *buf,
reg_sender send_reg)
{
int res = access_prelude(reg, /*write*/ true);
if (res != ERROR_OK)
return res;
res = send_reg(reg, buf);
mark_invalid(reg);
if (res != ERROR_OK)
return res;
set_cache_value(reg, buf);
access_epilogue(reg, /*write*/ true);
return ERROR_OK;
}
static int caching_set(struct reg *reg, const uint8_t *buf,
reg_sender send_reg)
{
assert(riscv_reg_impl_is_initialized(reg));
const struct target * const target = riscv_reg_impl_get_target(reg);
if (target->state != TARGET_HALTED) {
LOG_TARGET_DEBUG(target, "Not caching the write to %s.", reg->name);
return noncaching_set(reg, buf, send_reg);
}
int res = access_prelude(reg, /*write*/ true);
if (res != ERROR_OK)
return res;
if (reg->valid && buf_eq(reg->value, buf, reg->size)) {
LOG_TARGET_DEBUG(riscv_reg_impl_get_target(reg),
"Writing the same value to %s.", reg->name);
} else {
LOG_TARGET_DEBUG(target, "Caching the write to %s.", reg->name);
set_cache_value(reg, buf);
mark_dirty(reg);
}
access_epilogue(reg, /*write*/ true);
return ERROR_OK;
}
static int flush_reg(struct reg *reg, reg_sender send_reg)
{
if (!reg->dirty)
return ERROR_OK;
int res = access_prelude(reg, /*write*/ true);
if (res != ERROR_OK)
return res;
assert(reg->dirty);
assert(reg->valid);
const struct target * const target = riscv_reg_impl_get_target(reg);
if (target->state != TARGET_HALTED) {
LOG_TARGET_ERROR(target,
"BUG: register %s is dirty while the target is not halted.",
reg->name);
return ERROR_TARGET_NOT_HALTED;
}
res = send_reg(reg, reg->value);
if (res != ERROR_OK) {
/* Register is not marked "invalid" here (like it's done on failure in
* "caching_set") since it is "dirty", i.e. the most recent value
* is currently in the cache. */
return res;
}
mark_clean(reg);
access_epilogue(reg, /*write*/ true);
return res;
}
/* End of cache entry utils. */
/* Start of "reg_arch_type" method definitions. */
static int riscv013_noncaching_get(struct reg *reg)
{
return noncaching_get(reg, fetch_riscv013_reg);
}
static int riscv013_caching_get(struct reg *reg)
{
return caching_get(reg, fetch_riscv013_reg);
}
static int riscv013_noncaching_set(struct reg *reg, uint8_t *buf)
{
return noncaching_set(reg, buf, send_riscv013_reg);
}
static int riscv013_caching_set(struct reg *reg, uint8_t *buf)
{
return caching_set(reg, buf, send_riscv013_reg);
}
static int riscv013_reg_flush(struct reg *reg)
{
return flush_reg(reg, send_riscv013_reg);
}
static int flush_uncacheable(struct reg *reg)
{
assert(!reg->dirty);
return ERROR_OK;
}
static int get_vreg(struct reg *reg)
{
assert(reg->number >= GDB_REGNO_V0 && reg->number <= GDB_REGNO_V31);
return caching_get(reg, fetch_vreg);
}
static int set_vreg(struct reg *reg, uint8_t *buf)
{
assert(reg->number >= GDB_REGNO_V0 && reg->number <= GDB_REGNO_V31);
return caching_set(reg, buf, send_vreg);
}
static int flush_vreg(struct reg *reg)
{
assert(reg->number >= GDB_REGNO_V0 && reg->number <= GDB_REGNO_V31);
return flush_reg(reg, send_vreg);
}
static int get_pc(struct reg *pc)
{
assert(pc->number == GDB_REGNO_PC);
assert(!pc->valid);
assert(!pc->dirty);
struct reg * const dpc =
riscv_reg_impl_cache_entry(riscv_reg_impl_get_target(pc),
GDB_REGNO_DPC);
int res = dpc->type->get(dpc);
if (res == ERROR_OK)
set_cache_value(pc, dpc->value);
return res;
}
static int set_pc(struct reg *pc, uint8_t *buf)
{
assert(pc->number == GDB_REGNO_PC);
assert(!pc->valid);
assert(!pc->dirty);
struct reg * const dpc =
riscv_reg_impl_cache_entry(riscv_reg_impl_get_target(pc),
GDB_REGNO_DPC);
int res = dpc->type->set(dpc, buf);
if (res == ERROR_OK)
set_cache_value(pc, dpc->value);
return res;
}
static int get_priv(struct reg *priv)
{
assert(priv->number == GDB_REGNO_PRIV);
assert(!priv->valid);
assert(!priv->dirty);
struct reg * const dcsr =
riscv_reg_impl_cache_entry(riscv_reg_impl_get_target(priv),
GDB_REGNO_DCSR);
assert(dcsr->size == 32);
int res = dcsr->type->get(dcsr);
if (res != ERROR_OK)
return res;
buf_set_u32(priv->value, VIRT_PRIV_PRV_OFFSET, VIRT_PRIV_PRV_LENGTH,
buf_get_u32(dcsr->value, CSR_DCSR_PRV_OFFSET, CSR_DCSR_PRV_LENGTH));
buf_set_u32(priv->value, VIRT_PRIV_V_OFFSET, VIRT_PRIV_V_LENGTH,
buf_get_u32(dcsr->value, CSR_DCSR_V_OFFSET, CSR_DCSR_V_LENGTH));
return ERROR_OK;
}
static int set_priv(struct reg *priv, uint8_t *priv_buf)
{
assert(priv->number == GDB_REGNO_PRIV);
assert(!priv->valid);
assert(!priv->dirty);
struct reg * const dcsr = riscv_reg_impl_cache_entry(riscv_reg_impl_get_target(priv),
GDB_REGNO_DCSR);
int res = dcsr->type->get(dcsr);
if (res != ERROR_OK)
return res;
assert(dcsr->size == 32);
uint8_t dcsr_buf[32 / 8];
buf_cpy(dcsr->value, dcsr_buf, dcsr->size);
buf_set_u32(dcsr_buf, CSR_DCSR_PRV_OFFSET, CSR_DCSR_PRV_LENGTH,
buf_get_u32(priv_buf, VIRT_PRIV_PRV_OFFSET, VIRT_PRIV_PRV_LENGTH));
buf_set_u32(dcsr_buf, CSR_DCSR_V_OFFSET, CSR_DCSR_V_LENGTH,
buf_get_u32(priv_buf, VIRT_PRIV_V_OFFSET, VIRT_PRIV_V_LENGTH));
res = dcsr->type->set(dcsr, dcsr_buf);
if (res == ERROR_OK)
set_cache_value(priv, priv_buf);
return res;
}
/* End of "reg_arch_type" method definitions. */
static const struct reg_arch_type zero_type = {
.get = riscv013_caching_get,
/* "zero" is read-only, but there is nothig wrong with attempting to
* write it (e.g. for the purpose of HW testing). */
.set = riscv013_noncaching_set,
.flush = riscv013_reg_flush,
};
static const struct reg_arch_type xreg_type = {
.get = riscv013_caching_get,
.set = riscv013_caching_set,
.flush = riscv013_reg_flush,
};
static const struct reg_arch_type freg_type = {
.get = riscv013_caching_get,
.set = riscv013_caching_set,
.flush = riscv013_reg_flush,
};
static const struct reg_arch_type vreg_type = {
.get = get_vreg,
.set = set_vreg,
.flush = flush_vreg
};
static const struct reg_arch_type pc_type = {
.get = get_pc,
.set = set_pc,
.flush = flush_uncacheable
};
static const struct reg_arch_type priv_type = {
.get = get_priv,
.set = set_priv,
.flush = flush_uncacheable
};
static const struct reg_arch_type warl_csr_type = {
.get = riscv013_caching_get,
.set = riscv013_noncaching_set,
.flush = riscv013_reg_flush
};
static const struct reg_arch_type uncacheable_type = {
.get = riscv013_noncaching_get,
.set = riscv013_noncaching_set,
.flush = flush_uncacheable
};
static const struct reg_arch_type *riscv013_gdb_regno_reg_type(uint32_t regno) static const struct reg_arch_type *riscv013_gdb_regno_reg_type(uint32_t regno)
{ {
static const struct reg_arch_type riscv013_reg_type = { if (regno != GDB_REGNO_ZERO && regno <= GDB_REGNO_XPR31)
.get = riscv013_reg_get, return &xreg_type;
.set = riscv013_reg_set, if (regno >= GDB_REGNO_FPR0 && regno <= GDB_REGNO_FPR31) {
.flush = NULL /* For now "freg_type" is the same as "xreg_type", but it will be
}; * changed soon */
return &riscv013_reg_type; return &freg_type;
}
if (regno >= GDB_REGNO_V0 && regno <= GDB_REGNO_V31)
return &vreg_type;
switch (regno) {
case GDB_REGNO_ZERO:
return &zero_type;
case GDB_REGNO_PC:
return &pc_type;
case GDB_REGNO_PRIV:
return &priv_type;
case GDB_REGNO_VLENB:
/* "vlenb" is read-only, but there is nothig wrong with attempting to
* write it (e.g. for the purpose of HW testing). */
case GDB_REGNO_DPC:
case GDB_REGNO_VSTART:
case GDB_REGNO_VXSAT:
case GDB_REGNO_VXRM:
case GDB_REGNO_VL:
case GDB_REGNO_VTYPE:
case GDB_REGNO_MISA:
case GDB_REGNO_DCSR:
case GDB_REGNO_DSCRATCH0:
case GDB_REGNO_MSTATUS:
case GDB_REGNO_MEPC:
case GDB_REGNO_MCAUSE:
case GDB_REGNO_SATP:
return &warl_csr_type;
/* TODO: Sdtrig register should be WARL. */
case GDB_REGNO_TSELECT:
case GDB_REGNO_TDATA1:
case GDB_REGNO_TDATA2:
default:
return &uncacheable_type;
}
} }
static int init_cache_entry(struct target *target, uint32_t regno) static int init_cache_entry(struct target *target, uint32_t regno)
@ -359,30 +737,21 @@ int riscv013_reg_examine_all(struct target *target)
*/ */
int riscv013_reg_save(struct target *target, enum gdb_regno regid) int riscv013_reg_save(struct target *target, enum gdb_regno regid)
{ {
if (target->state != TARGET_HALTED) { assert(regid >= GDB_REGNO_ZERO && regid <= GDB_REGNO_XPR31);
LOG_TARGET_ERROR(target, "Can't save register %s on a hart that is not halted.", struct reg *reg = riscv_reg_impl_cache_entry(target, regid);
riscv_reg_gdb_regno_name(target, regid)); assert(riscv_reg_impl_is_initialized(reg));
return ERROR_FAIL;
}
assert(riscv_reg_impl_gdb_regno_cacheable(regid, /* is write? */ false) &&
"Only cacheable registers can be saved.");
RISCV_INFO(r); RISCV_INFO(r);
riscv_reg_t value; if (target->state != TARGET_HALTED) {
if (!target->reg_cache) { LOG_TARGET_ERROR(target, "Can't save register %s on a hart that is not halted.",
assert(!target_was_examined(target)); reg->name);
/* To create register cache it is needed to examine the target first, return ERROR_TARGET_NOT_HALTED;
* therefore during examine, any changed register needs to be saved
* and restored manually.
*/
return ERROR_OK;
} }
struct reg *reg = riscv_reg_impl_cache_entry(target, regid);
LOG_TARGET_DEBUG(target, "Saving %s", reg->name); LOG_TARGET_DEBUG(target, "Saving %s", reg->name);
if (riscv_reg_get(target, &value, regid) != ERROR_OK) int res = reg->type->get(reg);
return ERROR_FAIL; if (res != ERROR_OK)
return res;
assert(reg->valid && assert(reg->valid &&
"The register is cacheable, so the cache entry must be valid now."); "The register is cacheable, so the cache entry must be valid now.");

View File

@ -20,12 +20,9 @@
int riscv013_reg_examine_all(struct target *target); int riscv013_reg_examine_all(struct target *target);
/** /**
* This function is used to save the value of a register in cache. The register * This function is used to save the value of a X-register in cache. The
* is marked as dirty, and writeback is delayed for as long as possible. * register is marked as dirty, and writeback is delayed for as long as
* Generally used to save registers before program buffer execution. * possible. Generally used to save registers before program buffer execution.
*
* TODO: The interface should be restricted in such a way that only GPRs can be
* saved.
*/ */
int riscv013_reg_save(struct target *target, enum gdb_regno regid); int riscv013_reg_save(struct target *target, enum gdb_regno regid);

View File

@ -8,19 +8,6 @@
#include "riscv.h" #include "riscv.h"
#include "riscv_reg.h" #include "riscv_reg.h"
#include "riscv_reg_impl.h" #include "riscv_reg_impl.h"
/**
* TODO: Currently `reg->get/set` is implemented in terms of
* `riscv_get/set_register`. However, the intention behind
* `riscv_get/set_register` is to work with the cache, therefore it accesses
* and modifyes register cache directly. The idea is to implement
* `riscv_get/set_register` in terms of `riscv_reg_impl_cache_entry` and
* `reg->get/set`.
* Once this is done, the following includes should be removed.
*/
#include "debug_defines.h"
#include "riscv-011.h"
#include "riscv-013.h"
#include "field_helpers.h"
static const char * const default_reg_names[GDB_REGNO_COUNT] = { static const char * const default_reg_names[GDB_REGNO_COUNT] = {
[GDB_REGNO_ZERO] = "zero", [GDB_REGNO_ZERO] = "zero",
@ -795,97 +782,17 @@ int riscv_reg_flush_all(struct target *target)
* may become dirty in the process (e.g. S0, S1). For that reason, flush * may become dirty in the process (e.g. S0, S1). For that reason, flush
* registers in reverse order, so that GPRs are flushed last. * registers in reverse order, so that GPRs are flushed last.
*/ */
int res = ERROR_OK;
for (unsigned int number = target->reg_cache->num_regs; number-- > 0; ) { for (unsigned int number = target->reg_cache->num_regs; number-- > 0; ) {
struct reg *reg = riscv_reg_impl_cache_entry(target, number); struct reg *reg = riscv_reg_impl_cache_entry(target, number);
if (reg->valid && reg->dirty) { if (reg->type->flush(reg) != ERROR_OK)
riscv_reg_t value = buf_get_u64(reg->value, 0, reg->size); res = ERROR_FAIL;
LOG_TARGET_DEBUG(target, "%s is dirty; write back 0x%" PRIx64,
reg->name, value);
if (riscv_reg_write(target, number, value) != ERROR_OK)
return ERROR_FAIL;
}
} }
LOG_TARGET_DEBUG(target, "Flush of register cache completed"); if (res == ERROR_OK)
return ERROR_OK; LOG_TARGET_DEBUG(target, "Flush of register cache completed successfully.");
} else
LOG_TARGET_DEBUG(target, "Flush of register cache failed.");
/** return res;
* This function is used internally by functions that change register values.
* If `write_through` is true, it is ensured that the value of the target's
* register is set to be equal to the `value` argument. The cached value is
* updated if the register is cacheable.
* TODO: Currently `reg->get/set` is implemented in terms of
* `riscv_get/set_register`. However, the intention behind
* `riscv_get/set_register` is to work with the cache, therefore it accesses
* and modifyes register cache directly. The idea is to implement
* `riscv_get/set_register` in terms of `riscv_reg_impl_cache_entry` and
* `reg->get/set`.
*/
static int riscv_set_or_write_register(struct target *target,
enum gdb_regno regid, riscv_reg_t value, bool write_through)
{
RISCV_INFO(r);
assert(r);
if (r->dtm_version == DTM_DTMCS_VERSION_0_11)
return riscv011_set_register(target, regid, value);
keep_alive();
if (regid == GDB_REGNO_PC) {
return riscv_set_or_write_register(target, GDB_REGNO_DPC, value, write_through);
} else if (regid == GDB_REGNO_PRIV) {
riscv_reg_t dcsr;
if (riscv_reg_get(target, &dcsr, GDB_REGNO_DCSR) != ERROR_OK)
return ERROR_FAIL;
dcsr = set_field(dcsr, CSR_DCSR_PRV, get_field(value, VIRT_PRIV_PRV));
dcsr = set_field(dcsr, CSR_DCSR_V, get_field(value, VIRT_PRIV_V));
return riscv_set_or_write_register(target, GDB_REGNO_DCSR, dcsr, write_through);
}
struct reg *reg = riscv_reg_impl_cache_entry(target, regid);
assert(riscv_reg_impl_is_initialized(reg));
if (!reg->exist) {
LOG_TARGET_DEBUG(target, "Register %s does not exist.", reg->name);
return ERROR_FAIL;
}
if (target->state != TARGET_HALTED) {
LOG_TARGET_DEBUG(target,
"Target not halted, writing to target: %s <- 0x%" PRIx64,
reg->name, value);
return riscv013_set_register(target, regid, value);
}
const bool need_to_write = !reg->valid || reg->dirty ||
value != buf_get_u64(reg->value, 0, reg->size);
const bool cacheable = riscv_reg_impl_gdb_regno_cacheable(regid, need_to_write);
if (!cacheable || (write_through && need_to_write)) {
LOG_TARGET_DEBUG(target,
"Writing to target: %s <- 0x%" PRIx64 " (cacheable=%s, valid=%s, dirty=%s)",
reg->name, value, cacheable ? "true" : "false",
reg->valid ? "true" : "false",
reg->dirty ? "true" : "false");
if (riscv013_set_register(target, regid, value) != ERROR_OK)
return ERROR_FAIL;
reg->dirty = false;
} else {
reg->dirty = need_to_write;
}
buf_set_u64(reg->value, 0, reg->size, value);
reg->valid = cacheable;
LOG_TARGET_DEBUG(target,
"Wrote 0x%" PRIx64 " to %s (cacheable=%s, valid=%s, dirty=%s)",
value, reg->name, cacheable ? "true" : "false",
reg->valid ? "true" : "false",
reg->dirty ? "true" : "false");
return ERROR_OK;
} }
bool riscv_reg_cache_any_dirty(const struct target *target, int log_level) bool riscv_reg_cache_any_dirty(const struct target *target, int log_level)
@ -919,83 +826,49 @@ void riscv_reg_cache_invalidate_all(struct target *target)
/** /**
* This function is used to change the value of a register. The new value may * This function is used to change the value of a register. The new value may
* be cached, and may not be written until the hart is resumed. * be cached, and may not be written until the hart is resumed.
* TODO: Currently `reg->get/set` is implemented in terms of
* `riscv_get/set_register`. However, the intention behind
* `riscv_get/set_register` is to work with the cache, therefore it accesses
* and modifyes register cache directly. The idea is to implement
* `riscv_get/set_register` in terms of `riscv_reg_impl_cache_entry` and
* `reg->get/set`.
*/ */
int riscv_reg_set(struct target *target, enum gdb_regno regid, int riscv_reg_set(struct target *target, enum gdb_regno regid,
riscv_reg_t value) riscv_reg_t value)
{ {
return riscv_set_or_write_register(target, regid, value, struct reg * const reg = riscv_reg_impl_cache_entry(target, regid);
/* write_through */ false); assert(riscv_reg_impl_is_initialized(reg));
assert(reg->size <= sizeof(riscv_reg_t) * CHAR_BIT);
assert(reg->size <= 64);
uint8_t buf[64 / 8];
buf_set_u64(buf, 0, reg->size, value);
return reg->type->set(reg, buf);
} }
/** /**
* This function is used to change the value of a register. The new value may * This function is used to change the value of a register. The new value may
* be cached, but it will be written to hart immediately. * be cached, but it will be written to hart immediately.
* TODO: Currently `reg->get/set` is implemented in terms of
* `riscv_get/set_register`. However, the intention behind
* `riscv_get/set_register` is to work with the cache, therefore it accesses
* and modifyes register cache directly. The idea is to implement
* `riscv_get/set_register` in terms of `riscv_reg_impl_cache_entry` and
* `reg->get/set`.
*/ */
int riscv_reg_write(struct target *target, enum gdb_regno regid, int riscv_reg_write(struct target *target, enum gdb_regno regid,
riscv_reg_t value) riscv_reg_t value)
{ {
return riscv_set_or_write_register(target, regid, value, int res = riscv_reg_set(target, regid, value);
/* write_through */ true); if (res != ERROR_OK)
return res;
struct reg * const reg = riscv_reg_impl_cache_entry(target, regid);
return reg->type->flush(reg);
} }
/** /**
* This function is used to get the value of a register. If possible, the value * This function is used to get the value of a register. If possible, the value
* in cache will be updated. * in cache will be updated.
* TODO: Currently `reg->get/set` is implemented in terms of
* `riscv_get/set_register`. However, the intention behind
* `riscv_get/set_register` is to work with the cache, therefore it accesses
* and modifyes register cache directly. The idea is to implement
* `riscv_get/set_register` in terms of `riscv_reg_impl_cache_entry` and
* `reg->get/set`.
*/ */
int riscv_reg_get(struct target *target, riscv_reg_t *value, int riscv_reg_get(struct target *target, riscv_reg_t *value,
enum gdb_regno regid) enum gdb_regno regid)
{ {
RISCV_INFO(r); assert(value);
assert(r);
if (r->dtm_version == DTM_DTMCS_VERSION_0_11)
return riscv011_get_register(target, value, regid);
keep_alive(); struct reg * const reg = riscv_reg_impl_cache_entry(target, regid);
if (regid == GDB_REGNO_PC)
return riscv_reg_get(target, value, GDB_REGNO_DPC);
struct reg *reg = riscv_reg_impl_cache_entry(target, regid);
assert(riscv_reg_impl_is_initialized(reg)); assert(riscv_reg_impl_is_initialized(reg));
if (!reg->exist) { assert(reg->size <= sizeof(riscv_reg_t) * CHAR_BIT);
LOG_TARGET_DEBUG(target, "Register %s does not exist.", reg->name); assert(reg->size <= 64);
return ERROR_FAIL; int res = reg->type->get(reg);
} if (res != ERROR_OK)
return res;
if (reg->valid) { *value = buf_get_u64(reg->value, 0, reg->size);
*value = buf_get_u64(reg->value, 0, reg->size);
LOG_TARGET_DEBUG(target, "Read %s: 0x%" PRIx64 " (cached)", reg->name,
*value);
return ERROR_OK;
}
LOG_TARGET_DEBUG(target, "Reading %s from target", reg->name);
if (riscv013_get_register(target, value, regid) != ERROR_OK)
return ERROR_FAIL;
buf_set_u64(reg->value, 0, reg->size, *value);
reg->valid = riscv_reg_impl_gdb_regno_cacheable(regid, /* is write? */ false) &&
target->state == TARGET_HALTED;
reg->dirty = false;
LOG_TARGET_DEBUG(target, "Read %s: 0x%" PRIx64, reg->name, *value);
return ERROR_OK; return ERROR_OK;
} }

View File

@ -171,54 +171,4 @@ int riscv_reg_impl_expose_csrs(const struct target *target);
/** Hide additional CSRs, as specified by `riscv_info_t::hide_csr` list. */ /** Hide additional CSRs, as specified by `riscv_info_t::hide_csr` list. */
void riscv_reg_impl_hide_csrs(const struct target *target); void riscv_reg_impl_hide_csrs(const struct target *target);
/**
* If write is true:
* return true iff we are guaranteed that the register will contain exactly
* the value we just wrote when it's read.
* If write is false:
* return true iff we are guaranteed that the register will read the same
* value in the future as the value we just read.
*/
static inline bool riscv_reg_impl_gdb_regno_cacheable(enum gdb_regno regno,
bool is_write)
{
if (regno == GDB_REGNO_ZERO)
return !is_write;
/* GPRs, FPRs, vector registers are just normal data stores. */
if (regno <= GDB_REGNO_XPR31 ||
(regno >= GDB_REGNO_FPR0 && regno <= GDB_REGNO_FPR31) ||
(regno >= GDB_REGNO_V0 && regno <= GDB_REGNO_V31))
return true;
/* Most CSRs won't change value on us, but we can't assume it about arbitrary
* CSRs. */
switch (regno) {
case GDB_REGNO_DPC:
case GDB_REGNO_VSTART:
case GDB_REGNO_VXSAT:
case GDB_REGNO_VXRM:
case GDB_REGNO_VLENB:
case GDB_REGNO_VL:
case GDB_REGNO_VTYPE:
case GDB_REGNO_MISA:
case GDB_REGNO_DCSR:
case GDB_REGNO_DSCRATCH0:
case GDB_REGNO_MSTATUS:
case GDB_REGNO_MEPC:
case GDB_REGNO_MCAUSE:
case GDB_REGNO_SATP:
/*
* WARL registers might not contain the value we just wrote, but
* these ones won't spontaneously change their value either. *
*/
return !is_write;
case GDB_REGNO_TSELECT: /* I think this should be above, but then it doesn't work. */
case GDB_REGNO_TDATA1: /* Changes value when tselect is changed. */
case GDB_REGNO_TDATA2: /* Changes value when tselect is changed. */
default:
return false;
}
}
#endif /* OPENOCD_TARGET_RISCV_RISCV_REG_IMPL_H */ #endif /* OPENOCD_TARGET_RISCV_RISCV_REG_IMPL_H */