Picolibc with TLS on FreeRTOS v11.1.0

Hi

I am trying to support thread local storage in PICOLIBC for FreeRTOS. I set the macro configUSE_PICOLIBC_TLS and wrote the following test demo. And test it on RISCV-32 platform.

#if ( configUSE_PICOLIBC_TLS == 1 )
static void prvPICOTLSTask( void *pvParameters );
__attribute__((section(".tdata"))) uint32_t tls_variable = 0;
#endif

int main( void )
{
    printf("hello \n");

#if ( configUSE_PICOLIBC_TLS == 1 )
    xTaskCreate(prvPICOTLSTask,
                "TLS1",
                configMINIMAL_STACK_SIZE,
                (void *) 1,
                mainQUEUE_SEND_TASK_PRIORITY,
                NULL );
    xTaskCreate(prvPICOTLSTask,
                "TLS2",
                configMINIMAL_STACK_SIZE,
                (void *) 2,
                mainQUEUE_SEND_TASK_PRIORITY,
                NULL );
#endif

    vTaskDelete(NULL);
}
/*-----------------------------------------------------------*/
#if ( configUSE_PICOLIBC_TLS == 1 )
static void prvPICOTLSTask( void *pvParameters )
{
    tls_variable = (uint32_t)(uint32_t *) pvParameters;
    while (1) {
        printf("Task %ld: tls_variable = %ld\n", tls_variable, tls_variable);
        vTaskDelay(300);
    }
}
#endif

I expected that the program result should be that “TLS1” prints the value of tls_variable as 1, and “TLS2” prints the value as 2. However, the test results did not meet expectations.

hello
Task 1: tls_variable = 1
Task 2: tls_variable = 2
Task 2: tls_variable = 2
Task 2: tls_variable = 2

In the end, tls_variable remained 2 and did not change.

When enable configUSE_PICOLIBC_TLS, each task would create xTLSBlock in TCB to store tis local storage pointer. xTLSBlock only is only used in context switch by macro configSET_TLS_BLOCK _set_tls

#define configSET_TLS_BLOCK( xTLSBlock )    _set_tls( xTLSBlock )

void
_set_tls(void *tls)
{
    __asm__("mov " REG(TLS_REGNO) ", %0" : : "r" (tls));
}

The compiled assembly is as follows:

80009a14 <_set_tls>:
80009a14:	822a                	c.mv	tp,a0
80009a16:	8082                	c.jr	ra

configSET_TLS_BLOCK() just set tp to xTLSBlock, which means that each context switch will set the tp register of the current task to xTLSBlock. It seems that there is no modification of local storage variable tls_variable. I would appreciate to know how tls works and whether it needs to make corresponding changes in the protable file (porASM.S or port.c).

Best regards.

picolibc TLS implementation creates a copy of data in the .tdata section for each task.

  +----------+
  |          |
  |  .tdata  |
  |          |
  +----------+
TLS Data in Flash

                                                                  
 +--------------------------+       +----------------------------+
 |                          |       |                            |
 |        Task1             |       |          Task2             |
 |                          |       |                            |
 +--------------------------+       +----------------------------+
                                                                  
  +----------+                       +-----------+                
  |          |                       |           |                
  |          |                       |           |                
  |          |                       |           |                
  +----------+                       +-----------+                
   Task1's copy                        Task2's copy
   of TLS data                         TLS data

To access the TLS data, you need to know the layout of the complete data in TLS. You can then obtain the location of the task specific TLS data (for example, by reading tp register on RISC-V) and interpret it. The following is an example -

typedef struct TLSData
{
    int errno;
    int stdin_fileno;
    int stdout_fileno;
    int stderr_fileno;
} TLSData_t;

TLSData_t tlsData __attribute__((section(".tdata")));

void Task1( void * param )
{
    TLSData_t * task1TlsData = ( TLSData_t * ) __builtin_thread_pointer();

    for( ;; )
    {
        /* Use task1TlsData. */
        vTaskDelay( pdMS_TO_TICKS( 1000 ) );
    }
}

void Task2( void * param )
{
    TLSData_t * task2TlsData = ( TLSData_t * ) __builtin_thread_pointer();

    for( ;; )
    {
        /* Use task2TlsData. */
        vTaskDelay( pdMS_TO_TICKS( 1000 ) );
    }
}

It is normally used by the c-runtime library to manage thread specific state - newlib/newlib/libc/include/sys/reent.h at master · eblot/newlib · GitHub. What problem are you trying to solve?

2 Likes

Thanks for your explanation.
I made stupid mistakes that caused this problem. First, the version of picolibc is old which does not support TLS properly. Second,__thread should be used to define thread local variables instead of using attribute to put them into .tdata section. __thread can put thread local variables into the appropriate data section (.tdata or .tbss) and generate corresponding assemblly.

__thread uint32_t tls_variable = 0;

After correcting these two errors, the compiler can generate correct assembly to load or store thread local variables through tp register. Now the test has passed after enabling configUSE_PICOLIBC_TLS .
Sorry for taking up your time, thanks again!