/****************************************************************************/
/*	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 Motorola S-record reading and writing.
*
*	The S-record format was invented by Motorola (the part of it that is now
*	known as Freescale). The structure of an S-record file is the following:
*
*	The file consists of S-records. Each record is a single line, terminated
*	by CR LF. (This code also accepts single LF as a terminator. Empty lines
*	are also tolerated, although there should not be any.)
*
*	All characters of the line are printable ASCII characters, in fact
*	apart from the starting 'S' they all must be hexadecimal characters.
*	The hex digits over 9 (i.e. A-F) should be upper case. (This implementation
*	also accepts lower case hex digits, but it never generates them.)
*	Specifying the upper case as well as demanding CR+LF are for historic
*	reasons, related to the Baudot code. If you don't know what that is, don't
*	worry about it; if you had to interface 300 baud, 5-bit electromechanical
*	teletypes, then chances are that your date of birth is a negative number
*	in the UNIX time format...
*
*	The record starts with the 'S' character, followed by a decimal digit
*	followed by a number of hexadecimal digits. There must be an even
*	number of hex digits as two consecutive digits form a byte of the
*	record, the first digit being the upper nibble.
*
*	Thus, an S-record line looks something like this:
*
*		S12A000023EC00005F3A ....
*
*	The decimal digit character that follows the S is the record type.
*	The next 2 hex characters contain the length of the record. The length
*	is the number of bytes to follow, i.e. there should be twice as many
*	characters remaining on the line. The length byte is followed by
*	2, 3 or 4 address bytes (depending on record type). The address is
*	always in big-endian order. The address is followed by data bytes
*	and the last data byte is followed by a record checksum. There
*	should be length - addr.length - 1 data bytes in the record.
*	The checksum is calculated as the 1's complement of all bytes
*	in the record added together modulo 256. That is, the S and the
*	record type are excluded, everything else is included.
*
*	The record types are as follows:
*
*	S0:
*		Header. The address is 2 bytes, usually set to 0 but otherwise
*		ignored. The content of the data field is not specified, it
*		usually contains the name of the file or the name of the tool
*		that generated the file, in ASCII string form (encoded as hex
*		digits, of course). The best bet is just ignore the content of
*		this record. Quite often there is no S0 record in the file at all.
*		It should be noted that some GNU objdump utility versions expect the
*		S0 record to contain the filename in uppercase; if it's not there
*		they do not dump the content (it is possibly a bug because it
*		is not documented anywhere...).
*
*	S1:	Data record. The address field is 2 bytes. The address is followed
*		by data bytes which should be placed in memory from the address,
*		in increasing *byte* addresses. S-records describe memory images
*		and have no notion of words and endiannes. Strictly byte addressing.
*		Do not assume that the addresses are continuous. Real-life tools
*		often emit records as they collect bits and pieces of various segments
*		and you can expect to see jumps. Furthermore, nothing stops an
*		S-record to overwrite already populated addresses, although that
*		is not likely to happen in practical applications.
*
*	S2:	As S1, except that the address is 3 bytes.
*
*	S3:	As S1, except that the address is 4 bytes.
*
*	S4:	Undefined by Motorola. It should be ignored. LSI Logic used this
*		field for encoding debug information. The address field is 4 bytes,
*		followed by a data field. The data field contains the name of the
*		symbol in hex-encoded ASCII and the address is the value associated
*		with the symbol.
*
*	S5:	Record count. The address field is 2 bytes and contains the number
*		of S1, S2 or S3 records in the file before the S5 record. The data
*		field should be empty (i.e. the length must be 3). Very rarely used.
*		This implementation ignores it.
*
*	S6:	Undefined by Motorola. Some tools treat it like an S5 record but
*		with a 3-byte address field so that you can count more than 65535
*		records in a single transmission. Even less frequent than the S5
*		record type. This implementation ignores it.
*
*	S7:	Closing record. The address field is 4 bytes and there is no
*		data field. The address is supposedly the starting address of
*		the program, assuming that your S-record contains an executable
*		program image. Otherwise it can be ignored. This record is quite
*		often omitted entirely, so the end of file should be treated as
*		an implicite closing record. In addition, after the closing record
*		the processing of the file must end, a tool should not try to
*		interpret anything after this record.
*
*	S8:	As S7 except that the address is 3 bytes.
*
*	S9:	As S7 except that the address is 2 bytes.
*
*	Any other character following the 'S' is invalid and should be treated
*	as and error.
*/

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

#define	MAXLINE			530				// Max length of a line (526+a bit)

struct s_hexio {

	int				read;				// Opened for read
	FILE			*file;				// File pointer
	int				done;				// Flag, the file is finished
	unsigned		addr;				// Current address
	unsigned		left;				// Number of leftover bytes
	unsigned char	*over;				// Actual leftover bytes
	unsigned		lnum;				// Number of lines read or written
	unsigned char	line[ MAXLINE ];	// A temporary line buffer
};

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

static	int		SrecOpenRead( LPC_IO *recp, const char *name  );
static	int		SrecOpenWrite( LPC_IO *recp, const char *name );
static	int		SrecRead( LPC_IO *recp );
static 	int		SrecWrite( LPC_IO *recp );
static	int		SrecClose( LPC_IO *recp );
static	int		SrecGetRecord( struct s_hexio *stat );
static	int		SrecGetHex( struct s_hexio *stat );

/****************************************************************************/
/*						Library internal public data						*/
/****************************************************************************/

const	LPC_IO_FUNC	_lpc_srec = {

	NULL,
	"srec",
	SrecOpenRead,
	SrecRead,
	SrecOpenWrite,
	SrecWrite,
	SrecClose,
};

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

/*
*	Opens an S-record file for reading
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

static	int		SrecOpenRead( 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->left = 0;
	stat->done = 0;

	// Create the default response values

	recp->addr = 0;
	recp->rlen = 0;
	recp->line = 0;

	// Everything is just fine.

	return( 0 );
}

/*
*	Reads the next record from the file
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns a negative error code on error, FIXME
*	S0, S4, S5 and S6 records are silently skipped, although they are
*	checked for validity.
*/

static	int		SrecRead( 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;

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

	for ( ;; ) {

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

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

			if ( xfer > rlen ) xfer = rlen;
			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;

			recp->rlen += xfer;

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

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

		if ( ! rlen ) return( 1 );

		// Sure we do. Load the next record.

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

		switch ( code  ) {

			case 0:		// Start record, ignore it
			case 4:		// LSI Logic symbol record, ignore it
			case 5:		// 2-byte record count, ignore it
			case 6:		// 3-byte record count, ignore it

				recp->stat->left = 0;
				continue;

			case 7:
			case 8:
			case 9:

				// End records.

				break;

			default:

				// 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;
		}

		// 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 S-record file for output
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns an error code or 0 on success
*/

static	int		SrecOpenWrite( LPC_IO *recp, const char *name )
{
struct s_hexio	*stat;
unsigned char	*rptr;
unsigned int	rlen, csum;
unsigned int	indx, temp;

	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

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

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

	stat->read = 0;
	stat->lnum = 0;
	stat->done = 0;
	stat->read = 0;
	stat->lnum = 1;

	if ( ! ( rptr = (unsigned char *) rindex( name, '/' ) ) )

		rptr = (unsigned char *) name;
	else
		rptr++;

	rlen = strlen( (char *) rptr );

	if ( rlen > 252 ) rlen = 252;

	if ( fprintf( stat->file, "S0%02X0000", rlen + 3 ) < 0 )

		return( LPC_ERROR_SYSTEM );

	for ( csum = rlen + 3, indx = 0 ; indx < rlen ; indx++ ) {

		temp = recp->data ? rptr[ indx ] : toupper( rptr[ indx ] );
		csum += temp;

		if ( fprintf( stat->file, "%02X", temp ) < 0 )

					return( LPC_ERROR_SYSTEM );
	}

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

		return( LPC_ERROR_SYSTEM );
	else
		return( 0 );
}

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

static 	int		SrecWrite( LPC_IO *recp )
{
char			form[ 40 ];
unsigned int	addr, csum, llen;
unsigned int	rlen, xlen, alen;
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;

	// GCC's flow analysis has some problems and it thinks that
	// xlen can be used unitialised. It's wrong, but we have to
	// set xlen to some value to make it happy.

	xlen = 0;

	// We set llen to the maximum number of data + address bytes
	// Now we have the max chars on the line. We have two chars
	// for the Sn, 2 chars for the length and 2 chars for the
	// checksum. In addition, the final result can't be more
	// than 254 bytes.

	llen = ( recp->line > 16 ? recp->line : 16 ) / 2 - 3;
	if ( llen > 254 ) llen = 254;

	while ( rlen ) {

		alen = 2;
		if ( addr > 0x00ffff || addr + xlen > 0x00ffff ) alen = 3;
		if ( addr > 0xffffff || addr + xlen > 0xffffff ) alen = 4;

		xlen = rlen;
		if ( xlen > llen - alen ) xlen = llen - alen;
		csum = xlen + alen + 1;

		sprintf( form, "S%d%02X%%0%dX", alen - 1, xlen + alen + 1, 2 * alen );

		if ( fprintf( stat->file, form, addr ) < 0 )

			return( LPC_ERROR_SYSTEM );

		csum += ( addr >> 24 ) & 255;
		csum += ( addr >> 16 ) & 255;
		csum += ( addr >>  8 ) & 255;
		csum += ( addr >>  0 ) & 255;

		addr += xlen;
		rlen -= xlen;

		while ( xlen-- ) {

			csum += *dptr;

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

				return( LPC_ERROR_SYSTEM );
		}

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

			return( LPC_ERROR_SYSTEM );
	}

	return( 0 );
}

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

static	int		SrecClose( LPC_IO *recp )
{
int				alen, csum;
char			form[ 20 ];
struct s_hexio	*stat;

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

	if ( stat->read ) {

		fclose( stat->file );
		alen = 0;
	}
	else {

		stat->lnum++;
		alen = 2;
		if ( recp->addr & 0x00ff0000 ) alen = 3;
		if ( recp->addr & 0xff000000 ) alen = 4;

		csum  = alen + 1;
		csum += ( recp->addr >> 24 ) & 255;
		csum += ( recp->addr >> 16 ) & 255;
		csum += ( recp->addr >>  8 ) & 255;
		csum += ( recp->addr >>  0 ) & 255;
		csum  = ~csum & 255;

		sprintf( form, "S%d%02X%%0%dX%02X\r\n", 11-alen, alen+1, 2*alen, csum );

		if ( fprintf( stat->file, form, recp->addr ) < 0 ||
			 fclose( stat->file ) < 0 ) {

			alen = LPC_ERROR_SYSTEM;
		}
		else {

			alen = 0;
		}

		stat->file = NULL;
	}

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

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

/*
*	Reads a record into the one-record buffer
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*	Returns the record type or a negative error code.
*	Updates the addr, lnum, left fields in the state.
*/

static	int		SrecGetRecord( struct s_hexio *stat )
{
int		alen, addr, rlen;
int		csum, cntr, temp;
int		type, c;

	for ( c = '\n' ; c == '\r' || c == '\n' ; c = getc( stat->file ) )

		 stat->lnum++;

	if ( c == EOF ) {

		// End of file without a closing record. Make it
		// a default closing record.

		stat->left = 0;
		stat->addr = 0;
		return( 7 );
	}

	if ( c < 0 ) {

		// Some error occured

		return( LPC_ERROR_SYSTEM );
	}

	if ( c != 'S' ) {

		// This is not an S record.

		return( LPC_ERROR_FILE );
	}

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

		if ( c == EOF )

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

	switch ( c ) {

		case '0':	alen = 2;	break;
		case '1':	alen = 2;	break;
		case '2':	alen = 3;	break;
		case '3':	alen = 4;	break;
		case '4':	alen = 4;	break;
		case '5':	alen = 2;	break;
		case '6':	alen = 3;	break;
		case '7':	alen = 4;	break;
		case '8':	alen = 3;	break;
		case '9':	alen = 2;	break;

		default:

			return( LPC_ERROR_FILE );
	}

	type = c - '0';

	if ( ( rlen = SrecGetHex( stat ) ) < 0 ) return( rlen );

	if ( rlen < alen + 1 ) {

		// Record length is less than the address plus the checksum.

		return( LPC_ERROR_FILE );
	}

	csum  = rlen;
	rlen -= alen + 1;

	for ( addr = 0 ; alen ; alen-- ) {

		if ( ( temp = SrecGetHex( stat ) ) < 0 ) return( temp );
		addr <<= 8;
		addr  |= temp;
		csum  += temp;
	}

	for ( cntr = 0 ; cntr < rlen ; cntr++ ) {

		if ( ( temp = SrecGetHex( stat ) ) < 0 ) return( temp );
		stat->line[ cntr ] = temp;
		csum  += temp;
	}

	if ( ( temp = SrecGetHex( stat ) ) < 0 ) return( temp );

	if ( ( ( csum + temp ) & 255 ) != 255 ) return( LPC_ERROR_FILE );

	stat->addr = addr;
	stat->left = rlen;
	stat->over = stat->line;

	return( type );
}

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

static int	SrecGetHex( 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 ) );
}

