freertos: Delay context switch from queue/task APIs until exiting critical section

Closes #374: https://github.com/espressif/esp-idf/issues/374
This commit is contained in:
Angus Gratton 2017-03-16 11:07:50 +08:00
parent fc15d72038
commit 45581dbaca
6 changed files with 92 additions and 28 deletions

View file

@ -79,6 +79,8 @@ extern "C" {
#include <xtensa/config/core.h> #include <xtensa/config/core.h>
#include <xtensa/config/system.h> /* required for XSHAL_CLIB */ #include <xtensa/config/system.h> /* required for XSHAL_CLIB */
#include <xtensa/xtruntime.h> #include <xtensa/xtruntime.h>
#include "esp_crosscore_int.h"
//#include "xtensa_context.h" //#include "xtensa_context.h"
@ -261,6 +263,18 @@ void vPortYield( void );
void _frxt_setup_switch( void ); void _frxt_setup_switch( void );
#define portYIELD() vPortYield() #define portYIELD() vPortYield()
#define portYIELD_FROM_ISR() _frxt_setup_switch() #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. */ /* Task function macros as described on the FreeRTOS.org WEB site. */

View file

@ -123,15 +123,9 @@ zero. */
#if( configUSE_PREEMPTION == 0 ) #if( configUSE_PREEMPTION == 0 )
/* If the cooperative scheduler is being used then a yield should not be /* If the cooperative scheduler is being used then a yield should not be
performed just because a higher priority task has been woken. */ performed just because a higher priority task has been woken. */
#define queueYIELD_IF_USING_PREEMPTION_MUX()
#define queueYIELD_IF_USING_PREEMPTION() #define queueYIELD_IF_USING_PREEMPTION()
#else #else
#define queueYIELD_IF_USING_PREEMPTION() portYIELD_WITHIN_API() #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 #endif
/* /*
@ -290,7 +284,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
{ {
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) == pdTRUE ) if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) == pdTRUE )
{ {
queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux); queueYIELD_IF_USING_PREEMPTION();
} }
else else
{ {
@ -753,7 +747,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
/* The queue is a member of a queue set, and posting /* The queue is a member of a queue set, and posting
to the queue set caused a higher priority task to to the queue set caused a higher priority task to
unblock. A context switch is required. */ unblock. A context switch is required. */
queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux); queueYIELD_IF_USING_PREEMPTION();
} }
else else
{ {
@ -772,7 +766,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
our own so yield immediately. Yes it is ok to our own so yield immediately. Yes it is ok to
do this from within the critical section - the do this from within the critical section - the
kernel takes care of that. */ kernel takes care of that. */
queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux); queueYIELD_IF_USING_PREEMPTION();
} }
else else
{ {
@ -785,7 +779,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
executed if the task was holding multiple mutexes executed if the task was holding multiple mutexes
and the mutexes were given back in an order that is and the mutexes were given back in an order that is
different to that in which they were taken. */ different to that in which they were taken. */
queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux); queueYIELD_IF_USING_PREEMPTION();
} }
else else
{ {
@ -805,7 +799,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
our own so yield immediately. Yes it is ok to do our own so yield immediately. Yes it is ok to do
this from within the critical section - the kernel this from within the critical section - the kernel
takes care of that. */ takes care of that. */
queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux); queueYIELD_IF_USING_PREEMPTION();
} }
else else
{ {
@ -818,7 +812,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
executed if the task was holding multiple mutexes and executed if the task was holding multiple mutexes and
the mutexes were given back in an order that is the mutexes were given back in an order that is
different to that in which they were taken. */ different to that in which they were taken. */
queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux); queueYIELD_IF_USING_PREEMPTION();
} }
else else
{ {
@ -1493,7 +1487,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
{ {
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) == pdTRUE ) if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) == pdTRUE )
{ {
queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux); queueYIELD_IF_USING_PREEMPTION();
} }
else else
{ {
@ -1522,7 +1516,7 @@ Queue_t * const pxQueue = ( Queue_t * ) xQueue;
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE ) if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{ {
/* The task waiting has a higher priority than this task. */ /* The task waiting has a higher priority than this task. */
queueYIELD_IF_USING_PREEMPTION_MUX(&pxQueue->mux); queueYIELD_IF_USING_PREEMPTION();
} }
else else
{ {

View 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 /* If the cooperative scheduler is being used then a yield should not be
performed just because a higher priority task has been woken. */ performed just because a higher priority task has been woken. */
#define taskYIELD_IF_USING_PREEMPTION() #define taskYIELD_IF_USING_PREEMPTION()
#define queueYIELD_IF_USING_PREEMPTION_MUX(mux)
#else #else
#define taskYIELD_IF_USING_PREEMPTION() portYIELD_WITHIN_API() #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 #endif
@ -1166,7 +1160,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode
{ {
if( xCoreID == xPortGetCoreID() ) if( xCoreID == xPortGetCoreID() )
{ {
taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex); taskYIELD_IF_USING_PREEMPTION();
} }
else { else {
taskYIELD_OTHER_CORE(xCoreID, pxNewTCB->uxPriority); taskYIELD_OTHER_CORE(xCoreID, pxNewTCB->uxPriority);
@ -1703,7 +1697,7 @@ static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB, TaskFunction_t pxTaskCode
if( xYieldRequired == pdTRUE ) if( xYieldRequired == pdTRUE )
{ {
taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex); taskYIELD_IF_USING_PREEMPTION();
} }
else 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, /* This yield may not cause the task just resumed to run,
but will leave the lists in the correct state for the but will leave the lists in the correct state for the
next yield. */ next yield. */
taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex); taskYIELD_IF_USING_PREEMPTION();
} }
else if( pxTCB->xCoreID != xPortGetCoreID() ) else if( pxTCB->xCoreID != xPortGetCoreID() )
{ {
@ -2206,7 +2200,7 @@ BaseType_t xAlreadyYielded = pdFALSE;
xAlreadyYielded = pdTRUE; xAlreadyYielded = pdTRUE;
} }
#endif #endif
taskYIELD_IF_USING_PREEMPTION_MUX(&xTaskQueueMutex); taskYIELD_IF_USING_PREEMPTION();
} }
else else
{ {

View file

@ -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);
}
}

View file

@ -9,6 +9,10 @@
#include <esp_err.h> #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_FLOAT
#define UNITY_EXCLUDE_DOUBLE #define UNITY_EXCLUDE_DOUBLE

View file

@ -2,9 +2,9 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "unity.h" #include "unity.h"
#include "unity_config.h"
void unityTask(void *pvParameters)
void unityTask(void *pvParameters)
{ {
vTaskDelay(1000 / portTICK_PERIOD_MS); vTaskDelay(1000 / portTICK_PERIOD_MS);
unity_run_menu(); unity_run_menu();
@ -15,6 +15,6 @@ void app_main()
{ {
// Note: if unpinning this task, change the way run times are calculated in // Note: if unpinning this task, change the way run times are calculated in
// unity_platform // unity_platform
xTaskCreatePinnedToCore(unityTask, "unityTask", 4096, NULL, 5, NULL, 0); xTaskCreatePinnedToCore(unityTask, "unityTask", 4096, NULL,
UNITY_FREERTOS_PRIORITY, NULL, UNITY_FREERTOS_CPU);
} }