/* * 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 * */ /* * This file contains some test scenarios that ensure tasks do not exit queue * send or receive functions prematurely. A description of the tests is * included within the code. */ /* Kernel includes. */ #include "FreeRTOS.h" #include "task.h" #include "queue.h" /* Demo includes. */ #include "blocktim.h" /* Task priorities and stack sizes. Allow these to be overridden. */ #ifndef bktPRIMARY_PRIORITY #define bktPRIMARY_PRIORITY ( configMAX_PRIORITIES - 3 ) #endif #ifndef bktSECONDARY_PRIORITY #define bktSECONDARY_PRIORITY ( configMAX_PRIORITIES - 4 ) #endif #ifndef bktBLOCK_TIME_TASK_STACK_SIZE #define bktBLOCK_TIME_TASK_STACK_SIZE configMINIMAL_STACK_SIZE #endif /* Task behaviour. */ #define bktQUEUE_LENGTH ( 5 ) #define bktSHORT_WAIT pdMS_TO_TICKS( ( TickType_t ) 20 ) #define bktPRIMARY_BLOCK_TIME ( 10 ) #define bktALLOWABLE_MARGIN ( 15 ) #define bktTIME_TO_BLOCK ( 175 ) #define bktDONT_BLOCK ( ( TickType_t ) 0 ) #define bktRUN_INDICATOR ( ( UBaseType_t ) 0x55 ) /* In case the demo does not have software timers enabled, as this file uses * the configTIMER_TASK_PRIORITY setting. */ #ifndef configTIMER_TASK_PRIORITY #define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 ) #endif /*-----------------------------------------------------------*/ /* * The two test tasks. Their behaviour is commented within the functions. */ static void vPrimaryBlockTimeTestTask( void * pvParameters ); static void vSecondaryBlockTimeTestTask( void * pvParameters ); /* * Very basic tests to verify the block times are as expected. */ static void prvBasicDelayTests( void ); /*-----------------------------------------------------------*/ /* The queue on which the tasks block. */ static QueueHandle_t xTestQueue; /* Handle to the secondary task is required by the primary task for calls * to vTaskSuspend/Resume(). */ static TaskHandle_t xSecondary; /* Used to ensure that tasks are still executing without error. */ static volatile BaseType_t xPrimaryCycles = 0, xSecondaryCycles = 0; static volatile BaseType_t xErrorOccurred = pdFALSE; /* Provides a simple mechanism for the primary task to know when the * secondary task has executed. */ static volatile UBaseType_t xRunIndicator; /*-----------------------------------------------------------*/ void vCreateBlockTimeTasks( void ) { /* Create the queue on which the two tasks block. */ xTestQueue = xQueueCreate( bktQUEUE_LENGTH, sizeof( BaseType_t ) ); if( xTestQueue != 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( xTestQueue, "Block_Time_Queue" ); /* Create the two test tasks. */ xTaskCreate( vPrimaryBlockTimeTestTask, "BTest1", bktBLOCK_TIME_TASK_STACK_SIZE, NULL, bktPRIMARY_PRIORITY, NULL ); xTaskCreate( vSecondaryBlockTimeTestTask, "BTest2", bktBLOCK_TIME_TASK_STACK_SIZE, NULL, bktSECONDARY_PRIORITY, &xSecondary ); } } /*-----------------------------------------------------------*/ static void vPrimaryBlockTimeTestTask( void * pvParameters ) { BaseType_t xItem, xData; TickType_t xTimeWhenBlocking; TickType_t xTimeToBlock, xBlockedTime; ( void ) pvParameters; for( ; ; ) { /********************************************************************* * Test 0 * * Basic vTaskDelay() and vTaskDelayUntil() tests. */ prvBasicDelayTests(); /********************************************************************* * Test 1 * * Simple block time wakeup test on queue receives. */ for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ ) { /* The queue is empty. Attempt to read from the queue using a block * time. When we wake, ensure the delta in time is as expected. */ xTimeToBlock = ( TickType_t ) ( bktPRIMARY_BLOCK_TIME << xItem ); xTimeWhenBlocking = xTaskGetTickCount(); /* We should unblock after xTimeToBlock having not received * anything on the queue. */ if( xQueueReceive( xTestQueue, &xData, xTimeToBlock ) != errQUEUE_EMPTY ) { xErrorOccurred = __LINE__; } /* How long were we blocked for? */ xBlockedTime = xTaskGetTickCount() - xTimeWhenBlocking; if( xBlockedTime < xTimeToBlock ) { /* Should not have blocked for less than we requested. */ xErrorOccurred = __LINE__; } if( xBlockedTime > ( xTimeToBlock + bktALLOWABLE_MARGIN ) ) { /* Should not have blocked for longer than we requested, * although we would not necessarily run as soon as we were * unblocked so a margin is allowed. */ xErrorOccurred = __LINE__; } } /********************************************************************* * Test 2 * * Simple block time wakeup test on queue sends. * * First fill the queue. It should be empty so all sends should pass. */ for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ ) { if( xQueueSend( xTestQueue, &xItem, bktDONT_BLOCK ) != pdPASS ) { xErrorOccurred = __LINE__; } #if configUSE_PREEMPTION == 0 taskYIELD(); #endif } for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ ) { /* The queue is full. Attempt to write to the queue using a block * time. When we wake, ensure the delta in time is as expected. */ xTimeToBlock = ( TickType_t ) ( bktPRIMARY_BLOCK_TIME << xItem ); xTimeWhenBlocking = xTaskGetTickCount(); /* We should unblock after xTimeToBlock having not received * anything on the queue. */ if( xQueueSend( xTestQueue, &xItem, xTimeToBlock ) != errQUEUE_FULL ) { xErrorOccurred = __LINE__; } /* How long were we blocked for? */ xBlockedTime = xTaskGetTickCount() - xTimeWhenBlocking; if( xBlockedTime < xTimeToBlock ) { /* Should not have blocked for less than we requested. */ xErrorOccurred = __LINE__; } if( xBlockedTime > ( xTimeToBlock + bktALLOWABLE_MARGIN ) ) { /* Should not have blocked for longer than we requested, * although we would not necessarily run as soon as we were * unblocked so a margin is allowed. */ xErrorOccurred = __LINE__; } } /********************************************************************* * Test 3 * * Wake the other task, it will block attempting to post to the queue. * When we read from the queue the other task will wake, but before it * can run we will post to the queue again. When the other task runs it * will find the queue still full, even though it was woken. It should * recognise that its block time has not expired and return to block for * the remains of its block time. * * Wake the other task so it blocks attempting to post to the already * full queue. */ xRunIndicator = 0; vTaskResume( xSecondary ); /* We need to wait a little to ensure the other task executes. */ while( xRunIndicator != bktRUN_INDICATOR ) { /* The other task has not yet executed. */ vTaskDelay( bktSHORT_WAIT ); } /* Make sure the other task is blocked on the queue. */ vTaskDelay( bktSHORT_WAIT ); xRunIndicator = 0; for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ ) { /* Now when we make space on the queue the other task should wake * but not execute as this task has higher priority. */ if( xQueueReceive( xTestQueue, &xData, bktDONT_BLOCK ) != pdPASS ) { xErrorOccurred = __LINE__; } /* Now fill the queue again before the other task gets a chance to * execute. If the other task had executed we would find the queue * full ourselves, and the other task have set xRunIndicator. */ if( xQueueSend( xTestQueue, &xItem, bktDONT_BLOCK ) != pdPASS ) { xErrorOccurred = __LINE__; } if( xRunIndicator == bktRUN_INDICATOR ) { /* The other task should not have executed. */ xErrorOccurred = __LINE__; } /* Raise the priority of the other task so it executes and blocks * on the queue again. */ vTaskPrioritySet( xSecondary, bktPRIMARY_PRIORITY + 2 ); /* The other task should now have re-blocked without exiting the * queue function. */ if( xRunIndicator == bktRUN_INDICATOR ) { /* The other task should not have executed outside of the * queue function. */ xErrorOccurred = __LINE__; } /* Set the priority back down. */ vTaskPrioritySet( xSecondary, bktSECONDARY_PRIORITY ); } /* Let the other task timeout. When it unblocks it will check that it * unblocked at the correct time, then suspend itself. */ while( xRunIndicator != bktRUN_INDICATOR ) { vTaskDelay( bktSHORT_WAIT ); } vTaskDelay( bktSHORT_WAIT ); xRunIndicator = 0; /********************************************************************* * Test 4 * * As per test 3 - but with the send and receive the other way around. * The other task blocks attempting to read from the queue. * * Empty the queue. We should find that it is full. */ for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ ) { if( xQueueReceive( xTestQueue, &xData, bktDONT_BLOCK ) != pdPASS ) { xErrorOccurred = __LINE__; } } /* Wake the other task so it blocks attempting to read from the * already empty queue. */ vTaskResume( xSecondary ); /* We need to wait a little to ensure the other task executes. */ while( xRunIndicator != bktRUN_INDICATOR ) { vTaskDelay( bktSHORT_WAIT ); } vTaskDelay( bktSHORT_WAIT ); xRunIndicator = 0; for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ ) { /* Now when we place an item on the queue the other task should * wake but not execute as this task has higher priority. */ if( xQueueSend( xTestQueue, &xItem, bktDONT_BLOCK ) != pdPASS ) { xErrorOccurred = __LINE__; } /* Now empty the queue again before the other task gets a chance to * execute. If the other task had executed we would find the queue * empty ourselves, and the other task would be suspended. */ if( xQueueReceive( xTestQueue, &xData, bktDONT_BLOCK ) != pdPASS ) { xErrorOccurred = __LINE__; } if( xRunIndicator == bktRUN_INDICATOR ) { /* The other task should not have executed. */ xErrorOccurred = __LINE__; } /* Raise the priority of the other task so it executes and blocks * on the queue again. */ vTaskPrioritySet( xSecondary, bktPRIMARY_PRIORITY + 2 ); /* The other task should now have re-blocked without exiting the * queue function. */ if( xRunIndicator == bktRUN_INDICATOR ) { /* The other task should not have executed outside of the * queue function. */ xErrorOccurred = __LINE__; } vTaskPrioritySet( xSecondary, bktSECONDARY_PRIORITY ); } /* Let the other task timeout. When it unblocks it will check that it * unblocked at the correct time, then suspend itself. */ while( xRunIndicator != bktRUN_INDICATOR ) { vTaskDelay( bktSHORT_WAIT ); } vTaskDelay( bktSHORT_WAIT ); xPrimaryCycles++; } } /*-----------------------------------------------------------*/ static void vSecondaryBlockTimeTestTask( void * pvParameters ) { TickType_t xTimeWhenBlocking, xBlockedTime; BaseType_t xData; ( void ) pvParameters; for( ; ; ) { /********************************************************************* * Test 0, 1 and 2 * * This task does not participate in these tests. */ vTaskSuspend( NULL ); /********************************************************************* * Test 3 * * The first thing we do is attempt to read from the queue. It should be * full so we block. Note the time before we block so we can check the * wake time is as per that expected. */ xTimeWhenBlocking = xTaskGetTickCount(); /* We should unblock after bktTIME_TO_BLOCK having not sent anything to * the queue. */ xData = 0; xRunIndicator = bktRUN_INDICATOR; if( xQueueSend( xTestQueue, &xData, bktTIME_TO_BLOCK ) != errQUEUE_FULL ) { xErrorOccurred = __LINE__; } /* How long were we inside the send function? */ xBlockedTime = xTaskGetTickCount() - xTimeWhenBlocking; /* We should not have blocked for less time than bktTIME_TO_BLOCK. */ if( xBlockedTime < bktTIME_TO_BLOCK ) { xErrorOccurred = __LINE__; } /* We should of not blocked for much longer than bktALLOWABLE_MARGIN * either. A margin is permitted as we would not necessarily run as * soon as we unblocked. */ if( xBlockedTime > ( bktTIME_TO_BLOCK + bktALLOWABLE_MARGIN ) ) { xErrorOccurred = __LINE__; } /* Suspend ready for test 3. */ xRunIndicator = bktRUN_INDICATOR; vTaskSuspend( NULL ); /********************************************************************* * Test 4 * * As per test three, but with the send and receive reversed. */ xTimeWhenBlocking = xTaskGetTickCount(); /* We should unblock after bktTIME_TO_BLOCK having not received * anything on the queue. */ xRunIndicator = bktRUN_INDICATOR; if( xQueueReceive( xTestQueue, &xData, bktTIME_TO_BLOCK ) != errQUEUE_EMPTY ) { xErrorOccurred = __LINE__; } xBlockedTime = xTaskGetTickCount() - xTimeWhenBlocking; /* We should not have blocked for less time than bktTIME_TO_BLOCK. */ if( xBlockedTime < bktTIME_TO_BLOCK ) { xErrorOccurred = __LINE__; } /* We should of not blocked for much longer than bktALLOWABLE_MARGIN * either. A margin is permitted as we would not necessarily run as soon * as we unblocked. */ if( xBlockedTime > ( bktTIME_TO_BLOCK + bktALLOWABLE_MARGIN ) ) { xErrorOccurred = __LINE__; } xRunIndicator = bktRUN_INDICATOR; xSecondaryCycles++; } } /*-----------------------------------------------------------*/ static void prvBasicDelayTests( void ) { TickType_t xPreTime, xPostTime, x, xLastUnblockTime, xExpectedUnblockTime; const TickType_t xPeriod = 75, xCycles = 5, xAllowableMargin = ( bktALLOWABLE_MARGIN >> 1 ), xHalfPeriod = xPeriod / ( TickType_t ) 2; BaseType_t xDidBlock; /* Temporarily increase priority so the timing is more accurate, but not so * high as to disrupt the timer tests. */ vTaskPrioritySet( NULL, configTIMER_TASK_PRIORITY - 1 ); /* Crude check to too see that vTaskDelay() blocks for the expected * period. */ xPreTime = xTaskGetTickCount(); vTaskDelay( bktTIME_TO_BLOCK ); xPostTime = xTaskGetTickCount(); /* The priority is higher, so the allowable margin is halved when compared * to the other tests in this file. */ if( ( xPostTime - xPreTime ) > ( bktTIME_TO_BLOCK + xAllowableMargin ) ) { xErrorOccurred = __LINE__; } /* Now crude tests to check the vTaskDelayUntil() functionality. */ xPostTime = xTaskGetTickCount(); xLastUnblockTime = xPostTime; for( x = 0; x < xCycles; x++ ) { /* Calculate the next expected unblock time from the time taken before * this loop was entered. */ xExpectedUnblockTime = xPostTime + ( x * xPeriod ); vTaskDelayUntil( &xLastUnblockTime, xPeriod ); if( ( xTaskGetTickCount() - xExpectedUnblockTime ) > ( bktTIME_TO_BLOCK + xAllowableMargin ) ) { xErrorOccurred = __LINE__; } xPrimaryCycles++; } /* Crude tests for return value of xTaskDelayUntil(). First a standard block * should return that the task does block. */ xDidBlock = xTaskDelayUntil( &xLastUnblockTime, xPeriod ); if( xDidBlock != pdTRUE ) { xErrorOccurred = __LINE__; } /* Now delay a few ticks so repeating the above block period will not block for * the full amount of time, but will still block. */ vTaskDelay( xHalfPeriod ); xDidBlock = xTaskDelayUntil( &xLastUnblockTime, xPeriod ); if( xDidBlock != pdTRUE ) { xErrorOccurred = __LINE__; } /* This time block for longer than xPeriod before calling xTaskDelayUntil() so * the call to xTaskDelayUntil() should not block. */ vTaskDelay( xPeriod ); xDidBlock = xTaskDelayUntil( &xLastUnblockTime, xPeriod ); if( xDidBlock != pdFALSE ) { xErrorOccurred = __LINE__; } /* Catch up. */ xDidBlock = xTaskDelayUntil( &xLastUnblockTime, xPeriod ); if( xDidBlock != pdTRUE ) { xErrorOccurred = __LINE__; } /* Again block for slightly longer than a period so ensure the time is in the * past next time xTaskDelayUntil() gets called. */ vTaskDelay( xPeriod + xAllowableMargin ); xDidBlock = xTaskDelayUntil( &xLastUnblockTime, xPeriod ); if( xDidBlock != pdFALSE ) { xErrorOccurred = __LINE__; } /* Reset to the original task priority ready for the other tests. */ vTaskPrioritySet( NULL, bktPRIMARY_PRIORITY ); } /*-----------------------------------------------------------*/ BaseType_t xAreBlockTimeTestTasksStillRunning( void ) { static BaseType_t xLastPrimaryCycleCount = 0, xLastSecondaryCycleCount = 0; BaseType_t xReturn = pdPASS; /* Have both tasks performed at least one cycle since this function was * last called? */ if( xPrimaryCycles == xLastPrimaryCycleCount ) { xReturn = pdFAIL; } if( xSecondaryCycles == xLastSecondaryCycleCount ) { xReturn = pdFAIL; } if( xErrorOccurred != pdFALSE ) { xReturn = pdFAIL; } xLastSecondaryCycleCount = xSecondaryCycles; xLastPrimaryCycleCount = xPrimaryCycles; return xReturn; }