feat(esp_riscv_trace): Add trace snapshot to the coredump file

This commit is contained in:
Erhan Kurubas
2026-09-03 22:07:42 +02:00
parent e492079576
commit 7ed58ee6a5
18 changed files with 1165 additions and 74 deletions

View File

@@ -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()

View File

@@ -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

View File

@@ -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.

View File

@@ -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 */

View File

@@ -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)

View File

@@ -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 <stdint.h>
#include <stddef.h>
#include <assert.h>
#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

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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 <string.h>
#include <stdint.h>
#include <stdbool.h>
#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
}

View File

@@ -12,6 +12,7 @@
#include <sys/lock.h>
#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

View File

@@ -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 <string.h>
#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();
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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.
*

View File

@@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#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

View File

@@ -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:

View File

@@ -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;
}