| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- /*
- * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
- *
- * SPDX-License-Identifier: Apache-2.0
- */
- #include "sdkconfig.h"
- #include <stdbool.h>
- #include "freertos/FreeRTOS.h"
- #include "freertos/task.h"
- #include "freertos/semphr.h"
- #include "driver/gptimer.h"
- #include "esp_rom_sys.h"
- #include "unity.h"
- #include "test_utils.h"
- /*
- Scheduler suspension behavior differs significantly in SMP FreeRTOS, thus none of these tests apply to SMP FreeRTOS
- */
- #if !CONFIG_FREERTOS_SMP
- /*
- GP timer is used to trigger an interrupt. Test cases will register an interrupt callback called from the timer's
- interrupt callback. The functions below simply the interrupt registration/trigger/deregistration process.
- */
- static gptimer_handle_t gptimer = NULL;
- static bool (*registered_intr_callback)(void *) = NULL;
- static bool on_timer_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
- {
- bool yield;
- if (registered_intr_callback) {
- yield = registered_intr_callback(user_ctx);
- } else {
- yield = false;
- }
- return yield;
- }
- static void register_intr_cb(bool (*callback)(void *), void *callback_arg)
- {
- gptimer_handle_t gptimer_temp;
- // Initialize a GP timer used to trigger an interrupt
- gptimer_config_t timer_config = {
- .clk_src = GPTIMER_CLK_SRC_DEFAULT,
- .direction = GPTIMER_COUNT_UP,
- .resolution_hz = 1000000, // 1MHz, 1 tick=1us
- };
- TEST_ESP_OK(gptimer_new_timer(&timer_config, &gptimer_temp));
- // Configure an alarm (of 1ms) and callback for the timer
- gptimer_alarm_config_t alarm_config = {
- .reload_count = 0,
- .alarm_count = 1000, // alarm period 1ms
- .flags.auto_reload_on_alarm = true,
- };
- gptimer_event_callbacks_t cbs = {
- .on_alarm = on_timer_alarm_cb,
- };
- TEST_ESP_OK(gptimer_register_event_callbacks(gptimer_temp, &cbs, callback_arg));
- TEST_ESP_OK(gptimer_enable(gptimer_temp));
- TEST_ESP_OK(gptimer_set_alarm_action(gptimer_temp, &alarm_config));
- gptimer = gptimer_temp;
- registered_intr_callback = callback;
- }
- static void trigger_intr_cb(void)
- {
- // Interrupt should be triggered in 1ms
- TEST_ESP_OK(gptimer_start(gptimer));
- }
- static void deregister_intr_cb(void)
- {
- gptimer_handle_t gptimer_temp = gptimer;
- gptimer = NULL;
- registered_intr_callback = NULL;
- TEST_ESP_OK(gptimer_stop(gptimer_temp));
- TEST_ESP_OK(gptimer_disable(gptimer_temp));
- TEST_ESP_OK(gptimer_del_timer(gptimer_temp));
- }
- /* ---------------------------------------------------------------------------------------------------------------------
- Test vTaskSuspendAll() and xTaskResumeAll() basic
- Purpose:
- - Test that vTaskSuspendAll() will suspends the scheduler for the calling core
- - Test that xTaskResumeAll() will resumes scheduling for the calling core
- Procedure:
- - Call vTaskSuspendAll() to suspend the scheduler
- - Call xTaskResumeAll() to resume the scheduler
- Expected:
- - xTaskGetSchedulerState() should return the correct state
- --------------------------------------------------------------------------------------------------------------------- */
- TEST_CASE("Test vTaskSuspendAll and xTaskResumeAll basic", "[freertos]")
- {
- // Check scheduler is running on the current core
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- vTaskSuspendAll();
- TEST_ASSERT_EQUAL(taskSCHEDULER_SUSPENDED, xTaskGetSchedulerState());
- xTaskResumeAll();
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- }
- /* ---------------------------------------------------------------------------------------------------------------------
- Test vTaskSuspendAll() and xTaskResumeAll() multicore
- Only runs on !CONFIG_FREERTOS_UNICORE
- Purpose:
- - Test that vTaskSuspendAll() will only suspends scheduling only for the calling core
- - Test that xTaskResumeAll() will only resume scheduling for the calling core
- Procedure:
- Each core gets tested in the role of core A
- - Create a taskA pinned to one core (e.g., core A) that will disable the scheduler
- - Created a "taskB" to another core (e.g., core B) that will not disable the scheduler
- - taskA calls vTaskSuspendAll() to suspend the scheduler on core A
- - taskA calls xTaskResumeAll() to resume the scheduler on core A
- Expected:
- - vTaskSuspendAll() should only disable the scheduler for the suspended core A
- - xTaskResumeAll() should resume the scheduler for the suspended core A
- - Scheduler on core B should remain enabled
- --------------------------------------------------------------------------------------------------------------------- */
- #if !CONFIG_FREERTOS_UNICORE
- #define TEST_BASIC_BUSY_DELAY_US 10000
- static volatile int taskA_sync;
- static volatile int taskB_sync;
- static void test_multicore_taskA(void *arg)
- {
- // Wait to be started
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
- // Check scheduler on core A is enabled
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- taskA_sync++;
- while (taskB_sync != 1) {
- ; // Wait for task B to complete its check
- }
- // Suspend the scheduler on core A
- vTaskSuspendAll();
- // Check scheduler is suspended on core A
- TEST_ASSERT_EQUAL(taskSCHEDULER_SUSPENDED, xTaskGetSchedulerState());
- taskA_sync++;
- while (taskB_sync != 2) {
- ; // Wait for task B to complete its check
- }
- // Busy spin for a while to simulate work done while scheduler is suspended
- esp_rom_delay_us(TEST_BASIC_BUSY_DELAY_US);
- // Check scheduler is still suspended on core A
- TEST_ASSERT_EQUAL(taskSCHEDULER_SUSPENDED, xTaskGetSchedulerState());
- taskA_sync++;
- while (taskB_sync != 3) {
- ; // Wait for task B to complete its check
- }
- // Resume the scheduler on core A
- xTaskResumeAll();
- // Check that scheduler has resumed resumed on core A
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- taskA_sync++;
- while (taskB_sync != 4) {
- ; // Wait for task B to complete its check
- }
- // Indicate done and wait to be deleted
- SemaphoreHandle_t done_sem = (SemaphoreHandle_t)arg;
- xSemaphoreGive(done_sem);
- vTaskSuspend(NULL);
- }
- static void test_multicore_taskB(void *arg)
- {
- // Wait to be started
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
- for (int i = 1; i <= 4; i++) {
- // Wait for suspended trigger from task A
- while (taskA_sync != i) {
- ;
- }
- // Check that scheduler is still running on core B
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- taskB_sync++;
- }
- // Indicate done and wait to be deleted
- SemaphoreHandle_t done_sem = (SemaphoreHandle_t)arg;
- xSemaphoreGive(done_sem);
- vTaskSuspend(NULL);
- }
- TEST_CASE("Test vTaskSuspendAll() and xTaskResumeAll() multicore", "[freertos]")
- {
- SemaphoreHandle_t done_sem = xSemaphoreCreateCounting(portNUM_PROCESSORS, 0);
- TEST_ASSERT_NOT_EQUAL(NULL, done_sem);
- for (int i = 0; i < portNUM_PROCESSORS; i++) {
- // Create tasks on core A and core B
- TaskHandle_t taskA_hdl;
- TaskHandle_t taskB_hdl;
- TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(test_multicore_taskA, "taskA", 2048, (void *)done_sem, UNITY_FREERTOS_PRIORITY - 1, &taskA_hdl, i));
- TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(test_multicore_taskB, "taskB", 2048, (void *)done_sem, UNITY_FREERTOS_PRIORITY - 1, &taskB_hdl, !i));
- // Start the tasks and wait for their completion
- taskA_sync = 0;
- taskB_sync = 0;
- xTaskNotifyGive(taskA_hdl);
- xTaskNotifyGive(taskB_hdl);
- for (int j = 0; j < 2; j++) {
- xSemaphoreTake(done_sem, portMAX_DELAY);
- }
- // Cleanup the tasks
- vTaskDelete(taskA_hdl);
- vTaskDelete(taskB_hdl);
- }
- vSemaphoreDelete(done_sem);
- }
- #endif // !CONFIG_FREERTOS_UNICORE
- /* ---------------------------------------------------------------------------------------------------------------------
- Test vTaskSuspendAll allows scheduling on other cores
- Only runs on !CONFIG_FREERTOS_UNICORE
- Purpose:
- - Test that disabling a scheduler on one core (e.g., core B) does not disable scheduling on the other core (e.g., core A)
- - While the scheduler on core B is disabled, test that...
- - A task on Core A can be unblocked by another task also on core A
- - A task on Core A can be unblocked by an interrupt on core A
- Procedure:
- Each core gets tested in the role of core A
- - Create task B1 pinned to core B that will suspend scheduling on core B
- - Create task A2 pinned to core A that will test unblocking on core A
- - Create task A1 pinned to core A that will unblock task A2
- - Register an interrupt on core A that will unblock task A2
- - Have A2 block
- - Have B1 disable scheduling on core B. A1 checks that scheduling is still enabled on core A
- - Have A1 unblock A2
- - Have the core A ISR unblock A2
- - Cleanup the tasks
- Expected:
- When B1 disables scheduling on core B...
- - Scheduling on core A should still be enabled
- - A2 should be unblocked by A1 and run without issue
- - A2 should be unblocked by core A ISR and run without issue
- --------------------------------------------------------------------------------------------------------------------- */
- #if !CONFIG_FREERTOS_UNICORE
- static volatile int test_unblk_sync;
- static SemaphoreHandle_t test_unblk_done_sem;
- static bool test_unblk_coreA_isr(void *arg)
- {
- TaskHandle_t a2_task_hdl = (TaskHandle_t)arg;
- BaseType_t task_woken = pdFALSE;
- // Unblock task b2
- vTaskNotifyGiveFromISR(a2_task_hdl, &task_woken);
- return (task_woken == pdTRUE);
- }
- static void test_unblk_a2_task(void *arg)
- {
- volatile int *a2_task_run_count = (volatile int *)arg;
- // Wait to be unblocked by A1
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
- (*a2_task_run_count)++;
- // Wait to be unblocked by Core A ISR
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
- (*a2_task_run_count)++;
- // Wait to be deleted
- vTaskSuspend(NULL);
- }
- static void test_unblk_a1_task(void *arg)
- {
- volatile int a2_task_run_count = 0;
- // Create task A2 to block on this core (i.e., core A)
- TaskHandle_t a2_task_hdl;
- TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(test_unblk_a2_task, "A2", 8192, (void *)&a2_task_run_count, UNITY_FREERTOS_PRIORITY + 2, &a2_task_hdl, xPortGetCoreID()));
- // Install an interrupt on core A
- register_intr_cb(test_unblk_coreA_isr, (void *)a2_task_hdl);
- // Wait to be started by the main task
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
- // Start B1
- xTaskNotifyGive((TaskHandle_t)arg);
- while (test_unblk_sync != 1) {
- ; // Wait for confirmation from B1 that scheduler has been suspended on Core B
- }
- // Verify that the scheduler is still enabled on core A
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- // Unblock A2, it should preempt immediately due to its higher priority
- xTaskNotifyGive(a2_task_hdl);
- // Verify that task A2 has run
- TEST_ASSERT_EQUAL(1, a2_task_run_count);
- // Trigger an ISR on this core A to unblock task A2. A2 should preempt immediately due to its higher priority
- trigger_intr_cb();
- esp_rom_delay_us(2000); // Short busy delay to ensure interrupt has triggered
- // Verify that task A2 has run
- TEST_ASSERT_EQUAL(2, a2_task_run_count);
- // Trigger B1 to resume scheduling on core B
- test_unblk_sync = 2;
- while (test_unblk_sync != 3) {
- ; // Wait for confirmation from B1 that scheduler has been resumed
- }
- // Verify that the scheduler is still enabled on core A
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- // Cleanup A2 and interrupt
- deregister_intr_cb();
- vTaskDelete(a2_task_hdl);
- // Indicate done and wait to be deleted
- xSemaphoreGive(test_unblk_done_sem);
- vTaskSuspend(NULL);
- }
- static void test_unblk_b1_task(void *arg)
- {
- // Wait to be started by A1
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
- // Check scheduler is running on core B
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- // Suspend the scheduler on core B
- vTaskSuspendAll();
- TEST_ASSERT_EQUAL(taskSCHEDULER_SUSPENDED, xTaskGetSchedulerState());
- // Indicate to A1 that core B scheduler has been suspended
- test_unblk_sync = 1;
- while (test_unblk_sync != 2) {
- ; // Wait for trigger from A1
- }
- // Resume the scheduler on core B
- xTaskResumeAll();
- TEST_ASSERT_EQUAL(taskSCHEDULER_RUNNING, xTaskGetSchedulerState());
- // Indicate to A1 that core B scheduler has been resumed
- test_unblk_sync = 3;
- // Indicate done and wait to be deleted
- xSemaphoreGive(test_unblk_done_sem);
- vTaskSuspend(NULL);
- }
- TEST_CASE("Test vTaskSuspendAll allows scheduling on other cores", "[freertos]")
- {
- test_unblk_done_sem = xSemaphoreCreateCounting(portNUM_PROCESSORS, 0);
- TEST_ASSERT_NOT_EQUAL(NULL, test_unblk_done_sem);
- for (int i = 0; i < portNUM_PROCESSORS; i++) {
- test_unblk_sync = 0;
- // Create a tasks
- TaskHandle_t a1_task_hdl;
- TaskHandle_t b1_task_hdl;
- TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(test_unblk_b1_task, "B1", 8192, NULL, UNITY_FREERTOS_PRIORITY + 1, &b1_task_hdl, !i));
- TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(test_unblk_a1_task, "A1", 8192, (void *)b1_task_hdl, UNITY_FREERTOS_PRIORITY + 1, &a1_task_hdl, i));
- // Start A1 to and wait for both tasks to complete
- xTaskNotifyGive(a1_task_hdl);
- for (int j = 0; j < 2; j++) {
- xSemaphoreTake(test_unblk_done_sem, portMAX_DELAY);
- }
- // Cleanup tasks
- vTaskDelete(a1_task_hdl);
- vTaskDelete(b1_task_hdl);
- }
- vSemaphoreDelete(test_unblk_done_sem);
- }
- #endif // !CONFIG_FREERTOS_UNICORE
- /* ---------------------------------------------------------------------------------------------------------------------
- Test xTaskResumeAll() resumes pended tasks on the current core
- Purpose:
- - When the scheduler is suspended on a particular core, test that tasks unblocked by an ISR on that core will place
- those tasks on the core's pending ready list (regardless of the task's affinity).
- - When the scheduler is resumed on a particular core, test that the tasks on core's pending ready list will be
- scheduled.
- Procedure:
- Test for each core
- - Create some blocking tasks on the same core
- - Register an interrupt on the same core to unblock those tasks
- - Suspend the scheduler on the core
- - Trigger the interrupt to unblock those tasks
- - Resume the scheduler
- - Cleanup
- Expected:
- - When the ISR unblocks the blocked tasks, the task's state should be ready
- - When the scheduler is resumed, the tasks should be scheduled and run without issue.
- --------------------------------------------------------------------------------------------------------------------- */
- #define TEST_PENDED_NUM_BLOCKED_TASKS 4
- static bool test_pended_isr(void *arg)
- {
- TaskHandle_t *blkd_tsks = (TaskHandle_t *)arg;
- BaseType_t task_woken = pdFALSE;
- // Unblock the blocked tasks
- for (int i = 0; i < TEST_PENDED_NUM_BLOCKED_TASKS; i++) {
- vTaskNotifyGiveFromISR(blkd_tsks[i], &task_woken);
- }
- return (task_woken == pdTRUE);
- }
- static void test_pended_blkd_task(void *arg)
- {
- volatile bool *has_run = (bool *)arg;
- // Wait to be unblocked
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
- // Indicate the task been unblocked and has run
- *has_run = true;
- // Wait to be deleted
- vTaskSuspend(NULL);
- }
- static void test_pended_running_task(void *arg)
- {
- TaskHandle_t main_task_hdl = (TaskHandle_t)arg;
- TaskHandle_t blkd_tsks[TEST_PENDED_NUM_BLOCKED_TASKS];
- volatile bool has_run[TEST_PENDED_NUM_BLOCKED_TASKS];
- // Created blocked tasks pinned to each core
- for (int i = 0; i < TEST_PENDED_NUM_BLOCKED_TASKS; i++) {
- has_run[i] = false;
- TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(test_pended_blkd_task, "blkd", 4096, (void *)&has_run[i], UNITY_FREERTOS_PRIORITY + 2, &blkd_tsks[i], i % portNUM_PROCESSORS));
- }
- vTaskDelay(10);
- // Install an interrupt on the current core core
- register_intr_cb(test_pended_isr, (void *)blkd_tsks);
- // Checked that all tasks are blocked and have no run yet
- for (int i = 0; i < TEST_PENDED_NUM_BLOCKED_TASKS; i++) {
- TEST_ASSERT_EQUAL(eBlocked, eTaskGetState(blkd_tsks[i])); // Should be eSuspended due to portMAX_DELAY
- TEST_ASSERT_EQUAL(false, has_run[i]);
- }
- // Suspend the scheduler on the current core
- vTaskSuspendAll();
- // Trigger the interrupt to unblocked the blocked tasks
- trigger_intr_cb();
- esp_rom_delay_us(2000); // Short busy delay to ensure interrupt has triggered
- // Check that all tasks are unblocked (but should not have run since the scheduler is suspend)
- for (int i = 0; i < TEST_PENDED_NUM_BLOCKED_TASKS; i++) {
- // Note: We use eBlocked instead of eReady due to a bug in eTaskGetState(). See (IDF-5543)
- TEST_ASSERT_EQUAL(eBlocked, eTaskGetState(blkd_tsks[i]));
- TEST_ASSERT_EQUAL(false, has_run[i]);
- }
- // Resume the scheduler on the current core to schedule the unblocked tasks
- xTaskResumeAll();
- esp_rom_delay_us(10000); // Busy delay to ensure each task has enough time to run
- // Check that all tasks have run
- for (int i = 0; i < TEST_PENDED_NUM_BLOCKED_TASKS; i++) {
- TEST_ASSERT_EQUAL(true, has_run[i]);
- }
- // Clean up the interrupt and tasks
- deregister_intr_cb();
- for (int i = 0; i < TEST_PENDED_NUM_BLOCKED_TASKS; i++) {
- vTaskDelete(blkd_tsks[i]);
- }
- // Notify completion and wait for deletion
- xTaskNotifyGive(main_task_hdl);
- vTaskSuspend(NULL);
- }
- TEST_CASE("Test xTaskResumeAll resumes pended tasks", "[freertos]")
- {
- // Run the test on each core
- for (int i = 0; i < portNUM_PROCESSORS; i++) {
- TaskHandle_t susp_tsk_hdl;
- TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(test_pended_running_task, "susp", 2048, (void *)xTaskGetCurrentTaskHandle(), UNITY_FREERTOS_PRIORITY + 1, &susp_tsk_hdl, i));
- // Wait for to be notified to test completion
- ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
- vTaskDelete(susp_tsk_hdl);
- }
- }
- #endif // !CONFIG_FREERTOS_SMP
|