X-Git-Url: https://git.gag.com/?a=blobdiff_plain;f=src%2Ftarget%2Friscv%2Friscv-013.c;h=1b1450a7dc7f62e84c6fd88c0e039c3a78221770;hb=7d2ea186cf212c64a8e5b10725e0a2512a137fbe;hp=32a7f024842ad998267c2fe34eb4839aefc92cb0;hpb=7e64e5a895ecd9bf25c5d2b39ff3119dafa30489;p=fw%2Fopenocd diff --git a/src/target/riscv/riscv-013.c b/src/target/riscv/riscv-013.c index 32a7f0248..1b1450a7d 100644 --- a/src/target/riscv/riscv-013.c +++ b/src/target/riscv/riscv-013.c @@ -16,7 +16,7 @@ #include "target/target.h" #include "target/algorithm.h" #include "target/target_type.h" -#include "log.h" +#include #include "jtag/jtag.h" #include "target/register.h" #include "target/breakpoints.h" @@ -29,9 +29,6 @@ #include "asm.h" #include "batch.h" -#define DM_DATA1 (DM_DATA0 + 1) -#define DM_PROGBUF1 (DM_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, bool use_hasel); @@ -39,8 +36,8 @@ 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); + riscv_reg_t *value, int rid); +static int riscv013_set_register(struct target *target, int regid, uint64_t value); static int riscv013_select_current_hart(struct target *target); static int riscv013_halt_prep(struct target *target); static int riscv013_halt_go(struct target *target); @@ -74,7 +71,6 @@ void write_memory_sba_simple(struct target *target, target_addr_t addr, uint32_t uint32_t write_size, uint32_t sbcs); void read_memory_sba_simple(struct target *target, target_addr_t addr, uint32_t *rd_buf, uint32_t read_size, uint32_t sbcs); -static int riscv013_test_compliance(struct target *target); /** * Since almost everything can be accomplish by scanning the dbus register, all @@ -86,8 +82,6 @@ static int riscv013_test_compliance(struct target *target); #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 @@ -208,6 +202,8 @@ typedef struct { bool abstract_read_fpr_supported; bool abstract_write_fpr_supported; + yes_no_maybe_t has_aampostincrement; + /* 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.) */ @@ -230,6 +226,8 @@ LIST_HEAD(dm_list); static riscv013_info_t *get_info(const struct target *target) { riscv_info_t *info = (riscv_info_t *) target->arch_info; + assert(info); + assert(info->version_specific); return (riscv013_info_t *) info->version_specific; } @@ -358,7 +356,7 @@ static void decode_dmi(char *text, unsigned address, unsigned data) }; text[0] = 0; - for (unsigned i = 0; i < DIM(description); i++) { + for (unsigned i = 0; i < ARRAY_SIZE(description); i++) { if (description[i].address == address) { uint64_t mask = description[i].mask; unsigned value = get_field(data, mask); @@ -590,6 +588,8 @@ static int dmi_op_timeout(struct target *target, uint32_t *data_in, return ERROR_FAIL; } + keep_alive(); + time_t start = time(NULL); /* This first loop performs the request. Note that if for some reason this * stays busy, it is actually due to the previous access. */ @@ -692,7 +692,7 @@ int dmstatus_read_timeout(struct target *target, uint32_t *dmstatus, return result; int dmstatus_version = get_field(*dmstatus, DM_DMSTATUS_VERSION); if (dmstatus_version != 2 && dmstatus_version != 3) { - LOG_ERROR("OpenOCD only supports Debug Module version 2 (0.13) and 3 (0.14), not " + LOG_ERROR("OpenOCD only supports Debug Module version 2 (0.13) and 3 (1.0), not " "%d (dmstatus=0x%x). This error might be caused by a JTAG " "signal issue. Try reducing the JTAG clock speed.", get_field(*dmstatus, DM_DMSTATUS_VERSION), *dmstatus); @@ -1199,10 +1199,7 @@ static int scratch_reserve(struct target *target, static int scratch_release(struct target *target, scratch_mem_t *scratch) { - if (scratch->area) - return target_free_working_area(target, scratch->area); - - return ERROR_OK; + return target_free_working_area(target, scratch->area); } static int scratch_read64(struct target *target, scratch_mem_t *scratch, @@ -1326,7 +1323,7 @@ static int register_write_direct(struct target *target, unsigned number, scratch_mem_t scratch; bool use_scratch = false; if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31 && - riscv_supports_extension(target, riscv_current_hartid(target), 'D') && + 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. */ @@ -1356,7 +1353,7 @@ static int register_write_direct(struct target *target, unsigned number, return ERROR_FAIL; if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) { - if (riscv_supports_extension(target, riscv_current_hartid(target), 'D')) + 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)); @@ -1397,7 +1394,7 @@ static int register_write_direct(struct target *target, unsigned number, return exec_out; } -/** Return the cached value, or read from the target if necessary. */ +/** Read register value from the target. Also update the cached value. */ static int register_read(struct target *target, uint64_t *value, uint32_t number) { if (number == GDB_REGNO_ZERO) { @@ -1440,7 +1437,7 @@ static int register_read_direct(struct target *target, uint64_t *value, uint32_t return ERROR_FAIL; if (number >= GDB_REGNO_FPR0 && number <= GDB_REGNO_FPR31) { - if (riscv_supports_extension(target, riscv_current_hartid(target), 'D') + 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. */ @@ -1456,8 +1453,7 @@ static int register_read_direct(struct target *target, uint64_t *value, uint32_t scratch_release(target, &scratch); return ERROR_FAIL; } - } else if (riscv_supports_extension(target, - riscv_current_hartid(target), 'D')) { + } 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)); @@ -1500,7 +1496,7 @@ static int register_read_direct(struct target *target, uint64_t *value, uint32_t return result; } -int wait_for_authbusy(struct target *target, uint32_t *dmstatus) +static int wait_for_authbusy(struct target *target, uint32_t *dmstatus) { time_t start = time(NULL); while (1) { @@ -1546,7 +1542,7 @@ static int set_haltgroup(struct target *target, bool *supported) return ERROR_OK; } -static int discover_vlenb(struct target *target, int hartid) +static int discover_vlenb(struct target *target) { RISCV_INFO(r); riscv_reg_t vlenb; @@ -1554,12 +1550,12 @@ static int discover_vlenb(struct target *target, int hartid) if (register_read(target, &vlenb, GDB_REGNO_VLENB) != ERROR_OK) { LOG_WARNING("Couldn't read vlenb for %s; vector register access won't work.", target_name(target)); - r->vlenb[hartid] = 0; + r->vlenb = 0; return ERROR_OK; } - r->vlenb[hartid] = vlenb; + r->vlenb = vlenb; - LOG_INFO("hart %d: Vector support with vlenb=%d", hartid, r->vlenb[hartid]); + LOG_INFO("Vector support with vlenb=%d", r->vlenb); return ERROR_OK; } @@ -1707,6 +1703,8 @@ static int examine(struct target *target) LOG_DEBUG("Detected %d harts.", dm->hart_count); } + r->current_hartid = target->coreid; + if (dm->hart_count == 0) { LOG_ERROR("No harts found!"); return ERROR_FAIL; @@ -1714,54 +1712,49 @@ static int examine(struct target *target) /* Don't call any riscv_* functions until after we've counted the number of * cores and initialized registers. */ - for (int i = 0; i < dm->hart_count; ++i) { - if (!riscv_rtos_enabled(target) && i != target->coreid) - continue; - r->current_hartid = i; - if (riscv013_select_current_hart(target) != ERROR_OK) - return ERROR_FAIL; + if (riscv013_select_current_hart(target) != ERROR_OK) + return ERROR_FAIL; - bool halted = riscv_is_halted(target); - if (!halted) { - if (riscv013_halt_go(target) != ERROR_OK) { - LOG_ERROR("Fatal: Hart %d failed to halt during examine()", i); - return ERROR_FAIL; - } + bool halted = riscv_is_halted(target); + if (!halted) { + if (riscv013_halt_go(target) != ERROR_OK) { + LOG_ERROR("Fatal: Hart %d failed to halt during examine()", r->current_hartid); + return ERROR_FAIL; } + } - /* Without knowing anything else we can at least mess with the - * program buffer. */ - r->debug_buffer_size[i] = info->progbufsize; + /* Without knowing anything else we can at least mess with the + * program buffer. */ + r->debug_buffer_size = 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(target, &r->misa[i], GDB_REGNO_MISA)) { - LOG_ERROR("Fatal: Failed to read MISA from hart %d.", i); - return ERROR_FAIL; - } + int result = register_read_abstract(target, NULL, GDB_REGNO_S0, 64); + if (result == ERROR_OK) + r->xlen = 64; + else + r->xlen = 32; - if (riscv_supports_extension(target, i, 'V')) { - if (discover_vlenb(target, i) != ERROR_OK) - return ERROR_FAIL; - } + if (register_read(target, &r->misa, GDB_REGNO_MISA)) { + LOG_ERROR("Fatal: Failed to read MISA from hart %d.", r->current_hartid); + return ERROR_FAIL; + } - /* Now init registers based on what we discovered. */ - if (riscv_init_registers(target) != ERROR_OK) + if (riscv_supports_extension(target, 'V')) { + if (discover_vlenb(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[i]); + /* Now init registers based on what we discovered. */ + if (riscv_init_registers(target) != ERROR_OK) + return ERROR_FAIL; - if (!halted) - riscv013_step_or_resume_current_hart(target, false, false); - } + /* Display this as early as possible to help people who are using + * really slow simulators. */ + LOG_DEBUG(" hart %d: XLEN=%d, misa=0x%" PRIx64, r->current_hartid, r->xlen, + r->misa); + + if (!halted) + riscv013_step_or_resume_current_hart(target, false, false); target_set_examined(target); @@ -1782,27 +1775,31 @@ static int examine(struct target *target) * 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, misa=0x%" PRIx64, i, r->xlen[i], - r->misa[i]); - } else { - LOG_INFO(" hart %d: currently disabled", i); - } - } + LOG_INFO(" hart %d: XLEN=%d, misa=0x%" PRIx64, r->current_hartid, r->xlen, + r->misa); return ERROR_OK; } -int riscv013_authdata_read(struct target *target, uint32_t *value) +static int riscv013_authdata_read(struct target *target, uint32_t *value, unsigned int index) { + if (index > 0) { + LOG_ERROR("Spec 0.13 only has a single authdata register."); + return ERROR_FAIL; + } + if (wait_for_authbusy(target, NULL) != ERROR_OK) return ERROR_FAIL; return dmi_read(target, value, DM_AUTHDATA); } -int riscv013_authdata_write(struct target *target, uint32_t value) +static int riscv013_authdata_write(struct target *target, uint32_t value, unsigned int index) { + if (index > 0) { + LOG_ERROR("Spec 0.13 only has a single authdata register."); + return ERROR_FAIL; + } + uint32_t before, after; if (wait_for_authbusy(target, &before) != ERROR_OK) return ERROR_FAIL; @@ -1837,27 +1834,74 @@ static int riscv013_hart_count(struct target *target) return dm->hart_count; } +/* Try to find out the widest memory access size depending on the selected memory access methods. */ static unsigned riscv013_data_bits(struct target *target) { RISCV013_INFO(info); - /* TODO: Once there is a spec for discovering abstract commands, we can - * take those into account as well. For now we assume abstract commands - * support XLEN-wide accesses. */ - if (has_sufficient_progbuf(target, 3) && !riscv_prefer_sba) - return riscv_xlen(target); + RISCV_INFO(r); - if (get_field(info->sbcs, DM_SBCS_SBACCESS128)) - return 128; - if (get_field(info->sbcs, DM_SBCS_SBACCESS64)) - return 64; - if (get_field(info->sbcs, DM_SBCS_SBACCESS32)) - return 32; - if (get_field(info->sbcs, DM_SBCS_SBACCESS16)) - return 16; - if (get_field(info->sbcs, DM_SBCS_SBACCESS8)) - return 8; - - return riscv_xlen(target); + for (unsigned int i = 0; i < RISCV_NUM_MEM_ACCESS_METHODS; i++) { + int method = r->mem_access_methods[i]; + + if (method == RISCV_MEM_ACCESS_PROGBUF) { + if (has_sufficient_progbuf(target, 3)) + return riscv_xlen(target); + } else if (method == RISCV_MEM_ACCESS_SYSBUS) { + if (get_field(info->sbcs, DM_SBCS_SBACCESS128)) + return 128; + if (get_field(info->sbcs, DM_SBCS_SBACCESS64)) + return 64; + if (get_field(info->sbcs, DM_SBCS_SBACCESS32)) + return 32; + if (get_field(info->sbcs, DM_SBCS_SBACCESS16)) + return 16; + if (get_field(info->sbcs, DM_SBCS_SBACCESS8)) + return 8; + } else if (method == RISCV_MEM_ACCESS_ABSTRACT) { + /* TODO: Once there is a spec for discovering abstract commands, we can + * take those into account as well. For now we assume abstract commands + * support XLEN-wide accesses. */ + return riscv_xlen(target); + } else if (method == RISCV_MEM_ACCESS_UNSPECIFIED) + /* No further mem access method to try. */ + break; + } + LOG_ERROR("Unable to determine supported data bits on this target. Assuming 32 bits."); + return 32; +} + +COMMAND_HELPER(riscv013_print_info, struct target *target) +{ + RISCV013_INFO(info); + + /* Abstract description. */ + riscv_print_info_line(CMD, "target", "memory.read_while_running8", get_field(info->sbcs, DM_SBCS_SBACCESS8)); + riscv_print_info_line(CMD, "target", "memory.write_while_running8", get_field(info->sbcs, DM_SBCS_SBACCESS8)); + riscv_print_info_line(CMD, "target", "memory.read_while_running16", get_field(info->sbcs, DM_SBCS_SBACCESS16)); + riscv_print_info_line(CMD, "target", "memory.write_while_running16", get_field(info->sbcs, DM_SBCS_SBACCESS16)); + riscv_print_info_line(CMD, "target", "memory.read_while_running32", get_field(info->sbcs, DM_SBCS_SBACCESS32)); + riscv_print_info_line(CMD, "target", "memory.write_while_running32", get_field(info->sbcs, DM_SBCS_SBACCESS32)); + riscv_print_info_line(CMD, "target", "memory.read_while_running64", get_field(info->sbcs, DM_SBCS_SBACCESS64)); + riscv_print_info_line(CMD, "target", "memory.write_while_running64", get_field(info->sbcs, DM_SBCS_SBACCESS64)); + riscv_print_info_line(CMD, "target", "memory.read_while_running128", get_field(info->sbcs, DM_SBCS_SBACCESS128)); + riscv_print_info_line(CMD, "target", "memory.write_while_running128", get_field(info->sbcs, DM_SBCS_SBACCESS128)); + + /* Lower level description. */ + riscv_print_info_line(CMD, "dm", "abits", info->abits); + riscv_print_info_line(CMD, "dm", "progbufsize", info->progbufsize); + riscv_print_info_line(CMD, "dm", "sbversion", get_field(info->sbcs, DM_SBCS_SBVERSION)); + riscv_print_info_line(CMD, "dm", "sbasize", get_field(info->sbcs, DM_SBCS_SBASIZE)); + riscv_print_info_line(CMD, "dm", "sbaccess128", get_field(info->sbcs, DM_SBCS_SBACCESS128)); + riscv_print_info_line(CMD, "dm", "sbaccess64", get_field(info->sbcs, DM_SBCS_SBACCESS64)); + riscv_print_info_line(CMD, "dm", "sbaccess32", get_field(info->sbcs, DM_SBCS_SBACCESS32)); + riscv_print_info_line(CMD, "dm", "sbaccess16", get_field(info->sbcs, DM_SBCS_SBACCESS16)); + riscv_print_info_line(CMD, "dm", "sbaccess8", get_field(info->sbcs, DM_SBCS_SBACCESS8)); + + uint32_t dmstatus; + if (dmstatus_read(target, &dmstatus, false) == ERROR_OK) + riscv_print_info_line(CMD, "dm", "authenticated", get_field(dmstatus, DM_DMSTATUS_AUTHENTICATED)); + + return 0; } static int prep_for_vector_access(struct target *target, uint64_t *vtype, @@ -1887,8 +1931,7 @@ static int prep_for_vector_access(struct target *target, uint64_t *vtype, if (register_write_direct(target, GDB_REGNO_VTYPE, encoded_vsew << 3) != ERROR_OK) return ERROR_FAIL; - *debug_vl = DIV_ROUND_UP(r->vlenb[r->current_hartid] * 8, - riscv_xlen(target)); + *debug_vl = DIV_ROUND_UP(r->vlenb * 8, riscv_xlen(target)); if (register_write_direct(target, GDB_REGNO_VL, *debug_vl) != ERROR_OK) return ERROR_FAIL; @@ -1911,6 +1954,9 @@ static int riscv013_get_register_buf(struct target *target, { assert(regno >= GDB_REGNO_V0 && regno <= GDB_REGNO_V31); + if (riscv_select_current_hart(target) != ERROR_OK) + return ERROR_FAIL; + riscv_reg_t s0; if (register_read(target, &s0, GDB_REGNO_S0) != ERROR_OK) return ERROR_FAIL; @@ -1967,6 +2013,9 @@ static int riscv013_set_register_buf(struct target *target, { assert(regno >= GDB_REGNO_V0 && regno <= GDB_REGNO_V31); + if (riscv_select_current_hart(target) != ERROR_OK) + return ERROR_FAIL; + riscv_reg_t s0; if (register_read(target, &s0, GDB_REGNO_S0) != ERROR_OK) return ERROR_FAIL; @@ -2007,11 +2056,227 @@ static int riscv013_set_register_buf(struct target *target, return result; } +static uint32_t sb_sbaccess(unsigned int size_bytes) +{ + switch (size_bytes) { + case 1: + return set_field(0, DM_SBCS_SBACCESS, 0); + case 2: + return set_field(0, DM_SBCS_SBACCESS, 1); + case 4: + return set_field(0, DM_SBCS_SBACCESS, 2); + case 8: + return set_field(0, DM_SBCS_SBACCESS, 3); + case 16: + return set_field(0, DM_SBCS_SBACCESS, 4); + } + assert(0); + return 0; +} + +static int sb_write_address(struct target *target, target_addr_t address, + bool ensure_success) +{ + RISCV013_INFO(info); + unsigned int sbasize = get_field(info->sbcs, DM_SBCS_SBASIZE); + /* There currently is no support for >64-bit addresses in OpenOCD. */ + if (sbasize > 96) + dmi_op(target, NULL, NULL, DMI_OP_WRITE, DM_SBADDRESS3, 0, false, false); + if (sbasize > 64) + dmi_op(target, NULL, NULL, DMI_OP_WRITE, DM_SBADDRESS2, 0, false, false); + if (sbasize > 32) + dmi_op(target, NULL, NULL, DMI_OP_WRITE, DM_SBADDRESS1, address >> 32, false, false); + return dmi_op(target, NULL, NULL, DMI_OP_WRITE, DM_SBADDRESS0, address, + false, ensure_success); +} + +static int batch_run(const struct target *target, struct riscv_batch *batch) +{ + RISCV013_INFO(info); + RISCV_INFO(r); + if (r->reset_delays_wait >= 0) { + r->reset_delays_wait -= batch->used_scans; + if (r->reset_delays_wait <= 0) { + batch->idle_count = 0; + info->dmi_busy_delay = 0; + info->ac_busy_delay = 0; + } + } + return riscv_batch_run(batch); +} + +static int sba_supports_access(struct target *target, unsigned int size_bytes) +{ + RISCV013_INFO(info); + switch (size_bytes) { + case 1: + return get_field(info->sbcs, DM_SBCS_SBACCESS8); + case 2: + return get_field(info->sbcs, DM_SBCS_SBACCESS16); + case 4: + return get_field(info->sbcs, DM_SBCS_SBACCESS32); + case 8: + return get_field(info->sbcs, DM_SBCS_SBACCESS64); + case 16: + return get_field(info->sbcs, DM_SBCS_SBACCESS128); + default: + return 0; + } +} + +static int sample_memory_bus_v1(struct target *target, + struct riscv_sample_buf *buf, + const riscv_sample_config_t *config, + int64_t until_ms) +{ + RISCV013_INFO(info); + unsigned int sbasize = get_field(info->sbcs, DM_SBCS_SBASIZE); + if (sbasize > 64) { + LOG_ERROR("Memory sampling is only implemented for sbasize <= 64."); + return ERROR_NOT_IMPLEMENTED; + } + + if (get_field(info->sbcs, DM_SBCS_SBVERSION) != 1) { + LOG_ERROR("Memory sampling is only implemented for SBA version 1."); + return ERROR_NOT_IMPLEMENTED; + } + + uint32_t sbcs = 0; + uint32_t sbcs_valid = false; + + uint32_t sbaddress0 = 0; + bool sbaddress0_valid = false; + uint32_t sbaddress1 = 0; + bool sbaddress1_valid = false; + + /* How often to read each value in a batch. */ + const unsigned int repeat = 5; + + unsigned int enabled_count = 0; + for (unsigned int i = 0; i < ARRAY_SIZE(config->bucket); i++) { + if (config->bucket[i].enabled) + enabled_count++; + } + + while (timeval_ms() < until_ms) { + /* + * batch_run() adds to the batch, so we can't simply reuse the same + * batch over and over. So we create a new one every time through the + * loop. + */ + struct riscv_batch *batch = riscv_batch_alloc( + target, 1 + enabled_count * 5 * repeat, + info->dmi_busy_delay + info->bus_master_read_delay); + if (!batch) + return ERROR_FAIL; + + unsigned int result_bytes = 0; + for (unsigned int n = 0; n < repeat; n++) { + for (unsigned int i = 0; i < ARRAY_SIZE(config->bucket); i++) { + if (config->bucket[i].enabled) { + if (!sba_supports_access(target, config->bucket[i].size_bytes)) { + LOG_ERROR("Hardware does not support SBA access for %d-byte memory sampling.", + config->bucket[i].size_bytes); + return ERROR_NOT_IMPLEMENTED; + } + + uint32_t sbcs_write = DM_SBCS_SBREADONADDR; + if (enabled_count == 1) + sbcs_write |= DM_SBCS_SBREADONDATA; + sbcs_write |= sb_sbaccess(config->bucket[i].size_bytes); + if (!sbcs_valid || sbcs_write != sbcs) { + riscv_batch_add_dmi_write(batch, DM_SBCS, sbcs_write); + sbcs = sbcs_write; + sbcs_valid = true; + } + + if (sbasize > 32 && + (!sbaddress1_valid || + sbaddress1 != config->bucket[i].address >> 32)) { + sbaddress1 = config->bucket[i].address >> 32; + riscv_batch_add_dmi_write(batch, DM_SBADDRESS1, sbaddress1); + sbaddress1_valid = true; + } + if (!sbaddress0_valid || + sbaddress0 != (config->bucket[i].address & 0xffffffff)) { + sbaddress0 = config->bucket[i].address; + riscv_batch_add_dmi_write(batch, DM_SBADDRESS0, sbaddress0); + sbaddress0_valid = true; + } + if (config->bucket[i].size_bytes > 4) + riscv_batch_add_dmi_read(batch, DM_SBDATA1); + riscv_batch_add_dmi_read(batch, DM_SBDATA0); + result_bytes += 1 + config->bucket[i].size_bytes; + } + } + } + + if (buf->used + result_bytes >= buf->size) { + riscv_batch_free(batch); + break; + } + + size_t sbcs_key = riscv_batch_add_dmi_read(batch, DM_SBCS); + + int result = batch_run(target, batch); + if (result != ERROR_OK) + return result; + + uint32_t sbcs_read = riscv_batch_get_dmi_read_data(batch, sbcs_key); + if (get_field(sbcs_read, DM_SBCS_SBBUSYERROR)) { + /* Discard this batch (too much hassle to try to recover partial + * data) and try again with a larger delay. */ + info->bus_master_read_delay += info->bus_master_read_delay / 10 + 1; + dmi_write(target, DM_SBCS, sbcs_read | DM_SBCS_SBBUSYERROR | DM_SBCS_SBERROR); + riscv_batch_free(batch); + continue; + } + if (get_field(sbcs_read, DM_SBCS_SBERROR)) { + /* The memory we're sampling was unreadable, somehow. Give up. */ + dmi_write(target, DM_SBCS, DM_SBCS_SBBUSYERROR | DM_SBCS_SBERROR); + riscv_batch_free(batch); + return ERROR_FAIL; + } + + unsigned int read = 0; + for (unsigned int n = 0; n < repeat; n++) { + for (unsigned int i = 0; i < ARRAY_SIZE(config->bucket); i++) { + if (config->bucket[i].enabled) { + assert(i < RISCV_SAMPLE_BUF_TIMESTAMP_BEFORE); + uint64_t value = 0; + if (config->bucket[i].size_bytes > 4) + value = ((uint64_t)riscv_batch_get_dmi_read_data(batch, read++)) << 32; + value |= riscv_batch_get_dmi_read_data(batch, read++); + + buf->buf[buf->used] = i; + buf_set_u64(buf->buf + buf->used + 1, 0, config->bucket[i].size_bytes * 8, value); + buf->used += 1 + config->bucket[i].size_bytes; + } + } + } + + riscv_batch_free(batch); + } + + return ERROR_OK; +} + +static int sample_memory(struct target *target, + struct riscv_sample_buf *buf, + riscv_sample_config_t *config, + int64_t until_ms) +{ + if (!config->enabled) + return ERROR_OK; + + return sample_memory_bus_v1(target, buf, config, until_ms); +} + 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; + RISCV_INFO(generic_info); generic_info->get_register = &riscv013_get_register; generic_info->set_register = &riscv013_set_register; @@ -2040,12 +2305,15 @@ static int init_target(struct command_context *cmd_ctx, generic_info->dmi_write = &dmi_write; generic_info->read_memory = read_memory; generic_info->test_sba_config_reg = &riscv013_test_sba_config_reg; - generic_info->test_compliance = &riscv013_test_compliance; generic_info->hart_count = &riscv013_hart_count; generic_info->data_bits = &riscv013_data_bits; - generic_info->version_specific = calloc(1, sizeof(riscv013_info_t)); - if (!generic_info->version_specific) - return ERROR_FAIL; + generic_info->print_info = &riscv013_print_info; + if (!generic_info->version_specific) { + generic_info->version_specific = calloc(1, sizeof(riscv013_info_t)); + if (!generic_info->version_specific) + return ERROR_FAIL; + } + generic_info->sample_memory = sample_memory; riscv013_info_t *info = get_info(target); info->progbufsize = -1; @@ -2065,6 +2333,8 @@ static int init_target(struct command_context *cmd_ctx, info->abstract_read_fpr_supported = true; info->abstract_write_fpr_supported = true; + info->has_aampostincrement = YNM_MAYBE; + return ERROR_OK; } @@ -2076,7 +2346,10 @@ static int assert_reset(struct target *target) uint32_t control_base = set_field(0, DM_DMCONTROL_DMACTIVE, 1); - if (target->rtos) { + if (target_has_event_action(target, TARGET_EVENT_RESET_ASSERT)) { + /* Run the user-supplied script if there is one. */ + target_handle_event(target, TARGET_EVENT_RESET_ASSERT); + } else if (target->rtos) { /* There's only one target, and OpenOCD thinks each hart is a thread. * We must reset them all. */ @@ -2084,15 +2357,12 @@ static int assert_reset(struct target *target) /* Set haltreq 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_hartsel(control_base, i); - control = set_field(control, DM_DMCONTROL_HALTREQ, - target->reset_halt ? 1 : 0); - dmi_write(target, DM_DMCONTROL, control); - } + control = set_hartsel(control_base, target->coreid); + control = set_field(control, DM_DMCONTROL_HALTREQ, + target->reset_halt ? 1 : 0); + dmi_write(target, DM_DMCONTROL, control); + /* Assert ndmreset */ control = set_field(control, DM_DMCONTROL_NDMRESET, 1); dmi_write(target, DM_DMCONTROL, control); @@ -2127,11 +2397,11 @@ static int deassert_reset(struct target *target) select_dmi(target); /* Clear the reset, but make sure haltreq is still set */ - uint32_t control = 0; - control = set_field(control, DM_DMCONTROL_HALTREQ, target->reset_halt ? 1 : 0); + uint32_t control = 0, control_haltreq; control = set_field(control, DM_DMCONTROL_DMACTIVE, 1); + control_haltreq = set_field(control, DM_DMCONTROL_HALTREQ, target->reset_halt ? 1 : 0); dmi_write(target, DM_DMCONTROL, - set_hartsel(control, r->current_hartid)); + set_hartsel(control_haltreq, r->current_hartid)); uint32_t dmstatus; int dmi_busy_delay = info->dmi_busy_delay; @@ -2140,24 +2410,15 @@ static int deassert_reset(struct target *target) for (int i = 0; i < riscv_count_harts(target); ++i) { int index = i; if (target->rtos) { - if (!riscv_hart_enabled(target, index)) + if (index != target->coreid) continue; dmi_write(target, DM_DMCONTROL, - set_hartsel(control, index)); + set_hartsel(control_haltreq, index)); } else { index = r->current_hartid; } - char *operation; - uint32_t expected_field; - if (target->reset_halt) { - operation = "halt"; - expected_field = DM_DMSTATUS_ALLHALTED; - } else { - operation = "run"; - expected_field = DM_DMSTATUS_ALLRUNNING; - } - LOG_DEBUG("Waiting for hart %d to %s out of reset.", index, operation); + LOG_DEBUG("Waiting for hart %d to come out of reset.", index); while (1) { int result = dmstatus_read_timeout(target, &dmstatus, true, riscv_reset_timeout_sec); @@ -2168,20 +2429,27 @@ static int deassert_reset(struct target *target) index, riscv_reset_timeout_sec); if (result != ERROR_OK) return result; - if (get_field(dmstatus, expected_field)) + /* Certain debug modules, like the one in GD32VF103 + * MCUs, violate the specification's requirement that + * each hart is in "exactly one of four states" and, + * during reset, report harts as both unavailable and + * halted/running. To work around this, we check for + * the absence of the unavailable state rather than + * the presence of any other state. */ + if (!get_field(dmstatus, DM_DMSTATUS_ALLUNAVAIL)) break; if (time(NULL) - start > riscv_reset_timeout_sec) { - LOG_ERROR("Hart %d didn't %s coming out of reset in %ds; " + LOG_ERROR("Hart %d didn't leave reset in %ds; " "dmstatus=0x%x; " "Increase the timeout with riscv set_reset_timeout_sec.", - index, operation, riscv_reset_timeout_sec, dmstatus); + index, riscv_reset_timeout_sec, dmstatus); return ERROR_FAIL; } } target->state = TARGET_HALTED; if (get_field(dmstatus, DM_DMSTATUS_ALLHAVERESET)) { - /* Ack reset. */ + /* Ack reset and clear DM_DMCONTROL_HALTREQ if previously set */ dmi_write(target, DM_DMCONTROL, set_hartsel(control, index) | DM_DMCONTROL_ACKHAVERESET); @@ -2196,8 +2464,6 @@ static int deassert_reset(struct target *target) static int execute_fence(struct target *target) { - int old_hartid = riscv_current_hartid(target); - /* FIXME: For non-coherent systems we need to flush the caches right * here, but there's no ISA-defined way of doing that. */ { @@ -2210,27 +2476,6 @@ static int execute_fence(struct target *target) LOG_DEBUG("Unable to execute pre-fence"); } - for (int i = 0; i < riscv_count_harts(target); ++i) { - if (!riscv_hart_enabled(target, i)) - continue; - - if (i == old_hartid) - /* Fence already executed for this hart */ - continue; - - riscv_set_current_hartid(target, i); - - struct riscv_program program; - riscv_program_init(&program, target); - riscv_program_fence_i(&program); - riscv_program_fence(&program); - int result = riscv_program_exec(&program, target); - if (result != ERROR_OK) - LOG_DEBUG("Unable to execute fence on hart %d", i); - } - - riscv_set_current_hartid(target, old_hartid); - return ERROR_OK; } @@ -2280,24 +2525,6 @@ static int read_memory_bus_word(struct target *target, target_addr_t address, return ERROR_OK; } -static uint32_t sb_sbaccess(unsigned size_bytes) -{ - switch (size_bytes) { - case 1: - return set_field(0, DM_SBCS_SBACCESS, 0); - case 2: - return set_field(0, DM_SBCS_SBACCESS, 1); - case 4: - return set_field(0, DM_SBCS_SBACCESS, 2); - case 8: - return set_field(0, DM_SBCS_SBACCESS, 3); - case 16: - return set_field(0, DM_SBCS_SBACCESS, 4); - } - assert(0); - return 0; /* Make mingw happy. */ -} - static target_addr_t sb_read_address(struct target *target) { RISCV013_INFO(info); @@ -2314,20 +2541,6 @@ static target_addr_t sb_read_address(struct target *target) return address; } -static int sb_write_address(struct target *target, target_addr_t address) -{ - RISCV013_INFO(info); - unsigned sbasize = get_field(info->sbcs, DM_SBCS_SBASIZE); - /* There currently is no support for >64-bit addresses in OpenOCD. */ - if (sbasize > 96) - dmi_write(target, DM_SBADDRESS3, 0); - if (sbasize > 64) - dmi_write(target, DM_SBADDRESS2, 0); - if (sbasize > 32) - dmi_write(target, DM_SBADDRESS1, address >> 32); - return dmi_write(target, DM_SBADDRESS0, address); -} - static int read_sbcs_nonbusy(struct target *target, uint32_t *sbcs) { time_t start = time(NULL); @@ -2455,6 +2668,10 @@ static int read_memory_bus_v0(struct target *target, target_addr_t address, } } + uint32_t sbcs; + if (dmi_read(target, &sbcs, DM_SBCS) != ERROR_OK) + return ERROR_FAIL; + return ERROR_OK; } @@ -2484,7 +2701,7 @@ static int read_memory_bus_v1(struct target *target, target_addr_t address, return ERROR_FAIL; /* This address write will trigger the first read. */ - if (sb_write_address(target, next_address) != ERROR_OK) + if (sb_write_address(target, next_address, true) != ERROR_OK) return ERROR_FAIL; if (info->bus_master_read_delay) { @@ -2511,6 +2728,7 @@ static int read_memory_bus_v1(struct target *target, target_addr_t address, next_read); return ERROR_FAIL; } + keep_alive(); dmi_status_t status = dmi_scan(target, NULL, &value, DMI_OP_READ, sbdata[j], 0, false); if (status == DMI_STATUS_BUSY) @@ -2572,7 +2790,7 @@ static int read_memory_bus_v1(struct target *target, target_addr_t address, if (get_field(sbcs_read, DM_SBCS_SBBUSYERROR)) { /* We read while the target was busy. Slow down and try again. */ - if (dmi_write(target, DM_SBCS, DM_SBCS_SBBUSYERROR) != ERROR_OK) + if (dmi_write(target, DM_SBCS, sbcs_read | DM_SBCS_SBBUSYERROR) != ERROR_OK) return ERROR_FAIL; next_address = sb_read_address(target); info->bus_master_read_delay += info->bus_master_read_delay / 10 + 1; @@ -2594,19 +2812,136 @@ static int read_memory_bus_v1(struct target *target, target_addr_t address, return ERROR_OK; } -static int batch_run(const struct target *target, struct riscv_batch *batch) +static void log_mem_access_result(struct target *target, bool success, int method, bool read) { - RISCV013_INFO(info); RISCV_INFO(r); - if (r->reset_delays_wait >= 0) { - r->reset_delays_wait -= batch->used_scans; - if (r->reset_delays_wait <= 0) { - batch->idle_count = 0; - info->dmi_busy_delay = 0; - info->ac_busy_delay = 0; + bool warn = false; + char msg[60]; + + /* Compose the message */ + snprintf(msg, 60, "%s to %s memory via %s.", + success ? "Succeeded" : "Failed", + read ? "read" : "write", + (method == RISCV_MEM_ACCESS_PROGBUF) ? "program buffer" : + (method == RISCV_MEM_ACCESS_SYSBUS) ? "system bus" : "abstract access"); + + /* Determine the log message severity. Show warnings only once. */ + if (!success) { + if (method == RISCV_MEM_ACCESS_PROGBUF) { + warn = r->mem_access_progbuf_warn; + r->mem_access_progbuf_warn = false; + } + if (method == RISCV_MEM_ACCESS_SYSBUS) { + warn = r->mem_access_sysbus_warn; + r->mem_access_sysbus_warn = false; + } + if (method == RISCV_MEM_ACCESS_ABSTRACT) { + warn = r->mem_access_abstract_warn; + r->mem_access_abstract_warn = false; } } - return riscv_batch_run(batch); + + if (warn) + LOG_WARNING("%s", msg); + else + LOG_DEBUG("%s", msg); +} + +static bool mem_should_skip_progbuf(struct target *target, target_addr_t address, + uint32_t size, bool read, char **skip_reason) +{ + assert(skip_reason); + + if (!has_sufficient_progbuf(target, 3)) { + LOG_DEBUG("Skipping mem %s via progbuf - insufficient progbuf size.", + read ? "read" : "write"); + *skip_reason = "skipped (insufficient progbuf)"; + return true; + } + if (target->state != TARGET_HALTED) { + LOG_DEBUG("Skipping mem %s via progbuf - target not halted.", + read ? "read" : "write"); + *skip_reason = "skipped (target not halted)"; + return true; + } + if (riscv_xlen(target) < size * 8) { + LOG_DEBUG("Skipping mem %s via progbuf - XLEN (%d) is too short for %d-bit memory access.", + read ? "read" : "write", riscv_xlen(target), size * 8); + *skip_reason = "skipped (XLEN too short)"; + return true; + } + if (size > 8) { + LOG_DEBUG("Skipping mem %s via progbuf - unsupported size.", + read ? "read" : "write"); + *skip_reason = "skipped (unsupported size)"; + return true; + } + if ((sizeof(address) * 8 > riscv_xlen(target)) && (address >> riscv_xlen(target))) { + LOG_DEBUG("Skipping mem %s via progbuf - progbuf only supports %u-bit address.", + read ? "read" : "write", riscv_xlen(target)); + *skip_reason = "skipped (too large address)"; + return true; + } + + return false; +} + +static bool mem_should_skip_sysbus(struct target *target, target_addr_t address, + uint32_t size, uint32_t increment, bool read, char **skip_reason) +{ + assert(skip_reason); + + RISCV013_INFO(info); + if (!sba_supports_access(target, size)) { + LOG_DEBUG("Skipping mem %s via system bus - unsupported size.", + read ? "read" : "write"); + *skip_reason = "skipped (unsupported size)"; + return true; + } + unsigned int sbasize = get_field(info->sbcs, DM_SBCS_SBASIZE); + if ((sizeof(address) * 8 > sbasize) && (address >> sbasize)) { + LOG_DEBUG("Skipping mem %s via system bus - sba only supports %u-bit address.", + read ? "read" : "write", sbasize); + *skip_reason = "skipped (too large address)"; + return true; + } + if (read && increment != size && (get_field(info->sbcs, DM_SBCS_SBVERSION) == 0 || increment != 0)) { + LOG_DEBUG("Skipping mem read via system bus - " + "sba reads only support size==increment or also size==0 for sba v1."); + *skip_reason = "skipped (unsupported increment)"; + return true; + } + + return false; +} + +static bool mem_should_skip_abstract(struct target *target, target_addr_t address, + uint32_t size, uint32_t increment, bool read, char **skip_reason) +{ + assert(skip_reason); + + if (size > 8) { + /* TODO: Add 128b support if it's ever used. Involves modifying + read/write_abstract_arg() to work on two 64b values. */ + LOG_DEBUG("Skipping mem %s via abstract access - unsupported size: %d bits", + read ? "read" : "write", size * 8); + *skip_reason = "skipped (unsupported size)"; + return true; + } + if ((sizeof(address) * 8 > riscv_xlen(target)) && (address >> riscv_xlen(target))) { + LOG_DEBUG("Skipping mem %s via abstract access - abstract access only supports %u-bit address.", + read ? "read" : "write", riscv_xlen(target)); + *skip_reason = "skipped (too large address)"; + return true; + } + if (read && size != increment) { + LOG_ERROR("Skipping mem read via abstract access - " + "abstract command reads only support size==increment."); + *skip_reason = "skipped (unsupported increment)"; + return true; + } + + return false; } /* @@ -2617,12 +2952,10 @@ static int batch_run(const struct target *target, struct riscv_batch *batch) static int read_memory_abstract(struct target *target, target_addr_t address, uint32_t size, uint32_t count, uint8_t *buffer, uint32_t increment) { - if (size != increment) { - LOG_ERROR("abstract command reads only support size==increment"); - return ERROR_NOT_IMPLEMENTED; - } + RISCV013_INFO(info); int result = ERROR_OK; + bool use_aampostincrement = info->has_aampostincrement != YNM_NO; LOG_DEBUG("reading %d words of %d bytes from 0x%" TARGET_PRIxADDR, count, size, address); @@ -2631,25 +2964,19 @@ static int read_memory_abstract(struct target *target, target_addr_t address, /* Convert the size (bytes) to width (bits) */ unsigned width = size << 3; - if (width > 64) { - /* TODO: Add 128b support if it's ever used. Involves modifying - read/write_abstract_arg() to work on two 64b values. */ - LOG_ERROR("Unsupported size: %d bits", size); - return ERROR_FAIL; - } /* Create the command (physical address, postincrement, read) */ - uint32_t command = access_memory_command(target, false, width, true, false); + uint32_t command = access_memory_command(target, false, width, use_aampostincrement, false); /* Execute the reads */ uint8_t *p = buffer; bool updateaddr = true; - unsigned width32 = (width + 31) / 32 * 32; + unsigned int width32 = (width < 32) ? 32 : width; for (uint32_t c = 0; c < count; c++) { - /* Only update the address initially and let postincrement update it */ + /* Update the address if it is the first time or aampostincrement is not supported by the target. */ if (updateaddr) { /* Set arg1 to the address: address + c * size */ - result = write_abstract_arg(target, 1, address, riscv_xlen(target)); + result = write_abstract_arg(target, 1, address + c * size, riscv_xlen(target)); if (result != ERROR_OK) { LOG_ERROR("Failed to write arg1 during read_memory_abstract()."); return result; @@ -2658,16 +2985,38 @@ static int read_memory_abstract(struct target *target, target_addr_t address, /* Execute the command */ result = execute_abstract_command(target, command); - if (result != ERROR_OK) { - LOG_ERROR("Failed to execute command read_memory_abstract()."); - return result; + + if (info->has_aampostincrement == YNM_MAYBE) { + if (result == ERROR_OK) { + /* Safety: double-check that the address was really auto-incremented */ + riscv_reg_t new_address = read_abstract_arg(target, 1, riscv_xlen(target)); + if (new_address == address + size) { + LOG_DEBUG("aampostincrement is supported on this target."); + info->has_aampostincrement = YNM_YES; + } else { + LOG_WARNING("Buggy aampostincrement! Address not incremented correctly."); + info->has_aampostincrement = YNM_NO; + } + } else { + /* Try the same access but with postincrement disabled. */ + command = access_memory_command(target, false, width, false, false); + result = execute_abstract_command(target, command); + if (result == ERROR_OK) { + LOG_DEBUG("aampostincrement is not supported on this target."); + info->has_aampostincrement = YNM_NO; + } + } } + if (result != ERROR_OK) + return result; + /* Copy arg0 to buffer (rounded width up to nearest 32) */ riscv_reg_t value = read_abstract_arg(target, 0, width32); buf_set_u64(p, 0, 8 * size, value); - updateaddr = false; + if (info->has_aampostincrement == YNM_YES) + updateaddr = false; p += size; } @@ -2682,22 +3031,18 @@ static int read_memory_abstract(struct target *target, target_addr_t address, static int write_memory_abstract(struct target *target, target_addr_t address, uint32_t size, uint32_t count, const uint8_t *buffer) { + RISCV013_INFO(info); int result = ERROR_OK; + bool use_aampostincrement = info->has_aampostincrement != YNM_NO; LOG_DEBUG("writing %d words of %d bytes from 0x%" TARGET_PRIxADDR, count, size, address); /* Convert the size (bytes) to width (bits) */ unsigned width = size << 3; - if (width > 64) { - /* TODO: Add 128b support if it's ever used. Involves modifying - read/write_abstract_arg() to work on two 64b values. */ - LOG_ERROR("Unsupported size: %d bits", width); - return ERROR_FAIL; - } /* Create the command (physical address, postincrement, write) */ - uint32_t command = access_memory_command(target, false, width, true, true); + uint32_t command = access_memory_command(target, false, width, use_aampostincrement, true); /* Execute the writes */ const uint8_t *p = buffer; @@ -2711,10 +3056,10 @@ static int write_memory_abstract(struct target *target, target_addr_t address, return result; } - /* Only update the address initially and let postincrement update it */ + /* Update the address if it is the first time or aampostincrement is not supported by the target. */ if (updateaddr) { /* Set arg1 to the address: address + c * size */ - result = write_abstract_arg(target, 1, address, riscv_xlen(target)); + result = write_abstract_arg(target, 1, address + c * size, riscv_xlen(target)); if (result != ERROR_OK) { LOG_ERROR("Failed to write arg1 during write_memory_abstract()."); return result; @@ -2723,12 +3068,34 @@ static int write_memory_abstract(struct target *target, target_addr_t address, /* Execute the command */ result = execute_abstract_command(target, command); - if (result != ERROR_OK) { - LOG_ERROR("Failed to execute command write_memory_abstract()."); - return result; + + if (info->has_aampostincrement == YNM_MAYBE) { + if (result == ERROR_OK) { + /* Safety: double-check that the address was really auto-incremented */ + riscv_reg_t new_address = read_abstract_arg(target, 1, riscv_xlen(target)); + if (new_address == address + size) { + LOG_DEBUG("aampostincrement is supported on this target."); + info->has_aampostincrement = YNM_YES; + } else { + LOG_WARNING("Buggy aampostincrement! Address not incremented correctly."); + info->has_aampostincrement = YNM_NO; + } + } else { + /* Try the same access but with postincrement disabled. */ + command = access_memory_command(target, false, width, false, true); + result = execute_abstract_command(target, command); + if (result == ERROR_OK) { + LOG_DEBUG("aampostincrement is not supported on this target."); + info->has_aampostincrement = YNM_NO; + } + } } - updateaddr = false; + if (result != ERROR_OK) + return result; + + if (info->has_aampostincrement == YNM_YES) + updateaddr = false; p += size; } @@ -2984,9 +3351,10 @@ static int read_memory_progbuf_one(struct target *target, target_addr_t address, return ERROR_FAIL; uint64_t s0; + int result = ERROR_FAIL; if (register_read(target, &s0, GDB_REGNO_S0) != ERROR_OK) - return ERROR_FAIL; + goto restore_mstatus; /* Write the program (load, increment) */ struct riscv_program program; @@ -3008,40 +3376,42 @@ static int read_memory_progbuf_one(struct target *target, target_addr_t address, break; default: LOG_ERROR("Unsupported size: %d", size); - return ERROR_FAIL; + goto restore_mstatus; } if (riscv_enable_virtual && has_sufficient_progbuf(target, 5) && get_field(mstatus, MSTATUS_MPRV)) riscv_program_csrrci(&program, GDB_REGNO_ZERO, CSR_DCSR_MPRVEN, GDB_REGNO_DCSR); if (riscv_program_ebreak(&program) != ERROR_OK) - return ERROR_FAIL; + goto restore_mstatus; if (riscv_program_write(&program) != ERROR_OK) - return ERROR_FAIL; + goto restore_mstatus; /* Write address to S0, and execute buffer. */ if (write_abstract_arg(target, 0, address, riscv_xlen(target)) != ERROR_OK) - return ERROR_FAIL; + goto restore_mstatus; uint32_t command = access_register_command(target, GDB_REGNO_S0, riscv_xlen(target), AC_ACCESS_REGISTER_WRITE | AC_ACCESS_REGISTER_TRANSFER | AC_ACCESS_REGISTER_POSTEXEC); if (execute_abstract_command(target, command) != ERROR_OK) - return ERROR_FAIL; + goto restore_s0; uint64_t value; if (register_read(target, &value, GDB_REGNO_S0) != ERROR_OK) - return ERROR_FAIL; + goto restore_s0; buf_set_u64(buffer, 0, 8 * size, value); log_memory_access(address, value, size, true); + result = ERROR_OK; +restore_s0: if (riscv_set_register(target, GDB_REGNO_S0, s0) != ERROR_OK) - return ERROR_FAIL; + result = ERROR_FAIL; - /* Restore MSTATUS */ +restore_mstatus: if (mstatus != mstatus_old) if (register_write_direct(target, GDB_REGNO_MSTATUS, mstatus_old)) - return ERROR_FAIL; + result = ERROR_FAIL; - return ERROR_OK; + return result; } /** @@ -3076,8 +3446,8 @@ static int read_memory_progbuf(struct target *target, target_addr_t address, if (modify_privilege(target, &mstatus, &mstatus_old) != ERROR_OK) return ERROR_FAIL; - /* s0 holds the next address to write to - * s1 holds the next data value to write + /* s0 holds the next address to read from + * s1 holds the next data value read * s2 is a counter in case increment is 0 */ uint64_t s0, s1, s2; @@ -3085,7 +3455,7 @@ static int read_memory_progbuf(struct target *target, target_addr_t address, return ERROR_FAIL; if (register_read(target, &s1, GDB_REGNO_S1) != ERROR_OK) return ERROR_FAIL; - if (increment == 0 && register_read(target, &s2, GDB_REGNO_S1) != ERROR_OK) + if (increment == 0 && register_read(target, &s2, GDB_REGNO_S2) != ERROR_OK) return ERROR_FAIL; /* Write the program (load, increment) */ @@ -3135,7 +3505,6 @@ static int read_memory_progbuf(struct target *target, target_addr_t address, uint8_t *buffer_i = buffer; for (uint32_t i = 0; i < count; i++, address_i += increment, buffer_i += size) { - keep_alive(); /* TODO: This is much slower than it needs to be because we end up * writing the address to read for every word we read. */ result = read_memory_progbuf_inner(target, address_i, size, count_i, buffer_i, increment); @@ -3170,30 +3539,62 @@ static int read_memory(struct target *target, target_addr_t address, if (count == 0) return ERROR_OK; - RISCV013_INFO(info); - if (has_sufficient_progbuf(target, 3) && !riscv_prefer_sba) - return read_memory_progbuf(target, address, size, count, buffer, - increment); - - if ((get_field(info->sbcs, DM_SBCS_SBACCESS8) && size == 1) || - (get_field(info->sbcs, DM_SBCS_SBACCESS16) && size == 2) || - (get_field(info->sbcs, DM_SBCS_SBACCESS32) && size == 4) || - (get_field(info->sbcs, DM_SBCS_SBACCESS64) && size == 8) || - (get_field(info->sbcs, DM_SBCS_SBACCESS128) && size == 16)) { - if (get_field(info->sbcs, DM_SBCS_SBVERSION) == 0) - return read_memory_bus_v0(target, address, size, count, buffer, - increment); - else if (get_field(info->sbcs, DM_SBCS_SBVERSION) == 1) - return read_memory_bus_v1(target, address, size, count, buffer, - increment); + if (size != 1 && size != 2 && size != 4 && size != 8 && size != 16) { + LOG_ERROR("BUG: Unsupported size for memory read: %d", size); + return ERROR_FAIL; } - if (has_sufficient_progbuf(target, 3)) - return read_memory_progbuf(target, address, size, count, buffer, - increment); + int ret = ERROR_FAIL; + RISCV_INFO(r); + RISCV013_INFO(info); + + char *progbuf_result = "disabled"; + char *sysbus_result = "disabled"; + char *abstract_result = "disabled"; + + for (unsigned int i = 0; i < RISCV_NUM_MEM_ACCESS_METHODS; i++) { + int method = r->mem_access_methods[i]; + + if (method == RISCV_MEM_ACCESS_PROGBUF) { + if (mem_should_skip_progbuf(target, address, size, true, &progbuf_result)) + continue; + + ret = read_memory_progbuf(target, address, size, count, buffer, increment); - return read_memory_abstract(target, address, size, count, buffer, - increment); + if (ret != ERROR_OK) + progbuf_result = "failed"; + } else if (method == RISCV_MEM_ACCESS_SYSBUS) { + if (mem_should_skip_sysbus(target, address, size, increment, true, &sysbus_result)) + continue; + + if (get_field(info->sbcs, DM_SBCS_SBVERSION) == 0) + ret = read_memory_bus_v0(target, address, size, count, buffer, increment); + else if (get_field(info->sbcs, DM_SBCS_SBVERSION) == 1) + ret = read_memory_bus_v1(target, address, size, count, buffer, increment); + + if (ret != ERROR_OK) + sysbus_result = "failed"; + } else if (method == RISCV_MEM_ACCESS_ABSTRACT) { + if (mem_should_skip_abstract(target, address, size, increment, true, &abstract_result)) + continue; + + ret = read_memory_abstract(target, address, size, count, buffer, increment); + + if (ret != ERROR_OK) + abstract_result = "failed"; + } else if (method == RISCV_MEM_ACCESS_UNSPECIFIED) + /* No further mem access method to try. */ + break; + + log_mem_access_result(target, ret == ERROR_OK, method, true); + + if (ret == ERROR_OK) + return ret; + } + + LOG_ERROR("Target %s: Failed to read memory (addr=0x%" PRIx64 ")", target_name(target), address); + LOG_ERROR(" progbuf=%s, sysbus=%s, abstract=%s", progbuf_result, sysbus_result, abstract_result); + return ret; } static int write_memory_bus_v0(struct target *target, target_addr_t address, @@ -3262,7 +3663,7 @@ static int write_memory_bus_v1(struct target *target, target_addr_t address, int result; - sb_write_address(target, next_address); + sb_write_address(target, next_address, true); while (next_address < end_address) { LOG_DEBUG("transferring burst starting at address 0x%" TARGET_PRIxADDR, next_address); @@ -3312,54 +3713,76 @@ static int write_memory_bus_v1(struct target *target, target_addr_t address, next_address += size; } + /* Execute the batch of writes */ result = batch_run(target, batch); riscv_batch_free(batch); if (result != ERROR_OK) return result; + /* Read sbcs value. + * At the same time, detect if DMI busy has occurred during the batch write. */ bool dmi_busy_encountered; if (dmi_op(target, &sbcs, &dmi_busy_encountered, DMI_OP_READ, - DM_SBCS, 0, false, false) != ERROR_OK) + DM_SBCS, 0, false, true) != ERROR_OK) return ERROR_FAIL; + if (dmi_busy_encountered) + LOG_DEBUG("DMI busy encountered during system bus write."); + /* Wait until sbbusy goes low */ time_t start = time(NULL); - bool dmi_busy = dmi_busy_encountered; - while (get_field(sbcs, DM_SBCS_SBBUSY) || dmi_busy) { + while (get_field(sbcs, DM_SBCS_SBBUSY)) { if (time(NULL) - start > riscv_command_timeout_sec) { LOG_ERROR("Timed out after %ds waiting for sbbusy to go low (sbcs=0x%x). " - "Increase the timeout with riscv set_command_timeout_sec.", - riscv_command_timeout_sec, sbcs); + "Increase the timeout with riscv set_command_timeout_sec.", + riscv_command_timeout_sec, sbcs); return ERROR_FAIL; } - - if (dmi_op(target, &sbcs, &dmi_busy, DMI_OP_READ, - DM_SBCS, 0, false, true) != ERROR_OK) + if (dmi_read(target, &sbcs, DM_SBCS) != ERROR_OK) return ERROR_FAIL; } if (get_field(sbcs, DM_SBCS_SBBUSYERROR)) { - /* We wrote while the target was busy. Slow down and try again. */ - dmi_write(target, DM_SBCS, DM_SBCS_SBBUSYERROR); + /* We wrote while the target was busy. */ + LOG_DEBUG("Sbbusyerror encountered during system bus write."); + /* Clear the sticky error flag. */ + dmi_write(target, DM_SBCS, sbcs | DM_SBCS_SBBUSYERROR); + /* Slow down before trying again. */ info->bus_master_write_delay += info->bus_master_write_delay / 10 + 1; } if (get_field(sbcs, DM_SBCS_SBBUSYERROR) || dmi_busy_encountered) { + /* Recover from the case when the write commands were issued too fast. + * Determine the address from which to resume writing. */ next_address = sb_read_address(target); if (next_address < address) { /* This should never happen, probably buggy hardware. */ - LOG_DEBUG("unexpected system bus address 0x%" TARGET_PRIxADDR, - next_address); + LOG_DEBUG("unexpected sbaddress=0x%" TARGET_PRIxADDR + " - buggy sbautoincrement in hw?", next_address); + /* Fail the whole operation. */ return ERROR_FAIL; } - + /* Try again - resume writing. */ continue; } - unsigned error = get_field(sbcs, DM_SBCS_SBERROR); - if (error != 0) { - /* Some error indicating the bus access failed, but not because of - * something we did wrong. */ + unsigned int sberror = get_field(sbcs, DM_SBCS_SBERROR); + if (sberror != 0) { + /* Sberror indicates the bus access failed, but not because we issued the writes + * too fast. Cannot recover. Sbaddress holds the address where the error occurred + * (unless sbautoincrement in the HW is buggy). + */ + target_addr_t sbaddress = sb_read_address(target); + LOG_DEBUG("System bus access failed with sberror=%u (sbaddress=0x%" TARGET_PRIxADDR ")", + sberror, sbaddress); + if (sbaddress < address) { + /* This should never happen, probably buggy hardware. + * Make a note to the user not to trust the sbaddress value. */ + LOG_DEBUG("unexpected sbaddress=0x%" TARGET_PRIxADDR + " - buggy sbautoincrement in hw?", next_address); + } + /* Clear the sticky error flag */ dmi_write(target, DM_SBCS, DM_SBCS_SBERROR); + /* Fail the whole operation */ return ERROR_FAIL; } } @@ -3562,26 +3985,62 @@ error: static int write_memory(struct target *target, target_addr_t address, uint32_t size, uint32_t count, const uint8_t *buffer) { + if (size != 1 && size != 2 && size != 4 && size != 8 && size != 16) { + LOG_ERROR("BUG: Unsupported size for memory write: %d", size); + return ERROR_FAIL; + } + + int ret = ERROR_FAIL; + RISCV_INFO(r); RISCV013_INFO(info); - if (has_sufficient_progbuf(target, 3) && !riscv_prefer_sba) - return write_memory_progbuf(target, address, size, count, buffer); + char *progbuf_result = "disabled"; + char *sysbus_result = "disabled"; + char *abstract_result = "disabled"; - if ((get_field(info->sbcs, DM_SBCS_SBACCESS8) && size == 1) || - (get_field(info->sbcs, DM_SBCS_SBACCESS16) && size == 2) || - (get_field(info->sbcs, DM_SBCS_SBACCESS32) && size == 4) || - (get_field(info->sbcs, DM_SBCS_SBACCESS64) && size == 8) || - (get_field(info->sbcs, DM_SBCS_SBACCESS128) && size == 16)) { - if (get_field(info->sbcs, DM_SBCS_SBVERSION) == 0) - return write_memory_bus_v0(target, address, size, count, buffer); - else if (get_field(info->sbcs, DM_SBCS_SBVERSION) == 1) - return write_memory_bus_v1(target, address, size, count, buffer); - } + for (unsigned int i = 0; i < RISCV_NUM_MEM_ACCESS_METHODS; i++) { + int method = r->mem_access_methods[i]; - if (has_sufficient_progbuf(target, 3)) - return write_memory_progbuf(target, address, size, count, buffer); + if (method == RISCV_MEM_ACCESS_PROGBUF) { + if (mem_should_skip_progbuf(target, address, size, false, &progbuf_result)) + continue; + + ret = write_memory_progbuf(target, address, size, count, buffer); + + if (ret != ERROR_OK) + progbuf_result = "failed"; + } else if (method == RISCV_MEM_ACCESS_SYSBUS) { + if (mem_should_skip_sysbus(target, address, size, 0, false, &sysbus_result)) + continue; + + if (get_field(info->sbcs, DM_SBCS_SBVERSION) == 0) + ret = write_memory_bus_v0(target, address, size, count, buffer); + else if (get_field(info->sbcs, DM_SBCS_SBVERSION) == 1) + ret = write_memory_bus_v1(target, address, size, count, buffer); + + if (ret != ERROR_OK) + sysbus_result = "failed"; + } else if (method == RISCV_MEM_ACCESS_ABSTRACT) { + if (mem_should_skip_abstract(target, address, size, 0, false, &abstract_result)) + continue; - return write_memory_abstract(target, address, size, count, buffer); + ret = write_memory_abstract(target, address, size, count, buffer); + + if (ret != ERROR_OK) + abstract_result = "failed"; + } else if (method == RISCV_MEM_ACCESS_UNSPECIFIED) + /* No further mem access method to try. */ + break; + + log_mem_access_result(target, ret == ERROR_OK, method, false); + + if (ret == ERROR_OK) + return ret; + } + + LOG_ERROR("Target %s: Failed to write memory (addr=0x%" PRIx64 ")", target_name(target), address); + LOG_ERROR(" progbuf=%s, sysbus=%s, abstract=%s", progbuf_result, sysbus_result, abstract_result); + return ret; } static int arch_state(struct target *target) @@ -3605,17 +4064,18 @@ struct target_type riscv013_target = { .write_memory = write_memory, - .arch_state = arch_state, + .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) + riscv_reg_t *value, int rid) { - LOG_DEBUG("[%d] reading register %s on hart %d", target->coreid, - gdb_regno_name(rid), hid); + LOG_DEBUG("[%s] reading register %s", target_name(target), + gdb_regno_name(rid)); - riscv_set_current_hartid(target, hid); + if (riscv_select_current_hart(target) != ERROR_OK) + return ERROR_FAIL; int result = ERROR_OK; if (rid == GDB_REGNO_PC) { @@ -3626,7 +4086,8 @@ static int riscv013_get_register(struct target *target, uint64_t dcsr; /* TODO: move this into riscv.c. */ result = register_read(target, &dcsr, GDB_REGNO_DCSR); - *value = get_field(dcsr, CSR_DCSR_PRV); + *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)); } else { result = register_read(target, value, rid); if (result != ERROR_OK) @@ -3636,12 +4097,11 @@ static int riscv013_get_register(struct target *target, return result; } -static int riscv013_set_register(struct target *target, int hid, int rid, uint64_t value) +static int riscv013_set_register(struct target *target, int rid, uint64_t value) { - LOG_DEBUG("[%d] writing 0x%" PRIx64 " to register %s on hart %d", - target->coreid, value, gdb_regno_name(rid), hid); - - riscv_set_current_hartid(target, hid); + riscv013_select_current_hart(target); + LOG_DEBUG("[%d] writing 0x%" PRIx64 " to register %s", + target->coreid, value, gdb_regno_name(rid)); if (rid <= GDB_REGNO_XPR31) { return register_write_direct(target, rid, value); @@ -3659,7 +4119,8 @@ static int riscv013_set_register(struct target *target, int hid, int rid, uint64 } else if (rid == GDB_REGNO_PRIV) { uint64_t dcsr; register_read(target, &dcsr, GDB_REGNO_DCSR); - dcsr = set_field(dcsr, CSR_DCSR_PRV, value); + 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 register_write_direct(target, GDB_REGNO_DCSR, dcsr); } else { return register_write_direct(target, rid, value); @@ -3750,10 +4211,8 @@ static int riscv013_halt_prep(struct target *target) static int riscv013_halt_go(struct target *target) { bool use_hasel = false; - if (!riscv_rtos_enabled(target)) { - if (select_prepped_harts(target, &use_hasel) != ERROR_OK) - return ERROR_FAIL; - } + if (select_prepped_harts(target, &use_hasel) != ERROR_OK) + return ERROR_FAIL; RISCV_INFO(r); LOG_DEBUG("halting hart %d", r->current_hartid); @@ -3804,10 +4263,8 @@ static int riscv013_halt_go(struct target *target) static int riscv013_resume_go(struct target *target) { bool use_hasel = false; - if (!riscv_rtos_enabled(target)) { - if (select_prepped_harts(target, &use_hasel) != ERROR_OK) - return ERROR_FAIL; - } + if (select_prepped_harts(target, &use_hasel) != ERROR_OK) + return ERROR_FAIL; return riscv013_step_or_resume_current_hart(target, false, use_hasel); } @@ -3867,6 +4324,8 @@ static enum riscv_halt_reason riscv013_halt_reason(struct target *target) if (result != ERROR_OK) return RISCV_HALT_UNKNOWN; + LOG_DEBUG("dcsr.cause: 0x%" PRIx64, get_field(dcsr, CSR_DCSR_CAUSE)); + switch (get_field(dcsr, CSR_DCSR_CAUSE)) { case CSR_DCSR_CAUSE_SWBP: return RISCV_HALT_BREAKPOINT; @@ -3886,7 +4345,7 @@ static enum riscv_halt_reason riscv013_halt_reason(struct target *target) return RISCV_HALT_GROUP; } - LOG_ERROR("Unknown DCSR cause field: %x", (int)get_field(dcsr, CSR_DCSR_CAUSE)); + LOG_ERROR("Unknown DCSR cause field: 0x%" PRIx64, get_field(dcsr, CSR_DCSR_CAUSE)); LOG_ERROR(" dcsr=0x%016lx", (long)dcsr); return RISCV_HALT_UNKNOWN; } @@ -4402,477 +4861,3 @@ void riscv013_clear_abstract_error(struct target *target) /* Clear the error status. */ dmi_write(target, DM_ABSTRACTCS, DM_ABSTRACTCS_CMDERR); } - -#ifdef _WIN32 -#define FILE_SEP '\\' -#else -#define FILE_SEP '/' -#endif -#define COMPLIANCE_TEST(b, message) \ -{ \ - const char *last_sep = strrchr(__FILE__, FILE_SEP); \ - const char *fname = (last_sep == NULL ? __FILE__ : last_sep + 1); \ - LOG_INFO("Executing test %d (%s:%d): %s", total_tests, fname, __LINE__, message); \ - int pass = 0; \ - if (b) { \ - pass = 1; \ - passed_tests++; \ - } \ - LOG_INFO(" %s", (pass) ? "PASSED" : "FAILED"); \ - assert(pass); \ - total_tests++; \ -} - -#define COMPLIANCE_MUST_PASS(b) COMPLIANCE_TEST(ERROR_OK == (b), "Regular calls must return ERROR_OK") - -#define COMPLIANCE_READ(target, addr, value) COMPLIANCE_MUST_PASS(dmi_read(target, addr, value)) -#define COMPLIANCE_WRITE(target, addr, value) COMPLIANCE_MUST_PASS(dmi_write(target, addr, value)) - -#define COMPLIANCE_CHECK_RO(target, addr) \ -{ \ - uint32_t orig; \ - uint32_t inverse; \ - COMPLIANCE_READ(target, &orig, addr); \ - COMPLIANCE_WRITE(target, addr, ~orig); \ - COMPLIANCE_READ(target, &inverse, addr); \ - COMPLIANCE_TEST(orig == inverse, "Register must be read-only"); \ -} - -int riscv013_test_compliance(struct target *target) -{ - LOG_INFO("Basic compliance test against RISC-V Debug Spec v0.13"); - LOG_INFO("This test is not complete, and not well supported."); - LOG_INFO("Your core might pass this test without being compliant."); - LOG_INFO("Your core might fail this test while being compliant."); - LOG_INFO("Use your judgment, and please contribute improvements."); - - if (!riscv_rtos_enabled(target)) { - LOG_ERROR("Please run with -rtos riscv to run compliance test."); - return ERROR_FAIL; - } - - if (!target_was_examined(target)) { - LOG_ERROR("Cannot run compliance test, because target has not yet " - "been examined, or the examination failed.\n"); - return ERROR_FAIL; - } - - int total_tests = 0; - int passed_tests = 0; - - uint32_t dmcontrol_orig = DM_DMCONTROL_DMACTIVE; - uint32_t dmcontrol; - uint32_t testvar; - uint32_t testvar_read; - riscv_reg_t value; - RISCV013_INFO(info); - - /* All the bits of HARTSEL are covered by the examine sequence. */ - - /* hartreset */ - /* This field is optional. Either we can read and write it to 1/0, - or it is tied to 0. This check doesn't really do anything, but - it does attempt to set the bit to 1 and then back to 0, which needs to - work if its implemented. */ - COMPLIANCE_WRITE(target, DM_DMCONTROL, set_field(dmcontrol_orig, DM_DMCONTROL_HARTRESET, 1)); - COMPLIANCE_WRITE(target, DM_DMCONTROL, set_field(dmcontrol_orig, DM_DMCONTROL_HARTRESET, 0)); - COMPLIANCE_READ(target, &dmcontrol, DM_DMCONTROL); - COMPLIANCE_TEST((get_field(dmcontrol, DM_DMCONTROL_HARTRESET) == 0), - "DMCONTROL.hartreset can be 0 or RW."); - - /* hasel */ - COMPLIANCE_WRITE(target, DM_DMCONTROL, set_field(dmcontrol_orig, DM_DMCONTROL_HASEL, 1)); - COMPLIANCE_WRITE(target, DM_DMCONTROL, set_field(dmcontrol_orig, DM_DMCONTROL_HASEL, 0)); - COMPLIANCE_READ(target, &dmcontrol, DM_DMCONTROL); - COMPLIANCE_TEST((get_field(dmcontrol, DM_DMCONTROL_HASEL) == 0), - "DMCONTROL.hasel can be 0 or RW."); - /* TODO: test that hamask registers exist if hasel does. */ - - /* haltreq */ - COMPLIANCE_MUST_PASS(riscv_halt(target)); - /* This bit is not actually readable according to the spec, so nothing to check.*/ - - /* DMSTATUS */ - COMPLIANCE_CHECK_RO(target, DM_DMSTATUS); - - /* resumereq */ - /* This bit is not actually readable according to the spec, so nothing to check.*/ - COMPLIANCE_MUST_PASS(riscv_resume(target, true, 0, false, false, false)); - - /* Halt all harts again so the test can continue.*/ - COMPLIANCE_MUST_PASS(riscv_halt(target)); - - /* HARTINFO: Read-Only. This is per-hart, so need to adjust hartsel. */ - uint32_t hartinfo; - COMPLIANCE_READ(target, &hartinfo, DM_HARTINFO); - for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) { - COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel)); - - COMPLIANCE_CHECK_RO(target, DM_HARTINFO); - - /* $dscratch CSRs */ - uint32_t nscratch = get_field(hartinfo, DM_HARTINFO_NSCRATCH); - for (unsigned int d = 0; d < nscratch; d++) { - riscv_reg_t testval, testval_read; - /* Because DSCRATCH0 is not guaranteed to last across PB executions, need to put - this all into one PB execution. Which may not be possible on all implementations.*/ - if (info->progbufsize >= 5) { - for (testval = 0x0011223300112233; - testval != 0xDEAD; - testval = testval == 0x0011223300112233 ? ~testval : 0xDEAD) { - COMPLIANCE_TEST(register_write_direct(target, GDB_REGNO_S0, testval) == ERROR_OK, - "Need to be able to write S0 in order to test DSCRATCH0."); - struct riscv_program program32; - riscv_program_init(&program32, target); - riscv_program_csrw(&program32, GDB_REGNO_S0, GDB_REGNO_DSCRATCH0 + d); - riscv_program_csrr(&program32, GDB_REGNO_S1, GDB_REGNO_DSCRATCH0 + d); - riscv_program_fence(&program32); - riscv_program_ebreak(&program32); - COMPLIANCE_TEST(riscv_program_exec(&program32, target) == ERROR_OK, - "Accessing DSCRATCH0 with program buffer should succeed."); - COMPLIANCE_TEST(register_read_direct(target, &testval_read, GDB_REGNO_S1) == ERROR_OK, - "Need to be able to read S1 in order to test DSCRATCH0."); - if (riscv_xlen(target) > 32) { - COMPLIANCE_TEST(testval == testval_read, - "All DSCRATCH0 registers in HARTINFO must be R/W."); - } else { - COMPLIANCE_TEST(testval_read == (testval & 0xFFFFFFFF), - "All DSCRATCH0 registers in HARTINFO must be R/W."); - } - } - } - } - /* TODO: dataaccess */ - if (get_field(hartinfo, DM_HARTINFO_DATAACCESS)) { - /* TODO: Shadowed in memory map. */ - /* TODO: datasize */ - /* TODO: dataaddr */ - } else { - /* TODO: Shadowed in CSRs. */ - /* TODO: datasize */ - /* TODO: dataaddr */ - } - - } - - /* HALTSUM -- TODO: More than 32 harts. Would need to loop over this to set hartsel */ - /* TODO: HALTSUM2, HALTSUM3 */ - /* HALTSUM0 */ - uint32_t expected_haltsum0 = 0; - for (int i = 0; i < MIN(riscv_count_harts(target), 32); i++) - expected_haltsum0 |= (1 << i); - - COMPLIANCE_READ(target, &testvar_read, DM_HALTSUM0); - COMPLIANCE_TEST(testvar_read == expected_haltsum0, - "HALTSUM0 should report summary of up to 32 halted harts"); - - COMPLIANCE_WRITE(target, DM_HALTSUM0, 0xffffffff); - COMPLIANCE_READ(target, &testvar_read, DM_HALTSUM0); - COMPLIANCE_TEST(testvar_read == expected_haltsum0, "HALTSUM0 should be R/O"); - - COMPLIANCE_WRITE(target, DM_HALTSUM0, 0x0); - COMPLIANCE_READ(target, &testvar_read, DM_HALTSUM0); - COMPLIANCE_TEST(testvar_read == expected_haltsum0, "HALTSUM0 should be R/O"); - - /* HALTSUM1 */ - uint32_t expected_haltsum1 = 0; - for (int i = 0; i < MIN(riscv_count_harts(target), 1024); i += 32) - expected_haltsum1 |= (1 << (i/32)); - - COMPLIANCE_READ(target, &testvar_read, DM_HALTSUM1); - COMPLIANCE_TEST(testvar_read == expected_haltsum1, - "HALTSUM1 should report summary of up to 1024 halted harts"); - - COMPLIANCE_WRITE(target, DM_HALTSUM1, 0xffffffff); - COMPLIANCE_READ(target, &testvar_read, DM_HALTSUM1); - COMPLIANCE_TEST(testvar_read == expected_haltsum1, "HALTSUM1 should be R/O"); - - COMPLIANCE_WRITE(target, DM_HALTSUM1, 0x0); - COMPLIANCE_READ(target, &testvar_read, DM_HALTSUM1); - COMPLIANCE_TEST(testvar_read == expected_haltsum1, "HALTSUM1 should be R/O"); - - /* TODO: HAWINDOWSEL */ - - /* TODO: HAWINDOW */ - - /* ABSTRACTCS */ - - uint32_t abstractcs; - COMPLIANCE_READ(target, &abstractcs, DM_ABSTRACTCS); - - /* Check that all reported Data Words are really R/W */ - for (int invert = 0; invert < 2; invert++) { - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_DATACOUNT); i++) { - testvar = (i + 1) * 0x11111111; - if (invert) - testvar = ~testvar; - COMPLIANCE_WRITE(target, DM_DATA0 + i, testvar); - } - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_DATACOUNT); i++) { - testvar = (i + 1) * 0x11111111; - if (invert) - testvar = ~testvar; - COMPLIANCE_READ(target, &testvar_read, DM_DATA0 + i); - COMPLIANCE_TEST(testvar_read == testvar, "All reported DATA words must be R/W"); - } - } - - /* Check that all reported ProgBuf words are really R/W */ - for (int invert = 0; invert < 2; invert++) { - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_PROGBUFSIZE); i++) { - testvar = (i + 1) * 0x11111111; - if (invert) - testvar = ~testvar; - COMPLIANCE_WRITE(target, DM_PROGBUF0 + i, testvar); - } - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_PROGBUFSIZE); i++) { - testvar = (i + 1) * 0x11111111; - if (invert) - testvar = ~testvar; - COMPLIANCE_READ(target, &testvar_read, DM_PROGBUF0 + i); - COMPLIANCE_TEST(testvar_read == testvar, "All reported PROGBUF words must be R/W"); - } - } - - /* TODO: Cause and clear all error types */ - - /* COMMAND - According to the spec, this register is only W, so can't really check the read result. - But at any rate, this is not legal and should cause an error. */ - COMPLIANCE_WRITE(target, DM_COMMAND, 0xAAAAAAAA); - COMPLIANCE_READ(target, &testvar_read, DM_ABSTRACTCS); - COMPLIANCE_TEST(get_field(testvar_read, DM_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, - "Illegal COMMAND should result in UNSUPPORTED"); - COMPLIANCE_WRITE(target, DM_ABSTRACTCS, DM_ABSTRACTCS_CMDERR); - - COMPLIANCE_WRITE(target, DM_COMMAND, 0x55555555); - COMPLIANCE_READ(target, &testvar_read, DM_ABSTRACTCS); - COMPLIANCE_TEST(get_field(testvar_read, DM_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, - "Illegal COMMAND should result in UNSUPPORTED"); - COMPLIANCE_WRITE(target, DM_ABSTRACTCS, DM_ABSTRACTCS_CMDERR); - - /* Basic Abstract Commands */ - for (unsigned int i = 1; i < 32; i = i << 1) { - riscv_reg_t testval = i | ((i + 1ULL) << 32); - riscv_reg_t testval_read; - COMPLIANCE_TEST(ERROR_OK == register_write_direct(target, GDB_REGNO_ZERO + i, testval), - "GPR Writes should be supported."); - COMPLIANCE_MUST_PASS(write_abstract_arg(target, 0, 0xDEADBEEFDEADBEEF, 64)); - COMPLIANCE_TEST(ERROR_OK == register_read_direct(target, &testval_read, GDB_REGNO_ZERO + i), - "GPR Reads should be supported."); - if (riscv_xlen(target) > 32) { - /* Dummy comment to satisfy linter, since removing the branches here doesn't actually compile. */ - COMPLIANCE_TEST(testval == testval_read, "GPR Reads and writes should be supported."); - } else { - /* Dummy comment to satisfy linter, since removing the branches here doesn't actually compile. */ - COMPLIANCE_TEST((testval & 0xFFFFFFFF) == testval_read, "GPR Reads and writes should be supported."); - } - } - - /* ABSTRACTAUTO - See which bits are actually writable */ - COMPLIANCE_WRITE(target, DM_ABSTRACTAUTO, 0xFFFFFFFF); - uint32_t abstractauto; - uint32_t busy; - COMPLIANCE_READ(target, &abstractauto, DM_ABSTRACTAUTO); - COMPLIANCE_WRITE(target, DM_ABSTRACTAUTO, 0x0); - if (abstractauto > 0) { - /* This mechanism only works when you have a reasonable sized progbuf, which is not - a true compliance requirement. */ - if (info->progbufsize >= 3) { - - testvar = 0; - COMPLIANCE_TEST(ERROR_OK == register_write_direct(target, GDB_REGNO_S0, 0), - "Need to be able to write S0 to test ABSTRACTAUTO"); - struct riscv_program program; - COMPLIANCE_MUST_PASS(riscv_program_init(&program, target)); - /* This is also testing that WFI() is a NOP during debug mode. */ - COMPLIANCE_MUST_PASS(riscv_program_insert(&program, wfi())); - COMPLIANCE_MUST_PASS(riscv_program_addi(&program, GDB_REGNO_S0, GDB_REGNO_S0, 1)); - COMPLIANCE_MUST_PASS(riscv_program_ebreak(&program)); - COMPLIANCE_WRITE(target, DM_ABSTRACTAUTO, 0x0); - COMPLIANCE_MUST_PASS(riscv_program_exec(&program, target)); - testvar++; - COMPLIANCE_WRITE(target, DM_ABSTRACTAUTO, 0xFFFFFFFF); - COMPLIANCE_READ(target, &abstractauto, DM_ABSTRACTAUTO); - uint32_t autoexec_data = get_field(abstractauto, DM_ABSTRACTAUTO_AUTOEXECDATA); - uint32_t autoexec_progbuf = get_field(abstractauto, DM_ABSTRACTAUTO_AUTOEXECPROGBUF); - for (unsigned int i = 0; i < 12; i++) { - COMPLIANCE_READ(target, &testvar_read, DM_DATA0 + i); - do { - COMPLIANCE_READ(target, &testvar_read, DM_ABSTRACTCS); - busy = get_field(testvar_read, DM_ABSTRACTCS_BUSY); - } while (busy); - if (autoexec_data & (1 << i)) { - COMPLIANCE_TEST(i < get_field(abstractcs, DM_ABSTRACTCS_DATACOUNT), - "AUTOEXEC may be writable up to DATACOUNT bits."); - testvar++; - } - } - for (unsigned int i = 0; i < 16; i++) { - COMPLIANCE_READ(target, &testvar_read, DM_PROGBUF0 + i); - do { - COMPLIANCE_READ(target, &testvar_read, DM_ABSTRACTCS); - busy = get_field(testvar_read, DM_ABSTRACTCS_BUSY); - } while (busy); - if (autoexec_progbuf & (1 << i)) { - COMPLIANCE_TEST(i < get_field(abstractcs, DM_ABSTRACTCS_PROGBUFSIZE), - "AUTOEXEC may be writable up to PROGBUFSIZE bits."); - testvar++; - } - } - - COMPLIANCE_WRITE(target, DM_ABSTRACTAUTO, 0); - COMPLIANCE_TEST(ERROR_OK == register_read_direct(target, &value, GDB_REGNO_S0), - "Need to be able to read S0 to test ABSTRACTAUTO"); - - COMPLIANCE_TEST(testvar == value, - "ABSTRACTAUTO should cause COMMAND to run the expected number of times."); - } - } - - /* Single-Step each hart. */ - for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) { - COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel)); - COMPLIANCE_MUST_PASS(riscv013_on_step(target)); - COMPLIANCE_MUST_PASS(riscv013_step_current_hart(target)); - COMPLIANCE_TEST(riscv_halt_reason(target, hartsel) == RISCV_HALT_SINGLESTEP, - "Single Step should result in SINGLESTEP"); - } - - /* Core Register Tests */ - uint64_t bogus_dpc = 0xdeadbeef; - for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) { - COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel)); - - /* DCSR Tests */ - COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DCSR, 0x0)); - COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DCSR)); - COMPLIANCE_TEST(value != 0, "Not all bits in DCSR are writable by Debugger"); - COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DCSR, 0xFFFFFFFF)); - COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DCSR)); - COMPLIANCE_TEST(value != 0, "At least some bits in DCSR must be 1"); - - /* DPC. Note that DPC is sign-extended. */ - riscv_reg_t dpcmask = 0xFFFFFFFCUL; - riscv_reg_t dpc; - - if (riscv_xlen(target) > 32) - dpcmask |= (0xFFFFFFFFULL << 32); - - if (riscv_supports_extension(target, riscv_current_hartid(target), 'C')) - dpcmask |= 0x2; - - COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DPC, dpcmask)); - COMPLIANCE_MUST_PASS(register_read_direct(target, &dpc, GDB_REGNO_DPC)); - COMPLIANCE_TEST(dpcmask == dpc, - "DPC must be sign-extended to XLEN and writable to all-1s (except the least significant bits)"); - COMPLIANCE_MUST_PASS(register_write_direct(target, GDB_REGNO_DPC, 0)); - COMPLIANCE_MUST_PASS(register_read_direct(target, &dpc, GDB_REGNO_DPC)); - COMPLIANCE_TEST(dpc == 0, "DPC must be writable to 0."); - if (hartsel == 0) - bogus_dpc = dpc; /* For a later test step */ - } - - /* NDMRESET - Asserting non-debug module reset should not reset Debug Module state. - But it should reset Hart State, e.g. DPC should get a different value. - Also make sure that DCSR reports cause of 'HALT' even though previously we single-stepped. - */ - - /* Write some registers. They should not be impacted by ndmreset. */ - COMPLIANCE_WRITE(target, DM_COMMAND, 0xFFFFFFFF); - - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_PROGBUFSIZE); i++) { - testvar = (i + 1) * 0x11111111; - COMPLIANCE_WRITE(target, DM_PROGBUF0 + i, testvar); - } - - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_DATACOUNT); i++) { - testvar = (i + 1) * 0x11111111; - COMPLIANCE_WRITE(target, DM_DATA0 + i, testvar); - } - - COMPLIANCE_WRITE(target, DM_ABSTRACTAUTO, 0xFFFFFFFF); - COMPLIANCE_READ(target, &abstractauto, DM_ABSTRACTAUTO); - - /* Pulse reset. */ - target->reset_halt = true; - COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, 0)); - COMPLIANCE_TEST(ERROR_OK == assert_reset(target), "Must be able to assert NDMRESET"); - COMPLIANCE_TEST(ERROR_OK == deassert_reset(target), "Must be able to deassert NDMRESET"); - - /* Verify that most stuff is not affected by ndmreset. */ - COMPLIANCE_READ(target, &testvar_read, DM_ABSTRACTCS); - COMPLIANCE_TEST(get_field(testvar_read, DM_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, - "NDMRESET should not affect DM_ABSTRACTCS"); - COMPLIANCE_READ(target, &testvar_read, DM_ABSTRACTAUTO); - COMPLIANCE_TEST(testvar_read == abstractauto, "NDMRESET should not affect DM_ABSTRACTAUTO"); - - /* Clean up to avoid future test failures */ - COMPLIANCE_WRITE(target, DM_ABSTRACTCS, DM_ABSTRACTCS_CMDERR); - COMPLIANCE_WRITE(target, DM_ABSTRACTAUTO, 0); - - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_PROGBUFSIZE); i++) { - testvar = (i + 1) * 0x11111111; - COMPLIANCE_READ(target, &testvar_read, DM_PROGBUF0 + i); - COMPLIANCE_TEST(testvar_read == testvar, "PROGBUF words must not be affected by NDMRESET"); - } - - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_DATACOUNT); i++) { - testvar = (i + 1) * 0x11111111; - COMPLIANCE_READ(target, &testvar_read, DM_DATA0 + i); - COMPLIANCE_TEST(testvar_read == testvar, "DATA words must not be affected by NDMRESET"); - } - - /* Verify that DPC *is* affected by ndmreset. Since we don't know what it *should* be, - just verify that at least it's not the bogus value anymore. */ - - COMPLIANCE_TEST(bogus_dpc != 0xdeadbeef, "BOGUS DPC should have been set somehow (bug in compliance test)"); - COMPLIANCE_MUST_PASS(register_read_direct(target, &value, GDB_REGNO_DPC)); - COMPLIANCE_TEST(bogus_dpc != value, "NDMRESET should move DPC to reset value."); - - COMPLIANCE_TEST(riscv_halt_reason(target, 0) == RISCV_HALT_INTERRUPT, - "After NDMRESET halt, DCSR should report cause of halt"); - - /* DMACTIVE -- deasserting DMACTIVE should reset all the above values. */ - - /* Toggle dmactive */ - COMPLIANCE_WRITE(target, DM_DMCONTROL, 0); - COMPLIANCE_WRITE(target, DM_DMCONTROL, DM_DMCONTROL_DMACTIVE); - COMPLIANCE_READ(target, &testvar_read, DM_ABSTRACTCS); - COMPLIANCE_TEST(get_field(testvar_read, DM_ABSTRACTCS_CMDERR) == 0, "ABSTRACTCS.cmderr should reset to 0"); - COMPLIANCE_READ(target, &testvar_read, DM_ABSTRACTAUTO); - COMPLIANCE_TEST(testvar_read == 0, "ABSTRACTAUTO should reset to 0"); - - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_PROGBUFSIZE); i++) { - COMPLIANCE_READ(target, &testvar_read, DM_PROGBUF0 + i); - COMPLIANCE_TEST(testvar_read == 0, "PROGBUF words should reset to 0"); - } - - for (unsigned int i = 0; i < get_field(abstractcs, DM_ABSTRACTCS_DATACOUNT); i++) { - COMPLIANCE_READ(target, &testvar_read, DM_DATA0 + i); - COMPLIANCE_TEST(testvar_read == 0, "DATA words should reset to 0"); - } - - /* - * TODO: - * DCSR.cause priorities - * DCSR.stoptime/stopcycle - * DCSR.stepie - * DCSR.ebreak - * DCSR.prv - */ - - /* Halt every hart for any follow-up tests*/ - COMPLIANCE_MUST_PASS(riscv_halt(target)); - - uint32_t failed_tests = total_tests - passed_tests; - if (total_tests == passed_tests) { - LOG_INFO("ALL TESTS PASSED\n"); - return ERROR_OK; - } else { - LOG_INFO("%d TESTS FAILED\n", failed_tests); - return ERROR_FAIL; - } -}