/*
 * FreeRTOS V202212.00
 * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * https://www.FreeRTOS.org
 * https://github.com/FreeRTOS
 *
 */

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"

/**
 * @brief Size of the shared memory region.
 */
#define SHARED_MEMORY_SIZE    32

/**
 * @brief Memory region shared between two tasks.
 */
static uint8_t ucSharedMemory[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
#if ( configTOTAL_MPU_REGIONS == 16 )
    static uint8_t ucSharedMemory1[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
    static uint8_t ucSharedMemory2[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
    static uint8_t ucSharedMemory3[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
    static uint8_t ucSharedMemory4[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
    static uint8_t ucSharedMemory5[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
    static uint8_t ucSharedMemory6[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
    static uint8_t ucSharedMemory7[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
    static uint8_t ucSharedMemory8[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) );
#endif /* configTOTAL_MPU_REGIONS == 16 */

/**
 * @brief Memory region used to track Memory Fault intentionally caused by the
 * RO Access task.
 *
 * RO Access task sets ucROTaskFaultTracker[ 0 ] to 1 before accessing illegal
 * memory. Illegal memory access causes Memory Fault and the fault handler
 * checks ucROTaskFaultTracker[ 0 ] to see if this is an expected fault. We
 * recover gracefully from an expected fault by jumping to the next instruction.
 *
 * @note We are declaring a region of 32 bytes even though we need only one. The
 * reason is that the size of an MPU region must be a multiple of 32 bytes.
 */
static volatile uint8_t ucROTaskFaultTracker[ SHARED_MEMORY_SIZE ] __attribute__( ( aligned( 32 ) ) ) = { 0 };
/*-----------------------------------------------------------*/

/**
 * @brief Implements the task which has Read Only access to the memory region
 * ucSharedMemory.
 *
 * @param pvParameters[in] Parameters as passed during task creation.
 */
static void prvROAccessTask( void * pvParameters );

/**
 * @brief Implements the task which has Read Write access to the memory region
 * ucSharedMemory.
 *
 * @param pvParameters[in] Parameters as passed during task creation.
 */
static void prvRWAccessTask( void * pvParameters );

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

static void prvROAccessTask( void * pvParameters )
{
    uint8_t ucVal;

    /* Unused parameters. */
    ( void ) pvParameters;

    for( ; ; )
    {
        /* This task performs the following sequence for all the shared memory
         * regions:
         *
         * 1. Perfrom a read access to the shared memory. Since this task has
         *    RO access to the shared memory, the read operation is successful.
         *
         * 2. Set ucROTaskFaultTracker[ 0 ] to 1 before performing a write to
         *    the shared memory. Since this task has Read Only access to the
         *    shared memory, the write operation would result in a Memory Fault.
         *    Setting ucROTaskFaultTracker[ 0 ] to 1 tells the Memory Fault
         *    Handler that this is an expected fault. The handler recovers from
         *    the expected fault gracefully by jumping to the next instruction.
         *
         * 3. Perfrom a write to the shared memory resulting in a memory fault.
         *
         * 4. Ensure that the write access did generate MemFault and the fault
         *    handler did clear the ucROTaskFaultTracker[ 0 ].
         */
        /* Perform the above mentioned sequence on ucSharedMemory. */
        ucVal = ucSharedMemory[ 0 ];
        /* Silent compiler warnings about unused variables. */
        ( void ) ucVal;
        ucROTaskFaultTracker[ 0 ] = 1;
        ucSharedMemory[ 0 ] = 0;
        configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );

        #if ( configTOTAL_MPU_REGIONS == 16 )
        {
            /* Perform the above mentioned sequence on ucSharedMemory1. */
            ucVal = ucSharedMemory1[ 0 ];
            /* Silent compiler warnings about unused variables. */
            ( void ) ucVal;
            ucROTaskFaultTracker[ 0 ] = 1;
            ucSharedMemory1[ 0 ] = 0;
            configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );

            /* Perform the above mentioned sequence on ucSharedMemory2. */
            ucVal = ucSharedMemory2[ 0 ];
            /* Silent compiler warnings about unused variables. */
            ( void ) ucVal;
            ucROTaskFaultTracker[ 0 ] = 1;
            ucSharedMemory2[ 0 ] = 0;
            configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );

            /* Perform the above mentioned sequence on ucSharedMemory3. */
            ucVal = ucSharedMemory3[ 0 ];
            /* Silent compiler warnings about unused variables. */
            ( void ) ucVal;
            ucROTaskFaultTracker[ 0 ] = 1;
            ucSharedMemory3[ 0 ] = 0;
            configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );

            /* Perform the above mentioned sequence on ucSharedMemory4. */
            ucVal = ucSharedMemory4[ 0 ];
            /* Silent compiler warnings about unused variables. */
            ( void ) ucVal;
            ucROTaskFaultTracker[ 0 ] = 1;
            ucSharedMemory4[ 0 ] = 0;
            configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );

            /* Perform the above mentioned sequence on ucSharedMemory5. */
            ucVal = ucSharedMemory5[ 0 ];
            /* Silent compiler warnings about unused variables. */
            ( void ) ucVal;
            ucROTaskFaultTracker[ 0 ] = 1;
            ucSharedMemory5[ 0 ] = 0;
            configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );

            /* Perform the above mentioned sequence on ucSharedMemory6. */
            ucVal = ucSharedMemory6[ 0 ];
            /* Silent compiler warnings about unused variables. */
            ( void ) ucVal;
            ucROTaskFaultTracker[ 0 ] = 1;
            ucSharedMemory6[ 0 ] = 0;
            configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );

            /* Perform the above mentioned sequence on ucSharedMemory7. */
            ucVal = ucSharedMemory7[ 0 ];
            /* Silent compiler warnings about unused variables. */
            ( void ) ucVal;
            ucROTaskFaultTracker[ 0 ] = 1;
            ucSharedMemory7[ 0 ] = 0;
            configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );

            /* Perform the above mentioned sequence on ucSharedMemory8. */
            ucVal = ucSharedMemory8[ 0 ];
            /* Silent compiler warnings about unused variables. */
            ( void ) ucVal;
            ucROTaskFaultTracker[ 0 ] = 1;
            ucSharedMemory8[ 0 ] = 0;
            configASSERT( ucROTaskFaultTracker[ 0 ] == 0 );
        }
        #endif /* configTOTAL_MPU_REGIONS == 16 */

        /* Wait for a second. */
        vTaskDelay( pdMS_TO_TICKS( 1000 ) );
    }
}
/*-----------------------------------------------------------*/

static void prvRWAccessTask( void * pvParameters )
{
    /* Unused parameters. */
    ( void ) pvParameters;

    for( ; ; )
    {
        /* This task has RW access to ucSharedMemory and therefore can write to
         * it. */
        ucSharedMemory[ 0 ] = 0;

        #if ( configTOTAL_MPU_REGIONS == 16 )
        {
            ucSharedMemory1[ 0 ] = 0;
            ucSharedMemory2[ 0 ] = 0;
            ucSharedMemory3[ 0 ] = 0;
            ucSharedMemory4[ 0 ] = 0;
            ucSharedMemory5[ 0 ] = 0;
            ucSharedMemory6[ 0 ] = 0;
            ucSharedMemory7[ 0 ] = 0;
            ucSharedMemory8[ 0 ] = 0;
        }
        #endif /* configTOTAL_MPU_REGIONS == 16 */

        /* Wait for a second. */
        vTaskDelay( pdMS_TO_TICKS( 1000 ) );
    }
}
/*-----------------------------------------------------------*/

void vStartMPUDemo( void )
{
    static StackType_t xROAccessTaskStack[ configMINIMAL_STACK_SIZE ] __attribute__( ( aligned( 32 ) ) );
    static StackType_t xRWAccessTaskStack[ configMINIMAL_STACK_SIZE ] __attribute__( ( aligned( 32 ) ) );
    TaskParameters_t xROAccessTaskParameters =
    {
        .pvTaskCode     = prvROAccessTask,
        .pcName         = "ROAccess",
        .usStackDepth   = configMINIMAL_STACK_SIZE,
        .pvParameters   = NULL,
        .uxPriority     = tskIDLE_PRIORITY,
        .puxStackBuffer = xROAccessTaskStack,
        .xRegions       =
        {
            { ( void * ) ucSharedMemory,       32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            #if ( configTOTAL_MPU_REGIONS == 16 )
            { ( void * ) ucSharedMemory1,      32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            { ( void * ) ucSharedMemory2,      32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            { ( void * ) ucSharedMemory3,      32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            { ( void * ) ucSharedMemory4,      32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            { ( void * ) ucSharedMemory5,      32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            { ( void * ) ucSharedMemory6,      32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            { ( void * ) ucSharedMemory7,      32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            { ( void * ) ucSharedMemory8,      32, tskMPU_REGION_READ_ONLY | tskMPU_REGION_EXECUTE_NEVER  },
            #endif /* configTOTAL_MPU_REGIONS == 16 */
            { ( void * ) ucROTaskFaultTracker, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            { 0,                               0,  0                                                      },
        }
    };
    TaskParameters_t xRWAccessTaskParameters =
    {
        .pvTaskCode     = prvRWAccessTask,
        .pcName         = "RWAccess",
        .usStackDepth   = configMINIMAL_STACK_SIZE,
        .pvParameters   = NULL,
        .uxPriority     = tskIDLE_PRIORITY,
        .puxStackBuffer = xRWAccessTaskStack,
        .xRegions       =
        {
            { ( void * ) ucSharedMemory,  32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            #if ( configTOTAL_MPU_REGIONS == 16 )
            { ( void * ) ucSharedMemory1, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            { ( void * ) ucSharedMemory2, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            { ( void * ) ucSharedMemory3, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            { ( void * ) ucSharedMemory4, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            { ( void * ) ucSharedMemory5, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            { ( void * ) ucSharedMemory6, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            { ( void * ) ucSharedMemory7, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            { ( void * ) ucSharedMemory8, 32, tskMPU_REGION_READ_WRITE | tskMPU_REGION_EXECUTE_NEVER },
            #endif /* configTOTAL_MPU_REGIONS == 16 */
            { 0,                          0,  0                                                      },
            { 0,                          0,  0                                                      },
        }
    };

    /* Create an unprivileged task with RO access to ucSharedMemory. */
    xTaskCreateRestricted( &( xROAccessTaskParameters ), NULL );

    /* Create an unprivileged task with RW access to ucSharedMemory. */
    xTaskCreateRestricted( &( xRWAccessTaskParameters ), NULL );
}
/*-----------------------------------------------------------*/

portDONT_DISCARD void vHandleMemoryFault( uint32_t * pulFaultStackAddress )
{
    uint32_t ulPC;
    uint16_t usOffendingInstruction;

    /* Is this an expected fault? */
    if( ucROTaskFaultTracker[ 0 ] == 1 )
    {
        /* Read program counter. */
        ulPC = pulFaultStackAddress[ 6 ];

        /* Read the offending instruction. */
        usOffendingInstruction = *( uint16_t * ) ulPC;

        /* From ARM docs:
         * If the value of bits[15:11] of the halfword being decoded is one of
         * the following, the halfword is the first halfword of a 32-bit
         * instruction:
         * - 0b11101.
         * - 0b11110.
         * - 0b11111.
         * Otherwise, the halfword is a 16-bit instruction.
         */

        /* Extract bits[15:11] of the offending instruction. */
        usOffendingInstruction = usOffendingInstruction & 0xF800;
        usOffendingInstruction = ( usOffendingInstruction >> 11 );

        /* Determine if the offending instruction is a 32-bit instruction or
         * a 16-bit instruction. */
        if( ( usOffendingInstruction == 0x001F ) ||
            ( usOffendingInstruction == 0x001E ) ||
            ( usOffendingInstruction == 0x001D ) )
        {
            /* Since the offending instruction is a 32-bit instruction,
             * increment the program counter by 4 to move to the next
             * instruction. */
            ulPC += 4;
        }
        else
        {
            /* Since the offending instruction is a 16-bit instruction,
             * increment the program counter by 2 to move to the next
             * instruction. */
            ulPC += 2;
        }

        /* Save the new program counter on the stack. */
        pulFaultStackAddress[ 6 ] = ulPC;

        /* Mark the fault as handled. */
        ucROTaskFaultTracker[ 0 ] = 0;
    }
    else
    {
        /* This is an unexpected fault - loop forever. */
        for( ; ; )
        {
        }
    }
}
/*-----------------------------------------------------------*/
