From 78420f261434fce831c7f72b9baaf529078fda20 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Fri, 22 May 2026 12:15:58 +0200 Subject: [PATCH] feat(freertos): soft-preempting linux simulator --- components/freertos/CMakeLists.txt | 37 +- .../linux/include/freertos/portmacro.h | 10 +- .../FreeRTOS-Kernel/portable/linux/port.c | 988 +++++++++--------- .../FreeRTOS-Kernel/portable/linux/port_idf.c | 230 ++-- .../linux/utils/linux_port_coop_syscalls.c | 452 ++++++++ .../linux/utils/linux_port_coop_syscalls.h | 23 + .../portable/linux/utils/linux_port_utils.h | 22 + .../portable/linux/utils/wait_for_event.c | 116 +- .../portable/linux/utils/wait_for_event.h | 70 +- .../FreeRTOSSimulator_wrappers.c | 95 -- .../esp_private/freertos_idf_additions_priv.h | 16 +- docs/en/api-guides/host-apps.rst | 22 +- tools/mocks/startup/CMakeLists.txt | 4 + .../kernel_tests/tasks/test_eTaskGetState.c | 4 +- .../tasks/test_freertos_task_delete.c | 3 +- .../kernel_tests/tasks/test_preemption.c | 74 +- .../tasks/test_priority_scheduling.c | 85 +- .../kernel_tests/tasks/test_task_priorities.c | 121 ++- .../linux_freertos/sdkconfig.defaults | 2 +- 19 files changed, 1428 insertions(+), 946 deletions(-) create mode 100644 components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.c create mode 100644 components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h create mode 100644 components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_utils.h delete mode 100644 components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c diff --git a/components/freertos/CMakeLists.txt b/components/freertos/CMakeLists.txt index 23187c5c3b6..3d91ec408a0 100644 --- a/components/freertos/CMakeLists.txt +++ b/components/freertos/CMakeLists.txt @@ -82,7 +82,8 @@ list(APPEND srcs if(arch STREQUAL "linux") list(APPEND srcs - "${kernel_impl}/portable/${arch}/utils/wait_for_event.c") + "${kernel_impl}/portable/${arch}/utils/wait_for_event.c" + "${kernel_impl}/portable/${arch}/utils/linux_port_coop_syscalls.c") if(kernel_impl STREQUAL "FreeRTOS-Kernel") list(APPEND srcs "${kernel_impl}/portable/${arch}/port_idf.c") @@ -110,7 +111,6 @@ if(arch STREQUAL "linux") set(BYPASS_EINTR_ISSUE 0) if(NOT CONFIG_LWIP_ENABLE) set(BYPASS_EINTR_ISSUE 1) - list(APPEND srcs "esp_additions/FreeRTOSSimulator_wrappers.c") endif() endif() @@ -190,6 +190,39 @@ if(arch STREQUAL "linux") target_link_libraries(${COMPONENT_LIB} PRIVATE dl) endif() + set(WRAP_FUNCTIONS + read + write + pread + pwrite + readv + writev + recv + send + recvfrom + sendto + recvmsg + sendmsg + connect + accept + close + select + pselect + poll + sleep + usleep + open + socket + socketpair + pipe + pipe2 + dup + dup2) + + foreach(wrap ${WRAP_FUNCTIONS}) + target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") + endforeach() + # Disable strict prototype warnings in upstream code # (struct event * event_create() is missing 'void') set_source_files_properties( diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/include/freertos/portmacro.h b/components/freertos/FreeRTOS-Kernel/portable/linux/include/freertos/portmacro.h index 583c568d888..282810a1aac 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/include/freertos/portmacro.h +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/include/freertos/portmacro.h @@ -77,8 +77,10 @@ typedef unsigned long TickType_t; /*-----------------------------------------------------------*/ /* Scheduler utilities. */ -extern void vPortYield( void ); +extern void vPortYieldWithinApi( void ); +#define portYIELD_WITHIN_API() vPortYieldWithinApi() +extern void vPortYield( void ); #define portYIELD() vPortYield() #define portEND_SWITCHING_ISR( xSwitchRequired ) if( (xSwitchRequired) != pdFALSE ) vPortYield() @@ -107,6 +109,12 @@ void vPortExitCritical( void ); #define portENTER_CRITICAL_ISR(mux) portENTER_CRITICAL(mux) #define portEXIT_CRITICAL_ISR(mux) portEXIT_CRITICAL(mux) +#define prvENTER_CRITICAL_SMP_ONLY( pxLock ) portENTER_CRITICAL( pxLock ) +#define prvEXIT_CRITICAL_SMP_ONLY( pxLock ) portEXIT_CRITICAL( pxLock ) + +extern void vPortSuspendScheduler(void); +#define portSOFTWARE_BARRIER() vPortSuspendScheduler() + /*-----------------------------------------------------------*/ extern void vPortThreadDying( void *pxTaskToDelete, volatile BaseType_t *pxPendYield ); diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/port.c b/components/freertos/FreeRTOS-Kernel/portable/linux/port.c index 436017b2113..ea370e0fd65 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/port.c +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/port.c @@ -1,584 +1,566 @@ /* - * FreeRTOS Kernel V10.5.1 (ESP-IDF SMP modified) - * Copyright (C) 2020 Cambridge Consultants Ltd. - * - * SPDX-FileCopyrightText: 2020 Cambridge Consultants Ltd - * - * SPDX-License-Identifier: MIT - * - * SPDX-FileContributor: 2023-2025 Espressif Systems (Shanghai) CO LTD - * - * 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 + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * + * SPDX-License-Identifier: Apache-2.0 */ - -/*----------------------------------------------------------- - * 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 - -/* Scheduler includes. */ +#include +#include +#include +#include +#include "string.h" #include "FreeRTOS.h" #include "task.h" -#include "timers.h" #include "utils/wait_for_event.h" -/*-----------------------------------------------------------*/ +#include "utils/linux_port_utils.h" -#define SIG_RESUME SIGUSR1 +#define FREERTOS_SIM_TICK_PERIOD_US (1000000 / CONFIG_FREERTOS_HZ) -typedef struct THREAD -{ +typedef struct thread { + const char *name; pthread_t pthread; TaskFunction_t pxCode; void *pvParams; - BaseType_t xDying; + bool is_dying; + bool yield_needed; struct event *ev; -} Thread_t; +} 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) +typedef struct task_thread_node { + TaskHandle_t handle; + thread_t *thread; + SLIST_ENTRY(task_thread_node) next; +} task_thread_node_t; + + +static SLIST_HEAD(task_thread_node_ll, task_thread_node) s_task_thread_list = SLIST_HEAD_INITIALIZER(task_thread_node); +static pthread_mutex_t s_thread_map_mutex = PTHREAD_MUTEX_INITIALIZER; + +static pthread_mutex_t s_port_mutex; +static pthread_t s_scheduler_thread; +static bool s_scheduler_started = false; +static int s_ux_critical_nesting = 0; + +/* TLS flag: true only when inside a real FreeRTOS task pthread */ +static __thread bool s_in_freertos_task = false; + +bool linux_port_in_freertos_task(void) { - StackType_t *pxTopOfStack = *(StackType_t **)xTask; - - return (Thread_t *)(pxTopOfStack + 1); + return s_in_freertos_task; } -/*-----------------------------------------------------------*/ - -static pthread_once_t hSigSetupThread = PTHREAD_ONCE_INIT; -static sigset_t xAllSignals; -static sigset_t xSchedulerOriginalSignalMask; -static pthread_t hMainThread = ( pthread_t )NULL; -static volatile BaseType_t uxCriticalNesting; -/*-----------------------------------------------------------*/ - -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 ) +static void linux_port_initialize_mutexes(void) { - fprintf( stderr, "%s: %s\n", pcCall, strerror( iErrno ) ); + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&s_port_mutex, &attr); + pthread_mutexattr_destroy(&attr); +} + +static void linux_port_fatal_error(const char *msg, int err) +{ + fprintf(stderr, "%s: %s\n", msg, strerror(err)); abort(); } -/* - * See header file for description. - */ -StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, - StackType_t *pxEndOfStack, - TaskFunction_t pxCode, - void *pvParameters ) +static void linux_port_register_thread(TaskHandle_t handle, thread_t *thread) { - 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(); - - vPortEnterCritical(); - - iRet = pthread_create( &thread->pthread, &xThreadAttributes, - prvWaitForStart, thread ); - if ( iRet ) - { - prvFatalError( "pthread_create", iRet ); + if (handle == NULL) { + return; } - vPortExitCritical(); - - 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 ); + task_thread_node_t *node = malloc(sizeof(task_thread_node_t)); + if (!node) { + linux_port_fatal_error("Failed to allocate thread map node", -1); } - /* Cancel the Idle task and free its resources */ -#if ( INCLUDE_xTaskGetIdleTaskHandle == 1 ) - vPortCancelThread( xTaskGetIdleTaskHandle() ); -#endif + node->handle = handle; + node->thread = thread; -#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; + pthread_mutex_lock(&s_thread_map_mutex); + SLIST_INSERT_HEAD(&s_task_thread_list, node, next); + pthread_mutex_unlock(&s_thread_map_mutex); } -/*-----------------------------------------------------------*/ -void vPortEndScheduler( void ) +static void linux_port_unregister_thread(TaskHandle_t handle) { - 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); -} -/*-----------------------------------------------------------*/ - -void vPortEnterCritical( void ) -{ - if ( uxCriticalNesting == 0 ) - { - vPortDisableInterrupts(); - } - uxCriticalNesting++; -} -/*-----------------------------------------------------------*/ - -void vPortExitCritical( void ) -{ - if ( uxCriticalNesting > 0 ) - { - uxCriticalNesting--; + if (handle == NULL) { + return; } - /* Critical section nesting count must always be >= 0. */ - configASSERT( uxCriticalNesting >= 0 ); + pthread_mutex_lock(&s_thread_map_mutex); - /* If we have reached 0 then re-enable the interrupts. */ - if( uxCriticalNesting == 0 ) - { - vPortEnableInterrupts(); - } -} -/*-----------------------------------------------------------*/ + task_thread_node_t *cur_node = SLIST_FIRST(&s_task_thread_list); + task_thread_node_t *prev_node = NULL; -void vPortYieldFromISR( void ) -{ - Thread_t *xThreadToSuspend; - Thread_t *xThreadToResume; + while (cur_node) { + if (cur_node->handle == handle) { + if (prev_node) { + prev_node->next.sle_next = SLIST_NEXT(cur_node, next); + } else { + SLIST_REMOVE_HEAD(&s_task_thread_list, next); + } - xThreadToSuspend = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); + free(cur_node); + pthread_mutex_unlock(&s_thread_map_mutex); + return; + } - vTaskSwitchContext(); - - xThreadToResume = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); - - prvSwitchThread( xThreadToResume, xThreadToSuspend ); -} -/*-----------------------------------------------------------*/ - -void vPortYield( void ) -{ - vPortEnterCritical(); - - vPortYieldFromISR(); - - vPortExitCritical(); -} -/*-----------------------------------------------------------*/ - -void vPortDisableInterrupts( void ) -{ - pthread_sigmask( SIG_BLOCK, &xAllSignals, NULL ); -} -/*-----------------------------------------------------------*/ - -void vPortEnableInterrupts( void ) -{ - pthread_sigmask( SIG_UNBLOCK, &xAllSignals, NULL ); -} -/*-----------------------------------------------------------*/ - -BaseType_t xPortSetInterruptMask( void ) -{ - /* Interrupts are always disabled inside ISRs (signals - handlers). */ - return pdTRUE; -} -/*-----------------------------------------------------------*/ - -void vPortClearInterruptMask( BaseType_t xMask ) -{ -} -/*-----------------------------------------------------------*/ - -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 ); + prev_node = cur_node; + cur_node = SLIST_NEXT(cur_node, next); } - /* Set the interval between timer events. */ - itimer.it_interval.tv_sec = 0; - itimer.it_interval.tv_usec = portTICK_RATE_MICROSECONDS; + pthread_mutex_unlock(&s_thread_map_mutex); +} - /* 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 ); +static thread_t *linux_port_get_thread_from_handle(TaskHandle_t handle) +{ + if (handle == NULL) { + return NULL; } - prvStartTimeNs = prvGetTimeNs(); -} -/*-----------------------------------------------------------*/ - -static void vPortSystemTickHandler( int sig ) -{ - Thread_t *pxThreadToSuspend; - Thread_t *pxThreadToResume; - /* uint64_t xExpectedTicks; */ - - uxCriticalNesting++; /* Signals are blocked in this signal handler. */ - -#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 { */ - xTaskIncrementTick(); -/* prvTickCount++; - * } while (prvTickCount < xExpectedTicks); -*/ - -#if ( configUSE_PREEMPTION == 1 ) - /* Select Next Task. */ - vTaskSwitchContext(); - - pxThreadToResume = prvGetThreadFromTask( xTaskGetCurrentTaskHandle() ); - - prvSwitchThread(pxThreadToResume, pxThreadToSuspend); -#endif - - uxCriticalNesting--; -} -/*-----------------------------------------------------------*/ - -void vPortThreadDying( void *pxTaskToDelete, volatile BaseType_t *pxPendYield ) -{ - Thread_t *pxThread = prvGetThreadFromTask( pxTaskToDelete ); - - pxThread->xDying = pdTRUE; + pthread_mutex_lock(&s_thread_map_mutex); + task_thread_node_t *node = NULL; + SLIST_FOREACH(node, &s_task_thread_list, next) { + if (node->handle == (TaskHandle_t)(*(StackType_t **)(handle))) { + thread_t *t = node->thread; + pthread_mutex_unlock(&s_thread_map_mutex); + return t; + } + } + pthread_mutex_unlock(&s_thread_map_mutex); + return NULL; } -void vPortCancelThread( void *pxTaskToDelete ) +static thread_t *linux_port_get_calling_thread(void) { - #if ( CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK ) - /* Call the user defined task pre-deletion hook before canceling the thread */ - extern void vTaskPreDeletionHook( void * pxTCB ); - vTaskPreDeletionHook( pxTaskToDelete ); - #endif /* CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK */ - - 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 ); + pthread_t self = pthread_self(); + pthread_mutex_lock(&s_thread_map_mutex); + task_thread_node_t *node = NULL; + SLIST_FOREACH(node, &s_task_thread_list, next) { + if (pthread_equal(node->thread->pthread, self)) { + thread_t *thread = node->thread; + pthread_mutex_unlock(&s_thread_map_mutex); + return thread; + } + } + pthread_mutex_unlock(&s_thread_map_mutex); + return NULL; } -/*-----------------------------------------------------------*/ -static void *prvWaitForStart( void * pvParams ) +pthread_t linux_port_get_scheduled_task_pthread(void) { - Thread_t *pxThread = pvParams; + thread_t *thread = linux_port_get_thread_from_handle(xTaskGetCurrentTaskHandle()); + return thread ? thread->pthread : pthread_self(); +} - prvSuspendSelf(pxThread); +static void *linux_port_task_runner(void *arg) +{ + /* Allow this thread to be cancelled */ + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); - /* Resumed for the first time, unblocks all signals. */ - uxCriticalNesting = 0; - vPortEnableInterrupts(); + /* set the flag showing that this is a freertos task */ + s_in_freertos_task = true; - /* Call the task's entry point. */ - pxThread->pxCode( pxThread->pvParams ); + /* setup the backtrace signal. ONLY triggered before abort so + * it will not interfere with the simulation while its running */ + linux_port_setup_backtrace_signal(); - /* 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 ); + thread_t *thread = arg; + + /* Block until scheduler signals first time, then run the task body. */ + event_wait(thread->ev); + thread->pxCode(thread->pvParams); return NULL; } -/*-----------------------------------------------------------*/ -static void prvSwitchThread( Thread_t *pxThreadToResume, - Thread_t *pxThreadToSuspend ) +static void linux_port_unblock_thread(thread_t *thread) { - BaseType_t uxSavedCriticalNesting; - - if ( pxThreadToSuspend != pxThreadToResume ) - { - /* - * Switch tasks. - * - * The critical section nesting is per-task, so save it on the - * stack of the current (suspending thread), restoring it when - * we switch back to this task. - */ - uxSavedCriticalNesting = uxCriticalNesting; - - prvResumeThread( pxThreadToResume ); - if ( pxThreadToSuspend->xDying ) - { - pthread_exit( NULL ); - } - prvSuspendSelf( pxThreadToSuspend ); - - uxCriticalNesting = uxSavedCriticalNesting; - } + event_signal(thread->ev); } -/*-----------------------------------------------------------*/ -static void prvSuspendSelf( Thread_t *thread ) +static void linux_port_block_thread(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 ) +static void linux_port_increment_tick(void) { - if ( pthread_self() != xThreadId->pthread ) - { - event_signal(xThreadId->ev); - } + (void)xTaskIncrementTick(); } -/*-----------------------------------------------------------*/ -static void prvSetupSignalsAndSchedulerPolicy( void ) +static void linux_port_switch_context(TaskHandle_t current_task_hdl) { - struct sigaction sigresume, sigtick; - int iRet; + pthread_mutex_lock(&s_port_mutex); - hMainThread = pthread_self(); + thread_t *current_thread = linux_port_get_thread_from_handle(current_task_hdl); - /* 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 ); + /* get the task that should be scheduled next */ + vTaskSwitchContext(); - /* - * 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 ); + /* get the new task to schedule and the associated thread item */ + TaskHandle_t next_task_hdl = xTaskGetCurrentTaskHandle(); + thread_t *next_thread = linux_port_get_thread_from_handle(next_task_hdl); - /* 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 ); + /* unblock the newly scheduled task if it is different from + * the one already scheduled */ + if (next_thread) { + /* Only unblock the thread if we are actually switching to a + * different one. Signaling the already-running thread would + * leave a stale event_triggered flag, causing its next + * event_wait (e.g. in vPortYield) to return immediately + * instead of blocking. + * + * Exception: on the very first switch, the task is still blocked + * in its initial event_wait (linux_port_task_runner), so we must + * signal it even though current_thread == next_thread. */ + if (next_thread != current_thread || !s_scheduler_started) { + linux_port_unblock_thread(next_thread); + } } - iRet = sigaction( SIGALRM, &sigtick, NULL ); - if ( iRet ) - { - prvFatalError( "sigaction", errno ); + if (!s_scheduler_started) { + s_scheduler_started = true; + } + + /* fill the name of the task in the thread item if not done already. */ + if (next_thread && next_thread->name == NULL) { + /* fill the name of the thread now */ + next_thread->name = pcTaskGetName(next_task_hdl); + } + + /* fill the name of the task in the thread item if not done already. */ + if (current_thread && current_thread->name == NULL) { + /* fill the name of the thread now */ + current_thread->name = pcTaskGetName(current_task_hdl); + } + + pthread_mutex_unlock(&s_port_mutex); +} + +static void *linux_port_scheduler_runner(void *arg) +{ + (void)arg; + + while (1) { + /* sleep for a period of 1 tick */ + usleep(FREERTOS_SIM_TICK_PERIOD_US); + + /* get the task that is currently scheduled */ + TaskHandle_t current_task_hdl = xTaskGetCurrentTaskHandle(); + + /* Lock the port mutex. This will block while any task is in a + * critical section, ensuring ticks don't preempt critical code. */ + pthread_mutex_lock(&s_port_mutex); + + /* increment the freertos tick */ + linux_port_increment_tick(); + + /* schedule a new task, and schedule out the currently running one */ + linux_port_switch_context(current_task_hdl); + + pthread_mutex_unlock(&s_port_mutex); + } + return NULL; +} + +StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, + StackType_t *pxEndOfStack, + TaskFunction_t pxCode, + void *pvParameters) +{ + pthread_attr_t thread_attr; + size_t thread_stack_size; + + /* Store the thread data at the start of the stack. */ + thread_stack_size = (pxTopOfStack - pxEndOfStack) * sizeof(*pxTopOfStack); + pthread_attr_init(&thread_attr); + pthread_attr_setstack(&thread_attr, pxEndOfStack, thread_stack_size); + + thread_t *thread = malloc(sizeof(thread_t)); + if (!thread) { + linux_port_fatal_error("Failed to allocate thread metadata", -1); + } + thread->name = NULL; // this will be filled later when we know about the task name + thread->pxCode = pxCode; + thread->pvParams = pvParameters; + thread->is_dying = false; + thread->yield_needed = false; + thread->ev = event_create(); + + linux_port_register_thread((TaskHandle_t)pxTopOfStack, thread); + + /* create the thread associated with the task being created */ + const int ret = pthread_create(&thread->pthread, &thread_attr, linux_port_task_runner, thread); + if (ret != 0) { + linux_port_fatal_error("pthread_create", ret); + } + return pxTopOfStack; +} + +BaseType_t xPortStartScheduler(void) +{ + /* set the port mutex to be recursive. Must be done before + * vPortEnableInterrupts() which calls vPortExitCritical(). */ + linux_port_initialize_mutexes(); + + /* enable interrupt that were disabled in vTaskStartScheduler */ + vPortEnableInterrupts(); + + /* init the cooperative syscall layer (sets stdio non-blocking). + * Provided by VFS component; weak no-op when VFS is not linked. */ + freertos_linux_coop_syscalls_init(); + + /* Start scheduler thread */ + int ret = pthread_create(&s_scheduler_thread, NULL, linux_port_scheduler_runner, NULL); + if (ret != 0) { + linux_port_fatal_error("pthread_create", ret); + } + + /* Should never return */ + pthread_join(s_scheduler_thread, NULL); + + return 0; +} + +void vPortEndScheduler(void) +{ + exit(0); +} + +void vPortEnterCritical(void) +{ + if (!s_scheduler_started) { + return; + } + + pthread_mutex_lock(&s_port_mutex); + + /* Non-FreeRTOS thread or recursive enter: just bump the counter. + * The mutex is already held (recursive lock succeeds for same thread). */ + if (!linux_port_in_freertos_task() || s_ux_critical_nesting > 0) { + s_ux_critical_nesting++; + return; + } + + /* First enter from a FreeRTOS task: ensure we're the scheduled task. + * If not, release the mutex, block until the scheduler switches to us, + * then re-acquire. */ + thread_t *calling_thread = linux_port_get_calling_thread(); + thread_t *scheduled_thread = linux_port_get_thread_from_handle(xTaskGetCurrentTaskHandle()); + + while (calling_thread && !calling_thread->is_dying && calling_thread != scheduled_thread) { + pthread_mutex_unlock(&s_port_mutex); + linux_port_block_thread(calling_thread); + pthread_mutex_lock(&s_port_mutex); + calling_thread = linux_port_get_calling_thread(); + scheduled_thread = linux_port_get_thread_from_handle(xTaskGetCurrentTaskHandle()); + } + + if (!calling_thread || calling_thread->is_dying) { + linux_port_switch_context(xTaskGetCurrentTaskHandle()); + pthread_mutex_unlock(&s_port_mutex); + return; + } + + s_ux_critical_nesting = 1; +} + +void vPortExitCritical(void) +{ + if (!s_scheduler_started || s_ux_critical_nesting == 0) { + return; + } + + s_ux_critical_nesting--; + + /* Check for deferred yield on final exit from a FreeRTOS task */ + if (s_ux_critical_nesting == 0 && linux_port_in_freertos_task()) { + thread_t *calling_thread = linux_port_get_calling_thread(); + if (calling_thread && calling_thread->yield_needed) { + calling_thread->yield_needed = false; + pthread_mutex_unlock(&s_port_mutex); + vPortYield(); + return; + } + } + + pthread_mutex_unlock(&s_port_mutex); +} + +/* Handle the case where the calling pthread is not a registered FreeRTOS task. + * If the calling thread has been deleted but is still the scheduled task, + * perform a context switch. Otherwise just release the mutex and return. + * Returns true if the caller should return early. */ +static bool linux_port_handle_deleted_task(TaskHandle_t scheduled_task_hdl, + thread_t *calling_thread) +{ + if (calling_thread != NULL) { + return false; + } + + thread_t *scheduled_thread = linux_port_get_thread_from_handle(scheduled_task_hdl); + if (scheduled_thread == NULL) { + linux_port_switch_context(scheduled_task_hdl); + } + pthread_mutex_unlock(&s_port_mutex); + return true; +} + +void vPortYield(void) +{ + pthread_mutex_lock(&s_port_mutex); + + thread_t *calling_thread = linux_port_get_calling_thread(); + TaskHandle_t scheduled_task_hdl = xTaskGetCurrentTaskHandle(); + + if (linux_port_handle_deleted_task(scheduled_task_hdl, calling_thread)) { + return; + } + + /* If in a critical section, defer the yield until the section exits. */ + if (s_ux_critical_nesting != 0) { + calling_thread->yield_needed = true; + pthread_mutex_unlock(&s_port_mutex); + return; + } + + /* Hand the CPU to the next ready task right now (mimics PendSV on real + * hardware). */ + linux_port_switch_context(scheduled_task_hdl); + pthread_mutex_unlock(&s_port_mutex); + + /* If the newly scheduled task is different from the calling thread, + * block until the scheduler resumes this task. */ + TaskHandle_t next_task_hdl = xTaskGetCurrentTaskHandle(); + thread_t *next_thread = linux_port_get_thread_from_handle(next_task_hdl); + if (calling_thread != next_thread) { + linux_port_block_thread(calling_thread); } } -/*-----------------------------------------------------------*/ -unsigned long ulPortGetRunTime( void ) +void vPortYieldWithinApi(void) { - struct tms xTimes; - - times( &xTimes ); - - return ( unsigned long ) xTimes.tms_utime; + vPortYield(); } -/*-----------------------------------------------------------*/ -void vPortSetStackWatchpoint( void *pxStackStart ) +void vPortSuspendScheduler(void) +{ + /* scheduled out task trying to suspend the scheduler should get blocked here */ + pthread_mutex_lock(&s_port_mutex); + + /* get the metadata of the pthread calling this function */ + thread_t *calling_thread = linux_port_get_calling_thread(); + + /* get the thread metadata from the scheduled task */ + TaskHandle_t scheduled_task_hdl = xTaskGetCurrentTaskHandle(); + + if (linux_port_handle_deleted_task(scheduled_task_hdl, calling_thread)) { + return; + } + + thread_t *scheduled_thread = linux_port_get_thread_from_handle(scheduled_task_hdl); + if (calling_thread != scheduled_thread) { + pthread_mutex_unlock(&s_port_mutex); + vPortYield(); + return; + } + pthread_mutex_unlock(&s_port_mutex); +} + +void vPortDisableInterrupts(void) +{ + vPortEnterCritical(); +} + +void vPortEnableInterrupts(void) +{ + vPortExitCritical(); +} + +BaseType_t xPortSetInterruptMask(void) +{ + vPortEnterCritical(); + return pdTRUE; +} + +void vPortClearInterruptMask(BaseType_t xMask) +{ + vPortExitCritical(); +} + +void vPortThreadDying(void *pxTaskToDelete, volatile BaseType_t *pxPendYield) +{ + pthread_mutex_lock(&s_port_mutex); + + thread_t *thread = linux_port_get_thread_from_handle((TaskHandle_t)pxTaskToDelete); + if (thread == NULL) { + pthread_mutex_unlock(&s_port_mutex); + return; + } + + /* Mark the thread as dying, cancel the thread. the pthread + * will be stopped on next cancellation point. Do not remove the + * thread item from the list since it will be done in vPortCancelThread */ + thread->is_dying = true; + pthread_cancel(thread->pthread); + + pthread_mutex_unlock(&s_port_mutex); +} + +#if CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS +static void vPortTLSPointersDelCb(void *pxTCB) +{ + StaticTask_t *tcb = (StaticTask_t *)pxTCB; + TlsDeleteCallbackFunction_t *pvDelCbs = (TlsDeleteCallbackFunction_t *)(&tcb->pvDummy15[configNUM_THREAD_LOCAL_STORAGE_POINTERS / 2]); + + for (int x = 0; x < (configNUM_THREAD_LOCAL_STORAGE_POINTERS / 2); x++) { + if (pvDelCbs[x] != NULL) { + pvDelCbs[x](x, tcb->pvDummy15[x]); + } + } +} +#endif /* CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS */ + +void vPortCancelThread(void *pxTaskToDelete) +{ + pthread_mutex_lock(&s_port_mutex); + +#if CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS + vPortTLSPointersDelCb(pxTaskToDelete); +#endif + + thread_t *thread = linux_port_get_thread_from_handle((TaskHandle_t)pxTaskToDelete); + if (!thread) { + pthread_mutex_unlock(&s_port_mutex); + return; + } + + if (thread->is_dying) { + /* vPortThreadDying already called */ + } else { + thread->is_dying = true; + pthread_cancel(thread->pthread); + } + + /* Save fields and unregister while holding the lock. */ + pthread_t pt = thread->pthread; + event_t *ev = thread->ev; + linux_port_unregister_thread((TaskHandle_t)pxTaskToDelete); + + /* Release the mutex before joining – the dying thread may need the + * scheduler (which also takes s_port_mutex) to reach a cancellation + * point. */ + pthread_mutex_unlock(&s_port_mutex); + + pthread_join(pt, NULL); + event_delete(ev); + free(thread); +} + +void vPortSetStackWatchpoint(void *pxStackStart) { - (void) pxStackStart; } -/*-----------------------------------------------------------*/ diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/port_idf.c b/components/freertos/FreeRTOS-Kernel/portable/linux/port_idf.c index c476decd762..242c1ca071a 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/port_idf.c +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/port_idf.c @@ -1,44 +1,71 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -/* - * This file contains most of the code located in the demo application in the - * upstream FreeRTOS repository. It is put here so that IDF applications can - * seamlessly switch between Linux and chip targets without the need to provide - * or implement additional functionality if the target is the Linux target. - */ - #include #include #include #include -#include #include #include #include +#include -/* Scheduler includes. */ #include "FreeRTOS.h" #include "task.h" -#include "utils/wait_for_event.h" #include "esp_log.h" +#include "utils/linux_port_utils.h" #define BACKTRACE_PC_ARRAY_SIZE 20 +#define FREERTOS_SIM_BACKTRACE_SIZE 16 #define ON_SEGFAULT_MESSAGE "ERROR: Segmentation Fault, here's your backtrace:\n" #define ON_ABORT_MESSAGE "ERROR: Aborted\n" +static void linux_port_backtrace_handler(int sig) +{ + /* All calls here must be async-signal-safe (no stdio, no malloc). + * write() and backtrace_symbols_fd() write directly to the fd. */ + void *buffer[FREERTOS_SIM_BACKTRACE_SIZE]; + int nptrs = backtrace(buffer, FREERTOS_SIM_BACKTRACE_SIZE); + + const char *name = pcTaskGetName(NULL); /* simple pointer dereference, no lock */ + ssize_t ignore __attribute__((unused)); + ignore = write(STDERR_FILENO, "=== Backtrace for task: ", 24); + if (name) { + size_t len = 0; + while (name[len] != '\0') { + len++; + } + ignore = write(STDERR_FILENO, name, len); + } + ignore = write(STDERR_FILENO, " ===\n", 5); + backtrace_symbols_fd(buffer, nptrs, STDERR_FILENO); +} + +void linux_port_setup_backtrace_signal(void) +{ + struct sigaction sa; + sa.sa_handler = linux_port_backtrace_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &sa, NULL); +} + +void linux_port_print_backtrace(void) +{ + pthread_kill(linux_port_get_scheduled_task_pthread(), SIGUSR1); +} + +ESP_LOG_ATTR_TAG(LINUX_TAG, "port_idf_linux"); +ESP_LOG_ATTR_TAG(MAIN_TAG, "main_task"); + #if (defined(__APPLE__) && defined(__MACH__)) typedef sig_t sighandler_t; #endif -static const char *TAG = "port"; - static volatile UBaseType_t uxInterruptNesting = 0; - - BaseType_t xPortCheckIfInISR(void) { return uxInterruptNesting; @@ -46,190 +73,133 @@ BaseType_t xPortCheckIfInISR(void) #if CONFIG_COMPILER_OPTIMIZATION_DEBUG #define BACKTRACE_PC_ARRAY_SIZE_DUMMY 1 -/** - * This function calls backtrace once to ensure that libgcc is loaded already. - */ static void load_libgcc(void) { void *array[BACKTRACE_PC_ARRAY_SIZE_DUMMY]; size_t size = backtrace(array, BACKTRACE_PC_ARRAY_SIZE_DUMMY); - assert(size == 1); // Since this function can be called, the first stack frame should be present + assert(size == 1); } -/* - * Print a rudimentary backtrace to help users a bit with segfaults. - */ static void segfault_handler(int sig) { void *array[BACKTRACE_PC_ARRAY_SIZE]; - size_t size; - - // get void*'s for all entries on the stack - size = backtrace(array, BACKTRACE_PC_ARRAY_SIZE); - - // we need a raw file write here because other functions are not async-signal-safe - int written = write(STDERR_FILENO, ON_SEGFAULT_MESSAGE, sizeof(ON_SEGFAULT_MESSAGE)); - (void) written; // The return value is ignored for now, as we don't have a lot of options in case of failure - // and EINTR can't happen in a signal handler anyways - + size_t size = backtrace(array, BACKTRACE_PC_ARRAY_SIZE); + ssize_t ignore __attribute__((unused)); + ignore = write(STDERR_FILENO, ON_SEGFAULT_MESSAGE, sizeof(ON_SEGFAULT_MESSAGE)); backtrace_symbols_fd(array, size, STDERR_FILENO); _exit(1); } -/* - * Print a message to signal abort, even in idf.py monitor. - */ static void abort_handler(int sig) { // we need a raw file write here because other functions are not async-signal-safe - int written = write(STDERR_FILENO, ON_ABORT_MESSAGE, sizeof(ON_ABORT_MESSAGE)); - (void) written; // The return value is ignored for now, as we don't have a lot of options in case of failure - // and EINTR can't happen in a signal handler anyways - + ssize_t ignore __attribute__((unused)); + ignore = write(STDERR_FILENO, ON_ABORT_MESSAGE, sizeof(ON_ABORT_MESSAGE)); _exit(1); } #endif // CONFIG_COMPILER_OPTIMIZATION_DEBUG -void app_main(void); - -static void main_task(void* args) +/*----------------------------------------------------------- +* Main FreeRTOS task +*-----------------------------------------------------------*/ +extern void app_main(void); +static void main_task(void *args) { + (void)args; + ESP_LOGI(MAIN_TAG, "Started on CPU%d", (int)xPortGetCoreID()); + + ESP_LOGI(MAIN_TAG, "Calling app_main()"); + app_main(); + + ESP_LOGI(MAIN_TAG, "Returned from app_main()"); vTaskDelete(NULL); } void esp_startup_start_app(void) { - // This makes sure that stdio is always synchronized so that idf.py monitor - // and other tools read text output on time. setvbuf(stdout, NULL, _IONBF, 0); #if CONFIG_COMPILER_OPTIMIZATION_DEBUG - // Ensures that libgcc is loaded to avoid problems when loading it later in - // the signal handler (see NOTES section in glibc backtrace man page) load_libgcc(); sighandler_t sig_res; - // Enable backtraces sig_res = signal(SIGSEGV, segfault_handler); if (sig_res == SIG_ERR) { - perror("Failed setting the segfault handler"); abort(); } - // Enable error message on abort sig_res = signal(SIGABRT, abort_handler); if (sig_res == SIG_ERR) { - perror("Failed setting the abort handler"); abort(); } -#endif // CONFIG_COMPILER_OPTIMIZATION_DEBUG +#endif - usleep(1000); - BaseType_t res = xTaskCreatePinnedToCore(&main_task, "main", - ESP_TASK_MAIN_STACK, NULL, - ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE); - assert(res == pdTRUE); - (void)res; + // Create main_task using FreeRTOS API + ESP_LOGI(LINUX_TAG, "Starting main task."); + assert(xTaskCreate(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL) == pdTRUE); - ESP_LOGI(TAG, "Starting scheduler."); + ESP_LOGI(LINUX_TAG, "Starting scheduler task."); vTaskStartScheduler(); - // This line should never be reached + // Should never reach here assert(false); } -void esp_vApplicationIdleHook(void) +/*----------------------------------------------------------- + * idle and tick hooks + *-----------------------------------------------------------*/ +#if (configUSE_IDLE_HOOK > 0) +void 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 -void vPortYieldOtherCore( BaseType_t coreid ) { } // trying to skip for now - -#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 (configUSE_TICK_HOOK > 0) +void vApplicationTickHook(void) +{ + extern void esp_vApplicationTickHook(void); + esp_vApplicationTickHook(); +} +#else +#endif + +/*----------------------------------------------------------- + * Static allocation support + *-----------------------------------------------------------*/ +#if (configSUPPORT_STATIC_ALLOCATION == 1) + +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 ]; + 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 bytes. */ *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } -#endif // configSUPPORT_STATIC_ALLOCATION == 1 -/*-----------------------------------------------------------*/ -#if ( (configSUPPORT_STATIC_ALLOCATION == 1) && (configUSE_TIMERS == 1)) +#if (configUSE_TIMERS == 1) +StackType_t uxTimerTaskStack[configTIMER_TASK_STACK_DEPTH]; -/* 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 ]; - -/* 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 ) +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 bytes. */ *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; } -#endif // (configSUPPORT_STATIC_ALLOCATION == 1) && (configUSE_TIMERS == 1) +#endif +#endif // configSUPPORT_STATIC_ALLOCATION + +/*----------------------------------------------------------- + * Stack overflow hook + *-----------------------------------------------------------*/ void __attribute__((weak)) vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) { #define ERR_STR1 "***ERROR*** A stack overflow in task " diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.c b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.c new file mode 100644 index 00000000000..7f61ecbb03a --- /dev/null +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.c @@ -0,0 +1,452 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Cooperative wrappers for Linux FreeRTOS simulator. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "task.h" + +#define COOP_SYSCALLS_WAIT_MS (1000 / CONFIG_FREERTOS_HZ) + +extern bool linux_port_in_freertos_task(void); + +static inline __attribute__((always_inline)) +void coop_set_fd_nonblocking(int fd) +{ + if (fd >= 0) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags >= 0) { + fcntl(fd, F_SETFL, flags | O_NONBLOCK); + } + } +} + +static inline __attribute__((always_inline)) +void coop_wait(int ms) +{ + if (linux_port_in_freertos_task()) { + vTaskDelay(ms); + } else { + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000L; + while (nanosleep(&ts, &ts) == -1 && errno == EINTR) { + // Retry with remaining time if interrupted + } + } +} + +/* Generic cooperative loop template */ +#define COOP_LOOP(start_expr) \ + while (1) \ + { \ + ssize_t n = start_expr; \ + if (n >= 0) { \ + return n; \ + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { \ + coop_wait(COOP_SYSCALLS_WAIT_MS); \ + continue; \ + } else { \ + return -1; \ + } \ + } + +ssize_t __real_read(int fd, void *buf, size_t count); +ssize_t __real_write(int fd, const void *buf, size_t count); +ssize_t __real_pread(int fd, void *buf, size_t count, off_t offset); +ssize_t __real_pwrite(int fd, const void *buf, size_t count, off_t offset); +ssize_t __real_readv(int fd, const struct iovec *iov, int iovcnt); +ssize_t __real_writev(int fd, const struct iovec *iov, int iovcnt); +ssize_t __real_recv(int sockfd, void *buf, size_t len, int flags); +ssize_t __real_send(int sockfd, const void *buf, size_t len, int flags); +ssize_t __real_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); +ssize_t __real_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); +ssize_t __real_recvmsg(int sockfd, struct msghdr *msg, int flags); +ssize_t __real_sendmsg(int sockfd, const struct msghdr *msg, int flags); +int __real_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +int __real_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); +int __real_close(int fd); +int __real_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); +int __real_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); +int __real_poll(struct pollfd *fds, nfds_t nfds, int timeout); +unsigned int __real_sleep(unsigned int seconds); +int __real_usleep(useconds_t usec); + +int __real_socket(int domain, int type, int protocol); +int __real_socketpair(int domain, int type, int protocol, int sv[2]); +int __real_pipe(int fds[2]); +int __real_pipe2(int fds[2], int flags); +int __real_dup(int oldfd); +int __real_dup2(int oldfd, int newfd); +int __real_open(const char *path, int flags, ...); + +ssize_t __wrap_read(int fd, void *buf, size_t count) +{ + COOP_LOOP(__real_read(fd, buf, count)) +} + +ssize_t __wrap_write(int fd, const void *buf, size_t count) +{ + COOP_LOOP(__real_write(fd, buf, count)) +} + +ssize_t __wrap_pread(int fd, void *buf, size_t count, off_t offset) +{ + COOP_LOOP(__real_pread(fd, buf, count, offset)) +} + +ssize_t __wrap_pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + COOP_LOOP(__real_pwrite(fd, buf, count, offset)) +} + +ssize_t __wrap_readv(int fd, const struct iovec *iov, int iovcnt) +{ + COOP_LOOP(__real_readv(fd, iov, iovcnt)) +} + +ssize_t __wrap_writev(int fd, const struct iovec *iov, int iovcnt) +{ + COOP_LOOP(__real_writev(fd, iov, iovcnt)) +} + +ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) +{ + COOP_LOOP(__real_recv(sockfd, buf, len, flags | MSG_DONTWAIT)) +} + +ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags) +{ + COOP_LOOP(__real_send(sockfd, buf, len, flags | MSG_DONTWAIT)) +} + +ssize_t __wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) +{ + COOP_LOOP(__real_recvfrom(sockfd, buf, len, flags | MSG_DONTWAIT, src_addr, addrlen)) +} + +ssize_t __wrap_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) +{ + COOP_LOOP(__real_sendto(sockfd, buf, len, flags | MSG_DONTWAIT, dest_addr, addrlen)) +} + +ssize_t __wrap_recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + COOP_LOOP(__real_recvmsg(sockfd, msg, flags | MSG_DONTWAIT)) +} + +ssize_t __wrap_sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + COOP_LOOP(__real_sendmsg(sockfd, msg, flags | MSG_DONTWAIT)) +} + +int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + while (1) + { + int ret = __real_connect(sockfd, addr, addrlen); + if (ret == 0) { + return ret; + } + + if (errno == EINPROGRESS || errno == EALREADY) { + /* Poll for writability (use __real_poll in cooperative loop, + but do not block the kernel thread). We'll emulate blocking + by repeatedly polling with 0 timeout and yielding. */ + struct pollfd pfd; + pfd.fd = sockfd; + pfd.events = POLLOUT; + pfd.revents = 0; + + while (1) + { + int press = __real_poll(&pfd, 1, 0); + if (press > 0) { + /* socket reported an event; check if connect succeeded */ + int so_err = 0; + socklen_t len = sizeof(so_err); + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_err, &len) < 0) { + /* getsockopt failed; treat as error */ + return -1; + } + + if (so_err == 0) { + return 0; /* connected */ + } else { + errno = so_err; + return -1; + } + } else if (press == 0) { + /* no event yet -> yield cooperatively and retry */ + coop_wait(COOP_SYSCALLS_WAIT_MS); + continue; + } else { + /* press < 0 */ + if (errno == EINTR) { + continue; /* retry poll */ + } + + /* treat other errors as transient and yield */ + coop_wait(COOP_SYSCALLS_WAIT_MS); + continue; + } + } + } + + if (errno == EINTR) { + /* POSIX: connect may fail with EINTR; return -1 with errno==EINTR */ + return -1; + } + + /* other fatal errors */ + return -1; + } +} + +int __wrap_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) +{ + COOP_LOOP(__real_accept(sockfd, addr, addrlen)) +} + +int __wrap_close(int fd) +{ + while (1) + { + int ret = __real_close(fd); + if (ret == 0) { + return 0; + } else if (errno == EINTR) { + coop_wait(COOP_SYSCALLS_WAIT_MS); + continue; + } else { + return -1; + } + } +} + +int __wrap_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +{ + /* compute timeout in milliseconds; -1 => infinite */ + long timeout_ms = -1; + if (timeout != NULL) { + /* convert timeval -> ms, rounding up microseconds */ + timeout_ms = (long)timeout->tv_sec * 1000 + (timeout->tv_usec + 999) / 1000; + if (timeout_ms == 0) { + /* immediate poll: call real_select with provided timeout */ + return __real_select(nfds, readfds, writefds, exceptfds, timeout); + } + } + + long waited_ms = 0; + while (1) + { + /* nonblocking check */ + struct timeval zero_tv = {0, 0}; + int ret = __real_select(nfds, readfds, writefds, exceptfds, &zero_tv); + if (ret != 0) { + /* ret > 0 => ready; ret < 0 => error and errno set */ + return ret; + } + + /* no descriptors ready */ + if (timeout_ms == 0) { + return 0; /* expired */ + } + + /* check timeout expiration */ + if (timeout_ms > 0 && waited_ms >= timeout_ms) { + return 0; /* timeout expired */ + } + + /* yield cooperatively */ + coop_wait(COOP_SYSCALLS_WAIT_MS); + waited_ms += COOP_SYSCALLS_WAIT_MS; + } +} + +int __wrap_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + const struct timespec *timeout, const sigset_t *sigmask) +{ + /* convert timespec -> ms, -1 for infinite */ + long timeout_ms = -1; + if (timeout != NULL) { + timeout_ms = (long)timeout->tv_sec * 1000 + (timeout->tv_nsec + 999999) / 1000000; + if (timeout_ms == 0) { + /* immediate poll: call real_pselect with provided timeout */ + return __real_pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask); + } + } + + long waited_ms = 0; + while (1) + { + struct timespec zero_ts = {0, 0}; + int ret = __real_pselect(nfds, readfds, writefds, exceptfds, &zero_ts, sigmask); + if (ret != 0) { + return ret; + } + + if (timeout_ms == 0 || + (timeout_ms > 0 && waited_ms >= timeout_ms)) { + return 0; + } + + coop_wait(COOP_SYSCALLS_WAIT_MS); + waited_ms += COOP_SYSCALLS_WAIT_MS; + } +} + +int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + if (timeout == 0) { + /* immediate poll: delegate */ + return __real_poll(fds, nfds, 0); + } + + /* compute wait semantics */ + long timeout_ms = -1; + if (timeout > 0) { + timeout_ms = timeout; + } + + long waited_ms = 0; + + while (1) + { + int ret = __real_poll(fds, nfds, 0); + if (ret > 0) { + return ret; + } + else if (ret == 0) { + /* no event; check timeout */ + if (timeout_ms == 0 || + (timeout_ms > 0 && waited_ms >= timeout_ms)) { + return 0; /* expired */ + } + + /* yield and continue */ + coop_wait(COOP_SYSCALLS_WAIT_MS); + waited_ms += COOP_SYSCALLS_WAIT_MS; + continue; + } else { + /* ret < 0: error */ + if (errno == EINTR) { + continue; /* retry */ + } + + /* for other errors, yield and retry (transient) */ + coop_wait(COOP_SYSCALLS_WAIT_MS); + + if (timeout_ms > 0 && waited_ms >= timeout_ms) { + return -1; + } + waited_ms += COOP_SYSCALLS_WAIT_MS; + } + } +} + +unsigned int __wrap_sleep(unsigned int seconds) +{ + coop_wait(seconds * 1000); + return 0; +} + +int __wrap_usleep(useconds_t usec) +{ + coop_wait((usec / 1000)); + return 0; +} + +int __wrap_socket(int domain, int type, int protocol) +{ + int fd = __real_socket(domain, type, protocol); + coop_set_fd_nonblocking(fd); + return fd; +} + +int __wrap_socketpair(int domain, int type, int protocol, int sv[2]) +{ + int ret = __real_socketpair(domain, type, protocol, sv); + if (ret == 0) { + coop_set_fd_nonblocking(sv[0]); + coop_set_fd_nonblocking(sv[1]); + } + return ret; +} + +int __wrap_pipe(int fds[2]) +{ + int ret = __real_pipe(fds); + if (ret == 0) { + coop_set_fd_nonblocking(fds[0]); + coop_set_fd_nonblocking(fds[1]); + } + return ret; +} + +int __wrap_pipe2(int fds[2], int flags) +{ + int ret = __real_pipe2(fds, flags); + if (ret == 0) { + coop_set_fd_nonblocking(fds[0]); + coop_set_fd_nonblocking(fds[1]); + } + return ret; +} + +int __wrap_dup(int oldfd) +{ + int fd = __real_dup(oldfd); + coop_set_fd_nonblocking(fd); + return fd; +} + +int __wrap_dup2(int oldfd, int newfd) +{ + int fd = __real_dup2(oldfd, newfd); + coop_set_fd_nonblocking(fd); + return fd; +} + +int __wrap_open(const char *path, int flags, ...) +{ + va_list ap; + int fd; + + if (flags & O_CREAT) { + va_start(ap, flags); + mode_t mode = va_arg(ap, mode_t); + va_end(ap); + fd = __real_open(path, flags, mode); + } else { + fd = __real_open(path, flags); + } + coop_set_fd_nonblocking(fd); + return fd; +} + +void linux_port_coop_syscalls_init(void) +{ + coop_set_fd_nonblocking(STDIN_FILENO); + coop_set_fd_nonblocking(STDOUT_FILENO); + coop_set_fd_nonblocking(STDERR_FILENO); +} diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h new file mode 100644 index 00000000000..bbeb9a2ab80 --- /dev/null +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h @@ -0,0 +1,23 @@ +/* + * Cooperative syscalls subsystem for the Linux FreeRTOS simulator. + * + * This header exposes the public initialization API needed by the + * FreeRTOS Linux port. The subsystem provides blocking read()/write() + * for FreeRTOS tasks without stalling the cooperative scheduler by + * forwarding operations to a dedicated I/O worker thread. + * + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +void linux_port_coop_syscalls_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_utils.h b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_utils.h new file mode 100644 index 00000000000..fa56679619d --- /dev/null +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_utils.h @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct thread *thread_hdl; + +void linux_port_setup_backtrace_signal(void); +void linux_port_print_backtrace(void); +pthread_t linux_port_get_scheduled_task_pthread(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.c b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.c index 19d225ba627..f816518b477 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.c +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.c @@ -1,110 +1,94 @@ /* - * SPDX-FileCopyrightText: 2021 Amazon.com, Inc. or its affiliates + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * - * SPDX-License-Identifier: MIT + * SPDX-License-Identifier: Apache-2.0 */ -/* - * FreeRTOS Kernel V10.4.6 - * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * SPDX-License-Identifier: MIT - * - * 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 - * - */ - #include #include #include #include - #include "wait_for_event.h" -struct event +/*-----------------------------------------------------------*/ +/* Create a new event */ +event_t *event_create(void) { - pthread_mutex_t mutex; - pthread_cond_t cond; - bool event_triggered; -}; - -struct event * event_create(void) -{ - struct event * ev = malloc( sizeof( struct event ) ); + event_t * ev = malloc(sizeof(event_t)); assert(ev != NULL); + ev->event_triggered = false; - pthread_mutex_init( &ev->mutex, NULL ); - pthread_cond_init( &ev->cond, NULL ); + pthread_mutex_init(&ev->mutex, NULL); + pthread_cond_init(&ev->cond, NULL); + return ev; } -void event_delete( struct event * ev ) +/*-----------------------------------------------------------*/ +/* Delete an event */ +void event_delete(event_t *ev) { - pthread_mutex_destroy( &ev->mutex ); - pthread_cond_destroy( &ev->cond ); - free( ev ); + pthread_mutex_destroy(&ev->mutex); + pthread_cond_destroy(&ev->cond); + free(ev); } -bool event_wait( struct event * ev ) +/*-----------------------------------------------------------*/ +/* Wait for event indefinitely (cooperative blocking) */ +bool event_wait(event_t *ev) { - pthread_mutex_lock( &ev->mutex ); + pthread_mutex_lock(&ev->mutex); - while( ev->event_triggered == false ) + while (!ev->event_triggered) { - pthread_cond_wait( &ev->cond, &ev->mutex ); + pthread_cond_wait(&ev->cond, &ev->mutex); } ev->event_triggered = false; - pthread_mutex_unlock( &ev->mutex ); + pthread_mutex_unlock(&ev->mutex); return true; } -bool event_wait_timed( struct event * ev, - time_t ms ) + +/*-----------------------------------------------------------*/ +/* Wait for event with timeout (milliseconds) */ +bool event_wait_timed(event_t *ev, time_t ms) { struct timespec ts; int ret = 0; - clock_gettime( CLOCK_REALTIME, &ts ); - ts.tv_sec += ms / 1000; - ts.tv_nsec += ((ms % 1000) * 1000000); - pthread_mutex_lock( &ev->mutex ); + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_sec += ms / 1000; + ts.tv_nsec += (ms % 1000) * 1000000; - while( (ev->event_triggered == false) && (ret == 0) ) + /* Normalize tv_nsec in case it exceeds 1,000,000,000 */ + if (ts.tv_nsec >= 1000000000L) { + ts.tv_sec += ts.tv_nsec / 1000000000L; + ts.tv_nsec = ts.tv_nsec % 1000000000L; + } + + pthread_mutex_lock(&ev->mutex); + + while (!ev->event_triggered && ret == 0) { - ret = pthread_cond_timedwait( &ev->cond, &ev->mutex, &ts ); - - if( ( ret == -1 ) && ( errno == ETIMEDOUT ) ) + ret = pthread_cond_timedwait(&ev->cond, &ev->mutex, &ts); + if (ret == ETIMEDOUT) { + ev->event_triggered = false; + pthread_mutex_unlock(&ev->mutex); return false; } } ev->event_triggered = false; - pthread_mutex_unlock( &ev->mutex ); + pthread_mutex_unlock(&ev->mutex); return true; } -void event_signal( struct event * ev ) +/*-----------------------------------------------------------*/ +/* Signal / resume an event */ +void event_signal(event_t *ev) { - pthread_mutex_lock( &ev->mutex ); + pthread_mutex_lock(&ev->mutex); ev->event_triggered = true; - pthread_cond_signal( &ev->cond ); - pthread_mutex_unlock( &ev->mutex ); + pthread_cond_signal(&ev->cond); + pthread_mutex_unlock(&ev->mutex); } diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.h b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.h index 11ef9929de1..4b20c802a59 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.h +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/wait_for_event.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Amazon.com, Inc. or its affiliates + * SPDX-FileCopyrightText: 2021-2025 Amazon.com, Inc. or its affiliates * * SPDX-License-Identifier: MIT */ @@ -31,21 +31,67 @@ * */ -#ifndef _WAIT_FOR_EVENT_H_ -#define _WAIT_FOR_EVENT_H_ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif #include #include -struct event; - -struct event * event_create(void); -void event_delete( struct event * ); -bool event_wait( struct event * ev ); -bool event_wait_timed( struct event * ev, - time_t ms ); -void event_signal( struct event * ev ); +/** + * @brief + * + */ +typedef struct event +{ + pthread_mutex_t mutex; + pthread_cond_t cond; + bool event_triggered; +} event_t; +/** + * @brief + * + * @return event_t* + */ +event_t *event_create(void); -#endif /* ifndef _WAIT_FOR_EVENT_H_ */ +/** + * @brief + * + * @param ev + */ +void event_delete(event_t *ev); + +/** + * @brief + * + * @param ev + * @return true + * @return false + */ +bool event_wait(event_t *ev); + +/** + * @brief + * + * @param ev + * @param ms + * @return true + * @return false + */ +bool event_wait_timed(event_t *ev, time_t ms); + +/** + * @brief + * + * @param ev + */ +void event_signal(event_t *ev); + +#ifdef __cplusplus +} +#endif diff --git a/components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c b/components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c deleted file mode 100644 index 9d513909597..00000000000 --- a/components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c +++ /dev/null @@ -1,95 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include -#include -#include -#include - -/** This module addresses the FreeRTOS simulator's coexistence with Linux system calls from user apps. - * It wraps select so that it doesn't block the FreeRTOS task calling it, so that the - * scheduler will allow lower priority tasks to run. - * Without the wrapper, most components such as ESP-MQTT block lower priority tasks from running at all. - */ -typedef int (*select_func_t)(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval); - -int select(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval) -{ - static select_func_t s_real_select = NULL; - TickType_t end_ticks = portMAX_DELAY; - fd_set o_rfds, o_wfds, o_efds; - - // Lookup the select symbol - if (s_real_select == NULL) { - s_real_select = (select_func_t)dlsym(RTLD_NEXT, "select"); - assert(s_real_select); // abort() if we cannot locate the symbol - } - - // Calculate the end_ticks if a timeout is provided - if (tval != NULL) { - end_ticks = xTaskGetTickCount() + pdMS_TO_TICKS(tval->tv_sec * 1000 + tval->tv_usec / 1000); - } - - // Preserve the original FD sets as select call will change them - if (rfds) { - o_rfds = *rfds; - } - if (wfds) { - o_wfds = *wfds; - } - if (efds) { - o_efds = *efds; - } - - while (1) { - // Restore original FD sets before the select call - if (rfds) { - *rfds = o_rfds; - } - if (wfds) { - *wfds = o_wfds; - } - if (efds) { - *efds = o_efds; - } - - // Call select with a zero timeout to avoid blocking - struct timeval zero_tv = {0, 0}; - int ret = s_real_select(fd, rfds, wfds, efds, &zero_tv); - - // Return on success - if (ret > 0) { - return ret; - } - - // Return on any error except EINTR - if (ret == -1 && errno != EINTR) { - return ret; - } - - /** - * Sleep for maximum 10 tick(s) to allow other tasks to run. - * This can be any value greater than zero. - * 10 is a good trade-off between CPU time usage and timeout resolution. - */ - const TickType_t max_sleep_ticks = 10; - TickType_t sleep_ticks = max_sleep_ticks; - - if (tval != NULL) { - TickType_t now_ticks = xTaskGetTickCount(); - if (now_ticks >= end_ticks) { - errno = 0; - return 0; - } - // Sleep for the remaining time or a maximum of 10 tick - TickType_t remaining_ticks = end_ticks - now_ticks; - sleep_ticks = (remaining_ticks < max_sleep_ticks) ? remaining_ticks : max_sleep_ticks; - } - - vTaskDelay(sleep_ticks); - } -} diff --git a/components/freertos/esp_additions/include/esp_private/freertos_idf_additions_priv.h b/components/freertos/esp_additions/include/esp_private/freertos_idf_additions_priv.h index 316c332976d..48ca003c659 100644 --- a/components/freertos/esp_additions/include/esp_private/freertos_idf_additions_priv.h +++ b/components/freertos/esp_additions/include/esp_private/freertos_idf_additions_priv.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -29,7 +29,7 @@ * The following macros are convenience macros used to account for different * thread safety behavior between single-core and SMP in ESP-IDF FreeRTOS. * - * For thread saftey... + * For thread safety... * * - Single-core will use the following for thread safety (depending on situation) * - `vTaskSuspendAll()`/`xTaskResumeAll()` for non-deterministic operations @@ -119,12 +119,24 @@ } /* Macros that enter/exit a critical section only when building for SMP */ +#if !defined prvENTER_CRITICAL_SMP_ONLY #define prvENTER_CRITICAL_SMP_ONLY( pxLock ) +#endif +#if !defined prvEXIT_CRITICAL_SMP_ONLY #define prvEXIT_CRITICAL_SMP_ONLY( pxLock ) +#endif +#if !defined prvENTER_CRITICAL_ISR_SMP_ONLY #define prvENTER_CRITICAL_ISR_SMP_ONLY( pxLock ) +#endif +#if !defined prvEXIT_CRITICAL_ISR_SMP_ONLY #define prvEXIT_CRITICAL_ISR_SMP_ONLY( pxLock ) +#endif +#if !defined prvENTER_CRITICAL_SAFE_SMP_ONLY #define prvENTER_CRITICAL_SAFE_SMP_ONLY( pxLock ) +#endif +#if !defined prvEXIT_CRITICAL_SAFE_SMP_ONLY #define prvEXIT_CRITICAL_SAFE_SMP_ONLY( pxLock ) +#endif /* Macros that enter/exit a critical section only when building for single-core */ #define prvENTER_CRITICAL_SC_ONLY( pxLock ) taskENTER_CRITICAL( pxLock ) diff --git a/docs/en/api-guides/host-apps.rst b/docs/en/api-guides/host-apps.rst index c1c5eec4b73..1d4453dd562 100644 --- a/docs/en/api-guides/host-apps.rst +++ b/docs/en/api-guides/host-apps.rst @@ -42,25 +42,29 @@ This approach uses the `CMock `_ framework POSIX/Linux Simulator Approach ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The `FreeRTOS POSIX/Linux simulator `_ is available on ESP-IDF as a preview target already. This simulator allows ESP-IDF components to be implemented on the host, making them accessible to ESP-IDF applications when running on host. Currently, only a limited number of components are ready to be built on Linux. Furthermore, the functionality of each component ported to Linux may also be limited or different compared to the functionality when building that component for a chip target. For more information about whether the desired components are supported on Linux, please refer to :ref:`component-linux-mock-support`. +The FreeRTOS Linux simulator is available on ESP-IDF as a preview target. This simulator runs each FreeRTOS task as a POSIX thread (pthread), allowing ESP-IDF components to be implemented on the host and making them accessible to ESP-IDF applications when running on host. Currently, not all components are supported on Linux. Furthermore, the functionality of each component ported to Linux may also be limited or different compared to the functionality when building that component for a chip target. For more information about whether the desired components are supported on Linux, please refer to :ref:`component-linux-mock-support`. -Note that this simulator relies heavily on POSIX signals and signal handlers to control and interrupt threads. Hence, it has the following *limitations*: +**Scheduling Model** + +The Linux simulator uses *cooperative preemption*: a dedicated scheduler thread increments the tick counter at regular intervals (configured by ``configTICK_RATE_HZ``) and performs context switches by blocking and unblocking pthreads. Because context switches can only occur when a task interacts with the FreeRTOS kernel, the scheduler is not truly preemptive at the instruction level as it would be on real hardware. This means: .. list:: - - Functions that are not *async-signal-safe*, e.g. ``printf()``, should be avoided. In particular, calling them from different tasks with different priority can lead to crashes and deadlocks. - - Calling any FreeRTOS primitives from threads not created by FreeRTOS API functions is forbidden. - - FreeRTOS tasks using any native blocking/waiting mechanism (e.g., ``select()``), may be perceived as *ready* by the simulated FreeRTOS scheduler and therefore may be scheduled, even though they are actually blocked. This is because the simulated FreeRTOS scheduler only recognizes tasks blocked on any FreeRTOS API as *waiting*. - - APIs that may be interrupted by signals will continually receive the signals simulating FreeRTOS tick interrupts when invoked from a running simulated FreeRTOS task. Consequently, code that calls these APIs should be designed to handle potential interrupting signals or the API needs to be wrapped by the linker. + - A task that busy-loops without calling any FreeRTOS API will never be preempted. Preemption can only occur when a task interacts with the FreeRTOS kernel, such as entering or exiting a critical section, yielding, or calling any API that may block (e.g., ``vTaskDelay()``, ``xQueueReceive()``). + - Timing granularity is limited to the tick period (e.g., 10 ms at 100 Hz). + - Critical sections are protected by mutexes rather than by disabling interrupts. -Since these limitations are not very practical, in particular for testing and development, we are currently evaluating if we can find a better solution for running ESP-IDF applications on the host machine. +**Limitations** -Note furthermore that if you use the ESP-IDF FreeRTOS mock component (``tools/mocks/freertos``), these limitations do not apply. But that mock component will not do any scheduling, either. +.. list:: + - Calling FreeRTOS primitives that may block or yield (e.g., queues, semaphores, task notifications, ``vTaskDelay()``) from threads not created by FreeRTOS API functions is not supported. However, critical sections (``taskENTER_CRITICAL`` / ``taskEXIT_CRITICAL``) can be used from any thread. + +Note that if you use the ESP-IDF FreeRTOS mock component (``tools/mocks/freertos``), these limitations do not apply. But that mock component will not do any scheduling, either. .. only:: not esp32p4 and not esp32h4 and not esp32s31 .. note:: - The FreeRTOS POSIX/Linux simulator allows configuring the :ref:`amazon_smp_freertos` version. However, the simulation still runs in single-core mode. The main reason allowing Amazon SMP FreeRTOS is to provide API compatibility with ESP-IDF applications written for Amazon SMP FreeRTOS. + The FreeRTOS POSIX/Linux simulator allows configuring the :ref:`amazon_smp_freertos` version. However, the simulation still runs in single-core mode (``configNUM_CORES = 1``). The main reason allowing Amazon SMP FreeRTOS is to provide API compatibility with ESP-IDF applications written for Amazon SMP FreeRTOS. Requirements for Using Mocks ---------------------------- diff --git a/tools/mocks/startup/CMakeLists.txt b/tools/mocks/startup/CMakeLists.txt index 1baae46a9c2..c404da8a52a 100644 --- a/tools/mocks/startup/CMakeLists.txt +++ b/tools/mocks/startup/CMakeLists.txt @@ -1,3 +1,7 @@ # This is a manual mock that supplies `main()` if FreeRTOS is mocked idf_component_register(SRCS "startup_mock.c" REQUIRES main esp_event) + +# Prevent esp_system from providing main() since this component provides it +idf_component_get_property(esp_system_lib esp_system COMPONENT_LIB) +target_compile_definitions(${esp_system_lib} PRIVATE ESP_SYSTEM_LINUX_NO_MAIN) diff --git a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_eTaskGetState.c b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_eTaskGetState.c index 98e598c897e..15d982db1c1 100644 --- a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_eTaskGetState.c +++ b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_eTaskGetState.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -43,7 +43,7 @@ static void loop_task(void *arg) // Short delay to allow other created tasks to run vTaskDelay(2); while (1) { - ; + vTaskDelay(1); } } diff --git a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_freertos_task_delete.c b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_freertos_task_delete.c index 9e2df436fd0..a8af82946b1 100644 --- a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_freertos_task_delete.c +++ b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_freertos_task_delete.c @@ -76,7 +76,7 @@ TEST_CASE("FreeRTOS Delete Blocked Tasks", "[freertos]") (1000 iterations takes about 9 seconds on ESP32 dual core) */ - for(unsigned iter = 0; iter < 1000; iter++) { + for(unsigned iter = 0; iter < 100; iter++) { // Create everything SemaphoreHandle_t sem = xSemaphoreCreateMutex(); for(unsigned i = 0; i < configNUM_CORES + 1; i++) { @@ -95,6 +95,7 @@ TEST_CASE("FreeRTOS Delete Blocked Tasks", "[freertos]") vTaskDelete(blocking_tasks[i]); params[i].deleted = true; } + vTaskDelay(4); // Yield to the idle task for cleanup vSemaphoreDelete(sem); diff --git a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_preemption.c b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_preemption.c index 79d2693308e..33c8ba35a74 100644 --- a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_preemption.c +++ b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_preemption.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -20,62 +20,58 @@ #include "unity.h" #include "sdkconfig.h" -static volatile bool trigger; -static volatile bool flag; +/* + * Test: A lower-priority task must be able to run and send data to a queue + * when the higher-priority task yields (blocks). + * + * Pattern: The lower-priority task blocks on a semaphore until told to proceed, + * then sends to a queue. The higher-priority task gives the semaphore and + * immediately blocks on the queue receive. + */ -#ifndef CONFIG_FREERTOS_SMP -#define MAX_YIELD_COUNT 10000 -#else -//TODO: IDF-5081 -#define MAX_YIELD_COUNT 17000 -#endif // CONFIG_FREERTOS_SMP - - -/* Task: - - Waits for 'trigger' variable to be set - - Reads the cycle count on this CPU - - Pushes it into a queue supplied as a param - - Busy-waits until the main task terminates it -*/ static void task_send_to_queue(void *param) { - QueueHandle_t queue = (QueueHandle_t) param; - uint32_t ccount; + void **args = (void **)param; + QueueHandle_t queue = (QueueHandle_t)args[0]; + SemaphoreHandle_t sem = (SemaphoreHandle_t)args[1]; - while(!trigger) {} + /* Block until the main task tells us to go */ + xSemaphoreTake(sem, portMAX_DELAY); - ccount = 0; - flag = true; - xQueueSendToBack(queue, &ccount, 0); - /* This is to ensure that higher priority task - won't wake anyhow, due to this task terminating. + uint32_t value = 42; + xQueueSendToBack(queue, &value, 0); - The task runs until terminated by the main task. - */ - while(1) {} + /* Stay alive until deleted */ + vTaskSuspend(NULL); } TEST_CASE("Yield from lower priority task, same CPU", "[freertos]") { - /* Do this 3 times, mostly for the benchmark value - the first - run includes a cache miss so uses more cycles than it should. */ for (int i = 0; i < 3; i++) { TaskHandle_t sender_task; QueueHandle_t queue = xQueueCreate(1, sizeof(uint32_t)); - flag = false; - trigger = false; + SemaphoreHandle_t sem = xSemaphoreCreateBinary(); + void *args[2] = { queue, sem }; - /* "yield" task sits on our CPU, lower priority to us */ - xTaskCreatePinnedToCore(task_send_to_queue, "YIELD", 2048, (void *)queue, CONFIG_UNITY_FREERTOS_PRIORITY - 1, &sender_task, CONFIG_UNITY_FREERTOS_CPU); + /* Lower-priority task blocks on the semaphore */ + xTaskCreatePinnedToCore(task_send_to_queue, "YIELD", 2048, args, + CONFIG_UNITY_FREERTOS_PRIORITY - 1, &sender_task, + CONFIG_UNITY_FREERTOS_CPU); - vTaskDelay(1); /* make sure everything is set up */ - trigger = true; + /* Let the task start and block on the semaphore */ + vTaskDelay(pdMS_TO_TICKS(10)); - uint32_t yield_ccount; - TEST_ASSERT( xQueueReceive(queue, &yield_ccount, 100 / portTICK_PERIOD_MS) ); - TEST_ASSERT( flag ); + /* Give the semaphore — the lower-priority task won't run yet because + * we (higher priority) are still ready. Then block on queue receive, + * which yields the CPU to the lower-priority task. */ + xSemaphoreGive(sem); + + uint32_t received; + TEST_ASSERT(xQueueReceive(queue, &received, pdMS_TO_TICKS(100))); + TEST_ASSERT_EQUAL(42, received); vTaskDelete(sender_task); vQueueDelete(queue); + vSemaphoreDelete(sem); } } diff --git a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_priority_scheduling.c b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_priority_scheduling.c index 18fb69f5ef3..0cd6f3e8711 100644 --- a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_priority_scheduling.c +++ b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_priority_scheduling.c @@ -1,11 +1,12 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" #include "unity.h" #include "portTestMacro.h" @@ -17,64 +18,88 @@ Test Priority Scheduling (Single Core) Purpose: - Test that the single-core scheduler always schedules the highest priority ready task Procedure: - - Raise the unityTask priority to (configMAX_PRIORITIES - 1) - - unityTask creates the following lower priority tasks + - unityTask (highest priority) creates a binary semaphore (initially empty) + - unityTask creates two lower-priority tasks that both block on the semaphore: - task_A (configMAX_PRIORITIES - 2) - task_B (configMAX_PRIORITIES - 3) - - UnityTask blocks for a short period of time to allow task_A to run - - Clean up and restore unityTask's original priority + - unityTask delays to let both tasks start and block on the semaphore + - unityTask gives the semaphore once — FreeRTOS wakes the highest-priority + waiter (task_A), which writes its ID to a shared variable and signals done + - unityTask verifies the shared variable holds task_A's ID Expected: - - task_A should run after unityTask blocks - - task_B should never have run + - task_A (higher priority) wins the semaphore race, not task_B */ #if ( configNUM_CORES == 1 ) -#define UNITY_TASK_DELAY_TICKS 10 +#define PRIO_TASK_A_ID 1 +#define PRIO_TASK_B_ID 2 -static BaseType_t task_A_ran; -static BaseType_t task_B_ran; +static volatile int s_prio_winner; +static SemaphoreHandle_t s_race_sem; +static SemaphoreHandle_t s_done_sem; static void task_A(void *arg) { - task_A_ran = pdTRUE; - /* Keeping spinning to prevent the lower priority task_B from running */ - while (1) { - ; - } + /* Block until the race semaphore is given */ + xSemaphoreTake(s_race_sem, portMAX_DELAY); + /* Higher priority: should be woken first */ + s_prio_winner = PRIO_TASK_A_ID; + xSemaphoreGive(s_done_sem); + vTaskSuspend(NULL); } static void task_B(void *arg) { - /* The following should never run due to task_B having a lower priority */ - task_B_ran = pdTRUE; - while (1) { - ; - } + /* Block until the race semaphore is given */ + xSemaphoreTake(s_race_sem, portMAX_DELAY); + /* Lower priority: should NOT be woken first */ + s_prio_winner = PRIO_TASK_B_ID; + xSemaphoreGive(s_done_sem); + vTaskSuspend(NULL); } TEST_CASE("Tasks: Test priority scheduling", "[freertos]") { TaskHandle_t task_A_handle; TaskHandle_t task_B_handle; - task_A_ran = pdFALSE; - task_B_ran = pdFALSE; + s_prio_winner = 0; + + /* Binary semaphores start empty — both tasks will block on take */ + s_race_sem = xSemaphoreCreateBinary(); + s_done_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(s_race_sem); + TEST_ASSERT_NOT_NULL(s_done_sem); /* Raise the priority of the unityTask */ vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1); - /* Create task_A and task_B */ - xTaskCreate(task_A, "task_A", configTEST_DEFAULT_STACK_SIZE, (void *)xTaskGetCurrentTaskHandle(), configMAX_PRIORITIES - 2, &task_A_handle); - xTaskCreate(task_B, "task_B", configTEST_DEFAULT_STACK_SIZE, (void *)xTaskGetCurrentTaskHandle(), configMAX_PRIORITIES - 3, &task_B_handle); - /* Block to allow task_A to be scheduled */ - vTaskDelay(UNITY_TASK_DELAY_TICKS); + /* Create task_A (higher prio) and task_B (lower prio) */ + xTaskCreate(task_A, "task_A", configTEST_DEFAULT_STACK_SIZE, NULL, + configMAX_PRIORITIES - 2, &task_A_handle); + xTaskCreate(task_B, "task_B", configTEST_DEFAULT_STACK_SIZE, NULL, + configMAX_PRIORITIES - 3, &task_B_handle); - /* Test that only task_A has run */ - TEST_ASSERT_EQUAL(pdTRUE, task_A_ran); - TEST_ASSERT_EQUAL(pdFALSE, task_B_ran); + /* Let both tasks start and block on s_race_sem */ + vTaskDelay(pdMS_TO_TICKS(50)); + /* Give the semaphore once. FreeRTOS wakes the highest-priority waiter + * (task_A). Since unityTask is still higher priority, task_A won't + * actually run until we block below. */ + xSemaphoreGive(s_race_sem); + + /* Block waiting for the winner to signal. This lets task_A run. */ + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done_sem, pdMS_TO_TICKS(200))); + + /* The higher-priority task_A should have won the race */ + TEST_ASSERT_EQUAL(PRIO_TASK_A_ID, s_prio_winner); + + /* Cleanup */ vTaskDelete(task_A_handle); vTaskDelete(task_B_handle); + vSemaphoreDelete(s_race_sem); + vSemaphoreDelete(s_done_sem); + /* Restore the priority of the unityTask */ vTaskPrioritySet(NULL, configTEST_UNITY_TASK_PRIORITY); } diff --git a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_task_priorities.c b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_task_priorities.c index 020761348c7..a65f0174c07 100644 --- a/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_task_priorities.c +++ b/tools/test_apps/linux_compatible/linux_freertos/components/kernel_tests/tasks/test_task_priorities.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -14,74 +14,89 @@ #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/semphr.h" #include "unity.h" +/* + * A race task blocks on a shared binary semaphore. When the semaphore is given, + * FreeRTOS wakes the highest-priority waiter, which writes its ID to s_winner + * and signals s_done_sem. The task then suspends itself (it is single-shot). + */ -static void counter_task(void *param) +#define TASK_ID_A 1 +#define TASK_ID_B 2 + +static volatile int s_winner; +static SemaphoreHandle_t s_race_sem; +static SemaphoreHandle_t s_done_sem; + +static void race_task(void *arg) { - volatile uint32_t *counter = (volatile uint32_t *)param; - while (1) { - (*counter)++; - } + int my_id = (int)(uintptr_t)arg; + xSemaphoreTake(s_race_sem, portMAX_DELAY); + s_winner = my_id; + xSemaphoreGive(s_done_sem); + vTaskSuspend(NULL); } - TEST_CASE("Get/Set Priorities", "[freertos]") { - /* Two tasks per processor */ - TaskHandle_t tasks[configNUM_CORES][2] = { 0 }; - unsigned volatile counters[configNUM_CORES][2] = { 0 }; + TaskHandle_t task_a, task_b; + s_race_sem = xSemaphoreCreateBinary(); + s_done_sem = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(s_race_sem); + TEST_ASSERT_NOT_NULL(s_done_sem); + + /* Verify unity task's own priority */ TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY, uxTaskPriorityGet(NULL)); - /* create a matrix of counter tasks on each core */ - for (int cpu = 0; cpu < configNUM_CORES; cpu++) { - for (int task = 0; task < 2; task++) { - xTaskCreatePinnedToCore(counter_task, "count", 2048, (void *)&(counters[cpu][task]), CONFIG_UNITY_FREERTOS_PRIORITY - task, &(tasks[cpu][task]), cpu); - } - } + /* --- Round 1: task_a has higher priority, should win the race --- */ + xTaskCreatePinnedToCore(race_task, "a", 2048, (void *)(uintptr_t)TASK_ID_A, + CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_a, 0); + xTaskCreatePinnedToCore(race_task, "b", 2048, (void *)(uintptr_t)TASK_ID_B, + CONFIG_UNITY_FREERTOS_PRIORITY - 2, &task_b, 0); - /* check they were created with the expected priorities */ - for (int cpu = 0; cpu < configNUM_CORES; cpu++) { - for (int task = 0; task < 2; task++) { - TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - task, uxTaskPriorityGet(tasks[cpu][task])); - } - } + /* Verify created priorities */ + TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 1, uxTaskPriorityGet(task_a)); + TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 2, uxTaskPriorityGet(task_b)); - vTaskDelay(10); + /* Let both tasks block on s_race_sem */ + vTaskDelay(pdMS_TO_TICKS(50)); - /* at this point, only the higher priority tasks (first index) should be counting */ - for (int cpu = 0; cpu < configNUM_CORES; cpu++) { - TEST_ASSERT_NOT_EQUAL(0, counters[cpu][0]); - TEST_ASSERT_EQUAL(0, counters[cpu][1]); - } + s_winner = 0; + xSemaphoreGive(s_race_sem); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done_sem, pdMS_TO_TICKS(200))); + TEST_ASSERT_EQUAL(TASK_ID_A, s_winner); - /* swap priorities! */ - for (int cpu = 0; cpu < configNUM_CORES; cpu++) { - vTaskPrioritySet(tasks[cpu][0], CONFIG_UNITY_FREERTOS_PRIORITY - 1); - vTaskPrioritySet(tasks[cpu][1], CONFIG_UNITY_FREERTOS_PRIORITY); - } + vTaskDelete(task_a); + vTaskDelete(task_b); - /* check priorities have swapped... */ - for (int cpu = 0; cpu < configNUM_CORES; cpu++) { - TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY -1, uxTaskPriorityGet(tasks[cpu][0])); - TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY, uxTaskPriorityGet(tasks[cpu][1])); - } + /* --- Test vTaskPrioritySet API --- */ + xTaskCreatePinnedToCore(race_task, "p", 2048, (void *)(uintptr_t)TASK_ID_A, + CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_a, 0); + TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 1, uxTaskPriorityGet(task_a)); + vTaskPrioritySet(task_a, CONFIG_UNITY_FREERTOS_PRIORITY - 2); + TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 2, uxTaskPriorityGet(task_a)); + vTaskDelete(task_a); - /* check the tasks which are counting have also swapped now... */ - for (int cpu = 0; cpu < configNUM_CORES; cpu++) { - unsigned old_counters[2]; - old_counters[0] = counters[cpu][0]; - old_counters[1] = counters[cpu][1]; - vTaskDelay(10); - TEST_ASSERT_EQUAL(old_counters[0], counters[cpu][0]); - TEST_ASSERT_NOT_EQUAL(old_counters[1], counters[cpu][1]); - } + /* --- Round 2: swap priorities — task_b now higher, should win --- */ + xTaskCreatePinnedToCore(race_task, "a2", 2048, (void *)(uintptr_t)TASK_ID_A, + CONFIG_UNITY_FREERTOS_PRIORITY - 2, &task_a, 0); + xTaskCreatePinnedToCore(race_task, "b2", 2048, (void *)(uintptr_t)TASK_ID_B, + CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_b, 0); - /* clean up */ - for (int cpu = 0; cpu < configNUM_CORES; cpu++) { - for (int task = 0; task < 2; task++) { - vTaskDelete(tasks[cpu][task]); - } - } + /* Let both tasks block on s_race_sem */ + vTaskDelay(pdMS_TO_TICKS(50)); + + s_winner = 0; + xSemaphoreGive(s_race_sem); + TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done_sem, pdMS_TO_TICKS(200))); + TEST_ASSERT_EQUAL(TASK_ID_B, s_winner); + + /* Cleanup */ + vTaskDelete(task_a); + vTaskDelete(task_b); + vSemaphoreDelete(s_race_sem); + vSemaphoreDelete(s_done_sem); } diff --git a/tools/test_apps/linux_compatible/linux_freertos/sdkconfig.defaults b/tools/test_apps/linux_compatible/linux_freertos/sdkconfig.defaults index 9bd030bcd7b..843d0aaf2ec 100644 --- a/tools/test_apps/linux_compatible/linux_freertos/sdkconfig.defaults +++ b/tools/test_apps/linux_compatible/linux_freertos/sdkconfig.defaults @@ -1,2 +1,2 @@ CONFIG_IDF_TARGET="linux" -CONFIG_FREERTOS_SMP=y +CONFIG_FREERTOS_SMP=n