diff --git a/doc/openocd.texi b/doc/openocd.texi index 34f1bb653..bd4380a2b 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -9503,13 +9503,146 @@ Selects whether interrupts will be processed when single stepping @end deffn -@subsection ARMv7-M specific commands +@subsection ARM CoreSight TPIU and SWO specific commands @cindex tracing @cindex SWO @cindex SWV @cindex TPIU -@cindex ITM -@cindex ETM + +ARM CoreSight provides several modules to generate debugging +information internally (ITM, DWT and ETM). Their output is directed +through TPIU or SWO modules to be captured externally either on an SWO pin (this +configuration is called SWV) or on a synchronous parallel trace port. + +ARM CoreSight provides independent HW blocks named TPIU and SWO each with its +own functionality. Embedded in Cortex-M3 and M4, ARM provides an optional HW +block that includes both TPIU and SWO functionalities and is again named TPIU, +which causes quite some confusion. +The registers map of all the TPIU and SWO implementations allows using a single +driver that detects at runtime the features available. + +The @command{tpiu} is used for either TPIU or SWO. +A convenient alias @command{swo} is available to help distinguish, in scripts, +the commands for SWO from the commands for TPIU. + +@deffn Command {swo} ... +Alias of @command{tpiu ...}. Can be used in scripts to distinguish the commands +for SWO from the commands for TPIU. +@end deffn + +@deffn Command {tpiu create} tpiu_name configparams... +Creates a TPIU or a SWO object. The two commands are equivalent. +Add the object in a list and add new commands (@command{@var{tpiu_name}}) +which are used for various purposes including additional configuration. + +@itemize @bullet +@item @var{tpiu_name} -- the name of the TPIU or SWO object. +This name is also used to create the object's command, referred to here +as @command{$tpiu_name}, and in other places where the TPIU or SWO needs to be identified. +@item @var{configparams} -- all parameters accepted by @command{$tpiu_name configure} are permitted. + +You @emph{must} set here the AP and MEM_AP base_address through @code{-dap @var{dap_name}}, +@code{-ap-num @var{ap_number}} and @code{-baseaddr @var{base_address}}. +@end itemize +@end deffn + +@deffn Command {tpiu names} +Lists all the TPIU or SWO objects created so far. The two commands are equivalent. +@end deffn + +@deffn Command {tpiu init} +Initialize all registered TPIU and SWO. The two commands are equivalent. +These commands are used internally during initialization. They can be issued +at any time after the initialization, too. +@end deffn + +@deffn Command {$tpiu_name cget} queryparm +Each configuration parameter accepted by @command{$tpiu_name configure} can be +individually queried, to return its current value. +The @var{queryparm} is a parameter name accepted by that command, such as @code{-dap}. +@end deffn + +@deffn Command {$tpiu_name configure} configparams... +The options accepted by this command may also be specified as parameters +to @command{tpiu create}. Their values can later be queried one at a time by +using the @command{$tpiu_name cget} command. + +@itemize @bullet +@item @code{-dap} @var{dap_name} -- names the DAP used to access this +TPIU. @xref{dapdeclaration,,DAP declaration}, on how to create and manage DAP instances. + +@item @code{-ap-num} @var{ap_number} -- sets DAP access port for TPIU, +@var{ap_number} is the numeric index of the DAP AP the TPIU is connected to. + +@item @code{-baseaddr} @var{base_address} -- sets the TPIU @var{base_address} where +to access the TPIU in the DAP AP memory space. + +@item @code{-protocol} (@option{sync}|@option{uart}|@option{manchester}) -- sets the +protocol used for trace data: +@itemize @minus +@item @option{sync} -- synchronous parallel trace output mode, using @var{port_width} + data bits (default); +@item @option{uart} -- use asynchronous SWO mode with NRZ (same as regular UART 8N1) coding; +@item @option{manchester} -- use asynchronous SWO mode with Manchester coding. +@end itemize + +@item @code{-event} @var{event_name} @var{event_body} -- assigns an event handler, +a TCL string which is evaluated when the event is triggered. The events +@code{pre-enable}, @code{post-enable}, @code{pre-disable} and @code{post-disable} +are defined for TPIU/SWO. +A typical use case for the event @code{pre-enable} is to enable the trace clock +of the TPIU. + +@item @code{-output} (@option{external}|@option{:}@var{port}|@var{filename}|@option{-}) -- specifies +the destination of the trace data: +@itemize @minus +@item @option{external} -- configure TPIU/SWO to let user capture trace +output externally, either with an additional UART or with a logic analyzer (default); +@item @option{-} -- configure TPIU/SWO and debug adapter to gather trace data +and forward it to @command{tcl_trace} command; +@item @option{:}@var{port} -- configure TPIU/SWO and debug adapter to gather +trace data, open a TCP server at port @var{port} and send the trace data to +each connected client; +@item @var{filename} -- configure TPIU/SWO and debug adapter to +gather trace data and append it to @var{filename}, which can be +either a regular file or a named pipe. +@end itemize + +@item @code{-traceclk} @var{TRACECLKIN_freq} -- mandatory parameter. +Specifies the frequency in Hz of the trace clock. For the TPIU embedded in +Cortex-M3 or M4, this is usually the same frequency as HCLK. For protocol +@option{sync} this is twice the frequency of the pin data rate. + +@item @code{-pin-freq} @var{trace_freq} -- specifies the expected data rate +in Hz of the SWO pin. Parameter used only on protocols @option{uart} and +@option{manchester}. Can be omitted to let the adapter driver select the +maximum supported rate automatically. + +@item @code{-port-width} @var{port_width} -- sets to @var{port_width} the width +of the synchronous parallel port used for trace output. Parameter used only on +protocol @option{sync}. If not specified, default value is @var{1}. + +@item @code{-formatter} (@option{0}|@option{1}) -- specifies if the formatter +should be enabled. Parameter used only on protocol @option{sync}. If not specified, +default value is @var{0}. +@end itemize +@end deffn + +@deffn Command {$tpiu_name enable} +Uses the parameters specified by the previous @command{$tpiu_name configure} +to configure and enable the TPIU or the SWO. +If required, the adapter is also configured and enabled to receive the trace +data. +This command can be used before @command{init}, but it will take effect only +after the @command{init}. +@end deffn + +@deffn Command {$tpiu_name disable} +Disable the TPIU or the SWO, terminating the receiving of the trace data. +@end deffn + + +TODO: remove the old tpiu commands @deffn Command {tpiu config} (@option{disable} | ((@option{external} | @option{internal (@var{filename} | @var{:port} | -)}) @ (@option{sync @var{port_width}} | ((@option{manchester} | @option{uart}) @var{formatter_enable})) @ @@ -9585,13 +9718,22 @@ baud with our custom divisor to get 12MHz) @item OpenOCD invocation line: @example openocd -f interface/stlink.cfg \ - -c "transport select hla_swd" \ - -f target/stm32l1.cfg \ - -c "tpiu config external uart off 24000000 12000000" +-c "transport select hla_swd" \ +-f target/stm32l1.cfg \ +-c "stm32l1.tpiu configure -protocol uart" \ +-c "stm32l1.tpiu configure -traceclk 24000000 -pin-freq 12000000" \ +-c "stm32l1.tpiu enable" @end example @end enumerate @end deffn +@subsection ARMv7-M specific commands +@cindex tracing +@cindex SWO +@cindex SWV +@cindex ITM +@cindex ETM + @deffn Command {itm port} @var{port} (@option{0}|@option{1}|@option{on}|@option{off}) Enable or disable trace output for ITM stimulus @var{port} (counting from 0). Port 0 is enabled on target creation automatically. diff --git a/src/openocd.c b/src/openocd.c index 83c35458b..4fec56338 100644 --- a/src/openocd.c +++ b/src/openocd.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -173,6 +174,10 @@ COMMAND_HANDLER(handle_init_command) return ERROR_FAIL; command_context_mode(CMD_CTX, COMMAND_EXEC); + /* in COMMAND_EXEC, after target_examine(), only tpiu or only swo */ + if (command_run_line(CMD_CTX, "tpiu init") != ERROR_OK) + return ERROR_FAIL; + /* initialize telnet subsystem */ gdb_target_add_all(all_targets); @@ -255,6 +260,7 @@ static struct command_context *setup_command_handler(Jim_Interp *interp) &pld_register_commands, &cti_register_commands, &dap_register_commands, + &arm_tpiu_swo_register_commands, NULL }; for (unsigned i = 0; NULL != command_registrants[i]; i++) { @@ -355,6 +361,7 @@ int openocd_main(int argc, char *argv[]) flash_free_all_banks(); gdb_service_free(); + arm_tpiu_swo_cleanup_all(); server_free(); unregister_all_commands(cmd_ctx, NULL); diff --git a/src/target/Makefile.am b/src/target/Makefile.am index 1d30747b6..6605b678b 100644 --- a/src/target/Makefile.am +++ b/src/target/Makefile.am @@ -113,6 +113,7 @@ ARM_DEBUG_SRC = \ %D%/etm.c \ $(OOCD_TRACE_FILES) \ %D%/etm_dummy.c \ + %D%/arm_tpiu_swo.c \ %D%/arm_cti.c AVR32_SRC = \ @@ -214,6 +215,7 @@ ARC_SRC = \ %D%/etb.h \ %D%/etm.h \ %D%/etm_dummy.h \ + %D%/arm_tpiu_swo.h \ %D%/image.h \ %D%/mips32.h \ %D%/mips64.h \ diff --git a/src/target/arm_tpiu_swo.c b/src/target/arm_tpiu_swo.c new file mode 100644 index 000000000..61891cb57 --- /dev/null +++ b/src/target/arm_tpiu_swo.c @@ -0,0 +1,998 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** + * @file + * This file implements support for the ARM CoreSight components Trace Port + * Interface Unit (TPIU) and Serial Wire Output (SWO). It also supports the + * CoreSight TPIU-Lite and the special TPIU version present with Cortex-M3 + * and Cortex-M4 (that includes SWO). + */ + +/* + * Relevant specifications from ARM include: + * + * CoreSight(tm) Components Technical Reference Manual ARM DDI 0314H + * CoreSight(tm) TPIU-Lite Technical Reference Manual ARM DDI 0317A + * Cortex(tm)-M3 Technical Reference Manual ARM DDI 0337G + * Cortex(tm)-M4 Technical Reference Manual ARM DDI 0439B + * CoreSight(tm) SoC-400 Technical Reference Manual ARM DDI 0480F + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "arm_tpiu_swo.h" + +#define TCP_SERVICE_NAME "tpiu_swo_trace" + +/* default for Cortex-M3 and Cortex-M4 specific TPIU */ +#define TPIU_SWO_DEFAULT_BASE 0xE0040000 + +#define TPIU_SSPSR_OFFSET 0x000 +#define TPIU_CSPSR_OFFSET 0x004 +#define TPIU_ACPR_OFFSET 0x010 +#define TPIU_SPPR_OFFSET 0x0F0 +#define TPIU_FFSR_OFFSET 0x300 +#define TPIU_FFCR_OFFSET 0x304 +#define TPIU_FSCR_OFFSET 0x308 +#define TPIU_DEVID_OFFSET 0xfc8 + +#define TPIU_ACPR_MAX_PRESCALER 0x1fff +#define TPIU_SPPR_PROTOCOL_SYNC 0x0 /**< synchronous trace output */ +#define TPIU_SPPR_PROTOCOL_MANCHESTER 0x1 /**< asynchronous output with NRZ coding */ +#define TPIU_SPPR_PROTOCOL_UART 0x2 /**< asynchronous output with Manchester coding */ +#define TPIU_DEVID_NOSUPPORT_SYNC BIT(9) +#define TPIU_DEVID_SUPPORT_MANCHESTER BIT(10) +#define TPIU_DEVID_SUPPORT_UART BIT(11) + +enum arm_tpiu_swo_event { + TPIU_SWO_EVENT_PRE_ENABLE, + TPIU_SWO_EVENT_POST_ENABLE, + TPIU_SWO_EVENT_PRE_DISABLE, + TPIU_SWO_EVENT_POST_DISABLE, +}; + +static const Jim_Nvp nvp_arm_tpiu_swo_event[] = { + { .value = TPIU_SWO_EVENT_PRE_ENABLE, .name = "pre-enable" }, + { .value = TPIU_SWO_EVENT_POST_ENABLE, .name = "post-enable" }, + { .value = TPIU_SWO_EVENT_PRE_DISABLE, .name = "pre-disable" }, + { .value = TPIU_SWO_EVENT_POST_DISABLE, .name = "post-disable" }, +}; + +struct arm_tpiu_swo_event_action { + enum arm_tpiu_swo_event event; + Jim_Interp *interp; + Jim_Obj *body; + struct arm_tpiu_swo_event_action *next; +}; + +struct arm_tpiu_swo_object { + struct list_head lh; + struct adiv5_mem_ap_spot spot; + char *name; + struct arm_tpiu_swo_event_action *event_action; + /* record enable before init */ + bool deferred_enable; + bool enabled; + bool en_capture; + /** Handle to output trace data in INTERNAL capture mode */ + /** Synchronous output port width */ + uint32_t port_width; + FILE *file; + /** output mode */ + unsigned int pin_protocol; + /** Enable formatter */ + bool en_formatter; + /** frequency of TRACECLKIN (usually matches HCLK) */ + unsigned int traceclkin_freq; + /** SWO pin frequency */ + unsigned int swo_pin_freq; + /** where to dump the captured output trace data */ + char *out_filename; + /** track TCP connections */ + struct list_head connections; +}; + +struct arm_tpiu_swo_connection { + struct list_head lh; + struct connection *connection; +}; + +struct arm_tpiu_swo_priv_connection { + struct arm_tpiu_swo_object *obj; +}; + +static LIST_HEAD(all_tpiu_swo); + +#define ARM_TPIU_SWO_TRACE_BUF_SIZE 4096 + +static int arm_tpiu_swo_poll_trace(void *priv) +{ + struct arm_tpiu_swo_object *obj = priv; + uint8_t buf[ARM_TPIU_SWO_TRACE_BUF_SIZE]; + size_t size = sizeof(buf); + struct arm_tpiu_swo_connection *c; + + int retval = adapter_poll_trace(buf, &size); + if (retval != ERROR_OK || !size) + return retval; + + target_call_trace_callbacks(/*target*/NULL, size, buf); + + if (obj->file) { + if (fwrite(buf, 1, size, obj->file) == size) { + fflush(obj->file); + } else { + LOG_ERROR("Error writing to the SWO trace destination file"); + return ERROR_FAIL; + } + } + + if (obj->out_filename && obj->out_filename[0] == ':') + list_for_each_entry(c, &obj->connections, lh) + if (connection_write(c->connection, buf, size) != (int)size) + retval = ERROR_FAIL; + + return ERROR_OK; +} + +static void arm_tpiu_swo_handle_event(struct arm_tpiu_swo_object *obj, enum arm_tpiu_swo_event event) +{ + for (struct arm_tpiu_swo_event_action *ea = obj->event_action; ea; ea = ea->next) { + if (ea->event != event) + continue; + + LOG_DEBUG("TPIU/SWO: %s event: %s (%d) action : %s", + obj->name, + Jim_Nvp_value2name_simple(nvp_arm_tpiu_swo_event, event)->name, + event, + Jim_GetString(ea->body, NULL)); + + /* prevent event execution to change current target */ + struct command_context *cmd_ctx = current_command_context(ea->interp); + struct target *saved_target = cmd_ctx->current_target; + int retval = Jim_EvalObj(ea->interp, ea->body); + cmd_ctx->current_target = saved_target; + + if (retval == JIM_RETURN) + retval = ea->interp->returnCode; + if (retval == JIM_OK || retval == ERROR_COMMAND_CLOSE_CONNECTION) + return; + + Jim_MakeErrorMessage(ea->interp); + LOG_USER("Error executing event %s on TPIU/SWO %s:\n%s", + Jim_Nvp_value2name_simple(nvp_arm_tpiu_swo_event, event)->name, + obj->name, + Jim_GetString(Jim_GetResult(ea->interp), NULL)); + /* clean both error code and stacktrace before return */ + Jim_Eval(ea->interp, "error \"\" \"\""); + return; + } +} + +static void arm_tpiu_swo_close_output(struct arm_tpiu_swo_object *obj) +{ + if (obj->file) { + fclose(obj->file); + obj->file = NULL; + } + if (obj->out_filename && obj->out_filename[0] == ':') + remove_service(TCP_SERVICE_NAME, &obj->out_filename[1]); +} + +int arm_tpiu_swo_cleanup_all(void) +{ + struct arm_tpiu_swo_object *obj, *tmp; + + list_for_each_entry_safe(obj, tmp, &all_tpiu_swo, lh) { + if (obj->enabled) + arm_tpiu_swo_handle_event(obj, TPIU_SWO_EVENT_PRE_DISABLE); + + arm_tpiu_swo_close_output(obj); + + if (obj->en_capture) { + target_unregister_timer_callback(arm_tpiu_swo_poll_trace, obj); + + int retval = adapter_config_trace(false, 0, 0, NULL, 0, NULL); + if (retval != ERROR_OK) + LOG_ERROR("Failed to stop adapter's trace"); + } + + if (obj->enabled) + arm_tpiu_swo_handle_event(obj, TPIU_SWO_EVENT_POST_DISABLE); + + struct arm_tpiu_swo_event_action *ea = obj->event_action; + while (ea) { + struct arm_tpiu_swo_event_action *next = ea->next; + Jim_DecrRefCount(ea->interp, ea->body); + free(ea); + ea = next; + } + + free(obj->name); + free(obj->out_filename); + free(obj); + } + + return ERROR_OK; +} + +static int arm_tpiu_swo_service_new_connection(struct connection *connection) +{ + struct arm_tpiu_swo_priv_connection *priv = connection->service->priv; + struct arm_tpiu_swo_object *obj = priv->obj; + struct arm_tpiu_swo_connection *c = malloc(sizeof(*c)); + if (!c) { + LOG_ERROR("Out of memory"); + return ERROR_FAIL; + } + c->connection = connection; + list_add(&c->lh, &obj->connections); + return ERROR_OK; +} + +static int arm_tpiu_swo_service_input(struct connection *connection) +{ + /* read a dummy buffer to check if the connection is still active */ + long dummy; + int bytes_read = connection_read(connection, &dummy, sizeof(dummy)); + + if (bytes_read == 0) { + return ERROR_SERVER_REMOTE_CLOSED; + } else if (bytes_read == -1) { + LOG_ERROR("error during read: %s", strerror(errno)); + return ERROR_SERVER_REMOTE_CLOSED; + } + + return ERROR_OK; +} + +static int arm_tpiu_swo_service_connection_closed(struct connection *connection) +{ + struct arm_tpiu_swo_priv_connection *priv = connection->service->priv; + struct arm_tpiu_swo_object *obj = priv->obj; + struct arm_tpiu_swo_connection *c, *tmp; + + list_for_each_entry_safe(c, tmp, &obj->connections, lh) + if (c->connection == connection) { + list_del(&c->lh); + free(c); + return ERROR_OK; + } + LOG_ERROR("Failed to find connection to close!"); + return ERROR_FAIL; +} + +COMMAND_HANDLER(handle_arm_tpiu_swo_event_list) +{ + struct arm_tpiu_swo_object *obj = CMD_DATA; + + command_print(CMD, "Event actions for TPIU/SWO %s\n", obj->name); + command_print(CMD, "%-25s | Body", "Event"); + command_print(CMD, "------------------------- | " + "----------------------------------------"); + + for (struct arm_tpiu_swo_event_action *ea = obj->event_action; ea; ea = ea->next) { + Jim_Nvp *opt = Jim_Nvp_value2name_simple(nvp_arm_tpiu_swo_event, ea->event); + command_print(CMD, "%-25s | %s", + opt->name, Jim_GetString(ea->body, NULL)); + } + command_print(CMD, "***END***"); + return ERROR_OK; +} + +enum arm_tpiu_swo_cfg_param { + CFG_PORT_WIDTH, + CFG_PROTOCOL, + CFG_FORMATTER, + CFG_TRACECLKIN, + CFG_BITRATE, + CFG_OUTFILE, + CFG_EVENT, +}; + +static const Jim_Nvp nvp_arm_tpiu_swo_config_opts[] = { + { .name = "-port-width", .value = CFG_PORT_WIDTH }, + { .name = "-protocol", .value = CFG_PROTOCOL }, + { .name = "-formatter", .value = CFG_FORMATTER }, + { .name = "-traceclk", .value = CFG_TRACECLKIN }, + { .name = "-pin-freq", .value = CFG_BITRATE }, + { .name = "-output", .value = CFG_OUTFILE }, + { .name = "-event", .value = CFG_EVENT }, + /* handled by mem_ap_spot, added for Jim_GetOpt_NvpUnknown() */ + { .name = "-dap", .value = -1 }, + { .name = "-ap-num", .value = -1 }, + { .name = "-baseaddr", .value = -1 }, + { .name = NULL, .value = -1 }, +}; + +static const Jim_Nvp nvp_arm_tpiu_swo_protocol_opts[] = { + { .name = "sync", .value = TPIU_SPPR_PROTOCOL_SYNC }, + { .name = "uart", .value = TPIU_SPPR_PROTOCOL_UART }, + { .name = "manchester", .value = TPIU_SPPR_PROTOCOL_MANCHESTER }, + { .name = NULL, .value = -1 }, +}; + +static const Jim_Nvp nvp_arm_tpiu_swo_bool_opts[] = { + { .name = "on", .value = 1 }, + { .name = "yes", .value = 1 }, + { .name = "1", .value = 1 }, + { .name = "true", .value = 1 }, + { .name = "off", .value = 0 }, + { .name = "no", .value = 0 }, + { .name = "0", .value = 0 }, + { .name = "false", .value = 0 }, + { .name = NULL, .value = -1 }, +}; + +static int arm_tpiu_swo_configure(Jim_GetOptInfo *goi, struct arm_tpiu_swo_object *obj) +{ + assert(obj != NULL); + + if (goi->isconfigure && obj->enabled) { + Jim_SetResultFormatted(goi->interp, "Cannot configure TPIU/SWO; %s is enabled!", obj->name); + return JIM_ERR; + } + + /* parse config or cget options ... */ + while (goi->argc > 0) { + Jim_SetEmptyResult(goi->interp); + + int e = adiv5_jim_mem_ap_spot_configure(&obj->spot, goi); + if (e == JIM_OK) + continue; + if (e == JIM_ERR) + return e; + + Jim_Nvp *n; + e = Jim_GetOpt_Nvp(goi, nvp_arm_tpiu_swo_config_opts, &n); + if (e != JIM_OK) { + Jim_GetOpt_NvpUnknown(goi, nvp_arm_tpiu_swo_config_opts, 0); + return e; + } + + switch (n->value) { + case CFG_PORT_WIDTH: + if (goi->isconfigure) { + jim_wide port_width; + e = Jim_GetOpt_Wide(goi, &port_width); + if (e != JIM_OK) + return e; + if (port_width < 1 || port_width > 32) { + Jim_SetResultString(goi->interp, "Invalid port width!", -1); + return JIM_ERR; + } + obj->port_width = (uint32_t)port_width; + } else { + if (goi->argc) + goto err_no_params; + Jim_SetResult(goi->interp, Jim_NewIntObj(goi->interp, obj->port_width)); + } + break; + case CFG_PROTOCOL: + if (goi->isconfigure) { + Jim_Nvp *p; + e = Jim_GetOpt_Nvp(goi, nvp_arm_tpiu_swo_protocol_opts, &p); + if (e != JIM_OK) + return e; + obj->pin_protocol = p->value; + } else { + if (goi->argc) + goto err_no_params; + Jim_Nvp *p; + e = Jim_Nvp_value2name(goi->interp, nvp_arm_tpiu_swo_protocol_opts, obj->pin_protocol, &p); + if (e != JIM_OK) { + Jim_SetResultString(goi->interp, "protocol error", -1); + return JIM_ERR; + } + Jim_SetResult(goi->interp, Jim_NewStringObj(goi->interp, p->name, -1)); + } + break; + case CFG_FORMATTER: + if (goi->isconfigure) { + Jim_Nvp *p; + e = Jim_GetOpt_Nvp(goi, nvp_arm_tpiu_swo_bool_opts, &p); + if (e != JIM_OK) + return e; + obj->en_formatter = p->value; + } else { + if (goi->argc) + goto err_no_params; + Jim_Nvp *p; + e = Jim_Nvp_value2name(goi->interp, nvp_arm_tpiu_swo_bool_opts, obj->en_formatter, &p); + if (e != JIM_OK) { + Jim_SetResultString(goi->interp, "formatter error", -1); + return JIM_ERR; + } + Jim_SetResult(goi->interp, Jim_NewStringObj(goi->interp, p->name, -1)); + } + break; + case CFG_TRACECLKIN: + if (goi->isconfigure) { + jim_wide clk; + e = Jim_GetOpt_Wide(goi, &clk); + if (e != JIM_OK) + return e; + obj->traceclkin_freq = clk; + } else { + if (goi->argc) + goto err_no_params; + Jim_SetResult(goi->interp, Jim_NewIntObj(goi->interp, obj->traceclkin_freq)); + } + break; + case CFG_BITRATE: + if (goi->isconfigure) { + jim_wide clk; + e = Jim_GetOpt_Wide(goi, &clk); + if (e != JIM_OK) + return e; + obj->swo_pin_freq = clk; + } else { + if (goi->argc) + goto err_no_params; + Jim_SetResult(goi->interp, Jim_NewIntObj(goi->interp, obj->swo_pin_freq)); + } + break; + case CFG_OUTFILE: + if (goi->isconfigure) { + const char *s; + e = Jim_GetOpt_String(goi, &s, NULL); + if (e != JIM_OK) + return e; + if (s[0] == ':') { + char *end; + long port = strtol(s + 1, &end, 0); + if (port <= 0 || port > UINT16_MAX || *end != '\0') { + Jim_SetResultFormatted(goi->interp, "Invalid TCP port \'%s\'", s + 1); + return JIM_ERR; + } + } + free(obj->out_filename); + obj->out_filename = strdup(s); + if (!obj->out_filename) { + LOG_ERROR("Out of memory"); + return JIM_ERR; + } + } else { + if (goi->argc) + goto err_no_params; + if (obj->out_filename) + Jim_SetResult(goi->interp, Jim_NewStringObj(goi->interp, obj->out_filename, -1)); + } + break; + case CFG_EVENT: + if (goi->isconfigure) { + if (goi->argc < 2) { + Jim_WrongNumArgs(goi->interp, goi->argc, goi->argv, "-event ?event-name? ?EVENT-BODY?"); + return JIM_ERR; + } + } else { + if (goi->argc != 1) { + Jim_WrongNumArgs(goi->interp, goi->argc, goi->argv, "-event ?event-name?"); + return JIM_ERR; + } + } + + { + Jim_Nvp *p; + Jim_Obj *o; + struct arm_tpiu_swo_event_action *ea = obj->event_action; + + e = Jim_GetOpt_Nvp(goi, nvp_arm_tpiu_swo_event, &p); + if (e != JIM_OK) { + Jim_GetOpt_NvpUnknown(goi, nvp_arm_tpiu_swo_event, 1); + return e; + } + + while (ea) { + /* replace existing? */ + if (ea->event == (enum arm_tpiu_swo_event)p->value) + break; + ea = ea->next; + } + + if (goi->isconfigure) { + if (!ea) { + ea = calloc(1, sizeof(*ea)); + if (!ea) { + LOG_ERROR("Out of memory"); + return JIM_ERR; + } + ea->next = obj->event_action; + obj->event_action = ea; + } + if (ea->body) + Jim_DecrRefCount(ea->interp, ea->body); + ea->event = p->value; + ea->interp = goi->interp; + Jim_GetOpt_Obj(goi, &o); + ea->body = Jim_DuplicateObj(goi->interp, o); + Jim_IncrRefCount(ea->body); + } else { + if (ea) + Jim_SetResult(goi->interp, Jim_DuplicateObj(goi->interp, ea->body)); + } + } + break; + } + } + + return JIM_OK; + +err_no_params: + Jim_WrongNumArgs(goi->interp, goi->argc, goi->argv, "NO PARAMS"); + return JIM_ERR; +} + +static int jim_arm_tpiu_swo_configure(Jim_Interp *interp, int argc, Jim_Obj * const *argv) +{ + Jim_GetOptInfo goi; + + Jim_GetOpt_Setup(&goi, interp, argc - 1, argv + 1); + goi.isconfigure = !strcmp(Jim_GetString(argv[0], NULL), "configure"); + if (goi.argc < 1) { + Jim_WrongNumArgs(goi.interp, goi.argc, goi.argv, + "missing: -option ..."); + return JIM_ERR; + } + struct arm_tpiu_swo_object *obj = Jim_CmdPrivData(interp); + return arm_tpiu_swo_configure(&goi, obj); +} + +static int wrap_write_u32(struct target *target, struct adiv5_ap *tpiu_ap, + target_addr_t address, uint32_t value) +{ + if (transport_is_hla()) + return target_write_u32(target, address, value); + else + return mem_ap_write_atomic_u32(tpiu_ap, address, value); +} + +static int wrap_read_u32(struct target *target, struct adiv5_ap *tpiu_ap, + target_addr_t address, uint32_t *value) +{ + if (transport_is_hla()) + return target_read_u32(target, address, value); + else + return mem_ap_read_atomic_u32(tpiu_ap, address, value); +} + +static int jim_arm_tpiu_swo_enable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct arm_tpiu_swo_object *obj = Jim_CmdPrivData(interp); + struct command_context *cmd_ctx = current_command_context(interp); + struct adiv5_ap *tpiu_ap = dap_ap(obj->spot.dap, obj->spot.ap_num); + uint32_t value; + int retval; + + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, "Too many parameters"); + return JIM_ERR; + } + + if (cmd_ctx->mode == COMMAND_CONFIG) { + LOG_DEBUG("%s: enable deferred", obj->name); + obj->deferred_enable = true; + return JIM_OK; + } + + if (obj->enabled) + return JIM_OK; + + if (transport_is_hla() && obj->spot.ap_num > 0) { + LOG_ERROR("Invalid access port %d. Only AP#0 allowed with hla transport", obj->spot.ap_num); + return JIM_ERR; + } + + if (!obj->traceclkin_freq) { + LOG_ERROR("Trace clock-in frequency not set"); + return JIM_ERR; + } + + if (obj->pin_protocol == TPIU_SPPR_PROTOCOL_MANCHESTER || obj->pin_protocol == TPIU_SPPR_PROTOCOL_UART) + if (!obj->swo_pin_freq) { + LOG_ERROR("SWO pin frequency not set"); + return JIM_ERR; + } + + struct target *target = get_current_target(cmd_ctx); + + /* trigger the event before any attempt to R/W in the TPIU/SWO */ + arm_tpiu_swo_handle_event(obj, TPIU_SWO_EVENT_PRE_ENABLE); + + retval = wrap_read_u32(target, tpiu_ap, obj->spot.base + TPIU_DEVID_OFFSET, &value); + if (retval != ERROR_OK) { + LOG_ERROR("Unable to read %s", obj->name); + return JIM_ERR; + } + switch (obj->pin_protocol) { + case TPIU_SPPR_PROTOCOL_SYNC: + value = !(value & TPIU_DEVID_NOSUPPORT_SYNC); + break; + case TPIU_SPPR_PROTOCOL_UART: + value &= TPIU_DEVID_SUPPORT_UART; + break; + case TPIU_SPPR_PROTOCOL_MANCHESTER: + value &= TPIU_DEVID_SUPPORT_MANCHESTER; + break; + default: + value = 0; + } + if (!value) { + Jim_Nvp *p; + Jim_Nvp_value2name(interp, nvp_arm_tpiu_swo_protocol_opts, obj->pin_protocol, &p); + LOG_ERROR("%s does not support protocol %s", obj->name, p->name); + return JIM_ERR; + } + + if (obj->pin_protocol == TPIU_SPPR_PROTOCOL_SYNC) { + retval = wrap_read_u32(target, tpiu_ap, obj->spot.base + TPIU_SSPSR_OFFSET, &value); + if (!(value & BIT(obj->port_width - 1))) { + LOG_ERROR("TPIU does not support port-width of %d bits", obj->port_width); + return JIM_ERR; + } + } + + uint16_t prescaler = 1; /* dummy value */ + unsigned int swo_pin_freq = obj->swo_pin_freq; /* could be replaced */ + + if (obj->out_filename && strcmp(obj->out_filename, "external") && obj->out_filename[0]) { + if (obj->out_filename[0] == ':') { + struct arm_tpiu_swo_priv_connection *priv = malloc(sizeof(*priv)); + if (!priv) { + LOG_ERROR("Out of memory"); + return JIM_ERR; + } + priv->obj = obj; + LOG_INFO("starting trace server for %s on %s", obj->name, &obj->out_filename[1]); + retval = add_service("tpiu_swo_trace", &obj->out_filename[1], + CONNECTION_LIMIT_UNLIMITED, arm_tpiu_swo_service_new_connection, + arm_tpiu_swo_service_input, arm_tpiu_swo_service_connection_closed, + priv, NULL); + if (retval != ERROR_OK) { + LOG_ERROR("Can't configure trace TCP port %s", &obj->out_filename[1]); + return JIM_ERR; + } + } else if (strcmp(obj->out_filename, "-")) { + obj->file = fopen(obj->out_filename, "ab"); + if (!obj->file) { + LOG_ERROR("Can't open trace destination file \"%s\"", obj->out_filename); + return JIM_ERR; + } + } + + retval = adapter_config_trace(true, obj->pin_protocol, obj->port_width, + &swo_pin_freq, obj->traceclkin_freq, &prescaler); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to start adapter's trace"); + arm_tpiu_swo_close_output(obj); + return JIM_ERR; + } + + if (obj->swo_pin_freq != swo_pin_freq) + LOG_INFO("SWO pin data rate adjusted by adapter to %d Hz", swo_pin_freq); + obj->swo_pin_freq = swo_pin_freq; + + target_register_timer_callback(arm_tpiu_swo_poll_trace, 1, + TARGET_TIMER_TYPE_PERIODIC, obj); + + obj->en_capture = true; + } else if (obj->pin_protocol == TPIU_SPPR_PROTOCOL_MANCHESTER || obj->pin_protocol == TPIU_SPPR_PROTOCOL_UART) { + prescaler = (obj->traceclkin_freq + obj->swo_pin_freq / 2) / obj->swo_pin_freq; + if (prescaler > TPIU_ACPR_MAX_PRESCALER) + prescaler = TPIU_ACPR_MAX_PRESCALER; + swo_pin_freq = obj->traceclkin_freq / prescaler; + + if (obj->swo_pin_freq != swo_pin_freq) + LOG_INFO("SWO pin data rate adjusted to %d Hz", swo_pin_freq); + obj->swo_pin_freq = swo_pin_freq; + } + + retval = wrap_write_u32(target, tpiu_ap, obj->spot.base + TPIU_CSPSR_OFFSET, BIT(obj->port_width - 1)); + if (retval != ERROR_OK) + goto error_exit; + + retval = wrap_write_u32(target, tpiu_ap, obj->spot.base + TPIU_ACPR_OFFSET, prescaler - 1); + if (retval != ERROR_OK) + goto error_exit; + + retval = wrap_write_u32(target, tpiu_ap, obj->spot.base + TPIU_SPPR_OFFSET, obj->pin_protocol); + if (retval != ERROR_OK) + goto error_exit; + + retval = wrap_read_u32(target, tpiu_ap, obj->spot.base + TPIU_FFCR_OFFSET, &value); + if (retval != ERROR_OK) + goto error_exit; + if (obj->en_formatter) + value |= BIT(1); + else + value &= ~BIT(1); + retval = wrap_write_u32(target, tpiu_ap, obj->spot.base + TPIU_FFCR_OFFSET, value); + if (retval != ERROR_OK) + goto error_exit; + + arm_tpiu_swo_handle_event(obj, TPIU_SWO_EVENT_POST_ENABLE); + + obj->enabled = true; + return JIM_OK; + +error_exit: + LOG_ERROR("Error!"); + + if (obj->en_capture) { + obj->en_capture = false; + + arm_tpiu_swo_close_output(obj); + + target_unregister_timer_callback(arm_tpiu_swo_poll_trace, obj); + + retval = adapter_config_trace(false, 0, 0, NULL, 0, NULL); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to stop adapter's trace"); + return JIM_ERR; + } + } + return JIM_ERR; +} + +static int jim_arm_tpiu_swo_disable(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct arm_tpiu_swo_object *obj = Jim_CmdPrivData(interp); + + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, "Too many parameters"); + return JIM_ERR; + } + + if (!obj->enabled) + return JIM_OK; + obj->enabled = false; + + arm_tpiu_swo_handle_event(obj, TPIU_SWO_EVENT_PRE_DISABLE); + + if (obj->en_capture) { + obj->en_capture = false; + + arm_tpiu_swo_close_output(obj); + + target_unregister_timer_callback(arm_tpiu_swo_poll_trace, obj); + + int retval = adapter_config_trace(false, 0, 0, NULL, 0, NULL); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to stop adapter's trace"); + return JIM_ERR; + } + } + + arm_tpiu_swo_handle_event(obj, TPIU_SWO_EVENT_POST_DISABLE); + return JIM_OK; +} + +static const struct command_registration arm_tpiu_swo_instance_command_handlers[] = { + { + .name = "configure", + .mode = COMMAND_ANY, + .jim_handler = jim_arm_tpiu_swo_configure, + .help = "configure a new TPIU/SWO for use", + .usage = "[attribute value ...]", + }, + { + .name = "cget", + .mode = COMMAND_ANY, + .jim_handler = jim_arm_tpiu_swo_configure, + .help = "returns the specified TPIU/SWO attribute", + .usage = "attribute", + }, + { + .name = "eventlist", + .mode = COMMAND_ANY, + .handler = handle_arm_tpiu_swo_event_list, + .help = "displays a table of events defined for this TPIU/SWO", + .usage = "", + }, + { + .name = "enable", + .mode = COMMAND_ANY, + .jim_handler = jim_arm_tpiu_swo_enable, + .usage = "", + .help = "Enables the TPIU/SWO output", + }, + { + .name = "disable", + .mode = COMMAND_EXEC, + .jim_handler = jim_arm_tpiu_swo_disable, + .usage = "", + .help = "Disables the TPIU/SWO output", + }, + COMMAND_REGISTRATION_DONE +}; + +static int arm_tpiu_swo_create(Jim_Interp *interp, struct arm_tpiu_swo_object *obj) +{ + struct command_context *cmd_ctx; + Jim_Cmd *cmd; + int e; + + cmd_ctx = current_command_context(interp); + assert(cmd_ctx != NULL); + + /* does this command exist? */ + cmd = Jim_GetCommand(interp, Jim_NewStringObj(interp, obj->name, -1), JIM_ERRMSG); + if (cmd) { + Jim_SetResultFormatted(interp, "Command: %s Exists", obj->name); + return JIM_ERR; + } + + /* now - create the new tpiu/swo name command */ + const struct command_registration obj_commands[] = { + { + .name = obj->name, + .mode = COMMAND_ANY, + .help = "tpiu/swo instance command group", + .usage = "", + .chain = arm_tpiu_swo_instance_command_handlers, + }, + COMMAND_REGISTRATION_DONE + }; + e = register_commands(cmd_ctx, NULL, obj_commands); + if (ERROR_OK != e) + return JIM_ERR; + + struct command *c = command_find_in_context(cmd_ctx, obj->name); + assert(c); + command_set_handler_data(c, obj); + + list_add_tail(&obj->lh, &all_tpiu_swo); + + return JIM_OK; +} + +static int jim_arm_tpiu_swo_create(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + Jim_GetOptInfo goi; + Jim_GetOpt_Setup(&goi, interp, argc - 1, argv + 1); + if (goi.argc < 1) { + Jim_WrongNumArgs(goi.interp, 1, goi.argv, "?name? ..options..."); + return JIM_ERR; + } + + struct arm_tpiu_swo_object *obj = calloc(1, sizeof(struct arm_tpiu_swo_object)); + if (!obj) { + LOG_ERROR("Out of memory"); + return JIM_ERR; + } + INIT_LIST_HEAD(&obj->connections); + adiv5_mem_ap_spot_init(&obj->spot); + obj->spot.base = TPIU_SWO_DEFAULT_BASE; + obj->port_width = 1; + + Jim_Obj *n; + Jim_GetOpt_Obj(&goi, &n); + obj->name = strdup(Jim_GetString(n, NULL)); + if (!obj->name) { + LOG_ERROR("Out of memory"); + free(obj); + return JIM_ERR; + } + + /* Do the rest as "configure" options */ + goi.isconfigure = 1; + int e = arm_tpiu_swo_configure(&goi, obj); + if (e != JIM_OK) + goto err_exit; + + if (!obj->spot.dap || obj->spot.ap_num == DP_APSEL_INVALID) { + Jim_SetResultString(goi.interp, "-dap and -ap-num required when creating TPIU", -1); + goto err_exit; + } + + e = arm_tpiu_swo_create(goi.interp, obj); + if (e != JIM_OK) + goto err_exit; + + return JIM_OK; + +err_exit: + free(obj->name); + free(obj->out_filename); + free(obj); + return JIM_ERR; +} + +static int jim_arm_tpiu_swo_names(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct arm_tpiu_swo_object *obj; + + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, "Too many parameters"); + return JIM_ERR; + } + Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0)); + list_for_each_entry(obj, &all_tpiu_swo, lh) { + Jim_ListAppendElement(interp, Jim_GetResult(interp), + Jim_NewStringObj(interp, obj->name, -1)); + } + return JIM_OK; +} + +static int jim_arm_tpiu_swo_init(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + struct command_context *cmd_ctx = current_command_context(interp); + struct arm_tpiu_swo_object *obj; + int retval = JIM_OK; + + if (argc != 1) { + Jim_WrongNumArgs(interp, 1, argv, "Too many parameters"); + return JIM_ERR; + } + list_for_each_entry(obj, &all_tpiu_swo, lh) { + if (!obj->deferred_enable) + continue; + LOG_DEBUG("%s: running enable during init", obj->name); + int retval2 = command_run_linef(cmd_ctx, "%s enable", obj->name); + if (retval2 != ERROR_OK) + retval = JIM_ERR; + } + return retval; +} + +static const struct command_registration arm_tpiu_swo_subcommand_handlers[] = { + { + .name = "create", + .mode = COMMAND_ANY, + .jim_handler = jim_arm_tpiu_swo_create, + .usage = "name [-dap dap] [-ap-num num] [-address baseaddr]", + .help = "Creates a new TPIU or SWO object", + }, + { + .name = "names", + .mode = COMMAND_ANY, + .jim_handler = jim_arm_tpiu_swo_names, + .usage = "", + .help = "Lists all registered TPIU and SWO objects by name", + }, + { + .name = "init", + .mode = COMMAND_EXEC, + .jim_handler = jim_arm_tpiu_swo_init, + .usage = "", + .help = "Initialize TPIU and SWO", + }, + COMMAND_REGISTRATION_DONE +}; + +static const struct command_registration arm_tpiu_swo_command_handlers[] = { + { + .name = "tpiu", + .chain = arm_tpiu_swo_subcommand_handlers, + .usage = "", + .help = "tpiu command group", + }, + { + .name = "swo", + .chain = arm_tpiu_swo_subcommand_handlers, + .usage = "", + .help = "swo command group", + }, + COMMAND_REGISTRATION_DONE +}; + +int arm_tpiu_swo_register_commands(struct command_context *cmd_ctx) +{ + return register_commands(cmd_ctx, NULL, arm_tpiu_swo_command_handlers); +} diff --git a/src/target/arm_tpiu_swo.h b/src/target/arm_tpiu_swo.h new file mode 100644 index 000000000..e0aed71cb --- /dev/null +++ b/src/target/arm_tpiu_swo.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef OPENOCD_TARGET_ARM_TPIU_SWO_H +#define OPENOCD_TARGET_ARM_TPIU_SWO_H + +int arm_tpiu_swo_register_commands(struct command_context *cmd_ctx); +int arm_tpiu_swo_cleanup_all(void); + +#endif /* OPENOCD_TARGET_ARM_TPIU_SWO_H */