diff --git a/src/target/riscv/riscv-013.c b/src/target/riscv/riscv-013.c index 4e3deabdf..34ec3c6d5 100644 --- a/src/target/riscv/riscv-013.c +++ b/src/target/riscv/riscv-013.c @@ -65,7 +65,13 @@ 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); +static int riscv013_test_sba_config_reg(struct target *target, target_addr_t legal_address, + uint32_t num_words, target_addr_t illegal_address, bool run_sbbusyerror_test); +void write_memory_sba_simple(struct target *target, target_addr_t addr, uint32_t* write_data, + 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 @@ -1588,6 +1594,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_sba_config_reg = &riscv013_test_sba_config_reg; generic_info->test_compliance = &riscv013_test_compliance; generic_info->version_specific = calloc(1, sizeof(riscv013_info_t)); if (!generic_info->version_specific) @@ -2935,6 +2942,357 @@ void riscv013_fill_dmi_nop_u64(struct target *target, char *buf) buf_set_u64((unsigned char *)buf, DTM_DMI_ADDRESS_OFFSET, info->abits, 0); } +/* Helper function for riscv013_test_sba_config_reg */ +static int get_max_sbaccess(struct target *target) +{ + RISCV013_INFO(info); + + uint32_t sbaccess128 = get_field(info->sbcs, DMI_SBCS_SBACCESS128); + uint32_t sbaccess64 = get_field(info->sbcs, DMI_SBCS_SBACCESS64); + uint32_t sbaccess32 = get_field(info->sbcs, DMI_SBCS_SBACCESS32); + uint32_t sbaccess16 = get_field(info->sbcs, DMI_SBCS_SBACCESS16); + uint32_t sbaccess8 = get_field(info->sbcs, DMI_SBCS_SBACCESS8); + + if (sbaccess128) + return 4; + else if (sbaccess64) + return 3; + else if (sbaccess32) + return 2; + else if (sbaccess16) + return 1; + else if (sbaccess8) + return 0; + else + return -1; +} + +static uint32_t get_num_sbdata_regs(struct target *target) +{ + RISCV013_INFO(info); + + uint32_t sbaccess128 = get_field(info->sbcs, DMI_SBCS_SBACCESS128); + uint32_t sbaccess64 = get_field(info->sbcs, DMI_SBCS_SBACCESS64); + uint32_t sbaccess32 = get_field(info->sbcs, DMI_SBCS_SBACCESS32); + + if (sbaccess128) + return 4; + else if (sbaccess64) + return 2; + else if (sbaccess32) + return 1; + else + return 0; +} + +static int riscv013_test_sba_config_reg(struct target *target, + target_addr_t legal_address, uint32_t num_words, + target_addr_t illegal_address, bool run_sbbusyerror_test) +{ + LOG_INFO("Testing System Bus Access as defined by RISC-V Debug Spec v0.13"); + + uint32_t tests_failed = 0; + + uint32_t rd_val; + uint32_t sbcs_orig; + dmi_read(target, &sbcs_orig, DMI_SBCS); + + uint32_t sbcs = sbcs_orig; + bool test_passed; + + int max_sbaccess = get_max_sbaccess(target); + + if (max_sbaccess == -1) { + LOG_ERROR("System Bus Access not supported in this config."); + return ERROR_FAIL; + } + + if (get_field(sbcs, DMI_SBCS_SBVERSION) != 1) { + LOG_ERROR("System Bus Access unsupported SBVERSION (%d). Only version 1 is supported.", + get_field(sbcs, DMI_SBCS_SBVERSION)); + return ERROR_FAIL; + } + + uint32_t num_sbdata_regs = get_num_sbdata_regs(target); + + uint32_t rd_buf[num_sbdata_regs]; + + /* Test 1: Simple write/read test */ + test_passed = true; + sbcs = set_field(sbcs_orig, DMI_SBCS_SBAUTOINCREMENT, 0); + dmi_write(target, DMI_SBCS, sbcs); + + uint32_t test_patterns[4] = {0xdeadbeef, 0xfeedbabe, 0x12345678, 0x08675309}; + for (uint32_t sbaccess = 0; sbaccess <= (uint32_t)max_sbaccess; sbaccess++) { + sbcs = set_field(sbcs, DMI_SBCS_SBACCESS, sbaccess); + dmi_write(target, DMI_SBCS, sbcs); + + uint32_t compare_mask = (sbaccess == 0) ? 0xff : (sbaccess == 1) ? 0xffff : 0xffffffff; + + for (uint32_t i = 0; i < num_words; i++) { + uint32_t addr = legal_address + (i << sbaccess); + uint32_t wr_data[num_sbdata_regs]; + for (uint32_t j = 0; j < num_sbdata_regs; j++) + wr_data[j] = test_patterns[j] + i; + write_memory_sba_simple(target, addr, wr_data, num_sbdata_regs, sbcs); + } + + for (uint32_t i = 0; i < num_words; i++) { + uint32_t addr = legal_address + (i << sbaccess); + read_memory_sba_simple(target, addr, rd_buf, num_sbdata_regs, sbcs); + for (uint32_t j = 0; j < num_sbdata_regs; j++) { + if (((test_patterns[j]+i)&compare_mask) != (rd_buf[j]&compare_mask)) { + LOG_ERROR("System Bus Access Test 1: Error reading non-autoincremented address %x," + "expected val = %x, read val = %x", addr, test_patterns[j]+i, rd_buf[j]); + test_passed = false; + tests_failed++; + } + } + } + } + if (test_passed) + LOG_INFO("System Bus Access Test 1: Simple write/read test PASSED."); + + /* Test 2: Address autoincrement test */ + target_addr_t curr_addr; + target_addr_t prev_addr; + test_passed = true; + sbcs = set_field(sbcs_orig, DMI_SBCS_SBAUTOINCREMENT, 1); + dmi_write(target, DMI_SBCS, sbcs); + + for (uint32_t sbaccess = 0; sbaccess <= (uint32_t)max_sbaccess; sbaccess++) { + sbcs = set_field(sbcs, DMI_SBCS_SBACCESS, sbaccess); + dmi_write(target, DMI_SBCS, sbcs); + + dmi_write(target, DMI_SBADDRESS0, legal_address); + read_sbcs_nonbusy(target, &sbcs); + curr_addr = legal_address; + for (uint32_t i = 0; i < num_words; i++) { + prev_addr = curr_addr; + read_sbcs_nonbusy(target, &sbcs); + curr_addr = sb_read_address(target); + if ((curr_addr - prev_addr != (uint32_t)(1 << sbaccess)) && (i != 0)) { + LOG_ERROR("System Bus Access Test 2: Error with address auto-increment, sbaccess = %x.", sbaccess); + test_passed = false; + tests_failed++; + } + dmi_write(target, DMI_SBDATA0, i); + } + + read_sbcs_nonbusy(target, &sbcs); + + dmi_write(target, DMI_SBADDRESS0, legal_address); + + uint32_t val; + sbcs = set_field(sbcs, DMI_SBCS_SBREADONDATA, 1); + dmi_write(target, DMI_SBCS, sbcs); + dmi_read(target, &val, DMI_SBDATA0); /* Dummy read to trigger first system bus read */ + curr_addr = legal_address; + for (uint32_t i = 0; i < num_words; i++) { + prev_addr = curr_addr; + read_sbcs_nonbusy(target, &sbcs); + curr_addr = sb_read_address(target); + if ((curr_addr - prev_addr != (uint32_t)(1 << sbaccess)) && (i != 0)) { + LOG_ERROR("System Bus Access Test 2: Error with address auto-increment, sbaccess = %x", sbaccess); + test_passed = false; + tests_failed++; + } + dmi_read(target, &val, DMI_SBDATA0); + read_sbcs_nonbusy(target, &sbcs); + if (i != val) { + LOG_ERROR("System Bus Access Test 2: Error reading auto-incremented address," + "expected val = %x, read val = %x.", i, val); + test_passed = false; + tests_failed++; + } + } + } + if (test_passed) + LOG_INFO("System Bus Access Test 2: Address auto-increment test PASSED."); + + /* Test 3: Read from illegal address */ + read_memory_sba_simple(target, illegal_address, rd_buf, 1, sbcs_orig); + + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBERROR) == 2) { + sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 2); + dmi_write(target, DMI_SBCS, sbcs); + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBERROR) == 0) + LOG_INFO("System Bus Access Test 3: Illegal address read test PASSED."); + else + LOG_ERROR("System Bus Access Test 3: Illegal address read test FAILED, unable to clear to 0."); + } else { + LOG_ERROR("System Bus Access Test 3: Illegal address read test FAILED, unable to set error code."); + } + + /* Test 4: Write to illegal address */ + write_memory_sba_simple(target, illegal_address, test_patterns, 1, sbcs_orig); + + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBERROR) == 2) { + sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 2); + dmi_write(target, DMI_SBCS, sbcs); + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBERROR) == 0) + LOG_INFO("System Bus Access Test 4: Illegal address write test PASSED."); + else { + LOG_ERROR("System Bus Access Test 4: Illegal address write test FAILED, unable to clear to 0."); + tests_failed++; + } + } else { + LOG_ERROR("System Bus Access Test 4: Illegal address write test FAILED, unable to set error code."); + tests_failed++; + } + + /* Test 5: Write with unsupported sbaccess size */ + uint32_t sbaccess128 = get_field(sbcs_orig, DMI_SBCS_SBACCESS128); + + if (sbaccess128) { + LOG_INFO("System Bus Access Test 5: SBCS sbaccess error test PASSED, all sbaccess sizes supported."); + } else { + sbcs = set_field(sbcs_orig, DMI_SBCS_SBACCESS, 4); + + write_memory_sba_simple(target, legal_address, test_patterns, 1, sbcs); + + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBERROR) == 4) { + sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 4); + dmi_write(target, DMI_SBCS, sbcs); + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBERROR) == 0) + LOG_INFO("System Bus Access Test 5: SBCS sbaccess error test PASSED."); + else { + LOG_ERROR("System Bus Access Test 5: SBCS sbaccess error test FAILED, unable to clear to 0."); + tests_failed++; + } + } else { + LOG_ERROR("System Bus Access Test 5: SBCS sbaccess error test FAILED, unable to set error code."); + tests_failed++; + } + } + + /* Test 6: Write to misaligned address */ + sbcs = set_field(sbcs_orig, DMI_SBCS_SBACCESS, 1); + + write_memory_sba_simple(target, legal_address+1, test_patterns, 1, sbcs); + + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBERROR) == 3) { + sbcs = set_field(sbcs_orig, DMI_SBCS_SBERROR, 3); + dmi_write(target, DMI_SBCS, sbcs); + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBERROR) == 0) + LOG_INFO("System Bus Access Test 6: SBCS address alignment error test PASSED"); + else { + LOG_ERROR("System Bus Access Test 6: SBCS address alignment error test FAILED, unable to clear to 0."); + tests_failed++; + } + } else { + LOG_ERROR("System Bus Access Test 6: SBCS address alignment error test FAILED, unable to set error code."); + tests_failed++; + } + + /* Test 7: Set sbbusyerror, only run this case in simulation as it is likely + * impossible to hit otherwise */ + if (run_sbbusyerror_test) { + sbcs = set_field(sbcs_orig, DMI_SBCS_SBREADONADDR, 1); + dmi_write(target, DMI_SBCS, sbcs); + + for (int i = 0; i < 16; i++) + dmi_write(target, DMI_SBDATA0, 0xdeadbeef); + + for (int i = 0; i < 16; i++) + dmi_write(target, DMI_SBADDRESS0, legal_address); + + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBBUSYERROR)) { + sbcs = set_field(sbcs_orig, DMI_SBCS_SBBUSYERROR, 1); + dmi_write(target, DMI_SBCS, sbcs); + dmi_read(target, &rd_val, DMI_SBCS); + if (get_field(rd_val, DMI_SBCS_SBBUSYERROR) == 0) + LOG_INFO("System Bus Access Test 7: SBCS sbbusyerror test PASSED."); + else { + LOG_ERROR("System Bus Access Test 7: SBCS sbbusyerror test FAILED, unable to clear to 0."); + tests_failed++; + } + } else { + LOG_ERROR("System Bus Access Test 7: SBCS sbbusyerror test FAILED, unable to set error code."); + tests_failed++; + } + } + + if (tests_failed == 0) { + LOG_INFO("ALL TESTS PASSED"); + return ERROR_OK; + } else { + LOG_ERROR("%d TESTS FAILED", tests_failed); + return ERROR_FAIL; + } + +} + +void write_memory_sba_simple(struct target *target, target_addr_t addr, + uint32_t *write_data, uint32_t write_size, uint32_t sbcs) +{ + RISCV013_INFO(info); + + uint32_t rd_sbcs; + uint32_t masked_addr; + + uint32_t sba_size = get_field(info->sbcs, DMI_SBCS_SBASIZE); + + read_sbcs_nonbusy(target, &rd_sbcs); + + uint32_t sbcs_no_readonaddr = set_field(sbcs, DMI_SBCS_SBREADONADDR, 0); + dmi_write(target, DMI_SBCS, sbcs_no_readonaddr); + + for (uint32_t i = 0; i < sba_size/32; i++) { + masked_addr = (addr >> 32*i) & 0xffffffff; + + if (i != 3) + dmi_write(target, DMI_SBADDRESS0+i, masked_addr); + else + dmi_write(target, DMI_SBADDRESS3, masked_addr); + } + + /* Write SBDATA registers starting with highest address, since write to + * SBDATA0 triggers write */ + for (int i = write_size-1; i >= 0; i--) + dmi_write(target, DMI_SBDATA0+i, write_data[i]); +} + +void read_memory_sba_simple(struct target *target, target_addr_t addr, + uint32_t *rd_buf, uint32_t read_size, uint32_t sbcs) +{ + RISCV013_INFO(info); + + uint32_t rd_sbcs; + uint32_t masked_addr; + + uint32_t sba_size = get_field(info->sbcs, DMI_SBCS_SBASIZE); + + read_sbcs_nonbusy(target, &rd_sbcs); + + uint32_t sbcs_readonaddr = set_field(sbcs, DMI_SBCS_SBREADONADDR, 1); + dmi_write(target, DMI_SBCS, sbcs_readonaddr); + + /* Write addresses starting with highest address register */ + for (int i = sba_size/32-1; i >= 0; i--) { + masked_addr = (addr >> 32*i) & 0xffffffff; + + if (i != 3) + dmi_write(target, DMI_SBADDRESS0+i, masked_addr); + else + dmi_write(target, DMI_SBADDRESS3, masked_addr); + } + + read_sbcs_nonbusy(target, &rd_sbcs); + + for (uint32_t i = 0; i < read_size; i++) + dmi_read(target, &(rd_buf[i]), DMI_SBDATA0+i); +} + int riscv013_dmi_write_u64_bits(struct target *target) { RISCV013_INFO(info); @@ -3143,8 +3501,8 @@ int riscv013_test_compliance(struct target *target) 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) { + 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; @@ -3278,7 +3636,7 @@ int riscv013_test_compliance(struct target *target) /* 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 = 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."); @@ -3492,10 +3850,12 @@ int riscv013_test_compliance(struct target *target) /* 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) + uint32_t failed_tests = total_tests - passed_tests; + if (total_tests == passed_tests) { + LOG_INFO("ALL TESTS PASSED\n"); return ERROR_OK; - else + } else { + LOG_INFO("%d TESTS FAILED\n", failed_tests); return ERROR_FAIL; + } } diff --git a/src/target/riscv/riscv.c b/src/target/riscv/riscv.c index de47d25fb..dceed61fa 100644 --- a/src/target/riscv/riscv.c +++ b/src/target/riscv/riscv.c @@ -1557,6 +1557,35 @@ COMMAND_HANDLER(riscv_dmi_write) } } +COMMAND_HANDLER(riscv_test_sba_config_reg) +{ + if (CMD_ARGC != 4) { + LOG_ERROR("Command takes exactly 4 arguments"); + return ERROR_COMMAND_SYNTAX_ERROR; + } + + struct target *target = get_current_target(CMD_CTX); + RISCV_INFO(r); + + target_addr_t legal_address; + uint32_t num_words; + target_addr_t illegal_address; + bool run_sbbusyerror_test; + + COMMAND_PARSE_NUMBER(target_addr, CMD_ARGV[0], legal_address); + COMMAND_PARSE_NUMBER(u32, CMD_ARGV[1], num_words); + COMMAND_PARSE_NUMBER(target_addr, CMD_ARGV[2], illegal_address); + COMMAND_PARSE_ON_OFF(CMD_ARGV[3], run_sbbusyerror_test); + + if (r->test_sba_config_reg) { + return r->test_sba_config_reg(target, legal_address, num_words, + illegal_address, run_sbbusyerror_test); + } else { + LOG_ERROR("test_sba_config_reg is not implemented for this target."); + return ERROR_FAIL; + } +} + static const struct command_registration riscv_exec_command_handlers[] = { { .name = "test_compliance", @@ -1633,6 +1662,19 @@ static const struct command_registration riscv_exec_command_handlers[] = { .usage = "riscv dmi_write address value", .help = "Perform a 32-bit DMI write of value at address." }, + { + .name = "test_sba_config_reg", + .handler = riscv_test_sba_config_reg, + .mode = COMMAND_ANY, + .usage = "riscv test_sba_config_reg legal_address num_words" + "illegal_address run_sbbusyerror_test[on/off]", + .help = "Perform a series of tests on the SBCS register." + "Inputs are a legal, 128-byte aligned address and a number of words to" + "read/write starting at that address (i.e., address range [legal address," + "legal_address+word_size*num_words) must be legally readable/writable)" + ", an illegal, 128-byte aligned address for error flag/handling cases," + "and whether sbbusyerror test should be run." + }, COMMAND_REGISTRATION_DONE }; diff --git a/src/target/riscv/riscv.h b/src/target/riscv/riscv.h index 419d05197..deca21e26 100644 --- a/src/target/riscv/riscv.h +++ b/src/target/riscv/riscv.h @@ -126,8 +126,10 @@ 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); + int (*test_sba_config_reg)(struct target *target, target_addr_t legal_address, + uint32_t num_words, target_addr_t illegal_address, bool run_sbbusyerror_test); + int (*test_compliance)(struct target *target); } riscv_info_t; /* Wall-clock timeout for a command/access. Settable via RISC-V Target commands.*/