target/espressif: add application tracing functionality over JTAG 63/7163/20
authorErhan Kurubas <erhan.kurubas@espressif.com>
Thu, 25 Aug 2022 12:38:34 +0000 (15:38 +0300)
committerAntonio Borneo <borneo.antonio@gmail.com>
Fri, 14 Apr 2023 15:15:54 +0000 (15:15 +0000)
This feature allows to transfer arbitrary data between host and
ESP32 via JTAG.

The main use cases:

1- Collecting application specific data
2- Lightweight logging to the host
3- System behaviour analysis with SEGGER SystemView
4- Source code coverage

Signed-off-by: Erhan Kurubas <erhan.kurubas@espressif.com>
Change-Id: I95dee00ac22891fa326915a3fcac3c088cbb2afc
Reviewed-on: https://review.openocd.org/c/openocd/+/7163
Tested-by: jenkins
Reviewed-by: Antonio Borneo <borneo.antonio@gmail.com>
doc/openocd.texi
src/target/espressif/Makefile.am
src/target/espressif/esp32.c
src/target/espressif/esp32_apptrace.c [new file with mode: 0644]
src/target/espressif/esp32_apptrace.h [new file with mode: 0644]
src/target/espressif/esp32s2.c
src/target/espressif/esp32s3.c
src/target/espressif/esp_xtensa.c
src/target/espressif/esp_xtensa.h
src/target/espressif/esp_xtensa_apptrace.c [new file with mode: 0644]
src/target/espressif/esp_xtensa_apptrace.h [new file with mode: 0644]

index fe72cf5fac0ba2c0f7b0a8c8ea216e375956bf2f..4154e56b54011e109e20b05b05d3932529244258 100644 (file)
@@ -11137,6 +11137,46 @@ Stop current trace as started by the tracestart command.
 Dump trace memory to a file.
 @end deffn
 
+@section Espressif Specific Commands
+
+@deffn {Command} {esp apptrace} (start <destination> [<poll_period> [<trace_size> [<stop_tmo> [<wait4halt> [<skip_size>]]]]])
+Starts
+@uref{https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/app_trace.html#application-level-tracing-library, application level tracing}.
+Data will be stored to specified destination. Available destinations are:
+@itemize @bullet
+@item @code{file://<outfile>} - Save trace logs into file.
+@item @code{tcp://<host>:<port>} - Send trace logs to tcp port on specified host. OpenOCD will act as a tcp client.
+@item @code{con:} - Print trace logs to the stdout.
+@end itemize
+Other parameters will be same for each destination.
+@itemize @bullet
+@item @code{poll_period} - trace data polling period in ms.
+@item @code{trace_size} - maximum trace data size.
+Tracing will be stopped automatically when that amount is reached.
+Use "-1" to disable the limitation.
+@item @code{stop_tmo} - Data reception timeout in ms.
+Tracing will be stopped automatically when no data is received within that period.
+@item @code{wait4halt} - if non-zero then wait for target to be halted before tracing start.
+@item @code{skip_size} - amount of tracing data to be skipped before writing it to destination.
+@end itemize
+@end deffn
+
+@deffn {Command} {esp apptrace} (stop)
+Stops tracing started with above command.
+@end deffn
+
+@deffn {Command} {esp apptrace} (status)
+Requests ongoing tracing status.
+@end deffn
+
+@deffn {Command} {esp apptrace} (dump file://<outfile>)
+Dumps tracing data from target buffer. It can be useful to dump the latest data
+buffered on target for post-mortem analysis. For example when target starts tracing automatically
+w/o OpenOCD command and keeps only the latest data window which fit into the buffer.
+@uref{https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/app_trace.html#application-level-tracing-library, application level tracing}.
+Data will be stored to specified destination.
+@end deffn
+
 @anchor{softwaredebugmessagesandtracing}
 @section Software Debug Messages and Tracing
 @cindex Linux-ARM DCC support
index 8367a3881931e4107e74c3abaa11468c1c3226e7..c1759ed77f7aea7a9187c87b79106b73addd7d70 100644 (file)
@@ -8,6 +8,10 @@ noinst_LTLIBRARIES += %D%/libespressif.la
        %D%/esp_xtensa_smp.h \
        %D%/esp_xtensa_semihosting.c \
        %D%/esp_xtensa_semihosting.h \
+       %D%/esp_xtensa_apptrace.c \
+       %D%/esp_xtensa_apptrace.h \
+       %D%/esp32_apptrace.c \
+       %D%/esp32_apptrace.h \
        %D%/esp32.c \
        %D%/esp32s2.c \
        %D%/esp32s3.c \
index 63055cf183e769906a2bc56b5b55f0904a058c99..74bbe50bdd1d3e7a0282b0d6466dd8ec2fe80f02 100644 (file)
@@ -440,6 +440,11 @@ static const struct command_registration esp32_command_handlers[] = {
        {
                .chain = esp_xtensa_smp_command_handlers,
        },
+       {
+               .name = "esp",
+               .usage = "",
+               .chain = esp32_apptrace_command_handlers,
+       },
        {
                .name = "esp32",
                .usage = "",
diff --git a/src/target/espressif/esp32_apptrace.c b/src/target/espressif/esp32_apptrace.c
new file mode 100644 (file)
index 0000000..dfeb794
--- /dev/null
@@ -0,0 +1,1376 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/***************************************************************************
+ *   ESP32xx application tracing module for OpenOCD                        *
+ *   Copyright (C) 2017 Espressif Systems Ltd.                             *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+
+#ifndef _WIN32
+#include <netinet/tcp.h>
+#include <sys/ioctl.h>
+#endif
+
+#include <helper/list.h>
+#include <helper/time_support.h>
+#include <target/target.h>
+#include <target/target_type.h>
+#include <target/smp.h>
+#include <server/server.h>
+#include "esp_xtensa.h"
+#include "esp_xtensa_smp.h"
+#include "esp_xtensa_apptrace.h"
+#include "esp32_apptrace.h"
+
+#define ESP32_APPTRACE_USER_BLOCK_CORE(_v_)     ((_v_) >> 15)
+#define ESP32_APPTRACE_USER_BLOCK_LEN(_v_)      ((_v_) & ~BIT(15))
+
+#define ESP32_APPTRACE_USER_BLOCK_HDR_SZ        4
+
+#define ESP_APPTRACE_CMD_MODE_GEN               0
+#define ESP_APPTRACE_CMD_MODE_SYSVIEW           1
+#define ESP_APPTRACE_CMD_MODE_SYSVIEW_MCORE     2
+#define ESP_APPTRACE_CMD_MODE_SYNC              3
+
+#define ESP32_APPTRACE_TGT_STATE_TMO            5000
+#define ESP_APPTRACE_BLOCKS_POOL_SZ             10
+
+struct esp32_apptrace_dest_file_data {
+       int fout;
+};
+
+struct esp32_apptrace_dest_tcp_data {
+       int sockfd;
+};
+
+struct esp32_apptrace_target_state {
+       int running;
+       uint32_t block_id;
+       uint32_t data_len;
+};
+
+struct esp_apptrace_target2host_hdr {
+       uint16_t block_sz;
+       uint16_t wr_sz;
+};
+#define APPTRACE_BLOCK_SIZE_OFFSET      0
+#define APPTRACE_WR_SIZE_OFFSET         2
+
+struct esp32_apptrace_block {
+       struct list_head node;
+       uint8_t *data;
+       uint32_t data_len;
+};
+
+static int esp32_apptrace_data_processor(void *priv);
+static int esp32_apptrace_get_data_info(struct esp32_apptrace_cmd_ctx *ctx,
+       struct esp32_apptrace_target_state *target_state,
+       uint32_t *fired_target_num);
+static int esp32_apptrace_safe_halt_targets(struct esp32_apptrace_cmd_ctx *ctx,
+       struct esp32_apptrace_target_state *targets);
+static struct esp32_apptrace_block *esp32_apptrace_free_block_get(struct esp32_apptrace_cmd_ctx *ctx);
+static int esp32_apptrace_handle_trace_block(struct esp32_apptrace_cmd_ctx *ctx,
+       struct esp32_apptrace_block *block);
+
+static const bool s_time_stats_enable = true;
+
+/*********************************************************************
+*                       Trace destination API
+**********************************************************************/
+
+static int esp32_apptrace_file_dest_write(void *priv, uint8_t *data, int size)
+{
+       struct esp32_apptrace_dest_file_data *dest_data = (struct esp32_apptrace_dest_file_data *)priv;
+
+       int wr_sz = write(dest_data->fout, data, size);
+       if (wr_sz != size) {
+               LOG_ERROR("Failed to write %d bytes to out file (%d)! Written %d.", size, errno, wr_sz);
+               return ERROR_FAIL;
+       }
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_file_dest_cleanup(void *priv)
+{
+       struct esp32_apptrace_dest_file_data *dest_data = (struct esp32_apptrace_dest_file_data *)priv;
+
+       if (dest_data->fout > 0)
+               close(dest_data->fout);
+       free(dest_data);
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_file_dest_init(struct esp32_apptrace_dest *dest, const char *dest_name)
+{
+       struct esp32_apptrace_dest_file_data *dest_data = calloc(1, sizeof(*dest_data));
+       if (!dest_data) {
+               LOG_ERROR("Failed to alloc mem for file dest!");
+               return ERROR_FAIL;
+       }
+
+       LOG_INFO("Open file %s", dest_name);
+       dest_data->fout = open(dest_name, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0666);
+       if (dest_data->fout <= 0) {
+               LOG_ERROR("Failed to open file %s", dest_name);
+               free(dest_data);
+               return ERROR_FAIL;
+       }
+
+       dest->priv = dest_data;
+       dest->write = esp32_apptrace_file_dest_write;
+       dest->clean = esp32_apptrace_file_dest_cleanup;
+       dest->log_progress = true;
+
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_console_dest_write(void *priv, uint8_t *data, int size)
+{
+       LOG_USER_N("%.*s", size, data);
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_console_dest_cleanup(void *priv)
+{
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_console_dest_init(struct esp32_apptrace_dest *dest, const char *dest_name)
+{
+       dest->priv = NULL;
+       dest->write = esp32_apptrace_console_dest_write;
+       dest->clean = esp32_apptrace_console_dest_cleanup;
+       dest->log_progress = false;
+
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_tcp_dest_write(void *priv, uint8_t *data, int size)
+{
+       struct esp32_apptrace_dest_tcp_data *dest_data = (struct esp32_apptrace_dest_tcp_data *)priv;
+       int wr_sz = write_socket(dest_data->sockfd, data, size);
+       if (wr_sz != size) {
+               LOG_ERROR("Failed to write %u bytes to out socket (%d)! Written %d.", size, errno, wr_sz);
+               return ERROR_FAIL;
+       }
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_tcp_dest_cleanup(void *priv)
+{
+       struct esp32_apptrace_dest_tcp_data *dest_data = (struct esp32_apptrace_dest_tcp_data *)priv;
+
+       if (dest_data->sockfd > 0)
+               close_socket(dest_data->sockfd);
+       free(dest_data);
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_tcp_dest_init(struct esp32_apptrace_dest *dest, const char *dest_name)
+{
+       const char *port_sep = strchr(dest_name, ':');
+       /* separator not found, or was the first or the last character */
+       if (!port_sep || port_sep == dest_name || port_sep == dest_name + strlen(dest_name) - 1) {
+               LOG_ERROR("apptrace: Invalid connection URI, format should be tcp://host:port");
+               return ERROR_COMMAND_ARGUMENT_INVALID;
+       }
+       size_t hostname_len = port_sep - dest_name;
+
+       char hostname[64] = { 0 };
+       if (hostname_len >= sizeof(hostname)) {
+               LOG_ERROR("apptrace: Hostname too long");
+               return ERROR_COMMAND_ARGUMENT_INVALID;
+       }
+       memcpy(hostname, dest_name, hostname_len);
+
+       const char *port_str = port_sep + 1;
+       struct addrinfo *ai;
+       int flags = 0;
+#ifdef AI_NUMERICSERV
+       flags |= AI_NUMERICSERV;
+#endif /* AI_NUMERICSERV */
+       struct addrinfo hint = {
+               .ai_family = AF_UNSPEC,
+               .ai_socktype = SOCK_STREAM,
+               .ai_protocol = 0,
+               .ai_flags = flags
+       };
+       int res = getaddrinfo(hostname, port_str, &hint, &ai);
+       if (res != 0) {
+               LOG_ERROR("apptrace: Failed to resolve host name: %s", hostname);
+               return ERROR_FAIL;
+       }
+       int sockfd = -1;
+       for (struct addrinfo *ai_it = ai; ai_it; ai_it = ai_it->ai_next) {
+               sockfd = socket(ai_it->ai_family, ai_it->ai_socktype, ai_it->ai_protocol);
+               if (sockfd < 0) {
+                       LOG_DEBUG("apptrace: Failed to create socket (%d, %d, %d) (%s)",
+                               ai_it->ai_family,
+                               ai_it->ai_socktype,
+                               ai_it->ai_protocol,
+                               strerror(errno));
+                       continue;
+               }
+
+               char cur_hostname[NI_MAXHOST];
+               char cur_portname[NI_MAXSERV];
+               res =
+                       getnameinfo(ai_it->ai_addr, ai_it->ai_addrlen, cur_hostname,
+                       sizeof(cur_hostname),
+                       cur_portname, sizeof(cur_portname),
+                       NI_NUMERICHOST | NI_NUMERICSERV);
+               if (res != 0)
+                       continue;
+
+               LOG_INFO("apptrace: Trying to connect to %s:%s", cur_hostname, cur_portname);
+               if (connect(sockfd, ai_it->ai_addr, ai_it->ai_addrlen) < 0) {
+                       close_socket(sockfd);
+                       sockfd = -1;
+                       LOG_WARNING("apptrace: Connection failed (%s)", strerror(errno));
+                       continue;
+               }
+               break;
+       }
+       freeaddrinfo(ai);
+       if (sockfd < 0) {
+               LOG_ERROR("apptrace: Could not connect to %s:%s", hostname, port_str);
+               return ERROR_FAIL;
+       }
+       LOG_INFO("apptrace: Connected!");
+
+       struct esp32_apptrace_dest_tcp_data *dest_data = calloc(1, sizeof(struct esp32_apptrace_dest_tcp_data));
+       if (!dest_data) {
+               LOG_ERROR("apptrace: Failed to alloc mem for tcp dest!");
+               close_socket(sockfd);
+               return ERROR_FAIL;
+       }
+
+       dest_data->sockfd = sockfd;
+       dest->priv = dest_data;
+       dest->write = esp32_apptrace_tcp_dest_write;
+       dest->clean = esp32_apptrace_tcp_dest_cleanup;
+       dest->log_progress = true;
+
+       return ERROR_OK;
+}
+
+int esp32_apptrace_dest_init(struct esp32_apptrace_dest dest[], const char *dest_paths[], unsigned int max_dests)
+{
+       int res;
+       unsigned int i;
+
+       for (i = 0; i < max_dests; i++) {
+               if (strncmp(dest_paths[i], "file://", 7) == 0)
+                       res = esp32_apptrace_file_dest_init(&dest[i], &dest_paths[i][7]);
+               else if (strncmp(dest_paths[i], "con:", 4) == 0)
+                       res = esp32_apptrace_console_dest_init(&dest[i], NULL);
+               else if (strncmp(dest_paths[i], "tcp://", 6) == 0)
+                       res = esp32_apptrace_tcp_dest_init(&dest[i], &dest_paths[i][6]);
+               else
+                       break;
+
+               if (res != ERROR_OK) {
+                       LOG_ERROR("apptrace: Failed to init trace data destination '%s'!", dest_paths[i]);
+                       return 0;
+               }
+       }
+
+       return i;
+}
+
+int esp32_apptrace_dest_cleanup(struct esp32_apptrace_dest dest[], unsigned int max_dests)
+{
+       for (unsigned int i = 0; i < max_dests; i++) {
+               if (dest[i].clean && dest[i].priv) {
+                       int res = dest[i].clean(dest[i].priv);
+                       dest[i].priv = NULL;
+                       return res;
+               }
+       }
+       return ERROR_OK;
+}
+
+/*********************************************************************
+*                 Trace data blocks management API
+**********************************************************************/
+static void esp32_apptrace_blocks_pool_cleanup(struct esp32_apptrace_cmd_ctx *ctx)
+{
+       struct esp32_apptrace_block *cur;
+       struct list_head *head = &ctx->free_trace_blocks;
+       struct list_head *tmp, *pos;
+
+       list_for_each_safe(pos, tmp, head) {
+               cur = list_entry(pos, struct esp32_apptrace_block, node);
+               if (cur) {
+                       list_del(&cur->node);
+                       free(cur->data);
+                       free(cur);
+               }
+       }
+
+       head = &ctx->ready_trace_blocks;
+
+       list_for_each_safe(pos, tmp, head) {
+               cur = list_entry(pos, struct esp32_apptrace_block, node);
+               if (cur) {
+                       list_del(&cur->node);
+                       free(cur->data);
+                       free(cur);
+               }
+       }
+}
+
+struct esp32_apptrace_block *esp32_apptrace_free_block_get(struct esp32_apptrace_cmd_ctx *ctx)
+{
+       struct esp32_apptrace_block *block = NULL;
+
+       if (!list_empty(&ctx->free_trace_blocks)) {
+               /*get first */
+               block = list_first_entry(&ctx->free_trace_blocks, struct esp32_apptrace_block, node);
+               list_del(&block->node);
+       }
+
+       return block;
+}
+
+static int esp32_apptrace_ready_block_put(struct esp32_apptrace_cmd_ctx *ctx, struct esp32_apptrace_block *block)
+{
+       LOG_DEBUG("esp32_apptrace_ready_block_put");
+       /* add to ready blocks list */
+       INIT_LIST_HEAD(&block->node);
+       list_add(&block->node, &ctx->ready_trace_blocks);
+
+       return ERROR_OK;
+}
+
+static struct esp32_apptrace_block *esp32_apptrace_ready_block_get(struct esp32_apptrace_cmd_ctx *ctx)
+{
+       struct esp32_apptrace_block *block = NULL;
+
+       if (!list_empty(&ctx->ready_trace_blocks)) {
+               struct list_head *head = &ctx->ready_trace_blocks;
+               struct list_head *tmp, *pos;
+
+               list_for_each_safe(pos, tmp, head) {
+                       block = list_entry(pos, struct esp32_apptrace_block, node);
+               }
+               /* remove it from ready list */
+               list_del(&block->node);
+       }
+
+       return block;
+}
+
+static int esp32_apptrace_block_free(struct esp32_apptrace_cmd_ctx *ctx, struct esp32_apptrace_block *block)
+{
+       /* add to free blocks list */
+       INIT_LIST_HEAD(&block->node);
+       list_add(&block->node, &ctx->free_trace_blocks);
+
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_wait_tracing_finished(struct esp32_apptrace_cmd_ctx *ctx)
+{
+       int64_t timeout = timeval_ms() + (LOG_LEVEL_IS(LOG_LVL_DEBUG) ? 70000 : 5000);
+       while (!list_empty(&ctx->ready_trace_blocks)) {
+               alive_sleep(100);
+               if (timeval_ms() >= timeout) {
+                       LOG_ERROR("Failed to wait for pended trace blocks!");
+                       return ERROR_FAIL;
+               }
+       }
+       /* signal timer callback to stop */
+       ctx->running = 0;
+       target_unregister_timer_callback(esp32_apptrace_data_processor, ctx);
+       return ERROR_OK;
+}
+
+/*********************************************************************
+*                          Trace commands
+**********************************************************************/
+
+int esp32_apptrace_cmd_ctx_init(struct esp32_apptrace_cmd_ctx *cmd_ctx, struct command_invocation *cmd, int mode)
+{
+       struct target *target = get_current_target(CMD_CTX);
+
+       memset(cmd_ctx, 0, sizeof(struct esp32_apptrace_cmd_ctx));
+       cmd_ctx->target = target;
+       cmd_ctx->mode = mode;
+       cmd_ctx->target_state = target->state;
+       cmd_ctx->cmd = cmd;
+
+       if (target->smp) {
+               struct target_list *head;
+               struct target *curr;
+               unsigned int i = 0;
+               cmd_ctx->cores_num = 0;
+               foreach_smp_target(head, target->smp_targets) {
+                       curr = head->target;
+                       if (i == ESP32_APPTRACE_MAX_CORES_NUM) {
+                               command_print(cmd, "Too many cores configured! Max %d cores are supported.",
+                                       ESP32_APPTRACE_MAX_CORES_NUM);
+                               return ERROR_FAIL;
+                       }
+                       if (!target_was_examined(curr))
+                               continue;
+                       cmd_ctx->cores_num++;
+                       cmd_ctx->cpus[i++] = curr;
+               }
+       } else {
+               cmd_ctx->cores_num = 1;
+               cmd_ctx->cpus[0] = target;
+       }
+       /* some relies on ESP32_APPTRACE_MAX_CORES_NUM
+        * TODO: remove that dependency */
+       assert(cmd_ctx->cores_num <= ESP32_APPTRACE_MAX_CORES_NUM && "Too many cores number!");
+
+       struct xtensa *xtensa = target->arch_info;
+       if (xtensa->common_magic == XTENSA_COMMON_MAGIC) {
+               cmd_ctx->hw = target_to_esp_xtensa(target)->apptrace.hw;
+       } else { /* TODO: riscv is not supported yet */
+               command_print(cmd, "Unsupported target arch 0x%X", xtensa->common_magic);
+               return ERROR_FAIL;
+       }
+
+       cmd_ctx->max_trace_block_sz = cmd_ctx->hw->max_block_size_get(cmd_ctx->cpus[0]);
+       if (cmd_ctx->max_trace_block_sz == 0) {
+               command_print(cmd, "Failed to get max trace block size!");
+               return ERROR_FAIL;
+       }
+       LOG_INFO("Total trace memory: %" PRIu32 " bytes", cmd_ctx->max_trace_block_sz);
+
+       INIT_LIST_HEAD(&cmd_ctx->ready_trace_blocks);
+       INIT_LIST_HEAD(&cmd_ctx->free_trace_blocks);
+       for (unsigned int i = 0; i < ESP_APPTRACE_BLOCKS_POOL_SZ; i++) {
+               struct esp32_apptrace_block *block = calloc(1, sizeof(struct esp32_apptrace_block));
+               if (!block) {
+                       command_print(cmd, "Failed to alloc trace buffer entry!");
+                       esp32_apptrace_blocks_pool_cleanup(cmd_ctx);
+                       return ERROR_FAIL;
+               }
+               block->data = malloc(cmd_ctx->max_trace_block_sz);
+               if (!block->data) {
+                       free(block);
+                       command_print(cmd, "Failed to alloc trace buffer %" PRIu32 " bytes!", cmd_ctx->max_trace_block_sz);
+                       esp32_apptrace_blocks_pool_cleanup(cmd_ctx);
+                       return ERROR_FAIL;
+               }
+               INIT_LIST_HEAD(&block->node);
+               list_add(&block->node, &cmd_ctx->free_trace_blocks);
+       }
+
+       cmd_ctx->running = 1;
+       if (cmd_ctx->mode != ESP_APPTRACE_CMD_MODE_SYNC) {
+               int res = target_register_timer_callback(esp32_apptrace_data_processor,
+                       0,
+                       TARGET_TIMER_TYPE_PERIODIC,
+                       cmd_ctx);
+               if (res != ERROR_OK) {
+                       command_print(cmd, "Failed to start trace data timer callback (%d)!", res);
+                       esp32_apptrace_blocks_pool_cleanup(cmd_ctx);
+                       return ERROR_FAIL;
+               }
+       }
+
+       if (s_time_stats_enable) {
+               cmd_ctx->stats.min_blk_read_time = 1000000.0;
+               cmd_ctx->stats.min_blk_proc_time = 1000000.0;
+       }
+       if (duration_start(&cmd_ctx->idle_time) != 0) {
+               command_print(cmd, "Failed to start idle time measurement!");
+               esp32_apptrace_cmd_ctx_cleanup(cmd_ctx);
+               return ERROR_FAIL;
+       }
+
+       return ERROR_OK;
+}
+
+int esp32_apptrace_cmd_ctx_cleanup(struct esp32_apptrace_cmd_ctx *cmd_ctx)
+{
+       esp32_apptrace_blocks_pool_cleanup(cmd_ctx);
+       return ERROR_OK;
+}
+
+#define ESP32_APPTRACE_CMD_NUM_ARG_CHECK(_cmd_, _arg_, _start_, _end_)    \
+       do { \
+               if ((_arg_) == 0 && (_start_) == (_end_)) { \
+                       command_print(_cmd_, "Invalid '" # _arg_ "' arg!"); \
+                       return; \
+               } \
+       } while (0)
+
+void esp32_apptrace_cmd_args_parse(struct esp32_apptrace_cmd_ctx *cmd_ctx,
+       struct esp32_apptrace_cmd_data *cmd_data,
+       const char **argv,
+       int argc)
+{
+       char *end;
+
+       cmd_data->poll_period = strtoul(argv[0], &end, 10);
+       ESP32_APPTRACE_CMD_NUM_ARG_CHECK(cmd_ctx->cmd, cmd_data->poll_period, argv[0], end);
+       if (argc > 1) {
+               cmd_data->max_len = strtoul(argv[1], &end, 10);
+               ESP32_APPTRACE_CMD_NUM_ARG_CHECK(cmd_ctx->cmd, cmd_data->max_len, argv[1], end);
+               if (argc > 2) {
+                       int32_t tmo = strtol(argv[2], &end, 10);
+                       ESP32_APPTRACE_CMD_NUM_ARG_CHECK(cmd_ctx->cmd, tmo, argv[2], end);
+                       cmd_ctx->stop_tmo = 1.0 * tmo;
+                       if (argc > 3) {
+                               cmd_data->wait4halt = strtoul(argv[3], &end, 10);
+                               ESP32_APPTRACE_CMD_NUM_ARG_CHECK(cmd_ctx->cmd, cmd_data->wait4halt, argv[3], end);
+                               if (argc > 4) {
+                                       cmd_data->skip_len = strtoul(argv[4], &end, 10);
+                                       ESP32_APPTRACE_CMD_NUM_ARG_CHECK(cmd_ctx->cmd, cmd_data->skip_len, argv[4], end);
+                               }
+                       }
+               }
+       }
+}
+
+static int esp32_apptrace_core_id_get(struct target *target, uint8_t *hdr_buf)
+{
+       return ESP32_APPTRACE_USER_BLOCK_CORE(target_buffer_get_u16(target, hdr_buf + APPTRACE_BLOCK_SIZE_OFFSET));
+}
+
+static uint32_t esp32_apptrace_usr_block_len_get(struct target *target, uint8_t *hdr_buf, uint32_t *wr_len)
+{
+       *wr_len = ESP32_APPTRACE_USER_BLOCK_LEN(target_buffer_get_u16(target, hdr_buf + APPTRACE_WR_SIZE_OFFSET));
+       return ESP32_APPTRACE_USER_BLOCK_LEN(target_buffer_get_u16(target, hdr_buf + APPTRACE_BLOCK_SIZE_OFFSET));
+}
+
+static int esp32_apptrace_cmd_init(struct esp32_apptrace_cmd_ctx *cmd_ctx,
+       struct command_invocation *cmd,
+       int mode,
+       const char **argv,
+       int argc)
+{
+       struct esp32_apptrace_cmd_data *cmd_data;
+
+       if (argc < 1) {
+               command_print(cmd, "Not enough args! Need trace data destination!");
+               return ERROR_FAIL;
+       }
+
+       int res = esp32_apptrace_cmd_ctx_init(cmd_ctx, cmd, mode);
+       if (res != ERROR_OK)
+               return res;
+
+       cmd_data = calloc(1, sizeof(*cmd_data));
+       assert(cmd_data && "No memory for command data!");
+       cmd_ctx->cmd_priv = cmd_data;
+
+       /*outfile1 [poll_period [trace_size [stop_tmo [wait4halt [skip_size]]]]] */
+       res = esp32_apptrace_dest_init(&cmd_data->data_dest, argv, 1);
+       if (res != 1) { /* only one destination needs to be initialized */
+               command_print(cmd, "Wrong args! Needs a trace data destination!");
+               free(cmd_data);
+               goto on_error;
+       }
+       cmd_ctx->stop_tmo = -1.0;       /* infinite */
+       cmd_data->max_len = UINT32_MAX;
+       cmd_data->poll_period = 0 /*ms*/;
+       if (argc > 1)
+               /* parse remaining args */
+               esp32_apptrace_cmd_args_parse(cmd_ctx, cmd_data, &argv[1], argc - 1);
+
+       LOG_USER("App trace params: from %d cores, size %" PRId32 " bytes, stop_tmo %g s, poll period %" PRId32
+               " ms, wait_rst %d, skip %" PRId32 " bytes", cmd_ctx->cores_num,
+               cmd_data->max_len,
+               cmd_ctx->stop_tmo,
+               cmd_data->poll_period,
+               cmd_data->wait4halt,
+               cmd_data->skip_len);
+
+       cmd_ctx->trace_format.hdr_sz = ESP32_APPTRACE_USER_BLOCK_HDR_SZ;
+       cmd_ctx->trace_format.core_id_get = esp32_apptrace_core_id_get;
+       cmd_ctx->trace_format.usr_block_len_get = esp32_apptrace_usr_block_len_get;
+       return ERROR_OK;
+on_error:
+       command_print(cmd, "Not enough args! Need %d trace data destinations!", cmd_ctx->cores_num);
+       cmd_ctx->running = 0;
+       esp32_apptrace_cmd_ctx_cleanup(cmd_ctx);
+       return res;
+}
+
+static int esp32_apptrace_cmd_cleanup(struct esp32_apptrace_cmd_ctx *cmd_ctx)
+{
+       struct esp32_apptrace_cmd_data *cmd_data = cmd_ctx->cmd_priv;
+
+       esp32_apptrace_dest_cleanup(&cmd_data->data_dest, 1);
+       free(cmd_data);
+       cmd_ctx->cmd_priv = NULL;
+       esp32_apptrace_cmd_ctx_cleanup(cmd_ctx);
+       return ERROR_OK;
+}
+
+static void esp32_apptrace_print_stats(struct esp32_apptrace_cmd_ctx *ctx)
+{
+       struct esp32_apptrace_cmd_data *cmd_data = ctx->cmd_priv;
+       uint32_t trace_sz = 0;
+
+       if (cmd_data)
+               trace_sz = ctx->tot_len > cmd_data->skip_len ? ctx->tot_len - cmd_data->skip_len : 0;
+       LOG_USER("Tracing is %s. Size is %" PRId32 " of %" PRId32 " @ %f (%f) KiB/s",
+               !ctx->running ? "STOPPED" : "RUNNING",
+               trace_sz,
+               cmd_data ? cmd_data->max_len : 0,
+               duration_kbps(&ctx->read_time, ctx->tot_len),
+               duration_kbps(&ctx->read_time, ctx->raw_tot_len));
+       LOG_USER("Data: blocks incomplete %" PRId32 ", lost bytes: %" PRId32,
+               ctx->stats.incompl_blocks,
+               ctx->stats.lost_bytes);
+       if (s_time_stats_enable) {
+               LOG_USER("Block read time [%f..%f] ms",
+                       1000 * ctx->stats.min_blk_read_time,
+                       1000 * ctx->stats.max_blk_read_time);
+               LOG_USER("Block proc time [%f..%f] ms",
+                       1000 * ctx->stats.min_blk_proc_time,
+                       1000 * ctx->stats.max_blk_proc_time);
+       }
+}
+
+static int esp32_apptrace_wait4halt(struct esp32_apptrace_cmd_ctx *ctx, struct target *target)
+{
+       LOG_USER("Wait for halt...");
+       while (!openocd_is_shutdown_pending()) {
+               int res = target_poll(target);
+               if (res != ERROR_OK)
+                       return res;
+               if (target->state == TARGET_HALTED) {
+                       LOG_USER("%s: HALTED", target->cmd_name);
+                       break;
+               }
+               alive_sleep(500);
+       }
+       return ERROR_OK;
+}
+
+int esp32_apptrace_safe_halt_targets(struct esp32_apptrace_cmd_ctx *ctx,
+       struct esp32_apptrace_target_state *targets)
+{
+       int res = ERROR_OK;
+
+       memset(targets, 0, ctx->cores_num * sizeof(struct esp32_apptrace_target_state));
+       /* halt all CPUs */
+       LOG_DEBUG("Halt all targets!");
+       for (unsigned int k = 0; k < ctx->cores_num; k++) {
+               if (!target_was_examined(ctx->cpus[k]))
+                       continue;
+               if (ctx->cpus[k]->state == TARGET_HALTED)
+                       continue;
+               res = target_halt(ctx->cpus[k]);
+               if (res != ERROR_OK) {
+                       LOG_ERROR("Failed to halt target (%d)!", res);
+                       return res;
+               }
+               res = target_wait_state(ctx->cpus[k], TARGET_HALTED, ESP32_APPTRACE_TGT_STATE_TMO);
+               if (res != ERROR_OK) {
+                       LOG_ERROR("Failed to wait halt target %s / %d (%d)!",
+                               target_name(ctx->cpus[k]),
+                               ctx->cpus[k]->state,
+                               res);
+                       return res;
+               }
+       }
+       /* read current block statuses from CPUs */
+       LOG_DEBUG("Read current block statuses");
+       for (unsigned int k = 0; k < ctx->cores_num; k++) {
+               uint32_t stat;
+               res = ctx->hw->status_reg_read(ctx->cpus[k], &stat);
+               if (res != ERROR_OK) {
+                       LOG_ERROR("Failed to read trace status (%d)!", res);
+                       return res;
+               }
+               /* check if some CPU stopped inside tracing regs update critical section */
+               if (stat) {
+                       if (ctx->hw->leave_trace_crit_section_start) {
+                               res = ctx->hw->leave_trace_crit_section_start(ctx->cpus[k]);
+                               if (res != ERROR_OK)
+                                       return res;
+                       }
+                       uint32_t bp_addr = stat;
+                       res = breakpoint_add(ctx->cpus[k], bp_addr, 1, BKPT_HARD);
+                       if (res != ERROR_OK) {
+                               LOG_ERROR("Failed to set breakpoint (%d)!", res);
+                               return res;
+                       }
+                       while (stat) {
+                               /* allow this CPU to leave ERI write critical section */
+                               res = target_resume(ctx->cpus[k], 1, 0, 1, 0);
+                               if (res != ERROR_OK) {
+                                       LOG_ERROR("Failed to resume target (%d)!", res);
+                                       breakpoint_remove(ctx->cpus[k], bp_addr);
+                                       return res;
+                               }
+                               /* wait for CPU to be halted on BP */
+                               enum target_debug_reason debug_reason = DBG_REASON_UNDEFINED;
+                               while (debug_reason != DBG_REASON_BREAKPOINT) {
+                                       res = target_wait_state(ctx->cpus[k], TARGET_HALTED,
+                                               ESP32_APPTRACE_TGT_STATE_TMO);
+                                       if (res != ERROR_OK) {
+                                               LOG_ERROR("Failed to wait halt on bp (%d)!", res);
+                                               breakpoint_remove(ctx->cpus[k], bp_addr);
+                                               return res;
+                                       }
+                                       debug_reason = ctx->cpus[k]->debug_reason;
+                               }
+                               res = ctx->hw->status_reg_read(ctx->cpus[k], &stat);
+                               if (res != ERROR_OK) {
+                                       LOG_ERROR("Failed to read trace status (%d)!", res);
+                                       breakpoint_remove(ctx->cpus[k], bp_addr);
+                                       return res;
+                               }
+                       }
+                       breakpoint_remove(ctx->cpus[k], bp_addr);
+                       if (ctx->hw->leave_trace_crit_section_stop) {
+                               res = ctx->hw->leave_trace_crit_section_stop(ctx->cpus[k]);
+                               if (res != ERROR_OK)
+                                       return res;
+                       }
+               }
+               res = ctx->hw->data_len_read(ctx->cpus[k], &targets[k].block_id, &targets[k].data_len);
+               if (res != ERROR_OK) {
+                       LOG_ERROR("Failed to read trace status (%d)!", res);
+                       return res;
+               }
+       }
+
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_connect_targets(struct esp32_apptrace_cmd_ctx *ctx,
+       bool conn,
+       bool resume_target)
+{
+       struct esp32_apptrace_target_state target_to_connect[ESP32_APPTRACE_MAX_CORES_NUM];
+
+       if (conn)
+               LOG_USER("Connect targets...");
+       else
+               LOG_USER("Disconnect targets...");
+
+       int res = esp32_apptrace_safe_halt_targets(ctx, target_to_connect);
+       if (res != ERROR_OK) {
+               command_print(ctx->cmd, "Failed to halt targets (%d)!", res);
+               return res;
+       }
+       if (ctx->cores_num > 1) {
+               /* set block ids to the highest value */
+               uint32_t max_id = 0;
+               for (unsigned int k = 0; k < ctx->cores_num; k++) {
+                       if (target_to_connect[k].block_id > max_id)
+                               max_id = target_to_connect[k].block_id;
+               }
+               for (unsigned int k = 0; k < ctx->cores_num; k++)
+                       target_to_connect[k].block_id = max_id;
+       }
+       for (unsigned int k = 0; k < ctx->cores_num; k++) {
+               /* update host connected status */
+               res = ctx->hw->ctrl_reg_write(ctx->cpus[k],
+                       target_to_connect[k].block_id,
+                       0 /*ack target data*/,
+                       conn,
+                       false /*no host data*/);
+               if (res != ERROR_OK) {
+                       command_print(ctx->cmd, "Failed to read trace status (%d)!", res);
+                       return res;
+               }
+       }
+       if (resume_target) {
+               LOG_DEBUG("Resume targets");
+               bool smp_resumed = false;
+               for (unsigned int k = 0; k < ctx->cores_num; k++) {
+                       if (smp_resumed && ctx->cpus[k]->smp) {
+                               /* in SMP mode we need to call target_resume for one core only */
+                               continue;
+                       }
+                       res = target_resume(ctx->cpus[k], 1, 0, 1, 0);
+                       if (res != ERROR_OK) {
+                               command_print(ctx->cmd, "Failed to resume target (%d)!", res);
+                               return res;
+                       }
+                       if (ctx->cpus[k]->smp)
+                               smp_resumed = true;
+               }
+       }
+       if (conn)
+               LOG_INFO("Targets connected.");
+       else
+               LOG_INFO("Targets disconnected.");
+       return ERROR_OK;
+}
+
+int esp_apptrace_usr_block_write(const struct esp32_apptrace_hw *hw, struct target *target,
+       uint32_t block_id,
+       const uint8_t *data,
+       uint32_t size)
+{
+       struct esp_apptrace_host2target_hdr hdr = { .block_sz = size };
+       uint32_t buf_sz[2] = { sizeof(hdr), size };
+       const uint8_t *bufs[2] = { (const uint8_t *)&hdr, data };
+
+       if (size > hw->usr_block_max_size_get(target)) {
+               LOG_ERROR("Too large user block %" PRId32, size);
+               return ERROR_FAIL;
+       }
+
+       return hw->buffs_write(target,
+               ARRAY_SIZE(buf_sz),
+               buf_sz,
+               bufs,
+               block_id,
+               true /*ack target data*/,
+               true /*host data*/);
+}
+
+static uint32_t esp32_apptrace_usr_block_check(struct esp32_apptrace_cmd_ctx *ctx, uint8_t *hdr_buf)
+{
+       uint32_t wr_len = 0;
+       uint32_t usr_len = ctx->trace_format.usr_block_len_get(ctx->target, hdr_buf, &wr_len);
+       if (usr_len != wr_len) {
+               LOG_ERROR("Incomplete block sz %" PRId32 ", wr %" PRId32, usr_len, wr_len);
+               ctx->stats.incompl_blocks++;
+               ctx->stats.lost_bytes += usr_len - wr_len;
+       }
+       return usr_len;
+}
+
+int esp32_apptrace_get_data_info(struct esp32_apptrace_cmd_ctx *ctx,
+       struct esp32_apptrace_target_state *target_state,
+       uint32_t *fired_target_num)
+{
+       if (fired_target_num)
+               *fired_target_num = UINT32_MAX;
+
+       for (unsigned int i = 0; i < ctx->cores_num; i++) {
+               int res = ctx->hw->data_len_read(ctx->cpus[i], &target_state[i].block_id, &target_state[i].data_len);
+               if (res != ERROR_OK) {
+                       LOG_ERROR("Failed to read data len on (%s)!", target_name(ctx->cpus[i]));
+                       return res;
+               }
+               if (target_state[i].data_len) {
+                       LOG_TARGET_DEBUG(ctx->cpus[i], "Block %" PRId32 ", len %" PRId32 " bytes on fired",
+                               target_state[i].block_id, target_state[i].data_len);
+                       if (fired_target_num)
+                               *fired_target_num = i;
+                       break;
+               }
+       }
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_process_data(struct esp32_apptrace_cmd_ctx *ctx,
+       unsigned int core_id,
+       uint8_t *data,
+       uint32_t data_len)
+{
+       struct esp32_apptrace_cmd_data *cmd_data = ctx->cmd_priv;
+
+       LOG_DEBUG("Got block %" PRId32 " bytes [%x %x...%x %x]", data_len, data[12], data[13],
+               data[data_len - 2], data[data_len - 1]);
+       if (ctx->tot_len + data_len > cmd_data->skip_len) {
+               uint32_t wr_idx = 0, wr_chunk_len = data_len;
+               if (ctx->tot_len < cmd_data->skip_len) {
+                       wr_chunk_len = (ctx->tot_len + wr_chunk_len) - cmd_data->skip_len;
+                       wr_idx = cmd_data->skip_len - ctx->tot_len;
+               }
+               if (ctx->tot_len + wr_chunk_len > cmd_data->max_len)
+                       wr_chunk_len -= (ctx->tot_len + wr_chunk_len - cmd_data->skip_len) - cmd_data->max_len;
+               if (wr_chunk_len > 0) {
+                       int res = cmd_data->data_dest.write(cmd_data->data_dest.priv, data + wr_idx, wr_chunk_len);
+                       if (res != ERROR_OK) {
+                               LOG_ERROR("Failed to write %" PRId32 " bytes to dest 0!", data_len);
+                               return res;
+                       }
+               }
+               ctx->tot_len += wr_chunk_len;
+       } else {
+               ctx->tot_len += data_len;
+       }
+
+       if (cmd_data->data_dest.log_progress)
+               LOG_USER("%" PRId32 " ", ctx->tot_len);
+       /* check for stop condition */
+       if (ctx->tot_len > cmd_data->skip_len && (ctx->tot_len - cmd_data->skip_len >= cmd_data->max_len)) {
+               ctx->running = 0;
+               if (duration_measure(&ctx->read_time) != 0) {
+                       LOG_ERROR("Failed to stop trace read time measure!");
+                       return ERROR_FAIL;
+               }
+       }
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_handle_trace_block(struct esp32_apptrace_cmd_ctx *ctx,
+       struct esp32_apptrace_block *block)
+{
+       uint32_t processed = 0;
+       uint32_t hdr_sz = ctx->trace_format.hdr_sz;
+
+       LOG_DEBUG("Got block %" PRId32 " bytes", block->data_len);
+       /* process user blocks one by one */
+       while (processed < block->data_len) {
+               LOG_DEBUG("Process usr block %" PRId32 "/%" PRId32, processed, block->data_len);
+               /* process user block */
+               uint32_t usr_len = esp32_apptrace_usr_block_check(ctx, block->data + processed);
+               int core_id = ctx->trace_format.core_id_get(ctx->target, block->data + processed);
+               /* process user data */
+               int res = ctx->process_data(ctx, core_id, block->data + processed + hdr_sz, usr_len);
+               if (res != ERROR_OK) {
+                       LOG_ERROR("Failed to process %" PRId32 " bytes!", usr_len);
+                       return res;
+               }
+               processed += usr_len + hdr_sz;
+       }
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_data_processor(void *priv)
+{
+       struct esp32_apptrace_cmd_ctx *ctx = (struct esp32_apptrace_cmd_ctx *)priv;
+
+       if (!ctx->running)
+               return ERROR_OK;
+
+       struct esp32_apptrace_block *block = esp32_apptrace_ready_block_get(ctx);
+       if (!block)
+               return ERROR_OK;
+
+       int res = esp32_apptrace_handle_trace_block(ctx, block);
+       if (res != ERROR_OK) {
+               ctx->running = 0;
+               LOG_ERROR("Failed to process trace block %" PRId32 " bytes!", block->data_len);
+               return res;
+       }
+       res = esp32_apptrace_block_free(ctx, block);
+       if (res != ERROR_OK) {
+               ctx->running = 0;
+               LOG_ERROR("Failed to free ready block!");
+               return res;
+       }
+
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_check_connection(struct esp32_apptrace_cmd_ctx *ctx)
+{
+       if (!ctx)
+               return ERROR_FAIL;
+
+       unsigned int busy_target_num = 0;
+
+       for (unsigned int i = 0; i < ctx->cores_num; i++) {
+               bool conn = true;
+               int res = ctx->hw->ctrl_reg_read(ctx->cpus[i], NULL, NULL, &conn);
+               if (res != ERROR_OK) {
+                       LOG_ERROR("Failed to read apptrace control reg for cpu(%d) res(%d)!", i, res);
+                       return res;
+               }
+               if (!conn) {
+                       uint32_t stat = 0;
+                       LOG_TARGET_WARNING(ctx->cpus[i], "apptrace connection is lost. Re-connect.");
+                       res = ctx->hw->status_reg_read(ctx->cpus[i], &stat);
+                       if (res != ERROR_OK) {
+                               LOG_ERROR("Failed to read trace status (%d)!", res);
+                               return res;
+                       }
+                       if (stat) {
+                               LOG_TARGET_WARNING(ctx->cpus[i], "in critical state. Retry in next poll");
+                               if (++busy_target_num == ctx->cores_num) {
+                                       LOG_WARNING("No available core");
+                                       return ERROR_WAIT;
+                               }
+                               continue;
+                       }
+                       res = ctx->hw->ctrl_reg_write(ctx->cpus[i],
+                               0,
+                               0,
+                               true /*host connected*/,
+                               false /*no host data*/);
+                       if (res != ERROR_OK) {
+                               LOG_ERROR("Failed to write apptrace control reg for cpu(%d) res(%d)!", i, res);
+                               return res;
+                       }
+                       if (ctx->stop_tmo != -1.0) {
+                               /* re-start idle time measurement */
+                               if (duration_start(&ctx->idle_time) != 0) {
+                                       LOG_ERROR("Failed to re-start idle time measure!");
+                                       return ERROR_FAIL;
+                               }
+                       }
+               }
+       }
+
+       return ERROR_OK;
+}
+
+static int esp32_apptrace_poll(void *priv)
+{
+       struct esp32_apptrace_cmd_ctx *ctx = (struct esp32_apptrace_cmd_ctx *)priv;
+       int res;
+       uint32_t fired_target_num = 0;
+       struct esp32_apptrace_target_state target_state[ESP32_APPTRACE_MAX_CORES_NUM];
+       struct duration blk_proc_time;
+
+       if (!ctx->running) {
+               if (ctx->auto_clean)
+                       ctx->auto_clean(ctx);
+               return ERROR_FAIL;
+       }
+
+       /*  Check for connection is alive.For some reason target and therefore host_connected flag
+        *  might have been reset */
+       res = esp32_apptrace_check_connection(ctx);
+       if (res != ERROR_OK) {
+               if (res != ERROR_WAIT)
+                       ctx->running = 0;
+               return res;
+       }
+
+       /* check for data from target */
+       res = esp32_apptrace_get_data_info(ctx, target_state, &fired_target_num);
+       if (res != ERROR_OK) {
+               ctx->running = 0;
+               LOG_ERROR("Failed to read data len!");
+               return res;
+       }
+       /* LOG_DEBUG("Block %d (%d bytes) on target (%s)!", target_state[0].block_id,
+        * target_state[0].data_len, target_name(ctx->cpus[0])); */
+       if (fired_target_num == UINT32_MAX) {
+               /* no data has been received, but block could be switched due to the data transferred
+                * from host to target */
+               if (ctx->cores_num > 1) {
+                       uint32_t max_block_id = 0, min_block_id = ctx->hw->max_block_id;
+                       /* find maximum block ID and set the same ID in control reg for both cores
+                        * */
+                       for (unsigned int i = 0; i < ctx->cores_num; i++) {
+                               if (max_block_id < target_state[i].block_id)
+                                       max_block_id = target_state[i].block_id;
+                               if (min_block_id > target_state[i].block_id)
+                                       min_block_id = target_state[i].block_id;
+                       }
+                       /* handle block ID overflow */
+                       if (max_block_id == ctx->hw->max_block_id && min_block_id == 0)
+                               max_block_id = 0;
+                       for (unsigned int i = 0; i < ctx->cores_num; i++) {
+                               if (max_block_id != target_state[i].block_id) {
+                                       LOG_TARGET_DEBUG(ctx->cpus[i], "Ack empty block %" PRId32 "!", max_block_id);
+                                       res = ctx->hw->ctrl_reg_write(ctx->cpus[i],
+                                               max_block_id,
+                                               0 /*all read*/,
+                                               true /*host connected*/,
+                                               false /*no host data*/);
+                                       if (res != ERROR_OK) {
+                                               ctx->running = 0;
+                                               LOG_TARGET_ERROR(ctx->cpus[i], "Failed to ack empty data block!");
+                                               return res;
+                                       }
+                               }
+                       }
+                       ctx->last_blk_id = max_block_id;
+               }
+               if (ctx->stop_tmo != -1.0) {
+                       if (duration_measure(&ctx->idle_time) != 0) {
+                               ctx->running = 0;
+                               LOG_ERROR("Failed to measure idle time!");
+                               return ERROR_FAIL;
+                       }
+                       if (duration_elapsed(&ctx->idle_time) >= ctx->stop_tmo) {
+                               ctx->running = 0;
+                               LOG_ERROR("Data timeout!");
+                               return ERROR_FAIL;
+                       }
+               }
+               return ERROR_OK;/* no data */
+       }
+       /* sanity check */
+       if (target_state[fired_target_num].data_len > ctx->max_trace_block_sz) {
+               ctx->running = 0;
+               LOG_ERROR("Too large block size %" PRId32 "!", target_state[fired_target_num].data_len);
+               return ERROR_FAIL;
+       }
+       if (ctx->tot_len == 0) {
+               if (duration_start(&ctx->read_time) != 0) {
+                       ctx->running = 0;
+                       LOG_ERROR("Failed to start trace read time measurement!");
+                       return ERROR_FAIL;
+               }
+       }
+       struct esp32_apptrace_block *block = esp32_apptrace_free_block_get(ctx);
+       if (!block) {
+               ctx->running = 0;
+               LOG_TARGET_ERROR(ctx->cpus[fired_target_num], "Failed to get free block for data!");
+               return ERROR_FAIL;
+       }
+       if (s_time_stats_enable) {
+               /* read block */
+               if (duration_start(&blk_proc_time) != 0) {
+                       ctx->running = 0;
+                       LOG_ERROR("Failed to start block read time measurement!");
+                       return ERROR_FAIL;
+               }
+       }
+       res =
+               ctx->hw->data_read(ctx->cpus[fired_target_num],
+               target_state[fired_target_num].data_len,
+               block->data,
+               target_state[fired_target_num].block_id,
+               /* do not ack target data in sync mode,
+                  esp32_apptrace_handle_trace_block() can write response data and will do ack thereafter */
+               ctx->mode != ESP_APPTRACE_CMD_MODE_SYNC);
+       if (res != ERROR_OK) {
+               ctx->running = 0;
+               LOG_TARGET_ERROR(ctx->cpus[fired_target_num], "Failed to read data!");
+               return res;
+       }
+       ctx->last_blk_id = target_state[fired_target_num].block_id;
+       block->data_len = target_state[fired_target_num].data_len;
+       ctx->raw_tot_len += block->data_len;
+       if (s_time_stats_enable) {
+               if (duration_measure(&blk_proc_time) != 0) {
+                       ctx->running = 0;
+                       LOG_ERROR("Failed to measure block read time!");
+                       return ERROR_FAIL;
+               }
+               /* update stats */
+               float brt = duration_elapsed(&blk_proc_time);
+               if (brt > ctx->stats.max_blk_read_time)
+                       ctx->stats.max_blk_read_time = brt;
+               if (brt < ctx->stats.min_blk_read_time)
+                       ctx->stats.min_blk_read_time = brt;
+
+               if (duration_start(&blk_proc_time) != 0) {
+                       ctx->running = 0;
+                       LOG_ERROR("Failed to start block proc time measurement!");
+                       return ERROR_FAIL;
+               }
+       }
+       /* in sync mode do not ack target data on other cores, esp32_apptrace_handle_trace_block() can write response
+        * data and will do ack thereafter */
+       if (ctx->mode != ESP_APPTRACE_CMD_MODE_SYNC) {
+               for (unsigned int i = 0; i < ctx->cores_num; i++) {
+                       if (i == fired_target_num)
+                               continue;
+                       res = ctx->hw->ctrl_reg_write(ctx->cpus[i],
+                               ctx->last_blk_id,
+                               0 /*all read*/,
+                               true /*host connected*/,
+                               false /*no host data*/);
+                       if (res != ERROR_OK) {
+                               ctx->running = 0;
+                               LOG_TARGET_ERROR(ctx->cpus[i], "Failed to ack data!");
+                               return res;
+                       }
+                       LOG_TARGET_DEBUG(ctx->cpus[i], "Ack block %" PRId32, ctx->last_blk_id);
+               }
+               res = esp32_apptrace_ready_block_put(ctx, block);
+               if (res != ERROR_OK) {
+                       ctx->running = 0;
+                       LOG_TARGET_ERROR(ctx->cpus[fired_target_num], "Failed to put ready block of data!");
+                       return res;
+               }
+       } else {
+               res = esp32_apptrace_handle_trace_block(ctx, block);
+               if (res != ERROR_OK) {
+                       ctx->running = 0;
+                       LOG_ERROR("Failed to process trace block %" PRId32 " bytes!", block->data_len);
+                       return res;
+               }
+               res = esp32_apptrace_block_free(ctx, block);
+               if (res != ERROR_OK) {
+                       ctx->running = 0;
+                       LOG_ERROR("Failed to free ready block!");
+                       return res;
+               }
+       }
+       if (ctx->stop_tmo != -1.0) {
+               /* start idle time measurement */
+               if (duration_start(&ctx->idle_time) != 0) {
+                       ctx->running = 0;
+                       LOG_ERROR("Failed to start idle time measure!");
+                       return ERROR_FAIL;
+               }
+       }
+       if (s_time_stats_enable) {
+               if (duration_measure(&blk_proc_time) != 0) {
+                       ctx->running = 0;
+                       LOG_ERROR("Failed to stop block proc time measure!");
+                       return ERROR_FAIL;
+               }
+               /* update stats */
+               float bt = duration_elapsed(&blk_proc_time);
+               if (bt > ctx->stats.max_blk_proc_time)
+                       ctx->stats.max_blk_proc_time = bt;
+               if (bt < ctx->stats.min_blk_proc_time)
+                       ctx->stats.min_blk_proc_time = bt;
+       }
+       return ERROR_OK;
+}
+
+static void esp32_apptrace_cmd_stop(struct esp32_apptrace_cmd_ctx *ctx)
+{
+       if (duration_measure(&ctx->read_time) != 0)
+               LOG_ERROR("Failed to stop trace read time measurement!");
+       int res = target_unregister_timer_callback(esp32_apptrace_poll, ctx);
+       if (res != ERROR_OK)
+               LOG_ERROR("Failed to unregister target timer handler (%d)!", res);
+
+       /* data processor is alive, so wait for all received blocks to be processed */
+       res = esp32_apptrace_wait_tracing_finished(ctx);
+       if (res != ERROR_OK)
+               LOG_ERROR("Failed to wait for pended blocks (%d)!", res);
+       res = esp32_apptrace_connect_targets(ctx, false, ctx->target_state == TARGET_RUNNING);
+       if (res != ERROR_OK)
+               LOG_ERROR("Failed to disconnect targets (%d)!", res);
+       esp32_apptrace_print_stats(ctx);
+       res = esp32_apptrace_cmd_cleanup(ctx);
+       if (res != ERROR_OK)
+               LOG_ERROR("Failed to cleanup cmd ctx (%d)!", res);
+}
+
+int esp32_cmd_apptrace_generic(struct command_invocation *cmd, int mode, const char **argv, int argc)
+{
+       static struct esp32_apptrace_cmd_ctx s_at_cmd_ctx;
+       struct esp32_apptrace_cmd_data *cmd_data;
+       int res = ERROR_FAIL;
+       enum target_state old_state;
+       struct target *target = get_current_target(CMD_CTX);
+
+       if (argc < 1)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       /* command can be invoked on unexamined core, if so find examined one */
+       if (target->smp && !target_was_examined(target)) {
+               struct target_list *head;
+               struct target *curr;
+               LOG_WARNING("Current target '%s' was not examined!", target_name(target));
+               foreach_smp_target(head, target->smp_targets) {
+                       curr = head->target;
+                       if (target_was_examined(curr)) {
+                               target = curr;
+                               LOG_WARNING("Run command on target '%s'", target_name(target));
+                               break;
+                       }
+               }
+       }
+       old_state = target->state;
+
+       if (strcmp(argv[0], "start") == 0) {
+               res = esp32_apptrace_cmd_init(&s_at_cmd_ctx,
+                       cmd,
+                       mode,
+                       &argv[1],
+                       argc - 1);
+               if (res != ERROR_OK) {
+                       command_print(cmd, "Failed to init cmd ctx (%d)!", res);
+                       return res;
+               }
+               cmd_data = s_at_cmd_ctx.cmd_priv;
+               s_at_cmd_ctx.process_data = esp32_apptrace_process_data;
+               s_at_cmd_ctx.auto_clean = esp32_apptrace_cmd_stop;
+               if (cmd_data->wait4halt) {
+                       res = esp32_apptrace_wait4halt(&s_at_cmd_ctx, target);
+                       if (res != ERROR_OK) {
+                               command_print(cmd, "Failed to wait for halt target (%d)!", res);
+                               goto _on_start_error;
+                       }
+               }
+               res = esp32_apptrace_connect_targets(&s_at_cmd_ctx, true, old_state == TARGET_RUNNING);
+               if (res != ERROR_OK) {
+                       command_print(cmd, "Failed to connect to targets (%d)!", res);
+                       goto _on_start_error;
+               }
+               res = target_register_timer_callback(esp32_apptrace_poll,
+                       cmd_data->poll_period,
+                       TARGET_TIMER_TYPE_PERIODIC,
+                       &s_at_cmd_ctx);
+               if (res != ERROR_OK) {
+                       command_print(cmd, "Failed to register target timer handler (%d)!", res);
+                       goto _on_start_error;
+               }
+       } else if (strcmp(argv[0], "stop") == 0) {
+               if (!s_at_cmd_ctx.running) {
+                       command_print(cmd, "Tracing is not running!");
+                       return ERROR_FAIL;
+               }
+               esp32_apptrace_cmd_stop(&s_at_cmd_ctx);
+               return ERROR_OK;
+       } else if (strcmp(argv[0], "status") == 0) {
+               if (s_at_cmd_ctx.running && duration_measure(&s_at_cmd_ctx.read_time) != 0)
+                       LOG_ERROR("Failed to measure trace read time!");
+               esp32_apptrace_print_stats(&s_at_cmd_ctx);
+               return ERROR_OK;
+       } else if (strcmp(argv[0], "dump") == 0) {
+               /* [dump outfile] - post-mortem dump without connection to targets */
+               res = esp32_apptrace_cmd_init(&s_at_cmd_ctx,
+                       cmd,
+                       mode,
+                       &argv[1],
+                       argc - 1);
+               if (res != ERROR_OK) {
+                       command_print(cmd, "Failed to init cmd ctx (%d)!", res);
+                       return res;
+               }
+               s_at_cmd_ctx.stop_tmo = 0.01;   /* use small stop tmo */
+               s_at_cmd_ctx.process_data = esp32_apptrace_process_data;
+               /* check for exit signal and command completion */
+               while (!openocd_is_shutdown_pending() && s_at_cmd_ctx.running) {
+                       res = esp32_apptrace_poll(&s_at_cmd_ctx);
+                       if (res != ERROR_OK) {
+                               LOG_ERROR("Failed to poll target for trace data (%d)!", res);
+                               break;
+                       }
+                       /* let registered timer callbacks to run */
+                       target_call_timer_callbacks();
+               }
+               if (s_at_cmd_ctx.running) {
+                       /* data processor is alive, so wait for all received blocks to be processed */
+                       res = esp32_apptrace_wait_tracing_finished(&s_at_cmd_ctx);
+                       if (res != ERROR_OK)
+                               LOG_ERROR("Failed to wait for pended blocks (%d)!", res);
+               }
+               esp32_apptrace_print_stats(&s_at_cmd_ctx);
+               res = esp32_apptrace_cmd_cleanup(&s_at_cmd_ctx);
+               if (res != ERROR_OK)
+                       command_print(cmd, "Failed to cleanup cmd ctx (%d)!", res);
+       } else {
+               command_print(cmd, "Invalid action '%s'!", argv[0]);
+       }
+
+       return res;
+
+_on_start_error:
+       s_at_cmd_ctx.running = 0;
+       esp32_apptrace_cmd_cleanup(&s_at_cmd_ctx);
+       return res;
+}
+
+COMMAND_HANDLER(esp32_cmd_apptrace)
+{
+       return esp32_cmd_apptrace_generic(CMD, ESP_APPTRACE_CMD_MODE_GEN, CMD_ARGV, CMD_ARGC);
+}
+
+const struct command_registration esp32_apptrace_command_handlers[] = {
+       {
+               .name = "apptrace",
+               .handler = esp32_cmd_apptrace,
+               .mode = COMMAND_EXEC,
+               .help =
+                       "App Tracing: application level trace control. Starts, stops or queries tracing process status.",
+               .usage =
+                       "[start <destination> [poll_period [trace_size [stop_tmo [wait4halt [skip_size]]]]] | [stop] | [status] | [dump <destination>]",
+       },
+       COMMAND_REGISTRATION_DONE
+};
diff --git a/src/target/espressif/esp32_apptrace.h b/src/target/espressif/esp32_apptrace.h
new file mode 100644 (file)
index 0000000..3873342
--- /dev/null
@@ -0,0 +1,126 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/***************************************************************************
+ *   ESP32 application trace module                                        *
+ *   Copyright (C) 2017-2019 Espressif Systems Ltd.                        *
+ ***************************************************************************/
+
+#ifndef OPENOCD_TARGET_ESP32_APPTRACE_H
+#define OPENOCD_TARGET_ESP32_APPTRACE_H
+
+#include <helper/command.h>
+#include <helper/time_support.h>
+#include <target/target.h>
+
+#define ESP32_APPTRACE_MAX_CORES_NUM 2
+
+struct esp32_apptrace_hw {
+       uint32_t max_block_id;
+       uint32_t (*max_block_size_get)(struct target *target);
+       int (*status_reg_read)(struct target *target, uint32_t *stat);
+       int (*ctrl_reg_write)(struct target *target,
+               uint32_t block_id,
+               uint32_t len,
+               bool conn,
+               bool data);
+       int (*ctrl_reg_read)(struct target *target,
+               uint32_t *block_id,
+               uint32_t *len,
+               bool *conn);
+       int (*data_len_read)(struct target *target,
+               uint32_t *block_id,
+               uint32_t *len);
+       int (*data_read)(struct target *target,
+               uint32_t size,
+               uint8_t *buffer,
+               uint32_t block_id,
+               bool ack);
+       uint32_t (*usr_block_max_size_get)(struct target *target);
+       int (*buffs_write)(struct target *target,
+               uint32_t bufs_num,
+               uint32_t buf_sz[],
+               const uint8_t *bufs[],
+               uint32_t block_id,
+               bool ack,
+               bool data);
+       int (*leave_trace_crit_section_start)(struct target *target);
+       int (*leave_trace_crit_section_stop)(struct target *target);
+};
+
+struct esp_apptrace_host2target_hdr {
+       uint16_t block_sz;
+};
+
+struct esp32_apptrace_dest {
+       void *priv;
+       int (*write)(void *priv, uint8_t *data, int size);
+       int (*clean)(void *priv);
+       bool log_progress;
+};
+
+struct esp32_apptrace_format {
+       uint32_t hdr_sz;
+       int (*core_id_get)(struct target *target, uint8_t *hdr_buf);
+       uint32_t (*usr_block_len_get)(struct target *target, uint8_t *hdr_buf, uint32_t *wr_len);
+};
+
+struct esp32_apptrace_cmd_stats {
+       uint32_t incompl_blocks;
+       uint32_t lost_bytes;
+       float min_blk_read_time;
+       float max_blk_read_time;
+       float min_blk_proc_time;
+       float max_blk_proc_time;
+};
+
+struct esp32_apptrace_cmd_ctx {
+       volatile int running;
+       int mode;
+       /* TODO: use subtargets from target arch info */
+       struct target *cpus[ESP32_APPTRACE_MAX_CORES_NUM];
+       /* TODO: use cores num from target */
+       unsigned int cores_num;
+       const struct esp32_apptrace_hw *hw;
+       enum target_state target_state;
+       uint32_t last_blk_id;
+       struct list_head free_trace_blocks;
+       struct list_head ready_trace_blocks;
+       uint32_t max_trace_block_sz;
+       struct esp32_apptrace_format trace_format;
+       int (*process_data)(struct esp32_apptrace_cmd_ctx *ctx, unsigned int core_id, uint8_t *data, uint32_t data_len);
+       void (*auto_clean)(struct esp32_apptrace_cmd_ctx *ctx);
+       uint32_t tot_len;
+       uint32_t raw_tot_len;
+       float stop_tmo;
+       struct esp32_apptrace_cmd_stats stats;
+       struct duration read_time;
+       struct duration idle_time;
+       void *cmd_priv;
+       struct target *target;
+       struct command_invocation *cmd;
+};
+
+struct esp32_apptrace_cmd_data {
+       struct esp32_apptrace_dest data_dest;
+       uint32_t poll_period;
+       uint32_t max_len;
+       uint32_t skip_len;
+       bool wait4halt;
+};
+
+int esp32_apptrace_cmd_ctx_init(struct esp32_apptrace_cmd_ctx *cmd_ctx, struct command_invocation *cmd, int mode);
+int esp32_apptrace_cmd_ctx_cleanup(struct esp32_apptrace_cmd_ctx *cmd_ctx);
+void esp32_apptrace_cmd_args_parse(struct esp32_apptrace_cmd_ctx *cmd_ctx,
+       struct esp32_apptrace_cmd_data *cmd_data,
+       const char **argv,
+       int argc);
+int esp32_apptrace_dest_init(struct esp32_apptrace_dest dest[], const char *dest_paths[], unsigned int max_dests);
+int esp32_apptrace_dest_cleanup(struct esp32_apptrace_dest dest[], unsigned int max_dests);
+int esp_apptrace_usr_block_write(const struct esp32_apptrace_hw *hw, struct target *target,
+       uint32_t block_id,
+       const uint8_t *data,
+       uint32_t size);
+
+extern const struct command_registration esp32_apptrace_command_handlers[];
+
+#endif /* OPENOCD_TARGET_ESP32_APPTRACE_H */
index 64fa690570233a57fd9e2ca5269f3628314f4e73..a11d05f0f22566866fe6d91adbfaac72c4764be1 100644 (file)
@@ -496,6 +496,11 @@ static const struct command_registration esp32s2_command_handlers[] = {
        {
                .chain = xtensa_command_handlers,
        },
+       {
+               .name = "esp",
+               .usage = "",
+               .chain = esp32_apptrace_command_handlers,
+       },
        {
                .name = "arm",
                .mode = COMMAND_ANY,
index 62b22b1355548812debdb0f9b94bd79430aa8c55..485567836bae78a3b7c0e94ed6728a58e81484e8 100644 (file)
@@ -364,6 +364,11 @@ static const struct command_registration esp32s3_command_handlers[] = {
                .usage = "",
                .chain = esp_xtensa_smp_command_handlers,
        },
+       {
+               .name = "esp",
+               .usage = "",
+               .chain = esp32_apptrace_command_handlers,
+       },
        {
                .name = "esp32",
                .usage = "",
index fcc340c8289110cccdaa345025b28149ed40fb87..44764aeca7df9b04a3dc7c06327c65f0ec74c05d 100644 (file)
@@ -12,6 +12,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 #include <target/smp.h>
+#include "esp_xtensa_apptrace.h"
 #include <target/register.h>
 #include "esp_xtensa.h"
 #include "esp_semihosting.h"
@@ -25,6 +26,7 @@ int esp_xtensa_init_arch_info(struct target *target,
        if (ret != ERROR_OK)
                return ret;
        esp_xtensa->semihost.ops = (struct esp_semihost_ops *)semihost_ops;
+       esp_xtensa->apptrace.hw = &esp_xtensa_apptrace_hw;
        return ERROR_OK;
 }
 
index 1ad6c377f038f635acffe513f31534738051c416..8807f0c3259929d1b6e91b68ff35b94c2e301ea0 100644 (file)
 #include <target/xtensa/xtensa.h>
 #include "esp_xtensa.h"
 #include "esp_semihosting.h"
+#include "esp_xtensa_apptrace.h"
 
 struct esp_xtensa_common {
        struct xtensa xtensa;   /* must be the first element */
        struct esp_semihost_data semihost;
+       struct esp_xtensa_apptrace_info apptrace;
 };
 
 static inline struct esp_xtensa_common *target_to_esp_xtensa(struct target *target)
diff --git a/src/target/espressif/esp_xtensa_apptrace.c b/src/target/espressif/esp_xtensa_apptrace.c
new file mode 100644 (file)
index 0000000..dfb846d
--- /dev/null
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/***************************************************************************
+ *   Xtensa application tracing module for OpenOCD                         *
+ *   Copyright (C) 2017 Espressif Systems Ltd.                             *
+ ***************************************************************************/
+
+/*
+    How it works?
+    https://github.com/espressif/esp-idf/blob/master/components/app_trace/port/xtensa/port.c#L8
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <helper/align.h>
+#include <target/xtensa/xtensa.h>
+#include <target/xtensa/xtensa_debug_module.h>
+#include "esp_xtensa_apptrace.h"
+
+/* TRAX is disabled, so we use its registers for our own purposes
+ * | 31..XXXXXX..24 | 23 .(host_connect). 23 | 22 .(host_data). 22| 21..(block_id)..15 | 14..(block_len)..0 |
+ */
+#define XTENSA_APPTRACE_CTRL_REG                XDMREG_DELAYCNT
+#define XTENSA_APPTRACE_BLOCK_ID_MSK            0x7FUL
+#define XTENSA_APPTRACE_BLOCK_ID_MAX            XTENSA_APPTRACE_BLOCK_ID_MSK
+/* if non-zero then apptrace code entered the critical section and the value is an address of the
+ * critical section's exit point */
+#define XTENSA_APPTRACE_STAT_REG                XDMREG_TRIGGERPC
+
+#define XTENSA_APPTRACE_BLOCK_LEN_MSK           0x7FFFUL
+#define XTENSA_APPTRACE_BLOCK_LEN(_l_)          ((_l_) & XTENSA_APPTRACE_BLOCK_LEN_MSK)
+#define XTENSA_APPTRACE_BLOCK_LEN_GET(_v_)      ((_v_) & XTENSA_APPTRACE_BLOCK_LEN_MSK)
+#define XTENSA_APPTRACE_BLOCK_ID(_id_)          (((_id_) & XTENSA_APPTRACE_BLOCK_ID_MSK) << 15)
+#define XTENSA_APPTRACE_BLOCK_ID_GET(_v_)       (((_v_) >> 15) & XTENSA_APPTRACE_BLOCK_ID_MSK)
+#define XTENSA_APPTRACE_HOST_DATA               BIT(22)
+#define XTENSA_APPTRACE_HOST_CONNECT            BIT(23)
+
+static int esp_xtensa_apptrace_leave_crit_section_start(struct target *target);
+static int esp_xtensa_apptrace_leave_crit_section_stop(struct target *target);
+static int esp_xtensa_apptrace_buffs_write(struct target *target,
+       uint32_t bufs_num,
+       uint32_t buf_sz[],
+       const uint8_t *bufs[],
+       uint32_t block_id,
+       bool ack,
+       bool data);
+
+struct esp32_apptrace_hw esp_xtensa_apptrace_hw = {
+       .max_block_id = XTENSA_APPTRACE_BLOCK_ID_MAX,
+       .max_block_size_get = esp_xtensa_apptrace_block_max_size_get,
+       .status_reg_read = esp_xtensa_apptrace_status_reg_read,
+       .ctrl_reg_write = esp_xtensa_apptrace_ctrl_reg_write,
+       .ctrl_reg_read = esp_xtensa_apptrace_ctrl_reg_read,
+       .data_len_read = esp_xtensa_apptrace_data_len_read,
+       .data_read = esp_xtensa_apptrace_data_read,
+       .usr_block_max_size_get = esp_xtensa_apptrace_usr_block_max_size_get,
+       .buffs_write = esp_xtensa_apptrace_buffs_write,
+       .leave_trace_crit_section_start = esp_xtensa_apptrace_leave_crit_section_start,
+       .leave_trace_crit_section_stop = esp_xtensa_apptrace_leave_crit_section_stop,
+};
+
+uint32_t esp_xtensa_apptrace_block_max_size_get(struct target *target)
+{
+       struct xtensa *xtensa = target_to_xtensa(target);
+       struct xtensa_trace_status trace_status;
+       struct xtensa_trace_config trace_config;
+       uint32_t max_trace_block_sz;
+
+       int res = xtensa_dm_trace_status_read(&xtensa->dbg_mod, &trace_status);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to read TRAX status (%d)!", res);
+               return 0;
+       }
+
+       max_trace_block_sz = BIT(((trace_status.stat >> 8) & 0x1f) - 2) * 4;
+       res = xtensa_dm_trace_config_read(&xtensa->dbg_mod, &trace_config);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to read TRAX config (%d)!", res);
+               return 0;
+       }
+       LOG_DEBUG("ctrl=0x%" PRIx32 " memadrstart=0x%" PRIx32 " memadrend=0x%" PRIx32 " traxadr=0x%" PRIx32,
+               trace_config.ctrl,
+               trace_config.memaddr_start,
+               trace_config.memaddr_end,
+               trace_config.addr);
+
+       return max_trace_block_sz;
+}
+
+uint32_t esp_xtensa_apptrace_usr_block_max_size_get(struct target *target)
+{
+       return esp_xtensa_apptrace_block_max_size_get(target) - sizeof(struct esp_apptrace_host2target_hdr);
+}
+
+int esp_xtensa_apptrace_data_len_read(struct target *target,
+       uint32_t *block_id,
+       uint32_t *len)
+{
+       return esp_xtensa_apptrace_ctrl_reg_read(target, block_id, len, NULL);
+}
+
+int esp_xtensa_apptrace_usr_block_write(struct target *target,
+       uint32_t block_id,
+       const uint8_t *data,
+       uint32_t size)
+{
+       return esp_apptrace_usr_block_write(&esp_xtensa_apptrace_hw, target, block_id, data, size);
+}
+
+static int esp_xtensa_apptrace_data_reverse_read(struct xtensa *xtensa,
+       uint32_t size,
+       uint8_t *buffer,
+       uint8_t *unal_bytes)
+{
+       int res = 0;
+       uint32_t rd_sz = ALIGN_UP(size, 4);
+
+       res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXADDR, (xtensa->core_config->trace.mem_sz - rd_sz) / 4);
+       if (res != ERROR_OK)
+               return res;
+       if (!IS_ALIGNED(size, 4)) {
+               res = xtensa_queue_dbg_reg_read(xtensa, XDMREG_TRAXDATA, unal_bytes);
+               if (res != ERROR_OK)
+                       return res;
+       }
+       for (unsigned int i = size / 4; i != 0; i--) {
+               res = xtensa_queue_dbg_reg_read(xtensa, XDMREG_TRAXDATA, &buffer[(i - 1) * 4]);
+               if (res != ERROR_OK)
+                       return res;
+       }
+       return ERROR_OK;
+}
+
+static int esp_xtensa_apptrace_data_normal_read(struct xtensa *xtensa,
+       uint32_t size,
+       uint8_t *buffer,
+       uint8_t *unal_bytes)
+{
+       int res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXADDR, 0);
+       if (res != ERROR_OK)
+               return res;
+       for (unsigned int i = 0; i < size / 4; i++) {
+               res = xtensa_queue_dbg_reg_read(xtensa, XDMREG_TRAXDATA, &buffer[i * 4]);
+               if (res != ERROR_OK)
+                       return res;
+       }
+       if (!IS_ALIGNED(size, 4)) {
+               res = xtensa_queue_dbg_reg_read(xtensa, XDMREG_TRAXDATA, unal_bytes);
+               if (res != ERROR_OK)
+                       return res;
+       }
+       return ERROR_OK;
+}
+
+int esp_xtensa_apptrace_data_read(struct target *target,
+       uint32_t size,
+       uint8_t *buffer,
+       uint32_t block_id,
+       bool ack)
+{
+       struct xtensa *xtensa = target_to_xtensa(target);
+       int res;
+       uint32_t tmp = XTENSA_APPTRACE_HOST_CONNECT | XTENSA_APPTRACE_BLOCK_ID(block_id) |
+               XTENSA_APPTRACE_BLOCK_LEN(0);
+       uint8_t unal_bytes[4];
+
+       LOG_DEBUG("Read data on target (%s)", target_name(target));
+       if (xtensa->core_config->trace.reversed_mem_access)
+               res = esp_xtensa_apptrace_data_reverse_read(xtensa, size, buffer, unal_bytes);
+       else
+               res = esp_xtensa_apptrace_data_normal_read(xtensa, size, buffer, unal_bytes);
+       if (res != ERROR_OK)
+               return res;
+       if (ack) {
+               LOG_DEBUG("Ack block %" PRIu32 " target (%s)!", block_id, target_name(target));
+               res = xtensa_queue_dbg_reg_write(xtensa, XTENSA_APPTRACE_CTRL_REG, tmp);
+               if (res != ERROR_OK)
+                       return res;
+       }
+       xtensa_dm_queue_tdi_idle(&xtensa->dbg_mod);
+       res = xtensa_dm_queue_execute(&xtensa->dbg_mod);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to exec JTAG queue!");
+               return res;
+       }
+       if (!IS_ALIGNED(size, 4)) {
+               /* copy the last unaligned bytes */
+               memcpy(buffer + ALIGN_DOWN(size, 4), unal_bytes, size & 0x3UL);
+       }
+       return ERROR_OK;
+}
+
+int esp_xtensa_apptrace_ctrl_reg_write(struct target *target,
+       uint32_t block_id,
+       uint32_t len,
+       bool conn,
+       bool data)
+{
+       struct xtensa *xtensa = target_to_xtensa(target);
+       uint32_t tmp = (conn ? XTENSA_APPTRACE_HOST_CONNECT : 0) |
+               (data ? XTENSA_APPTRACE_HOST_DATA : 0) | XTENSA_APPTRACE_BLOCK_ID(block_id) |
+               XTENSA_APPTRACE_BLOCK_LEN(len);
+
+       xtensa_queue_dbg_reg_write(xtensa, XTENSA_APPTRACE_CTRL_REG, tmp);
+       xtensa_dm_queue_tdi_idle(&xtensa->dbg_mod);
+       int res = xtensa_dm_queue_execute(&xtensa->dbg_mod);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to exec JTAG queue!");
+               return res;
+       }
+
+       return ERROR_OK;
+}
+
+int esp_xtensa_apptrace_ctrl_reg_read(struct target *target,
+       uint32_t *block_id,
+       uint32_t *len,
+       bool *conn)
+{
+       struct xtensa *xtensa = target_to_xtensa(target);
+       uint8_t tmp[4];
+
+       xtensa_queue_dbg_reg_read(xtensa, XTENSA_APPTRACE_CTRL_REG, tmp);
+       xtensa_dm_queue_tdi_idle(&xtensa->dbg_mod);
+       int res = xtensa_dm_queue_execute(&xtensa->dbg_mod);
+       if (res != ERROR_OK)
+               return res;
+       uint32_t val = target_buffer_get_u32(target, tmp);
+       if (block_id)
+               *block_id = XTENSA_APPTRACE_BLOCK_ID_GET(val);
+       if (len)
+               *len = XTENSA_APPTRACE_BLOCK_LEN_GET(val);
+       if (conn)
+               *conn = val & XTENSA_APPTRACE_HOST_CONNECT;
+       return ERROR_OK;
+}
+
+int esp_xtensa_apptrace_status_reg_read(struct target *target, uint32_t *stat)
+{
+       struct xtensa *xtensa = target_to_xtensa(target);
+       uint8_t tmp[4];
+
+       xtensa_queue_dbg_reg_read(xtensa, XTENSA_APPTRACE_STAT_REG, tmp);
+       xtensa_dm_queue_tdi_idle(&xtensa->dbg_mod);
+       int res = xtensa_dm_queue_execute(&xtensa->dbg_mod);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to exec JTAG queue!");
+               return res;
+       }
+       *stat = buf_get_u32(tmp, 0, 32);
+       return ERROR_OK;
+}
+
+int esp_xtensa_apptrace_status_reg_write(struct target *target, uint32_t stat)
+{
+       struct xtensa *xtensa = target_to_xtensa(target);
+
+       xtensa_queue_dbg_reg_write(xtensa, XTENSA_APPTRACE_STAT_REG, stat);
+       xtensa_dm_queue_tdi_idle(&xtensa->dbg_mod);
+       int res = xtensa_dm_queue_execute(&xtensa->dbg_mod);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to exec JTAG queue!");
+               return res;
+       }
+       return ERROR_OK;
+}
+
+static int esp_xtensa_swdbg_activate(struct target *target, int enab)
+{
+       struct xtensa *xtensa = target_to_xtensa(target);
+
+       xtensa_queue_dbg_reg_write(xtensa, enab ? XDMREG_DCRSET : XDMREG_DCRCLR, OCDDCR_DEBUGSWACTIVE);
+       xtensa_dm_queue_tdi_idle(&xtensa->dbg_mod);
+       int res = xtensa_dm_queue_execute(&xtensa->dbg_mod);
+       if (res != ERROR_OK) {
+               LOG_ERROR("%s: writing DCR failed!", target->cmd_name);
+               return res;
+       }
+
+       return ERROR_OK;
+}
+
+static int esp_xtensa_apptrace_leave_crit_section_start(struct target *target)
+{
+       /* TODO: not sure that we need this, but it seems that we fail to leave tracing critical
+        *section w/o this */
+       int res = esp_xtensa_swdbg_activate(target, 1 /*enable*/);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to activate SW debug (%d)!", res);
+               return res;
+       }
+       return ERROR_OK;
+}
+
+static int esp_xtensa_apptrace_leave_crit_section_stop(struct target *target)
+{
+       int res = esp_xtensa_swdbg_activate(target, 0 /*disable*/);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to activate SW debug (%d)!", res);
+               return res;
+       }
+       return ERROR_OK;
+}
+
+static int esp_xtensa_apptrace_queue_reverse_write(struct target *target, uint32_t bufs_num,
+       uint32_t buf_sz[], const uint8_t *bufs[])
+{
+       int res = ERROR_OK;
+       uint32_t cached_bytes = 0, total_sz = 0;
+       uint8_t cached_data8[sizeof(uint32_t)] = { 0 };
+       uint32_t cached_data32 = 0;
+
+       struct xtensa *xtensa = target_to_xtensa(target);
+
+       for (uint32_t i = 0; i < bufs_num; i++)
+               total_sz += buf_sz[i];
+       if (!IS_ALIGNED(total_sz, 4)) {
+               cached_bytes = sizeof(uint32_t) - (total_sz & 0x3UL);
+               total_sz = ALIGN_UP(total_sz, 4);
+       }
+       xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXADDR, (xtensa->core_config->trace.mem_sz - total_sz) / 4);
+       for (uint32_t i = bufs_num; i > 0; i--) {
+               uint32_t bsz = buf_sz[i - 1];
+               const uint8_t *cur_buf = &bufs[i - 1][bsz];
+               uint32_t bytes_to_cache;
+               /* if there are cached bytes from the previous buffer, combine them with the last
+                * from the current buffer */
+               if (cached_bytes) {
+                       if ((cached_bytes + bsz) < sizeof(uint32_t))
+                               bytes_to_cache = bsz;
+                       else
+                               bytes_to_cache = sizeof(uint32_t) - cached_bytes;
+                       memcpy(&cached_data8[sizeof(uint32_t) - cached_bytes - bytes_to_cache],
+                               cur_buf - bytes_to_cache,
+                               bytes_to_cache);
+                       cached_data32 = target_buffer_get_u32(target, cached_data8);
+                       cached_bytes += bytes_to_cache;
+                       if (cached_bytes < sizeof(uint32_t))
+                               continue;
+                       res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXDATA, cached_data32);
+                       if (res != ERROR_OK)
+                               return res;
+                       bsz -= bytes_to_cache;
+                       cur_buf -= bytes_to_cache;
+                       memset(cached_data8, 0x00, sizeof(cached_data8));
+                       cached_bytes = 0;
+               }
+               /* write full dwords */
+               for (unsigned int k = bsz; k >= sizeof(uint32_t); k -= sizeof(uint32_t)) {
+                       uint32_t temp = target_buffer_get_u32(target, cur_buf - sizeof(uint32_t));
+                       res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXDATA, temp);
+                       if (res != ERROR_OK)
+                               return res;
+                       cur_buf -= sizeof(uint32_t);
+               }
+               /* if there are bytes to be cached (1..3) */
+               bytes_to_cache = bsz & 0x3UL;
+               if (bytes_to_cache > 0) {
+                       if (bytes_to_cache + cached_bytes >= sizeof(uint32_t)) {
+                               /* filling the cache buffer from the end to beginning */
+                               uint32_t to_copy = sizeof(uint32_t) - cached_bytes;
+                               memcpy(&cached_data8[0], cur_buf - to_copy, to_copy);
+                               cached_data32 = target_buffer_get_u32(target, cached_data8);
+                               /* write full word of cached bytes */
+                               res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXDATA, cached_data32);
+                               if (res != ERROR_OK)
+                                       return res;
+                               /* cache remaining bytes */
+                               memset(cached_data8, 0x00, sizeof(cached_data8));
+                               cur_buf -= to_copy;
+                               to_copy = bytes_to_cache + cached_bytes - sizeof(uint32_t);
+                               memcpy(&cached_data8[sizeof(uint32_t) - to_copy], cur_buf - to_copy, to_copy);
+                               cached_bytes = to_copy;
+                       } else {
+                               /* filling the cache buffer from the end to beginning */
+                               memcpy(&cached_data8[sizeof(uint32_t) - cached_bytes - bytes_to_cache],
+                                       cur_buf - bytes_to_cache,
+                                       bytes_to_cache);
+                               cached_bytes += bytes_to_cache;
+                       }
+               }
+       }
+       return ERROR_OK;
+}
+
+static int esp_xtensa_apptrace_queue_normal_write(struct target *target, uint32_t bufs_num,
+       uint32_t buf_sz[], const uint8_t *bufs[])
+{
+       int res = ERROR_OK;
+       uint32_t cached_bytes = 0;
+       uint8_t cached_data8[4] = { 0 };
+       uint32_t cached_data32 = 0;
+
+       struct xtensa *xtensa = target_to_xtensa(target);
+
+       /* | 1 |   2   | 1 | 2     |       4       |.......|
+        * |       4       |       4       |       4       | */
+       xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXADDR, 0);
+       for (unsigned int i = 0; i < bufs_num; i++) {
+               uint32_t bsz = buf_sz[i];
+               const uint8_t *cur_buf = bufs[i];
+               uint32_t bytes_to_cache;
+               /* if there are cached bytes from the previous buffer, combine them with the last
+                * from the current buffer */
+               if (cached_bytes) {
+                       if ((cached_bytes + bsz) < sizeof(uint32_t))
+                               bytes_to_cache = bsz;
+                       else
+                               bytes_to_cache = sizeof(uint32_t) - cached_bytes;
+                       memcpy(&cached_data8[cached_bytes], cur_buf, bytes_to_cache);
+                       cached_bytes += bytes_to_cache;
+                       if (cached_bytes < sizeof(uint32_t))
+                               continue;
+                       cached_data32 = target_buffer_get_u32(target, cached_data8);
+                       res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXDATA, cached_data32);
+                       if (res != ERROR_OK)
+                               return res;
+                       bsz -= bytes_to_cache;
+                       cur_buf += bytes_to_cache;
+                       memset(cached_data8, 0x00, sizeof(cached_data8));
+                       cached_bytes = 0;
+               }
+               /* write full dwords */
+               for (unsigned int k = 0; (k + sizeof(uint32_t)) <= bsz; k += sizeof(uint32_t)) {
+                       uint32_t temp = target_buffer_get_u32(target, cur_buf);
+                       res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXDATA, temp);
+                       if (res != ERROR_OK)
+                               return res;
+                       cur_buf += sizeof(uint32_t);
+               }
+               /* if there are bytes to be cached (1..3) */
+               bytes_to_cache = bsz & 0x3UL;
+               if (bytes_to_cache > 0) {
+                       if (bytes_to_cache + cached_bytes >= sizeof(uint32_t)) {
+                               memcpy(&cached_data8[0], cur_buf, sizeof(uint32_t) - cached_bytes);
+                               cached_data32 = target_buffer_get_u32(target, cached_data8);
+                               /* write full word of cached bytes */
+                               res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXDATA, cached_data32);
+                               if (res != ERROR_OK)
+                                       return res;
+                               /* cache remaining bytes */
+                               memset(cached_data8, 0x00, sizeof(cached_data8));
+                               cur_buf += sizeof(uint32_t) - cached_bytes;
+                               cached_bytes = bytes_to_cache + cached_bytes - sizeof(uint32_t);
+                               memcpy(&cached_data8[0], cur_buf, cached_bytes);
+                       } else {
+                               memcpy(&cached_data8[cached_bytes], cur_buf, bytes_to_cache);
+                               cached_bytes += bytes_to_cache;
+                       }
+               }
+       }
+       if (cached_bytes) {
+               /* write remaining cached bytes */
+               cached_data32 = target_buffer_get_u32(target, cached_data8);
+               res = xtensa_queue_dbg_reg_write(xtensa, XDMREG_TRAXDATA, cached_data32);
+               if (res != ERROR_OK)
+                       return res;
+       }
+       return ERROR_OK;
+}
+
+static int esp_xtensa_apptrace_buffs_write(struct target *target,
+       uint32_t bufs_num,
+       uint32_t buf_sz[],
+       const uint8_t *bufs[],
+       uint32_t block_id,
+       bool ack,
+       bool data)
+{
+       struct xtensa *xtensa = target_to_xtensa(target);
+       int res = ERROR_OK;
+       uint32_t tmp = XTENSA_APPTRACE_HOST_CONNECT |
+               (data ? XTENSA_APPTRACE_HOST_DATA : 0) | XTENSA_APPTRACE_BLOCK_ID(block_id) |
+               XTENSA_APPTRACE_BLOCK_LEN(0);
+
+       if (xtensa->core_config->trace.reversed_mem_access)
+               res = esp_xtensa_apptrace_queue_reverse_write(target, bufs_num, buf_sz, bufs);
+       else
+               res = esp_xtensa_apptrace_queue_normal_write(target, bufs_num, buf_sz, bufs);
+       if (res != ERROR_OK)
+               return res;
+       if (ack) {
+               LOG_DEBUG("Ack block %" PRId32 " on target (%s)!", block_id, target_name(target));
+               res = xtensa_queue_dbg_reg_write(xtensa, XTENSA_APPTRACE_CTRL_REG, tmp);
+               if (res != ERROR_OK)
+                       return res;
+       }
+       xtensa_dm_queue_tdi_idle(&xtensa->dbg_mod);
+       res = xtensa_dm_queue_execute(&xtensa->dbg_mod);
+       if (res != ERROR_OK) {
+               LOG_ERROR("Failed to exec JTAG queue!");
+               return res;
+       }
+       return ERROR_OK;
+}
diff --git a/src/target/espressif/esp_xtensa_apptrace.h b/src/target/espressif/esp_xtensa_apptrace.h
new file mode 100644 (file)
index 0000000..0a9be73
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/***************************************************************************
+ *   Xtensa application tracing module for OpenOCD                         *
+ *   Copyright (C) 2017 Espressif Systems Ltd.                             *
+ ***************************************************************************/
+
+#ifndef OPENOCD_TARGET_ESP_XTENSA_APPTRACE_H
+#define OPENOCD_TARGET_ESP_XTENSA_APPTRACE_H
+
+#include "esp32_apptrace.h"
+
+struct esp_xtensa_apptrace_info {
+       const struct esp32_apptrace_hw *hw;
+};
+
+extern struct esp32_apptrace_hw esp_xtensa_apptrace_hw;
+
+int esp_xtensa_apptrace_data_len_read(struct target *target, uint32_t *block_id, uint32_t *len);
+int esp_xtensa_apptrace_data_read(struct target *target,
+       uint32_t size,
+       uint8_t *buffer,
+       uint32_t block_id,
+       bool ack);
+int esp_xtensa_apptrace_ctrl_reg_read(struct target *target, uint32_t *block_id, uint32_t *len, bool *conn);
+int esp_xtensa_apptrace_ctrl_reg_write(struct target *target,
+       uint32_t block_id,
+       uint32_t len,
+       bool conn,
+       bool data);
+int esp_xtensa_apptrace_status_reg_write(struct target *target, uint32_t stat);
+int esp_xtensa_apptrace_status_reg_read(struct target *target, uint32_t *stat);
+uint32_t esp_xtensa_apptrace_block_max_size_get(struct target *target);
+uint32_t esp_xtensa_apptrace_usr_block_max_size_get(struct target *target);
+int esp_xtensa_apptrace_usr_block_write(struct target *target, uint32_t block_id, const uint8_t *data, uint32_t size);
+
+#endif /* OPENOCD_TARGET_ESP_XTENSA_APPTRACE_H */

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)