feat(freertos): soft-preempting linux simulator

This commit is contained in:
Guillaume Souchere
2026-06-04 11:36:21 +02:00
parent 848fbc8d74
commit 78420f2614
19 changed files with 1428 additions and 946 deletions
+4
View File
@@ -1,3 +1,7 @@
# This is a manual mock that supplies `main()` if FreeRTOS is mocked
idf_component_register(SRCS "startup_mock.c"
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)
@@ -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
*/
@@ -43,7 +43,7 @@ static void loop_task(void *arg)
// Short delay to allow other created tasks to run
vTaskDelay(2);
while (1) {
;
vTaskDelay(1);
}
}
@@ -76,7 +76,7 @@ TEST_CASE("FreeRTOS Delete Blocked Tasks", "[freertos]")
(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
SemaphoreHandle_t sem = xSemaphoreCreateMutex();
for(unsigned i = 0; i < configNUM_CORES + 1; i++) {
@@ -95,6 +95,7 @@ TEST_CASE("FreeRTOS Delete Blocked Tasks", "[freertos]")
vTaskDelete(blocking_tasks[i]);
params[i].deleted = true;
}
vTaskDelay(4); // Yield to the idle task for cleanup
vSemaphoreDelete(sem);
@@ -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
*/
@@ -20,62 +20,58 @@
#include "unity.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)
{
QueueHandle_t queue = (QueueHandle_t) param;
uint32_t ccount;
void **args = (void **)param;
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;
flag = true;
xQueueSendToBack(queue, &ccount, 0);
/* This is to ensure that higher priority task
won't wake anyhow, due to this task terminating.
uint32_t value = 42;
xQueueSendToBack(queue, &value, 0);
The task runs until terminated by the main task.
*/
while(1) {}
/* Stay alive until deleted */
vTaskSuspend(NULL);
}
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++) {
TaskHandle_t sender_task;
QueueHandle_t queue = xQueueCreate(1, sizeof(uint32_t));
flag = false;
trigger = false;
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
void *args[2] = { queue, sem };
/* "yield" task sits on our CPU, lower priority to us */
xTaskCreatePinnedToCore(task_send_to_queue, "YIELD", 2048, (void *)queue, CONFIG_UNITY_FREERTOS_PRIORITY - 1, &sender_task, CONFIG_UNITY_FREERTOS_CPU);
/* Lower-priority task blocks on the semaphore */
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 */
trigger = true;
/* Let the task start and block on the semaphore */
vTaskDelay(pdMS_TO_TICKS(10));
uint32_t yield_ccount;
TEST_ASSERT( xQueueReceive(queue, &yield_ccount, 100 / portTICK_PERIOD_MS) );
TEST_ASSERT( flag );
/* Give the semaphore — the lower-priority task won't run yet because
* we (higher priority) are still ready. Then block on queue receive,
* 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);
vQueueDelete(queue);
vSemaphoreDelete(sem);
}
}
@@ -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
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "unity.h"
#include "portTestMacro.h"
@@ -17,64 +18,88 @@ Test Priority Scheduling (Single Core)
Purpose:
- Test that the single-core scheduler always schedules the highest priority ready task
Procedure:
- Raise the unityTask priority to (configMAX_PRIORITIES - 1)
- unityTask creates the following lower priority tasks
- unityTask (highest priority) creates a binary semaphore (initially empty)
- unityTask creates two lower-priority tasks that both block on the semaphore:
- task_A (configMAX_PRIORITIES - 2)
- task_B (configMAX_PRIORITIES - 3)
- UnityTask blocks for a short period of time to allow task_A to run
- Clean up and restore unityTask's original priority
- unityTask delays to let both tasks start and block on the semaphore
- 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:
- task_A should run after unityTask blocks
- task_B should never have run
- task_A (higher priority) wins the semaphore race, not task_B
*/
#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 BaseType_t task_B_ran;
static volatile int s_prio_winner;
static SemaphoreHandle_t s_race_sem;
static SemaphoreHandle_t s_done_sem;
static void task_A(void *arg)
{
task_A_ran = pdTRUE;
/* Keeping spinning to prevent the lower priority task_B from running */
while (1) {
;
}
/* Block until the race semaphore is given */
xSemaphoreTake(s_race_sem, portMAX_DELAY);
/* 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)
{
/* The following should never run due to task_B having a lower priority */
task_B_ran = pdTRUE;
while (1) {
;
}
/* Block until the race semaphore is given */
xSemaphoreTake(s_race_sem, portMAX_DELAY);
/* 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]")
{
TaskHandle_t task_A_handle;
TaskHandle_t task_B_handle;
task_A_ran = pdFALSE;
task_B_ran = pdFALSE;
s_prio_winner = 0;
/* 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 */
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 */
vTaskDelay(UNITY_TASK_DELAY_TICKS);
/* Create task_A (higher prio) and task_B (lower prio) */
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 */
TEST_ASSERT_EQUAL(pdTRUE, task_A_ran);
TEST_ASSERT_EQUAL(pdFALSE, task_B_ran);
/* Let both tasks start and block on s_race_sem */
vTaskDelay(pdMS_TO_TICKS(50));
/* 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_B_handle);
vSemaphoreDelete(s_race_sem);
vSemaphoreDelete(s_done_sem);
/* Restore the priority of the unityTask */
vTaskPrioritySet(NULL, configTEST_UNITY_TASK_PRIORITY);
}
@@ -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
*/
@@ -14,74 +14,89 @@
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.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;
while (1) {
(*counter)++;
}
int my_id = (int)(uintptr_t)arg;
xSemaphoreTake(s_race_sem, portMAX_DELAY);
s_winner = my_id;
xSemaphoreGive(s_done_sem);
vTaskSuspend(NULL);
}
TEST_CASE("Get/Set Priorities", "[freertos]")
{
/* Two tasks per processor */
TaskHandle_t tasks[configNUM_CORES][2] = { 0 };
unsigned volatile counters[configNUM_CORES][2] = { 0 };
TaskHandle_t task_a, task_b;
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));
/* create a matrix of counter tasks on each core */
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
for (int task = 0; task < 2; task++) {
xTaskCreatePinnedToCore(counter_task, "count", 2048, (void *)&(counters[cpu][task]), CONFIG_UNITY_FREERTOS_PRIORITY - task, &(tasks[cpu][task]), cpu);
}
}
/* --- Round 1: task_a has higher priority, should win the race --- */
xTaskCreatePinnedToCore(race_task, "a", 2048, (void *)(uintptr_t)TASK_ID_A,
CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_a, 0);
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 */
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
for (int task = 0; task < 2; task++) {
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - task, uxTaskPriorityGet(tasks[cpu][task]));
}
}
/* Verify created priorities */
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 1, uxTaskPriorityGet(task_a));
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY - 2, uxTaskPriorityGet(task_b));
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 */
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
TEST_ASSERT_NOT_EQUAL(0, counters[cpu][0]);
TEST_ASSERT_EQUAL(0, counters[cpu][1]);
}
s_winner = 0;
xSemaphoreGive(s_race_sem);
TEST_ASSERT_EQUAL(pdTRUE, xSemaphoreTake(s_done_sem, pdMS_TO_TICKS(200)));
TEST_ASSERT_EQUAL(TASK_ID_A, s_winner);
/* swap priorities! */
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
vTaskPrioritySet(tasks[cpu][0], CONFIG_UNITY_FREERTOS_PRIORITY - 1);
vTaskPrioritySet(tasks[cpu][1], CONFIG_UNITY_FREERTOS_PRIORITY);
}
vTaskDelete(task_a);
vTaskDelete(task_b);
/* check priorities have swapped... */
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY -1, uxTaskPriorityGet(tasks[cpu][0]));
TEST_ASSERT_EQUAL(CONFIG_UNITY_FREERTOS_PRIORITY, uxTaskPriorityGet(tasks[cpu][1]));
}
/* --- Test vTaskPrioritySet API --- */
xTaskCreatePinnedToCore(race_task, "p", 2048, (void *)(uintptr_t)TASK_ID_A,
CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_a, 0);
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... */
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
unsigned old_counters[2];
old_counters[0] = counters[cpu][0];
old_counters[1] = counters[cpu][1];
vTaskDelay(10);
TEST_ASSERT_EQUAL(old_counters[0], counters[cpu][0]);
TEST_ASSERT_NOT_EQUAL(old_counters[1], counters[cpu][1]);
}
/* --- Round 2: swap priorities — task_b now higher, should win --- */
xTaskCreatePinnedToCore(race_task, "a2", 2048, (void *)(uintptr_t)TASK_ID_A,
CONFIG_UNITY_FREERTOS_PRIORITY - 2, &task_a, 0);
xTaskCreatePinnedToCore(race_task, "b2", 2048, (void *)(uintptr_t)TASK_ID_B,
CONFIG_UNITY_FREERTOS_PRIORITY - 1, &task_b, 0);
/* clean up */
for (int cpu = 0; cpu < configNUM_CORES; cpu++) {
for (int task = 0; task < 2; task++) {
vTaskDelete(tasks[cpu][task]);
}
}
/* Let both tasks block on s_race_sem */
vTaskDelay(pdMS_TO_TICKS(50));
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_FREERTOS_SMP=y
CONFIG_FREERTOS_SMP=n