Merge branch 'feat/new-freertos-linux-simulator' into 'master'

feat(freertos): Refactor linux simulator

Closes IDF-8339

See merge request espressif/esp-idf!43564
This commit is contained in:
Guillaume Souchere
2026-06-05 12:18:44 +02:00
35 changed files with 3033 additions and 1158 deletions
+5 -15
View File
@@ -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")
@@ -103,17 +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)
list(APPEND srcs "esp_additions/FreeRTOSSimulator_wrappers.c")
endif()
endif()
# ------------------------------------------------ Set Public Includes -------------------------------------------------
# Add common public include directories
@@ -186,9 +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()
# 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')
@@ -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 <stddef.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <poll.h>
#include <signal.h>
#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
@@ -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 );
File diff suppressed because it is too large Load Diff
@@ -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 <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <time.h>
#include <unistd.h>
#include <execinfo.h>
#include <signal.h>
#include <pthread.h>
/* 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 "
@@ -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 <errno.h>
#include <time.h>
#include <dlfcn.h>
#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_<name> 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
@@ -0,0 +1,795 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <time.h>
#include <poll.h>
#include <dlfcn.h>
#include <pthread.h>
#include "linux_port_coop_internal.h"
#include "esp_private/freertos_linux_coop_syscalls.h"
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 *);
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 = real_fcntl(fd, F_GETFL, 0);
if (flags >= 0) {
real_fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
}
}
void linux_coop_track_fd(int fd, int user_flags)
{
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);
}
}
/* 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;
}
long waited_ms = 0;
while (1) {
if (readfds != NULL) {
*readfds = rfds_in;
}
if (writefds != NULL) {
*writefds = wfds_in;
}
if (exceptfds != NULL) {
*exceptfds = efds_in;
}
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;
}
}
void freertos_linux_coop_syscalls_init(void)
{
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);
}
/* 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)
{
return freertos_linux_coop_read(fd, buf, count);
}
__attribute__((weak)) ssize_t write(int fd, const void *buf, size_t count)
{
return freertos_linux_coop_write(fd, buf, count);
}
__attribute__((weak)) ssize_t pread(int fd, void *buf, size_t count, off_t offset)
{
return freertos_linux_coop_pread(fd, buf, count, offset);
}
__attribute__((weak)) ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset)
{
return freertos_linux_coop_pwrite(fd, buf, count, offset);
}
__attribute__((weak)) int open(const char *path, int flags, ...)
{
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);
}
__attribute__((weak)) int close(int fd)
{
return freertos_linux_coop_close(fd);
}
__attribute__((weak)) 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);
}
return freertos_linux_coop_fcntl(fd, cmd, arg);
}
__attribute__((weak)) int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout)
{
return freertos_linux_coop_select(nfds, readfds, writefds, exceptfds, timeout);
}
ssize_t freertos_linux_coop_readv(int fd, const struct iovec *iov, int iovcnt)
{
LINUX_COOP_IO_LOOP(fd, real_readv(fd, iov, iovcnt))
}
ssize_t freertos_linux_coop_writev(int fd, const struct iovec *iov, int iovcnt)
{
LINUX_COOP_IO_LOOP(fd, real_writev(fd, iov, iovcnt))
}
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);
if (ret == 0) {
return ret;
}
if (errno == EINPROGRESS || errno == EALREADY) {
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);
if (press > 0) {
int so_err = 0;
socklen_t len = sizeof(so_err);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR,
&so_err, &len) < 0) {
return -1;
}
if (so_err == 0) {
return 0;
} else {
errno = so_err;
return -1;
}
} else if (press == 0) {
linux_coop_yield(LINUX_COOP_TICK_MS);
continue;
} else {
if (errno == EINTR) {
continue;
}
/* 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) {
return -1;
}
return -1;
}
}
int freertos_linux_coop_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
while (1)
{
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;
}
return -1;
}
}
int freertos_linux_coop_pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask)
{
long timeout_ms = -1;
if (timeout != NULL) {
timeout_ms = (long)timeout->tv_sec * 1000 +
(timeout->tv_nsec + 999999) / 1000000;
if (timeout_ms == 0) {
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;
}
linux_coop_yield(LINUX_COOP_TICK_MS);
waited_ms += LINUX_COOP_TICK_MS;
}
}
int freertos_linux_coop_poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
if (timeout == 0) {
return real_poll(fds, nfds, 0);
}
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) {
if (timeout_ms == 0 ||
(timeout_ms > 0 && waited_ms >= timeout_ms)) {
return 0;
}
linux_coop_yield(LINUX_COOP_TICK_MS);
waited_ms += LINUX_COOP_TICK_MS;
continue;
} else {
if (errno == EINTR) {
continue;
}
linux_coop_yield(LINUX_COOP_TICK_MS);
if (timeout_ms > 0 && waited_ms >= timeout_ms) {
return -1;
}
waited_ms += LINUX_COOP_TICK_MS;
}
}
}
int nanosleep(const struct timespec *req, struct timespec *rem)
{
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;
}
unsigned int sleep(unsigned int seconds)
{
linux_coop_yield(seconds * 1000);
return 0;
}
int usleep(useconds_t usec)
{
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 freertos_linux_coop_socketpair(int domain, int type, int protocol, int *sv)
{
int ret = real_socketpair(domain, type, protocol, sv);
if (ret == 0) {
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 freertos_linux_coop_pipe(int *fds)
{
int ret = real_pipe(fds);
if (ret == 0) {
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 freertos_linux_coop_pipe2(int *fds, int flags)
{
int ret = real_pipe2(fds, flags);
if (ret == 0) {
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 freertos_linux_coop_dup(int oldfd)
{
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 freertos_linux_coop_dup2(int oldfd, int newfd)
{
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;
}
__attribute__((weak)) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
{
return freertos_linux_coop_connect(sockfd, addr, addrlen);
}
__attribute__((weak)) int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
{
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);
}
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <pthread.h>
#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
@@ -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 <pthread.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#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);
}
@@ -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 <stdbool.h>
#include <time.h>
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
@@ -1,95 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <dlfcn.h>
#include <assert.h>
#include <sys/select.h>
#include <errno.h>
/** 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);
}
}
@@ -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 )