Skip to content

Commit f8c4236

Browse files
committed
freertos: Adds C11 TLS support
1 parent 532107c commit f8c4236

File tree

6 files changed

+210
-2
lines changed

6 files changed

+210
-2
lines changed

components/esp32/ld/esp32.common.ld

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,13 @@ SECTIONS
194194
*(.gnu.linkonce.lit4.*)
195195
_lit4_end = ABSOLUTE(.);
196196
. = ALIGN(4);
197+
_thread_local_start = ABSOLUTE(.);
198+
*(.tdata)
199+
*(.tdata.*)
200+
*(.tbss)
201+
*(.tbss.*)
202+
_thread_local_end = ABSOLUTE(.);
203+
. = ALIGN(4);
197204
} >drom0_0_seg
198205

199206
.flash.text :

components/freertos/include/freertos/task.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,9 @@ is used in assert() statements. */
380380
* @return pdPASS if the task was successfully created and added to a ready
381381
* list, otherwise an error code defined in the file projdefs.h
382382
*
383+
* @note If program uses thread local variables (ones specified with "__thread" keyword)
384+
* then storage for them will be allocated on the task's stack.
385+
*
383386
* Example usage:
384387
* @code{c}
385388
* // Task to be created.
@@ -530,6 +533,9 @@ is used in assert() statements. */
530533
* are NULL then the task will not be created and
531534
* errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY is returned.
532535
*
536+
* @note If program uses thread local variables (ones specified with "__thread" keyword)
537+
* then storage for them will be allocated on the task's stack.
538+
*
533539
* Example usage:
534540
* @code{c}
535541
*

components/freertos/port.c

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
*/
9393

9494
#include <stdlib.h>
95+
#include <string.h>
9596
#include <xtensa/config/core.h>
9697

9798
#include "xtensa_rtos.h"
@@ -146,9 +147,24 @@ StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t px
146147
#if XCHAL_CP_NUM > 0
147148
uint32_t *p;
148149
#endif
150+
uint32_t *threadptr;
151+
void *task_thread_local_start;
152+
extern int _thread_local_start, _thread_local_end, _rodata_start;
153+
// TODO: check that TLS area fits the stack
154+
uint32_t thread_local_sz = (uint8_t *)&_thread_local_end - (uint8_t *)&_thread_local_start;
149155

150-
/* Create interrupt stack frame aligned to 16 byte boundary */
151-
sp = (StackType_t *) (((UBaseType_t)(pxTopOfStack + 1) - XT_CP_SIZE - XT_STK_FRMSZ) & ~0xf);
156+
thread_local_sz = ALIGNUP(0x10, thread_local_sz);
157+
158+
/* Initialize task's stack so that we have the following structure at the top:
159+
160+
----LOW ADDRESSES ----------------------------------------HIGH ADDRESSES----------
161+
task stack | interrupt stack frame | thread local vars | co-processor save area |
162+
----------------------------------------------------------------------------------
163+
| |
164+
SP pxTopOfStack
165+
166+
All parts are aligned to 16 byte boundary. */
167+
sp = (StackType_t *) (((UBaseType_t)(pxTopOfStack + 1) - XT_CP_SIZE - thread_local_sz - XT_STK_FRMSZ) & ~0xf);
152168

153169
/* Clear the entire frame (do not use memset() because we don't depend on C library) */
154170
for (tp = sp; tp <= pxTopOfStack; ++tp)
@@ -178,6 +194,14 @@ StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t px
178194
frame->vpri = 0xFFFFFFFF;
179195
#endif
180196

197+
/* Init threadptr reg and TLS vars */
198+
task_thread_local_start = (void *)(((uint32_t)pxTopOfStack - XT_CP_SIZE - thread_local_sz) & ~0xf);
199+
memcpy(task_thread_local_start, &_thread_local_start, thread_local_sz);
200+
threadptr = (uint32_t *)(sp + XT_STK_EXTRA);
201+
/* shift threadptr by the offset of _thread_local_start from DROM start;
202+
need to take into account extra 16 bytes offset */
203+
*threadptr = (uint32_t)task_thread_local_start - ((uint32_t)&_thread_local_start - (uint32_t)&_rodata_start) - 0x10;
204+
181205
#if XCHAL_CP_NUM > 0
182206
/* Init the coprocessor save area (see xtensa_context.h) */
183207
/* No access to TCB here, so derive indirectly. Stack growth is top to bottom.
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
Test for thread local storage support.
3+
*/
4+
5+
#include <string.h>
6+
#include <esp_types.h>
7+
8+
#include "freertos/FreeRTOS.h"
9+
#include "freertos/task.h"
10+
#include "unity.h"
11+
#include "sdkconfig.h"
12+
13+
static __thread int tl_test_var1;
14+
static __thread uint8_t tl_test_var2 = 55;
15+
static __thread uint16_t tl_test_var3 = 44;
16+
static __thread uint8_t tl_test_arr_var[10];
17+
static __thread struct test_tls_var {
18+
int f32;
19+
uint8_t f8;
20+
uint16_t f16;
21+
uint8_t farr[10];
22+
} tl_test_struct_var;
23+
24+
static void task_test_tls(void *arg)
25+
{
26+
bool *running = (bool *)arg;
27+
uint32_t tp = (uint32_t)-1;
28+
int test_var1_old = 0;
29+
uint8_t test_var2_old = 0;
30+
uint16_t test_var3_old = 0;
31+
int f32_old = 0;
32+
uint8_t f8_old = 0;
33+
uint16_t f16_old = 0;
34+
35+
asm volatile ("rur.threadptr %0":"=r"(tp));
36+
for (int i = 0; i < 5; i++) {
37+
printf("Task[%x]: var = 0x%x 0x%x\n", tp, tl_test_var1, tl_test_var2);
38+
if (i == 0) {
39+
TEST_ASSERT_EQUAL(0, tl_test_var1);
40+
TEST_ASSERT_EQUAL(55, tl_test_var2);
41+
TEST_ASSERT_EQUAL(44, tl_test_var3);
42+
for (int k = 0; k < sizeof(tl_test_arr_var); k++) {
43+
TEST_ASSERT_EQUAL(0, tl_test_arr_var[k]);
44+
}
45+
TEST_ASSERT_EQUAL(0, tl_test_struct_var.f32);
46+
TEST_ASSERT_EQUAL(0, tl_test_struct_var.f8);
47+
TEST_ASSERT_EQUAL(0, tl_test_struct_var.f16);
48+
for (int k = 0; k < sizeof(tl_test_struct_var.farr); k++) {
49+
TEST_ASSERT_EQUAL(0, tl_test_struct_var.farr[k]);
50+
}
51+
} else {
52+
TEST_ASSERT_EQUAL(test_var1_old+1, tl_test_var1);
53+
TEST_ASSERT_EQUAL(test_var2_old+1, tl_test_var2);
54+
TEST_ASSERT_EQUAL(test_var3_old+1, tl_test_var3);
55+
for (int k = 0; k < sizeof(tl_test_arr_var); k++) {
56+
TEST_ASSERT_EQUAL(i-1, tl_test_arr_var[k]);
57+
}
58+
TEST_ASSERT_EQUAL(f32_old+1, tl_test_struct_var.f32);
59+
TEST_ASSERT_EQUAL(f8_old+1, tl_test_struct_var.f8);
60+
TEST_ASSERT_EQUAL(f16_old+1, tl_test_struct_var.f16);
61+
for (int k = 0; k < sizeof(tl_test_struct_var.farr); k++) {
62+
TEST_ASSERT_EQUAL(i-1, tl_test_struct_var.farr[k]);
63+
}
64+
}
65+
test_var1_old = tl_test_var1;
66+
test_var2_old = tl_test_var2;
67+
test_var3_old = tl_test_var3;
68+
f32_old = tl_test_struct_var.f32;
69+
f8_old = tl_test_struct_var.f8;
70+
f16_old = tl_test_struct_var.f16;
71+
tl_test_var1++;
72+
tl_test_var2++;
73+
tl_test_var3++;
74+
memset(tl_test_arr_var, i, sizeof(tl_test_arr_var));
75+
tl_test_struct_var.f32++;
76+
tl_test_struct_var.f8++;
77+
tl_test_struct_var.f16++;
78+
memset(tl_test_struct_var.farr, i, sizeof(tl_test_struct_var.farr));
79+
vTaskDelay(10);
80+
}
81+
82+
if (running) {
83+
*running = false;
84+
vTaskDelete(NULL);
85+
}
86+
}
87+
88+
TEST_CASE("TLS test", "[freertos]")
89+
{
90+
static StackType_t s_stack[2048];
91+
StaticTask_t s_task;
92+
bool running[2] = {true, true};
93+
#if CONFIG_FREERTOS_UNICORE == 0
94+
int other_core = 1;
95+
#else
96+
int other_core = 0;
97+
#endif
98+
99+
xTaskCreatePinnedToCore((TaskFunction_t)&task_test_tls, "task_test_tls", 3072, &running[0], 5, NULL, 0);
100+
xTaskCreateStaticPinnedToCore((TaskFunction_t)&task_test_tls, "task_test_tls", sizeof(s_stack), &running[1],
101+
5, s_stack, &s_task, other_core);
102+
while (running[0] || running[1]) {
103+
vTaskDelay(10);
104+
}
105+
}

docs/api-guides/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ API Guides
1010
ESP32 Core Dump <core_dump>
1111
Flash Encryption <../security/flash-encryption>
1212
FreeRTOS SMP Changes <freertos-smp>
13+
Thread Local Storage <thread-local-storage>
1314
High Level Interrupts <hlinterrupts>
1415
JTAG Debugging <jtag-debugging/index>
1516
Partition Tables <partition-tables>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
Thread Local Storage
2+
====================
3+
4+
Overview
5+
--------
6+
7+
Thread-local storage (TLS) is a mechanism by which variables are allocated such that there
8+
is one instance of the variable per extant thread. ESP-IDF provides three ways to make use
9+
of such variables:
10+
11+
- :ref:`freertos-native`: ESP-IDF FreeRTOS native API.
12+
- :ref:`pthread-api`: ESP-IDF's pthread API.
13+
- :ref:`c11-std`: C11 standard introduces special keyword to declare variables as thread local.
14+
15+
.. _freertos-native:
16+
17+
FreeRTOS Native API
18+
--------------------
19+
20+
The ESP-IDF FreeRTOS provides the following API to manage thread local variables:
21+
22+
- :cpp:func:`vTaskSetThreadLocalStoragePointer`
23+
- :cpp:func:`pvTaskGetThreadLocalStoragePointer`
24+
- :cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback`
25+
26+
In this case maximum number of variables that can be allocated is limited by
27+
``configNUM_THREAD_LOCAL_STORAGE_POINTERS`` macro. Variables are kept in the task control block (TCB)
28+
and accessed by their index. Note that index 0 is reserved for ESP-IDF internal uses.
29+
Using that API user can allocate thread local variables of an arbitrary size and assign them to any number of tasks.
30+
Different tasks can have different sets of TLS variables.
31+
If size of the variable is more then 4 bytes then user is responsible for allocating/deallocating memory for it.
32+
Variable's deallocation is initiated by FreeRTOS when task is deleted, but user must provide function (callback)
33+
to do proper cleanup.
34+
35+
.. _pthread-api:
36+
37+
Pthread API
38+
----------------
39+
40+
The ESP-IDF provides the following pthread API to manage thtread local variables:
41+
42+
- :cpp:func:`pthread_key_create`
43+
- :cpp:func:`pthread_key_delete`
44+
- :cpp:func:`pthread_getspecific`
45+
- :cpp:func:`pthread_setspecific`
46+
47+
This API has all benefits of the one above, but eliminates some its limits. The number of variables is
48+
limited only by size of available memory on the heap.
49+
Due to the dynamic nature this API introduces additional performance overhead compared to the native one.
50+
51+
.. _c11-std:
52+
53+
C11 Standard
54+
------------
55+
56+
The ESP-IDF FreeRTOS supports thread local variables according to C11 standard (ones specified with ``__thread`` keyword).
57+
For details on this GCC feature please see https://gcc.gnu.org/onlinedocs/gcc-5.5.0/gcc/Thread-Local.html#Thread-Local.
58+
Storage for that kind of variables is allocated on the task's stack.
59+
Note that area for all such variables in the program will be allocated on the stack of
60+
every task in the system even if that task does not use such variables at all. For example
61+
ESP-IDF system tasks (like ``ipc``, ``timer`` tasks etc.) will also have that extra stack space allocated.
62+
So this feature should be used with care. There is a tradeoff: C11 thread local variables are quite handy
63+
to use in programming and can be accessed using just a few Xtensa instructions, but this benefit goes
64+
with the cost of additional stack usage for all tasks in the system.
65+
Due to static nature of variables allocation all tasks in the system have the same sets of C11 thread local variables.

0 commit comments

Comments
 (0)