/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "lpc-internals.h"

/*
*	Routines to handle Intel hex file reading and writing.
*
*	The Intel hex format, as the name implies, was invented by Intel. It is
*	tightly tied to the 808x and 80x86 architectures and much less thought
*	through than the Motorola S-record format. The actual specification leaves
*	a lot of issues open, too. It seems that the original version was designed
*	with the 8080 in mind, then with the advent of the 8086 they bolted the
*	segmented stuff on top and then when the 80386 came to being, they added
*	the linear addressing on top of that. That results in your having to keep
*	a state and use different calculation methods for addresses depending on
*	that state. The choice of addressing method should be dependent on whether
*	the hexfile descibes an executabe object for a 8-, 16- or 32-bit Intel
*	processor, pretty much implicitely assuming that that's the only reason
*	for generating a hexfile. Regardless, the Intel hex format is used for
*	many other processors and also for general data.
*
*	Each line (record) starts with a ':' (colon). It is followed by an even
*	number of hex digits, two digits forming a byte. The line is not necessarily
*	terminated, the specification does not mandate that. After you processed a
*	record, you just start hunting for the next ':', ignoring everything until
*	you find it. This implementation terminates lines with CR-LF, as that is
*	the most common termination used in Intel hex files.
*
*	The bytes of the record are as follows:
*	- length byte, a 1-byte number of bytes in the data field
*	- load offset, a 2-byte, big-endian(!) field
*	- record type, a 1-byte number
*	- data, 0-255 bytes long
*	- checksum, 1 byte, the 2's complement of the sum of all bytes from the
*	  length, load offset, record type and data fields.
*
*	The following record types are defined:
*
*	00	Data record
*
*		Loads the data bytes into the memory image. The address for the
*		bytes depends on whether the last address specified was a linear
*		or segmented address. In case of linear addressing:
*
*			ADDR = ( LBA + load offset + byte number ) mod 2^32
*
*		where LBA is the Linear Base Address, while in case of segment
*		addressing:
*
*			ADDR = SBA + ( load offset + byte number ) mod 2^16
*
*		where SBA is the Segmented Base Address. It should be noted that
*		the Intel processors that use the segmented architecture will also
*		limit the ADDR to be modulo 2^20 (that is, 1MB) but since that is
*		not in the specs, this implementation doesn't enforce it.
*		If you encounter a record that specifies either the LBA or
*		the SBA, then after that you have to use the relevant method.
*		However, the specs leave it unspecified which method to use until you
*		see an LBA or SBA record. This implementation defaults to LBA mode.
*		Due to the load offset being 16 bits, you can not load more than 64K
*		without setting the LBA or the SBA (well, using LBA you can write
*		255 byte over 64K but that's beside the point).
*
*	01	End of File
*
*		The record indicates the end of the file. The length must be 0,
*		the load offset must be 0 and consequently the checksum must be
*		0xFF. This implementation simply ignores the load offset and the
*		record lenth.
*
*	02	Extended Segment Address

*		The record length must be 2, the load offset must be 0.
*		The data field contains a 16 bit unsigned number, in big-endian format.
*		The record sets the SBA: SBA = number * 16. Until this record is
*		encountered, SBA defaults to 0 and this implementation will use the
*		linear addressing model. This implementation never generates this
*		record type.
*
*	03	Start Segmented Address
*
*		Specifies the start address of the program. The data length must be
*		4 bytes and the load offset must be 0. The data field contains 2
*		16-bit unsigned numbers in big-endian order. The two numbers, that
*		represent the CS and IP registers of the 8086, specify the start
*		address: start = ( first_num * 16 + second_num ) mod 2^20.
*		This implementation ignores this record and never generates it.
*
*	04	Extended Linear Address
*
*		The record length must be 2, the load offset must be 0.
*		The data field is a 2-byte unsigned number, in big-endian order.
*		The number is to be loaded into the LBA: LBA = number * 65536.
*		The LBA defaults to 0. After encountering this field data loads
*		use linear addressing.
*
*	05	Start Linear Address
*
*		Specifies the start address of the program. The data length must be
*		4 bytes and the load offset must be 0. The data field contains a
*		32-bit unsigned number in big-endian order. The number is the
*		start address.
*		This implementation ignores this record and never generates it.
*
*	Any other record type is treated as error.
*/

/****************************************************************************/
/*						Local definitions									*/
/****************************************************************************/

#define	MAXLINE			260				/* Max length of a line (256+4)		*/

struct s_hexio {

	int				read;				// Opened for read
	FILE			*file;				// File pointer
	int				segm;				// Indicates that we use segmented addr
	unsigned int	base;				// Current base address
	unsigned int	lnum;				// Number of lines read or written
	unsigned int	addr;				// Address of the last read record
	unsigned char	line[ MAXLINE ];	// A temporary line buffer
	int				done;				// Flag, the file is finished
	unsigned		left;				// Number of leftover bytes
	unsigned char	*over;				// Actual leftover bytes

};

/****************************************************************************/
/*						Prototypes											*/
/****************************************************************************/

static	int		IhexOpenRead( LPC_IO *recp, const char *name  );
static	int		IhexOpenWrite( LPC_IO *recp, const char *name );
static	int		IhexRead( LPC_IO *recp );
static 	int		IhexWrite( LPC_IO *recp );
static	int		IhexClose( LPC_IO *recp );

static	int		IhexGetRecord( struct s_hexio *stat );
static	int		IhexGetHex( struct s_hexio *stat );

/****************************************************************************/
/*						Public data											*/
/****************************************************************************/

const	LPC_IO_FUNC	_lpc_ihex = {

	NULL,
	"ihex",
	IhexOpenRead,
	IhexRead,
	IhexOpenWrite,
	IhexWrite,
	IhexClose,
};

/****************************************************************************/
/*						Interface functions									*/
/****************************************************************************/

/*
*	Opens an Intel hex file for reading
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		IhexOpenRead( LPC_IO *recp, const char *name  )
{
struct s_hexio	*stat;

	if ( ! ( recp->stat = stat = malloc( sizeof( struct s_hexio ) ) ) ) {

		// Can't allocate the structure

		return( LPC_ERROR_SYSTEM );
	}

	if ( ! ( stat->file = fopen( name, "r" ) ) ) {

		// Can't open the file

		free( recp->stat );
		recp->stat = NULL;
		return( LPC_ERROR_SYSTEM );
	}

	// Indicate read mode and that we have not processed a single line yet.

	stat->read = 1;
	stat->lnum = 1;
	stat->base = 0;
	stat->segm = 0;
	stat->left = 0;
	stat->over = 0;

	// Create the default response values and report success

	recp->addr = 0;
	recp->rlen = 0;
	recp->line = 0;
	return( 0 );
}

/*
*	Reads the next record from the file
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		IhexRead( LPC_IO *recp )
{
int			code, aset;
unsigned	rlen, prev, xfer;
char		*data;

	if ( ! recp->stat ) return( LPC_ERROR_PARAM );
	if ( ! recp->stat->read ) return( LPC_ERROR_PARAM );

	if ( recp->stat->done ) {

		// We've already finished with this file

		recp->line = recp->stat->lnum;
		recp->addr = recp->stat->addr;
		recp->rlen = 0;
		return( 0 );
	}

	rlen = recp->rlen;
	data = recp->data;
	aset = 0;
	prev = 0;

	recp->rlen = 0;

	for ( ;; ) {

		// If we have leftover from the previous record, use that.

		if ( ( xfer = recp->stat->left ) ) {

			// Indicate that we haven't split a record

			code = 0;

			// Limit the transfer length

			if ( xfer > rlen ) xfer = rlen;

			// If we use segmented address, we have to cater for
			// the segment wrap-around.

			if ( recp->stat->segm ) {

				if ( recp->stat->addr - recp->stat->base + xfer > 65536 ) {

					xfer = 65536 + recp->stat->base - recp->stat->addr;
					code = 1;
				}
			}

			if ( data ) memcpy( data, recp->stat->over, xfer );

			if ( ! aset ) {

				recp->addr = recp->stat->addr;
				recp->line = recp->stat->lnum;
				prev	   = recp->addr;
				aset	   = 1;
			}

			recp->stat->left -= xfer;
			recp->stat->over += xfer;
			recp->stat->addr += xfer;

			// Bloody semgments again...

			if ( recp->stat->segm ) {

				if ( recp->stat->addr >= recp->stat->base + 65536 )

					recp->stat->addr -= 65536;
			}

			recp->rlen += xfer;

			data += xfer;
			prev += xfer;
			rlen -= xfer;

			// If we had to split the leftover due to segment wrap-around,
			// we must return now.

			if ( code ) return( 1 );
		}

		// We have no leftover. Do we need to do anything?

		if ( ! rlen ) return( 1 );

		// Sure we do. Load the next record.

		if ( ( code = IhexGetRecord( recp->stat ) ) < 0 ) return( code );

		switch ( code  ) {

			case 0:		// Data record. If it is of 0 length, we ignore it.

				if ( ! recp->stat->left ) continue;

				// If we already transferred some data, we have to see if
				// the address matches. If not, we have to return. Otherwise
				// we keep looping.

				if ( aset && recp->stat->addr != prev ) return( 1 );
				continue;

			case 1:		// End record

				break;

			case 2:		// Segmented address

				if ( recp->stat->addr ) return( LPC_ERROR_FILE );
				if ( recp->stat->line[ 0 ] != 2 ) return( LPC_ERROR_FILE );
				recp->stat->base  = recp->stat->line[ 4 ] * 4096;
				recp->stat->base += recp->stat->line[ 5 ] * 16;
				recp->stat->segm  = 1;
				continue;

			case 4:		// Linear address

				if ( recp->stat->addr ) return( LPC_ERROR_FILE );
				if ( recp->stat->line[ 0 ] != 2 ) return( LPC_ERROR_FILE );
				recp->stat->base  = recp->stat->line[ 4 ] * 16777216;
				recp->stat->base += recp->stat->line[ 5 ] * 65536;
				recp->stat->segm  = 0;
				continue;

			case 3:		// Segmented start address, ignore it
			case 5:		// Linear start address, ignore it

				continue;

			default: 	// Invalid record type

				return( LPC_ERROR_FILE );
		}

		// We get here if and only if we saw an end record. Set the flag
		// in the structure. Then, if we've transferred some data, we
		// return with that, otherwise return with an end of file
		// condition.

		recp->stat->done = 1;
		return( aset );
	}
}

/*
*	Creates a new Intel-hex file for output
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns an error code or 0 on success
*/

static	int		IhexOpenWrite( LPC_IO *recp, const char *name )
{
struct s_hexio	*stat;

	if ( ! ( recp->stat = stat = malloc( sizeof( struct s_hexio ) ) ) ) {

		// Can't allocate the structure

		return( LPC_ERROR_SYSTEM );
	}

	if ( ! ( stat->file = fopen( name, "w" ) ) ) {

		// Can't open the file

		return( LPC_ERROR_SYSTEM );
	}

	// Indicate write mode and that we have not processed a single line yet.

	stat->read = 0;
	stat->lnum = 1;
	stat->base = 0;
	stat->segm = 0;
	recp->line = recp->line;

	// Write a 0 base address so that the reader knows that we use
	// linear addressing mode

	if ( fprintf( stat->file, ":020000040000FA\r\n" ) < 0 ) {

		return( LPC_ERROR_SYSTEM );
	}

	return( 0 );
}

/*
*	Writes a data record into a file
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns -1 on error and +1 on success.
*/

static 	int		IhexWrite( LPC_IO *recp )
{
unsigned int	addr, csum, wlen;
unsigned int	rlen, xlen, cntr;
unsigned char	*dptr;
struct s_hexio	*stat;

	if ( ! ( stat = recp->stat ) ) return( LPC_ERROR_PARAM );
	if ( stat->read ) return( LPC_ERROR_PARAM );

	addr = recp->addr;
	dptr = recp->data;
	rlen = recp->rlen;
	wlen = recp->line > 16 ? recp->line : 16;

	while ( rlen ) {

		// See if we have to write a new address record

		if ( stat->base != ( addr & 0xffff0000 ) ) {

			stat->base = addr & 0xffff0000;
			csum  = ( stat->base >> 24 ) + 6;
			csum += ( stat->base >> 16 ) & 255;
			csum  = -csum & 255;

			if ( fprintf( stat->file, ":02000004%04X%02X\r\n",
						  stat->base >> 16, csum ) < 0 ) {

				return( LPC_ERROR_SYSTEM );
			}

			stat->lnum++;
		}

		if ( rlen + addr - stat->base > 65536 )

			xlen = 65536 + stat->base - addr;
		else
			xlen = rlen;

		if ( xlen > wlen ) xlen = wlen;

		if ( fprintf( stat->file, ":%02X%04X00",
					  xlen, addr - stat->base ) < 0 ) {

			return( LPC_ERROR_SYSTEM );
		}

		csum  = addr - stat->base;
		csum += csum >> 8;
		csum += xlen;

		for ( cntr = xlen ; cntr-- ; ) {

			csum += *dptr;

			if ( fprintf( stat->file, "%02X", *dptr++ ) < 0 ) {

				return( LPC_ERROR_SYSTEM );
			}
		}

		csum = -csum & 255;

		if ( fprintf( stat->file, "%02X\r\n", csum ) < 0 ) {

			return( LPC_ERROR_SYSTEM );
		}

		stat->lnum++;
		rlen -= xlen;
		addr += xlen;
	}

	recp->line = stat->lnum;
	return( 0 );
}

/*
*	Closes an Intel-hex file
*	~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns an error code or 0 if everything was OK.
*/

static	int		IhexClose( LPC_IO *recp )
{
int		alen;
struct s_hexio	*stat;

	if ( ! ( stat = recp->stat ) ) return( LPC_ERROR_PARAM );

	alen = 0;

	if ( ! stat->read ) {

		if ( fprintf( stat->file, ":00000001FF\r\n" ) < 0 ) {

			alen = LPC_ERROR_SYSTEM;
		}

		stat->lnum++;
	}

	if ( fclose( stat->file ) < 0 ) {

		alen = LPC_ERROR_SYSTEM;
	}

	free( stat );

	recp->line = stat->lnum;
	recp->stat = NULL;
	return( alen );
}

/****************************************************************************/
/*						Utility functions									*/
/****************************************************************************/

/*
*	Reads a record into the temporary buffer and decodes it
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		IhexGetRecord( struct s_hexio *stat )
{
int		i, c, s;

	for ( ;; ) {

		c = getc( stat->file );

		if ( c == ':' ) {

			// Start of the record

			break;
		}

		if ( c == EOF ) {

			// We treat it as if it was an end of file record

			stat->line[ 0 ] = 0x00;
			stat->line[ 1 ] = 0x00;
			stat->line[ 2 ] = 0x00;
			stat->line[ 3 ] = 0x00;
			stat->line[ 4 ] = 0x01;
			stat->line[ 5 ] = 0xff;
			return( 0 );
		}

		if ( c == '\n' ) {

			// Bump up the line number

			stat->lnum++;
		}

		// Any other characters are silently discarded
	}

	// Get the record content. Start with the first 4 bytes

	for ( s = i = 0 ; i < 4 ; i++ ) {

		if ( ( c = IhexGetHex( stat ) ) < 0 ) {

			// Error

			return( c );
		}

		stat->line[ i ] = c;
		s += c;
	}

	// Now get the content of the line

	for ( i = 0 ; i < stat->line[ 0 ] ; i++ ) {

		if ( ( c = IhexGetHex( stat ) ) < 0 ) {

			// Error

			return( c );
		}

		stat->line[ i+4 ] = c;
		s += c;
	}

	// Get the checksum and match it

	if ( ( c = IhexGetHex( stat ) ) < 0 ) {

		// Error

		return( c );
	}

	if ( ( -c & 255 ) != ( s & 255 ) ) {

		// Checksum error

		return( LPC_ERROR_FILE );
	}

	// Everything is fine

	return( 0 );
}

/*
*	Reads a hex byte from the source
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns the value of the byte or a negative error code
*/

static int	IhexGetHex( struct s_hexio *stat )
{
int		byte, data;

	if ( ( data = getc( stat->file ) ) < 0 ) {

		if ( data == EOF )

			return( LPC_ERROR_FILE );
		else
			return( LPC_ERROR_SYSTEM );
	}

	if ( ! isxdigit( data ) ) return( LPC_ERROR_FILE );
	byte = ( data <= '9' ? data - '0' : toupper( data ) - 'A' + 10 ) << 4;

	if ( ( data = getc( stat->file ) ) < 0 ) {

		if ( data == EOF )

			return( LPC_ERROR_FILE );
		else
			return( LPC_ERROR_SYSTEM );
	}

	if ( ! isxdigit( data ) ) return( LPC_ERROR_FILE );
	return( byte + ( data <= '9' ? data - '0' : toupper( data ) - 'A' + 10 ) );
}

