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
pull/13090/head
Jakob Hasse 2023-06-30 11:21:12 +08:00
rodzic 261651fc19
commit be59c94917
13 zmienionych plików z 304 dodań i 17 usunięć

Wyświetl plik

@ -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);

Wyświetl plik

@ -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!");

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -0,0 +1,2 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |

Wyświetl plik

@ -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

Wyświetl plik

@ -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();
}

Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -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;

Wyświetl plik

@ -14,6 +14,8 @@ Because {IDF_TARGET_NAME} uses multiple types of RAM, it also contains multiple
For most purposes, the C Standard Library's ``malloc()`` and ``free()`` functions can be used for heap allocation without any special consideration. However, in order to fully make use of all of the memory types and their characteristics, ESP-IDF also has a capabilities-based heap memory allocator. If you want to have a memory with certain properties (e.g., :ref:`dma-capable-memory` or executable-memory), you can create an OR-mask of the required capabilities and pass that to :cpp:func:`heap_caps_malloc`.
.. _memory_capabilities:
Memory Capabilities
-------------------

Wyświetl plik

@ -188,6 +188,7 @@ The API :cpp:func:`esp_pthread_set_cfg` defined in the ``esp_pthreads.h`` header
.. list::
- Default stack size of new threads, if not specified when calling ``pthread_create()`` (overrides :ref:`CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT`).
- Stack memory capabilities: They determine which kind of memory is used for allocating pthread stacks. The field takes ESP-IDF heap capability flags, as defined in :component_file:`heap/include/esp_heap_caps.h`. The memory must be 8-bit accessible (MALLOC_CAP_8BIT), besides other custom flags the user can choose from. The user is responsible for ensuring the correctness of the stack memory capabilities. For more information about memory locations, refer to the documentation of :ref:`Memory Capabilities <memory_capabilities>`
- RTOS priority of new threads (overrides :ref:`CONFIG_PTHREAD_TASK_PRIO_DEFAULT`).
:SOC_HP_CPU_HAS_MULTIPLE_CORES: - Core affinity / core pinning of new threads (overrides :ref:`CONFIG_PTHREAD_TASK_CORE_DEFAULT`).
- FreeRTOS task name for new threads (overrides :ref:`CONFIG_PTHREAD_TASK_NAME_DEFAULT`)

Wyświetl plik

@ -14,6 +14,8 @@ ESP-IDF 应用程序使用常见的计算机架构模式:由程序控制流动
多数情况下,可直接使用 C 标准函数库中的 ``malloc()````free()`` 函数实现堆分配。为充分利用各种内存类型及其特性ESP-IDF 还具有基于内存属性的堆内存分配器。要配备具有特定属性的内存,如 :ref:`dma-capable-memory` 或可执行内存,可以创建具备所需属性的 OR 掩码,将其传递给 :cpp:func:`heap_caps_malloc`
.. _memory_capabilities:
内存属性
-------------------