mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'tracing_test_fixes' into 'master'
Tracing test fixes See merge request espressif/esp-idf!51601
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*/
|
||||
@@ -75,7 +75,7 @@ static esp_err_t esp_apptrace_membufs_swap(esp_apptrace_membufs_proto_data_t *pr
|
||||
|
||||
esp_err_t res = proto->hw->swap_start(proto->state.in_block);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to swap to new block: %d", res);
|
||||
ESP_APPTRACE_LOGV("Failed to swap to new block: %s", esp_err_to_name(res));
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ static esp_err_t esp_apptrace_membufs_swap(esp_apptrace_membufs_proto_data_t *pr
|
||||
*(p - 8), *(p - 7), *(p - 6), *(p - 5), *(p - 4), *(p - 3), *(p - 2), *(p - 1));
|
||||
uint32_t sz = esp_apptrace_membufs_down_buffer_write_nolock(proto, (uint8_t *)(hdr + 1), hdr->block_sz);
|
||||
if (sz != hdr->block_sz) {
|
||||
ESP_APPTRACE_LOGE("Failed to write %" PRIu32 " bytes to down buffer (%" PRIu16 " %" PRIu32 ")!",
|
||||
ESP_APPTRACE_LOGE("Failed to write %" PRIu32 " bytes to down buffer (%" PRIu32 " %" PRIu32 ")!",
|
||||
hdr->block_sz - sz, hdr->block_sz, sz);
|
||||
}
|
||||
hdr->block_sz = 0;
|
||||
@@ -149,20 +149,18 @@ uint8_t *esp_apptrace_membufs_down_buffer_get(esp_apptrace_membufs_proto_data_t
|
||||
}
|
||||
break;
|
||||
}
|
||||
// may need to flush
|
||||
// may need to flush to expose host down-channel data
|
||||
if (proto->hw->host_data_pending()) {
|
||||
ESP_APPTRACE_LOGD("force flush");
|
||||
int res = esp_apptrace_membufs_swap_waitus(proto, tmo);
|
||||
if (res != ESP_OK) {
|
||||
ESP_APPTRACE_LOGE("Failed to switch to another block to recv data from host!");
|
||||
/*do not return error because data can be in down buffer already*/
|
||||
}
|
||||
} else {
|
||||
// check tmo only if there is no data from host
|
||||
int res = esp_apptrace_tmo_check(tmo);
|
||||
if (res != ESP_OK) {
|
||||
return NULL;
|
||||
if (res == ESP_OK) {
|
||||
continue; /* re-check rb_down */
|
||||
}
|
||||
ESP_APPTRACE_LOGE("Failed to switch to another block to recv data from host!");
|
||||
}
|
||||
/* Check the timeout even while host data is pending so a zero timeout returns immediately instead of spinning. */
|
||||
if (esp_apptrace_tmo_check(tmo) != ESP_OK) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
|
||||
@@ -380,7 +380,7 @@ void esp_panic_handler(panic_info_t *info)
|
||||
// then only print up to details. Users should be able to probe for the other information
|
||||
// in debug mode.
|
||||
#if CONFIG_ESP_DEBUG_OCDAWARE
|
||||
if (esp_cpu_dbgr_is_attached()) {
|
||||
if (esp_cpu_dbgr_is_attached() && g_panic_entry_count[esp_cpu_get_core_id()] <= 1) {
|
||||
char *panic_reason_str = NULL;
|
||||
if (info->pseudo_excause) {
|
||||
panic_reason_str = (char *)info->reason;
|
||||
|
||||
@@ -76,12 +76,13 @@ menu "ESP Trace Configuration"
|
||||
config ESP_TRACE_USJ_TX_BUFFER_SIZE
|
||||
int "TX buffer size"
|
||||
default 2048
|
||||
range 256 32768
|
||||
range 2048 32768
|
||||
help
|
||||
Size of the TX ring buffer for USB Serial JTAG trace transport.
|
||||
Larger buffer allows more trace data to be queued before blocking.
|
||||
|
||||
Note: Buffer size must be a power of 2.
|
||||
Note: Buffer size must be a power of 2. Minimum is 2048 since
|
||||
smaller buffers overflow easily on the USJ transport.
|
||||
|
||||
endmenu
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
* is responsible for serializing access using esp_trace_lock_init(), esp_trace_lock_take(),
|
||||
* and esp_trace_lock_give(). All transport operations (read, write, flush_nolock) are
|
||||
* invoked while the encoder holds the lock, so no transport-level locking is required.
|
||||
*
|
||||
* Logging: use ESP_EARLY_LOGx only, ESP_LOGx is not safe in ISR context.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
@@ -29,6 +31,7 @@
|
||||
#include "esp_cpu.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_rom_caps.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "hal/usb_serial_jtag_ll.h"
|
||||
@@ -51,7 +54,6 @@ static const char *TAG = "usj_transport";
|
||||
*/
|
||||
typedef enum {
|
||||
USJ_TX_IDLE,
|
||||
USJ_TX_SHORT_PENDING,
|
||||
USJ_TX_ZLP_PENDING,
|
||||
} usj_tx_state_t;
|
||||
|
||||
@@ -67,11 +69,8 @@ typedef struct {
|
||||
uint32_t flush_thresh; ///< Flush threshold in bytes
|
||||
} usj_ctx_t;
|
||||
|
||||
/*
|
||||
* flush_nolock() runs with interrupts masked, so normal flushes use a short
|
||||
* fixed timeout even if an encoder requests a longer one.
|
||||
*/
|
||||
#define USJ_FLUSH_TIMEOUT_US (1000) // 1 ms
|
||||
/* Default flush timeout, used on non-masked paths (e.g. panic handler). */
|
||||
#define USJ_FLUSH_TIMEOUT_US (1000000) // 1 s
|
||||
#define USJ_FLUSH_THRESH_BYTES (0) // 0 bytes
|
||||
#define USJ_FLUSH_MAX_INTR_MASKED_US (2000) // 2 ms
|
||||
#define USJ_FLUSH_POLL_STEP_US (100) // delay between no-progress polls
|
||||
@@ -84,7 +83,7 @@ typedef struct {
|
||||
static uint32_t usj_write_fifo(usj_ctx_t *ctx, esp_trace_rb_t *rb)
|
||||
{
|
||||
if (!usb_serial_jtag_ll_txfifo_writable()) {
|
||||
/* FIFO is full, no blocking */
|
||||
/* Previous packet is not sent yet */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -97,17 +96,21 @@ static uint32_t usj_write_fifo(usj_ctx_t *ctx, esp_trace_rb_t *rb)
|
||||
uint32_t written = usb_serial_jtag_ll_write_txfifo(ptr, to_send);
|
||||
esp_trace_rb_consume(rb, written);
|
||||
|
||||
ctx->tx_state = usb_serial_jtag_ll_txfifo_writable() ? USJ_TX_SHORT_PENDING : USJ_TX_ZLP_PENDING;
|
||||
/* Checked before wr_done, which also clears the writable flag */
|
||||
ctx->tx_state = usb_serial_jtag_ll_txfifo_writable() ? USJ_TX_IDLE : USJ_TX_ZLP_PENDING;
|
||||
|
||||
/* Send the packet. A no-op if the HW already flushed a full FIFO. */
|
||||
usb_serial_jtag_ll_txfifo_flush();
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
static uint32_t usj_fill_txfifo(usj_ctx_t *ctx, bool commit_short)
|
||||
static uint32_t usj_fill_txfifo(usj_ctx_t *ctx)
|
||||
{
|
||||
esp_trace_rb_t *rb = &ctx->tx_ring;
|
||||
uint32_t total_written = 0;
|
||||
|
||||
while (esp_trace_rb_data_len(rb) > 0 && usb_serial_jtag_ll_txfifo_writable()) {
|
||||
while (esp_trace_rb_data_len(rb) > 0) {
|
||||
uint32_t written = usj_write_fifo(ctx, rb);
|
||||
if (written == 0) {
|
||||
break;
|
||||
@@ -115,11 +118,6 @@ static uint32_t usj_fill_txfifo(usj_ctx_t *ctx, bool commit_short)
|
||||
total_written += written;
|
||||
}
|
||||
|
||||
if (commit_short && ctx->tx_state == USJ_TX_SHORT_PENDING) {
|
||||
usb_serial_jtag_ll_txfifo_flush();
|
||||
ctx->tx_state = USJ_TX_IDLE;
|
||||
}
|
||||
|
||||
return total_written;
|
||||
}
|
||||
|
||||
@@ -127,7 +125,15 @@ static void usj_read_rx_fifo(usj_ctx_t *ctx)
|
||||
{
|
||||
uint8_t tmp[USJ_HW_FIFO_SIZE];
|
||||
while (usb_serial_jtag_ll_rxfifo_data_available()) {
|
||||
uint32_t n = usb_serial_jtag_ll_read_rxfifo(tmp, sizeof(tmp));
|
||||
/* Read only what fits, the rest stays in the HW FIFO */
|
||||
uint32_t space = esp_trace_rb_free_len(&ctx->rx_ring);
|
||||
if (space == 0) {
|
||||
break;
|
||||
}
|
||||
if (space > sizeof(tmp)) {
|
||||
space = sizeof(tmp);
|
||||
}
|
||||
uint32_t n = usb_serial_jtag_ll_read_rxfifo(tmp, space);
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
@@ -185,22 +191,23 @@ static esp_err_t usj_init(esp_trace_transport_t *tp, const void *tp_cfg)
|
||||
/* Initialize TX ring buffer */
|
||||
esp_err_t ret = esp_trace_rb_init(&ctx->tx_ring, CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize TX ring buffer");
|
||||
ESP_EARLY_LOGE(TAG, "Failed to initialize TX ring buffer");
|
||||
goto err_ctx;
|
||||
}
|
||||
|
||||
/* Initialize RX ring buffer to capture host commands */
|
||||
ret = esp_trace_rb_init(&ctx->rx_ring, USJ_RX_BUFFER_SIZE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize RX ring buffer");
|
||||
ESP_EARLY_LOGE(TAG, "Failed to initialize RX ring buffer");
|
||||
goto err_tx_ring;
|
||||
}
|
||||
|
||||
#if ESP_ROM_HAS_ETS_PRINTF_BUG
|
||||
/* Make sure no printf output is sent to USB-Serial-JTAG */
|
||||
#if ESP_ROM_HAS_ETS_PRINTF_BUG
|
||||
extern bool g_usb_print;
|
||||
g_usb_print = false;
|
||||
#endif
|
||||
esp_rom_install_channel_putc(2, NULL);
|
||||
|
||||
}
|
||||
|
||||
@@ -267,13 +274,15 @@ static esp_err_t usj_write(esp_trace_transport_t *tp, const void *data, size_t s
|
||||
/* Read any new RX data into the RX ring buffer to avoid losing host commands in case of heavy trace output */
|
||||
usj_read_rx_fifo(ctx);
|
||||
|
||||
/* Add data to TX ring buffer */
|
||||
esp_trace_rb_put(rb, (const uint8_t *)data, size);
|
||||
esp_err_t ret = esp_trace_rb_put(rb, (const uint8_t *)data, size);
|
||||
if (ret != ESP_OK) {
|
||||
/* Free what the HW can take, then retry before dropping the record */
|
||||
usj_fill_txfifo(ctx);
|
||||
ret = esp_trace_rb_put(rb, (const uint8_t *)data, size);
|
||||
}
|
||||
usj_fill_txfifo(ctx);
|
||||
|
||||
/* Try to move data to HW FIFO immediately, without forcing a short packet. */
|
||||
usj_fill_txfifo(ctx, false);
|
||||
|
||||
return ESP_OK;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t usj_down_buffer_config(esp_trace_transport_t *tp, uint8_t *buf, uint32_t size)
|
||||
@@ -292,7 +301,7 @@ static esp_err_t usj_flush_with_timeout(usj_ctx_t *ctx, uint32_t tmo_us)
|
||||
esp_trace_rb_t *rb = &ctx->tx_ring;
|
||||
|
||||
uint32_t pending = esp_trace_rb_data_len(rb);
|
||||
if (pending < ctx->flush_thresh && ctx->tx_state == USJ_TX_IDLE) {
|
||||
if ((pending == 0 || pending < ctx->flush_thresh) && ctx->tx_state == USJ_TX_IDLE) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -300,9 +309,11 @@ static esp_err_t usj_flush_with_timeout(usj_ctx_t *ctx, uint32_t tmo_us)
|
||||
esp_trace_tmo_init(&timeout, tmo_us);
|
||||
|
||||
while (esp_trace_rb_data_len(rb) > 0 || ctx->tx_state != USJ_TX_IDLE) {
|
||||
uint32_t written = usj_fill_txfifo(ctx, true);
|
||||
uint32_t written = usj_fill_txfifo(ctx);
|
||||
|
||||
if (ctx->tx_state == USJ_TX_ZLP_PENDING && usb_serial_jtag_ll_txfifo_writable()) {
|
||||
/* Ring is empty: send a ZLP if the last packet filled the endpoint */
|
||||
if (esp_trace_rb_data_len(rb) == 0 && ctx->tx_state == USJ_TX_ZLP_PENDING
|
||||
&& usb_serial_jtag_ll_txfifo_writable()) {
|
||||
usb_serial_jtag_ll_txfifo_flush();
|
||||
ctx->tx_state = USJ_TX_IDLE;
|
||||
continue;
|
||||
@@ -363,7 +374,7 @@ static esp_err_t usj_set_config(esp_trace_transport_t *tp, esp_trace_transport_c
|
||||
ctx->flush_thresh = *(const uint32_t *)value;
|
||||
return ESP_OK;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Key %d is not supported", key);
|
||||
ESP_EARLY_LOGE(TAG, "Key %d is not supported", key);
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
@@ -387,7 +398,7 @@ static esp_err_t usj_get_config(esp_trace_transport_t *tp, esp_trace_transport_c
|
||||
*(uint32_t *)value = ctx->flush_thresh;
|
||||
return ESP_OK;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Key %d is not supported", key);
|
||||
ESP_EARLY_LOGE(TAG, "Key %d is not supported", key);
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,12 +157,22 @@ static inline uint32_t esp_trace_rb_data_len(const esp_trace_rb_t *rb)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write data into the ring buffer (overwrites oldest data if full)
|
||||
* @brief Get number of bytes that can still be written to the ring buffer
|
||||
*/
|
||||
static inline uint32_t esp_trace_rb_free_len(const esp_trace_rb_t *rb)
|
||||
{
|
||||
return rb->max_size - rb->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write data into the ring buffer, all of it or none
|
||||
*
|
||||
* Data already in the buffer is never overwritten.
|
||||
*
|
||||
* @param rb Ring buffer
|
||||
* @param data Source data
|
||||
* @param len Number of bytes to write
|
||||
* @return ESP_OK always (data is always accepted; oldest data may be dropped)
|
||||
* @return ESP_OK on success, ESP_ERR_NO_MEM if the data does not fit
|
||||
*/
|
||||
esp_err_t esp_trace_rb_put(esp_trace_rb_t *rb, const uint8_t *data, uint32_t len);
|
||||
|
||||
|
||||
@@ -195,10 +195,10 @@ esp_err_t esp_trace_rb_init(esp_trace_rb_t *rb, uint32_t size)
|
||||
|
||||
esp_err_t esp_trace_rb_put(esp_trace_rb_t *rb, const uint8_t *data, uint32_t len)
|
||||
{
|
||||
/* Drop oldest data if needed to make room */
|
||||
uint32_t free_len = rb->max_size - rb->count;
|
||||
if (len > free_len) {
|
||||
rb_advance_tail(rb, len - free_len);
|
||||
/* Drop the new record instead of overwriting old ones, so that the records
|
||||
* in the buffer stay complete */
|
||||
if (len > rb->max_size - rb->count) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
uint32_t head = rb->head;
|
||||
|
||||
@@ -271,6 +271,14 @@ class OpenOCD:
|
||||
return ''
|
||||
return to_str(resp)
|
||||
|
||||
def consume_output(self, duration: float) -> None:
|
||||
if self.telnet is None:
|
||||
return
|
||||
end = time.time() + duration
|
||||
while time.time() < end:
|
||||
self.telnet.read_very_eager()
|
||||
time.sleep(0.05)
|
||||
|
||||
def apptrace_wait_stop(self, timeout: int = 30) -> None:
|
||||
stopped = False
|
||||
end_before = time.time() + timeout
|
||||
|
||||
@@ -36,6 +36,9 @@ void init_trace_lib(esp_trace_encoder_t *enc);
|
||||
void trace_lib_start(void);
|
||||
void trace_lib_stop(void);
|
||||
|
||||
/* @brief Get the number of trace lines written to and dropped by the transport */
|
||||
void trace_lib_get_stats(uint32_t *written, uint32_t *dropped);
|
||||
|
||||
/* Hook implementations — defined in trace_FreeRTOS.c.
|
||||
* Kept void*-typed to avoid depending on FreeRTOS types in this header. */
|
||||
void trace_lib_task_switched_in(void);
|
||||
|
||||
@@ -85,14 +85,6 @@ static esp_err_t stop(esp_trace_encoder_t *enc)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t flush(esp_trace_encoder_t *enc)
|
||||
{
|
||||
if (!enc || !enc->tp || !enc->tp->vt->flush_nolock) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
return enc->tp->vt->flush_nolock(enc->tp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Panic handler
|
||||
*
|
||||
@@ -104,7 +96,11 @@ static esp_err_t flush(esp_trace_encoder_t *enc)
|
||||
static void panic_handler(esp_trace_encoder_t *enc, const void *info)
|
||||
{
|
||||
(void)info;
|
||||
flush(enc);
|
||||
|
||||
/* No lock here, the panicking core may already hold it */
|
||||
if (enc && enc->tp && enc->tp->vt->flush_nolock) {
|
||||
enc->tp->vt->flush_nolock(enc->tp);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int take_lock(esp_trace_encoder_t *enc, uint32_t tmo_us)
|
||||
@@ -127,6 +123,34 @@ static void give_lock(esp_trace_encoder_t *enc, unsigned int int_state)
|
||||
esp_trace_lock_give(&ctx->lock);
|
||||
}
|
||||
|
||||
/* Total time for the flush, split over several flush_nolock() calls */
|
||||
#define EXT_TRACE_LIB_FLUSH_TMO_US (1000000)
|
||||
|
||||
static esp_err_t flush(esp_trace_encoder_t *enc)
|
||||
{
|
||||
if (!enc || !enc->tp || !enc->tp->vt->flush_nolock) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/* One call may be too short to send all the data, so repeat it and release
|
||||
* the lock in between to keep interrupts enabled */
|
||||
esp_trace_tmo_t tmo;
|
||||
esp_trace_tmo_init(&tmo, EXT_TRACE_LIB_FLUSH_TMO_US);
|
||||
|
||||
esp_err_t err;
|
||||
do {
|
||||
unsigned int int_state = take_lock(enc, ESP_TRACE_TMO_INFINITE);
|
||||
err = enc->tp->vt->flush_nolock(enc->tp);
|
||||
give_lock(enc, int_state);
|
||||
|
||||
if (err != ESP_ERR_TIMEOUT) {
|
||||
break; /* done, or an error that repeating cannot fix */
|
||||
}
|
||||
} while (esp_trace_tmo_check(&tmo) == ESP_OK);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static const esp_trace_encoder_vtable_t s_ext_trace_lib_vt = {
|
||||
.init = init,
|
||||
.write = write,
|
||||
|
||||
@@ -35,6 +35,8 @@ static esp_trace_encoder_t *s_enc = NULL;
|
||||
static uint32_t s_ts_freq_hz = 1000000; /* default assume 1 MHz */
|
||||
static uint32_t s_last_ts = 0;
|
||||
static volatile bool s_enabled = false;
|
||||
static uint32_t s_written = 0;
|
||||
static uint32_t s_dropped = 0;
|
||||
|
||||
void init_trace_lib(esp_trace_encoder_t *enc)
|
||||
{
|
||||
@@ -87,12 +89,26 @@ static void encode(const char *type, const char *detail)
|
||||
if (n >= (int)sizeof(line)) {
|
||||
n = (int)sizeof(line) - 1;
|
||||
}
|
||||
esp_trace_write(s_esp_trace_handle, line, (size_t)n, 0);
|
||||
if (esp_trace_write(s_esp_trace_handle, line, (size_t)n, 0) == ESP_OK) {
|
||||
s_written++;
|
||||
} else {
|
||||
s_dropped++; /* transport buffer was full, line dropped */
|
||||
}
|
||||
}
|
||||
|
||||
s_enc->vt->give_lock(s_enc, int_state);
|
||||
}
|
||||
|
||||
void trace_lib_get_stats(uint32_t *written, uint32_t *dropped)
|
||||
{
|
||||
if (written) {
|
||||
*written = s_written;
|
||||
}
|
||||
if (dropped) {
|
||||
*dropped = s_dropped;
|
||||
}
|
||||
}
|
||||
|
||||
void trace_lib_task_switched_in(void)
|
||||
{
|
||||
TaskHandle_t h = xTaskGetCurrentTaskHandle();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -13,6 +14,7 @@
|
||||
#include "freertos/queue.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_trace.h"
|
||||
#include "trace_FreeRTOS.h"
|
||||
|
||||
static const char *TAG = "main";
|
||||
|
||||
@@ -70,7 +72,16 @@ void app_main(void)
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
esp_trace_stop();
|
||||
esp_trace_flush();
|
||||
esp_err_t err = esp_trace_flush();
|
||||
|
||||
/* Logged from task context, so it goes to the console UART and not to the
|
||||
* trace port. Shows whether the device sent the trace data or not. */
|
||||
uint32_t written = 0;
|
||||
uint32_t dropped = 0;
|
||||
trace_lib_get_stats(&written, &dropped);
|
||||
ESP_LOGI(TAG, "Trace flush: %s, host connected: %d, lines written: %" PRIu32 ", dropped: %" PRIu32,
|
||||
esp_err_to_name(err), (int)esp_trace_is_host_connected(esp_trace_get_active_handle()),
|
||||
written, dropped);
|
||||
|
||||
ESP_LOGI(TAG, "End of trace session");
|
||||
}
|
||||
|
||||
@@ -76,7 +76,6 @@ def _validate_trace_data(trace_log_path: str) -> None:
|
||||
|
||||
def _capture_trace(ser: serial.Serial, trace_log_path: str, capture_s: float = 5.0) -> None:
|
||||
"""Capture trace output from the USB-Serial-JTAG endpoint."""
|
||||
ser.reset_input_buffer()
|
||||
with open(trace_log_path, 'w+b') as f:
|
||||
end_time = time.time() + capture_s
|
||||
while time.time() < end_time:
|
||||
@@ -106,10 +105,13 @@ def _capture_trace(ser: serial.Serial, trace_log_path: str, capture_s: float = 5
|
||||
def test_esp_trace_ext_lib_usj(dut: IdfDut) -> None:
|
||||
dut.expect('Start of trace session', timeout=5)
|
||||
|
||||
time.sleep(1) # wait for USJ port to be ready
|
||||
# Open the port before the DUT starts tracing
|
||||
usj_port = '/dev/serial_ports/ttyACM-esp32'
|
||||
ser = serial.Serial(usj_port, baudrate=1000000, timeout=10)
|
||||
trace_log_path = os.path.join(dut.logdir, 'ext_trace.log')
|
||||
time.sleep(1) # Wait for the USJ port to be ready
|
||||
with serial.Serial(usj_port, baudrate=1000000, timeout=10) as ser:
|
||||
# Discard data from before this test, the DUT is not tracing yet
|
||||
ser.reset_input_buffer()
|
||||
_capture_trace(ser, trace_log_path)
|
||||
|
||||
_capture_trace(ser, trace_log_path)
|
||||
_validate_trace_data(trace_log_path)
|
||||
|
||||
@@ -3,4 +3,4 @@ CONFIG_ESP_TRACE_LIB_EXTERNAL=y
|
||||
CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y
|
||||
CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE=32768
|
||||
CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE=16384
|
||||
|
||||
@@ -129,7 +129,9 @@ def _test_function_tracing_jtag(openocd_dut: 'OpenOCD', idf_path: str, dut: IdfD
|
||||
dut.expect(re.compile(rb'function-tracing: workload iteration \d+'), timeout=30)
|
||||
|
||||
# Let function-trace samples accumulate while recording.
|
||||
time.sleep(1)
|
||||
# Keep reading telnet so OpenOCD's blocking log writes don't stall its
|
||||
# main loop and leave the 'stop' below unserviced.
|
||||
openocd.consume_output(1)
|
||||
openocd.write('esp sysview_mcore stop')
|
||||
openocd.apptrace_wait_stop()
|
||||
|
||||
|
||||
@@ -16,18 +16,60 @@ if typing.TYPE_CHECKING:
|
||||
from conftest import OpenOCD
|
||||
|
||||
|
||||
STOP_EVENT_ID = 0x0B # SYSVIEW_EVTID_TRACE_STOP
|
||||
|
||||
|
||||
def _assert_has_stop_record(segment: bytes, label: str) -> None:
|
||||
"""Assert a SysView data segment ends with a TRACE_STOP record.
|
||||
|
||||
A STOP record is the STOP event ID followed by a variable-length timestamp
|
||||
delta. Walk back over the trailing continuation bytes (0x80 bit set) to find
|
||||
the event ID, since its offset is not fixed.
|
||||
"""
|
||||
size = len(segment)
|
||||
assert size >= 2, f'{label}: segment too small to contain STOP record'
|
||||
i = size - 2
|
||||
while i >= 0 and (segment[i] & 0x80):
|
||||
i -= 1
|
||||
assert i >= 0 and segment[i] == STOP_EVENT_ID, f'{label}: does not end with a TRACE_STOP record'
|
||||
|
||||
|
||||
def _split_mcore_file(content: bytes) -> list[bytes]:
|
||||
"""Split an ``esp sysview_mcore`` combined file into per-core data segments.
|
||||
|
||||
Layout is an ASCII comment header followed by core0 data then core1 data::
|
||||
|
||||
; ...
|
||||
; Offset Core0 0
|
||||
; Offset Core1 <size of core0 data in bytes>
|
||||
;
|
||||
<core0 data><core1 data>
|
||||
|
||||
The header ends at the first line that does not start with ';', (the data
|
||||
begins with the all-zero sync sequence).
|
||||
"""
|
||||
header = re.match(rb'(?:;[^\n]*\n)*', content)
|
||||
assert header is not None
|
||||
pos = header.end()
|
||||
m = re.search(rb';\s*Offset\s+Core1\s+(\d+)', content[:pos])
|
||||
assert m is not None, 'mcore file header missing "Offset Core1"'
|
||||
offset_core1 = int(m.group(1))
|
||||
core0 = content[pos : pos + offset_core1]
|
||||
core1 = content[pos + offset_core1 :]
|
||||
return [core0, core1]
|
||||
|
||||
|
||||
def _validate_trace_data(trace_log: str, target: str, dual_core: bool = False, is_uart: bool = False) -> None:
|
||||
"""Validate SysView trace data in a single trace log file.
|
||||
|
||||
Args:
|
||||
trace_log: Path to the trace log file
|
||||
target: Target chip name (e.g., 'esp32', 'esp32s3')
|
||||
dual_core: If True, expect a per-core description block for both cores
|
||||
(the ``esp sysview_mcore`` multi-core capture embeds one per core)
|
||||
dual_core: If True, this is an 'esp sysview_mcore' combined file that
|
||||
embeds core0 and core1 data segments (see '_split_mcore_file');
|
||||
each segment is validated separately.
|
||||
is_uart: If True, also validate STOP record at end of file
|
||||
"""
|
||||
STOP_EVENT_ID = 0x0B # SYSVIEW_EVTID_TRACE_STOP
|
||||
|
||||
with open(trace_log, 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
@@ -35,16 +77,13 @@ def _validate_trace_data(trace_log: str, target: str, dual_core: bool = False, i
|
||||
search_str = f'N=FreeRTOS Application,D={target},C=core{idx},O=FreeRTOS'.encode()
|
||||
assert search_str in content, f'SysView core{idx} trace data not found in {trace_log}'
|
||||
|
||||
# The file must end with a TRACE_STOP record: the STOP event ID
|
||||
# followed by a variable-length timestamp delta. Walk back
|
||||
# over the trailing continuation bytes (0x80 bit set)
|
||||
# to find the event ID, since its offset is not fixed.
|
||||
size = len(content)
|
||||
assert size >= 2, 'Trace file too small to contain STOP record'
|
||||
i = size - 2
|
||||
while i >= 0 and (content[i] & 0x80):
|
||||
i -= 1
|
||||
assert i >= 0 and content[i] == STOP_EVENT_ID, 'STOP record does not start with STOP eventID'
|
||||
if dual_core:
|
||||
# Seek to each core's segment via the header offset
|
||||
# and require each to end with its own TRACE_STOP record.
|
||||
for idx, segment in enumerate(_split_mcore_file(content)):
|
||||
_assert_has_stop_record(segment, f'core{idx}')
|
||||
else:
|
||||
_assert_has_stop_record(content, 'trace')
|
||||
|
||||
|
||||
def _capture_sysview_trace(ser: serial.Serial, trace_log_path: str) -> None:
|
||||
@@ -144,8 +183,9 @@ def _test_sysview_tracing_jtag(openocd_dut: 'OpenOCD', dut: IdfDut) -> None:
|
||||
dut.expect('example: Created task') # dut has been restarted by gdb since the last dut.expect()
|
||||
dut_expect_task_event()
|
||||
|
||||
# Do a sleep while sysview samples are captured.
|
||||
time.sleep(3)
|
||||
# Capture sysview samples and keep reading telnet so OpenOCD's blocking log writes don't stall its
|
||||
# main loop and leave the 'stop' below unserviced.
|
||||
openocd.consume_output(3)
|
||||
openocd.write('esp sysview_mcore stop')
|
||||
openocd.apptrace_wait_stop()
|
||||
|
||||
@@ -202,7 +242,7 @@ def test_sysview_tracing_uart_c2(dut: IdfDut) -> None:
|
||||
def test_sysview_tracing_usj_serial(dut: IdfDut) -> None:
|
||||
time.sleep(1) # wait for USJ port to be ready
|
||||
usj_port = '/dev/serial_ports/ttyACM-esp32'
|
||||
ser = serial.Serial(usj_port, baudrate=1000000, timeout=10)
|
||||
trace_log = os.path.join(dut.logdir, 'sys_log_usj.svdat') # pylint: disable=protected-access
|
||||
_capture_sysview_trace(ser, trace_log)
|
||||
_validate_trace_data(trace_log, dut.target, is_uart=True)
|
||||
with serial.Serial(usj_port, baudrate=1000000, timeout=10) as ser:
|
||||
_capture_sysview_trace(ser, trace_log)
|
||||
_validate_trace_data(trace_log, dut.target, is_uart=True)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
||||
CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y
|
||||
CONFIG_USE_CUSTOM_EVENT_ID=y
|
||||
CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE=16384
|
||||
|
||||
Reference in New Issue
Block a user