feat(freertos/vfs): add Linux cooperative syscall dispatch with VFS FD registration

Rework the syscall interposition architecture so FreeRTOS works
standalone (without VFS) and VFS optionally overrides with strong
symbols.  All kernel FDs are registered with VFS via
esp_vfs_register_fd_with_local_fd, so the application only sees
VFS-allocated FD numbers, preventing numerical collisions between
kernel FDs and VFS-internal FD slots.

FreeRTOS side:
- Create linux_port_coop_internal.h with LINUX_COOP_IO_LOOP,
  LINUX_COOP_RESOLVE, linux_coop_yield and FD state table functions
- Export cooperative I/O primitives (freertos_linux_coop_read/write/
  open/close/fcntl/select/pread/pwrite/readv/writev/recv/send/
  recvfrom/sendto/recvmsg/sendmsg/connect/accept/pselect/poll/
  socket/socketpair/pipe/pipe2/dup/dup2/syscalls_init)
- Define weak POSIX symbols calling through to the cooperative
  primitives; keep nanosleep/sleep/usleep as strong (not FD-related)
- Refactor freertos_linux_coop_syscalls.h into a pure public API header

VFS side:
- Register the Linux host FS with esp_vfs_register_fs_with_id() as a
  proper VFS driver; register stdin/stdout/stderr at init (priority 99)
- Rewrite vfs_linux.c with strong POSIX symbols dispatching through
  esp_vfs_*
- Add strong overrides for FD-creating syscalls (open, pipe, pipe2,
  socket, socketpair, dup, dup2, accept) that register returned FDs
  with VFS
- Add strong overrides for FD-translating syscalls (readv, writev,
  recv, send, recvfrom, sendto, recvmsg, sendmsg, connect, pselect,
  poll) that translate VFS FD to kernel FD
- Delete vfs_coop_syscalls.c (absorbed into FreeRTOS weak + VFS strong)
- Update CMakeLists.txt: remove vfs_coop_syscalls.c, add linker hook
This commit is contained in:
Guillaume Souchere
2026-06-04 11:36:34 +02:00
parent 78420f2614
commit 9835daba70
13 changed files with 1747 additions and 385 deletions
+3 -46
View File
@@ -104,16 +104,6 @@ list(APPEND srcs
"esp_additions/idf_additions_event_groups.c"
"esp_additions/idf_additions.c")
if(arch STREQUAL "linux")
# Check if we need to address the FreeRTOS EINTR coexistence with linux system calls if we're building without
# lwIP enabled, we need to use linux system select which will receive EINTR event on every FreeRTOS interrupt, we
# workaround this problem by wrapping select() to bypass and silence the EINTR events
set(BYPASS_EINTR_ISSUE 0)
if(NOT CONFIG_LWIP_ENABLE)
set(BYPASS_EINTR_ISSUE 1)
endif()
endif()
# ------------------------------------------------ Set Public Includes -------------------------------------------------
# Add common public include directories
@@ -186,42 +176,9 @@ idf_component_register(SRCS ${srcs}
if(arch STREQUAL "linux")
target_compile_definitions(${COMPONENT_LIB} PUBLIC "projCOVERAGE_TEST=0")
target_link_libraries(${COMPONENT_LIB} PUBLIC pthread)
if(BYPASS_EINTR_ISSUE)
target_link_libraries(${COMPONENT_LIB} PRIVATE dl)
endif()
set(WRAP_FUNCTIONS
read
write
pread
pwrite
readv
writev
recv
send
recvfrom
sendto
recvmsg
sendmsg
connect
accept
close
select
pselect
poll
sleep
usleep
open
socket
socketpair
pipe
pipe2
dup
dup2)
foreach(wrap ${WRAP_FUNCTIONS})
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}")
endforeach()
# dl is needed for dlsym(RTLD_NEXT, ...) used by the cooperative syscall
# interposition layer (linux_port_coop_syscalls.c)
target_link_libraries(${COMPONENT_LIB} PRIVATE dl)
# Disable strict prototype warnings in upstream code
# (struct event * event_create() is missing 'void')
@@ -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
@@ -13,6 +13,7 @@
#include "FreeRTOS.h"
#include "task.h"
#include "utils/wait_for_event.h"
#include "esp_private/freertos_linux_coop_syscalls.h"
#include "utils/linux_port_utils.h"
#define FREERTOS_SIM_TICK_PERIOD_US (1000000 / CONFIG_FREERTOS_HZ)
@@ -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
File diff suppressed because it is too large Load Diff
@@ -1,23 +0,0 @@
/*
* Cooperative syscalls subsystem for the Linux FreeRTOS simulator.
*
* This header exposes the public initialization API needed by the
* FreeRTOS Linux port. The subsystem provides blocking read()/write()
* for FreeRTOS tasks without stalling the cooperative scheduler by
* forwarding operations to a dedicated I/O worker thread.
*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void linux_port_coop_syscalls_init(void);
#ifdef __cplusplus
}
#endif