mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
Merge branch 'esp_riscv_trace' into 'master'
RISC-V Trace Encoder See merge request espressif/esp-idf!49821
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
set(srcs)
|
||||
set(public_include "include")
|
||||
|
||||
if(CONFIG_ESP_RISCV_TRACE_ENABLE)
|
||||
list(APPEND srcs "src/esp_riscv_trace.c")
|
||||
endif()
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
set(priv_requires "")
|
||||
else()
|
||||
set(priv_requires esp_mm)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS ${public_include}
|
||||
PRIV_INCLUDE_DIRS "src"
|
||||
PRIV_REQUIRES "${priv_requires}"
|
||||
)
|
||||
@@ -0,0 +1,223 @@
|
||||
menu "RISC-V Trace Encoder Configurations"
|
||||
depends on SOC_RISCV_TRACE_SUPPORTED
|
||||
|
||||
config ESP_RISCV_TRACE_ENABLE
|
||||
bool "Enable ESP RISC-V trace encoder"
|
||||
default n
|
||||
help
|
||||
Build the ESP RISC-V trace encoder driver and expose its runtime API.
|
||||
|
||||
if ESP_RISCV_TRACE_ENABLE
|
||||
|
||||
config ESP_RISCV_TRACE_BUFFER_SIZE
|
||||
int "Trace buffer size (bytes)"
|
||||
default 2048
|
||||
help
|
||||
Size in bytes of the trace buffer allocated by the driver.
|
||||
|
||||
choice ESP_RISCV_TRACE_BUFFER_MEM_SELECT
|
||||
prompt "Trace buffer memory placement"
|
||||
default ESP_RISCV_TRACE_BUFFER_IN_INTERNAL
|
||||
help
|
||||
Where the driver allocates the trace buffer. The encoder writes the buffer over
|
||||
its AHB master, which can reach both internal and external PSRAM.
|
||||
|
||||
Internal RAM is fastest and always usable. PSRAM frees internal RAM and allows
|
||||
much larger buffers, but is slower (higher risk of FIFO overflow at high trace
|
||||
rates) and is inaccessible while the cache is disabled (e.g. during flash writes).
|
||||
|
||||
config ESP_RISCV_TRACE_BUFFER_IN_INTERNAL
|
||||
bool "Internal RAM"
|
||||
config ESP_RISCV_TRACE_BUFFER_IN_EXTERNAL
|
||||
bool "External RAM (PSRAM)"
|
||||
depends on SPIRAM
|
||||
endchoice
|
||||
|
||||
config ESP_RISCV_TRACE_BUFFER_MEM
|
||||
int
|
||||
default 0 if ESP_RISCV_TRACE_BUFFER_IN_INTERNAL
|
||||
default 1 if ESP_RISCV_TRACE_BUFFER_IN_EXTERNAL
|
||||
|
||||
choice ESP_RISCV_TRACE_CORE_SELECT
|
||||
prompt "Cores to trace"
|
||||
default ESP_RISCV_TRACE_BOTH_CORES if SOC_HP_CPU_HAS_MULTIPLE_CORES && !FREERTOS_UNICORE
|
||||
default ESP_RISCV_TRACE_CORE_0
|
||||
help
|
||||
Select which RISC-V core(s) the trace encoder is set up for. Each traced core
|
||||
gets its own encoder instance and trace buffer, so tracing both cores doubles
|
||||
the memory used.
|
||||
|
||||
config ESP_RISCV_TRACE_CORE_0
|
||||
bool "Core 0 only"
|
||||
config ESP_RISCV_TRACE_CORE_1
|
||||
bool "Core 1 only"
|
||||
depends on SOC_HP_CPU_HAS_MULTIPLE_CORES && !FREERTOS_UNICORE
|
||||
config ESP_RISCV_TRACE_BOTH_CORES
|
||||
bool "Both cores"
|
||||
depends on SOC_HP_CPU_HAS_MULTIPLE_CORES && !FREERTOS_UNICORE
|
||||
endchoice
|
||||
|
||||
config ESP_RISCV_TRACE_CORE_MASK
|
||||
hex
|
||||
default 0x1 if ESP_RISCV_TRACE_CORE_0
|
||||
default 0x2 if ESP_RISCV_TRACE_CORE_1
|
||||
default 0x3 if ESP_RISCV_TRACE_BOTH_CORES
|
||||
|
||||
choice ESP_RISCV_TRACE_ADDRESS_MODE_SELECT
|
||||
prompt "Instruction address encoding"
|
||||
depends on SOC_RISCV_TRACE_HAS_CONFIG_REG
|
||||
default ESP_RISCV_TRACE_ADDRESS_MODE_DELTA
|
||||
help
|
||||
How instruction addresses are encoded in the trace stream.
|
||||
|
||||
config ESP_RISCV_TRACE_ADDRESS_MODE_DELTA
|
||||
bool "Delta addresses"
|
||||
config ESP_RISCV_TRACE_ADDRESS_MODE_FULL
|
||||
bool "Full addresses"
|
||||
endchoice
|
||||
|
||||
config ESP_RISCV_TRACE_ADDRESS_MODE
|
||||
int
|
||||
default 0 if ESP_RISCV_TRACE_ADDRESS_MODE_DELTA
|
||||
default 1 if ESP_RISCV_TRACE_ADDRESS_MODE_FULL
|
||||
default 0
|
||||
|
||||
choice ESP_RISCV_TRACE_MEM_MODE_SELECT
|
||||
prompt "Trace buffer memory mode"
|
||||
default ESP_RISCV_TRACE_MEM_MODE_LOOP
|
||||
help
|
||||
Behaviour of the trace buffer when it becomes full.
|
||||
|
||||
config ESP_RISCV_TRACE_MEM_MODE_NON_LOOP
|
||||
bool "Stop when buffer is full"
|
||||
config ESP_RISCV_TRACE_MEM_MODE_LOOP
|
||||
bool "Wrap around (loop)"
|
||||
endchoice
|
||||
|
||||
config ESP_RISCV_TRACE_MEM_MODE
|
||||
int
|
||||
default 0 if ESP_RISCV_TRACE_MEM_MODE_NON_LOOP
|
||||
default 1 if ESP_RISCV_TRACE_MEM_MODE_LOOP
|
||||
|
||||
config ESP_RISCV_TRACE_AUTO_RESTART_ENABLE
|
||||
bool "Auto-restart encoder after a FIFO overflow"
|
||||
default n
|
||||
help
|
||||
A FIFO overflow stops the encoder. When enabled, it restarts automatically once
|
||||
the FIFO is empty again.
|
||||
|
||||
config ESP_RISCV_TRACE_AUTO_RESTART
|
||||
int
|
||||
default 1 if ESP_RISCV_TRACE_AUTO_RESTART_ENABLE
|
||||
default 0
|
||||
|
||||
config ESP_RISCV_TRACE_STALL_CPU_ENABLE
|
||||
bool "Stall CPU on FIFO full"
|
||||
depends on SOC_RISCV_TRACE_HAS_CONFIG_REG
|
||||
default n
|
||||
help
|
||||
Stall the CPU instead of dropping packets when the FIFO is full.
|
||||
|
||||
config ESP_RISCV_TRACE_STALL_CPU
|
||||
int
|
||||
default 1 if ESP_RISCV_TRACE_STALL_CPU_ENABLE
|
||||
default 0
|
||||
|
||||
config ESP_RISCV_TRACE_HALT_ENABLE
|
||||
bool "Report hart halt in the trace"
|
||||
depends on SOC_RISCV_TRACE_HAS_CONFIG_REG
|
||||
default n
|
||||
help
|
||||
When the hart halts, report the address of the last instruction before the halt;
|
||||
tracing resumes with a synchronization packet when the hart continues.
|
||||
|
||||
config ESP_RISCV_TRACE_HALT
|
||||
int
|
||||
default 1 if ESP_RISCV_TRACE_HALT_ENABLE
|
||||
default 0
|
||||
|
||||
config ESP_RISCV_TRACE_RESET_ENABLE
|
||||
bool "Report hart reset in the trace"
|
||||
depends on SOC_RISCV_TRACE_HAS_CONFIG_REG
|
||||
default n
|
||||
help
|
||||
When the hart enters reset, report the address of the last instruction before the
|
||||
reset. Tracing resumes with a synchronization packet afterwards.
|
||||
|
||||
config ESP_RISCV_TRACE_RESET
|
||||
int
|
||||
default 1 if ESP_RISCV_TRACE_RESET_ENABLE
|
||||
default 0
|
||||
|
||||
config ESP_RISCV_TRACE_DEBUG_TRIGGER_ENABLE
|
||||
bool "Enable Debug Module trigger input"
|
||||
depends on SOC_RISCV_TRACE_HAS_CONFIG_REG
|
||||
default n
|
||||
help
|
||||
Enable the Debug Module trigger input to the trace encoder.
|
||||
|
||||
config ESP_RISCV_TRACE_DEBUG_TRIGGER
|
||||
int
|
||||
default 1 if ESP_RISCV_TRACE_DEBUG_TRIGGER_ENABLE
|
||||
default 0
|
||||
|
||||
choice ESP_RISCV_TRACE_RESYNC_MODE_SELECT
|
||||
prompt "Resynchronization mode"
|
||||
default ESP_RISCV_TRACE_RESYNC_MODE_DISABLED
|
||||
help
|
||||
Periodic resynchronization mode of the trace encoder.
|
||||
|
||||
config ESP_RISCV_TRACE_RESYNC_MODE_DISABLED
|
||||
bool "Disabled"
|
||||
config ESP_RISCV_TRACE_RESYNC_MODE_PACKET
|
||||
bool "By packet count"
|
||||
config ESP_RISCV_TRACE_RESYNC_MODE_CYCLE
|
||||
bool "By cycle count"
|
||||
endchoice
|
||||
|
||||
config ESP_RISCV_TRACE_RESYNC_MODE
|
||||
int
|
||||
default 0 if ESP_RISCV_TRACE_RESYNC_MODE_DISABLED
|
||||
default 2 if ESP_RISCV_TRACE_RESYNC_MODE_PACKET
|
||||
default 3 if ESP_RISCV_TRACE_RESYNC_MODE_CYCLE
|
||||
|
||||
config ESP_RISCV_TRACE_RESYNC_THRESHOLD
|
||||
int "Resync threshold" if !ESP_RISCV_TRACE_RESYNC_MODE_DISABLED
|
||||
default 128
|
||||
help
|
||||
Threshold (packet or cycle count) used by the selected resynchronization mode.
|
||||
|
||||
choice ESP_RISCV_TRACE_AHB_BURST_SELECT
|
||||
prompt "AHB burst type"
|
||||
depends on SOC_RISCV_TRACE_AHB_CONFIGURABLE
|
||||
default ESP_RISCV_TRACE_AHB_BURST_SINGLE
|
||||
help
|
||||
AHB burst type used by the trace write master.
|
||||
|
||||
config ESP_RISCV_TRACE_AHB_BURST_SINGLE
|
||||
bool "Single transfer"
|
||||
config ESP_RISCV_TRACE_AHB_BURST_INCR
|
||||
bool "Incrementing (undefined length)"
|
||||
config ESP_RISCV_TRACE_AHB_BURST_INCR4
|
||||
bool "4-beat incrementing"
|
||||
config ESP_RISCV_TRACE_AHB_BURST_INCR8
|
||||
bool "8-beat incrementing"
|
||||
endchoice
|
||||
|
||||
config ESP_RISCV_TRACE_AHB_BURST
|
||||
int
|
||||
default 0 if ESP_RISCV_TRACE_AHB_BURST_SINGLE
|
||||
default 1 if ESP_RISCV_TRACE_AHB_BURST_INCR
|
||||
default 2 if ESP_RISCV_TRACE_AHB_BURST_INCR4
|
||||
default 4 if ESP_RISCV_TRACE_AHB_BURST_INCR8
|
||||
default 0
|
||||
|
||||
config ESP_RISCV_TRACE_AHB_MAX_INCR
|
||||
int "Max INCR burst beats" if SOC_RISCV_TRACE_AHB_CONFIGURABLE
|
||||
default 0
|
||||
help
|
||||
Maximum number of beats for an undefined-length INCR burst.
|
||||
|
||||
endif # ESP_RISCV_TRACE_ENABLE
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,52 @@
|
||||
# RISC-V Trace Encoder Driver
|
||||
|
||||
## Overview
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
`ESP_RISCV_TRACE_DEFAULT_CONFIG()`.
|
||||
|
||||
## State Transition
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> created: startup auto-init
|
||||
created --> started: esp_riscv_trace_start
|
||||
started --> stopped: esp_riscv_trace_stop
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
|
||||
## Buffer and Trace Stream Notes
|
||||
|
||||
The trace buffer must be reachable by the trace encoder AHB master. Driver
|
||||
allocated buffers are cache-line aligned and placed in internal RAM or PSRAM
|
||||
according to configuration. Caller-provided buffers are validated for reachable
|
||||
memory and cache-line alignment.
|
||||
|
||||
In loop memory mode, wrapped buffers need periodic resynchronization packets to
|
||||
remain decodable after the original start sync has been 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.
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
/** @brief RISC-V core whose execution is traced. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_CORE_0 = 0,
|
||||
ESP_RISCV_TRACE_CORE_1 = 1,
|
||||
} esp_riscv_trace_core_t;
|
||||
|
||||
/** @brief Instruction address encoding in the trace stream. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_ADDR_DELTA = 0, /*!< Differential addresses (hardware reset default) */
|
||||
ESP_RISCV_TRACE_ADDR_FULL = 1, /*!< Full addresses */
|
||||
} esp_riscv_trace_address_mode_t;
|
||||
|
||||
/** @brief Trace buffer memory mode. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_MEM_NON_LOOP = 0, /*!< Stop when buffer is full */
|
||||
ESP_RISCV_TRACE_MEM_LOOP, /*!< Wrap around (hardware reset default) */
|
||||
} esp_riscv_trace_mem_mode_t;
|
||||
|
||||
/** @brief Resynchronization mode. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_RESYNC_DISABLED = 0, /*!< No periodic resync (hardware reset default) */
|
||||
ESP_RISCV_TRACE_RESYNC_PACKET = 2, /*!< Resync by packet count */
|
||||
ESP_RISCV_TRACE_RESYNC_CYCLE = 3, /*!< Resync by cycle count */
|
||||
} esp_riscv_trace_resync_mode_t;
|
||||
|
||||
/**
|
||||
* @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).
|
||||
*/
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_AHB_SINGLE = 0, /*!< Single transfer (hardware reset default) */
|
||||
ESP_RISCV_TRACE_AHB_INCR = 1, /*!< Incrementing transfer (length not defined) */
|
||||
ESP_RISCV_TRACE_AHB_INCR4 = 2, /*!< 4-beat incrementing transfer */
|
||||
ESP_RISCV_TRACE_AHB_INCR8 = 4, /*!< 8-beat incrementing transfer */
|
||||
} esp_riscv_trace_ahb_burst_t;
|
||||
|
||||
/** @brief Where the driver allocates the trace buffer. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_BUFFER_INTERNAL = 0, /*!< Internal RAM / L2MEM (hardware reset default) */
|
||||
ESP_RISCV_TRACE_BUFFER_EXTERNAL = 1, /*!< External RAM / PSRAM */
|
||||
} esp_riscv_trace_buffer_mem_t;
|
||||
|
||||
/** @brief Filter comparator input. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_FILTER_INPUT_IADDR = 0, /*!< Compare the instruction address (PC) */
|
||||
ESP_RISCV_TRACE_FILTER_INPUT_TVAL = 1, /*!< Compare the trap value (tval) */
|
||||
} esp_riscv_trace_filter_input_t;
|
||||
|
||||
/** @brief Filter comparator function. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_FILTER_COMPARATOR_EQ = 0, /*!< input == match_value */
|
||||
ESP_RISCV_TRACE_FILTER_COMPARATOR_NE = 1, /*!< input != match_value */
|
||||
ESP_RISCV_TRACE_FILTER_COMPARATOR_LT = 2, /*!< input < match_value */
|
||||
ESP_RISCV_TRACE_FILTER_COMPARATOR_LE = 3, /*!< input <= match_value */
|
||||
ESP_RISCV_TRACE_FILTER_COMPARATOR_GT = 4, /*!< input > match_value */
|
||||
ESP_RISCV_TRACE_FILTER_COMPARATOR_GE = 5, /*!< input >= match_value */
|
||||
} esp_riscv_trace_filter_comparator_func_t;
|
||||
|
||||
/** @brief How the primary (P) and secondary (S) comparators combine. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_FILTER_MODE_PRIMARY = 0, /*!< Primary comparator only */
|
||||
ESP_RISCV_TRACE_FILTER_MODE_AND = 1, /*!< P && S */
|
||||
ESP_RISCV_TRACE_FILTER_MODE_NAND = 2, /*!< !(P && S) */
|
||||
ESP_RISCV_TRACE_FILTER_MODE_RANGE = 3, /*!< Start when P matches, continue until S matches */
|
||||
} esp_riscv_trace_filter_mode_t;
|
||||
|
||||
/** @brief Privilege level selected by the privilege qualifier. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_FILTER_PRIV_USER = 0, /*!< User mode */
|
||||
ESP_RISCV_TRACE_FILTER_PRIV_MACHINE = 1, /*!< Machine mode */
|
||||
} esp_riscv_trace_filter_priv_t;
|
||||
|
||||
/** @brief One filter comparator. */
|
||||
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 */
|
||||
} esp_riscv_trace_filter_comparator_t;
|
||||
|
||||
/**
|
||||
* @brief Filter (trace qualifier) configuration.
|
||||
*
|
||||
* The filter restricts which execution produces trace packets. With .enable = false the encoder
|
||||
* traces everything (the default). Apply via esp_riscv_trace_set_filter() before a capture.
|
||||
*/
|
||||
typedef struct {
|
||||
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 */
|
||||
esp_riscv_trace_filter_mode_t mode; /*!< How P and S combine */
|
||||
bool match_privilege; /*!< Gate matching on the privilege level */
|
||||
esp_riscv_trace_filter_priv_t privilege; /*!< Privilege level to match */
|
||||
bool match_ecause; /*!< Match from an exception cause */
|
||||
uint8_t ecause; /*!< Exception cause code (6-bit) */
|
||||
bool match_interrupt; /*!< Match from an interrupt trap */
|
||||
bool interrupt_itype2; /*!< true = match itype 2, false = itype 1 */
|
||||
} esp_riscv_trace_filter_config_t;
|
||||
|
||||
/**
|
||||
* @brief Configuration for a trace encoder instance.
|
||||
*
|
||||
* @note On targets without SOC_RISCV_TRACE_HAS_CONFIG_REG (address_mode, stall_cpu,
|
||||
* halt_enable, reset_enable, debug_trigger_enable) or without SOC_RISCV_TRACE_AHB_CONFIGURABLE
|
||||
* (ahb_burst, ahb_max_incr), those fields are accepted but ignored. The encoder keeps its
|
||||
* fixed default behavior.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t core_mask; /*!< Bitmask of cores to trace (BIT(n) for core n) */
|
||||
size_t buffer_size; /*!< Trace buffer size in bytes (driver-allocated) */
|
||||
esp_riscv_trace_buffer_mem_t buffer_mem; /*!< Where the driver allocates the buffer */
|
||||
esp_riscv_trace_address_mode_t address_mode; /*!< Instruction address encoding in the trace stream. */
|
||||
esp_riscv_trace_mem_mode_t mem_mode; /*!< Trace buffer memory mode */
|
||||
bool auto_restart; /*!< Auto-restart encoder after a FIFO overflow */
|
||||
bool stall_cpu; /*!< Stall CPU instead of dropping packets on FIFO full */
|
||||
bool halt_enable; /*!< Trace through hart halt */
|
||||
bool reset_enable; /*!< Trace through hart reset */
|
||||
bool debug_trigger_enable; /*!< Enable Debug Module trigger input */
|
||||
esp_riscv_trace_resync_mode_t resync_mode; /*!< Resynchronization mode */
|
||||
uint32_t resync_threshold; /*!< Resync threshold. (default 128) */
|
||||
esp_riscv_trace_ahb_burst_t ahb_burst; /*!< AHB burst type used by the trace write master. */
|
||||
uint8_t ahb_max_incr; /*!< Max INCR burst beats. (default 0) */
|
||||
uint32_t intr_mask; /*!< Interrupt bits to enable for polling via get_intr_status() */
|
||||
} esp_riscv_trace_config_t;
|
||||
|
||||
/**
|
||||
* @brief Default trace encoder configuration built from Kconfig values.
|
||||
*
|
||||
* Use this to get a ready-to-use configuration without filling every field by hand,
|
||||
* then override individual members afterwards if needed.
|
||||
*
|
||||
* Example:
|
||||
* @code{c}
|
||||
* esp_riscv_trace_config_t config = ESP_RISCV_TRACE_DEFAULT_CONFIG();
|
||||
* @endcode
|
||||
*/
|
||||
#define ESP_RISCV_TRACE_DEFAULT_CONFIG() { \
|
||||
.core_mask = CONFIG_ESP_RISCV_TRACE_CORE_MASK, \
|
||||
.buffer_size = CONFIG_ESP_RISCV_TRACE_BUFFER_SIZE, \
|
||||
.buffer_mem = CONFIG_ESP_RISCV_TRACE_BUFFER_MEM, \
|
||||
.address_mode = CONFIG_ESP_RISCV_TRACE_ADDRESS_MODE, \
|
||||
.mem_mode = CONFIG_ESP_RISCV_TRACE_MEM_MODE, \
|
||||
.auto_restart = CONFIG_ESP_RISCV_TRACE_AUTO_RESTART, \
|
||||
.stall_cpu = CONFIG_ESP_RISCV_TRACE_STALL_CPU, \
|
||||
.halt_enable = CONFIG_ESP_RISCV_TRACE_HALT, \
|
||||
.reset_enable = CONFIG_ESP_RISCV_TRACE_RESET, \
|
||||
.debug_trigger_enable = CONFIG_ESP_RISCV_TRACE_DEBUG_TRIGGER, \
|
||||
.resync_mode = CONFIG_ESP_RISCV_TRACE_RESYNC_MODE, \
|
||||
.resync_threshold = CONFIG_ESP_RISCV_TRACE_RESYNC_THRESHOLD, \
|
||||
.ahb_burst = CONFIG_ESP_RISCV_TRACE_AHB_BURST, \
|
||||
.ahb_max_incr = CONFIG_ESP_RISCV_TRACE_AHB_MAX_INCR, \
|
||||
.intr_mask = 0, \
|
||||
}
|
||||
|
||||
/** @brief Encoder work status. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_WORK_STATUS_IDLE = 0,
|
||||
ESP_RISCV_TRACE_WORK_STATUS_WORKING = 1,
|
||||
ESP_RISCV_TRACE_WORK_STATUS_WAIT = 2,
|
||||
ESP_RISCV_TRACE_WORK_STATUS_LOST = 3,
|
||||
} esp_riscv_trace_work_status_t;
|
||||
|
||||
/** @brief Live encoder status snapshot. */
|
||||
typedef struct {
|
||||
esp_riscv_trace_work_status_t work_status;
|
||||
bool fifo_empty;
|
||||
bool memory_full;
|
||||
bool fifo_overflowed;
|
||||
} esp_riscv_trace_status_t;
|
||||
|
||||
/**
|
||||
* @note RISC-V trace driver APIs are serialized per trace core and are intended
|
||||
* for task context. Do not call them from ISR context.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Start tracing on a specified core.
|
||||
*
|
||||
* @param core_id Core whose trace to start
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_STATE if the core is not initialized
|
||||
*/
|
||||
esp_err_t esp_riscv_trace_start(esp_riscv_trace_core_t core_id);
|
||||
|
||||
/**
|
||||
* @brief Stop tracing on a specified core and wait for the FIFO to empty.
|
||||
*
|
||||
* @param core_id Core whose trace to stop
|
||||
* @param timeout_us Timeout in microseconds
|
||||
* @return ESP_OK on success, ESP_ERR_TIMEOUT if the FIFO is not empty within the timeout
|
||||
*/
|
||||
esp_err_t esp_riscv_trace_stop(esp_riscv_trace_core_t core_id, uint32_t timeout_us);
|
||||
|
||||
/**
|
||||
* @brief Get a core's trace buffer.
|
||||
*
|
||||
* @param core_id Core whose buffer to retrieve
|
||||
* @param buffer Out: buffer base address
|
||||
* @param capacity Out: total buffer size in bytes
|
||||
* @param head_offset Out: current write offset within the buffer [0, capacity]
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_STATE if the core is not initialized
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @brief Get the status of a specified core's trace encoder.
|
||||
*
|
||||
* @param core_id Core whose status to retrieve
|
||||
* @param status Out: status snapshot
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_STATE if the core is not initialized
|
||||
*/
|
||||
esp_err_t esp_riscv_trace_get_status(esp_riscv_trace_core_t core_id, esp_riscv_trace_status_t *status);
|
||||
|
||||
/**
|
||||
* @brief Apply a filter (trace qualifier) to a core's encoder.
|
||||
*
|
||||
* @param core_id Core whose filter to configure
|
||||
* @param config Filter configuration
|
||||
* @return ESP_ERR_NOT_SUPPORTED on targets without a filter unit
|
||||
*/
|
||||
esp_err_t esp_riscv_trace_set_filter(esp_riscv_trace_core_t core_id, const esp_riscv_trace_filter_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Provide the per-core configuration used by the startup auto-initialization.
|
||||
*
|
||||
* Called once per core during startup, so each trace encoder can be configured independently
|
||||
* (different buffer, address mode, resync, etc.). The default (weak) implementation returns
|
||||
* ESP_RISCV_TRACE_DEFAULT_CONFIG() for every core. Define your own strong version in your
|
||||
* application (typically switching on @p core_id) to override the configuration.
|
||||
*
|
||||
* Only the @p core_id bit of the returned .core_mask is checked for this call: clear it to skip
|
||||
* tracing this core at runtime even if it is selected by Kconfig.
|
||||
*
|
||||
* @param core_id Core the returned configuration applies to
|
||||
* @return The configuration the startup auto-init uses for @p core_id.
|
||||
*/
|
||||
esp_riscv_trace_config_t esp_riscv_trace_get_user_config(int core_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_cache.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_private/esp_cache_private.h"
|
||||
#include "esp_private/startup_internal.h"
|
||||
#include "esp_cpu.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/riscv_trace_hal.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
#include "esp_riscv_trace_priv.h"
|
||||
|
||||
#define ESP_RISCV_TRACE_OBJ_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
|
||||
#define ESP_RISCV_TRACE_BUFFER_CAPS_INTERNAL (MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#define ESP_RISCV_TRACE_BUFFER_CAPS_EXTERNAL (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)
|
||||
|
||||
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
||||
|
||||
static const char *TAG = "esp_riscv_trace";
|
||||
|
||||
/* Handles created at startup by esp_riscv_trace_early_init(), one slot per core. */
|
||||
static esp_riscv_trace_handle_t s_handle[SOC_CPU_CORES_NUM];
|
||||
|
||||
static uint8_t *alloc_aligned_buffer(size_t requested, uint32_t caps, size_t *out_size)
|
||||
{
|
||||
*out_size = 0;
|
||||
|
||||
size_t cache_line = 0;
|
||||
ESP_RETURN_ON_FALSE(esp_cache_get_alignment(caps, &cache_line) == ESP_OK, NULL, TAG,
|
||||
"failed to get buffer alignment");
|
||||
ESP_RETURN_ON_FALSE(requested <= SIZE_MAX - (cache_line - 1), NULL, TAG,
|
||||
"trace buffer size too large");
|
||||
size_t size = ALIGN_UP(requested, cache_line);
|
||||
uint8_t *buf = heap_caps_calloc(1, size, caps | MALLOC_CAP_CACHE_ALIGNED);
|
||||
ESP_RETURN_ON_FALSE(buf != NULL, NULL, TAG, "failed to allocate buffer");
|
||||
*out_size = size;
|
||||
return buf;
|
||||
}
|
||||
|
||||
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) {
|
||||
/* Buffer is not cacheable. Skip synchronization. */
|
||||
return ESP_OK;
|
||||
}
|
||||
return esp_cache_msync(buffer, size, flags);
|
||||
}
|
||||
|
||||
static esp_err_t clear_trace_buffer(esp_riscv_trace_handle_t handle)
|
||||
{
|
||||
memset(handle->buffer, 0, handle->buffer_size);
|
||||
return sync_trace_buffer(handle->buffer, handle->buffer_size,
|
||||
ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
|
||||
}
|
||||
|
||||
static esp_riscv_trace_handle_t handle_from_core(esp_riscv_trace_core_t core_id)
|
||||
{
|
||||
if ((int)core_id < 0 || (int)core_id >= SOC_CPU_CORES_NUM) {
|
||||
return NULL;
|
||||
}
|
||||
return s_handle[core_id];
|
||||
}
|
||||
|
||||
static bool is_valid_address_mode(esp_riscv_trace_address_mode_t mode)
|
||||
{
|
||||
return mode == ESP_RISCV_TRACE_ADDR_DELTA || mode == ESP_RISCV_TRACE_ADDR_FULL;
|
||||
}
|
||||
|
||||
static bool is_valid_mem_mode(esp_riscv_trace_mem_mode_t mode)
|
||||
{
|
||||
return mode == ESP_RISCV_TRACE_MEM_NON_LOOP || mode == ESP_RISCV_TRACE_MEM_LOOP;
|
||||
}
|
||||
|
||||
static bool is_valid_resync_mode(esp_riscv_trace_resync_mode_t mode)
|
||||
{
|
||||
return mode == ESP_RISCV_TRACE_RESYNC_DISABLED ||
|
||||
mode == ESP_RISCV_TRACE_RESYNC_PACKET ||
|
||||
mode == ESP_RISCV_TRACE_RESYNC_CYCLE;
|
||||
}
|
||||
|
||||
static bool is_valid_ahb_burst(esp_riscv_trace_ahb_burst_t burst)
|
||||
{
|
||||
return burst == ESP_RISCV_TRACE_AHB_SINGLE ||
|
||||
burst == ESP_RISCV_TRACE_AHB_INCR ||
|
||||
burst == ESP_RISCV_TRACE_AHB_INCR4 ||
|
||||
burst == ESP_RISCV_TRACE_AHB_INCR8;
|
||||
}
|
||||
|
||||
static bool is_valid_core_mask(uint32_t core_mask)
|
||||
{
|
||||
const uint32_t valid_mask = BIT(SOC_CPU_CORES_NUM) - 1;
|
||||
return core_mask != 0 && (core_mask & ~valid_mask) == 0;
|
||||
}
|
||||
|
||||
static bool is_valid_buffer_mem(esp_riscv_trace_buffer_mem_t mem)
|
||||
{
|
||||
return mem == ESP_RISCV_TRACE_BUFFER_INTERNAL || mem == ESP_RISCV_TRACE_BUFFER_EXTERNAL;
|
||||
}
|
||||
|
||||
static uint32_t trace_buffer_caps(esp_riscv_trace_buffer_mem_t mem)
|
||||
{
|
||||
return mem == ESP_RISCV_TRACE_BUFFER_EXTERNAL ? ESP_RISCV_TRACE_BUFFER_CAPS_EXTERNAL
|
||||
: ESP_RISCV_TRACE_BUFFER_CAPS_INTERNAL;
|
||||
}
|
||||
|
||||
static bool is_valid_filter_comparator(const esp_riscv_trace_filter_comparator_t *c)
|
||||
{
|
||||
return (c->input == ESP_RISCV_TRACE_FILTER_INPUT_IADDR ||
|
||||
c->input == ESP_RISCV_TRACE_FILTER_INPUT_TVAL) &&
|
||||
((int)c->function >= ESP_RISCV_TRACE_FILTER_COMPARATOR_EQ &&
|
||||
(int)c->function <= ESP_RISCV_TRACE_FILTER_COMPARATOR_GE);
|
||||
}
|
||||
|
||||
static esp_err_t validate_filter_config(const esp_riscv_trace_filter_config_t *config)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
if (!config->enable) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (config->match_comparators) {
|
||||
ESP_RETURN_ON_FALSE(is_valid_filter_comparator(&config->primary) &&
|
||||
is_valid_filter_comparator(&config->secondary),
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid filter comparator input/function");
|
||||
ESP_RETURN_ON_FALSE((int)config->mode >= ESP_RISCV_TRACE_FILTER_MODE_PRIMARY &&
|
||||
(int)config->mode <= ESP_RISCV_TRACE_FILTER_MODE_RANGE,
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid filter match mode");
|
||||
}
|
||||
if (config->match_privilege) {
|
||||
ESP_RETURN_ON_FALSE(config->privilege == ESP_RISCV_TRACE_FILTER_PRIV_USER ||
|
||||
config->privilege == ESP_RISCV_TRACE_FILTER_PRIV_MACHINE,
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid filter privilege");
|
||||
}
|
||||
if (config->match_ecause) {
|
||||
ESP_RETURN_ON_FALSE(config->ecause <= 0x3F, ESP_ERR_INVALID_ARG, TAG, "filter ecause out of range");
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t validate_trace_config(esp_riscv_trace_core_t core_id, const esp_riscv_trace_config_t *config,
|
||||
esp_riscv_trace_handle_t *ret_handle)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(ret_handle != NULL, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE((int)core_id >= 0 && (int)core_id < SOC_CPU_CORES_NUM,
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid core id");
|
||||
ESP_RETURN_ON_FALSE(config->buffer_size != 0, ESP_ERR_INVALID_SIZE, TAG, "trace buffer size is 0");
|
||||
ESP_RETURN_ON_FALSE(is_valid_address_mode(config->address_mode), ESP_ERR_INVALID_ARG, TAG, "invalid address mode");
|
||||
ESP_RETURN_ON_FALSE(is_valid_mem_mode(config->mem_mode), ESP_ERR_INVALID_ARG, TAG, "invalid memory mode");
|
||||
ESP_RETURN_ON_FALSE(is_valid_resync_mode(config->resync_mode), ESP_ERR_INVALID_ARG, TAG, "invalid resync mode");
|
||||
ESP_RETURN_ON_FALSE(is_valid_ahb_burst(config->ahb_burst), ESP_ERR_INVALID_ARG, TAG, "invalid AHB burst");
|
||||
ESP_RETURN_ON_FALSE(is_valid_core_mask(config->core_mask), ESP_ERR_INVALID_ARG, TAG, "invalid core mask");
|
||||
ESP_RETURN_ON_FALSE(is_valid_buffer_mem(config->buffer_mem), ESP_ERR_INVALID_ARG, TAG, "invalid buffer memory");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t esp_riscv_trace_new(esp_riscv_trace_core_t core_id, const esp_riscv_trace_config_t *config,
|
||||
esp_riscv_trace_handle_t *ret_handle)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
ESP_RETURN_ON_ERROR(validate_trace_config(core_id, config, ret_handle), TAG, "invalid trace configuration");
|
||||
|
||||
size_t trace_mem_size = 0;
|
||||
uint8_t *trace_mem = alloc_aligned_buffer(config->buffer_size, trace_buffer_caps(config->buffer_mem),
|
||||
&trace_mem_size);
|
||||
ESP_RETURN_ON_FALSE(trace_mem != NULL, ESP_ERR_NO_MEM, TAG, "failed to allocate trace buffer");
|
||||
|
||||
esp_riscv_trace_handle_t handle = heap_caps_calloc(1, sizeof(struct esp_riscv_trace_context_t),
|
||||
ESP_RISCV_TRACE_OBJ_CAPS);
|
||||
ESP_GOTO_ON_FALSE(handle != NULL, ESP_ERR_NO_MEM, err_alloc, TAG, "no mem for the trace handle");
|
||||
|
||||
riscv_trace_hal_context_t hal_ctx;
|
||||
riscv_trace_hal_config_t hal_config = {
|
||||
.mem_start_addr = (uint32_t)trace_mem,
|
||||
.mem_end_addr = (uint32_t)trace_mem + trace_mem_size,
|
||||
.full_address = (config->address_mode == ESP_RISCV_TRACE_ADDR_FULL),
|
||||
.mem_loop = (config->mem_mode == ESP_RISCV_TRACE_MEM_LOOP),
|
||||
.auto_restart = config->auto_restart,
|
||||
.stall_cpu = config->stall_cpu,
|
||||
.halt_enable = config->halt_enable,
|
||||
.reset_enable = config->reset_enable,
|
||||
.debug_trigger_enable = config->debug_trigger_enable,
|
||||
.resync_mode = config->resync_mode,
|
||||
.resync_threshold = config->resync_threshold,
|
||||
.ahb_burst = config->ahb_burst,
|
||||
.ahb_max_incr = config->ahb_max_incr,
|
||||
.intr_mask = config->intr_mask,
|
||||
};
|
||||
riscv_trace_hal_init(core_id, &hal_config, &hal_ctx);
|
||||
|
||||
handle->hal = hal_ctx;
|
||||
handle->core_id = core_id;
|
||||
handle->buffer = trace_mem;
|
||||
handle->buffer_size = trace_mem_size;
|
||||
handle->state = ESP_RISCV_TRACE_STATE_CREATED;
|
||||
handle->auto_restart = config->auto_restart;
|
||||
*ret_handle = handle;
|
||||
|
||||
ESP_EARLY_LOGD(TAG, "RISC-V trace encoder initialized on core %d", core_id);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
err_alloc:
|
||||
heap_caps_free(trace_mem);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_riscv_trace_start(esp_riscv_trace_core_t core_id)
|
||||
{
|
||||
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");
|
||||
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;
|
||||
|
||||
out:
|
||||
_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_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:
|
||||
_lock_release(&handle->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint32_t base = 0;
|
||||
uint32_t current = 0;
|
||||
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);
|
||||
ESP_RETURN_ON_FALSE(buffer != NULL && capacity != NULL && head_offset != NULL,
|
||||
ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
_lock_acquire(&handle->lock);
|
||||
ESP_GOTO_ON_FALSE(handle->state != ESP_RISCV_TRACE_STATE_STARTED, ESP_ERR_INVALID_STATE, out, TAG,
|
||||
"buffer not coherent while started");
|
||||
|
||||
base = (uint32_t)handle->buffer;
|
||||
current = riscv_trace_hal_get_current_addr(&handle->hal);
|
||||
ESP_GOTO_ON_FALSE(current >= base && current <= base + handle->buffer_size,
|
||||
ESP_ERR_INVALID_STATE, out, TAG, "trace write pointer 0x%08x out of buffer range",
|
||||
(unsigned)current);
|
||||
|
||||
*buffer = handle->buffer;
|
||||
*capacity = handle->buffer_size;
|
||||
*head_offset = current - base;
|
||||
|
||||
out:
|
||||
_lock_release(&handle->lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_riscv_trace_get_status(esp_riscv_trace_core_t core_id, esp_riscv_trace_status_t *status)
|
||||
{
|
||||
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);
|
||||
ESP_RETURN_ON_FALSE(status != NULL, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
|
||||
_lock_acquire(&handle->lock);
|
||||
uint32_t fifo_status = riscv_trace_hal_read_fifo_status(&handle->hal);
|
||||
uint32_t intr_status = riscv_trace_hal_read_intr_raw(&handle->hal);
|
||||
|
||||
status->work_status = riscv_trace_hal_get_work_status(fifo_status);
|
||||
status->fifo_empty = riscv_trace_hal_fifo_is_empty(fifo_status);
|
||||
status->memory_full = riscv_trace_hal_memory_is_full(intr_status);
|
||||
status->fifo_overflowed = riscv_trace_hal_fifo_is_overflowed(intr_status);
|
||||
_lock_release(&handle->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if SOC_RISCV_TRACE_FILTER_SUPPORTED
|
||||
esp_err_t esp_riscv_trace_set_filter(esp_riscv_trace_core_t core_id, const esp_riscv_trace_filter_config_t *config)
|
||||
{
|
||||
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);
|
||||
ESP_RETURN_ON_ERROR(validate_filter_config(config), TAG, "invalid filter configuration");
|
||||
|
||||
riscv_trace_hal_filter_config_t hal_filter = { .enable = config->enable };
|
||||
if (config->enable) {
|
||||
hal_filter.match_comparators = config->match_comparators;
|
||||
hal_filter.match_privilege = config->match_privilege;
|
||||
hal_filter.match_ecause = config->match_ecause;
|
||||
hal_filter.match_interrupt = config->match_interrupt;
|
||||
if (config->match_comparators) {
|
||||
hal_filter.primary = (riscv_trace_hal_comparator_t) {
|
||||
.input = (uint32_t)config->primary.input,
|
||||
.function = (uint32_t)config->primary.function,
|
||||
.match_value = config->primary.match_value,
|
||||
.notify = config->primary.notify,
|
||||
};
|
||||
hal_filter.secondary = (riscv_trace_hal_comparator_t) {
|
||||
.input = (uint32_t)config->secondary.input,
|
||||
.function = (uint32_t)config->secondary.function,
|
||||
.match_value = config->secondary.match_value,
|
||||
.notify = config->secondary.notify,
|
||||
};
|
||||
hal_filter.match_mode = (uint32_t)config->mode;
|
||||
}
|
||||
if (config->match_privilege) {
|
||||
hal_filter.privilege_machine = (config->privilege == ESP_RISCV_TRACE_FILTER_PRIV_MACHINE);
|
||||
}
|
||||
if (config->match_ecause) {
|
||||
hal_filter.ecause = config->ecause;
|
||||
}
|
||||
if (config->match_interrupt) {
|
||||
hal_filter.interrupt_itype2 = config->interrupt_itype2;
|
||||
}
|
||||
}
|
||||
_lock_acquire(&handle->lock);
|
||||
ESP_GOTO_ON_FALSE(handle->state != ESP_RISCV_TRACE_STATE_STARTED, ESP_ERR_INVALID_STATE, out, TAG,
|
||||
"filter must be set while the core is not running");
|
||||
riscv_trace_hal_set_filter(&handle->hal, &hal_filter);
|
||||
out:
|
||||
_lock_release(&handle->lock);
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
esp_err_t esp_riscv_trace_set_filter(esp_riscv_trace_core_t core_id, const esp_riscv_trace_filter_config_t *config)
|
||||
{
|
||||
(void)core_id;
|
||||
(void)config;
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
#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(). */
|
||||
esp_riscv_trace_config_t __attribute__((weak)) esp_riscv_trace_get_user_config(int core_id)
|
||||
{
|
||||
(void)core_id;
|
||||
esp_riscv_trace_config_t config = ESP_RISCV_TRACE_DEFAULT_CONFIG();
|
||||
return config;
|
||||
}
|
||||
|
||||
ESP_SYSTEM_INIT_FN(esp_riscv_trace_early_init, SECONDARY, ESP_SYSTEM_INIT_ALL_CORES, 160)
|
||||
{
|
||||
int core_id = esp_cpu_get_core_id();
|
||||
esp_riscv_trace_config_t config = esp_riscv_trace_get_user_config(core_id);
|
||||
|
||||
if (!is_valid_core_mask(config.core_mask)) {
|
||||
ESP_EARLY_LOGE(TAG, "invalid core mask");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if ((config.core_mask & BIT(core_id)) == 0) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/lock.h>
|
||||
#include "hal/riscv_trace_hal.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @brief Opaque handle to a RISC-V trace encoder instance (driver-internal). */
|
||||
typedef struct esp_riscv_trace_context_t *esp_riscv_trace_handle_t;
|
||||
|
||||
/** @brief Driver lifecycle state. */
|
||||
typedef enum {
|
||||
ESP_RISCV_TRACE_STATE_CREATED = 0, /*!< Configured, not yet started */
|
||||
ESP_RISCV_TRACE_STATE_STARTED = 1, /*!< Capturing */
|
||||
ESP_RISCV_TRACE_STATE_STOPPED = 2, /*!< Stopped. Buffer synced and readable */
|
||||
} esp_riscv_trace_state_t;
|
||||
|
||||
struct esp_riscv_trace_context_t {
|
||||
riscv_trace_hal_context_t hal;
|
||||
_lock_t lock;
|
||||
esp_riscv_trace_state_t state;
|
||||
int core_id;
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size;
|
||||
bool auto_restart;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
components/esp_riscv_trace/test_apps:
|
||||
disable:
|
||||
- if: SOC_RISCV_TRACE_SUPPORTED != 1
|
||||
- if: CONFIG_NAME == "psram" and SOC_SPIRAM_SUPPORTED != 1
|
||||
depends_components:
|
||||
- esp_riscv_trace
|
||||
@@ -0,0 +1,7 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(riscv_trace_basic_test)
|
||||
@@ -0,0 +1,48 @@
|
||||
| Supported Targets | ESP32-P4 |
|
||||
| ----------------- | -------- |
|
||||
|
||||
# RISC-V Trace Basic Test App
|
||||
|
||||
This app validates the basic RISC-V trace encoder driver flow on targets that
|
||||
support the trace peripheral. It is intentionally small: the tests focus on
|
||||
startup configuration, start/stop sequencing, trace-buffer capture, status
|
||||
reporting, and basic filter programming.
|
||||
|
||||
## Test Configurations
|
||||
|
||||
Pytest runs the app with two configurations:
|
||||
|
||||
- `default`: uses the driver-allocated internal trace buffer.
|
||||
- `psram`: enables PSRAM and selects `CONFIG_ESP_RISCV_TRACE_BUFFER_IN_EXTERNAL`
|
||||
so the driver allocates the trace buffer from external RAM. This configuration
|
||||
also enables CPU stall-on-FIFO-full to avoid losing coverage when the external
|
||||
memory target is slower than internal RAM.
|
||||
|
||||
Both configurations inherit `sdkconfig.defaults`, which enables the driver,
|
||||
sets packet resynchronization, and configures the trace buffer size.
|
||||
|
||||
## Unity Test Cases
|
||||
|
||||
- `RISC-V trace loop capture`
|
||||
- Starts tracing on the configured core mask.
|
||||
- Runs a branch-heavy workload to fill and wrap the loop buffer.
|
||||
- Stops tracing, checks the encoder status, and decodes the wrapped buffer
|
||||
from surviving resynchronization anchors.
|
||||
|
||||
- `RISC-V trace address-window filter`
|
||||
- Captures a baseline trace with filtering disabled.
|
||||
- Programs an instruction-address window filter around one workload function.
|
||||
- Confirms the filtered capture is smaller and that sync packets land inside
|
||||
the selected address window.
|
||||
|
||||
## Running
|
||||
|
||||
The pytest entry points run all Unity cases for each selected configuration:
|
||||
|
||||
```bash
|
||||
idf-ci build run -t esp32p4 -p components/esp_riscv_trace/test_apps/basic
|
||||
pytest --target esp32p4 components/esp_riscv_trace/test_apps/basic
|
||||
```
|
||||
|
||||
For manual hardware validation, build and flash the app with the desired
|
||||
configuration and run all Unity cases from the device menu.
|
||||
@@ -0,0 +1,12 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_riscv_trace_common.c"
|
||||
"test_riscv_trace_basic.c")
|
||||
|
||||
if(CONFIG_SOC_RISCV_TRACE_FILTER_SUPPORTED)
|
||||
list(APPEND srcs "test_riscv_trace_filter.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity esp_riscv_trace esp_psram
|
||||
WHOLE_ARCHIVE)
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD (-400)
|
||||
|
||||
static size_t before_free_8bit;
|
||||
static size_t before_free_32bit;
|
||||
|
||||
/* Clear any filter left set by a previous (possibly failed) test case, so each case starts with
|
||||
* the encoder tracing everything. Only the configured cores have an initialized handle.
|
||||
* No-op (ESP_ERR_NOT_SUPPORTED) on targets without a filter unit. */
|
||||
static void clear_all_filters(void)
|
||||
{
|
||||
esp_riscv_trace_filter_config_t filter_off = { .enable = false };
|
||||
for (int core = 0; core < SOC_CPU_CORES_NUM; core++) {
|
||||
if (CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << core)) {
|
||||
esp_riscv_trace_set_filter((esp_riscv_trace_core_t)core, &filter_off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_leak(size_t before_free, size_t after_free, const char *type)
|
||||
{
|
||||
ssize_t delta = after_free - before_free;
|
||||
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n",
|
||||
type, before_free, after_free, delta);
|
||||
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
|
||||
}
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
clear_all_filters();
|
||||
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
check_leak(before_free_8bit, after_free_8bit, "8BIT");
|
||||
check_leak(before_free_32bit, after_free_32bit, "32BIT");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "soc/soc.h"
|
||||
#include "unity.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
#include "test_riscv_trace_common.h"
|
||||
|
||||
static const char *TAG = "riscv_trace_basic";
|
||||
|
||||
/* Large enough to wrap each core's buffer several times, so each physical side carries anchor tags. */
|
||||
#define TRACE_TEST_WORKLOAD_ITERATIONS 8192
|
||||
|
||||
/* Per-core buffer sizes, applied via the strong esp_riscv_trace_get_user_config() below to
|
||||
* demonstrate (and test) that each core's encoder is configured independently. */
|
||||
#define TRACE_CORE0_BUFFER_SIZE 2048
|
||||
#define TRACE_CORE1_BUFFER_SIZE 4096
|
||||
|
||||
static volatile uint32_t s_trace_sink;
|
||||
|
||||
/* Strong override of the weak startup hook: configure each core's encoder independently. Only the
|
||||
* buffer size differs here; everything else stays at the Kconfig defaults. */
|
||||
esp_riscv_trace_config_t esp_riscv_trace_get_user_config(int core_id)
|
||||
{
|
||||
esp_riscv_trace_config_t config = ESP_RISCV_TRACE_DEFAULT_CONFIG();
|
||||
config.buffer_size = (core_id == ESP_RISCV_TRACE_CORE_1) ? TRACE_CORE1_BUFFER_SIZE
|
||||
: TRACE_CORE0_BUFFER_SIZE;
|
||||
return config;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
size_t packets;
|
||||
size_t syncs;
|
||||
} trace_walk_result_t;
|
||||
|
||||
/* A branchy workload so the encoder emits many Format 1 (branch) packets. */
|
||||
static void IRAM_ATTR branchy_work(void)
|
||||
{
|
||||
uint32_t acc = s_trace_sink;
|
||||
for (volatile int i = 0; i < TRACE_TEST_WORKLOAD_ITERATIONS; i++) {
|
||||
if ((i & 3) == 0) {
|
||||
acc += (uint32_t)i;
|
||||
} else {
|
||||
acc ^= ((uint32_t)i << 1);
|
||||
}
|
||||
}
|
||||
s_trace_sink = acc;
|
||||
}
|
||||
|
||||
static bool addr_in_code(uint32_t addr)
|
||||
{
|
||||
/* A periodic sync can be taken anywhere the core executes */
|
||||
return (addr >= SOC_IROM_LOW && addr < SOC_IROM_HIGH) ||
|
||||
(addr >= SOC_IRAM_LOW && addr < SOC_IRAM_HIGH) ||
|
||||
(addr >= SOC_IROM_MASK_LOW && addr < SOC_IROM_MASK_HIGH);
|
||||
}
|
||||
|
||||
/* If payload is a sync packet, validate its full address is in a code region and return true. */
|
||||
static bool sync_addr_ok(esp_riscv_trace_core_t core, const uint8_t *payload, size_t payload_len)
|
||||
{
|
||||
uint32_t addr = 0;
|
||||
if (!decode_sync_pc(payload, payload_len, &addr)) {
|
||||
return false;
|
||||
}
|
||||
bool in_code = addr_in_code(addr);
|
||||
if (!in_code) {
|
||||
ESP_LOGE(TAG, "core %d: sync address 0x%08x not in a code region", (int)core, (unsigned)addr);
|
||||
}
|
||||
TEST_ASSERT_TRUE_MESSAGE(in_code, "sync address not in a code region");
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t *linearize_ring(const uint8_t *buf, size_t capacity, size_t head)
|
||||
{
|
||||
/* Linearize the ring into chronological order: [head..capacity) then [0..head). */
|
||||
uint8_t *lin = malloc(capacity);
|
||||
TEST_ASSERT_NOT_NULL(lin);
|
||||
memcpy(lin, buf + head, capacity - head);
|
||||
memcpy(lin + (capacity - head), buf, head);
|
||||
return lin;
|
||||
}
|
||||
|
||||
static bool find_anchor_boundary(const uint8_t *lin, size_t start, size_t end, size_t *offset)
|
||||
{
|
||||
/* Find the first anchor tag (>=14 zeros); the next byte is a clean packet boundary. */
|
||||
size_t zero_run = 0;
|
||||
bool at_boundary = false;
|
||||
*offset = start;
|
||||
while (*offset < end) {
|
||||
if (lin[*offset] == 0) {
|
||||
zero_run++;
|
||||
(*offset)++;
|
||||
if (zero_run >= TRACE_ANCHOR_ZERO_RUN_BYTES) {
|
||||
at_boundary = true;
|
||||
}
|
||||
} else if (at_boundary) {
|
||||
return true;
|
||||
} else {
|
||||
zero_run = 0;
|
||||
(*offset)++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static trace_walk_result_t walk_packets(esp_riscv_trace_core_t core, const uint8_t *lin,
|
||||
size_t offset, size_t end)
|
||||
{
|
||||
trace_walk_result_t result = { 0 };
|
||||
bool have_index = false;
|
||||
uint16_t prev_index = 0;
|
||||
|
||||
while (offset < end) {
|
||||
if (lin[offset] == 0) {
|
||||
while (offset < end && lin[offset] == 0) {
|
||||
offset++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
uint8_t plen = lin[offset] & 0x1F;
|
||||
if (plen < TRACE_PACKET_MIN_LEN || plen > TRACE_PACKET_MAX_LEN || offset + plen > end) {
|
||||
break; /* reached this side's newest end; stop cleanly */
|
||||
}
|
||||
TEST_ASSERT_EQUAL_UINT8_MESSAGE(0, lin[offset] >> 5, "reserved header bits set");
|
||||
uint16_t index = (uint16_t)(lin[offset + 1] | (lin[offset + 2] << 8));
|
||||
const uint8_t *payload = &lin[offset + TRACE_PACKET_PREFIX_SIZE];
|
||||
uint8_t format = payload[0] & 0x3;
|
||||
TEST_ASSERT_TRUE_MESSAGE(format == 1 || format == 2 || format == 3, "invalid packet format");
|
||||
if (have_index) {
|
||||
TEST_ASSERT_EQUAL_UINT16_MESSAGE((uint16_t)(prev_index + 1), index, "index not monotonic");
|
||||
}
|
||||
prev_index = index;
|
||||
have_index = true;
|
||||
if (sync_addr_ok(core, payload, plen - TRACE_PACKET_PREFIX_SIZE)) {
|
||||
result.syncs++;
|
||||
}
|
||||
offset += plen;
|
||||
result.packets++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static trace_walk_result_t walk_side_from_anchor(esp_riscv_trace_core_t core, const uint8_t *lin,
|
||||
size_t start, size_t end)
|
||||
{
|
||||
trace_walk_result_t result = { 0 };
|
||||
size_t off = 0;
|
||||
if (find_anchor_boundary(lin, start, end, &off)) {
|
||||
result = walk_packets(core, lin, off, end);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static trace_walk_result_t add_walk_results(trace_walk_result_t a, trace_walk_result_t b)
|
||||
{
|
||||
trace_walk_result_t result = {
|
||||
.packets = a.packets + b.packets,
|
||||
.syncs = a.syncs + b.syncs,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Loop mode has no "wrapped" HW flag, so infer the wrap from data */
|
||||
static void validate_wrapped_stream(esp_riscv_trace_core_t core)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_MESSAGE(ESP_RISCV_TRACE_MEM_LOOP, CONFIG_ESP_RISCV_TRACE_MEM_MODE,
|
||||
"test expects loop memory mode");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(ESP_RISCV_TRACE_RESYNC_PACKET, CONFIG_ESP_RISCV_TRACE_RESYNC_MODE,
|
||||
"test expects packet resync mode");
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, CONFIG_ESP_RISCV_TRACE_RESYNC_THRESHOLD,
|
||||
"resync threshold must be nonzero");
|
||||
|
||||
uint8_t *buf = NULL;
|
||||
size_t capacity = 0;
|
||||
size_t head = 0;
|
||||
TEST_ESP_OK(esp_riscv_trace_get_buffer(core, &buf, &capacity, &head));
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, capacity, "trace buffer capacity is zero");
|
||||
TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(capacity, head, "trace buffer head is out of range");
|
||||
TEST_ASSERT_TRUE_MESSAGE(buffer_has_wrapped(buf, capacity, head),
|
||||
"buffer did not wrap (shrink buffer or grow workload)");
|
||||
|
||||
/* The encoder keeps no packet/index continuity across the physical wrap (at capacity - head),
|
||||
* so decode each side from its own anchor tag instead of walking across it. */
|
||||
uint8_t *lin = linearize_ring(buf, capacity, head);
|
||||
const size_t mem_wrap = capacity - head;
|
||||
trace_walk_result_t before_wrap = walk_side_from_anchor(core, lin, 0, mem_wrap);
|
||||
trace_walk_result_t after_wrap = walk_side_from_anchor(core, lin, mem_wrap, capacity);
|
||||
trace_walk_result_t walk = add_walk_results(before_wrap, after_wrap);
|
||||
free(lin);
|
||||
|
||||
/* ~1 sync per `threshold` packets; each side may miss its first/last interval, so allow margin. */
|
||||
const size_t threshold = CONFIG_ESP_RISCV_TRACE_RESYNC_THRESHOLD;
|
||||
const size_t expected = walk.packets / threshold;
|
||||
ESP_LOGI(TAG, "core %d wrap: capacity %u, head %u, %u packets, %u sync (approx %u, tolerance -2/+1)",
|
||||
(int)core, (unsigned)capacity, (unsigned)head,
|
||||
(unsigned)walk.packets, (unsigned)walk.syncs, (unsigned)expected);
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, walk.packets, "no packets walked from wrapped buffer");
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, walk.syncs, "no sync survived in the wrapped buffer");
|
||||
TEST_ASSERT_TRUE_MESSAGE(walk.syncs + 2 >= expected && walk.syncs <= expected + 1,
|
||||
"packet-resync sync count does not match packets / threshold");
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) core1_work_task(void *arg)
|
||||
{
|
||||
TaskHandle_t notify_to = (TaskHandle_t)arg;
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* wait until tracing is started */
|
||||
branchy_work();
|
||||
xTaskNotifyGive(notify_to); /* signal work done */
|
||||
vTaskSuspend(NULL); /* park until the test deletes us */
|
||||
}
|
||||
|
||||
/* End-to-end loop-buffer capture: start tracing, run a branchy workload that wraps the ring buffer
|
||||
* several times, then stop and check the captured stream (buffer wrapped, packets well-formed with
|
||||
* monotonic indices, sync addresses land in code, sync count ~ packets/threshold). Runs on core 0,
|
||||
* and on core 1 in parallel if it is in the configured core mask. */
|
||||
TEST_CASE("RISC-V trace loop capture", "[riscv_trace]")
|
||||
{
|
||||
TEST_ASSERT_EQUAL_MESSAGE(ESP_RISCV_TRACE_CORE_0, xPortGetCoreID(), "test must run on core 0");
|
||||
TEST_ASSERT_TRUE_MESSAGE(CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << ESP_RISCV_TRACE_CORE_0),
|
||||
"core 0 must be in the configured trace core mask");
|
||||
const bool trace_core1 = (CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << ESP_RISCV_TRACE_CORE_1)) != 0;
|
||||
|
||||
TaskHandle_t worker = NULL;
|
||||
if (trace_core1) {
|
||||
TEST_ASSERT_EQUAL(pdPASS, xTaskCreatePinnedToCore(core1_work_task, "trc_c1", 4096,
|
||||
xTaskGetCurrentTaskHandle(), 5, &worker, 1));
|
||||
}
|
||||
|
||||
TEST_ESP_OK(esp_riscv_trace_start(ESP_RISCV_TRACE_CORE_0));
|
||||
if (trace_core1) {
|
||||
TEST_ESP_OK(esp_riscv_trace_start(ESP_RISCV_TRACE_CORE_1));
|
||||
xTaskNotifyGive(worker); /* let core 1 run its workload */
|
||||
}
|
||||
|
||||
branchy_work(); /* core 0 workload */
|
||||
|
||||
if (trace_core1) {
|
||||
TEST_ASSERT_EQUAL(1, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
|
||||
TEST_ESP_OK(esp_riscv_trace_stop(ESP_RISCV_TRACE_CORE_1, TRACE_TEST_STOP_TIMEOUT_US));
|
||||
}
|
||||
TEST_ESP_OK(esp_riscv_trace_stop(ESP_RISCV_TRACE_CORE_0, TRACE_TEST_STOP_TIMEOUT_US));
|
||||
|
||||
validate_trace_status(ESP_RISCV_TRACE_CORE_0);
|
||||
validate_wrapped_stream(ESP_RISCV_TRACE_CORE_0);
|
||||
if (trace_core1) {
|
||||
validate_trace_status(ESP_RISCV_TRACE_CORE_1);
|
||||
validate_wrapped_stream(ESP_RISCV_TRACE_CORE_1);
|
||||
vTaskDelete(worker);
|
||||
}
|
||||
}
|
||||
|
||||
/* Each core was set up by esp_riscv_trace_get_user_config() with a different buffer size, proving
|
||||
* the two encoders are configured independently rather than sharing one config. */
|
||||
TEST_CASE("RISC-V trace per-core independent config", "[riscv_trace]")
|
||||
{
|
||||
TEST_ASSERT_TRUE_MESSAGE(CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << ESP_RISCV_TRACE_CORE_0),
|
||||
"core 0 must be in the configured trace core mask");
|
||||
if ((CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << ESP_RISCV_TRACE_CORE_1)) == 0) {
|
||||
TEST_IGNORE_MESSAGE("per-core comparison needs core 1 in the trace core mask");
|
||||
}
|
||||
|
||||
uint8_t *buf0 = NULL, *buf1 = NULL;
|
||||
size_t cap0 = 0, cap1 = 0, head0 = 0, head1 = 0;
|
||||
TEST_ESP_OK(esp_riscv_trace_get_buffer(ESP_RISCV_TRACE_CORE_0, &buf0, &cap0, &head0));
|
||||
TEST_ESP_OK(esp_riscv_trace_get_buffer(ESP_RISCV_TRACE_CORE_1, &buf1, &cap1, &head1));
|
||||
|
||||
ESP_LOGI(TAG, "core 0 buffer %p cap %u, core 1 buffer %p cap %u",
|
||||
buf0, (unsigned)cap0, buf1, (unsigned)cap1);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT_MESSAGE(TRACE_CORE0_BUFFER_SIZE, cap0, "core 0 buffer size mismatch");
|
||||
TEST_ASSERT_EQUAL_UINT_MESSAGE(TRACE_CORE1_BUFFER_SIZE, cap1, "core 1 buffer size mismatch");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(cap0, cap1, "cores ended up with identical config");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(buf0, buf1, "cores share the same trace buffer");
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
#include "test_riscv_trace_common.h"
|
||||
|
||||
bool decode_sync_pc(const uint8_t *payload, size_t payload_len, uint32_t *pc)
|
||||
{
|
||||
if (payload_len < TRACE_SYNC_PAYLOAD_BYTES) {
|
||||
return false;
|
||||
}
|
||||
if ((payload[0] & 0x3) != 3 || ((payload[0] >> 2) & 0x3) != 0) {
|
||||
return false;
|
||||
}
|
||||
/* Address is 31 bits starting at payload bit 6; byte address is addr << 1. */
|
||||
uint64_t v = 0;
|
||||
for (int i = 0; i < TRACE_SYNC_PAYLOAD_BYTES; i++) {
|
||||
v |= (uint64_t)payload[i] << (8 * i);
|
||||
}
|
||||
*pc = (uint32_t)((v >> 6) & 0x7FFFFFFFu) << 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool buffer_has_wrapped(const uint8_t *buf, size_t capacity, size_t head)
|
||||
{
|
||||
for (size_t i = head; i < capacity; i++) {
|
||||
if (buf[i] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void validate_trace_status(esp_riscv_trace_core_t core)
|
||||
{
|
||||
esp_riscv_trace_status_t status = { 0 };
|
||||
TEST_ESP_OK(esp_riscv_trace_get_status(core, &status));
|
||||
TEST_ASSERT_TRUE_MESSAGE(status.fifo_empty, "trace FIFO is not empty after stop");
|
||||
TEST_ASSERT_FALSE_MESSAGE(status.fifo_overflowed, "trace FIFO overflowed");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(ESP_RISCV_TRACE_WORK_STATUS_LOST, status.work_status,
|
||||
"trace encoder entered lost state");
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_riscv_trace.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* RISC-V trace packet framing: 1-byte header + 2-byte index, then payload (1-10B). */
|
||||
#define TRACE_PACKET_HEADER_SIZE 1 /* length + format/reserved byte */
|
||||
#define TRACE_PACKET_INDEX_SIZE 2 /* monotonic packet index */
|
||||
#define TRACE_PACKET_PREFIX_SIZE (TRACE_PACKET_HEADER_SIZE + TRACE_PACKET_INDEX_SIZE) /* bytes before payload */
|
||||
#define TRACE_PACKET_MIN_LEN 4 /* prefix (3) + >=1 payload byte */
|
||||
#define TRACE_PACKET_MAX_LEN 13 /* prefix (3) + <=10 payload bytes */
|
||||
#define TRACE_SYNC_PAYLOAD_BYTES 5
|
||||
#define TRACE_ANCHOR_ZERO_RUN_BYTES 14 /* an anchor tag is >=14 zero bytes */
|
||||
#define TRACE_TEST_STOP_TIMEOUT_US 1000000
|
||||
|
||||
/* If payload is a Format 3 / Subformat 0 (sync) packet with at least TRACE_SYNC_PAYLOAD_BYTES
|
||||
* payload bytes, decode its full PC into *pc and return true. Otherwise return false.
|
||||
* payload_len is the number of valid payload bytes (packet length minus the 3-byte header). */
|
||||
bool decode_sync_pc(const uint8_t *payload, size_t payload_len, uint32_t *pc);
|
||||
|
||||
/* True if any byte after the write head is non-zero, i.e. a wrap overwrote that region (the
|
||||
* driver pre-zeros the buffer, so there is no hardware "wrapped" flag in loop mode). */
|
||||
bool buffer_has_wrapped(const uint8_t *buf, size_t capacity, size_t head);
|
||||
|
||||
/* Assert the encoder stopped cleanly: FIFO empty, no overflow, not in the lost state. */
|
||||
void validate_trace_status(esp_riscv_trace_core_t core);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
#include "unity.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
#include "test_riscv_trace_common.h"
|
||||
|
||||
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. */
|
||||
#define TRACE_FILTER_WORKLOAD_ITERATIONS 512
|
||||
|
||||
static volatile uint32_t s_filter_sink;
|
||||
static uintptr_t s_filtered_end;
|
||||
static uintptr_t s_noise_end;
|
||||
|
||||
static void __attribute__((noinline)) IRAM_ATTR filtered_work(void)
|
||||
{
|
||||
uint32_t acc = s_filter_sink;
|
||||
for (volatile int i = 0; i < TRACE_FILTER_WORKLOAD_ITERATIONS; i++) {
|
||||
if ((i & 3) == 0) {
|
||||
acc += (uint32_t)i;
|
||||
} else {
|
||||
acc ^= ((uint32_t)i << 1);
|
||||
}
|
||||
}
|
||||
s_filter_sink = acc;
|
||||
filtered_end:
|
||||
s_filtered_end = (uintptr_t) && filtered_end;
|
||||
}
|
||||
|
||||
static void __attribute__((noinline)) noise_work(void)
|
||||
{
|
||||
uint32_t acc = s_filter_sink;
|
||||
for (volatile int i = 0; i < TRACE_FILTER_WORKLOAD_ITERATIONS; i++) {
|
||||
if ((i & 3) == 0) {
|
||||
acc += (uint32_t)i + 0x55u;
|
||||
} else {
|
||||
acc ^= ((uint32_t)i << 1);
|
||||
}
|
||||
}
|
||||
s_filter_sink = acc;
|
||||
noise_end:
|
||||
s_noise_end = (uintptr_t) && noise_end;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
size_t packets; /* all framed packets */
|
||||
size_t total; /* sync packets */
|
||||
size_t in_filtered; /* sync PCs inside the filtered_work window */
|
||||
size_t in_noise; /* sync PCs inside the noise_work window */
|
||||
} filter_sync_counts_t;
|
||||
|
||||
static bool pc_in_window(uint32_t pc, uintptr_t start, uintptr_t end)
|
||||
{
|
||||
return pc >= start && pc <= end;
|
||||
}
|
||||
|
||||
/* Walk a non-wrapped snapshot [0, head) and classify each sync packet's PC by window. */
|
||||
static filter_sync_counts_t count_syncs_in_windows(const uint8_t *buf, size_t head)
|
||||
{
|
||||
filter_sync_counts_t c = { 0 };
|
||||
size_t off = 0;
|
||||
while (off < head) {
|
||||
if (buf[off] == 0) { /* skip anchor zero-runs between packets */
|
||||
while (off < head && buf[off] == 0) {
|
||||
off++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
uint8_t plen = buf[off] & 0x1F;
|
||||
if (plen < TRACE_PACKET_MIN_LEN || plen > TRACE_PACKET_MAX_LEN || off + plen > head) {
|
||||
break;
|
||||
}
|
||||
c.packets++;
|
||||
uint32_t pc = 0;
|
||||
if (decode_sync_pc(&buf[off + TRACE_PACKET_PREFIX_SIZE], plen - TRACE_PACKET_PREFIX_SIZE, &pc)) {
|
||||
c.total++;
|
||||
if (pc_in_window(pc, (uintptr_t)&filtered_work, s_filtered_end)) {
|
||||
c.in_filtered++;
|
||||
} else if (pc_in_window(pc, (uintptr_t)&noise_work, s_noise_end)) {
|
||||
c.in_noise++;
|
||||
}
|
||||
}
|
||||
off += plen;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static filter_sync_counts_t capture_and_count(esp_riscv_trace_core_t core)
|
||||
{
|
||||
TEST_ESP_OK(esp_riscv_trace_start(core));
|
||||
noise_work();
|
||||
filtered_work();
|
||||
noise_work();
|
||||
TEST_ESP_OK(esp_riscv_trace_stop(core, TRACE_TEST_STOP_TIMEOUT_US));
|
||||
validate_trace_status(core);
|
||||
|
||||
uint8_t *buf = NULL;
|
||||
size_t capacity = 0;
|
||||
size_t head = 0;
|
||||
TEST_ESP_OK(esp_riscv_trace_get_buffer(core, &buf, &capacity, &head));
|
||||
TEST_ASSERT_FALSE_MESSAGE(buffer_has_wrapped(buf, capacity, head),
|
||||
"filter capture wrapped. Reduce TRACE_FILTER_WORKLOAD_ITERATIONS.");
|
||||
return count_syncs_in_windows(buf, head);
|
||||
}
|
||||
|
||||
/* Verifies the address-window filter restricts tracing to a chosen PC range. Captures the same
|
||||
* workload twice. Filter off (baseline), then a window over filtered_work only. Checks the
|
||||
* filtered run produced fewer packets, with every sync inside the filtered window and none in the
|
||||
* noise window. */
|
||||
TEST_CASE("RISC-V trace address-window filter", "[riscv_trace]")
|
||||
{
|
||||
const esp_riscv_trace_core_t core = ESP_RISCV_TRACE_CORE_0;
|
||||
TEST_ASSERT_EQUAL_MESSAGE(ESP_RISCV_TRACE_CORE_0, xPortGetCoreID(), "test must run on core 0");
|
||||
|
||||
/* Prime: run each function once (not traced) to record its end-marker PC. */
|
||||
filtered_work();
|
||||
noise_work();
|
||||
TEST_ASSERT_GREATER_THAN_UINT(0, s_filtered_end - (uintptr_t)&filtered_work);
|
||||
TEST_ASSERT_GREATER_THAN_UINT(0, s_noise_end - (uintptr_t)&noise_work);
|
||||
|
||||
/* Baseline (filter off): all three calls traced. Gives the reference packet count that the
|
||||
* filtered run is compared against below. */
|
||||
esp_riscv_trace_filter_config_t filter_off = { .enable = false };
|
||||
TEST_ESP_OK(esp_riscv_trace_set_filter(core, &filter_off));
|
||||
filter_sync_counts_t base = capture_and_count(core);
|
||||
ESP_LOGI(TAG, "baseline: %u packets, %u sync (filtered %u, noise %u)",
|
||||
(unsigned)base.packets, (unsigned)base.total,
|
||||
(unsigned)base.in_filtered, (unsigned)base.in_noise);
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, base.packets, "baseline produced no trace");
|
||||
|
||||
/* Filtered: AND-window over filtered_work only (P: iaddr>=start, S: iaddr<=end). */
|
||||
esp_riscv_trace_filter_config_t filter_window = {
|
||||
.enable = true,
|
||||
.match_comparators = true,
|
||||
.primary = {
|
||||
.input = ESP_RISCV_TRACE_FILTER_INPUT_IADDR,
|
||||
.function = ESP_RISCV_TRACE_FILTER_COMPARATOR_GE,
|
||||
.match_value = (uint32_t)(uintptr_t) &filtered_work,
|
||||
},
|
||||
.secondary = {
|
||||
.input = ESP_RISCV_TRACE_FILTER_INPUT_IADDR,
|
||||
.function = ESP_RISCV_TRACE_FILTER_COMPARATOR_LE,
|
||||
.match_value = (uint32_t)s_filtered_end,
|
||||
},
|
||||
.mode = ESP_RISCV_TRACE_FILTER_MODE_AND,
|
||||
};
|
||||
TEST_ESP_OK(esp_riscv_trace_set_filter(core, &filter_window));
|
||||
filter_sync_counts_t filtered = capture_and_count(core);
|
||||
ESP_LOGI(TAG, "filtered: %u packets, %u sync (filtered %u, noise %u)",
|
||||
(unsigned)filtered.packets, (unsigned)filtered.total,
|
||||
(unsigned)filtered.in_filtered, (unsigned)filtered.in_noise);
|
||||
/* The filter dropped the noise calls, so the filtered capture has fewer packets than the baseline. */
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, filtered.packets, "filtered window produced no trace");
|
||||
TEST_ASSERT_LESS_THAN_UINT_MESSAGE(base.packets, filtered.packets,
|
||||
"filter did not reduce trace volume");
|
||||
/*
|
||||
Every sync is in the filtered window and none in the noise window.
|
||||
The workload guarantees >=1 sync in the window.
|
||||
*/
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, filtered.in_filtered, "filtered: no sync located in filtered window");
|
||||
TEST_ASSERT_EQUAL_UINT_MESSAGE(filtered.in_filtered, filtered.total,
|
||||
"filtered: a sync was located outside the filtered window");
|
||||
TEST_ASSERT_EQUAL_UINT_MESSAGE(0, filtered.in_noise, "filtered: a noise-window sync was observed");
|
||||
|
||||
/* Clear the filter so it does not affect other test cases or re-runs. */
|
||||
TEST_ESP_OK(esp_riscv_trace_set_filter(core, &filter_off));
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_idf.utils import soc_filtered_targets
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize('config', ['default'], indirect=True)
|
||||
@idf_parametrize('target', soc_filtered_targets('SOC_RISCV_TRACE_SUPPORTED == 1'), indirect=['target'])
|
||||
def test_riscv_trace(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize('config', ['psram'], indirect=True)
|
||||
@idf_parametrize(
|
||||
'target', soc_filtered_targets('SOC_RISCV_TRACE_SUPPORTED == 1 and SOC_SPIRAM_SUPPORTED == 1'), indirect=['target']
|
||||
)
|
||||
def test_riscv_trace_psram(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_ESP_RISCV_TRACE_BUFFER_IN_EXTERNAL=y
|
||||
# PSRAM is slower than internal RAM, so stall the CPU on FIFO-full to avoid losing trace coverage.
|
||||
CONFIG_ESP_RISCV_TRACE_STALL_CPU_ENABLE=y
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_ESP_RISCV_TRACE_ENABLE=y
|
||||
CONFIG_ESP_RISCV_TRACE_RESYNC_MODE_PACKET=y
|
||||
CONFIG_ESP_RISCV_TRACE_RESYNC_THRESHOLD=32
|
||||
CONFIG_ESP_RISCV_TRACE_BUFFER_SIZE=4096
|
||||
@@ -121,6 +121,9 @@ SECONDARY: 140: init_dbg_stubs in components/app_trace/debug_stubs.c on BIT(0)
|
||||
SECONDARY: 150: nvs_sec_provider_register_flash_enc_scheme in components/nvs_sec_provider/nvs_sec_provider.c on BIT(0)
|
||||
SECONDARY: 151: nvs_sec_provider_register_hmac_scheme in components/nvs_sec_provider/nvs_sec_provider.c on BIT(0)
|
||||
|
||||
# esp_riscv_trace doesn't have init dependencies
|
||||
SECONDARY: 160: esp_riscv_trace_early_init in components/esp_riscv_trace/src/esp_riscv_trace.c on ESP_SYSTEM_INIT_ALL_CORES
|
||||
|
||||
# the rest of the components which are initialized from startup_funcs.c
|
||||
# [refactor-todo]: move init calls into respective components
|
||||
SECONDARY: 201: init_pm in components/esp_system/startup_funcs.c on BIT(0)
|
||||
|
||||
@@ -56,6 +56,10 @@ if(NOT esp_tee_build AND NOT BOOTLOADER_BUILD)
|
||||
if(CONFIG_SOC_MODEM_CLOCK_IS_INDEPENDENT AND CONFIG_SOC_MODEM_CLOCK_SUPPORTED)
|
||||
list(APPEND srcs "${target}/modem_clock_hal.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_RISCV_TRACE_SUPPORTED)
|
||||
list(APPEND srcs "riscv_trace_hal.c")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(ldfragments linker.lf)
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "soc/trace_reg.h"
|
||||
#include "soc/trace_struct.h"
|
||||
#include "soc/hp_sys_clkrst_struct.h"
|
||||
#include "hal/assert.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @brief Return the register block for the given core. */
|
||||
static inline trace_dev_t *riscv_trace_ll_get_hw(int core)
|
||||
{
|
||||
HAL_ASSERT(core == 0 || core == 1);
|
||||
return core == 0 ? &TRACE0 : &TRACE1;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Clock and reset
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
/** @brief Enable or disable the common TRACE CPU and system clocks. */
|
||||
static inline void _riscv_trace_ll_enable_bus_clock(bool enable)
|
||||
{
|
||||
HP_SYS_CLKRST.soc_clk_ctrl0.reg_trace_cpu_clk_en = enable;
|
||||
HP_SYS_CLKRST.soc_clk_ctrl0.reg_trace_sys_clk_en = enable;
|
||||
}
|
||||
|
||||
#define riscv_trace_ll_enable_bus_clock(...) do { \
|
||||
(void)__DECLARE_RCC_ATOMIC_ENV; \
|
||||
_riscv_trace_ll_enable_bus_clock(__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/** @brief Assert and release the reset of the given encoder core. */
|
||||
static inline void _riscv_trace_ll_reset_register(int core)
|
||||
{
|
||||
if (core == 0) {
|
||||
HP_SYS_CLKRST.hp_rst_en0.reg_rst_en_coretrace0 = 1;
|
||||
HP_SYS_CLKRST.hp_rst_en0.reg_rst_en_coretrace0 = 0;
|
||||
} else {
|
||||
HP_SYS_CLKRST.hp_rst_en0.reg_rst_en_coretrace1 = 1;
|
||||
HP_SYS_CLKRST.hp_rst_en0.reg_rst_en_coretrace1 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#define riscv_trace_ll_reset_register(...) do { \
|
||||
(void)__DECLARE_RCC_ATOMIC_ENV; \
|
||||
_riscv_trace_ll_reset_register(__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/** @brief Enable the per-module register clock gate. */
|
||||
static inline void riscv_trace_ll_enable_module_clock(trace_dev_t *hw, bool enable)
|
||||
{
|
||||
hw->clock_gate.clk_en = enable;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Memory configuration
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline void riscv_trace_ll_set_mem_start_addr(trace_dev_t *hw, uint32_t addr)
|
||||
{
|
||||
hw->mem_start_addr.mem_start_addr = addr;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_mem_end_addr(trace_dev_t *hw, uint32_t addr)
|
||||
{
|
||||
hw->mem_end_addr.mem_end_addr = addr;
|
||||
}
|
||||
|
||||
static inline uint32_t riscv_trace_ll_get_mem_current_addr(trace_dev_t *hw)
|
||||
{
|
||||
return hw->mem_current_addr.mem_current_addr;
|
||||
}
|
||||
|
||||
/** @brief Reload the current address from the start address. */
|
||||
static inline void riscv_trace_ll_update_mem_current_addr(trace_dev_t *hw)
|
||||
{
|
||||
hw->mem_addr_update.mem_current_addr_update = 1;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Status
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline uint32_t riscv_trace_ll_get_fifo_status(trace_dev_t *hw)
|
||||
{
|
||||
return hw->fifo_status.val;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Trigger, loop mode and restart
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline void riscv_trace_ll_trigger_on(trace_dev_t *hw)
|
||||
{
|
||||
hw->trigger.trigger_on = 1;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_trigger_off(trace_dev_t *hw)
|
||||
{
|
||||
hw->trigger.trigger_off = 1;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_mem_loop(trace_dev_t *hw, bool loop)
|
||||
{
|
||||
hw->trigger.mem_loop = loop;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_restart_ena(trace_dev_t *hw, bool enable)
|
||||
{
|
||||
hw->trigger.restart_ena = enable;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Encoder options (config register)
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline void riscv_trace_ll_set_full_address(trace_dev_t *hw, bool full)
|
||||
{
|
||||
hw->config.full_address = full;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_stall_ena(trace_dev_t *hw, bool enable)
|
||||
{
|
||||
hw->config.stall_ena = enable;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_halt_ena(trace_dev_t *hw, bool enable)
|
||||
{
|
||||
hw->config.halt_ena = enable;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_reset_ena(trace_dev_t *hw, bool enable)
|
||||
{
|
||||
hw->config.reset_ena = enable;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_dm_trigger_ena(trace_dev_t *hw, bool enable)
|
||||
{
|
||||
hw->config.dm_trigger_ena = enable;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Resynchronization
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline void riscv_trace_ll_set_resync_mode(trace_dev_t *hw, uint32_t mode)
|
||||
{
|
||||
hw->resync_prolonged.resync_mode = mode;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_resync_threshold(trace_dev_t *hw, uint32_t threshold)
|
||||
{
|
||||
hw->resync_prolonged.resync_prolonged = threshold;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* AHB configuration
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline void riscv_trace_ll_set_ahb_burst(trace_dev_t *hw, uint32_t hburst)
|
||||
{
|
||||
hw->ahb_config.hburst = hburst;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_ahb_max_incr(trace_dev_t *hw, uint32_t max_incr)
|
||||
{
|
||||
hw->ahb_config.max_incr = max_incr;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Interrupts
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline void riscv_trace_ll_set_intr_ena(trace_dev_t *hw, uint32_t mask)
|
||||
{
|
||||
hw->intr_ena.val = mask;
|
||||
}
|
||||
|
||||
static inline uint32_t riscv_trace_ll_get_intr_raw(trace_dev_t *hw)
|
||||
{
|
||||
return hw->intr_raw.val;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_clear_intr(trace_dev_t *hw, uint32_t mask)
|
||||
{
|
||||
hw->intr_clr.val = mask;
|
||||
}
|
||||
|
||||
/*---------------------------------------------------------------------------
|
||||
* Filter unit
|
||||
*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline void riscv_trace_ll_set_filter_en(trace_dev_t *hw, bool enable)
|
||||
{
|
||||
hw->filter_control.filter_en = enable;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_filter_control(trace_dev_t *hw, bool match_comp,
|
||||
bool match_privilege, bool match_ecause,
|
||||
bool match_interrupt)
|
||||
{
|
||||
hw->filter_control.match_comp = match_comp;
|
||||
hw->filter_control.match_privilege = match_privilege;
|
||||
hw->filter_control.match_ecause = match_ecause;
|
||||
hw->filter_control.match_interrupt = match_interrupt;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_filter_match_control(trace_dev_t *hw, bool priv_choice,
|
||||
bool intr_value, uint32_t ecause_choice)
|
||||
{
|
||||
hw->filter_match_control.match_choice_privilege = priv_choice;
|
||||
hw->filter_match_control.match_value_interrupt = intr_value;
|
||||
hw->filter_match_control.match_choice_ecause = ecause_choice;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_p_comparator(trace_dev_t *hw, uint32_t input,
|
||||
uint32_t function, bool notify)
|
||||
{
|
||||
hw->filter_comparator_control.p_input = input;
|
||||
hw->filter_comparator_control.p_function = function;
|
||||
hw->filter_comparator_control.p_notify = notify;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_s_comparator(trace_dev_t *hw, uint32_t input,
|
||||
uint32_t function, bool notify)
|
||||
{
|
||||
hw->filter_comparator_control.s_input = input;
|
||||
hw->filter_comparator_control.s_function = function;
|
||||
hw->filter_comparator_control.s_notify = notify;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_match_mode(trace_dev_t *hw, uint32_t mode)
|
||||
{
|
||||
hw->filter_comparator_control.match_mode = mode;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_p_match_value(trace_dev_t *hw, uint32_t value)
|
||||
{
|
||||
hw->filter_p_comparator_match.p_match = value;
|
||||
}
|
||||
|
||||
static inline void riscv_trace_ll_set_s_match_value(trace_dev_t *hw, uint32_t value)
|
||||
{
|
||||
hw->filter_s_comparator_match.s_match = value;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "hal/riscv_trace_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @brief HAL context for one trace encoder core. */
|
||||
typedef struct {
|
||||
void *dev; /*!< Register block */
|
||||
int core_id; /*!< Core index */
|
||||
} riscv_trace_hal_context_t;
|
||||
|
||||
/** @brief Hardware-level capture configuration. */
|
||||
typedef struct {
|
||||
uint32_t mem_start_addr; /*!< Trace memory start address */
|
||||
uint32_t mem_end_addr; /*!< Trace memory end address */
|
||||
bool mem_loop; /*!< Loop/wrap memory mode (vs stop when full) */
|
||||
bool auto_restart; /*!< Auto-restart encoder after a FIFO overflow */
|
||||
bool full_address; /*!< Full address mode (vs delta) */
|
||||
bool stall_cpu; /*!< Stall CPU when the FIFO is almost full */
|
||||
bool halt_enable; /*!< Keep tracing through hart halt */
|
||||
bool reset_enable; /*!< Keep tracing through hart reset */
|
||||
bool debug_trigger_enable; /*!< Enable Debug Module trigger input */
|
||||
uint32_t resync_mode; /*!< Resynchronization mode */
|
||||
uint32_t resync_threshold; /*!< Resynchronization counter threshold */
|
||||
uint32_t ahb_burst; /*!< AHB burst type (hburst) */
|
||||
uint32_t ahb_max_incr; /*!< Max INCR burst beats */
|
||||
uint32_t intr_mask; /*!< Interrupt enable mask (riscv_trace_intr_t flags) */
|
||||
} riscv_trace_hal_config_t;
|
||||
|
||||
/** @brief One comparator of the filter unit (raw register values). */
|
||||
typedef struct {
|
||||
uint32_t input; /*!< Compared input: 0 = iaddr, 1 = tval */
|
||||
uint32_t function; /*!< Compare function: 0 ==, 1 !=, 2 <, 3 <=, 4 >, 5 >= */
|
||||
uint32_t match_value; /*!< 32-bit value compared against the input */
|
||||
bool notify; /*!< Emit a packet reporting the address that caused the match */
|
||||
} riscv_trace_hal_comparator_t;
|
||||
|
||||
/** @brief Filter (trace qualifier) configuration. */
|
||||
typedef struct {
|
||||
bool enable; /*!< Master filter enable (filter_en). false = trace everything */
|
||||
bool match_comparators; /*!< Gate matching on the comparators (match_comp) */
|
||||
bool match_privilege; /*!< Gate matching on the privilege level (match_privilege) */
|
||||
bool match_ecause; /*!< Match from an exception cause (match_ecause) */
|
||||
bool match_interrupt; /*!< Match from an interrupt trap (match_interrupt) */
|
||||
bool privilege_machine; /*!< match_choice_privilege: true = machine, false = user */
|
||||
bool interrupt_itype2; /*!< match_value_interrupt: true = itype 2, false = itype 1 */
|
||||
uint32_t ecause; /*!< match_choice_ecause (6-bit exception cause code) */
|
||||
riscv_trace_hal_comparator_t primary; /*!< Primary comparator */
|
||||
riscv_trace_hal_comparator_t secondary; /*!< Secondary comparator */
|
||||
uint32_t match_mode; /*!< 0 primary only, 1 P&&S, 2 !(P&&S), 3 range (start P..until S) */
|
||||
} riscv_trace_hal_filter_config_t;
|
||||
|
||||
void riscv_trace_hal_init(int core_id, const riscv_trace_hal_config_t *config, riscv_trace_hal_context_t *ctx);
|
||||
|
||||
/** @brief Start tracing */
|
||||
void riscv_trace_hal_start(riscv_trace_hal_context_t *ctx);
|
||||
|
||||
/** @brief Enable or disable auto-restart after FIFO overflow. */
|
||||
void riscv_trace_hal_set_auto_restart(riscv_trace_hal_context_t *ctx, bool enable);
|
||||
|
||||
/** @brief Set the interrupt enable mask (see riscv_trace_intr_t flags). 0 disables all. */
|
||||
void riscv_trace_hal_set_intr_enable(riscv_trace_hal_context_t *ctx, uint32_t mask);
|
||||
|
||||
/** @brief Stop tracing and wait for the FIFO to empty. */
|
||||
bool riscv_trace_hal_stop(riscv_trace_hal_context_t *ctx, uint32_t timeout_us);
|
||||
|
||||
/** @brief Deinitialize the trace HAL driver */
|
||||
void riscv_trace_hal_deinit(riscv_trace_hal_context_t *ctx);
|
||||
|
||||
/** @brief Read the raw TRACE_FIFO_STATUS register value. */
|
||||
uint32_t riscv_trace_hal_read_fifo_status(riscv_trace_hal_context_t *ctx);
|
||||
|
||||
/** @brief Read the current (next) write address from the encoder. */
|
||||
uint32_t riscv_trace_hal_get_current_addr(riscv_trace_hal_context_t *ctx);
|
||||
|
||||
/** @brief Read the raw TRACE_INTR_RAW register value. */
|
||||
uint32_t riscv_trace_hal_read_intr_raw(riscv_trace_hal_context_t *ctx);
|
||||
|
||||
/** @brief Check whether TRACE_FIFO_STATUS reports an empty FIFO. */
|
||||
bool riscv_trace_hal_fifo_is_empty(uint32_t fifo_status);
|
||||
|
||||
/** @brief Get the work-status field from TRACE_FIFO_STATUS. */
|
||||
riscv_trace_work_status_t riscv_trace_hal_get_work_status(uint32_t fifo_status);
|
||||
|
||||
/** @brief Check whether TRACE_INTR_RAW reports trace memory full. */
|
||||
bool riscv_trace_hal_memory_is_full(uint32_t intr_status);
|
||||
|
||||
/** @brief Check whether TRACE_INTR_RAW reports FIFO overflow. */
|
||||
bool riscv_trace_hal_fifo_is_overflowed(uint32_t intr_status);
|
||||
|
||||
/** @brief Reset the hardware write pointer and clear interrupts before a new capture. */
|
||||
void riscv_trace_hal_prepare_capture(riscv_trace_hal_context_t *ctx);
|
||||
|
||||
/** @brief Apply a filter (trace qualifier) configuration. Set before starting a capture. */
|
||||
void riscv_trace_hal_set_filter(riscv_trace_hal_context_t *ctx, const riscv_trace_hal_filter_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_bit_defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Trace encoder interrupt flags. */
|
||||
typedef enum {
|
||||
RISCV_TRACE_INTR_FIFO_OVERFLOW = BIT(0), /*!< Trace FIFO overflowed. Some packets were lost */
|
||||
RISCV_TRACE_INTR_MEM_FULL = BIT(1), /*!< Trace memory region became full. */
|
||||
} riscv_trace_intr_t;
|
||||
|
||||
/**
|
||||
* @brief Trace encoder work-status field.
|
||||
*
|
||||
* Some targets implement a narrower work_status field and only produce
|
||||
* the lower values (e.g., IDLE and WORKING). The enum is the union of
|
||||
* all observable values across targets.
|
||||
*/
|
||||
typedef enum {
|
||||
RISCV_TRACE_WORK_IDLE = 0, /*!< Encoder is not tracing */
|
||||
RISCV_TRACE_WORK_WORKING = 1, /*!< Encoder is actively tracing */
|
||||
RISCV_TRACE_WORK_WAIT = 2, /*!< Paused because the hart is halted or in reset */
|
||||
RISCV_TRACE_WORK_LOST = 3, /*!< Trace data was lost (e.g. FIFO overflow) */
|
||||
} riscv_trace_work_status_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* RISC-V trace encoder HAL: register-sequencing layer used by the
|
||||
* `esp_riscv_trace` driver. It sits on top of the target-specific LL
|
||||
* (hal/riscv_trace_ll.h), which performs the raw register accesses.
|
||||
*
|
||||
* These interfaces are internal to ESP-IDF and subject to change. The HAL
|
||||
* performs no locking: callers that use it directly are responsible for
|
||||
* serializing concurrent access to the same trace encoder instance.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/riscv_trace_hal.h"
|
||||
#include "hal/assert.h"
|
||||
#include "hal/riscv_trace_ll.h"
|
||||
|
||||
/* Keep the chip-independent HAL interrupt flags in sync with this target's register layout. */
|
||||
_Static_assert(RISCV_TRACE_INTR_FIFO_OVERFLOW == TRACE_FIFO_OVERFLOW_INTR_ENA,
|
||||
"RISCV_TRACE_INTR_FIFO_OVERFLOW does not match TRACE_FIFO_OVERFLOW_INTR_ENA");
|
||||
_Static_assert(RISCV_TRACE_INTR_MEM_FULL == TRACE_MEM_FULL_INTR_ENA,
|
||||
"RISCV_TRACE_INTR_MEM_FULL does not match TRACE_MEM_FULL_INTR_ENA");
|
||||
|
||||
static void riscv_trace_hal_enable_clock_and_reset(int core_id, trace_dev_t *dev)
|
||||
{
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
PERIPH_RCC_ATOMIC()
|
||||
#endif
|
||||
{
|
||||
riscv_trace_ll_enable_bus_clock(true);
|
||||
riscv_trace_ll_reset_register(core_id);
|
||||
}
|
||||
|
||||
riscv_trace_ll_enable_module_clock(dev, true);
|
||||
}
|
||||
|
||||
void riscv_trace_hal_init(int core_id, const riscv_trace_hal_config_t *config, riscv_trace_hal_context_t *ctx)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL && config != NULL);
|
||||
HAL_ASSERT(config->mem_end_addr > config->mem_start_addr);
|
||||
|
||||
ctx->dev = riscv_trace_ll_get_hw(core_id);
|
||||
ctx->core_id = core_id;
|
||||
|
||||
riscv_trace_hal_enable_clock_and_reset(core_id, ctx->dev);
|
||||
|
||||
/* Memory configuration */
|
||||
riscv_trace_ll_set_mem_start_addr(ctx->dev, config->mem_start_addr);
|
||||
riscv_trace_ll_set_mem_end_addr(ctx->dev, config->mem_end_addr);
|
||||
riscv_trace_ll_update_mem_current_addr(ctx->dev);
|
||||
|
||||
/* Trace configuration */
|
||||
riscv_trace_ll_set_mem_loop(ctx->dev, config->mem_loop);
|
||||
riscv_trace_ll_set_restart_ena(ctx->dev, config->auto_restart);
|
||||
riscv_trace_ll_set_resync_mode(ctx->dev, config->resync_mode);
|
||||
riscv_trace_ll_set_resync_threshold(ctx->dev, config->resync_threshold);
|
||||
#if SOC_RISCV_TRACE_HAS_CONFIG_REG
|
||||
riscv_trace_ll_set_full_address(ctx->dev, config->full_address);
|
||||
riscv_trace_ll_set_stall_ena(ctx->dev, config->stall_cpu);
|
||||
riscv_trace_ll_set_halt_ena(ctx->dev, config->halt_enable);
|
||||
riscv_trace_ll_set_reset_ena(ctx->dev, config->reset_enable);
|
||||
riscv_trace_ll_set_dm_trigger_ena(ctx->dev, config->debug_trigger_enable);
|
||||
#endif
|
||||
#if SOC_RISCV_TRACE_AHB_CONFIGURABLE
|
||||
riscv_trace_ll_set_ahb_burst(ctx->dev, config->ahb_burst);
|
||||
riscv_trace_ll_set_ahb_max_incr(ctx->dev, config->ahb_max_incr);
|
||||
#endif
|
||||
|
||||
/* Interrupts */
|
||||
riscv_trace_ll_set_intr_ena(ctx->dev, config->intr_mask);
|
||||
}
|
||||
|
||||
void riscv_trace_hal_start(riscv_trace_hal_context_t *ctx)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
riscv_trace_ll_trigger_on(ctx->dev);
|
||||
}
|
||||
|
||||
void riscv_trace_hal_set_auto_restart(riscv_trace_hal_context_t *ctx, bool enable)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
riscv_trace_ll_set_restart_ena(ctx->dev, enable);
|
||||
}
|
||||
|
||||
void riscv_trace_hal_set_intr_enable(riscv_trace_hal_context_t *ctx, uint32_t mask)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
riscv_trace_ll_set_intr_ena(ctx->dev, mask);
|
||||
}
|
||||
|
||||
bool riscv_trace_hal_stop(riscv_trace_hal_context_t *ctx, uint32_t timeout_us)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
|
||||
/* Clear auto-restart before the off-trigger so the encoder cannot
|
||||
* restart itself while the FIFO is emptying. */
|
||||
riscv_trace_hal_set_auto_restart(ctx, false);
|
||||
riscv_trace_ll_trigger_off(ctx->dev);
|
||||
|
||||
const uint32_t poll_step_us = 10;
|
||||
uint64_t waited_us = 0;
|
||||
while (!riscv_trace_hal_fifo_is_empty(riscv_trace_hal_read_fifo_status(ctx))) {
|
||||
if (waited_us >= timeout_us) {
|
||||
return false;
|
||||
}
|
||||
esp_rom_delay_us(poll_step_us);
|
||||
waited_us += poll_step_us;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void riscv_trace_hal_deinit(riscv_trace_hal_context_t *ctx)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
riscv_trace_ll_set_restart_ena(ctx->dev, false);
|
||||
riscv_trace_ll_trigger_off(ctx->dev);
|
||||
riscv_trace_ll_set_intr_ena(ctx->dev, 0);
|
||||
riscv_trace_ll_enable_module_clock(ctx->dev, false);
|
||||
}
|
||||
|
||||
uint32_t riscv_trace_hal_read_fifo_status(riscv_trace_hal_context_t *ctx)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
return riscv_trace_ll_get_fifo_status(ctx->dev);
|
||||
}
|
||||
|
||||
uint32_t riscv_trace_hal_get_current_addr(riscv_trace_hal_context_t *ctx)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
return riscv_trace_ll_get_mem_current_addr(ctx->dev);
|
||||
}
|
||||
|
||||
uint32_t riscv_trace_hal_read_intr_raw(riscv_trace_hal_context_t *ctx)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
return riscv_trace_ll_get_intr_raw(ctx->dev);
|
||||
}
|
||||
|
||||
bool riscv_trace_hal_fifo_is_empty(uint32_t fifo_status)
|
||||
{
|
||||
return (fifo_status & TRACE_FIFO_EMPTY_M) != 0;
|
||||
}
|
||||
|
||||
riscv_trace_work_status_t riscv_trace_hal_get_work_status(uint32_t fifo_status)
|
||||
{
|
||||
return (riscv_trace_work_status_t)((fifo_status & TRACE_WORK_STATUS_M) >> TRACE_WORK_STATUS_S);
|
||||
}
|
||||
|
||||
bool riscv_trace_hal_memory_is_full(uint32_t intr_status)
|
||||
{
|
||||
return (intr_status & TRACE_MEM_FULL_INTR_RAW_M) != 0;
|
||||
}
|
||||
|
||||
bool riscv_trace_hal_fifo_is_overflowed(uint32_t intr_status)
|
||||
{
|
||||
return (intr_status & TRACE_FIFO_OVERFLOW_INTR_RAW_M) != 0;
|
||||
}
|
||||
|
||||
void riscv_trace_hal_prepare_capture(riscv_trace_hal_context_t *ctx)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL);
|
||||
riscv_trace_ll_update_mem_current_addr(ctx->dev);
|
||||
riscv_trace_ll_clear_intr(ctx->dev, TRACE_FIFO_OVERFLOW_INTR_RAW | TRACE_MEM_FULL_INTR_RAW);
|
||||
}
|
||||
|
||||
#if SOC_RISCV_TRACE_FILTER_SUPPORTED
|
||||
void riscv_trace_hal_set_filter(riscv_trace_hal_context_t *ctx, const riscv_trace_hal_filter_config_t *config)
|
||||
{
|
||||
HAL_ASSERT(ctx != NULL && config != NULL);
|
||||
|
||||
riscv_trace_ll_set_filter_control(ctx->dev, config->match_comparators, config->match_privilege,
|
||||
config->match_ecause, config->match_interrupt);
|
||||
riscv_trace_ll_set_filter_match_control(ctx->dev, config->privilege_machine,
|
||||
config->interrupt_itype2, config->ecause);
|
||||
|
||||
riscv_trace_ll_set_p_comparator(ctx->dev, config->primary.input, config->primary.function,
|
||||
config->primary.notify);
|
||||
riscv_trace_ll_set_p_match_value(ctx->dev, config->primary.match_value);
|
||||
riscv_trace_ll_set_s_comparator(ctx->dev, config->secondary.input, config->secondary.function,
|
||||
config->secondary.notify);
|
||||
riscv_trace_ll_set_s_match_value(ctx->dev, config->secondary.match_value);
|
||||
riscv_trace_ll_set_match_mode(ctx->dev, config->match_mode);
|
||||
|
||||
/* Enable last, once all matching parameters are in place. */
|
||||
riscv_trace_ll_set_filter_en(ctx->dev, config->enable);
|
||||
}
|
||||
#else
|
||||
void riscv_trace_hal_set_filter(riscv_trace_hal_context_t *ctx, const riscv_trace_hal_filter_config_t *config)
|
||||
{
|
||||
(void)ctx;
|
||||
(void)config;
|
||||
}
|
||||
#endif // SOC_RISCV_TRACE_FILTER_SUPPORTED
|
||||
@@ -395,6 +395,10 @@ config SOC_SPI_EXTERNAL_NOR_FLASH_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_RISCV_TRACE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_XTAL_SUPPORT_40M
|
||||
bool
|
||||
default y
|
||||
@@ -1930,3 +1934,15 @@ config SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE
|
||||
config SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_RISCV_TRACE_HAS_CONFIG_REG
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_RISCV_TRACE_AHB_CONFIGURABLE
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_RISCV_TRACE_FILTER_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
@@ -121,6 +121,7 @@
|
||||
#define SOC_SIMD_INSTRUCTION_SUPPORTED 1
|
||||
#define SOC_I3C_MASTER_SUPPORTED 1
|
||||
#define SOC_SPI_EXTERNAL_NOR_FLASH_SUPPORTED 1
|
||||
#define SOC_RISCV_TRACE_SUPPORTED 1
|
||||
|
||||
/*-------------------------- XTAL CAPS ---------------------------------------*/
|
||||
#define SOC_XTAL_SUPPORT_40M 1
|
||||
@@ -737,3 +738,8 @@
|
||||
#define SOC_LP_CORE_SUPPORT_I2C (1) /*!< LP Core supports I2C */
|
||||
#define SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE (1) /*!< LP core requests sleep, PMU clears both HP and LP wakeup causes */
|
||||
#define SOC_LP_CORE_LP_UART_WAKEUP_KEEP_TRIGGERED (1) /*!< LP UART wakeup source is kept triggered */
|
||||
|
||||
/*-------------------------- RISC-V TRACE CAPS ------------------------------*/
|
||||
#define SOC_RISCV_TRACE_HAS_CONFIG_REG (1) /*!< Has the encoder config register */
|
||||
#define SOC_RISCV_TRACE_AHB_CONFIGURABLE (1) /*!< AHB write master is configurable */
|
||||
#define SOC_RISCV_TRACE_FILTER_SUPPORTED (1) /*!< Has the filter unit */
|
||||
|
||||
Reference in New Issue
Block a user