]> granicus.if.org Git - esp-idf/commitdiff
freertos: Delay context switch from queue/task APIs until exiting critical section
authorAngus Gratton <angus@espressif.com>
Thu, 16 Mar 2017 03:07:50 +0000 (11:07 +0800)
committerAngus Gratton <angus@espressif.com>
Tue, 4 Apr 2017 00:10:08 +0000 (10:10 +1000)
Closes #374: https://github.com/espressif/esp-idf/issues/374

components/freertos/include/freertos/portmacro.h
components/freertos/queue.c
components/freertos/tasks.c
components/freertos/test/test_preemption.c [new file with mode: 0644]
tools/unit-test-app/components/unity/include/unity_config.h
tools/unit-test-app/main/app_main.c

index 7cae4b05b641a89ec3d8c202d1f3c7c47007ecd2..157b9156f7d830e445ff903e102b716da8542651 100644 (file)
@@ -79,6 +79,8 @@ extern "C" {
 #include <xtensa/config/core.h>
 #include <xtensa/config/system.h>      /* required for XSHAL_CLIB */
 #include <xtensa/xtruntime.h>
+#include "esp_crosscore_int.h"
+
 
 //#include "xtensa_context.h"
 
@@ -261,6 +263,18 @@ void vPortYield( void );
 void _frxt_setup_switch( void );
 #define portYIELD()                                    vPortYield()
 #define portYIELD_FROM_ISR()           _frxt_setup_switch()
+
+static inline uint32_t xPortGetCoreID();
+
+/* Yielding within an API call (when interrupts are off), means the yield should be delayed
+   until interrupts are re-enabled.
+
+   To do this, we use the "cross-core" interrupt as a trigger to yield on this core when interrupts are re-enabled.This
+   is the same interrupt & code path which is used to trigger a yield between CPUs, although in this case the yield is
+   happening on the same CPU.
+*/
+#define portYIELD_WITHIN_API() esp_crosscore_int_send_yield(xPortGetCoreID())
+
 /*-----------------------------------------------------------*/
 
 /* Task function macros as described on the FreeRTOS.org WEB site. */
index 7c491f6952ff69356f518570479a4e9e8855656a..66a7ed04973a4311f51152bfd8b159f1da73391d 100644 (file)
@@ -123,15 +123,9 @@ zero. */
 #if( configUSE_PREEMPTION == 0 )
        /* If the cooperative scheduler is being used then a yield should not be
        performed just because a higher priority task has been woken. */
-       #define queueYIELD_IF_USING_PREEMPTION_MUX()
        #define queueYIELD_IF_USING_PREEMPTION()
 #else
        #define queueYIELD_IF_USING_PREEMPTION() portYIELD_WITHIN_API()
-       #define queueYIELD_IF_USING_PREEMPTION_MUX(mux) { \
-                                       taskEXIT_CRITICAL(mux); \
-                                       portYIELD_WITHIN_API(); \
-                                       taskENTER_CRITICAL(mux); \
-                                       } while(0)
 #endif
 
 /*
@@ -290,7 +284,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
                        {
                                if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) == pdTRUE )
                                {
-                                       queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux);
+                                       queueYIELD_IF_USING_PREEMPTION();
                                }
                                else
                                {
@@ -753,7 +747,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
                                                        /* The queue is a member of a queue set, and posting
                                                        to the queue set caused a higher priority task to
                                                        unblock. A context switch is required. */
-                                                       queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux);
+                                                       queueYIELD_IF_USING_PREEMPTION();
                                                }
                                                else
                                                {
@@ -772,7 +766,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
                                                                our own so yield immediately.  Yes it is ok to
                                                                do this from within the critical section - the
                                                                kernel takes care of that. */
-                                                               queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux);
+                                                               queueYIELD_IF_USING_PREEMPTION();
                                                        }
                                                        else
                                                        {
@@ -785,7 +779,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
                                                        executed if the task was holding multiple mutexes
                                                        and the mutexes were given back in an order that is
                                                        different to that in which they were taken. */
-                                                       queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux);
+                                                       queueYIELD_IF_USING_PREEMPTION();
                                                }
                                                else
                                                {
@@ -805,7 +799,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
                                                        our own so yield immediately.  Yes it is ok to do
                                                        this from within the critical section - the kernel
                                                        takes care of that. */
-                                                       queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux);
+                                                       queueYIELD_IF_USING_PREEMPTION();
                                                }
                                                else
                                                {
@@ -818,7 +812,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
                                                executed if the task was holding multiple mutexes and
                                                the mutexes were given back in an order that is
                                                different to that in which they were taken. */
-                                               queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux);
+                                               queueYIELD_IF_USING_PREEMPTION();
                                        }
                                        else
                                        {
@@ -1493,7 +1487,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
                                        {
                                                if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) == pdTRUE )
                                                {
-                                                       queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux);
+                                                       queueYIELD_IF_USING_PREEMPTION();
                                                }
                                                else
                                                {
@@ -1522,7 +1516,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
                                                if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
                                                {
                                                        /* The task waiting has a higher priority than this task. */
-                                                       queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux);
+                                                       queueYIELD_IF_USING_PREEMPTION();
                                                }
                                                else
                                                {
index 145554a740ce1511e1e53d9c36c502affd8364cb..ff407d9baf2d2b255134d5fb0e35dcfbfba0aa41 100644 (file)
@@ -120,14 +120,8 @@ functions but without including stdio.h here. */
        /* If the cooperative scheduler is being used then a yield should not be
        performed just because a higher priority task has been woken. */
        #define taskYIELD_IF_USING_PREEMPTION()
-       #define queueYIELD_IF_USING_PREEMPTION_MUX(mux)
 #else
        #define taskYIELD_IF_USING_PREEMPTION() portYIELD_WITHIN_API()
-       #define taskYIELD_IF_USING_PREEMPTION_MUX(mux) { \
-                                       taskEXIT_CRITICAL(mux); \
-                                       portYIELD_WITHIN_API(); \
-                                       taskENTER_CRITICAL(mux); \
-                                       } while(0)
 #endif
 
 
@@ -1166,7 +1160,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode
                {
                        if( xCoreID == xPortGetCoreID() )
                        {
-                               taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex);
+                               taskYIELD_IF_USING_PREEMPTION();
                        }
                        else {
                                taskYIELD_OTHER_CORE(xCoreID, pxNewTCB->uxPriority);
@@ -1703,7 +1697,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode
 
                                if( xYieldRequired == pdTRUE )
                                {
-                                       taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex);
+                                       taskYIELD_IF_USING_PREEMPTION();
                                }
                                else
                                {
@@ -1895,7 +1889,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode
                                                /* This yield may not cause the task just resumed to run,
                                                but will leave the lists in the correct state for the
                                                next yield. */
-                                               taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex);
+                                               taskYIELD_IF_USING_PREEMPTION();
                                        }
                                        else if( pxTCB->xCoreID != xPortGetCoreID() )
                                        {
@@ -2206,7 +2200,7 @@ BaseType_t xAlreadyYielded = pdFALSE;
                                                xAlreadyYielded = pdTRUE;
                                        }
                                        #endif
-                                       taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex);
+                                       taskYIELD_IF_USING_PREEMPTION();
                                }
                                else
                                {
diff --git a/components/freertos/test/test_preemption.c b/components/freertos/test/test_preemption.c
new file mode 100644 (file)
index 0000000..2b77918
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ Unit tests for FreeRTOS preemption
+*/
+
+#include <esp_types.h>
+#include <stdio.h>
+#include "rom/ets_sys.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "freertos/queue.h"
+#include "freertos/xtensa_api.h"
+#include "unity.h"
+#include "soc/cpu.h"
+
+
+static volatile bool flag;
+
+static void task_yield(void *param)
+{
+    QueueHandle_t queue = (QueueHandle_t) param;
+    uint32_t ccount;
+    RSR(CCOUNT, ccount);
+    flag = true;
+    xQueueSendToBack(queue, &ccount, 0);
+    /* This is to ensure that higher priority task
+       won't wake anyhow, due to this task terminating.
+    */
+    for (int i = 0; i < 1000; i++) {
+        ets_delay_us(1000);
+    }
+    vTaskDelete(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++) {
+        QueueHandle_t queue = xQueueCreate(1, sizeof(uint32_t));
+        flag = false;
+
+        /* "yield" task sits on our CPU, lower priority to us */
+        xTaskCreatePinnedToCore(task_yield, "YIELD", 2048, (void *)queue, UNITY_FREERTOS_PRIORITY - 1, NULL, UNITY_FREERTOS_CPU);
+
+        uint32_t yield_ccount, now_ccount, delta;
+        TEST_ASSERT( xQueueReceive(queue, &yield_ccount, 100 / portTICK_PERIOD_MS) );
+        RSR(CCOUNT, now_ccount);
+        TEST_ASSERT( flag );
+
+        delta = now_ccount - yield_ccount;
+        printf("Yielding from lower priority task took %u cycles\n", delta);
+        TEST_ASSERT(delta < 10000);
+
+        vQueueDelete(queue);
+    }
+}
index 07df5b3058cb211dac77c8aaaf1101b5b44819b4..bacb79e00b3b9472360423623293dffceb412256 100644 (file)
@@ -9,6 +9,10 @@
 
 #include <esp_err.h>
 
+/* Some definitions applicable to Unity running in FreeRTOS */
+#define UNITY_FREERTOS_PRIORITY 5
+#define UNITY_FREERTOS_CPU 0
+
 #define UNITY_EXCLUDE_FLOAT
 #define UNITY_EXCLUDE_DOUBLE
 
index 58a195172016d2408fcd23bea103080c4022d9d4..c5df02b943f343c8c4d8a1c42e0436ebc9c03f86 100644 (file)
@@ -2,9 +2,9 @@
 #include "freertos/FreeRTOS.h"
 #include "freertos/task.h"
 #include "unity.h"
+#include "unity_config.h"
 
-
-void unityTask(void *pvParameters) 
+void unityTask(void *pvParameters)
 {
     vTaskDelay(1000 / portTICK_PERIOD_MS);
     unity_run_menu();
@@ -15,6 +15,6 @@ void app_main()
 {
     // Note: if unpinning this task, change the way run times are calculated in
     // unity_platform
-    xTaskCreatePinnedToCore(unityTask, "unityTask", 4096, NULL, 5, NULL, 0);
+    xTaskCreatePinnedToCore(unityTask, "unityTask", 4096, NULL,
+                            UNITY_FREERTOS_PRIORITY, NULL, UNITY_FREERTOS_CPU);
 }
-