diff --git a/components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c b/components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c index 0e5c211584..b5634e32d2 100644 --- a/components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c +++ b/components/freertos/esp_additions/FreeRTOSSimulator_wrappers.c @@ -1,56 +1,84 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include +#include +#include #include -#include -#include "esp_err.h" -#include "errno.h" +#include +#include -/** This module addresses the FreeRTOS simulator's coexistence with linux system calls from user apps. - * It's only included when building without lwIP, so we need to use linux system's select() which would receive - * EINTR event on every FreeRTOS interrupt; we workaround this problem by wrapping select() - * to bypass and silence these events. +/** This module addresses the FreeRTOS simulator's coexistence with Linux system calls from user apps. + * It wraps select so that it doesn't block the FreeRTOS task calling it, so that the + * scheduler will allow lower priority tasks to run. + * Without the wrapper, most components such as ESP-MQTT block lower priority tasks from running at all. */ -typedef int (*select_func_t) (int fd, fd_set * rfds, fd_set * wfds, fd_set *efds, struct timeval *tval); +typedef int (*select_func_t)(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval); -static inline int64_t get_us(void) +int select(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval) { - struct timespec spec; - clock_gettime(CLOCK_REALTIME, &spec); - return spec.tv_nsec / 1000 + spec.tv_sec * 1000000; -} + static select_func_t s_real_select = NULL; + TickType_t end_ticks = portMAX_DELAY; + fd_set o_rfds, o_wfds, o_efds; -int select (int fd, fd_set * rfds, fd_set * wfds, fd_set *efds, struct timeval *tval) -{ - int ret; - struct timeval *tv = tval; - struct timeval timeval_local = {}; - int64_t start = 0; - int64_t timeout_us = 0; - select_func_t real_select = (select_func_t) dlsym(RTLD_NEXT, "select"); - if (tv != NULL) { - start = get_us(); - timeout_us = tval->tv_sec * 1000000 + tval->tv_usec; - timeval_local.tv_sec = tval->tv_sec; - timeval_local.tv_usec = tval->tv_usec; - tv = &timeval_local; // this (tv != NULL) indicates that we should handle timeouts + // Lookup the select symbol + if (s_real_select == NULL) { + s_real_select = (select_func_t)dlsym(RTLD_NEXT, "select"); } - while ((ret = real_select(fd, rfds, wfds, efds, tv)) < 0 && errno == EINTR) { - if (tv != NULL) { - int64_t now = get_us(); - timeout_us -= now - start; - if (timeout_us < 0) { - errno = 0; - ret = 0; - break; - } - start = now; - tv->tv_usec = timeout_us % 1000000; - tv->tv_sec = timeout_us / 1000000; + + // Calculate the end_ticks if a timeout is provided + if (tval != NULL) { + end_ticks = xTaskGetTickCount() + pdMS_TO_TICKS(tval->tv_sec * 1000 + tval->tv_usec / 1000); + } + + // Preserve the original FD sets as select call will change them + if (rfds) { + o_rfds = *rfds; + } + if (wfds) { + o_wfds = *wfds; + } + if (efds) { + o_efds = *efds; + } + + while (1) { + // Restore original FD sets before the select call + if (rfds) { + *rfds = o_rfds; } + if (wfds) { + *wfds = o_wfds; + } + if (efds) { + *efds = o_efds; + } + + // Call select with a zero timeout to avoid blocking + struct timeval zero_tv = {0, 0}; + int ret = s_real_select(fd, rfds, wfds, efds, &zero_tv); + + // Return on success + if (ret > 0) { + return ret; + } + + // Return on any error but EINTR + if (ret == -1 && errno != EINTR) { + return ret; + } + + if (tval != NULL && xTaskGetTickCount() >= end_ticks) { + errno = 0; + return 0; + } + + /** + * Sleep for 10 tick(s) to allow other tasks to run. + * This can be any value greater than zero. + * 10 is a good trade-off between CPU time usage and timeout resolution. + */ + vTaskDelay(10); } - return ret; }