diff --git a/src/target/riscv32.c b/src/target/riscv32.c index 5f2ce004ed4..b9d01797f7c 100644 --- a/src/target/riscv32.c +++ b/src/target/riscv32.c @@ -38,6 +38,7 @@ #include "jep106.h" #include "riscv_debug.h" #include "gdb_packet.h" +#include "adiv5.h" typedef struct riscv32_regs { uint32_t gprs[32]; @@ -71,8 +72,6 @@ static ssize_t riscv32_reg_read(target_s *target, uint32_t c, void *data, size_t static ssize_t riscv32_reg_write(target_s *target, uint32_t c, const void *data, size_t max); static void riscv32_regs_read(target_s *target, void *data); static void riscv32_regs_write(target_s *target, const void *data); -static void riscv32_mem_read(target_s *target, void *dest, target_addr_t src, size_t len); -static void riscv32_mem_write(target_s *target, target_addr_t dest, const void *src, size_t len); static int riscv32_breakwatch_set(target_s *target, breakwatch_s *breakwatch); static int riscv32_breakwatch_clear(target_s *target, breakwatch_s *breakwatch); @@ -215,19 +214,15 @@ uint32_t riscv32_pack_data(const void *const src, const uint8_t access_width) return 0; } -static void riscv32_mem_read(target_s *const target, void *const dest, const target_addr_t src, const size_t len) +static void riscv32_abstract_mem_read( + riscv_hart_s *const hart, void *const dest, const target_addr_t src, const size_t len) { - DEBUG_TARGET("Performing %zu byte read of %08" PRIx32 "\n", len, src); - /* If we're asked to do a 0-byte read, do nothing */ - if (!len) - return; - riscv_hart_s *const hart = riscv_hart_struct(target); - /* Figure out the maxmial width of access to perform, up to the bitness of the target */ + /* Figure out the maximal width of access to perform, up to the bitness of the target */ const uint8_t access_width = riscv_mem_access_width(hart, src, len); const uint8_t access_length = 1U << access_width; /* Build the access command */ - const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_READ | (access_width << RV_MEM_ACCESS_SHIFT) | - (access_length < len ? RV_MEM_ADDR_POST_INC : 0U); + const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_READ | (access_width << RV_ABST_MEM_ACCESS_SHIFT) | + (access_length < len ? RV_ABST_MEM_ADDR_POST_INC : 0U); /* Write the address to read to arg1 */ if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA1, src)) return; @@ -244,19 +239,15 @@ static void riscv32_mem_read(target_s *const target, void *const dest, const tar } } -static void riscv32_mem_write(target_s *const target, const target_addr_t dest, const void *const src, const size_t len) +static void riscv32_abstract_mem_write( + riscv_hart_s *const hart, const target_addr_t dest, const void *const src, const size_t len) { - DEBUG_TARGET("Performing %zu byte write of %08" PRIx32 "\n", len, dest); - /* If we're asked to do a 0-byte read, do nothing */ - if (!len) - return; - riscv_hart_s *const hart = riscv_hart_struct(target); /* Figure out the maxmial width of access to perform, up to the bitness of the target */ const uint8_t access_width = riscv_mem_access_width(hart, dest, len); const uint8_t access_length = 1U << access_width; /* Build the access command */ - const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_WRITE | (access_width << RV_MEM_ACCESS_SHIFT) | - (access_length < len ? RV_MEM_ADDR_POST_INC : 0U); + const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_WRITE | (access_width << RV_ABST_MEM_ACCESS_SHIFT) | + (access_length < len ? RV_ABST_MEM_ADDR_POST_INC : 0U); /* Write the address to write to arg1 */ if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA1, dest)) return; @@ -272,6 +263,340 @@ static void riscv32_mem_write(target_s *const target, const target_addr_t dest, } } +static void riscv_sysbus_check(riscv_hart_s *const hart) +{ + uint32_t status = 0; + /* Read back the system bus status */ + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &status)) + return; + /* Store the result and reset the value in the control/status register */ + hart->status = (status >> 12U) & RISCV_HART_OTHER; + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, RISCV_HART_OTHER << 12U)) + return; + /* If something goes wrong, tell the user */ + if (hart->status != RISCV_HART_NO_ERROR) + DEBUG_WARN("memory access failed: %u\n", hart->status); +} + +static void riscv32_sysbus_mem_native_read(riscv_hart_s *const hart, void *const dest, const target_addr_t src, + const size_t len, const uint8_t access_width, const uint8_t access_length) +{ + /* Build the access command */ + const uint32_t command = ((uint32_t)access_width << RV_SYSBUS_MEM_ACCESS_SHIFT) | RV_SYSBUS_MEM_READ_ON_ADDR | + (access_length < len ? RV_SYSBUS_MEM_ADDR_POST_INC | RV_SYSBUS_MEM_READ_ON_DATA : 0U); + /* + * Write the command setup to the access control register + * Then set up the read by writing the address to the address register + */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, command) || + !riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_ADDR0, src)) + return; + uint8_t *const data = (uint8_t *)dest; + for (size_t offset = 0; offset < len; offset += access_length) { + uint32_t status = RV_SYSBUS_STATUS_BUSY; + /* Wait for the current read cycle to complete */ + while (status & RV_SYSBUS_STATUS_BUSY) { + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &status)) + return; + } + /* If this would be the last read, clean up the access control register */ + if (offset + access_length == len && (command & RV_SYSBUS_MEM_ADDR_POST_INC)) { + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, 0)) + return; + } + uint32_t value = 0; + /* Read back and unpack the data for this block */ + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_DATA0, &value)) + return; + riscv32_unpack_data(data + offset, value, access_width); + } + riscv_sysbus_check(hart); +} + +static void riscv32_sysbus_mem_adjusted_read(riscv_hart_s *const hart, void *const dest, const target_addr_t src, + const uint8_t access_length, const uint8_t access_width, const uint8_t native_access_length) +{ + const target_addr_t alignment = ~(native_access_length - 1U); + /* + * On a 32-bit target the only possible widths are 8- 16- and 32-bit, so after the adjustment loop, + * there are only and exactly 2 possible cases to handle here: 16- and 32-bit access. + */ + switch (access_width) { + case RV_MEM_ACCESS_16_BIT: { + uint16_t value = 0; + /* Run the 16-bit native read, storing the result in `value` */ + riscv32_sysbus_mem_native_read( + hart, &value, src & alignment, native_access_length, RV_MEM_ACCESS_16_BIT, native_access_length); + /* Having completed the read, unpack the data (we only care about a single byte in the access) */ + adiv5_unpack_data(dest, src, value, ALIGN_8BIT); + break; + } + case RV_MEM_ACCESS_32_BIT: { + uint32_t value = 0; + /* Run the 32-bit native read, storing the result in `value` */ + riscv32_sysbus_mem_native_read( + hart, &value, src & alignment, native_access_length, RV_MEM_ACCESS_32_BIT, native_access_length); + + char *data = (char *)dest; + /* Figure out from the access length the initial unpack and adjustment */ + const uint8_t adjustment = access_length & (uint8_t)~1U; + /* Having completed the read, unpack the first part of the data (two bytes) */ + if (adjustment) + data = (char *)adiv5_unpack_data(data, src, value, ALIGN_16BIT); + /* Now unpack the remaining byte if necessary */ + if (access_length & 1U) + adiv5_unpack_data(data, src + adjustment, value, ALIGN_8BIT); + break; + } + } +} + +static void riscv32_sysbus_mem_read( + riscv_hart_s *const hart, void *const dest, const target_addr_t src, const size_t len) +{ + /* Figure out the maxmial width of access to perform, up to the bitness of the target */ + const uint8_t access_width = riscv_mem_access_width(hart, src, len); + const uint8_t access_length = (uint8_t)(1U << access_width); + /* Check if the access is a natural/native width */ + if (hart->flags & access_length) { + riscv32_sysbus_mem_native_read(hart, dest, src, len, access_width, access_length); + return; + } + + /* If we were unable to do this using a native access, find the next largest supported access width */ + uint8_t native_access_width = access_width; + while (!((hart->flags >> native_access_width) & 1U) && native_access_width < RV_MEM_ACCESS_32_BIT) + ++native_access_width; + const uint8_t native_access_length = (uint8_t)(1U << native_access_width); + + /* Figure out how much the length is getting adjusted by in the first read to make it aligned */ + const target_addr_t length_adjustment = src & (native_access_length - 1U); + /* + * Having done this, figure out how long the resulting read actually is so we can fill enough of the + * destination buffer with a single read + */ + const uint8_t read_length = + len + length_adjustment <= native_access_length ? len : native_access_length - length_adjustment; + + /* Do the initial adjusted access */ + size_t remainder = len; + target_addr_t address = src; + uint8_t *data = (uint8_t *)dest; + riscv32_sysbus_mem_adjusted_read(hart, data, address, read_length, native_access_width, native_access_length); + + /* After doing the initial access, adjust the location of the next and do any follow-up accesses required */ + remainder -= read_length; + address += read_length; + data += read_length; + + /* + * Now we're aligned to the wider access width, do another set of reads if there's + * any remainder. Do this till we either reach nothing left, or we have another small left-over amount + */ + if (!remainder) + return; + const size_t amount = remainder & ~(native_access_length - 1U); + if (amount) + riscv32_sysbus_mem_native_read(hart, data, address, amount, native_access_width, native_access_length); + remainder -= amount; + address += (uint32_t)amount; + data += amount; + + /* If there's any data left to read, do another adjusted access to grab it */ + if (remainder) + riscv32_sysbus_mem_adjusted_read(hart, data, address, remainder, native_access_width, native_access_length); +} + +static void riscv32_sysbus_mem_native_write(riscv_hart_s *const hart, const target_addr_t dest, const void *const src, + const size_t len, const uint8_t access_width, const uint8_t access_length) +{ + /* Build the access command */ + const uint32_t command = ((uint32_t)access_width << RV_SYSBUS_MEM_ACCESS_SHIFT) | + (access_length < len ? RV_SYSBUS_MEM_ADDR_POST_INC : 0U); + /* + * Write the command setup to the access control register + * Then set up the write by writing the address to the address register + */ + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, command) || + !riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_ADDR0, dest)) + return; + const uint8_t *const data = (const uint8_t *)src; + for (size_t offset = 0; offset < len; offset += access_length) { + /* Pack the data for this block and write it */ + const uint32_t value = riscv32_pack_data(data + offset, access_width); + if (!riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_DATA0, value)) + return; + + uint32_t status = RV_SYSBUS_STATUS_BUSY; + /* Wait for the current write cycle to complete */ + while (status & RV_SYSBUS_STATUS_BUSY) { + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &status)) + return; + } + } + riscv_sysbus_check(hart); +} + +static void riscv32_sysbus_mem_adjusted_write(riscv_hart_s *const hart, const target_addr_t dest, const void *const src, + const uint8_t access_length, const uint8_t access_width, const uint8_t native_access_length) +{ + const target_addr_t alignment = ~(native_access_length - 1U); + /* + * On a 32-bit target the only possible widths are 8- 16- and 32-bit, so after the adjustment loop, + * there are only and exactly 2 possible cases to handle here: 16- and 32-bit access. + * The basic premise here is that we have to read to correctly write - to do a N bit write with a + * wider access primitive, we first have to read back what's at the target aligned location, replace + * the correct set of bits in the target value, then write the new combined value back + */ + switch (access_width) { + case RV_MEM_ACCESS_16_BIT: { + uint16_t value = 0; + /* Start by reading 16 bits */ + riscv32_sysbus_mem_native_read( + hart, &value, dest & alignment, native_access_length, RV_MEM_ACCESS_16_BIT, native_access_length); + /* Now replace the part to write (must be done on the widened version of the value) */ + uint32_t widened_value = value; + /* + * Note that to get here we're doing a 2 byte access for 1 byte so we only care about a single byte + * replacement. We also have to constrain the replacement to only happen in the lower 16 bits. + */ + adiv5_pack_data(dest & ~2U, src, &widened_value, ALIGN_8BIT); + value = (uint16_t)widened_value; + /* And finally write the new value back */ + riscv32_sysbus_mem_native_write( + hart, dest & alignment, &value, native_access_length, RV_MEM_ACCESS_16_BIT, native_access_length); + break; + } + case RV_MEM_ACCESS_32_BIT: { + uint32_t value = 0; + /* Start by reading 32 bits */ + riscv32_sysbus_mem_native_read( + hart, &value, dest & alignment, native_access_length, RV_MEM_ACCESS_32_BIT, native_access_length); + + /* Now replace the part to write */ + const char *data = (const char *)src; + /* Figure out from the access length the initial pack and adjustment */ + const uint8_t adjustment = access_length & (uint8_t)~1U; + if (adjustment) + data = (const char *)adiv5_pack_data(dest, data, &value, ALIGN_16BIT); + /* Now pack the remaining byte if necessary */ + if (access_length & 1) + adiv5_pack_data(dest + adjustment, data, &value, ALIGN_8BIT); + /* And finally write the new value back */ + riscv32_sysbus_mem_native_write( + hart, dest & alignment, &value, native_access_length, RV_MEM_ACCESS_32_BIT, native_access_length); + break; + } + } +} + +static void riscv32_sysbus_mem_write( + riscv_hart_s *const hart, const target_addr_t dest, const void *const src, const size_t len) +{ + /* Figure out the maxmial width of access to perform, up to the bitness of the target */ + const uint8_t access_width = riscv_mem_access_width(hart, dest, len); + const uint8_t access_length = 1U << access_width; + /* Check if the access is a natural/native width */ + if (hart->flags & access_length) { + riscv32_sysbus_mem_native_write(hart, dest, src, len, access_width, access_length); + return; + } + + /* If we were unable to do this using a native access, find the next largest supported access width */ + uint8_t native_access_width = access_width; + while (!((hart->flags >> native_access_width) & 1U) && native_access_width < RV_MEM_ACCESS_32_BIT) + ++native_access_width; + const uint8_t native_access_length = (uint8_t)(1U << native_access_width); + + /* Figure out how much the length is getting adjusted by in the first write to make it aligned */ + const target_addr_t length_adjustment = dest & (native_access_length - 1U); + /* + * Having done this, figure out how long the resulting write actually is so we can fill enough of the + * destination buffer with a single write + */ + const uint8_t write_length = + len + length_adjustment <= native_access_length ? len : native_access_length - length_adjustment; + + /* Do the initial adjusted access */ + size_t remainder = len; + target_addr_t address = dest; + const uint8_t *data = (const uint8_t *)src; + riscv32_sysbus_mem_adjusted_write(hart, address, data, write_length, native_access_width, native_access_length); + + /* After doing the initial access, adjust the location of the next and do any follow-up accesses required */ + remainder -= write_length; + address += write_length; + data += write_length; + + /* + * Now we're aligned to the wider access width, do another set of writes if there's + * any remainder. Do this till we either reach nothing left, or we have another small left-over amount + */ + if (!remainder) + return; + const size_t amount = remainder & ~(native_access_length - 1U); + if (amount) + riscv32_sysbus_mem_native_write(hart, address, data, amount, native_access_width, native_access_length); + remainder -= amount; + address += (uint32_t)amount; + data += amount; + + /* If there's any data left to write, do another adjusted access to perform it */ + if (remainder) + riscv32_sysbus_mem_adjusted_write(hart, address, data, remainder, native_access_width, native_access_length); +} + +void riscv32_mem_read(target_s *const target, void *const dest, const target_addr_t src, const size_t len) +{ + /* If we're asked to do a 0-byte read, do nothing */ + if (!len) { + DEBUG_PROTO("%s: @ %08" PRIx32 " len %zu\n", __func__, src, len); + return; + } + + riscv_hart_s *const hart = riscv_hart_struct(target); + if (hart->flags & RV_HART_FLAG_MEMORY_SYSBUS) + riscv32_sysbus_mem_read(hart, dest, src, len); + else + riscv32_abstract_mem_read(hart, dest, src, len); + +#if ENABLE_DEBUG + DEBUG_PROTO("%s: @ %08" PRIx32 " len %zu:", __func__, src, len); + const uint8_t *const data = (const uint8_t *)dest; + for (size_t offset = 0; offset < len; ++offset) { + if (offset == 16U) + break; + DEBUG_PROTO(" %02x", data[offset]); + } + if (len > 16U) + DEBUG_PROTO(" ..."); + DEBUG_PROTO("\n"); +#endif +} + +void riscv32_mem_write(target_s *const target, const target_addr_t dest, const void *const src, const size_t len) +{ + DEBUG_PROTO("%s: @ %" PRIx32 " len %zu:", __func__, dest, len); + const uint8_t *const data = (const uint8_t *)src; + for (size_t offset = 0; offset < len; ++offset) { + if (offset == 16U) + break; + DEBUG_PROTO(" %02x", data[offset]); + } + if (len > 16U) + DEBUG_PROTO(" ..."); + DEBUG_PROTO("\n"); + /* If we're asked to do a 0-byte read, do nothing */ + if (!len) + return; + + riscv_hart_s *const hart = riscv_hart_struct(target); + if (hart->flags & RV_HART_FLAG_MEMORY_SYSBUS) + riscv32_sysbus_mem_write(hart, dest, src, len); + else + riscv32_abstract_mem_write(hart, dest, src, len); +} + /* * The following can be used as a key for understanding the various return results from the breakwatch functions: * 0 -> success diff --git a/src/target/riscv64.c b/src/target/riscv64.c index 54ab5aa500a..2e25bd17b42 100644 --- a/src/target/riscv64.c +++ b/src/target/riscv64.c @@ -121,8 +121,8 @@ static void riscv64_mem_read(target_s *const target, void *const dest, const tar const uint8_t access_width = riscv_mem_access_width(hart, src, len); const uint8_t access_length = 1U << access_width; /* Build the access command */ - const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_READ | (access_width << RV_MEM_ACCESS_SHIFT) | - (access_length < len ? RV_MEM_ADDR_POST_INC : 0U); + const uint32_t command = RV_DM_ABST_CMD_ACCESS_MEM | RV_ABST_READ | (access_width << RV_ABST_MEM_ACCESS_SHIFT) | + (access_length < len ? RV_ABST_MEM_ADDR_POST_INC : 0U); /* Write the address to read to arg1 */ if (!riscv_dm_write(hart->dbg_module, RV_DM_DATA2, src) || !riscv_dm_write(hart->dbg_module, RV_DM_DATA3, 0U)) return; diff --git a/src/target/riscv_debug.c b/src/target/riscv_debug.c index c43b0a9b148..edd2cb60d38 100644 --- a/src/target/riscv_debug.c +++ b/src/target/riscv_debug.c @@ -73,6 +73,8 @@ #define RV_DM_ABST_STATUS_BUSY 0x00001000U #define RV_DM_ABST_STATUS_DATA_COUNT 0x0000000fU +#define RV_DM_SYSBUS_STATUS_ADDR_WIDTH_MASK 0x00000fe0U + #define RV_CSR_FORCE_MASK 0xc000U #define RV_CSR_FORCE_32_BIT 0x4000U #define RV_CSR_FORCE_64_BIT 0x8000U @@ -210,6 +212,7 @@ static riscv_debug_version_e riscv_dm_version(uint32_t status); static uint32_t riscv_hart_discover_isa(riscv_hart_s *hart); static void riscv_hart_discover_triggers(riscv_hart_s *hart); +static void riscv_hart_memory_access_type(riscv_hart_s *hart); static bool riscv_attach(target_s *target); static void riscv_detach(target_s *target); @@ -365,6 +368,9 @@ static bool riscv_hart_init(riscv_hart_s *const hart) uint32_t isa = riscv_hart_discover_isa(hart); hart->address_width = riscv_isa_address_width(isa); hart->extensions = isa & RV_ISA_EXTENSIONS_MASK; + /* Figure out if the target needs us to use sysbus or not for memory access */ + riscv_hart_memory_access_type(hart); + /* Then read out the ID registers */ riscv_hart_read_ids(hart); DEBUG_INFO("Hart %" PRIx32 ": %u-bit RISC-V (arch = %08" PRIx32 "), vendor = %" PRIx32 ", impl = %" PRIx32 @@ -382,6 +388,7 @@ static bool riscv_hart_init(riscv_hart_s *const hart) target->designer_code = hart->vendorid ? hart->vendorid : hart->dbg_module->dmi_bus->designer_code; target->cpuid = hart->archid; + /* Now we're in a safe environment, leasurely read out the triggers, etc. */ riscv_hart_discover_triggers(hart); /* Setup core-agnostic target functions */ @@ -423,11 +430,15 @@ static void riscv_hart_free(void *const priv) static bool riscv_dmi_read(riscv_dmi_s *const dmi, const uint32_t address, uint32_t *const value) { - return dmi->read(dmi, address, value); + const bool result = dmi->read(dmi, address, value); + if (result) + DEBUG_PROTO("%s: %08" PRIx32 " -> %08" PRIx32 "\n", __func__, address, *value); + return result; } static bool riscv_dmi_write(riscv_dmi_s *const dmi, const uint32_t address, const uint32_t value) { + DEBUG_PROTO("%s: %08" PRIx32 " <- %08" PRIx32 "\n", __func__, address, value); return dmi->write(dmi, address, value); } @@ -522,7 +533,8 @@ static uint32_t riscv_hart_discover_isa(riscv_hart_s *const hart) return isa_data[0]; } /* If that failed, then find out why and instead try the next narrower width */ - if (hart->status != RISCV_HART_BUS_ERROR && hart->status != RISCV_HART_EXCEPTION) + if (hart->status != RISCV_HART_BUS_ERROR && hart->status != RISCV_HART_EXCEPTION && + hart->status != RISCV_HART_NOT_SUPP) return 0; if (hart->access_width == 32U) { hart->access_width = 0U; @@ -622,7 +634,7 @@ bool riscv_csr_write(riscv_hart_s *const hart, const uint16_t reg, const void *c uint8_t riscv_mem_access_width(const riscv_hart_s *const hart, const target_addr_t address, const size_t length) { /* Grab the Hart's most maxmimally aligned possible write width */ - uint8_t access_width = riscv_hart_access_width(hart->address_width) >> RV_MEM_ACCESS_SHIFT; + uint8_t access_width = riscv_hart_access_width(hart->address_width) >> RV_ABST_MEM_ACCESS_SHIFT; /* Convert the hart access width to a mask - for example, for 32-bit harts, this gives (1U << 2U) - 1U = 3U */ uint8_t align_mask = (1U << access_width) - 1U; /* Mask out the bottom bits of both the address and length - anything that determines the alignment */ @@ -691,6 +703,23 @@ static void riscv_hart_discover_triggers(riscv_hart_s *const hart) } } +static void riscv_hart_memory_access_type(riscv_hart_s *const hart) +{ + uint32_t sysbus_status; + hart->flags &= (uint8_t)~RV_HART_FLAG_MEMORY_SYSBUS; + /* + * Try reading the system bus access control and status register. + * Check if the value read back is non-zero for the sbasize field + */ + if (!riscv_dm_read(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, &sysbus_status) || + !(sysbus_status & RV_DM_SYSBUS_STATUS_ADDR_WIDTH_MASK)) + return; + /* If all the checks passed, we now have a valid system bus so can proceed with using it for memory access */ + hart->flags = RV_HART_FLAG_MEMORY_SYSBUS | (sysbus_status & RV_HART_FLAG_ACCESS_WIDTH_MASK); + /* Make sure the system bus is not in any kind of error state */ + (void)riscv_dm_write(hart->dbg_module, RV_DM_SYSBUS_CTRLSTATUS, 0x00407000U); +} + riscv_match_size_e riscv_breakwatch_match_size(const size_t size) { switch (size) { diff --git a/src/target/riscv_debug.h b/src/target/riscv_debug.h index 56655fa867a..069066a2144 100644 --- a/src/target/riscv_debug.h +++ b/src/target/riscv_debug.h @@ -84,6 +84,15 @@ typedef enum riscv_match_size { RV_MATCH_SIZE_128_BIT = 0x00410000U, } riscv_match_size_e; +/* These defines specify Hart-specific information such as which memory access style to use */ +#define RV_HART_FLAG_MEMORY_ABSTRACT 0x00U +#define RV_HART_FLAG_MEMORY_SYSBUS 0x10U +#define RV_HART_FLAG_ACCESS_WIDTH_MASK 0x0fU +#define RV_HART_FLAG_ACCESS_WIDTH_8BIT 0x01U +#define RV_HART_FLAG_ACCESS_WIDTH_16BIT 0x02U +#define RV_HART_FLAG_ACCESS_WIDTH_32BIT 0x04U +#define RV_HART_FLAG_ACCESS_WIDTH_64BIT 0x08U + typedef struct riscv_dmi riscv_dmi_s; /* This structure represents a version-agnostic Debug Module Interface on a RISC-V device */ @@ -122,6 +131,7 @@ typedef struct riscv_hart { uint32_t hartsel; uint8_t access_width; uint8_t address_width; + uint8_t flags; riscv_hart_status_e status; uint32_t extensions; @@ -136,12 +146,17 @@ typedef struct riscv_hart { #define RV_STATUS_VERSION_MASK 0x0000000fU -#define RV_DM_DATA0 0x04U -#define RV_DM_DATA1 0x05U -#define RV_DM_DATA2 0x06U -#define RV_DM_DATA3 0x07U -#define RV_DM_ABST_CTRLSTATUS 0x16U -#define RV_DM_ABST_COMMAND 0x17U +#define RV_DM_DATA0 0x04U +#define RV_DM_DATA1 0x05U +#define RV_DM_DATA2 0x06U +#define RV_DM_DATA3 0x07U +#define RV_DM_ABST_CTRLSTATUS 0x16U +#define RV_DM_ABST_COMMAND 0x17U +#define RV_DM_SYSBUS_CTRLSTATUS 0x38U +#define RV_DM_SYSBUS_ADDR0 0x39U +#define RV_DM_SYSBUS_ADDR1 0x3aU +#define RV_DM_SYSBUS_DATA0 0x3cU +#define RV_DM_SYSBUS_DATA1 0x3dU #define RV_DM_ABST_CMD_ACCESS_REG 0x00000000U #define RV_DM_ABST_CMD_ACCESS_MEM 0x02000000U @@ -152,14 +167,21 @@ typedef struct riscv_hart { #define RV_REG_ACCESS_32_BIT 0x00200000U #define RV_REG_ACCESS_64_BIT 0x00300000U #define RV_REG_ACCESS_128_BIT 0x00400000U -#define RV_MEM_ADDR_POST_INC 0x00080000U #define RV_MEM_ACCESS_8_BIT 0x0U #define RV_MEM_ACCESS_16_BIT 0x1U #define RV_MEM_ACCESS_32_BIT 0x2U #define RV_MEM_ACCESS_64_BIT 0x3U #define RV_MEM_ACCESS_128_BIT 0x4U -#define RV_MEM_ACCESS_SHIFT 20U + +#define RV_ABST_MEM_ADDR_POST_INC 0x00080000U +#define RV_ABST_MEM_ACCESS_SHIFT 20U + +#define RV_SYSBUS_MEM_ADDR_POST_INC 0x00010000U +#define RV_SYSBUS_MEM_READ_ON_ADDR 0x00100000U +#define RV_SYSBUS_MEM_READ_ON_DATA 0x00008000U +#define RV_SYSBUS_STATUS_BUSY 0x00200000U +#define RV_SYSBUS_MEM_ACCESS_SHIFT 17U /* dpc -> Debug Program Counter */ #define RV_DPC 0x7b1U @@ -209,4 +231,7 @@ uint8_t riscv_mem_access_width(const riscv_hart_s *hart, target_addr_t address, void riscv32_unpack_data(void *dest, uint32_t data, uint8_t access_width); uint32_t riscv32_pack_data(const void *src, uint8_t access_width); +void riscv32_mem_read(target_s *target, void *dest, target_addr_t src, size_t len); +void riscv32_mem_write(target_s *target, target_addr_t dest, const void *src, size_t len); + #endif /*TARGET_RISCV_DEBUG_H*/ diff --git a/src/target/riscv_jtag_dtm.c b/src/target/riscv_jtag_dtm.c index 678f1a33ac6..144e907bb74 100644 --- a/src/target/riscv_jtag_dtm.c +++ b/src/target/riscv_jtag_dtm.c @@ -125,7 +125,13 @@ uint32_t riscv_shift_dtmcs(const riscv_dmi_s *const dmi, const uint32_t control) return status; } -static bool riscv_shift_dmi(riscv_dmi_s *const dmi, const uint8_t operation, const uint32_t address, +static void riscv_dmi_reset(const riscv_dmi_s *const dmi) +{ + riscv_shift_dtmcs(dmi, RV_DTMCS_DMI_RESET); + jtag_dev_write_ir(dmi->dev_index, IR_DMI); +} + +static uint8_t riscv_shift_dmi(riscv_dmi_s *const dmi, const uint8_t operation, const uint32_t address, const uint32_t data_in, uint32_t *const data_out) { jtag_dev_s *device = &jtag_devs[dmi->dev_index]; @@ -143,18 +149,52 @@ static bool riscv_shift_dmi(riscv_dmi_s *const dmi, const uint8_t operation, con jtag_proc.jtagtap_tdi_seq(!device->dr_postscan, (const uint8_t *)&address, dmi->address_width); jtag_proc.jtagtap_tdi_seq(true, ones, device->dr_postscan); jtagtap_return_idle(dmi->idle_cycles); - /* XXX: Need to deal with when status is 3. */ + /* Translate error 1 into RV_DMI_FAILURE per the spec */ + if (status == 1U) + return RV_DMI_FAILURE; + return status; +} + +static bool riscv_dmi_transfer(riscv_dmi_s *const dmi, const uint8_t operation, const uint32_t address, + const uint32_t data_in, uint32_t *const data_out) +{ + /* Try the transfer */ + uint8_t status = riscv_shift_dmi(dmi, operation, address, data_in, data_out); + + /* Handle status == 3 (RV_DMI_TOO_SOON) */ + if (status == RV_DMI_TOO_SOON) { + /* + * If we got RV_DMI_TOO_SOON and we're under 8 idle cycles, increase the number + * of idle cycles used to compensate and have the outer code re-run the transnfers + */ + if (dmi->idle_cycles < 8) + ++dmi->idle_cycles; + /* + * Otherwise we've hit 8 idle cycles, it doesn't matter if we get another + * RV_DMI_TOO_SOON, treat that as a hard error and bail out. + */ + else + status = RV_DMI_FAILURE; + } + dmi->fault = status; + /* If we get straight failure, do a DMI reset */ + if (status == RV_DMI_FAILURE || status == RV_DMI_TOO_SOON) + riscv_dmi_reset(dmi); return status == RV_DMI_SUCCESS; } static bool riscv_jtag_dmi_read(riscv_dmi_s *const dmi, const uint32_t address, uint32_t *const value) { - /* Setup the location to read from */ - bool result = riscv_shift_dmi(dmi, RV_DMI_READ, address, 0, NULL); - if (result) - /* If that worked, read back the value and check the operation status */ - result = riscv_shift_dmi(dmi, RV_DMI_NOOP, 0, 0, value); + bool result = true; + do { + /* Setup the location to read from */ + result = riscv_dmi_transfer(dmi, RV_DMI_READ, address, 0, NULL); + if (result) + /* If that worked, read back the value and check the operation status */ + result = riscv_dmi_transfer(dmi, RV_DMI_NOOP, 0, 0, value); + } while (dmi->fault == RV_DMI_TOO_SOON); + if (!result) DEBUG_WARN("DMI read at 0x%08" PRIx32 " failed with status %u\n", address, dmi->fault); return result; @@ -162,11 +202,15 @@ static bool riscv_jtag_dmi_read(riscv_dmi_s *const dmi, const uint32_t address, static bool riscv_jtag_dmi_write(riscv_dmi_s *const dmi, const uint32_t address, const uint32_t value) { - /* Write a value to the requested register */ - bool result = riscv_shift_dmi(dmi, RV_DMI_WRITE, address, value, NULL); - if (result) - /* If that worked, read back the operation status to ensure the write actually worked */ - result = riscv_shift_dmi(dmi, RV_DMI_NOOP, 0, 0, NULL); + bool result = true; + do { + /* Write a value to the requested register */ + result = riscv_dmi_transfer(dmi, RV_DMI_WRITE, address, value, NULL); + if (result) + /* If that worked, read back the operation status to ensure the write actually worked */ + result = riscv_dmi_transfer(dmi, RV_DMI_NOOP, 0, 0, NULL); + } while (dmi->fault == RV_DMI_TOO_SOON); + if (!result) DEBUG_WARN("DMI write at 0x%08" PRIx32 " failed with status %u\n", address, dmi->fault); return result;