mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(freertos): soft-preempting linux simulator
This commit is contained in:
@@ -77,8 +77,10 @@ typedef unsigned long TickType_t;
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
/* Scheduler utilities. */
|
||||
extern void vPortYield( void );
|
||||
extern void vPortYieldWithinApi( void );
|
||||
#define portYIELD_WITHIN_API() vPortYieldWithinApi()
|
||||
|
||||
extern void vPortYield( void );
|
||||
#define portYIELD() vPortYield()
|
||||
|
||||
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( (xSwitchRequired) != pdFALSE ) vPortYield()
|
||||
@@ -107,6 +109,12 @@ void vPortExitCritical( void );
|
||||
#define portENTER_CRITICAL_ISR(mux) portENTER_CRITICAL(mux)
|
||||
#define portEXIT_CRITICAL_ISR(mux) portEXIT_CRITICAL(mux)
|
||||
|
||||
#define prvENTER_CRITICAL_SMP_ONLY( pxLock ) portENTER_CRITICAL( pxLock )
|
||||
#define prvEXIT_CRITICAL_SMP_ONLY( pxLock ) portEXIT_CRITICAL( pxLock )
|
||||
|
||||
extern void vPortSuspendScheduler(void);
|
||||
#define portSOFTWARE_BARRIER() vPortSuspendScheduler()
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
extern void vPortThreadDying( void *pxTaskToDelete, volatile BaseType_t *pxPendYield );
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,44 +1,71 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file contains most of the code located in the demo application in the
|
||||
* upstream FreeRTOS repository. It is put here so that IDF applications can
|
||||
* seamlessly switch between Linux and chip targets without the need to provide
|
||||
* or implement additional functionality if the target is the Linux target.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <execinfo.h>
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
|
||||
/* Scheduler includes. */
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "utils/wait_for_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "utils/linux_port_utils.h"
|
||||
|
||||
#define BACKTRACE_PC_ARRAY_SIZE 20
|
||||
#define FREERTOS_SIM_BACKTRACE_SIZE 16
|
||||
#define ON_SEGFAULT_MESSAGE "ERROR: Segmentation Fault, here's your backtrace:\n"
|
||||
#define ON_ABORT_MESSAGE "ERROR: Aborted\n"
|
||||
|
||||
static void linux_port_backtrace_handler(int sig)
|
||||
{
|
||||
/* All calls here must be async-signal-safe (no stdio, no malloc).
|
||||
* write() and backtrace_symbols_fd() write directly to the fd. */
|
||||
void *buffer[FREERTOS_SIM_BACKTRACE_SIZE];
|
||||
int nptrs = backtrace(buffer, FREERTOS_SIM_BACKTRACE_SIZE);
|
||||
|
||||
const char *name = pcTaskGetName(NULL); /* simple pointer dereference, no lock */
|
||||
ssize_t ignore __attribute__((unused));
|
||||
ignore = write(STDERR_FILENO, "=== Backtrace for task: ", 24);
|
||||
if (name) {
|
||||
size_t len = 0;
|
||||
while (name[len] != '\0') {
|
||||
len++;
|
||||
}
|
||||
ignore = write(STDERR_FILENO, name, len);
|
||||
}
|
||||
ignore = write(STDERR_FILENO, " ===\n", 5);
|
||||
backtrace_symbols_fd(buffer, nptrs, STDERR_FILENO);
|
||||
}
|
||||
|
||||
void linux_port_setup_backtrace_signal(void)
|
||||
{
|
||||
struct sigaction sa;
|
||||
sa.sa_handler = linux_port_backtrace_handler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sigaction(SIGUSR1, &sa, NULL);
|
||||
}
|
||||
|
||||
void linux_port_print_backtrace(void)
|
||||
{
|
||||
pthread_kill(linux_port_get_scheduled_task_pthread(), SIGUSR1);
|
||||
}
|
||||
|
||||
ESP_LOG_ATTR_TAG(LINUX_TAG, "port_idf_linux");
|
||||
ESP_LOG_ATTR_TAG(MAIN_TAG, "main_task");
|
||||
|
||||
#if (defined(__APPLE__) && defined(__MACH__))
|
||||
typedef sig_t sighandler_t;
|
||||
#endif
|
||||
|
||||
static const char *TAG = "port";
|
||||
|
||||
static volatile UBaseType_t uxInterruptNesting = 0;
|
||||
|
||||
|
||||
BaseType_t xPortCheckIfInISR(void)
|
||||
{
|
||||
return uxInterruptNesting;
|
||||
@@ -46,190 +73,133 @@ BaseType_t xPortCheckIfInISR(void)
|
||||
|
||||
#if CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
||||
#define BACKTRACE_PC_ARRAY_SIZE_DUMMY 1
|
||||
/**
|
||||
* This function calls backtrace once to ensure that libgcc is loaded already.
|
||||
*/
|
||||
static void load_libgcc(void)
|
||||
{
|
||||
void *array[BACKTRACE_PC_ARRAY_SIZE_DUMMY];
|
||||
size_t size = backtrace(array, BACKTRACE_PC_ARRAY_SIZE_DUMMY);
|
||||
assert(size == 1); // Since this function can be called, the first stack frame should be present
|
||||
assert(size == 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a rudimentary backtrace to help users a bit with segfaults.
|
||||
*/
|
||||
static void segfault_handler(int sig)
|
||||
{
|
||||
void *array[BACKTRACE_PC_ARRAY_SIZE];
|
||||
size_t size;
|
||||
|
||||
// get void*'s for all entries on the stack
|
||||
size = backtrace(array, BACKTRACE_PC_ARRAY_SIZE);
|
||||
|
||||
// we need a raw file write here because other functions are not async-signal-safe
|
||||
int written = write(STDERR_FILENO, ON_SEGFAULT_MESSAGE, sizeof(ON_SEGFAULT_MESSAGE));
|
||||
(void) written; // The return value is ignored for now, as we don't have a lot of options in case of failure
|
||||
// and EINTR can't happen in a signal handler anyways
|
||||
|
||||
size_t size = backtrace(array, BACKTRACE_PC_ARRAY_SIZE);
|
||||
ssize_t ignore __attribute__((unused));
|
||||
ignore = write(STDERR_FILENO, ON_SEGFAULT_MESSAGE, sizeof(ON_SEGFAULT_MESSAGE));
|
||||
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Print a message to signal abort, even in idf.py monitor.
|
||||
*/
|
||||
static void abort_handler(int sig)
|
||||
{
|
||||
// we need a raw file write here because other functions are not async-signal-safe
|
||||
int written = write(STDERR_FILENO, ON_ABORT_MESSAGE, sizeof(ON_ABORT_MESSAGE));
|
||||
(void) written; // The return value is ignored for now, as we don't have a lot of options in case of failure
|
||||
// and EINTR can't happen in a signal handler anyways
|
||||
|
||||
ssize_t ignore __attribute__((unused));
|
||||
ignore = write(STDERR_FILENO, ON_ABORT_MESSAGE, sizeof(ON_ABORT_MESSAGE));
|
||||
_exit(1);
|
||||
}
|
||||
#endif // CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
||||
|
||||
void app_main(void);
|
||||
|
||||
static void main_task(void* args)
|
||||
/*-----------------------------------------------------------
|
||||
* Main FreeRTOS task
|
||||
*-----------------------------------------------------------*/
|
||||
extern void app_main(void);
|
||||
static void main_task(void *args)
|
||||
{
|
||||
(void)args;
|
||||
ESP_LOGI(MAIN_TAG, "Started on CPU%d", (int)xPortGetCoreID());
|
||||
|
||||
ESP_LOGI(MAIN_TAG, "Calling app_main()");
|
||||
|
||||
app_main();
|
||||
|
||||
ESP_LOGI(MAIN_TAG, "Returned from app_main()");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void esp_startup_start_app(void)
|
||||
{
|
||||
// This makes sure that stdio is always synchronized so that idf.py monitor
|
||||
// and other tools read text output on time.
|
||||
setvbuf(stdout, NULL, _IONBF, 0);
|
||||
|
||||
#if CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
||||
// Ensures that libgcc is loaded to avoid problems when loading it later in
|
||||
// the signal handler (see NOTES section in glibc backtrace man page)
|
||||
load_libgcc();
|
||||
sighandler_t sig_res;
|
||||
|
||||
// Enable backtraces
|
||||
sig_res = signal(SIGSEGV, segfault_handler);
|
||||
if (sig_res == SIG_ERR) {
|
||||
perror("Failed setting the segfault handler");
|
||||
abort();
|
||||
}
|
||||
|
||||
// Enable error message on abort
|
||||
sig_res = signal(SIGABRT, abort_handler);
|
||||
if (sig_res == SIG_ERR) {
|
||||
perror("Failed setting the abort handler");
|
||||
abort();
|
||||
}
|
||||
#endif // CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
||||
#endif
|
||||
|
||||
usleep(1000);
|
||||
BaseType_t res = xTaskCreatePinnedToCore(&main_task, "main",
|
||||
ESP_TASK_MAIN_STACK, NULL,
|
||||
ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE);
|
||||
assert(res == pdTRUE);
|
||||
(void)res;
|
||||
// Create main_task using FreeRTOS API
|
||||
ESP_LOGI(LINUX_TAG, "Starting main task.");
|
||||
assert(xTaskCreate(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL) == pdTRUE);
|
||||
|
||||
ESP_LOGI(TAG, "Starting scheduler.");
|
||||
ESP_LOGI(LINUX_TAG, "Starting scheduler task.");
|
||||
vTaskStartScheduler();
|
||||
|
||||
// This line should never be reached
|
||||
// Should never reach here
|
||||
assert(false);
|
||||
}
|
||||
|
||||
void esp_vApplicationIdleHook(void)
|
||||
/*-----------------------------------------------------------
|
||||
* idle and tick hooks
|
||||
*-----------------------------------------------------------*/
|
||||
#if (configUSE_IDLE_HOOK > 0)
|
||||
void vApplicationIdleHook(void)
|
||||
{
|
||||
/* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set
|
||||
* to 1 in FreeRTOSConfig.h. It will be called on each iteration of the idle
|
||||
* task. It is essential that code added to this hook function never attempts
|
||||
* to block in any way (for example, call xQueueReceive() with a block time
|
||||
* specified, or call vTaskDelay()). If application tasks make use of the
|
||||
* vTaskDelete() API function to delete themselves then it is also important
|
||||
* that vApplicationIdleHook() is permitted to return to its calling function,
|
||||
* because it is the responsibility of the idle task to clean up memory
|
||||
* allocated by the kernel to any task that has since deleted itself. */
|
||||
|
||||
|
||||
usleep( 15000 );
|
||||
}
|
||||
|
||||
void esp_vApplicationTickHook( void ) { }
|
||||
|
||||
#if ( configUSE_TICK_HOOK > 0 )
|
||||
void vApplicationTickHook( void )
|
||||
{
|
||||
esp_vApplicationTickHook();
|
||||
}
|
||||
#endif
|
||||
|
||||
void vPortYieldOtherCore( BaseType_t coreid ) { } // trying to skip for now
|
||||
|
||||
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
/* configUSE_STATIC_ALLOCATION is set to 1, so the application must provide an
|
||||
* implementation of vApplicationGetIdleTaskMemory() to provide the memory that is
|
||||
* used by the Idle task. */
|
||||
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
|
||||
StackType_t ** ppxIdleTaskStackBuffer,
|
||||
uint32_t * pulIdleTaskStackSize )
|
||||
#if (configUSE_TICK_HOOK > 0)
|
||||
void vApplicationTickHook(void)
|
||||
{
|
||||
extern void esp_vApplicationTickHook(void);
|
||||
esp_vApplicationTickHook();
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------
|
||||
* Static allocation support
|
||||
*-----------------------------------------------------------*/
|
||||
#if (configSUPPORT_STATIC_ALLOCATION == 1)
|
||||
|
||||
void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer,
|
||||
StackType_t **ppxIdleTaskStackBuffer,
|
||||
uint32_t *pulIdleTaskStackSize)
|
||||
{
|
||||
/* If the buffers to be provided to the Idle task are declared inside this
|
||||
* function then they must be declared static - otherwise they will be allocated on
|
||||
* the stack and so not exists after this function exits. */
|
||||
static StaticTask_t xIdleTaskTCB;
|
||||
static StackType_t uxIdleTaskStack[ configMINIMAL_STACK_SIZE ];
|
||||
static StackType_t uxIdleTaskStack[configMINIMAL_STACK_SIZE];
|
||||
|
||||
/* Pass out a pointer to the StaticTask_t structure in which the Idle task's
|
||||
* state will be stored. */
|
||||
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
|
||||
|
||||
/* Pass out the array that will be used as the Idle task's stack. */
|
||||
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
|
||||
|
||||
/* Pass out the size of the array pointed to by *ppxIdleTaskStackBuffer.
|
||||
* Note that, as the array is necessarily of type StackType_t,
|
||||
* configMINIMAL_STACK_SIZE is specified in bytes. */
|
||||
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
|
||||
}
|
||||
#endif // configSUPPORT_STATIC_ALLOCATION == 1
|
||||
/*-----------------------------------------------------------*/
|
||||
|
||||
#if ( (configSUPPORT_STATIC_ALLOCATION == 1) && (configUSE_TIMERS == 1))
|
||||
#if (configUSE_TIMERS == 1)
|
||||
StackType_t uxTimerTaskStack[configTIMER_TASK_STACK_DEPTH];
|
||||
|
||||
/* When configSUPPORT_STATIC_ALLOCATION is set to 1 the application writer can
|
||||
* use a callback function to optionally provide the memory required by the idle
|
||||
* and timer tasks. This is the stack that will be used by the timer task. It is
|
||||
* declared here, as a global, so it can be checked by a test that is implemented
|
||||
* in a different file. */
|
||||
StackType_t uxTimerTaskStack[ configTIMER_TASK_STACK_DEPTH ];
|
||||
|
||||
/* configUSE_STATIC_ALLOCATION and configUSE_TIMERS are both set to 1, so the
|
||||
* application must provide an implementation of vApplicationGetTimerTaskMemory()
|
||||
* to provide the memory that is used by the Timer service task. */
|
||||
void vApplicationGetTimerTaskMemory( StaticTask_t ** ppxTimerTaskTCBBuffer,
|
||||
StackType_t ** ppxTimerTaskStackBuffer,
|
||||
uint32_t * pulTimerTaskStackSize )
|
||||
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
|
||||
StackType_t **ppxTimerTaskStackBuffer,
|
||||
uint32_t *pulTimerTaskStackSize)
|
||||
{
|
||||
/* If the buffers to be provided to the Timer task are declared inside this
|
||||
* function then they must be declared static - otherwise they will be allocated on
|
||||
* the stack and so not exists after this function exits. */
|
||||
static StaticTask_t xTimerTaskTCB;
|
||||
|
||||
/* Pass out a pointer to the StaticTask_t structure in which the Timer
|
||||
* task's state will be stored. */
|
||||
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
|
||||
|
||||
/* Pass out the array that will be used as the Timer task's stack. */
|
||||
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
|
||||
|
||||
/* Pass out the size of the array pointed to by *ppxTimerTaskStackBuffer.
|
||||
* Note that, as the array is necessarily of type StackType_t,
|
||||
* configMINIMAL_STACK_SIZE is specified in bytes. */
|
||||
*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
|
||||
}
|
||||
#endif // (configSUPPORT_STATIC_ALLOCATION == 1) && (configUSE_TIMERS == 1)
|
||||
#endif
|
||||
|
||||
#endif // configSUPPORT_STATIC_ALLOCATION
|
||||
|
||||
/*-----------------------------------------------------------
|
||||
* Stack overflow hook
|
||||
*-----------------------------------------------------------*/
|
||||
void __attribute__((weak)) vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
|
||||
{
|
||||
#define ERR_STR1 "***ERROR*** A stack overflow in task "
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Cooperative wrappers for Linux FreeRTOS simulator.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
#include <poll.h>
|
||||
#include <dlfcn.h>
|
||||
#include <time.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
#define COOP_SYSCALLS_WAIT_MS (1000 / CONFIG_FREERTOS_HZ)
|
||||
|
||||
extern bool linux_port_in_freertos_task(void);
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void coop_set_fd_nonblocking(int fd)
|
||||
{
|
||||
if (fd >= 0) {
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags >= 0) {
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void coop_wait(int ms)
|
||||
{
|
||||
if (linux_port_in_freertos_task()) {
|
||||
vTaskDelay(ms);
|
||||
} else {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000L;
|
||||
while (nanosleep(&ts, &ts) == -1 && errno == EINTR) {
|
||||
// Retry with remaining time if interrupted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Generic cooperative loop template */
|
||||
#define COOP_LOOP(start_expr) \
|
||||
while (1) \
|
||||
{ \
|
||||
ssize_t n = start_expr; \
|
||||
if (n >= 0) { \
|
||||
return n; \
|
||||
} else if (errno == EAGAIN || errno == EWOULDBLOCK) { \
|
||||
coop_wait(COOP_SYSCALLS_WAIT_MS); \
|
||||
continue; \
|
||||
} else { \
|
||||
return -1; \
|
||||
} \
|
||||
}
|
||||
|
||||
ssize_t __real_read(int fd, void *buf, size_t count);
|
||||
ssize_t __real_write(int fd, const void *buf, size_t count);
|
||||
ssize_t __real_pread(int fd, void *buf, size_t count, off_t offset);
|
||||
ssize_t __real_pwrite(int fd, const void *buf, size_t count, off_t offset);
|
||||
ssize_t __real_readv(int fd, const struct iovec *iov, int iovcnt);
|
||||
ssize_t __real_writev(int fd, const struct iovec *iov, int iovcnt);
|
||||
ssize_t __real_recv(int sockfd, void *buf, size_t len, int flags);
|
||||
ssize_t __real_send(int sockfd, const void *buf, size_t len, int flags);
|
||||
ssize_t __real_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
|
||||
ssize_t __real_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
|
||||
ssize_t __real_recvmsg(int sockfd, struct msghdr *msg, int flags);
|
||||
ssize_t __real_sendmsg(int sockfd, const struct msghdr *msg, int flags);
|
||||
int __real_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
|
||||
int __real_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
||||
int __real_close(int fd);
|
||||
int __real_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
|
||||
int __real_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask);
|
||||
int __real_poll(struct pollfd *fds, nfds_t nfds, int timeout);
|
||||
unsigned int __real_sleep(unsigned int seconds);
|
||||
int __real_usleep(useconds_t usec);
|
||||
|
||||
int __real_socket(int domain, int type, int protocol);
|
||||
int __real_socketpair(int domain, int type, int protocol, int sv[2]);
|
||||
int __real_pipe(int fds[2]);
|
||||
int __real_pipe2(int fds[2], int flags);
|
||||
int __real_dup(int oldfd);
|
||||
int __real_dup2(int oldfd, int newfd);
|
||||
int __real_open(const char *path, int flags, ...);
|
||||
|
||||
ssize_t __wrap_read(int fd, void *buf, size_t count)
|
||||
{
|
||||
COOP_LOOP(__real_read(fd, buf, count))
|
||||
}
|
||||
|
||||
ssize_t __wrap_write(int fd, const void *buf, size_t count)
|
||||
{
|
||||
COOP_LOOP(__real_write(fd, buf, count))
|
||||
}
|
||||
|
||||
ssize_t __wrap_pread(int fd, void *buf, size_t count, off_t offset)
|
||||
{
|
||||
COOP_LOOP(__real_pread(fd, buf, count, offset))
|
||||
}
|
||||
|
||||
ssize_t __wrap_pwrite(int fd, const void *buf, size_t count, off_t offset)
|
||||
{
|
||||
COOP_LOOP(__real_pwrite(fd, buf, count, offset))
|
||||
}
|
||||
|
||||
ssize_t __wrap_readv(int fd, const struct iovec *iov, int iovcnt)
|
||||
{
|
||||
COOP_LOOP(__real_readv(fd, iov, iovcnt))
|
||||
}
|
||||
|
||||
ssize_t __wrap_writev(int fd, const struct iovec *iov, int iovcnt)
|
||||
{
|
||||
COOP_LOOP(__real_writev(fd, iov, iovcnt))
|
||||
}
|
||||
|
||||
ssize_t __wrap_recv(int sockfd, void *buf, size_t len, int flags)
|
||||
{
|
||||
COOP_LOOP(__real_recv(sockfd, buf, len, flags | MSG_DONTWAIT))
|
||||
}
|
||||
|
||||
ssize_t __wrap_send(int sockfd, const void *buf, size_t len, int flags)
|
||||
{
|
||||
COOP_LOOP(__real_send(sockfd, buf, len, flags | MSG_DONTWAIT))
|
||||
}
|
||||
|
||||
ssize_t __wrap_recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen)
|
||||
{
|
||||
COOP_LOOP(__real_recvfrom(sockfd, buf, len, flags | MSG_DONTWAIT, src_addr, addrlen))
|
||||
}
|
||||
|
||||
ssize_t __wrap_sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen)
|
||||
{
|
||||
COOP_LOOP(__real_sendto(sockfd, buf, len, flags | MSG_DONTWAIT, dest_addr, addrlen))
|
||||
}
|
||||
|
||||
ssize_t __wrap_recvmsg(int sockfd, struct msghdr *msg, int flags)
|
||||
{
|
||||
COOP_LOOP(__real_recvmsg(sockfd, msg, flags | MSG_DONTWAIT))
|
||||
}
|
||||
|
||||
ssize_t __wrap_sendmsg(int sockfd, const struct msghdr *msg, int flags)
|
||||
{
|
||||
COOP_LOOP(__real_sendmsg(sockfd, msg, flags | MSG_DONTWAIT))
|
||||
}
|
||||
|
||||
int __wrap_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
int ret = __real_connect(sockfd, addr, addrlen);
|
||||
if (ret == 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (errno == EINPROGRESS || errno == EALREADY) {
|
||||
/* Poll for writability (use __real_poll in cooperative loop,
|
||||
but do not block the kernel thread). We'll emulate blocking
|
||||
by repeatedly polling with 0 timeout and yielding. */
|
||||
struct pollfd pfd;
|
||||
pfd.fd = sockfd;
|
||||
pfd.events = POLLOUT;
|
||||
pfd.revents = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
int press = __real_poll(&pfd, 1, 0);
|
||||
if (press > 0) {
|
||||
/* socket reported an event; check if connect succeeded */
|
||||
int so_err = 0;
|
||||
socklen_t len = sizeof(so_err);
|
||||
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_err, &len) < 0) {
|
||||
/* getsockopt failed; treat as error */
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (so_err == 0) {
|
||||
return 0; /* connected */
|
||||
} else {
|
||||
errno = so_err;
|
||||
return -1;
|
||||
}
|
||||
} else if (press == 0) {
|
||||
/* no event yet -> yield cooperatively and retry */
|
||||
coop_wait(COOP_SYSCALLS_WAIT_MS);
|
||||
continue;
|
||||
} else {
|
||||
/* press < 0 */
|
||||
if (errno == EINTR) {
|
||||
continue; /* retry poll */
|
||||
}
|
||||
|
||||
/* treat other errors as transient and yield */
|
||||
coop_wait(COOP_SYSCALLS_WAIT_MS);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errno == EINTR) {
|
||||
/* POSIX: connect may fail with EINTR; return -1 with errno==EINTR */
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* other fatal errors */
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int __wrap_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
|
||||
{
|
||||
COOP_LOOP(__real_accept(sockfd, addr, addrlen))
|
||||
}
|
||||
|
||||
int __wrap_close(int fd)
|
||||
{
|
||||
while (1)
|
||||
{
|
||||
int ret = __real_close(fd);
|
||||
if (ret == 0) {
|
||||
return 0;
|
||||
} else if (errno == EINTR) {
|
||||
coop_wait(COOP_SYSCALLS_WAIT_MS);
|
||||
continue;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int __wrap_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
|
||||
{
|
||||
/* compute timeout in milliseconds; -1 => infinite */
|
||||
long timeout_ms = -1;
|
||||
if (timeout != NULL) {
|
||||
/* convert timeval -> ms, rounding up microseconds */
|
||||
timeout_ms = (long)timeout->tv_sec * 1000 + (timeout->tv_usec + 999) / 1000;
|
||||
if (timeout_ms == 0) {
|
||||
/* immediate poll: call real_select with provided timeout */
|
||||
return __real_select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
long waited_ms = 0;
|
||||
while (1)
|
||||
{
|
||||
/* nonblocking check */
|
||||
struct timeval zero_tv = {0, 0};
|
||||
int ret = __real_select(nfds, readfds, writefds, exceptfds, &zero_tv);
|
||||
if (ret != 0) {
|
||||
/* ret > 0 => ready; ret < 0 => error and errno set */
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* no descriptors ready */
|
||||
if (timeout_ms == 0) {
|
||||
return 0; /* expired */
|
||||
}
|
||||
|
||||
/* check timeout expiration */
|
||||
if (timeout_ms > 0 && waited_ms >= timeout_ms) {
|
||||
return 0; /* timeout expired */
|
||||
}
|
||||
|
||||
/* yield cooperatively */
|
||||
coop_wait(COOP_SYSCALLS_WAIT_MS);
|
||||
waited_ms += COOP_SYSCALLS_WAIT_MS;
|
||||
}
|
||||
}
|
||||
|
||||
int __wrap_pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||
const struct timespec *timeout, const sigset_t *sigmask)
|
||||
{
|
||||
/* convert timespec -> ms, -1 for infinite */
|
||||
long timeout_ms = -1;
|
||||
if (timeout != NULL) {
|
||||
timeout_ms = (long)timeout->tv_sec * 1000 + (timeout->tv_nsec + 999999) / 1000000;
|
||||
if (timeout_ms == 0) {
|
||||
/* immediate poll: call real_pselect with provided timeout */
|
||||
return __real_pselect(nfds, readfds, writefds, exceptfds, timeout, sigmask);
|
||||
}
|
||||
}
|
||||
|
||||
long waited_ms = 0;
|
||||
while (1)
|
||||
{
|
||||
struct timespec zero_ts = {0, 0};
|
||||
int ret = __real_pselect(nfds, readfds, writefds, exceptfds, &zero_ts, sigmask);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (timeout_ms == 0 ||
|
||||
(timeout_ms > 0 && waited_ms >= timeout_ms)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
coop_wait(COOP_SYSCALLS_WAIT_MS);
|
||||
waited_ms += COOP_SYSCALLS_WAIT_MS;
|
||||
}
|
||||
}
|
||||
|
||||
int __wrap_poll(struct pollfd *fds, nfds_t nfds, int timeout)
|
||||
{
|
||||
if (timeout == 0) {
|
||||
/* immediate poll: delegate */
|
||||
return __real_poll(fds, nfds, 0);
|
||||
}
|
||||
|
||||
/* compute wait semantics */
|
||||
long timeout_ms = -1;
|
||||
if (timeout > 0) {
|
||||
timeout_ms = timeout;
|
||||
}
|
||||
|
||||
long waited_ms = 0;
|
||||
|
||||
while (1)
|
||||
{
|
||||
int ret = __real_poll(fds, nfds, 0);
|
||||
if (ret > 0) {
|
||||
return ret;
|
||||
}
|
||||
else if (ret == 0) {
|
||||
/* no event; check timeout */
|
||||
if (timeout_ms == 0 ||
|
||||
(timeout_ms > 0 && waited_ms >= timeout_ms)) {
|
||||
return 0; /* expired */
|
||||
}
|
||||
|
||||
/* yield and continue */
|
||||
coop_wait(COOP_SYSCALLS_WAIT_MS);
|
||||
waited_ms += COOP_SYSCALLS_WAIT_MS;
|
||||
continue;
|
||||
} else {
|
||||
/* ret < 0: error */
|
||||
if (errno == EINTR) {
|
||||
continue; /* retry */
|
||||
}
|
||||
|
||||
/* for other errors, yield and retry (transient) */
|
||||
coop_wait(COOP_SYSCALLS_WAIT_MS);
|
||||
|
||||
if (timeout_ms > 0 && waited_ms >= timeout_ms) {
|
||||
return -1;
|
||||
}
|
||||
waited_ms += COOP_SYSCALLS_WAIT_MS;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int __wrap_sleep(unsigned int seconds)
|
||||
{
|
||||
coop_wait(seconds * 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __wrap_usleep(useconds_t usec)
|
||||
{
|
||||
coop_wait((usec / 1000));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __wrap_socket(int domain, int type, int protocol)
|
||||
{
|
||||
int fd = __real_socket(domain, type, protocol);
|
||||
coop_set_fd_nonblocking(fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int __wrap_socketpair(int domain, int type, int protocol, int sv[2])
|
||||
{
|
||||
int ret = __real_socketpair(domain, type, protocol, sv);
|
||||
if (ret == 0) {
|
||||
coop_set_fd_nonblocking(sv[0]);
|
||||
coop_set_fd_nonblocking(sv[1]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __wrap_pipe(int fds[2])
|
||||
{
|
||||
int ret = __real_pipe(fds);
|
||||
if (ret == 0) {
|
||||
coop_set_fd_nonblocking(fds[0]);
|
||||
coop_set_fd_nonblocking(fds[1]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __wrap_pipe2(int fds[2], int flags)
|
||||
{
|
||||
int ret = __real_pipe2(fds, flags);
|
||||
if (ret == 0) {
|
||||
coop_set_fd_nonblocking(fds[0]);
|
||||
coop_set_fd_nonblocking(fds[1]);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __wrap_dup(int oldfd)
|
||||
{
|
||||
int fd = __real_dup(oldfd);
|
||||
coop_set_fd_nonblocking(fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int __wrap_dup2(int oldfd, int newfd)
|
||||
{
|
||||
int fd = __real_dup2(oldfd, newfd);
|
||||
coop_set_fd_nonblocking(fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
int __wrap_open(const char *path, int flags, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int fd;
|
||||
|
||||
if (flags & O_CREAT) {
|
||||
va_start(ap, flags);
|
||||
mode_t mode = va_arg(ap, mode_t);
|
||||
va_end(ap);
|
||||
fd = __real_open(path, flags, mode);
|
||||
} else {
|
||||
fd = __real_open(path, flags);
|
||||
}
|
||||
coop_set_fd_nonblocking(fd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
void linux_port_coop_syscalls_init(void)
|
||||
{
|
||||
coop_set_fd_nonblocking(STDIN_FILENO);
|
||||
coop_set_fd_nonblocking(STDOUT_FILENO);
|
||||
coop_set_fd_nonblocking(STDERR_FILENO);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Cooperative syscalls subsystem for the Linux FreeRTOS simulator.
|
||||
*
|
||||
* This header exposes the public initialization API needed by the
|
||||
* FreeRTOS Linux port. The subsystem provides blocking read()/write()
|
||||
* for FreeRTOS tasks without stalling the cooperative scheduler by
|
||||
* forwarding operations to a dedicated I/O worker thread.
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void linux_port_coop_syscalls_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct thread *thread_hdl;
|
||||
|
||||
void linux_port_setup_backtrace_signal(void);
|
||||
void linux_port_print_backtrace(void);
|
||||
pthread_t linux_port_get_scheduled_task_pthread(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,110 +1,94 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Amazon.com, Inc. or its affiliates
|
||||
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/*
|
||||
* FreeRTOS Kernel V10.4.6
|
||||
* Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* https://www.FreeRTOS.org
|
||||
* https://github.com/FreeRTOS
|
||||
*
|
||||
*/
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "wait_for_event.h"
|
||||
|
||||
struct event
|
||||
/*-----------------------------------------------------------*/
|
||||
/* Create a new event */
|
||||
event_t *event_create(void)
|
||||
{
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
bool event_triggered;
|
||||
};
|
||||
|
||||
struct event * event_create(void)
|
||||
{
|
||||
struct event * ev = malloc( sizeof( struct event ) );
|
||||
event_t * ev = malloc(sizeof(event_t));
|
||||
assert(ev != NULL);
|
||||
|
||||
ev->event_triggered = false;
|
||||
pthread_mutex_init( &ev->mutex, NULL );
|
||||
pthread_cond_init( &ev->cond, NULL );
|
||||
pthread_mutex_init(&ev->mutex, NULL);
|
||||
pthread_cond_init(&ev->cond, NULL);
|
||||
|
||||
return ev;
|
||||
}
|
||||
|
||||
void event_delete( struct event * ev )
|
||||
/*-----------------------------------------------------------*/
|
||||
/* Delete an event */
|
||||
void event_delete(event_t *ev)
|
||||
{
|
||||
pthread_mutex_destroy( &ev->mutex );
|
||||
pthread_cond_destroy( &ev->cond );
|
||||
free( ev );
|
||||
pthread_mutex_destroy(&ev->mutex);
|
||||
pthread_cond_destroy(&ev->cond);
|
||||
free(ev);
|
||||
}
|
||||
|
||||
bool event_wait( struct event * ev )
|
||||
/*-----------------------------------------------------------*/
|
||||
/* Wait for event indefinitely (cooperative blocking) */
|
||||
bool event_wait(event_t *ev)
|
||||
{
|
||||
pthread_mutex_lock( &ev->mutex );
|
||||
pthread_mutex_lock(&ev->mutex);
|
||||
|
||||
while( ev->event_triggered == false )
|
||||
while (!ev->event_triggered)
|
||||
{
|
||||
pthread_cond_wait( &ev->cond, &ev->mutex );
|
||||
pthread_cond_wait(&ev->cond, &ev->mutex);
|
||||
}
|
||||
|
||||
ev->event_triggered = false;
|
||||
pthread_mutex_unlock( &ev->mutex );
|
||||
pthread_mutex_unlock(&ev->mutex);
|
||||
return true;
|
||||
}
|
||||
bool event_wait_timed( struct event * ev,
|
||||
time_t ms )
|
||||
|
||||
/*-----------------------------------------------------------*/
|
||||
/* Wait for event with timeout (milliseconds) */
|
||||
bool event_wait_timed(event_t *ev, time_t ms)
|
||||
{
|
||||
struct timespec ts;
|
||||
int ret = 0;
|
||||
|
||||
clock_gettime( CLOCK_REALTIME, &ts );
|
||||
ts.tv_sec += ms / 1000;
|
||||
ts.tv_nsec += ((ms % 1000) * 1000000);
|
||||
pthread_mutex_lock( &ev->mutex );
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_sec += ms / 1000;
|
||||
ts.tv_nsec += (ms % 1000) * 1000000;
|
||||
|
||||
while( (ev->event_triggered == false) && (ret == 0) )
|
||||
/* Normalize tv_nsec in case it exceeds 1,000,000,000 */
|
||||
if (ts.tv_nsec >= 1000000000L) {
|
||||
ts.tv_sec += ts.tv_nsec / 1000000000L;
|
||||
ts.tv_nsec = ts.tv_nsec % 1000000000L;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&ev->mutex);
|
||||
|
||||
while (!ev->event_triggered && ret == 0)
|
||||
{
|
||||
ret = pthread_cond_timedwait( &ev->cond, &ev->mutex, &ts );
|
||||
|
||||
if( ( ret == -1 ) && ( errno == ETIMEDOUT ) )
|
||||
ret = pthread_cond_timedwait(&ev->cond, &ev->mutex, &ts);
|
||||
if (ret == ETIMEDOUT)
|
||||
{
|
||||
ev->event_triggered = false;
|
||||
pthread_mutex_unlock(&ev->mutex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ev->event_triggered = false;
|
||||
pthread_mutex_unlock( &ev->mutex );
|
||||
pthread_mutex_unlock(&ev->mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
void event_signal( struct event * ev )
|
||||
/*-----------------------------------------------------------*/
|
||||
/* Signal / resume an event */
|
||||
void event_signal(event_t *ev)
|
||||
{
|
||||
pthread_mutex_lock( &ev->mutex );
|
||||
pthread_mutex_lock(&ev->mutex);
|
||||
ev->event_triggered = true;
|
||||
pthread_cond_signal( &ev->cond );
|
||||
pthread_mutex_unlock( &ev->mutex );
|
||||
pthread_cond_signal(&ev->cond);
|
||||
pthread_mutex_unlock(&ev->mutex);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Amazon.com, Inc. or its affiliates
|
||||
* SPDX-FileCopyrightText: 2021-2025 Amazon.com, Inc. or its affiliates
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
@@ -31,21 +31,67 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _WAIT_FOR_EVENT_H_
|
||||
#define _WAIT_FOR_EVENT_H_
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
struct event;
|
||||
|
||||
struct event * event_create(void);
|
||||
void event_delete( struct event * );
|
||||
bool event_wait( struct event * ev );
|
||||
bool event_wait_timed( struct event * ev,
|
||||
time_t ms );
|
||||
void event_signal( struct event * ev );
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
typedef struct event
|
||||
{
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond;
|
||||
bool event_triggered;
|
||||
} event_t;
|
||||
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @return event_t*
|
||||
*/
|
||||
event_t *event_create(void);
|
||||
|
||||
#endif /* ifndef _WAIT_FOR_EVENT_H_ */
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param ev
|
||||
*/
|
||||
void event_delete(event_t *ev);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param ev
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool event_wait(event_t *ev);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param ev
|
||||
* @param ms
|
||||
* @return true
|
||||
* @return false
|
||||
*/
|
||||
bool event_wait_timed(event_t *ev, time_t ms);
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param ev
|
||||
*/
|
||||
void event_signal(event_t *ev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user