/* * SPDX-FileCopyrightText: 2020 Amazon.com, Inc. or its affiliates * * SPDX-License-Identifier: MIT */ /* * FreeRTOS Kernel V10.4.3 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * https://www.FreeRTOS.org * https://github.com/FreeRTOS * */ /*----------------------------------------------------------- * Implementation of functions defined in portable.h for the Posix port. * * Each task has a pthread which eases use of standard debuggers * (allowing backtraces of tasks etc). Threads for tasks that are not * running are blocked in sigwait(). * * Task switch is done by resuming the thread for the next task by * signaling the condition variable and then waiting on a condition variable * with the current thread. * * The timer interrupt uses SIGALRM and care is taken to ensure that * the signal handler runs only on the thread for the current task. * * Use of part of the standard C library requires care as some * functions can take pthread mutexes internally which can result in * deadlocks as the FreeRTOS kernel can switch tasks while they're * holding a pthread mutex. * * stdio (printf() and friends) should be called from a single task * only or serialized with a FreeRTOS primitive such as a binary * semaphore or mutex. *----------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include /* Scheduler includes. */ #include "esp_heap_caps.h" #include "FreeRTOS.h" #include "task.h" #include "esp_task.h" #include "timers.h" #include "utils/wait_for_event.h" #include "esp_log.h" /*-----------------------------------------------------------*/ #define SIG_RESUME SIGUSR1 typedef struct THREAD { pthread_t pthread; TaskFunction_t pxCode; void *pvParams; BaseType_t xDying; struct event *ev; } Thread_t; /* * The additional per-thread data is stored at the beginning of the * task's stack. */ static inline Thread_t *prvGetThreadFromTask(TaskHandle_t xTask) { StackType_t *pxTopOfStack = *(StackType_t **)xTask; return (Thread_t *)(pxTopOfStack + 1); } /*-----------------------------------------------------------*/ static pthread_once_t hSigSetupThread = PTHREAD_ONCE_INIT; static sigset_t xAllSignals; static sigset_t xSchedulerOriginalSignalMask; static pthread_t hMainThread = ( pthread_t )NULL; // These are saved as part of a thread's state in prvSwitchThread() static volatile BaseType_t uxCriticalNestingIDF = 0; /* Track nesting calls for IDF style critical sections. FreeRTOS critical section nesting is maintained in the TCB. */ static volatile UBaseType_t uxInterruptNesting = 0; /* Tracks if we are currently in an interrupt. */ static volatile BaseType_t uxInterruptLevel = 0; /* Tracks the current level (i.e., interrupt mask) */ /*-----------------------------------------------------------*/ static BaseType_t xSchedulerEnd = pdFALSE; /*-----------------------------------------------------------*/ static void prvSetupSignalsAndSchedulerPolicy( void ); static void prvSetupTimerInterrupt( void ); static void *prvWaitForStart( void * pvParams ); static void prvSwitchThread( Thread_t * xThreadToResume, Thread_t *xThreadToSuspend ); static void prvSuspendSelf( Thread_t * thread); static void prvResumeThread( Thread_t * xThreadId ); static void vPortSystemTickHandler( int sig ); static void vPortStartFirstTask( void ); /*-----------------------------------------------------------*/ static void prvFatalError( const char *pcCall, int iErrno ) { fprintf( stderr, "%s: %s\n", pcCall, strerror( iErrno ) ); abort(); } /* * See header file for description. */ StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, StackType_t *pxEndOfStack, TaskFunction_t pxCode, void *pvParameters ) { Thread_t *thread; pthread_attr_t xThreadAttributes; size_t ulStackSize; int iRet; (void)pthread_once( &hSigSetupThread, prvSetupSignalsAndSchedulerPolicy ); /* * Store the additional thread data at the start of the stack. */ thread = (Thread_t *)(pxTopOfStack + 1) - 1; pxTopOfStack = (StackType_t *)thread - 1; ulStackSize = (pxTopOfStack + 1 - pxEndOfStack) * sizeof(*pxTopOfStack); thread->pxCode = pxCode; thread->pvParams = pvParameters; thread->xDying = pdFALSE; pthread_attr_init( &xThreadAttributes ); pthread_attr_setstack( &xThreadAttributes, pxEndOfStack, ulStackSize ); thread->ev = event_create(); BaseType_t prev_intr_level = xPortSetInterruptMask(); iRet = pthread_create( &thread->pthread, &xThreadAttributes, prvWaitForStart, thread ); if ( iRet ) { prvFatalError( "pthread_create", iRet ); } vPortClearInterruptMask( prev_intr_level ); return pxTopOfStack; } /*-----------------------------------------------------------*/ void vPortStartFirstTask( void ) { Thread_t *pxFirstThread = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); /* Start the first task. */ prvResumeThread( pxFirstThread ); } /*-----------------------------------------------------------*/ /* * See header file for description. */ BaseType_t xPortStartScheduler( void ) { int iSignal; sigset_t xSignals; hMainThread = pthread_self(); /* Start the timer that generates the tick ISR(SIGALRM). Interrupts are disabled here already. */ prvSetupTimerInterrupt(); /* Start the first task. */ vPortStartFirstTask(); /* Wait until signaled by vPortEndScheduler(). */ sigemptyset( &xSignals ); sigaddset( &xSignals, SIG_RESUME ); while ( !xSchedulerEnd ) { sigwait( &xSignals, &iSignal ); } /* Cancel the Idle task and free its resources */ #if ( INCLUDE_xTaskGetIdleTaskHandle == 1 ) vPortCancelThread( xTaskGetIdleTaskHandle() ); #endif #if ( configUSE_TIMERS == 1 ) /* Cancel the Timer task and free its resources */ vPortCancelThread( xTimerGetTimerDaemonTaskHandle() ); #endif /* configUSE_TIMERS */ /* Restore original signal mask. */ (void)pthread_sigmask( SIG_SETMASK, &xSchedulerOriginalSignalMask, NULL ); return 0; } /*-----------------------------------------------------------*/ void vPortEndScheduler( void ) { struct itimerval itimer; struct sigaction sigtick; Thread_t *xCurrentThread; /* Stop the timer and ignore any pending SIGALRMs that would end * up running on the main thread when it is resumed. */ itimer.it_value.tv_sec = 0; itimer.it_value.tv_usec = 0; itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = 0; (void)setitimer( ITIMER_REAL, &itimer, NULL ); sigtick.sa_flags = 0; sigtick.sa_handler = SIG_IGN; sigemptyset( &sigtick.sa_mask ); sigaction( SIGALRM, &sigtick, NULL ); /* Signal the scheduler to exit its loop. */ xSchedulerEnd = pdTRUE; (void)pthread_kill( hMainThread, SIG_RESUME ); xCurrentThread = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); prvSuspendSelf(xCurrentThread); } /*-----------------------------------------------------------*/ static void vPortDisableInterrupts( void ) { pthread_sigmask( SIG_BLOCK, &xAllSignals, NULL ); } /*-----------------------------------------------------------*/ static void vPortEnableInterrupts( void ) { pthread_sigmask( SIG_UNBLOCK, &xAllSignals, NULL ); } /*-----------------------------------------------------------*/ void vPortEnterCriticalIDF( void ) { if ( uxCriticalNestingIDF == 0 && uxInterruptLevel == 0) { vPortDisableInterrupts(); } uxCriticalNestingIDF++; } /*-----------------------------------------------------------*/ void vPortExitCriticalIDF( void ) { uxCriticalNestingIDF--; /* If we have reached 0 then re-enable the interrupts. */ if( uxCriticalNestingIDF == 0 && uxInterruptLevel == 0) { vPortEnableInterrupts(); } } /*-----------------------------------------------------------*/ void vPortYieldFromISR( void ) { Thread_t *xThreadToSuspend; Thread_t *xThreadToResume; xThreadToSuspend = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); vTaskSwitchContext(xPortGetCoreID()); xThreadToResume = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); prvSwitchThread( xThreadToResume, xThreadToSuspend ); } /*-----------------------------------------------------------*/ void vPortYield( void ) { BaseType_t prev_intr_level = xPortSetInterruptMask(); vPortYieldFromISR(); vPortClearInterruptMask( prev_intr_level ); } /*-----------------------------------------------------------*/ /* In SMP code, the disable/enable interrupt macros are calling the set/get interrupt mask functions below. Hence, we need to call vPortDisableInterrupts() and vPortEnableInterrupts(), otherwise interrupts are never disabled/enabled. */ BaseType_t xPortSetInterruptMask( void ) { if (uxInterruptLevel == 0 && uxCriticalNestingIDF == 0) { vPortDisableInterrupts(); } BaseType_t prev_intr_level = uxInterruptLevel; uxInterruptLevel++; return prev_intr_level; } /*-----------------------------------------------------------*/ void vPortClearInterruptMask( BaseType_t xMask ) { // Only reenable interrupts if xMask is 0 uxInterruptLevel = xMask; if (uxInterruptLevel == 0 && uxCriticalNestingIDF == 0) { vPortEnableInterrupts(); } } /*-----------------------------------------------------------*/ static uint64_t prvGetTimeNs(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec * 1000000000ull + t.tv_nsec; } static uint64_t prvStartTimeNs; /* commented as part of the code below in vPortSystemTickHandler, * to adjust timing according to full demo requirements */ /* static uint64_t prvTickCount; */ /* * Setup the systick timer to generate the tick interrupts at the required * frequency. */ void prvSetupTimerInterrupt( void ) { struct itimerval itimer; int iRet; /* Initialise the structure with the current timer information. */ iRet = getitimer( ITIMER_REAL, &itimer ); if ( iRet ) { prvFatalError( "getitimer", errno ); } /* Set the interval between timer events. */ itimer.it_interval.tv_sec = 0; itimer.it_interval.tv_usec = portTICK_RATE_MICROSECONDS; /* Set the current count-down. */ itimer.it_value.tv_sec = 0; itimer.it_value.tv_usec = portTICK_RATE_MICROSECONDS; /* Set-up the timer interrupt. */ iRet = setitimer( ITIMER_REAL, &itimer, NULL ); if ( iRet ) { prvFatalError( "setitimer", errno ); } prvStartTimeNs = prvGetTimeNs(); } /*-----------------------------------------------------------*/ static void vPortSystemTickHandler( int sig ) { Thread_t *pxThreadToSuspend; Thread_t *pxThreadToResume; BaseType_t xSwitchRequired; /* uint64_t xExpectedTicks; */ // Handling a timer signal, so we are currently in an interrupt. uxInterruptNesting++; #if ( configUSE_PREEMPTION == 1 ) pxThreadToSuspend = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); #endif /* Tick Increment, accounting for any lost signals or drift in * the timer. */ /* * Comment code to adjust timing according to full demo requirements * xExpectedTicks = (prvGetTimeNs() - prvStartTimeNs) * / (portTICK_RATE_MICROSECONDS * 1000); * do { */ xSwitchRequired = xTaskIncrementTick(); /* prvTickCount++; * } while (prvTickCount < xExpectedTicks); */ #if ( configUSE_PREEMPTION == 1 ) if (xSwitchRequired == pdTRUE) { /* Select Next Task. */ vTaskSwitchContext(xPortGetCoreID()); pxThreadToResume = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); prvSwitchThread(pxThreadToResume, pxThreadToSuspend); } #else (void)xSwitchRequired; #endif // Returning from the timer signal handler, so we are exiting the interrupt. uxInterruptNesting--; } /*-----------------------------------------------------------*/ void vPortThreadDying( void *pxTaskToDelete, volatile BaseType_t *pxPendYield ) { Thread_t *pxThread = prvGetThreadFromTask( pxTaskToDelete ); pxThread->xDying = pdTRUE; } void vPortCancelThread( void *pxTaskToDelete ) { Thread_t *pxThreadToCancel = prvGetThreadFromTask( pxTaskToDelete ); /* * The thread has already been suspended so it can be safely cancelled. */ pthread_cancel( pxThreadToCancel->pthread ); pthread_join( pxThreadToCancel->pthread, NULL ); event_delete( pxThreadToCancel->ev ); } /*-----------------------------------------------------------*/ static void *prvWaitForStart( void * pvParams ) { Thread_t *pxThread = pvParams; prvSuspendSelf(pxThread); /* Resumed for the first time, thus this thread didn't previously call * prvSwitchThread(). So we need to initialise the state variables for this * thread. */ uxCriticalNestingIDF = 0; uxInterruptNesting = 0; uxInterruptLevel = 0; vPortEnableInterrupts(); /* Call the task's entry point. */ pxThread->pxCode( pxThread->pvParams ); /* A function that implements a task must not exit or attempt to return to * its caller as there is nothing to return to. If a task wants to exit it * should instead call vTaskDelete( NULL ). Artificially force an assert() * to be triggered if configASSERT() is defined, so application writers can * catch the error. */ configASSERT( pdFALSE ); return NULL; } /*-----------------------------------------------------------*/ static void prvSwitchThread( Thread_t *pxThreadToResume, Thread_t *pxThreadToSuspend ) { BaseType_t uxSavedCriticalNestingIDF; BaseType_t uxSavedInterruptNesting; BaseType_t uxSavedInterruptLevel; if ( pxThreadToSuspend != pxThreadToResume ) { /* It is possible for prvSwitchThread() to be called... * - while inside an ISR (i.e., via vPortSystemTickHandler() or vPortYieldFromISR()) * - while interrupts are disabled or in a critical section (i.e., via vPortYield()) * * So we need to save the various count variables as part of the thread's context. * They are restored when the pthread switches back. */ uxSavedCriticalNestingIDF = uxCriticalNestingIDF; uxSavedInterruptNesting = uxInterruptNesting; uxSavedInterruptLevel = uxInterruptLevel; prvResumeThread( pxThreadToResume ); if ( pxThreadToSuspend->xDying ) { pthread_exit( NULL ); } prvSuspendSelf( pxThreadToSuspend ); uxCriticalNestingIDF = uxSavedCriticalNestingIDF; uxInterruptNesting = uxSavedInterruptNesting; uxInterruptLevel = uxSavedInterruptLevel; } } /*-----------------------------------------------------------*/ static void prvSuspendSelf( Thread_t *thread ) { /* * Suspend this thread by waiting for a pthread_cond_signal event. * * A suspended thread must not handle signals (interrupts) so * all signals must be blocked by calling this from: * * - Inside a critical section (vPortEnterCritical() / * vPortExitCritical()). * * - From a signal handler that has all signals masked. * * - A thread with all signals blocked with pthread_sigmask(). */ event_wait(thread->ev); } /*-----------------------------------------------------------*/ static void prvResumeThread( Thread_t *xThreadId ) { if ( pthread_self() != xThreadId->pthread ) { event_signal(xThreadId->ev); } } /*-----------------------------------------------------------*/ static void prvSetupSignalsAndSchedulerPolicy( void ) { struct sigaction sigresume, sigtick; int iRet; hMainThread = pthread_self(); /* Initialise common signal masks. */ sigfillset( &xAllSignals ); /* Don't block SIGINT so this can be used to break into GDB while * in a critical section. */ sigdelset( &xAllSignals, SIGINT ); /* * Block all signals in this thread so all new threads * inherits this mask. * * When a thread is resumed for the first time, all signals * will be unblocked. */ (void)pthread_sigmask( SIG_SETMASK, &xAllSignals, &xSchedulerOriginalSignalMask ); /* SIG_RESUME is only used with sigwait() so doesn't need a handler. */ sigresume.sa_flags = 0; sigresume.sa_handler = SIG_IGN; sigfillset( &sigresume.sa_mask ); sigtick.sa_flags = 0; sigtick.sa_handler = vPortSystemTickHandler; sigfillset( &sigtick.sa_mask ); iRet = sigaction( SIG_RESUME, &sigresume, NULL ); if ( iRet ) { prvFatalError( "sigaction", errno ); } iRet = sigaction( SIGALRM, &sigtick, NULL ); if ( iRet ) { prvFatalError( "sigaction", errno ); } } /*-----------------------------------------------------------*/ unsigned long ulPortGetRunTime( void ) { struct tms xTimes; times( &xTimes ); return ( unsigned long ) xTimes.tms_utime; } /*-----------------------------------------------------------*/ bool portVALID_TCB_MEM(const void *ptr) { return true; } bool portVALID_STACK_MEM(const void *ptr) { return true; } /*-----------------------------------------------------------*/ portMUX_TYPE port_xTaskLock = portMUX_INITIALIZER_UNLOCKED; portMUX_TYPE port_xISRLock = portMUX_INITIALIZER_UNLOCKED; static const char *TAG = "port"; /* When configSUPPORT_STATIC_ALLOCATION is set to 1 the application writer can * use a callback function to optionally provide the memory required by the idle * and timer tasks. This is the stack that will be used by the timer task. It is * declared here, as a global, so it can be checked by a test that is implemented * in a different file. */ StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ]; BaseType_t xPortCheckIfInISR(void) { return (uxInterruptNesting == 0) ? pdFALSE : pdTRUE; } void app_main(void); static void main_task(void* args) { app_main(); vTaskDelete(NULL); } int main(int argc, const char **argv) { // This makes sure that stdio is flushed after each '\n' so that idf.py monitor // reads the program output on time. setvbuf(stdout, NULL, _IOLBF, 0); usleep(1000); BaseType_t res; #if ( configNUM_CORES > 1 ) res = xTaskCreateAffinitySet(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, ESP_TASK_MAIN_CORE, NULL); #else res = xTaskCreate(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL); #endif assert(res == pdTRUE); (void)res; ESP_LOGI(TAG, "Starting SMP scheduler."); vTaskStartScheduler(); // This line should never be reached assert(false); } void esp_vApplicationIdleHook(void) { /* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set * to 1 in FreeRTOSConfig.h. It will be called on each iteration of the idle * task. It is essential that code added to this hook function never attempts * to block in any way (for example, call xQueueReceive() with a block time * specified, or call vTaskDelay()). If application tasks make use of the * vTaskDelete() API function to delete themselves then it is also important * that vApplicationIdleHook() is permitted to return to its calling function, * because it is the responsibility of the idle task to clean up memory * allocated by the kernel to any task that has since deleted itself. */ usleep( 15000 ); } void esp_vApplicationTickHook( void ) { } #if ( configUSE_TICK_HOOK > 0 ) void vApplicationTickHook( void ) { esp_vApplicationTickHook(); } #endif #if ( configSUPPORT_STATIC_ALLOCATION == 1 ) /* configUSE_STATIC_ALLOCATION is set to 1, so the application must provide an * implementation of vApplicationGetIdleTaskMemory() to provide the memory that is * used by the Idle task. */ void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, StackType_t ** ppxIdleTaskStackBuffer, uint32_t * pulIdleTaskStackSize ) { /* If the buffers to be provided to the Idle task are declared inside this * function then they must be declared static - otherwise they will be allocated on * the stack and so not exists after this function exits. */ static StaticTask_t xIdleTaskTCB; static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ]; /* Pass out a pointer to the StaticTask_t structure in which the Idle task's * state will be stored. */ *ppxIdleTaskTCBBuffer = &xIdleTaskTCB; /* Pass out the array that will be used as the Idle task's stack. */ *ppxIdleTaskStackBuffer = uxIdleTaskStack; /* Pass out the size of the array pointed to by *ppxIdleTaskStackBuffer. * Note that, as the array is necessarily of type StackType_t, * configMINIMAL_STACK_SIZE is specified in words, not bytes. */ *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } #endif // configSUPPORT_STATIC_ALLOCATION == 1 /*-----------------------------------------------------------*/ #if ( configSUPPORT_STATIC_ALLOCATION == 1 ) /* configUSE_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1, so the * application must provide an implementation of vApplicationGetTimerTaskMemory() * to provide the memory that is used by the Timer service task. */ void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer, StackType_t ** ppxTimerTaskStackBuffer, uint32_t * pulTimerTaskStackSize ) { /* If the buffers to be provided to the Timer task are declared inside this * function then they must be declared static - otherwise they will be allocated on * the stack and so not exists after this function exits. */ static StaticTask_t xTimerTaskTCB; /* Pass out a pointer to the StaticTask_t structure in which the Timer * task's state will be stored. */ *ppxTimerTaskTCBBuffer = &xTimerTaskTCB; /* Pass out the array that will be used as the Timer task's stack. */ *ppxTimerTaskStackBuffer = uxTimerTaskStack; /* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer. * Note that, as the array is necessarily of type StackType_t, * configMINIMAL_STACK_SIZE is specified in words, not bytes. */ *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; } #endif // configSUPPORT_STATIC_ALLOCATION == 1 void vPortTakeLock( portMUX_TYPE *lock ) { spinlock_acquire( lock, portMUX_NO_TIMEOUT); } void vPortReleaseLock( portMUX_TYPE *lock ) { spinlock_release( lock ); } #define FREERTOS_SMP_MALLOC_CAPS (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT) void *pvPortMalloc( size_t xSize ) { return heap_caps_malloc(xSize, FREERTOS_SMP_MALLOC_CAPS); } void vPortFree( void *pv ) { heap_caps_free(pv); } void __attribute__((weak)) vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { #define ERR_STR1 "***ERROR*** A stack overflow in task " #define ERR_STR2 " has been detected." const char *str[] = {ERR_STR1, pcTaskName, ERR_STR2}; char buf[sizeof(ERR_STR1) + CONFIG_FREERTOS_MAX_TASK_NAME_LEN + sizeof(ERR_STR2) + 1 /* null char */] = {0}; char *dest = buf; for (int i = 0; i < sizeof(str) / sizeof(str[0]); i++) { dest = strcat(dest, str[i]); } printf("%s\n", buf); abort(); } // ------- Thread Local Storage Pointers Deletion Callbacks ------- #if ( CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS ) void vPortTLSPointersDelCb( void *pxTCB ) { /* Typecast pxTCB to StaticTask_t type to access TCB struct members. * pvDummy15 corresponds to pvThreadLocalStoragePointers member of the TCB. */ StaticTask_t *tcb = ( StaticTask_t * )pxTCB; /* The TLSP deletion callbacks are stored at an offset of (configNUM_THREAD_LOCAL_STORAGE_POINTERS/2) */ TlsDeleteCallbackFunction_t *pvThreadLocalStoragePointersDelCallback = ( TlsDeleteCallbackFunction_t * )( &( tcb->pvDummy15[ ( configNUM_THREAD_LOCAL_STORAGE_POINTERS / 2 ) ] ) ); /* We need to iterate over half the depth of the pvThreadLocalStoragePointers area * to access all TLS pointers and their respective TLS deletion callbacks. */ for ( int x = 0; x < ( configNUM_THREAD_LOCAL_STORAGE_POINTERS / 2 ); x++ ) { if ( pvThreadLocalStoragePointersDelCallback[ x ] != NULL ) { //If del cb is set // We skip the check if the callback is executable as that is difficult to determine for different // platforms (compare xtensa and riscv code). pvThreadLocalStoragePointersDelCallback[ x ]( x, tcb->pvDummy15[ x ] ); //Call del cb } } } #endif // CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS void vPortCleanUpTCB ( void *pxTCB ) { #if ( CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS ) /* Call TLS pointers deletion callbacks */ vPortTLSPointersDelCb( pxTCB ); #endif /* CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS */ vPortCancelThread(pxTCB); }