diff --git a/components/esp_riscv_trace/CMakeLists.txt b/components/esp_riscv_trace/CMakeLists.txt index c641cb9c7b1..d1e8f1bd976 100644 --- a/components/esp_riscv_trace/CMakeLists.txt +++ b/components/esp_riscv_trace/CMakeLists.txt @@ -2,22 +2,32 @@ idf_build_get_property(target IDF_TARGET) set(srcs) set(public_include "include") +set(ldfragments) if(CONFIG_ESP_RISCV_TRACE_ENABLE) - list(APPEND srcs "src/esp_riscv_trace.c") + list(APPEND srcs "src/esp_riscv_trace.c" "src/esp_riscv_trace_snapshot.c") + if(CONFIG_ESP_COREDUMP_ENABLE) + list(APPEND srcs "src/esp_riscv_trace_coredump.c") + list(APPEND ldfragments "linker.lf") + endif() endif() -if(${target} STREQUAL "linux") - set(priv_requires "") - set(requires "") -else() - set(priv_requires esp_mm) - set(requires esp_hal_debug_assist) +set(priv_requires) +if(NOT ${target} STREQUAL "linux") + set(priv_requires esp_mm hal esp_app_format esp_hw_support esp_hal_debug_assist) endif() idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${public_include} - PRIV_INCLUDE_DIRS "src" - REQUIRES "${requires}" + PRIV_INCLUDE_DIRS "src" "private_include" PRIV_REQUIRES "${priv_requires}" + LDFRAGMENTS "${ldfragments}" ) + +if(CONFIG_ESP_RISCV_TRACE_ENABLE) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_panic_handler_inst_trace_stop") + if(CONFIG_ESP_COREDUMP_ENABLE) + idf_component_optional_requires(PRIVATE espcoredump) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_riscv_trace_coredump_include_func") + endif() +endif() diff --git a/components/esp_riscv_trace/Kconfig b/components/esp_riscv_trace/Kconfig index d2ce4541f62..3bef58aa61b 100644 --- a/components/esp_riscv_trace/Kconfig +++ b/components/esp_riscv_trace/Kconfig @@ -7,11 +7,27 @@ menu "RISC-V Trace Encoder Configurations" help Build the ESP RISC-V trace encoder driver and expose its runtime API. + When core dump is also enabled, a panic freezes the snapshot and the + coredump writer stores an ESP_RISCV_TRACE note plus the per-core buffers. + if ESP_RISCV_TRACE_ENABLE + config ESP_RISCV_TRACE_AUTOSTART + bool "Start capture automatically at startup" + default y if ESP_COREDUMP_ENABLE + default n + help + Start the encoder on every selected core during system startup, + before the scheduler runs. If a panic happens after that point, + the core dump includes the recent execution history even if the + application did not start tracing manually. + + In loop mode with periodic resync (the defaults), the buffer + keeps the latest trace data before the panic. + config ESP_RISCV_TRACE_BUFFER_SIZE int "Trace buffer size (bytes)" - default 2048 + default 8192 help Size in bytes of the trace buffer allocated by the driver. @@ -163,10 +179,19 @@ menu "RISC-V Trace Encoder Configurations" choice ESP_RISCV_TRACE_RESYNC_MODE_SELECT prompt "Resynchronization mode" + default ESP_RISCV_TRACE_RESYNC_MODE_PACKET if ESP_RISCV_TRACE_MEM_MODE_LOOP default ESP_RISCV_TRACE_RESYNC_MODE_DISABLED help Periodic resynchronization mode of the trace encoder. + In loop memory mode the initial synchronization packet is + overwritten once the ring buffer wraps, leaving the decoder + with no anchor. PACKET writes a fresh sync every + RESYNC_THRESHOLD packets so a wrapped capture stays + decodable. DISABLED is safe only with non-loop memory mode, + where the encoder stops before the initial sync can be + overwritten. + config ESP_RISCV_TRACE_RESYNC_MODE_DISABLED bool "Disabled" config ESP_RISCV_TRACE_RESYNC_MODE_PACKET diff --git a/components/esp_riscv_trace/README.md b/components/esp_riscv_trace/README.md index 86d90e6de7d..67e3f6ae4cc 100644 --- a/components/esp_riscv_trace/README.md +++ b/components/esp_riscv_trace/README.md @@ -4,12 +4,11 @@ The `esp_riscv_trace` component provides the public driver API for the RISC-V trace encoder peripheral. The driver is enabled by -`CONFIG_ESP_RISCV_TRACE_ENABLE` and creates one encoder handle per selected core -during startup auto-initialization. +`CONFIG_ESP_RISCV_TRACE_ENABLE` and creates one encoder handle per core during +startup. Applications can override the weak `esp_riscv_trace_get_user_config(int core_id)` -function to customize the startup configuration per core (each encoder can be -configured independently), or use Kconfig defaults through +function to customize configuration per core, or use Kconfig defaults via `ESP_RISCV_TRACE_DEFAULT_CONFIG()`. ## State Transition @@ -22,34 +21,67 @@ stateDiagram-v2 stopped --> started: esp_riscv_trace_start ``` -`esp_riscv_trace_set_filter()` and `esp_riscv_trace_get_buffer()` are only valid -while the encoder is not started. `esp_riscv_trace_get_status()` can be used to -read a coherent status snapshot. +`esp_riscv_trace_set_filter()` and `esp_riscv_trace_get_buffer()` are valid only +while the encoder is stopped. `esp_riscv_trace_get_status()` reads the current +status. + +With `CONFIG_ESP_RISCV_TRACE_AUTOSTART` the driver also starts each encoder +during startup, so the application does not need to call +`esp_riscv_trace_start()`. This option defaults to enabled when core dumps are +enabled, so a panic always carries the recent execution history. ## Concurrency -Public driver APIs are serialized per trace core with a task-level lock. They -are task-context APIs and must not be called from ISR context. +Public driver APIs use a per-core lock. They must only be called from FreeRTOS +tasks, not from interrupt service routines (ISRs). -The driver keeps the lifecycle state check and the corresponding HAL register -operation under the same per-core lock. This prevents concurrent callers from -double-starting an encoder, racing a stop against filter programming, or reading -the buffer before a stop has completed its cache synchronization. +The driver checks lifecycle state and executes HAL register operations under the +same per-core lock. This prevents race conditions between start, stop, filter +configuration, and buffer cache synchronization. + +## Snapshot + +The snapshot is an in-memory structure that describes a capture. Firmware +updates `g_esp_riscv_trace_snapshot` as encoders start, stop, or freeze on +panic. + +The root descriptor contains the ABI version, chip ID, chip revision, capture +reason, and a `write_seq` counter. The `write_seq` counter is odd while the +snapshot is being updated and even when stable. `cores_addr` points to an array of +per-core records. Each record stores the trace buffer address, size, head offset, +encoder parameters, wire format, memory mode, and head offset. + +```text +g_esp_riscv_trace_snapshot + `-- cores_addr --> core_desc[core_count] + |-- buffer_addr --> raw encoder packets + `-- encoder_params_addr --> encoder params +``` + +## How Coredump Reads the Snapshot + +On panic, the driver freezes the snapshot. The coredump writer copies a +core buffer when that core is stopped or frozen, `head_valid` is set, and +the encoder FIFO is empty. + +The coredump writer adds those buffers as `PT_LOAD` segments and writes +metadata into an `ESP_RISCV_TRACE` ELF note (type 680). The `esp-riscv-trace` tool +reads the note and trace segments directly from the coredump ELF file. ## Buffer and Trace Stream Notes -The trace buffer must be reachable by the trace encoder AHB master. Driver -allocated buffers are placed in internal RAM or PSRAM according to -configuration, and are cache-line aligned when that memory is reached through a -data cache. Caller-provided buffers are validated for reachable memory and -cache-line alignment. PSRAM placement is only available on targets whose -encoder can reach external RAM (`SOC_RISCV_TRACE_MEM_SUPPORT_PSRAM`); on other -targets the buffer is always internal. +The trace buffer must be reachable by the trace encoder AHB master. +Driver-allocated buffers are placed in internal RAM or PSRAM based on +configuration, and are cache-line aligned when placed in cached memory. +Caller-provided buffers are validated for memory reachability and cache-line +alignment. PSRAM placement is only available on targets where the encoder can +access external RAM (`SOC_RISCV_TRACE_MEM_SUPPORT_PSRAM`). On other targets, +the buffer is always in internal RAM. -In loop memory mode, wrapped buffers need periodic resynchronization packets to -remain decodable after the original start sync has been overwritten. +In loop memory mode, wrapped buffers require periodic resynchronization packets +to remain decodable after the initial sync packet is overwritten. ## Dependencies -This driver depends on the RISC-V trace HAL (part of the `hal` component) and -currently targets SoCs that support the RISC-V trace encoder peripheral. +This driver depends on the RISC-V trace HAL (`hal` component) and supports +SoCs with RISC-V trace hardware. diff --git a/components/esp_riscv_trace/include/esp_riscv_trace.h b/components/esp_riscv_trace/include/esp_riscv_trace.h index 9d53910c094..959f47334f1 100644 --- a/components/esp_riscv_trace/include/esp_riscv_trace.h +++ b/components/esp_riscv_trace/include/esp_riscv_trace.h @@ -42,9 +42,8 @@ typedef enum { /** * @brief AHB burst type used by the trace write master. * - * These are the trace IP's custom hburst field codes, NOT the standard AMBA - * HBURST encoding: 0=SINGLE, 1=INCR, 2=INCR4, 4=INCR8; values 3, 5, 6, 7 are - * invalid. Do not "correct" 2/4 to the AMBA INCR4/INCR8 codes (3/5). + * Custom hburst field codes (0=SINGLE, 1=INCR, 2=INCR4, 4=INCR8). + * Values 3, 5, 6, 7 are invalid. */ typedef enum { ESP_RISCV_TRACE_AHB_SINGLE = 0, /*!< Single transfer (hardware reset default) */ @@ -90,7 +89,7 @@ typedef enum { */ typedef enum { ESP_RISCV_TRACE_FILTER_PRIV_USER = 0, /*!< User mode */ - ESP_RISCV_TRACE_FILTER_PRIV_SUPERVISOR = 1, /*!< Supervisor mode; needs SOC_RISCV_TRACE_PRIV_WIDTH >= 2 */ + ESP_RISCV_TRACE_FILTER_PRIV_SUPERVISOR = 1, /*!< Supervisor mode. Requires SOC_RISCV_TRACE_PRIV_WIDTH >= 2 */ ESP_RISCV_TRACE_FILTER_PRIV_MACHINE = 3, /*!< Machine mode */ } esp_riscv_trace_filter_priv_t; @@ -99,7 +98,7 @@ typedef struct { esp_riscv_trace_filter_input_t input; /*!< Input to compare (iaddr or tval) */ esp_riscv_trace_filter_comparator_func_t function; /*!< Compare function */ uint32_t match_value; /*!< 32-bit value compared against the input */ - bool notify; /*!< Emit a packet reporting the matching address */ + bool notify; /*!< Send a packet reporting the matching address */ } esp_riscv_trace_filter_comparator_t; /** @@ -109,7 +108,7 @@ typedef struct { * traces everything (the default). Apply via esp_riscv_trace_set_filter() before a capture. */ typedef struct { - bool enable; /*!< Master enable; false = trace everything */ + bool enable; /*!< Master enable. False = trace everything */ bool match_comparators; /*!< Gate matching on the comparators below */ esp_riscv_trace_filter_comparator_t primary; /*!< Primary (P) comparator */ esp_riscv_trace_filter_comparator_t secondary; /*!< Secondary (S) comparator */ diff --git a/components/esp_riscv_trace/linker.lf b/components/esp_riscv_trace/linker.lf new file mode 100644 index 00000000000..d36ac82aa5d --- /dev/null +++ b/components/esp_riscv_trace/linker.lf @@ -0,0 +1,18 @@ +[mapping:esp_riscv_trace] +archive: libesp_riscv_trace.a +entries: + # These run from the panic path. They need IRAM only when the panic handler + # itself runs without flash cache. + if ESP_PANIC_HANDLER_IRAM = y: + esp_riscv_trace:esp_panic_handler_inst_trace_stop (noflash) + esp_riscv_trace:snapshot_freeze_state (noflash) + esp_riscv_trace_snapshot:esp_riscv_trace_snapshot_panic_write (noflash) + esp_riscv_trace_snapshot:panic_write_begin (noflash) + esp_riscv_trace_snapshot:panic_write_end (noflash) + +[mapping:esp_riscv_trace_coredump] +archive: libesp_riscv_trace.a +entries: + # The coredump provider runs from the panic path. + if ESP_PANIC_HANDLER_IRAM = y && ESP_COREDUMP_ENABLE = y: + esp_riscv_trace_coredump (noflash) diff --git a/components/esp_riscv_trace/private_include/esp_riscv_trace_snapshot.h b/components/esp_riscv_trace/private_include/esp_riscv_trace_snapshot.h new file mode 100644 index 00000000000..5926bc3219a --- /dev/null +++ b/components/esp_riscv_trace/private_include/esp_riscv_trace_snapshot.h @@ -0,0 +1,171 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/* + * RISC-V trace snapshot target-memory ABI v1.0 + */ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Magic "RVTS" and ABI version. */ +#define ESP_RISCV_TRACE_SNAPSHOT_MAGIC ((uint32_t)0x53545652) +#define ESP_RISCV_TRACE_SNAPSHOT_ABI_MAJOR ((uint16_t)1) +#define ESP_RISCV_TRACE_SNAPSHOT_ABI_MINOR ((uint16_t)0) + +/* ABI 1.0 structure sizes. */ +#define ESP_RISCV_TRACE_SNAPSHOT_DESC_SIZE 32 +#define ESP_RISCV_TRACE_SNAPSHOT_CORE_DESC_SIZE 36 +#define ESP_RISCV_TRACE_ENCODER_PARAMS_SIZE 28 +#define ESP_RISCV_TRACE_ENCODER_PARAMS_VERSION 1 + +/* Reason the capture stopped. */ +typedef enum { + ESP_RISCV_TRACE_CAPTURE_REASON_UNKNOWN = 0, + ESP_RISCV_TRACE_CAPTURE_REASON_EXPLICIT_STOP = 1, + ESP_RISCV_TRACE_CAPTURE_REASON_PANIC = 2, +} esp_riscv_trace_capture_reason_t; + +/* Trace packet format. */ +typedef enum { + ESP_RISCV_TRACE_PACKET_FORMAT_UNKNOWN = 0, + ESP_RISCV_TRACE_PACKET_FORMAT_PT10 = 100, /**< Processor Trace v1.0 */ + ESP_RISCV_TRACE_PACKET_FORMAT_ET20 = 200, /**< Efficient Trace v2.0 */ +} esp_riscv_trace_packet_format_t; + +/* Per-core snapshot state. */ +typedef enum { + ESP_RISCV_TRACE_SNAPSHOT_STATE_UNAVAILABLE = 0, + ESP_RISCV_TRACE_SNAPSHOT_STATE_READY = 1, + ESP_RISCV_TRACE_SNAPSHOT_STATE_CAPTURING = 2, + ESP_RISCV_TRACE_SNAPSHOT_STATE_STOPPED = 3, + ESP_RISCV_TRACE_SNAPSHOT_STATE_FROZEN = 4, +} esp_riscv_trace_snapshot_state_t; + +/* Trace-buffer mode. */ +typedef enum { + ESP_RISCV_TRACE_MEMORY_MODE_UNKNOWN = 0, + ESP_RISCV_TRACE_MEMORY_MODE_LINEAR = 1, + ESP_RISCV_TRACE_MEMORY_MODE_LOOP = 2, +} esp_riscv_trace_snapshot_memory_mode_t; + +/* Instruction-address encoding. */ +typedef enum { + ESP_RISCV_TRACE_ADDRESS_MODE_UNKNOWN = 0, + ESP_RISCV_TRACE_ADDRESS_MODE_DELTA = 1, + ESP_RISCV_TRACE_ADDRESS_MODE_FULL = 2, +} esp_riscv_trace_snapshot_address_mode_t; + +/* Hardware resynchronization mode reported for a core. */ +typedef enum { + ESP_RISCV_TRACE_RESYNC_MODE_UNKNOWN = 0, + ESP_RISCV_TRACE_RESYNC_MODE_DISABLED = 1, + ESP_RISCV_TRACE_RESYNC_MODE_PACKET = 2, + ESP_RISCV_TRACE_RESYNC_MODE_CYCLE = 3, +} esp_riscv_trace_snapshot_resync_mode_t; + +/* + * Snapshot object graph in target memory: + * + * g_esp_riscv_trace_snapshot (esp_riscv_trace_snapshot_desc_t) + * |-- cores_addr --> esp_riscv_trace_snapshot_core_desc_t[core_count] + * | `-- buffer_addr --> raw trace bytes + * `-- encoder_params_addr --> esp_riscv_trace_encoder_params_t (shared by all cores) + */ + +/* Trace encoder parameters from the target TRM. */ +typedef struct { + uint8_t params_version; /*!< 1 */ + uint8_t params_size; /*!< 28 */ + uint8_t arch_p; /*!< Architecture version */ + uint8_t bpred_size_p; /*!< Branch prediction mode */ + uint8_t cache_size_p; /*!< Jump target cache mode */ + uint8_t call_counter_size_p; /*!< Implicit return mode */ + uint8_t ctype_width_p; /*!< Width of the ctype bus */ + uint8_t context_width_p; /*!< Width of the context bus */ + uint8_t ecause_width_p; /*!< Width of the exception cause */ + uint8_t ecause_choice_p; /*!< Multiple ecause choice */ + uint8_t f0s_width_p; /*!< Format 0 packets */ + uint8_t filter_context_p; /*!< Filtering on context */ + uint8_t filter_excint_p; /*!< Filtering on exception cause or interrupt */ + uint8_t filter_privilege_p; /*!< Filtering on privilege */ + uint8_t filter_tval_p; /*!< Filtering on trap value */ + uint8_t iaddress_lsb_p; /*!< Compressed instructions (address is PC >> 1) */ + uint8_t iaddress_width_p; /*!< Instruction bus width */ + uint8_t iretire_width_p; /*!< Width of the iretire bus */ + uint8_t ilastsize_width_p; /*!< Width of ilastsize */ + uint8_t itype_width_p; /*!< Width of the itype bus */ + uint8_t nocontext_p; /*!< Exclude context from te_inst packets */ + uint8_t notime_p; /*!< Exclude time from te_inst packets */ + uint8_t privilege_width_p; /*!< Width of the privilege field */ + uint8_t retires_p; /*!< Max instructions retired per block */ + uint8_t return_stack_size_p; /*!< Implicit return mode */ + uint8_t sijump_p; /*!< Sequentially inferable jump mode */ + uint8_t taken_branches_p; /*!< Instructions retired per cycle */ + uint8_t impdef_width_p; /*!< Implementation-defined field */ +} esp_riscv_trace_encoder_params_t; + +static_assert(sizeof(esp_riscv_trace_encoder_params_t) == ESP_RISCV_TRACE_ENCODER_PARAMS_SIZE, + "encoder params block must be 28 bytes"); + +/* Core configuration and capture state. */ +typedef struct { + uint32_t buffer_addr; /*!< Target address of the trace buffer */ + uint32_t capacity; /*!< Trace-buffer size in bytes */ + uint32_t head_offset; /*!< Next hardware write offset */ + uint32_t resync_threshold; /*!< Resync interval reported by hardware. Zero when not applicable */ + uint32_t fifo_status_raw; /*!< Unmodified hardware status register */ + uint32_t intr_status_raw; /*!< Unmodified hardware interrupt status */ + uint8_t core_id; /*!< Traced hart ID */ + uint8_t state; /*!< esp_riscv_trace_snapshot_state_t */ + uint8_t memory_mode; /*!< esp_riscv_trace_snapshot_memory_mode_t */ + uint8_t packet_format; /*!< esp_riscv_trace_packet_format_t */ + uint8_t address_mode; /*!< esp_riscv_trace_snapshot_address_mode_t */ + uint8_t resync_mode; /*!< esp_riscv_trace_snapshot_resync_mode_t */ + uint8_t reserved0; /*!< Unused. Stays zero */ + uint8_t head_valid; /*!< 1 when head_offset is the hardware write position */ + uint8_t fifo_empty; /*!< 1 when the encoder FIFO was empty after stop or freeze */ + uint8_t memory_full; /*!< 1 when the memory-full interrupt was set */ + uint8_t fifo_overflow; /*!< 1 when the FIFO-overflow interrupt was set */ + uint8_t reserved1; /*!< Keeps the descriptor 4-byte aligned */ +} esp_riscv_trace_snapshot_core_desc_t; + +static_assert(sizeof(esp_riscv_trace_snapshot_core_desc_t) == ESP_RISCV_TRACE_SNAPSHOT_CORE_DESC_SIZE, + "core descriptor must be 36 bytes"); + +/* Snapshot root descriptor. */ +typedef struct { + uint32_t magic; /*!< ESP_RISCV_TRACE_SNAPSHOT_MAGIC. Must be written last */ + uint32_t write_seq; /*!< Odd while the snapshot is changing */ + uint32_t cores_addr; /*!< Target address of the core array */ + uint32_t app_elf_sha256_addr; /*!< Target address of the raw ELF SHA-256 bytes */ + uint32_t encoder_params_addr; /*!< Target address of the shared encoder-parameter block */ + uint16_t target_id; /*!< ESP image chip ID */ + uint16_t chip_revision; /*!< Major times 100 plus minor */ + uint8_t abi_major; /*!< 1 */ + uint8_t abi_minor; /*!< 0 */ + uint8_t snapshot_desc_size; /*!< 32 */ + uint8_t core_desc_size; /*!< 36 */ + uint8_t core_count; /*!< Number of contiguous core entries */ + uint8_t capture_reason; /*!< esp_riscv_trace_capture_reason_t */ + uint8_t app_elf_sha256_size; /*!< Zero or 32 */ + uint8_t encoder_params_size; /*!< Encoder-parameter block size in bytes */ +} esp_riscv_trace_snapshot_desc_t; + +static_assert(sizeof(esp_riscv_trace_snapshot_desc_t) == ESP_RISCV_TRACE_SNAPSHOT_DESC_SIZE, + "snapshot descriptor must be 32 bytes"); +extern esp_riscv_trace_snapshot_desc_t g_esp_riscv_trace_snapshot; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_riscv_trace/project_include.cmake b/components/esp_riscv_trace/project_include.cmake new file mode 100644 index 00000000000..ecad11ad0ba --- /dev/null +++ b/components/esp_riscv_trace/project_include.cmake @@ -0,0 +1,12 @@ +idf_build_get_property(target IDF_TARGET) +idf_build_get_property(non_os_build NON_OS_BUILD) + +if(IDF_BUILD_V2 AND CONFIG_ESP_RISCV_TRACE_ENABLE + AND NOT non_os_build + AND NOT "${target}" STREQUAL "linux") + # This ensures that the component is added to the build when the trace encoder + # is enabled, even if no other component depends on it. + # This allows users to simply enable it in menuconfig without needing to modify + # their component dependencies. + idf_project_add_default_build_component(esp_riscv_trace) +endif() diff --git a/components/esp_riscv_trace/src/esp_riscv_trace.c b/components/esp_riscv_trace/src/esp_riscv_trace.c index b6f4d0a5f11..896379d103b 100644 --- a/components/esp_riscv_trace/src/esp_riscv_trace.c +++ b/components/esp_riscv_trace/src/esp_riscv_trace.c @@ -14,6 +14,7 @@ #include "esp_err.h" #include "esp_cache.h" #include "esp_check.h" +#include "esp_rom_sys.h" #include "esp_private/esp_cache_private.h" #include "esp_private/startup_internal.h" #include "esp_private/periph_ctrl.h" @@ -22,6 +23,7 @@ #include "hal/riscv_trace_hal.h" #include "hal/riscv_trace_ll.h" #include "esp_riscv_trace.h" +#include "esp_riscv_trace_snapshot.h" #include "esp_riscv_trace_priv.h" #define ESP_RISCV_TRACE_OBJ_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) @@ -57,6 +59,7 @@ static uint8_t *alloc_aligned_buffer(size_t requested, uint32_t caps, size_t *ou return buf; } +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE || SOC_RISCV_TRACE_MEM_SUPPORT_PSRAM static esp_err_t sync_trace_buffer(uint8_t *buffer, size_t size, int flags) { if (esp_cache_get_line_size_by_addr(buffer) == 0) { @@ -65,6 +68,15 @@ static esp_err_t sync_trace_buffer(uint8_t *buffer, size_t size, int flags) } return esp_cache_msync(buffer, size, flags); } +#else +static inline esp_err_t sync_trace_buffer(uint8_t *buffer, size_t size, int flags) +{ + (void)buffer; + (void)size; + (void)flags; + return ESP_OK; +} +#endif static esp_err_t clear_trace_buffer(esp_riscv_trace_handle_t handle) { @@ -237,53 +249,162 @@ err_alloc: return ret; } -esp_err_t esp_riscv_trace_start(esp_riscv_trace_core_t core_id) +/* Common start work. Lock must be held. */ +static esp_err_t trace_start_locked(esp_riscv_trace_handle_t handle) { esp_err_t ret = ESP_OK; - esp_riscv_trace_handle_t handle = handle_from_core(core_id); - - ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_STATE, TAG, "core %d trace not initialized", (int)core_id); - - _lock_acquire(&handle->lock); - ESP_GOTO_ON_FALSE(handle->state == ESP_RISCV_TRACE_STATE_CREATED || - handle->state == ESP_RISCV_TRACE_STATE_STOPPED, - ESP_ERR_INVALID_STATE, out, TAG, "not startable from this state"); - - ESP_GOTO_ON_ERROR(clear_trace_buffer(handle), out, TAG, "failed to sync cleared trace buffer"); + ESP_RETURN_ON_FALSE_ISR(handle->state == ESP_RISCV_TRACE_STATE_CREATED || + handle->state == ESP_RISCV_TRACE_STATE_STOPPED, + ESP_ERR_INVALID_STATE, TAG, "not startable from this state"); + ESP_RETURN_ON_ERROR_ISR(clear_trace_buffer(handle), TAG, "failed to sync cleared trace buffer"); riscv_trace_hal_prepare_capture(&handle->hal); riscv_trace_hal_set_auto_restart(&handle->hal, handle->auto_restart); - riscv_trace_hal_start(&handle->hal); handle->state = ESP_RISCV_TRACE_STATE_STARTED; + riscv_trace_hal_start(&handle->hal); + esp_riscv_trace_snapshot_start(handle->core_id); -out: + return ret; +} + +/* Common stop work. Lock must be held. Writes the final snapshot fields on the way out. */ +static esp_err_t trace_stop_locked(esp_riscv_trace_handle_t handle, uint32_t timeout_us) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(handle->state == ESP_RISCV_TRACE_STATE_STARTED, ESP_ERR_INVALID_STATE, TAG, + "trace not started"); + + bool flushed = riscv_trace_hal_stop(&handle->hal, timeout_us); + if (flushed) { + esp_err_t sync_ret = sync_trace_buffer(handle->buffer, handle->buffer_size, + ESP_CACHE_MSYNC_FLAG_DIR_M2C | ESP_CACHE_MSYNC_FLAG_INVALIDATE); + if (sync_ret != ESP_OK) { + ESP_LOGE(TAG, "failed to sync trace buffer after stop"); + ret = sync_ret; + } + } else { + ESP_LOGE(TAG, "timed out waiting for trace FIFO to empty"); + ret = ESP_ERR_TIMEOUT; + } + + handle->state = ESP_RISCV_TRACE_STATE_STOPPED; + + uint32_t fifo_status = riscv_trace_hal_read_fifo_status(&handle->hal); + uint32_t intr_status = riscv_trace_hal_read_intr_raw(&handle->hal); + uint32_t base = (uint32_t)handle->buffer; + uint32_t current = riscv_trace_hal_get_current_addr(&handle->hal); + bool head_valid = (current >= base) && (current <= base + handle->buffer_size); + esp_riscv_trace_snapshot_stop(handle->core_id, fifo_status, intr_status, + head_valid ? (current - base) : 0, head_valid); + + return ret; +} + +esp_err_t esp_riscv_trace_start(esp_riscv_trace_core_t core_id) +{ + esp_err_t ret; + esp_riscv_trace_handle_t handle = handle_from_core(core_id); + + ESP_RETURN_ON_FALSE_ISR(handle != NULL, ESP_ERR_INVALID_STATE, TAG, "core %d trace not initialized", + (int)core_id); + + _lock_acquire(&handle->lock); + ret = trace_start_locked(handle); _lock_release(&handle->lock); return ret; } esp_err_t esp_riscv_trace_stop(esp_riscv_trace_core_t core_id, uint32_t timeout_us) { - esp_err_t ret = ESP_OK; + esp_err_t ret; esp_riscv_trace_handle_t handle = handle_from_core(core_id); ESP_RETURN_ON_FALSE(handle != NULL, ESP_ERR_INVALID_STATE, TAG, "core %d trace not initialized", (int)core_id); _lock_acquire(&handle->lock); - ESP_GOTO_ON_FALSE(handle->state == ESP_RISCV_TRACE_STATE_STARTED, ESP_ERR_INVALID_STATE, out, TAG, - "trace not started"); - ESP_GOTO_ON_FALSE(riscv_trace_hal_stop(&handle->hal, timeout_us), ESP_ERR_TIMEOUT, out, TAG, - "timed out waiting for trace FIFO to empty"); - - ESP_GOTO_ON_ERROR(sync_trace_buffer(handle->buffer, handle->buffer_size, - ESP_CACHE_MSYNC_FLAG_DIR_M2C | ESP_CACHE_MSYNC_FLAG_INVALIDATE), - out, TAG, "failed to sync trace buffer after stop"); - - handle->state = ESP_RISCV_TRACE_STATE_STOPPED; - -out: + ret = trace_stop_locked(handle, timeout_us); _lock_release(&handle->lock); return ret; } +static uint16_t IRAM_ATTR snapshot_freeze_state(esp_riscv_trace_state_t state) +{ + switch (state) { + case ESP_RISCV_TRACE_STATE_STARTED: return ESP_RISCV_TRACE_SNAPSHOT_STATE_FROZEN; + case ESP_RISCV_TRACE_STATE_STOPPED: return ESP_RISCV_TRACE_SNAPSHOT_STATE_STOPPED; + default: return ESP_RISCV_TRACE_SNAPSHOT_STATE_READY; + } +} + +#define ESP_RISCV_TRACE_PANIC_FLUSH_TIMEOUT_US 2000 +#define ESP_RISCV_TRACE_PANIC_FLUSH_STEP_US 10 + +void esp_panic_handler_inst_trace_stop(void) +{ + /* Runs at panic entry before other core is stalled, so guard against both cores stopping at once. */ + static uint32_t s_stopped; + + if (!esp_cpu_compare_and_set(&s_stopped, 0, 1)) { + return; + } + + for (int core = 0; core < SOC_CPU_CORES_NUM; core++) { + esp_riscv_trace_handle_t handle = s_handle[core]; + if (handle == NULL) { + continue; + } + riscv_trace_ll_set_restart_ena(handle->hal.dev, false); + riscv_trace_ll_trigger_off(handle->hal.dev); + } +} + +void esp_riscv_trace_snapshot_finalize(void) +{ + static uint32_t s_finalized; + + if (!esp_cpu_compare_and_set(&s_finalized, 0, 1)) { + return; + } + + /* Encoders were stopped at panic entry. Wait for each FIFO to empty and + record the final status. */ + esp_riscv_trace_snapshot_panic_core_t status[SOC_CPU_CORES_NUM] = {0}; + for (int core = 0; core < SOC_CPU_CORES_NUM; core++) { + esp_riscv_trace_handle_t handle = s_handle[core]; + if (handle == NULL) { + continue; + } + + void *dev = handle->hal.dev; + uint32_t waited_us = 0; + uint32_t fifo_status = riscv_trace_ll_get_fifo_status(dev); + while ((fifo_status & TRACE_FIFO_EMPTY_M) == 0) { + if (waited_us >= ESP_RISCV_TRACE_PANIC_FLUSH_TIMEOUT_US) { + break; + } + esp_rom_delay_us(ESP_RISCV_TRACE_PANIC_FLUSH_STEP_US); + waited_us += ESP_RISCV_TRACE_PANIC_FLUSH_STEP_US; + fifo_status = riscv_trace_ll_get_fifo_status(dev); + } + uint32_t base = (uint32_t)handle->buffer; + uint32_t current = riscv_trace_ll_get_mem_current_addr(dev); + uint32_t intr_status = riscv_trace_ll_get_intr_raw(dev); + bool head_valid = (current >= base) && (current <= base + handle->buffer_size); + + status[core].present = true; + status[core].capturing = (handle->state == ESP_RISCV_TRACE_STATE_STARTED); + status[core].state = snapshot_freeze_state(handle->state); + status[core].head_valid = head_valid; + status[core].head_offset = head_valid ? (current - base) : 0; + status[core].fifo_status_raw = fifo_status; + status[core].intr_status_raw = intr_status; + status[core].fifo_empty = (fifo_status & TRACE_FIFO_EMPTY_M) != 0; + status[core].memory_full = (intr_status & TRACE_MEM_FULL_INTR_RAW_M) != 0; + status[core].fifo_overflow = (intr_status & TRACE_FIFO_OVERFLOW_INTR_RAW_M) != 0; + } + + esp_riscv_trace_snapshot_panic_write(status, SOC_CPU_CORES_NUM); +} + esp_err_t esp_riscv_trace_get_buffer(esp_riscv_trace_core_t core_id, uint8_t **buffer, size_t *capacity, size_t *head_offset) { @@ -393,8 +514,7 @@ esp_err_t esp_riscv_trace_set_filter(esp_riscv_trace_core_t core_id, const esp_r } #endif // SOC_RISCV_TRACE_FILTER_SUPPORTED -/* Default per-core configuration for startup auto-init. Applications can override this by providing - * their own (strong) definition of esp_riscv_trace_get_user_config(). */ +/* Default per-core configuration. Applications can override this function. */ esp_riscv_trace_config_t __attribute__((weak)) esp_riscv_trace_get_user_config(int core_id) { (void)core_id; @@ -407,7 +527,9 @@ ESP_SYSTEM_INIT_FN(esp_riscv_trace_early_init, SECONDARY, ESP_SYSTEM_INIT_ALL_CO int core_id = esp_cpu_get_core_id(); esp_riscv_trace_config_t config = esp_riscv_trace_get_user_config(core_id); - // Enable the clocks and reset the encoder core before accessing its registers. + esp_riscv_trace_snapshot_early_init(core_id); + + /* Enable clock and reset the encoder before accessing registers. */ PERIPH_RCC_ATOMIC() { riscv_trace_ll_enable_bus_clock(true); riscv_trace_ll_reset_register(core_id); @@ -425,6 +547,18 @@ ESP_SYSTEM_INIT_FN(esp_riscv_trace_early_init, SECONDARY, ESP_SYSTEM_INIT_ALL_CO esp_err_t ret = esp_riscv_trace_new(core_id, &config, &s_handle[core_id]); if (ret != ESP_OK) { ESP_EARLY_LOGE(TAG, "early init failed on core %d: %s", core_id, esp_err_to_name(ret)); + return ret; } - return ret; + + esp_riscv_trace_snapshot_write_core_desc(core_id, s_handle[core_id]); + +#if CONFIG_ESP_RISCV_TRACE_AUTOSTART + /* Each core starts its own encoder, so the order between cores does not matter. */ + esp_err_t start_ret = esp_riscv_trace_start(core_id); + if (start_ret != ESP_OK) { + ESP_EARLY_LOGW(TAG, "autostart failed on core %d: %s", core_id, esp_err_to_name(start_ret)); + } +#endif + + return ESP_OK; } diff --git a/components/esp_riscv_trace/src/esp_riscv_trace_coredump.c b/components/esp_riscv_trace/src/esp_riscv_trace_coredump.c new file mode 100644 index 00000000000..f5588691cfe --- /dev/null +++ b/components/esp_riscv_trace/src/esp_riscv_trace_coredump.c @@ -0,0 +1,209 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Writes the frozen snapshot as one ELF note and one PT_LOAD per stable core. */ + +#include +#include +#include +#include "esp_attr.h" +#include "esp_memory_utils.h" +#include "hal/cache_hal.h" +#include "soc/soc_caps.h" +#include "esp_private/esp_core_dump_extension.h" +#include "esp_riscv_trace_snapshot.h" +#include "esp_riscv_trace_priv.h" +#include "esp_log.h" + +static const char *TAG = "esp_riscv_trace_coredump"; + +#define ESP_RISCV_TRACE_NOTE_NAME "ESP_RISCV_TRACE" +#define ESP_RISCV_TRACE_NOTE_TYPE 680 +#define ESP_RISCV_TRACE_NOTE_HEADER_SIZE 80 +#define ESP_RISCV_TRACE_NOTE_RECORD_SIZE 44 + +/* Record has no PT_LOAD segment. */ +#define ESP_RISCV_TRACE_NOTE_NO_SEGMENT 0xFFFFFFFF + +static inline void put_u16(uint8_t *p, uint16_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); +} + +static inline void put_u32(uint8_t *p, uint32_t v) +{ + p[0] = (uint8_t)v; + p[1] = (uint8_t)(v >> 8); + p[2] = (uint8_t)(v >> 16); + p[3] = (uint8_t)(v >> 24); +} + +static bool range_readable(uint32_t addr, uint32_t size) +{ + if (addr == 0 || size == 0 || addr + size < addr) { + return false; + } + const void *first = (const void *)(uintptr_t)addr; + const void *last = (const void *)(uintptr_t)(addr + size - 1); + /* Both addresses must fall in one region. */ + bool internal = esp_ptr_internal(first) && esp_ptr_internal(last); + bool psram = esp_ptr_external_ram(first) && esp_ptr_external_ram(last); + bool drom = esp_ptr_in_drom(first) && esp_ptr_in_drom(last); + return internal || psram || drom; +} + +static bool core_buffer_stable(const esp_riscv_trace_snapshot_core_desc_t *cd) +{ + if (cd->state != ESP_RISCV_TRACE_SNAPSHOT_STATE_STOPPED && cd->state != ESP_RISCV_TRACE_SNAPSHOT_STATE_FROZEN) { + return false; + } + if (!cd->head_valid) { + return false; + } + if (!cd->fifo_empty) { + return false; + } + return range_readable(cd->buffer_addr, cd->capacity); +} + +/* Reserved for every CPU. add_note writes only the filled header and records. */ +static uint8_t s_note[ESP_RISCV_TRACE_NOTE_HEADER_SIZE + SOC_CPU_CORES_NUM * ESP_RISCV_TRACE_NOTE_RECORD_SIZE]; + +static void serialize_note_header(uint8_t *hdr, const esp_riscv_trace_snapshot_desc_t *snap, uint8_t core_count) +{ + memset(hdr, 0, ESP_RISCV_TRACE_NOTE_HEADER_SIZE); + memcpy(hdr + 20, (const void *)(uintptr_t)snap->app_elf_sha256_addr, 32); + memcpy(hdr + 52, (const void *)(uintptr_t)snap->encoder_params_addr, ESP_RISCV_TRACE_ENCODER_PARAMS_SIZE); + + put_u32(hdr + 0, snap->magic); + put_u32(hdr + 4, snap->write_seq); + put_u16(hdr + 8, snap->target_id); + put_u16(hdr + 10, snap->chip_revision); + hdr[12] = (uint8_t)snap->abi_major; + hdr[13] = (uint8_t)snap->abi_minor; + hdr[14] = ESP_RISCV_TRACE_NOTE_HEADER_SIZE; + hdr[15] = ESP_RISCV_TRACE_NOTE_RECORD_SIZE; + hdr[16] = core_count; + hdr[17] = (uint8_t)snap->capture_reason; + hdr[18] = 32; + hdr[19] = ESP_RISCV_TRACE_ENCODER_PARAMS_SIZE; +} + +static void serialize_note_record(uint8_t *rec, const esp_riscv_trace_snapshot_core_desc_t *cd, + uint32_t segment_index) +{ + memset(rec, 0, ESP_RISCV_TRACE_NOTE_RECORD_SIZE); + + bool present = segment_index != ESP_RISCV_TRACE_NOTE_NO_SEGMENT; + rec[0] = (uint8_t)cd->core_id; + rec[1] = (uint8_t)cd->state; + rec[2] = (uint8_t)cd->memory_mode; + rec[3] = (uint8_t)cd->packet_format; + rec[4] = (uint8_t)cd->address_mode; + rec[5] = (uint8_t)cd->resync_mode; + rec[6] = 0; /* Unused ABI slot. */ + rec[7] = cd->head_valid ? 1 : 0; + rec[8] = cd->fifo_empty ? 1 : 0; + rec[9] = cd->memory_full ? 1 : 0; + rec[10] = cd->fifo_overflow ? 1 : 0; + put_u32(rec + 12, segment_index); + put_u32(rec + 16, present ? cd->buffer_addr : 0); + put_u32(rec + 20, present ? cd->capacity : 0); + put_u32(rec + 24, present ? cd->capacity : 0); + put_u32(rec + 28, cd->head_offset); + put_u32(rec + 32, cd->resync_threshold); + put_u32(rec + 36, cd->fifo_status_raw); + put_u32(rec + 40, cd->intr_status_raw); +} + +static bool validate_snapshot_for_coredump(const esp_riscv_trace_snapshot_desc_t *snap, + uint16_t *core_count) +{ + if (snap->magic != ESP_RISCV_TRACE_SNAPSHOT_MAGIC) { + return false; + } + if (snap->abi_major != ESP_RISCV_TRACE_SNAPSHOT_ABI_MAJOR || + snap->snapshot_desc_size != ESP_RISCV_TRACE_SNAPSHOT_DESC_SIZE || + snap->core_desc_size != ESP_RISCV_TRACE_SNAPSHOT_CORE_DESC_SIZE) { + return false; + } + /* Odd write_seq means a writer is still updating the snapshot. */ + if (snap->write_seq & 1u) { + return false; + } + if (snap->cores_addr == 0 || snap->core_count == 0) { + return false; + } + + uint16_t used = snap->core_count < SOC_CPU_CORES_NUM ? snap->core_count : SOC_CPU_CORES_NUM; + if (!range_readable(snap->cores_addr, (uint32_t)used * sizeof(esp_riscv_trace_snapshot_core_desc_t))) { + return false; + } + + *core_count = used; + return true; +} + +static void esp_riscv_trace_coredump_write(core_dump_sink_t *sink) +{ + /* Flush the encoder FIFOs and finalize the snapshot before reading it. */ + esp_riscv_trace_snapshot_finalize(); + + const esp_riscv_trace_snapshot_desc_t *snap = &g_esp_riscv_trace_snapshot; + + uint16_t core_count; + if (!validate_snapshot_for_coredump(snap, &core_count)) { + return; + } + + const esp_riscv_trace_snapshot_core_desc_t *cores = + (const esp_riscv_trace_snapshot_core_desc_t *)(uintptr_t)snap->cores_addr; + + uint32_t seg_index[SOC_CPU_CORES_NUM]; + const esp_riscv_trace_snapshot_core_desc_t *records[SOC_CPU_CORES_NUM]; + int count = 0; + + for (uint16_t i = 0; i < core_count; i++) { + const esp_riscv_trace_snapshot_core_desc_t *cd = &cores[i]; + if (cd->state == ESP_RISCV_TRACE_SNAPSHOT_STATE_UNAVAILABLE) { + continue; + } + seg_index[count] = ESP_RISCV_TRACE_NOTE_NO_SEGMENT; + if (core_buffer_stable(cd)) { + if (esp_core_dump_sink_is_data_stage(sink)) { + /* Only the data stage reads the buffer. */ + cache_hal_invalidate_addr(cd->buffer_addr, cd->capacity); + } + if (esp_core_dump_sink_add_segment(sink, cd->buffer_addr, (const void *)(uintptr_t)cd->buffer_addr, + cd->capacity, &seg_index[count]) != ESP_OK) { + return; + } + } + records[count] = cd; + count++; + } + + if (count == 0) { + return; + } + + serialize_note_header(s_note, snap, (uint8_t)count); + for (int i = 0; i < count; i++) { + serialize_note_record( + s_note + ESP_RISCV_TRACE_NOTE_HEADER_SIZE + (size_t)i * ESP_RISCV_TRACE_NOTE_RECORD_SIZE, + records[i], seg_index[i]); + } + uint32_t note_size = ESP_RISCV_TRACE_NOTE_HEADER_SIZE + (uint32_t)count * ESP_RISCV_TRACE_NOTE_RECORD_SIZE; + esp_core_dump_sink_add_note(sink, ESP_RISCV_TRACE_NOTE_NAME, ESP_RISCV_TRACE_NOTE_TYPE, s_note, note_size); +} + +ESP_COREDUMP_REGISTER_EXTRA(esp_riscv_trace_coredump_write); + +void esp_riscv_trace_coredump_include_func(void) +{ + // Hook to force the linker to include this file +} diff --git a/components/esp_riscv_trace/src/esp_riscv_trace_priv.h b/components/esp_riscv_trace/src/esp_riscv_trace_priv.h index ca39dd01c5a..c36c8a39416 100644 --- a/components/esp_riscv_trace/src/esp_riscv_trace_priv.h +++ b/components/esp_riscv_trace/src/esp_riscv_trace_priv.h @@ -12,6 +12,7 @@ #include #include "hal/riscv_trace_hal.h" #include "esp_riscv_trace.h" +#include "esp_riscv_trace_snapshot.h" #ifdef __cplusplus extern "C" { @@ -37,6 +38,32 @@ struct esp_riscv_trace_context_t { bool auto_restart; }; +/* Snapshot writes. */ +void esp_riscv_trace_snapshot_early_init(int core_id); +void esp_riscv_trace_snapshot_write_core_desc(int core_id, const esp_riscv_trace_handle_t handle); +void esp_riscv_trace_snapshot_start(int core_id); +void esp_riscv_trace_snapshot_stop(int core_id, uint32_t fifo_status, uint32_t intr_status, + uint32_t head_offset, bool head_valid); + +typedef struct { + bool present; /*!< Write this core */ + bool capturing; /*!< Encoder was running at freeze */ + uint16_t state; + bool head_valid; /*!< head_offset is the hardware write position */ + uint32_t head_offset; + uint32_t fifo_status_raw; + uint32_t intr_status_raw; + bool fifo_empty; + bool memory_full; + bool fifo_overflow; +} esp_riscv_trace_snapshot_panic_core_t; + +/* IRAM. No lock. Skips cores that are not present. */ +void esp_riscv_trace_snapshot_panic_write(const esp_riscv_trace_snapshot_panic_core_t *cores, int count); + +/* Waits for the encoder FIFOs to empty and records the final per-core status. */ +void esp_riscv_trace_snapshot_finalize(void); + #ifdef __cplusplus } #endif diff --git a/components/esp_riscv_trace/src/esp_riscv_trace_snapshot.c b/components/esp_riscv_trace/src/esp_riscv_trace_snapshot.c new file mode 100644 index 00000000000..cb34c17bfb9 --- /dev/null +++ b/components/esp_riscv_trace/src/esp_riscv_trace_snapshot.c @@ -0,0 +1,246 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Maintains the trace snapshot consumed by the coredump writer. */ + +#include +#include "freertos/FreeRTOS.h" +#include "soc/soc_caps.h" +#include "hal/riscv_trace_hal.h" +#include "hal/efuse_hal.h" +#include "esp_app_desc.h" +#include "esp_riscv_trace.h" +#include "esp_riscv_trace_priv.h" +#include "esp_riscv_trace_snapshot.h" + +/* Per-core snapshot descriptors, reached through g_esp_riscv_trace_snapshot.cores_addr. */ +static esp_riscv_trace_snapshot_core_desc_t s_core_desc[SOC_CPU_CORES_NUM]; + +/* Target-wide encoder parameters, reached through g_esp_riscv_trace_snapshot.encoder_params_addr. */ +static esp_riscv_trace_encoder_params_t s_encoder_params; + +/* Target ELF SHA-256 hash, reached through g_esp_riscv_trace_snapshot.app_elf_sha256_addr. */ +static uint8_t s_app_elf_sha256[32]; + +esp_riscv_trace_snapshot_desc_t g_esp_riscv_trace_snapshot = { + .abi_major = ESP_RISCV_TRACE_SNAPSHOT_ABI_MAJOR, + .abi_minor = ESP_RISCV_TRACE_SNAPSHOT_ABI_MINOR, + .snapshot_desc_size = ESP_RISCV_TRACE_SNAPSHOT_DESC_SIZE, + .core_desc_size = ESP_RISCV_TRACE_SNAPSHOT_CORE_DESC_SIZE, + .cores_addr = (uint32_t)(uintptr_t)s_core_desc, + .core_count = SOC_CPU_CORES_NUM, + .encoder_params_addr = (uint32_t)(uintptr_t) &s_encoder_params, + .encoder_params_size = ESP_RISCV_TRACE_ENCODER_PARAMS_SIZE, + .app_elf_sha256_addr = (uint32_t)(uintptr_t)s_app_elf_sha256, +}; + +static portMUX_TYPE s_snapshot_lock = portMUX_INITIALIZER_UNLOCKED; +static uint8_t s_active_capture_cores; + +/* + * Writers set an odd write_seq before updates and an even one afterward. + * Hardware fences keep descriptor stores inside that interval. + */ +static void snapshot_write_begin(void) +{ + portENTER_CRITICAL(&s_snapshot_lock); + g_esp_riscv_trace_snapshot.write_seq++; + __asm__ __volatile__("fence rw, rw" ::: "memory"); +} + +static void snapshot_write_end(void) +{ + __asm__ __volatile__("fence rw, rw" ::: "memory"); + g_esp_riscv_trace_snapshot.write_seq++; + portEXIT_CRITICAL(&s_snapshot_lock); +} + +static uint16_t snapshot_memory_mode_from_hw(bool loop) +{ + return loop ? ESP_RISCV_TRACE_MEMORY_MODE_LOOP : ESP_RISCV_TRACE_MEMORY_MODE_LINEAR; +} + +static uint16_t snapshot_resync_mode_from_hw(uint32_t hw_mode) +{ + switch (hw_mode) { + case RISCV_TRACE_RESYNC_DISABLED: return ESP_RISCV_TRACE_RESYNC_MODE_DISABLED; + case RISCV_TRACE_RESYNC_PACKET: return ESP_RISCV_TRACE_RESYNC_MODE_PACKET; + case RISCV_TRACE_RESYNC_CYCLE: return ESP_RISCV_TRACE_RESYNC_MODE_CYCLE; + default: return ESP_RISCV_TRACE_RESYNC_MODE_UNKNOWN; + } +} + +static uint16_t snapshot_address_mode_from_hal(riscv_trace_hal_context_t *hal) +{ +#if SOC_RISCV_TRACE_HAS_CONFIG_REG + return riscv_trace_hal_get_full_address(hal) ? ESP_RISCV_TRACE_ADDRESS_MODE_FULL + : ESP_RISCV_TRACE_ADDRESS_MODE_DELTA; +#else + (void)hal; + /* Without a config register the encoder is fixed in delta mode. */ + return ESP_RISCV_TRACE_ADDRESS_MODE_DELTA; +#endif +} + +static void fill_encoder_params(esp_riscv_trace_encoder_params_t *params) +{ + memset(params, 0, sizeof(*params)); + params->params_version = ESP_RISCV_TRACE_ENCODER_PARAMS_VERSION; + params->params_size = ESP_RISCV_TRACE_ENCODER_PARAMS_SIZE; + params->ecause_width_p = SOC_RISCV_TRACE_ECAUSE_WIDTH; +#if SOC_RISCV_TRACE_FILTER_SUPPORTED + params->filter_excint_p = 1; + params->filter_privilege_p = 1; + params->filter_tval_p = 1; +#endif + params->iaddress_lsb_p = 1; + params->iaddress_width_p = 32; + params->iretire_width_p = 1; + params->itype_width_p = 3; + params->nocontext_p = 1; + params->notime_p = 1; + params->privilege_width_p = SOC_RISCV_TRACE_PRIV_WIDTH; + params->retires_p = 1; + params->taken_branches_p = 1; +} + +static void write_target_identity(void) +{ + g_esp_riscv_trace_snapshot.target_id = (uint16_t)CONFIG_IDF_FIRMWARE_CHIP_ID; + g_esp_riscv_trace_snapshot.chip_revision = (uint16_t)efuse_hal_chip_revision(); + + const esp_app_desc_t *desc = esp_app_get_description(); + memcpy(s_app_elf_sha256, desc->app_elf_sha256, sizeof(s_app_elf_sha256)); + g_esp_riscv_trace_snapshot.app_elf_sha256_size = (uint8_t)sizeof(s_app_elf_sha256); +} + +void esp_riscv_trace_snapshot_early_init(int core_id) +{ + snapshot_write_begin(); + s_core_desc[core_id].core_id = (uint16_t)core_id; + + /* Core 0 writes the shared data. */ + if (core_id == 0) { + write_target_identity(); + fill_encoder_params(&s_encoder_params); + g_esp_riscv_trace_snapshot.magic = ESP_RISCV_TRACE_SNAPSHOT_MAGIC; + } + snapshot_write_end(); +} + +void esp_riscv_trace_snapshot_write_core_desc(int core_id, const esp_riscv_trace_handle_t handle) +{ + esp_riscv_trace_snapshot_core_desc_t *desc = &s_core_desc[core_id]; + riscv_trace_hal_context_t *hal = &handle->hal; + + snapshot_write_begin(); + desc->buffer_addr = (uint32_t)(uintptr_t)handle->buffer; + desc->capacity = (uint32_t)handle->buffer_size; + desc->packet_format = SOC_RISCV_TRACE_PACKET_FORMAT_VER; + desc->memory_mode = snapshot_memory_mode_from_hw(riscv_trace_hal_get_mem_loop(hal)); + desc->address_mode = snapshot_address_mode_from_hal(hal); + desc->resync_mode = snapshot_resync_mode_from_hw(riscv_trace_hal_get_resync_mode(hal)); + desc->resync_threshold = riscv_trace_hal_get_resync_threshold(hal); + desc->state = ESP_RISCV_TRACE_SNAPSHOT_STATE_READY; + desc->head_valid = 0; + snapshot_write_end(); +} + +void esp_riscv_trace_snapshot_start(int core_id) +{ + snapshot_write_begin(); + if (s_active_capture_cores == 0) { + g_esp_riscv_trace_snapshot.capture_reason = ESP_RISCV_TRACE_CAPTURE_REASON_UNKNOWN; + } + s_active_capture_cores++; + + esp_riscv_trace_snapshot_core_desc_t *desc = &s_core_desc[core_id]; + desc->state = ESP_RISCV_TRACE_SNAPSHOT_STATE_CAPTURING; + desc->head_offset = 0; + desc->head_valid = 0; + desc->fifo_status_raw = 0; + desc->intr_status_raw = 0; + desc->fifo_empty = 0; + desc->memory_full = 0; + desc->fifo_overflow = 0; + snapshot_write_end(); +} + +void esp_riscv_trace_snapshot_stop(int core_id, uint32_t fifo_status, uint32_t intr_status, + uint32_t head_offset, bool head_valid) +{ + snapshot_write_begin(); + esp_riscv_trace_snapshot_core_desc_t *desc = &s_core_desc[core_id]; + desc->state = ESP_RISCV_TRACE_SNAPSHOT_STATE_STOPPED; + desc->fifo_status_raw = fifo_status; + desc->intr_status_raw = intr_status; + desc->fifo_empty = riscv_trace_hal_fifo_is_empty(fifo_status) ? 1 : 0; + desc->memory_full = riscv_trace_hal_memory_is_full(intr_status) ? 1 : 0; + desc->fifo_overflow = riscv_trace_hal_fifo_is_overflowed(intr_status) ? 1 : 0; + desc->head_offset = head_valid ? head_offset : 0; + desc->head_valid = head_valid ? 1 : 0; + + if (s_active_capture_cores > 0) { + s_active_capture_cores--; + } + if (s_active_capture_cores == 0) { + g_esp_riscv_trace_snapshot.capture_reason = ESP_RISCV_TRACE_CAPTURE_REASON_EXPLICIT_STOP; + } + snapshot_write_end(); +} + +/* + * The panic handler has already stalled the other cores + * (esp_cpu_stall), so this core is the sole writer. Taking s_snapshot_lock + * could deadlock against a stalled core that still holds it, so update + * lock-free. Force write_seq odd (not ++) to discard any interrupted + * normal writer, then close it even. + */ +static void panic_write_begin(void) +{ + g_esp_riscv_trace_snapshot.write_seq |= 1u; + __asm__ __volatile__("fence rw, rw" ::: "memory"); +} + +static void panic_write_end(void) +{ + __asm__ __volatile__("fence rw, rw" ::: "memory"); + g_esp_riscv_trace_snapshot.write_seq++; +} + +void esp_riscv_trace_snapshot_panic_write(const esp_riscv_trace_snapshot_panic_core_t *cores, int count) +{ + panic_write_begin(); + + bool any_capturing = false; + for (int core_id = 0; core_id < count; core_id++) { + const esp_riscv_trace_snapshot_panic_core_t *cs = &cores[core_id]; + /* Replace state possibly left by an interrupted normal writer. */ + if (!cs->present) { + continue; + } + + esp_riscv_trace_snapshot_core_desc_t *desc = &s_core_desc[core_id]; + desc->state = cs->state; + desc->fifo_status_raw = cs->fifo_status_raw; + desc->intr_status_raw = cs->intr_status_raw; + desc->fifo_empty = cs->fifo_empty ? 1 : 0; + desc->memory_full = cs->memory_full ? 1 : 0; + desc->fifo_overflow = cs->fifo_overflow ? 1 : 0; + desc->head_offset = cs->head_valid ? cs->head_offset : 0; + desc->head_valid = cs->head_valid ? 1 : 0; + + if (cs->capturing) { + any_capturing = true; + } + } + + if (any_capturing) { + g_esp_riscv_trace_snapshot.capture_reason = ESP_RISCV_TRACE_CAPTURE_REASON_PANIC; + } + + panic_write_end(); +} diff --git a/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_basic.c b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_basic.c index cb58087a478..52a2be6f97f 100644 --- a/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_basic.c +++ b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_basic.c @@ -41,7 +41,7 @@ typedef struct { size_t syncs; } trace_walk_result_t; -/* A branchy workload so the encoder emits many Format 1 (branch) packets. */ +/* Branchy workload so the encoder produces many Format 1 (branch) packets. */ static void IRAM_ATTR branchy_work(void) { uint32_t acc = s_trace_sink; diff --git a/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_filter.c b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_filter.c index 79c2da1c7f5..e85bd45e5ba 100644 --- a/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_filter.c +++ b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_filter.c @@ -15,9 +15,8 @@ static const char *TAG = "riscv_trace_filter"; -/* Iterations per call. High enough that the filtered window alone still emits at least one sync - * packet, but low enough that the full baseline workload (the three calls in capture_and_count: - * noise, filtered, noise) fits the buffer without wrapping. */ +/* Workload iterations per call. High enough that the filtered window produces at least one sync + * packet, but small enough to fit within the buffer without wrapping. */ #define TRACE_FILTER_WORKLOAD_ITERATIONS 512 static volatile uint32_t s_filter_sink; diff --git a/components/esp_system/include/esp_private/panic_internal.h b/components/esp_system/include/esp_private/panic_internal.h index 6fd164ae817..c5d179e5100 100644 --- a/components/esp_system/include/esp_private/panic_internal.h +++ b/components/esp_system/include/esp_private/panic_internal.h @@ -103,6 +103,11 @@ bool panic_memprot_fill_info(panic_info_t *info); */ void panic_disable_all_wdts(void); +/** + * @brief Stop instruction trace encoders on panic + */ +void esp_panic_handler_inst_trace_stop(void) __attribute__((weak)); + #ifdef __cplusplus } #endif diff --git a/components/esp_system/port/panic_handler.c b/components/esp_system/port/panic_handler.c index 5d540d1c3bc..ff1f02760bd 100644 --- a/components/esp_system/port/panic_handler.c +++ b/components/esp_system/port/panic_handler.c @@ -148,6 +148,15 @@ static void panic_handler(void *frame, bool pseudo_excause) kasan_disable_checks(); #endif +#if CONFIG_ESP_RISCV_TRACE_ENABLE + /* Stop the instruction trace as early as possible so the capture ends at the + crash rather than inside the panic handler. This only stops the encoders. + The FIFO flush and snapshot run later at coredump time. */ + if (esp_panic_handler_inst_trace_stop) { + esp_panic_handler_inst_trace_stop(); + } +#endif + /* If watchdogs are enabled, the panic handler runs the risk of getting aborted pre-emptively because * an overzealous watchdog decides to reset it. Hence, we feed the WDTs here. * diff --git a/components/espcoredump/include/esp_private/esp_core_dump_extension.h b/components/espcoredump/include/esp_private/esp_core_dump_extension.h new file mode 100644 index 00000000000..91228af870f --- /dev/null +++ b/components/espcoredump/include/esp_private/esp_core_dump_extension.h @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Attachment interface for adding extra ELF program segments and notes to a + * coredump. A data-owning component registers a write callback with + * ESP_COREDUMP_REGISTER_EXTRA(); the coredump writer walks that list on every + * ELF pass. Each callback must add the same segments and notes in the same + * order on every pass, and must not allocate. + */ + +/** @brief Opaque coredump attachment sink handed to extra-write callbacks. */ +typedef struct core_dump_sink_s core_dump_sink_t; + +/** + * @brief Add one PT_LOAD segment covering [vaddr, vaddr+size). + * + * @param sink Sink passed to the callback. + * @param vaddr Target address recorded in the program header. + * @param data Source bytes read during the data pass. Must stay valid and + * readable for the whole coredump write. + * @param size Segment byte count. Must be a multiple of 4 (the ELF writer + * reads a 4-byte-aligned length). + * @param out_index Receives the zero-based program-header index, or NULL. + * @return ESP_OK, or an error that makes the coredump writer abort the attachment. + */ +esp_err_t esp_core_dump_sink_add_segment(core_dump_sink_t *sink, uint32_t vaddr, + const void *data, uint32_t size, uint32_t *out_index); + +/** + * @brief Add one ELF note to the shared attachment PT_NOTE segment. + * + * @param sink Sink passed to the callback. + * @param name Note name (a terminating null is added and counted). + * @param type Note type. + * @param desc Note description bytes. Must stay valid for the whole write. + * @param desc_size Description byte count. + * @return ESP_OK, or an error that makes the coredump writer abort the attachment. + */ +esp_err_t esp_core_dump_sink_add_note(core_dump_sink_t *sink, const char *name, + uint32_t type, const void *desc, uint32_t desc_size); + +/** + * @brief True when the ELF writer is in ELF_STAGE_PLACE_DATA. + * + * Earlier stages only count sizes and write headers. + */ +bool esp_core_dump_sink_is_data_stage(const core_dump_sink_t *sink); + +/** @brief Extra-content callback invoked once per ELF write pass. */ +typedef void (*esp_core_dump_extra_cb_t)(core_dump_sink_t *sink); + +/** + * @brief Register @p fn as a coredump extra-content callback. + * + * The linker collects every registration into one array that the ELF writer + * walks. Place this in the same translation unit as @p fn. + */ +#define ESP_COREDUMP_REGISTER_EXTRA(fn) \ + static const esp_core_dump_extra_cb_t _esp_coredump_extra_##fn \ + __attribute__((used, section(".esp_coredump_extra"))) = (fn) + +#ifdef __cplusplus +} +#endif diff --git a/components/espcoredump/linker.lf b/components/espcoredump/linker.lf index 5e1cae98ce6..7528873995c 100644 --- a/components/espcoredump/linker.lf +++ b/components/espcoredump/linker.lf @@ -72,6 +72,20 @@ entries: else: * (default) +[sections:esp_coredump_extra] +entries: + .esp_coredump_extra+ + +[scheme:esp_coredump_extra_default] +entries: + esp_coredump_extra -> dram0_data + +[mapping:esp_coredump_extra] +archive: * +entries: + * (esp_coredump_extra_default); + esp_coredump_extra -> dram0_data KEEP() SORT(name) ALIGN(4, pre) SURROUND(esp_coredump_extra_array) + [mapping:spi_flash_override] archive: libspi_flash.a entries: diff --git a/components/espcoredump/src/core_dump_elf.c b/components/espcoredump/src/core_dump_elf.c index 1fc18439a9b..54b31247fd2 100644 --- a/components/espcoredump/src/core_dump_elf.c +++ b/components/espcoredump/src/core_dump_elf.c @@ -19,6 +19,7 @@ #include "esp_app_desc.h" #include "esp_memory_utils.h" #include "esp_macros.h" +#include "esp_private/esp_core_dump_extension.h" #define ELF_CLASS ELFCLASS32 @@ -78,10 +79,18 @@ typedef struct _core_dump_elf_t { uint16_t elf_stage; uint32_t elf_next_data_offset; uint16_t segs_count; + uint16_t phdr_index; /* running program-header index within the current pass */ core_dump_write_data_t write_data; uint32_t note_data_size; /* can be used where static storage needed */ } core_dump_elf_t; +struct core_dump_sink_s { + core_dump_elf_t *self; + int seg_total; /* provider PT_LOAD contribution for the current pass */ + int note_bytes; /* accumulated provider note-description bytes */ + esp_err_t err; /* first failure, if any */ +}; + typedef struct { core_dump_elf_t *self; uint32_t total_size; @@ -158,6 +167,8 @@ static int elf_add_segment(core_dump_elf_t *self, ELF_CHECK_ERR((data != NULL), ELF_PROC_ERR_OTHER, "Invalid data for segment."); + self->phdr_index++; + if (self->elf_stage == ELF_STAGE_CALC_SPACE) { self->segs_count++; return data_len + sizeof(elf_phdr); @@ -371,6 +382,8 @@ static int elf_process_note_segment(core_dump_elf_t *self, int notes_size) int ret; elf_phdr seg_hdr = { 0 }; + self->phdr_index++; + if (self->elf_stage == ELF_STAGE_PLACE_HEADERS) { // segment header for PR_STATUS notes seg_hdr.p_type = PT_NOTE; @@ -758,10 +771,96 @@ static int elf_write_core_dump_info(core_dump_elf_t *self) return ret; } +esp_err_t esp_core_dump_sink_add_segment(core_dump_sink_t *sink, uint32_t vaddr, + const void *data, uint32_t size, uint32_t *out_index) +{ + if (sink->err != ESP_OK) { + return sink->err; + } + if (data == NULL || size == 0) { + sink->err = ESP_ERR_INVALID_ARG; + return sink->err; + } + if (size % 4 != 0) { + sink->err = ESP_ERR_INVALID_SIZE; + return sink->err; + } + uint32_t index = sink->self->phdr_index; + int ret = elf_add_segment(sink->self, PT_LOAD, vaddr, (void *)data, size); + if (ret <= 0) { + sink->err = ESP_FAIL; + return sink->err; + } + sink->seg_total += ret; + if (out_index != NULL) { + *out_index = index; + } + return ESP_OK; +} + +esp_err_t esp_core_dump_sink_add_note(core_dump_sink_t *sink, const char *name, + uint32_t type, const void *desc, uint32_t desc_size) +{ + if (sink->err != ESP_OK) { + return sink->err; + } + if (name == NULL || desc == NULL || desc_size == 0) { + sink->err = ESP_ERR_INVALID_ARG; + return sink->err; + } + int ret = elf_add_note(sink->self, name, type, (void *)desc, desc_size); + if (ret <= 0) { + sink->err = ESP_FAIL; + return sink->err; + } + sink->note_bytes += ret; + return ESP_OK; +} + +bool esp_core_dump_sink_is_data_stage(const core_dump_sink_t *sink) +{ + return sink->self->elf_stage == ELF_STAGE_PLACE_DATA; +} + +/* Linker-collected extra-write callbacks (ESP_COREDUMP_REGISTER_EXTRA). Weak so + * a build with no providers still links when the SURROUND section is empty. */ +extern const esp_core_dump_extra_cb_t _esp_coredump_extra_array_start __attribute__((weak)); +extern const esp_core_dump_extra_cb_t _esp_coredump_extra_array_end __attribute__((weak)); + +/* Provider PT_LOAD segments first, then all provider notes wrapped in one + * trailing PT_NOTE segment. This ordering keeps every note's segment index + * stable and matches the segment/note byte order across passes. */ +static int elf_write_extra_providers(core_dump_elf_t *self) +{ + core_dump_sink_t sink = { .self = self, .seg_total = 0, .note_bytes = 0, .err = ESP_OK }; + + const esp_core_dump_extra_cb_t *start = &_esp_coredump_extra_array_start; + const esp_core_dump_extra_cb_t *end = &_esp_coredump_extra_array_end; + if (start != NULL && end != NULL) { + for (const esp_core_dump_extra_cb_t *it = start; it < end; ++it) { + if (*it != NULL) { + (*it)(&sink); + ELF_CHECK_ERR((sink.err == ESP_OK), ELF_PROC_ERR_OTHER, + "coredump attachment failed (%d)", sink.err); + } + } + } + + int total = sink.seg_total; + if (sink.note_bytes > 0) { + int ret = elf_process_note_segment(self, sink.note_bytes); + ELF_CHECK_ERR((ret > 0), ret, "attachment note segment processing failure, returned (%d).", ret); + total += ret; + } + return total; +} + static int esp_core_dump_do_write_elf_pass(core_dump_elf_t *self) { int tot_len = 0; + self->phdr_index = 0; + int data_sz = elf_write_file_header(self, ELF_SEG_HEADERS_COUNT(self)); if (self->elf_stage == ELF_STAGE_PLACE_DATA) { ELF_CHECK_ERR((data_sz >= 0), data_sz, "ELF header writing error, returned (%d).", data_sz); @@ -785,6 +884,11 @@ static int esp_core_dump_do_write_elf_pass(core_dump_elf_t *self) ELF_CHECK_ERR((data_sz > 0), data_sz, "Version info writing failed. Returned (%d).", data_sz); tot_len += data_sz; + // write segments and notes contributed by other components + data_sz = elf_write_extra_providers(self); + ELF_CHECK_ERR((data_sz >= 0), data_sz, "Coredump attachment writing failed. Returned (%d).", data_sz); + tot_len += data_sz; + return tot_len; }