From d69361779ecb24e1d5cb8c33b8d77d87f182ca34 Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Mon, 10 Oct 2022 16:36:18 +0800 Subject: [PATCH] freertos: Refactor FPU unit tests This commit refactors the existing FPU unit tests as follows - Rename them from coproc to FPU - Reorganize test placement - Make existing tests work on both IDF and SMP FreeRTOS - Update test documentation - Remove old "test_float_in_isr.c" --- .../freertos/test/port/test_float_in_isr.c | 70 ------ .../freertos/test/port/test_fpu_in_isr.c | 164 ++++++++++++++ .../freertos/test/port/test_fpu_in_task.c | 212 +++++++++++------- 3 files changed, 291 insertions(+), 155 deletions(-) delete mode 100644 components/freertos/test/port/test_float_in_isr.c create mode 100644 components/freertos/test/port/test_fpu_in_isr.c diff --git a/components/freertos/test/port/test_float_in_isr.c b/components/freertos/test/port/test_float_in_isr.c deleted file mode 100644 index 3269ca426a6..00000000000 --- a/components/freertos/test/port/test_float_in_isr.c +++ /dev/null @@ -1,70 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "freertos/queue.h" -#include "esp_intr_alloc.h" -#include "unity.h" -#include "test_utils.h" -#include "math.h" - -#define SW_ISR_LEVEL_1 7 -#ifdef CONFIG_FREERTOS_FPU_IN_ISR - -struct fp_test_context { - SemaphoreHandle_t sync; - float expected; -}; - -static void software_isr(void *arg) { - (void)arg; - BaseType_t yield; - xt_set_intclear(1 << SW_ISR_LEVEL_1); - - struct fp_test_context *ctx = (struct fp_test_context *)arg; - - for(int i = 0; i < 16; i++) { - ctx->expected = ctx->expected * 2.0f * cosf(0.0f); - } - - xSemaphoreGiveFromISR(ctx->sync, &yield); - if(yield) { - portYIELD_FROM_ISR(); - } -} - - -TEST_CASE("Floating point usage in ISR test", "[freertos]" "[fp]") -{ - struct fp_test_context ctx; - float fp_math_operation_result = 0.0f; - - intr_handle_t handle; - esp_err_t err = esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, &software_isr, &ctx, &handle); - TEST_ASSERT_EQUAL_HEX32(ESP_OK, err); - - ctx.sync = xSemaphoreCreateBinary(); - TEST_ASSERT(ctx.sync != NULL); - ctx.expected = 1.0f; - - fp_math_operation_result = cosf(0.0f); - - xt_set_intset(1 << SW_ISR_LEVEL_1); - xSemaphoreTake(ctx.sync, portMAX_DELAY); - - esp_intr_free(handle); - vSemaphoreDelete(ctx.sync); - - printf("FP math isr result: %f \n", ctx.expected); - TEST_ASSERT_FLOAT_WITHIN(0.1f, ctx.expected, fp_math_operation_result * 65536.0f); -} - -#endif diff --git a/components/freertos/test/port/test_fpu_in_isr.c b/components/freertos/test/port/test_fpu_in_isr.c new file mode 100644 index 00000000000..cc5adc571eb --- /dev/null +++ b/components/freertos/test/port/test_fpu_in_isr.c @@ -0,0 +1,164 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include +#include "soc/soc_caps.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "unity.h" +#include "test_utils.h" + +#if SOC_CPU_HAS_FPU && CONFIG_FREERTOS_FPU_IN_ISR + +// We can use xtensa API here as currently, non of the RISC-V targets have an FPU +#include "xtensa/xtensa_api.h" +#include "esp_intr_alloc.h" + +#define SW_ISR_LEVEL_1 7 + +static void fpu_isr(void *arg) +{ + // Clear the interrupt + xt_set_intclear(1 << SW_ISR_LEVEL_1); + /* + Use the FPU + - We test using a calculation that will cause a change in mantissa and exponent for extra thoroughness + - cosf(0.0f) should return 1.0f, thus we are simply doubling test_float every iteration. + - Therefore, we should end up with (0.01) * (2^8) = 2.56 at the end of the loop + */ + volatile float test_float = 0.01f; + for (int i = 0; i < 8; i++) { + test_float = test_float * 2.0f * cosf(0.0f); + } + // We allow a 0.1% delta on the final result in case of any loss of precision from floating point calculations + TEST_ASSERT_FLOAT_WITHIN(0.00256f, 2.56f, test_float); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ + +/* +Test FPU usage from a level 1 ISR + +Purpose: + - Test that the FPU can be used from a level 1 ISR + - Test that the ISR using the FPU does not corrupt the interrupted task's FPU context +Procedure: + - Allocate a level 1 ISR + - Task uses the FPU then triggers the ISR + - ISR uses the FPU as well (forcing the task's FPU context to be saved) + - Task continues using the FPU (forcing its FPU context to be restored) +Expected: + - ISR should use the FPU without issue + - The interrupted task can continue using the FPU without issue +*/ + +TEST_CASE("FPU: Usage in level 1 ISR", "[freertos]") +{ + intr_handle_t isr_handle; + TEST_ASSERT_EQUAL(ESP_OK, esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, &fpu_isr, NULL, &isr_handle)); + /* + Use the FPU (calculate a different value than in the ISR) + - We test using a calculation that will cause a change in mantissa and exponent for extra thoroughness + - cosf(0.0f) should return 1.0f, thus we are simply dividing test_float every iteration. + */ + // We should end up with (2.56) / (2^4) = 0.16 at the end of the first loop + volatile float test_float = 2.56f; + for (int i = 0; i < 4; i++) { + test_float = test_float / (2.0f * cosf(0.0f)); + } + // We allow a 0.1% delta on the final result in case of any loss of precision from floating point calculations + TEST_ASSERT_FLOAT_WITHIN(0.00016f, 0.16f, test_float); + + // Trigger the ISR + xt_set_intset(1 << SW_ISR_LEVEL_1); + + // Continue using the FPU from a task context after the interrupt returns + // We should end up with (0.16) / (2^4) = 0.01 at the end of the first loop + for (int i = 0; i < 4; i++) { + test_float = test_float / (2.0f * cosf(0.0f)); + } + // We allow a 0.1% delta on the final result in case of any loss of precision from floating point calculations + TEST_ASSERT_FLOAT_WITHIN(0.00001f, 0.01f, test_float); + + // Free the ISR + esp_intr_free(isr_handle); +} + +/* ------------------------------------------------------------------------------------------------------------------ */ + +/* +Test FPU usage in ISR does not affect an unpinned tasks + +Purpose: + - Test that the ISR using the FPU will not affect the interrupted task's affinity +Procedure: + - Create an unpinned task + - Unpinned task disables scheduling/preemption to ensure that it does not switch cores + - Unpinned task allocates an ISR then triggers the ISR + - The ISR interrupts the unpinned task then uses the FPU + - Task reenables scheduling/preemption and cleans up +Expected: + - The ISR using the FPU will not affect the unpinned task's affinity +*/ + +// Known issue in IDF FreeRTOS (IDF-6068), already fixed in SMP FreeRTOS +#if CONFIG_FREERTOS_SMP + +static void unpinned_task(void *arg) +{ + // Disable scheduling/preemption to make sure the current task doesn't switch cores +#if CONFIG_FREERTOS_SMP + vTaskPreemptionDisable(NULL); +#else + vTaskSuspendAll(); +#endif + // Check that the task is unpinned +#if CONFIG_FREERTOS_SMP + TEST_ASSERT_EQUAL(tskNO_AFFINITY, vTaskCoreAffinityGet(NULL)); +#else + TEST_ASSERT_EQUAL(tskNO_AFFINITY, xTaskGetAffinity(NULL)); +#endif + + // Allocate an ISR to use the FPU + intr_handle_t isr_handle; + TEST_ASSERT_EQUAL(ESP_OK, esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, &fpu_isr, NULL, &isr_handle)); + // Trigger the ISR + xt_set_intset(1 << SW_ISR_LEVEL_1); + // Free the ISR + esp_intr_free(isr_handle); + + // Task should remain unpinned after the ISR uses the FPU +#if CONFIG_FREERTOS_SMP + TEST_ASSERT_EQUAL(tskNO_AFFINITY, vTaskCoreAffinityGet(NULL)); +#else + TEST_ASSERT_EQUAL(tskNO_AFFINITY, xTaskGetAffinity(NULL)); +#endif + // Reenable scheduling/preemption +#if CONFIG_FREERTOS_SMP + vTaskPreemptionEnable(NULL); +#else + xTaskResumeAll(); +#endif + + // Indicate done and self delete + xTaskNotifyGive((TaskHandle_t)arg); + vTaskDelete(NULL); +} + +TEST_CASE("FPU: Level 1 ISR does not affect unpinned task", "[freertos]") +{ + TaskHandle_t unity_task_handle = xTaskGetCurrentTaskHandle(); + xTaskCreate(unpinned_task, "unpin", 2048, (void *)unity_task_handle, UNITY_FREERTOS_PRIORITY + 1, NULL); + // Wait for task to complete + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + vTaskDelay(10); // Short delay to allow task memory to be freed +} + +#endif // CONFIG_FREERTOS_SMP + +#endif // SOC_CPU_HAS_FPU && CONFIG_FREERTOS_FPU_IN_ISR diff --git a/components/freertos/test/port/test_fpu_in_task.c b/components/freertos/test/port/test_fpu_in_task.c index f1b163d76e9..6c5c0657ee2 100644 --- a/components/freertos/test/port/test_fpu_in_task.c +++ b/components/freertos/test/port/test_fpu_in_task.c @@ -5,127 +5,169 @@ */ #include "sdkconfig.h" +#include #include "soc/soc_caps.h" - -#if CONFIG_FREERTOS_SMP && SOC_CPU_HAS_FPU - #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "xtensa/xtensa_api.h" -#include "esp_intr_alloc.h" +#include "freertos/semphr.h" #include "unity.h" #include "test_utils.h" -/* -Note: We need to declare the float here to prevent compiler optimizing float -operations into non-float instructions. -*/ -static volatile float test_float; +#if SOC_CPU_HAS_FPU + +/* ------------------------------------------------------------------------------------------------------------------ */ /* -Test coprocessor (CP) usage in task +Test FPU usage from a task context Purpose: - - Test that CP can be used in a task (e.g., the FPU) - - Test that unpinned tasks are pinned when the CP is used - - Test that CP is properly saved in restored - - Test that CP context is properly cleaned up when task is deleted + - Test that the FPU can be used from a task context + - Test that FPU context is properly saved and restored +Procedure: + - Create TEST_PINNED_NUM_TASKS tasks pinned to each core + - Start each task + - Each task updates a float variable and then blocks (to allow other tasks to run thus forcing the an FPU context + save and restore). +Expected: + - Correct float value calculated by each task */ -#define COPROC_TASK_TEST_ITER 10 +#define TEST_PINNED_NUM_TASKS 3 -static void unpinned_task(void *arg) +static void pinned_task(void *arg) { - // Test that task gets pinned to the current core when it uses the coprocessor - vTaskPreemptionDisable(NULL); // Disable preemption to make current core ID doesn't change - BaseType_t xCoreID = xPortGetCoreID(); - TEST_ASSERT_EQUAL(tskNO_AFFINITY, vTaskCoreAffinityGet(NULL)); - test_float = 1.1f; - test_float *= 2.0f; - TEST_ASSERT_EQUAL(2.2f, test_float); - TEST_ASSERT_EQUAL(1 << xCoreID, vTaskCoreAffinityGet(NULL)); - vTaskPreemptionEnable(NULL); - - // Delay to trigger a solicited context switch - vTaskDelay(1); - // Test that CP context was saved and restored properly - test_float *= 2.0f; - TEST_ASSERT_EQUAL(4.4f, test_float); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + /* + Use the FPU + - We test using a calculation that will cause a change in mantissa and exponent for extra thoroughness + - cosf(0.0f) should return 1.0f, thus we are simply doubling test_float every iteration. + - Therefore, we should end up with (0.01) * (2^8) = 2.56 at the end of the loop + */ + volatile float test_float = 0.01f; + for (int i = 0; i < 8; i++) { + test_float = test_float * 2.0f * cosf(0.0f); + vTaskDelay(1); // Block to cause a context switch, forcing the FPU context to be saved + } + // We allow a 0.1% delta on the final result in case of any loss of precision from floating point calculations + TEST_ASSERT_FLOAT_WITHIN(0.00256f, 2.56f, test_float); - // Wait to be deleted - xTaskNotifyGive((TaskHandle_t)arg); + // Indicate done wand wait to be deleted + xSemaphoreGive((SemaphoreHandle_t)arg); vTaskSuspend(NULL); } -TEST_CASE("Test coproc usage in task", "[freertos]") +TEST_CASE("FPU: Usage in task", "[freertos]") { - TaskHandle_t unity_task_handle = xTaskGetCurrentTaskHandle(); - for (int i = 0; i < COPROC_TASK_TEST_ITER; i++) { - TaskHandle_t unpinned_task_handle; - xTaskCreate(unpinned_task, "unpin", 2048, (void *)unity_task_handle, UNITY_FREERTOS_PRIORITY + 1, &unpinned_task_handle); - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - vTaskDelete(unpinned_task_handle); - vTaskDelay(10); // Short delay to allow task memory to be freed + SemaphoreHandle_t done_sem = xSemaphoreCreateCounting(configNUM_CORES * TEST_PINNED_NUM_TASKS, 0); + TEST_ASSERT_NOT_EQUAL(NULL, done_sem); + + TaskHandle_t task_handles[configNUM_CORES][TEST_PINNED_NUM_TASKS]; + + // Create test tasks for each core + for (int i = 0; i < configNUM_CORES; i++) { + for (int j = 0; j < TEST_PINNED_NUM_TASKS; j++) { + TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(pinned_task, "task", 4096, (void *)done_sem, UNITY_FREERTOS_PRIORITY + 1, &task_handles[i][j], i)); + } + } + + // Start the created tasks simultaneously + for (int i = 0; i < configNUM_CORES; i++) { + for (int j = 0; j < TEST_PINNED_NUM_TASKS; j++) { + xTaskNotifyGive(task_handles[i][j]); + } } + + // Wait for the tasks to complete + for (int i = 0; i < configNUM_CORES * TEST_PINNED_NUM_TASKS; i++) { + xSemaphoreTake(done_sem, portMAX_DELAY); + } + + // Delete the tasks + for (int i = 0; i < configNUM_CORES; i++) { + for (int j = 0; j < TEST_PINNED_NUM_TASKS; j++) { + vTaskDelete(task_handles[i][j]); + } + } + + vTaskDelay(10); // Short delay to allow idle task to be free task memory and FPU contexts + vSemaphoreDelete(done_sem); } -#if CONFIG_FREERTOS_FPU_IN_ISR +/* ------------------------------------------------------------------------------------------------------------------ */ /* -Test coprocessor (CP) usage in ISR +Test FPU usage will pin an unpinned task Purpose: - - Test that CP can be used in an ISR - - Test that an interrupted unpinned task will not be pinned by the ISR using the CP + - Test that unpinned tasks are automatically pinned to the current core on the task's first use of the FPU +Procedure: + - Create an unpinned task + - Task disables scheduling/preemption to ensure that it does not switch cores + - Task uses the FPU + - Task checks its core affinity after FPU usage +Expected: + - Task remains unpinned until its first usage of the FPU + - The task becomes pinned to the current core after first use of the FPU */ -#define SW_ISR_LEVEL_1 7 - -static void software_isr(void *arg) -{ - (void) arg; - xt_set_intclear(1 << SW_ISR_LEVEL_1); - test_float *= 2; -} +#if configNUM_CORES > 1 -static void unpinned_task_isr(void *arg) +static void unpinned_task(void *arg) { - vTaskPreemptionDisable(NULL); // Disable preemption to make current core ID doesn't change - TEST_ASSERT_EQUAL(tskNO_AFFINITY, vTaskCoreAffinityGet(NULL)); - // Allocate ISR on the current core - intr_handle_t handle; - esp_err_t err = esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1, &software_isr, NULL, &handle); - TEST_ASSERT_EQUAL_HEX32(ESP_OK, err); - // Trigger the ISR - xt_set_intset(1 << SW_ISR_LEVEL_1); - // Test that task affinity hasn't changed + // Disable scheduling/preemption to make sure current core ID doesn't change +#if CONFIG_FREERTOS_SMP + vTaskPreemptionDisable(NULL); +#else + vTaskSuspendAll(); +#endif + BaseType_t cur_core_num = xPortGetCoreID(); + // Check that the task is unpinned +#if CONFIG_FREERTOS_SMP TEST_ASSERT_EQUAL(tskNO_AFFINITY, vTaskCoreAffinityGet(NULL)); - // Free the ISR - esp_intr_free(handle); - // Wait to be deleted +#else + TEST_ASSERT_EQUAL(tskNO_AFFINITY, xTaskGetAffinity(NULL)); +#endif + + /* + Use the FPU + - We test using a calculation that will cause a change in mantissa and exponent for extra thoroughness + - cosf(0.0f) should return 1.0f, thus we are simply doubling test_float every iteration. + - Therefore, we should end up with (0.01) * (2^8) = 2.56 at the end of the loop + */ + volatile float test_float = 0.01f; + for (int i = 0; i < 8; i++) { + test_float = test_float * 2.0f * cosf(0.0f); + } + // We allow a 0.1% delta on the final result in case of any loss of precision from floating point calculations + TEST_ASSERT_FLOAT_WITHIN(0.00256f, 2.56f, test_float); + +#if CONFIG_FREERTOS_SMP + TEST_ASSERT_EQUAL(1 << cur_core_num, vTaskCoreAffinityGet(NULL)); +#else + TEST_ASSERT_EQUAL(cur_core_num, xTaskGetAffinity(NULL)); +#endif + // Reenable scheduling/preemption +#if CONFIG_FREERTOS_SMP vTaskPreemptionEnable(NULL); +#else + xTaskResumeAll(); +#endif + + // Indicate done and self delete xTaskNotifyGive((TaskHandle_t)arg); - vTaskSuspend(NULL); + vTaskDelete(NULL); } -TEST_CASE("Test coproc usage in ISR", "[freertos]") +TEST_CASE("FPU: Usage in unpinned task", "[freertos]") { TaskHandle_t unity_task_handle = xTaskGetCurrentTaskHandle(); - for (int i = 0; i < COPROC_TASK_TEST_ITER; i++) { - // Initialize the test float - test_float = 1.1f; - // Create an unpinned task to trigger the ISR - TaskHandle_t unpinned_task_handle; - xTaskCreate(unpinned_task_isr, "unpin", 2048, (void *)unity_task_handle, UNITY_FREERTOS_PRIORITY + 1, &unpinned_task_handle); - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - // Check that the test float value - TEST_ASSERT_EQUAL(2.2f, test_float); - // Delete the unpinned task. Short delay to allow task memory to be freed - vTaskDelete(unpinned_task_handle); - vTaskDelay(10); - } + // Create unpinned task + xTaskCreate(unpinned_task, "unpin", 4096, (void *)unity_task_handle, UNITY_FREERTOS_PRIORITY + 1, NULL); + // Wait for task to complete + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + vTaskDelay(10); // Short delay to allow task memory to be freed } -#endif // CONFIG_FREERTOS_FPU_IN_ISR - -#endif // CONFIG_FREERTOS_SMP && SOC_CPU_HAS_FPU +#endif // configNUM_CORES > 1 +#endif // SOC_CPU_HAS_FPU