// xmodem.c

#include <string.h>
#include "xmodem.h"
#include "flash.h"

// definition for the structure which stores the Flash programming and erasing code
extern struct rom_data {
	unsigned long start_address;
	unsigned long data_length;
	unsigned char data[1];
};

// extern constant structure containing the programming function that
// is copied to RAM and executed from RAM
extern const struct rom_data program;

// storage for the program and erase routines
extern unsigned char ProgEraseArray[ 512 ];

// type definition for pointer to the Flash programming function
typedef unsigned short (*pt2FunctionProg)( unsigned long, unsigned char * );

unsigned char XmodemDownloadAndProgramFlash (unsigned long FlashAddress)
{
/*
XmodemDownloadAndProgramFlash() takes a memory address as the base address to
which data downloaded is programmed.  The data is downloaded using the XModem
protocol developed in 1982 by Ward Christensen.
The routine detects errors due to timeouts, comms errors or invalid checksums.
The routine reports the following to the caller:
-Success
-Invalid address	(not implemented here as the address is checked prior to this
					function being called)
-Comms error
-Timeout error
-Failure to program flash


Expects:    
--------
FlashAddress:
32-bit address located in Flash memory space starting on a 128-byte boundary

Returns:
--------
XM_OK				-	Download and Flash programming performed ok
XM_ADDRESS_ERROR 	-	Address was either not on a 128-bit boundary or not in valid Flash
XM_COMMS_ERROR	 	-	Comms parity, framing or overrun error
XM_TIMEOUT			-	Transmitter did not respond to this receiver
XM_PROG_FAIL		-	Falied to program one or more bytes of the Flash memory
*/

	union union_c2s c2s;
	unsigned char ExpectedBlkNum;
	unsigned char RetryCounter;
	unsigned char RxByteCount;
	unsigned char RxByteBufferIndex;
	unsigned short Status;
	unsigned char XmodemStatus;
	unsigned char checksum;
	unsigned char StartCondition;
	unsigned long Address;
	pt2FunctionProg fprog;
	unsigned long *ptr;
	unsigned char RxByteBuffer[ 128 + 4 + 1 ];	// array to store received data bytes via xmodem protocol

	// first xmodem block number is 1
	ExpectedBlkNum = 1;
	
	StartCondition = 0;
	
	Address = FlashAddress;
	
	// load the program function into RAM
	memcpy( (void *)program.start_address, (void *)program.data, program.data_length );
				
	// flush the comms rx buffer with a delay of 1 sec
	// function will return when no data has been received for 1 sec
	PurgeComms( 1000 );
		
		
	while(1)
	{
		//	{1}
		//	initialise Rx attempts
		RetryCounter = 10;
	
		//	decrement Rx attempts counter & get Rx byte with a 10 sec timeout repeat until Rx attempts is 0
		c2s.uc[0] = TIMEOUT;
		while ( (RetryCounter > 0) && (c2s.uc[0] == TIMEOUT) )
		{
			if (StartCondition == 0)
			{
				//	if this is the start of the xmodem frame
				//	send a NAK to the transmitter
				SendByte( NAK );
				c2s.us = GetByte( 10000 );
			}                             
			else
			{
				c2s.us = GetByte( 1000 );
			}
			RetryCounter--;
		}
			
		StartCondition = 1;
	
		//	if timed out after 10 attempts or comms error
		//	return relevant error state to caller
		if ( c2s.uc[0] == TIMEOUT )
		{
			return ( XM_TIMEOUT ); 
		}
		else if ( c2s.uc[0] == ERROR )
		{
			// loop back to (1)
			// do nothing
			return ( XM_COMMS_ERROR );
		}
		else			
		{
			// if first received byte is 'end of frame'
			// return ACK to sender
			if ( c2s.uc[1] == EOT )
				{
				SendByte( ACK );
				return( XM_OK );
			}
			else
			{				
				//	initialise counter for incoming Rx bytes
				// start of header + block num + (255 - block num) + 128 data bytes + checksum
				RxByteCount = 128 + 4;
				// RxByteBufferIndex is initiales to 1 to ensure correct boundary for the data
				RxByteBufferIndex = 1;
				
				XmodemStatus = XM_OK;
					
				// store the byte we have just received
				RxByteBuffer[ RxByteBufferIndex++ ] = c2s.uc[1];
				RxByteCount--;
					
				while( RxByteCount > 0 )
				{
					//	get Rx byte with 1 second timeout
					c2s.us = GetByte( 1000 );

					//	if timed out or comms error
					if ( (c2s.uc[0] == TIMEOUT) || (c2s.uc[0] == ERROR) )
					{
						XmodemStatus = XM_TIMEOUT;
						//	timed out so purge incoming data
						PurgeComms( 1000 );
						// send NAK and return loop back start of while loop
						SendByte( NAK );
						RxByteCount = 0;
					}
					else
					{
						// no timeout or comms error
						// store Rx byte
						RxByteBuffer[ RxByteBufferIndex++ ] = c2s.uc[1];
						RxByteCount--;
					}
				}
					
				if (XmodemStatus == XM_TIMEOUT)
				{
						// loop back to (1)
						// do nothing
				}
				else
				{
					// data Rx ok
					// calculate the checksum of the data bytes only
					checksum = 0;
					for (RxByteBufferIndex=0; RxByteBufferIndex<128; RxByteBufferIndex++)
					{
						checksum += RxByteBuffer[ RxByteBufferIndex + 3 + 1 ];
					}

					//	if SOH, BLK#, 255-BLK# or checksum not valid
					//	(BLK# is valid if the same as expected blk counter or is 1 less
					if ( !( (RxByteBuffer[0 + 1] == SOH) && ((RxByteBuffer[1 + 1] == ExpectedBlkNum) || (RxByteBuffer[1 + 1] == ExpectedBlkNum - 1) ) && (RxByteBuffer[2 + 1] + RxByteBuffer[1 + 1] == 255 ) && (RxByteBuffer[131 + 1] == checksum) ) )
					{
						//	send NAK and loop back to (1)
						SendByte( NAK );
					}
					else
					{
						//	if blk# is expected blk num
						if ( RxByteBuffer[1 + 1] == ExpectedBlkNum )
						{
							// load the function pointer
							ptr = ( (unsigned long *) &ProgEraseArray);
							fprog = (pt2FunctionProg) *ptr;
							
							Status = fprog( Address, (unsigned char *) &RxByteBuffer[3 + 1] );
							if( Status == FLASH_NO_ERROR )
							{
								//	if prog routine passed ok increment flash address by 128
								Address += 128;
								ExpectedBlkNum++;
								SendByte( ACK );

								//	loop back to (1)
							}
							else
							{
								// prog fail
								SendByte( NAK );
								// cancel xmodem download
								SendByte( CAN );
									
								return( XM_PROG_FAIL );
							}
						}
						else
						{
							// 	block number is valid but this data block has already been received
							//	send ACK and loop to (1)
							SendByte( ACK );
						}
					}
				}
            }
		}
	}		
}
