| /* | |
| * FreeRTOS Kernel V10.2.1 | |
| * Copyright (C) 2019 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. | |
| * | |
| * http://www.FreeRTOS.org | |
| * http://aws.amazon.com/freertos | |
| * | |
| * 1 tab == 4 spaces! | |
| */ | |
| /* | |
| * 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 = pdTRUE; | |
| } | |
| /* Take the semaphore that is shared with the slave. */ | |
| if( xSemaphoreTake( xMasterSlaveMutex, intsemNO_BLOCK ) != pdPASS ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| xOkToGiveMutex = pdFALSE; | |
| /* Attempting to take again immediately should fail as the mutex is | |
| already held. */ | |
| if( xSemaphoreTake( xISRMutex, intsemNO_BLOCK ) != pdFAIL ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| /* Should still be at the priority of the slave task. */ | |
| if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| if( uxTaskPriorityGet( NULL ) != intsemMASTER_PRIORITY ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| #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 = pdTRUE; | |
| } | |
| /* Take the semaphore that is shared with the slave. */ | |
| if( xSemaphoreTake( xMasterSlaveMutex, intsemNO_BLOCK ) != pdPASS ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| xOkToGiveMutex = pdFALSE; | |
| /* Attempting to take again immediately should fail as the mutex is | |
| already held. */ | |
| if( xSemaphoreTake( xISRMutex, intsemNO_BLOCK ) != pdFAIL ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| /* Should still be at the priority of the slave task. */ | |
| if( uxTaskPriorityGet( NULL ) != intsemSLAVE_PRIORITY ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| if( uxTaskPriorityGet( NULL ) != intsemMASTER_PRIORITY ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| if( xSemaphoreGive( xMasterSlaveMutex ) != pdPASS ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| } | |
| } | |
| /*-----------------------------------------------------------*/ | |
| 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 = pdTRUE; | |
| } | |
| /* 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 = pdTRUE; | |
| } | |
| if( uxQueueSpacesAvailable( ( QueueHandle_t ) xISRCountingSemaphore ) != 0 ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| 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 = pdTRUE; | |
| } | |
| /* 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 disturbe 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; | |
| /* 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 = pdTRUE; | |
| } | |
| ulLastMasterLoopCounter = ulMasterLoops; | |
| if( ulLastCountingSemaphoreLoops == ulCountingSemaphoreLoops ) | |
| { | |
| xErrorDetected = pdTRUE; | |
| } | |
| ulLastCountingSemaphoreLoops = ulCountingSemaphoreLoops++; | |
| /* Errors detected in the task itself will have latched xErrorDetected | |
| to true. */ | |
| return ( BaseType_t ) !xErrorDetected; | |
| } | |