mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
feat(freertos): soft-preempting linux simulator
This commit is contained in:
@@ -82,7 +82,8 @@ list(APPEND srcs
|
|||||||
|
|
||||||
if(arch STREQUAL "linux")
|
if(arch STREQUAL "linux")
|
||||||
list(APPEND srcs
|
list(APPEND srcs
|
||||||
"${kernel_impl}/portable/${arch}/utils/wait_for_event.c")
|
"${kernel_impl}/portable/${arch}/utils/wait_for_event.c"
|
||||||
|
"${kernel_impl}/portable/${arch}/utils/linux_port_coop_syscalls.c")
|
||||||
if(kernel_impl STREQUAL "FreeRTOS-Kernel")
|
if(kernel_impl STREQUAL "FreeRTOS-Kernel")
|
||||||
list(APPEND srcs
|
list(APPEND srcs
|
||||||
"${kernel_impl}/portable/${arch}/port_idf.c")
|
"${kernel_impl}/portable/${arch}/port_idf.c")
|
||||||
@@ -110,7 +111,6 @@ if(arch STREQUAL "linux")
|
|||||||
set(BYPASS_EINTR_ISSUE 0)
|
set(BYPASS_EINTR_ISSUE 0)
|
||||||
if(NOT CONFIG_LWIP_ENABLE)
|
if(NOT CONFIG_LWIP_ENABLE)
|
||||||
set(BYPASS_EINTR_ISSUE 1)
|
set(BYPASS_EINTR_ISSUE 1)
|
||||||
list(APPEND srcs "esp_additions/FreeRTOSSimulator_wrappers.c")
|
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -190,6 +190,39 @@ if(arch STREQUAL "linux")
|
|||||||
target_link_libraries(${COMPONENT_LIB} PRIVATE dl)
|
target_link_libraries(${COMPONENT_LIB} PRIVATE dl)
|
||||||
endif()
|
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()
|
||||||
|
|
||||||
# Disable strict prototype warnings in upstream code
|
# Disable strict prototype warnings in upstream code
|
||||||
# (struct event * event_create() is missing 'void')
|
# (struct event * event_create() is missing 'void')
|
||||||
set_source_files_properties(
|
set_source_files_properties(
|
||||||
|
|||||||
@@ -77,8 +77,10 @@ typedef unsigned long TickType_t;
|
|||||||
/*-----------------------------------------------------------*/
|
/*-----------------------------------------------------------*/
|
||||||
|
|
||||||
/* Scheduler utilities. */
|
/* Scheduler utilities. */
|
||||||
extern void vPortYield( void );
|
extern void vPortYieldWithinApi( void );
|
||||||
|
#define portYIELD_WITHIN_API() vPortYieldWithinApi()
|
||||||
|
|
||||||
|
extern void vPortYield( void );
|
||||||
#define portYIELD() vPortYield()
|
#define portYIELD() vPortYield()
|
||||||
|
|
||||||
#define portEND_SWITCHING_ISR( xSwitchRequired ) if( (xSwitchRequired) != pdFALSE ) 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 portENTER_CRITICAL_ISR(mux) portENTER_CRITICAL(mux)
|
||||||
#define portEXIT_CRITICAL_ISR(mux) portEXIT_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 );
|
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
|
* 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 <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
/* Scheduler includes. */
|
|
||||||
#include "FreeRTOS.h"
|
#include "FreeRTOS.h"
|
||||||
#include "task.h"
|
#include "task.h"
|
||||||
#include "utils/wait_for_event.h"
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "utils/linux_port_utils.h"
|
||||||
|
|
||||||
#define BACKTRACE_PC_ARRAY_SIZE 20
|
#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_SEGFAULT_MESSAGE "ERROR: Segmentation Fault, here's your backtrace:\n"
|
||||||
#define ON_ABORT_MESSAGE "ERROR: Aborted\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__))
|
#if (defined(__APPLE__) && defined(__MACH__))
|
||||||
typedef sig_t sighandler_t;
|
typedef sig_t sighandler_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const char *TAG = "port";
|
|
||||||
|
|
||||||
static volatile UBaseType_t uxInterruptNesting = 0;
|
static volatile UBaseType_t uxInterruptNesting = 0;
|
||||||
|
|
||||||
|
|
||||||
BaseType_t xPortCheckIfInISR(void)
|
BaseType_t xPortCheckIfInISR(void)
|
||||||
{
|
{
|
||||||
return uxInterruptNesting;
|
return uxInterruptNesting;
|
||||||
@@ -46,190 +73,133 @@ BaseType_t xPortCheckIfInISR(void)
|
|||||||
|
|
||||||
#if CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
#if CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
||||||
#define BACKTRACE_PC_ARRAY_SIZE_DUMMY 1
|
#define BACKTRACE_PC_ARRAY_SIZE_DUMMY 1
|
||||||
/**
|
|
||||||
* This function calls backtrace once to ensure that libgcc is loaded already.
|
|
||||||
*/
|
|
||||||
static void load_libgcc(void)
|
static void load_libgcc(void)
|
||||||
{
|
{
|
||||||
void *array[BACKTRACE_PC_ARRAY_SIZE_DUMMY];
|
void *array[BACKTRACE_PC_ARRAY_SIZE_DUMMY];
|
||||||
size_t size = backtrace(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)
|
static void segfault_handler(int sig)
|
||||||
{
|
{
|
||||||
void *array[BACKTRACE_PC_ARRAY_SIZE];
|
void *array[BACKTRACE_PC_ARRAY_SIZE];
|
||||||
size_t size;
|
size_t size = backtrace(array, BACKTRACE_PC_ARRAY_SIZE);
|
||||||
|
ssize_t ignore __attribute__((unused));
|
||||||
// get void*'s for all entries on the stack
|
ignore = write(STDERR_FILENO, ON_SEGFAULT_MESSAGE, sizeof(ON_SEGFAULT_MESSAGE));
|
||||||
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
|
|
||||||
|
|
||||||
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
backtrace_symbols_fd(array, size, STDERR_FILENO);
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Print a message to signal abort, even in idf.py monitor.
|
|
||||||
*/
|
|
||||||
static void abort_handler(int sig)
|
static void abort_handler(int sig)
|
||||||
{
|
{
|
||||||
// we need a raw file write here because other functions are not async-signal-safe
|
// 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));
|
ssize_t ignore __attribute__((unused));
|
||||||
(void) written; // The return value is ignored for now, as we don't have a lot of options in case of failure
|
ignore = write(STDERR_FILENO, ON_ABORT_MESSAGE, sizeof(ON_ABORT_MESSAGE));
|
||||||
// and EINTR can't happen in a signal handler anyways
|
|
||||||
|
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
#endif // CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
#endif // CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
||||||
|
|
||||||
void app_main(void);
|
/*-----------------------------------------------------------
|
||||||
|
* Main FreeRTOS task
|
||||||
static void main_task(void* args)
|
*-----------------------------------------------------------*/
|
||||||
|
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();
|
app_main();
|
||||||
|
|
||||||
|
ESP_LOGI(MAIN_TAG, "Returned from app_main()");
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void esp_startup_start_app(void)
|
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);
|
setvbuf(stdout, NULL, _IONBF, 0);
|
||||||
|
|
||||||
#if CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
#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();
|
load_libgcc();
|
||||||
sighandler_t sig_res;
|
sighandler_t sig_res;
|
||||||
|
|
||||||
// Enable backtraces
|
|
||||||
sig_res = signal(SIGSEGV, segfault_handler);
|
sig_res = signal(SIGSEGV, segfault_handler);
|
||||||
if (sig_res == SIG_ERR) {
|
if (sig_res == SIG_ERR) {
|
||||||
perror("Failed setting the segfault handler");
|
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable error message on abort
|
|
||||||
sig_res = signal(SIGABRT, abort_handler);
|
sig_res = signal(SIGABRT, abort_handler);
|
||||||
if (sig_res == SIG_ERR) {
|
if (sig_res == SIG_ERR) {
|
||||||
perror("Failed setting the abort handler");
|
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
#endif // CONFIG_COMPILER_OPTIMIZATION_DEBUG
|
#endif
|
||||||
|
|
||||||
usleep(1000);
|
// Create main_task using FreeRTOS API
|
||||||
BaseType_t res = xTaskCreatePinnedToCore(&main_task, "main",
|
ESP_LOGI(LINUX_TAG, "Starting main task.");
|
||||||
ESP_TASK_MAIN_STACK, NULL,
|
assert(xTaskCreate(&main_task, "main", ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL) == pdTRUE);
|
||||||
ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE);
|
|
||||||
assert(res == pdTRUE);
|
|
||||||
(void)res;
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "Starting scheduler.");
|
ESP_LOGI(LINUX_TAG, "Starting scheduler task.");
|
||||||
vTaskStartScheduler();
|
vTaskStartScheduler();
|
||||||
|
|
||||||
// This line should never be reached
|
// Should never reach here
|
||||||
assert(false);
|
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
|
#endif
|
||||||
|
|
||||||
void vPortYieldOtherCore( BaseType_t coreid ) { } // trying to skip for now
|
#if (configUSE_TICK_HOOK > 0)
|
||||||
|
void vApplicationTickHook(void)
|
||||||
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
|
{
|
||||||
/* configUSE_STATIC_ALLOCATION is set to 1, so the application must provide an
|
extern void esp_vApplicationTickHook(void);
|
||||||
* implementation of vApplicationGetIdleTaskMemory() to provide the memory that is
|
esp_vApplicationTickHook();
|
||||||
* used by the Idle task. */
|
}
|
||||||
void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer,
|
#else
|
||||||
StackType_t ** ppxIdleTaskStackBuffer,
|
#endif
|
||||||
uint32_t * pulIdleTaskStackSize )
|
|
||||||
|
/*-----------------------------------------------------------
|
||||||
|
* 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 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;
|
*ppxIdleTaskTCBBuffer = &xIdleTaskTCB;
|
||||||
|
|
||||||
/* Pass out the array that will be used as the Idle task's stack. */
|
|
||||||
*ppxIdleTaskStackBuffer = uxIdleTaskStack;
|
*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;
|
*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
|
void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer,
|
||||||
* use a callback function to optionally provide the memory required by the idle
|
StackType_t **ppxTimerTaskStackBuffer,
|
||||||
* and timer tasks. This is the stack that will be used by the timer task. It is
|
uint32_t *pulTimerTaskStackSize)
|
||||||
* 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 )
|
|
||||||
{
|
{
|
||||||
/* 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;
|
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;
|
*ppxTimerTaskTCBBuffer = &xTimerTaskTCB;
|
||||||
|
|
||||||
/* Pass out the array that will be used as the Timer task's stack. */
|
|
||||||
*ppxTimerTaskStackBuffer = uxTimerTaskStack;
|
*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;
|
*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)
|
void __attribute__((weak)) vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName)
|
||||||
{
|
{
|
||||||
#define ERR_STR1 "***ERROR*** A stack overflow in task "
|
#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 <pthread.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "wait_for_event.h"
|
#include "wait_for_event.h"
|
||||||
|
|
||||||
struct event
|
/*-----------------------------------------------------------*/
|
||||||
|
/* Create a new event */
|
||||||
|
event_t *event_create(void)
|
||||||
{
|
{
|
||||||
pthread_mutex_t mutex;
|
event_t * ev = malloc(sizeof(event_t));
|
||||||
pthread_cond_t cond;
|
|
||||||
bool event_triggered;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct event * event_create(void)
|
|
||||||
{
|
|
||||||
struct event * ev = malloc( sizeof( struct event ) );
|
|
||||||
assert(ev != NULL);
|
assert(ev != NULL);
|
||||||
|
|
||||||
ev->event_triggered = false;
|
ev->event_triggered = false;
|
||||||
pthread_mutex_init( &ev->mutex, NULL );
|
pthread_mutex_init(&ev->mutex, NULL);
|
||||||
pthread_cond_init( &ev->cond, NULL );
|
pthread_cond_init(&ev->cond, NULL);
|
||||||
|
|
||||||
return ev;
|
return ev;
|
||||||
}
|
}
|
||||||
|
|
||||||
void event_delete( struct event * ev )
|
/*-----------------------------------------------------------*/
|
||||||
|
/* Delete an event */
|
||||||
|
void event_delete(event_t *ev)
|
||||||
{
|
{
|
||||||
pthread_mutex_destroy( &ev->mutex );
|
pthread_mutex_destroy(&ev->mutex);
|
||||||
pthread_cond_destroy( &ev->cond );
|
pthread_cond_destroy(&ev->cond);
|
||||||
free( ev );
|
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;
|
ev->event_triggered = false;
|
||||||
pthread_mutex_unlock( &ev->mutex );
|
pthread_mutex_unlock(&ev->mutex);
|
||||||
return true;
|
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;
|
struct timespec ts;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
clock_gettime( CLOCK_REALTIME, &ts );
|
clock_gettime(CLOCK_REALTIME, &ts);
|
||||||
ts.tv_sec += ms / 1000;
|
ts.tv_sec += ms / 1000;
|
||||||
ts.tv_nsec += ((ms % 1000) * 1000000);
|
ts.tv_nsec += (ms % 1000) * 1000000;
|
||||||
pthread_mutex_lock( &ev->mutex );
|
|
||||||
|
|
||||||
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 );
|
ret = pthread_cond_timedwait(&ev->cond, &ev->mutex, &ts);
|
||||||
|
if (ret == ETIMEDOUT)
|
||||||
if( ( ret == -1 ) && ( errno == ETIMEDOUT ) )
|
|
||||||
{
|
{
|
||||||
|
ev->event_triggered = false;
|
||||||
|
pthread_mutex_unlock(&ev->mutex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ev->event_triggered = false;
|
ev->event_triggered = false;
|
||||||
pthread_mutex_unlock( &ev->mutex );
|
pthread_mutex_unlock(&ev->mutex);
|
||||||
return true;
|
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;
|
ev->event_triggered = true;
|
||||||
pthread_cond_signal( &ev->cond );
|
pthread_cond_signal(&ev->cond);
|
||||||
pthread_mutex_unlock( &ev->mutex );
|
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
|
* SPDX-License-Identifier: MIT
|
||||||
*/
|
*/
|
||||||
@@ -31,21 +31,67 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _WAIT_FOR_EVENT_H_
|
#pragma once
|
||||||
#define _WAIT_FOR_EVENT_H_
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
struct event;
|
/**
|
||||||
|
* @brief
|
||||||
struct event * event_create(void);
|
*
|
||||||
void event_delete( struct event * );
|
*/
|
||||||
bool event_wait( struct event * ev );
|
typedef struct event
|
||||||
bool event_wait_timed( struct event * ev,
|
{
|
||||||
time_t ms );
|
pthread_mutex_t mutex;
|
||||||
void event_signal( struct event * ev );
|
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
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include <sys/select.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
/** 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);
|
|
||||||
|
|
||||||
int select(int fd, fd_set *rfds, fd_set *wfds, fd_set *efds, struct timeval *tval)
|
|
||||||
{
|
|
||||||
static select_func_t s_real_select = NULL;
|
|
||||||
TickType_t end_ticks = portMAX_DELAY;
|
|
||||||
fd_set o_rfds, o_wfds, o_efds;
|
|
||||||
|
|
||||||
// Lookup the select symbol
|
|
||||||
if (s_real_select == NULL) {
|
|
||||||
s_real_select = (select_func_t)dlsym(RTLD_NEXT, "select");
|
|
||||||
assert(s_real_select); // abort() if we cannot locate the symbol
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 except EINTR
|
|
||||||
if (ret == -1 && errno != EINTR) {
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sleep for maximum 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.
|
|
||||||
*/
|
|
||||||
const TickType_t max_sleep_ticks = 10;
|
|
||||||
TickType_t sleep_ticks = max_sleep_ticks;
|
|
||||||
|
|
||||||
if (tval != NULL) {
|
|
||||||
TickType_t now_ticks = xTaskGetTickCount();
|
|
||||||
if (now_ticks >= end_ticks) {
|
|
||||||
errno = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// Sleep for the remaining time or a maximum of 10 tick
|
|
||||||
TickType_t remaining_ticks = end_ticks - now_ticks;
|
|
||||||
sleep_ticks = (remaining_ticks < max_sleep_ticks) ? remaining_ticks : max_sleep_ticks;
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(sleep_ticks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+14
-2
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
* The following macros are convenience macros used to account for different
|
* The following macros are convenience macros used to account for different
|
||||||
* thread safety behavior between single-core and SMP in ESP-IDF FreeRTOS.
|
* thread safety behavior between single-core and SMP in ESP-IDF FreeRTOS.
|
||||||
*
|
*
|
||||||
* For thread saftey...
|
* For thread safety...
|
||||||
*
|
*
|
||||||
* - Single-core will use the following for thread safety (depending on situation)
|
* - Single-core will use the following for thread safety (depending on situation)
|
||||||
* - `vTaskSuspendAll()`/`xTaskResumeAll()` for non-deterministic operations
|
* - `vTaskSuspendAll()`/`xTaskResumeAll()` for non-deterministic operations
|
||||||
@@ -119,12 +119,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Macros that enter/exit a critical section only when building for SMP */
|
/* Macros that enter/exit a critical section only when building for SMP */
|
||||||
|
#if !defined prvENTER_CRITICAL_SMP_ONLY
|
||||||
#define prvENTER_CRITICAL_SMP_ONLY( pxLock )
|
#define prvENTER_CRITICAL_SMP_ONLY( pxLock )
|
||||||
|
#endif
|
||||||
|
#if !defined prvEXIT_CRITICAL_SMP_ONLY
|
||||||
#define prvEXIT_CRITICAL_SMP_ONLY( pxLock )
|
#define prvEXIT_CRITICAL_SMP_ONLY( pxLock )
|
||||||
|
#endif
|
||||||
|
#if !defined prvENTER_CRITICAL_ISR_SMP_ONLY
|
||||||
#define prvENTER_CRITICAL_ISR_SMP_ONLY( pxLock )
|
#define prvENTER_CRITICAL_ISR_SMP_ONLY( pxLock )
|
||||||
|
#endif
|
||||||
|
#if !defined prvEXIT_CRITICAL_ISR_SMP_ONLY
|
||||||
#define prvEXIT_CRITICAL_ISR_SMP_ONLY( pxLock )
|
#define prvEXIT_CRITICAL_ISR_SMP_ONLY( pxLock )
|
||||||
|
#endif
|
||||||
|
#if !defined prvENTER_CRITICAL_SAFE_SMP_ONLY
|
||||||
#define prvENTER_CRITICAL_SAFE_SMP_ONLY( pxLock )
|
#define prvENTER_CRITICAL_SAFE_SMP_ONLY( pxLock )
|
||||||
|
#endif
|
||||||
|
#if !defined prvEXIT_CRITICAL_SAFE_SMP_ONLY
|
||||||
#define prvEXIT_CRITICAL_SAFE_SMP_ONLY( pxLock )
|
#define prvEXIT_CRITICAL_SAFE_SMP_ONLY( pxLock )
|
||||||
|
#endif
|
||||||
|
|
||||||
/* Macros that enter/exit a critical section only when building for single-core */
|
/* Macros that enter/exit a critical section only when building for single-core */
|
||||||
#define prvENTER_CRITICAL_SC_ONLY( pxLock ) taskENTER_CRITICAL( pxLock )
|
#define prvENTER_CRITICAL_SC_ONLY( pxLock ) taskENTER_CRITICAL( pxLock )
|
||||||
|
|||||||
@@ -42,25 +42,29 @@ This approach uses the `CMock <https://www.throwtheswitch.org/cmock>`_ framework
|
|||||||
POSIX/Linux Simulator Approach
|
POSIX/Linux Simulator Approach
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
The `FreeRTOS POSIX/Linux simulator <https://www.freertos.org/FreeRTOS-simulator-for-Linux.html>`_ is available on ESP-IDF as a preview target already. This simulator allows ESP-IDF components to be implemented on the host, making them accessible to ESP-IDF applications when running on host. Currently, only a limited number of components are ready to be built on Linux. Furthermore, the functionality of each component ported to Linux may also be limited or different compared to the functionality when building that component for a chip target. For more information about whether the desired components are supported on Linux, please refer to :ref:`component-linux-mock-support`.
|
The FreeRTOS Linux simulator is available on ESP-IDF as a preview target. This simulator runs each FreeRTOS task as a POSIX thread (pthread), allowing ESP-IDF components to be implemented on the host and making them accessible to ESP-IDF applications when running on host. Currently, not all components are supported on Linux. Furthermore, the functionality of each component ported to Linux may also be limited or different compared to the functionality when building that component for a chip target. For more information about whether the desired components are supported on Linux, please refer to :ref:`component-linux-mock-support`.
|
||||||
|
|
||||||
Note that this simulator relies heavily on POSIX signals and signal handlers to control and interrupt threads. Hence, it has the following *limitations*:
|
**Scheduling Model**
|
||||||
|
|
||||||
|
The Linux simulator uses *cooperative preemption*: a dedicated scheduler thread increments the tick counter at regular intervals (configured by ``configTICK_RATE_HZ``) and performs context switches by blocking and unblocking pthreads. Because context switches can only occur when a task interacts with the FreeRTOS kernel, the scheduler is not truly preemptive at the instruction level as it would be on real hardware. This means:
|
||||||
|
|
||||||
.. list::
|
.. list::
|
||||||
- Functions that are not *async-signal-safe*, e.g. ``printf()``, should be avoided. In particular, calling them from different tasks with different priority can lead to crashes and deadlocks.
|
- A task that busy-loops without calling any FreeRTOS API will never be preempted. Preemption can only occur when a task interacts with the FreeRTOS kernel, such as entering or exiting a critical section, yielding, or calling any API that may block (e.g., ``vTaskDelay()``, ``xQueueReceive()``).
|
||||||
- Calling any FreeRTOS primitives from threads not created by FreeRTOS API functions is forbidden.
|
- Timing granularity is limited to the tick period (e.g., 10 ms at 100 Hz).
|
||||||
- FreeRTOS tasks using any native blocking/waiting mechanism (e.g., ``select()``), may be perceived as *ready* by the simulated FreeRTOS scheduler and therefore may be scheduled, even though they are actually blocked. This is because the simulated FreeRTOS scheduler only recognizes tasks blocked on any FreeRTOS API as *waiting*.
|
- Critical sections are protected by mutexes rather than by disabling interrupts.
|
||||||
- APIs that may be interrupted by signals will continually receive the signals simulating FreeRTOS tick interrupts when invoked from a running simulated FreeRTOS task. Consequently, code that calls these APIs should be designed to handle potential interrupting signals or the API needs to be wrapped by the linker.
|
|
||||||
|
|
||||||
Since these limitations are not very practical, in particular for testing and development, we are currently evaluating if we can find a better solution for running ESP-IDF applications on the host machine.
|
**Limitations**
|
||||||
|
|
||||||
Note furthermore that if you use the ESP-IDF FreeRTOS mock component (``tools/mocks/freertos``), these limitations do not apply. But that mock component will not do any scheduling, either.
|
.. list::
|
||||||
|
- Calling FreeRTOS primitives that may block or yield (e.g., queues, semaphores, task notifications, ``vTaskDelay()``) from threads not created by FreeRTOS API functions is not supported. However, critical sections (``taskENTER_CRITICAL`` / ``taskEXIT_CRITICAL``) can be used from any thread.
|
||||||
|
|
||||||
|
Note that if you use the ESP-IDF FreeRTOS mock component (``tools/mocks/freertos``), these limitations do not apply. But that mock component will not do any scheduling, either.
|
||||||
|
|
||||||
.. only:: not esp32p4 and not esp32h4 and not esp32s31
|
.. only:: not esp32p4 and not esp32h4 and not esp32s31
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The FreeRTOS POSIX/Linux simulator allows configuring the :ref:`amazon_smp_freertos` version. However, the simulation still runs in single-core mode. The main reason allowing Amazon SMP FreeRTOS is to provide API compatibility with ESP-IDF applications written for Amazon SMP FreeRTOS.
|
The FreeRTOS POSIX/Linux simulator allows configuring the :ref:`amazon_smp_freertos` version. However, the simulation still runs in single-core mode (``configNUM_CORES = 1``). The main reason allowing Amazon SMP FreeRTOS is to provide API compatibility with ESP-IDF applications written for Amazon SMP FreeRTOS.
|
||||||
|
|
||||||
Requirements for Using Mocks
|
Requirements for Using Mocks
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
# This is a manual mock that supplies `main()` if FreeRTOS is mocked
|
# This is a manual mock that supplies `main()` if FreeRTOS is mocked
|
||||||
idf_component_register(SRCS "startup_mock.c"
|
idf_component_register(SRCS "startup_mock.c"
|
||||||
REQUIRES main esp_event)
|
REQUIRES main esp_event)
|
||||||
|
|
||||||
|
# Prevent esp_system from providing main() since this component provides it
|
||||||
|
idf_component_get_property(esp_system_lib esp_system COMPONENT_LIB)
|
||||||
|
target_compile_definitions(${esp_system_lib} PRIVATE ESP_SYSTEM_LINUX_NO_MAIN)
|
||||||
|
|||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -43,7 +43,7 @@ static void loop_task(void *arg)
|
|||||||
// Short delay to allow other created tasks to run
|
// Short delay to allow other created tasks to run
|
||||||
vTaskDelay(2);
|
vTaskDelay(2);
|
||||||
while (1) {
|
while (1) {
|
||||||
;
|
vTaskDelay(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -76,7 +76,7 @@ TEST_CASE("FreeRTOS Delete Blocked Tasks", "[freertos]")
|
|||||||
|
|
||||||
(1000 iterations takes about 9 seconds on ESP32 dual core)
|
(1000 iterations takes about 9 seconds on ESP32 dual core)
|
||||||
*/
|
*/
|
||||||
for(unsigned iter = 0; iter < 1000; iter++) {
|
for(unsigned iter = 0; iter < 100; iter++) {
|
||||||
// Create everything
|
// Create everything
|
||||||
SemaphoreHandle_t sem = xSemaphoreCreateMutex();
|
SemaphoreHandle_t sem = xSemaphoreCreateMutex();
|
||||||
for(unsigned i = 0; i < configNUM_CORES + 1; i++) {
|
for(unsigned i = 0; i < configNUM_CORES + 1; i++) {
|
||||||
@@ -95,6 +95,7 @@ TEST_CASE("FreeRTOS Delete Blocked Tasks", "[freertos]")
|
|||||||
vTaskDelete(blocking_tasks[i]);
|
vTaskDelete(blocking_tasks[i]);
|
||||||
params[i].deleted = true;
|
params[i].deleted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
vTaskDelay(4); // Yield to the idle task for cleanup
|
vTaskDelay(4); // Yield to the idle task for cleanup
|
||||||
|
|
||||||
vSemaphoreDelete(sem);
|
vSemaphoreDelete(sem);
|
||||||
|
|||||||
+35
-39
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -20,62 +20,58 @@
|
|||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
static volatile bool trigger;
|
/*
|
||||||
static volatile bool flag;
|
* Test: A lower-priority task must be able to run and send data to a queue
|
||||||
|
* when the higher-priority task yields (blocks).
|
||||||
|
*
|
||||||
|
* Pattern: The lower-priority task blocks on a semaphore until told to proceed,
|
||||||
|
* then sends to a queue. The higher-priority task gives the semaphore and
|
||||||
|
* immediately blocks on the queue receive.
|
||||||
|
*/
|
||||||
|
|
||||||
#ifndef CONFIG_FREERTOS_SMP
|
|
||||||
#define MAX_YIELD_COUNT 10000
|
|
||||||
#else
|
|
||||||
//TODO: IDF-5081
|
|
||||||
#define MAX_YIELD_COUNT 17000
|
|
||||||
#endif // CONFIG_FREERTOS_SMP
|
|
||||||
|
|
||||||
|
|
||||||
/* Task:
|
|
||||||
- Waits for 'trigger' variable to be set
|
|
||||||
- Reads the cycle count on this CPU
|
|
||||||
- Pushes it into a queue supplied as a param
|
|
||||||
- Busy-waits until the main task terminates it
|
|
||||||
*/
|
|
||||||
static void task_send_to_queue(void *param)
|
static void task_send_to_queue(void *param)
|
||||||
{
|
{
|
||||||
QueueHandle_t queue = (QueueHandle_t) param;
|
void **args = (void **)param;
|
||||||
uint32_t ccount;
|
QueueHandle_t queue = (QueueHandle_t)args[0];
|
||||||
|
SemaphoreHandle_t sem = (SemaphoreHandle_t)args[1];
|
||||||
|
|
||||||
while(!trigger) {}
|
/* Block until the main task tells us to go */
|
||||||
|
xSemaphoreTake(sem, portMAX_DELAY);
|
||||||
|
|
||||||
ccount = 0;
|
uint32_t value = 42;
|
||||||
flag = true;
|
xQueueSendToBack(queue, &value, 0);
|
||||||
xQueueSendToBack(queue, &ccount, 0);
|
|
||||||
/* This is to ensure that higher priority task
|
|
||||||
won't wake anyhow, due to this task terminating.
|
|
||||||
|
|
||||||
The task runs until terminated by the main task.
|
/* Stay alive until deleted */
|
||||||
*/
|
vTaskSuspend(NULL);
|
||||||
while(1) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Yield from lower priority task, same CPU", "[freertos]")
|
TEST_CASE("Yield from lower priority task, same CPU", "[freertos]")
|
||||||
{
|
{
|
||||||
/* Do this 3 times, mostly for the benchmark value - the first
|
|
||||||
run includes a cache miss so uses more cycles than it should. */
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
TaskHandle_t sender_task;
|
TaskHandle_t sender_task;
|
||||||
QueueHandle_t queue = xQueueCreate(1, sizeof(uint32_t));
|
QueueHandle_t queue = xQueueCreate(1, sizeof(uint32_t));
|
||||||
flag = false;
|
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
|
||||||
trigger = false;
|
void *args[2] = { queue, sem };
|
||||||
|
|
||||||
/* "yield" task sits on our CPU, lower priority to us */
|
/* Lower-priority task blocks on the semaphore */
|
||||||
xTaskCreatePinnedToCore(task_send_to_queue, "YIELD", 2048, (void *)queue, CONFIG_UNITY_FREERTOS_PRIORITY - 1, &sender_task, CONFIG_UNITY_FREERTOS_CPU);
|
xTaskCreatePinnedToCore(task_send_to_queue, "YIELD", 2048, args,
|
||||||
|
CONFIG_UNITY_FREERTOS_PRIORITY - 1, &sender_task,
|
||||||
|
CONFIG_UNITY_FREERTOS_CPU);
|
||||||
|
|
||||||
vTaskDelay(1); /* make sure everything is set up */
|
/* Let the task start and block on the semaphore */
|
||||||
trigger = true;
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
|
||||||
uint32_t yield_ccount;
|
/* Give the semaphore — the lower-priority task won't run yet because
|
||||||
TEST_ASSERT( xQueueReceive(queue, &yield_ccount, 100 / portTICK_PERIOD_MS) );
|
* we (higher priority) are still ready. Then block on queue receive,
|
||||||
TEST_ASSERT( flag );
|
* which yields the CPU to the lower-priority task. */
|
||||||
|
xSemaphoreGive(sem);
|
||||||
|
|
||||||
|
uint32_t received;
|
||||||
|
TEST_ASSERT(xQueueReceive(queue, &received, pdMS_TO_TICKS(100)));
|
||||||
|
TEST_ASSERT_EQUAL(42, received);
|
||||||
|
|
||||||
vTaskDelete(sender_task);
|
vTaskDelete(sender_task);
|
||||||
vQueueDelete(queue);
|
vQueueDelete(queue);
|
||||||
|
vSemaphoreDelete(sem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+55
-30
@@ -1,11 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
#include "portTestMacro.h"
|
#include "portTestMacro.h"
|
||||||
|
|
||||||
@@ -17,64 +18,88 @@ Test Priority Scheduling (Single Core)
|
|||||||
Purpose:
|
Purpose:
|
||||||
- Test that the single-core scheduler always schedules the highest priority ready task
|
- Test that the single-core scheduler always schedules the highest priority ready task
|
||||||
Procedure:
|
Procedure:
|
||||||
- Raise the unityTask priority to (configMAX_PRIORITIES - 1)
|
- unityTask (highest priority) creates a binary semaphore (initially empty)
|
||||||
- unityTask creates the following lower priority tasks
|
- unityTask creates two lower-priority tasks that both block on the semaphore:
|
||||||
- task_A (configMAX_PRIORITIES - 2)
|
- task_A (configMAX_PRIORITIES - 2)
|
||||||
- task_B (configMAX_PRIORITIES - 3)
|
- task_B (configMAX_PRIORITIES - 3)
|
||||||
- UnityTask blocks for a short period of time to allow task_A to run
|
- unityTask delays to let both tasks start and block on the semaphore
|
||||||
- Clean up and restore unityTask's original priority
|
- unityTask gives the semaphore once — FreeRTOS wakes the highest-priority
|
||||||
|
waiter (task_A), which writes its ID to a shared variable and signals done
|
||||||
|
- unityTask verifies the shared variable holds task_A's ID
|
||||||
Expected:
|
Expected:
|
||||||
- task_A should run after unityTask blocks
|
- task_A (higher priority) wins the semaphore race, not task_B
|
||||||
- task_B should never have run
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#if ( configNUM_CORES == 1 )
|
#if ( configNUM_CORES == 1 )
|
||||||
|
|
||||||
#define UNITY_TASK_DELAY_TICKS 10
|
#define PRIO_TASK_A_ID 1
|
||||||
|
#define PRIO_TASK_B_ID 2
|
||||||
|
|
||||||
static BaseType_t task_A_ran;
|
static volatile int s_prio_winner;
|
||||||
static BaseType_t task_B_ran;
|
static SemaphoreHandle_t s_race_sem;
|
||||||
|
static SemaphoreHandle_t s_done_sem;
|
||||||
|
|
||||||
static void task_A(void *arg)
|
static void task_A(void *arg)
|
||||||
{
|
{
|
||||||
task_A_ran = pdTRUE;
|
/* Block until the race semaphore is given */
|
||||||
/* Keeping spinning to prevent the lower priority task_B from running */
|
xSemaphoreTake(s_race_sem, portMAX_DELAY);
|
||||||
while (1) {
|
/* Higher priority: should be woken first */
|
||||||
;
|
s_prio_winner = PRIO_TASK_A_ID;
|
||||||
}
|
xSemaphoreGive(s_done_sem);
|
||||||
|
vTaskSuspend(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void task_B(void *arg)
|
static void task_B(void *arg)
|
||||||
{
|
{
|
||||||
/* The following should never run due to task_B having a lower priority */
|
/* Block until the race semaphore is given */
|
||||||
task_B_ran = pdTRUE;
|
xSemaphoreTake(s_race_sem, portMAX_DELAY);
|
||||||
while (1) {
|
/* Lower priority: should NOT be woken first */
|
||||||
;
|
s_prio_winner = PRIO_TASK_B_ID;
|
||||||
}
|
xSemaphoreGive(s_done_sem);
|
||||||
|
vTaskSuspend(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("Tasks: Test priority scheduling", "[freertos]")
|
TEST_CASE("Tasks: Test priority scheduling", "[freertos]")
|
||||||
{
|
{
|
||||||
TaskHandle_t task_A_handle;
|
TaskHandle_t task_A_handle;
|
||||||
TaskHandle_t task_B_handle;
|
TaskHandle_t task_B_handle;
|
||||||
task_A_ran = pdFALSE;
|
s_prio_winner = 0;
|
||||||
task_B_ran = pdFALSE;
|
|
||||||
|
/* Binary semaphores start empty — both tasks will block on take */
|
||||||
|
s_race_sem = xSemaphoreCreateBinary();
|
||||||
|
s_done_sem = xSemaphoreCreateBinary();
|
||||||
|
TEST_ASSERT_NOT_NULL(s_race_sem);
|
||||||
|
TEST_ASSERT_NOT_NULL(s_done_sem);
|
||||||
|
|
||||||
/* Raise the priority of the unityTask */
|
/* Raise the priority of the unityTask */
|
||||||
vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1);
|
vTaskPrioritySet(NULL, configMAX_PRIORITIES - 1);
|
||||||
/* Create task_A and task_B */
|
|
||||||
xTaskCreate(task_A, "task_A", configTEST_DEFAULT_STACK_SIZE, (void *)xTaskGetCurrentTaskHandle(), configMAX_PRIORITIES - 2, &task_A_handle);
|
|
||||||
xTaskCreate(task_B, "task_B", configTEST_DEFAULT_STACK_SIZE, (void *)xTaskGetCurrentTaskHandle(), configMAX_PRIORITIES - 3, &task_B_handle);
|
|
||||||
|
|
||||||
/* Block to allow task_A to be scheduled */
|
/* Create task_A (higher prio) and task_B (lower prio) */
|
||||||
vTaskDelay(UNITY_TASK_DELAY_TICKS);
|
xTaskCreate(task_A, "task_A", configTEST_DEFAULT_STACK_SIZE, NULL,
|
||||||
|
configMAX_PRIORITIES - 2, &task_A_handle);
|
||||||
|
xTaskCreate(task_B, "task_B", configTEST_DEFAULT_STACK_SIZE, NULL,
|
||||||
|
configMAX_PRIORITIES - 3, &task_B_handle);
|
||||||
|
|
||||||
/* Test that only task_A has run */
|
/* Let both tasks start and block on s_race_sem */
|
||||||
TEST_ASSERT_EQUAL(pdTRUE, task_A_ran);
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
TEST_ASSERT_EQUAL(pdFALSE, task_B_ran);
|
|
||||||
|
|
||||||
|
/* Give the semaphore once. FreeRTOS wakes the highest-priority waiter
|
||||||
|
* (task_A). Since unityTask is still higher priority, task_A won't
|
||||||
|
* actually run until we block below. */
|
||||||
|
xSemaphoreGive(s_race_sem);
|
||||||
|
|
||||||
|
/* Block waiting for the winner to signal. This lets task_A run. */
|
||||||
|
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done_sem, pdMS_TO_TICKS(200)));
|
||||||
|
|
||||||
|
/* The higher-priority task_A should have won the race */
|
||||||
|
TEST_ASSERT_EQUAL(PRIO_TASK_A_ID, s_prio_winner);
|
||||||
|
|
||||||
|
/* Cleanup */
|
||||||
vTaskDelete(task_A_handle);
|
vTaskDelete(task_A_handle);
|
||||||
vTaskDelete(task_B_handle);
|
vTaskDelete(task_B_handle);
|
||||||
|
vSemaphoreDelete(s_race_sem);
|
||||||
|
vSemaphoreDelete(s_done_sem);
|
||||||
|
|
||||||
/* Restore the priority of the unityTask */
|
/* Restore the priority of the unityTask */
|
||||||
vTaskPrioritySet(NULL, configTEST_UNITY_TASK_PRIORITY);
|
vTaskPrioritySet(NULL, configTEST_UNITY_TASK_PRIORITY);
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-53
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@@ -14,74 +14,89 @@
|
|||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A race task blocks on a shared binary semaphore. When the semaphore is given,
|
||||||
|
* FreeRTOS wakes the highest-priority waiter, which writes its ID to s_winner
|
||||||
|
* and signals s_done_sem. The task then suspends itself (it is single-shot).
|
||||||
|
*/
|
||||||
|
|
||||||
static void counter_task(void *param)
|
#define TASK_ID_A 1
|
||||||
|
#define TASK_ID_B 2
|
||||||
|
|
||||||
|
static volatile int s_winner;
|
||||||
|
static SemaphoreHandle_t s_race_sem;
|
||||||
|
static SemaphoreHandle_t s_done_sem;
|
||||||
|
|
||||||
|
static void race_task(void *arg)
|
||||||
{
|
{
|
||||||
volatile uint32_t *counter = (volatile uint32_t *)param;
|
int my_id = (int)(uintptr_t)arg;
|
||||||
while (1) {
|
xSemaphoreTake(s_race_sem, portMAX_DELAY);
|
||||||
(*counter)++;
|
s_winner = my_id;
|
||||||
}
|
xSemaphoreGive(s_done_sem);
|
||||||
|
vTaskSuspend(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_CASE("Get/Set Priorities", "[freertos]")
|
TEST_CASE("Get/Set Priorities", "[freertos]")
|
||||||
{
|
{
|
||||||
/* Two tasks per processor */
|
TaskHandle_t task_a, task_b;
|
||||||
TaskHandle_t tasks[configNUM_CORES][2] = { 0 };
|
|
||||||
unsigned volatile counters[configNUM_CORES][2] = { 0 };
|
|
||||||
|
|
||||||
|
s_race_sem = xSemaphoreCreateBinary();
|
||||||
|
s_done_sem = xSemaphoreCreateBinary();
|
||||||
|
TEST_ASSERT_NOT_NULL(s_race_sem);
|
||||||
|
TEST_ASSERT_NOT_NULL(s_done_sem);
|
||||||
|
|
||||||
|
/* Verify unity task's own priority */
|
||||||
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY, uxTaskPriorityGet(NULL));
|
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY, uxTaskPriorityGet(NULL));
|
||||||
|
|
||||||
/* create a matrix of counter tasks on each core */
|
/* --- Round 1: task_a has higher priority, should win the race --- */
|
||||||
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
|
xTaskCreatePinnedToCore(race_task, "a", 2048, (void *)(uintptr_t)TASK_ID_A,
|
||||||
for (int task = 0; task < 2; task++) {
|
CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_a, 0);
|
||||||
xTaskCreatePinnedToCore(counter_task, "count", 2048, (void *)&(counters[cpu][task]), CONFIG_UNITY_FREERTOS_PRIORITY - task, &(tasks[cpu][task]), cpu);
|
xTaskCreatePinnedToCore(race_task, "b", 2048, (void *)(uintptr_t)TASK_ID_B,
|
||||||
}
|
CONFIG_UNITY_FREERTOS_PRIORITY - 2, &task_b, 0);
|
||||||
}
|
|
||||||
|
|
||||||
/* check they were created with the expected priorities */
|
/* Verify created priorities */
|
||||||
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
|
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 1, uxTaskPriorityGet(task_a));
|
||||||
for (int task = 0; task < 2; task++) {
|
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 2, uxTaskPriorityGet(task_b));
|
||||||
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - task, uxTaskPriorityGet(tasks[cpu][task]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vTaskDelay(10);
|
/* Let both tasks block on s_race_sem */
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
|
|
||||||
/* at this point, only the higher priority tasks (first index) should be counting */
|
s_winner = 0;
|
||||||
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
|
xSemaphoreGive(s_race_sem);
|
||||||
TEST_ASSERT_NOT_EQUAL(0, counters[cpu][0]);
|
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done_sem, pdMS_TO_TICKS(200)));
|
||||||
TEST_ASSERT_EQUAL(0, counters[cpu][1]);
|
TEST_ASSERT_EQUAL(TASK_ID_A, s_winner);
|
||||||
}
|
|
||||||
|
|
||||||
/* swap priorities! */
|
vTaskDelete(task_a);
|
||||||
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
|
vTaskDelete(task_b);
|
||||||
vTaskPrioritySet(tasks[cpu][0], CONFIG_UNITY_FREERTOS_PRIORITY - 1);
|
|
||||||
vTaskPrioritySet(tasks[cpu][1], CONFIG_UNITY_FREERTOS_PRIORITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check priorities have swapped... */
|
/* --- Test vTaskPrioritySet API --- */
|
||||||
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
|
xTaskCreatePinnedToCore(race_task, "p", 2048, (void *)(uintptr_t)TASK_ID_A,
|
||||||
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY -1, uxTaskPriorityGet(tasks[cpu][0]));
|
CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_a, 0);
|
||||||
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY, uxTaskPriorityGet(tasks[cpu][1]));
|
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 1, uxTaskPriorityGet(task_a));
|
||||||
}
|
vTaskPrioritySet(task_a, CONFIG_UNITY_FREERTOS_PRIORITY - 2);
|
||||||
|
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 2, uxTaskPriorityGet(task_a));
|
||||||
|
vTaskDelete(task_a);
|
||||||
|
|
||||||
/* check the tasks which are counting have also swapped now... */
|
/* --- Round 2: swap priorities — task_b now higher, should win --- */
|
||||||
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
|
xTaskCreatePinnedToCore(race_task, "a2", 2048, (void *)(uintptr_t)TASK_ID_A,
|
||||||
unsigned old_counters[2];
|
CONFIG_UNITY_FREERTOS_PRIORITY - 2, &task_a, 0);
|
||||||
old_counters[0] = counters[cpu][0];
|
xTaskCreatePinnedToCore(race_task, "b2", 2048, (void *)(uintptr_t)TASK_ID_B,
|
||||||
old_counters[1] = counters[cpu][1];
|
CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_b, 0);
|
||||||
vTaskDelay(10);
|
|
||||||
TEST_ASSERT_EQUAL(old_counters[0], counters[cpu][0]);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(old_counters[1], counters[cpu][1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* clean up */
|
/* Let both tasks block on s_race_sem */
|
||||||
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
|
vTaskDelay(pdMS_TO_TICKS(50));
|
||||||
for (int task = 0; task < 2; task++) {
|
|
||||||
vTaskDelete(tasks[cpu][task]);
|
s_winner = 0;
|
||||||
}
|
xSemaphoreGive(s_race_sem);
|
||||||
}
|
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done_sem, pdMS_TO_TICKS(200)));
|
||||||
|
TEST_ASSERT_EQUAL(TASK_ID_B, s_winner);
|
||||||
|
|
||||||
|
/* Cleanup */
|
||||||
|
vTaskDelete(task_a);
|
||||||
|
vTaskDelete(task_b);
|
||||||
|
vSemaphoreDelete(s_race_sem);
|
||||||
|
vSemaphoreDelete(s_done_sem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
CONFIG_IDF_TARGET="linux"
|
CONFIG_IDF_TARGET="linux"
|
||||||
CONFIG_FREERTOS_SMP=y
|
CONFIG_FREERTOS_SMP=n
|
||||||
|
|||||||
Reference in New Issue
Block a user