EBIKE-FreeRTOS/Common/Minimal/IntSemTest.c

538 lines
18 KiB
C
Raw Permalink Normal View History

2024-04-14 18:38:39 +08:00
/*
* 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
*
*/
/*
* Demonstrates and tests mutexes being used from an interrupt.
*/
#include <stdlib.h>
/* Scheduler include files. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
/* Demo program include files. */
#include "IntSemTest.h"
/*-----------------------------------------------------------*/
/* The priorities of the test tasks. */
#define intsemMASTER_PRIORITY ( tskIDLE_PRIORITY )
#define intsemSLAVE_PRIORITY ( tskIDLE_PRIORITY + 1 )
/* The rate at which the tick hook will give the mutex. */
#define intsemINTERRUPT_MUTEX_GIVE_PERIOD_MS ( 100 )
/* A block time of 0 means 'don't block'. */
#define intsemNO_BLOCK 0
/* The maximum count value for the counting semaphore given from an
* interrupt. */
#define intsemMAX_COUNT 3
/*-----------------------------------------------------------*/
/*
* The master is a task that receives a mutex that is given from an interrupt -
* although generally mutexes should not be used given in interrupts (and
* definitely never taken in an interrupt) there are some circumstances when it
* may be desirable.
*
* The slave task is just used by the master task to force priority inheritance
* on a mutex that is shared between the master and the slave - which is a
* separate mutex to that given by the interrupt.
*/
static void vInterruptMutexSlaveTask( void * pvParameters );
static void vInterruptMutexMasterTask( void * pvParameters );
/*
* A test whereby the master takes the shared and interrupt mutexes in that
* order, then gives them back in the same order, ensuring the priority
* inheritance is behaving as expected at each step.
*/
static void prvTakeAndGiveInTheSameOrder( void );
/*
* A test whereby the master takes the shared and interrupt mutexes in that
* order, then gives them back in the opposite order to which they were taken,
* ensuring the priority inheritance is behaving as expected at each step.
*/
static void prvTakeAndGiveInTheOppositeOrder( void );
/*
* A simple task that interacts with an interrupt using a counting semaphore,
* primarily for code coverage purposes.
*/
static void vInterruptCountingSemaphoreTask( void * pvParameters );
/*-----------------------------------------------------------*/
/* 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 ulMasterLoops = 0, ulCountingSemaphoreLoops = 0;
/* Handles of the test tasks that must be accessed from other test tasks. */
static TaskHandle_t xSlaveHandle;
/* A mutex which is given from an interrupt - although generally mutexes should
* not be used given in interrupts (and definitely never taken in an interrupt)
* there are some circumstances when it may be desirable. */
static SemaphoreHandle_t xISRMutex = NULL;
/* A counting semaphore which is given from an interrupt. */
static SemaphoreHandle_t xISRCountingSemaphore = NULL;
/* A mutex which is shared between the master and slave tasks - the master
* does both sharing of this mutex with the slave and receiving a mutex from the
* interrupt. */
static SemaphoreHandle_t xMasterSlaveMutex = NULL;
/* Flag that allows the master task to control when the interrupt gives or does
* not give the mutex. There is no mutual exclusion on this variable, but this is
* only test code and it should be fine in the 32=bit test environment. */
static BaseType_t xOkToGiveMutex = pdFALSE, xOkToGiveCountingSemaphore = pdFALSE;
/* Used to coordinate timing between tasks and the interrupt. */
const TickType_t xInterruptGivePeriod = pdMS_TO_TICKS( intsemINTERRUPT_MUTEX_GIVE_PERIOD_MS );
/*-----------------------------------------------------------*/
void vStartInterruptSemaphoreTasks( void )
{
/* Create the semaphores that are given from an interrupt. */
xISRMutex = xSemaphoreCreateMutex();
configASSERT( xISRMutex );
xISRCountingSemaphore = xSemaphoreCreateCounting( intsemMAX_COUNT, 0 );
configASSERT( xISRCountingSemaphore );
/* Create the mutex that is shared between the master and slave tasks (the
* master receives a mutex from an interrupt and shares a mutex with the
* slave. */
xMasterSlaveMutex = xSemaphoreCreateMutex();
configASSERT( xMasterSlaveMutex );
/* Create the tasks that share mutexes between then and with interrupts. */
xTaskCreate( vInterruptMutexSlaveTask, "IntMuS", configMINIMAL_STACK_SIZE, NULL, intsemSLAVE_PRIORITY, &xSlaveHandle );
xTaskCreate( vInterruptMutexMasterTask, "IntMuM", configMINIMAL_STACK_SIZE, NULL, intsemMASTER_PRIORITY, NULL );
/* Create the task that blocks on the counting semaphore. */
xTaskCreate( vInterruptCountingSemaphoreTask, "IntCnt", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );
}
/*-----------------------------------------------------------*/
static void vInterruptMutexMasterTask( void * pvParameters )
{
/* Just to avoid compiler warnings. */
( void ) pvParameters;
for( ; ; )
{
prvTakeAndGiveInTheSameOrder();
/* Ensure not to starve out other tests. */
ulMasterLoops++;
vTaskDelay( intsemINTERRUPT_MUTEX_GIVE_PERIOD_MS );
prvTakeAndGiveInTheOppositeOrder();
/* Ensure not to starve out other tests. */
ulMasterLoops++;
vTaskDelay( intsemINTERRUPT_MUTEX_GIVE_PERIOD_MS );
}
}
/*-----------------------------------------------------------*/
static void prvTakeAndGiveInTheSameOrder( void )
{
/* Ensure the slave is suspended, and that this task is running at the
* lower priority as expected as the start conditions. */
#if ( INCLUDE_eTaskGetState == 1 )
{
configASSERT( eTaskGetState( xSlaveHandle ) == eSuspended );
}
#endif /* INCLUDE_eTaskGetState */
if( uxTaskPriorityGet( NULL ) != intsemMASTER_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Take the semaphore that is shared with the slave. */
if( xSemaphoreTake( xMasterSlaveMutex, intsemNO_BLOCK ) != pdPASS )
{
xErrorDetected = __LINE__;
}
/* This task now has the mutex. Unsuspend the slave so it too
* attempts to take the mutex. */
vTaskResume( xSlaveHandle );
/* The slave has the higher priority so should now have executed and
* blocked on the semaphore. */
#if ( INCLUDE_eTaskGetState == 1 )
{
configASSERT( eTaskGetState( xSlaveHandle ) == eBlocked );
}
#endif /* INCLUDE_eTaskGetState */
/* This task should now have inherited the priority of the slave
* task. */
if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Now wait a little longer than the time between ISR gives to also
* obtain the ISR mutex. */
xOkToGiveMutex = pdTRUE;
if( xSemaphoreTake( xISRMutex, ( xInterruptGivePeriod * 2 ) ) != pdPASS )
{
xErrorDetected = __LINE__;
}
xOkToGiveMutex = pdFALSE;
/* Attempting to take again immediately should fail as the mutex is
* already held. */
if( xSemaphoreTake( xISRMutex, intsemNO_BLOCK ) != pdFAIL )
{
xErrorDetected = __LINE__;
}
/* Should still be at the priority of the slave task. */
if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Give back the ISR semaphore to ensure the priority is not
* disinherited as the shared mutex (which the higher priority task is
* attempting to obtain) is still held. */
if( xSemaphoreGive( xISRMutex ) != pdPASS )
{
xErrorDetected = __LINE__;
}
if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Finally give back the shared mutex. This time the higher priority
* task should run before this task runs again - so this task should have
* disinherited the priority and the higher priority task should be in the
* suspended state again. */
if( xSemaphoreGive( xMasterSlaveMutex ) != pdPASS )
{
xErrorDetected = __LINE__;
}
if( uxTaskPriorityGet( NULL ) != intsemMASTER_PRIORITY )
{
xErrorDetected = __LINE__;
}
#if ( INCLUDE_eTaskGetState == 1 )
{
configASSERT( eTaskGetState( xSlaveHandle ) == eSuspended );
}
#endif /* INCLUDE_eTaskGetState */
/* Reset the mutex ready for the next round. */
xQueueReset( xISRMutex );
}
/*-----------------------------------------------------------*/
static void prvTakeAndGiveInTheOppositeOrder( void )
{
/* Ensure the slave is suspended, and that this task is running at the
* lower priority as expected as the start conditions. */
#if ( INCLUDE_eTaskGetState == 1 )
{
configASSERT( eTaskGetState( xSlaveHandle ) == eSuspended );
}
#endif /* INCLUDE_eTaskGetState */
if( uxTaskPriorityGet( NULL ) != intsemMASTER_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Take the semaphore that is shared with the slave. */
if( xSemaphoreTake( xMasterSlaveMutex, intsemNO_BLOCK ) != pdPASS )
{
xErrorDetected = __LINE__;
}
/* This task now has the mutex. Unsuspend the slave so it too
* attempts to take the mutex. */
vTaskResume( xSlaveHandle );
/* The slave has the higher priority so should now have executed and
* blocked on the semaphore. */
#if ( INCLUDE_eTaskGetState == 1 )
{
configASSERT( eTaskGetState( xSlaveHandle ) == eBlocked );
}
#endif /* INCLUDE_eTaskGetState */
/* This task should now have inherited the priority of the slave
* task. */
if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Now wait a little longer than the time between ISR gives to also
* obtain the ISR mutex. */
xOkToGiveMutex = pdTRUE;
if( xSemaphoreTake( xISRMutex, ( xInterruptGivePeriod * 2 ) ) != pdPASS )
{
xErrorDetected = __LINE__;
}
xOkToGiveMutex = pdFALSE;
/* Attempting to take again immediately should fail as the mutex is
* already held. */
if( xSemaphoreTake( xISRMutex, intsemNO_BLOCK ) != pdFAIL )
{
xErrorDetected = __LINE__;
}
/* Should still be at the priority of the slave task. */
if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Give back the shared semaphore to ensure the priority is not disinherited
* as the ISR mutex is still held. The higher priority slave task should run
* before this task runs again. */
if( xSemaphoreGive( xMasterSlaveMutex ) != pdPASS )
{
xErrorDetected = __LINE__;
}
/* Should still be at the priority of the slave task as this task still
* holds one semaphore (this is a simplification in the priority inheritance
* mechanism. */
if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Give back the ISR semaphore, which should result in the priority being
* disinherited as it was the last mutex held. */
if( xSemaphoreGive( xISRMutex ) != pdPASS )
{
xErrorDetected = __LINE__;
}
if( uxTaskPriorityGet( NULL ) != intsemMASTER_PRIORITY )
{
xErrorDetected = __LINE__;
}
/* Reset the mutex ready for the next round. */
xQueueReset( xISRMutex );
}
/*-----------------------------------------------------------*/
static void vInterruptMutexSlaveTask( void * pvParameters )
{
/* Just to avoid compiler warnings. */
( void ) pvParameters;
for( ; ; )
{
/* This task starts by suspending itself so when it executes can be
* controlled by the master task. */
vTaskSuspend( NULL );
/* This task will execute when the master task already holds the mutex.
* Attempting to take the mutex will place this task in the Blocked
* state. */
if( xSemaphoreTake( xMasterSlaveMutex, portMAX_DELAY ) != pdPASS )
{
xErrorDetected = __LINE__;
}
if( xSemaphoreGive( xMasterSlaveMutex ) != pdPASS )
{
xErrorDetected = __LINE__;
}
}
}
/*-----------------------------------------------------------*/
static void vInterruptCountingSemaphoreTask( void * pvParameters )
{
BaseType_t xCount;
const TickType_t xDelay = pdMS_TO_TICKS( intsemINTERRUPT_MUTEX_GIVE_PERIOD_MS ) * ( intsemMAX_COUNT + 1 );
( void ) pvParameters;
for( ; ; )
{
/* Expect to start with the counting semaphore empty. */
if( uxQueueMessagesWaiting( ( QueueHandle_t ) xISRCountingSemaphore ) != 0 )
{
xErrorDetected = __LINE__;
}
/* Wait until it is expected that the interrupt will have filled the
* counting semaphore. */
xOkToGiveCountingSemaphore = pdTRUE;
vTaskDelay( xDelay );
xOkToGiveCountingSemaphore = pdFALSE;
/* Now it is expected that the counting semaphore is full. */
if( uxQueueMessagesWaiting( ( QueueHandle_t ) xISRCountingSemaphore ) != intsemMAX_COUNT )
{
xErrorDetected = __LINE__;
}
if( uxQueueSpacesAvailable( ( QueueHandle_t ) xISRCountingSemaphore ) != 0 )
{
xErrorDetected = __LINE__;
}
ulCountingSemaphoreLoops++;
/* Expect to be able to take the counting semaphore intsemMAX_COUNT
* times. A block time of 0 is used as the semaphore should already be
* there. */
xCount = 0;
while( xSemaphoreTake( xISRCountingSemaphore, 0 ) == pdPASS )
{
xCount++;
}
if( xCount != intsemMAX_COUNT )
{
xErrorDetected = __LINE__;
}
/* Now raise the priority of this task so it runs immediately that the
* semaphore is given from the interrupt. */
vTaskPrioritySet( NULL, configMAX_PRIORITIES - 1 );
/* Block to wait for the semaphore to be given from the interrupt. */
xOkToGiveCountingSemaphore = pdTRUE;
xSemaphoreTake( xISRCountingSemaphore, portMAX_DELAY );
xSemaphoreTake( xISRCountingSemaphore, portMAX_DELAY );
xOkToGiveCountingSemaphore = pdFALSE;
/* Reset the priority so as not to disturb other tests too much. */
vTaskPrioritySet( NULL, tskIDLE_PRIORITY );
ulCountingSemaphoreLoops++;
}
}
/*-----------------------------------------------------------*/
void vInterruptSemaphorePeriodicTest( void )
{
static TickType_t xLastGiveTime = 0;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
TickType_t xTimeNow;
/* No mutual exclusion on xOkToGiveMutex, but this is only test code (and
* only executed on a 32-bit architecture) so ignore that in this case. */
xTimeNow = xTaskGetTickCountFromISR();
if( ( ( TickType_t ) ( xTimeNow - xLastGiveTime ) ) >= pdMS_TO_TICKS( intsemINTERRUPT_MUTEX_GIVE_PERIOD_MS ) )
{
configASSERT( xISRMutex );
if( xOkToGiveMutex != pdFALSE )
{
/* Null is used as the second parameter in this give, and non-NULL
* in the other gives for code coverage reasons. */
xSemaphoreGiveFromISR( xISRMutex, NULL );
/* Second give attempt should fail. */
configASSERT( xSemaphoreGiveFromISR( xISRMutex, &xHigherPriorityTaskWoken ) == pdFAIL );
}
if( xOkToGiveCountingSemaphore != pdFALSE )
{
xSemaphoreGiveFromISR( xISRCountingSemaphore, &xHigherPriorityTaskWoken );
}
xLastGiveTime = xTimeNow;
}
/* Remove compiler warnings about the value being set but not used. */
( void ) xHigherPriorityTaskWoken;
}
/*-----------------------------------------------------------*/
/* This is called to check that all the created tasks are still running. */
BaseType_t xAreInterruptSemaphoreTasksStillRunning( void )
{
static uint32_t ulLastMasterLoopCounter = 0, ulLastCountingSemaphoreLoops = 0;
BaseType_t xReturn;
/* If the demo tasks are running then it is expected that the loop counters
* will have changed since this function was last called. */
if( ulLastMasterLoopCounter == ulMasterLoops )
{
xErrorDetected = __LINE__;
}
ulLastMasterLoopCounter = ulMasterLoops;
if( ulLastCountingSemaphoreLoops == ulCountingSemaphoreLoops )
{
xErrorDetected = __LINE__;
}
ulLastCountingSemaphoreLoops = ulCountingSemaphoreLoops++;
if( xErrorDetected != pdFALSE )
{
xReturn = pdFALSE;
}
else
{
xReturn = pdTRUE;
}
return xReturn;
}