1040 lines
37 KiB
C
1040 lines
37 KiB
C
/*
|
|
* FreeRTOS V202212.00
|
|
* Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
* this software and associated documentation files (the "Software"), to deal in
|
|
* the Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* https://www.FreeRTOS.org
|
|
* https://github.com/FreeRTOS
|
|
*
|
|
*/
|
|
|
|
|
|
/*
|
|
* Tests the extra queue functionality introduced in FreeRTOS.org V4.5.0 -
|
|
* including xQueueSendToFront(), xQueueSendToBack(), xQueuePeek() and
|
|
* mutex behaviour.
|
|
*
|
|
* See the comments above the prvSendFrontAndBackTest() and
|
|
* prvLowPriorityMutexTask() prototypes below for more information.
|
|
*/
|
|
|
|
/* Standard includes. */
|
|
#include <stdlib.h>
|
|
|
|
/* Scheduler include files. */
|
|
#include "FreeRTOS.h"
|
|
#include "task.h"
|
|
#include "queue.h"
|
|
#include "semphr.h"
|
|
|
|
/* Demo program include files. */
|
|
#include "GenQTest.h"
|
|
|
|
#define genqQUEUE_LENGTH ( 5 )
|
|
#define intsemNO_BLOCK ( 0 )
|
|
#define genqSHORT_BLOCK ( pdMS_TO_TICKS( 2 ) )
|
|
|
|
#define genqMUTEX_LOW_PRIORITY ( tskIDLE_PRIORITY )
|
|
#define genqMUTEX_TEST_PRIORITY ( tskIDLE_PRIORITY + 1 )
|
|
#define genqMUTEX_MEDIUM_PRIORITY ( tskIDLE_PRIORITY + 2 )
|
|
#define genqMUTEX_HIGH_PRIORITY ( tskIDLE_PRIORITY + 3 )
|
|
|
|
#ifndef genqMUTEX_TEST_TASK_STACK_SIZE
|
|
#define genqMUTEX_TEST_TASK_STACK_SIZE configMINIMAL_STACK_SIZE
|
|
#endif
|
|
|
|
#ifndef genqGENERIC_QUEUE_TEST_TASK_STACK_SIZE
|
|
#define genqGENERIC_QUEUE_TEST_TASK_STACK_SIZE configMINIMAL_STACK_SIZE
|
|
#endif
|
|
/*-----------------------------------------------------------*/
|
|
|
|
/*
|
|
* Tests the behaviour of the xQueueSendToFront() and xQueueSendToBack()
|
|
* macros by using both to fill a queue, then reading from the queue to
|
|
* check the resultant queue order is as expected. Queue data is also
|
|
* peeked.
|
|
*/
|
|
static void prvSendFrontAndBackTest( void * pvParameters );
|
|
|
|
/*
|
|
* The following three tasks are used to demonstrate the mutex behaviour.
|
|
* Each task is given a different priority to demonstrate the priority
|
|
* inheritance mechanism.
|
|
*
|
|
* The low priority task obtains a mutex. After this a high priority task
|
|
* attempts to obtain the same mutex, causing its priority to be inherited
|
|
* by the low priority task. The task with the inherited high priority then
|
|
* resumes a medium priority task to ensure it is not blocked by the medium
|
|
* priority task while it holds the inherited high priority. Once the mutex
|
|
* is returned the task with the inherited priority returns to its original
|
|
* low priority, and is therefore immediately preempted by first the high
|
|
* priority task and then the medium priority task before it can continue.
|
|
*/
|
|
static void prvLowPriorityMutexTask( void * pvParameters );
|
|
static void prvMediumPriorityMutexTask( void * pvParameters );
|
|
static void prvHighPriorityMutexTask( void * pvParameters );
|
|
|
|
/*
|
|
* Tests the behaviour when a low priority task inherits the priority of a
|
|
* higher priority task when taking two mutexes, and returns the mutexes in
|
|
* first the same order as the two mutexes were obtained, and second the
|
|
* opposite order as the two mutexes were obtained.
|
|
*/
|
|
static void prvTakeTwoMutexesReturnInSameOrder( SemaphoreHandle_t xMutex,
|
|
SemaphoreHandle_t xLocalMutex );
|
|
static void prvTakeTwoMutexesReturnInDifferentOrder( SemaphoreHandle_t xMutex,
|
|
SemaphoreHandle_t xLocalMutex );
|
|
|
|
#if ( INCLUDE_xTaskAbortDelay == 1 )
|
|
|
|
#if ( configUSE_PREEMPTION == 0 )
|
|
#error The additional tests included when INCLUDE_xTaskAbortDelay is 1 expect preemption to be used.
|
|
#endif
|
|
|
|
/* Tests the behaviour when a low priority task inherits the priority of a
|
|
* high priority task only for the high priority task to timeout before
|
|
* obtaining the mutex. */
|
|
static void prvHighPriorityTimeout( SemaphoreHandle_t xMutex );
|
|
#endif
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
/* Flag that will be latched to pdTRUE should any unexpected behaviour be
|
|
* detected in any of the tasks. */
|
|
static volatile BaseType_t xErrorDetected = pdFALSE;
|
|
|
|
/* Counters that are incremented on each cycle of a test. This is used to
|
|
* detect a stalled task - a test that is no longer running. */
|
|
static volatile uint32_t ulLoopCounter = 0;
|
|
static volatile uint32_t ulLoopCounter2 = 0;
|
|
|
|
/* The variable that is guarded by the mutex in the mutex demo tasks. */
|
|
static volatile uint32_t ulGuardedVariable = 0;
|
|
|
|
/* Handles used in the mutex test to suspend and resume the high and medium
|
|
* priority mutex test tasks. */
|
|
static TaskHandle_t xHighPriorityMutexTask, xMediumPriorityMutexTask;
|
|
|
|
/* If INCLUDE_xTaskAbortDelay is 1 additional tests are performed, requiring an
|
|
* additional task. */
|
|
#if ( INCLUDE_xTaskAbortDelay == 1 )
|
|
static TaskHandle_t xSecondMediumPriorityMutexTask;
|
|
#endif
|
|
|
|
/* Lets the high priority semaphore task know that its wait for the semaphore
|
|
* was aborted, in which case not being able to obtain the semaphore is not to be
|
|
* considered an error. */
|
|
static volatile BaseType_t xBlockWasAborted = pdFALSE;
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
void vStartGenericQueueTasks( UBaseType_t uxPriority )
|
|
{
|
|
QueueHandle_t xQueue;
|
|
SemaphoreHandle_t xMutex;
|
|
|
|
/* Create the queue that we are going to use for the
|
|
* prvSendFrontAndBackTest demo. */
|
|
xQueue = xQueueCreate( genqQUEUE_LENGTH, sizeof( uint32_t ) );
|
|
|
|
if( xQueue != NULL )
|
|
{
|
|
/* vQueueAddToRegistry() adds the queue to the queue registry, if one
|
|
* is in use. The queue registry is provided as a means for kernel aware
|
|
* debuggers to locate queues and has no purpose if a kernel aware debugger
|
|
* is not being used. The call to vQueueAddToRegistry() will be removed
|
|
* by the pre-processor if configQUEUE_REGISTRY_SIZE is not defined or is
|
|
* defined to be less than 1. */
|
|
vQueueAddToRegistry( xQueue, "Gen_Queue_Test" );
|
|
|
|
/* Create the demo task and pass it the queue just created. We are
|
|
* passing the queue handle by value so it does not matter that it is
|
|
* declared on the stack here. */
|
|
xTaskCreate( prvSendFrontAndBackTest, "GenQ", genqGENERIC_QUEUE_TEST_TASK_STACK_SIZE, ( void * ) xQueue, uxPriority, NULL );
|
|
}
|
|
|
|
/* Create the mutex used by the prvMutexTest task. */
|
|
xMutex = xSemaphoreCreateMutex();
|
|
|
|
if( xMutex != NULL )
|
|
{
|
|
/* vQueueAddToRegistry() adds the mutex to the registry, if one is
|
|
* in use. The registry is provided as a means for kernel aware
|
|
* debuggers to locate mutexes and has no purpose if a kernel aware
|
|
* debugger is not being used. The call to vQueueAddToRegistry() will be
|
|
* removed by the pre-processor if configQUEUE_REGISTRY_SIZE is not
|
|
* defined or is defined to be less than 1. */
|
|
vQueueAddToRegistry( ( QueueHandle_t ) xMutex, "Gen_Queue_Mutex" );
|
|
|
|
/* Create the mutex demo tasks and pass it the mutex just created. We
|
|
* are passing the mutex handle by value so it does not matter that it is
|
|
* declared on the stack here. */
|
|
xTaskCreate( prvLowPriorityMutexTask, "MuLow", genqMUTEX_TEST_TASK_STACK_SIZE, ( void * ) xMutex, genqMUTEX_LOW_PRIORITY, NULL );
|
|
xTaskCreate( prvMediumPriorityMutexTask, "MuMed", configMINIMAL_STACK_SIZE, NULL, genqMUTEX_MEDIUM_PRIORITY, &xMediumPriorityMutexTask );
|
|
xTaskCreate( prvHighPriorityMutexTask, "MuHigh", genqMUTEX_TEST_TASK_STACK_SIZE, ( void * ) xMutex, genqMUTEX_HIGH_PRIORITY, &xHighPriorityMutexTask );
|
|
|
|
/* If INCLUDE_xTaskAbortDelay is set then additional tests are performed,
|
|
* requiring two instances of prvHighPriorityMutexTask(). */
|
|
#if ( INCLUDE_xTaskAbortDelay == 1 )
|
|
{
|
|
xTaskCreate( prvHighPriorityMutexTask, "MuHigh2", configMINIMAL_STACK_SIZE, ( void * ) xMutex, genqMUTEX_MEDIUM_PRIORITY, &xSecondMediumPriorityMutexTask );
|
|
}
|
|
#endif /* INCLUDE_xTaskAbortDelay */
|
|
}
|
|
}
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void prvSendFrontAndBackTest( void * pvParameters )
|
|
{
|
|
uint32_t ulData, ulData2, ulLoopCounterSnapshot;
|
|
QueueHandle_t xQueue;
|
|
|
|
#ifdef USE_STDIO
|
|
void vPrintDisplayMessage( const char * const * ppcMessageToSend );
|
|
|
|
const char * const pcTaskStartMsg = "Queue SendToFront/SendToBack/Peek test started.\r\n";
|
|
|
|
/* Queue a message for printing to say the task has started. */
|
|
vPrintDisplayMessage( &pcTaskStartMsg );
|
|
#endif
|
|
|
|
xQueue = ( QueueHandle_t ) pvParameters;
|
|
|
|
for( ; ; )
|
|
{
|
|
/* The queue is empty, so sending an item to the back of the queue
|
|
* should have the same effect as sending it to the front of the queue.
|
|
*
|
|
* First send to the front and check everything is as expected. */
|
|
ulLoopCounterSnapshot = ulLoopCounter;
|
|
xQueueSendToFront( xQueue, ( void * ) &ulLoopCounterSnapshot, intsemNO_BLOCK );
|
|
|
|
if( uxQueueMessagesWaiting( xQueue ) != 1 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( xQueueReceive( xQueue, ( void * ) &ulData, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* The data we sent to the queue should equal the data we just received
|
|
* from the queue. */
|
|
if( ulLoopCounter != ulData )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Then do the same, sending the data to the back, checking everything
|
|
* is as expected. */
|
|
if( uxQueueMessagesWaiting( xQueue ) != 0 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
ulLoopCounterSnapshot = ulLoopCounter;
|
|
xQueueSendToBack( xQueue, ( void * ) &ulLoopCounterSnapshot, intsemNO_BLOCK );
|
|
|
|
if( uxQueueMessagesWaiting( xQueue ) != 1 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( xQueueReceive( xQueue, ( void * ) &ulData, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( uxQueueMessagesWaiting( xQueue ) != 0 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* The data sent to the queue should equal the data just received from
|
|
* the queue. */
|
|
if( ulLoopCounter != ulData )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* Place 2, 3, 4 into the queue, adding items to the back of the queue. */
|
|
for( ulData = 2; ulData < 5; ulData++ )
|
|
{
|
|
xQueueSendToBack( xQueue, ( void * ) &ulData, intsemNO_BLOCK );
|
|
}
|
|
|
|
/* Now the order in the queue should be 2, 3, 4, with 2 being the first
|
|
* thing to be read out. Now add 1 then 0 to the front of the queue. */
|
|
if( uxQueueMessagesWaiting( xQueue ) != 3 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
ulData = 1;
|
|
xQueueSendToFront( xQueue, ( void * ) &ulData, intsemNO_BLOCK );
|
|
ulData = 0;
|
|
xQueueSendToFront( xQueue, ( void * ) &ulData, intsemNO_BLOCK );
|
|
|
|
/* Now the queue should be full, and when we read the data out we
|
|
* should receive 0, 1, 2, 3, 4. */
|
|
if( uxQueueMessagesWaiting( xQueue ) != 5 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( xQueueSendToFront( xQueue, ( void * ) &ulData, intsemNO_BLOCK ) != errQUEUE_FULL )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( xQueueSendToBack( xQueue, ( void * ) &ulData, intsemNO_BLOCK ) != errQUEUE_FULL )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* Check the data we read out is in the expected order. */
|
|
for( ulData = 0; ulData < genqQUEUE_LENGTH; ulData++ )
|
|
{
|
|
/* Try peeking the data first. */
|
|
if( xQueuePeek( xQueue, &ulData2, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( ulData != ulData2 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now try receiving the data for real. The value should be the
|
|
* same. Clobber the value first so we know we really received it. */
|
|
ulData2 = ~ulData2;
|
|
|
|
if( xQueueReceive( xQueue, &ulData2, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( ulData != ulData2 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
}
|
|
|
|
/* The queue should now be empty again. */
|
|
if( uxQueueMessagesWaiting( xQueue ) != 0 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
|
|
/* Our queue is empty once more, add 10, 11 to the back. */
|
|
ulData = 10;
|
|
|
|
if( xQueueSend( xQueue, &ulData, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
ulData = 11;
|
|
|
|
if( xQueueSend( xQueue, &ulData, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( uxQueueMessagesWaiting( xQueue ) != 2 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now we should have 10, 11 in the queue. Add 7, 8, 9 to the
|
|
* front. */
|
|
for( ulData = 9; ulData >= 7; ulData-- )
|
|
{
|
|
if( xQueueSendToFront( xQueue, ( void * ) &ulData, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
}
|
|
|
|
/* Now check that the queue is full, and that receiving data provides
|
|
* the expected sequence of 7, 8, 9, 10, 11. */
|
|
if( uxQueueMessagesWaiting( xQueue ) != 5 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( xQueueSendToFront( xQueue, ( void * ) &ulData, intsemNO_BLOCK ) != errQUEUE_FULL )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( xQueueSendToBack( xQueue, ( void * ) &ulData, intsemNO_BLOCK ) != errQUEUE_FULL )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* Check the data we read out is in the expected order. */
|
|
for( ulData = 7; ulData < ( 7 + genqQUEUE_LENGTH ); ulData++ )
|
|
{
|
|
if( xQueueReceive( xQueue, &ulData2, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( ulData != ulData2 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
}
|
|
|
|
if( uxQueueMessagesWaiting( xQueue ) != 0 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Increment the loop counter to indicate these tasks are still
|
|
* executing. */
|
|
ulLoopCounter++;
|
|
}
|
|
}
|
|
/*-----------------------------------------------------------*/
|
|
|
|
#if ( INCLUDE_xTaskAbortDelay == 1 )
|
|
|
|
static void prvHighPriorityTimeout( SemaphoreHandle_t xMutex )
|
|
{
|
|
static UBaseType_t uxLoopCount = 0;
|
|
|
|
/* The tests in this function are very similar, the slight variations
|
|
* are for code coverage purposes. */
|
|
|
|
/* Take the mutex. It should be available now. Check before and after
|
|
* taking that the holder is reported correctly. */
|
|
if( xSemaphoreGetMutexHolder( xMutex ) != NULL )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( xSemaphoreTake( xMutex, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( xSemaphoreGetMutexHolder( xMutex ) != xTaskGetCurrentTaskHandle() )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* This task's priority should be as per that assigned when the task was
|
|
* created. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_LOW_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now unsuspend the high priority task. This will attempt to take the
|
|
* mutex, and block when it finds it cannot obtain it. */
|
|
vTaskResume( xHighPriorityMutexTask );
|
|
|
|
/* This task should now have inherited the priority of the high priority
|
|
* task as by now the high priority task will have attempted to obtain the
|
|
* mutex. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_HIGH_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Unblock a second medium priority task. It too will attempt to take
|
|
* the mutex and enter the Blocked state - it won't run yet though as this
|
|
* task has inherited a priority above it. */
|
|
vTaskResume( xSecondMediumPriorityMutexTask );
|
|
|
|
/* This task should still have the priority of the high priority task as
|
|
* that had already been inherited as is the highest priority of the three
|
|
* tasks using the mutex. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_HIGH_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* On some loops, block for a short while to provide additional
|
|
* code coverage. Blocking here will allow the medium priority task to
|
|
* execute and so also block on the mutex so when the high priority task
|
|
* causes this task to disinherit the high priority it is inherited down to
|
|
* the priority of the medium priority task. When there is no delay the
|
|
* medium priority task will not run until after the disinheritance, so
|
|
* this task will disinherit back to its base priority, then only up to the
|
|
* medium priority after the medium priority has executed. */
|
|
vTaskDelay( uxLoopCount & ( UBaseType_t ) 0x07 );
|
|
|
|
/* Now force the high priority task to unblock. It will fail to obtain
|
|
* the mutex and go back to the suspended state - allowing this task to
|
|
* execute again. xBlockWasAborted is set to pdTRUE so the higher priority
|
|
* task knows that its failure to obtain the semaphore is not an error. */
|
|
xBlockWasAborted = pdTRUE;
|
|
|
|
if( xTaskAbortDelay( xHighPriorityMutexTask ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* This task has inherited the priority of xHighPriorityMutexTask so
|
|
* could still be running even though xHighPriorityMutexTask is no longer
|
|
* blocked. Delay for a short while to ensure xHighPriorityMutexTask gets
|
|
* a chance to run - indicated by this task changing priority. It should
|
|
* disinherit the high priority task, but then inherit the priority of the
|
|
* medium priority task that is waiting for the same mutex. */
|
|
while( uxTaskPriorityGet( NULL ) != genqMUTEX_MEDIUM_PRIORITY )
|
|
{
|
|
/* If this task gets stuck here then the check variables will stop
|
|
* incrementing and the check task will detect the error. */
|
|
vTaskDelay( genqSHORT_BLOCK );
|
|
}
|
|
|
|
/* Now force the medium priority task to unblock. xBlockWasAborted is
|
|
* set to pdTRUE so the medium priority task knows that its failure to
|
|
* obtain the semaphore is not an error. */
|
|
xBlockWasAborted = pdTRUE;
|
|
|
|
if( xTaskAbortDelay( xSecondMediumPriorityMutexTask ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* This time no other tasks are waiting for the mutex, so this task
|
|
* should return to its base priority. This might not happen straight
|
|
* away as it is running at the same priority as the task it just
|
|
* unblocked. */
|
|
while( uxTaskPriorityGet( NULL ) != genqMUTEX_LOW_PRIORITY )
|
|
{
|
|
/* If this task gets stuck here then the check variables will stop
|
|
* incrementing and the check task will detect the error. */
|
|
vTaskDelay( genqSHORT_BLOCK );
|
|
}
|
|
|
|
/* Give the semaphore back ready for the next test. Check the mutex
|
|
* holder before and after using the "FromISR" version for code coverage. */
|
|
if( xSemaphoreGetMutexHolderFromISR( xMutex ) != xTaskGetCurrentTaskHandle() )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
xSemaphoreGive( xMutex );
|
|
|
|
if( xSemaphoreGetMutexHolderFromISR( xMutex ) != NULL )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
configASSERT( xErrorDetected == pdFALSE );
|
|
|
|
/* Now do the same again, but this time unsuspend the tasks in the
|
|
* opposite order. This takes a different path though the code because
|
|
* when the high priority task has its block aborted there is already
|
|
* another task in the list of tasks waiting for the mutex, and the
|
|
* low priority task drops down to that priority, rather than dropping
|
|
* down to its base priority before inheriting the priority of the medium
|
|
* priority task. */
|
|
if( xSemaphoreTake( xMutex, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_LOW_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* This time unsuspend the medium priority task first. This will
|
|
* attempt to take the mutex, and block when it finds it cannot obtain it. */
|
|
vTaskResume( xSecondMediumPriorityMutexTask );
|
|
|
|
/* This time this task should now have inherited the priority of the
|
|
* medium task. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_MEDIUM_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* This time the high priority task in unsuspended second. */
|
|
vTaskResume( xHighPriorityMutexTask );
|
|
|
|
/* The high priority task should already have run, causing this task to
|
|
* inherit a priority for the second time. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_HIGH_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* This time, when the high priority task has its delay aborted and it
|
|
* fails to obtain the mutex this task will immediately have its priority
|
|
* lowered down to that of the highest priority task waiting on the mutex,
|
|
* which is the medium priority task. */
|
|
xBlockWasAborted = pdTRUE;
|
|
|
|
if( xTaskAbortDelay( xHighPriorityMutexTask ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
while( uxTaskPriorityGet( NULL ) != genqMUTEX_MEDIUM_PRIORITY )
|
|
{
|
|
/* If this task gets stuck here then the check variables will stop
|
|
* incrementing and the check task will detect the error. */
|
|
vTaskDelay( genqSHORT_BLOCK );
|
|
}
|
|
|
|
/* And finally, when the medium priority task also have its delay
|
|
* aborted there are no other tasks waiting for the mutex so this task
|
|
* returns to its base priority. */
|
|
xBlockWasAborted = pdTRUE;
|
|
|
|
if( xTaskAbortDelay( xSecondMediumPriorityMutexTask ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
while( uxTaskPriorityGet( NULL ) != genqMUTEX_LOW_PRIORITY )
|
|
{
|
|
/* If this task gets stuck here then the check variables will stop
|
|
* incrementing and the check task will detect the error. */
|
|
vTaskDelay( genqSHORT_BLOCK );
|
|
}
|
|
|
|
/* Give the semaphore back ready for the next test. */
|
|
xSemaphoreGive( xMutex );
|
|
|
|
configASSERT( xErrorDetected == pdFALSE );
|
|
|
|
/* uxLoopCount is used to add a variable delay, and in-so-doing provide
|
|
* additional code coverage. */
|
|
uxLoopCount++;
|
|
}
|
|
|
|
#endif /* INCLUDE_xTaskAbortDelay == 1 */
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void prvTakeTwoMutexesReturnInDifferentOrder( SemaphoreHandle_t xMutex,
|
|
SemaphoreHandle_t xLocalMutex )
|
|
{
|
|
/* Take the mutex. It should be available now. */
|
|
if( xSemaphoreTake( xMutex, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Set the guarded variable to a known start value. */
|
|
ulGuardedVariable = 0;
|
|
|
|
/* This task's priority should be as per that assigned when the task was
|
|
* created. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_LOW_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now unsuspend the high priority task. This will attempt to take the
|
|
* mutex, and block when it finds it cannot obtain it. */
|
|
vTaskResume( xHighPriorityMutexTask );
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* Ensure the task is reporting its priority as blocked and not
|
|
* suspended (as it would have done in versions up to V7.5.3). */
|
|
#if ( INCLUDE_eTaskGetState == 1 )
|
|
{
|
|
configASSERT( eTaskGetState( xHighPriorityMutexTask ) == eBlocked );
|
|
}
|
|
#endif /* INCLUDE_eTaskGetState */
|
|
|
|
/* This task should now have inherited the priority of the high priority
|
|
* task as by now the high priority task will have attempted to obtain the
|
|
* mutex. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_HIGH_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Attempt to set the priority of this task to the test priority -
|
|
* between the idle priority and the medium/high test priorities, but the
|
|
* actual priority should remain at the high priority. */
|
|
vTaskPrioritySet( NULL, genqMUTEX_TEST_PRIORITY );
|
|
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_HIGH_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now unsuspend the medium priority task. This should not run as the
|
|
* inherited priority of this task is above that of the medium priority
|
|
* task. */
|
|
vTaskResume( xMediumPriorityMutexTask );
|
|
|
|
/* If the medium priority task did run then it will have incremented the
|
|
* guarded variable. */
|
|
if( ulGuardedVariable != 0 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Take the local mutex too, so two mutexes are now held. */
|
|
if( xSemaphoreTake( xLocalMutex, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* When the semaphore is given back the priority of this task should not
|
|
* yet be disinherited because the local mutex is still held. This is a
|
|
* simplification to allow FreeRTOS to be integrated with middleware that
|
|
* attempts to hold multiple mutexes without bloating the code with complex
|
|
* algorithms. It is possible that the high priority mutex task will
|
|
* execute as it shares a priority with this task. */
|
|
if( xSemaphoreGive( xMutex ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* The guarded variable is only incremented by the medium priority task,
|
|
* which still should not have executed as this task should remain at the
|
|
* higher priority, ensure this is the case. */
|
|
if( ulGuardedVariable != 0 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_HIGH_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now also give back the local mutex, taking the held count back to 0.
|
|
* This time the priority of this task should be disinherited back to the
|
|
* priority to which it was set while the mutex was held. This means
|
|
* the medium priority task should execute and increment the guarded
|
|
* variable. When this task next runs both the high and medium priority
|
|
* tasks will have been suspended again. */
|
|
if( xSemaphoreGive( xLocalMutex ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* Check the guarded variable did indeed increment... */
|
|
if( ulGuardedVariable != 1 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* ... and that the priority of this task has been disinherited to
|
|
* genqMUTEX_TEST_PRIORITY. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_TEST_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Set the priority of this task back to its original value, ready for
|
|
* the next loop around this test. */
|
|
vTaskPrioritySet( NULL, genqMUTEX_LOW_PRIORITY );
|
|
}
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void prvTakeTwoMutexesReturnInSameOrder( SemaphoreHandle_t xMutex,
|
|
SemaphoreHandle_t xLocalMutex )
|
|
{
|
|
/* Take the mutex. It should be available now. */
|
|
if( xSemaphoreTake( xMutex, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Set the guarded variable to a known start value. */
|
|
ulGuardedVariable = 0;
|
|
|
|
/* This task's priority should be as per that assigned when the task was
|
|
* created. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_LOW_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now unsuspend the high priority task. This will attempt to take the
|
|
* mutex, and block when it finds it cannot obtain it. */
|
|
vTaskResume( xHighPriorityMutexTask );
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* Ensure the task is reporting its priority as blocked and not
|
|
* suspended (as it would have done in versions up to V7.5.3). */
|
|
#if ( INCLUDE_eTaskGetState == 1 )
|
|
{
|
|
configASSERT( eTaskGetState( xHighPriorityMutexTask ) == eBlocked );
|
|
}
|
|
#endif /* INCLUDE_eTaskGetState */
|
|
|
|
/* This task should now have inherited the priority of the high priority
|
|
* task as by now the high priority task will have attempted to obtain the
|
|
* mutex. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_HIGH_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now unsuspend the medium priority task. This should not run as the
|
|
* inherited priority of this task is above that of the medium priority
|
|
* task. */
|
|
vTaskResume( xMediumPriorityMutexTask );
|
|
|
|
/* If the medium priority task did run then it will have incremented the
|
|
* guarded variable. */
|
|
if( ulGuardedVariable != 0 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Take the local mutex too, so two mutexes are now held. */
|
|
if( xSemaphoreTake( xLocalMutex, intsemNO_BLOCK ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* When the local semaphore is given back the priority of this task should
|
|
* not yet be disinherited because the shared mutex is still held. This is a
|
|
* simplification to allow FreeRTOS to be integrated with middleware that
|
|
* attempts to hold multiple mutexes without bloating the code with complex
|
|
* algorithms. It is possible that the high priority mutex task will
|
|
* execute as it shares a priority with this task. */
|
|
if( xSemaphoreGive( xLocalMutex ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* The guarded variable is only incremented by the medium priority task,
|
|
* which still should not have executed as this task should remain at the
|
|
* higher priority, ensure this is the case. */
|
|
if( ulGuardedVariable != 0 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_HIGH_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* Now also give back the shared mutex, taking the held count back to 0.
|
|
* This time the priority of this task should be disinherited back to the
|
|
* priority at which it was created. This means the medium priority task
|
|
* should execute and increment the guarded variable. When this task next runs
|
|
* both the high and medium priority tasks will have been suspended again. */
|
|
if( xSemaphoreGive( xMutex ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* Check the guarded variable did indeed increment... */
|
|
if( ulGuardedVariable != 1 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
/* ... and that the priority of this task has been disinherited to
|
|
* genqMUTEX_LOW_PRIORITY. */
|
|
if( uxTaskPriorityGet( NULL ) != genqMUTEX_LOW_PRIORITY )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
}
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void prvLowPriorityMutexTask( void * pvParameters )
|
|
{
|
|
SemaphoreHandle_t xMutex = ( SemaphoreHandle_t ) pvParameters, xLocalMutex;
|
|
|
|
#ifdef USE_STDIO
|
|
void vPrintDisplayMessage( const char * const * ppcMessageToSend );
|
|
|
|
const char * const pcTaskStartMsg = "Mutex with priority inheritance test started.\r\n";
|
|
|
|
/* Queue a message for printing to say the task has started. */
|
|
vPrintDisplayMessage( &pcTaskStartMsg );
|
|
#endif
|
|
|
|
/* The local mutex is used to check the 'mutex held' count. */
|
|
xLocalMutex = xSemaphoreCreateMutex();
|
|
configASSERT( xLocalMutex );
|
|
|
|
for( ; ; )
|
|
{
|
|
/* The first tests exercise the priority inheritance when two mutexes
|
|
* are taken then returned in a different order to which they were
|
|
* taken. */
|
|
prvTakeTwoMutexesReturnInDifferentOrder( xMutex, xLocalMutex );
|
|
|
|
/* Just to show this task is still running. */
|
|
ulLoopCounter2++;
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
/* The second tests exercise the priority inheritance when two mutexes
|
|
* are taken then returned in the same order in which they were taken. */
|
|
prvTakeTwoMutexesReturnInSameOrder( xMutex, xLocalMutex );
|
|
|
|
/* Just to show this task is still running. */
|
|
ulLoopCounter2++;
|
|
|
|
#if configUSE_PREEMPTION == 0
|
|
taskYIELD();
|
|
#endif
|
|
|
|
#if ( INCLUDE_xTaskAbortDelay == 1 )
|
|
{
|
|
/* Tests the behaviour when a low priority task inherits the
|
|
* priority of a high priority task only for the high priority task to
|
|
* timeout before obtaining the mutex. */
|
|
prvHighPriorityTimeout( xMutex );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void prvMediumPriorityMutexTask( void * pvParameters )
|
|
{
|
|
( void ) pvParameters;
|
|
|
|
for( ; ; )
|
|
{
|
|
/* The medium priority task starts by suspending itself. The low
|
|
* priority task will unsuspend this task when required. */
|
|
vTaskSuspend( NULL );
|
|
|
|
/* When this task unsuspends all it does is increment the guarded
|
|
* variable, this is so the low priority task knows that it has
|
|
* executed. */
|
|
ulGuardedVariable++;
|
|
}
|
|
}
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void prvHighPriorityMutexTask( void * pvParameters )
|
|
{
|
|
SemaphoreHandle_t xMutex = ( SemaphoreHandle_t ) pvParameters;
|
|
|
|
for( ; ; )
|
|
{
|
|
/* The high priority task starts by suspending itself. The low
|
|
* priority task will unsuspend this task when required. */
|
|
vTaskSuspend( NULL );
|
|
|
|
/* When this task unsuspends all it does is attempt to obtain the
|
|
* mutex. It should find the mutex is not available so a block time is
|
|
* specified. */
|
|
if( xSemaphoreTake( xMutex, portMAX_DELAY ) != pdPASS )
|
|
{
|
|
/* This task would expect to obtain the mutex unless its wait for
|
|
* the mutex was aborted. */
|
|
if( xBlockWasAborted == pdFALSE )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
else
|
|
{
|
|
xBlockWasAborted = pdFALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* When the mutex is eventually obtained it is just given back before
|
|
* returning to suspend ready for the next cycle. */
|
|
if( xSemaphoreGive( xMutex ) != pdPASS )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*-----------------------------------------------------------*/
|
|
|
|
|
|
/* This is called to check that all the created tasks are still running. */
|
|
BaseType_t xAreGenericQueueTasksStillRunning( void )
|
|
{
|
|
static uint32_t ulLastLoopCounter = 0, ulLastLoopCounter2 = 0;
|
|
|
|
/* If the demo task is still running then we expect the loop counters to
|
|
* have incremented since this function was last called. */
|
|
if( ulLastLoopCounter == ulLoopCounter )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
if( ulLastLoopCounter2 == ulLoopCounter2 )
|
|
{
|
|
xErrorDetected = pdTRUE;
|
|
}
|
|
|
|
ulLastLoopCounter = ulLoopCounter;
|
|
ulLastLoopCounter2 = ulLoopCounter2;
|
|
|
|
/* Errors detected in the task itself will have latched xErrorDetected
|
|
* to true. */
|
|
|
|
return ( BaseType_t ) !xErrorDetected;
|
|
}
|