From 2c11810ad963c628b4b2a9c93daca79ec58894ac Mon Sep 17 00:00:00 2001 From: Erhan Kurubas Date: Fri, 12 Jun 2026 16:33:59 +0200 Subject: [PATCH] feat(esp_riscv_trace): add riscv trace encoder driver with hal/ll layer --- .gitlab/CODEOWNERS | 1 + components/esp_riscv_trace/CMakeLists.txt | 20 + components/esp_riscv_trace/Kconfig | 223 ++++++++++ components/esp_riscv_trace/README.md | 52 +++ .../esp_riscv_trace/include/esp_riscv_trace.h | 261 +++++++++++ .../esp_riscv_trace/src/esp_riscv_trace.c | 404 ++++++++++++++++++ .../src/esp_riscv_trace_priv.h | 42 ++ .../test_apps/.build-test-rules.yml | 8 + .../test_apps/basic/CMakeLists.txt | 7 + .../esp_riscv_trace/test_apps/basic/README.md | 48 +++ .../test_apps/basic/main/CMakeLists.txt | 12 + .../test_apps/basic/main/test_app_main.c | 57 +++ .../basic/main/test_riscv_trace_basic.c | 283 ++++++++++++ .../basic/main/test_riscv_trace_common.c | 46 ++ .../basic/main/test_riscv_trace_common.h | 42 ++ .../basic/main/test_riscv_trace_filter.c | 179 ++++++++ .../test_apps/basic/pytest_riscv_trace.py | 22 + .../test_apps/basic/sdkconfig.ci.default | 0 .../test_apps/basic/sdkconfig.ci.psram | 4 + .../test_apps/basic/sdkconfig.defaults | 4 + components/esp_system/system_init_fn.txt | 3 + components/hal/CMakeLists.txt | 4 + .../hal/esp32p4/include/hal/riscv_trace_ll.h | 261 +++++++++++ components/hal/include/hal/riscv_trace_hal.h | 110 +++++ .../hal/include/hal/riscv_trace_types.h | 40 ++ components/hal/riscv_trace_hal.c | 203 +++++++++ .../esp32p4/include/soc/Kconfig.soc_caps.in | 16 + components/soc/esp32p4/include/soc/soc_caps.h | 6 + 28 files changed, 2358 insertions(+) create mode 100644 components/esp_riscv_trace/CMakeLists.txt create mode 100644 components/esp_riscv_trace/Kconfig create mode 100644 components/esp_riscv_trace/README.md create mode 100644 components/esp_riscv_trace/include/esp_riscv_trace.h create mode 100644 components/esp_riscv_trace/src/esp_riscv_trace.c create mode 100644 components/esp_riscv_trace/src/esp_riscv_trace_priv.h create mode 100644 components/esp_riscv_trace/test_apps/.build-test-rules.yml create mode 100644 components/esp_riscv_trace/test_apps/basic/CMakeLists.txt create mode 100644 components/esp_riscv_trace/test_apps/basic/README.md create mode 100644 components/esp_riscv_trace/test_apps/basic/main/CMakeLists.txt create mode 100644 components/esp_riscv_trace/test_apps/basic/main/test_app_main.c create mode 100644 components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_basic.c create mode 100644 components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_common.c create mode 100644 components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_common.h create mode 100644 components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_filter.c create mode 100644 components/esp_riscv_trace/test_apps/basic/pytest_riscv_trace.py create mode 100644 components/esp_riscv_trace/test_apps/basic/sdkconfig.ci.default create mode 100644 components/esp_riscv_trace/test_apps/basic/sdkconfig.ci.psram create mode 100644 components/esp_riscv_trace/test_apps/basic/sdkconfig.defaults create mode 100644 components/hal/esp32p4/include/hal/riscv_trace_ll.h create mode 100644 components/hal/include/hal/riscv_trace_hal.h create mode 100644 components/hal/include/hal/riscv_trace_types.h create mode 100644 components/hal/riscv_trace_hal.c diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 623605c8bf3..4fba07a6647 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -122,6 +122,7 @@ /components/esp_psram/ @esp-idf-codeowners/peripherals /components/esp_psram/system_layer/ @esp-idf-codeowners/peripherals @esp-idf-codeowners/system /components/esp_ringbuf/ @esp-idf-codeowners/system +/components/esp_riscv_trace/ @esp-idf-codeowners/debugging /components/esp_rom/ @esp-idf-codeowners/system @esp-idf-codeowners/bluetooth @esp-idf-codeowners/wifi /components/esp_security/ @esp-idf-codeowners/security /components/esp_stdio/ @esp-idf-codeowners/storage @esp-idf-codeowners/system diff --git a/components/esp_riscv_trace/CMakeLists.txt b/components/esp_riscv_trace/CMakeLists.txt new file mode 100644 index 00000000000..bad315aeea5 --- /dev/null +++ b/components/esp_riscv_trace/CMakeLists.txt @@ -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}" + ) diff --git a/components/esp_riscv_trace/Kconfig b/components/esp_riscv_trace/Kconfig new file mode 100644 index 00000000000..b608e1e4a83 --- /dev/null +++ b/components/esp_riscv_trace/Kconfig @@ -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 diff --git a/components/esp_riscv_trace/README.md b/components/esp_riscv_trace/README.md new file mode 100644 index 00000000000..e931f58fb14 --- /dev/null +++ b/components/esp_riscv_trace/README.md @@ -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. diff --git a/components/esp_riscv_trace/include/esp_riscv_trace.h b/components/esp_riscv_trace/include/esp_riscv_trace.h new file mode 100644 index 00000000000..baa6e3e0eee --- /dev/null +++ b/components/esp_riscv_trace/include/esp_riscv_trace.h @@ -0,0 +1,261 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @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 diff --git a/components/esp_riscv_trace/src/esp_riscv_trace.c b/components/esp_riscv_trace/src/esp_riscv_trace.c new file mode 100644 index 00000000000..5b9448f32cd --- /dev/null +++ b/components/esp_riscv_trace/src/esp_riscv_trace.c @@ -0,0 +1,404 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#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; +} diff --git a/components/esp_riscv_trace/src/esp_riscv_trace_priv.h b/components/esp_riscv_trace/src/esp_riscv_trace_priv.h new file mode 100644 index 00000000000..ca39dd01c5a --- /dev/null +++ b/components/esp_riscv_trace/src/esp_riscv_trace_priv.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include +#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 diff --git a/components/esp_riscv_trace/test_apps/.build-test-rules.yml b/components/esp_riscv_trace/test_apps/.build-test-rules.yml new file mode 100644 index 00000000000..8f9e2890723 --- /dev/null +++ b/components/esp_riscv_trace/test_apps/.build-test-rules.yml @@ -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 diff --git a/components/esp_riscv_trace/test_apps/basic/CMakeLists.txt b/components/esp_riscv_trace/test_apps/basic/CMakeLists.txt new file mode 100644 index 00000000000..6cec0b2f27d --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/CMakeLists.txt @@ -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) diff --git a/components/esp_riscv_trace/test_apps/basic/README.md b/components/esp_riscv_trace/test_apps/basic/README.md new file mode 100644 index 00000000000..a8fdf5cd5dd --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/README.md @@ -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. diff --git a/components/esp_riscv_trace/test_apps/basic/main/CMakeLists.txt b/components/esp_riscv_trace/test_apps/basic/main/CMakeLists.txt new file mode 100644 index 00000000000..37f0f6f542b --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/main/CMakeLists.txt @@ -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) diff --git a/components/esp_riscv_trace/test_apps/basic/main/test_app_main.c b/components/esp_riscv_trace/test_apps/basic/main/test_app_main.c new file mode 100644 index 00000000000..78e30fe1b81 --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/main/test_app_main.c @@ -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(); +} diff --git a/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_basic.c b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_basic.c new file mode 100644 index 00000000000..cb58087a478 --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_basic.c @@ -0,0 +1,283 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#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"); +} diff --git a/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_common.c b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_common.c new file mode 100644 index 00000000000..0ebb1574f9d --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_common.c @@ -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"); +} diff --git a/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_common.h b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_common.h new file mode 100644 index 00000000000..8707c1ae3a8 --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_common.h @@ -0,0 +1,42 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#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 diff --git a/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_filter.c b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_filter.c new file mode 100644 index 00000000000..79c2da1c7f5 --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/main/test_riscv_trace_filter.c @@ -0,0 +1,179 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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)); +} diff --git a/components/esp_riscv_trace/test_apps/basic/pytest_riscv_trace.py b/components/esp_riscv_trace/test_apps/basic/pytest_riscv_trace.py new file mode 100644 index 00000000000..578e1d6e0dc --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/pytest_riscv_trace.py @@ -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() diff --git a/components/esp_riscv_trace/test_apps/basic/sdkconfig.ci.default b/components/esp_riscv_trace/test_apps/basic/sdkconfig.ci.default new file mode 100644 index 00000000000..e69de29bb2d diff --git a/components/esp_riscv_trace/test_apps/basic/sdkconfig.ci.psram b/components/esp_riscv_trace/test_apps/basic/sdkconfig.ci.psram new file mode 100644 index 00000000000..c13aacb1afc --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/sdkconfig.ci.psram @@ -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 diff --git a/components/esp_riscv_trace/test_apps/basic/sdkconfig.defaults b/components/esp_riscv_trace/test_apps/basic/sdkconfig.defaults new file mode 100644 index 00000000000..8b345df6d85 --- /dev/null +++ b/components/esp_riscv_trace/test_apps/basic/sdkconfig.defaults @@ -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 diff --git a/components/esp_system/system_init_fn.txt b/components/esp_system/system_init_fn.txt index 4812dbd8566..af5c98acb8d 100644 --- a/components/esp_system/system_init_fn.txt +++ b/components/esp_system/system_init_fn.txt @@ -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) diff --git a/components/hal/CMakeLists.txt b/components/hal/CMakeLists.txt index b7bd8ad5871..a4fac2dddd5 100644 --- a/components/hal/CMakeLists.txt +++ b/components/hal/CMakeLists.txt @@ -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) diff --git a/components/hal/esp32p4/include/hal/riscv_trace_ll.h b/components/hal/esp32p4/include/hal/riscv_trace_ll.h new file mode 100644 index 00000000000..71b85feb29d --- /dev/null +++ b/components/hal/esp32p4/include/hal/riscv_trace_ll.h @@ -0,0 +1,261 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#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 diff --git a/components/hal/include/hal/riscv_trace_hal.h b/components/hal/include/hal/riscv_trace_hal.h new file mode 100644 index 00000000000..a2208f244c9 --- /dev/null +++ b/components/hal/include/hal/riscv_trace_hal.h @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 diff --git a/components/hal/include/hal/riscv_trace_types.h b/components/hal/include/hal/riscv_trace_types.h new file mode 100644 index 00000000000..b3676f20d75 --- /dev/null +++ b/components/hal/include/hal/riscv_trace_types.h @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#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 diff --git a/components/hal/riscv_trace_hal.c b/components/hal/riscv_trace_hal.c new file mode 100644 index 00000000000..467fb6e5909 --- /dev/null +++ b/components/hal/riscv_trace_hal.c @@ -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 +#include +#include + +#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 diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index a79897368c0..1e9882f2469 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -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 diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index d291d16232b..26dcea77bc1 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -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 */