Skip to content

Commit

Permalink
feat(pthread): Pthread can now use PSRAM as stack
Browse files Browse the repository at this point in the history
Closes #10623
Closes #8662

Thanks to f-hoepfinger-hr-agrartechnik for the contribution
in #10623
  • Loading branch information
0xjakob committed Jan 22, 2024
1 parent 261651f commit be59c94
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 17 deletions.
20 changes: 14 additions & 6 deletions components/pthread/include/esp_pthread.h
Original file line number Diff line number Diff line change
@@ -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
*/
Expand All @@ -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;

/**
Expand All @@ -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
*
Expand All @@ -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);

Expand Down
97 changes: 87 additions & 10 deletions components/pthread/pthread.c
Original file line number Diff line number Diff line change
@@ -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
*/
Expand All @@ -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"
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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!");
Expand Down
6 changes: 6 additions & 0 deletions components/pthread/test_apps/.build-test-rules.yml
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions components/pthread/test_apps/pthread_psram_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions components/pthread/test_apps/pthread_psram_tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
| Supported Targets | ESP32 |
| ----------------- | ----- |
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit be59c94

Please sign in to comment.