feat(freertos): added option for automatically placing task stacks in PSRAM

When CONFIG_FREERTOS_PLACE_TASK_STACKS_IN_EXT_RAM is enabled freertos will
now automatically allocate task stacks to PSRAM.
This commit is contained in:
Marius Vikhammer
2026-05-27 16:11:36 +08:00
parent 4a0c14dc7d
commit d21dc8ca2e
26 changed files with 560 additions and 7 deletions

View File

@@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.22)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
idf_build_set_property(MINIMAL_BUILD ON)
project(psram_stack)

View File

@@ -0,0 +1,2 @@
| 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 | Linux |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | ----- |

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "test_psram_stack.c"
INCLUDE_DIRS "."
PRIV_REQUIRES unity nvs_flash esp_partition esp_hw_support esp_psram esp_pm)

View File

@@ -0,0 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_flash_dispatcher: ==1.0.1

View File

@@ -0,0 +1,210 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* System-level tests for CONFIG_FREERTOS_PLACE_TASK_STACKS_IN_EXT_RAM.
*
* Unity is run from a task created with xTaskCreate(), so every TEST_CASE
* body executes directly on a PSRAM-backed stack. No worker-task indirection
* is needed except for the concurrency test.
*/
#include <inttypes.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "esp_attr.h"
#include "esp_heap_caps.h"
#include "esp_memory_utils.h"
#include "esp_partition.h"
#include "esp_pm.h"
#include "esp_sleep.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_flash_dispatcher.h"
#include "esp_private/esp_clk.h"
#include "sdkconfig.h"
#define WORKER_STACK_SIZE 4096
#define WORKER_PRIORITY 5
#define UNITY_STACK_SIZE 8192
#define NUM_CONCURRENT 5
#define CONCURRENT_READS 10
#define MHZ 1000000
static const char *TAG = "psram_stack_test";
#if CONFIG_PM_ENABLE && CONFIG_FREERTOS_USE_TICKLESS_IDLE && CONFIG_PM_LIGHT_SLEEP_CALLBACKS
static volatile uint32_t s_light_sleep_exit_count;
static volatile int64_t s_last_light_sleep_us;
#endif
/* Confirm the calling task's stack is in PSRAM before each test body runs. */
#define ASSERT_STACK_IN_PSRAM() \
do { volatile uint8_t _probe = 0; \
TEST_ASSERT_TRUE_MESSAGE(esp_ptr_external_ram((void *)&_probe), \
"test task stack is not in PSRAM"); } while (0)
TEST_CASE("PSRAM stack: flash read via dispatcher succeeds", "[psram_stack]")
{
ASSERT_STACK_IN_PSRAM();
const esp_partition_t *part = esp_partition_find_first(
ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_ANY, NULL);
TEST_ASSERT_NOT_NULL(part);
uint8_t buf[16];
TEST_ASSERT_EQUAL(ESP_OK, esp_partition_read(part, 0, buf, sizeof(buf)));
}
TEST_CASE("PSRAM stack: NVS read/write via dispatcher succeeds", "[psram_stack]")
{
ASSERT_STACK_IN_PSRAM();
nvs_handle_t handle;
TEST_ASSERT_EQUAL(ESP_OK, nvs_open("psram_test", NVS_READWRITE, &handle));
TEST_ASSERT_EQUAL(ESP_OK, nvs_set_u32(handle, "key", 0xDEADBEEFUL));
TEST_ASSERT_EQUAL(ESP_OK, nvs_commit(handle));
uint32_t val = 0;
TEST_ASSERT_EQUAL(ESP_OK, nvs_get_u32(handle, "key", &val));
TEST_ASSERT_EQUAL_HEX32(0xDEADBEEFUL, val);
nvs_close(handle);
}
TEST_CASE("PSRAM stack: deep sleep rejected from PSRAM-stacked task", "[psram_stack]")
{
ASSERT_STACK_IN_PSRAM();
esp_sleep_enable_timer_wakeup(1000000ULL);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_ALLOWED, esp_deep_sleep_try_to_start());
}
#if CONFIG_PM_ENABLE && CONFIG_FREERTOS_USE_TICKLESS_IDLE && CONFIG_PM_LIGHT_SLEEP_CALLBACKS
static esp_err_t IRAM_ATTR light_sleep_exit_cb(int64_t slept_us, void *arg)
{
(void)arg;
if (slept_us > 0) {
s_last_light_sleep_us = slept_us;
s_light_sleep_exit_count++;
}
return ESP_OK;
}
TEST_CASE("PSRAM stack: task resumes correctly after tickless-idle light sleep", "[psram_stack][light_sleep]")
{
ASSERT_STACK_IN_PSRAM();
s_light_sleep_exit_count = 0;
s_last_light_sleep_us = 0;
esp_pm_sleep_cbs_register_config_t sleep_cbs = {
.exit_cb = light_sleep_exit_cb,
};
TEST_ESP_OK(esp_pm_light_sleep_unregister_cbs(&sleep_cbs));
TEST_ESP_OK(esp_pm_light_sleep_register_cbs(&sleep_cbs));
printf("Waiting for tickless-idle light sleep...\n");
vTaskDelay(pdMS_TO_TICKS(100));
const uint32_t exit_count = s_light_sleep_exit_count;
const int64_t last_slept_us = s_last_light_sleep_us;
TEST_ESP_OK(esp_pm_light_sleep_unregister_cbs(&sleep_cbs));
ESP_LOGI(TAG, "Auto light sleep exit callbacks: %" PRIu32 ", last slept: %" PRId64 " us",
exit_count, last_slept_us);
TEST_ASSERT_GREATER_THAN_UINT32(0, exit_count);
TEST_ASSERT_GREATER_THAN_UINT32(0, last_slept_us);
ASSERT_STACK_IN_PSRAM();
}
#endif /* CONFIG_PM_ENABLE && CONFIG_FREERTOS_USE_TICKLESS_IDLE && CONFIG_PM_LIGHT_SLEEP_CALLBACKS */
/* ---------- Test 6: Concurrent flash reads from multiple PSRAM-stacked tasks ---------- */
typedef struct {
esp_err_t err;
int reads_done;
TaskHandle_t parent;
} concurrent_result_t;
static void task_concurrent_flash(void *arg)
{
concurrent_result_t *r = arg;
const esp_partition_t *part = esp_partition_find_first(
ESP_PARTITION_TYPE_APP,
ESP_PARTITION_SUBTYPE_ANY, NULL);
if (!part) {
r->err = ESP_ERR_NOT_FOUND;
xTaskNotifyGive(r->parent);
vTaskDelete(NULL);
return;
}
r->err = ESP_OK;
for (int i = 0; i < CONCURRENT_READS; i++) {
uint8_t buf[16];
r->err = esp_partition_read(part, 0, buf, sizeof(buf));
if (r->err != ESP_OK) {
break;
}
r->reads_done++;
}
xTaskNotifyGive(r->parent);
vTaskDelete(NULL);
}
TEST_CASE("PSRAM stack: concurrent flash reads from multiple PSRAM-stacked tasks", "[psram_stack]")
{
ASSERT_STACK_IN_PSRAM();
static concurrent_result_t results[NUM_CONCURRENT];
TaskHandle_t self = xTaskGetCurrentTaskHandle();
for (int i = 0; i < NUM_CONCURRENT; i++) {
results[i] = (concurrent_result_t){ .parent = self };
TEST_ASSERT_EQUAL(pdPASS, xTaskCreate(task_concurrent_flash, "cflash",
WORKER_STACK_SIZE, &results[i], WORKER_PRIORITY, NULL));
}
for (int i = 0; i < NUM_CONCURRENT; i++) {
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
}
for (int i = 0; i < NUM_CONCURRENT; i++) {
ESP_LOGI(TAG, "worker[%d]: %d reads, err=%s",
i, results[i].reads_done, esp_err_to_name(results[i].err));
TEST_ASSERT_EQUAL(ESP_OK, results[i].err);
TEST_ASSERT_EQUAL(CONCURRENT_READS, results[i].reads_done);
}
}
static void unity_task(void *arg)
{
unity_run_menu();
vTaskDelete(NULL);
}
void app_main(void)
{
const esp_flash_dispatcher_config_t disp_cfg = {
.task_stack_size = 4096,
.task_priority = configMAX_PRIORITIES - 1,
.task_core_id = tskNO_AFFINITY,
.queue_size = 8,
};
ESP_ERROR_CHECK(esp_flash_dispatcher_init(&disp_cfg));
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
/* Run Unity from a PSRAM-stacked task so all TEST_CASE bodies execute
* with a PSRAM stack without needing per-test worker task indirection. */
xTaskCreate(unity_task, "unity", UNITY_STACK_SIZE, NULL, WORKER_PRIORITY, NULL);
}

View File

@@ -0,0 +1,20 @@
# 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',
'light_sleep',
],
indirect=True,
)
@idf_parametrize('target', soc_filtered_targets('SOC_SPIRAM_SUPPORTED == 1'), indirect=['target'])
def test_psram_stack(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@@ -0,0 +1 @@
# Base configuration — no additional options beyond sdkconfig.defaults.

View File

@@ -0,0 +1,4 @@
CONFIG_PM_ENABLE=y
CONFIG_PM_LIGHT_SLEEP_CALLBACKS=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_PM_DFS_INIT_AUTO=y

View File

@@ -0,0 +1,3 @@
CONFIG_SPIRAM=y
CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y
CONFIG_FREERTOS_PLACE_TASK_STACKS_IN_EXT_RAM=y