mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(esp_riscv_trace): Add trace snapshot to the coredump file
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 */
|
||||
|
||||
18
components/esp_riscv_trace/linker.lf
Normal file
18
components/esp_riscv_trace/linker.lf
Normal 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)
|
||||
@@ -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
|
||||
12
components/esp_riscv_trace/project_include.cmake
Normal file
12
components/esp_riscv_trace/project_include.cmake
Normal 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()
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
209
components/esp_riscv_trace/src/esp_riscv_trace_coredump.c
Normal file
209
components/esp_riscv_trace/src/esp_riscv_trace_coredump.c
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
246
components/esp_riscv_trace/src/esp_riscv_trace_snapshot.c
Normal file
246
components/esp_riscv_trace/src/esp_riscv_trace_snapshot.c
Normal 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();
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user