blob: d489746f57a1dc8419e0f09b835a9928839e6c1e [file] [log] [blame]
/*
* FreeRTOS Kernel <DEVELOPMENT BRANCH>
* Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: MIT
*
* 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
*
*/
/* Standard includes. */
#include <stdio.h>
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#ifdef __GNUC__
#include "mmsystem.h"
#else
#pragma comment(lib, "winmm.lib")
#endif
#define portMAX_INTERRUPTS ( ( uint32_t ) sizeof( uint32_t ) * 8UL ) /* The number of bits in an uint32_t. */
#define portNO_CRITICAL_NESTING ( ( uint32_t ) 0 )
/* The priorities at which the various components of the simulation execute. */
#define portDELETE_SELF_THREAD_PRIORITY THREAD_PRIORITY_TIME_CRITICAL /* Must be highest. */
#define portSIMULATED_INTERRUPTS_THREAD_PRIORITY THREAD_PRIORITY_TIME_CRITICAL
#define portSIMULATED_TIMER_THREAD_PRIORITY THREAD_PRIORITY_HIGHEST
#define portTASK_THREAD_PRIORITY THREAD_PRIORITY_ABOVE_NORMAL
/*
* Created as a high priority thread, this function uses a timer to simulate
* a tick interrupt being generated on an embedded target. In this Windows
* environment the timer does not achieve anything approaching real time
* performance though.
*/
static DWORD WINAPI prvSimulatedPeripheralTimer( LPVOID lpParameter );
/*
* Process all the simulated interrupts - each represented by a bit in
* ulPendingInterrupts variable.
*/
static void prvProcessSimulatedInterrupts( void );
/*
* Interrupt handlers used by the kernel itself. These are executed from the
* simulated interrupt handler thread.
*/
static uint32_t prvProcessYieldInterrupt( void );
static uint32_t prvProcessTickInterrupt( void );
/*
* Exiting a critical section will cause the calling task to block on yield
* event to wait for an interrupt to process if an interrupt was pended while
* inside the critical section. This variable protects against a recursive
* attempt to obtain pvInterruptEventMutex if a critical section is used inside
* an interrupt handler itself.
*/
volatile BaseType_t xInsideInterrupt = pdFALSE;
/*
* Called when the process exits to let Windows know the high timer resolution
* is no longer required.
*/
static BOOL WINAPI prvEndProcess( DWORD dwCtrlType );
/*-----------------------------------------------------------*/
/* The WIN32 simulator runs each task in a thread. The context switching is
* managed by the threads, so the task stack does not have to be managed directly,
* although the task stack is still used to hold an xThreadState structure this is
* the only thing it will ever hold. The structure indirectly maps the task handle
* to a thread handle. */
typedef struct
{
/* Handle of the thread that executes the task. */
void * pvThread;
/* Event used to make sure the thread does not execute past a yield point
* between the call to SuspendThread() to suspend the thread and the
* asynchronous SuspendThread() operation actually being performed. */
void * pvYieldEvent;
} ThreadState_t;
/* Simulated interrupts waiting to be processed. This is a bit mask where each
* bit represents one interrupt, so a maximum of 32 interrupts can be simulated. */
static volatile uint32_t ulPendingInterrupts = 0UL;
/* An event used to inform the simulated interrupt processing thread (a high
* priority thread that simulated interrupt processing) that an interrupt is
* pending. */
static void * pvInterruptEvent = NULL;
/* Mutex used to protect all the simulated interrupt variables that are accessed
* by multiple threads. */
static void * pvInterruptEventMutex = NULL;
/* The critical nesting count for the currently executing task. This is
* initialised to a non-zero value so interrupts do not become enabled during
* the initialisation phase. As each task has its own critical nesting value
* ulCriticalNesting will get set to zero when the first task runs. This
* initialisation is probably not critical in this simulated environment as the
* simulated interrupt handlers do not get created until the FreeRTOS scheduler is
* started anyway. */
static volatile uint32_t ulCriticalNesting = 9999UL;
/* Handlers for all the simulated software interrupts. The first two positions
* are used for the Yield and Tick interrupts so are handled slightly differently,
* all the other interrupts can be user defined. */
static uint32_t (* ulIsrHandler[ portMAX_INTERRUPTS ])( void ) = { 0 };
/* Pointer to the TCB of the currently executing task. */
extern void * volatile pxCurrentTCB;
/* Used to ensure nothing is processed during the startup sequence. */
static BaseType_t xPortRunning = pdFALSE;
/*-----------------------------------------------------------*/
static DWORD WINAPI prvSimulatedPeripheralTimer( LPVOID lpParameter )
{
TickType_t xMinimumWindowsBlockTime;
TIMECAPS xTimeCaps;
/* Set the timer resolution to the maximum possible. */
if( timeGetDevCaps( &xTimeCaps, sizeof( xTimeCaps ) ) == MMSYSERR_NOERROR )
{
xMinimumWindowsBlockTime = ( TickType_t ) xTimeCaps.wPeriodMin;
timeBeginPeriod( xTimeCaps.wPeriodMin );
/* Register an exit handler so the timeBeginPeriod() function can be
* matched with a timeEndPeriod() when the application exits. */
SetConsoleCtrlHandler( prvEndProcess, TRUE );
}
else
{
xMinimumWindowsBlockTime = ( TickType_t ) 20;
}
/* Just to prevent compiler warnings. */
( void ) lpParameter;
while( xPortRunning == pdTRUE )
{
/* Wait until the timer expires and we can access the simulated interrupt
* variables. *NOTE* this is not a 'real time' way of generating tick
* events as the next wake time should be relative to the previous wake
* time, not the time that Sleep() is called. It is done this way to
* prevent overruns in this very non real time simulated/emulated
* environment. */
if( portTICK_PERIOD_MS < xMinimumWindowsBlockTime )
{
Sleep( xMinimumWindowsBlockTime );
}
else
{
Sleep( portTICK_PERIOD_MS );
}
if( xPortRunning == pdTRUE )
{
configASSERT( xPortRunning );
/* Can't proceed if in a critical section as pvInterruptEventMutex won't
* be available. */
WaitForSingleObject( pvInterruptEventMutex, INFINITE );
/* The timer has expired, generate the simulated tick event. */
ulPendingInterrupts |= ( 1 << portINTERRUPT_TICK );
/* The interrupt is now pending - notify the simulated interrupt
* handler thread. Must be outside of a critical section to get here so
* the handler thread can execute immediately pvInterruptEventMutex is
* released. */
configASSERT( ulCriticalNesting == 0UL );
SetEvent( pvInterruptEvent );
/* Give back the mutex so the simulated interrupt handler unblocks
* and can access the interrupt handler variables. */
ReleaseMutex( pvInterruptEventMutex );
}
}
return 0;
}
/*-----------------------------------------------------------*/
static BOOL WINAPI prvEndProcess( DWORD dwCtrlType )
{
TIMECAPS xTimeCaps;
( void ) dwCtrlType;
if( timeGetDevCaps( &xTimeCaps, sizeof( xTimeCaps ) ) == MMSYSERR_NOERROR )
{
/* Match the call to timeBeginPeriod( xTimeCaps.wPeriodMin ) made when
* the process started with a timeEndPeriod() as the process exits. */
timeEndPeriod( xTimeCaps.wPeriodMin );
}
return pdFALSE;
}
/*-----------------------------------------------------------*/
StackType_t * pxPortInitialiseStack( StackType_t * pxTopOfStack,
TaskFunction_t pxCode,
void * pvParameters )
{
ThreadState_t * pxThreadState = NULL;
int8_t * pcTopOfStack = ( int8_t * ) pxTopOfStack;
const SIZE_T xStackSize = 1024; /* Set the size to a small number which will get rounded up to the minimum possible. */
/* In this simulated case a stack is not initialised, but instead a thread
* is created that will execute the task being created. The thread handles
* the context switching itself. The ThreadState_t object is placed onto
* the stack that was created for the task - so the stack buffer is still
* used, just not in the conventional way. It will not be used for anything
* other than holding this structure. */
pxThreadState = ( ThreadState_t * ) ( pcTopOfStack - sizeof( ThreadState_t ) );
/* Create the event used to prevent the thread from executing past its yield
* point if the SuspendThread() call that suspends the thread does not take
* effect immediately (it is an asynchronous call). */
pxThreadState->pvYieldEvent = CreateEvent( NULL, /* Default security attributes. */
FALSE, /* Auto reset. */
FALSE, /* Start not signalled. */
NULL ); /* No name. */
#ifdef __GNUC__
/* GCC reports the warning for the cast operation from TaskFunction_t to LPTHREAD_START_ROUTINE. */
/* Disable this warning here by the #pragma option. */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-function-type"
#endif
/* Create the thread itself. */
pxThreadState->pvThread = CreateThread( NULL, xStackSize, ( LPTHREAD_START_ROUTINE ) pxCode, pvParameters, CREATE_SUSPENDED | STACK_SIZE_PARAM_IS_A_RESERVATION, NULL );
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
configASSERT( pxThreadState->pvThread ); /* See comment where TerminateThread() is called. */
SetThreadAffinityMask( pxThreadState->pvThread, 0x01 );
SetThreadPriorityBoost( pxThreadState->pvThread, TRUE );
SetThreadPriority( pxThreadState->pvThread, portTASK_THREAD_PRIORITY );
return ( StackType_t * ) pxThreadState;
}
/*-----------------------------------------------------------*/
BaseType_t xPortStartScheduler( void )
{
void * pvHandle = NULL;
int32_t lSuccess;
ThreadState_t * pxThreadState = NULL;
SYSTEM_INFO xSystemInfo;
/* This port runs windows threads with extremely high priority. All the
* threads execute on the same core - to prevent locking up the host only start
* if the host has multiple cores. */
GetSystemInfo( &xSystemInfo );
if( xSystemInfo.dwNumberOfProcessors <= 1 )
{
printf( "This version of the FreeRTOS Windows port can only be used on multi-core hosts.\r\n" );
lSuccess = pdFAIL;
}
else
{
lSuccess = pdPASS;
/* The highest priority class is used to [try to] prevent other Windows
* activity interfering with FreeRTOS timing too much. */
if( SetPriorityClass( GetCurrentProcess(), REALTIME_PRIORITY_CLASS ) == 0 )
{
printf( "SetPriorityClass() failed\r\n" );
}
/* Install the interrupt handlers used by the scheduler itself. */
vPortSetInterruptHandler( portINTERRUPT_YIELD, prvProcessYieldInterrupt );
vPortSetInterruptHandler( portINTERRUPT_TICK, prvProcessTickInterrupt );
/* Create the events and mutexes that are used to synchronise all the
* threads. */
pvInterruptEventMutex = CreateMutex( NULL, FALSE, NULL );
pvInterruptEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
if( ( pvInterruptEventMutex == NULL ) || ( pvInterruptEvent == NULL ) )
{
lSuccess = pdFAIL;
}
/* Set the priority of this thread such that it is above the priority of
* the threads that run tasks. This higher priority is required to ensure
* simulated interrupts take priority over tasks. */
pvHandle = GetCurrentThread();
if( pvHandle == NULL )
{
lSuccess = pdFAIL;
}
}
if( lSuccess == pdPASS )
{
if( SetThreadPriority( pvHandle, portSIMULATED_INTERRUPTS_THREAD_PRIORITY ) == 0 )
{
lSuccess = pdFAIL;
}
SetThreadPriorityBoost( pvHandle, TRUE );
SetThreadAffinityMask( pvHandle, 0x01 );
}
if( lSuccess == pdPASS )
{
/* Start the thread that simulates the timer peripheral to generate
* tick interrupts. The priority is set below that of the simulated
* interrupt handler so the interrupt event mutex is used for the
* handshake / overrun protection. */
pvHandle = CreateThread( NULL, 0, prvSimulatedPeripheralTimer, NULL, CREATE_SUSPENDED, NULL );
if( pvHandle != NULL )
{
SetThreadPriority( pvHandle, portSIMULATED_TIMER_THREAD_PRIORITY );
SetThreadPriorityBoost( pvHandle, TRUE );
SetThreadAffinityMask( pvHandle, 0x01 );
ResumeThread( pvHandle );
}
/* Start the highest priority task by obtaining its associated thread
* state structure, in which is stored the thread handle. */
pxThreadState = ( ThreadState_t * ) *( ( size_t * ) pxCurrentTCB );
ulCriticalNesting = portNO_CRITICAL_NESTING;
/* Start the first task. */
ResumeThread( pxThreadState->pvThread );
/* The scheduler is now running. */
xPortRunning = pdTRUE;
/* Handle all simulated interrupts - including yield requests and
* simulated ticks. */
prvProcessSimulatedInterrupts();
}
/* Would not expect to return from prvProcessSimulatedInterrupts(), so should
* not get here. */
return 0;
}
/*-----------------------------------------------------------*/
static uint32_t prvProcessYieldInterrupt( void )
{
/* Always return true as this is a yield. */
return pdTRUE;
}
/*-----------------------------------------------------------*/
static uint32_t prvProcessTickInterrupt( void )
{
uint32_t ulSwitchRequired;
/* Process the tick itself. */
configASSERT( xPortRunning );
ulSwitchRequired = ( uint32_t ) xTaskIncrementTick();
return ulSwitchRequired;
}
/*-----------------------------------------------------------*/
static void prvProcessSimulatedInterrupts( void )
{
uint32_t ulSwitchRequired, i;
ThreadState_t * pxThreadState;
void * pvObjectList[ 2 ];
CONTEXT xContext;
DWORD xWinApiResult;
const DWORD xTimeoutMilliseconds = 1000;
/* Going to block on the mutex that ensured exclusive access to the simulated
* interrupt objects, and the event that signals that a simulated interrupt
* should be processed. */
pvObjectList[ 0 ] = pvInterruptEventMutex;
pvObjectList[ 1 ] = pvInterruptEvent;
/* Create a pending tick to ensure the first task is started as soon as
* this thread pends. */
ulPendingInterrupts |= ( 1 << portINTERRUPT_TICK );
SetEvent( pvInterruptEvent );
while( xPortRunning == pdTRUE )
{
xInsideInterrupt = pdFALSE;
/* Wait with timeout so that we can exit from this loop when
* the scheduler is stopped by calling vPortEndScheduler. */
xWinApiResult = WaitForMultipleObjects( sizeof( pvObjectList ) / sizeof( void * ), pvObjectList, TRUE, xTimeoutMilliseconds );
if( xWinApiResult != WAIT_TIMEOUT )
{
/* Cannot be in a critical section to get here. Tasks that exit a
* critical section will block on a yield mutex to wait for an interrupt to
* process if an interrupt was set pending while the task was inside the
* critical section. xInsideInterrupt prevents interrupts that contain
* critical sections from doing the same. */
xInsideInterrupt = pdTRUE;
/* Used to indicate whether the simulated interrupt processing has
* necessitated a context switch to another task/thread. */
ulSwitchRequired = pdFALSE;
/* For each interrupt we are interested in processing, each of which is
* represented by a bit in the 32bit ulPendingInterrupts variable. */
for( i = 0; i < portMAX_INTERRUPTS; i++ )
{
/* Is the simulated interrupt pending? */
if( ( ulPendingInterrupts & ( 1UL << i ) ) != 0 )
{
/* Is a handler installed? */
if( ulIsrHandler[ i ] != NULL )
{
/* Run the actual handler. Handlers return pdTRUE if they
* necessitate a context switch. */
if( ulIsrHandler[ i ]() != pdFALSE )
{
/* A bit mask is used purely to help debugging. */
ulSwitchRequired |= ( 1 << i );
}
}
/* Clear the interrupt pending bit. */
ulPendingInterrupts &= ~( 1UL << i );
}
}
if( ulSwitchRequired != pdFALSE )
{
void * pvOldCurrentTCB;
pvOldCurrentTCB = pxCurrentTCB;
/* Select the next task to run. */
vTaskSwitchContext();
/* If the task selected to enter the running state is not the task
* that is already in the running state. */
if( pvOldCurrentTCB != pxCurrentTCB )
{
/* Suspend the old thread. In the cases where the (simulated)
* interrupt is asynchronous (tick event swapping a task out rather
* than a task blocking or yielding) it doesn't matter if the
* 'suspend' operation doesn't take effect immediately - if it
* doesn't it would just be like the interrupt occurring slightly
* later. In cases where the yield was caused by a task blocking
* or yielding then the task will block on a yield event after the
* yield operation in case the 'suspend' operation doesn't take
* effect immediately. */
pxThreadState = ( ThreadState_t * ) *( ( size_t * ) pvOldCurrentTCB );
SuspendThread( pxThreadState->pvThread );
/* Ensure the thread is actually suspended by performing a
* synchronous operation that can only complete when the thread is
* actually suspended. The below code asks for dummy register
* data. Experimentation shows that these two lines don't appear
* to do anything now, but according to
* https://devblogs.microsoft.com/oldnewthing/20150205-00/?p=44743
* they do - so as they do not harm (slight run-time hit). */
xContext.ContextFlags = CONTEXT_INTEGER;
( void ) GetThreadContext( pxThreadState->pvThread, &xContext );
/* Obtain the state of the task now selected to enter the
* Running state. */
pxThreadState = ( ThreadState_t * ) ( *( size_t * ) pxCurrentTCB );
/* pxThreadState->pvThread can be NULL if the task deleted
* itself - but a deleted task should never be resumed here. */
configASSERT( pxThreadState->pvThread != NULL );
ResumeThread( pxThreadState->pvThread );
}
}
/* If the thread that is about to be resumed stopped running
* because it yielded then it will wait on an event when it resumed
* (to ensure it does not continue running after the call to
* SuspendThread() above as SuspendThread() is asynchronous).
* Signal the event to ensure the thread can proceed now it is
* valid for it to do so. Signaling the event is benign in the case that
* the task was switched out asynchronously by an interrupt as the event
* is reset before the task blocks on it. */
pxThreadState = ( ThreadState_t * ) ( *( size_t * ) pxCurrentTCB );
SetEvent( pxThreadState->pvYieldEvent );
ReleaseMutex( pvInterruptEventMutex );
}
}
}
/*-----------------------------------------------------------*/
void vPortDeleteThread( void * pvTaskToDelete )
{
ThreadState_t * pxThreadState;
uint32_t ulErrorCode;
/* Remove compiler warnings if configASSERT() is not defined. */
( void ) ulErrorCode;
/* Find the handle of the thread being deleted. */
pxThreadState = ( ThreadState_t * ) ( *( size_t * ) pvTaskToDelete );
/* Check that the thread is still valid, it might have been closed by
* vPortCloseRunningThread() - which will be the case if the task associated
* with the thread originally deleted itself rather than being deleted by a
* different task. */
if( pxThreadState->pvThread != NULL )
{
WaitForSingleObject( pvInterruptEventMutex, INFINITE );
/* !!! This is not a nice way to terminate a thread, and will eventually
* result in resources being depleted if tasks frequently delete other
* tasks (rather than deleting themselves) as the task stacks will not be
* freed. */
ulErrorCode = TerminateThread( pxThreadState->pvThread, 0 );
configASSERT( ulErrorCode );
ulErrorCode = CloseHandle( pxThreadState->pvThread );
configASSERT( ulErrorCode );
ReleaseMutex( pvInterruptEventMutex );
}
}
/*-----------------------------------------------------------*/
void vPortCloseRunningThread( void * pvTaskToDelete,
volatile BaseType_t * pxPendYield )
{
ThreadState_t * pxThreadState;
void * pvThread;
uint32_t ulErrorCode;
/* Remove compiler warnings if configASSERT() is not defined. */
( void ) ulErrorCode;
/* Find the handle of the thread being deleted. */
pxThreadState = ( ThreadState_t * ) ( *( size_t * ) pvTaskToDelete );
pvThread = pxThreadState->pvThread;
/* Raise the Windows priority of the thread to ensure the FreeRTOS scheduler
* does not run and swap it out before it is closed. If that were to happen
* the thread would never run again and effectively be a thread handle and
* memory leak. */
SetThreadPriority( pvThread, portDELETE_SELF_THREAD_PRIORITY );
/* This function will not return, therefore a yield is set as pending to
* ensure a context switch occurs away from this thread on the next tick. */
*pxPendYield = pdTRUE;
/* Mark the thread associated with this task as invalid so
* vPortDeleteThread() does not try to terminate it. */
pxThreadState->pvThread = NULL;
/* Close the thread. */
ulErrorCode = CloseHandle( pvThread );
configASSERT( ulErrorCode );
/* This is called from a critical section, which must be exited before the
* thread stops. */
taskEXIT_CRITICAL();
CloseHandle( pxThreadState->pvYieldEvent );
ExitThread( 0 );
}
/*-----------------------------------------------------------*/
void vPortEndScheduler( void )
{
xPortRunning = pdFALSE;
}
/*-----------------------------------------------------------*/
void vPortGenerateSimulatedInterrupt( uint32_t ulInterruptNumber )
{
ThreadState_t * pxThreadState = ( ThreadState_t * ) *( ( size_t * ) pxCurrentTCB );
configASSERT( xPortRunning );
if( ( ulInterruptNumber < portMAX_INTERRUPTS ) && ( pvInterruptEventMutex != NULL ) )
{
WaitForSingleObject( pvInterruptEventMutex, INFINITE );
ulPendingInterrupts |= ( 1 << ulInterruptNumber );
/* The simulated interrupt is now held pending, but don't actually
* process it yet if this call is within a critical section. It is
* possible for this to be in a critical section as calls to wait for
* mutexes are accumulative. If in a critical section then the event
* will get set when the critical section nesting count is wound back
* down to zero. */
if( ulCriticalNesting == portNO_CRITICAL_NESTING )
{
SetEvent( pvInterruptEvent );
/* Going to wait for an event - make sure the event is not already
* signaled. */
ResetEvent( pxThreadState->pvYieldEvent );
}
ReleaseMutex( pvInterruptEventMutex );
if( ulCriticalNesting == portNO_CRITICAL_NESTING )
{
/* An interrupt was pended so ensure to block to allow it to
* execute. In most cases the (simulated) interrupt will have
* executed before the next line is reached - so this is just to make
* sure. */
WaitForSingleObject( pxThreadState->pvYieldEvent, INFINITE );
}
}
}
/*-----------------------------------------------------------*/
void vPortSetInterruptHandler( uint32_t ulInterruptNumber,
uint32_t ( * pvHandler )( void ) )
{
if( ulInterruptNumber < portMAX_INTERRUPTS )
{
if( pvInterruptEventMutex != NULL )
{
WaitForSingleObject( pvInterruptEventMutex, INFINITE );
ulIsrHandler[ ulInterruptNumber ] = pvHandler;
ReleaseMutex( pvInterruptEventMutex );
}
else
{
ulIsrHandler[ ulInterruptNumber ] = pvHandler;
}
}
}
/*-----------------------------------------------------------*/
void vPortEnterCritical( void )
{
if( xPortRunning == pdTRUE )
{
/* The interrupt event mutex is held for the entire critical section,
* effectively disabling (simulated) interrupts. */
WaitForSingleObject( pvInterruptEventMutex, INFINITE );
}
ulCriticalNesting++;
}
/*-----------------------------------------------------------*/
void vPortExitCritical( void )
{
int32_t lMutexNeedsReleasing;
/* The interrupt event mutex should already be held by this thread as it was
* obtained on entry to the critical section. */
lMutexNeedsReleasing = pdTRUE;
if( ulCriticalNesting > portNO_CRITICAL_NESTING )
{
ulCriticalNesting--;
/* Don't need to wait for any pending interrupts to execute if the
* critical section was exited from inside an interrupt. */
if( ( ulCriticalNesting == portNO_CRITICAL_NESTING ) && ( xInsideInterrupt == pdFALSE ) )
{
/* Were any interrupts set to pending while interrupts were
* (simulated) disabled? */
if( ulPendingInterrupts != 0UL )
{
ThreadState_t * pxThreadState = ( ThreadState_t * ) *( ( size_t * ) pxCurrentTCB );
configASSERT( xPortRunning );
/* The interrupt won't actually executed until
* pvInterruptEventMutex is released as it waits on both
* pvInterruptEventMutex and pvInterruptEvent.
* pvInterruptEvent is only set when the simulated
* interrupt is pended if the interrupt is pended
* from outside a critical section - hence it is set
* here. */
SetEvent( pvInterruptEvent );
/* The calling task is going to wait for an event to ensure the
* interrupt that is pending executes immediately after the
* critical section is exited - so make sure the event is not
* already signaled. */
ResetEvent( pxThreadState->pvYieldEvent );
/* Mutex will be released now so the (simulated) interrupt can
* execute, so does not require releasing on function exit. */
lMutexNeedsReleasing = pdFALSE;
ReleaseMutex( pvInterruptEventMutex );
WaitForSingleObject( pxThreadState->pvYieldEvent, INFINITE );
}
}
}
if( pvInterruptEventMutex != NULL )
{
if( lMutexNeedsReleasing == pdTRUE )
{
configASSERT( xPortRunning );
ReleaseMutex( pvInterruptEventMutex );
}
}
}
/*-----------------------------------------------------------*/