mirror of
https://github.com/espressif/esp-idf.git
synced 2026-08-18 06:35:35 +03:00
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:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/param.h>
|
||||
#include <poll.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
158
components/vfs/vfs_linux_default_coop.c
Normal file
158
components/vfs/vfs_linux_default_coop.c
Normal file
@@ -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 <dlfcn.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#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
|
||||
}
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user