This is an automated email from Gerrit. Marc Schink (d...@zapb.de) just uploaded a new patch set to Gerrit, which you can find at http://openocd.zylin.com/6307
-- gerrit commit eb896d13e3d52dc4d49686bb5d214286cbe5b45a Author: Marc Schink <d...@zapb.de> Date: Mon Jun 7 14:40:11 2021 +0200 target/tcl: Add 'read_memory' and 'write_memory' These functions are meant as replacement for 'mem2array' and 'array2mem'. The main benefits of these new functions are: * They do not use Tcl arrays but lists which makes it easier to parse (generate) the data. See the Python Tcl RPC code in contrib as a negative example. * They do not operate on Tcl variables but instead return (accept) the Tcl list directly. This makes the C and Tcl code base smaller and cleaner. * The code is slightly more performant when reading / writing large amount of data. Tested with a simple Python Tcl RPC benchmark. Change-Id: Ibd6ece3360c0d002abaadc37f078b10a8bb606f8 Signed-off-by: Marc Schink <d...@zapb.de> diff --git a/doc/openocd.texi b/doc/openocd.texi index fc44833..27af1a2 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -4779,6 +4779,45 @@ and neither store nor return those values. @end itemize @end deffn +@deffn {Command} {$target_name write_memory} aiddress width data ['phys'] +This function provides an efficient way to write to the target memory from a Tcl +script. + +@itemize +@item @var{address} ... target memory address +@item @var{width} ... memory access bit size, can be 8, 16, 32 or 64 +@item @var{data} ... Tcl list with the elements to write +@item ['phys'] ... treat the memory address as physical instead of virtual address +@end itemize + +For example, the following command writes two 32 bit words into the target +memory at address 0x20000000: + +@example +write_memory 0x20000000 32 @{0xdeadbeef 0x00230500@} +@end example +@end deffn + +@deffn {Command} {$target_name read_memory} address width count ['phys'] +This function provides an efficient way to read the target memory from a Tcl +script. +A Tcl list containing the requested memory elements is returned by this function. + +@itemize +@item @var{address} ... target memory address +@item @var{width} ... memory access bit size, can be 8, 16, 32 or 64 +@item @var{count} ... number of elements to read +@item ['phys'] ... treat the memory address as physical instead of virtual address +@end itemize + +For example, the following command reads two 32 bit words from the target +memory at address 0x20000000: + +@example +read_memory 0x20000000 32 2 +@end example +@end deffn + @deffn {Command} {$target_name cget} queryparm Each configuration parameter accepted by @command{$target_name configure} @@ -11123,12 +11162,22 @@ By "low-level," we mean commands that a human would typically not invoke directly. @itemize @bullet +@item @b{read_memory} <@var{address}> <@var{width}> <@var{count}> ['phys'] + +Read memory and return a Tcl list. + +@item @b{write_memory} <@var{address}> <@var{width}> <@var{data}> ['phys'] + +Write a Tcl list of numbers to memory. @item @b{mem2array} <@var{varname}> <@var{width}> <@var{addr}> <@var{nelems}> -Read memory and return as a Tcl array for script processing +Read memory and return as a Tcl array for script processing. +@emph{This command is deprecated, use @b{read_memory} instead.} + @item @b{array2mem} <@var{varname}> <@var{width}> <@var{addr}> <@var{nelems}> -Convert a Tcl array to memory locations and write the values +Convert a Tcl array to memory locations and write the values. +@emph{This command is deprecated, use @b{write_memory} instead.} @item @b{flash banks} <@var{driver}> <@var{base}> <@var{size}> <@var{chip_width}> <@var{bus_width}> <@var{target}> [@option{driver options} ...] Return information about the flash banks diff --git a/src/target/target.c b/src/target/target.c index a788a68..7e9a988 100644 --- a/src/target/target.c +++ b/src/target/target.c @@ -4556,6 +4556,179 @@ static int target_mem2array(Jim_Interp *interp, struct target *target, int argc, return e; } +static int target_jim_read_memory(Jim_Interp *interp, struct target *target, + int argc, Jim_Obj * const *argv) +{ + /* + * argv[0] = memory address + * argv[1] = desired element width in bits + * argv[2] = number of elements to read + * argv[3] = optional "phys" + */ + + if (argc < 3 || argc > 4) { + Jim_WrongNumArgs(interp, 0, argv, "address width count [phys]"); + return JIM_ERR; + } + + /* Arg 0: Memory address. */ + jim_wide wide_addr; + int e; + e = Jim_GetWide(interp, argv[0], &wide_addr); + + if (e != JIM_OK) + return e; + + target_addr_t addr = (target_addr_t)wide_addr; + + /* Arg 1: Bit width of one element. */ + long l; + e = Jim_GetLong(interp, argv[1], &l); + + if (e != JIM_OK) + return e; + + const unsigned int width_bits = l; + + /* Arg 2: Number of elements to read. */ + e = Jim_GetLong(interp, argv[2], &l); + + if (e != JIM_OK) + return e; + + size_t count = l; + + /* Arg 4: Optional 'phys'. */ + bool is_phys = false; + + if (argc > 3) { + int str_len; + const char *phys = Jim_GetString(argv[3], &str_len); + + if (!strncmp(phys, "phys", str_len)) + is_phys = true; + else + return JIM_ERR; + } + + switch (width_bits) { + case 8: + case 16: + case 32: + case 64: + break; + default: + Jim_SetResultString(interp, "invalid width param, must be 8, 16, 32 or 64", -1); + return JIM_ERR; + } + + const unsigned int width = width_bits / 8; + + if ((addr + (count * width)) < addr) { + Jim_SetResultString(interp, "read_memory: addr + count wraps to zero", -1); + return JIM_ERR; + } + + if (count > 65536) { + Jim_SetResultString(interp, "read_memory: too large read request, exeeds 64K elements", -1); + return JIM_ERR; + } + + if ((width == 1) || + ((width == 2) && ((addr & 1) == 0)) || + ((width == 4) && ((addr & 3) == 0)) || + ((width == 8) && ((addr & 7) == 0))) { + /* Alignment is correct. */ + } else { + char buf[128]; + Jim_SetResult(interp, Jim_NewEmptyStringObj(interp)); + snprintf(buf, sizeof(buf), "read_memory: " TARGET_ADDR_FMT " is not aligned for %u byte reads", + addr, width); + Jim_AppendStrings(interp, Jim_GetResult(interp), buf, NULL); + return JIM_ERR; + } + + const size_t buffersize = 4096; + uint8_t *buffer = malloc(buffersize); + + if (!buffer) + return JIM_ERR; + + Jim_Obj *result_list = Jim_NewListObj(interp, NULL, 0); + + while (count > 0) { + const unsigned int max_chunk_len = buffersize / width; + const size_t chunk_len = MIN(count, max_chunk_len); + + int retval; + + if (is_phys) + retval = target_read_phys_memory(target, addr, width, chunk_len, buffer); + else + retval = target_read_memory(target, addr, width, chunk_len, buffer); + + if (retval != ERROR_OK) { + LOG_ERROR("read_memory: read at " TARGET_ADDR_FMT " with width=%u and count=%zu failed", + addr, width_bits, chunk_len); + Jim_SetResultString(interp, "read_memory: failed to read memory", -1); + e = JIM_ERR; + break; + } else { + uint64_t v = 0; + + for (size_t i = 0; i < chunk_len ; i++) { + switch (width) { + case 8: + v = target_buffer_get_u64(target, &buffer[i * width]); + break; + case 4: + v = target_buffer_get_u32(target, &buffer[i * width]); + break; + case 2: + v = target_buffer_get_u16(target, &buffer[i * width]); + break; + case 1: + v = buffer[i] & 0x0ff; + break; + } + + Jim_ListAppendElement(interp, result_list, + Jim_NewWideObj(interp, v)); + } + + count -= chunk_len; + addr += chunk_len * width; + } + } + + free(buffer); + + if (e != JIM_OK) { + Jim_DecrRefCount(interp, result_list); + return e; + } + + Jim_SetResult(interp, result_list); + + return JIM_OK; +} + +static int jim_read_memory(Jim_Interp *interp, int argc, Jim_Obj * const *argv) +{ + struct command_context *context; + + context = current_command_context(interp); + assert(context != NULL); + + struct target *target = get_current_target(context); + if (!target) { + LOG_ERROR("read_memory: no current target"); + return JIM_ERR; + } + + return target_jim_read_memory(interp, target, argc - 1, argv + 1); +} + static int get_u64_array_element(Jim_Interp *interp, const char *varname, size_t idx, uint64_t *val) { char *namebuf = alloc_printf("%s(%u)", varname, (unsigned int)idx); @@ -4764,6 +4937,168 @@ static int target_array2mem(Jim_Interp *interp, struct target *target, return e; } +static int target_jim_write_memory(Jim_Interp *interp, struct target *target, + int argc, Jim_Obj *const *argv) +{ + /* + * argv[0] = memory address + * argv[1] = desired element width in bits + * argv[2] = number of elements to write + * argv[3] = optional "phys" + */ + + if (argc < 3 || argc > 4) { + Jim_WrongNumArgs(interp, 0, argv, "address width data [phys]"); + return JIM_ERR; + } + + /* Arg 0: Memory address. */ + int e; + jim_wide wide_addr; + e = Jim_GetWide(interp, argv[0], &wide_addr); + + if (e != JIM_OK) + return e; + + target_addr_t addr = (target_addr_t)wide_addr; + + /* Arg 1: Bit width of one element. */ + long l; + e = Jim_GetLong(interp, argv[1], &l); + + if (e != JIM_OK) + return e; + + const unsigned int width_bits = l; + size_t count = Jim_ListLength(interp, argv[2]); + + /* Arg 3: Optional 'phys'. */ + bool is_phys = false; + + if (argc > 3) { + int str_len; + const char *phys = Jim_GetString(argv[3], &str_len); + if (!strncmp(phys, "phys", str_len)) + is_phys = true; + else + return JIM_ERR; + } + + switch (width_bits) { + case 8: + case 16: + case 32: + case 64: + break; + default: + Jim_SetResultString(interp, "Invalid width param, must be 8/16/32", -1); + return JIM_ERR; + } + + const unsigned int width = width_bits / 8; + + if ((addr + (count * width)) < addr) { + Jim_SetResultString(interp, "write_memory: addr + len wraps to zero", -1); + return JIM_ERR; + } + + if (count > 65536) { + Jim_SetResultString(interp, "write_memory: too large memory write request, exceeds 64K elements", -1); + return JIM_ERR; + } + + if ((width == 1) || + ((width == 2) && ((addr & 1) == 0)) || + ((width == 4) && ((addr & 3) == 0)) || + ((width == 8) && ((addr & 7) == 0))) { + /* Alignment is correct. */ + } else { + char buf[128]; + Jim_SetResult(interp, Jim_NewEmptyStringObj(interp)); + snprintf(buf, sizeof(buf), "write_memory: " TARGET_ADDR_FMT " is not aligned for %u byte writes", + addr, width); + Jim_AppendStrings(interp, Jim_GetResult(interp), buf, NULL); + return JIM_ERR; + } + + const size_t buffersize = 4096; + uint8_t *buffer = malloc(buffersize); + + if (buffer == NULL) + return JIM_ERR; + + Jim_SetResult(interp, Jim_NewEmptyStringObj(interp)); + + size_t j = 0; + + while (count > 0) { + const unsigned int max_chunk_len = buffersize / width; + const size_t chunk_len = MIN(count, max_chunk_len); + + for (size_t i = 0; i < chunk_len; i++, j++) { + Jim_Obj *tmp = Jim_ListGetIndex(interp, argv[2], j); + jim_wide element_wide; + Jim_GetWide(interp, tmp, &element_wide); + + const uint64_t v = element_wide; + + switch (width) { + case 8: + target_buffer_set_u64(target, &buffer[i * width], v); + break; + case 4: + target_buffer_set_u32(target, &buffer[i * width], v); + break; + case 2: + target_buffer_set_u16(target, &buffer[i * width], v); + break; + case 1: + buffer[i] = v & 0x0ff; + break; + } + } + + count -= chunk_len; + + int retval; + + if (is_phys) + retval = target_write_phys_memory(target, addr, width, chunk_len, buffer); + else + retval = target_write_memory(target, addr, width, chunk_len, buffer); + if (retval != ERROR_OK) { + LOG_ERROR("write_memory: write at " TARGET_ADDR_FMT " with width=%u and count=%zu failed", + addr, width_bits, chunk_len); + Jim_SetResultString(interp, "write_memory: failed to write memory", -1); + e = JIM_ERR; + break; + } + + addr += chunk_len * width; + } + + free(buffer); + + return e; +} + +static int jim_write_memory(Jim_Interp *interp, int argc, Jim_Obj * const *argv) +{ + struct command_context *context; + + context = current_command_context(interp); + assert(context != NULL); + + struct target *target = get_current_target(context); + if (!target) { + LOG_ERROR("write_memory: no current target"); + return JIM_ERR; + } + + return target_jim_write_memory(interp, target, argc - 1, argv + 1); +} + + /* FIX? should we propagate errors here rather than printing them * and continuing? */ @@ -5235,6 +5570,24 @@ static int jim_target_array2mem(Jim_Interp *interp, return target_array2mem(interp, target, argc - 1, argv + 1); } +static int jim_target_read_memory(Jim_Interp *interp, int argc, + Jim_Obj * const *argv) +{ + struct command_context *cmd_ctx = current_command_context(interp); + assert(cmd_ctx); + return target_jim_read_memory(interp, get_current_target(cmd_ctx), argc - 1, + argv + 1); +} + +static int jim_target_write_memory(Jim_Interp *interp, int argc, + Jim_Obj * const *argv) +{ + struct command_context *cmd_ctx = current_command_context(interp); + assert(cmd_ctx); + return target_jim_write_memory(interp, get_current_target(cmd_ctx), + argc - 1, argv + 1); +} + static int jim_target_tap_disabled(Jim_Interp *interp) { Jim_SetResultFormatted(interp, "[TAP is disabled]"); @@ -5591,6 +5944,20 @@ static const struct command_registration target_instance_command_handlers[] = { .usage = "arrayname bitwidth address count", }, { + .name = "read_memory", + .mode = COMMAND_EXEC, + .jim_handler = jim_target_read_memory, + .help = "Load Tcl list of 8/16/32/64 bit numbers from target memory", + .usage = "address width count ['phys']", + }, + { + .name = "write_memory", + .mode = COMMAND_EXEC, + .jim_handler = jim_target_write_memory, + .help = "Write Tcl list of 8/16/32/64 bit numbers to target memory", + .usage = "address width data ['phys']", + }, + { .name = "eventlist", .handler = handle_target_event_list, .mode = COMMAND_EXEC, @@ -6677,6 +7044,20 @@ static const struct command_registration target_exec_command_handlers[] = { .usage = "arrayname bitwidth address count", }, { + .name = "read_memory", + .mode = COMMAND_EXEC, + .jim_handler = jim_read_memory, + .help = "Load Tcl list of 8/16/32/64 bit numbers from target memory", + .usage = "address width count ['phys']", + }, + { + .name = "write_memory", + .mode = COMMAND_EXEC, + .jim_handler = jim_write_memory, + .help = "Write Tcl list of 8/16/32/64 bit numbers to target memory", + .usage = "address width data ['phys']", + }, + { .name = "reset_nag", .handler = handle_target_reset_nag, .mode = COMMAND_ANY, --