| /* | |
| * FreeRTOS Task Pool v1.0.0 | |
| * 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. | |
| */ | |
| /** | |
| * @file iot_taskpool_freertos.c | |
| * @brief Implements the task pool functions in iot_taskpool.h for FreeRTOS. | |
| */ | |
| /* | |
| * The full IoT Task Pool Library has many use cases, including Linux development. | |
| * Typical FreeRTOS use cases do not require the full functionality so an optimized | |
| * implementation is provided specifically for use with FreeRTOS. The optimized | |
| * version has a fixed number of tasks in the pool, each of which uses statically | |
| * allocated memory to ensure creation of the pool is guaranteed (it does not run out | |
| * of heap space). The constant IOT_TASKPOOL_NUMBER_OF_WORKERS sets the number of | |
| * tasks in the pool. | |
| * | |
| * Unlike the full version, this optimized version: | |
| * + Only supports a single task pool (system task pool) at a time. | |
| * + Does not auto-scale by dynamically adding more tasks if the number of | |
| * tasks in the pool becomes exhausted. The number of tasks in the pool | |
| * are fixed at compile time. See the task pool configuration options for | |
| * more information. | |
| * + Cannot be shut down - it exists for the lifetime of the application. | |
| * | |
| * Users who are interested in the functionality of the full IoT Task Pool | |
| * library can us it in place of this optimized version. | |
| */ | |
| /* Kernel includes. */ | |
| #include "FreeRTOS.h" | |
| #include "semphr.h" | |
| /* IoT libraries includes. */ | |
| #include "iot_config.h" | |
| /* Standard includes. */ | |
| #include <stdbool.h> | |
| #include <stdio.h> | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <string.h> | |
| #if !defined( configSUPPORT_STATIC_ALLOCATION ) || ( configSUPPORT_STATIC_ALLOCATION != 1 ) | |
| #error configSUPPORT_STATIC_ALLOCATION must be set to 1 in FreeRTOSConfig.h to build this file. | |
| #endif | |
| /* Task pool internal include. */ | |
| #include "private/iot_taskpool_internal_freertos.h" | |
| /** | |
| * @brief Maximum semaphore value for wait operations. | |
| */ | |
| #define TASKPOOL_MAX_SEM_VALUE ( ( UBaseType_t ) 0xFFFF ) | |
| /** | |
| * @brief Reschedule delay in milliseconds for deferred jobs. | |
| */ | |
| #define TASKPOOL_JOB_RESCHEDULE_DELAY_MS ( 10ULL ) | |
| /* ---------------------------------------------------------------------------------- */ | |
| /** | |
| * @brief Get the job associated with a timer. | |
| * | |
| * @warning This only works on a _taskPoolTimerEvent_t within a _taskPoolJob_t. | |
| */ | |
| #define GET_JOB_FROM_TIMER(t) ((_taskPoolJob_t *)((uint8_t*)(t) - offsetof(_taskPoolJob_t, timer))) | |
| /** | |
| * brief The maximum time to wait when attempting to obtain an internal semaphore. | |
| * Don't wait indefinitely to give the application a chance to recover in the case | |
| * of an error. | |
| */ | |
| #define MAX_SEMAPHORE_TAKE_WAIT_TIME_MS ( pdMS_TO_TICKS( 10000UL ) ) | |
| /* ---------------------------------------------------------------------------------- */ | |
| /** | |
| * Doxygen should ignore this section. | |
| * | |
| * @brief The system task pool handle for all libraries to use. | |
| * User application can use the system task pool as well knowing that the usage will be shared with | |
| * the system libraries as well. The system task pool needs to be initialized before any library is used or | |
| * before any code that posts jobs to the task pool runs. | |
| */ | |
| static _taskPool_t _IotSystemTaskPool = { 0 }; | |
| /* -------------- Convenience functions to create/recycle/destroy jobs -------------- */ | |
| /** | |
| * @brief Initialize a job. | |
| * | |
| * @param[in] pJob The job to initialize. | |
| * @param[in] userCallback The user callback for the job. | |
| * @param[in] pUserContext The context to be passed to the callback. | |
| */ | |
| static void _initializeJob( _taskPoolJob_t * const pJob, | |
| IotTaskPoolRoutine_t userCallback, | |
| void * pUserContext ); | |
| /* -------------- The worker thread procedure for a task pool thread -------------- */ | |
| /** | |
| * The procedure for a task pool worker thread. | |
| * | |
| * @param[in] pUserContext The user context. | |
| * | |
| */ | |
| static void _taskPoolWorker( void * pUserContext ); | |
| /* -------------- Convenience functions to handle timer events -------------- */ | |
| /** | |
| * Comparer for the time list. | |
| * | |
| * param[in] pTimerEventLink1 The link to the first timer event. | |
| * param[in] pTimerEventLink1 The link to the first timer event. | |
| */ | |
| static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, | |
| const IotLink_t * const pTimerEventLink2 ); | |
| /** | |
| * Reschedules the timer for handling deferred jobs to the next timeout. | |
| * | |
| * param[in] timer The timer to reschedule. | |
| * param[in] pFirstTimerEvent The timer event that carries the timeout and job information. | |
| */ | |
| static void _rescheduleDeferredJobsTimer( TimerHandle_t const timer, | |
| _taskPoolTimerEvent_t * const pFirstTimerEvent ); | |
| /** | |
| * The task pool timer procedure for scheduling deferred jobs. | |
| * | |
| * param[in] timer The timer to handle. | |
| */ | |
| static void _timerCallback( TimerHandle_t xTimer ); | |
| /* -------------- Convenience functions to create/initialize/destroy the task pool -------------- */ | |
| /** | |
| * Initializes a pre-allocated instance of a task pool. | |
| */ | |
| static void _initTaskPoolControlStructures( void ); | |
| /** | |
| * Initializes a pre-allocated instance of a task pool. | |
| * | |
| * @param[in] pInfo The initialization information for the task pool. | |
| * | |
| */ | |
| static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo ); | |
| /** | |
| * Places a job in the dispatch queue. | |
| * | |
| * @param[in] pJob The job to schedule. | |
| * | |
| */ | |
| static void _scheduleInternal( _taskPoolJob_t * const pJob ); | |
| /** | |
| * Matches a deferred job in the timer queue with its timer event wrapper. | |
| * | |
| * @param[in] pLink A pointer to the timer event link in the timer queue. | |
| * @param[in] pMatch A pointer to the job to match. | |
| * | |
| */ | |
| static bool _matchJobByPointer( const IotLink_t * const pLink, | |
| void * pMatch ); | |
| /** | |
| * Tries to cancel a job. | |
| * | |
| * @param[in] pJob The job to cancel. | |
| * @param[out] pStatus The status of the job at the time of cancellation. | |
| * | |
| */ | |
| static IotTaskPoolError_t _tryCancelInternal( _taskPoolJob_t * const pJob, | |
| IotTaskPoolJobStatus_t * const pStatus ); | |
| /* ---------------------------------------------------------------------------------------------- */ | |
| IotTaskPoolError_t IotTaskPool_CreateSystemTaskPool( const IotTaskPoolInfo_t * const pInfo ) | |
| { | |
| IotTaskPoolError_t status; | |
| static BaseType_t isInitialized = pdFALSE; | |
| configASSERT( pInfo ); | |
| configASSERT( pInfo->minThreads >= 1UL ); | |
| /* This version of the task pool does not auto-scale so the specified minimum | |
| number of threads in the pool must match the specified maximum number of threads | |
| in the pool. */ | |
| configASSERT( pInfo->maxThreads == pInfo->minThreads ); | |
| /* Guard against multiple attempts to create the system task pool in case | |
| this function is called by more than one library initialization routine. */ | |
| taskENTER_CRITICAL(); | |
| { | |
| /* If the task pool has already been initialized then | |
| IOT_TASKPOOL_ILLEGAL_OPERATION will be returned - but that does not guarantee | |
| the initialization operation is complete as the task to which | |
| IOT_TASKPOOL_ILLEGAL_OPERATION is returned may have preempted the task that | |
| was performing the initialization before the initialization was complete - | |
| hence the assert to catch this occurrence during development and debug. */ | |
| configASSERT( isInitialized == pdFALSE ); | |
| if( isInitialized == pdFALSE ) | |
| { | |
| /* The task pool has not been initialized already so will be initialized | |
| now. */ | |
| status = IOT_TASKPOOL_SUCCESS; | |
| isInitialized = pdTRUE; | |
| } | |
| else | |
| { | |
| /* This function has already been called but executing this path does | |
| not guarantee the task pool has already been initialized as the task | |
| to which this error is returned may have preempted the task that was | |
| performing the initialization before the initialization was complete. */ | |
| status = IOT_TASKPOOL_ILLEGAL_OPERATION; | |
| } | |
| } | |
| taskEXIT_CRITICAL(); | |
| if( status == IOT_TASKPOOL_SUCCESS ) | |
| { | |
| /* Create the system task pool. Note in this version _createTaskPool() | |
| cannot fail because it is using statically allocated memory. Therefore the | |
| return value can be safely ignored and there is no need to consider resetting | |
| isInitialized in a failure case. */ | |
| ( void ) _createTaskPool( pInfo ); | |
| } | |
| return status; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| IotTaskPoolError_t IotTaskPool_CreateJob( IotTaskPoolRoutine_t userCallback, | |
| void * pUserContext, | |
| IotTaskPoolJobStorage_t * const pJobStorage, | |
| IotTaskPoolJob_t * const ppJob ) | |
| { | |
| /* Parameter checking. */ | |
| configASSERT( userCallback != NULL ); | |
| configASSERT( pJobStorage != NULL ); | |
| configASSERT( ppJob != NULL ); | |
| /* Build a job around the user-provided storage. */ | |
| _initializeJob( ( _taskPoolJob_t * ) pJobStorage, userCallback, pUserContext ); | |
| *ppJob = ( IotTaskPoolJob_t ) pJobStorage; | |
| return IOT_TASKPOOL_SUCCESS; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| IotTaskPoolError_t IotTaskPool_Schedule( IotTaskPool_t taskPoolHandle, | |
| IotTaskPoolJob_t pJob, | |
| uint32_t flags ) | |
| { | |
| IotTaskPoolError_t status = IOT_TASKPOOL_SUCCESS; | |
| /* Task pool must have been created. */ | |
| configASSERT( _IotSystemTaskPool.running != false ); | |
| /* This lean version of the task pool only supports the task pool created | |
| by this library (the system task pool). NULL means use the system task | |
| pool - no other values are allowed. Use the full implementation of this | |
| library if you want multiple task pools (there is more than one task in | |
| each pool. */ | |
| configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) ); | |
| /* Avoid compiler warnings about unused parameters if configASSERT() is not | |
| defined. */ | |
| ( void ) taskPoolHandle; | |
| configASSERT( pJob != NULL ); | |
| configASSERT( ( flags == 0UL ) || ( flags == IOT_TASKPOOL_JOB_HIGH_PRIORITY ) ); | |
| /* Acquire the mutex for manipulating the job timer queue. */ | |
| if ( xSemaphoreTake( _IotSystemTaskPool.xTimerEventMutex, MAX_SEMAPHORE_TAKE_WAIT_TIME_MS ) == pdTRUE ) | |
| { | |
| _scheduleInternal( pJob ); | |
| if ( xSemaphoreGive( _IotSystemTaskPool.xTimerEventMutex ) == pdFALSE ) | |
| { | |
| /* This can only be reached if semaphores are configured incorrectly. */ | |
| status = IOT_TASKPOOL_GENERAL_FAILURE; | |
| } | |
| /* Signal a worker task that a job was queued. */ | |
| if ( xSemaphoreGive( _IotSystemTaskPool.dispatchSignal ) == pdFALSE ) | |
| { | |
| /* This can only be reached if semaphores are configured incorrectly. */ | |
| status = IOT_TASKPOOL_GENERAL_FAILURE; | |
| } | |
| } | |
| else | |
| { | |
| status = IOT_TASKPOOL_GENERAL_FAILURE; | |
| } | |
| return status; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| IotTaskPoolError_t IotTaskPool_ScheduleDeferred( IotTaskPool_t taskPoolHandle, | |
| IotTaskPoolJob_t job, | |
| uint32_t timeMs ) | |
| { | |
| TickType_t now; | |
| IotTaskPoolError_t status = IOT_TASKPOOL_SUCCESS; | |
| /* This lean version of the task pool only supports the task pool created | |
| by this library (the system task pool). NULL means use the system task | |
| pool - no other values are allowed. Use the full implementation of this | |
| library if you want multiple task pools (there is more than one task in | |
| each pool. */ | |
| configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) ); | |
| configASSERT( job != NULL ); | |
| /* If the timer period is zero, just immediately queue the job for execution. */ | |
| if( timeMs == 0UL ) | |
| { | |
| status = IotTaskPool_Schedule( &_IotSystemTaskPool, job, 0 ); | |
| } | |
| else | |
| { | |
| _taskPoolTimerEvent_t* pTimerEvent = &(job->timer); | |
| configASSERT( job->timer.link.pNext == NULL ); | |
| IotLink_t* pTimerEventLink; | |
| pTimerEvent->link.pNext = NULL; | |
| pTimerEvent->link.pPrevious = NULL; | |
| now = xTaskGetTickCount(); | |
| pTimerEvent->expirationTime = now + pdMS_TO_TICKS( timeMs ); | |
| if ( xSemaphoreTake( _IotSystemTaskPool.xTimerEventMutex, MAX_SEMAPHORE_TAKE_WAIT_TIME_MS ) == pdTRUE ) | |
| { | |
| /* Append the timer event to the timer list. */ | |
| IotListDouble_InsertSorted( &( _IotSystemTaskPool.timerEventsList ), &( pTimerEvent->link ), _timerEventCompare ); | |
| /* Update the job status to 'scheduled'. */ | |
| job->status = IOT_TASKPOOL_STATUS_DEFERRED; | |
| /* Peek the first event in the timer event list. There must be at least one, | |
| * since we just inserted it. */ | |
| pTimerEventLink = IotListDouble_PeekHead( &( _IotSystemTaskPool.timerEventsList ) ); | |
| configASSERT( pTimerEventLink != NULL ); | |
| /* If the event we inserted is at the front of the queue, then | |
| * we need to reschedule the underlying timer. */ | |
| if ( pTimerEventLink == &( pTimerEvent->link ) ) | |
| { | |
| pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pTimerEventLink, link ); | |
| _rescheduleDeferredJobsTimer( _IotSystemTaskPool.timer, pTimerEvent ); | |
| } | |
| if ( xSemaphoreGive( _IotSystemTaskPool.xTimerEventMutex ) == pdFALSE ) | |
| { | |
| /* This can only be reached if semaphores are configured incorrectly. */ | |
| status = IOT_TASKPOOL_GENERAL_FAILURE; | |
| } | |
| } | |
| else | |
| { | |
| status = IOT_TASKPOOL_GENERAL_FAILURE; | |
| } | |
| } | |
| return status; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| IotTaskPoolError_t IotTaskPool_GetStatus( IotTaskPool_t taskPoolHandle, | |
| IotTaskPoolJob_t job, | |
| IotTaskPoolJobStatus_t * const pStatus ) | |
| { | |
| /* This lean version of the task pool only supports the task pool created by | |
| this library (the system task pool). NULL means use the system task pool - | |
| no other values are allowed. Use the full implementation of this library if you | |
| want multiple task pools (there is more than one task in each pool. */ | |
| configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) ); | |
| /* Remove warning about unused parameter. */ | |
| ( void ) taskPoolHandle; | |
| /* Parameter checking. */ | |
| configASSERT( job != NULL ); | |
| configASSERT( pStatus != NULL ); | |
| taskENTER_CRITICAL(); | |
| { | |
| *pStatus = job->status; | |
| } | |
| taskEXIT_CRITICAL(); | |
| return IOT_TASKPOOL_SUCCESS; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| IotTaskPoolError_t IotTaskPool_TryCancel( IotTaskPool_t taskPoolHandle, | |
| IotTaskPoolJob_t job, | |
| IotTaskPoolJobStatus_t * const pStatus ) | |
| { | |
| IotTaskPoolError_t status = IOT_TASKPOOL_SUCCESS; | |
| const TickType_t dontBlock = ( TickType_t ) 0; | |
| /* This lean version of the task pool only supports the task pool created | |
| by this library (the system task pool). NULL means use the system task | |
| pool - no other values are allowed. Use the full implementation of this | |
| library if you want multiple task pools (there is more than one task in | |
| each pool. */ | |
| configASSERT( ( taskPoolHandle == NULL ) || ( taskPoolHandle == &_IotSystemTaskPool ) ); | |
| if( job != NULL ) | |
| { | |
| if( pStatus != NULL ) | |
| { | |
| *pStatus = IOT_TASKPOOL_STATUS_UNDEFINED; | |
| } | |
| if ( xSemaphoreTake( _IotSystemTaskPool.xTimerEventMutex, dontBlock ) != pdFALSE ) | |
| { | |
| status = _tryCancelInternal( job, pStatus ); | |
| if ( xSemaphoreGive( _IotSystemTaskPool.xTimerEventMutex ) == pdFALSE ) | |
| { | |
| /* This can only be reached if semaphores are configured incorrectly. */ | |
| status = IOT_TASKPOOL_GENERAL_FAILURE; | |
| } | |
| } | |
| else | |
| { | |
| /* If we fail to take the semaphore, just abort the cancel. */ | |
| status = IOT_TASKPOOL_CANCEL_FAILED; | |
| } | |
| } | |
| else | |
| { | |
| status = IOT_TASKPOOL_BAD_PARAMETER; | |
| } | |
| return status; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| IotTaskPoolJobStorage_t * IotTaskPool_GetJobStorageFromHandle( IotTaskPoolJob_t pJob ) | |
| { | |
| return ( IotTaskPoolJobStorage_t * ) pJob; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| const char * IotTaskPool_strerror( IotTaskPoolError_t status ) | |
| { | |
| const char * pMessage = NULL; | |
| switch( status ) | |
| { | |
| case IOT_TASKPOOL_SUCCESS: | |
| pMessage = "SUCCESS"; | |
| break; | |
| case IOT_TASKPOOL_BAD_PARAMETER: | |
| pMessage = "BAD PARAMETER"; | |
| break; | |
| case IOT_TASKPOOL_ILLEGAL_OPERATION: | |
| pMessage = "ILLEGAL OPERATION"; | |
| break; | |
| case IOT_TASKPOOL_NO_MEMORY: | |
| pMessage = "NO MEMORY"; | |
| break; | |
| case IOT_TASKPOOL_SHUTDOWN_IN_PROGRESS: | |
| pMessage = "SHUTDOWN IN PROGRESS"; | |
| break; | |
| case IOT_TASKPOOL_CANCEL_FAILED: | |
| pMessage = "CANCEL FAILED"; | |
| break; | |
| case IOT_TASKPOOL_GENERAL_FAILURE: | |
| pMessage = "GENERAL FAILURE"; | |
| break; | |
| default: | |
| pMessage = "INVALID STATUS"; | |
| break; | |
| } | |
| return pMessage; | |
| } | |
| /* ---------------------------------------------------------------------------------------------- */ | |
| /* ---------------------------------------------------------------------------------------------- */ | |
| /* ---------------------------------------------------------------------------------------------- */ | |
| static void _initTaskPoolControlStructures( void ) | |
| { | |
| /* Initialize a job data structures that require no de-initialization. | |
| * All other data structures carry a value of 'NULL' before initialization. | |
| */ | |
| IotDeQueue_Create( &( _IotSystemTaskPool.dispatchQueue ) ); | |
| IotListDouble_Create( &( _IotSystemTaskPool.timerEventsList ) ); | |
| /* Initialize the semaphore for waiting for incoming work. Cannot fail as | |
| statically allocated. */ | |
| _IotSystemTaskPool.dispatchSignal = xSemaphoreCreateCountingStatic( TASKPOOL_MAX_SEM_VALUE, 0, &( _IotSystemTaskPool.dispatchSignalBuffer ) ); | |
| _IotSystemTaskPool.xTimerEventMutex = xSemaphoreCreateMutexStatic( &( _IotSystemTaskPool.xTimerEventMutexBuffer ) ); | |
| } | |
| static IotTaskPoolError_t _createTaskPool( const IotTaskPoolInfo_t * const pInfo ) | |
| { | |
| /* The taskpool will create a number of threads equal to the minThreads | |
| setting. The number of workers should be equal to avoid over/under | |
| allocation. */ | |
| configASSERT( IOT_TASKPOOL_NUMBER_OF_WORKERS == pInfo->minThreads ); | |
| /* Static TCB structures and arrays to be used by statically allocated worker | |
| tasks. */ | |
| static StaticTask_t workerTaskTCBs[ IOT_TASKPOOL_NUMBER_OF_WORKERS ]; | |
| static StackType_t workerTaskStacks[ IOT_TASKPOOL_NUMBER_OF_WORKERS ][ IOT_TASKPOOL_WORKER_STACK_SIZE_BYTES / sizeof( portSTACK_TYPE ) ]; | |
| /* Static structure to hold the software timer. */ | |
| static StaticTimer_t staticTimer; | |
| uint32_t threadsCreated = 0; /* Although initialized before use removing the initializer here results in compiler warnings. */ | |
| char taskName[ 10 ]; | |
| /* This assert is primarily to catch the function being called more than once, | |
| but will also ensure the C start up code has zeroed out the structure | |
| correctly. */ | |
| #if( configASSERT_DEFINED == 1 ) | |
| { | |
| size_t x; | |
| uint8_t *pucNextByte = ( uint8_t * ) &_IotSystemTaskPool; | |
| for( x = 0; x < sizeof( _taskPool_t ); x++ ) | |
| { | |
| configASSERT( pucNextByte[ x ] == ( uint8_t ) 0x00 ); | |
| } | |
| } | |
| #endif /* configASSERT_DEFINED */ | |
| /* Initialize all internal data structure prior to creating all threads. */ | |
| _initTaskPoolControlStructures(); | |
| /* Create the FreeRTOS timer for managing Task Pool timers. */ | |
| _IotSystemTaskPool.timer = xTimerCreateStatic( NULL, /* Text name for the timer, only used for debugging. */ | |
| portMAX_DELAY, /* Timer period in ticks. */ | |
| pdFALSE, /* pdFALSE means its a one-shot timer. */ | |
| ( void * ) &_IotSystemTaskPool, /* Parameter passed into callback. */ | |
| _timerCallback, /* Callback that executes when the timer expires. */ | |
| &staticTimer ); /* Static storage for the timer's data structure. */ | |
| /* The task pool will initialize the minimum number of threads requested by the user upon start. | |
| Note this tailored version of the task pool does not auto-scale, but fixes the number of tasks | |
| in the pool to the originally specified minimum, and the specified maximum value is ignored. */ | |
| /* Create the minimum number of threads specified by the user, and if one fails shutdown and return error. */ | |
| for( threadsCreated = 0; threadsCreated < pInfo->minThreads; ) | |
| { | |
| /* Generate a unique name for the task. */ | |
| snprintf( taskName, sizeof( taskName ), "pool%d", ( int ) threadsCreated ); | |
| xTaskCreateStatic( _taskPoolWorker, /* Function that implements the task. */ | |
| taskName, /* Text name for the task, used for debugging only. */ | |
| IOT_TASKPOOL_WORKER_STACK_SIZE_BYTES / sizeof( portSTACK_TYPE ), /* xTaskCreate() expects the stack size to be specified in words. */ | |
| &_IotSystemTaskPool, /* Parameter passed into the task. */ | |
| pInfo->priority, /* Priority at which the task starts running. */ | |
| &( workerTaskStacks[ threadsCreated ][ 0 ] ), /* Pointer to static storage for the task's stack. */ | |
| &( workerTaskTCBs[ threadsCreated ] ) ); /* Pointer to static storage for the task's TCB. */ | |
| ++threadsCreated; | |
| } | |
| _IotSystemTaskPool.running = true; | |
| /* This version of this function cannot fail as all the memory is allocated | |
| statically at compile time. */ | |
| return IOT_TASKPOOL_SUCCESS; | |
| } | |
| /* ---------------------------------------------------------------------------------------------- */ | |
| static void _taskPoolWorker( void * pUserContext ) | |
| { | |
| configASSERT( pUserContext != NULL ); | |
| IotTaskPoolRoutine_t userCallback = NULL; | |
| /* Extract pTaskPool pointer from context. */ | |
| _taskPool_t * pTaskPool = ( _taskPool_t * ) pUserContext; | |
| /* OUTER LOOP: it controls the lifetime of the worker thread. */ | |
| for( ; ; ) | |
| { | |
| IotLink_t * pFirst = NULL; | |
| _taskPoolJob_t * pJob = NULL; | |
| /* Wait on incoming notifications... */ | |
| configASSERT( pTaskPool->dispatchSignal ); | |
| /* If the semaphore for job dispatch expires without a job, a critical | |
| precondition of this task has not been met. See the xBlockTime | |
| parameter of xSemaphoreTake for details. */ | |
| configASSERT( xSemaphoreTake( pTaskPool->dispatchSignal, portMAX_DELAY ) ); | |
| /* Acquire the lock to check for incoming notifications. This call | |
| should not expire. See the xBlockTime parameter of xSemaphoreTake | |
| for details. */ | |
| configASSERT( xSemaphoreTake( pTaskPool->xTimerEventMutex, portMAX_DELAY ) ); | |
| /* Dequeue the first job in FIFO order. */ | |
| pFirst = IotDeQueue_DequeueHead( &pTaskPool->dispatchQueue ); | |
| /* If there is indeed a job, then update status under lock, and release the lock before processing the job. */ | |
| if( pFirst != NULL ) | |
| { | |
| /* Extract the job from its link. */ | |
| pJob = IotLink_Container( _taskPoolJob_t, pFirst, link ); | |
| /* Update status to 'completed' to indicate it is queued for execution. */ | |
| pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; | |
| userCallback = pJob->userCallback; | |
| } | |
| /* Release the lock now that the job dispatch queue has been checked. | |
| This call should not expire. See the xBlockTime parameter of | |
| xSemaphoreTake for details. */ | |
| configASSERT( xSemaphoreGive( pTaskPool->xTimerEventMutex ) ); | |
| /* INNER LOOP: it controls the execution of jobs: the exit condition is the lack of a job to execute. */ | |
| while( pJob != NULL ) | |
| { | |
| /* Process the job by invoking the associated callback with the user context. | |
| * This task pool thread will not be available until the user callback returns. | |
| */ | |
| { | |
| configASSERT( IotLink_IsLinked( &pJob->link ) == false ); | |
| configASSERT( userCallback != NULL ); | |
| userCallback( pTaskPool, pJob, pJob->pUserContext ); | |
| /* This job is finished, clear its pointer. */ | |
| pJob = NULL; | |
| userCallback = NULL; | |
| } | |
| /* Acquire the lock to check for incoming notifications. This call | |
| should not expire. See the xBlockTime parameter of xSemaphoreTake | |
| for details. */ | |
| configASSERT( xSemaphoreTake( pTaskPool->xTimerEventMutex, portMAX_DELAY ) ); | |
| { | |
| /* Try and dequeue the next job in the dispatch queue. */ | |
| IotLink_t * pItem = NULL; | |
| /* Dequeue the next job from the dispatch queue. */ | |
| pItem = IotDeQueue_DequeueHead( &( pTaskPool->dispatchQueue ) ); | |
| /* If there is no job left in the dispatch queue, update the worker status and leave. */ | |
| if( pItem == NULL ) | |
| { | |
| /* Release the lock before exiting the loop. */ | |
| configASSERT( xSemaphoreGive( pTaskPool->xTimerEventMutex ) ); | |
| /* Abandon the INNER LOOP. Execution will transfer back to the OUTER LOOP condition. */ | |
| break; | |
| } | |
| else | |
| { | |
| pJob = IotLink_Container( _taskPoolJob_t, pItem, link ); | |
| userCallback = pJob->userCallback; | |
| } | |
| pJob->status = IOT_TASKPOOL_STATUS_COMPLETED; | |
| } | |
| /* Release the lock now that the job dispatch queue has been checked. */ | |
| configASSERT( xSemaphoreGive( pTaskPool->xTimerEventMutex ) ); | |
| } | |
| } | |
| } | |
| /* ---------------------------------------------------------------------------------------------- */ | |
| static void _initializeJob( _taskPoolJob_t * const pJob, | |
| IotTaskPoolRoutine_t userCallback, | |
| void * pUserContext ) | |
| { | |
| memset( ( void * ) pJob, 0x00, sizeof( _taskPoolJob_t ) ); | |
| pJob->userCallback = userCallback; | |
| pJob->pUserContext = pUserContext; | |
| pJob->status = IOT_TASKPOOL_STATUS_READY; | |
| } | |
| /* ---------------------------------------------------------------------------------------------- */ | |
| static void _scheduleInternal( _taskPoolJob_t * const pJob ) | |
| { | |
| /* Update the job status to 'scheduled'. */ | |
| pJob->status = IOT_TASKPOOL_STATUS_SCHEDULED; | |
| /* Append the job to the dispatch queue. */ | |
| IotDeQueue_EnqueueTail( &( _IotSystemTaskPool.dispatchQueue ), &( pJob->link ) ); | |
| /* NOTE: Every call to this function must be followed by giving the | |
| dispatchSignal semaphore - but do not give the semaphore directly in | |
| this function as giving the semaphore will result in the execution of | |
| a task pool worker task (depending on relative priorities) and we don't | |
| want the worker task to execute until all semaphores obtained before calling | |
| this function have been released. */ | |
| } | |
| /*-----------------------------------------------------------*/ | |
| static bool _matchJobByPointer( const IotLink_t * const pLink, | |
| void * pMatch ) | |
| { | |
| const _taskPoolJob_t * const pJob = ( _taskPoolJob_t * ) pMatch; | |
| const _taskPoolTimerEvent_t * const pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); | |
| if( pJob == GET_JOB_FROM_TIMER( pTimerEvent ) ) | |
| { | |
| return true; | |
| } | |
| return false; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| static IotTaskPoolError_t _tryCancelInternal( _taskPoolJob_t * const pJob, | |
| IotTaskPoolJobStatus_t * const pStatus ) | |
| { | |
| IotTaskPoolError_t result = IOT_TASKPOOL_SUCCESS; | |
| bool cancelable = false; | |
| /* We can only cancel jobs that are either 'ready' (waiting to be scheduled). 'deferred', or 'scheduled'. */ | |
| IotTaskPoolJobStatus_t currentStatus = pJob->status; | |
| switch( currentStatus ) | |
| { | |
| case IOT_TASKPOOL_STATUS_READY: | |
| case IOT_TASKPOOL_STATUS_DEFERRED: | |
| case IOT_TASKPOOL_STATUS_SCHEDULED: | |
| case IOT_TASKPOOL_STATUS_CANCELED: | |
| cancelable = true; | |
| break; | |
| case IOT_TASKPOOL_STATUS_COMPLETED: | |
| /* Log message for debug purposes. */ | |
| IotLogWarn( "Attempt to cancel a job that is already executing, or canceled." ); | |
| break; | |
| default: | |
| /* Log message for debug purposes. */ | |
| IotLogError( "Attempt to cancel a job with an undefined state." ); | |
| break; | |
| } | |
| /* Update the returned status to the current status of the job. */ | |
| if( pStatus != NULL ) | |
| { | |
| *pStatus = currentStatus; | |
| } | |
| if( cancelable == false ) | |
| { | |
| result = IOT_TASKPOOL_CANCEL_FAILED; | |
| } | |
| else | |
| { | |
| /* Update the status of the job. */ | |
| pJob->status = IOT_TASKPOOL_STATUS_CANCELED; | |
| /* If the job is cancelable and its current status is 'scheduled' then unlink it from the dispatch | |
| * queue and signal any waiting threads. */ | |
| if( currentStatus == IOT_TASKPOOL_STATUS_SCHEDULED ) | |
| { | |
| /* A scheduled work items must be in the dispatch queue. */ | |
| configASSERT( IotLink_IsLinked( &pJob->link ) ); | |
| IotDeQueue_Remove( &pJob->link ); | |
| } | |
| /* If the job current status is 'deferred' then the job has to be pending | |
| * in the timeouts queue. */ | |
| else if( currentStatus == IOT_TASKPOOL_STATUS_DEFERRED ) | |
| { | |
| /* Find the timer event associated with the current job. There MUST be one, hence assert if not. */ | |
| IotLink_t * pTimerEventLink = IotListDouble_FindFirstMatch( &( _IotSystemTaskPool.timerEventsList ), NULL, _matchJobByPointer, pJob ); | |
| configASSERT( pTimerEventLink != NULL ); | |
| if( pTimerEventLink != NULL ) | |
| { | |
| bool shouldReschedule = false; | |
| /* If the job being canceled was at the head of the timeouts queue, then we need to reschedule the timer | |
| * with the next job timeout */ | |
| IotLink_t * pHeadLink = IotListDouble_PeekHead( &( _IotSystemTaskPool.timerEventsList ) ); | |
| if( pHeadLink == pTimerEventLink ) | |
| { | |
| shouldReschedule = true; | |
| } | |
| /* Remove the timer event associated with the canceled job and free the associated memory. */ | |
| IotListDouble_Remove( pTimerEventLink ); | |
| memset( IotLink_Container( _taskPoolTimerEvent_t, pTimerEventLink, link ), 0, sizeof( IotLink_t ) ); | |
| if( shouldReschedule ) | |
| { | |
| IotLink_t * pNextTimerEventLink = IotListDouble_PeekHead( &( _IotSystemTaskPool.timerEventsList ) ); | |
| if( pNextTimerEventLink != NULL ) | |
| { | |
| _rescheduleDeferredJobsTimer( _IotSystemTaskPool.timer, IotLink_Container( _taskPoolTimerEvent_t, pNextTimerEventLink, link ) ); | |
| } | |
| } | |
| } | |
| } | |
| else | |
| { | |
| /* A cancelable job status should be either 'scheduled' or 'deferred'. */ | |
| configASSERT( ( currentStatus == IOT_TASKPOOL_STATUS_READY ) || ( currentStatus == IOT_TASKPOOL_STATUS_CANCELED ) ); | |
| } | |
| } | |
| return result; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| static int32_t _timerEventCompare( const IotLink_t * const pTimerEventLink1, | |
| const IotLink_t * const pTimerEventLink2 ) | |
| { | |
| const _taskPoolTimerEvent_t * const pTimerEvent1 = IotLink_Container( _taskPoolTimerEvent_t, | |
| pTimerEventLink1, | |
| link ); | |
| const _taskPoolTimerEvent_t * const pTimerEvent2 = IotLink_Container( _taskPoolTimerEvent_t, | |
| pTimerEventLink2, | |
| link ); | |
| if( pTimerEvent1->expirationTime < pTimerEvent2->expirationTime ) | |
| { | |
| return -1; | |
| } | |
| if( pTimerEvent1->expirationTime > pTimerEvent2->expirationTime ) | |
| { | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| /*-----------------------------------------------------------*/ | |
| static void _rescheduleDeferredJobsTimer( TimerHandle_t const timer, | |
| _taskPoolTimerEvent_t * const pFirstTimerEvent ) | |
| { | |
| uint64_t delta = 0; | |
| TickType_t now = xTaskGetTickCount(); | |
| configASSERT( pFirstTimerEvent != NULL ); | |
| configASSERT( timer != NULL ); | |
| /* Determine how much time is left for the deferred job. */ | |
| if( pFirstTimerEvent->expirationTime > now ) | |
| { | |
| delta = pFirstTimerEvent->expirationTime - now; | |
| } | |
| /* If the job timer has exceeded it's period, schedule it to be executed shortly. */ | |
| if( delta < TASKPOOL_JOB_RESCHEDULE_DELAY_MS ) | |
| { | |
| delta = TASKPOOL_JOB_RESCHEDULE_DELAY_MS; /* The job will be late... */ | |
| } | |
| /* Change the period of the task pools timer to be the period of this | |
| timer. A precondition of this function is that this TimerEvent is the | |
| timer event with the shortest deadline. | |
| */ | |
| if( xTimerChangePeriod( timer, ( uint32_t ) delta, portMAX_DELAY ) == pdFAIL ) | |
| { | |
| IotLogWarn( "Failed to re-arm timer for task pool" ); | |
| } | |
| } | |
| /*-----------------------------------------------------------*/ | |
| static void _timerCallback( TimerHandle_t xTimer ) | |
| { | |
| _taskPool_t * pTaskPool = pvTimerGetTimerID( xTimer ); | |
| configASSERT( pTaskPool ); | |
| _taskPoolTimerEvent_t * pTimerEvent = NULL; | |
| BaseType_t numberOfSchedules = 0; | |
| IotLogDebug( "Timer thread started for task pool %p.", pTaskPool ); | |
| /* Attempt to lock the timer mutex. Return immediately if the mutex cannot be locked. | |
| * If this mutex cannot be locked it means that another thread is manipulating the | |
| * timeouts list, and will reset the timer to fire again, although it will be late. | |
| */ | |
| if ( xSemaphoreTake( pTaskPool->xTimerEventMutex, 0 ) == pdPASS ) | |
| { | |
| /* Dispatch all deferred job whose timer expired, then reset the timer for the next | |
| * job down the line. */ | |
| for( ; ; ) | |
| { | |
| /* Peek the first event in the timer event list. */ | |
| IotLink_t * pLink = IotListDouble_PeekHead( &pTaskPool->timerEventsList ); | |
| /* Check if the timer misfired for any reason. */ | |
| if( pLink != NULL ) | |
| { | |
| /* Record the current time. */ | |
| TickType_t now = xTaskGetTickCount(); | |
| /* Extract the job from its envelope. */ | |
| pTimerEvent = IotLink_Container( _taskPoolTimerEvent_t, pLink, link ); | |
| /* Check if the first event should be processed now. */ | |
| if( pTimerEvent->expirationTime <= now ) | |
| { | |
| /* Remove the timer event for immediate processing. */ | |
| IotListDouble_Remove( &( pTimerEvent->link ) ); | |
| } | |
| else | |
| { | |
| /* The first element in the timer queue shouldn't be processed yet. | |
| * Arm the timer for when it should be processed and leave altogether. */ | |
| _rescheduleDeferredJobsTimer( pTaskPool->timer, pTimerEvent ); | |
| break; | |
| } | |
| } | |
| /* If there are no timer events to process, terminate this thread. */ | |
| else | |
| { | |
| IotLogDebug( "Finished scheduling deferred jobs." ); | |
| break; | |
| } | |
| /* Queue the job associated with the received timer event. */ | |
| _scheduleInternal( GET_JOB_FROM_TIMER( pTimerEvent ) ); | |
| numberOfSchedules++; | |
| IotLogDebug( "Scheduled a job." ); | |
| /* Free the timer event. */ | |
| memset( &( pTimerEvent->link ), 0, sizeof( pTimerEvent->link ) ); | |
| } | |
| /* Release mutex guarding the timer list. */ | |
| configASSERT( xSemaphoreGive( pTaskPool->xTimerEventMutex ) == pdPASS ); | |
| for (; numberOfSchedules > 0; numberOfSchedules--) | |
| { | |
| /* Signal a worker task that a job was queued. */ | |
| configASSERT( xSemaphoreGive( pTaskPool->dispatchSignal ) ); | |
| } | |
| } | |
| } |