Merge branch 'esp_riscv_trace' into 'master'

RISC-V Trace Encoder

See merge request espressif/esp-idf!49821
This commit is contained in:
Erhan Kurubas
2026-07-20 09:15:17 +02:00
28 changed files with 2358 additions and 0 deletions
+20
View File
@@ -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}"
)
+223
View File
@@ -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
+52
View File
@@ -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
+3
View File
@@ -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)
+4
View File
@@ -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
+203
View File
@@ -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 */