This is an automated email from Gerrit. "Erhan Kurubas <erhan.kuru...@espressif.com>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/7163
-- gerrit commit 492945ab337a25b2333e1b84fca6c4f0257d47e6 Author: Erhan Kurubas <erhan.kuru...@espressif.com> Date: Thu Aug 25 15:38:34 2022 +0300 target/espressif: add application tracing functionality over JTAG 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.kuru...@espressif.com> Change-Id: I95dee00ac22891fa326915a3fcac3c088cbb2afc diff --git a/doc/openocd.texi b/doc/openocd.texi index 0df25406ff..d12071e103 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -11149,6 +11149,40 @@ 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 file://<outfile> [<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. +@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 receiption timeout in ms. +Tracing will be stopped automatically when no data is received withn 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 usefull 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 diff --git a/src/target/espressif/Makefile.am b/src/target/espressif/Makefile.am index 8367a38819..c1759ed77f 100644 --- a/src/target/espressif/Makefile.am +++ b/src/target/espressif/Makefile.am @@ -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 \ diff --git a/src/target/espressif/esp32.c b/src/target/espressif/esp32.c index 8ad8bad835..76fa2483bc 100644 --- a/src/target/espressif/esp32.c +++ b/src/target/espressif/esp32.c @@ -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 index 0000000000..b5335d08f4 --- /dev/null +++ b/src/target/espressif/esp32_apptrace.c @@ -0,0 +1,1457 @@ +/* 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/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; +}; + +struct esp32_apptrace_block { + struct hlist_node node; + uint8_t *data; + uint32_t data_len; +}; + +static void *esp32_apptrace_data_processor(void *arg); +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(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(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; + } + assert(port_sep > dest_name); + 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)", + (int)ai_it->ai_family, + (int)ai_it->ai_socktype, + (int)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(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(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[], int max_dests) +{ + for (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 hlist_head *head = &ctx->free_trace_blocks; + struct hlist_node *tmp, *pos = head->first; + + while (pos && ({ tmp = pos->next; 1; })) { + cur = hlist_entry(pos, struct esp32_apptrace_block, node); + if (cur) { + hlist_del(&cur->node); + free(cur->data); + free(cur); + } + pos = tmp; + } + + head = &ctx->ready_trace_blocks; + pos = head->first; + + while (pos && ({ tmp = pos->next; 1; })) { + cur = hlist_entry(pos, struct esp32_apptrace_block, node); + if (cur) { + hlist_del(&cur->node); + free(cur->data); + free(cur); + } + pos = tmp; + } +} + +struct esp32_apptrace_block *esp32_apptrace_free_block_get(struct esp32_apptrace_cmd_ctx *ctx) +{ + struct esp32_apptrace_block *block = NULL; + + int res = pthread_mutex_lock(&ctx->trax_blocks_mux); + if (res == 0) { + if (!hlist_empty(&ctx->free_trace_blocks)) { + /*get first */ + block = hlist_entry(ctx->free_trace_blocks.first, struct esp32_apptrace_block, node); + hlist_del(&block->node); + } + res = pthread_mutex_unlock(&ctx->trax_blocks_mux); + if (res) + LOG_ERROR("Failed to unlock blocks pool (%d)!", res); + } + + return block; +} + +static int esp32_apptrace_ready_block_put(struct esp32_apptrace_cmd_ctx *ctx, struct esp32_apptrace_block *block) +{ + int res = pthread_mutex_lock(&ctx->trax_blocks_mux); + if (res == 0) { + LOG_DEBUG("esp32_apptrace_ready_block_put"); + /* add to ready blocks list */ + INIT_HLIST_NODE(&block->node); + hlist_add_head(&block->node, &ctx->ready_trace_blocks); + res = pthread_mutex_unlock(&ctx->trax_blocks_mux); + if (res) + LOG_ERROR("Failed to unlock blocks pool (%d)!", res); + } else { + LOG_ERROR("Failed to lock blocks pool (%d)!", res); + } + + return res == 0 ? ERROR_OK : ERROR_FAIL; +} + +static struct esp32_apptrace_block *esp32_apptrace_ready_block_get(struct esp32_apptrace_cmd_ctx *ctx) +{ + struct esp32_apptrace_block *block = NULL; + if (pthread_mutex_trylock(&ctx->trax_blocks_mux) == 0) { + if (!hlist_empty(&ctx->ready_trace_blocks)) { + struct hlist_head *head = &ctx->ready_trace_blocks; + struct hlist_node *pos = head->first; + while (pos) { + block = hlist_entry(pos, struct esp32_apptrace_block, node); + pos = pos->next; + } + /* remove it from ready list */ + hlist_del(&block->node); + } + int res = pthread_mutex_unlock(&ctx->trax_blocks_mux); + if (res) + LOG_ERROR("Failed to unlock blocks pool (%d)!", res); + /* TODO; return NULL and free block?? */ + } + return block; +} + +static int esp32_apptrace_block_free(struct esp32_apptrace_cmd_ctx *ctx, struct esp32_apptrace_block *block) +{ + int res = pthread_mutex_lock(&ctx->trax_blocks_mux); + if (res == 0) { + /* add to free blocks list */ + INIT_HLIST_NODE(&block->node); + hlist_add_head(&block->node, &ctx->free_trace_blocks); + res = pthread_mutex_unlock(&ctx->trax_blocks_mux); + if (res) + LOG_ERROR("Failed to unlock blocks pool (%d)!", res); + } else { + LOG_ERROR("Failed to lock blocks pool (%d)!", res); + } + + return res == 0 ? ERROR_OK : ERROR_FAIL; +} + +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 (!hlist_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 thread to stop */ + ctx->running = 0; + /* wait for the processor thread to finish */ + if (ctx->data_processor != (pthread_t)-1) { + void *thr_res; + int res = pthread_join(ctx->data_processor, (void *)&thr_res); + if (res) + LOG_ERROR("Failed to join trace data processor thread (%d)!", res); + else + LOG_INFO("Trace data processor thread exited with %ld", (long)thr_res); + } + + return ERROR_OK; +} + +/********************************************************************* +* Trace commands +**********************************************************************/ + +int esp32_apptrace_cmd_ctx_init(struct target *target, struct esp32_apptrace_cmd_ctx *cmd_ctx, int mode) +{ + memset(cmd_ctx, 0, sizeof(struct esp32_apptrace_cmd_ctx)); + + cmd_ctx->data_processor = (pthread_t)-1; + cmd_ctx->mode = mode; + cmd_ctx->target_state = target->state; + + 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) { + LOG_ERROR("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!"); + + const char *arch = target_get_gdb_arch(target); + if (arch) { + /* TODO: riscv is not supported yet */ + if (strncmp(arch, "xtensa", 6) == 0) { + cmd_ctx->hw = target_to_esp_xtensa(target)->apptrace.hw; + } else { + LOG_ERROR("Unsupported target arch '%s'!", arch); + return ERROR_FAIL; + } + } else { + LOG_ERROR("Unknown target arch!"); + 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) { + LOG_ERROR("Failed to get max trace block size!"); + return ERROR_FAIL; + } + LOG_INFO("Total trace memory: %d bytes", cmd_ctx->max_trace_block_sz); + + INIT_HLIST_HEAD(&cmd_ctx->ready_trace_blocks); + INIT_HLIST_HEAD(&cmd_ctx->free_trace_blocks); + for (unsigned int i = 0; i < ESP_APPTRACE_BLOCKS_POOL_SZ; i++) { + struct esp32_apptrace_block *block = malloc(sizeof(struct esp32_apptrace_block)); + if (!block) { + LOG_ERROR("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); + LOG_ERROR("Failed to alloc trace buffer %" PRId32 " bytes!", cmd_ctx->max_trace_block_sz); + esp32_apptrace_blocks_pool_cleanup(cmd_ctx); + return ERROR_FAIL; + } + INIT_HLIST_NODE(&block->node); + hlist_add_head(&block->node, &cmd_ctx->free_trace_blocks); + } + + cmd_ctx->running = 1; + + int res = pthread_mutex_init(&cmd_ctx->trax_blocks_mux, NULL); + if (res) { + LOG_ERROR("Failed to blocks pool mux (%d)!", res); + esp32_apptrace_blocks_pool_cleanup(cmd_ctx); + return ERROR_FAIL; + } + if (cmd_ctx->mode != ESP_APPTRACE_CMD_MODE_SYNC) { + res = pthread_create(&cmd_ctx->data_processor, + NULL, + esp32_apptrace_data_processor, + cmd_ctx); + if (res) { + LOG_ERROR("Failed to start trace data processor thread (%d)!", res); + cmd_ctx->data_processor = (pthread_t)-1; + pthread_mutex_destroy(&cmd_ctx->trax_blocks_mux); + 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) { + LOG_ERROR("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) +{ + pthread_mutex_destroy(&cmd_ctx->trax_blocks_mux); + esp32_apptrace_blocks_pool_cleanup(cmd_ctx); + return ERROR_OK; +} + +#define ESP32_APPTRACE_CMD_NUM_ARG_CHECK(_arg_, _start_, _end_) \ + do { \ + if ((_arg_) == 0 && (_start_) == (_end_)) { \ + LOG_ERROR("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_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_data->max_len, argv[1], end); + if (argc > 2) { + int32_t tmo = strtol(argv[2], &end, 10); + ESP32_APPTRACE_CMD_NUM_ARG_CHECK(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_data->wait4halt, argv[3], end); + if (argc > 4) { + cmd_data->skip_len = strtoul(argv[4], &end, 10); + ESP32_APPTRACE_CMD_NUM_ARG_CHECK(cmd_data->skip_len, argv[4], end); + } + } + } + } +} + +static int esp32_apptrace_core_id_get(uint8_t *hdr_buf) +{ + struct esp_apptrace_target2host_hdr tmp_hdr; + memcpy(&tmp_hdr, hdr_buf, sizeof(tmp_hdr)); + return ESP32_APPTRACE_USER_BLOCK_CORE(tmp_hdr.block_sz); +} + +static uint32_t esp32_apptrace_usr_block_len_get(uint8_t *hdr_buf, uint32_t *wr_len) +{ + struct esp_apptrace_target2host_hdr tmp_hdr; + memcpy(&tmp_hdr, hdr_buf, sizeof(tmp_hdr)); + *wr_len = ESP32_APPTRACE_USER_BLOCK_LEN(tmp_hdr.wr_sz); + return ESP32_APPTRACE_USER_BLOCK_LEN(tmp_hdr.block_sz); +} + +static int esp32_apptrace_cmd_init(struct target *target, + struct esp32_apptrace_cmd_ctx *cmd_ctx, + int mode, + const char **argv, + int argc) +{ + struct esp32_apptrace_cmd_data *cmd_data; + + if (argc < 1) { + LOG_ERROR("Not enough args! Need trace data destination!"); + return ERROR_FAIL; + } + + int res = esp32_apptrace_cmd_ctx_init(target, cmd_ctx, 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 */ + LOG_ERROR("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: + LOG_ERROR("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) +{ + int halted = 0; + + LOG_USER("Wait for halt..."); + while (!openocd_is_shutdown_pending()) { + int res = target_poll(target); + if (res != ERROR_OK) { + LOG_ERROR("Failed to poll target (%d)!", res); + return res; + } + if (target->state == TARGET_HALTED) { + LOG_USER("%s: HALTED", target->cmd_name); + break; + } + if (halted) + 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 != NULL) { + 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) { + LOG_ERROR("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) { + LOG_ERROR("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) { + LOG_ERROR("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 res; +} + +uint8_t *esp_apptrace_usr_block_get(uint8_t *buffer, uint32_t *size) +{ + struct esp_apptrace_target2host_hdr tmp_hdr; + memcpy(&tmp_hdr, buffer, sizeof(tmp_hdr)); + + *size = tmp_hdr.wr_sz; + + return buffer + sizeof(tmp_hdr); +} + +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, + sizeof(buf_sz) / sizeof(buf_sz[0]), + 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(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(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 void *esp32_apptrace_data_processor(void *arg) +{ + long res = ERROR_OK; + struct esp32_apptrace_cmd_ctx *ctx = (struct esp32_apptrace_cmd_ctx *)arg; + + while (ctx->running) { + struct esp32_apptrace_block *block = esp32_apptrace_ready_block_get(ctx); + if (!block) + continue; + 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); + break; + } + res = esp32_apptrace_block_free(ctx, block); + if (res != ERROR_OK) { + ctx->running = 0; + LOG_ERROR("Failed to free ready block!"); + break; + } + } + + return (void *)res; /*TODO /??? */ +} + +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 transfered + * 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 target *target, 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_OK; + enum target_state old_state; + + if (argc < 1) { + LOG_ERROR("Action missed!"); + 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(target, + &s_at_cmd_ctx, + mode, + &argv[1], + argc - 1); + if (res != ERROR_OK) { + LOG_ERROR("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) { + LOG_ERROR("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) { + LOG_ERROR("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) { + LOG_ERROR("Failed to register target timer handler (%d)!", res); + return res; + } + } else if (strcmp(argv[0], "stop") == 0) { + if (!s_at_cmd_ctx.running) { + LOG_WARNING("Tracing is not running!"); + return ERROR_FAIL; + } + esp32_apptrace_cmd_stop(&s_at_cmd_ctx); + } 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); + } else if (strcmp(argv[0], "dump") == 0) { + /* [dump outfile] - post-mortem dump without connection to targets */ + res = esp32_apptrace_cmd_init(target, + &s_at_cmd_ctx, + mode, + &argv[1], + argc - 1); + if (res != ERROR_OK) { + LOG_ERROR("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 comand 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) + LOG_ERROR("Failed to cleanup cmd ctx (%d)!", res); + } else { + LOG_ERROR("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(get_current_target(CMD_CTX), + ESP_APPTRACE_CMD_MODE_GEN, + CMD_ARGV, + CMD_ARGC); +} + +#ifdef _WIN32 +#define DIR_SEPARATORS ("\\/") +#define IS_DIR_SEPARATOR(c) (c == '\\' || c == '/') +#else +#define DIR_SEPARATORS ("/") +#define IS_DIR_SEPARATOR(c) (c == '/') +#endif + +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 file://<outfile> [poll_period [trace_size [stop_tmo [wait4halt [skip_size]]]]] | [stop] | [status] | [dump file://<outfile>]", + }, + COMMAND_REGISTRATION_DONE +}; diff --git a/src/target/espressif/esp32_apptrace.h b/src/target/espressif/esp32_apptrace.h new file mode 100644 index 0000000000..cf790bbd33 --- /dev/null +++ b/src/target/espressif/esp32_apptrace.h @@ -0,0 +1,128 @@ +/* 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 <pthread.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)(uint8_t *hdr_buf); + uint32_t (*usr_block_len_get)(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; + pthread_mutex_t trax_blocks_mux; + struct hlist_head free_trace_blocks; + struct hlist_head ready_trace_blocks; + uint32_t max_trace_block_sz; + pthread_t data_processor; + 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 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 target *target, struct esp32_apptrace_cmd_ctx *cmd_ctx, 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[], 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); +uint8_t *esp_apptrace_usr_block_get(uint8_t *buffer, uint32_t *size); + +extern const struct command_registration esp32_apptrace_command_handlers[]; + +#endif /* OPENOCD_TARGET_ESP32_APPTRACE_H */ diff --git a/src/target/espressif/esp32s2.c b/src/target/espressif/esp32s2.c index 4aef3791bf..03e582e58d 100644 --- a/src/target/espressif/esp32s2.c +++ b/src/target/espressif/esp32s2.c @@ -494,6 +494,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, diff --git a/src/target/espressif/esp32s3.c b/src/target/espressif/esp32s3.c index 0da8552a35..d7c9c017d6 100644 --- a/src/target/espressif/esp32s3.c +++ b/src/target/espressif/esp32s3.c @@ -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 = "", diff --git a/src/target/espressif/esp_xtensa.c b/src/target/espressif/esp_xtensa.c index 6a1b72ec48..697da3f6bb 100644 --- a/src/target/espressif/esp_xtensa.c +++ b/src/target/espressif/esp_xtensa.c @@ -12,6 +12,8 @@ #include <stdbool.h> #include <stdint.h> #include <target/smp.h> +#include "esp_xtensa.h" +#include "esp_xtensa_apptrace.h" #include <target/register.h> #include "esp_xtensa.h" #include "esp_semihosting.h" @@ -25,6 +27,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; } diff --git a/src/target/espressif/esp_xtensa.h b/src/target/espressif/esp_xtensa.h index 1ad6c377f0..8807f0c325 100644 --- a/src/target/espressif/esp_xtensa.h +++ b/src/target/espressif/esp_xtensa.h @@ -12,10 +12,12 @@ #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 index 0000000000..86b430168c --- /dev/null +++ b/src/target/espressif/esp_xtensa_apptrace.c @@ -0,0 +1,498 @@ +/* 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 NARADR_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 NARADR_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, NARADR_TRAXADDR, (xtensa->core_config->trace.mem_sz - rd_sz) / 4); + if (res != ERROR_OK) + return res; + if (size & 0x3UL) { + res = xtensa_queue_dbg_reg_read(xtensa, NARADR_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, NARADR_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, NARADR_TRAXADDR, 0); + if (res != ERROR_OK) + return res; + for (unsigned int i = 0; i < size / 4; i++) { + res = xtensa_queue_dbg_reg_read(xtensa, NARADR_TRAXDATA, &buffer[i * 4]); + if (res != ERROR_OK) + return res; + } + if (size & 0x3UL) { + res = xtensa_queue_dbg_reg_read(xtensa, NARADR_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 %d 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 = jtag_execute_queue(); + if (res != ERROR_OK) { + LOG_ERROR("Failed to exec JTAG queue!"); + return res; + } + if (size & 0x3UL) { + /* copy the last unaligned bytes */ + memcpy(buffer + size - (size & 0x3UL), 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 = jtag_execute_queue(); + 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 = jtag_execute_queue(); + if (res != ERROR_OK) + return res; + uint32_t val = buf_get_u32(tmp, 0, 32); + 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 = jtag_execute_queue(); + 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 = jtag_execute_queue(); + 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 ? NARADR_DCRSET : NARADR_DCRCLR, OCDDCR_DEBUGSWACTIVE); + xtensa_dm_queue_tdi_idle(&xtensa->dbg_mod); + int res = jtag_execute_queue(); + if (res != ERROR_OK) { + LOG_ERROR("%s: writing DCR failed!", target->cmd_name); + return ERROR_FAIL; + } + + 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; +} + +/* TODO: for now this function assumes the same endianness on the target and host */ +static int esp_xtensa_apptrace_queue_reverse_write(struct xtensa *xtensa, uint32_t bufs_num, + uint32_t buf_sz[], const uint8_t *bufs[]) +{ + int res = ERROR_OK; + uint32_t cached_bytes = 0, total_sz = 0; + union { + uint8_t data8[4]; + uint32_t data32; + } dword_cache; + + for (uint32_t i = 0; i < bufs_num; i++) + total_sz += buf_sz[i]; + if (total_sz & 0x3UL) { + cached_bytes = 4 - (total_sz & 0x3UL); + total_sz = ALIGN_UP(total_sz, 4); + } + dword_cache.data32 = 0; + xtensa_queue_dbg_reg_write(xtensa, NARADR_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(&dword_cache.data8[sizeof(uint32_t) - cached_bytes - bytes_to_cache], + cur_buf - bytes_to_cache, + bytes_to_cache); + cached_bytes += bytes_to_cache; + if (cached_bytes < sizeof(uint32_t)) + continue; + res = xtensa_queue_dbg_reg_write(xtensa, NARADR_TRAXDATA, dword_cache.data32); + if (res != ERROR_OK) + return res; + bsz -= bytes_to_cache; + cur_buf -= bytes_to_cache; + dword_cache.data32 = 0; + cached_bytes = 0; + } + /* write full dwords */ + for (unsigned int k = bsz; k >= sizeof(uint32_t); k -= sizeof(uint32_t)) { + uint32_t temp = 0; + memcpy(&temp, cur_buf - sizeof(uint32_t), sizeof(uint32_t)); + res = xtensa_queue_dbg_reg_write(xtensa, NARADR_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(&dword_cache.data8, cur_buf - to_copy, to_copy); + /* write full word of cached bytes */ + res = xtensa_queue_dbg_reg_write(xtensa, NARADR_TRAXDATA, dword_cache.data32); + if (res != ERROR_OK) + return res; + /* cache remaining bytes */ + dword_cache.data32 = 0; + cur_buf -= to_copy; + to_copy = bytes_to_cache + cached_bytes - sizeof(uint32_t); + memcpy(&dword_cache.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(&dword_cache.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; +} + +/* TODO: for now this function assumes the same endianness on the target and host */ +static int esp_xtensa_apptrace_queue_normal_write(struct xtensa *xtensa, uint32_t bufs_num, + uint32_t buf_sz[], const uint8_t *bufs[]) +{ + int res = ERROR_OK; + uint32_t cached_bytes = 0; + union { + uint8_t data8[4]; + uint32_t data32; + } dword_cache; + + /* | 1 | 2 | 1 | 2 | 4 |.......| + * | 4 | 4 | 4 | */ + dword_cache.data32 = 0; + xtensa_queue_dbg_reg_write(xtensa, NARADR_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(&dword_cache.data8[cached_bytes], cur_buf, bytes_to_cache); + cached_bytes += bytes_to_cache; + if (cached_bytes < sizeof(uint32_t)) + continue; + res = xtensa_queue_dbg_reg_write(xtensa, NARADR_TRAXDATA, dword_cache.data32); + if (res != ERROR_OK) + return res; + bsz -= bytes_to_cache; + cur_buf += bytes_to_cache; + dword_cache.data32 = 0; + cached_bytes = 0; + } + /* write full dwords */ + for (unsigned int k = 0; (k + sizeof(uint32_t)) <= bsz; k += sizeof(uint32_t)) { + uint32_t temp = 0; + memcpy(&temp, cur_buf, sizeof(uint32_t)); + res = xtensa_queue_dbg_reg_write(xtensa, NARADR_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(&dword_cache.data8[cached_bytes], cur_buf, sizeof(uint32_t) - cached_bytes); + /* write full word of cached bytes */ + res = xtensa_queue_dbg_reg_write(xtensa, NARADR_TRAXDATA, dword_cache.data32); + if (res != ERROR_OK) + return res; + /* cache remaining bytes */ + dword_cache.data32 = 0; + cur_buf += sizeof(uint32_t) - cached_bytes; + cached_bytes = bytes_to_cache + cached_bytes - sizeof(uint32_t); + memcpy(&dword_cache.data8[0], cur_buf, cached_bytes); + } else { + memcpy(&dword_cache.data8[cached_bytes], cur_buf, bytes_to_cache); + cached_bytes += bytes_to_cache; + } + } + } + if (cached_bytes) { + /* write remaining cached bytes */ + res = xtensa_queue_dbg_reg_write(xtensa, NARADR_TRAXDATA, dword_cache.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(xtensa, bufs_num, buf_sz, bufs); + else + res = esp_xtensa_apptrace_queue_normal_write(xtensa, 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 = jtag_execute_queue(); + 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 index 0000000000..0a9be731fe --- /dev/null +++ b/src/target/espressif/esp_xtensa_apptrace.h @@ -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 */ --