diff --git a/components/esp_system/system_init_fn.txt b/components/esp_system/system_init_fn.txt index 0b9c8d8f736..252598df81c 100644 --- a/components/esp_system/system_init_fn.txt +++ b/components/esp_system/system_init_fn.txt @@ -24,6 +24,9 @@ CORE: 10: init_show_cpu_freq in components/esp_system/startup_funcs.c on BIT(0) CORE: 20: init_show_app_info in components/esp_app_format/esp_app_desc.c on BIT(0) CORE: 21: init_efuse_show_app_info in components/efuse/src/esp_efuse_startup.c on BIT(0) +# Set the standard stream to non blocking and register the default vfs +CORE: 99: init_vfs_linux_coop in components/vfs/vfs_linux_default_coop.c on BIT(0) + # Initialize heap allocator. WARNING: This *needs* to happen *after* the app cpu has booted. # If the heap allocator is initialized first, it will put free memory linked list items into # memory also used by the ROM. Starting the app cpu will let its ROM initialize that memory, diff --git a/components/freertos/CMakeLists.txt b/components/freertos/CMakeLists.txt index 3d91ec408a0..d9017f3c15d 100644 --- a/components/freertos/CMakeLists.txt +++ b/components/freertos/CMakeLists.txt @@ -104,16 +104,6 @@ list(APPEND srcs "esp_additions/idf_additions_event_groups.c" "esp_additions/idf_additions.c") -if(arch STREQUAL "linux") - # Check if we need to address the FreeRTOS EINTR coexistence with linux system calls if we're building without - # lwIP enabled, we need to use linux system select which will receive EINTR event on every FreeRTOS interrupt, we - # workaround this problem by wrapping select() to bypass and silence the EINTR events - set(BYPASS_EINTR_ISSUE 0) - if(NOT CONFIG_LWIP_ENABLE) - set(BYPASS_EINTR_ISSUE 1) - endif() -endif() - # ------------------------------------------------ Set Public Includes ------------------------------------------------- # Add common public include directories @@ -186,42 +176,9 @@ idf_component_register(SRCS ${srcs} if(arch STREQUAL "linux") target_compile_definitions(${COMPONENT_LIB} PUBLIC "projCOVERAGE_TEST=0") target_link_libraries(${COMPONENT_LIB} PUBLIC pthread) - if(BYPASS_EINTR_ISSUE) - target_link_libraries(${COMPONENT_LIB} PRIVATE dl) - endif() - - set(WRAP_FUNCTIONS - read - write - pread - pwrite - readv - writev - recv - send - recvfrom - sendto - recvmsg - sendmsg - connect - accept - close - select - pselect - poll - sleep - usleep - open - socket - socketpair - pipe - pipe2 - dup - dup2) - - foreach(wrap ${WRAP_FUNCTIONS}) - target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=${wrap}") - endforeach() + # dl is needed for dlsym(RTLD_NEXT, ...) used by the cooperative syscall + # interposition layer (linux_port_coop_syscalls.c) + target_link_libraries(${COMPONENT_LIB} PRIVATE dl) # Disable strict prototype warnings in upstream code # (struct event * event_create() is missing 'void') diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h b/components/freertos/FreeRTOS-Kernel/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h new file mode 100644 index 00000000000..098129cf737 --- /dev/null +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h @@ -0,0 +1,314 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Public API for the FreeRTOS Linux cooperative syscall layer. + * + * This header is included by the VFS component (and any other component + * that needs to call the cooperative I/O functions directly). + * + * Internal helpers (LINUX_COOP_IO_LOOP, LINUX_COOP_RESOLVE, FD state + * table functions, etc.) live in linux_port_coop_internal.h. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Cooperative read — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to read from. + * @param buf Destination buffer. + * @param count Maximum number of bytes to read. + * @return Number of bytes read on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_read(int fd, void *buf, size_t count); + +/** + * @brief Cooperative write — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to write to. + * @param buf Source buffer. + * @param count Number of bytes to write. + * @return Number of bytes written on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_write(int fd, const void *buf, size_t count); + +/** + * @brief Cooperative pread — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to read from. + * @param buf Destination buffer. + * @param count Maximum number of bytes to read. + * @param offset File offset to read from. + * @return Number of bytes read on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_pread(int fd, void *buf, size_t count, off_t offset); + +/** + * @brief Cooperative pwrite — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to write to. + * @param buf Source buffer. + * @param count Number of bytes to write. + * @param offset File offset to write at. + * @return Number of bytes written on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_pwrite(int fd, const void *buf, size_t count, off_t offset); + +/** + * @brief Cooperative readv — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to read from. + * @param iov Array of iovec structures describing the buffers. + * @param iovcnt Number of elements in the iov array. + * @return Number of bytes read on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_readv(int fd, const struct iovec *iov, int iovcnt); + +/** + * @brief Cooperative writev — retries on EAGAIN when user-visible mode is blocking. + * + * @param fd File descriptor to write to. + * @param iov Array of iovec structures describing the buffers. + * @param iovcnt Number of elements in the iov array. + * @return Number of bytes written on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_writev(int fd, const struct iovec *iov, int iovcnt); + +/** + * @brief Cooperative recv — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param buf Destination buffer. + * @param len Maximum number of bytes to receive. + * @param flags recv flags (MSG_PEEK, etc.). + * @return Number of bytes received on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_recv(int sockfd, void *buf, size_t len, int flags); + +/** + * @brief Cooperative send — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param buf Source buffer. + * @param len Number of bytes to send. + * @param flags send flags (MSG_NOSIGNAL, etc.). + * @return Number of bytes sent on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_send(int sockfd, const void *buf, size_t len, int flags); + +/** + * @brief Cooperative recvfrom — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param buf Destination buffer. + * @param len Maximum number of bytes to receive. + * @param flags recv flags. + * @param src_addr Source address (may be NULL). + * @param addrlen Length of source address (may be NULL). + * @return Number of bytes received on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen); + +/** + * @brief Cooperative sendto — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param buf Source buffer. + * @param len Number of bytes to send. + * @param flags send flags. + * @param dest_addr Destination address. + * @param addrlen Length of destination address. + * @return Number of bytes sent on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen); + +/** + * @brief Cooperative recvmsg — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param msg Message header describing buffers and ancillary data. + * @param flags recv flags. + * @return Number of bytes received on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_recvmsg(int sockfd, struct msghdr *msg, int flags); + +/** + * @brief Cooperative sendmsg — retries on EAGAIN when user-visible mode is blocking. + * + * @param sockfd Socket file descriptor. + * @param msg Message header describing buffers and ancillary data. + * @param flags send flags. + * @return Number of bytes sent on success, or -1 on error (errno is set). + */ +ssize_t freertos_linux_coop_sendmsg(int sockfd, const struct msghdr *msg, int flags); + +/** + * @brief Cooperative connect — retries on EINPROGRESS with cooperative yield. + * + * @param sockfd Socket file descriptor. + * @param addr Destination address. + * @param addrlen Length of destination address. + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); + +/** + * @brief Cooperative accept — retries on EAGAIN when user-visible mode is blocking. + * + * Sets the accepted socket to non-blocking and starts tracking it. + * + * @param sockfd Listening socket file descriptor. + * @param addr Peer address (may be NULL). + * @param addrlen Length of peer address (may be NULL). + * @return New socket file descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); + +/** + * @brief Cooperative open — sets the new FD to non-blocking and tracks it. + * + * @param path Path of the file to open. + * @param flags Open flags (O_RDONLY, O_WRONLY, O_CREAT, etc.). + * @param mode File creation mode (used when O_CREAT is set). + * @return File descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_open(const char *path, int flags, int mode); + +/** + * @brief Cooperative close — untracks the FD, retries on EINTR. + * + * @param fd File descriptor to close. + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_close(int fd); + +/** + * @brief Cooperative fcntl — intercepts F_GETFL/F_SETFL for shadow mode. + * + * @param fd File descriptor. + * @param cmd fcntl command (F_GETFL, F_SETFL, etc.). + * @param arg Command argument. + * @return Command-dependent value on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_fcntl(int fd, int cmd, int arg); + +/** + * @brief Cooperative socket — sets the new socket to non-blocking and tracks it. + * + * @param domain Communication domain (AF_INET, AF_UNIX, etc.). + * @param type Socket type (SOCK_STREAM, SOCK_DGRAM, etc.). + * @param protocol Protocol number (0 for default). + * @return Socket file descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_socket(int domain, int type, int protocol); + +/** + * @brief Cooperative socketpair — sets both sockets to non-blocking and tracks them. + * + * @param domain Communication domain. + * @param type Socket type. + * @param protocol Protocol number. + * @param sv Array of two ints to receive the file descriptors. + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_socketpair(int domain, int type, int protocol, int *sv); + +/** + * @brief Cooperative pipe — sets both ends to non-blocking and tracks them. + * + * @param fds Array of two ints: fds[0] is the read end, fds[1] is the write end. + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_pipe(int *fds); + +/** + * @brief Cooperative pipe2 — sets both ends to non-blocking and tracks them. + * + * @param fds Array of two ints: fds[0] is the read end, fds[1] is the write end. + * @param flags Pipe flags (O_CLOEXEC, O_NONBLOCK, etc.). + * @return 0 on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_pipe2(int *fds, int flags); + +/** + * @brief Cooperative dup — duplicates a FD, sets the new FD to non-blocking and tracks it. + * + * @param oldfd File descriptor to duplicate. + * @return New file descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_dup(int oldfd); + +/** + * @brief Cooperative dup2 — duplicates a FD to a specific number, sets non-blocking and tracks it. + * + * @param oldfd File descriptor to duplicate. + * @param newfd Desired file descriptor number. + * @return New file descriptor on success, or -1 on error (errno is set). + */ +int freertos_linux_coop_dup2(int oldfd, int newfd); + +/** + * @brief Cooperative select — polls with cooperative yield. + * + * @param nfds Highest-numbered file descriptor plus one. + * @param readfds Set of file descriptors to watch for readability (may be NULL). + * @param writefds Set of file descriptors to watch for writability (may be NULL). + * @param exceptfds Set of file descriptors to watch for exceptions (may be NULL). + * @param timeout Maximum wait time (NULL for infinite). + * @return Number of ready descriptors, 0 on timeout, or -1 on error (errno is set). + */ +int freertos_linux_coop_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); + +/** + * @brief Cooperative pselect — polls with cooperative yield and signal mask. + * + * @param nfds Highest-numbered file descriptor plus one. + * @param readfds Set of file descriptors to watch for readability (may be NULL). + * @param writefds Set of file descriptors to watch for writability (may be NULL). + * @param exceptfds Set of file descriptors to watch for exceptions (may be NULL). + * @param timeout Maximum wait time (NULL for infinite). + * @param sigmask Signal mask to apply during the wait (may be NULL). + * @return Number of ready descriptors, 0 on timeout, or -1 on error (errno is set). + */ +int freertos_linux_coop_pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask); + +/** + * @brief Cooperative poll — polls with cooperative yield. + * + * @param fds Array of pollfd structures describing the FDs to watch. + * @param nfds Number of elements in the fds array. + * @param timeout Timeout in milliseconds (-1 for infinite, 0 for non-blocking). + * @return Number of ready descriptors, 0 on timeout, or -1 on error (errno is set). + */ +int freertos_linux_coop_poll(struct pollfd *fds, nfds_t nfds, int timeout); + +/** + * @brief Set stdin/stdout/stderr to non-blocking and start tracking them. + * + * Must be called early during system init. + */ +void freertos_linux_coop_syscalls_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/port.c b/components/freertos/FreeRTOS-Kernel/portable/linux/port.c index ea370e0fd65..f358a96e258 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/port.c +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/port.c @@ -13,6 +13,7 @@ #include "FreeRTOS.h" #include "task.h" #include "utils/wait_for_event.h" +#include "esp_private/freertos_linux_coop_syscalls.h" #include "utils/linux_port_utils.h" #define FREERTOS_SIM_TICK_PERIOD_US (1000000 / CONFIG_FREERTOS_HZ) diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_internal.h b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_internal.h new file mode 100644 index 00000000000..24073f6f34e --- /dev/null +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_internal.h @@ -0,0 +1,145 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Internal header for FreeRTOS Linux cooperative syscall wrappers. + * Not for use outside the FreeRTOS portable layer. + */ + +#pragma once + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Cooperative tick period in milliseconds. + */ +#define LINUX_COOP_TICK_MS (1000 / CONFIG_FREERTOS_HZ) + +/** + * @brief Resolve real libc symbol into a file-static function pointer. + * + * Use inside a constructor function. real_ must be declared as + * a file-static variable of the correct function-pointer type. + * + * @param name Name of the libc symbol to resolve. + */ +#define LINUX_COOP_RESOLVE(name) real_##name = dlsym(RTLD_NEXT, #name) + +/** + * @brief Check whether the calling thread is a FreeRTOS task pthread. + * + * @return true if called from a FreeRTOS task, false otherwise. + */ +bool linux_port_in_freertos_task(void); + +/** + * @brief Set a kernel file descriptor to O_NONBLOCK mode. + * + * @param fd File descriptor to make non-blocking. + */ +void linux_coop_set_nonblocking(int fd); + +/** + * @brief Track a file descriptor in the cooperative mode table. + * + * Real kernel FDs are kept non-blocking internally. This function stores the + * user-visible file status flags so wrappers can preserve POSIX blocking/ + * non-blocking semantics. + * + * @param fd File descriptor to track. + * @param user_flags User-visible status flags (as passed to fcntl F_SETFL). + */ +void linux_coop_track_fd(int fd, int user_flags); + +/** + * @brief Stop tracking a file descriptor in the cooperative mode table. + * + * @param fd File descriptor to untrack. + */ +void linux_coop_untrack_fd(int fd); + +/** + * @brief Update user-visible status flags for a tracked file descriptor. + * + * @param fd File descriptor to update. + * @param user_flags New user-visible status flags. + */ +void linux_coop_set_user_flags(int fd, int user_flags); + +/** + * @brief Query whether user-visible mode is non-blocking for a file descriptor. + * + * @param fd File descriptor to query. + * @return true if user-visible mode is non-blocking, false otherwise. + */ +bool linux_coop_fd_user_nonblocking(int fd); + +/** + * @brief Get tracked user-visible status flags for a file descriptor. + * + * @param[in] fd File descriptor to query. + * @param[out] flags Output for tracked user-visible flags. + * @return true if the fd is tracked and flags were written, false otherwise. + */ +bool linux_coop_get_user_flags(int fd, int *flags); + +/** + * @brief Cooperatively yield for @p ms milliseconds. + * + * Maps to vTaskDelay() inside a FreeRTOS task, nanosleep() otherwise. + * + * @param ms Number of milliseconds to yield. + */ +static inline __attribute__((always_inline)) +void linux_coop_yield(int ms) +{ + if (linux_port_in_freertos_task()) { + vTaskDelay(pdMS_TO_TICKS(ms)); + } else { + struct timespec ts; + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000L; + while (nanosleep(&ts, &ts) == -1 && errno == EINTR) { + /* retry with remaining time */ + } + } +} + +/** + * @brief Cooperative I/O retry loop. + * + * If user-visible mode is non-blocking, returns EAGAIN/EWOULDBLOCK as-is. + * Otherwise yields and retries until success or a real error. + * + * @param fd File descriptor used to determine user-visible blocking mode. + * @param expr Expression to evaluate in the retry loop (must yield ssize_t). + */ +#define LINUX_COOP_IO_LOOP(fd, expr) \ + while (1) \ + { \ + ssize_t _n = (expr); \ + if (_n >= 0) { \ + return _n; \ + } \ + if (errno == EAGAIN || errno == EWOULDBLOCK) { \ + if (linux_coop_fd_user_nonblocking(fd)) { \ + return -1; \ + } \ + linux_coop_yield(LINUX_COOP_TICK_MS); \ + continue; \ + } \ + return -1; \ + } + +#ifdef __cplusplus +} +#endif diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.c b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.c index 7f61ecbb03a..82cf1c67196 100644 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.c +++ b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.c @@ -3,7 +3,16 @@ * * SPDX-License-Identifier: Apache-2.0 * - * Cooperative wrappers for Linux FreeRTOS simulator. + * Cooperative syscall interposition for Linux/macOS FreeRTOS simulator. + * + * This file provides: + * - Shadow FD state table (user-visible blocking/non-blocking tracking) + * - Cooperative I/O primitives (freertos_linux_coop_read/write/open/close/…) + * - Weak POSIX symbol definitions (read/write/open/close/fcntl/select/…) + * that VFS can override with strong definitions + * - Socket, scatter/gather, multiplexing, sleep wrappers + * + * Real libc entry points are resolved via dlsym(RTLD_NEXT, …). */ #include @@ -21,278 +30,552 @@ #include #include #include -#include -#include "freertos/FreeRTOS.h" -#include "task.h" +#include -#define COOP_SYSCALLS_WAIT_MS (1000 / CONFIG_FREERTOS_HZ) +#include "linux_port_coop_internal.h" +#include "esp_private/freertos_linux_coop_syscalls.h" -extern bool linux_port_in_freertos_task(void); +static ssize_t (*real_readv)(int, const struct iovec *, int); +static ssize_t (*real_writev)(int, const struct iovec *, int); +static ssize_t (*real_recv)(int, void *, size_t, int); +static ssize_t (*real_send)(int, const void *, size_t, int); +static ssize_t (*real_recvfrom)(int, void *, size_t, int, + struct sockaddr *, socklen_t *); +static ssize_t (*real_sendto)(int, const void *, size_t, int, + const struct sockaddr *, socklen_t); +static ssize_t (*real_recvmsg)(int, struct msghdr *, int); +static ssize_t (*real_sendmsg)(int, const struct msghdr *, int); +static int (*real_connect)(int, const struct sockaddr *, socklen_t); +static int (*real_accept)(int, struct sockaddr *, socklen_t *); +static int (*real_pselect)(int, fd_set *, fd_set *, fd_set *, + const struct timespec *, const sigset_t *); +static int (*real_poll)(struct pollfd *, nfds_t, int); +static int (*real_socket)(int, int, int); +static int (*real_socketpair)(int, int, int, int[2]); +static int (*real_pipe)(int[2]); +static int (*real_pipe2)(int[2], int); +static int (*real_dup)(int); +static int (*real_dup2)(int, int); +static int (*real_nanosleep)(const struct timespec *, struct timespec *); +static ssize_t (*real_read)(int, void *, size_t); +static ssize_t (*real_write)(int, const void *, size_t); +static ssize_t (*real_pread)(int, void *, size_t, off_t); +static ssize_t (*real_pwrite)(int, const void *, size_t, off_t); +static int (*real_close)(int); +static int (*real_select)(int, fd_set *, fd_set *, fd_set *, + struct timeval *); -static inline __attribute__((always_inline)) -void coop_set_fd_nonblocking(int fd) +typedef int (*linux_coop_fcntl_fn_t)(int, int, ...); +static linux_coop_fcntl_fn_t real_fcntl; + +typedef int (*linux_coop_open_fn_t)(const char *, int, mode_t); +static linux_coop_open_fn_t real_open; + +typedef struct linux_coop_fd_state { + int fd; + int user_flags; + struct linux_coop_fd_state *next; +} linux_coop_fd_state_t; + +static linux_coop_fd_state_t *s_fd_state_list; +static pthread_mutex_t s_fd_state_lock = PTHREAD_MUTEX_INITIALIZER; + +static linux_coop_fd_state_t *linux_coop_find_fd_locked(int fd) +{ + linux_coop_fd_state_t *node = s_fd_state_list; + while (node != NULL) { + if (node->fd == fd) { + return node; + } + node = node->next; + } + return NULL; +} + +static void __attribute__((constructor)) linux_coop_resolve_all(void) +{ + LINUX_COOP_RESOLVE(readv); + LINUX_COOP_RESOLVE(writev); + LINUX_COOP_RESOLVE(recv); + LINUX_COOP_RESOLVE(send); + LINUX_COOP_RESOLVE(recvfrom); + LINUX_COOP_RESOLVE(sendto); + LINUX_COOP_RESOLVE(recvmsg); + LINUX_COOP_RESOLVE(sendmsg); + LINUX_COOP_RESOLVE(connect); + LINUX_COOP_RESOLVE(accept); + LINUX_COOP_RESOLVE(pselect); + LINUX_COOP_RESOLVE(poll); + LINUX_COOP_RESOLVE(socket); + LINUX_COOP_RESOLVE(socketpair); + LINUX_COOP_RESOLVE(pipe); + LINUX_COOP_RESOLVE(pipe2); /* may be NULL on macOS */ + LINUX_COOP_RESOLVE(dup); + LINUX_COOP_RESOLVE(dup2); + LINUX_COOP_RESOLVE(nanosleep); + LINUX_COOP_RESOLVE(fcntl); + LINUX_COOP_RESOLVE(read); + LINUX_COOP_RESOLVE(write); + LINUX_COOP_RESOLVE(pread); + LINUX_COOP_RESOLVE(pwrite); + LINUX_COOP_RESOLVE(close); + LINUX_COOP_RESOLVE(select); + LINUX_COOP_RESOLVE(open); +} + +void linux_coop_set_nonblocking(int fd) { if (fd >= 0) { - int flags = fcntl(fd, F_GETFL, 0); + int flags = real_fcntl(fd, F_GETFL, 0); if (flags >= 0) { - fcntl(fd, F_SETFL, flags | O_NONBLOCK); + real_fcntl(fd, F_SETFL, flags | O_NONBLOCK); } } } -static inline __attribute__((always_inline)) -void coop_wait(int ms) +void linux_coop_track_fd(int fd, int user_flags) { - if (linux_port_in_freertos_task()) { - vTaskDelay(ms); - } else { - struct timespec ts; - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000L; - while (nanosleep(&ts, &ts) == -1 && errno == EINTR) { - // Retry with remaining time if interrupted + if (fd < 0) { + return; + } + + pthread_mutex_lock(&s_fd_state_lock); + linux_coop_fd_state_t *node = linux_coop_find_fd_locked(fd); + if (node != NULL) { + node->user_flags = user_flags; + pthread_mutex_unlock(&s_fd_state_lock); + return; + } + + node = calloc(1, sizeof(*node)); + if (node != NULL) { + node->fd = fd; + node->user_flags = user_flags; + node->next = s_fd_state_list; + s_fd_state_list = node; + } + pthread_mutex_unlock(&s_fd_state_lock); +} + +void linux_coop_untrack_fd(int fd) +{ + if (fd < 0) { + return; + } + + pthread_mutex_lock(&s_fd_state_lock); + linux_coop_fd_state_t *prev = NULL; + linux_coop_fd_state_t *node = s_fd_state_list; + while (node != NULL) { + if (node->fd == fd) { + if (prev == NULL) { + s_fd_state_list = node->next; + } else { + prev->next = node->next; + } + free(node); + break; + } + prev = node; + node = node->next; + } + pthread_mutex_unlock(&s_fd_state_lock); +} + +void linux_coop_set_user_flags(int fd, int user_flags) +{ + linux_coop_track_fd(fd, user_flags); +} + +bool linux_coop_get_user_flags(int fd, int *flags) +{ + if (fd < 0 || flags == NULL) { + return false; + } + + pthread_mutex_lock(&s_fd_state_lock); + linux_coop_fd_state_t *node = linux_coop_find_fd_locked(fd); + if (node == NULL) { + pthread_mutex_unlock(&s_fd_state_lock); + return false; + } + *flags = node->user_flags; + pthread_mutex_unlock(&s_fd_state_lock); + return true; +} + +bool linux_coop_fd_user_nonblocking(int fd) +{ + int user_flags = 0; + if (linux_coop_get_user_flags(fd, &user_flags)) { + return (user_flags & O_NONBLOCK) != 0; + } + return false; +} + + +ssize_t freertos_linux_coop_read(int fd, void *buf, size_t count) +{ + LINUX_COOP_IO_LOOP(fd, real_read(fd, buf, count)) +} + +ssize_t freertos_linux_coop_write(int fd, const void *buf, size_t count) +{ + LINUX_COOP_IO_LOOP(fd, real_write(fd, buf, count)) +} + +ssize_t freertos_linux_coop_pread(int fd, void *buf, size_t count, off_t offset) +{ + LINUX_COOP_IO_LOOP(fd, real_pread(fd, buf, count, offset)) +} + +ssize_t freertos_linux_coop_pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + LINUX_COOP_IO_LOOP(fd, real_pwrite(fd, buf, count, offset)) +} + +int freertos_linux_coop_open(const char *path, int flags, int mode) +{ + int fd = real_open(path, flags, (mode_t)mode); + linux_coop_set_nonblocking(fd); + linux_coop_track_fd(fd, flags & O_NONBLOCK); + return fd; +} + +int freertos_linux_coop_close(int fd) +{ + int ret = real_close(fd); + if (ret == 0 || errno == EINTR) { + /* On Linux, close() always releases the fd even when returning + * EINTR. Retrying would risk closing a fd reused by another + * thread. Treat EINTR as success. */ + linux_coop_untrack_fd(fd); + return 0; + } + return -1; +} + +int freertos_linux_coop_fcntl(int fd, int cmd, int arg) +{ + if (cmd == F_GETFL) { + int real_flags = real_fcntl(fd, F_GETFL, 0); + if (real_flags < 0) { + return -1; + } + int user_flags = 0; + if (linux_coop_get_user_flags(fd, &user_flags)) { + real_flags &= ~O_NONBLOCK; + real_flags |= (user_flags & O_NONBLOCK); + } + return real_flags; + } + if (cmd == F_SETFL) { + linux_coop_set_user_flags(fd, arg); + int internal_flags = arg | O_NONBLOCK; + return real_fcntl(fd, F_SETFL, internal_flags); + } + return real_fcntl(fd, cmd, arg); +} + +int freertos_linux_coop_select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) +{ + long timeout_ms = -1; + if (timeout != NULL) { + timeout_ms = (long)timeout->tv_sec * 1000 + + (timeout->tv_usec + 999) / 1000; + if (timeout_ms == 0) { + return real_select(nfds, readfds, writefds, exceptfds, timeout); } } -} -/* Generic cooperative loop template */ -#define COOP_LOOP(start_expr) \ - while (1) \ - { \ - ssize_t n = start_expr; \ - if (n >= 0) { \ - return n; \ - } else if (errno == EAGAIN || errno == EWOULDBLOCK) { \ - coop_wait(COOP_SYSCALLS_WAIT_MS); \ - continue; \ - } else { \ - return -1; \ - } \ + /* real_select() mutates the fd_sets in place (clears them, then sets bits + * for ready descriptors). To keep polling the same descriptors across + * iterations we must snapshot the caller's sets and restore them on every + * loop. */ + fd_set rfds_in, wfds_in, efds_in; + if (readfds != NULL) { + rfds_in = *readfds; + } + if (writefds != NULL) { + wfds_in = *writefds; + } + if (exceptfds != NULL) { + efds_in = *exceptfds; } -ssize_t __real_read(int fd, void *buf, size_t count); -ssize_t __real_write(int fd, const void *buf, size_t count); -ssize_t __real_pread(int fd, void *buf, size_t count, off_t offset); -ssize_t __real_pwrite(int fd, const void *buf, size_t count, off_t offset); -ssize_t __real_readv(int fd, const struct iovec *iov, int iovcnt); -ssize_t __real_writev(int fd, const struct iovec *iov, int iovcnt); -ssize_t __real_recv(int sockfd, void *buf, size_t len, int flags); -ssize_t __real_send(int sockfd, const void *buf, size_t len, int flags); -ssize_t __real_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); -ssize_t __real_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); -ssize_t __real_recvmsg(int sockfd, struct msghdr *msg, int flags); -ssize_t __real_sendmsg(int sockfd, const struct msghdr *msg, int flags); -int __real_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); -int __real_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); -int __real_close(int fd); -int __real_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); -int __real_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); -int __real_poll(struct pollfd *fds, nfds_t nfds, int timeout); -unsigned int __real_sleep(unsigned int seconds); -int __real_usleep(useconds_t usec); + long waited_ms = 0; + while (1) { + if (readfds != NULL) { + *readfds = rfds_in; + } + if (writefds != NULL) { + *writefds = wfds_in; + } + if (exceptfds != NULL) { + *exceptfds = efds_in; + } -int __real_socket(int domain, int type, int protocol); -int __real_socketpair(int domain, int type, int protocol, int sv[2]); -int __real_pipe(int fds[2]); -int __real_pipe2(int fds[2], int flags); -int __real_dup(int oldfd); -int __real_dup2(int oldfd, int newfd); -int __real_open(const char *path, int flags, ...); - -ssize_t __wrap_read(int fd, void *buf, size_t count) -{ - COOP_LOOP(__real_read(fd, buf, count)) + struct timeval zero_tv = {0, 0}; + int ret = real_select(nfds, readfds, writefds, exceptfds, &zero_tv); + if (ret != 0) { + return ret; + } + if (timeout_ms == 0) { + return 0; + } + if (timeout_ms > 0 && waited_ms >= timeout_ms) { + return 0; + } + linux_coop_yield(LINUX_COOP_TICK_MS); + waited_ms += LINUX_COOP_TICK_MS; + } } -ssize_t __wrap_write(int fd, const void *buf, size_t count) +void freertos_linux_coop_syscalls_init(void) { - COOP_LOOP(__real_write(fd, buf, count)) + linux_coop_set_nonblocking(STDIN_FILENO); + linux_coop_set_nonblocking(STDOUT_FILENO); + linux_coop_set_nonblocking(STDERR_FILENO); + linux_coop_track_fd(STDIN_FILENO, 0); + linux_coop_track_fd(STDOUT_FILENO, 0); + linux_coop_track_fd(STDERR_FILENO, 0); } -ssize_t __wrap_pread(int fd, void *buf, size_t count, off_t offset) +/* These are overridden by strong definitions in VFS when that component + * is linked. When VFS is absent the weak versions provide cooperative + * behaviour directly via real libc. */ + +__attribute__((weak)) ssize_t read(int fd, void *buf, size_t count) { - COOP_LOOP(__real_pread(fd, buf, count, offset)) + return freertos_linux_coop_read(fd, buf, count); } -ssize_t __wrap_pwrite(int fd, const void *buf, size_t count, off_t offset) +__attribute__((weak)) ssize_t write(int fd, const void *buf, size_t count) { - COOP_LOOP(__real_pwrite(fd, buf, count, offset)) + return freertos_linux_coop_write(fd, buf, count); } -ssize_t __wrap_readv(int fd, const struct iovec *iov, int iovcnt) +__attribute__((weak)) ssize_t pread(int fd, void *buf, size_t count, off_t offset) { - COOP_LOOP(__real_readv(fd, iov, iovcnt)) + return freertos_linux_coop_pread(fd, buf, count, offset); } -ssize_t __wrap_writev(int fd, const struct iovec *iov, int iovcnt) +__attribute__((weak)) ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { - COOP_LOOP(__real_writev(fd, iov, iovcnt)) + return freertos_linux_coop_pwrite(fd, buf, count, offset); } -ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags) +__attribute__((weak)) int open(const char *path, int flags, ...) { - COOP_LOOP(__real_recv(sockfd, buf, len, flags | MSG_DONTWAIT)) + int mode = 0; + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + return freertos_linux_coop_open(path, flags, mode); } -ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags) +__attribute__((weak)) int close(int fd) { - COOP_LOOP(__real_send(sockfd, buf, len, flags | MSG_DONTWAIT)) + return freertos_linux_coop_close(fd); } -ssize_t __wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) +__attribute__((weak)) int fcntl(int fd, int cmd, ...) { - COOP_LOOP(__real_recvfrom(sockfd, buf, len, flags | MSG_DONTWAIT, src_addr, addrlen)) + int arg = 0; + if (cmd != F_GETFL) { + va_list list; + va_start(list, cmd); + arg = va_arg(list, int); + va_end(list); + } + return freertos_linux_coop_fcntl(fd, cmd, arg); } -ssize_t __wrap_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) +__attribute__((weak)) int select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) { - COOP_LOOP(__real_sendto(sockfd, buf, len, flags | MSG_DONTWAIT, dest_addr, addrlen)) + return freertos_linux_coop_select(nfds, readfds, writefds, exceptfds, timeout); } -ssize_t __wrap_recvmsg(int sockfd, struct msghdr *msg, int flags) +ssize_t freertos_linux_coop_readv(int fd, const struct iovec *iov, int iovcnt) { - COOP_LOOP(__real_recvmsg(sockfd, msg, flags | MSG_DONTWAIT)) + LINUX_COOP_IO_LOOP(fd, real_readv(fd, iov, iovcnt)) } -ssize_t __wrap_sendmsg(int sockfd, const struct msghdr *msg, int flags) +ssize_t freertos_linux_coop_writev(int fd, const struct iovec *iov, int iovcnt) { - COOP_LOOP(__real_sendmsg(sockfd, msg, flags | MSG_DONTWAIT)) + LINUX_COOP_IO_LOOP(fd, real_writev(fd, iov, iovcnt)) } -int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +ssize_t freertos_linux_coop_recv(int sockfd, void *buf, size_t len, int flags) +{ + LINUX_COOP_IO_LOOP(sockfd, real_recv(sockfd, buf, len, flags | MSG_DONTWAIT)) +} + +ssize_t freertos_linux_coop_send(int sockfd, const void *buf, size_t len, int flags) +{ + LINUX_COOP_IO_LOOP(sockfd, real_send(sockfd, buf, len, flags | MSG_DONTWAIT)) +} + +ssize_t freertos_linux_coop_recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + LINUX_COOP_IO_LOOP(sockfd, real_recvfrom(sockfd, buf, len, flags | MSG_DONTWAIT, + src_addr, addrlen)) +} + +ssize_t freertos_linux_coop_sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + LINUX_COOP_IO_LOOP(sockfd, real_sendto(sockfd, buf, len, flags | MSG_DONTWAIT, + dest_addr, addrlen)) +} + +ssize_t freertos_linux_coop_recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + LINUX_COOP_IO_LOOP(sockfd, real_recvmsg(sockfd, msg, flags | MSG_DONTWAIT)) +} + +ssize_t freertos_linux_coop_sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + LINUX_COOP_IO_LOOP(sockfd, real_sendmsg(sockfd, msg, flags | MSG_DONTWAIT)) +} + +__attribute__((weak)) ssize_t readv(int fd, const struct iovec *iov, int iovcnt) +{ + return freertos_linux_coop_readv(fd, iov, iovcnt); +} + +__attribute__((weak)) ssize_t writev(int fd, const struct iovec *iov, int iovcnt) +{ + return freertos_linux_coop_writev(fd, iov, iovcnt); +} + +__attribute__((weak)) ssize_t recv(int sockfd, void *buf, size_t len, int flags) +{ + return freertos_linux_coop_recv(sockfd, buf, len, flags); +} + +__attribute__((weak)) ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + return freertos_linux_coop_send(sockfd, buf, len, flags); +} + +__attribute__((weak)) ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + return freertos_linux_coop_recvfrom(sockfd, buf, len, flags, src_addr, addrlen); +} + +__attribute__((weak)) ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + return freertos_linux_coop_sendto(sockfd, buf, len, flags, dest_addr, addrlen); +} + +__attribute__((weak)) ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + return freertos_linux_coop_recvmsg(sockfd, msg, flags); +} + +__attribute__((weak)) ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + return freertos_linux_coop_sendmsg(sockfd, msg, flags); +} + +int freertos_linux_coop_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { while (1) { - int ret = __real_connect(sockfd, addr, addrlen); + int ret = real_connect(sockfd, addr, addrlen); if (ret == 0) { return ret; } if (errno == EINPROGRESS || errno == EALREADY) { - /* Poll for writability (use __real_poll in cooperative loop, - but do not block the kernel thread). We'll emulate blocking - by repeatedly polling with 0 timeout and yielding. */ struct pollfd pfd; pfd.fd = sockfd; pfd.events = POLLOUT; pfd.revents = 0; + int poll_retries = 0; while (1) { - int press = __real_poll(&pfd, 1, 0); + int press = real_poll(&pfd, 1, 0); if (press > 0) { - /* socket reported an event; check if connect succeeded */ int so_err = 0; socklen_t len = sizeof(so_err); - if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_err, &len) < 0) { - /* getsockopt failed; treat as error */ + if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, + &so_err, &len) < 0) { return -1; } - if (so_err == 0) { - return 0; /* connected */ + return 0; } else { errno = so_err; return -1; } } else if (press == 0) { - /* no event yet -> yield cooperatively and retry */ - coop_wait(COOP_SYSCALLS_WAIT_MS); + linux_coop_yield(LINUX_COOP_TICK_MS); continue; } else { - /* press < 0 */ if (errno == EINTR) { - continue; /* retry poll */ + continue; } - - /* treat other errors as transient and yield */ - coop_wait(COOP_SYSCALLS_WAIT_MS); + /* Non-EINTR poll error; bail out after a few retries */ + if (++poll_retries > 3) { + return -1; + } + linux_coop_yield(LINUX_COOP_TICK_MS); continue; } } } if (errno == EINTR) { - /* POSIX: connect may fail with EINTR; return -1 with errno==EINTR */ return -1; } - /* other fatal errors */ return -1; } } -int __wrap_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) -{ - COOP_LOOP(__real_accept(sockfd, addr, addrlen)) -} - -int __wrap_close(int fd) +int freertos_linux_coop_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { while (1) { - int ret = __real_close(fd); - if (ret == 0) { - return 0; - } else if (errno == EINTR) { - coop_wait(COOP_SYSCALLS_WAIT_MS); + int fd = real_accept(sockfd, addr, addrlen); + if (fd >= 0) { + linux_coop_set_nonblocking(fd); + linux_coop_track_fd(fd, linux_coop_fd_user_nonblocking(sockfd) ? O_NONBLOCK : 0); + return fd; + } + if (errno == EAGAIN || errno == EWOULDBLOCK) { + if (linux_coop_fd_user_nonblocking(sockfd)) { + return -1; + } + linux_coop_yield(LINUX_COOP_TICK_MS); continue; - } else { - return -1; } + return -1; } } -int __wrap_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +int freertos_linux_coop_pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask) { - /* compute timeout in milliseconds; -1 => infinite */ long timeout_ms = -1; if (timeout != NULL) { - /* convert timeval -> ms, rounding up microseconds */ - timeout_ms = (long)timeout->tv_sec * 1000 + (timeout->tv_usec + 999) / 1000; + timeout_ms = (long)timeout->tv_sec * 1000 + + (timeout->tv_nsec + 999999) / 1000000; if (timeout_ms == 0) { - /* immediate poll: call real_select with provided timeout */ - return __real_select(nfds, readfds, writefds, exceptfds, timeout); - } - } - - long waited_ms = 0; - while (1) - { - /* nonblocking check */ - struct timeval zero_tv = {0, 0}; - int ret = __real_select(nfds, readfds, writefds, exceptfds, &zero_tv); - if (ret != 0) { - /* ret > 0 => ready; ret < 0 => error and errno set */ - return ret; - } - - /* no descriptors ready */ - if (timeout_ms == 0) { - return 0; /* expired */ - } - - /* check timeout expiration */ - if (timeout_ms > 0 && waited_ms >= timeout_ms) { - return 0; /* timeout expired */ - } - - /* yield cooperatively */ - coop_wait(COOP_SYSCALLS_WAIT_MS); - waited_ms += COOP_SYSCALLS_WAIT_MS; - } -} - -int __wrap_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, - const struct timespec *timeout, const sigset_t *sigmask) -{ - /* convert timespec -> ms, -1 for infinite */ - long timeout_ms = -1; - if (timeout != NULL) { - timeout_ms = (long)timeout->tv_sec * 1000 + (timeout->tv_nsec + 999999) / 1000000; - if (timeout_ms == 0) { - /* immediate poll: call real_pselect with provided timeout */ - return __real_pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask); + return real_pselect(nfds, readfds, writefds, exceptfds, + timeout, sigmask); } } @@ -300,7 +583,8 @@ int __wrap_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfd while (1) { struct timespec zero_ts = {0, 0}; - int ret = __real_pselect(nfds, readfds, writefds, exceptfds, &zero_ts, sigmask); + int ret = real_pselect(nfds, readfds, writefds, exceptfds, + &zero_ts, sigmask); if (ret != 0) { return ret; } @@ -310,19 +594,17 @@ int __wrap_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfd return 0; } - coop_wait(COOP_SYSCALLS_WAIT_MS); - waited_ms += COOP_SYSCALLS_WAIT_MS; + linux_coop_yield(LINUX_COOP_TICK_MS); + waited_ms += LINUX_COOP_TICK_MS; } } -int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) +int freertos_linux_coop_poll(struct pollfd *fds, nfds_t nfds, int timeout) { if (timeout == 0) { - /* immediate poll: delegate */ - return __real_poll(fds, nfds, 0); + return real_poll(fds, nfds, 0); } - /* compute wait semantics */ long timeout_ms = -1; if (timeout > 0) { timeout_ms = timeout; @@ -332,121 +614,182 @@ int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout) while (1) { - int ret = __real_poll(fds, nfds, 0); + int ret = real_poll(fds, nfds, 0); if (ret > 0) { return ret; - } - else if (ret == 0) { - /* no event; check timeout */ + } else if (ret == 0) { if (timeout_ms == 0 || (timeout_ms > 0 && waited_ms >= timeout_ms)) { - return 0; /* expired */ + return 0; } - - /* yield and continue */ - coop_wait(COOP_SYSCALLS_WAIT_MS); - waited_ms += COOP_SYSCALLS_WAIT_MS; + linux_coop_yield(LINUX_COOP_TICK_MS); + waited_ms += LINUX_COOP_TICK_MS; continue; } else { - /* ret < 0: error */ if (errno == EINTR) { - continue; /* retry */ + continue; } - - /* for other errors, yield and retry (transient) */ - coop_wait(COOP_SYSCALLS_WAIT_MS); - + linux_coop_yield(LINUX_COOP_TICK_MS); if (timeout_ms > 0 && waited_ms >= timeout_ms) { return -1; } - waited_ms += COOP_SYSCALLS_WAIT_MS; + waited_ms += LINUX_COOP_TICK_MS; } } } -unsigned int __wrap_sleep(unsigned int seconds) +int nanosleep(const struct timespec *req, struct timespec *rem) { - coop_wait(seconds * 1000); + if (req == NULL) { + errno = EFAULT; + return -1; + } + if (!linux_port_in_freertos_task()) { + return real_nanosleep(req, rem); + } + long ms = req->tv_sec * 1000 + (req->tv_nsec + 999999) / 1000000; + if (ms <= 0) { + ms = 1; + } + vTaskDelay(ms); + if (rem) { + rem->tv_sec = 0; + rem->tv_nsec = 0; + } return 0; } -int __wrap_usleep(useconds_t usec) +unsigned int sleep(unsigned int seconds) { - coop_wait((usec / 1000)); + linux_coop_yield(seconds * 1000); return 0; } -int __wrap_socket(int domain, int type, int protocol) +int usleep(useconds_t usec) { - int fd = __real_socket(domain, type, protocol); - coop_set_fd_nonblocking(fd); + linux_coop_yield(usec / 1000); + return 0; +} + +int freertos_linux_coop_socket(int domain, int type, int protocol) +{ + int fd = real_socket(domain, type, protocol); + linux_coop_set_nonblocking(fd); +#ifdef SOCK_NONBLOCK + const int user_flags = (type & SOCK_NONBLOCK) ? O_NONBLOCK : 0; +#else + const int user_flags = 0; +#endif + linux_coop_track_fd(fd, user_flags); return fd; } -int __wrap_socketpair(int domain, int type, int protocol, int sv[2]) +int freertos_linux_coop_socketpair(int domain, int type, int protocol, int *sv) { - int ret = __real_socketpair(domain, type, protocol, sv); + int ret = real_socketpair(domain, type, protocol, sv); if (ret == 0) { - coop_set_fd_nonblocking(sv[0]); - coop_set_fd_nonblocking(sv[1]); + linux_coop_set_nonblocking(sv[0]); + linux_coop_set_nonblocking(sv[1]); +#ifdef SOCK_NONBLOCK + const int user_flags = (type & SOCK_NONBLOCK) ? O_NONBLOCK : 0; +#else + const int user_flags = 0; +#endif + linux_coop_track_fd(sv[0], user_flags); + linux_coop_track_fd(sv[1], user_flags); } return ret; } -int __wrap_pipe(int fds[2]) +int freertos_linux_coop_pipe(int *fds) { - int ret = __real_pipe(fds); + int ret = real_pipe(fds); if (ret == 0) { - coop_set_fd_nonblocking(fds[0]); - coop_set_fd_nonblocking(fds[1]); + linux_coop_set_nonblocking(fds[0]); + linux_coop_set_nonblocking(fds[1]); + linux_coop_track_fd(fds[0], 0); + linux_coop_track_fd(fds[1], 0); } return ret; } -int __wrap_pipe2(int fds[2], int flags) +int freertos_linux_coop_pipe2(int *fds, int flags) { - int ret = __real_pipe2(fds, flags); + int ret = real_pipe2(fds, flags); if (ret == 0) { - coop_set_fd_nonblocking(fds[0]); - coop_set_fd_nonblocking(fds[1]); + linux_coop_set_nonblocking(fds[0]); + linux_coop_set_nonblocking(fds[1]); + const int user_flags = (flags & O_NONBLOCK) ? O_NONBLOCK : 0; + linux_coop_track_fd(fds[0], user_flags); + linux_coop_track_fd(fds[1], user_flags); } return ret; } -int __wrap_dup(int oldfd) +int freertos_linux_coop_dup(int oldfd) { - int fd = __real_dup(oldfd); - coop_set_fd_nonblocking(fd); + int fd = real_dup(oldfd); + linux_coop_set_nonblocking(fd); + linux_coop_track_fd(fd, linux_coop_fd_user_nonblocking(oldfd) ? O_NONBLOCK : 0); return fd; } -int __wrap_dup2(int oldfd, int newfd) +int freertos_linux_coop_dup2(int oldfd, int newfd) { - int fd = __real_dup2(oldfd, newfd); - coop_set_fd_nonblocking(fd); + int fd = real_dup2(oldfd, newfd); + linux_coop_set_nonblocking(fd); + linux_coop_track_fd(fd, linux_coop_fd_user_nonblocking(oldfd) ? O_NONBLOCK : 0); return fd; } -int __wrap_open(const char *path, int flags, ...) +__attribute__((weak)) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { - va_list ap; - int fd; - - if (flags & O_CREAT) { - va_start(ap, flags); - mode_t mode = va_arg(ap, mode_t); - va_end(ap); - fd = __real_open(path, flags, mode); - } else { - fd = __real_open(path, flags); - } - coop_set_fd_nonblocking(fd); - return fd; + return freertos_linux_coop_connect(sockfd, addr, addrlen); } -void linux_port_coop_syscalls_init(void) +__attribute__((weak)) int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { - coop_set_fd_nonblocking(STDIN_FILENO); - coop_set_fd_nonblocking(STDOUT_FILENO); - coop_set_fd_nonblocking(STDERR_FILENO); + return freertos_linux_coop_accept(sockfd, addr, addrlen); +} + +__attribute__((weak)) int pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask) +{ + return freertos_linux_coop_pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask); +} + +__attribute__((weak)) int poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + return freertos_linux_coop_poll(fds, nfds, timeout); +} + +__attribute__((weak)) int socket(int domain, int type, int protocol) +{ + return freertos_linux_coop_socket(domain, type, protocol); +} + +__attribute__((weak)) int socketpair(int domain, int type, int protocol, int sv[2]) +{ + return freertos_linux_coop_socketpair(domain, type, protocol, sv); +} + +__attribute__((weak)) int pipe(int fds[2]) +{ + return freertos_linux_coop_pipe(fds); +} + +__attribute__((weak)) int pipe2(int fds[2], int flags) +{ + return freertos_linux_coop_pipe2(fds, flags); +} + +__attribute__((weak)) int dup(int oldfd) +{ + return freertos_linux_coop_dup(oldfd); +} + +__attribute__((weak)) int dup2(int oldfd, int newfd) +{ + return freertos_linux_coop_dup2(oldfd, newfd); } diff --git a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h b/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h deleted file mode 100644 index bbeb9a2ab80..00000000000 --- a/components/freertos/FreeRTOS-Kernel/portable/linux/utils/linux_port_coop_syscalls.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Cooperative syscalls subsystem for the Linux FreeRTOS simulator. - * - * This header exposes the public initialization API needed by the - * FreeRTOS Linux port. The subsystem provides blocking read()/write() - * for FreeRTOS tasks without stalling the cooperative scheduler by - * forwarding operations to a dedicated I/O worker thread. - * - * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -void linux_port_coop_syscalls_init(void); - -#ifdef __cplusplus -} -#endif diff --git a/components/vfs/CMakeLists.txt b/components/vfs/CMakeLists.txt index 788e4e5cb08..1574b183e69 100644 --- a/components/vfs/CMakeLists.txt +++ b/components/vfs/CMakeLists.txt @@ -10,7 +10,7 @@ if(${target} STREQUAL "linux") if(CONFIG_VFS_SUPPORT_IO) list(APPEND inc linux_include) list(APPEND priv_inc private_include) - list(APPEND srcs "vfs_linux.c" "vfs.c" "vfs_calls.c") + list(APPEND srcs "vfs_linux.c" "vfs_linux_default_coop.c" "vfs.c" "vfs_calls.c") endif() if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") @@ -20,7 +20,20 @@ if(${target} STREQUAL "linux") idf_component_register(SRCS ${srcs} LDFRAGMENTS "linker.lf" INCLUDE_DIRS ${inc} - PRIV_INCLUDE_DIRS ${priv_inc}) + PRIV_INCLUDE_DIRS ${priv_inc} + PRIV_REQUIRES freertos) + + target_link_libraries(${COMPONENT_LIB} PRIVATE dl) + + if(CONFIG_VFS_SUPPORT_IO) + # On macOS, C symbols have a leading underscore prefix. + if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(${COMPONENT_LIB} INTERFACE "-u _esp_vfs_include_linux_coop_init") + else() + target_link_libraries(${COMPONENT_LIB} INTERFACE "-u esp_vfs_include_linux_coop_init") + endif() + endif() + return() endif() diff --git a/components/vfs/private_include/esp_vfs_private.h b/components/vfs/private_include/esp_vfs_private.h index d599f3e90b7..fdb8cf73a42 100644 --- a/components/vfs/private_include/esp_vfs_private.h +++ b/components/vfs/private_include/esp_vfs_private.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index 7a71c468c19..d0bb3309d58 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/vfs/vfs_linux.c b/components/vfs/vfs_linux.c index e59502d1e85..433b3ffb4ae 100644 --- a/components/vfs/vfs_linux.c +++ b/components/vfs/vfs_linux.c @@ -1,101 +1,83 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ /** - * @brief Linux target VFS syscall wrappers + * @brief Linux target VFS syscall wrappers (strong POSIX symbols) * - * This file provides POSIX syscall wrappers that redirect to ESP-IDF VFS functions - * when building for Linux target. + * This file provides strong POSIX symbol definitions that dispatch through + * esp_vfs_* for registered FDs, and fall back to the cooperative syscall + * layer for unregistered FDs (e.g. during early boot before VFS init). + * + * All kernel FDs created by open/pipe/socket/dup/accept are registered + * with VFS via esp_vfs_register_fd_with_local_fd, so the application + * only sees VFS-allocated FD numbers. This eliminates FD collisions + * between kernel FDs and VFS-internal FD allocations. + * + * Init and ops registration live in vfs_linux_default_coop.c. */ #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "esp_vfs.h" +#include "esp_vfs_private.h" +#include "esp_private/freertos_linux_coop_syscalls.h" -int open(const char * path, int flags, ...) +/* Defined in vfs_linux_default_coop.c — set during init_vfs_linux_coop() */ +extern esp_vfs_id_t s_linux_host_vfs_id; + +/** + * Helper: register a kernel FD with VFS after a successful FD-creating syscall. + * Returns the VFS FD on success, or the raw kernel FD if VFS is not yet initialized. + */ +static int register_kernel_fd(int kernel_fd) { - struct _reent r; - va_list list; - va_start(list, flags); - int mode = va_arg(list, int); - va_end(list); - return esp_vfs_open(&r, path, flags, mode); + if (kernel_fd < 0) { + return kernel_fd; + } + if (s_linux_host_vfs_id < 0) { + return kernel_fd; /* VFS not initialized yet (early boot) */ + } + int vfs_fd; + esp_err_t err = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, kernel_fd, false, &vfs_fd); + if (err != ESP_OK) { + freertos_linux_coop_close(kernel_fd); + errno = ENFILE; + return -1; + } + return vfs_fd; } -int close(int fd) +/** + * Helper: translate a VFS FD to a kernel FD. + * Returns the kernel (local) FD, or -1 with errno=EBADF. + */ +static int vfs_fd_to_kernel_fd(int fd) { - struct _reent r; - return esp_vfs_close(&r, fd); + const vfs_entry_t *vfs = get_vfs_for_fd(fd); + if (vfs == NULL) { + return fd; /* Not registered — use directly (early boot) */ + } + int local_fd = get_local_fd(vfs, fd); + if (local_fd < 0) { + errno = EBADF; + return -1; + } + return local_fd; } -ssize_t read(int fd, void * dst, size_t size) -{ - struct _reent r; - return esp_vfs_read(&r, fd, dst, size); -} - -ssize_t write(int fd, const void * data, size_t size) -{ - struct _reent r; - return esp_vfs_write(&r, fd, data, size); -} - -off_t lseek(int fd, off_t size, int mode) -{ - struct _reent r; - return esp_vfs_lseek(&r, fd, size, mode); -} - -ssize_t pread(int fd, void *dst, size_t size, off_t offset) -{ - return esp_vfs_pread(fd,dst, size, offset); -} - -ssize_t pwrite(int fd, const void *src, size_t size, off_t offset) -{ - return esp_vfs_pwrite(fd, src,size, offset); -} - -int fstat(int fd, struct stat *st) -{ - struct _reent r; - return esp_vfs_fstat(&r, fd, st); -} - -int fcntl(int fd, int cmd, ...) -{ - struct _reent r; - va_list list; - va_start(list, cmd); - int args = va_arg(list, int); - va_end(list); - return esp_vfs_fcntl_r(&r, fd, cmd, args); -} - -int ioctl(int fd, unsigned long cmd, ...) -{ - va_list args; - va_start(args, cmd); - int ret = esp_vfs_ioctl(fd, cmd, args); - va_end(args); - return ret; -} - -int fsync(int fd) -{ - return esp_vfs_fsync(fd); -} - -#ifdef CONFIG_VFS_SUPPORT_SELECT -int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) -{ - return esp_vfs_select(nfds, readfds, writefds, errorfds, timeout); -} -#endif // CONFIG_VFS_SUPPORT_SELECT - #ifdef CONFIG_VFS_SUPPORT_DIR int stat(const char * path, struct stat * st) { @@ -187,3 +169,470 @@ void rewinddir(DIR* pdir) } #endif // CONFIG_VFS_SUPPORT_DIR + + +ssize_t read(int fd, void *buf, size_t count) +{ + struct _reent r; + return esp_vfs_read(&r, fd, buf, count); +} + +ssize_t write(int fd, const void *buf, size_t count) +{ + struct _reent r; + return esp_vfs_write(&r, fd, buf, count); +} + +ssize_t pread(int fd, void *buf, size_t count, off_t offset) +{ + return esp_vfs_pread(fd, buf, count, offset); +} + +ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + return esp_vfs_pwrite(fd, buf, count, offset); +} + +int open(const char *path, int flags, ...) +{ + int mode = 0; + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + struct _reent r; + return esp_vfs_open(&r, path, flags, mode); +} + +int close(int fd) +{ + struct _reent r; + return esp_vfs_close(&r, fd); +} + +int fcntl(int fd, int cmd, ...) +{ + int arg = 0; + if (cmd != F_GETFL) { + va_list list; + va_start(list, cmd); + arg = va_arg(list, int); + va_end(list); + } + struct _reent r; + return esp_vfs_fcntl_r(&r, fd, cmd, arg); +} + +off_t lseek(int fd, off_t size, int mode) +{ + struct _reent r; + return esp_vfs_lseek(&r, fd, size, mode); +} + +int fstat(int fd, struct stat *st) +{ + struct _reent r; + return esp_vfs_fstat(&r, fd, st); +} + +int ioctl(int fd, signed long cmd, ...) +{ + va_list args; + va_start(args, cmd); + int ret = esp_vfs_ioctl(fd, cmd, args); + va_end(args); + return ret; +} + +int fsync(int fd) +{ + return esp_vfs_fsync(fd); +} + +int pipe(int fds[2]) +{ + int ret = freertos_linux_coop_pipe(fds); + if (ret != 0) { + return ret; + } + if (s_linux_host_vfs_id >= 0) { + int vfs_fd0, vfs_fd1; + esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[0], false, &vfs_fd0); + esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[1], false, &vfs_fd1); + if (e0 != ESP_OK || e1 != ESP_OK) { + freertos_linux_coop_close(fds[0]); + freertos_linux_coop_close(fds[1]); + errno = ENFILE; + return -1; + } + fds[0] = vfs_fd0; + fds[1] = vfs_fd1; + } + return 0; +} + +int pipe2(int fds[2], int flags) +{ + int ret = freertos_linux_coop_pipe2(fds, flags); + if (ret != 0) { + return ret; + } + if (s_linux_host_vfs_id >= 0) { + int vfs_fd0, vfs_fd1; + esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[0], false, &vfs_fd0); + esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, fds[1], false, &vfs_fd1); + if (e0 != ESP_OK || e1 != ESP_OK) { + freertos_linux_coop_close(fds[0]); + freertos_linux_coop_close(fds[1]); + errno = ENFILE; + return -1; + } + fds[0] = vfs_fd0; + fds[1] = vfs_fd1; + } + return 0; +} + +int socket(int domain, int type, int protocol) +{ + int kernel_fd = freertos_linux_coop_socket(domain, type, protocol); + return register_kernel_fd(kernel_fd); +} + +int socketpair(int domain, int type, int protocol, int sv[2]) +{ + int ret = freertos_linux_coop_socketpair(domain, type, protocol, sv); + if (ret != 0) { + return ret; + } + if (s_linux_host_vfs_id >= 0) { + int vfs_fd0, vfs_fd1; + esp_err_t e0 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, sv[0], false, &vfs_fd0); + esp_err_t e1 = esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, sv[1], false, &vfs_fd1); + if (e0 != ESP_OK || e1 != ESP_OK) { + freertos_linux_coop_close(sv[0]); + freertos_linux_coop_close(sv[1]); + errno = ENFILE; + return -1; + } + sv[0] = vfs_fd0; + sv[1] = vfs_fd1; + } + return 0; +} + +int dup(int oldfd) +{ + int kernel_old = vfs_fd_to_kernel_fd(oldfd); + if (kernel_old < 0) { + return -1; + } + int kernel_fd = freertos_linux_coop_dup(kernel_old); + return register_kernel_fd(kernel_fd); +} + +int dup2(int oldfd, int newfd) +{ + int kernel_old = vfs_fd_to_kernel_fd(oldfd); + if (kernel_old < 0) { + return -1; + } + /* If newfd is a registered VFS FD, we need its kernel FD and must unregister it */ + int kernel_new = newfd; + if (get_vfs_for_fd(newfd) != NULL) { + kernel_new = vfs_fd_to_kernel_fd(newfd); + if (kernel_new < 0) { + return -1; + } + unregister_fd(newfd); + } + int kernel_fd = freertos_linux_coop_dup2(kernel_old, kernel_new); + return register_kernel_fd(kernel_fd); +} + +int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) +{ + int kernel_sock = vfs_fd_to_kernel_fd(sockfd); + if (kernel_sock < 0) { + return -1; + } + int kernel_fd = freertos_linux_coop_accept(kernel_sock, addr, addrlen); + return register_kernel_fd(kernel_fd); +} + +ssize_t readv(int fd, const struct iovec *iov, int iovcnt) +{ + int kfd = vfs_fd_to_kernel_fd(fd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_readv(kfd, iov, iovcnt); +} + +ssize_t writev(int fd, const struct iovec *iov, int iovcnt) +{ + int kfd = vfs_fd_to_kernel_fd(fd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_writev(kfd, iov, iovcnt); +} + +ssize_t recv(int sockfd, void *buf, size_t len, int flags) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_recv(kfd, buf, len, flags); +} + +ssize_t send(int sockfd, const void *buf, size_t len, int flags) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_send(kfd, buf, len, flags); +} + +ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, + struct sockaddr *src_addr, socklen_t *addrlen) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_recvfrom(kfd, buf, len, flags, src_addr, addrlen); +} + +ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, + const struct sockaddr *dest_addr, socklen_t addrlen) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_sendto(kfd, buf, len, flags, dest_addr, addrlen); +} + +ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_recvmsg(kfd, msg, flags); +} + +ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_sendmsg(kfd, msg, flags); +} + +int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) +{ + int kfd = vfs_fd_to_kernel_fd(sockfd); + if (kfd < 0) { + return -1; + } + return freertos_linux_coop_connect(kfd, addr, addrlen); +} + +/** + * Translate VFS fd_sets to kernel fd_sets. + * Returns the nfds value for the kernel sets, or -1 on error (errno set). + */ +static int fdsets_vfs_to_kernel(int nfds, + const fd_set *readfds, const fd_set *writefds, const fd_set *exceptfds, + fd_set *k_readfds, fd_set *k_writefds, fd_set *k_exceptfds) +{ + int max_kfd = 0; + FD_ZERO(k_readfds); + FD_ZERO(k_writefds); + FD_ZERO(k_exceptfds); + + for (int fd = 0; fd < nfds; ++fd) { + int kfd = -1; + bool active = false; + if (readfds && FD_ISSET(fd, readfds)) { + kfd = vfs_fd_to_kernel_fd(fd); + if (kfd < 0) { return -1; } + FD_SET(kfd, k_readfds); + active = true; + } + if (writefds && FD_ISSET(fd, writefds)) { + if (kfd < 0) { kfd = vfs_fd_to_kernel_fd(fd); } + if (kfd < 0) { return -1; } + FD_SET(kfd, k_writefds); + active = true; + } + if (exceptfds && FD_ISSET(fd, exceptfds)) { + if (kfd < 0) { kfd = vfs_fd_to_kernel_fd(fd); } + if (kfd < 0) { return -1; } + FD_SET(kfd, k_exceptfds); + active = true; + } + if (active && kfd >= max_kfd) { + max_kfd = kfd + 1; + } + } + return max_kfd; +} + +/** + * Translate kernel fd_set results back to VFS fd_sets. + */ +static void fdsets_kernel_to_vfs(int nfds, + fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + fd_set *k_readfds, fd_set *k_writefds, fd_set *k_exceptfds) +{ + if (readfds) { FD_ZERO(readfds); } + if (writefds) { FD_ZERO(writefds); } + if (exceptfds) { FD_ZERO(exceptfds); } + for (int fd = 0; fd < nfds; ++fd) { + int kfd = vfs_fd_to_kernel_fd(fd); + if (kfd < 0) { continue; } + if (readfds && FD_ISSET(kfd, k_readfds)) { FD_SET(fd, readfds); } + if (writefds && FD_ISSET(kfd, k_writefds)) { FD_SET(fd, writefds); } + if (exceptfds && FD_ISSET(kfd, k_exceptfds)) { FD_SET(fd, exceptfds); } + } +} + +int select(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout) +{ +#ifdef CONFIG_VFS_SUPPORT_SELECT + for (int fd = 0; fd < nfds; ++fd) { + if ((readfds && FD_ISSET(fd, readfds)) || + (writefds && FD_ISSET(fd, writefds)) || + (exceptfds && FD_ISSET(fd, exceptfds))) { + const vfs_entry_t *vfs = get_vfs_for_fd(fd); + if (vfs != NULL && vfs->offset != s_linux_host_vfs_id) { + return esp_vfs_select(nfds, readfds, writefds, exceptfds, timeout); + } + } + } +#endif + fd_set k_readfds, k_writefds, k_exceptfds; + int max_kfd = fdsets_vfs_to_kernel(nfds, readfds, writefds, exceptfds, + &k_readfds, &k_writefds, &k_exceptfds); + if (max_kfd < 0) { return -1; } + + int ret = freertos_linux_coop_select(max_kfd, + readfds ? &k_readfds : NULL, + writefds ? &k_writefds : NULL, + exceptfds ? &k_exceptfds : NULL, + timeout); + if (ret >= 0) { + fdsets_kernel_to_vfs(nfds, readfds, writefds, exceptfds, + &k_readfds, &k_writefds, &k_exceptfds); + } + return ret; +} + +int pselect(int nfds, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, const struct timespec *timeout, + const sigset_t *sigmask) +{ + fd_set k_readfds, k_writefds, k_exceptfds; + int max_kfd = fdsets_vfs_to_kernel(nfds, readfds, writefds, exceptfds, + &k_readfds, &k_writefds, &k_exceptfds); + if (max_kfd < 0) { return -1; } + + int ret = freertos_linux_coop_pselect(max_kfd, + readfds ? &k_readfds : NULL, + writefds ? &k_writefds : NULL, + exceptfds ? &k_exceptfds : NULL, + timeout, sigmask); + if (ret >= 0) { + fdsets_kernel_to_vfs(nfds, readfds, writefds, exceptfds, + &k_readfds, &k_writefds, &k_exceptfds); + } + return ret; +} + +int poll(struct pollfd *fds, nfds_t nfds, int timeout) +{ + struct timeval tv = { + .tv_sec = timeout / 1000, + .tv_usec = (timeout % 1000) * 1000, + }; + int max_fd = -1; + fd_set readfds; + fd_set writefds; + fd_set errorfds; + + if (fds == NULL) { + errno = ENOENT; + return -1; + } + + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&errorfds); + + for (unsigned int i = 0; i < nfds; ++i) { + fds[i].revents = 0; + + if (fds[i].fd < 0) { + continue; + } + + if (fds[i].fd >= FD_SETSIZE) { + fds[i].revents |= POLLNVAL; + continue; + } + + if (fds[i].events & (POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI)) { + FD_SET(fds[i].fd, &readfds); + FD_SET(fds[i].fd, &errorfds); + max_fd = MAX(max_fd, fds[i].fd); + } + + if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) { + FD_SET(fds[i].fd, &writefds); + FD_SET(fds[i].fd, &errorfds); + max_fd = MAX(max_fd, fds[i].fd); + } + } + + const int select_ret = select(max_fd + 1, &readfds, &writefds, &errorfds, timeout < 0 ? NULL : &tv); + + if (select_ret < 0) { + return select_ret; + } + + if (select_ret > 0) { + for (unsigned int i = 0; i < nfds; ++i) { + if (fds[i].fd < 0 || fds[i].fd >= FD_SETSIZE) { + continue; + } + + if (FD_ISSET(fds[i].fd, &readfds)) { + fds[i].revents |= POLLIN; + } + + if (FD_ISSET(fds[i].fd, &writefds)) { + fds[i].revents |= POLLOUT; + } + + if (FD_ISSET(fds[i].fd, &errorfds)) { + fds[i].revents |= POLLERR; + } + } + } + + int ret = 0; + for (unsigned int i = 0; i < nfds; ++i) { + if (fds[i].revents != 0) { + ++ret; + } + } + + return ret; +} diff --git a/components/vfs/vfs_linux_default_coop.c b/components/vfs/vfs_linux_default_coop.c new file mode 100644 index 00000000000..c1e44573a89 --- /dev/null +++ b/components/vfs/vfs_linux_default_coop.c @@ -0,0 +1,158 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @brief Linux target VFS host filesystem registration + init + * + * Registers the cooperative libc ops (backed by freertos_linux_coop_*) + * as a VFS filesystem using esp_vfs_register_fs_with_id, so that + * file descriptors registered against this VFS ID are dispatched + * through the cooperative syscall layer. + * + * stdin/stdout/stderr are registered with VFS here, and all other + * kernel FDs are registered by the strong POSIX overrides in vfs_linux.c. + * + * Non-blocking syscalls (lseek, fstat, ioctl, fsync) are resolved directly + * via dlsym and bypass the cooperative interposition layer because they + * complete synchronously and never return EAGAIN/EWOULDBLOCK. + */ + +#include +#include +#include +#include +#include "esp_vfs.h" +#include "esp_private/freertos_linux_coop_syscalls.h" +#include "esp_private/startup_internal.h" + +/* VFS ID for the Linux host filesystem — used by vfs_linux.c to register FDs */ +esp_vfs_id_t s_linux_host_vfs_id = -1; + +/* Real libc entry points for non-blocking syscalls, resolved at startup */ +static off_t (*real_lseek)(int, off_t, int); +static int (*real_fstat)(int, struct stat *); +static int (*real_fsync)(int); + +typedef int (*real_ioctl_fn_t)(int, unsigned long, ...); +static real_ioctl_fn_t real_ioctl; + +static ssize_t default_read(void *ctx, int fd, void *dst, size_t size) +{ + (void)ctx; + return freertos_linux_coop_read(fd, dst, size); +} + +static ssize_t default_write(void *ctx, int fd, const void *data, size_t size) +{ + (void)ctx; + return freertos_linux_coop_write(fd, data, size); +} + +static ssize_t default_pread(void *ctx, int fd, void *dst, size_t size, off_t offset) +{ + (void)ctx; + return freertos_linux_coop_pread(fd, dst, size, offset); +} + +static ssize_t default_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset) +{ + (void)ctx; + return freertos_linux_coop_pwrite(fd, src, size, offset); +} + +static int default_open(void *ctx, const char *path, int flags, int mode) +{ + (void)ctx; + return freertos_linux_coop_open(path, flags, mode); +} + +static int default_close(void *ctx, int fd) +{ + (void)ctx; + return freertos_linux_coop_close(fd); +} + +static int default_fcntl(void *ctx, int fd, int cmd, int arg) +{ + (void)ctx; + return freertos_linux_coop_fcntl(fd, cmd, arg); +} + +/* lseek never blocks — call real libc directly, no cooperative retry needed */ +static off_t default_lseek(void *ctx, int fd, off_t offset, int whence) +{ + (void)ctx; + return real_lseek(fd, offset, whence); +} + +/* fstat never blocks — call real libc directly, no cooperative retry needed */ +static int default_fstat(void *ctx, int fd, struct stat *st) +{ + (void)ctx; + return real_fstat(fd, st); +} + +/* ioctl never blocks — call real libc directly, no cooperative retry needed */ +static int default_ioctl(void *ctx, int fd, int cmd, va_list args) +{ + (void)ctx; + void *arg = va_arg(args, void *); + return real_ioctl(fd, (unsigned long)cmd, arg); +} + +/* fsync never blocks — call real libc directly, no cooperative retry needed */ +static int default_fsync(void *ctx, int fd) +{ + (void)ctx; + return real_fsync(fd); +} + +static const esp_vfs_fs_ops_t s_linux_host_ops = { + .read_p = default_read, + .write_p = default_write, + .pread_p = default_pread, + .pwrite_p = default_pwrite, + .open_p = default_open, + .close_p = default_close, + .fcntl_p = default_fcntl, + .lseek_p = default_lseek, + .fstat_p = default_fstat, + .ioctl_p = default_ioctl, + .fsync_p = default_fsync, +}; + +ESP_SYSTEM_INIT_FN(init_vfs_linux_coop, CORE, BIT(0), 99) +{ + /* Resolve real libc symbols for non-blocking syscalls */ + real_lseek = dlsym(RTLD_NEXT, "lseek"); + real_fstat = dlsym(RTLD_NEXT, "fstat"); + real_ioctl = dlsym(RTLD_NEXT, "ioctl"); + real_fsync = dlsym(RTLD_NEXT, "fsync"); + + freertos_linux_coop_syscalls_init(); + + /* Register the Linux host FS with VFS (id-based, no path prefix) */ + esp_err_t err = esp_vfs_register_fs_with_id(&s_linux_host_ops, + ESP_VFS_FLAG_CONTEXT_PTR, + NULL, + &s_linux_host_vfs_id); + if (err != ESP_OK) { + return err; + } + + /* Register stdin/stdout/stderr so apps see them as VFS FDs 0, 1, 2 */ + int fd; + esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDIN_FILENO, true, &fd); + esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDOUT_FILENO, true, &fd); + esp_vfs_register_fd_with_local_fd(s_linux_host_vfs_id, STDERR_FILENO, true, &fd); + + return ESP_OK; +} + +void esp_vfs_include_linux_coop_init(void) +{ + // Linker hook function, exists to make the linker examine this file +} diff --git a/tools/mocks/freertos/CMakeLists.txt b/tools/mocks/freertos/CMakeLists.txt index 8cf8adddf92..21598b6fe26 100644 --- a/tools/mocks/freertos/CMakeLists.txt +++ b/tools/mocks/freertos/CMakeLists.txt @@ -16,6 +16,7 @@ set(include_dirs "${original_freertos_dir}/esp_additions/include/freertos" "${kernel_dir}/portable/linux/include" # For "spinlock.h" "${kernel_dir}/portable/linux/include/freertos" # For "portmacro.h" + "${kernel_dir}/portable/linux/include/esp_private" # CMock strips directory prefixes from includes "${kernel_dir}/include/freertos" # this is due to the way includes are generated in CMock (without freertos prefix) ) @@ -26,7 +27,8 @@ idf_component_mock(INCLUDE_DIRS ${include_dirs} ${original_freertos_dir}/FreeRTOS-Kernel/include/freertos/task.h ${original_freertos_dir}/FreeRTOS-Kernel/include/freertos/event_groups.h ${original_freertos_dir}/FreeRTOS-Kernel/include/freertos/queue.h - ${original_freertos_dir}/FreeRTOS-Kernel/portable/linux/include/freertos/portmacro.h) + ${original_freertos_dir}/FreeRTOS-Kernel/portable/linux/include/freertos/portmacro.h + ${kernel_dir}/portable/linux/include/esp_private/freertos_linux_coop_syscalls.h) idf_component_get_property(freertos_lib freertos COMPONENT_LIB) target_compile_definitions(${freertos_lib} PUBLIC "projCOVERAGE_TEST=0")