mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-04 11:55:21 +02:00
feat(pthread): Pthread can now use PSRAM as stack
Closes https://github.com/espressif/esp-idf/pull/10623 Closes https://github.com/espressif/esp-idf/issues/8662 Thanks to f-hoepfinger-hr-agrartechnik for the contribution in https://github.com/espressif/esp-idf/pull/10623
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -20,11 +20,15 @@ extern "C" {
|
||||
|
||||
/** pthread configuration structure that influences pthread creation */
|
||||
typedef struct {
|
||||
size_t stack_size; ///< The stack size of the pthread
|
||||
size_t prio; ///< The thread's priority
|
||||
bool inherit_cfg; ///< Inherit this configuration further
|
||||
const char* thread_name; ///< The thread name.
|
||||
int pin_to_core; ///< The core id to pin the thread to. Has the same value range as xCoreId argument of xTaskCreatePinnedToCore.
|
||||
size_t stack_size; /**< The stack size of the pthread */
|
||||
size_t prio; /**< The thread's priority */
|
||||
bool inherit_cfg; /**< Inherit this configuration further */
|
||||
const char* thread_name; /**< The thread name. */
|
||||
int pin_to_core; /**< The core id to pin the thread to. Has the same value range as xCoreId
|
||||
argument of xTaskCreatePinnedToCore. */
|
||||
uint32_t stack_alloc_caps; /**< A bit mask of memory capabilities (MALLOC_CAPS*) to use when
|
||||
allocating the stack. The memory must be 8 bit accessible (MALLOC_CAP_8BIT).
|
||||
The developer is responsible for the correctenss of \c stack_alloc_caps. */
|
||||
} esp_pthread_cfg_t;
|
||||
|
||||
/**
|
||||
@@ -48,6 +52,9 @@ esp_pthread_cfg_t esp_pthread_get_default_config(void);
|
||||
* then the same configuration is also inherited in the thread
|
||||
* subtree.
|
||||
*
|
||||
* @note If cfg->stack_alloc_caps is 0, it is automatically set to valid default stack memory
|
||||
* capabilities. If cfg->stack_alloc_caps is non-zero, the developer is responsible for its correctenss.
|
||||
* This function only checks that the capabilities are MALLOC_CAP_8BIT, the rest is unchecked.
|
||||
* @note Passing non-NULL attributes to pthread_create() will override
|
||||
* the stack_size parameter set using this API
|
||||
*
|
||||
@@ -57,6 +64,7 @@ esp_pthread_cfg_t esp_pthread_get_default_config(void);
|
||||
* - ESP_OK if configuration was successfully set
|
||||
* - ESP_ERR_NO_MEM if out of memory
|
||||
* - ESP_ERR_INVALID_ARG if stack_size is less than PTHREAD_STACK_MIN
|
||||
* - ESP_ERR_INVALID_ARG if stack_alloc_caps does not include MALLOC_CAP_8BIT
|
||||
*/
|
||||
esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2018-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2018-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -15,6 +15,10 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#if CONFIG_SPIRAM
|
||||
#include "esp_private/freertos_idf_additions_priv.h"
|
||||
#endif
|
||||
#include "esp_heap_caps.h"
|
||||
#include "soc/soc_memory_layout.h"
|
||||
|
||||
#include "pthread_internal.h"
|
||||
@@ -131,6 +135,19 @@ esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// 0 is treated as default value, hence change caps to MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL in that case
|
||||
int heap_caps;
|
||||
if (cfg->stack_alloc_caps == 0) {
|
||||
heap_caps = MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL;
|
||||
} else {
|
||||
// Check that memory is 8-bit capable
|
||||
if (!(cfg->stack_alloc_caps & MALLOC_CAP_8BIT)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
heap_caps = cfg->stack_alloc_caps;
|
||||
}
|
||||
|
||||
/* If a value is already set, update that value */
|
||||
esp_pthread_cfg_t *p = pthread_getspecific(s_pthread_cfg_key);
|
||||
if (!p) {
|
||||
@@ -140,6 +157,7 @@ esp_err_t esp_pthread_set_cfg(const esp_pthread_cfg_t *cfg)
|
||||
}
|
||||
}
|
||||
*p = *cfg;
|
||||
p->stack_alloc_caps = heap_caps;
|
||||
pthread_setspecific(s_pthread_cfg_key, p);
|
||||
return 0;
|
||||
}
|
||||
@@ -167,7 +185,8 @@ esp_pthread_cfg_t esp_pthread_get_default_config(void)
|
||||
.prio = CONFIG_PTHREAD_TASK_PRIO_DEFAULT,
|
||||
.inherit_cfg = false,
|
||||
.thread_name = NULL,
|
||||
.pin_to_core = get_default_pthread_core()
|
||||
.pin_to_core = get_default_pthread_core(),
|
||||
.stack_alloc_caps = MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT,
|
||||
};
|
||||
|
||||
return cfg;
|
||||
@@ -201,6 +220,57 @@ static void pthread_task_func(void *arg)
|
||||
ESP_LOGV(TAG, "%s EXIT", __FUNCTION__);
|
||||
}
|
||||
|
||||
#if CONFIG_SPIRAM && CONFIG_FREERTOS_SMP
|
||||
static UBaseType_t coreID_to_AffinityMask(BaseType_t core_id)
|
||||
{
|
||||
UBaseType_t affinity_mask = tskNO_AFFINITY;
|
||||
if (core_id != tskNO_AFFINITY) {
|
||||
affinity_mask = 1 << core_id;
|
||||
}
|
||||
return affinity_mask;
|
||||
}
|
||||
#endif
|
||||
|
||||
static BaseType_t pthread_create_freertos_task_with_caps(TaskFunction_t pxTaskCode,
|
||||
const char * const pcName,
|
||||
const configSTACK_DEPTH_TYPE usStackDepth,
|
||||
void * const pvParameters,
|
||||
UBaseType_t uxPriority,
|
||||
BaseType_t core_id,
|
||||
UBaseType_t uxStackMemoryCaps,
|
||||
TaskHandle_t * const pxCreatedTask)
|
||||
{
|
||||
#if CONFIG_SPIRAM
|
||||
#if CONFIG_FREERTOS_SMP
|
||||
return prvTaskCreateDynamicAffinitySetWithCaps(pxTaskCode,
|
||||
pcName,
|
||||
usStackDepth,
|
||||
pvParameters,
|
||||
uxPriority,
|
||||
coreID_to_AffinityMask(core_id),
|
||||
uxStackMemoryCaps,
|
||||
pxCreatedTask);
|
||||
#else
|
||||
return prvTaskCreateDynamicPinnedToCoreWithCaps(pxTaskCode,
|
||||
pcName,
|
||||
usStackDepth,
|
||||
pvParameters,
|
||||
uxPriority,
|
||||
core_id,
|
||||
uxStackMemoryCaps,
|
||||
pxCreatedTask);
|
||||
#endif
|
||||
#else
|
||||
return xTaskCreatePinnedToCore(pxTaskCode,
|
||||
pcName,
|
||||
usStackDepth,
|
||||
pvParameters,
|
||||
uxPriority,
|
||||
pxCreatedTask,
|
||||
core_id);
|
||||
#endif
|
||||
}
|
||||
|
||||
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
void *(*start_routine) (void *), void *arg)
|
||||
{
|
||||
@@ -224,6 +294,7 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
BaseType_t prio = CONFIG_PTHREAD_TASK_PRIO_DEFAULT;
|
||||
BaseType_t core_id = get_default_pthread_core();
|
||||
const char *task_name = CONFIG_PTHREAD_TASK_NAME_DEFAULT;
|
||||
uint32_t stack_alloc_caps = MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT;
|
||||
|
||||
esp_pthread_cfg_t *pthread_cfg = pthread_getspecific(s_pthread_cfg_key);
|
||||
if (pthread_cfg) {
|
||||
@@ -252,6 +323,9 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
core_id = pthread_cfg->pin_to_core;
|
||||
}
|
||||
|
||||
// Note: validity has been checked during esp_pthread_set_cfg()
|
||||
stack_alloc_caps = pthread_cfg->stack_alloc_caps;
|
||||
|
||||
task_arg->cfg = *pthread_cfg;
|
||||
}
|
||||
|
||||
@@ -269,20 +343,23 @@ int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
}
|
||||
}
|
||||
|
||||
// stack_size is in bytes. This transformation ensures that the units are
|
||||
// transformed to the units used in FreeRTOS.
|
||||
// Note: float division of ceil(m / n) ==
|
||||
// integer division of (m + n - 1) / n
|
||||
stack_size = (stack_size + sizeof(StackType_t) - 1) / sizeof(StackType_t);
|
||||
task_arg->func = start_routine;
|
||||
task_arg->arg = arg;
|
||||
pthread->task_arg = task_arg;
|
||||
BaseType_t res = xTaskCreatePinnedToCore(&pthread_task_func,
|
||||
|
||||
BaseType_t res = pthread_create_freertos_task_with_caps(&pthread_task_func,
|
||||
task_name,
|
||||
// stack_size is in bytes. This transformation ensures that the units are
|
||||
// transformed to the units used in FreeRTOS.
|
||||
// Note: float division of ceil(m / n) ==
|
||||
// integer division of (m + n - 1) / n
|
||||
(stack_size + sizeof(StackType_t) - 1) / sizeof(StackType_t),
|
||||
stack_size,
|
||||
task_arg,
|
||||
prio,
|
||||
&xHandle,
|
||||
core_id);
|
||||
core_id,
|
||||
stack_alloc_caps,
|
||||
&xHandle);
|
||||
|
||||
if (res != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create task!");
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
components/pthread/test_apps/pthread_psram_tests:
|
||||
enable:
|
||||
- if: IDF_TARGET in ["esp32"]
|
||||
reason: PSRAM only available on ESP32, S2, S3; code is fairly generic
|
||||
@@ -0,0 +1,13 @@
|
||||
# For more information about build system see
|
||||
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
set(COMPONENTS main esp_psram)
|
||||
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") # For test_utils component
|
||||
|
||||
project(pthread_psram_tests)
|
||||
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "pthread_psram_tests.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity test_utils pthread) # note: esp_psram is set in the project's CMakeLists.txt
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "memory_checks.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_pthread.h"
|
||||
#include <errno.h>
|
||||
#include "pthread.h"
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
esp_pthread_cfg_t config = esp_pthread_get_default_config();
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_pthread_set_cfg(&config));
|
||||
test_utils_record_free_mem();
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
test_utils_finish_and_evaluate_leaks(0, 0);
|
||||
}
|
||||
|
||||
TEST_CASE("esp_pthread_get_default_config creates correct stack memory capabilities", "[set_cfg]")
|
||||
{
|
||||
esp_pthread_cfg_t default_config = esp_pthread_get_default_config();
|
||||
|
||||
// The default must always be internal, 8-bit accessible RAM
|
||||
TEST_ASSERT_EQUAL_HEX(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, default_config.stack_alloc_caps);
|
||||
}
|
||||
|
||||
TEST_CASE("correct memory is accepted", "[set_cfg]")
|
||||
{
|
||||
esp_pthread_cfg_t default_config = esp_pthread_get_default_config();
|
||||
|
||||
default_config.stack_alloc_caps = MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL;
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_pthread_set_cfg(&default_config));
|
||||
}
|
||||
|
||||
TEST_CASE("Setting stack with heap caps 0 sets the default value", "[set_cfg]")
|
||||
{
|
||||
esp_pthread_cfg_t config = { .stack_size = 4096 }; // all other values are set to 0
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_pthread_set_cfg(&config));
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_pthread_get_cfg(&config));
|
||||
TEST_ASSERT_EQUAL(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, config.stack_alloc_caps);
|
||||
}
|
||||
|
||||
TEST_CASE("Setting stack with non 8-bit caps fails", "[set_cfg]")
|
||||
{
|
||||
esp_pthread_cfg_t config = esp_pthread_get_default_config();
|
||||
config.stack_alloc_caps = MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL;
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pthread_set_cfg(&config));
|
||||
}
|
||||
|
||||
static void *check_stack_in_spiram(void *arg)
|
||||
{
|
||||
int ret_value;
|
||||
if (esp_ptr_internal(&ret_value)) {
|
||||
ret_value = 0;
|
||||
} else {
|
||||
ret_value = 1;
|
||||
}
|
||||
vTaskDelay(2); // ensure the test task has time to continue execution
|
||||
pthread_exit((void *) ret_value);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TEST_CASE("pthread_create fails because out of PSRAM", "[psram]")
|
||||
{
|
||||
esp_pthread_cfg_t config = esp_pthread_get_default_config();
|
||||
config.stack_alloc_caps = MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM;
|
||||
config.stack_size = 0xFFFFFFFF; // far larger than the virtual address space on ESP32
|
||||
pthread_t pthread_object = (pthread_t)NULL;
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_pthread_set_cfg(&config));
|
||||
|
||||
TEST_ASSERT_EQUAL(ENOMEM, pthread_create(&pthread_object, NULL, check_stack_in_spiram, NULL));
|
||||
}
|
||||
|
||||
TEST_CASE("pthread create large PSRAM stack", "[psram]")
|
||||
{
|
||||
esp_pthread_cfg_t config = esp_pthread_get_default_config();
|
||||
config.stack_alloc_caps = MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM;
|
||||
config.stack_size = 0x80000; // value is too large for any internal RAM on current chips
|
||||
int res = -1;
|
||||
int thread_rval = -1;
|
||||
pthread_t pthread_object = (pthread_t)NULL;
|
||||
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_pthread_set_cfg(&config));
|
||||
|
||||
res = pthread_create(&pthread_object, NULL, check_stack_in_spiram, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, res);
|
||||
|
||||
res = pthread_join(pthread_object, (void*) &thread_rval);
|
||||
TEST_ASSERT_EQUAL_INT(0, res);
|
||||
TEST_ASSERT_EQUAL_INT(1, thread_rval);
|
||||
|
||||
// Add a short delay to allow the idle task to free any remaining memory
|
||||
vTaskDelay(2);
|
||||
}
|
||||
|
||||
TEST_CASE("pthread with stack in internal RAM", "[psram]")
|
||||
{
|
||||
int res = -1;
|
||||
int thread_rval = -1;
|
||||
pthread_t pthread_object = (pthread_t)NULL;
|
||||
|
||||
res = pthread_create(&pthread_object, NULL, check_stack_in_spiram, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(0, res);
|
||||
|
||||
res = pthread_join(pthread_object, (void*) &thread_rval);
|
||||
TEST_ASSERT_EQUAL_INT(0, res);
|
||||
TEST_ASSERT_EQUAL_INT(0, thread_rval);
|
||||
|
||||
// Add a short delay to allow the idle task to free any remaining memory
|
||||
vTaskDelay(2);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
vTaskPrioritySet(NULL, CONFIG_UNITY_FREERTOS_PRIORITY);
|
||||
printf("pthread PSRAM Test");
|
||||
unity_run_menu();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.esp32
|
||||
def test_pthread_psram(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases(timeout=10)
|
||||
@@ -0,0 +1,5 @@
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
|
||||
CONFIG_SPIRAM=y
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
@@ -13,6 +13,33 @@
|
||||
|
||||
#include "unity.h"
|
||||
|
||||
TEST_CASE("esp_pthread_get_default_config creates correct stack memory capabilities", "[set_cfg]")
|
||||
{
|
||||
esp_pthread_cfg_t default_config = esp_pthread_get_default_config();
|
||||
|
||||
// The default must always be internal, 8-bit accessible RAM
|
||||
TEST_ASSERT_EQUAL_HEX(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, default_config.stack_alloc_caps);
|
||||
}
|
||||
|
||||
TEST_CASE("wrong heap caps are rejected", "[set_cfg]")
|
||||
{
|
||||
esp_pthread_cfg_t default_config = esp_pthread_get_default_config();
|
||||
|
||||
default_config.stack_alloc_caps = MALLOC_CAP_32BIT;
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pthread_set_cfg(&default_config));
|
||||
|
||||
default_config.stack_alloc_caps = MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL;
|
||||
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, esp_pthread_set_cfg(&default_config));
|
||||
}
|
||||
|
||||
TEST_CASE("correct memory is accepted", "[set_cfg]")
|
||||
{
|
||||
esp_pthread_cfg_t default_config = esp_pthread_get_default_config();
|
||||
|
||||
default_config.stack_alloc_caps = MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL;
|
||||
TEST_ASSERT_EQUAL(ESP_OK, esp_pthread_set_cfg(&default_config));
|
||||
}
|
||||
|
||||
static void *compute_square(void *arg)
|
||||
{
|
||||
int *num = (int *) arg;
|
||||
|
||||
Reference in New Issue
Block a user