Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
build_*/
sdkconfig
sdkconfig.old
10 changes: 10 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
# ESP32 board integration for the FreeRTOS granular-lock performance-comparison
# target tests. Builds one test at a time, selected with -DPERF_TEST=<name>
# (crit_speed | queue_speed | queue_contention | lock_contention_end_to_end).
cmake_minimum_required(VERSION 3.16)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(freertos_perf_comp_tests)
64 changes: 64 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# ESP32 board — FreeRTOS Test/Target tests

An ESP-IDF integration that can build and run any `Test/Target` SMP test on the
dual-core ESP32 (the granular-lock performance-comparison tests, the functional
SMP tests, and the scheduler-owner regression test).

## How it works

Unlike bare-metal targets that append the per-test `test_config.h` to
`FreeRTOSConfig.h`, ESP-IDF generates its kernel configuration from Kconfig, so:

- Kernel options are set through `sdkconfig.defaults` (`CONFIG_FREERTOS_SMP`,
`CONFIG_FREERTOS_RUN_MULTIPLE_PRIORITIES`, ...).
- `main/board_test_config.h` is force-included into every TU to satisfy the
framework's include-order contract (`TEST_CONFIG_H`,
`configTARGET_TEST_USE_CUSTOM_SETTING`) and to provide an `XT_NOP()` fallback
used by tests that busy-loop with `portNOP()`.
- `main/test_setting_config.h` supplies the ESP32 hooks:
`testGET_TIME_FUNCTION()` (CPU cycle counter) and `testPRINTF_FUNCTION`.

The `main/CMakeLists.txt` is **generic**: it discovers the test source as any
`<name>/<name>.c` under `tests/` and reads its entry function from the
`testENTRY_FUNCTION_PROTOTYPE( ... )` line - adding a new test needs no changes.

## Build & run

One binary per test, selected with `-DTEST=<name>` (legacy `-DPERF_TEST=` also
works):

```bash
. $IDF_PATH/export.sh
cd FreeRTOS/Test/Target/boards/esp32

idf.py -B build_suspend_scheduler -DIDF_TARGET=esp32 -DTEST=suspend_scheduler \
-DSDKCONFIG=build_suspend_scheduler/sdkconfig build
idf.py -B build_suspend_scheduler -p <PORT> flash monitor
```

`<name>` is any test dir, e.g. `crit_speed`, `queue_speed`, `queue_contention`,
`lock_contention_end_to_end`, `scheduler_owner_no_yield`, `disable_preemption`,
`suspend_scheduler`, `task_delete`, `schedule_affinity`, ...

## ESP-IDF compatibility notes

ESP-IDF only lets `configRUN_MULTIPLE_PRIORITIES` be configured (via
`CONFIG_FREERTOS_RUN_MULTIPLE_PRIORITIES`); `configUSE_PREEMPTION`,
`configUSE_TIME_SLICING`, `configUSE_CORE_AFFINITY` and
`configUSE_TASK_PREEMPTION_DISABLE` are fixed to `1`. Therefore:

- Tests requiring `configRUN_MULTIPLE_PRIORITIES=0` use the
`sdkconfig.no_multi_prio` fragment
(`-DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.no_multi_prio"`).
- `disable_multiple_priorities` cannot build (requires `configUSE_CORE_AFFINITY=0`).
- `interrupt_wait_critical` fails: it expects a task spinning to enter
`taskENTER_CRITICAL()` to be preempted before acquiring the lock, but the
ESP-IDF port disables interrupts before spinning, so the entry is not
preemptible - a port-behavior difference, not a test bug.

## Granular vs. non-granular

The tests select the critical-section API via `portUSING_GRANULAR_LOCKS`, which
the ESP-IDF SMP port ties to `configNUMBER_OF_CORES > 1`. To measure the
non-granular baseline on the same two-core kernel, temporarily force
`portUSING_GRANULAR_LOCKS` to `0` in the port's `portmacro.h` and rebuild.
69 changes: 69 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/main/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
#
# Generic runner for the FreeRTOS Test/Target tests on ESP32.
#
# Select a test with -DTEST=<name> (or the legacy -DPERF_TEST=<name>). The test
# source is discovered automatically as any <name>/<name>.c under tests/, and its
# entry function is read from the testENTRY_FUNCTION_PROTOTYPE( <fxn> ) line - so
# adding a new test needs no changes here.

if(NOT DEFINED TEST)
if(DEFINED PERF_TEST) # backwards-compatible alias
set(TEST "${PERF_TEST}")
else()
set(TEST "crit_speed")
endif()
endif()

set(TESTS_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../tests")
set(INC_DIR "${TESTS_ROOT}/include")

# Locate <name>/<name>.c anywhere under tests/.
file(GLOB_RECURSE _all_test_c LIST_DIRECTORIES false "${TESTS_ROOT}/*.c")
set(TEST_SRC "")
foreach(_c ${_all_test_c})
get_filename_component(_fn "${_c}" NAME_WE)
get_filename_component(_dir "${_c}" DIRECTORY)
get_filename_component(_dn "${_dir}" NAME)
if(_fn STREQUAL "${TEST}" AND _dn STREQUAL "${TEST}")
set(TEST_SRC "${_c}")
set(TEST_DIR "${_dir}")
break()
endif()
endforeach()
if(NOT TEST_SRC)
message(FATAL_ERROR "No test '<dir>/${TEST}/${TEST}.c' found under ${TESTS_ROOT}")
endif()

# Read the entry function from testENTRY_FUNCTION_PROTOTYPE( <fxn> ).
file(READ "${TEST_SRC}" _test_src)
string(REGEX MATCH
"testENTRY_FUNCTION_PROTOTYPE[ \t\r\n]*\\([ \t\r\n]*([A-Za-z_][A-Za-z0-9_]*)"
_unused "${_test_src}")
if(NOT CMAKE_MATCH_1)
message(FATAL_ERROR "No testENTRY_FUNCTION_PROTOTYPE(...) in ${TEST_SRC}")
endif()
set(TEST_ENTRY "${CMAKE_MATCH_1}")

idf_component_register(
SRCS "main.c" "${TEST_SRC}"
INCLUDE_DIRS "." "${INC_DIR}" "${TEST_DIR}"
REQUIRES freertos esp_hw_support unity
)

# The upstream test files use bare kernel includes ("FreeRTOS.h", "task.h", ...),
# whereas ESP-IDF exposes them under a freertos/ prefix. Add the SMP kernel's
# header directory so the unmodified test sources compile.
idf_build_get_property(idf_path IDF_PATH)
target_include_directories(${COMPONENT_LIB} PRIVATE
"${idf_path}/components/freertos/FreeRTOS-Kernel-SMP/include/freertos")

# Force-include the board config so the framework's TEST_CONFIG_H guard is
# satisfied and the custom test-setting hooks are picked up.
target_compile_options(${COMPONENT_LIB} PRIVATE
"-include" "${CMAKE_CURRENT_LIST_DIR}/board_test_config.h")

target_compile_definitions(${COMPONENT_LIB} PRIVATE "TEST_ENTRY_FN=${TEST_ENTRY}")

message(STATUS "Target test '${TEST}': ${TEST_SRC} (entry ${TEST_ENTRY})")
32 changes: 32 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/main/board_test_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

/*
* Force-included (via -include) into every translation unit of the ESP32
* perf-comparison board. ESP-IDF generates its FreeRTOSConfig from Kconfig, so
* instead of appending the per-test test_config.h to FreeRTOSConfig.h (as the
* framework does on bare-metal targets), the required kernel options are set
* through sdkconfig (e.g. CONFIG_FREERTOS_RUN_MULTIPLE_PRIORITIES) and this
* header only satisfies the framework's include-order contract:
* - defines TEST_CONFIG_H so the "test_config.h must be included" guard passes
* - selects the custom (non-Unity) test-setting framework
*/

#ifndef BOARD_TEST_CONFIG_H
#define BOARD_TEST_CONFIG_H

#define TEST_CONFIG_H
#define configTARGET_TEST_USE_CUSTOM_SETTING 1

/* Several target tests busy-loop with portNOP(), which the ESP-IDF Xtensa port
* maps to XT_NOP() (see portmacro.h "Todo: Check if XT_NOP exists"). That symbol
* is not declared in a plain test translation unit, so provide a fallback here
* (this header is force-included ahead of portmacro.h). */
#ifndef XT_NOP
#define XT_NOP() __asm__ __volatile__ ( "nop" )
#endif

#endif /* BOARD_TEST_CONFIG_H */
48 changes: 48 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/main/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @file main.c
* @brief Runner for a single granular-lock performance-comparison test on ESP32.
*
* The test to run is selected at build time via -DPERF_TEST=<name>; the CMake
* layer passes the matching entry function as TEST_ENTRY_FN.
*/

#include <stdio.h>

#include "FreeRTOS.h"
#include "task.h"

/* Provided by the selected test file (see boards/esp32/main/CMakeLists.txt). */
extern void TEST_ENTRY_FN( void );

/*-----------------------------------------------------------*/

static void prvRunnerTask( void * pvParameters )
{
( void ) pvParameters;

printf( "\n===PERF_TEST_BEGIN===\n" );
TEST_ENTRY_FN();
printf( "===PERF_TEST_END===\n" );

vTaskDelete( NULL );
}
/*-----------------------------------------------------------*/

void app_main( void )
{
/* Match the reference (pico) board: highest priority, no core affinity. The
* tests that care about placement set their own affinity; the perf tests
* spawn their own affinity-pinned worker tasks from here. */
( void ) xTaskCreate( prvRunnerTask,
"test_runner",
8192,
NULL,
configMAX_PRIORITIES - 1,
NULL );
}
27 changes: 27 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/main/test_setting_config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

/*
* ESP32 test-setting overrides for the target test framework. Included by every
* test file when configTARGET_TEST_USE_CUSTOM_SETTING == 1, before
* test_default_setting_config.h. Only the two target hooks are overridden; the
* default (Unity-based) harness macros are kept, since some tests use Unity
* assertions (TEST_ASSERT_*).
*/

#ifndef TEST_SETTING_CONFIG_H
#define TEST_SETTING_CONFIG_H

#include <stdio.h>
#include "esp_cpu.h"

/* High-resolution timer: the CPU cycle counter. */
#define testGET_TIME_FUNCTION() ( ( UBaseType_t ) esp_cpu_get_cycle_count() )

/* Output. */
#define testPRINTF_FUNCTION printf

#endif /* TEST_SETTING_CONFIG_H */
15 changes: 15 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/sdkconfig.defaults
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0

# SMP, dual-core, multiple priorities (required by the perf-comparison tests).
CONFIG_FREERTOS_SMP=y
CONFIG_FREERTOS_RUN_MULTIPLE_PRIORITIES=y
CONFIG_FREERTOS_HZ=1000

# Measure realistic code (-O2).
CONFIG_COMPILER_OPTIMIZATION_PERF=y

# Keep the measurement loops off the watchdogs.
CONFIG_ESP_TASK_WDT_INIT=n
CONFIG_ESP_INT_WDT=n
CONFIG_FREERTOS_USE_TICKLESS_IDLE=n
2 changes: 2 additions & 0 deletions FreeRTOS/Test/Target/boards/esp32/sdkconfig.no_multi_prio
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# For tests that require configRUN_MULTIPLE_PRIORITIES = 0.
CONFIG_FREERTOS_RUN_MULTIPLE_PRIORITIES=n
Loading