From 35139f1647c946da865fe1613a1edee190feba40 Mon Sep 17 00:00:00 2001 From: erhankur Date: Tue, 23 Jun 2026 20:57:14 +0200 Subject: [PATCH] feat(esp_sysview): add function tracing SystemView backend --- esp_sysview/CMakeLists.txt | 17 +++-- esp_sysview/Kconfig | 13 +++- esp_sysview/README.md | 10 +++ esp_sysview/idf_component.yml | 2 +- esp_sysview/src/SEGGER/SEGGER_SYSVIEW.c | 29 ++++++++ esp_sysview/src/SEGGER/SEGGER_SYSVIEW.h | 1 - .../esp/SEGGER_SYSVIEW_Config_FreeRTOS.c | 6 ++ esp_sysview/src/esp/SEGGER_RTT_esp.c | 7 +- esp_sysview/src/esp/SEGGER_SYSVIEW_esp.c | 19 ----- esp_sysview/src/esp/adapter_encoder_sysview.c | 22 ++++++ esp_sysview/src/esp/adapter_encoder_sysview.h | 18 ++++- esp_sysview/src/esp/function_trace_sysview.c | 71 +++++++++++++++++++ 12 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 esp_sysview/src/esp/function_trace_sysview.c diff --git a/esp_sysview/CMakeLists.txt b/esp_sysview/CMakeLists.txt index 71d0923caa..d09804de74 100644 --- a/esp_sysview/CMakeLists.txt +++ b/esp_sysview/CMakeLists.txt @@ -1,7 +1,9 @@ -if(CONFIG_ESP_TRACE_LIB_EXTERNAL) - set(include_dirs "") - set(srcs "") +set(include_dirs "") +set(srcs "") +set(requires "esp_trace") +set(priv_requires "esp_app_format") +if(CONFIG_ESP_TRACE_LIB_EXTERNAL) list(APPEND include_dirs src/Config src/SEGGER @@ -17,6 +19,10 @@ if(CONFIG_ESP_TRACE_LIB_EXTERNAL) src/esp/adapter_encoder_sysview.c src/ext/logging.c) + if(CONFIG_ESP_TRACE_FUNCTION_TRACE) + list(APPEND srcs src/esp/function_trace_sysview.c) + endif() + if(CONFIG_HEAP_TRACING_TOHOST) list(APPEND srcs src/ext/heap_trace_module.c @@ -30,7 +36,8 @@ if(CONFIG_ESP_TRACE_LIB_EXTERNAL) idf_component_register(SRCS "${srcs}" INCLUDE_DIRS "${include_dirs}" - REQUIRES esp_trace + REQUIRES "${requires}" + PRIV_REQUIRES "${priv_requires}" LDFRAGMENTS ${CMAKE_CURRENT_BINARY_DIR}/linker.lf WHOLE_ARCHIVE TRUE # Link all symbols so self-registering adapters (ESP_TRACE_REGISTER_*) are not discarded ) @@ -40,5 +47,5 @@ if(CONFIG_ESP_TRACE_LIB_EXTERNAL) idf_component_get_property(freertos_lib freertos COMPONENT_LIB) target_include_directories(${freertos_lib} INTERFACE ${include_dirs}) else() - idf_component_register(REQUIRES "esp_trace") + idf_component_register(REQUIRES "${requires}" PRIV_REQUIRES "${priv_requires}") endif() diff --git a/esp_sysview/Kconfig b/esp_sysview/Kconfig index 7b9af4bd71..6bec042cec 100644 --- a/esp_sysview/Kconfig +++ b/esp_sysview/Kconfig @@ -1,11 +1,20 @@ menu "SEGGER SystemView Configuration" depends on ESP_TRACE_LIB_EXTERNAL + + config SEGGER_SYSVIEW_UART_AVAILABLE + bool + default y if ESP_TRACE_TRANSPORT_APPTRACE && (APPTRACE_DEST_UART || APPTRACE_DEST_ALL) + choice SEGGER_SYSVIEW_DEST_CPU prompt "CPU to trace" - depends on ESP_TRACE_TRANSPORT_APPTRACE && APPTRACE_DEST_UART && !ESP_SYSTEM_SINGLE_CORE_MODE + depends on SEGGER_SYSVIEW_UART_AVAILABLE && !ESP_SYSTEM_SINGLE_CORE_MODE default SEGGER_SYSVIEW_DEST_CPU_0 help - Define the CPU to trace. + Select the CPU to trace when SystemView uses a single UART stream. + + UART transport carries one SystemView stream, so events from only one + CPU can be represented reliably. JTAG tracing can keep + events from multiple CPUs and this option is ignored there. config SEGGER_SYSVIEW_DEST_CPU_0 bool "CPU0" diff --git a/esp_sysview/README.md b/esp_sysview/README.md index e0da6583e8..20e972e9a1 100644 --- a/esp_sysview/README.md +++ b/esp_sysview/README.md @@ -19,6 +19,16 @@ Configure SystemView tracing in `idf.py menuconfig`: - Select timestamp source: Component config > ESP Trace Configuration > Trace timestamp source - Configure event filters: Component config > SEGGER SystemView Configuration +## Function tracing + +When `CONFIG_ESP_TRACE_FUNCTION_TRACE` is enabled, this component implements the +`esp_trace` function-trace encoder callbacks and sends enter/exit events through +a dedicated SystemView module (`ESP_FunctionTrace`). Host-driven start/stop (over +JTAG or UART) is propagated to the function-trace runtime so recording follows +the SystemView session. Addresses are sent as `U32`. SystemView records them as +raw addresses. Symbols can be mapped to function names offline against the ELF file (for example +with `addr2line`). + ## Documentation and examples - ESP-IDF examples: diff --git a/esp_sysview/idf_component.yml b/esp_sysview/idf_component.yml index 712903a021..a0634bfb82 100644 --- a/esp_sysview/idf_component.yml +++ b/esp_sysview/idf_component.yml @@ -1,4 +1,4 @@ -version: 1.0.2 +version: 1.1.0 description: SEGGER SystemView component for ESP-IDF url: https://github.com/espressif/idf-extra-components/tree/master/esp_sysview issues: https://github.com/espressif/idf-extra-components/issues diff --git a/esp_sysview/src/SEGGER/SEGGER_SYSVIEW.c b/esp_sysview/src/SEGGER/SEGGER_SYSVIEW.c index e21e196861..528ca4f473 100644 --- a/esp_sysview/src/SEGGER/SEGGER_SYSVIEW.c +++ b/esp_sysview/src/SEGGER/SEGGER_SYSVIEW.c @@ -150,10 +150,19 @@ Additional information: #include #include #include +#include #include #include "SEGGER_SYSVIEW_Int.h" #include "SEGGER_RTT.h" +/* + * ESP: notify esp_trace core so host-driven start/stop propagates to + * dependent features (e.g. function tracing). Declared weak so esp_sysview + * still links against an esp_trace revision that does not provide it. + * The notification becomes a no-op when the symbol is absent. + */ +extern void esp_trace_notify_recording_state(bool active) __attribute__((weak)); + /********************************************************************* * * Defines, fixed @@ -1902,6 +1911,7 @@ void SEGGER_SYSVIEW_RecordString(unsigned int EventID, const char *pString) */ void SEGGER_SYSVIEW_Start(void) { + U8 WasEnabled = _SYSVIEW_Globals.EnableState; /* ESP */ #if (SEGGER_SYSVIEW_CAN_RESTART == 0) if (_SYSVIEW_Globals.EnableState == 0) { #endif @@ -1933,10 +1943,24 @@ void SEGGER_SYSVIEW_Start(void) SEGGER_SYSVIEW_RecordSystime(); SEGGER_SYSVIEW_SendTaskList(); SEGGER_SYSVIEW_SendNumModules(); + /* ESP: send module descriptions on start so streaming captures (e.g. over + * JTAG, where the host never requests them) are self-describing. */ + if (_NumModules > 0) { + int n; + for (n = 0; n < _NumModules; n++) { + SEGGER_SYSVIEW_SendModule(n); + } + } #endif #if (SEGGER_SYSVIEW_CAN_RESTART == 0) } #endif + /* ESP: notify on an actual disabled->enabled transition, or unconditionally + * for restart-capable builds. This avoids resetting dependent features + * (e.g. function tracing) on redundant Start commands. */ + if ((WasEnabled == 0 || SEGGER_SYSVIEW_CAN_RESTART) && esp_trace_notify_recording_state) { + esp_trace_notify_recording_state(true); /* ESP */ + } } /********************************************************************* @@ -1959,6 +1983,11 @@ void SEGGER_SYSVIEW_Start(void) void SEGGER_SYSVIEW_Stop(void) { U8 *pPayloadStart; + + if (esp_trace_notify_recording_state) { + esp_trace_notify_recording_state(false); /* ESP */ + } + RECORD_START(SEGGER_SYSVIEW_INFO_SIZE); // We should send answer for the Stop command in any case. _SYSVIEW_Globals.EnableState = 1; diff --git a/esp_sysview/src/SEGGER/SEGGER_SYSVIEW.h b/esp_sysview/src/SEGGER/SEGGER_SYSVIEW.h index 825cc1ba2b..f6513ad7eb 100644 --- a/esp_sysview/src/SEGGER/SEGGER_SYSVIEW.h +++ b/esp_sysview/src/SEGGER/SEGGER_SYSVIEW.h @@ -426,7 +426,6 @@ void SEGGER_SYSVIEW_X_OnEventRecorded (unsigned NumBytes); */ int SEGGER_SYSVIEW_ESP_SetEncoder(void *encoder); void *SEGGER_SYSVIEW_ESP_GetEncoder(void); -int SEGGER_SYSVIEW_ESP_GetDestCpu(void); #ifdef __cplusplus } diff --git a/esp_sysview/src/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c b/esp_sysview/src/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c index 59f760f1ec..e7cc0777bb 100644 --- a/esp_sysview/src/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c +++ b/esp_sysview/src/Sample/FreeRTOSV10.4/Config/esp/SEGGER_SYSVIEW_Config_FreeRTOS.c @@ -62,6 +62,7 @@ Revision: $Rev: 7745 $ #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "SEGGER_SYSVIEW.h" +#include "esp_app_desc.h" #include "esp_intr_alloc.h" #include "soc/soc.h" #include "soc/interrupts.h" @@ -128,6 +129,11 @@ static void _cbSendSystemDesc(void) strncat(irq_str, esp_isr_names[i], sizeof(irq_str) - strlen(irq_str) - 1); SEGGER_SYSVIEW_SendSysDesc(irq_str); } + /* Application ELF identity so host tools can resolve the matching ELF */ + char elf_desc[sizeof("ELF_SHA256=") + CONFIG_APP_RETRIEVE_LEN_ELF_SHA]; + strlcpy(elf_desc, "ELF_SHA256=", sizeof(elf_desc)); + strlcat(elf_desc, esp_app_get_elf_sha256_str(), sizeof(elf_desc)); + SEGGER_SYSVIEW_SendSysDesc(elf_desc); } /********************************************************************* diff --git a/esp_sysview/src/esp/SEGGER_RTT_esp.c b/esp_sysview/src/esp/SEGGER_RTT_esp.c index 95aacec3dd..46c504c4d3 100644 --- a/esp_sysview/src/esp/SEGGER_RTT_esp.c +++ b/esp_sysview/src/esp/SEGGER_RTT_esp.c @@ -15,6 +15,7 @@ #include "esp_trace_port_transport.h" #include "esp_trace_port_encoder.h" #include "esp_trace_types.h" +#include "adapter_encoder_sysview.h" #include "esp_private/startup_internal.h" const static char *TAG = "segger_rtt"; @@ -163,10 +164,10 @@ unsigned SEGGER_RTT_WriteSkipNoLock(unsigned BufferIndex, const void *pBuffer, u uint8_t event_id = *pbuf; esp_trace_link_types_t link_type = tp->vt->get_link_type(tp); - if (link_type != ESP_TRACE_LINK_DEBUG_PROBE) { // Uart and USB Serial JTAG are handled separately - int dest_cpu = SEGGER_SYSVIEW_ESP_GetDestCpu(); + sysview_encoder_ctx_t *ctx = encoder->ctx; + if (ctx && ctx->filter_by_cpu) { if ( - (dest_cpu != esp_cpu_get_core_id()) && + (ctx->dest_cpu != esp_cpu_get_core_id()) && ( (event_id == SYSVIEW_EVTID_ISR_ENTER) || (event_id == SYSVIEW_EVTID_ISR_EXIT) || diff --git a/esp_sysview/src/esp/SEGGER_SYSVIEW_esp.c b/esp_sysview/src/esp/SEGGER_SYSVIEW_esp.c index 480f4786f9..4d47ce4d10 100644 --- a/esp_sysview/src/esp/SEGGER_SYSVIEW_esp.c +++ b/esp_sysview/src/esp/SEGGER_SYSVIEW_esp.c @@ -79,22 +79,3 @@ void *SEGGER_SYSVIEW_ESP_GetEncoder(void) { return s_encoder; } - -/********************************************************************* -* -* SEGGER_SYSVIEW_ESP_GetDestCpu() -* -* Function description -* Gets the destination CPU from the encoder context. -* -* Parameters -* None -* -* Return value -* CPU ID (0 or 1) to trace -*/ -int SEGGER_SYSVIEW_ESP_GetDestCpu(void) -{ - sysview_encoder_ctx_t *ctx = s_encoder->ctx; - return ctx->dest_cpu; -} diff --git a/esp_sysview/src/esp/adapter_encoder_sysview.c b/esp_sysview/src/esp/adapter_encoder_sysview.c index 8af3a7c22d..f60334e01b 100644 --- a/esp_sysview/src/esp/adapter_encoder_sysview.c +++ b/esp_sysview/src/esp/adapter_encoder_sysview.c @@ -23,6 +23,8 @@ * All encoding and transport operations are done by SystemView component. (SEGGER_RTT_esp.c) */ +extern void esp_trace_notify_recording_state(bool active) __attribute__((weak)); + #define SYSVIEW_FLUSH_TMO_US (1000 * 1000) /* 1second */ #define SYSVIEW_FLUSH_THRESH 0 @@ -51,6 +53,10 @@ static esp_err_t init(esp_trace_encoder_t *enc, const void *enc_cfg) const esp_trace_sysview_config_t *cfg = enc_cfg; int dest_cpu = 0; // Default to CPU0 +#if CONFIG_SEGGER_SYSVIEW_DEST_CPU_1 + dest_cpu = 1; +#endif + if (cfg) { dest_cpu = cfg->dest_cpu; } @@ -64,6 +70,10 @@ static esp_err_t init(esp_trace_encoder_t *enc, const void *enc_cfg) /* Segger Sysview needs locking mechanism. */ esp_trace_lock_init(&ctx->lock); ctx->dest_cpu = dest_cpu; + ctx->filter_by_cpu = true; + if (enc->tp && enc->tp->vt && enc->tp->vt->get_link_type) { + ctx->filter_by_cpu = (enc->tp->vt->get_link_type(enc->tp) != ESP_TRACE_LINK_DEBUG_PROBE); + } enc->ctx = ctx; if (SEGGER_SYSVIEW_ESP_SetEncoder(enc) != 0) { @@ -84,6 +94,14 @@ static esp_err_t init(esp_trace_encoder_t *enc, const void *enc_cfg) SEGGER_SYSVIEW_Conf(); +#if CONFIG_ESP_TRACE_FUNCTION_TRACE + esp_sysview_function_trace_register(); +#endif + + if (esp_trace_notify_recording_state) { + esp_trace_notify_recording_state(SEGGER_SYSVIEW_Started() != 0); + } + initialized = true; return ESP_OK; @@ -142,6 +160,10 @@ static const esp_trace_encoder_vtable_t s_sysview_vt = { .panic_handler = panic_handler, .take_lock = take_lock, .give_lock = give_lock, +#if CONFIG_ESP_TRACE_FUNCTION_TRACE + .function_enter = esp_sysview_function_enter, + .function_exit = esp_sysview_function_exit, +#endif }; ESP_TRACE_REGISTER_ENCODER("sysview", &s_sysview_vt); diff --git a/esp_sysview/src/esp/adapter_encoder_sysview.h b/esp_sysview/src/esp/adapter_encoder_sysview.h index cd367324cc..c0ded6b672 100644 --- a/esp_sysview/src/esp/adapter_encoder_sysview.h +++ b/esp_sysview/src/esp/adapter_encoder_sysview.h @@ -5,6 +5,7 @@ */ #pragma once +#include #include "esp_trace_util.h" #ifdef __cplusplus @@ -18,8 +19,8 @@ typedef struct { /** * @brief CPU to trace (0 or 1) * - * Determines which CPU's events are captured by SystemView. - * Only relevant when using UART apptrace transport. + * UART apptrace carries one SystemView stream, so events are filtered to + * this CPU. JTAG tracing keeps multicore events and ignores this value. * * - 0: Capture events from CPU0 (Pro CPU) * - 1: Capture events from CPU1 (App CPU) @@ -38,9 +39,22 @@ typedef struct { */ typedef struct { int dest_cpu; ///< CPU to trace (0 or 1) + bool filter_by_cpu; ///< true for single-stream transports that cannot represent multicore events esp_trace_lock_t lock; ///< Encoder lock } sysview_encoder_ctx_t; +/* Forward declaration to avoid pulling the encoder port header here. */ +struct esp_trace_encoder; + +/** @brief Send a function entry event (esp_trace function-trace callback). */ +void esp_sysview_function_enter(struct esp_trace_encoder *enc, void *func, void *call_site); + +/** @brief Send a function exit event (esp_trace function-trace callback). */ +void esp_sysview_function_exit(struct esp_trace_encoder *enc, void *func, void *call_site); + +/** @brief Register the function-trace SystemView module (call once at init). */ +void esp_sysview_function_trace_register(void); + #ifdef __cplusplus } #endif diff --git a/esp_sysview/src/esp/function_trace_sysview.c b/esp_sysview/src/esp/function_trace_sysview.c new file mode 100644 index 0000000000..4bcd97dfc0 --- /dev/null +++ b/esp_sysview/src/esp/function_trace_sysview.c @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * SystemView backend for esp_trace compiler-instrumented function tracing. + */ + +#include +#include +#include "esp_cpu.h" +#include "esp_trace_port_encoder.h" +#include "adapter_encoder_sysview.h" +#include "SEGGER_SYSVIEW.h" + +/* Event IDs sent as EventOffset + id. EventOffset is allocated in registration order starting at 512. */ +enum { + FT_EVT_ENTER = 0, + FT_EVT_EXIT, + FT_EVT_COUNT, +}; + +static SEGGER_SYSVIEW_MODULE s_ft_module = { + .sModule = + "M=ESP_FunctionTrace, " + "0 function_enter func=%p call_site=%p, " + "1 function_exit func=%p call_site=%p", + .NumEvents = FT_EVT_COUNT, +}; + +static bool s_registered; + +static inline bool should_drop_for_cpu(struct esp_trace_encoder *enc) +{ + sysview_encoder_ctx_t *ctx = enc->ctx; + return ctx && ctx->filter_by_cpu && (esp_cpu_get_core_id() != ctx->dest_cpu); +} + +void esp_sysview_function_trace_register(void) +{ + if (!s_registered) { + SEGGER_SYSVIEW_RegisterModule(&s_ft_module); + s_registered = true; + } +} + +void esp_sysview_function_enter(struct esp_trace_encoder *enc, void *func, void *call_site) +{ + if (!s_registered || should_drop_for_cpu(enc)) { + return; + } + U8 aPacket[SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32]; + U8 *pPayload = SEGGER_SYSVIEW_PREPARE_PACKET(aPacket); + pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, (U32)(uintptr_t)func); + pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, (U32)(uintptr_t)call_site); + SEGGER_SYSVIEW_SendPacket(aPacket, pPayload, s_ft_module.EventOffset + FT_EVT_ENTER); +} + +void esp_sysview_function_exit(struct esp_trace_encoder *enc, void *func, void *call_site) +{ + if (!s_registered || should_drop_for_cpu(enc)) { + return; + } + U8 aPacket[SEGGER_SYSVIEW_INFO_SIZE + 2 * SEGGER_SYSVIEW_QUANTA_U32]; + U8 *pPayload = SEGGER_SYSVIEW_PREPARE_PACKET(aPacket); + pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, (U32)(uintptr_t)func); + pPayload = SEGGER_SYSVIEW_EncodeU32(pPayload, (U32)(uintptr_t)call_site); + SEGGER_SYSVIEW_SendPacket(aPacket, pPayload, s_ft_module.EventOffset + FT_EVT_EXIT); +}