mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-03 03:31:41 +03:00
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:
@@ -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")
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user