From b0896d9171235d682c90e330e88ffb82adadccfe Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Tue, 3 Feb 2026 14:11:43 +0100 Subject: [PATCH 1/5] feat(esp_trace): add usb-serial-jtag as a transport channel --- components/esp_trace/CMakeLists.txt | 11 +- components/esp_trace/Kconfig | 26 ++ .../adapter_transport_usb_serial_jtag.c | 361 ++++++++++++++++++ .../esp_trace/include/esp_trace_types.h | 1 + components/esp_trace/include/esp_trace_util.h | 83 +++- components/esp_trace/linker.lf | 2 + components/esp_trace/src/ports/port_utils.c | 105 ++++- .../sysview_tracing/main/sysview_tracing.c | 18 +- .../sysview_tracing/pytest_sysview_tracing.py | 13 + .../sysview_tracing/sdkconfig.ci.sysview_jtag | 1 + .../sysview_tracing/sdkconfig.ci.sysview_uart | 1 + .../sysview_tracing/sdkconfig.ci.sysview_usj | 3 + .../system/sysview_tracing/sdkconfig.defaults | 1 - 13 files changed, 620 insertions(+), 6 deletions(-) create mode 100644 components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c create mode 100644 examples/system/sysview_tracing/sdkconfig.ci.sysview_usj diff --git a/components/esp_trace/CMakeLists.txt b/components/esp_trace/CMakeLists.txt index 1e26e6ce7fb..876cdf00e77 100644 --- a/components/esp_trace/CMakeLists.txt +++ b/components/esp_trace/CMakeLists.txt @@ -14,13 +14,22 @@ if(CONFIG_ESP_TRACE_ENABLE) if(CONFIG_ESP_TRACE_TRANSPORT_APPTRACE) list(APPEND srcs "adapters/transport/adapter_transport_apptrace.c") endif() + + if(CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG) + list(APPEND srcs "adapters/transport/adapter_transport_usb_serial_jtag.c") + endif() endif() set(includes "include" ) -set(priv_requires "esp_driver_gptimer") +set(priv_requires + "esp_driver_gptimer" + "esp_hal_usb" + "esp_driver_usb_serial_jtag" + "esp_timer" +) set(priv_includes "") set(requires "app_trace") diff --git a/components/esp_trace/Kconfig b/components/esp_trace/Kconfig index 944ac322fb0..f37761be603 100644 --- a/components/esp_trace/Kconfig +++ b/components/esp_trace/Kconfig @@ -34,6 +34,16 @@ menu "ESP Trace Configuration" config ESP_TRACE_TRANSPORT_APPTRACE bool "ESP-IDF apptrace" + config ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG + bool "USB Serial JTAG" + depends on SOC_USB_SERIAL_JTAG_SUPPORTED && !ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED + help + Use USB Serial JTAG peripheral as trace transport. + + Note: This option is not available when USB Serial JTAG is used as + primary or secondary console (ESP_CONSOLE_USB_SERIAL_JTAG or + ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG). + config ESP_TRACE_TRANSPORT_EXTERNAL bool "External transport from component registry" depends on !ESP_TRACE_LIB_NONE @@ -48,6 +58,7 @@ menu "ESP Trace Configuration" config ESP_TRACE_TRANSPORT_NAME string default "apptrace" if ESP_TRACE_TRANSPORT_APPTRACE + default "usb_serial_jtag" if ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG default "ext" if ESP_TRACE_TRANSPORT_EXTERNAL default "none" if ESP_TRACE_TRANSPORT_NONE @@ -59,6 +70,21 @@ menu "ESP Trace Configuration" rsource "$IDF_PATH/components/app_trace/Kconfig.apptrace" + menu "USB Serial JTAG Trace Configuration" + depends on ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG + + config ESP_TRACE_USJ_TX_BUFFER_SIZE + int "TX buffer size" + default 2048 + range 256 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. + + endmenu + choice ESP_TRACE_TIMESTAMP_SOURCE depends on ESP_TRACE_ENABLE prompt "Trace timestamp source" diff --git a/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c b/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c new file mode 100644 index 00000000000..b6d2415b854 --- /dev/null +++ b/components/esp_trace/adapters/transport/adapter_transport_usb_serial_jtag.c @@ -0,0 +1,361 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief USB-Serial-JTAG transport adapter for esp_trace + * + * This implementation uses LL (Low-Level) functions directly instead of the + * high-level USB-Serial-JTAG driver to avoid FreeRTOS primitives, which would + * cause deadlocks when tracing FreeRTOS operations with SystemView. + * + * Locking: This transport does not implement its own lock. The encoder (e.g. sysview) + * 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. + */ + +#include +#include +#include +#include +#include + +#include "sdkconfig.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_cpu.h" +#include "esp_attr.h" +#include "esp_rom_caps.h" +#include "esp_heap_caps.h" +#include "esp_private/periph_ctrl.h" +#include "hal/usb_serial_jtag_ll.h" +#include "soc/usb_serial_jtag_struct.h" +#include "driver/usb_serial_jtag.h" +#include "esp_trace_registry.h" +#include "esp_trace_port_transport.h" +#include "esp_trace_types.h" +#include "esp_trace_util.h" + +static const char *TAG = "usj_transport"; + +/* Transport context */ +typedef struct { + int inited; ///< Initialization flag (bitmask per core) + esp_trace_rb_t tx_ring; ///< TX ring buffer + esp_trace_rb_t rx_ring; ///< RX ring buffer + + /* Flush configuration */ + uint32_t flush_tmo; ///< Flush timeout in microseconds + uint32_t flush_thresh; ///< Flush threshold in bytes +} usj_ctx_t; + +#define USJ_FLUSH_TIMEOUT_US (1000000) // 1 second +#define USJ_FLUSH_THRESH_BYTES (0) // 0 bytes + +/* USB Serial JTAG hardware FIFO size (RX and TX) is 64 bytes (USB FS bulk endpoint max packet size) */ +#define USJ_HW_FIFO_SIZE (64) +#define USJ_RX_BUFFER_SIZE USJ_HW_FIFO_SIZE + +/* ----------------------- HW FIFO Helpers ----------------------- */ +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 */ + return 0; + } + + const uint8_t *ptr; + uint32_t to_send = esp_trace_rb_peek_contiguous(rb, &ptr); + if (to_send == 0) { + return 0; + } + + uint32_t written = usb_serial_jtag_ll_write_txfifo(ptr, to_send); + esp_trace_rb_consume(rb, written); + + /* Flush to send data or zero-byte packet to end USB transfer */ + usb_serial_jtag_ll_txfifo_flush(); + + return written; +} + +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)); + if (n == 0) { + break; + } + esp_trace_rb_put(&ctx->rx_ring, tmp, n); + } +} + +/* ----------------------- Transport Functions ----------------------- */ +_Static_assert((CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE & (CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE - 1)) == 0, + "CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE must be a power of 2"); + +static esp_err_t usj_init(esp_trace_transport_t *tp, const void *tp_cfg) +{ + (void)tp_cfg; + + if (!tp) { + return ESP_ERR_INVALID_ARG; + } + + /* Create context if not already done */ + if (!tp->ctx) { + usj_ctx_t *ctx = heap_caps_calloc(1, sizeof(*ctx), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (!ctx) { + return ESP_ERR_NO_MEM; + } + tp->ctx = ctx; + } + + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + int core_id = esp_cpu_get_core_id(); + + /* Only do main setup on core 0 */ + if (core_id == 0) { + /* Set default flush configuration */ + ctx->flush_tmo = USJ_FLUSH_TIMEOUT_US; + ctx->flush_thresh = USJ_FLUSH_THRESH_BYTES; + + /* Enable USB-Serial-JTAG peripheral module clock */ + PERIPH_RCC_ATOMIC() { + usb_serial_jtag_ll_enable_bus_clock(true); + } + + /* Configure USB PHY */ +#if USB_SERIAL_JTAG_LL_EXT_PHY_SUPPORTED + usb_serial_jtag_ll_phy_enable_external(false); /* Use internal PHY */ + usb_serial_jtag_ll_phy_enable_pad(true); /* Enable USB PHY pads */ +#else + usb_serial_jtag_ll_phy_set_defaults(); /* Set default PHY values */ +#endif + + /* Disable RX and TX interrupts */ + usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY + | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); + + /* 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"); + 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"); + goto err_tx_ring; + } + +#if ESP_ROM_HAS_ETS_PRINTF_BUG + /* Make sure no printf output is sent to USB-Serial-JTAG */ + extern bool g_usb_print; + g_usb_print = false; +#endif + + } + + ctx->inited |= (1 << core_id); + + return ESP_OK; + +err_tx_ring: + heap_caps_free(ctx->tx_ring.buffer); +err_ctx: + heap_caps_free(ctx); + tp->ctx = NULL; + return ESP_FAIL; +} + +static esp_err_t usj_read(esp_trace_transport_t *tp, void *data, size_t *size, uint32_t tmo) +{ + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + + if (!data || !size || *size == 0) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t *buf = data; + uint32_t req_size = *size; + uint32_t total_read = 0; + + esp_trace_tmo_t timeout; + esp_trace_tmo_init(&timeout, tmo); + + /* First read any pending RX data from the RX ring buffer */ + total_read = esp_trace_rb_get(&ctx->rx_ring, buf, req_size); + + while (total_read < req_size) { + /* Try to read from HW RX FIFO directly */ + if (usb_serial_jtag_ll_rxfifo_data_available()) { + uint32_t to_read = req_size - total_read; + uint32_t read = usb_serial_jtag_ll_read_rxfifo(buf + total_read, to_read); + total_read += read; + continue; + } + + if (esp_trace_tmo_check(&timeout) != ESP_OK) { + break; + } + esp_rom_delay_us(100); + } + + *size = total_read; + return (total_read > 0) ? ESP_OK : ESP_ERR_TIMEOUT; +} + +static esp_err_t usj_write(esp_trace_transport_t *tp, const void *data, size_t size, uint32_t tmo) +{ + (void)tmo; /* Write is non-blocking via ring buffer */ + + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + esp_trace_rb_t *rb = &ctx->tx_ring; + + if (!data || size == 0) { + return ESP_ERR_INVALID_ARG; + } + + /* 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); + + /* Try to flush some data to HW FIFO immediately (non-blocking) */ + while (esp_trace_rb_data_len(rb) > 0) { + if (usj_write_fifo(ctx, rb) == 0) { + break; /* FIFO full, will be drained on next write or flush */ + } + } + + return ESP_OK; +} + +static esp_err_t usj_down_buffer_config(esp_trace_transport_t *tp, uint8_t *buf, uint32_t size) +{ + (void)tp; + (void)buf; + (void)size; + + /* No action needed - data was already read in get function */ + + return ESP_OK; +} + +static esp_err_t usj_flush_nolock(esp_trace_transport_t *tp) +{ + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + esp_trace_rb_t *rb = &ctx->tx_ring; + + uint32_t pending = esp_trace_rb_data_len(rb); + if (pending < ctx->flush_thresh) { + return ESP_OK; + } + + esp_trace_tmo_t timeout; + esp_trace_tmo_init(&timeout, ctx->flush_tmo); + + /* Drain ring buffer to HW FIFO */ + while (esp_trace_rb_data_len(rb) > 0) { + usj_write_fifo(ctx, rb); + if (esp_trace_tmo_check(&timeout) != ESP_OK) { + return ESP_ERR_TIMEOUT; + } + esp_rom_delay_us(100); + } + + return ESP_OK; +} + +static bool usj_is_host_connected(esp_trace_transport_t *tp) +{ + (void)tp; + return usb_serial_jtag_is_connected(); +} + +static esp_trace_link_types_t usj_get_link_type(esp_trace_transport_t *tp) +{ + (void)tp; + return ESP_TRACE_LINK_USB_SERIAL_JTAG; +} + +static esp_err_t usj_set_config(esp_trace_transport_t *tp, esp_trace_transport_cfg_key_t key, const void *value) +{ + if (!value) { + return ESP_ERR_INVALID_ARG; + } + + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + if (!ctx) { + return ESP_ERR_INVALID_STATE; + } + + switch (key) { + case ESP_TRACE_TRANSPORT_CFG_HEADER_SIZE: + /* USB-Serial-JTAG doesn't need header size configuration */ + return ESP_OK; + case ESP_TRACE_TRANSPORT_CFG_FLUSH_TMO: + ctx->flush_tmo = *(const uint32_t *)value; + return ESP_OK; + case ESP_TRACE_TRANSPORT_CFG_FLUSH_THRESH: + ctx->flush_thresh = *(const uint32_t *)value; + return ESP_OK; + default: + ESP_LOGE(TAG, "Key %d is not supported", key); + return ESP_ERR_NOT_SUPPORTED; + } +} + +static esp_err_t usj_get_config(esp_trace_transport_t *tp, esp_trace_transport_cfg_key_t key, void *value) +{ + if (!value) { + return ESP_ERR_INVALID_ARG; + } + + usj_ctx_t *ctx = (usj_ctx_t *)tp->ctx; + if (!ctx) { + return ESP_ERR_INVALID_STATE; + } + + switch (key) { + case ESP_TRACE_TRANSPORT_CFG_FLUSH_TMO: + *(uint32_t *)value = ctx->flush_tmo; + return ESP_OK; + case ESP_TRACE_TRANSPORT_CFG_FLUSH_THRESH: + *(uint32_t *)value = ctx->flush_thresh; + return ESP_OK; + default: + ESP_LOGE(TAG, "Key %d is not supported", key); + return ESP_ERR_NOT_SUPPORTED; + } +} + +static void usj_panic_handler(esp_trace_transport_t *tp, const void *info) +{ + (void)info; + usj_flush_nolock(tp); +} + +/* ----------------------- Transport Registration ----------------------- */ +static const esp_trace_transport_vtable_t s_usb_serial_jtag_vt = { + .init = usj_init, + .set_config = usj_set_config, + .get_config = usj_get_config, + .read = usj_read, + .write = usj_write, + .flush_nolock = usj_flush_nolock, + .down_buffer_config = usj_down_buffer_config, + .is_host_connected = usj_is_host_connected, + .get_link_type = usj_get_link_type, + .panic_handler = usj_panic_handler, +}; + +ESP_TRACE_REGISTER_TRANSPORT("usb_serial_jtag", &s_usb_serial_jtag_vt); diff --git a/components/esp_trace/include/esp_trace_types.h b/components/esp_trace/include/esp_trace_types.h index 5b1efae25ac..dae063aa79c 100644 --- a/components/esp_trace/include/esp_trace_types.h +++ b/components/esp_trace/include/esp_trace_types.h @@ -18,6 +18,7 @@ typedef enum { ESP_TRACE_LINK_UNKNOWN = 0, ESP_TRACE_LINK_DEBUG_PROBE, ESP_TRACE_LINK_UART, + ESP_TRACE_LINK_USB_SERIAL_JTAG, } esp_trace_link_types_t; /* Timeout constants for trace operations */ diff --git a/components/esp_trace/include/esp_trace_util.h b/components/esp_trace/include/esp_trace_util.h index a2c158b6c84..e95d5ceea37 100644 --- a/components/esp_trace/include/esp_trace_util.h +++ b/components/esp_trace/include/esp_trace_util.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -118,6 +118,87 @@ esp_err_t esp_trace_lock_take(esp_trace_lock_t *lock, uint32_t tmo_us); */ esp_err_t esp_trace_lock_give(esp_trace_lock_t *lock); +/////////////////////////////////////////////////////////////////////////////// +//////////////////////////////// RING BUFFER ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#include + +/** + * @brief Power-of-2 ring buffer for trace transports + * + * Lightweight, FreeRTOS-free ring buffer suitable for use in trace hot paths + * (ISR-safe contexts, critical sections). Size must be a power of 2. + * + */ +typedef struct { + uint8_t *buffer; ///< Heap-allocated data buffer + uint32_t max_size; ///< Buffer capacity (must be power of 2) + volatile uint32_t count; ///< Bytes currently stored + volatile uint32_t head; ///< Write index + volatile uint32_t tail; ///< Read index +} esp_trace_rb_t; + +/** + * @brief Initialize ring buffer (allocates internal memory) + * + * @param rb Pointer to ring buffer structure + * @param size Buffer size in bytes (must be power of 2) + * @return ESP_OK on success, ESP_ERR_NO_MEM on allocation failure + */ +esp_err_t esp_trace_rb_init(esp_trace_rb_t *rb, uint32_t size); + +/** + * @brief Get number of bytes currently stored in the ring buffer + */ +static inline uint32_t esp_trace_rb_data_len(const esp_trace_rb_t *rb) +{ + return rb->count; +} + +/** + * @brief Write data into the ring buffer (overwrites oldest data if full) + * + * @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) + */ +esp_err_t esp_trace_rb_put(esp_trace_rb_t *rb, const uint8_t *data, uint32_t len); + +/** + * @brief Read and consume data from the ring buffer + * + * @param rb Ring buffer + * @param data Destination buffer + * @param len Maximum number of bytes to read + * @return Number of bytes actually read + */ +uint32_t esp_trace_rb_get(esp_trace_rb_t *rb, uint8_t *data, uint32_t len); + +/** + * @brief Peek at contiguous readable data without consuming + * + * Returns a pointer to the contiguous block of data starting at the tail. + * When the readable region wraps around the end of the buffer, only the + * first contiguous portion is returned. Call again after consume() for the rest. + * + * @param rb Ring buffer + * @param[out] data Set to point at the contiguous data (valid until next put/consume) + * @return Number of contiguous bytes available (0 if empty) + */ +uint32_t esp_trace_rb_peek_contiguous(const esp_trace_rb_t *rb, const uint8_t **data); + +/** + * @brief Consume (discard) bytes from the read side of the ring buffer + * + * Typically called after esp_trace_rb_peek_contiguous() + processing. + * + * @param rb Ring buffer + * @param len Number of bytes to consume (must be <= esp_trace_rb_data_len()) + */ +void esp_trace_rb_consume(esp_trace_rb_t *rb, uint32_t len); + #ifdef __cplusplus } #endif diff --git a/components/esp_trace/linker.lf b/components/esp_trace/linker.lf index b2dbeec9304..555ca4dad10 100644 --- a/components/esp_trace/linker.lf +++ b/components/esp_trace/linker.lf @@ -8,6 +8,8 @@ entries: port_utils (noflash) if ESP_TRACE_TRANSPORT_APPTRACE: adapter_transport_apptrace (noflash) + if ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG: + adapter_transport_usb_serial_jtag (noflash) [mapping:esp_trace_driver] archive: libesp_driver_gptimer.a diff --git a/components/esp_trace/src/ports/port_utils.c b/components/esp_trace/src/ports/port_utils.c index 30cad3d97c6..f2cc592c681 100644 --- a/components/esp_trace/src/ports/port_utils.c +++ b/components/esp_trace/src/ports/port_utils.c @@ -1,15 +1,17 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include "sdkconfig.h" #include "esp_timer.h" #include "esp_clk_tree.h" #include "esp_cpu.h" #include "esp_private/esp_clk.h" #include "esp_err.h" +#include "esp_heap_caps.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -152,3 +154,104 @@ esp_err_t esp_trace_lock_give(esp_trace_lock_t *lock) portEXIT_CRITICAL(&lock->mux); return ESP_OK; } + +/////////////////////////////////////////////////////////////////////////////// +//////////////////////////////// RING BUFFER ////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +static inline uint32_t rb_mask(const esp_trace_rb_t *rb) +{ + return rb->max_size - 1; +} + +static inline void rb_advance_tail(esp_trace_rb_t *rb, uint32_t n) +{ + rb->tail = (rb->tail + n) & rb_mask(rb); + rb->count -= n; +} + +static inline void rb_advance_head(esp_trace_rb_t *rb, uint32_t n) +{ + rb->head = (rb->head + n) & rb_mask(rb); + rb->count += n; +} + +esp_err_t esp_trace_rb_init(esp_trace_rb_t *rb, uint32_t size) +{ + rb->buffer = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (!rb->buffer) { + return ESP_ERR_NO_MEM; + } + rb->max_size = size; + rb->count = 0; + rb->head = 0; + rb->tail = 0; + return ESP_OK; +} + +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); + } + + uint32_t head = rb->head; + uint32_t space_to_end = rb->max_size - head; + + if (len <= space_to_end) { + memcpy(&rb->buffer[head], data, len); + } else { + memcpy(&rb->buffer[head], data, space_to_end); + memcpy(&rb->buffer[0], &data[space_to_end], len - space_to_end); + } + + rb_advance_head(rb, len); + + return ESP_OK; +} + +uint32_t esp_trace_rb_get(esp_trace_rb_t *rb, uint8_t *data, uint32_t len) +{ + uint32_t available = rb->count; + if (available == 0 || len == 0) { + return 0; + } + + uint32_t to_read = MIN(len, available); + uint32_t tail = rb->tail; + uint32_t cont = rb->max_size - tail; + + if (to_read <= cont) { + memcpy(data, &rb->buffer[tail], to_read); + } else { + memcpy(data, &rb->buffer[tail], cont); + memcpy(&data[cont], &rb->buffer[0], to_read - cont); + } + + rb_advance_tail(rb, to_read); + + return to_read; +} + +uint32_t esp_trace_rb_peek_contiguous(const esp_trace_rb_t *rb, const uint8_t **data) +{ + uint32_t used = rb->count; + if (used == 0) { + *data = NULL; + return 0; + } + *data = &rb->buffer[rb->tail]; + uint32_t contiguous = rb->max_size - rb->tail; + return MIN(used, contiguous); +} + +void esp_trace_rb_consume(esp_trace_rb_t *rb, uint32_t len) +{ + rb_advance_tail(rb, len); +} diff --git a/examples/system/sysview_tracing/main/sysview_tracing.c b/examples/system/sysview_tracing/main/sysview_tracing.c index 79505963558..b988471ba27 100644 --- a/examples/system/sysview_tracing/main/sysview_tracing.c +++ b/examples/system/sysview_tracing/main/sysview_tracing.c @@ -15,11 +15,9 @@ #include #include #include "esp_log.h" -#include "esp_app_trace.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gptimer.h" -#include "soc/uart_pins.h" #include "esp_trace.h" static const char *TAG = "example"; @@ -127,6 +125,9 @@ static void example_task(void *p) } } +#if CONFIG_ESP_TRACE_TRANSPORT_APPTRACE +#include "soc/uart_pins.h" +#include "esp_app_trace.h" esp_trace_open_params_t esp_trace_get_user_params(void) { static esp_apptrace_config_t app_trace_config = APPTRACE_CONFIG_DEFAULT(); @@ -145,6 +146,19 @@ esp_trace_open_params_t esp_trace_get_user_params(void) }; return trace_params; } +#elif CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG +esp_trace_open_params_t esp_trace_get_user_params(void) +{ + esp_trace_open_params_t trace_params = { + .core_cfg = NULL, + .encoder_name = "sysview", + .encoder_cfg = NULL, + .transport_name = "usb_serial_jtag", + .transport_cfg = NULL, + }; + return trace_params; +} +#endif void app_main(void) { diff --git a/examples/system/sysview_tracing/pytest_sysview_tracing.py b/examples/system/sysview_tracing/pytest_sysview_tracing.py index 56ae1ec20fd..3f93ef8e3d0 100644 --- a/examples/system/sysview_tracing/pytest_sysview_tracing.py +++ b/examples/system/sysview_tracing/pytest_sysview_tracing.py @@ -10,6 +10,7 @@ import pytest import serial from pytest_embedded_idf import IdfDut from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets if typing.TYPE_CHECKING: from conftest import OpenOCD @@ -158,3 +159,15 @@ def _test_sysview_tracing_uart(dut: IdfDut) -> None: @idf_parametrize('target', ['supported_targets'], indirect=['target']) def test_sysview_tracing_uart(dut: IdfDut) -> None: _test_sysview_tracing_uart(dut) + + +@pytest.mark.usb_serial_jtag +@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) +@pytest.mark.parametrize('config', [pytest.param('sysview_usj')], indirect=True) +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[0]) + _validate_trace_data(trace_log, dut.target, is_uart=True) diff --git a/examples/system/sysview_tracing/sdkconfig.ci.sysview_jtag b/examples/system/sysview_tracing/sdkconfig.ci.sysview_jtag index 772bddde156..621f02d84f9 100644 --- a/examples/system/sysview_tracing/sdkconfig.ci.sysview_jtag +++ b/examples/system/sysview_tracing/sdkconfig.ci.sysview_jtag @@ -1 +1,2 @@ +CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y CONFIG_APPTRACE_DEST_JTAG=y diff --git a/examples/system/sysview_tracing/sdkconfig.ci.sysview_uart b/examples/system/sysview_tracing/sdkconfig.ci.sysview_uart index 15555d5e93a..2d827ec7bba 100644 --- a/examples/system/sysview_tracing/sdkconfig.ci.sysview_uart +++ b/examples/system/sysview_tracing/sdkconfig.ci.sysview_uart @@ -1,4 +1,5 @@ CONFIG_ESP_CONSOLE_NONE=y +CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y CONFIG_APPTRACE_DEST_UART=y CONFIG_APPTRACE_DEST_UART_NUM=0 CONFIG_APPTRACE_UART_BAUDRATE=1000000 diff --git a/examples/system/sysview_tracing/sdkconfig.ci.sysview_usj b/examples/system/sysview_tracing/sdkconfig.ci.sysview_usj new file mode 100644 index 00000000000..cafe4275e66 --- /dev/null +++ b/examples/system/sysview_tracing/sdkconfig.ci.sysview_usj @@ -0,0 +1,3 @@ +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y +CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y +CONFIG_USE_CUSTOM_EVENT_ID=y diff --git a/examples/system/sysview_tracing/sdkconfig.defaults b/examples/system/sysview_tracing/sdkconfig.defaults index 9a260fdef2e..b58df2ad9cd 100644 --- a/examples/system/sysview_tracing/sdkconfig.defaults +++ b/examples/system/sysview_tracing/sdkconfig.defaults @@ -3,7 +3,6 @@ CONFIG_FREERTOS_HZ=1000 # Enable FreeRTOS SystemView Tracing by default CONFIG_ESP_TRACE_ENABLE=y CONFIG_ESP_TRACE_LIB_EXTERNAL=y -CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y CONFIG_SEGGER_SYSVIEW_EVT_OVERFLOW_ENABLE=y CONFIG_SEGGER_SYSVIEW_EVT_ISR_ENTER_ENABLE=y From 71daed48d4c15dd453c0e710fbfdac0a57df66bb Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Thu, 14 May 2026 09:38:05 +0200 Subject: [PATCH 2/5] change(esp_trace): allow init without transport(none) --- components/esp_trace/src/core/esp_trace_core.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/components/esp_trace/src/core/esp_trace_core.c b/components/esp_trace/src/core/esp_trace_core.c index 13fd193e8cb..885b807b040 100644 --- a/components/esp_trace/src/core/esp_trace_core.c +++ b/components/esp_trace/src/core/esp_trace_core.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -53,8 +53,9 @@ static esp_err_t esp_trace_create(const esp_trace_open_params_t *params) const esp_trace_encoder_vtable_t *enc_vt = esp_trace_find_encoder(params->encoder_name); const esp_trace_transport_vtable_t *tp_vt = esp_trace_find_transport(params->transport_name); - if (!enc_vt || !tp_vt) { - ESP_EARLY_LOGE(TAG, "Encoder '%s' or transport '%s' not found", params->encoder_name, params->transport_name); + // Encoder must be found but transport is optional + if (!enc_vt) { + ESP_EARLY_LOGE(TAG, "Encoder '%s' not found", params->encoder_name); return ESP_ERR_NOT_FOUND; } @@ -103,7 +104,7 @@ static esp_err_t esp_trace_init(const esp_trace_open_params_t *params) portENTER_CRITICAL(&s_init_lock); /* Setup transport first (encoder depends on it) */ - if (h->transport.vt->init) { + if (h->transport.vt && h->transport.vt->init) { err = h->transport.vt->init(&h->transport, params->transport_cfg); if (err != ESP_OK) { ESP_EARLY_LOGE(TAG, "Transport open failed: %d", err); @@ -152,7 +153,7 @@ esp_err_t esp_trace_write(esp_trace_handle_t h, const void *data, size_t size, u bool esp_trace_is_host_connected(esp_trace_handle_t h) { - if (!h || !h->transport.vt->is_host_connected) { + if (!h || !h->transport.vt || !h->transport.vt->is_host_connected) { return false; } @@ -161,7 +162,7 @@ bool esp_trace_is_host_connected(esp_trace_handle_t h) esp_trace_link_types_t esp_trace_get_link_type(esp_trace_handle_t h) { - if (!h || !h->transport.vt->get_link_type) { + if (!h || !h->transport.vt || !h->transport.vt->get_link_type) { return ESP_TRACE_LINK_UNKNOWN; } @@ -183,7 +184,7 @@ void esp_trace_panic_handler(const void *info) h->encoder.vt->panic_handler(&h->encoder, info); } - if (h->transport.vt->panic_handler) { + if (h->transport.vt && h->transport.vt->panic_handler) { h->transport.vt->panic_handler(&h->transport, info); } } From f948d8ade144a29e152818c911860b5df7d4bda6 Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Fri, 15 May 2026 15:55:47 +0200 Subject: [PATCH 3/5] feat(esp_trace): extend encoder interface Add esp_trace_flush, esp_trace_start and esp_trace_stop APIs --- components/esp_trace/README.md | 9 ++-- components/esp_trace/include/esp_trace.h | 23 +++++++++- .../include/esp_trace_port_encoder.h | 16 ++++++- .../esp_trace/src/core/esp_trace_core.c | 42 +++++++++++++++++++ 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/components/esp_trace/README.md b/components/esp_trace/README.md index 45196dd0985..47564a2983d 100644 --- a/components/esp_trace/README.md +++ b/components/esp_trace/README.md @@ -33,11 +33,12 @@ end %% ======================= subgraph PRIMARY["🔌 PUBLIC INTERFACE"] api["- esp_trace.h - - esp_trace_init() - - esp_trace_record() - esp_trace_write() + - esp_trace_start() + - esp_trace_stop() - esp_trace_flush() - - esp_trace_print()"] + - esp_trace_is_host_connected() + - esp_trace_get_link_type()"] end %% wiring: App uses API (labels land on the short pre-edges to api_in) @@ -203,7 +204,7 @@ idf_component_register( ) ``` -This means you can directly use both the trace library APIs (e.g., SystemView) and `esp_trace` APIs (like `esp_trace_get_user_params()`, `esp_trace_is_host_connected()`, etc.) without explicitly declaring the dependency. +This means you can directly use both the trace library APIs (e.g., SystemView) and `esp_trace` APIs (like `esp_trace_get_user_params()`, `esp_trace_is_host_connected()`, `esp_trace_start()` / `esp_trace_stop()` / `esp_trace_flush()`, etc.) without explicitly declaring the dependency. ### When Using Standalone Apptrace diff --git a/components/esp_trace/include/esp_trace.h b/components/esp_trace/include/esp_trace.h index 5ed7ccb3247..d17f7f60738 100644 --- a/components/esp_trace/include/esp_trace.h +++ b/components/esp_trace/include/esp_trace.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -100,6 +100,27 @@ esp_trace_handle_t esp_trace_get_active_handle(void); */ esp_err_t esp_trace_write(esp_trace_handle_t handle, const void *data, size_t size, unsigned long tmo); +/** + * @brief Resume trace event emission on the active session. + * + * @return ESP_OK on success, otherwise see esp_err_t + */ +esp_err_t esp_trace_start(void); + +/** + * @brief Pause trace event emission on the active session. + * + * @return ESP_OK on success, otherwise see esp_err_t + */ +esp_err_t esp_trace_stop(void); + +/** + * @brief Flush pending trace data through the encoder + * + * @return ESP_OK on success, otherwise see esp_err_t + */ +esp_err_t esp_trace_flush(void); + /** * @brief Check if the host is connected * diff --git a/components/esp_trace/include/esp_trace_port_encoder.h b/components/esp_trace/include/esp_trace_port_encoder.h index 2aa7bd8b9f6..59a603ad0fe 100644 --- a/components/esp_trace/include/esp_trace_port_encoder.h +++ b/components/esp_trace/include/esp_trace_port_encoder.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -49,8 +49,20 @@ typedef struct { */ void (*panic_handler)(esp_trace_encoder_t *enc, const void *info); + /** @brief Resume trace event emission */ + esp_err_t (*start)(esp_trace_encoder_t *enc); + + /** @brief Pause trace event emission */ + esp_err_t (*stop)(esp_trace_encoder_t *enc); + + /** @brief Flush pending trace data through the encoder */ + esp_err_t (*flush)(esp_trace_encoder_t *enc); + /** - * @brief Take encoder lock + * @brief Take encoder lock. + * Callers should pass ESP_TRACE_TMO_INFINITE unless they explicitly + * check the return value — pairing a failed take with give_lock() + * causes a spinlock owner-mismatch assert. * @param enc Encoder instance * @param tmo Timeout in microseconds * @return Lock state (for recursive locking) or 0 on failure diff --git a/components/esp_trace/src/core/esp_trace_core.c b/components/esp_trace/src/core/esp_trace_core.c index 885b807b040..7fd81c5a544 100644 --- a/components/esp_trace/src/core/esp_trace_core.c +++ b/components/esp_trace/src/core/esp_trace_core.c @@ -151,6 +151,48 @@ esp_err_t esp_trace_write(esp_trace_handle_t h, const void *data, size_t size, u return h->encoder.vt->write(&h->encoder, data, size, tmo); } +esp_err_t esp_trace_start(void) +{ + esp_trace_handle_t h = s_active_handle; + if (!h) { + return ESP_ERR_INVALID_STATE; + } + + if (!h->encoder.vt->start) { + return ESP_ERR_NOT_SUPPORTED; + } + + return h->encoder.vt->start(&h->encoder); +} + +esp_err_t esp_trace_stop(void) +{ + esp_trace_handle_t h = s_active_handle; + if (!h) { + return ESP_ERR_INVALID_STATE; + } + + if (!h->encoder.vt->stop) { + return ESP_ERR_NOT_SUPPORTED; + } + + return h->encoder.vt->stop(&h->encoder); +} + +esp_err_t esp_trace_flush(void) +{ + esp_trace_handle_t h = s_active_handle; + if (!h) { + return ESP_ERR_INVALID_STATE; + } + + if (!h->encoder.vt->flush) { + return ESP_ERR_NOT_SUPPORTED; + } + + return h->encoder.vt->flush(&h->encoder); +} + bool esp_trace_is_host_connected(esp_trace_handle_t h) { if (!h || !h->transport.vt || !h->transport.vt->is_host_connected) { From 6cb1b59e7c9a4dc200bd3a32d570c26a64124f0f Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Fri, 15 May 2026 14:55:02 +0200 Subject: [PATCH 4/5] feat(esp_trace): add example to demonstrate external lib integration --- components/esp_trace/README.md | 15 ++ .../include/esp_trace_port_encoder.h | 8 +- .../include/esp_trace_port_transport.h | 7 +- docs/en/api-guides/app_trace.rst | 18 ++ docs/zh_CN/api-guides/app_trace.rst | 18 ++ examples/system/.build-test-rules.yml | 15 ++ examples/system/esp_trace/CMakeLists.txt | 8 + examples/system/esp_trace/README.md | 207 ++++++++++++++++++ .../components/ext_trace_lib/CMakeLists.txt | 23 ++ .../include/esp_trace_freertos_impl.h | 9 + .../ext_trace_lib/include/trace_FreeRTOS.h | 68 ++++++ .../src/adapter_encoder_ext_trace_lib.c | 141 ++++++++++++ .../ext_trace_lib/src/trace_FreeRTOS.c | 147 +++++++++++++ examples/system/esp_trace/main/CMakeLists.txt | 3 + examples/system/esp_trace/main/app_main.c | 76 +++++++ examples/system/esp_trace/pytest_esp_trace.py | 115 ++++++++++ examples/system/esp_trace/sdkconfig.defaults | 6 + 17 files changed, 882 insertions(+), 2 deletions(-) create mode 100644 examples/system/esp_trace/CMakeLists.txt create mode 100644 examples/system/esp_trace/README.md create mode 100644 examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt create mode 100644 examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h create mode 100644 examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h create mode 100644 examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c create mode 100644 examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c create mode 100644 examples/system/esp_trace/main/CMakeLists.txt create mode 100644 examples/system/esp_trace/main/app_main.c create mode 100644 examples/system/esp_trace/pytest_esp_trace.py create mode 100644 examples/system/esp_trace/sdkconfig.defaults diff --git a/components/esp_trace/README.md b/components/esp_trace/README.md index 47564a2983d..7ec47c6bf8d 100644 --- a/components/esp_trace/README.md +++ b/components/esp_trace/README.md @@ -227,6 +227,20 @@ The `esp_trace` component supports integration of external trace libraries throu - **Transport Adapters**: Handle the physical transport layer (e.g., JTAG, UART) - **Encoder Adapters**: Handle the trace encoding/formatting (e.g., SystemView, custom formats) +> ⚠️ **Reentrancy constraint for adapter runtime callbacks** +> +> Encoder and transport callbacks invoked from the hot path — `write`, `flush` / `flush_nolock`, `read`, `take_lock` / `give_lock`, `panic_handler` — run from inside FreeRTOS trace hooks (and from ISR context for `traceISR_ENTER` / `traceISR_EXIT`). They are also called while the encoder's lock is held. +> +> Do **not** call FreeRTOS / IDF APIs that themselves trigger trace hooks from these callbacks. Anything that would emit a `trace*()` macro re-enters the tracing path: it can recurse into your own encoder, deadlock on the encoder's non-recursive spinlock, or call a task-only API from ISR context. +> +> Specifically avoid: +> - Task APIs: `vTaskDelay`, `vTaskSuspend`, `xTaskNotify*`, anything that yields. +> - Queue / semaphore / mutex APIs: `xQueueSend/Receive`, `xSemaphoreTake/Give`, `xQueueSemaphoreTake`. +> - Stream / message buffer APIs. +> - Heap allocations that may take an internal mutex. +> +> Safe building blocks for adapter code: lock-free or spinlock-only primitives (e.g. `esp_trace_lock_*`, `esp_trace_rb_*`), low-level peripheral register access, atomic operations, and `esp_rom_*` helpers. Do any heavier work (FreeRTOS APIs, allocations) only at adapter `init()` time, before the trace session is in steady state. + ### Creating a Transport Adapter Transport adapters provide the physical communication layer for trace data. @@ -408,4 +422,5 @@ For detailed usage instructions, see: Examples demonstrating trace usage can be found in: - `examples/system/app_trace_basic/` - Basic application tracing - `examples/system/sysview_tracing/` - SystemView tracing example +- `examples/system/esp_trace/` - Minimal template for integrating an external trace library (encoder + FreeRTOS hooks + vtable lock) - `examples/system/sysview_tracing_heap_log/` - SystemView heap and log tracing example diff --git a/components/esp_trace/include/esp_trace_port_encoder.h b/components/esp_trace/include/esp_trace_port_encoder.h index 59a603ad0fe..5f1365d51e6 100644 --- a/components/esp_trace/include/esp_trace_port_encoder.h +++ b/components/esp_trace/include/esp_trace_port_encoder.h @@ -21,7 +21,13 @@ typedef struct esp_trace_transport esp_trace_transport_t; /** * @brief Encoder Virtual Table * - * Defines the interface for trace encoders (libraries) + * Defines the interface for trace encoders (libraries). + * + * @warning Runtime callbacks (write, flush, take_lock, give_lock, panic_handler) + * must not call FreeRTOS / IDF APIs that themselves emit trace hooks + * (e.g. vTaskDelay, xQueue*, xSemaphore*) — doing so re-enters the + * tracing path and can deadlock on the encoder lock or crash in ISR + * context. */ typedef struct { /** diff --git a/components/esp_trace/include/esp_trace_port_transport.h b/components/esp_trace/include/esp_trace_port_transport.h index 39d435e9242..8385ce4a396 100644 --- a/components/esp_trace/include/esp_trace_port_transport.h +++ b/components/esp_trace/include/esp_trace_port_transport.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -34,6 +34,11 @@ typedef enum { * * Defines the interface for trace transports. * + * @warning Runtime callbacks (read, write, flush_nolock, panic_handler) must + * not call FreeRTOS / IDF APIs that themselves emit trace hooks + * (e.g. vTaskDelay, xQueue*, xSemaphore*) — they are invoked from + * inside the encoder's lock and from ISR context, so re-entering + * the tracing path can deadlock or assert. */ typedef struct { /** diff --git a/docs/en/api-guides/app_trace.rst b/docs/en/api-guides/app_trace.rst index c3d6456b171..5c4818c121c 100644 --- a/docs/en/api-guides/app_trace.rst +++ b/docs/en/api-guides/app_trace.rst @@ -557,9 +557,27 @@ Good instructions on how to install, configure, and visualize data in Impulse fr If you have problems with visualization (no data is shown or strange behaviors of zoom action are observed), you can try to delete current signal hierarchy and double-click on the necessary file or port. Eclipse will ask you to create a new signal hierarchy. +Application Examples +"""""""""""""""""""" + +- :example:`system/sysview_tracing` demonstrates how to trace FreeRTOS task and system events using SEGGER SystemView. +- :example:`system/sysview_tracing_heap_log` demonstrates heap allocation tracing alongside SystemView events. + .. _app_trace-gcov-source-code-coverage: Gcov (Source Code Coverage) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ In ESP-IDF projects, code coverage analysis using gcov can be done with the help of `espressif/esp_gcov `_ managed component. + +.. _app_trace-integrating-a-custom-trace-library: + +Integrating a Custom Trace Library +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ``esp_trace`` component exposes a stable extension point (``CONFIG_ESP_TRACE_LIB_EXTERNAL``) for plugging in a third-party trace recorder without patching ESP-IDF. An external component provides an encoder adapter (registered via ``ESP_TRACE_REGISTER_ENCODER()``) and a slim ``esp_trace_freertos_impl.h`` that injects the desired FreeRTOS trace hooks. The encoder vtable also offers optional ``start`` / ``stop`` / ``flush`` and ``take_lock`` / ``give_lock`` entries dispatched from the public :cpp:func:`esp_trace_start`, :cpp:func:`esp_trace_stop`, :cpp:func:`esp_trace_flush` API. + +Application Examples +"""""""""""""""""""" + +- :example:`system/esp_trace` is a minimal copy-paste template that wires up an external encoder, demonstrates the FreeRTOS trace-hook include-chain contract, and covers cross-core serialization through the encoder lock. diff --git a/docs/zh_CN/api-guides/app_trace.rst b/docs/zh_CN/api-guides/app_trace.rst index b4aa69dd8da..ed71462c971 100644 --- a/docs/zh_CN/api-guides/app_trace.rst +++ b/docs/zh_CN/api-guides/app_trace.rst @@ -557,9 +557,27 @@ Start 子命令语法: 如果你在可视化方面遇到了问题(未显示数据或者缩放操作异常),可以尝试删除当前的信号层次结构,再双击必要的文件或端口。Eclipse 会请求创建新的信号层次结构。 +应用示例 +"""""""" + +- :example:`system/sysview_tracing` 演示了如何使用 SEGGER SystemView 跟踪 FreeRTOS 任务和系统事件。 +- :example:`system/sysview_tracing_heap_log` 演示了如何在 SystemView 事件的同时跟踪堆内存分配。 + .. _app_trace-gcov-source-code-coverage: Gcov(源代码覆盖率) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 在 ESP-IDF 项目中,可以借助 `espressif/esp_gcov `_ 托管组件使用 gcov 进行代码覆盖率分析。 + +.. _app_trace-integrating-a-custom-trace-library: + +集成自定义跟踪库 +^^^^^^^^^^^^^^^^ + +``esp_trace`` 组件提供了稳定的扩展点 (``CONFIG_ESP_TRACE_LIB_EXTERNAL``),允许在不修改 ESP-IDF 的情况下接入第三方跟踪记录器。外部组件需提供一个编码器适配器(通过 ``ESP_TRACE_REGISTER_ENCODER()`` 注册)以及一个轻量的 ``esp_trace_freertos_impl.h``,用于注入所需的 FreeRTOS 跟踪钩子。编码器虚表还提供可选的 ``start`` / ``stop`` / ``flush`` 及 ``take_lock`` / ``give_lock`` 入口,由公共 API :cpp:func:`esp_trace_start`、:cpp:func:`esp_trace_stop`、:cpp:func:`esp_trace_flush` 调度。 + +应用示例 +"""""""" + +- :example:`system/esp_trace` 是一个最简的复制粘贴模板,演示如何接入外部编码器、说明 FreeRTOS 跟踪钩子头文件的包含链约束,以及通过编码器锁实现多核序列化。 diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index 46de6e7ebc2..a1993e0256c 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -68,6 +68,21 @@ examples/system/esp_timer: - *common_components - esp_timer +examples/system/esp_trace: + disable: + - if: SOC_USB_SERIAL_JTAG_SUPPORTED != 1 + reason: example transport is USB Serial JTAG + disable_test: + - if: IDF_TARGET == "esp32h21" + temporary: true + reason: lack of runners + - if: IDF_TARGET == "esp32h4" + temporary: true + reason: lack of runners + depends_components: + - esp_trace + - freertos + examples/system/eventfd: disable: - if: SOC_GPTIMER_SUPPORTED != 1 and (IDF_TARGET != "esp32" and (NIGHTLY_RUN != "1" or IDF_TARGET == "linux")) diff --git a/examples/system/esp_trace/CMakeLists.txt b/examples/system/esp_trace/CMakeLists.txt new file mode 100644 index 00000000000..dd56d133980 --- /dev/null +++ b/examples/system/esp_trace/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +idf_build_set_property(MINIMAL_BUILD ON) +project(esp_trace_example) diff --git a/examples/system/esp_trace/README.md b/examples/system/esp_trace/README.md new file mode 100644 index 00000000000..aca36972975 --- /dev/null +++ b/examples/system/esp_trace/README.md @@ -0,0 +1,207 @@ +| Supported Targets | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | + +# ESP Trace External Library Integration Example + +This example shows the **minimal** set of files and configuration needed to plug a third-party trace library into the [`esp_trace`](../../../components/esp_trace) component using the public `CONFIG_ESP_TRACE_LIB_EXTERNAL` extension point. It is meant as a copy-paste starting point for vendors and users who want to integrate their own trace recorder (e.g. Percepio TraceRecorder, a custom CTF emitter, a printf-style logger, …) without patching ESP-IDF itself. + +The example covers: + +* How to expose a custom **encoder** to `esp_trace` via `ESP_TRACE_REGISTER_ENCODER()`. +* How to provide an **`esp_trace_freertos_impl.h`** header that injects your trace hooks into FreeRTOS without breaking the `FreeRTOSConfig.h` include chain. +* How to wire everything up in **CMake** so the registration is not stripped by the linker, and so your header is visible to the FreeRTOS kernel. +* How to override the trace session parameters from the application via **`esp_trace_get_user_params()`**. + +## How to Use + +### Hardware Required + +By default this example targets devices with built-in USB Serial JTAG (ESP32-C3/C5/C6/C61/H2/P4/S3, …). For other transports, see [Changing the Transport](#changing-the-transport). + +You only need a development board and a USB cable. + +### Configure the Project + +``` +idf.py set-target +idf.py menuconfig +``` + +The defaults in [`sdkconfig.defaults`](sdkconfig.defaults) already enable everything the example needs: + +```ini +CONFIG_ESP_TRACE_ENABLE=y +CONFIG_ESP_TRACE_LIB_EXTERNAL=y # use an external encoder +CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y # transport: built-in USB-Serial-JTAG +CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER=y # timestamp source +CONFIG_ESP_CONSOLE_SECONDARY_NONE=y # free up USB-Serial-JTAG for trace +``` + +`CONFIG_ESP_CONSOLE_SECONDARY_NONE=y` is required so the USB-Serial-JTAG peripheral is not claimed by the secondary console — otherwise the transport option is not selectable. + +### Build, Flash, and Monitor + +``` +idf.py -p PORT flash monitor +``` + +You should see the app start up and create a task. Whatever trace bytes your encoder produces will be emitted over the configured transport — in this example, the encoder writes a short string to the transport on every task switch. + +(To exit the serial monitor, type `Ctrl-]`.) + +## Project Layout + +``` +esp_trace/ +├── CMakeLists.txt +├── sdkconfig.defaults +├── main/ +│ ├── CMakeLists.txt +│ └── app_main.c # overrides esp_trace_get_user_params() +└── components/ + └── ext_trace_lib/ # the external trace library component + ├── CMakeLists.txt # WHOLE_ARCHIVE + freertos include trick + ├── include/ + │ ├── esp_trace_freertos_impl.h # entry point pulled in by FreeRTOSConfig.h + │ └── trace_FreeRTOS.h # trace*() macros + forward declarations + └── src/ + ├── adapter_encoder_ext_trace_lib.c # vtable + ESP_TRACE_REGISTER_ENCODER() + └── trace_FreeRTOS.c # hook implementations (may include FreeRTOS.h) +``` + +## How the Integration Works + +### 1. Selecting the external library + +`CONFIG_ESP_TRACE_LIB_EXTERNAL=y` tells `esp_trace` that the encoder lives in a separate component. Internally, `CONFIG_ESP_TRACE_LIB_NAME` resolves to `"ext"`. You can either: + +* register your encoder under that default name — `ESP_TRACE_REGISTER_ENCODER("ext", &vt);` — and the system picks it up automatically, **or** +* register under any name you like (this example uses `"ext_trace_lib"`) and override the session parameters at runtime via `esp_trace_get_user_params()`. See [`main/app_main.c`](main/app_main.c): + + ```c + esp_trace_open_params_t esp_trace_get_user_params(void) + { + esp_trace_open_params_t trace_params = { + .core_cfg = NULL, + .encoder_name = "ext_trace_lib", + .encoder_cfg = NULL, + .transport_name = "usb_serial_jtag", + .transport_cfg = NULL, + }; + return trace_params; + } + ``` + +### 2. Providing the FreeRTOS trace hooks + +`esp_trace`'s public header [`esp_trace_freertos.h`](../../../components/esp_trace/include/esp_trace_freertos.h) is included from `FreeRTOSConfig.h`. When `CONFIG_ESP_TRACE_LIB_EXTERNAL=y` is set, it pulls in **your** `esp_trace_freertos_impl.h`: + +```c +#if CONFIG_ESP_TRACE_LIB_EXTERNAL +#include "esp_trace_freertos_impl.h" +#endif +``` + +The example splits the contract into two files: + +* [`esp_trace_freertos_impl.h`](components/ext_trace_lib/include/esp_trace_freertos_impl.h) — a one-line shim that pulls in `trace_FreeRTOS.h`. +* [`trace_FreeRTOS.h`](components/ext_trace_lib/include/trace_FreeRTOS.h) — defines only the `trace*()` macros this example actually hooks into, plus forward declarations of the helper functions called from them. **No FreeRTOS includes.** Anything left undefined here falls back to FreeRTOS's own empty default (every trace macro is guarded by `#ifndef traceXXX / #define traceXXX() / #endif` in `freertos/FreeRTOS.h`), so you only need to declare what you actually intercept. Trace macros are allowed to reference FreeRTOS identifiers like `pxTCB` or `xTicksToWait` by name — they are resolved later, when the macro is expanded inside the FreeRTOS kernel `.c` files where those names are already in scope. + +The actual hook implementation lives in [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) and is free to `#include "freertos/FreeRTOS.h"`. By the time a `.c` file is compiled, `FreeRTOSConfig.h` has been fully parsed. + +### 3. Registering the encoder + +[`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c) implements the encoder vtable (`init`, `write`, `panic_handler`) and registers it at link time: + +```c +ESP_TRACE_REGISTER_ENCODER("ext_trace_lib", &s_ext_trace_lib_vt); +``` + +The registration places a descriptor into a dedicated linker section that `esp_trace_core` scans during startup. Because nothing in the application references that descriptor directly, the linker would normally garbage-collect it — `WHOLE_ARCHIVE TRUE` in the component's `CMakeLists.txt` prevents that. + +### 4. CMake setup + +[`components/ext_trace_lib/CMakeLists.txt`](components/ext_trace_lib/CMakeLists.txt) shows the two pieces of CMake plumbing every external trace library needs: + +```cmake +if(CONFIG_ESP_TRACE_LIB_EXTERNAL) + idf_component_register(SRC_DIRS ${src_dirs} + INCLUDE_DIRS ${include_dirs} + PRIV_REQUIRES esp_trace + WHOLE_ARCHIVE TRUE) # keep ESP_TRACE_REGISTER_* symbols + + # Expose esp_trace_freertos_impl.h to the freertos component + idf_component_get_property(freertos_lib freertos COMPONENT_LIB) + target_include_directories(${freertos_lib} INTERFACE ${include_dirs}) +else() + idf_component_register(PRIV_REQUIRES esp_trace) +endif() +``` + +The second `target_include_directories(...)` call is what makes `esp_trace_freertos_impl.h` resolvable from inside the FreeRTOS kernel's translation units. + +## Changing the Transport + +The example defaults to USB Serial JTAG. To use a different transport, edit `sdkconfig.defaults` (or run `idf.py menuconfig` → *Component config → ESP Trace Configuration → Trace transport*): + +| Transport | Config | Notes | +| --- | --- | --- | +| USB Serial JTAG | `CONFIG_ESP_TRACE_TRANSPORT_USB_SERIAL_JTAG=y` | Default. Requires `ESP_CONSOLE_SECONDARY_NONE=y`. | +| apptrace over JTAG | `CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y` + `CONFIG_APPTRACE_DEST_JTAG=y` | Needs OpenOCD on the host to drain the buffer. | +| apptrace over UART | `CONFIG_ESP_TRACE_TRANSPORT_APPTRACE=y` + `CONFIG_APPTRACE_DEST_UART=y` | Pick a UART different from the console. | +| External transport | `CONFIG_ESP_TRACE_TRANSPORT_EXTERNAL=y` | Another component must register a transport with `ESP_TRACE_REGISTER_TRANSPORT(...)`. | +| None | `CONFIG_ESP_TRACE_TRANSPORT_NONE=y` | Useful if your library streams data over its own channel and just needs the FreeRTOS hooks. | + +Don't forget to update the `transport_name` field in `esp_trace_get_user_params()` to match (e.g. `"apptrace"`, `"usb_serial_jtag"`, or your custom transport's registered name). + +## What the Demo Emits + +`encode()` in [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) writes one line per trace event in the form: + +``` +[+ 9933 us] ISR_IN irq=63 +[+ 29 us] ISR_IN irq=57 +[+ 21 us] ISR_YIELD +[+ 16 us] ISR_OUT +[+ 1234 us] Q_CREATE q=0x3fc8a210 +[+ 167 us] TASK_IN producer +[+ 54 us] Q_SEND q=0x3fc8a210 +[+ 32 us] TASK_IN consumer +``` + +The leading number is the time elapsed since the previous traced event (microseconds when `CONFIG_ESP_TRACE_TS_SOURCE_ESP_TIMER` is selected). Eight FreeRTOS hooks are wired up — see the *active hooks* block at the top of [`trace_FreeRTOS.h`](components/ext_trace_lib/include/trace_FreeRTOS.h). The rest stay as no-ops (FreeRTOS still expects every `trace*()` macro to be defined). + +`ISR_OUT` vs `ISR_YIELD` reflects how FreeRTOS leaves the interrupt: `ISR_OUT` when the handler returns to the interrupted task without scheduling, `ISR_YIELD` when it calls `portYIELD_FROM_ISR()` (triggering `traceISR_EXIT_TO_SCHEDULER`). On a busy SMP target the yield path dominates; on a mostly-idle single-core target the plain `ISR_OUT` path does. + +Because the transport is USB-Serial-JTAG and the console is on UART (`CONFIG_ESP_CONSOLE_SECONDARY_NONE=y`), `idf.py monitor` shows ESP-IDF logs while the trace stream is on a separate USB endpoint — open it in any serial terminal (`screen /dev/cu.usbmodem...`, picocom, etc.) to read the output above. + +## Runtime Control — `esp_trace_start` / `_stop` / `_flush` + +[`esp_trace.h`](../../../components/esp_trace/include/esp_trace.h) exposes three generic lifecycle calls that dispatch to the active encoder's vtable. The application uses only the public API — it never reaches into the external library: + +```c +esp_trace_start(); // resume emission (also resets the delta baseline) +// ... do stuff ... +esp_trace_stop(); // pause emission +esp_trace_flush(); // drain transport buffers +``` + +In this example the library boots with `s_enabled = false`, so nothing is emitted until `app_main()` calls `esp_trace_start()`. The trailing pair `esp_trace_flush(); esp_trace_stop();` makes sure the last events reach the host before the trace channel goes silent. Adapter wiring lives in [`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c) (`start` / `stop` / `flush` callbacks); flush forwards to the transport's `flush_nolock`. + +## Cross-Core Serialization + +`encode()` wraps its body in the encoder's `take_lock` / `give_lock` vtable entries (an `esp_trace_lock_t` allocated in the adapter's `init()`). See [`trace_FreeRTOS.c`](components/ext_trace_lib/src/trace_FreeRTOS.c) and [`adapter_encoder_ext_trace_lib.c`](components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c). + +## Other `esp_trace` Helpers + +Beyond what this example uses, [`esp_trace.h`](../../../components/esp_trace/include/esp_trace.h) and [`esp_trace_util.h`](../../../components/esp_trace/include/esp_trace_util.h) also expose: + +* `esp_trace_is_host_connected()` — gate expensive work when no host is listening. +* `esp_trace_get_link_type()` — returns `ESP_TRACE_LINK_DEBUG_PROBE`, `_UART`, or `_USB_SERIAL_JTAG`. +* `esp_trace_rb_*()` — power-of-2, FreeRTOS-free ring buffer for trace hot paths. +* `esp_trace_tmo_init/check()` — cooperative timeouts for flush loops. + +## See Also + +* [`components/esp_trace/README.md`](../../../components/esp_trace/README.md) — full architecture overview and adapter API reference. +* [`examples/system/sysview_tracing`](../sysview_tracing) — a production-grade integration of SEGGER SystemView built on the same extension points. diff --git a/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt b/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt new file mode 100644 index 00000000000..ce4555c4eb5 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/CMakeLists.txt @@ -0,0 +1,23 @@ +set(src_dirs + "src" +) + +set(include_dirs + "include" +) + +set(priv_requires + "esp_trace" +) + +if(CONFIG_ESP_TRACE_LIB_EXTERNAL) + idf_component_register(SRC_DIRS ${src_dirs} + INCLUDE_DIRS ${include_dirs} + PRIV_REQUIRES ${priv_requires} + WHOLE_ARCHIVE TRUE) + + idf_component_get_property(freertos_lib freertos COMPONENT_LIB) + target_include_directories(${freertos_lib} INTERFACE ${include_dirs}) +else() + idf_component_register(PRIV_REQUIRES ${priv_requires}) +endif() diff --git a/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h b/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h new file mode 100644 index 00000000000..fc365434365 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/include/esp_trace_freertos_impl.h @@ -0,0 +1,9 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "trace_FreeRTOS.h" diff --git a/examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h b/examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h new file mode 100644 index 00000000000..c49ca59bf49 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/include/trace_FreeRTOS.h @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* + * External Trace Library - FreeRTOS trace hooks + * + * This header is pulled in (via esp_trace_freertos_impl.h) from FreeRTOSConfig.h + * Do NOT include any FreeRTOS header here — keep this file restricted to: + * - forward declarations of the C functions called by the macros below, + * - the trace*() macro definitions themselves. + * + * Macros are allowed to reference FreeRTOS identifiers (pxTCB, xTicksToWait, ...) + * by name — they are resolved later, when the macro is expanded inside the + * FreeRTOS kernel .c files where those names are already in scope. + * + * Only the trace*() macros this example actually hooks are defined here. + * Anything left undefined falls back to FreeRTOS's own empty default (see + * the #ifndef guards in freertos/FreeRTOS.h). + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Forward declaration so we can pass an encoder pointer from the adapter to + * init_trace_lib() without pulling in esp_trace_port_encoder.h. */ +typedef struct esp_trace_encoder esp_trace_encoder_t; + +void init_trace_lib(esp_trace_encoder_t *enc); +void trace_lib_start(void); +void trace_lib_stop(void); + +/* 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); +void trace_lib_task_create(void *pxNewTCB); +void trace_lib_isr_enter(uint32_t irq); +void trace_lib_isr_exit(void); +void trace_lib_isr_exit_to_scheduler(void); +void trace_lib_queue_send(void *pxQueue); +void trace_lib_queue_receive(void *pxQueue); +void trace_lib_queue_create(void *pxNewQueue); + +#ifdef __cplusplus +} +#endif + +/* ------------------------------------------------------------------ * + * Active hooks (forwarded to trace_FreeRTOS.c) + * ------------------------------------------------------------------ */ +#define traceTASK_SWITCHED_IN() trace_lib_task_switched_in() +#define traceTASK_CREATE(pxNewTCB) trace_lib_task_create(pxNewTCB) +#define traceISR_ENTER(n) trace_lib_isr_enter(n) +#define traceISR_EXIT() trace_lib_isr_exit() +#define traceISR_EXIT_TO_SCHEDULER() trace_lib_isr_exit_to_scheduler() +#define traceQUEUE_SEND(pxQueue) trace_lib_queue_send(pxQueue) +#define traceQUEUE_RECEIVE(pxQueue) trace_lib_queue_receive(pxQueue) +#define traceQUEUE_CREATE(pxNewQueue) trace_lib_queue_create(pxNewQueue) + +/* All other trace*() macros fall back to FreeRTOS's default empty defines + * (#ifndef ... #define ... empty in freertos/FreeRTOS.h); no need to list + * them here. Add a mapping above when you want to hook one. */ diff --git a/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c b/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c new file mode 100644 index 00000000000..976704b44e4 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/src/adapter_encoder_ext_trace_lib.c @@ -0,0 +1,141 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include + +#include "esp_err.h" +#include "esp_heap_caps.h" +#include "esp_trace_types.h" +#include "esp_trace_registry.h" +#include "esp_trace_port_encoder.h" +#include "esp_trace_port_transport.h" +#include "esp_trace_util.h" +#include "trace_FreeRTOS.h" + +typedef struct { + esp_trace_lock_t lock; +} ext_trace_lib_ctx_t; +/** + * @brief Initializes ext_trace_lib encoder. + * This function is called for each core. + * Adapter implementations do NOT need their own multi-core protection. Core does it for them. + * + * @param enc Pointer to the encoder structure. Must not be NULL. + * @param enc_cfg Pointer to the encoder configuration. Can be NULL for defaults. + * + * @return ESP_OK on success, otherwise \see esp_err_t + */ +static esp_err_t init(esp_trace_encoder_t *enc, const void *enc_cfg) +{ + (void)enc_cfg; + + // Ensure the encoder is initialized only once unless something todo for both cores + static bool initialized = false; + + if (!enc) { + return ESP_ERR_INVALID_ARG; + } + + if (initialized) { + return ESP_OK; + } + + ext_trace_lib_ctx_t *ctx = heap_caps_calloc(1, sizeof(*ctx), + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (!ctx) { + return ESP_ERR_NO_MEM; + } + esp_trace_lock_init(&ctx->lock); + enc->ctx = ctx; + + init_trace_lib(enc); + + initialized = true; + + return ESP_OK; +} + +static esp_err_t write(esp_trace_encoder_t *enc, const void *data, size_t size, uint32_t tmo) +{ + if (!enc || !data || size == 0) { + return ESP_ERR_INVALID_ARG; + } + + if (!enc->tp || !enc->tp->vt->write) { + return ESP_ERR_NOT_SUPPORTED; + } + + return enc->tp->vt->write(enc->tp, data, size, tmo); +} + +static esp_err_t start(esp_trace_encoder_t *enc) +{ + (void)enc; + trace_lib_start(); + return ESP_OK; +} + +static esp_err_t stop(esp_trace_encoder_t *enc) +{ + (void)enc; + trace_lib_stop(); + 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 + * + * Called during system panic to finalize encoder state. + * + * @param enc Pointer to the encoder structure. Must not be NULL. + * @param info Panic information + */ +static void panic_handler(esp_trace_encoder_t *enc, const void *info) +{ + (void)info; + flush(enc); +} + +static unsigned int take_lock(esp_trace_encoder_t *enc, uint32_t tmo_us) +{ + if (!enc || !enc->ctx) { + return 0; + } + ext_trace_lib_ctx_t *ctx = enc->ctx; + esp_trace_lock_take(&ctx->lock, tmo_us); + return ctx->lock.int_state; +} + +static void give_lock(esp_trace_encoder_t *enc, unsigned int int_state) +{ + if (!enc || !enc->ctx) { + return; + } + ext_trace_lib_ctx_t *ctx = enc->ctx; + ctx->lock.int_state = int_state; + esp_trace_lock_give(&ctx->lock); +} + +static const esp_trace_encoder_vtable_t s_ext_trace_lib_vt = { + .init = init, + .write = write, + .panic_handler = panic_handler, + .start = start, + .stop = stop, + .flush = flush, + .take_lock = take_lock, + .give_lock = give_lock, +}; + +ESP_TRACE_REGISTER_ENCODER("ext_trace_lib", &s_ext_trace_lib_vt); diff --git a/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c b/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c new file mode 100644 index 00000000000..5e88bf6df37 --- /dev/null +++ b/examples/system/esp_trace/components/ext_trace_lib/src/trace_FreeRTOS.c @@ -0,0 +1,147 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Implementations of the trace*() hooks declared in trace_FreeRTOS.h. + * + * Each hook encodes a single human-readable line that can be observed directly + * in any serial monitor — no decoder is needed: + * + * [+ 123 us] TASK_IN Task 1 + * [+ 1000 us] ISR_IN irq=5 + * + * The leading number is the time elapsed since the previous traced event. + * + */ + +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "trace_FreeRTOS.h" +#include "esp_trace.h" +#include "esp_trace_port_encoder.h" +#include "esp_trace_util.h" + +static esp_trace_handle_t s_esp_trace_handle = NULL; +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; + +void init_trace_lib(esp_trace_encoder_t *enc) +{ + s_esp_trace_handle = esp_trace_get_active_handle(); + s_enc = enc; + + uint32_t freq = esp_trace_timestamp_init(); + if (freq != 0) { + s_ts_freq_hz = freq; + } + s_last_ts = esp_trace_timestamp_get(); +} + +void trace_lib_start(void) +{ + if (!s_esp_trace_handle) { + return; + } + + s_last_ts = esp_trace_timestamp_get(); + s_enabled = true; +} + +void trace_lib_stop(void) +{ + s_enabled = false; +} + +/* Encode one trace line and write it through. */ +static void encode(const char *type, const char *detail) +{ + if (!s_enabled || !s_esp_trace_handle || !s_enc) { + return; + } + + unsigned int int_state = s_enc->vt->take_lock(s_enc, ESP_TRACE_TMO_INFINITE); + + uint32_t now = esp_trace_timestamp_get(); + uint32_t delta = now - s_last_ts; /* uint32 modular subtraction handles wrap */ + s_last_ts = now; + + uint32_t delta_us = (s_ts_freq_hz == 1000000) + ? delta + : (uint32_t)((uint64_t)delta * 1000000ULL / s_ts_freq_hz); + + char line[96]; + int n = snprintf(line, sizeof(line), "[+%7lu us] %-12s %s\n", + (unsigned long)delta_us, type, detail ? detail : ""); + if (n > 0) { + if (n >= (int)sizeof(line)) { + n = (int)sizeof(line) - 1; + } + esp_trace_write(s_esp_trace_handle, line, (size_t)n, 0); + } + + s_enc->vt->give_lock(s_enc, int_state); +} + +void trace_lib_task_switched_in(void) +{ + TaskHandle_t h = xTaskGetCurrentTaskHandle(); + encode("TASK_IN", h ? pcTaskGetName(h) : "?"); +} + +void trace_lib_task_create(void *pxNewTCB) +{ + if (!pxNewTCB) { + encode("TASK_CREATE", "(null)"); + return; + } + encode("TASK_CREATE", pcTaskGetName((TaskHandle_t)pxNewTCB)); +} + +void trace_lib_isr_enter(uint32_t irq) +{ + char d[24]; + snprintf(d, sizeof(d), "irq=%lu", (unsigned long)irq); + encode("ISR_IN", d); +} + +void trace_lib_isr_exit(void) +{ + encode("ISR_OUT", ""); +} + +void trace_lib_isr_exit_to_scheduler(void) +{ + encode("ISR_YIELD", ""); +} + +void trace_lib_queue_send(void *pxQueue) +{ + char d[24]; + snprintf(d, sizeof(d), "q=%p", pxQueue); + encode("Q_SEND", d); +} + +void trace_lib_queue_receive(void *pxQueue) +{ + char d[24]; + snprintf(d, sizeof(d), "q=%p", pxQueue); + encode("Q_RECEIVE", d); +} + +void trace_lib_queue_create(void *pxNewQueue) +{ + char d[24]; + snprintf(d, sizeof(d), "q=%p", pxNewQueue); + encode("Q_CREATE", d); +} diff --git a/examples/system/esp_trace/main/CMakeLists.txt b/examples/system/esp_trace/main/CMakeLists.txt new file mode 100644 index 00000000000..f9cd30438ad --- /dev/null +++ b/examples/system/esp_trace/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "app_main.c" + REQUIRES ext_trace_lib + INCLUDE_DIRS "") diff --git a/examples/system/esp_trace/main/app_main.c b/examples/system/esp_trace/main/app_main.c new file mode 100644 index 00000000000..28d7c07aa27 --- /dev/null +++ b/examples/system/esp_trace/main/app_main.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include "sdkconfig.h" + +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include "esp_trace.h" + +static const char *TAG = "main"; + +esp_trace_open_params_t esp_trace_get_user_params(void) +{ + esp_trace_open_params_t trace_params = { + .core_cfg = NULL, + .encoder_name = "ext_trace_lib", + .encoder_cfg = NULL, + .transport_name = "usb_serial_jtag", + .transport_cfg = NULL, + }; + return trace_params; +} + +static QueueHandle_t s_q; + +/* Producer: sends a counter value to s_q every 50 ms. Generates Q_SEND + * trace events and unblocks the consumer (driving TASK_IN switches). */ +static void producer(void *arg) +{ + uint32_t v = 0; + while (1) { + xQueueSend(s_q, &v, portMAX_DELAY); + v++; + vTaskDelay(50 / portTICK_PERIOD_MS); + } +} + +/* Consumer: blocks on s_q indefinitely. Each receive wakes this task and + * fires a TASK_IN trace event when the scheduler switches us in. */ +static void consumer(void *arg) +{ + uint32_t v; + while (1) { + xQueueReceive(s_q, &v, portMAX_DELAY); + } +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Start of trace session"); + + // Wait some time for host to be ready + vTaskDelay(2000 / portTICK_PERIOD_MS); + + esp_trace_start(); + + s_q = xQueueCreate(4, sizeof(uint32_t)); + + xTaskCreatePinnedToCore(producer, "producer", 2048, NULL, 5, NULL, 0); + xTaskCreatePinnedToCore(consumer, "consumer", 2048, NULL, 5, NULL, portNUM_PROCESSORS - 1); + + // 1 second delay is enough to generate the expected number of trace events. + vTaskDelay(1000 / portTICK_PERIOD_MS); + + esp_trace_stop(); + esp_trace_flush(); + + ESP_LOGI(TAG, "End of trace session"); +} diff --git a/examples/system/esp_trace/pytest_esp_trace.py b/examples/system/esp_trace/pytest_esp_trace.py new file mode 100644 index 00000000000..d1f031f5201 --- /dev/null +++ b/examples/system/esp_trace/pytest_esp_trace.py @@ -0,0 +1,115 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import os.path +import re +import time + +import pytest +import serial +from pytest_embedded_idf import IdfDut +from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets + +# Matches lines emitted by encode() in trace_FreeRTOS.c, e.g. +# [+ 12345 us] TASK_IN producer +# [+ 12 us] Q_SEND q=0x3fc8a210 +TRACE_LINE_RE = re.compile(r'^\[\+\s*\d+ us\] ([A-Z_]+)\s*(.*)$') + +# Minimum number of occurrences each event type must reach within the capture window. +EXPECTED_MIN_COUNTS = { + 'TASK_CREATE': 2, # producer + consumer + 'Q_CREATE': 1, # xQueueCreate in app_main + 'TASK_IN': 5, # producer/consumer + idle task + 'Q_SEND': 5, # producer sends every 50 ms + 'Q_RECEIVE': 5, # consumer receives every send + 'ISR_IN': 5, # FreeRTOS systick +} + +ISR_EXIT_MIN = 5 + +# Fraction of malformed lines we tolerate before failing. +MAX_MALFORMED_RATIO = 0.05 + + +def _validate_trace_data(trace_log_path: str) -> None: + """Validate the human-readable trace log produced by ext_trace_lib.""" + with open(trace_log_path, encoding='utf-8', errors='replace') as f: + lines = [line.rstrip() for line in f if line.strip()] + + assert lines, f'No trace data captured in {trace_log_path}' + + counts: dict[str, int] = {} + create_names: set[str] = set() + malformed = 0 + + for line in lines: + m = TRACE_LINE_RE.match(line) + if not m: + malformed += 1 + continue + evt, detail = m.group(1), m.group(2).strip() + counts[evt] = counts.get(evt, 0) + 1 + if evt == 'TASK_CREATE': + create_names.add(detail) + + ratio = malformed / len(lines) + assert ratio <= MAX_MALFORMED_RATIO, ( + f'Too many malformed lines: {malformed}/{len(lines)} ' + f'({ratio:.1%} > {MAX_MALFORMED_RATIO:.0%}). Likely USJ TX ring overflow; ' + f'bump CONFIG_ESP_TRACE_USJ_TX_BUFFER_SIZE or slow down the producer.' + ) + + for evt, minimum in EXPECTED_MIN_COUNTS.items(): + seen = counts.get(evt, 0) + assert seen >= minimum, f'Expected at least {minimum} {evt} events, got {seen}' + + isr_exits = counts.get('ISR_OUT', 0) + counts.get('ISR_YIELD', 0) + assert isr_exits >= ISR_EXIT_MIN, ( + f'Expected at least {ISR_EXIT_MIN} ISR_OUT+ISR_YIELD events, ' + f'got {isr_exits} (ISR_OUT={counts.get("ISR_OUT", 0)}, ' + f'ISR_YIELD={counts.get("ISR_YIELD", 0)})' + ) + + assert 'producer' in create_names, f'producer task not seen in TASK_CREATE: {create_names}' + assert 'consumer' in create_names, f'consumer task not seen in TASK_CREATE: {create_names}' + + +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: + try: + if ser.in_waiting: + f.write(ser.read(ser.in_waiting)) + except serial.SerialTimeoutException: + assert False, 'Timeout reached while reading from serial port, exiting...' + + # Drain anything still in flight after the capture window. + time.sleep(0.2) + end_time = time.time() + 1.0 + last_data_time = time.time() + while time.time() < end_time and (time.time() - last_data_time) <= 0.3: + try: + if ser.in_waiting: + f.write(ser.read(ser.in_waiting)) + last_data_time = time.time() + except serial.SerialTimeoutException: + assert False, 'Timeout reached while reading from serial port, exiting...' + + +@pytest.mark.usb_serial_jtag +@idf_parametrize('target', soc_filtered_targets('SOC_USB_SERIAL_JTAG_SUPPORTED == 1'), indirect=['target']) +@pytest.mark.parametrize('config', [pytest.param('default')], indirect=True) +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='lack of runner # TODO: IDFCI-10703') +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 + 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') + + _capture_trace(ser, trace_log_path) + _validate_trace_data(trace_log_path) diff --git a/examples/system/esp_trace/sdkconfig.defaults b/examples/system/esp_trace/sdkconfig.defaults new file mode 100644 index 00000000000..f90dbb5d285 --- /dev/null +++ b/examples/system/esp_trace/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_ESP_TRACE_ENABLE=y +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 From 90576f7b667752d5bf8e3743a253defcf217eb84 Mon Sep 17 00:00:00 2001 From: renpeiying Date: Tue, 26 May 2026 10:54:45 +0800 Subject: [PATCH 5/5] docs: Update CN translation --- docs/zh_CN/api-guides/app_trace.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/zh_CN/api-guides/app_trace.rst b/docs/zh_CN/api-guides/app_trace.rst index ed71462c971..2d4965cc10d 100644 --- a/docs/zh_CN/api-guides/app_trace.rst +++ b/docs/zh_CN/api-guides/app_trace.rst @@ -560,8 +560,8 @@ Start 子命令语法: 应用示例 """""""" -- :example:`system/sysview_tracing` 演示了如何使用 SEGGER SystemView 跟踪 FreeRTOS 任务和系统事件。 -- :example:`system/sysview_tracing_heap_log` 演示了如何在 SystemView 事件的同时跟踪堆内存分配。 +- :example:`system/sysview_tracing` 演示如何使用 SEGGER SystemView 记录 FreeRTOS 任务与系统事件。 +- :example:`system/sysview_tracing_heap_log` 演示如何在记录 SystemView 事件的同时,对堆内存分配进行跟踪。 .. _app_trace-gcov-source-code-coverage: