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
@@ -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