From 78420f261434fce831c7f72b9baaf529078fda20 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Fri, 22 May 2026 12:15:58 +0200 Subject: [PATCH 1/4] 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 From 9835daba70b0796cf3bcda7e89891866589cb58b Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Wed, 11 Mar 2026 12:30:19 +0100 Subject: [PATCH 2/4] feat(freertos/vfs): add Linux cooperative syscall dispatch with VFS FD registration Rework the syscall interposition architecture so FreeRTOS works standalone (without VFS) and VFS optionally overrides with strong symbols. All kernel FDs are registered with VFS via esp_vfs_register_fd_with_local_fd, so the application only sees VFS-allocated FD numbers, preventing numerical collisions between kernel FDs and VFS-internal FD slots. FreeRTOS side: - Create linux_port_coop_internal.h with LINUX_COOP_IO_LOOP, LINUX_COOP_RESOLVE, linux_coop_yield and FD state table functions - Export cooperative I/O primitives (freertos_linux_coop_read/write/ open/close/fcntl/select/pread/pwrite/readv/writev/recv/send/ recvfrom/sendto/recvmsg/sendmsg/connect/accept/pselect/poll/ socket/socketpair/pipe/pipe2/dup/dup2/syscalls_init) - Define weak POSIX symbols calling through to the cooperative primitives; keep nanosleep/sleep/usleep as strong (not FD-related) - Refactor freertos_linux_coop_syscalls.h into a pure public API header VFS side: - Register the Linux host FS with esp_vfs_register_fs_with_id() as a proper VFS driver; register stdin/stdout/stderr at init (priority 99) - Rewrite vfs_linux.c with strong POSIX symbols dispatching through esp_vfs_* - Add strong overrides for FD-creating syscalls (open, pipe, pipe2, socket, socketpair, dup, dup2, accept) that register returned FDs with VFS - Add strong overrides for FD-translating syscalls (readv, writev, recv, send, recvfrom, sendto, recvmsg, sendmsg, connect, pselect, poll) that translate VFS FD to kernel FD - Delete vfs_coop_syscalls.c (absorbed into FreeRTOS weak + VFS strong) - Update CMakeLists.txt: remove vfs_coop_syscalls.c, add linker hook --- components/esp_system/system_init_fn.txt | 3 + components/freertos/CMakeLists.txt | 49 +- .../freertos_linux_coop_syscalls.h | 314 +++++++ .../FreeRTOS-Kernel/portable/linux/port.c | 1 + .../linux/utils/linux_port_coop_internal.h | 145 ++++ .../linux/utils/linux_port_coop_syscalls.c | 807 +++++++++++++----- .../linux/utils/linux_port_coop_syscalls.h | 23 - components/vfs/CMakeLists.txt | 17 +- .../vfs/private_include/esp_vfs_private.h | 2 +- components/vfs/vfs.c | 2 +- components/vfs/vfs_linux.c | 607 +++++++++++-- components/vfs/vfs_linux_default_coop.c | 158 ++++ tools/mocks/freertos/CMakeLists.txt | 4 +- 13 files changed, 1747 insertions(+), 385 deletions(-) create mode 100644 components/freertos/FreeRTOS-Kernel/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h create mode 100644 components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_internal.h delete mode 100644 components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h create mode 100644 components/vfs/vfs_linux_default_coop.c diff --git a/components/esp_system/system_init_fn.txt b/components/esp_system/system_init_fn.txt index 0b9c8d8f736..252598df81c 100644 --- a/components/esp_system/system_init_fn.txt +++ b/components/esp_system/system_init_fn.txt @@ -24,6 +24,9 @@ CORE: 10: init_show_cpu_freq in components/esp_system/startup_funcs.c on BIT(0) CORE: 20: init_show_app_info in components/esp_app_format/esp_app_desc.c on BIT(0) CORE: 21: init_efuse_show_app_info in components/efuse/src/esp_efuse_startup.c on BIT(0) +# Set the standard stream to non blocking and register the default vfs +CORE: 99: init_vfs_linux_coop in components/vfs/vfs_linux_default_coop.c on BIT(0) + # Initialize heap allocator. WARNING: This *needs* to happen *after* the app cpu has booted. # If the heap allocator is initialized first, it will put free memory linked list items into # memory also used by the ROM. Starting the app cpu will let its ROM initialize that memory, diff --git a/components/freertos/CMakeLists.txt b/components/freertos/CMakeLists.txt index 3d91ec408a0..d9017f3c15d 100644 --- a/components/freertos/CMakeLists.txt +++ b/components/freertos/CMakeLists.txt @@ -104,16 +104,6 @@ list(APPEND srcs "esp_additions/idf_additions_event_groups.c" "esp_additions/idf_additions.c") -if(arch STREQUAL "linux") - # Check if we need to address the FreeRTOS EINTR coexistence with linux system calls if we're building without - # lwIP enabled, we need to use linux system select which will receive EINTR event on every FreeRTOS interrupt, we - # workaround this problem by wrapping select() to bypass and silence the EINTR events - set(BYPASS_EINTR_ISSUE 0) - if(NOT CONFIG_LWIP_ENABLE) - set(BYPASS_EINTR_ISSUE 1) - endif() -endif() - # ------------------------------------------------ Set Public Includes ------------------------------------------------- # Add common public include directories @@ -186,42 +176,9 @@ idf_component_register(SRCS ${srcs} if(arch STREQUAL "linux") target_compile_definitions(${COMPONENT_LIB} PUBLIC "projCOVERAGE_TEST=0") target_link_libraries(${COMPONENT_LIB} PUBLIC pthread) - if(BYPASS_EINTR_ISSUE) - 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() + # dl is needed for dlsym(RTLD_NEXT, ...) used by the cooperative syscall + # interposition layer (linux_port_coop_syscalls.c) + target_link_libraries(${COMPONENT_LIB} PRIVATE dl) # Disable strict prototype warnings in upstream code # (struct event * event_create() is missing 'void') diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h b/components/freertos/FreeRTOS-Kernel/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h new file mode 100644 index 00000000000..098129cf737 --- /dev/null +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h @@ -0,0 +1,314 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Public API for the FreeRTOS Linux cooperative syscall layer. + * + * This header is included by the VFS component (and any other component + * that needs to call the cooperative I/O functions directly). + * + * Internal helpers (LINUX_COOP_IO_LOOP, LINUX_COOP_RESOLVE, FD state + * table functions, etc.) live in linux_port_coop_internal.h. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Cooperative read — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to read from. + * @param buf Destination buffer. + * @param count Maximum number of bytes to read. + * @return Number of bytes read on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_read(int fd, void *buf, size_t count); + +/** + * @brief Cooperative write — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to write to. + * @param buf Source buffer. + * @param count Number of bytes to write. + * @return Number of bytes written on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_write(int fd, const void *buf, size_t count); + +/** + * @brief Cooperative pread — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to read from. + * @param buf Destination buffer. + * @param count Maximum number of bytes to read. + * @param offset File offset to read from. + * @return Number of bytes read on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_pread(int fd, void *buf, size_t count, off_t offset); + +/** + * @brief Cooperative pwrite — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to write to. + * @param buf Source buffer. + * @param count Number of bytes to write. + * @param offset File offset to write at. + * @return Number of bytes written on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_pwrite(int fd, const void *buf, size_t count, off_t offset); + +/** + * @brief Cooperative readv — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to read from. + * @param iov Array of iovec structures describing the buffers. + * @param iovcnt Number of elements in the iov array. + * @return Number of bytes read on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_readv(int fd, const struct iovec *iov, int iovcnt); + +/** + * @brief Cooperative writev — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to write to. + * @param iov Array of iovec structures describing the buffers. + * @param iovcnt Number of elements in the iov array. + * @return Number of bytes written on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_writev(int fd, const struct iovec *iov, int iovcnt); + +/** + * @brief Cooperative recv — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param buf Destination buffer. + * @param len Maximum number of bytes to receive. + * @param flags recv flags (MSG_PEEK, etc.). + * @return Number of bytes received on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_recv(int sockfd, void *buf, size_t len, int flags); + +/** + * @brief Cooperative send — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param buf Source buffer. + * @param len Number of bytes to send. + * @param flags send flags (MSG_NOSIGNAL, etc.). + * @return Number of bytes sent on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_send(int sockfd, const void *buf, size_t len, int flags); + +/** + * @brief Cooperative recvfrom — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param buf Destination buffer. + * @param len Maximum number of bytes to receive. + * @param flags recv flags. + * @param src_addr Source address (may be NULL). + * @param addrlen Length of source address (may be NULL). + * @return Number of bytes received on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen); + +/** + * @brief Cooperative sendto — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param buf Source buffer. + * @param len Number of bytes to send. + * @param flags send flags. + * @param dest_addr Destination address. + * @param addrlen Length of destination address. + * @return Number of bytes sent on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen); + +/** + * @brief Cooperative recvmsg — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param msg Message header describing buffers and ancillary data. + * @param flags recv flags. + * @return Number of bytes received on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_recvmsg(int sockfd, struct msghdr *msg, int flags); + +/** + * @brief Cooperative sendmsg — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param msg Message header describing buffers and ancillary data. + * @param flags send flags. + * @return Number of bytes sent on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_sendmsg(int sockfd, const struct msghdr *msg, int flags); + +/** + * @brief Cooperative connect — retries on EINPROGRESS with cooperative yield. + * + * @param sockfd Socket file descriptor. + * @param addr Destination address. + * @param addrlen Length of destination address. + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +/** + * @brief Cooperative accept — retries on EAGAIN when user-visible mode is blocking. + * + * Sets the accepted socket to non-blocking and starts tracking it. + * + * @param sockfd Listening socket file descriptor. + * @param addr Peer address (may be NULL). + * @param addrlen Length of peer address (may be NULL). + * @return New socket file descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + +/** + * @brief Cooperative open — sets the new FD to non-blocking and tracks it. + * + * @param path Path of the file to open. + * @param flags Open flags (O_RDONLY, O_WRONLY, O_CREAT, etc.). + * @param mode File creation mode (used when O_CREAT is set). + * @return File descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_open(const char *path, int flags, int mode); + +/** + * @brief Cooperative close — untracks the FD, retries on EINTR. + * + * @param fd File descriptor to close. + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_close(int fd); + +/** + * @brief Cooperative fcntl — intercepts F_GETFL/F_SETFL for shadow mode. + * + * @param fd File descriptor. + * @param cmd fcntl command (F_GETFL, F_SETFL, etc.). + * @param arg Command argument. + * @return Command-dependent value on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_fcntl(int fd, int cmd, int arg); + +/** + * @brief Cooperative socket — sets the new socket to non-blocking and tracks it. + * + * @param domain Communication domain (AF_INET, AF_UNIX, etc.). + * @param type Socket type (SOCK_STREAM, SOCK_DGRAM, etc.). + * @param protocol Protocol number (0 for default). + * @return Socket file descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_socket(int domain, int type, int protocol); + +/** + * @brief Cooperative socketpair — sets both sockets to non-blocking and tracks them. + * + * @param domain Communication domain. + * @param type Socket type. + * @param protocol Protocol number. + * @param sv Array of two ints to receive the file descriptors. + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_socketpair(int domain, int type, int protocol, int *sv); + +/** + * @brief Cooperative pipe — sets both ends to non-blocking and tracks them. + * + * @param fds Array of two ints: fds[0] is the read end, fds[1] is the write end. + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_pipe(int *fds); + +/** + * @brief Cooperative pipe2 — sets both ends to non-blocking and tracks them. + * + * @param fds Array of two ints: fds[0] is the read end, fds[1] is the write end. + * @param flags Pipe flags (O_CLOEXEC, O_NONBLOCK, etc.). + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_pipe2(int *fds, int flags); + +/** + * @brief Cooperative dup — duplicates a FD, sets the new FD to non-blocking and tracks it. + * + * @param oldfd File descriptor to duplicate. + * @return New file descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_dup(int oldfd); + +/** + * @brief Cooperative dup2 — duplicates a FD to a specific number, sets non-blocking and tracks it. + * + * @param oldfd File descriptor to duplicate. + * @param newfd Desired file descriptor number. + * @return New file descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_dup2(int oldfd, int newfd); + +/** + * @brief Cooperative select — polls with cooperative yield. + * + * @param nfds Highest-numbered file descriptor plus one. + * @param readfds Set of file descriptors to watch for readability (may be NULL). + * @param writefds Set of file descriptors to watch for writability (may be NULL). + * @param exceptfds Set of file descriptors to watch for exceptions (may be NULL). + * @param timeout Maximum wait time (NULL for infinite). + * @return Number of ready descriptors, 0 on timeout, or -1 on error (errno is set). + */ +int freertos_linux_coop_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); + +/** + * @brief Cooperative pselect — polls with cooperative yield and signal mask. + * + * @param nfds Highest-numbered file descriptor plus one. + * @param readfds Set of file descriptors to watch for readability (may be NULL). + * @param writefds Set of file descriptors to watch for writability (may be NULL). + * @param exceptfds Set of file descriptors to watch for exceptions (may be NULL). + * @param timeout Maximum wait time (NULL for infinite). + * @param sigmask Signal mask to apply during the wait (may be NULL). + * @return Number of ready descriptors, 0 on timeout, or -1 on error (errno is set). + */ +int freertos_linux_coop_pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask); + +/** + * @brief Cooperative poll — polls with cooperative yield. + * + * @param fds Array of pollfd structures describing the FDs to watch. + * @param nfds Number of elements in the fds array. + * @param timeout Timeout in milliseconds (-1 for infinite, 0 for non-blocking). + * @return Number of ready descriptors, 0 on timeout, or -1 on error (errno is set). + */ +int freertos_linux_coop_poll(struct pollfd *fds, nfds_t nfds, int timeout); + +/** + * @brief Set stdin/stdout/stderr to non-blocking and start tracking them. + * + * Must be called early during system init. + */ +void freertos_linux_coop_syscalls_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/port.c b/components/freertos/FreeRTOS-Kernel/portable/linux/port.c index ea370e0fd65..f358a96e258 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/port.c +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/port.c @@ -13,6 +13,7 @@ #include "FreeRTOS.h" #include "task.h" #include "utils/wait_for_event.h" +#include "esp_private/freertos_linux_coop_syscalls.h" #include "utils/linux_port_utils.h" #define FREERTOS_SIM_TICK_PERIOD_US (1000000 / CONFIG_FREERTOS_HZ) diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_internal.h b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_internal.h new file mode 100644 index 00000000000..24073f6f34e --- /dev/null +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_internal.h @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Internal header for FreeRTOS Linux cooperative syscall wrappers. + * Not for use outside the FreeRTOS portable layer. + */ + +#pragma once + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Cooperative tick period in milliseconds. + */ +#define LINUX_COOP_TICK_MS (1000 / CONFIG_FREERTOS_HZ) + +/** + * @brief Resolve real libc symbol into a file-static function pointer. + * + * Use inside a constructor function. real_ must be declared as + * a file-static variable of the correct function-pointer type. + * + * @param name Name of the libc symbol to resolve. + */ +#define LINUX_COOP_RESOLVE(name) real_##name = dlsym(RTLD_NEXT, #name) + +/** + * @brief Check whether the calling thread is a FreeRTOS task pthread. + * + * @return true if called from a FreeRTOS task, false otherwise. + */ +bool linux_port_in_freertos_task(void); + +/** + * @brief Set a kernel file descriptor to O_NONBLOCK mode. + * + * @param fd File descriptor to make non-blocking. + */ +void linux_coop_set_nonblocking(int fd); + +/** + * @brief Track a file descriptor in the cooperative mode table. + * + * Real kernel FDs are kept non-blocking internally. This function stores the + * user-visible file status flags so wrappers can preserve POSIX blocking/ + * non-blocking semantics. + * + * @param fd File descriptor to track. + * @param user_flags User-visible status flags (as passed to fcntl F_SETFL). + */ +void linux_coop_track_fd(int fd, int user_flags); + +/** + * @brief Stop tracking a file descriptor in the cooperative mode table. + * + * @param fd File descriptor to untrack. + */ +void linux_coop_untrack_fd(int fd); + +/** + * @brief Update user-visible status flags for a tracked file descriptor. + * + * @param fd File descriptor to update. + * @param user_flags New user-visible status flags. + */ +void linux_coop_set_user_flags(int fd, int user_flags); + +/** + * @brief Query whether user-visible mode is non-blocking for a file descriptor. + * + * @param fd File descriptor to query. + * @return true if user-visible mode is non-blocking, false otherwise. + */ +bool linux_coop_fd_user_nonblocking(int fd); + +/** + * @brief Get tracked user-visible status flags for a file descriptor. + * + * @param[in] fd File descriptor to query. + * @param[out] flags Output for tracked user-visible flags. + * @return true if the fd is tracked and flags were written, false otherwise. + */ +bool linux_coop_get_user_flags(int fd, int *flags); + +/** + * @brief Cooperatively yield for @p ms milliseconds. + * + * Maps to vTaskDelay() inside a FreeRTOS task, nanosleep() otherwise. + * + * @param ms Number of milliseconds to yield. + */ +static inline __attribute__((always_inline)) +void linux_coop_yield(int ms) +{ + if (linux_port_in_freertos_task()) { + vTaskDelay(pdMS_TO_TICKS(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 */ + } + } +} + +/** + * @brief Cooperative I/O retry loop. + * + * If user-visible mode is non-blocking, returns EAGAIN/EWOULDBLOCK as-is. + * Otherwise yields and retries until success or a real error. + * + * @param fd File descriptor used to determine user-visible blocking mode. + * @param expr Expression to evaluate in the retry loop (must yield ssize_t). + */ +#define LINUX_COOP_IO_LOOP(fd, expr) \ + while (1) \ + { \ + ssize_t _n = (expr); \ + if (_n >= 0) { \ + return _n; \ + } \ + if (errno == EAGAIN || errno == EWOULDBLOCK) { \ + if (linux_coop_fd_user_nonblocking(fd)) { \ + return -1; \ + } \ + linux_coop_yield(LINUX_COOP_TICK_MS); \ + continue; \ + } \ + return -1; \ + } + +#ifdef __cplusplus +} +#endif 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 index 7f61ecbb03a..82cf1c67196 100644 --- 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 @@ -3,7 +3,16 @@ * * SPDX-License-Identifier: Apache-2.0 * - * Cooperative wrappers for Linux FreeRTOS simulator. + * Cooperative syscall interposition for Linux/macOS FreeRTOS simulator. + * + * This file provides: + * - Shadow FD state table (user-visible blocking/non-blocking tracking) + * - Cooperative I/O primitives (freertos_linux_coop_read/write/open/close/…) + * - Weak POSIX symbol definitions (read/write/open/close/fcntl/select/…) + * that VFS can override with strong definitions + * - Socket, scatter/gather, multiplexing, sleep wrappers + * + * Real libc entry points are resolved via dlsym(RTLD_NEXT, …). */ #include @@ -21,278 +30,552 @@ #include #include #include -#include -#include "freertos/FreeRTOS.h" -#include "task.h" +#include -#define COOP_SYSCALLS_WAIT_MS (1000 / CONFIG_FREERTOS_HZ) +#include "linux_port_coop_internal.h" +#include "esp_private/freertos_linux_coop_syscalls.h" -extern bool linux_port_in_freertos_task(void); +static ssize_t (*real_readv)(int, const struct iovec *, int); +static ssize_t (*real_writev)(int, const struct iovec *, int); +static ssize_t (*real_recv)(int, void *, size_t, int); +static ssize_t (*real_send)(int, const void *, size_t, int); +static ssize_t (*real_recvfrom)(int, void *, size_t, int, + struct sockaddr *, socklen_t *); +static ssize_t (*real_sendto)(int, const void *, size_t, int, + const struct sockaddr *, socklen_t); +static ssize_t (*real_recvmsg)(int, struct msghdr *, int); +static ssize_t (*real_sendmsg)(int, const struct msghdr *, int); +static int (*real_connect)(int, const struct sockaddr *, socklen_t); +static int (*real_accept)(int, struct sockaddr *, socklen_t *); +static int (*real_pselect)(int, fd_set *, fd_set *, fd_set *, + const struct timespec *, const sigset_t *); +static int (*real_poll)(struct pollfd *, nfds_t, int); +static int (*real_socket)(int, int, int); +static int (*real_socketpair)(int, int, int, int[2]); +static int (*real_pipe)(int[2]); +static int (*real_pipe2)(int[2], int); +static int (*real_dup)(int); +static int (*real_dup2)(int, int); +static int (*real_nanosleep)(const struct timespec *, struct timespec *); +static ssize_t (*real_read)(int, void *, size_t); +static ssize_t (*real_write)(int, const void *, size_t); +static ssize_t (*real_pread)(int, void *, size_t, off_t); +static ssize_t (*real_pwrite)(int, const void *, size_t, off_t); +static int (*real_close)(int); +static int (*real_select)(int, fd_set *, fd_set *, fd_set *, + struct timeval *); -static inline __attribute__((always_inline)) -void coop_set_fd_nonblocking(int fd) +typedef int (*linux_coop_fcntl_fn_t)(int, int, ...); +static linux_coop_fcntl_fn_t real_fcntl; + +typedef int (*linux_coop_open_fn_t)(const char *, int, mode_t); +static linux_coop_open_fn_t real_open; + +typedef struct linux_coop_fd_state { + int fd; + int user_flags; + struct linux_coop_fd_state *next; +} linux_coop_fd_state_t; + +static linux_coop_fd_state_t *s_fd_state_list; +static pthread_mutex_t s_fd_state_lock = PTHREAD_MUTEX_INITIALIZER; + +static linux_coop_fd_state_t *linux_coop_find_fd_locked(int fd) +{ + linux_coop_fd_state_t *node = s_fd_state_list; + while (node != NULL) { + if (node->fd == fd) { + return node; + } + node = node->next; + } + return NULL; +} + +static void __attribute__((constructor)) linux_coop_resolve_all(void) +{ + LINUX_COOP_RESOLVE(readv); + LINUX_COOP_RESOLVE(writev); + LINUX_COOP_RESOLVE(recv); + LINUX_COOP_RESOLVE(send); + LINUX_COOP_RESOLVE(recvfrom); + LINUX_COOP_RESOLVE(sendto); + LINUX_COOP_RESOLVE(recvmsg); + LINUX_COOP_RESOLVE(sendmsg); + LINUX_COOP_RESOLVE(connect); + LINUX_COOP_RESOLVE(accept); + LINUX_COOP_RESOLVE(pselect); + LINUX_COOP_RESOLVE(poll); + LINUX_COOP_RESOLVE(socket); + LINUX_COOP_RESOLVE(socketpair); + LINUX_COOP_RESOLVE(pipe); + LINUX_COOP_RESOLVE(pipe2); /* may be NULL on macOS */ + LINUX_COOP_RESOLVE(dup); + LINUX_COOP_RESOLVE(dup2); + LINUX_COOP_RESOLVE(nanosleep); + LINUX_COOP_RESOLVE(fcntl); + LINUX_COOP_RESOLVE(read); + LINUX_COOP_RESOLVE(write); + LINUX_COOP_RESOLVE(pread); + LINUX_COOP_RESOLVE(pwrite); + LINUX_COOP_RESOLVE(close); + LINUX_COOP_RESOLVE(select); + LINUX_COOP_RESOLVE(open); +} + +void linux_coop_set_nonblocking(int fd) { if (fd >= 0) { - int flags = fcntl(fd, F_GETFL, 0); + int flags = real_fcntl(fd, F_GETFL, 0); if (flags >= 0) { - fcntl(fd, F_SETFL, flags | O_NONBLOCK); + real_fcntl(fd, F_SETFL, flags | O_NONBLOCK); } } } -static inline __attribute__((always_inline)) -void coop_wait(int ms) +void linux_coop_track_fd(int fd, int user_flags) { - 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 + if (fd < 0) { + return; + } + + pthread_mutex_lock(&s_fd_state_lock); + linux_coop_fd_state_t *node = linux_coop_find_fd_locked(fd); + if (node != NULL) { + node->user_flags = user_flags; + pthread_mutex_unlock(&s_fd_state_lock); + return; + } + + node = calloc(1, sizeof(*node)); + if (node != NULL) { + node->fd = fd; + node->user_flags = user_flags; + node->next = s_fd_state_list; + s_fd_state_list = node; + } + pthread_mutex_unlock(&s_fd_state_lock); +} + +void linux_coop_untrack_fd(int fd) +{ + if (fd < 0) { + return; + } + + pthread_mutex_lock(&s_fd_state_lock); + linux_coop_fd_state_t *prev = NULL; + linux_coop_fd_state_t *node = s_fd_state_list; + while (node != NULL) { + if (node->fd == fd) { + if (prev == NULL) { + s_fd_state_list = node->next; + } else { + prev->next = node->next; + } + free(node); + break; + } + prev = node; + node = node->next; + } + pthread_mutex_unlock(&s_fd_state_lock); +} + +void linux_coop_set_user_flags(int fd, int user_flags) +{ + linux_coop_track_fd(fd, user_flags); +} + +bool linux_coop_get_user_flags(int fd, int *flags) +{ + if (fd < 0 || flags == NULL) { + return false; + } + + pthread_mutex_lock(&s_fd_state_lock); + linux_coop_fd_state_t *node = linux_coop_find_fd_locked(fd); + if (node == NULL) { + pthread_mutex_unlock(&s_fd_state_lock); + return false; + } + *flags = node->user_flags; + pthread_mutex_unlock(&s_fd_state_lock); + return true; +} + +bool linux_coop_fd_user_nonblocking(int fd) +{ + int user_flags = 0; + if (linux_coop_get_user_flags(fd, &user_flags)) { + return (user_flags & O_NONBLOCK) != 0; + } + return false; +} + + +ssize_t freertos_linux_coop_read(int fd, void *buf, size_t count) +{ + LINUX_COOP_IO_LOOP(fd, real_read(fd, buf, count)) +} + +ssize_t freertos_linux_coop_write(int fd, const void *buf, size_t count) +{ + LINUX_COOP_IO_LOOP(fd, real_write(fd, buf, count)) +} + +ssize_t freertos_linux_coop_pread(int fd, void *buf, size_t count, off_t offset) +{ + LINUX_COOP_IO_LOOP(fd, real_pread(fd, buf, count, offset)) +} + +ssize_t freertos_linux_coop_pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + LINUX_COOP_IO_LOOP(fd, real_pwrite(fd, buf, count, offset)) +} + +int freertos_linux_coop_open(const char *path, int flags, int mode) +{ + int fd = real_open(path, flags, (mode_t)mode); + linux_coop_set_nonblocking(fd); + linux_coop_track_fd(fd, flags & O_NONBLOCK); + return fd; +} + +int freertos_linux_coop_close(int fd) +{ + int ret = real_close(fd); + if (ret == 0 || errno == EINTR) { + /* On Linux, close() always releases the fd even when returning + * EINTR. Retrying would risk closing a fd reused by another + * thread. Treat EINTR as success. */ + linux_coop_untrack_fd(fd); + return 0; + } + return -1; +} + +int freertos_linux_coop_fcntl(int fd, int cmd, int arg) +{ + if (cmd == F_GETFL) { + int real_flags = real_fcntl(fd, F_GETFL, 0); + if (real_flags < 0) { + return -1; + } + int user_flags = 0; + if (linux_coop_get_user_flags(fd, &user_flags)) { + real_flags &= ~O_NONBLOCK; + real_flags |= (user_flags & O_NONBLOCK); + } + return real_flags; + } + if (cmd == F_SETFL) { + linux_coop_set_user_flags(fd, arg); + int internal_flags = arg | O_NONBLOCK; + return real_fcntl(fd, F_SETFL, internal_flags); + } + return real_fcntl(fd, cmd, arg); +} + +int freertos_linux_coop_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) +{ + long timeout_ms = -1; + if (timeout != NULL) { + timeout_ms = (long)timeout->tv_sec * 1000 + + (timeout->tv_usec + 999) / 1000; + if (timeout_ms == 0) { + return real_select(nfds, readfds, writefds, exceptfds, timeout); } } -} -/* 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; \ - } \ + /* real_select() mutates the fd_sets in place (clears them, then sets bits + * for ready descriptors). To keep polling the same descriptors across + * iterations we must snapshot the caller's sets and restore them on every + * loop. */ + fd_set rfds_in, wfds_in, efds_in; + if (readfds != NULL) { + rfds_in = *readfds; + } + if (writefds != NULL) { + wfds_in = *writefds; + } + if (exceptfds != NULL) { + efds_in = *exceptfds; } -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); + long waited_ms = 0; + while (1) { + if (readfds != NULL) { + *readfds = rfds_in; + } + if (writefds != NULL) { + *writefds = wfds_in; + } + if (exceptfds != NULL) { + *exceptfds = efds_in; + } -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)) + struct timeval zero_tv = {0, 0}; + int ret = real_select(nfds, readfds, writefds, exceptfds, &zero_tv); + if (ret != 0) { + return ret; + } + if (timeout_ms == 0) { + return 0; + } + if (timeout_ms > 0 && waited_ms >= timeout_ms) { + return 0; + } + linux_coop_yield(LINUX_COOP_TICK_MS); + waited_ms += LINUX_COOP_TICK_MS; + } } -ssize_t __wrap_write(int fd, const void *buf, size_t count) +void freertos_linux_coop_syscalls_init(void) { - COOP_LOOP(__real_write(fd, buf, count)) + linux_coop_set_nonblocking(STDIN_FILENO); + linux_coop_set_nonblocking(STDOUT_FILENO); + linux_coop_set_nonblocking(STDERR_FILENO); + linux_coop_track_fd(STDIN_FILENO, 0); + linux_coop_track_fd(STDOUT_FILENO, 0); + linux_coop_track_fd(STDERR_FILENO, 0); } -ssize_t __wrap_pread(int fd, void *buf, size_t count, off_t offset) +/* These are overridden by strong definitions in VFS when that component + * is linked. When VFS is absent the weak versions provide cooperative + * behaviour directly via real libc. */ + +__attribute__((weak)) ssize_t read(int fd, void *buf, size_t count) { - COOP_LOOP(__real_pread(fd, buf, count, offset)) + return freertos_linux_coop_read(fd, buf, count); } -ssize_t __wrap_pwrite(int fd, const void *buf, size_t count, off_t offset) +__attribute__((weak)) ssize_t write(int fd, const void *buf, size_t count) { - COOP_LOOP(__real_pwrite(fd, buf, count, offset)) + return freertos_linux_coop_write(fd, buf, count); } -ssize_t __wrap_readv(int fd, const struct iovec *iov, int iovcnt) +__attribute__((weak)) ssize_t pread(int fd, void *buf, size_t count, off_t offset) { - COOP_LOOP(__real_readv(fd, iov, iovcnt)) + return freertos_linux_coop_pread(fd, buf, count, offset); } -ssize_t __wrap_writev(int fd, const struct iovec *iov, int iovcnt) +__attribute__((weak)) ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { - COOP_LOOP(__real_writev(fd, iov, iovcnt)) + return freertos_linux_coop_pwrite(fd, buf, count, offset); } -ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) +__attribute__((weak)) int open(const char *path, int flags, ...) { - COOP_LOOP(__real_recv(sockfd, buf, len, flags | MSG_DONTWAIT)) + int mode = 0; + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + return freertos_linux_coop_open(path, flags, mode); } -ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags) +__attribute__((weak)) int close(int fd) { - COOP_LOOP(__real_send(sockfd, buf, len, flags | MSG_DONTWAIT)) + return freertos_linux_coop_close(fd); } -ssize_t __wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) +__attribute__((weak)) int fcntl(int fd, int cmd, ...) { - COOP_LOOP(__real_recvfrom(sockfd, buf, len, flags | MSG_DONTWAIT, src_addr, addrlen)) + int arg = 0; + if (cmd != F_GETFL) { + va_list list; + va_start(list, cmd); + arg = va_arg(list, int); + va_end(list); + } + return freertos_linux_coop_fcntl(fd, cmd, arg); } -ssize_t __wrap_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) +__attribute__((weak)) int select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) { - COOP_LOOP(__real_sendto(sockfd, buf, len, flags | MSG_DONTWAIT, dest_addr, addrlen)) + return freertos_linux_coop_select(nfds, readfds, writefds, exceptfds, timeout); } -ssize_t __wrap_recvmsg(int sockfd, struct msghdr *msg, int flags) +ssize_t freertos_linux_coop_readv(int fd, const struct iovec *iov, int iovcnt) { - COOP_LOOP(__real_recvmsg(sockfd, msg, flags | MSG_DONTWAIT)) + LINUX_COOP_IO_LOOP(fd, real_readv(fd, iov, iovcnt)) } -ssize_t __wrap_sendmsg(int sockfd, const struct msghdr *msg, int flags) +ssize_t freertos_linux_coop_writev(int fd, const struct iovec *iov, int iovcnt) { - COOP_LOOP(__real_sendmsg(sockfd, msg, flags | MSG_DONTWAIT)) + LINUX_COOP_IO_LOOP(fd, real_writev(fd, iov, iovcnt)) } -int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +ssize_t freertos_linux_coop_recv(int sockfd, void *buf, size_t len, int flags) +{ + LINUX_COOP_IO_LOOP(sockfd, real_recv(sockfd, buf, len, flags | MSG_DONTWAIT)) +} + +ssize_t freertos_linux_coop_send(int sockfd, const void *buf, size_t len, int flags) +{ + LINUX_COOP_IO_LOOP(sockfd, real_send(sockfd, buf, len, flags | MSG_DONTWAIT)) +} + +ssize_t freertos_linux_coop_recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + LINUX_COOP_IO_LOOP(sockfd, real_recvfrom(sockfd, buf, len, flags | MSG_DONTWAIT, + src_addr, addrlen)) +} + +ssize_t freertos_linux_coop_sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + LINUX_COOP_IO_LOOP(sockfd, real_sendto(sockfd, buf, len, flags | MSG_DONTWAIT, + dest_addr, addrlen)) +} + +ssize_t freertos_linux_coop_recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + LINUX_COOP_IO_LOOP(sockfd, real_recvmsg(sockfd, msg, flags | MSG_DONTWAIT)) +} + +ssize_t freertos_linux_coop_sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + LINUX_COOP_IO_LOOP(sockfd, real_sendmsg(sockfd, msg, flags | MSG_DONTWAIT)) +} + +__attribute__((weak)) ssize_t readv(int fd, const struct iovec *iov, int iovcnt) +{ + return freertos_linux_coop_readv(fd, iov, iovcnt); +} + +__attribute__((weak)) ssize_t writev(int fd, const struct iovec *iov, int iovcnt) +{ + return freertos_linux_coop_writev(fd, iov, iovcnt); +} + +__attribute__((weak)) ssize_t recv(int sockfd, void *buf, size_t len, int flags) +{ + return freertos_linux_coop_recv(sockfd, buf, len, flags); +} + +__attribute__((weak)) ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + return freertos_linux_coop_send(sockfd, buf, len, flags); +} + +__attribute__((weak)) ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + return freertos_linux_coop_recvfrom(sockfd, buf, len, flags, src_addr, addrlen); +} + +__attribute__((weak)) ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + return freertos_linux_coop_sendto(sockfd, buf, len, flags, dest_addr, addrlen); +} + +__attribute__((weak)) ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + return freertos_linux_coop_recvmsg(sockfd, msg, flags); +} + +__attribute__((weak)) ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + return freertos_linux_coop_sendmsg(sockfd, msg, flags); +} + +int freertos_linux_coop_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { while (1) { - int ret = __real_connect(sockfd, addr, addrlen); + 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; + int poll_retries = 0; while (1) { - int press = __real_poll(&pfd, 1, 0); + 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 */ + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, + &so_err, &len) < 0) { return -1; } - if (so_err == 0) { - return 0; /* connected */ + return 0; } else { errno = so_err; return -1; } } else if (press == 0) { - /* no event yet -> yield cooperatively and retry */ - coop_wait(COOP_SYSCALLS_WAIT_MS); + linux_coop_yield(LINUX_COOP_TICK_MS); continue; } else { - /* press < 0 */ if (errno == EINTR) { - continue; /* retry poll */ + continue; } - - /* treat other errors as transient and yield */ - coop_wait(COOP_SYSCALLS_WAIT_MS); + /* Non-EINTR poll error; bail out after a few retries */ + if (++poll_retries > 3) { + return -1; + } + linux_coop_yield(LINUX_COOP_TICK_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) +int freertos_linux_coop_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { while (1) { - int ret = __real_close(fd); - if (ret == 0) { - return 0; - } else if (errno == EINTR) { - coop_wait(COOP_SYSCALLS_WAIT_MS); + int fd = real_accept(sockfd, addr, addrlen); + if (fd >= 0) { + linux_coop_set_nonblocking(fd); + linux_coop_track_fd(fd, linux_coop_fd_user_nonblocking(sockfd) ? O_NONBLOCK : 0); + return fd; + } + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (linux_coop_fd_user_nonblocking(sockfd)) { + return -1; + } + linux_coop_yield(LINUX_COOP_TICK_MS); continue; - } else { - return -1; } + return -1; } } -int __wrap_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +int freertos_linux_coop_pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask) { - /* 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; + timeout_ms = (long)timeout->tv_sec * 1000 + + (timeout->tv_nsec + 999999) / 1000000; 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); + return real_pselect(nfds, readfds, writefds, exceptfds, + timeout, sigmask); } } @@ -300,7 +583,8 @@ int __wrap_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfd while (1) { struct timespec zero_ts = {0, 0}; - int ret = __real_pselect(nfds, readfds, writefds, exceptfds, &zero_ts, sigmask); + int ret = real_pselect(nfds, readfds, writefds, exceptfds, + &zero_ts, sigmask); if (ret != 0) { return ret; } @@ -310,19 +594,17 @@ int __wrap_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfd return 0; } - coop_wait(COOP_SYSCALLS_WAIT_MS); - waited_ms += COOP_SYSCALLS_WAIT_MS; + linux_coop_yield(LINUX_COOP_TICK_MS); + waited_ms += LINUX_COOP_TICK_MS; } } -int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) +int freertos_linux_coop_poll(struct pollfd *fds, nfds_t nfds, int timeout) { if (timeout == 0) { - /* immediate poll: delegate */ - return __real_poll(fds, nfds, 0); + return real_poll(fds, nfds, 0); } - /* compute wait semantics */ long timeout_ms = -1; if (timeout > 0) { timeout_ms = timeout; @@ -332,121 +614,182 @@ int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) while (1) { - int ret = __real_poll(fds, nfds, 0); + int ret = real_poll(fds, nfds, 0); if (ret > 0) { return ret; - } - else if (ret == 0) { - /* no event; check timeout */ + } else if (ret == 0) { if (timeout_ms == 0 || (timeout_ms > 0 && waited_ms >= timeout_ms)) { - return 0; /* expired */ + return 0; } - - /* yield and continue */ - coop_wait(COOP_SYSCALLS_WAIT_MS); - waited_ms += COOP_SYSCALLS_WAIT_MS; + linux_coop_yield(LINUX_COOP_TICK_MS); + waited_ms += LINUX_COOP_TICK_MS; continue; } else { - /* ret < 0: error */ if (errno == EINTR) { - continue; /* retry */ + continue; } - - /* for other errors, yield and retry (transient) */ - coop_wait(COOP_SYSCALLS_WAIT_MS); - + linux_coop_yield(LINUX_COOP_TICK_MS); if (timeout_ms > 0 && waited_ms >= timeout_ms) { return -1; } - waited_ms += COOP_SYSCALLS_WAIT_MS; + waited_ms += LINUX_COOP_TICK_MS; } } } -unsigned int __wrap_sleep(unsigned int seconds) +int nanosleep(const struct timespec *req, struct timespec *rem) { - coop_wait(seconds * 1000); + if (req == NULL) { + errno = EFAULT; + return -1; + } + if (!linux_port_in_freertos_task()) { + return real_nanosleep(req, rem); + } + long ms = req->tv_sec * 1000 + (req->tv_nsec + 999999) / 1000000; + if (ms <= 0) { + ms = 1; + } + vTaskDelay(ms); + if (rem) { + rem->tv_sec = 0; + rem->tv_nsec = 0; + } return 0; } -int __wrap_usleep(useconds_t usec) +unsigned int sleep(unsigned int seconds) { - coop_wait((usec / 1000)); + linux_coop_yield(seconds * 1000); return 0; } -int __wrap_socket(int domain, int type, int protocol) +int usleep(useconds_t usec) { - int fd = __real_socket(domain, type, protocol); - coop_set_fd_nonblocking(fd); + linux_coop_yield(usec / 1000); + return 0; +} + +int freertos_linux_coop_socket(int domain, int type, int protocol) +{ + int fd = real_socket(domain, type, protocol); + linux_coop_set_nonblocking(fd); +#ifdef SOCK_NONBLOCK + const int user_flags = (type & SOCK_NONBLOCK) ? O_NONBLOCK : 0; +#else + const int user_flags = 0; +#endif + linux_coop_track_fd(fd, user_flags); return fd; } -int __wrap_socketpair(int domain, int type, int protocol, int sv[2]) +int freertos_linux_coop_socketpair(int domain, int type, int protocol, int *sv) { - int ret = __real_socketpair(domain, type, protocol, sv); + int ret = real_socketpair(domain, type, protocol, sv); if (ret == 0) { - coop_set_fd_nonblocking(sv[0]); - coop_set_fd_nonblocking(sv[1]); + linux_coop_set_nonblocking(sv[0]); + linux_coop_set_nonblocking(sv[1]); +#ifdef SOCK_NONBLOCK + const int user_flags = (type & SOCK_NONBLOCK) ? O_NONBLOCK : 0; +#else + const int user_flags = 0; +#endif + linux_coop_track_fd(sv[0], user_flags); + linux_coop_track_fd(sv[1], user_flags); } return ret; } -int __wrap_pipe(int fds[2]) +int freertos_linux_coop_pipe(int *fds) { - int ret = __real_pipe(fds); + int ret = real_pipe(fds); if (ret == 0) { - coop_set_fd_nonblocking(fds[0]); - coop_set_fd_nonblocking(fds[1]); + linux_coop_set_nonblocking(fds[0]); + linux_coop_set_nonblocking(fds[1]); + linux_coop_track_fd(fds[0], 0); + linux_coop_track_fd(fds[1], 0); } return ret; } -int __wrap_pipe2(int fds[2], int flags) +int freertos_linux_coop_pipe2(int *fds, int flags) { - int ret = __real_pipe2(fds, flags); + int ret = real_pipe2(fds, flags); if (ret == 0) { - coop_set_fd_nonblocking(fds[0]); - coop_set_fd_nonblocking(fds[1]); + linux_coop_set_nonblocking(fds[0]); + linux_coop_set_nonblocking(fds[1]); + const int user_flags = (flags & O_NONBLOCK) ? O_NONBLOCK : 0; + linux_coop_track_fd(fds[0], user_flags); + linux_coop_track_fd(fds[1], user_flags); } return ret; } -int __wrap_dup(int oldfd) +int freertos_linux_coop_dup(int oldfd) { - int fd = __real_dup(oldfd); - coop_set_fd_nonblocking(fd); + int fd = real_dup(oldfd); + linux_coop_set_nonblocking(fd); + linux_coop_track_fd(fd, linux_coop_fd_user_nonblocking(oldfd) ? O_NONBLOCK : 0); return fd; } -int __wrap_dup2(int oldfd, int newfd) +int freertos_linux_coop_dup2(int oldfd, int newfd) { - int fd = __real_dup2(oldfd, newfd); - coop_set_fd_nonblocking(fd); + int fd = real_dup2(oldfd, newfd); + linux_coop_set_nonblocking(fd); + linux_coop_track_fd(fd, linux_coop_fd_user_nonblocking(oldfd) ? O_NONBLOCK : 0); return fd; } -int __wrap_open(const char *path, int flags, ...) +__attribute__((weak)) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { - 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; + return freertos_linux_coop_connect(sockfd, addr, addrlen); } -void linux_port_coop_syscalls_init(void) +__attribute__((weak)) int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { - coop_set_fd_nonblocking(STDIN_FILENO); - coop_set_fd_nonblocking(STDOUT_FILENO); - coop_set_fd_nonblocking(STDERR_FILENO); + return freertos_linux_coop_accept(sockfd, addr, addrlen); +} + +__attribute__((weak)) int pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask) +{ + return freertos_linux_coop_pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask); +} + +__attribute__((weak)) int poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + return freertos_linux_coop_poll(fds, nfds, timeout); +} + +__attribute__((weak)) int socket(int domain, int type, int protocol) +{ + return freertos_linux_coop_socket(domain, type, protocol); +} + +__attribute__((weak)) int socketpair(int domain, int type, int protocol, int sv[2]) +{ + return freertos_linux_coop_socketpair(domain, type, protocol, sv); +} + +__attribute__((weak)) int pipe(int fds[2]) +{ + return freertos_linux_coop_pipe(fds); +} + +__attribute__((weak)) int pipe2(int fds[2], int flags) +{ + return freertos_linux_coop_pipe2(fds, flags); +} + +__attribute__((weak)) int dup(int oldfd) +{ + return freertos_linux_coop_dup(oldfd); +} + +__attribute__((weak)) int dup2(int oldfd, int newfd) +{ + return freertos_linux_coop_dup2(oldfd, newfd); } 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 deleted file mode 100644 index bbeb9a2ab80..00000000000 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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/vfs/CMakeLists.txt b/components/vfs/CMakeLists.txt index 788e4e5cb08..1574b183e69 100644 --- a/components/vfs/CMakeLists.txt +++ b/components/vfs/CMakeLists.txt @@ -10,7 +10,7 @@ if(${target} STREQUAL "linux") if(CONFIG_VFS_SUPPORT_IO) list(APPEND inc linux_include) list(APPEND priv_inc private_include) - list(APPEND srcs "vfs_linux.c" "vfs.c" "vfs_calls.c") + list(APPEND srcs "vfs_linux.c" "vfs_linux_default_coop.c" "vfs.c" "vfs_calls.c") endif() if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") @@ -20,7 +20,20 @@ if(${target} STREQUAL "linux") idf_component_register(SRCS ${srcs} LDFRAGMENTS "linker.lf" INCLUDE_DIRS ${inc} - PRIV_INCLUDE_DIRS ${priv_inc}) + PRIV_INCLUDE_DIRS ${priv_inc} + PRIV_REQUIRES freertos) + + target_link_libraries(${COMPONENT_LIB} PRIVATE dl) + + if(CONFIG_VFS_SUPPORT_IO) + # On macOS, C symbols have a leading underscore prefix. + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-u _esp_vfs_include_linux_coop_init") + else() + target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_vfs_include_linux_coop_init") + endif() + endif() + return() endif() diff --git a/components/vfs/private_include/esp_vfs_private.h b/components/vfs/private_include/esp_vfs_private.h index d599f3e90b7..fdb8cf73a42 100644 --- a/components/vfs/private_include/esp_vfs_private.h +++ b/components/vfs/private_include/esp_vfs_private.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index 7a71c468c19..d0bb3309d58 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/vfs/vfs_linux.c b/components/vfs/vfs_linux.c index e59502d1e85..433b3ffb4ae 100644 --- a/components/vfs/vfs_linux.c +++ b/components/vfs/vfs_linux.c @@ -1,101 +1,83 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ /** - * @brief Linux target VFS syscall wrappers + * @brief Linux target VFS syscall wrappers (strong POSIX symbols) * - * This file provides POSIX syscall wrappers that redirect to ESP-IDF VFS functions - * when building for Linux target. + * This file provides strong POSIX symbol definitions that dispatch through + * esp_vfs_* for registered FDs, and fall back to the cooperative syscall + * layer for unregistered FDs (e.g. during early boot before VFS init). + * + * All kernel FDs created by open/pipe/socket/dup/accept are registered + * with VFS via esp_vfs_register_fd_with_local_fd, so the application + * only sees VFS-allocated FD numbers. This eliminates FD collisions + * between kernel FDs and VFS-internal FD allocations. + * + * Init and ops registration live in vfs_linux_default_coop.c. */ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "esp_vfs.h" +#include "esp_vfs_private.h" +#include "esp_private/freertos_linux_coop_syscalls.h" -int open(const char * path, int flags, ...) +/* Defined in vfs_linux_default_coop.c — set during init_vfs_linux_coop() */ +extern esp_vfs_id_t s_linux_host_vfs_id; + +/** + * Helper: register a kernel FD with VFS after a successful FD-creating syscall. + * Returns the VFS FD on success, or the raw kernel FD if VFS is not yet initialized. + */ +static int register_kernel_fd(int kernel_fd) { - struct _reent r; - va_list list; - va_start(list, flags); - int mode = va_arg(list, int); - va_end(list); - return esp_vfs_open(&r, path, flags, mode); + if (kernel_fd < 0) { + return kernel_fd; + } + if (s_linux_host_vfs_id < 0) { + return kernel_fd; /* VFS not initialized yet (early boot) */ + } + int vfs_fd; + esp_err_t err = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, kernel_fd, false, &vfs_fd); + if (err != ESP_OK) { + freertos_linux_coop_close(kernel_fd); + errno = ENFILE; + return -1; + } + return vfs_fd; } -int close(int fd) +/** + * Helper: translate a VFS FD to a kernel FD. + * Returns the kernel (local) FD, or -1 with errno=EBADF. + */ +static int vfs_fd_to_kernel_fd(int fd) { - struct _reent r; - return esp_vfs_close(&r, fd); + const vfs_entry_t *vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + return fd; /* Not registered — use directly (early boot) */ + } + int local_fd = get_local_fd(vfs, fd); + if (local_fd < 0) { + errno = EBADF; + return -1; + } + return local_fd; } -ssize_t read(int fd, void * dst, size_t size) -{ - struct _reent r; - return esp_vfs_read(&r, fd, dst, size); -} - -ssize_t write(int fd, const void * data, size_t size) -{ - struct _reent r; - return esp_vfs_write(&r, fd, data, size); -} - -off_t lseek(int fd, off_t size, int mode) -{ - struct _reent r; - return esp_vfs_lseek(&r, fd, size, mode); -} - -ssize_t pread(int fd, void *dst, size_t size, off_t offset) -{ - return esp_vfs_pread(fd,dst, size, offset); -} - -ssize_t pwrite(int fd, const void *src, size_t size, off_t offset) -{ - return esp_vfs_pwrite(fd, src,size, offset); -} - -int fstat(int fd, struct stat *st) -{ - struct _reent r; - return esp_vfs_fstat(&r, fd, st); -} - -int fcntl(int fd, int cmd, ...) -{ - struct _reent r; - va_list list; - va_start(list, cmd); - int args = va_arg(list, int); - va_end(list); - return esp_vfs_fcntl_r(&r, fd, cmd, args); -} - -int ioctl(int fd, unsigned long cmd, ...) -{ - va_list args; - va_start(args, cmd); - int ret = esp_vfs_ioctl(fd, cmd, args); - va_end(args); - return ret; -} - -int fsync(int fd) -{ - return esp_vfs_fsync(fd); -} - -#ifdef CONFIG_VFS_SUPPORT_SELECT -int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) -{ - return esp_vfs_select(nfds, readfds, writefds, errorfds, timeout); -} -#endif // CONFIG_VFS_SUPPORT_SELECT - #ifdef CONFIG_VFS_SUPPORT_DIR int stat(const char * path, struct stat * st) { @@ -187,3 +169,470 @@ void rewinddir(DIR* pdir) } #endif // CONFIG_VFS_SUPPORT_DIR + + +ssize_t read(int fd, void *buf, size_t count) +{ + struct _reent r; + return esp_vfs_read(&r, fd, buf, count); +} + +ssize_t write(int fd, const void *buf, size_t count) +{ + struct _reent r; + return esp_vfs_write(&r, fd, buf, count); +} + +ssize_t pread(int fd, void *buf, size_t count, off_t offset) +{ + return esp_vfs_pread(fd, buf, count, offset); +} + +ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + return esp_vfs_pwrite(fd, buf, count, offset); +} + +int open(const char *path, int flags, ...) +{ + int mode = 0; + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + struct _reent r; + return esp_vfs_open(&r, path, flags, mode); +} + +int close(int fd) +{ + struct _reent r; + return esp_vfs_close(&r, fd); +} + +int fcntl(int fd, int cmd, ...) +{ + int arg = 0; + if (cmd != F_GETFL) { + va_list list; + va_start(list, cmd); + arg = va_arg(list, int); + va_end(list); + } + struct _reent r; + return esp_vfs_fcntl_r(&r, fd, cmd, arg); +} + +off_t lseek(int fd, off_t size, int mode) +{ + struct _reent r; + return esp_vfs_lseek(&r, fd, size, mode); +} + +int fstat(int fd, struct stat *st) +{ + struct _reent r; + return esp_vfs_fstat(&r, fd, st); +} + +int ioctl(int fd, signed long cmd, ...) +{ + va_list args; + va_start(args, cmd); + int ret = esp_vfs_ioctl(fd, cmd, args); + va_end(args); + return ret; +} + +int fsync(int fd) +{ + return esp_vfs_fsync(fd); +} + +int pipe(int fds[2]) +{ + int ret = freertos_linux_coop_pipe(fds); + if (ret != 0) { + return ret; + } + if (s_linux_host_vfs_id >= 0) { + int vfs_fd0, vfs_fd1; + esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[0], false, &vfs_fd0); + esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[1], false, &vfs_fd1); + if (e0 != ESP_OK || e1 != ESP_OK) { + freertos_linux_coop_close(fds[0]); + freertos_linux_coop_close(fds[1]); + errno = ENFILE; + return -1; + } + fds[0] = vfs_fd0; + fds[1] = vfs_fd1; + } + return 0; +} + +int pipe2(int fds[2], int flags) +{ + int ret = freertos_linux_coop_pipe2(fds, flags); + if (ret != 0) { + return ret; + } + if (s_linux_host_vfs_id >= 0) { + int vfs_fd0, vfs_fd1; + esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[0], false, &vfs_fd0); + esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[1], false, &vfs_fd1); + if (e0 != ESP_OK || e1 != ESP_OK) { + freertos_linux_coop_close(fds[0]); + freertos_linux_coop_close(fds[1]); + errno = ENFILE; + return -1; + } + fds[0] = vfs_fd0; + fds[1] = vfs_fd1; + } + return 0; +} + +int socket(int domain, int type, int protocol) +{ + int kernel_fd = freertos_linux_coop_socket(domain, type, protocol); + return register_kernel_fd(kernel_fd); +} + +int socketpair(int domain, int type, int protocol, int sv[2]) +{ + int ret = freertos_linux_coop_socketpair(domain, type, protocol, sv); + if (ret != 0) { + return ret; + } + if (s_linux_host_vfs_id >= 0) { + int vfs_fd0, vfs_fd1; + esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, sv[0], false, &vfs_fd0); + esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, sv[1], false, &vfs_fd1); + if (e0 != ESP_OK || e1 != ESP_OK) { + freertos_linux_coop_close(sv[0]); + freertos_linux_coop_close(sv[1]); + errno = ENFILE; + return -1; + } + sv[0] = vfs_fd0; + sv[1] = vfs_fd1; + } + return 0; +} + +int dup(int oldfd) +{ + int kernel_old = vfs_fd_to_kernel_fd(oldfd); + if (kernel_old < 0) { + return -1; + } + int kernel_fd = freertos_linux_coop_dup(kernel_old); + return register_kernel_fd(kernel_fd); +} + +int dup2(int oldfd, int newfd) +{ + int kernel_old = vfs_fd_to_kernel_fd(oldfd); + if (kernel_old < 0) { + return -1; + } + /* If newfd is a registered VFS FD, we need its kernel FD and must unregister it */ + int kernel_new = newfd; + if (get_vfs_for_fd(newfd) != NULL) { + kernel_new = vfs_fd_to_kernel_fd(newfd); + if (kernel_new < 0) { + return -1; + } + unregister_fd(newfd); + } + int kernel_fd = freertos_linux_coop_dup2(kernel_old, kernel_new); + return register_kernel_fd(kernel_fd); +} + +int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) +{ + int kernel_sock = vfs_fd_to_kernel_fd(sockfd); + if (kernel_sock < 0) { + return -1; + } + int kernel_fd = freertos_linux_coop_accept(kernel_sock, addr, addrlen); + return register_kernel_fd(kernel_fd); +} + +ssize_t readv(int fd, const struct iovec *iov, int iovcnt) +{ + int kfd = vfs_fd_to_kernel_fd(fd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_readv(kfd, iov, iovcnt); +} + +ssize_t writev(int fd, const struct iovec *iov, int iovcnt) +{ + int kfd = vfs_fd_to_kernel_fd(fd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_writev(kfd, iov, iovcnt); +} + +ssize_t recv(int sockfd, void *buf, size_t len, int flags) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_recv(kfd, buf, len, flags); +} + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_send(kfd, buf, len, flags); +} + +ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_recvfrom(kfd, buf, len, flags, src_addr, addrlen); +} + +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_sendto(kfd, buf, len, flags, dest_addr, addrlen); +} + +ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_recvmsg(kfd, msg, flags); +} + +ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_sendmsg(kfd, msg, flags); +} + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_connect(kfd, addr, addrlen); +} + +/** + * Translate VFS fd_sets to kernel fd_sets. + * Returns the nfds value for the kernel sets, or -1 on error (errno set). + */ +static int fdsets_vfs_to_kernel(int nfds, + const fd_set *readfds, const fd_set *writefds, const fd_set *exceptfds, + fd_set *k_readfds, fd_set *k_writefds, fd_set *k_exceptfds) +{ + int max_kfd = 0; + FD_ZERO(k_readfds); + FD_ZERO(k_writefds); + FD_ZERO(k_exceptfds); + + for (int fd = 0; fd < nfds; ++fd) { + int kfd = -1; + bool active = false; + if (readfds && FD_ISSET(fd, readfds)) { + kfd = vfs_fd_to_kernel_fd(fd); + if (kfd < 0) { return -1; } + FD_SET(kfd, k_readfds); + active = true; + } + if (writefds && FD_ISSET(fd, writefds)) { + if (kfd < 0) { kfd = vfs_fd_to_kernel_fd(fd); } + if (kfd < 0) { return -1; } + FD_SET(kfd, k_writefds); + active = true; + } + if (exceptfds && FD_ISSET(fd, exceptfds)) { + if (kfd < 0) { kfd = vfs_fd_to_kernel_fd(fd); } + if (kfd < 0) { return -1; } + FD_SET(kfd, k_exceptfds); + active = true; + } + if (active && kfd >= max_kfd) { + max_kfd = kfd + 1; + } + } + return max_kfd; +} + +/** + * Translate kernel fd_set results back to VFS fd_sets. + */ +static void fdsets_kernel_to_vfs(int nfds, + fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + fd_set *k_readfds, fd_set *k_writefds, fd_set *k_exceptfds) +{ + if (readfds) { FD_ZERO(readfds); } + if (writefds) { FD_ZERO(writefds); } + if (exceptfds) { FD_ZERO(exceptfds); } + for (int fd = 0; fd < nfds; ++fd) { + int kfd = vfs_fd_to_kernel_fd(fd); + if (kfd < 0) { continue; } + if (readfds && FD_ISSET(kfd, k_readfds)) { FD_SET(fd, readfds); } + if (writefds && FD_ISSET(kfd, k_writefds)) { FD_SET(fd, writefds); } + if (exceptfds && FD_ISSET(kfd, k_exceptfds)) { FD_SET(fd, exceptfds); } + } +} + +int select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) +{ +#ifdef CONFIG_VFS_SUPPORT_SELECT + for (int fd = 0; fd < nfds; ++fd) { + if ((readfds && FD_ISSET(fd, readfds)) || + (writefds && FD_ISSET(fd, writefds)) || + (exceptfds && FD_ISSET(fd, exceptfds))) { + const vfs_entry_t *vfs = get_vfs_for_fd(fd); + if (vfs != NULL && vfs->offset != s_linux_host_vfs_id) { + return esp_vfs_select(nfds, readfds, writefds, exceptfds, timeout); + } + } + } +#endif + fd_set k_readfds, k_writefds, k_exceptfds; + int max_kfd = fdsets_vfs_to_kernel(nfds, readfds, writefds, exceptfds, + &k_readfds, &k_writefds, &k_exceptfds); + if (max_kfd < 0) { return -1; } + + int ret = freertos_linux_coop_select(max_kfd, + readfds ? &k_readfds : NULL, + writefds ? &k_writefds : NULL, + exceptfds ? &k_exceptfds : NULL, + timeout); + if (ret >= 0) { + fdsets_kernel_to_vfs(nfds, readfds, writefds, exceptfds, + &k_readfds, &k_writefds, &k_exceptfds); + } + return ret; +} + +int pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask) +{ + fd_set k_readfds, k_writefds, k_exceptfds; + int max_kfd = fdsets_vfs_to_kernel(nfds, readfds, writefds, exceptfds, + &k_readfds, &k_writefds, &k_exceptfds); + if (max_kfd < 0) { return -1; } + + int ret = freertos_linux_coop_pselect(max_kfd, + readfds ? &k_readfds : NULL, + writefds ? &k_writefds : NULL, + exceptfds ? &k_exceptfds : NULL, + timeout, sigmask); + if (ret >= 0) { + fdsets_kernel_to_vfs(nfds, readfds, writefds, exceptfds, + &k_readfds, &k_writefds, &k_exceptfds); + } + return ret; +} + +int poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + struct timeval tv = { + .tv_sec = timeout / 1000, + .tv_usec = (timeout % 1000) * 1000, + }; + int max_fd = -1; + fd_set readfds; + fd_set writefds; + fd_set errorfds; + + if (fds == NULL) { + errno = ENOENT; + return -1; + } + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&errorfds); + + for (unsigned int i = 0; i < nfds; ++i) { + fds[i].revents = 0; + + if (fds[i].fd < 0) { + continue; + } + + if (fds[i].fd >= FD_SETSIZE) { + fds[i].revents |= POLLNVAL; + continue; + } + + if (fds[i].events & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) { + FD_SET(fds[i].fd, &readfds); + FD_SET(fds[i].fd, &errorfds); + max_fd = MAX(max_fd, fds[i].fd); + } + + if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) { + FD_SET(fds[i].fd, &writefds); + FD_SET(fds[i].fd, &errorfds); + max_fd = MAX(max_fd, fds[i].fd); + } + } + + const int select_ret = select(max_fd + 1, &readfds, &writefds, &errorfds, timeout < 0 ? NULL : &tv); + + if (select_ret < 0) { + return select_ret; + } + + if (select_ret > 0) { + for (unsigned int i = 0; i < nfds; ++i) { + if (fds[i].fd < 0 || fds[i].fd >= FD_SETSIZE) { + continue; + } + + if (FD_ISSET(fds[i].fd, &readfds)) { + fds[i].revents |= POLLIN; + } + + if (FD_ISSET(fds[i].fd, &writefds)) { + fds[i].revents |= POLLOUT; + } + + if (FD_ISSET(fds[i].fd, &errorfds)) { + fds[i].revents |= POLLERR; + } + } + } + + int ret = 0; + for (unsigned int i = 0; i < nfds; ++i) { + if (fds[i].revents != 0) { + ++ret; + } + } + + return ret; +} diff --git a/components/vfs/vfs_linux_default_coop.c b/components/vfs/vfs_linux_default_coop.c new file mode 100644 index 00000000000..c1e44573a89 --- /dev/null +++ b/components/vfs/vfs_linux_default_coop.c @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief Linux target VFS host filesystem registration + init + * + * Registers the cooperative libc ops (backed by freertos_linux_coop_*) + * as a VFS filesystem using esp_vfs_register_fs_with_id, so that + * file descriptors registered against this VFS ID are dispatched + * through the cooperative syscall layer. + * + * stdin/stdout/stderr are registered with VFS here, and all other + * kernel FDs are registered by the strong POSIX overrides in vfs_linux.c. + * + * Non-blocking syscalls (lseek, fstat, ioctl, fsync) are resolved directly + * via dlsym and bypass the cooperative interposition layer because they + * complete synchronously and never return EAGAIN/EWOULDBLOCK. + */ + +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_private/freertos_linux_coop_syscalls.h" +#include "esp_private/startup_internal.h" + +/* VFS ID for the Linux host filesystem — used by vfs_linux.c to register FDs */ +esp_vfs_id_t s_linux_host_vfs_id = -1; + +/* Real libc entry points for non-blocking syscalls, resolved at startup */ +static off_t (*real_lseek)(int, off_t, int); +static int (*real_fstat)(int, struct stat *); +static int (*real_fsync)(int); + +typedef int (*real_ioctl_fn_t)(int, unsigned long, ...); +static real_ioctl_fn_t real_ioctl; + +static ssize_t default_read(void *ctx, int fd, void *dst, size_t size) +{ + (void)ctx; + return freertos_linux_coop_read(fd, dst, size); +} + +static ssize_t default_write(void *ctx, int fd, const void *data, size_t size) +{ + (void)ctx; + return freertos_linux_coop_write(fd, data, size); +} + +static ssize_t default_pread(void *ctx, int fd, void *dst, size_t size, off_t offset) +{ + (void)ctx; + return freertos_linux_coop_pread(fd, dst, size, offset); +} + +static ssize_t default_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset) +{ + (void)ctx; + return freertos_linux_coop_pwrite(fd, src, size, offset); +} + +static int default_open(void *ctx, const char *path, int flags, int mode) +{ + (void)ctx; + return freertos_linux_coop_open(path, flags, mode); +} + +static int default_close(void *ctx, int fd) +{ + (void)ctx; + return freertos_linux_coop_close(fd); +} + +static int default_fcntl(void *ctx, int fd, int cmd, int arg) +{ + (void)ctx; + return freertos_linux_coop_fcntl(fd, cmd, arg); +} + +/* lseek never blocks — call real libc directly, no cooperative retry needed */ +static off_t default_lseek(void *ctx, int fd, off_t offset, int whence) +{ + (void)ctx; + return real_lseek(fd, offset, whence); +} + +/* fstat never blocks — call real libc directly, no cooperative retry needed */ +static int default_fstat(void *ctx, int fd, struct stat *st) +{ + (void)ctx; + return real_fstat(fd, st); +} + +/* ioctl never blocks — call real libc directly, no cooperative retry needed */ +static int default_ioctl(void *ctx, int fd, int cmd, va_list args) +{ + (void)ctx; + void *arg = va_arg(args, void *); + return real_ioctl(fd, (unsigned long)cmd, arg); +} + +/* fsync never blocks — call real libc directly, no cooperative retry needed */ +static int default_fsync(void *ctx, int fd) +{ + (void)ctx; + return real_fsync(fd); +} + +static const esp_vfs_fs_ops_t s_linux_host_ops = { + .read_p = default_read, + .write_p = default_write, + .pread_p = default_pread, + .pwrite_p = default_pwrite, + .open_p = default_open, + .close_p = default_close, + .fcntl_p = default_fcntl, + .lseek_p = default_lseek, + .fstat_p = default_fstat, + .ioctl_p = default_ioctl, + .fsync_p = default_fsync, +}; + +ESP_SYSTEM_INIT_FN(init_vfs_linux_coop, CORE, BIT(0), 99) +{ + /* Resolve real libc symbols for non-blocking syscalls */ + real_lseek = dlsym(RTLD_NEXT, "lseek"); + real_fstat = dlsym(RTLD_NEXT, "fstat"); + real_ioctl = dlsym(RTLD_NEXT, "ioctl"); + real_fsync = dlsym(RTLD_NEXT, "fsync"); + + freertos_linux_coop_syscalls_init(); + + /* Register the Linux host FS with VFS (id-based, no path prefix) */ + esp_err_t err = esp_vfs_register_fs_with_id(&s_linux_host_ops, + ESP_VFS_FLAG_CONTEXT_PTR, + NULL, + &s_linux_host_vfs_id); + if (err != ESP_OK) { + return err; + } + + /* Register stdin/stdout/stderr so apps see them as VFS FDs 0, 1, 2 */ + int fd; + esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDIN_FILENO, true, &fd); + esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDOUT_FILENO, true, &fd); + esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDERR_FILENO, true, &fd); + + return ESP_OK; +} + +void esp_vfs_include_linux_coop_init(void) +{ + // Linker hook function, exists to make the linker examine this file +} diff --git a/tools/mocks/freertos/CMakeLists.txt b/tools/mocks/freertos/CMakeLists.txt index 8cf8adddf92..21598b6fe26 100644 --- a/tools/mocks/freertos/CMakeLists.txt +++ b/tools/mocks/freertos/CMakeLists.txt @@ -16,6 +16,7 @@ set(include_dirs "${original_freertos_dir}/esp_additions/include/freertos" "${kernel_dir}/portable/linux/include" # For "spinlock.h" "${kernel_dir}/portable/linux/include/freertos" # For "portmacro.h" + "${kernel_dir}/portable/linux/include/esp_private" # CMock strips directory prefixes from includes "${kernel_dir}/include/freertos" # this is due to the way includes are generated in CMock (without freertos prefix) ) @@ -26,7 +27,8 @@ idf_component_mock(INCLUDE_DIRS ${include_dirs} ${original_freertos_dir}/FreeRTOS-Kernel/include/freertos/task.h ${original_freertos_dir}/FreeRTOS-Kernel/include/freertos/event_groups.h ${original_freertos_dir}/FreeRTOS-Kernel/include/freertos/queue.h - ${original_freertos_dir}/FreeRTOS-Kernel/portable/linux/include/freertos/portmacro.h) + ${original_freertos_dir}/FreeRTOS-Kernel/portable/linux/include/freertos/portmacro.h + ${kernel_dir}/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h) idf_component_get_property(freertos_lib freertos COMPONENT_LIB) target_compile_definitions(${freertos_lib} PUBLIC "projCOVERAGE_TEST=0") From 445db75612e28a0aca6736634173954a71c58a13 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Tue, 17 Mar 2026 11:14:52 +0100 Subject: [PATCH 3/4] fix(linux): implement pthread-based locks for soft-preemption safety The Linux target's sys/lock.h provided no-op inline stubs, which was safe only under the assumption of single-threaded execution. With the new FreeRTOS Linux simulator using soft preemption, an outgoing task can still run concurrently with the incoming task until it reaches a yield point, making no-op locks unsafe. Replace the no-op implementation with real pthread mutexes: - Change _lock_t from `typedef int` to `typedef void *` (pointer to a heap-allocated pthread_mutex_t). - Implement all _lock_* functions in a new lock.c, supporting both normal and recursive mutexes. - Zero-initialized locks are lazily created on first acquire using double-checked locking, preserving newlib/esp_libc semantics. - Add lock.c unconditionally to the linux component sources and link pthread. --- components/linux/CMakeLists.txt | 4 +- components/linux/include/sys/lock.h | 39 +++++----- components/linux/lock.c | 107 ++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 24 deletions(-) create mode 100644 components/linux/lock.c diff --git a/components/linux/CMakeLists.txt b/components/linux/CMakeLists.txt index d2716494317..1164b72f61b 100644 --- a/components/linux/CMakeLists.txt +++ b/components/linux/CMakeLists.txt @@ -3,7 +3,7 @@ if(NOT "${target}" STREQUAL "linux") return() endif() -set(srcs) +set(srcs "lock.c") set(includes "include") if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") list(APPEND srcs getrandom.c assert_func.c) @@ -16,6 +16,8 @@ idf_component_register(INCLUDE_DIRS ${includes} REQUIRED_IDF_TARGETS linux SRCS ${srcs}) +target_link_libraries(${COMPONENT_LIB} PRIVATE pthread) + find_library(LIB_BSD bsd) if(LIB_BSD) target_link_libraries(${COMPONENT_LIB} PRIVATE ${LIB_BSD}) diff --git a/components/linux/include/sys/lock.h b/components/linux/include/sys/lock.h index a2930d26c26..81e6273732a 100644 --- a/components/linux/include/sys/lock.h +++ b/components/linux/include/sys/lock.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,30 +12,23 @@ extern "C" { #endif -/* newlib locks implementation for CONFIG_IDF_TARGET_LINUX, single threaded. - * Note, currently this doesn't implement the functions required - * when _RETARGETABLE_LOCKING is defined. They should be added. +/** + * Lock type backed by pthread mutexes. A zero-initialized _lock_t is valid + * and will be lazily created on first acquire (matching newlib/esp_libc + * semantics). */ +typedef void * _lock_t; -/* Compatibility definitions for legacy newlib locking functions */ -typedef int _lock_t; - -static inline void _lock_init(_lock_t *plock) {} -static inline void _lock_init_recursive(_lock_t *plock) {} -static inline void _lock_close(_lock_t *plock) {} -static inline void _lock_close_recursive(_lock_t *plock) {} -static inline void _lock_acquire(_lock_t *plock) {} -static inline void _lock_acquire_recursive(_lock_t *plock) {} -static inline int _lock_try_acquire(_lock_t *plock) -{ - return 1; -} -static inline int _lock_try_acquire_recursive(_lock_t *plock) -{ - return 1; -} -static inline void _lock_release(_lock_t *plock) {} -static inline void _lock_release_recursive(_lock_t *plock) {} +void _lock_init(_lock_t *plock); +void _lock_init_recursive(_lock_t *plock); +void _lock_close(_lock_t *plock); +void _lock_close_recursive(_lock_t *plock); +void _lock_acquire(_lock_t *plock); +void _lock_acquire_recursive(_lock_t *plock); +int _lock_try_acquire(_lock_t *plock); +int _lock_try_acquire_recursive(_lock_t *plock); +void _lock_release(_lock_t *plock); +void _lock_release_recursive(_lock_t *plock); #ifdef __cplusplus } diff --git a/components/linux/lock.c b/components/linux/lock.c new file mode 100644 index 00000000000..5e22a345556 --- /dev/null +++ b/components/linux/lock.c @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +/* + * Protects lazy initialization of locks that were zero-initialized + * (e.g. static _lock_t my_lock = 0;). Two threads calling + * _lock_acquire on such a lock race to create the underlying mutex; + * this guard serialises the creation. + */ +static pthread_mutex_t s_lock_init_guard = PTHREAD_MUTEX_INITIALIZER; + +static void lock_init_generic(_lock_t *plock, int recursive) +{ + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + if (recursive) { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + } + pthread_mutex_t *m = calloc(1, sizeof(pthread_mutex_t)); + if (m == NULL) { + pthread_mutexattr_destroy(&attr); + abort(); + } + pthread_mutex_init(m, &attr); + pthread_mutexattr_destroy(&attr); + *plock = (_lock_t)m; +} + +static void lazy_init(_lock_t *plock, int recursive) +{ + if (__atomic_load_n(plock, __ATOMIC_ACQUIRE) == NULL) { + pthread_mutex_lock(&s_lock_init_guard); + if (*plock == NULL) { + lock_init_generic(plock, recursive); + } + pthread_mutex_unlock(&s_lock_init_guard); + } +} + +void _lock_init(_lock_t *plock) +{ + *plock = NULL; + lock_init_generic(plock, 0); +} + +void _lock_init_recursive(_lock_t *plock) +{ + *plock = NULL; + lock_init_generic(plock, 1); +} + +void _lock_close(_lock_t *plock) +{ + if (*plock) { + pthread_mutex_destroy((pthread_mutex_t *)(*plock)); + free(*plock); + *plock = NULL; + } +} + +void _lock_close_recursive(_lock_t *plock) +{ + _lock_close(plock); +} + +void _lock_acquire(_lock_t *plock) +{ + lazy_init(plock, 0); + pthread_mutex_lock((pthread_mutex_t *)(*plock)); +} + +void _lock_acquire_recursive(_lock_t *plock) +{ + lazy_init(plock, 1); + pthread_mutex_lock((pthread_mutex_t *)(*plock)); +} + +int _lock_try_acquire(_lock_t *plock) +{ + lazy_init(plock, 0); + return (pthread_mutex_trylock((pthread_mutex_t *)(*plock)) == 0) ? 0 : -1; +} + +int _lock_try_acquire_recursive(_lock_t *plock) +{ + lazy_init(plock, 1); + return (pthread_mutex_trylock((pthread_mutex_t *)(*plock)) == 0) ? 0 : -1; +} + +void _lock_release(_lock_t *plock) +{ + if (*plock) { + pthread_mutex_unlock((pthread_mutex_t *)(*plock)); + } +} + +void _lock_release_recursive(_lock_t *plock) +{ + _lock_release(plock); +} From 62a28e250f60832511b6c46f86d6e75d3b911724 Mon Sep 17 00:00:00 2001 From: Guillaume Souchere Date: Fri, 20 Mar 2026 12:38:12 +0100 Subject: [PATCH 4/4] feat(lwip): replace --wrap linker hacks with proper VFS registration on linux builds The Linux LWIP port was using --wrap linker flags to intercept read, write, close, fcntl and select, routing socket FDs (>= LWIP_SOCKET_OFFSET) to lwip_*() functions. This conflicted with the dlsym(RTLD_NEXT) cooperative syscall interposition used by the new FreeRTOS Linux simulator. Replace the --wrap approach with esp_vfs_register_fd_range(), matching how LWIP already integrates with VFS on bare-metal ESP32 targets. The cooperative read()/write() wrappers in vfs_coop_syscalls.c check VFS first, so LWIP FDs are dispatched to lwip_read() etc. before the cooperative fallback path is reached. --- components/lwip/CMakeLists.txt | 28 ++------ .../lwip/port/linux/include/arch/vfs_lwip.h | 12 +++- components/lwip/port/linux/vfs_lwip.c | 65 ------------------- components/lwip/port/{esp32xx => }/vfs_lwip.c | 4 +- docs/en/api-reference/storage/vfs.rst | 2 +- docs/zh_CN/api-reference/storage/vfs.rst | 2 +- 6 files changed, 21 insertions(+), 92 deletions(-) delete mode 100644 components/lwip/port/linux/vfs_lwip.c rename components/lwip/port/{esp32xx => }/vfs_lwip.c (95%) diff --git a/components/lwip/CMakeLists.txt b/components/lwip/CMakeLists.txt index 160e89a020f..b929bdb5e2b 100644 --- a/components/lwip/CMakeLists.txt +++ b/components/lwip/CMakeLists.txt @@ -143,17 +143,14 @@ if(CONFIG_LWIP_ENABLE) endif() -if(NOT ${target} STREQUAL "linux") - # Support for vfs and linker fragments only for target builds + if(NOT ${target} STREQUAL "linux") set(linker_fragments linker.lf) - if(CONFIG_VFS_SUPPORT_IO) - list(APPEND srcs "port/${target}/vfs_lwip.c") - else() - list(APPEND srcs "port/${target}/no_vfs_syscalls.c") - endif() + endif() + + if(CONFIG_VFS_SUPPORT_IO) + list(APPEND srcs "port/vfs_lwip.c") else() - # This wraps some posix IO functions to conditionally pass control to lwip - list(APPEND srcs "port/${target}/vfs_lwip.c") + list(APPEND srcs "port/${target}/no_vfs_syscalls.c") endif() if(CONFIG_LWIP_ICMP) @@ -179,9 +176,7 @@ if(NOT ${target} STREQUAL "linux") endif() endif() # CONFIG_LWIP_ENABLE -if(NOT ${target} STREQUAL "linux") - set(priv_requires vfs) -endif() +set(priv_requires vfs) idf_component_register(SRCS "${srcs}" INCLUDE_DIRS ${include_dirs} @@ -229,14 +224,5 @@ if(CONFIG_LWIP_ENABLE) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) target_link_libraries(${COMPONENT_LIB} PRIVATE Threads::Threads) - set(WRAP_FUNCTIONS select - read - fcntl - write - close) - foreach(wrap ${WRAP_FUNCTIONS}) - target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") - target_link_libraries(${COMPONENT_LIB} INTERFACE "-u __wrap_${wrap}") - endforeach() endif() endif() diff --git a/components/lwip/port/linux/include/arch/vfs_lwip.h b/components/lwip/port/linux/include/arch/vfs_lwip.h index 79de75600fc..ae9ced18af0 100644 --- a/components/lwip/port/linux/include/arch/vfs_lwip.h +++ b/components/lwip/port/linux/include/arch/vfs_lwip.h @@ -1,8 +1,16 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #pragma once -static inline void esp_vfs_lwip_sockets_register(void) {} +#ifdef __cplusplus +extern "C" { +#endif + +void esp_vfs_lwip_sockets_register(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/lwip/port/linux/vfs_lwip.c b/components/lwip/port/linux/vfs_lwip.c deleted file mode 100644 index 116f8bc695e..00000000000 --- a/components/lwip/port/linux/vfs_lwip.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include - -extern int __real_fcntl(int s, int cmd, ...); -extern int __real_close(int s); -extern ssize_t __real_write (int fd, const void *buf, size_t n); -extern ssize_t __real_read (int fd, void *buf, size_t n); -extern int __real_select (int fd, fd_set * rfds, fd_set * wfds, fd_set *efds, struct timeval *tval); - -ssize_t __wrap_write (int fd, const void *buf, size_t n) -{ -#ifdef CONFIG_LWIP_MAX_SOCKETS - if (fd >= LWIP_SOCKET_OFFSET) - return lwip_write(fd, buf, n); -#endif - return __real_write(fd, buf, n); -} - -ssize_t __wrap_read (int fd, void *buf, size_t n) -{ -#ifdef CONFIG_LWIP_MAX_SOCKETS - if (fd >= LWIP_SOCKET_OFFSET) - return lwip_read(fd, buf, n); -#endif - return __real_read(fd, buf, n); -} - -int __wrap_select (int fd, fd_set * rds, fd_set * wfds, fd_set *efds, struct timeval *tval) -{ -#ifdef CONFIG_LWIP_MAX_SOCKETS - if (fd >= LWIP_SOCKET_OFFSET) - return lwip_select(fd, rds, wfds, efds, tval); -#endif - return __real_select(fd, rds, wfds, efds, tval); -} - -int __wrap_fcntl(int fd, int cmd, ...) -{ - va_list args; -#ifdef CONFIG_LWIP_MAX_SOCKETS - if (fd >= LWIP_SOCKET_OFFSET) { - va_start(args, cmd); - int arg = va_arg(args, int); - va_end(args); - return lwip_fcntl(fd, cmd, arg); - } -#endif - - return __real_fcntl(fd, cmd, args); -} - -int __wrap_close(int fd) -{ -#ifdef CONFIG_LWIP_MAX_SOCKETS - if (fd >= LWIP_SOCKET_OFFSET) - return lwip_close(fd); -#endif - return __real_close(fd); -} diff --git a/components/lwip/port/esp32xx/vfs_lwip.c b/components/lwip/port/vfs_lwip.c similarity index 95% rename from components/lwip/port/esp32xx/vfs_lwip.c rename to components/lwip/port/vfs_lwip.c index dc466e4c4f8..da468d698ef 100644 --- a/components/lwip/port/esp32xx/vfs_lwip.c +++ b/components/lwip/port/vfs_lwip.c @@ -65,12 +65,12 @@ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct #endif // CONFIG_VFS_SUPPORT_SELECT -static int lwip_write_r_wrapper(__attribute__((unused)) void *ctx, int fd, const void *data, size_t size) +static ssize_t lwip_write_r_wrapper(__attribute__((unused)) void *ctx, int fd, const void *data, size_t size) { return lwip_write(fd, data, size); } -static int lwip_read_r_wrapper(__attribute__((unused)) void *ctx, int fd, void *data, size_t size) +static ssize_t lwip_read_r_wrapper(__attribute__((unused)) void *ctx, int fd, void *data, size_t size) { return lwip_read(fd, data, size); } diff --git a/docs/en/api-reference/storage/vfs.rst b/docs/en/api-reference/storage/vfs.rst index 181a8cfdd33..c7c3376d158 100644 --- a/docs/en/api-reference/storage/vfs.rst +++ b/docs/en/api-reference/storage/vfs.rst @@ -168,7 +168,7 @@ A socket VFS driver needs to be registered with the following functions defined: :cpp:func:`stop_socket_select_isr` has the same functionality as :cpp:func:`stop_socket_select` but it can be used from ISR. -Please see :component_file:`lwip/port/esp32xx/vfs_lwip.c` for a reference socket driver implementation using LWIP. +Please see :component_file:`lwip/port/vfs_lwip.c` for a reference socket driver implementation using LWIP. .. note:: diff --git a/docs/zh_CN/api-reference/storage/vfs.rst b/docs/zh_CN/api-reference/storage/vfs.rst index c291cea17d7..0c141f354b5 100644 --- a/docs/zh_CN/api-reference/storage/vfs.rst +++ b/docs/zh_CN/api-reference/storage/vfs.rst @@ -168,7 +168,7 @@ VFS 组件支持通过 :cpp:func:`select` 进行同步输入/输出多路复用 :cpp:func:`stop_socket_select_isr` 与 :cpp:func:`stop_socket_select` 的作用相似,但是前者可在 ISR 中使用。 -请参考 :component_file:`lwip/port/esp32xx/vfs_lwip.c` 以了解使用 LWIP 的套接字驱动参考实现。 +请参考 :component_file:`lwip/port/vfs_lwip.c` 以了解使用 LWIP 的套接字驱动参考实现。 .. note::