Files
openocd/src/target/riscv/riscv-013_reg.c
Bernhard Rosenkränzer 5754aebc49 target: riscv: Sync with the RISC-V fork
Regenerate autogenerated debug_defines.{c,h} files from current
riscv-debug-spec, sync remaining RISC-V target files with the
RISC-V fork.

This is based on the work of (in alphabetic order):

Aleksey Lotosh <lotosh@gmail.com>
Alexander Rumyantsev <cetygamer@gmail.com>
Anastasiya Chernikova <anastasiya.chernikova@syntacore.com>
Anatoly Parshintsev <114445139+aap-sc@users.noreply.github.com>
Bernhard Rosenkränzer <bero@baylibre.com>
bluew <bluewww@users.noreply.github.com>
Carsten Gosvig <40368726+cgsfv@users.noreply.github.com>
cgsfv <cgsfv@users.noreply.github.com>
Craig Blackmore <craig.blackmore@embecosm.com>
Dan Robertson <danlrobertson89@gmail.com>
Darius Rad <darius@bluespec.com>
dave-estes-syzexion <53795406+dave-estes-syzexion@users.noreply.github.com>
Dmitry Ryzhov <dmitry.ryzhov@cloudbear.ru>
Dolu1990 <charles.papon.90@gmail.com>
Emmanuel Blot <emmanuel.blot@free.fr>
Ernie Edgar <43148441+ernie-sifive@users.noreply.github.com>
Evgeniy Naydanov <evgeniy.naydanov@syntacore.com>
Farid Khaydari <f.khaydari@syntacore.com>
Gleb Gagarin <gleb@sifive.com>
Greg Savin <43152568+SiFiveGregS@users.noreply.github.com>
Hang Xu <xuhang@eswincomputing.com>
Hsiangkai <Hsiangkai@gmail.com>
Jan Matyas <jan.matyas@codasip.com>
jhjung81 <48940114+jhjung81@users.noreply.github.com>
Jiuyang Liu <liu@jiuyang.me>
Kaspar Schleiser <kaspar@schleiser.de>
Khem Raj <raj.khem@gmail.com>
Kirill Radkin <kirill.radkin@syntacore.com>
liangzhen <zhen.liang@spacemit.com>
Liviu Ionescu <ilg@livius.net>
Marc Schink <openocd-dev@marcschink.de>
Megan Wachs <megan@sifive.com>
Nils Wistoff <git@wistoff.net>
Palmer Dabbelt <palmer@dabbelt.com>
panciyan <panciyan@eswincomputing.com>
Parshintsev Anatoly <anatoly.parshintsev@syntacore.com>
Paul George <command.paul@gmail.com>
Pavel S. Smirnov <Paul.Smirnov.aka.sps@gmail.com>
Philipp Wagner <mail@philipp-wagner.com>
Ryan Macdonald <rmac@sifive.com>
Samuel Obuch <samuel.obuch17@gmail.com>
Tarek BOCHKATI <tarek.bouchkati@gmail.com>
Tim Newsome <tim@casualhacker.net>
Tobias Kaiser <mail@tb-kaiser.de>
Tom Hebb <tommyhebb@gmail.com>
Tommy Murphy <tommy_murphy@hotmail.com>
wxjstz <wxjstz@126.com>
wzgpeter <wzgpeter@outlook.com>
Xiang W <wxjstz@126.com>
zhusonghe <zhusonghe@eswincomputing.com>

Checkpatch-ignore MULTISTATEMENT_MACRO_USE_DO_WHILE is added to allow a
macro in riscv-013.c that can't use do/while because it expands to a
"case ...:" statement.

Checkpatch-ignore TRAILING_SEMICOLON is added to allow a construct in
riscv-013.c where a macro expands to either code (where it needs the
semicolon) or a member of an enum (where it needs a comma).

Checkpatch-ignore LONG_LINE_COMMENT and NEW_TYPEDEFS lines are added for
the sake of the autogenerated files from riscv-debug-spec.
All non-autogenerated files have been updated for checkpatch compliance.

Checkpatch-ignore: LONG_LINE_COMMENT
Checkpatch-ignore: NEW_TYPEDEFS
Checkpatch-ignore: MULTISTATEMENT_MACRO_USE_DO_WHILE
Checkpatch-ignore: TRAILING_SEMICOLON
Change-Id: Ie594915a4d6e6f9d9dad6016b176ab76409a099a
Signed-off-by: Bernhard Rosenkränzer <bero@baylibre.com>
Reviewed-on: https://review.openocd.org/c/openocd/+/8893
Tested-by: jenkins
Reviewed-by: Evgeniy Naydanov <evgeniy.naydanov@syntacore.com>
Reviewed-by: Tomas Vanek <vanekt@fbl.cz>
2025-11-12 20:14:47 +00:00

390 lines
11 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "riscv-013_reg.h"
#include "field_helpers.h"
#include "riscv_reg.h"
#include "riscv_reg_impl.h"
#include "riscv-013.h"
#include "debug_defines.h"
#include <helper/time_support.h>
static int riscv013_reg_get(struct reg *reg)
{
struct target *target = riscv_reg_impl_get_target(reg);
/* TODO: Hack to deal with gdb that thinks these registers still exist. */
if (reg->number > GDB_REGNO_XPR15 && reg->number <= GDB_REGNO_XPR31 &&
riscv_supports_extension(target, 'E')) {
buf_set_u64(reg->value, 0, reg->size, 0);
return ERROR_OK;
}
if (reg->number >= GDB_REGNO_V0 && reg->number <= GDB_REGNO_V31) {
if (riscv013_get_register_buf(target, reg->value, reg->number) != ERROR_OK)
return ERROR_FAIL;
reg->valid = riscv_reg_impl_gdb_regno_cacheable(reg->number, /* is write? */ false);
} else {
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);
LOG_TARGET_DEBUG(target, "Read 0x%s from %s (valid=%d).", str, reg->name,
reg->valid);
free(str);
return ERROR_OK;
}
static int riscv013_reg_set(struct reg *reg, uint8_t *buf)
{
struct target *target = riscv_reg_impl_get_target(reg);
char *str = buf_to_hex_str(buf, reg->size);
LOG_TARGET_DEBUG(target, "Write 0x%s to %s (valid=%d).", str, reg->name,
reg->valid);
free(str);
/* TODO: Hack to deal with gdb that thinks these registers still exist. */
if (reg->number > GDB_REGNO_XPR15 && reg->number <= GDB_REGNO_XPR31 &&
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;
}
static const struct reg_arch_type *riscv013_gdb_regno_reg_type(uint32_t regno)
{
static const struct reg_arch_type riscv013_reg_type = {
.get = riscv013_reg_get,
.set = riscv013_reg_set
};
return &riscv013_reg_type;
}
static int init_cache_entry(struct target *target, uint32_t regno)
{
struct reg * const reg = riscv_reg_impl_cache_entry(target, regno);
if (riscv_reg_impl_is_initialized(reg))
return ERROR_OK;
return riscv_reg_impl_init_cache_entry(target, regno,
riscv_reg_impl_gdb_regno_exist(target, regno),
riscv013_gdb_regno_reg_type(regno));
}
/**
* Some registers are optional (e.g. "misa"). For such registers it is first
* assumed they exist (via "assume_reg_exist()"), then the read is attempted
* (via the usual "riscv_reg_get()") and if the read fails, the register is
* marked as non-existing (via "riscv_reg_impl_set_exist()").
*/
static int assume_reg_exist(struct target *target, uint32_t regno)
{
return riscv_reg_impl_init_cache_entry(target, regno,
/* exist */ true, riscv013_gdb_regno_reg_type(regno));
}
static int examine_xlen(struct target *target)
{
RISCV_INFO(r);
unsigned int cmderr;
const uint32_t command = riscv013_access_register_command(target,
GDB_REGNO_S0, /* size */ 64, AC_ACCESS_REGISTER_TRANSFER);
int res = riscv013_execute_abstract_command(target, command, &cmderr);
if (res == ERROR_OK) {
r->xlen = 64;
return ERROR_OK;
}
if (res == ERROR_TIMEOUT_REACHED)
return ERROR_FAIL;
r->xlen = 32;
return ERROR_OK;
}
static int examine_vlenb(struct target *target)
{
RISCV_INFO(r);
/* Reading "vlenb" requires "mstatus.vs" to be set, so "mstatus" should
* be accessible.*/
int res = init_cache_entry(target, GDB_REGNO_MSTATUS);
if (res != ERROR_OK)
return res;
res = assume_reg_exist(target, GDB_REGNO_VLENB);
if (res != ERROR_OK)
return res;
riscv_reg_t vlenb_val;
if (riscv_reg_get(target, &vlenb_val, GDB_REGNO_VLENB) != ERROR_OK) {
if (riscv_supports_extension(target, 'V'))
LOG_TARGET_WARNING(target, "Couldn't read vlenb; vector register access won't work.");
r->vlenb = 0;
return riscv_reg_impl_set_exist(target, GDB_REGNO_VLENB, false);
}
/* As defined by RISC-V V extension specification:
* https://github.com/riscv/riscv-v-spec/blob/2f68ef7256d6ec53e4d2bd7cb12862f406d64e34/v-spec.adoc?plain=1#L67-L72 */
const unsigned int vlen_max = 65536;
const unsigned int vlenb_max = vlen_max / 8;
if (vlenb_val > vlenb_max) {
LOG_TARGET_WARNING(target, "'vlenb == %" PRIu64
"' is greater than maximum allowed by specification (%u); vector register access won't work.",
vlenb_val, vlenb_max);
r->vlenb = 0;
return ERROR_OK;
}
assert(vlenb_max <= UINT_MAX);
r->vlenb = (unsigned int)vlenb_val;
LOG_TARGET_INFO(target, "Vector support with vlenb=%u", r->vlenb);
return ERROR_OK;
}
enum misa_mxl {
MISA_MXL_INVALID = 0,
MISA_MXL_32 = 1,
MISA_MXL_64 = 2,
MISA_MXL_128 = 3
};
unsigned int mxl_to_xlen(enum misa_mxl mxl)
{
switch (mxl) {
case MISA_MXL_32:
return 32;
case MISA_MXL_64:
return 64;
case MISA_MXL_128:
return 128;
case MISA_MXL_INVALID:
assert(0);
}
return 0;
}
static int check_misa_mxl(const struct target *target)
{
RISCV_INFO(r);
if (r->misa == 0) {
LOG_TARGET_WARNING(target, "'misa' register is read as zero."
"OpenOCD will not be able to determine some hart's capabilities.");
return ERROR_OK;
}
const unsigned int dxlen = riscv_xlen(target);
assert(dxlen <= sizeof(riscv_reg_t) * CHAR_BIT);
assert(dxlen >= 2);
const riscv_reg_t misa_mxl_mask = (riscv_reg_t)0x3 << (dxlen - 2);
const unsigned int mxl = get_field(r->misa, misa_mxl_mask);
if (mxl == MISA_MXL_INVALID) {
/* This is not an error!
* Imagine the platform that:
* - Has no abstract access to CSRs, so that CSRs are read
* through Program Buffer via "csrr" instruction.
* - Complies to v1.10 of the Priveleged Spec, so that misa.mxl
* is WARL and MXLEN may be chainged.
* https://github.com/riscv/riscv-isa-manual/commit/9a7dd2fe29011587954560b5dcf1875477b27ad8
* - DXLEN == MXLEN on reset == 64.
* In a following scenario:
* - misa.mxl was written, so that MXLEN is 32.
* - Debugger connects to the target.
* - Debugger observes DXLEN == 64.
* - Debugger reads misa:
* - Abstract access fails with "cmderr == not supported".
* - Access via Program Buffer involves reading "misa" to an
* "xreg" via "csrr", so that the "xreg" is filled with
* zero-extended value of "misa" (since "misa" is
* MXLEN-wide).
* - Debugger derives "misa.mxl" assumig "misa" is DXLEN-bit
* wide (64) while MXLEN is 32 and therefore erroneously
* assumes "misa.mxl" to be zero (invalid).
*/
LOG_TARGET_WARNING(target, "Detected DXLEN (%u) does not match "
"MXLEN: misa.mxl == 0, misa == 0x%" PRIx64 ".",
dxlen, r->misa);
return ERROR_OK;
}
const unsigned int mxlen = mxl_to_xlen(mxl);
if (dxlen < mxlen) {
LOG_TARGET_ERROR(target,
"MXLEN (%u) reported in misa.mxl field exceeds "
"the detected DXLEN (%u)",
mxlen, dxlen);
return ERROR_FAIL;
}
/* NOTE:
* The value of "misa.mxl" may stil not coincide with "xlen".
* "misa[26:XLEN-3]" bits are marked as WIRI in at least version 1.10
* of RISC-V Priveleged Spec. Therefore, if "xlen" is erroneously
* assumed to be 32 when it actually is 64, "mxl" will be read from
* this WIRI field and may be equal to "MISA_MXL_32" by coincidence.
* This is not an issue though from the version 1.11 onward, since
* "misa[26:XLEN-3]" became WARL and equal to 0.
*/
/* Display this as early as possible to help people who are using
* really slow simulators. */
LOG_TARGET_DEBUG(target, " XLEN=%d, misa=0x%" PRIx64, riscv_xlen(target), r->misa);
return ERROR_OK;
}
static int examine_misa(struct target *target)
{
RISCV_INFO(r);
int res = init_cache_entry(target, GDB_REGNO_MISA);
if (res != ERROR_OK)
return res;
res = riscv_reg_get(target, &r->misa, GDB_REGNO_MISA);
if (res != ERROR_OK)
return res;
return check_misa_mxl(target);
}
static int examine_mtopi(struct target *target)
{
/* Assume the registers exist */
int res = assume_reg_exist(target, GDB_REGNO_MTOPI);
if (res != ERROR_OK)
return res;
res = assume_reg_exist(target, GDB_REGNO_MTOPEI);
if (res != ERROR_OK)
return res;
riscv_reg_t value;
if (riscv_reg_get(target, &value, GDB_REGNO_MTOPI) != ERROR_OK) {
res = riscv_reg_impl_set_exist(target, GDB_REGNO_MTOPI, false);
if (res != ERROR_OK)
return res;
return riscv_reg_impl_set_exist(target, GDB_REGNO_MTOPEI, false);
}
if (riscv_reg_get(target, &value, GDB_REGNO_MTOPEI) != ERROR_OK) {
LOG_TARGET_INFO(target, "S?aia detected without IMSIC");
return riscv_reg_impl_set_exist(target, GDB_REGNO_MTOPEI, false);
}
LOG_TARGET_INFO(target, "S?aia detected with IMSIC");
return ERROR_OK;
}
/**
* This function assumes target's DM to be initialized (target is able to
* access DMs registers, execute program buffer, etc.)
*/
int riscv013_reg_examine_all(struct target *target)
{
int res = riscv_reg_impl_init_cache(target);
if (res != ERROR_OK)
return res;
init_shared_reg_info(target);
assert(target->state == TARGET_HALTED);
res = examine_xlen(target);
if (res != ERROR_OK)
return res;
/* Reading CSRs may clobber "s0", "s1", so it should be possible to
* save them in cache. */
res = init_cache_entry(target, GDB_REGNO_S0);
if (res != ERROR_OK)
return res;
res = init_cache_entry(target, GDB_REGNO_S1);
if (res != ERROR_OK)
return res;
res = examine_misa(target);
if (res != ERROR_OK)
return res;
res = examine_vlenb(target);
if (res != ERROR_OK)
return res;
riscv_reg_impl_init_vector_reg_type(target);
res = examine_mtopi(target);
if (res != ERROR_OK)
return res;
for (uint32_t regno = 0; regno < target->reg_cache->num_regs; ++regno) {
res = init_cache_entry(target, regno);
if (res != ERROR_OK)
return res;
}
res = riscv_reg_impl_expose_csrs(target);
if (res != ERROR_OK)
return res;
riscv_reg_impl_hide_csrs(target);
return ERROR_OK;
}
/**
* This function is used to save the value of a register in cache. The register
* is marked as dirty, and writeback is delayed for as long as possible.
*/
int riscv013_reg_save(struct target *target, enum gdb_regno regid)
{
if (target->state != TARGET_HALTED) {
LOG_TARGET_ERROR(target, "Can't save register %s on a hart that is not halted.",
riscv_reg_gdb_regno_name(target, regid));
return ERROR_FAIL;
}
assert(riscv_reg_impl_gdb_regno_cacheable(regid, /* is write? */ false) &&
"Only cacheable registers can be saved.");
RISCV_INFO(r);
riscv_reg_t value;
if (!target->reg_cache) {
assert(!target_was_examined(target));
/* To create register cache it is needed to examine the target first,
* 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);
if (riscv_reg_get(target, &value, regid) != ERROR_OK)
return ERROR_FAIL;
assert(reg->valid &&
"The register is cacheable, so the cache entry must be valid now.");
/* Mark the register dirty. We assume that this function is called
* because the caller is about to mess with the underlying value of the
* register. */
reg->dirty = true;
r->last_activity = timeval_ms();
return ERROR_OK;
}