Merge branch 'feat/esp_trace_example_v6.0' into 'release/v6.0'

esp_trace example library integration (v6.0)

See merge request espressif/esp-idf!49092
This commit is contained in:
Jiang Jiang Jian
2026-06-29 15:35:30 +08:00
32 changed files with 1593 additions and 22 deletions
+10 -1
View File
@@ -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")
+26
View File
@@ -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"
+20 -4
View File
@@ -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
@@ -226,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.
@@ -407,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
@@ -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 <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#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);
+22 -1
View File
@@ -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
*
@@ -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
*/
@@ -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 {
/**
@@ -49,8 +55,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
@@ -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 {
/**
@@ -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 */
+82 -1
View File
@@ -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 <stdbool.h>
/**
* @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
+2
View File
@@ -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
+50 -7
View File
@@ -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);
@@ -150,9 +151,51 @@ 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->is_host_connected) {
if (!h || !h->transport.vt || !h->transport.vt->is_host_connected) {
return false;
}
@@ -161,7 +204,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 +226,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);
}
}
+104 -1
View File
@@ -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 <stdint.h>
#include <string.h>
#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);
}