/* ---------------------------------------------------------------------------
 * IspProtocol.c - v0.1                                (c) 2008 Micro-key bv
 * ---------------------------------------------------------------------------
 * Micro-key bv
 * Industrieweg 28, 9804 TG Noordhorn
 * Postbus 92, 9800 AB Zuidhorn
 * The Netherlands
 * Tel: +31 594 503020
 * Fax: +31 594 505825
 * Email: support@microkey.nl  
 * Web: www.microkey.nl
 * ---------------------------------------------------------------------------
 * Description: 
 * ---------------------------------------------------------------------------
 * Version(s):  0.1, Feb 13, 2008, FSc
 *                   Creation.
 * ---------------------------------------------------------------------------
 */

/* ---------------------------------------------------------------------------
 * System include files
 * ---------------------------------------------------------------------------
 */

/* ---------------------------------------------------------------------------
 * Application include files
 * ---------------------------------------------------------------------------
 */
#include "types.h"
#include "IspProtocol.h"
#include "InternalFlash.h"
#include "Crc.h"
#include "Leds.h"
#include "appImage.h"

/* ---------------------------------------------------------------------------
 * Local constant and macro definitions
 * ---------------------------------------------------------------------------
 */
#define MAX_ADMINS              (4)
#define START_BYTE              (0xAA)
#define MAX_PAYLOAD_SIZE        (80) 

#define MSGID_ACKNOWLEDGE       (0x01)
#define MSGID_UNLOCK            (0x02)
#define MSGID_ERASEBLOCK        (0x03)
#define MSGID_PROGRAMFLASH      (0x04)
#define MSGID_VERIFYFLASH       (0x05)
#define MSGID_FINISHPROGRAMMING (0x06)
#define MSGID_STARTPROGRAM      (0x07)

#define APP_FLASH_START_ADDR    (0x00005000)
#define APP_FLASH_END_ADDR      (0x0007DFFF)
#define APP_FLASH_START_SECTOR  (5)
#define APP_FLASH_END_SECTOR    (27)
#define APP_IMAGE_LENGTH_OFFS   0x0008
#define APP_IMAGE_CRC_OFFS      0x000C

#define PROGRAM_BLOCK_SIZE      (256)
#define INVALID_ADDRESS         (0xFFFFFFFF)


/* ---------------------------------------------------------------------------
 * Global variable definitions
 * ---------------------------------------------------------------------------
 */

/* ---------------------------------------------------------------------------
 * Local variable definitions
 * ---------------------------------------------------------------------------
 */
typedef enum
{
	IDLE,
	MESSAGE_ID,
	PAYLOAD_SIZE,
	PAYLOAD,
	CRC
} t_isp_decodestatus;

typedef struct t_ISP_MESSAGE {
	UINT8                  messageId;
	UINT8                  payloadSize;
	UINT8                  payload[MAX_PAYLOAD_SIZE];
	UINT16                 crc;
} t_isp_message;

typedef struct t_ISP_ADMIN {
	t_isp_decodestatus   status; 
	UINT8                rxIndex;
	UINT16               rxCrc;
	t_isp_message        rxMessage;
	t_isp_ack_callback   ackCallback;
} t_isp_admin;

static t_isp_admin ispAdmins[MAX_ADMINS];
static UINT8 lastReservedAdmin = 0;
static UINT32 maxProgrammedAddress = 0;
static UINT32 startAddress = INVALID_ADDRESS;
static UINT8 verifyBlock[PROGRAM_BLOCK_SIZE] __attribute__((aligned(PROGRAM_BLOCK_SIZE)));
static UINT8 programBlock[PROGRAM_BLOCK_SIZE] __attribute__((aligned(PROGRAM_BLOCK_SIZE)));
static UINT8 programFirstBlock[PROGRAM_BLOCK_SIZE] __attribute__((aligned(PROGRAM_BLOCK_SIZE)));

/* ---------------------------------------------------------------------------
 * Local function definitions
 * ---------------------------------------------------------------------------
 */
void sendAcknowledge(t_isp_admin *handle, t_isp_responses response);
BOOLEAN ValidMessageId( UINT8 messageId);
void handleMessage(t_isp_admin *admin);
void handleUnlockMessage(t_isp_admin *admin);
void handleEraseBlockMessage(t_isp_admin *admin);
void handleStartProgram(t_isp_admin *admin);
void handleFinishProgramming(t_isp_admin *admin);
void handleVerifyFlashMessage(t_isp_admin *admin);
void handleProgramFlashMessage(t_isp_admin *admin);
iflashresult fillProgramBlock( UINT32 address, UINT8 size, UINT8 *data);
iflashresult programFlash();
void resetRamBlock( UINT32 address );
iflashresult StoreCrcAndLength( UINT32 imageSize );



/** \brief Initialises a instance of the ISP-protocol handler. 
 * 
 * For each communication port, one ISP-protocol handler must be initialised.
 * 
 * \param ackCallback Callback-function used for parent to send acknowledge
 * \returns handle Handle for this ISP-protocol handler.
 */
int ispInitProtocol(t_isp_ack_callback ackCallback )
{
	int result;
	if (lastReservedAdmin < MAX_ADMINS)
	{
		ispAdmins[lastReservedAdmin].status = IDLE;
		ispAdmins[lastReservedAdmin].ackCallback = ackCallback;
		          
		result = lastReservedAdmin;
		lastReservedAdmin++;
	}
	else
	{
		result = -1;
	}
	return result;
}

/** \brief Does the protocol handling by parsing each byte received on the communication port. 
 * 
 * \param handle Handle for the ISP-protocol handler
 * \param byte The received byte on the communication port
 */
void ispHandleRxByte( int handle, UINT8 byte )
{
	t_isp_admin *admin = &(ispAdmins[handle]);

	switch( admin->status )
	{
	case(IDLE):
		if (byte == START_BYTE)  
		{
			admin->status = MESSAGE_ID;
		}
		break;
	case(MESSAGE_ID):
		if (ValidMessageId(byte) == TRUE )
		{
			// Determine Payload-size
			admin->rxCrc = crcCalc(&byte, 1, 0);
			admin->rxMessage.messageId = byte;
			admin->status = PAYLOAD_SIZE;
		}
		else
		{
			sendAcknowledge( admin, ISP_INVALID_MESSAGE_ID );
			admin->status = IDLE;
		}
		break;
	case(PAYLOAD_SIZE):
		// Determine Payload-size
		if (byte <=MAX_PAYLOAD_SIZE)
		{
			admin->rxMessage.payloadSize = byte;
			admin->rxCrc = crcCalc(&byte, 1, admin->rxCrc);
			admin->rxIndex = 0;
			if (byte > 0)
			{
				admin->status = PAYLOAD;
			}
			else
			{
				admin->status = CRC;
			}
		}
		else
		{
			sendAcknowledge( admin, ISP_PAYLOAD_TOO_LARGE );
			admin->status = IDLE;
		}
		break;
	case(PAYLOAD):
		admin->rxMessage.payload[ admin->rxIndex ] = byte;
		admin->rxCrc = crcCalc(&byte, 1, admin->rxCrc);
		admin->rxIndex++;
	
		if (admin->rxIndex >= admin->rxMessage.payloadSize)
		{
			admin->rxIndex = 0;
			admin->status = CRC;
		}
		break;
	case(CRC):
		// Receive and check CRC
		if (admin->rxIndex == 0)
		{
			admin->rxMessage.crc = (byte << 8);
		}
		else
		{
			admin->rxMessage.crc |= (byte & 0x00FF);
		}
	
		admin->rxIndex++;
	
		if (admin->rxIndex >= 2)
		{
			if (admin->rxCrc == admin->rxMessage.crc)
			{
				/* Crc was succesfully checked */
				handleMessage(admin);
			}
			else
			{
				sendAcknowledge( admin, ISP_RECV_BAD_CRC );
			}
			admin->status = IDLE; 
		}
		break;
	default:
		admin->status = IDLE;
		break;
	}
}

void handleMessage(t_isp_admin *admin)
{
	switch( admin->rxMessage.messageId )
	{
	case(MSGID_UNLOCK):
		//sendAcknowledge( admin, ISP_CMD_SUCCESS);
		handleUnlockMessage( admin );
		break;
	case(MSGID_ERASEBLOCK):
		handleEraseBlockMessage( admin );
		break;
	case(MSGID_PROGRAMFLASH):
		handleProgramFlashMessage( admin );
		break;
	case(MSGID_VERIFYFLASH):
		handleVerifyFlashMessage( admin );
		break;
	case(MSGID_FINISHPROGRAMMING):
		handleFinishProgramming( admin );
		break;
	case(MSGID_STARTPROGRAM):
		handleStartProgram( admin );
		break;
	default:
		sendAcknowledge( admin, ISP_INVALID_MESSAGE_ID);
		break;
	}
}

BOOLEAN ValidMessageId( UINT8 messageId)
{
	if ((messageId >= 0x01) && (messageId <= 0x07))
	{
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

void sendAcknowledge(t_isp_admin *handle, t_isp_responses response)
{
	handle->ackCallback( response );
}


void handleUnlockMessage(t_isp_admin *admin)
{
	iflashresult unlockResult = ISP_CMD_SUCCESS;

	maxProgrammedAddress = 0;
	unlockResult =iflashPrepare( APP_FLASH_START_SECTOR, APP_FLASH_END_SECTOR); 
	
	admin->ackCallback( (t_isp_responses)unlockResult );
}

void handleEraseBlockMessage(t_isp_admin *admin)
{
	iflashresult result;
	UINT8 blockNr;
	
	// Extract block nr from message
	blockNr = admin->rxMessage.payload[0];
	
	if (    ( blockNr >= APP_FLASH_START_SECTOR)
	     && ( blockNr <= APP_FLASH_END_SECTOR)) 
	{
		result = iflashErase( blockNr );
	}
	else
	{
		result = ISP_INVALID_SECTOR;
	}
	
	admin->ackCallback( (t_isp_responses)result );
}

void handleProgramFlashMessage(t_isp_admin *admin)
{
	iflashresult result;
	UINT32 address;
	UINT8 size;
	UINT8 *data;
	UINT8 index = 0;

	// Extract address, datalength & data from message
	address = ispGet32bit(admin->rxMessage.payload, &index);
	size = ispGet8bit(admin->rxMessage.payload, &index);
	data = &(admin->rxMessage.payload[index]);
	
	if (maxProgrammedAddress < address) maxProgrammedAddress = address;
	
	if (    (address >= APP_FLASH_START_ADDR)
		 && (address <= APP_FLASH_END_ADDR)
	   )
	{
		result = fillProgramBlock(address, size, data );
	}
	else
	{
		result = ISP_DST_ADDR_ERROR;
	}
	
	admin->ackCallback( (t_isp_responses)result );
}

void handleVerifyFlashMessage(t_isp_admin *admin)
{
	iflashresult result;
	UINT32 address;
	UINT8 size;
	UINT8 index = 0;
	UINT8 bufferIdx;

	// Extract address, datalength & data from message
	address = ispGet32bit(admin->rxMessage.payload, &index);
	size = ispGet8bit(admin->rxMessage.payload, &index);
	for (bufferIdx = 0; bufferIdx < size; bufferIdx++)
	{
		verifyBlock[bufferIdx] = ispGet8bit(admin->rxMessage.payload, &index);
	}
	
	// Make size dividable by 4
	size = size - (size % 4); 
	
	if ((size >= 4) && (address != APP_FLASH_START_ADDR))
	{
		result = iflashVerify( address, size, verifyBlock );
	}
	else
	{
		result = ISP_CMD_SUCCESS;
	}
	
	admin->ackCallback( (t_isp_responses)result );
}

void handleFinishProgramming(t_isp_admin *admin)
{
	iflashresult result;
	UINT32 imageSize;

	// Program remaining block on flash
	fillProgramBlock(0, 0, 0 );

	// Calculate image length
	imageSize = maxProgrammedAddress  - APP_FLASH_START_ADDR;
	
	// Calculate and store Crc & imageSize on flash
	result = StoreCrcAndLength( imageSize );
	
	admin->ackCallback( (t_isp_responses)result );
}

void handleStartProgram(t_isp_admin *admin)
{
	//if (appiValidAppImageAvail() == TRUE)
	{
		ledSet( LED1, 0 ); 
		ledSet( LED0, 0 ); 

		appiJumpToAppImage();
	}
	//else
	//{
	//	admin->ackCallback( ISP_BUSY );
	//}
}


void ispAdd16bit(UINT8 *payloadlocation, UINT16 data)
{
	UINT8 index = 0;
	
	payloadlocation[index] = (UINT8)(data >> 8);
	index++;
	payloadlocation[index] = (UINT8)(data & 0x00FF);
}


void ispAdd32bit(UINT8 *payloadlocation, UINT32 data)
{
	UINT8 index = 0;
	
	payloadlocation[index] = (UINT8)(data >> 24);
	index++;
	payloadlocation[index] = (UINT8)(data >> 16);
	index++;
	payloadlocation[index] = (UINT8)(data >> 8);
	index++;
	payloadlocation[index] = (UINT8)(data & 0xFF);
	
}

UINT8 ispGet8bit(UINT8 *payload, UINT8 *payloadIndex)
{
	UINT8 result;
	
	result = (UINT8)payload[*payloadIndex];
	(*payloadIndex)++;
	
	return result;
}


UINT16 ispGet16bit(UINT8 *payload, UINT8 *payloadIndex)
{
	UINT16 result;
	
	result = ((UINT16)payload[*payloadIndex]) << 8;
	(*payloadIndex)++;
	result += ((UINT16)payload[*payloadIndex] & 0x00FF);
	(*payloadIndex)++;
	
	return result;
}


UINT32 ispGet32bit(UINT8 *payload, UINT8 *payloadIndex)
{
	UINT32 result;
	
	result = ((UINT32)payload[*payloadIndex]) << 24;
	(*payloadIndex)++;
	result += ((UINT32)payload[*payloadIndex]) << 16;
	(*payloadIndex)++;
	result += ((UINT32)payload[*payloadIndex]) << 8;
	(*payloadIndex)++;
	result += ((UINT32)payload[*payloadIndex] & 0x000000FF);
	(*payloadIndex)++;
	
	return result;
}


iflashresult fillProgramBlock( UINT32 address, UINT8 size, UINT8 *data)
{
	UINT32 programIndex;
	UINT32 beginIndex;
	UINT32 dataIndex;
	UINT32 endIndex;
	iflashresult result = ISP_CMD_SUCCESS;
	
	if (data == 0)
	{
		// finish sending last block
		if (startAddress > 0)
		{
			result = programFlash();
		}
		else
		{
			result = ISP_CMD_SUCCESS;
		}
		startAddress = INVALID_ADDRESS;
		return result;
	}
	
	// Determine startAddress. 
	if (startAddress == INVALID_ADDRESS)
	{
		// Make sure address is alligned to 256
		resetRamBlock(address - (address % PROGRAM_BLOCK_SIZE));
	}
	else
	{
		if (address > (startAddress + PROGRAM_BLOCK_SIZE))
		{
			result = programFlash();
			resetRamBlock( address );
		}
	}

	// Determine endIndex;
	if ( (startAddress + PROGRAM_BLOCK_SIZE) > (address + size) )
	{
		endIndex = (address - startAddress) + size;
	}
	else
	{
		endIndex = PROGRAM_BLOCK_SIZE; 
	}
	
	// Copy data into programblock
	dataIndex = 0;
	beginIndex = address - startAddress;
	for (programIndex = beginIndex; programIndex < endIndex; programIndex++)
	{
		programBlock[programIndex] = data[dataIndex];
		dataIndex++;
	}
	
	// if program block is full, program it
	if (dataIndex < size)
	{
		result = programFlash();
		resetRamBlock( startAddress +  PROGRAM_BLOCK_SIZE );
		
		// Copy rest in new block
		endIndex = size - dataIndex; 
		for (programIndex = 0; programIndex < endIndex; programIndex++)
		{
			programBlock[programIndex] = data[dataIndex];
			dataIndex++;
		}
	}
	else
	{
		if ((startAddress + PROGRAM_BLOCK_SIZE) == (address + size))
		{
			result = programFlash();
			resetRamBlock( INVALID_ADDRESS );
		}
	}
	
	return result;
}

void resetRamBlock( UINT32 address )
{
	UINT32 programIndex;
	
	startAddress = address;
	for (programIndex = 0; programIndex < PROGRAM_BLOCK_SIZE; programIndex++)
	{
		programBlock[programIndex] = 0x00;
	}
}

iflashresult programFlash()
{
	UINT16 index;
	
	if (startAddress != APP_FLASH_START_ADDR)
	{
		iflashPrepare( APP_FLASH_START_SECTOR, APP_FLASH_END_SECTOR); 
		return iflashProgram( startAddress, PROGRAM_BLOCK_SIZE, programBlock );
	}
	else
	{
		// keep copy of first block, programmed later at FinishedProgramming
		for (index = 0; index < PROGRAM_BLOCK_SIZE; index++)
		{
			programFirstBlock[index] = programBlock[index];
		}
		
		return ISP_CMD_SUCCESS;
	}
}

iflashresult StoreCrcAndLength( UINT32 imageSize )
{
	static UINT16 crc = 0;
	static UINT8 *image = 0;

	// Calculate Crc over part before Length & CRC
	crc = crcCalc(programFirstBlock, APP_IMAGE_LENGTH_OFFS, crc);

	// Calculate Crc over part after Length & CRC in first program block
	image = (UINT8 *)(programFirstBlock + APP_IMAGE_CRC_OFFS + 4);
	crc = crcCalc(image, PROGRAM_BLOCK_SIZE - (APP_IMAGE_CRC_OFFS + 4), crc);

	// Calculate Crc over rest of image already programmed
	image = (UINT8 *)(APP_FLASH_START_ADDR + PROGRAM_BLOCK_SIZE);
	crc = crcCalc(image, imageSize - PROGRAM_BLOCK_SIZE, crc);
	
	// Fill in Length & CRC
	ispAdd32bit(programFirstBlock + APP_IMAGE_LENGTH_OFFS, imageSize);
	ispAdd16bit(programFirstBlock + APP_IMAGE_CRC_OFFS, crc);
	
	// Program first block on flash
	iflashPrepare( APP_FLASH_START_SECTOR, APP_FLASH_START_SECTOR); 
	return iflashProgram( APP_FLASH_START_ADDR, PROGRAM_BLOCK_SIZE, programFirstBlock );
}
