This is an automated email from Gerrit. "Antonio Borneo <borneo.anto...@gmail.com>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/8815
-- gerrit commit 58969f71c7f2792a6972d7e550bcbc19a464d828 Author: Antonio Borneo <borneo.anto...@gmail.com> Date: Fri Jan 17 15:48:57 2025 +0100 target: add events read-buffer and write-buffer The internal API target_read_buffer() and target_write_buffer() are mainly used by the GDB connection for all the target's memory read/write operations. There are cases where the user needs to override these API, e.g.: - on emulated targets, where it's too slow accessing the memory through the CPU and some specific workaround for direct access to the emulated memory could be provided by the emulator [1]; - to access HW registers while the target cannot provide such access, e.g. on not halted ARM Cortex-A or Cortex-R. By abusing of the target events, add two new events 'read-buffer' and 'write-buffer' that will be called to override the API above. Since we cannot pass arguments to the body of the events, trick it by requiring to have, as body of these events, the name of a Tcl proc; the caller of the event will add the arguments. Add two helper commands: - $target_name read_buffer address count - $target_name write_buffer address count data that generate a new call to the internal API. These are useful to forward the API to another target (e.g. mem_ap) or to modify the arguments. Change-Id: I63a1090e025d4188973b34c71d023b9047a8baa1 Signed-off-by: Antonio Borneo <borneo.anto...@gmail.com> Link: [1] https://review.openocd.org/c/openocd/+/7149 diff --git a/doc/openocd.texi b/doc/openocd.texi index 9ff524b749..a0406dd730 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -5377,6 +5377,19 @@ read_memory 0x20000000 32 2 @end example @end deffn +@deffn {Command} {$target_name write_buffer} address count data +Helper command for write-buffer event. +Writes @var{count} bytes at target @var{address}. +The values in @var{data} are stored as a single hexadecimal string without separations nor 0x prefix. +The length of @var{data} should match @var{count}. +@end deffn + +@deffn {Command} {$target_name read_buffer} address count +Helper command for read-buffer event. +Reads @var{count} bytes at target @var{address}. +Returns the bytes as a single hexadecimal string without separations nor 0x prefix. +@end deffn + @deffn {Command} {$target_name cget} queryparm Each configuration parameter accepted by @command{$target_name configure} @@ -5628,6 +5641,58 @@ when reset disables PLLs needed to use a fast clock. @* The target made a semihosting call with user-defined operation number 0x106 @item @b{semihosting-user-cmd-0x107} @* The target made a semihosting call with user-defined operation number 0x107 + +@item @b{write-buffer} +Issued by any memory write from GDB. +It is suitable to redirect GDB memory writes to other interfaces, e.g.: +@itemize @minus +@item emulated targets where it's too slow accessing memory through the CPU and +specific workaround could be provided by the emulator; +@item for targets that should be halted to access its memory (e.g. ARM Cortex-A +and Cortex-R), allow writing HW registers while the target is running. +@end itemize + +The event handler should be the name of a Tcl proc that accepts three +parameters for @var{address}, @var{count} and @var{data}, where: +@itemize @minus +@item @var{address} is the target's address where to write to; +@item @var{count} is the number of bytes to write; +@item @var{data} is a single string of hexadecimal bytes without separators +nor 0x prefix. +@end itemize +The Tcl proc should return: +@itemize @minus +@item nothing when it succeeds performing the write; +@item an error when it fails during the write; +@item the string "-1" when the it cannot process the request and requires the +fallback to the standard write buffer method of the target to proceed. +@end itemize + +@item @b{read-buffer} +Issued by any memory read from GDB. +It is suitable to redirect GDB memory reads to other interfaces, e.g.: +@itemize @minus +@item emulated targets where it's too slow accessing memory through the CPU and +specific workaround could be provided by the emulator; +@item for targets that should be halted to access its memory (e.g. ARM Cortex-A +and Cortex-R), allowing reading or sampling HW registers while the target is running. +@end itemize + +The event handler should be the name of a Tcl proc that accepts two +parameters for @var{address} and @var{count}, where: +@itemize @minus +@item @var{address} is the target's address where to read from; +@item @var{count} is the number of bytes to read. +@end itemize +The Tcl proc should return: +@itemize @minus +@item a single string of hexadecimal bytes without separators nor 0x prefix, when +the read succeeds; +@item an error when it fails during the read; +@item the string "-1" when the it cannot process the request and requires the +fallback to the standard read buffer method of the target to proceed. +@end itemize + @end itemize @quotation Note diff --git a/src/target/target.c b/src/target/target.c index b3ad35dc7f..431e3f48bf 100644 --- a/src/target/target.c +++ b/src/target/target.c @@ -31,6 +31,7 @@ #endif #include <helper/align.h> +#include <helper/binarybuffer.h> #include <helper/list.h> #include <helper/nvp.h> #include <helper/time_support.h> @@ -200,6 +201,9 @@ static const struct jim_nvp nvp_target_event[] = { { .value = TARGET_EVENT_GDB_FLASH_ERASE_START, .name = "gdb-flash-erase-start" }, { .value = TARGET_EVENT_GDB_FLASH_ERASE_END, .name = "gdb-flash-erase-end" }, + { .value = TARGET_EVENT_READ_BUFFER, .name = "read-buffer" }, + { .value = TARGET_EVENT_WRITE_BUFFER, .name = "write-buffer" }, + { .value = TARGET_EVENT_TRACE_CONFIG, .name = "trace-config" }, { .value = TARGET_EVENT_SEMIHOSTING_USER_CMD_0X100, .name = "semihosting-user-cmd-0x100" }, @@ -2343,6 +2347,72 @@ int target_profiling_default(struct target *target, uint32_t *samples, return retval; } +/* + * Try to write through event write-buffer. It returns: + * ERROR_NOT_IMPLEMENTED => no override, use standard method + * ERROR_OK => overriding successful, return it + * ERROR_xxx => error during overriding, propagate it + */ +static int target_write_buffer_override(struct target *target, + target_addr_t address, uint32_t size, const uint8_t *buffer) +{ + struct target_event_action *teap; + + list_for_each_entry(teap, &target->events_action, list) + if (teap->event == TARGET_EVENT_WRITE_BUFFER) + break; + + if (&teap->list == &target->events_action) + return ERROR_NOT_IMPLEMENTED; + + char *hex = malloc(2 * size + 1); + if (!hex) { + LOG_ERROR("Out of memory"); + return ERROR_FAIL; + } + + hexify(hex, buffer, size, 2 * size + 1); + + struct command_context *cmd_ctx = current_command_context(teap->interp); + char *query_cmd = alloc_printf("%s " TARGET_ADDR_FMT " 0x%" PRIx32 " %s", + Jim_GetString(teap->body, NULL), address, size, hex); + free(hex); + if (!query_cmd) { + LOG_ERROR("Out of memory"); + return ERROR_FAIL; + } + + /* Override current target */ + struct target *saved_target_override = cmd_ctx->current_target_override; + cmd_ctx->current_target_override = target; + + int retval = Jim_Eval(teap->interp, query_cmd); + + cmd_ctx->current_target_override = saved_target_override; + + free(query_cmd); + + if (retval == JIM_RETURN) + retval = teap->interp->returnCode; + + if (retval != JIM_OK) { + Jim_MakeErrorMessage(teap->interp); + LOG_TARGET_ERROR(target, "Execution of event %s failed:\n%s", + target_event_name(TARGET_EVENT_WRITE_BUFFER), + Jim_GetString(Jim_GetResult(teap->interp), NULL)); + /* clean both error code and stacktrace before return */ + Jim_Eval(teap->interp, "error \"\" \"\""); + + return ERROR_FAIL; + } + + const char *result = Jim_GetString(Jim_GetResult(teap->interp), NULL); + if (strcmp(result, "-1") == 0) + return ERROR_NOT_IMPLEMENTED; + + return ERROR_OK; +} + /* Single aligned words are guaranteed to use 16 or 32 bit access * mode respectively, otherwise data is handled as quickly as * possible @@ -2368,6 +2438,10 @@ int target_write_buffer(struct target *target, target_addr_t address, uint32_t s return ERROR_FAIL; } + int retval = target_write_buffer_override(target, address, size, buffer); + if (retval != ERROR_NOT_IMPLEMENTED) + return retval; + return target->type->write_buffer(target, address, size, buffer); } @@ -2408,6 +2482,70 @@ static int target_write_buffer_default(struct target *target, return ERROR_OK; } +/* + * Try to read through event read-buffer. It returns: + * ERROR_NOT_IMPLEMENTED => no override, use standard method + * ERROR_OK => overriding successful, return it + * ERROR_xxx => error during overriding, propagate it + */ +static int target_read_buffer_override(struct target *target, + target_addr_t address, uint32_t size, uint8_t *buffer) +{ + struct target_event_action *teap; + + list_for_each_entry(teap, &target->events_action, list) + if (teap->event == TARGET_EVENT_READ_BUFFER) + break; + + if (&teap->list == &target->events_action) + return ERROR_NOT_IMPLEMENTED; + + struct command_context *cmd_ctx = current_command_context(teap->interp); + char *query_cmd = alloc_printf("%s " TARGET_ADDR_FMT " 0x%" PRIx32, + Jim_GetString(teap->body, NULL), address, size); + if (!query_cmd) { + LOG_ERROR("Out of memory"); + return ERROR_FAIL; + } + + /* Override current target */ + struct target *saved_target_override = cmd_ctx->current_target_override; + cmd_ctx->current_target_override = target; + + int retval = Jim_Eval(teap->interp, query_cmd); + + cmd_ctx->current_target_override = saved_target_override; + + free(query_cmd); + + if (retval == JIM_RETURN) + retval = teap->interp->returnCode; + + if (retval != JIM_OK) { + Jim_MakeErrorMessage(teap->interp); + LOG_TARGET_ERROR(target, "Execution of event %s failed:\n%s", + target_event_name(TARGET_EVENT_READ_BUFFER), + Jim_GetString(Jim_GetResult(teap->interp), NULL)); + /* clean both error code and stacktrace before return */ + Jim_Eval(teap->interp, "error \"\" \"\""); + + return ERROR_FAIL; + } + + int len; + const char *result = Jim_GetString(Jim_GetResult(teap->interp), &len); + if (strcmp(result, "-1") == 0) + return ERROR_NOT_IMPLEMENTED; + + if ((unsigned int)len != 2 * size) + return ERROR_FAIL; + + /* TODO: there is no check for incorrect hex content */ + unhexify(buffer, result, size); + + return ERROR_OK; +} + /* Single aligned words are guaranteed to use 16 or 32 bit access * mode respectively, otherwise data is handled as quickly as * possible @@ -2433,6 +2571,10 @@ int target_read_buffer(struct target *target, target_addr_t address, uint32_t si return ERROR_FAIL; } + int retval = target_read_buffer_override(target, address, size, buffer); + if (retval != ERROR_NOT_IMPLEMENTED) + return retval; + return target->type->read_buffer(target, address, size, buffer); } @@ -4408,6 +4550,76 @@ COMMAND_HANDLER(handle_profile_command) return retval; } +COMMAND_HANDLER(handle_target_read_buffer) +{ + if (CMD_ARGC != 2) + return ERROR_COMMAND_SYNTAX_ERROR; + + target_addr_t addr; + COMMAND_PARSE_ADDRESS(CMD_ARGV[0], addr); + + unsigned int count; + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[1], count); + + struct target *target = get_current_target(CMD_CTX); + + uint8_t *buf = malloc(count); + char *hex = malloc(2 * count + 1); + if (!buf || !hex) { + LOG_ERROR("Out of memory"); + free(hex); + free(buf); + return ERROR_FAIL; + } + + int retval = target_read_buffer(target, addr, count, buf); + if (retval != ERROR_OK) { + free(hex); + free(buf); + return retval; + } + + hexify(hex, buf, count, 2 * count + 1); + command_print(CMD, "%s", hex); + + free(hex); + free(buf); + + return ERROR_OK; +} + +COMMAND_HANDLER(handle_target_write_buffer) +{ + if (CMD_ARGC != 3) + return ERROR_COMMAND_SYNTAX_ERROR; + + target_addr_t addr; + COMMAND_PARSE_ADDRESS(CMD_ARGV[0], addr); + + unsigned int count; + COMMAND_PARSE_NUMBER(uint, CMD_ARGV[1], count); + + if (strlen(CMD_ARGV[2]) != 2 * count) + return ERROR_COMMAND_ARGUMENT_INVALID; + + struct target *target = get_current_target(CMD_CTX); + + uint8_t *buf = malloc(count); + if (!buf) { + LOG_ERROR("Out of memory"); + return ERROR_FAIL; + } + + /* TODO: there is no check for incorrect hex content */ + unhexify(buf, CMD_ARGV[2], count); + + int retval = target_write_buffer(target, addr, count, buf); + + free(buf); + + return retval; +} + COMMAND_HANDLER(handle_target_read_memory) { /* @@ -5636,6 +5848,20 @@ static const struct command_registration target_instance_command_handlers[] = { .help = "Write Tcl list of 8/16/32/64 bit numbers to target memory", .usage = "address width data ['phys']", }, + { + .name = "read_buffer", + .mode = COMMAND_EXEC, + .handler = handle_target_read_buffer, + .help = "Read 8 bit values from target memory", + .usage = "address count", + }, + { + .name = "write_buffer", + .mode = COMMAND_EXEC, + .handler = handle_target_write_buffer, + .help = "Write 8 bit values to target memory", + .usage = "address count data", + }, { .name = "eventlist", .handler = handle_target_event_list, @@ -6773,6 +6999,20 @@ static const struct command_registration target_exec_command_handlers[] = { .help = "Write Tcl list of 8/16/32/64 bit numbers to target memory", .usage = "address width data ['phys']", }, + { + .name = "read_buffer", + .mode = COMMAND_EXEC, + .handler = handle_target_read_buffer, + .help = "Read 8 bit values from target memory", + .usage = "address count", + }, + { + .name = "write_buffer", + .mode = COMMAND_EXEC, + .handler = handle_target_write_buffer, + .help = "Write 8 bit values to target memory", + .usage = "address count data", + }, { .name = "debug_reason", .mode = COMMAND_EXEC, diff --git a/src/target/target.h b/src/target/target.h index b698f250ce..c40ac199cd 100644 --- a/src/target/target.h +++ b/src/target/target.h @@ -285,6 +285,10 @@ enum target_event { TARGET_EVENT_TRACE_CONFIG, + /* abuse events to override GDB read/write buffer */ + TARGET_EVENT_READ_BUFFER, + TARGET_EVENT_WRITE_BUFFER, + TARGET_EVENT_SEMIHOSTING_USER_CMD_0X100 = 0x100, /* semihosting allows user cmds from 0x100 to 0x1ff */ TARGET_EVENT_SEMIHOSTING_USER_CMD_0X101 = 0x101, TARGET_EVENT_SEMIHOSTING_USER_CMD_0X102 = 0x102, --