/*
 * FreeRTOS V202012.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.
 *
 * http://www.FreeRTOS.org
 * http://aws.amazon.com/freertos
 *
 * 1 tab == 4 spaces!
 */

/*
 * An example that mimics a message buffer being used to pass data from one core
 * to another.  The core that sends the data is referred to as core A.  The core
 * that receives the data is referred to as core B.  The task implemented by
 * prvCoreATask() runs on core A.  Two instances of the task implemented by
 * prvCoreBTasks() run on core B.  prvCoreATask() sends messages via message
 * buffers to both instances of prvCoreBTasks(), one message buffer per channel.
 * A third message buffer is used to pass the handle of the message buffer
 * written to by core A to an interrupt service routine that is triggered by
 * core A but executes on core B.
 *
 * The example relies on the FreeRTOS provided default implementation of
 * sbSEND_COMPLETED() being overridden by an implementation in FreeRTOSConfig.h
 * that writes the handle of the message buffer that contains data into the
 * control message buffer, then generates an interrupt in core B.  The necessary
 * implementation is provided in this file and can be enabled by adding the
 * following to FreeRTOSConfig.h:
 *
 * #define sbSEND_COMPLETED( pxStreamBuffer ) vGenerateCoreBInterrupt( pxStreamBuffer )
 *
 * Core to core communication via message buffer requires the message buffers
 * to be at an address known to both cores within shared memory.
 *
 * Note that, while this example uses three message buffers, the same
 * functionality can be implemented using a single message buffer by using the
 * same design pattern described on the link below for queues, but using message
 * buffers instead.  It is actually simpler with a message buffer as variable
 * length data can be written into the message buffer directly:
 * http://www.freertos.org/Pend-on-multiple-rtos-objects.html#alternative_design_pattern
 */

/* Standard includes. */
#include "stdio.h"
#include "string.h"

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

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

/* Enough for 3 4 byte pointers, including the additional 4 bytes per message
overhead of message buffers. */
#define mbaCONTROL_MESSAGE_BUFFER_SIZE ( 24 )

/* Enough four 4 8 byte strings, plus the additional 4 bytes per message
overhead of message buffers. */
#define mbaTASK_MESSAGE_BUFFER_SIZE ( 60 )

/* The number of instances of prvCoreBTasks that are created. */
#define mbaNUMBER_OF_CORE_B_TASKS	2

/* A block time of 0 simply means, don't block. */
#define mbaDONT_BLOCK				0

/* Macro that mimics an interrupt service routine executing by simply calling
the routine inline. */
#define mbaGENERATE_CORE_B_INTERRUPT() prvCoreBInterruptHandler()

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

/*
 * Implementation of the task that, on a real dual core device, would run on
 * core A and send message to tasks running on core B.
 */
static void prvCoreATask( void *pvParameters );

/*
 * Implementation of the task that, on a real dual core device, would run on
 * core B and receive message from core A.  The demo creates two instances of
 * this task.
 */
static void prvCoreBTasks( void *pvParameters );

/*
 * The function that, on a real dual core device, would handle inter-core
 * interrupts, but in this case is just called inline.
 */
static void prvCoreBInterruptHandler( void );

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

/* The message buffers used to pass data from core A to core B. */
static MessageBufferHandle_t xCoreBMessageBuffers[ mbaNUMBER_OF_CORE_B_TASKS ];

/* The control message buffer.  This is used to pass the handle of the message
message buffer that holds application data into the core to core interrupt
service routine. */
static MessageBufferHandle_t xControlMessageBuffer;

/* Counters used to indicate to the check that the tasks are still executing. */
static uint32_t ulCycleCounters[ mbaNUMBER_OF_CORE_B_TASKS ];

/* Set to pdFALSE if any errors are detected.  Used to inform the check task
that something might be wrong. */
BaseType_t xDemoStatus = pdPASS;

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

void vStartMessageBufferAMPTasks( configSTACK_DEPTH_TYPE xStackSize )
{
BaseType_t x;

	xControlMessageBuffer = xMessageBufferCreate( mbaCONTROL_MESSAGE_BUFFER_SIZE );

	xTaskCreate( prvCoreATask,		/* The function that implements the task. */
				 "AMPCoreA",		/* Human readable name for the task. */
				 xStackSize,		/* Stack size (in words!). */
				 NULL,				/* Task parameter is not used. */
				 tskIDLE_PRIORITY,	/* The priority at which the task is created. */
				 NULL );			/* No use for the task handle. */

	for( x = 0; x < mbaNUMBER_OF_CORE_B_TASKS; x++ )
	{
		xCoreBMessageBuffers[ x ] = xMessageBufferCreate( mbaTASK_MESSAGE_BUFFER_SIZE );
		configASSERT( xCoreBMessageBuffers[ x ] );

		/* Pass the loop counter into the created task using the task's
		parameter.  The task then uses the value as an index into the
		ulCycleCounters and xCoreBMessageBuffers arrays. */
		xTaskCreate( prvCoreBTasks,
					 "AMPCoreB1",
					 xStackSize,
					 ( void * ) x,
					 tskIDLE_PRIORITY + 1,
					 NULL );
	}
}
/*-----------------------------------------------------------*/

static void prvCoreATask( void *pvParameters )
{
BaseType_t x;
uint32_t ulNextValue = 0;
const TickType_t xDelay = pdMS_TO_TICKS( 250 );
char cString[ 15 ]; /* At least large enough to hold "4294967295\0" (0xffffffff). */

	/* Remove warning about unused parameters. */
	( void ) pvParameters;

	for( ;; )
	{
		/* Create the next string to send.  The value is incremented on each
		loop iteration, and the length of the string changes as the number of
		digits in the value increases. */
		sprintf( cString, "%lu", ( unsigned long ) ulNextValue );

		/* Send the value from this (pseudo) Core A to the tasks on the (pseudo)
		Core B via the message buffers.  This will result in sbSEND_COMPLETED()
		being executed, which in turn will write the handle of the message
		buffer written to into xControlMessageBuffer then generate an interrupt
		in core B. */
		for( x = 0; x < mbaNUMBER_OF_CORE_B_TASKS; x++ )
		{
			xMessageBufferSend( /* The message buffer to write to. */
								xCoreBMessageBuffers[ x ],
								/* The source of the data to send. */
								( void * ) cString,
								/* The length of the data to send. */
								strlen( cString ),
								/* The block time, should the buffer be full. */
								mbaDONT_BLOCK );
		}

		/* Delay before repeating with a different and potentially different
		length string. */
		vTaskDelay( xDelay );
		ulNextValue++;
	}
}
/*-----------------------------------------------------------*/

static void prvCoreBTasks( void *pvParameters )
{
BaseType_t x;
size_t xReceivedBytes;
uint32_t ulNextValue = 0;
char cExpectedString[ 15 ]; /* At least large enough to hold "4294967295\0" (0xffffffff). */
char cReceivedString[ 15 ];

	/* The index into the xCoreBMessageBuffers and ulLoopCounter arrays is
	passed into this task using the task's parameter. */
	x = ( BaseType_t ) pvParameters;
	configASSERT( x < mbaNUMBER_OF_CORE_B_TASKS );

	for( ;; )
	{
		/* Create the string that is expected to be received this time round. */
		sprintf( cExpectedString, "%lu", ( unsigned long ) ulNextValue );

		/* Wait to receive the next message from core A. */
		memset( cReceivedString, 0x00, sizeof( cReceivedString ) );
		xReceivedBytes = xMessageBufferReceive( /* The message buffer to receive from. */
												xCoreBMessageBuffers[ x ],
												/* Location to store received data. */
												cReceivedString,
												/* Maximum number of bytes to receive. */
												sizeof( cReceivedString ),
												/* Ticks to wait if buffer is empty. */
												portMAX_DELAY );

		/* Check the number of bytes received was as expected. */
		configASSERT( xReceivedBytes == strlen( cExpectedString ) );
		( void ) xReceivedBytes; /* Incase configASSERT() is not defined. */

		/* If the received string matches that expected then increment the loop
		counter so the check task knows this task is still running. */
		if( strcmp( cReceivedString, cExpectedString ) == 0 )
		{
			( ulCycleCounters[ x ] )++;
		}
		else
		{
			xDemoStatus = pdFAIL;
		}

		/* Expect the next string in sequence the next time around. */
		ulNextValue++;
	}
}
/*-----------------------------------------------------------*/

/* Called by the reimplementation of sbSEND_COMPLETED(), which can be defined
as follows in FreeRTOSConfig.h:
#define sbSEND_COMPLETED( pxStreamBuffer ) vGenerateCoreBInterrupt( pxStreamBuffer )
*/
void vGenerateCoreBInterrupt( void * xUpdatedMessageBuffer )
{
MessageBufferHandle_t xUpdatedBuffer = ( MessageBufferHandle_t ) xUpdatedMessageBuffer;

	/* If sbSEND_COMPLETED() has been implemented as above, then this function
	is called from within xMessageBufferSend().  As this function also calls
	xMessageBufferSend() itself it is necessary to guard against a recursive
	call.  If the message buffer just updated is the message buffer written to
	by this function, then this is a recursive call, and the function can just
	exit without taking further action. */
	if( xUpdatedBuffer != xControlMessageBuffer )
	{
		/* Use xControlMessageBuffer to pass the handle of the message buffer
		written to by core A to the interrupt handler about to be generated in
		core B. */
		xMessageBufferSend( xControlMessageBuffer, &xUpdatedBuffer, sizeof( xUpdatedBuffer ), mbaDONT_BLOCK );

		/* This is where the interrupt would be generated.  In this case it is
		not a genuine interrupt handler that executes, just a standard function
		call. */
		mbaGENERATE_CORE_B_INTERRUPT();
	}
}
/*-----------------------------------------------------------*/

/* Handler for the interrupts that are triggered on core A but execute on core
B. */
static void prvCoreBInterruptHandler( void )
{
MessageBufferHandle_t xUpdatedMessageBuffer;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;

	/* xControlMessageBuffer contains the handle of the message buffer that
	contains data. */
	if( xMessageBufferReceive( xControlMessageBuffer,
							   &xUpdatedMessageBuffer,
							   sizeof( xUpdatedMessageBuffer ),
							   mbaDONT_BLOCK ) == sizeof( xUpdatedMessageBuffer ) )
	{
		/* Call the API function that sends a notification to any task that is
		blocked on the xUpdatedMessageBuffer message buffer waiting for data to
		arrive. */
		xMessageBufferSendCompletedFromISR( xUpdatedMessageBuffer, &xHigherPriorityTaskWoken );
	}

	/* Normal FreeRTOS yield from interrupt semantics, where
	xHigherPriorityTaskWoken is initialzed to pdFALSE and will then get set to
	pdTRUE if the interrupt safe API unblocks a task that has a priority above
	that of the currently executing task. */
	portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
/*-----------------------------------------------------------*/

BaseType_t xAreMessageBufferAMPTasksStillRunning( void )
{
static uint32_t ulLastCycleCounters[ mbaNUMBER_OF_CORE_B_TASKS ] = { 0 };
BaseType_t x;

	/* Called by the check task to determine the health status of the tasks
	implemented in this demo. */
	for( x = 0; x < mbaNUMBER_OF_CORE_B_TASKS; x++ )
	{
		if( ulLastCycleCounters[ x ] == ulCycleCounters[ x ] )
		{
			xDemoStatus = pdFAIL;
		}
		else
		{
			ulLastCycleCounters[ x ] = ulCycleCounters[ x ];
		}
	}

	return xDemoStatus;
}

