/*
 * FreeRTOS V202411.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 fairly comprehensive checks on the behaviour of event
 * groups.  It is not intended to be a user friendly demonstration of the
 * event groups API.
 *
 * NOTE:  The tests implemented in this file are informal 'sanity' tests
 * only and are not part of the module tests that make use of the
 * mtCOVERAGE_TEST_MARKER macro within the event groups implementation.
 */


/* Scheduler include files. */
#include "FreeRTOS.h"
#include "task.h"
#include "event_groups.h"

/* Demo app includes. */
#include "EventGroupsDemo.h"

#if ( INCLUDE_eTaskGetState != 1 )
    #error INCLUDE_eTaskGetState must be set to 1 in FreeRTOSConfig.h to use this demo file.
#endif

/* Priorities used by the tasks. */
#define ebSET_BIT_TASK_PRIORITY                  ( tskIDLE_PRIORITY )
#define ebWAIT_BIT_TASK_PRIORITY                 ( tskIDLE_PRIORITY + 1 )

/* Generic bit definitions. */
#define ebBIT_0                                  ( 0x01 )
#define ebBIT_1                                  ( 0x02 )
#define ebBIT_2                                  ( 0x04 )
#define ebBIT_3                                  ( 0x08 )
#define ebBIT_4                                  ( 0x10 )
#define ebBIT_5                                  ( 0x20 )
#define ebBIT_6                                  ( 0x40 )
#define ebBIT_7                                  ( 0x80 )

/* Combinations of bits used in the demo. */
#define ebCOMBINED_BITS                          ( ebBIT_1 | ebBIT_5 | ebBIT_7 )
#define ebALL_BITS                               ( ebBIT_0 | ebBIT_1 | ebBIT_2 | ebBIT_3 | ebBIT_4 | ebBIT_5 | ebBIT_6 | ebBIT_7 )

/* Associate a bit to each task.  These bits are used to identify all the tasks
 * that synchronise with the xEventGroupSync() function. */
#define ebSET_BIT_TASK_SYNC_BIT                  ebBIT_0
#define ebWAIT_BIT_TASK_SYNC_BIT                 ebBIT_1
#define ebRENDEZVOUS_TASK_1_SYNC_BIT             ebBIT_2
#define ebRENDEZVOUS_TASK_2_SYNC_BIT             ebBIT_3
#define ebALL_SYNC_BITS                          ( ebSET_BIT_TASK_SYNC_BIT | ebWAIT_BIT_TASK_SYNC_BIT | ebRENDEZVOUS_TASK_1_SYNC_BIT | ebRENDEZVOUS_TASK_2_SYNC_BIT )

/* A block time of zero simply means "don't block". */
#define ebDONT_BLOCK                             ( 0 )

/* A 5ms delay. */
#define ebSHORT_DELAY                            pdMS_TO_TICKS( ( TickType_t ) 5 )

/* Used in the selective bits test which checks no, one or both tasks blocked on
 *  event bits in a group are unblocked as appropriate as different bits get set. */
#define ebSELECTIVE_BITS_1                       0x03
#define ebSELECTIVE_BITS_2                       0x05

#ifndef ebRENDEZVOUS_TEST_TASK_STACK_SIZE
    #define ebRENDEZVOUS_TEST_TASK_STACK_SIZE    configMINIMAL_STACK_SIZE
#endif

#ifndef ebEVENT_GROUP_SET_BITS_TEST_TASK_STACK_SIZE
    #define ebEVENT_GROUP_SET_BITS_TEST_TASK_STACK_SIZE    configMINIMAL_STACK_SIZE
#endif

/*-----------------------------------------------------------*/

/*
 * NOTE:  The tests implemented in this function are informal 'sanity' tests
 * only and are not part of the module tests that make use of the
 * mtCOVERAGE_TEST_MARKER macro within the event groups implementation.
 *
 * The master test task.  This task:
 *
 * 1) Calls prvSelectiveBitsTestMasterFunction() to test the behaviour when two
 *    tasks are blocked on different bits in an event group.  The counterpart of
 *    this test is implemented by the prvSelectiveBitsTestSlaveFunction()
 *    function (which is called by the two tasks that block on the event group).
 *
 * 2) Calls prvBitCombinationTestMasterFunction() to test the behaviour when
 *    just one task is blocked on various combinations of bits within an event
 *    group.  The counterpart of this test is implemented within the 'test
 *    slave' task.
 *
 * 3) Calls prvPerformTaskSyncTests() to test task synchronisation behaviour.
 */
static void prvTestMasterTask( void * pvParameters );

/*
 * A helper task that enables the 'test master' task to perform several
 * behavioural tests.  See the comments above the prvTestMasterTask() prototype
 * above.
 */
static void prvTestSlaveTask( void * pvParameters );

/*
 * The part of the test that is performed between the 'test master' task and the
 * 'test slave' task to test the behaviour when the slave blocks on various
 * event bit combinations.
 */
static BaseType_t prvBitCombinationTestMasterFunction( BaseType_t xError,
                                                       TaskHandle_t xTestSlaveTaskHandle );

/*
 * The part of the test that uses all the tasks to test the task synchronisation
 * behaviour.
 */
static BaseType_t prvPerformTaskSyncTests( BaseType_t xError,
                                           TaskHandle_t xTestSlaveTaskHandle );

/*
 * Two instances of prvSyncTask() are created.  They start by calling
 * prvSelectiveBitsTestSlaveFunction() to act as slaves when the test master is
 * executing the prvSelectiveBitsTestMasterFunction() function.  They then loop
 * to test the task synchronisation (rendezvous) behaviour.
 */
static void prvSyncTask( void * pvParameters );

/*
 * Functions used in a test that blocks two tasks on various different bits
 * within an event group - then sets each bit in turn and checks that the
 * correct tasks unblock at the correct times.
 */
static BaseType_t prvSelectiveBitsTestMasterFunction( void );
static void prvSelectiveBitsTestSlaveFunction( void );

/*-----------------------------------------------------------*/

/* Variables that are incremented by the tasks on each cycle provided no errors
 * have been found.  Used to detect an error or stall in the test cycling. */
static volatile uint32_t ulTestMasterCycles = 0, ulTestSlaveCycles = 0, ulISRCycles = 0;

/* The event group used by all the task based tests. */
static EventGroupHandle_t xEventGroup = NULL;

/* The event group used by the interrupt based tests. */
static EventGroupHandle_t xISREventGroup = NULL;

/* Handles to the tasks that only take part in the synchronisation calls. */
static TaskHandle_t xSyncTask1 = NULL, xSyncTask2 = NULL;

/*-----------------------------------------------------------*/

void vStartEventGroupTasks( void )
{
    TaskHandle_t xTestSlaveTaskHandle;

    /*
     * This file contains fairly comprehensive checks on the behaviour of event
     * groups.  It is not intended to be a user friendly demonstration of the
     * event groups API.
     *
     * NOTE:  The tests implemented in this file are informal 'sanity' tests
     * only and are not part of the module tests that make use of the
     * mtCOVERAGE_TEST_MARKER macro within the event groups implementation.
     *
     * Create the test tasks as described at the top of this file.
     */
    xTaskCreate( prvTestSlaveTask, "WaitO", ebRENDEZVOUS_TEST_TASK_STACK_SIZE, NULL, ebWAIT_BIT_TASK_PRIORITY, &xTestSlaveTaskHandle );
    xTaskCreate( prvTestMasterTask, "SetB", ebEVENT_GROUP_SET_BITS_TEST_TASK_STACK_SIZE, ( void * ) xTestSlaveTaskHandle, ebSET_BIT_TASK_PRIORITY, NULL );
    xTaskCreate( prvSyncTask, "Rndv", ebRENDEZVOUS_TEST_TASK_STACK_SIZE, ( void * ) ebRENDEZVOUS_TASK_1_SYNC_BIT, ebWAIT_BIT_TASK_PRIORITY, &xSyncTask1 );
    xTaskCreate( prvSyncTask, "Rndv", ebRENDEZVOUS_TEST_TASK_STACK_SIZE, ( void * ) ebRENDEZVOUS_TASK_2_SYNC_BIT, ebWAIT_BIT_TASK_PRIORITY, &xSyncTask2 );

    /* If the last task was created then the others will have been too. */
    configASSERT( xSyncTask2 );

    /* Create the event group used by the ISR tests.  The event group used by
     * the tasks is created by the tasks themselves. */
    xISREventGroup = xEventGroupCreate();
    configASSERT( xISREventGroup );
}
/*-----------------------------------------------------------*/

static void prvTestMasterTask( void * pvParameters )
{
    BaseType_t xError;

/* The handle to the slave task is passed in as the task parameter. */
    TaskHandle_t xTestSlaveTaskHandle = ( TaskHandle_t ) pvParameters;

    /* Avoid compiler warnings. */
    ( void ) pvParameters;

    /* Create the event group used by the tasks ready for the initial tests. */
    xEventGroup = xEventGroupCreate();
    configASSERT( xEventGroup );

    /* Perform the tests that block two tasks on different combinations of bits,
     * then set each bit in turn and check the correct tasks unblock at the correct
     * times. */
    xError = prvSelectiveBitsTestMasterFunction();

    for( ; ; )
    {
        /* Recreate the event group ready for the next cycle. */
        xEventGroup = xEventGroupCreate();
        configASSERT( xEventGroup );

        /* Perform the tests that check the behaviour when a single task is
         * blocked on various combinations of event bits. */
        xError = prvBitCombinationTestMasterFunction( xError, xTestSlaveTaskHandle );

        /* Perform the task synchronisation tests. */
        xError = prvPerformTaskSyncTests( xError, xTestSlaveTaskHandle );

        /* Delete the event group. */
        vEventGroupDelete( xEventGroup );

        /* Now all the other tasks should have completed and suspended
         * themselves ready for the next go around the loop. */
        if( eTaskGetState( xTestSlaveTaskHandle ) != eSuspended )
        {
            xError = pdTRUE;
        }

        if( eTaskGetState( xSyncTask1 ) != eSuspended )
        {
            xError = pdTRUE;
        }

        if( eTaskGetState( xSyncTask2 ) != eSuspended )
        {
            xError = pdTRUE;
        }

        /* Only increment the cycle variable if no errors have been detected. */
        if( xError == pdFALSE )
        {
            ulTestMasterCycles++;
        }

        configASSERT( xError == pdFALSE );
    }
}
/*-----------------------------------------------------------*/

static void prvSyncTask( void * pvParameters )
{
    EventBits_t uxSynchronisationBit, uxReturned;

    /* A few tests that check the behaviour when two tasks are blocked on
     * various different bits within an event group are performed before this task
     * enters its infinite loop to carry out its main demo function. */
    prvSelectiveBitsTestSlaveFunction();

    /* The bit to use to indicate this task is at the synchronisation point is
     * passed in as the task parameter. */
    uxSynchronisationBit = ( EventBits_t ) pvParameters;

    for( ; ; )
    {
        /* Now this task takes part in a task synchronisation - sometimes known
         * as a 'rendezvous'.  Its execution pattern is controlled by the 'test
         * master' task, which is responsible for taking this task out of the
         * Suspended state when it is time to test the synchronisation behaviour.
         * See: http://www.freertos.org/xEventGroupSync.html. */
        vTaskSuspend( NULL );

        /* Set the bit that indicates this task is at the synchronisation
         * point.  The first time this is done the 'test master' task has a lower
         * priority than this task so this task will get to the sync point before
         * the set bits task - test this by calling xEventGroupSync() with a zero
         * block time before calling again with a max delay - the first call should
         * return before the rendezvous completes, the second only after the
         * rendezvous is complete. */
        uxReturned = xEventGroupSync( xEventGroup,          /* The event group used for the synchronisation. */
                                      uxSynchronisationBit, /* The bit to set in the event group to indicate this task is at the sync point. */
                                      ebALL_SYNC_BITS,      /* The bits to wait for - these bits are set by the other tasks taking part in the sync. */
                                      ebDONT_BLOCK );       /* The maximum time to wait for the sync condition to be met before giving up. */

        /* No block time was specified, so as per the comments above, the
         * rendezvous is not expected to have completed yet. */
        configASSERT( ( uxReturned & ebALL_SYNC_BITS ) != ebALL_SYNC_BITS );

        uxReturned = xEventGroupSync( xEventGroup,          /* The event group used for the synchronisation. */
                                      uxSynchronisationBit, /* The bit to set in the event group to indicate this task is at the sync point. */
                                      ebALL_SYNC_BITS,      /* The bits to wait for - these bits are set by the other tasks taking part in the sync. */
                                      portMAX_DELAY );      /* The maximum time to wait for the sync condition to be met before giving up. */

        /* A max delay was used, so this task should only exit the above
         * function call when the sync condition is met.  Check this is the
         * case. */
        configASSERT( ( uxReturned & ebALL_SYNC_BITS ) == ebALL_SYNC_BITS );

        /* Remove compiler warning if configASSERT() is not defined. */
        ( void ) uxReturned;

        /* Wait until the 'test master' task unsuspends this task again. */
        vTaskSuspend( NULL );

        /* Set the bit that indicates this task is at the synchronisation
         *  point again.  This time the 'test master' task has a higher priority
         *  than this task so will get to the sync point before this task. */
        uxReturned = xEventGroupSync( xEventGroup, uxSynchronisationBit, ebALL_SYNC_BITS, portMAX_DELAY );

        /* Again a max delay was used, so this task should only exit the above
         * function call when the sync condition is met.  Check this is the
         * case. */
        configASSERT( ( uxReturned & ebALL_SYNC_BITS ) == ebALL_SYNC_BITS );

        /* Block on the event group again.  This time the event group is going
         * to be deleted while this task is blocked on it so it is expected that 0
         * be returned. */
        uxReturned = xEventGroupWaitBits( xEventGroup, ebALL_SYNC_BITS, pdFALSE, pdTRUE, portMAX_DELAY );
        configASSERT( uxReturned == 0 );
    }
}
/*-----------------------------------------------------------*/

static void prvTestSlaveTask( void * pvParameters )
{
    EventBits_t uxReturned;
    BaseType_t xError = pdFALSE;

    /* Avoid compiler warnings. */
    ( void ) pvParameters;

    for( ; ; )
    {
        /**********************************************************************
         * Part 1:  This section is the counterpart to the
         * prvBitCombinationTestMasterFunction() function which is called by the
         * test master task.
         ***********************************************************************
         *
         * This task is controller by the 'test master' task (which is
         * implemented by prvTestMasterTask()).  Suspend until resumed by the
         * 'test master' task. */
        vTaskSuspend( NULL );

        /* Wait indefinitely for one of the bits in ebCOMBINED_BITS to get
         * set.  Clear the bit on exit. */
        uxReturned = xEventGroupWaitBits( xEventGroup,     /* The event group that contains the event bits being queried. */
                                          ebBIT_1,         /* The bit to wait for. */
                                          pdTRUE,          /* Clear the bit on exit. */
                                          pdTRUE,          /* Wait for all the bits (only one in this case anyway). */
                                          portMAX_DELAY ); /* Block indefinitely to wait for the condition to be met. */

        /* The 'test master' task set all the bits defined by ebCOMBINED_BITS,
         * only one of which was being waited for by this task.  The return value
         * shows the state of the event bits when the task was unblocked, however
         * because the task was waiting for ebBIT_1 and 'clear on exit' was set to
         * the current state of the event bits will have ebBIT_1 clear.  */
        if( uxReturned != ebCOMBINED_BITS )
        {
            xError = pdTRUE;
        }

        /* Now call xEventGroupWaitBits() again, this time waiting for all the
         * bits in ebCOMBINED_BITS to be set.  This call should block until the
         * 'test master' task sets ebBIT_1 - which was the bit cleared in the call
         * to xEventGroupWaitBits() above. */
        uxReturned = xEventGroupWaitBits( xEventGroup,
                                          ebCOMBINED_BITS, /* The bits being waited on. */
                                          pdFALSE,         /* Don't clear the bits on exit. */
                                          pdTRUE,          /* All the bits must be set to unblock. */
                                          portMAX_DELAY );

        /* Were all the bits set? */
        if( ( uxReturned & ebCOMBINED_BITS ) != ebCOMBINED_BITS )
        {
            xError = pdTRUE;
        }

        /* Suspend again to wait for the 'test master' task. */
        vTaskSuspend( NULL );

        /* Now call xEventGroupWaitBits() again, again waiting for all the bits
         * in ebCOMBINED_BITS to be set, but this time clearing the bits when the
         * task is unblocked. */
        uxReturned = xEventGroupWaitBits( xEventGroup,
                                          ebCOMBINED_BITS, /* The bits being waited on. */
                                          pdTRUE,          /* Clear the bits on exit. */
                                          pdTRUE,          /* All the bits must be set to unblock. */
                                          portMAX_DELAY );

        /* The 'test master' task set all the bits in the event group, so that
         * is the value that should have been returned.  The bits defined by
         * ebCOMBINED_BITS will have been clear again in the current value though
         * as 'clear on exit' was set to pdTRUE. */
        if( uxReturned != ebALL_BITS )
        {
            xError = pdTRUE;
        }

        /**********************************************************************
         * Part 2:  This section is the counterpart to the
         * prvPerformTaskSyncTests() function which is called by the
         * test master task.
         ***********************************************************************
         *
         *
         * Once again wait for the 'test master' task to unsuspend this task
         * when it is time for the next test. */
        vTaskSuspend( NULL );

        /* Now perform a synchronisation with all the other tasks.  At this point
         * the 'test master' task has the lowest priority so will get to the sync
         * point after all the other synchronising tasks. */
        uxReturned = xEventGroupSync( xEventGroup,              /* The event group used for the sync. */
                                      ebWAIT_BIT_TASK_SYNC_BIT, /* The bit in the event group used to indicate this task is at the sync point. */
                                      ebALL_SYNC_BITS,          /* The bits to wait for.  These bits are set by the other tasks taking part in the sync. */
                                      portMAX_DELAY );          /* The maximum time to wait for the sync condition to be met before giving up. */

        /* A sync with a max delay should only exit when all the synchronisation
         * bits are set... */
        if( ( uxReturned & ebALL_SYNC_BITS ) != ebALL_SYNC_BITS )
        {
            xError = pdTRUE;
        }

        /* ...but now the synchronisation bits should be clear again.  Read back
         * the current value of the bits within the event group to check that is
         * the case.  Setting the bits to zero will return the bits previous value
         * then leave all the bits clear. */
        if( xEventGroupSetBits( xEventGroup, 0x00 ) != 0 )
        {
            xError = pdTRUE;
        }

        /* Check the bits are indeed 0 now by simply reading then. */
        if( xEventGroupGetBits( xEventGroup ) != 0 )
        {
            xError = pdTRUE;
        }

        if( xError == pdFALSE )
        {
            /* This task is still cycling without finding an error. */
            ulTestSlaveCycles++;
        }

        vTaskSuspend( NULL );

        /* This time sync when the 'test master' task has the highest priority
         * at the point where it sets its sync bit - so this time the 'test master'
         * task will get to the sync point before this task. */
        uxReturned = xEventGroupSync( xEventGroup, ebWAIT_BIT_TASK_SYNC_BIT, ebALL_SYNC_BITS, portMAX_DELAY );

        /* A sync with a max delay should only exit when all the synchronisation
         * bits are set... */
        if( ( uxReturned & ebALL_SYNC_BITS ) != ebALL_SYNC_BITS )
        {
            xError = pdTRUE;
        }

        /* ...but now the sync bits should be clear again. */
        if( xEventGroupSetBits( xEventGroup, 0x00 ) != 0 )
        {
            xError = pdTRUE;
        }

        /* Block on the event group again.  This time the event group is going
         * to be deleted while this task is blocked on it, so it is expected that 0
         * will be returned. */
        uxReturned = xEventGroupWaitBits( xEventGroup, ebALL_SYNC_BITS, pdFALSE, pdTRUE, portMAX_DELAY );

        if( uxReturned != 0 )
        {
            xError = pdTRUE;
        }

        if( xError == pdFALSE )
        {
            /* This task is still cycling without finding an error. */
            ulTestSlaveCycles++;
        }

        configASSERT( xError == pdFALSE );
    }
}
/*-----------------------------------------------------------*/

static BaseType_t prvPerformTaskSyncTests( BaseType_t xError,
                                           TaskHandle_t xTestSlaveTaskHandle )
{
    EventBits_t uxBits;

    /* The three tasks that take part in the synchronisation (rendezvous) are
     * expected to be in the suspended state at the start of the test. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eSuspended )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask1 ) != eSuspended )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask2 ) != eSuspended )
    {
        xError = pdTRUE;
    }

    /* Try a synch with no other tasks involved.  First set all the bits other
     * than this task's bit. */
    xEventGroupSetBits( xEventGroup, ( ebALL_SYNC_BITS & ~ebSET_BIT_TASK_SYNC_BIT ) );

    /* Then wait on just one bit - the bit that is being set. */
    uxBits = xEventGroupSync( xEventGroup,             /* The event group used for the synchronisation. */
                              ebSET_BIT_TASK_SYNC_BIT, /* The bit set by this task when it reaches the sync point. */
                              ebSET_BIT_TASK_SYNC_BIT, /* The bits to wait for - in this case it is just waiting for itself. */
                              portMAX_DELAY );         /* The maximum time to wait for the sync condition to be met. */

    /* A sync with a max delay should only exit when all the synchronise
     * bits are set...check that is the case.  In this case there is only one
     * sync bit anyway. */
    if( ( uxBits & ebSET_BIT_TASK_SYNC_BIT ) != ebSET_BIT_TASK_SYNC_BIT )
    {
        xError = pdTRUE;
    }

    /* ...but now the sync bits should be clear again, leaving all the other
     * bits set (as only one bit was being waited for). */
    if( xEventGroupGetBits( xEventGroup ) != ( ebALL_SYNC_BITS & ~ebSET_BIT_TASK_SYNC_BIT ) )
    {
        xError = pdTRUE;
    }

    /* Clear all the bits to zero again. */
    xEventGroupClearBits( xEventGroup, ( ebALL_SYNC_BITS & ~ebSET_BIT_TASK_SYNC_BIT ) );

    if( xEventGroupGetBits( xEventGroup ) != 0 )
    {
        xError = pdTRUE;
    }

    /* Unsuspend the other tasks then check they have executed up to the
     * synchronisation point. */
    vTaskResume( xTestSlaveTaskHandle );
    vTaskResume( xSyncTask1 );
    vTaskResume( xSyncTask2 );

    if( eTaskGetState( xTestSlaveTaskHandle ) != eBlocked )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask1 ) != eBlocked )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask2 ) != eBlocked )
    {
        xError = pdTRUE;
    }

    /* Set this task's sync bit. */
    uxBits = xEventGroupSync( xEventGroup,             /* The event group used for the synchronisation. */
                              ebSET_BIT_TASK_SYNC_BIT, /* The bit set by this task when it reaches the sync point. */
                              ebALL_SYNC_BITS,         /* The bits to wait for - these bits are set by the other tasks that take part in the sync. */
                              portMAX_DELAY );         /* The maximum time to wait for the sync condition to be met. */

    /* A sync with a max delay should only exit when all the synchronise
     * bits are set...check that is the case. */
    if( ( uxBits & ebALL_SYNC_BITS ) != ebALL_SYNC_BITS )
    {
        xError = pdTRUE;
    }

    /* ...but now the sync bits should be clear again. */
    if( xEventGroupGetBits( xEventGroup ) != 0 )
    {
        xError = pdTRUE;
    }

    /* The other tasks should now all be suspended again, ready for the next
     * synchronisation. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eSuspended )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask1 ) != eSuspended )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask2 ) != eSuspended )
    {
        xError = pdTRUE;
    }

    /* Sync again - but this time set the last necessary bit as the
     * highest priority task, rather than the lowest priority task.  Unsuspend
     * the other tasks then check they have executed up to the	synchronisation
     * point. */
    vTaskResume( xTestSlaveTaskHandle );
    vTaskResume( xSyncTask1 );
    vTaskResume( xSyncTask2 );

    if( eTaskGetState( xTestSlaveTaskHandle ) != eBlocked )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask1 ) != eBlocked )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask2 ) != eBlocked )
    {
        xError = pdTRUE;
    }

    /* Raise the priority of this task above that of the other tasks. */
    vTaskPrioritySet( NULL, ebWAIT_BIT_TASK_PRIORITY + 1 );

    /* Set this task's sync bit. */
    uxBits = xEventGroupSync( xEventGroup, ebSET_BIT_TASK_SYNC_BIT, ebALL_SYNC_BITS, portMAX_DELAY );

    /* A sync with a max delay should only exit when all the synchronisation
     * bits are set... */
    if( ( uxBits & ebALL_SYNC_BITS ) != ebALL_SYNC_BITS )
    {
        xError = pdTRUE;
    }

    /* ...but now the sync bits should be clear again. */
    if( xEventGroupGetBits( xEventGroup ) != 0 )
    {
        xError = pdTRUE;
    }

    /* The other tasks should now all be in the ready state again, but not
     * executed yet as this task still has a higher relative priority. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eReady )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask1 ) != eReady )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask2 ) != eReady )
    {
        xError = pdTRUE;
    }

    /* Reset the priority of this task back to its original value. */
    vTaskPrioritySet( NULL, ebSET_BIT_TASK_PRIORITY );

    /* Now all the other tasks should have reblocked on the event bits
     * to test the behaviour when the event bits are deleted. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eBlocked )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask1 ) != eBlocked )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask2 ) != eBlocked )
    {
        xError = pdTRUE;
    }

    return xError;
}
/*-----------------------------------------------------------*/

static BaseType_t prvBitCombinationTestMasterFunction( BaseType_t xError,
                                                       TaskHandle_t xTestSlaveTaskHandle )
{
    EventBits_t uxBits;

    /* Resume the other task.  It will block, pending a single bit from
     * within ebCOMBINED_BITS. */
    vTaskResume( xTestSlaveTaskHandle );

    /* Ensure the other task is blocked on the task. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eBlocked )
    {
        xError = pdTRUE;
    }

    /* Set all the bits in ebCOMBINED_BITS - the 'test slave' task is only
     * blocked waiting for one of them. */
    xEventGroupSetBits( xEventGroup, ebCOMBINED_BITS );

    /* The 'test slave' task should now have executed, clearing ebBIT_1 (the
     * bit it was blocked on), then re-entered the Blocked state to wait for
     * all the other bits in ebCOMBINED_BITS to be set again.  First check
     * ebBIT_1 is clear. */
    uxBits = xEventGroupWaitBits( xEventGroup, ebALL_BITS, pdFALSE, pdFALSE, ebDONT_BLOCK );

    if( uxBits != ( ebCOMBINED_BITS & ~ebBIT_1 ) )
    {
        xError = pdTRUE;
    }

    /* Ensure the other task is still in the blocked state. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eBlocked )
    {
        xError = pdTRUE;
    }

    /* Set all the bits other than ebBIT_1 - which is the bit that must be
     * set before the other task unblocks. */
    xEventGroupSetBits( xEventGroup, ebALL_BITS & ~ebBIT_1 );

    /* Ensure all the expected bits are still set. */
    uxBits = xEventGroupWaitBits( xEventGroup, ebALL_BITS, pdFALSE, pdFALSE, ebDONT_BLOCK );

    if( uxBits != ( ebALL_BITS & ~ebBIT_1 ) )
    {
        xError = pdTRUE;
    }

    /* Ensure the other task is still in the blocked state. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eBlocked )
    {
        xError = pdTRUE;
    }

    /* Now also set ebBIT_1, which should unblock the other task, which will
     * then suspend itself. */
    xEventGroupSetBits( xEventGroup, ebBIT_1 );

    /* Ensure the other task is suspended. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eSuspended )
    {
        xError = pdTRUE;
    }

    /* The other task should not have cleared the bits - so all the bits
     * should still be set. */
    if( xEventGroupSetBits( xEventGroup, 0x00 ) != ebALL_BITS )
    {
        xError = pdTRUE;
    }

    /* Clear ebBIT_1 again. */
    if( xEventGroupClearBits( xEventGroup, ebBIT_1 ) != ebALL_BITS )
    {
        xError = pdTRUE;
    }

    /* Resume the other task - which will wait on all the ebCOMBINED_BITS
     * again - this time clearing the bits when it is unblocked. */
    vTaskResume( xTestSlaveTaskHandle );

    /* Ensure the other task is blocked once again. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eBlocked )
    {
        xError = pdTRUE;
    }

    /* Set the bit the other task is waiting for. */
    xEventGroupSetBits( xEventGroup, ebBIT_1 );

    /* Ensure the other task is suspended once again. */
    if( eTaskGetState( xTestSlaveTaskHandle ) != eSuspended )
    {
        xError = pdTRUE;
    }

    /* The other task should have cleared the bits in ebCOMBINED_BITS.
     * Clear the remaining bits. */
    uxBits = xEventGroupWaitBits( xEventGroup, ebALL_BITS, pdFALSE, pdFALSE, ebDONT_BLOCK );

    if( uxBits != ( ebALL_BITS & ~ebCOMBINED_BITS ) )
    {
        xError = pdTRUE;
    }

    /* Clear all bits ready for the sync with the other three tasks.  The
     * value returned is the value prior to the bits being cleared. */
    if( xEventGroupClearBits( xEventGroup, ebALL_BITS ) != ( ebALL_BITS & ~ebCOMBINED_BITS ) )
    {
        xError = pdTRUE;
    }

    /* The bits should be clear now. */
    if( xEventGroupGetBits( xEventGroup ) != 0x00 )
    {
        xError = pdTRUE;
    }

    return xError;
}
/*-----------------------------------------------------------*/

static void prvSelectiveBitsTestSlaveFunction( void )
{
    EventBits_t uxPendBits, uxReturned;

    /* Used in a test that blocks two tasks on various different bits within an
     * event group - then sets each bit in turn and checks that the correct tasks
     * unblock at the correct times.
     *
     * This function is called by two different tasks - each of which will use a
     * different bit.  Check the task handle to see which task the function was
     * called by. */
    if( xTaskGetCurrentTaskHandle() == xSyncTask1 )
    {
        uxPendBits = ebSELECTIVE_BITS_1;
    }
    else
    {
        uxPendBits = ebSELECTIVE_BITS_2;
    }

    for( ; ; )
    {
        /* Wait until it is time to perform the next cycle of the test.  The
         * task is unsuspended by the tests implemented in the
         * prvSelectiveBitsTestMasterFunction() function. */
        vTaskSuspend( NULL );
        uxReturned = xEventGroupWaitBits( xEventGroup, uxPendBits, pdTRUE, pdFALSE, portMAX_DELAY );

        if( uxReturned == ( EventBits_t ) 0 )
        {
            break;
        }
    }
}
/*-----------------------------------------------------------*/

static BaseType_t prvSelectiveBitsTestMasterFunction( void )
{
    BaseType_t xError = pdFALSE;
    EventBits_t uxBit;

    /* Used in a test that blocks two tasks on various different bits within an
     * event group - then sets each bit in turn and checks that the correct tasks
     * unblock at the correct times.  The two other tasks (xSyncTask1 and
     * xSyncTask2) call prvSelectiveBitsTestSlaveFunction() to perform their parts in
     * this test.
     *
     * Both other tasks should start in the suspended state. */
    if( eTaskGetState( xSyncTask1 ) != eSuspended )
    {
        xError = pdTRUE;
    }

    if( eTaskGetState( xSyncTask2 ) != eSuspended )
    {
        xError = pdTRUE;
    }

    /* Test each bit in the byte individually. */
    for( uxBit = 0x01; uxBit < 0x100; uxBit <<= 1 )
    {
        /* Resume both tasks. */
        vTaskResume( xSyncTask1 );
        vTaskResume( xSyncTask2 );

        /* Now both tasks should be blocked on the event group. */
        if( eTaskGetState( xSyncTask1 ) != eBlocked )
        {
            xError = pdTRUE;
        }

        if( eTaskGetState( xSyncTask2 ) != eBlocked )
        {
            xError = pdTRUE;
        }

        /* Set one bit. */
        xEventGroupSetBits( xEventGroup, uxBit );

        /* Is the bit set in the first set of selective bits?  If so the first
         * sync task should have unblocked and returned to the suspended state. */
        if( ( uxBit & ebSELECTIVE_BITS_1 ) == 0 )
        {
            /* Task should not have unblocked. */
            if( eTaskGetState( xSyncTask1 ) != eBlocked )
            {
                xError = pdTRUE;
            }
        }
        else
        {
            /* Task should have unblocked and returned to the suspended state. */
            if( eTaskGetState( xSyncTask1 ) != eSuspended )
            {
                xError = pdTRUE;
            }
        }

        /* Same checks for the second sync task. */
        if( ( uxBit & ebSELECTIVE_BITS_2 ) == 0 )
        {
            /* Task should not have unblocked. */
            if( eTaskGetState( xSyncTask2 ) != eBlocked )
            {
                xError = pdTRUE;
            }
        }
        else
        {
            /* Task should have unblocked and returned to the suspended state. */
            if( eTaskGetState( xSyncTask2 ) != eSuspended )
            {
                xError = pdTRUE;
            }
        }
    }

    /* Ensure both tasks are blocked on the event group again, then delete the
     * event group so the other tasks leave this portion of the test. */
    vTaskResume( xSyncTask1 );
    vTaskResume( xSyncTask2 );

    /* Deleting the event group is the signal that the two other tasks should
     * leave the prvSelectiveBitsTestSlaveFunction() function and continue to the main
     * part of their functionality. */
    vEventGroupDelete( xEventGroup );

    return xError;
}
/*-----------------------------------------------------------*/

void vPeriodicEventGroupsProcessing( void )
{
    static BaseType_t xCallCount = 0, xISRTestError = pdFALSE;
    const BaseType_t xSetBitCount = 100, xGetBitsCount = 200, xClearBitsCount = 300;
    const EventBits_t uxBitsToSet = 0x12U;
    EventBits_t uxReturned;
    BaseType_t xMessagePosted;

    /* Called periodically from the tick hook to exercise the "FromISR"
     * functions. */

    /* Check the even group tasks were actually created. */
    configASSERT( xISREventGroup );

    xCallCount++;

    if( xCallCount == xSetBitCount )
    {
        /* All the event bits should start clear. */
        uxReturned = xEventGroupGetBitsFromISR( xISREventGroup );

        if( uxReturned != 0x00 )
        {
            xISRTestError = pdTRUE;
        }
        else
        {
            /* Set the bits.  This is called from the tick hook so it is not
             * necessary to use the last parameter to ensure a context switch
             * occurs immediately. */
            xMessagePosted = xEventGroupSetBitsFromISR( xISREventGroup, uxBitsToSet, NULL );

            if( xMessagePosted != pdPASS )
            {
                xISRTestError = pdTRUE;
            }
        }
    }
    else if( xCallCount == xGetBitsCount )
    {
        /* Check the bits were set as expected. */
        uxReturned = xEventGroupGetBitsFromISR( xISREventGroup );

        if( uxReturned != uxBitsToSet )
        {
            xISRTestError = pdTRUE;
        }
    }
    else if( xCallCount == xClearBitsCount )
    {
        /* Clear the bits again. */
        uxReturned = ( EventBits_t ) xEventGroupClearBitsFromISR( xISREventGroup, uxBitsToSet );

        /* Check the message was posted. */
        if( uxReturned != pdPASS )
        {
            xISRTestError = pdTRUE;
        }

        /* Go back to the start. */
        xCallCount = 0;

        /* If no errors have been detected then increment the count of test
         * cycles. */
        if( xISRTestError == pdFALSE )
        {
            ulISRCycles++;
        }
    }
    else
    {
        /* Nothing else to do. */
    }
}

/*-----------------------------------------------------------*/
/* This is called to check that all the created tasks are still running. */
BaseType_t xAreEventGroupTasksStillRunning( void )
{
    static uint32_t ulPreviousWaitBitCycles = 0, ulPreviousSetBitCycles = 0, ulPreviousISRCycles = 0;
    BaseType_t xStatus = pdPASS;

    /* Check the tasks are still cycling without finding any errors. */
    if( ulPreviousSetBitCycles == ulTestMasterCycles )
    {
        xStatus = pdFAIL;
    }

    ulPreviousSetBitCycles = ulTestMasterCycles;

    if( ulPreviousWaitBitCycles == ulTestSlaveCycles )
    {
        xStatus = pdFAIL;
    }

    ulPreviousWaitBitCycles = ulTestSlaveCycles;

    if( ulPreviousISRCycles == ulISRCycles )
    {
        xStatus = pdFAIL;
    }

    ulPreviousISRCycles = ulISRCycles;

    return xStatus;
}
