diff --git a/src/target/riscv/opcodes.h b/src/target/riscv/opcodes.h index dd51c809d..de85aadd8 100644 --- a/src/target/riscv/opcodes.h +++ b/src/target/riscv/opcodes.h @@ -224,6 +224,9 @@ static uint32_t ebreak_c(void) return MATCH_C_EBREAK; } +static uint32_t wfi(void) __attribute__ ((unused)); +static uint32_t wfi(void) { return MATCH_WFI; } + static uint32_t fence_i(void) __attribute__ ((unused)); static uint32_t fence_i(void) { diff --git a/src/target/riscv/program.h b/src/target/riscv/program.h index d641be1be..310460c28 100644 --- a/src/target/riscv/program.h +++ b/src/target/riscv/program.h @@ -52,8 +52,8 @@ int riscv_program_insert(struct riscv_program *p, riscv_insn_t i); * memory. */ int riscv_program_save_to_dscratch(struct riscv_program *p, enum gdb_regno to_save); -/* Helpers to assembly various instructions. Return 0 on success. These might - * assembly into a multi-instruction sequence that overwrites some other +/* Helpers to assemble various instructions. Return 0 on success. These might + * assemble into a multi-instruction sequence that overwrites some other * register, but those will be properly saved and restored. */ int riscv_program_lwr(struct riscv_program *p, enum gdb_regno d, enum gdb_regno a, int o); int riscv_program_lhr(struct riscv_program *p, enum gdb_regno d, enum gdb_regno a, int o); diff --git a/src/target/riscv/riscv-013.c b/src/target/riscv/riscv-013.c index 55075de80..4e3deabdf 100644 --- a/src/target/riscv/riscv-013.c +++ b/src/target/riscv/riscv-013.c @@ -65,6 +65,7 @@ static int read_memory(struct target *target, target_addr_t address, uint32_t size, uint32_t count, uint8_t *buffer); static int write_memory(struct target *target, target_addr_t address, uint32_t size, uint32_t count, const uint8_t *buffer); +static int riscv013_test_compliance(struct target *target); /** * Since almost everything can be accomplish by scanning the dbus register, all @@ -1587,6 +1588,7 @@ static int init_target(struct command_context *cmd_ctx, generic_info->authdata_write = &riscv013_authdata_write; generic_info->dmi_read = &dmi_read; generic_info->dmi_write = &dmi_write; + generic_info->test_compliance = &riscv013_test_compliance; generic_info->version_specific = calloc(1, sizeof(riscv013_info_t)); if (!generic_info->version_specific) return ERROR_FAIL; @@ -3043,3 +3045,457 @@ void riscv013_clear_abstract_error(struct target *target) /* Clear the error status. */ dmi_write(target, DMI_ABSTRACTCS, abstractcs & DMI_ABSTRACTCS_CMDERR); } + +#define COMPLIANCE_TEST(b, message) \ +{ \ + int pass = 0; \ + if (b) { \ + pass = 1; \ + passed_tests++; \ + } \ + LOG_INFO("%s test %d (%s)\n", (pass) ? "PASSED" : "FAILED", total_tests, message); \ + 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("Testing Compliance against RISC-V Debug Spec v0.13"); + + if (!riscv_rtos_enabled(target)) { + LOG_ERROR("Please run with -rtos riscv to run compliance test."); + return ERROR_FAIL; + } + + int total_tests = 0; + int passed_tests = 0; + + uint32_t dmcontrol_orig = DMI_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, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HARTRESET, 1)); + COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HARTRESET, 0)); + COMPLIANCE_READ(target, &dmcontrol, DMI_DMCONTROL); + COMPLIANCE_TEST((get_field(dmcontrol, DMI_DMCONTROL_HARTRESET) == 0), + "DMCONTROL.hartreset can be 0 or RW."); + + /* hasel */ + COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HASEL, 1)); + COMPLIANCE_WRITE(target, DMI_DMCONTROL, set_field(dmcontrol_orig, DMI_DMCONTROL_HASEL, 0)); + COMPLIANCE_READ(target, &dmcontrol, DMI_DMCONTROL); + COMPLIANCE_TEST((get_field(dmcontrol, DMI_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_all_harts(target)); + /* This bit is not actually readable according to the spec, so nothing to check.*/ + + /* DMSTATUS */ + COMPLIANCE_CHECK_RO(target, DMI_DMSTATUS); + + /* resumereq */ + /* This bit is not actually readable according to the spec, so nothing to check.*/ + COMPLIANCE_MUST_PASS(riscv_resume_all_harts(target)); + + /* Halt all harts again so the test can continue.*/ + COMPLIANCE_MUST_PASS(riscv_halt_all_harts(target)); + + /* HARTINFO: Read-Only. This is per-hart, so need to adjust hartsel. */ + uint32_t hartinfo; + COMPLIANCE_READ(target, &hartinfo, DMI_HARTINFO); + for (int hartsel = 0; hartsel < riscv_count_harts(target); hartsel++) { + COMPLIANCE_MUST_PASS(riscv_set_current_hartid(target, hartsel)); + + COMPLIANCE_CHECK_RO(target, DMI_HARTINFO); + + /* $dscratch CSRs */ + uint32_t nscratch = get_field(hartinfo, DMI_HARTINFO_NSCRATCH); + for (unsigned int d = 0; d < nscratch; d++) { + riscv_reg_t testval, testval_read; + /* Because DSCRATCH 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 DSCRATCH."); + struct riscv_program program32; + riscv_program_init(&program32, target); + riscv_program_csrw(&program32, GDB_REGNO_S0, GDB_REGNO_DSCRATCH + d); + riscv_program_csrr(&program32, GDB_REGNO_S1, GDB_REGNO_DSCRATCH + d); + riscv_program_fence(&program32); + riscv_program_ebreak(&program32); + COMPLIANCE_TEST(riscv_program_exec(&program32, target) == ERROR_OK, + "Accessing DSCRATCH 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 DSCRATCH."); + if (riscv_xlen(target) > 32) { + COMPLIANCE_TEST(testval == testval_read, + "All DSCRATCH registers in HARTINFO must be R/W."); + } else { + COMPLIANCE_TEST(testval_read == (testval & 0xFFFFFFFF), + "All DSCRATCH registers in HARTINFO must be R/W."); + } + } + } + } + /* TODO: dataaccess */ + if (get_field(hartinfo, DMI_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, DMI_HALTSUM0); + COMPLIANCE_TEST(testvar_read == expected_haltsum0, + "HALTSUM0 should report summary of up to 32 halted harts"); + + COMPLIANCE_WRITE(target, DMI_HALTSUM0, 0xffffffff); + COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM0); + COMPLIANCE_TEST(testvar_read == expected_haltsum0, "HALTSUM0 should be R/O"); + + COMPLIANCE_WRITE(target, DMI_HALTSUM0, 0x0); + COMPLIANCE_READ(target, &testvar_read, DMI_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, DMI_HALTSUM1); + COMPLIANCE_TEST(testvar_read == expected_haltsum1, + "HALTSUM1 should report summary of up to 1024 halted harts"); + + COMPLIANCE_WRITE(target, DMI_HALTSUM1, 0xffffffff); + COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM1); + COMPLIANCE_TEST(testvar_read == expected_haltsum1, "HALTSUM1 should be R/O"); + + COMPLIANCE_WRITE(target, DMI_HALTSUM1, 0x0); + COMPLIANCE_READ(target, &testvar_read, DMI_HALTSUM1); + COMPLIANCE_TEST(testvar_read == expected_haltsum1, "HALTSUM1 should be R/O"); + + /* TODO: HAWINDOWSEL */ + + /* TODO: HAWINDOW */ + + /* ABSTRACTCS */ + + uint32_t abstractcs; + COMPLIANCE_READ(target, &abstractcs, DMI_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, DMI_ABSTRACTCS_DATACOUNT); i++) { + testvar = (i + 1) * 0x11111111; + if (invert) + testvar = ~testvar; + COMPLIANCE_WRITE(target, DMI_DATA0 + i, testvar); + } + for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) { + testvar = (i + 1) * 0x11111111; + if (invert) + testvar = ~testvar; + COMPLIANCE_READ(target, &testvar_read, DMI_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, DMI_ABSTRACTCS_PROGBUFSIZE); i++) { + testvar = (i + 1) * 0x11111111; + if (invert) + testvar = ~testvar; + COMPLIANCE_WRITE(target, DMI_PROGBUF0 + i, testvar); + } + for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) { + testvar = (i + 1) * 0x11111111; + if (invert) + testvar = ~testvar; + COMPLIANCE_READ(target, &testvar_read, DMI_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, DMI_COMMAND, 0xAAAAAAAA); + COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS); + COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, \ + "Illegal COMMAND should result in UNSUPPORTED"); + COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR); + + COMPLIANCE_WRITE(target, DMI_COMMAND, 0x55555555); + COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS); + COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, \ + "Illegal COMMAND should result in UNSUPPORTED"); + COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_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 brances 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 brances 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, DMI_ABSTRACTAUTO, 0xFFFFFFFF); + uint32_t abstractauto; + uint32_t busy; + COMPLIANCE_READ(target, &abstractauto, DMI_ABSTRACTAUTO); + COMPLIANCE_WRITE(target, DMI_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, DMI_ABSTRACTAUTO, 0x0); + COMPLIANCE_MUST_PASS(riscv_program_exec(&program, target)); + testvar++; + COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0xFFFFFFFF); + COMPLIANCE_READ(target, &abstractauto, DMI_ABSTRACTAUTO); + uint32_t autoexec_data = get_field(abstractauto, DMI_ABSTRACTAUTO_AUTOEXECDATA); + uint32_t autoexec_progbuf = get_field(abstractauto, DMI_ABSTRACTAUTO_AUTOEXECPROGBUF); + for (unsigned int i = 0; i < 12; i++) { + COMPLIANCE_READ(target, &testvar_read, DMI_DATA0 + i); + do { + COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS); + busy = get_field(testvar_read, DMI_ABSTRACTCS_BUSY); + } while (busy); + if (autoexec_data & (1 << i)) { + COMPLIANCE_TEST(i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT), + "AUTOEXEC may be writable up to DATACOUNT bits."); + testvar++; + } + } + for (unsigned int i = 0; i < 16; i++) { + COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i); + do { + COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS); + busy = get_field(testvar_read, DMI_ABSTRACTCS_BUSY); + } while (busy); + if (autoexec_progbuf & (1 << i)) { + COMPLIANCE_TEST(i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE), + "AUTOEXEC may be writable up to PROGBUFSIZE bits."); + testvar++; + } + } + + COMPLIANCE_WRITE(target, DMI_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, DMI_COMMAND, 0xFFFFFFFF); + + for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) { + testvar = (i + 1) * 0x11111111; + COMPLIANCE_WRITE(target, DMI_PROGBUF0 + i, testvar); + } + + for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) { + testvar = (i + 1) * 0x11111111; + COMPLIANCE_WRITE(target, DMI_DATA0 + i, testvar); + } + + COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0xFFFFFFFF); + COMPLIANCE_READ(target, &abstractauto, DMI_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, DMI_ABSTRACTCS); + COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR) == CMDERR_NOT_SUPPORTED, + "NDMRESET should not affect DMI_ABSTRACTCS"); + COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTAUTO); + COMPLIANCE_TEST(testvar_read == abstractauto, "NDMRESET should not affect DMI_ABSTRACTAUTO"); + + /* Clean up to avoid future test failures */ + COMPLIANCE_WRITE(target, DMI_ABSTRACTCS, DMI_ABSTRACTCS_CMDERR); + COMPLIANCE_WRITE(target, DMI_ABSTRACTAUTO, 0); + + for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) { + testvar = (i + 1) * 0x11111111; + COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i); + COMPLIANCE_TEST(testvar_read == testvar, "PROGBUF words must not be affected by NDMRESET"); + } + + for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) { + testvar = (i + 1) * 0x11111111; + COMPLIANCE_READ(target, &testvar_read, DMI_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, DMI_DMCONTROL, 0); + COMPLIANCE_WRITE(target, DMI_DMCONTROL, DMI_DMCONTROL_DMACTIVE); + COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTCS); + COMPLIANCE_TEST(get_field(testvar_read, DMI_ABSTRACTCS_CMDERR) == 0, "ABSTRACTCS.cmderr should reset to 0"); + COMPLIANCE_READ(target, &testvar_read, DMI_ABSTRACTAUTO); + COMPLIANCE_TEST(testvar_read == 0, "ABSTRACTAUTO should reset to 0"); + + for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_PROGBUFSIZE); i++) { + COMPLIANCE_READ(target, &testvar_read, DMI_PROGBUF0 + i); + COMPLIANCE_TEST(testvar_read == 0, "PROGBUF words should reset to 0"); + } + + for (unsigned int i = 0; i < get_field(abstractcs, DMI_ABSTRACTCS_DATACOUNT); i++) { + COMPLIANCE_READ(target, &testvar_read, DMI_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_all_harts(target)); + + LOG_INFO("PASSED %d of %d TESTS\n", passed_tests, total_tests); + + if (total_tests == passed_tests) + return ERROR_OK; + else + return ERROR_FAIL; +} diff --git a/src/target/riscv/riscv.c b/src/target/riscv/riscv.c index 9dca04db4..de47d25fb 100644 --- a/src/target/riscv/riscv.c +++ b/src/target/riscv/riscv.c @@ -1323,6 +1323,25 @@ COMMAND_HANDLER(riscv_set_reset_timeout_sec) return ERROR_OK; } +COMMAND_HANDLER(riscv_test_compliance) { + + struct target *target = get_current_target(CMD_CTX); + + RISCV_INFO(r); + + if (CMD_ARGC > 0) { + LOG_ERROR("Command does not take any parameters."); + return ERROR_COMMAND_SYNTAX_ERROR; + } + + if (r->test_compliance) { + return r->test_compliance(target); + } else { + LOG_ERROR("This target does not support this command (may implement an older version of the spec)."); + return ERROR_FAIL; + } +} + COMMAND_HANDLER(riscv_set_prefer_sba) { if (CMD_ARGC != 1) { @@ -1539,6 +1558,13 @@ COMMAND_HANDLER(riscv_dmi_write) } static const struct command_registration riscv_exec_command_handlers[] = { + { + .name = "test_compliance", + .handler = riscv_test_compliance, + .mode = COMMAND_EXEC, + .usage = "riscv test_compliance", + .help = "Runs a basic compliance test suite against the RISC-V Debug Spec." + }, { .name = "set_command_timeout_sec", .handler = riscv_set_command_timeout_sec, diff --git a/src/target/riscv/riscv.h b/src/target/riscv/riscv.h index 47782d4e2..419d05197 100644 --- a/src/target/riscv/riscv.h +++ b/src/target/riscv/riscv.h @@ -125,6 +125,9 @@ typedef struct { int (*dmi_read)(struct target *target, uint32_t *value, uint32_t address); int (*dmi_write)(struct target *target, uint32_t address, uint32_t value); + + int (*test_compliance)(struct target *target); + } riscv_info_t; /* Wall-clock timeout for a command/access. Settable via RISC-V Target commands.*/