feat(freertos): soft-preempting linux simulator

This commit is contained in:
Guillaume Souchere
2026-06-04 11:36:21 +02:00
parent 848fbc8d74
commit 78420f2614
19 changed files with 1428 additions and 946 deletions
@@ -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,452 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*
* Cooperative wrappers for Linux FreeRTOS simulator.
*/
#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 <time.h>
#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);
}
@@ -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
@@ -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