mirror of
https://github.com/espressif/esp-idf.git
synced 2026-04-27 19:13:21 +00:00
test(esp_hw_support): add esp_cpu api test app
Add a dedicated esp_cpu test app that covers the supported helper APIs, interrupt helpers, debug helper validation, and destructive trigger cases. Made-with: Cursor
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
|
||||
components/esp_hw_support/test_apps/cpu:
|
||||
components/esp_hw_support/test_apps/esp_hw_support_unity_tests:
|
||||
|
||||
components/esp_hw_support/test_apps/host_test_linux:
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
set(COMPONENTS main)
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
|
||||
|
||||
list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
set(COMPONENTS main)
|
||||
|
||||
project(cpu)
|
||||
@@ -0,0 +1,12 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
|
||||
|
||||
This test app collects `esp_cpu.h` coverage in one place.
|
||||
|
||||
It currently includes:
|
||||
|
||||
- safe Unity tests for basic CPU helpers and interrupt helpers
|
||||
- a multi-stage reset test for `esp_cpu_reset()`
|
||||
- a thread-pointer roundtrip test and per-core `esp_cpu_get_core_id()` coverage
|
||||
- non-destructive sanity checks for breakpoint and watchpoint setup APIs
|
||||
- non-recoverable breakpoint and watchpoint trigger tests, plus a multi-core stall test where supported
|
||||
@@ -0,0 +1,14 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_cpu_basic.c"
|
||||
"test_cpu_interrupts.c"
|
||||
"test_cpu_reset.c"
|
||||
"test_cpu_watchpoint.c"
|
||||
"test_cpu_breakpoint.c"
|
||||
"test_cpu_non_recoverable.c"
|
||||
"test_cpu_stall.c")
|
||||
|
||||
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
|
||||
# the component can be registered as WHOLE_ARCHIVE
|
||||
idf_component_register(SRCS ${srcs}
|
||||
REQUIRES unity test_utils esp_hw_support esp_system
|
||||
WHOLE_ARCHIVE)
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "cpu_test_app";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Running esp_cpu test app");
|
||||
unity_run_menu();
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_cpu.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
static intptr_t __attribute__((noinline)) get_call_addr_delta(void)
|
||||
{
|
||||
intptr_t return_addr = (intptr_t)__builtin_return_address(0);
|
||||
intptr_t call_addr = esp_cpu_get_call_addr(return_addr);
|
||||
return return_addr - call_addr;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
TaskHandle_t waiter;
|
||||
int observed_core_id;
|
||||
} core_id_task_ctx_t;
|
||||
|
||||
static void core_id_task(void *arg)
|
||||
{
|
||||
core_id_task_ctx_t *ctx = (core_id_task_ctx_t *)arg;
|
||||
|
||||
ctx->observed_core_id = esp_cpu_get_core_id();
|
||||
xTaskNotifyGive(ctx->waiter);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: current core id matches pinned task core", "[cpu][cpu_basic]")
|
||||
{
|
||||
const TaskHandle_t waiter = xTaskGetCurrentTaskHandle();
|
||||
core_id_task_ctx_t task_ctx[SOC_CPU_CORES_NUM] = {0};
|
||||
|
||||
for (int core_id = 0; core_id < SOC_CPU_CORES_NUM; ++core_id) {
|
||||
task_ctx[core_id].waiter = waiter;
|
||||
task_ctx[core_id].observed_core_id = -1;
|
||||
TEST_ASSERT_EQUAL(pdPASS, xTaskCreatePinnedToCore(core_id_task,
|
||||
"cpu_core_id",
|
||||
2048,
|
||||
&task_ctx[core_id],
|
||||
UNITY_FREERTOS_PRIORITY + 1,
|
||||
NULL,
|
||||
core_id));
|
||||
}
|
||||
|
||||
for (int core_id = 0; core_id < SOC_CPU_CORES_NUM; ++core_id) {
|
||||
/* Use pdFALSE (counting mode) so accumulated notifications don't cause
|
||||
* a false failure if both tasks complete before the first wait. */
|
||||
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
|
||||
}
|
||||
|
||||
for (int core_id = 0; core_id < SOC_CPU_CORES_NUM; ++core_id) {
|
||||
TEST_ASSERT_EQUAL(core_id, task_ctx[core_id].observed_core_id);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: stack pointer is near current stack frame", "[cpu][cpu_basic]")
|
||||
{
|
||||
uint32_t local_marker = 0;
|
||||
uintptr_t sp = (uintptr_t)esp_cpu_get_sp();
|
||||
uintptr_t local = (uintptr_t)&local_marker;
|
||||
uintptr_t delta = (sp > local) ? (sp - local) : (local - sp);
|
||||
|
||||
TEST_ASSERT_TRUE(delta < 4096);
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: cycle count increases", "[cpu][cpu_basic]")
|
||||
{
|
||||
esp_cpu_cycle_count_t start = esp_cpu_get_cycle_count();
|
||||
for (volatile int i = 0; i < 256; i++) {
|
||||
__asm__ __volatile__("nop");
|
||||
}
|
||||
esp_cpu_cycle_count_t end = esp_cpu_get_cycle_count();
|
||||
|
||||
TEST_ASSERT_TRUE((end - start) > 0);
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: current privilege level is sane", "[cpu][cpu_basic]")
|
||||
{
|
||||
int privilege = esp_cpu_get_curr_privilege_level();
|
||||
|
||||
#if __XTENSA__
|
||||
TEST_ASSERT_EQUAL(-1, privilege);
|
||||
#else
|
||||
TEST_ASSERT_TRUE(privilege >= 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: pc to addr conversion matches architecture", "[cpu][cpu_basic]")
|
||||
{
|
||||
uintptr_t addr = (uintptr_t)esp_cpu_pc_to_addr(0x00001234U);
|
||||
|
||||
#if __XTENSA__
|
||||
TEST_ASSERT_EQUAL_HEX32(0x40001234U, addr);
|
||||
#else
|
||||
TEST_ASSERT_EQUAL_HEX32(0x00001234U, addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: call addr delta matches architecture", "[cpu][cpu_basic]")
|
||||
{
|
||||
#if __XTENSA__
|
||||
TEST_ASSERT_EQUAL(3, get_call_addr_delta());
|
||||
#else
|
||||
TEST_ASSERT_EQUAL(4, get_call_addr_delta());
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: compare and set succeeds on matching value", "[cpu][cpu_basic]")
|
||||
{
|
||||
volatile uint32_t value = 1;
|
||||
|
||||
TEST_ASSERT_TRUE(esp_cpu_compare_and_set(&value, 1, 2));
|
||||
TEST_ASSERT_EQUAL_UINT32(2, value);
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: compare and set fails on mismatched value", "[cpu][cpu_basic]")
|
||||
{
|
||||
volatile uint32_t value = 2;
|
||||
|
||||
TEST_ASSERT_FALSE(esp_cpu_compare_and_set(&value, 1, 3));
|
||||
TEST_ASSERT_EQUAL_UINT32(2, value);
|
||||
}
|
||||
|
||||
TEST_CASE("CPU basic: thread pointer can be set and restored", "[cpu][cpu_basic]")
|
||||
{
|
||||
uintptr_t marker_words[4] = {0};
|
||||
void *original = esp_cpu_get_threadptr();
|
||||
void *readback;
|
||||
void *restored;
|
||||
|
||||
esp_cpu_set_threadptr((void *)marker_words);
|
||||
readback = esp_cpu_get_threadptr();
|
||||
esp_cpu_set_threadptr(original);
|
||||
restored = esp_cpu_get_threadptr();
|
||||
|
||||
TEST_ASSERT_EQUAL_PTR(marker_words, readback);
|
||||
TEST_ASSERT_EQUAL_PTR(original, restored);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_cpu.h"
|
||||
|
||||
#if SOC_CPU_BREAKPOINTS_NUM > 0
|
||||
static void __attribute__((noinline)) breakpoint_target_function(void)
|
||||
{
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
}
|
||||
|
||||
TEST_CASE("CPU breakpoint: all breakpoints can be set and cleared", "[cpu][cpu_breakpoint]")
|
||||
{
|
||||
for (int bp_num = 0; bp_num < SOC_CPU_BREAKPOINTS_NUM; ++bp_num) {
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_cpu_set_breakpoint(bp_num, (const void *)breakpoint_target_function));
|
||||
}
|
||||
|
||||
for (int bp_num = 0; bp_num < SOC_CPU_BREAKPOINTS_NUM; ++bp_num) {
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_cpu_clear_breakpoint(bp_num));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CPU breakpoint: invalid index is rejected", "[cpu][cpu_breakpoint]")
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_set_breakpoint(-1, (const void *)breakpoint_target_function));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_clear_breakpoint(-1));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_set_breakpoint(SOC_CPU_BREAKPOINTS_NUM, (const void *)breakpoint_target_function));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_clear_breakpoint(SOC_CPU_BREAKPOINTS_NUM));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_bit_defs.h"
|
||||
#include "esp_cpu.h"
|
||||
|
||||
static void dummy_handler(void *arg)
|
||||
{
|
||||
volatile uint32_t *counter = (volatile uint32_t *)arg;
|
||||
if (counter != NULL) {
|
||||
(*counter)++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_valid_desc_type(esp_cpu_intr_type_t type)
|
||||
{
|
||||
return type == ESP_CPU_INTR_TYPE_LEVEL
|
||||
|| type == ESP_CPU_INTR_TYPE_EDGE
|
||||
|| type == ESP_CPU_INTR_TYPE_NA;
|
||||
}
|
||||
|
||||
static int find_test_interrupt(void)
|
||||
{
|
||||
const int core_id = esp_cpu_get_core_id();
|
||||
|
||||
for (int intr_num = 0; intr_num < SOC_CPU_INTR_NUM; intr_num++) {
|
||||
esp_cpu_intr_desc_t intr_desc;
|
||||
esp_cpu_intr_get_desc(core_id, intr_num, &intr_desc);
|
||||
|
||||
if (intr_desc.flags & (ESP_CPU_INTR_DESC_FLAG_SPECIAL | ESP_CPU_INTR_DESC_FLAG_RESVD)) {
|
||||
continue;
|
||||
}
|
||||
if (esp_cpu_intr_has_handler(intr_num)) {
|
||||
continue;
|
||||
}
|
||||
return intr_num;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
TEST_CASE("CPU intr: descriptor is valid for all interrupt lines", "[cpu][cpu_intr]")
|
||||
{
|
||||
const int core_id = esp_cpu_get_core_id();
|
||||
const uint32_t valid_flags = ESP_CPU_INTR_DESC_FLAG_SPECIAL | ESP_CPU_INTR_DESC_FLAG_RESVD;
|
||||
|
||||
for (int intr_num = 0; intr_num < SOC_CPU_INTR_NUM; intr_num++) {
|
||||
esp_cpu_intr_desc_t intr_desc;
|
||||
esp_cpu_intr_get_desc(core_id, intr_num, &intr_desc);
|
||||
|
||||
TEST_ASSERT_TRUE(is_valid_desc_type(intr_desc.type));
|
||||
TEST_ASSERT_TRUE(intr_desc.priority >= -1);
|
||||
TEST_ASSERT_EQUAL_HEX32(0, intr_desc.flags & ~valid_flags);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CPU intr: handler and arg can be set and cleared", "[cpu][cpu_intr]")
|
||||
{
|
||||
int intr_num = find_test_interrupt();
|
||||
if (intr_num < 0) {
|
||||
TEST_IGNORE_MESSAGE("No free interrupt line was found for the handler test");
|
||||
}
|
||||
|
||||
volatile uint32_t counter = 0;
|
||||
|
||||
esp_cpu_intr_set_handler(intr_num, dummy_handler, (void *)&counter);
|
||||
TEST_ASSERT_TRUE(esp_cpu_intr_has_handler(intr_num));
|
||||
#if !__XTENSA__
|
||||
TEST_ASSERT_EQUAL_PTR(&counter, esp_cpu_intr_get_handler_arg(intr_num));
|
||||
#endif
|
||||
|
||||
esp_cpu_intr_set_handler(intr_num, NULL, NULL);
|
||||
TEST_ASSERT_FALSE(esp_cpu_intr_has_handler(intr_num));
|
||||
}
|
||||
|
||||
TEST_CASE("CPU intr: enabled mask can be toggled for a free line", "[cpu][cpu_intr]")
|
||||
{
|
||||
int intr_num = find_test_interrupt();
|
||||
if (intr_num < 0) {
|
||||
TEST_IGNORE_MESSAGE("No free interrupt line was found for the enabled mask test");
|
||||
}
|
||||
|
||||
const uint32_t bit = BIT(intr_num);
|
||||
const uint32_t old_mask = esp_cpu_intr_get_enabled_mask();
|
||||
|
||||
esp_cpu_intr_disable(bit);
|
||||
TEST_ASSERT_EQUAL_HEX32(0, esp_cpu_intr_get_enabled_mask() & bit);
|
||||
|
||||
esp_cpu_intr_enable(bit);
|
||||
TEST_ASSERT_NOT_EQUAL(0, esp_cpu_intr_get_enabled_mask() & bit);
|
||||
|
||||
if ((old_mask & bit) == 0) {
|
||||
esp_cpu_intr_disable(bit);
|
||||
} else {
|
||||
esp_cpu_intr_enable(bit);
|
||||
}
|
||||
}
|
||||
|
||||
#if SOC_CPU_HAS_FLEXIBLE_INTC
|
||||
TEST_CASE("CPU intr: type can be changed on a free line", "[cpu][cpu_intr]")
|
||||
{
|
||||
int intr_num = find_test_interrupt();
|
||||
if (intr_num < 0) {
|
||||
TEST_IGNORE_MESSAGE("No free interrupt line was found for the type test");
|
||||
}
|
||||
|
||||
esp_cpu_intr_type_t old_type = esp_cpu_intr_get_type(intr_num);
|
||||
esp_cpu_intr_type_t new_type = (old_type == ESP_CPU_INTR_TYPE_EDGE) ? ESP_CPU_INTR_TYPE_LEVEL : ESP_CPU_INTR_TYPE_EDGE;
|
||||
|
||||
esp_cpu_intr_set_type(intr_num, new_type);
|
||||
TEST_ASSERT_EQUAL(new_type, esp_cpu_intr_get_type(intr_num));
|
||||
esp_cpu_intr_set_type(intr_num, old_type);
|
||||
}
|
||||
|
||||
TEST_CASE("CPU intr: priority can be changed on a free line", "[cpu][cpu_intr]")
|
||||
{
|
||||
int intr_num = find_test_interrupt();
|
||||
if (intr_num < 0) {
|
||||
TEST_IGNORE_MESSAGE("No free interrupt line was found for the priority test");
|
||||
}
|
||||
|
||||
int old_priority = esp_cpu_intr_get_priority(intr_num);
|
||||
int new_priority = (old_priority == 1) ? 2 : 1;
|
||||
|
||||
esp_cpu_intr_set_priority(intr_num, new_priority);
|
||||
TEST_ASSERT_EQUAL(new_priority, esp_cpu_intr_get_priority(intr_num));
|
||||
esp_cpu_intr_set_priority(intr_num, old_priority);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("CPU intr: edge ack is callable", "[cpu][cpu_intr]")
|
||||
{
|
||||
int intr_num = find_test_interrupt();
|
||||
if (intr_num < 0) {
|
||||
TEST_IGNORE_MESSAGE("No free interrupt line was found for the edge ack test");
|
||||
}
|
||||
|
||||
#if SOC_CPU_HAS_FLEXIBLE_INTC
|
||||
esp_cpu_intr_type_t old_type = esp_cpu_intr_get_type(intr_num);
|
||||
esp_cpu_intr_set_type(intr_num, ESP_CPU_INTR_TYPE_EDGE);
|
||||
esp_cpu_intr_edge_ack(intr_num);
|
||||
esp_cpu_intr_set_type(intr_num, old_type);
|
||||
#else
|
||||
const int core_id = esp_cpu_get_core_id();
|
||||
esp_cpu_intr_desc_t intr_desc;
|
||||
esp_cpu_intr_get_desc(core_id, intr_num, &intr_desc);
|
||||
if (intr_desc.type != ESP_CPU_INTR_TYPE_EDGE) {
|
||||
TEST_IGNORE_MESSAGE("No free edge interrupt line was found for the edge ack test");
|
||||
}
|
||||
esp_cpu_intr_edge_ack(intr_num);
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_cpu.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
#if __XTENSA__
|
||||
#define CPU_DEBUG_EXCEPTION_REASON "Unhandled debug exception"
|
||||
#else
|
||||
#define CPU_DEBUG_EXCEPTION_REASON "Breakpoint"
|
||||
#endif
|
||||
|
||||
#if SOC_CPU_BREAKPOINTS_NUM > 0
|
||||
static void __attribute__((noinline)) breakpoint_target_function(void)
|
||||
{
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
}
|
||||
|
||||
static void trigger_breakpoint_panic(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_cpu_set_breakpoint(0, (const void *)breakpoint_target_function));
|
||||
breakpoint_target_function();
|
||||
while (true) {
|
||||
}
|
||||
}
|
||||
|
||||
static void check_breakpoint_reset_reason(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_RST_PANIC, esp_reset_reason());
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_STAGES("CPU breakpoint: trigger causes panic",
|
||||
"[cpu][cpu_breakpoint][cpu_non_recoverable][reset=" CPU_DEBUG_EXCEPTION_REASON ",SW_CPU_RESET]",
|
||||
trigger_breakpoint_panic, check_breakpoint_reset_reason)
|
||||
#endif
|
||||
|
||||
#if SOC_CPU_WATCHPOINTS_NUM > 0
|
||||
static volatile uint32_t s_watchpoint_target;
|
||||
static volatile uint32_t s_watchpoint_sink;
|
||||
#define WATCHPOINT_TARGET_ADDR ((const void *)&s_watchpoint_target)
|
||||
|
||||
static void __attribute__((noinline)) trigger_watchpoint_store(void)
|
||||
{
|
||||
s_watchpoint_target++;
|
||||
}
|
||||
|
||||
static void __attribute__((noinline)) trigger_watchpoint_load(void)
|
||||
{
|
||||
s_watchpoint_sink = s_watchpoint_target;
|
||||
}
|
||||
|
||||
static void trigger_watchpoint_store_panic(void)
|
||||
{
|
||||
s_watchpoint_target = 0;
|
||||
TEST_ASSERT_EQUAL(ESP_OK,
|
||||
esp_cpu_set_watchpoint(0, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_STORE));
|
||||
trigger_watchpoint_store();
|
||||
while (true) {
|
||||
}
|
||||
}
|
||||
|
||||
static void check_watchpoint_reset_reason(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_RST_PANIC, esp_reset_reason());
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_STAGES("CPU watchpoint: store trigger causes panic",
|
||||
"[cpu][cpu_watchpoint][cpu_non_recoverable][reset=" CPU_DEBUG_EXCEPTION_REASON ",SW_CPU_RESET]",
|
||||
trigger_watchpoint_store_panic, check_watchpoint_reset_reason)
|
||||
|
||||
static void trigger_watchpoint_load_panic(void)
|
||||
{
|
||||
s_watchpoint_target = 0x12345678;
|
||||
s_watchpoint_sink = 0;
|
||||
TEST_ASSERT_EQUAL(ESP_OK,
|
||||
esp_cpu_set_watchpoint(0, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_LOAD));
|
||||
trigger_watchpoint_load();
|
||||
while (true) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_STAGES("CPU watchpoint: load trigger causes panic",
|
||||
"[cpu][cpu_watchpoint][cpu_non_recoverable][reset=" CPU_DEBUG_EXCEPTION_REASON ",SW_CPU_RESET]",
|
||||
trigger_watchpoint_load_panic, check_watchpoint_reset_reason)
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_cpu.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
#if SOC_CPU_CORES_NUM == 1
|
||||
static void do_cpu_reset(void)
|
||||
{
|
||||
esp_cpu_reset(esp_cpu_get_core_id());
|
||||
while (true) {
|
||||
}
|
||||
}
|
||||
|
||||
static void check_cpu_reset_reason(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_RST_SW, esp_reset_reason());
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_STAGES("CPU reset: current core reset reboots the chip", "[cpu][cpu_reset][reset=SW_CPU_RESET]",
|
||||
do_cpu_reset, check_cpu_reset_reason)
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_rom_sys.h"
|
||||
#include "esp_cpu.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
#if SOC_CPU_CORES_NUM > 1
|
||||
static volatile bool s_stall_task_run;
|
||||
static volatile bool s_stall_task_started;
|
||||
static volatile uint32_t s_stall_counter;
|
||||
|
||||
static void stall_counter_task(void *arg)
|
||||
{
|
||||
s_stall_task_started = true;
|
||||
while (s_stall_task_run) {
|
||||
s_stall_counter++;
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
TEST_CASE("CPU stall: other core stops and resumes", "[cpu][cpu_stall]")
|
||||
{
|
||||
const int current_core = esp_cpu_get_core_id();
|
||||
const int other_core = !current_core;
|
||||
TaskHandle_t task_handle = NULL;
|
||||
|
||||
s_stall_task_run = true;
|
||||
s_stall_task_started = false;
|
||||
s_stall_counter = 0;
|
||||
|
||||
TEST_ASSERT_EQUAL(pdPASS,
|
||||
xTaskCreatePinnedToCore(stall_counter_task, "cpu_stall_counter", 2048, NULL,
|
||||
UNITY_FREERTOS_PRIORITY + 1, &task_handle, other_core));
|
||||
|
||||
for (int i = 0; i < 1000 && !s_stall_task_started; i++) {
|
||||
esp_rom_delay_us(100);
|
||||
}
|
||||
TEST_ASSERT_TRUE(s_stall_task_started);
|
||||
|
||||
uint32_t start_count = s_stall_counter;
|
||||
for (int i = 0; i < 1000 && s_stall_counter == start_count; i++) {
|
||||
esp_rom_delay_us(100);
|
||||
}
|
||||
TEST_ASSERT_TRUE(s_stall_counter > start_count);
|
||||
|
||||
esp_cpu_stall(other_core);
|
||||
esp_rom_delay_us(1000);
|
||||
uint32_t stalled_count = s_stall_counter;
|
||||
esp_rom_delay_us(20000);
|
||||
uint32_t stalled_count_late = s_stall_counter;
|
||||
esp_cpu_unstall(other_core);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(stalled_count, stalled_count_late);
|
||||
|
||||
for (int i = 0; i < 1000 && s_stall_counter == stalled_count_late; i++) {
|
||||
esp_rom_delay_us(100);
|
||||
}
|
||||
TEST_ASSERT_TRUE(s_stall_counter > stalled_count_late);
|
||||
|
||||
s_stall_task_run = false;
|
||||
vTaskDelay(1);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "unity.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_cpu.h"
|
||||
|
||||
#if SOC_CPU_WATCHPOINTS_NUM > 0
|
||||
static volatile uint32_t s_watchpoint_target;
|
||||
#define WATCHPOINT_TARGET_ADDR ((const void *)&s_watchpoint_target)
|
||||
|
||||
TEST_CASE("CPU watchpoint: invalid index is rejected", "[cpu][cpu_watchpoint]")
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
|
||||
esp_cpu_set_watchpoint(-1, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_ACCESS));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
|
||||
esp_cpu_set_watchpoint(SOC_CPU_WATCHPOINTS_NUM, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_ACCESS));
|
||||
}
|
||||
|
||||
TEST_CASE("CPU watchpoint: invalid non-power-of-two size is rejected", "[cpu][cpu_watchpoint]")
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
|
||||
esp_cpu_set_watchpoint(0, WATCHPOINT_TARGET_ADDR, 0, ESP_CPU_WATCHPOINT_ACCESS));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
|
||||
esp_cpu_set_watchpoint(0, WATCHPOINT_TARGET_ADDR, 3, ESP_CPU_WATCHPOINT_ACCESS));
|
||||
}
|
||||
|
||||
TEST_CASE("CPU watchpoint: misaligned address is rejected", "[cpu][cpu_watchpoint]")
|
||||
{
|
||||
const void *misaligned_addr = (const void *)((uintptr_t)&s_watchpoint_target + 1);
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG,
|
||||
esp_cpu_set_watchpoint(0, misaligned_addr, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_ACCESS));
|
||||
}
|
||||
|
||||
TEST_CASE("CPU watchpoint: all watchpoints can be set and cleared", "[cpu][cpu_watchpoint]")
|
||||
{
|
||||
for (int wp_num = 0; wp_num < SOC_CPU_WATCHPOINTS_NUM; ++wp_num) {
|
||||
TEST_ASSERT_EQUAL(ESP_OK,
|
||||
esp_cpu_set_watchpoint(wp_num, WATCHPOINT_TARGET_ADDR, sizeof(s_watchpoint_target), ESP_CPU_WATCHPOINT_ACCESS));
|
||||
}
|
||||
|
||||
for (int wp_num = 0; wp_num < SOC_CPU_WATCHPOINTS_NUM; ++wp_num) {
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_cpu_clear_watchpoint(wp_num));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("CPU watchpoint: invalid clear index is rejected", "[cpu][cpu_watchpoint]")
|
||||
{
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_clear_watchpoint(-1));
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_cpu_clear_watchpoint(SOC_CPU_WATCHPOINTS_NUM));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,59 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
def _run_non_recoverable_case(dut: IdfDut, test_name: str, expected_reason: str) -> None:
|
||||
dut.skip_decode_panic = True
|
||||
dut.expect_exact('Press ENTER to see the list of tests')
|
||||
dut.write(f'"{test_name}"')
|
||||
# TEST_CASE_MULTIPLE_STAGES shows a stage sub-menu before running; select stage 1 to trigger the fault
|
||||
dut.expect_exact('(1)')
|
||||
dut.write('1')
|
||||
|
||||
actual_reason = dut.expect(r"Core\s+\d+\s+panic'ed \(([^)]+)\)", timeout=10).group(1).decode()
|
||||
assert actual_reason == expected_reason, f"expected panic reason '{expected_reason}', got '{actual_reason}'"
|
||||
|
||||
dut.expect_exact('Rebooting...')
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'config,target',
|
||||
[('default', 'supported_targets')],
|
||||
indirect=['config', 'target'],
|
||||
)
|
||||
def test_cpu(dut: IdfDut) -> None:
|
||||
dut.run_all_single_board_cases(group='!cpu_reset&!cpu_non_recoverable', timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'config,target',
|
||||
[('default', 'supported_targets')],
|
||||
indirect=['config', 'target'],
|
||||
)
|
||||
def test_cpu_reset(dut: IdfDut) -> None:
|
||||
dut.run_all_single_board_cases(group='cpu_reset', timeout=120)
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize(
|
||||
'config,target',
|
||||
[('default', 'supported_targets')],
|
||||
indirect=['config', 'target'],
|
||||
)
|
||||
def test_cpu_non_recoverable(dut: IdfDut) -> None:
|
||||
expected_reason = 'Unhandled debug exception' if dut.target in {'esp32', 'esp32s2', 'esp32s3'} else 'Breakpoint'
|
||||
present_cases = {case.name for case in dut.test_menu}
|
||||
|
||||
for case_name in (
|
||||
'CPU breakpoint: trigger causes panic',
|
||||
'CPU watchpoint: store trigger causes panic',
|
||||
'CPU watchpoint: load trigger causes panic',
|
||||
):
|
||||
if case_name in present_cases:
|
||||
_run_non_recoverable_case(dut, case_name, expected_reason)
|
||||
@@ -0,0 +1,2 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
@@ -0,0 +1,4 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
Reference in New Issue
Block a user