/* **********************************************************************
 * *                                                                    *
 * *          Routines to Work With Intel Hex "ROM" Records             *
 * *                                                                    *
 * **********************************************************************
 * File: libs/dsp/intelhex.c
 *
 *                    COPYRIGHT (c) 2000, 2002  BY
 *         SIGMET INCORPORATED, WESTFORD MASSACHUSETTS, U.S.A.
 *
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */
#include <stdio.h>

#define INCLUDE_STDIO_PROTOTYPES 1
#define DSP_INFO_DRIVER 1
#include "sigtypes.h"
#include "user_lib.h"
#include "dsp_lib.h"

/* This describes the format of an Intel hex file produced by a suitable
 * assembler. This code is often downloaded from a PC to a development
 * system and run from RAM. Alternatively, the hex file can be converted
 * to a binary file and programmed into an EPROM.
 *
 * Here is an example hex file:
 *
 * :10008000AF5F67F0602703E0322CFA92007780C361
 * :1000900089001C6B7EA7CA9200FE10D2AA00477D81
 * :0B00A00080FA92006F3600C3A00076CB
 * :00000001FF
 * 
 * Now look at the top line...
 *
 *   The first character (:) indicates the start of a record.
 *   The next two characters indicate the record length (10h in this case).
 *   The next four characters give the load address (0080h in this case).
 *   The next two characters indicate the record type (see below).
 *   Then we have the actual data.
 *   The last two characters are a checksum (sum of all bytes + checksum = 00).
 *
 * The EOF record on the last line of the file is special (Length=0,
 * Addr=0, Type=1), and will always look like that above.
 */

/* Record types
 */
#define IHEX_TYPE_DATA    0	/* Data record */
#define IHEX_TYPE_EOF     1	/* End of file record */
#define IHEX_TYPE_SEGADR  2	/* Extended segment address record */
                                /*   (Address offset is 16-bit value * 2^4) */
#define IHEX_TYPE_SEGST   3	/* Start segment address record */
#define IHEX_TYPE_LINADR  4	/* Extended linear address record */
                                /*   (Address offset is 16-bit value * 2^16) */
#define IHEX_TYPE_LINST   5	/* Start linear address record */


/* Local static data and functions
 */
UINT1 iHexCheckSum_c ;		/* Checksum of converted bytes */
const char *sHexRecordPtr_c ;	/* ==> next record byte to convert */

static SINT4 getHex( SINT4 ichars_a ) ;
static  void putHex( UINT4 ival_a, SINT4 ichars_a, FILE *file_a ) ;

/* ======================================================================
 * Generate the 32-bit checksum term contributed by a given byte appearing
 * at a given address.
 */
UINT4 intelHexCS( UINT4 iAddress_a, UINT1 iByte_a )
{
  UINT4 iHashIndex = 0x12345678 + iAddress_a ;
  UINT4 iDatum = (UINT4)iByte_a, iCS ;

  (void) iMpyU64( iHashIndex,              iDatum, &iCS ) ;
  iCS += iMpyU64( iHashIndex, iHashIndex ^ iDatum, NULL ) ;

  return( iCS ) ;
}

/* ======================================================================
 * Encode a run of data bytes into Intex-Hex records, and write to a
 * file descriptor.
 */
void intelHexEncode
( UINT1 iData_a[],		/* Byte data to be encoded */
  SINT4 iLength_a,		/* Length of the data array */
  UINT4 iAddress_a,		/* Address of the first byte of data */
  UINT4 *iOffset_a,		/* Address offset (retained state) */
  FILE *file_a )		/* File descriptor for output */
{
  SINT4 ii, iDatum ;

  /* Run through the entire array of data that was supplied to us.
   */
  for( iDatum=0 ; iDatum < iLength_a ; ) {
    UINT4 iAddress = (iAddress_a + iDatum) ;
    SINT4 iDataLen = MIN( IHEX_MAXDATA, (iLength_a - iDatum) ) ;

    /* Write out a new address record whenever we cross a 64K
     * boundary, and update the internal offset.
     */
    if( (iAddress ^ *iOffset_a) & 0xFFFF0000 ) {
      iHexCheckSum_c = 0 ; fputc( ':', file_a ) ;
      putHex(                2, 2, file_a ) ;
      putHex(                0, 4, file_a ) ;
      putHex( IHEX_TYPE_LINADR, 2, file_a ) ;
      putHex(   iAddress >> 16, 4, file_a ) ;
      putHex(       -iHexCheckSum_c, 2, file_a ) ;      
      fputc( '\n', file_a ) ;

      *iOffset_a = (iAddress & 0xFFFF0000) ;
    }

    /* Write out some more of the user's data, being careful not to
     * cross over a 64K boundary in the process.
     */
    if( ((iAddress + iDataLen) ^ *iOffset_a) & 0xFFFF0000 )
      iDataLen -= ((iAddress + iDataLen) & 0x0000FFFF) ;

    if( iDataLen > 0 ) {
      iHexCheckSum_c = 0 ; fputc( ':', file_a ) ;
      putHex(              iDataLen, 2, file_a ) ;
      putHex( iAddress & 0x0000FFFF, 4, file_a ) ;
      putHex(        IHEX_TYPE_DATA, 2, file_a ) ;
      for( ii=0 ; ii < iDataLen ; ii++ ) putHex( iData_a[ iDatum++ ], 2, file_a ) ;
      putHex(       -iHexCheckSum_c, 2, file_a ) ;
      fputc( '\n', file_a ) ;
    }
  }
}

/* ======================================================================
 * Extract the contents of a single Intel-HEX record.
 */
SINT4 intelHexDecode
( const char sRecord_a[],	/* ASCII Hex record */
  UINT1 iData_a[],		/* Decoded data */
  SINT4 *iLength_a,		/* Length of the data array */
  UINT4 *iAddress_a,		/* Address of the first byte of data */
  UINT4 *iOffset_a )		/* Address offset (retained state) */
{
  SINT4 iret = IHEX_NORMAL, ival, ii ;
  SINT4 iLength ; UINT4 iAddress ;

  /* Zero the record checksum, init the reading pointer, and check for
   * the initial ':'.
   */
  *iLength_a = *iAddress_a = 0 ; /* Default return values */

  if( sRecord_a[0] != ':' ) return( IHEX_BADREC ) ;
  iHexCheckSum_c = 0 ; sHexRecordPtr_c = (sRecord_a + 1) ;

  /* Get record length and load address.
   */
  if( -1 == (ival = getHex(2)) ) return( IHEX_BADREC ) ;
  if( ival > IHEX_MAXDATA ) return( IHEX_BADREC ) ;
  iLength = ival ;

  if( -1 == (ival = getHex(4)) ) return( IHEX_BADREC ) ;
  iAddress = ival ;

  /* Get record type, and extract the appropriate contents
   */
  if( -1 == (ival = getHex(2)) ) return( IHEX_BADREC ) ;  

  switch( ival ) {
  default:			/* Unknown type code */
    return( IHEX_BADREC ) ;

  case IHEX_TYPE_DATA:		/* Fill in the data array */
    for( ii=0 ; ii < iLength ; ii++ ) {
      if( -1 == (ival = getHex(2)) ) return( IHEX_BADREC ) ;
      iData_a[ii] = ival ;
    }
    *iAddress_a = iAddress + *iOffset_a ;
    *iLength_a  = iLength  ;
    break ;

  case IHEX_TYPE_LINADR:	/* Linear address record */
    if( -1 == (ival = getHex(4)) ) return( IHEX_BADREC ) ;
    if( (iLength != 2) || (iAddress != 0) ) return( IHEX_BADREC ) ;
    *iOffset_a = (ival << 16) ;
    break ;

  case IHEX_TYPE_SEGADR:	/* Segment address record */
    if( -1 == (ival = getHex(4)) ) return( IHEX_BADREC ) ;
    if( (iLength != 2) || (iAddress != 0) ) return( IHEX_BADREC ) ;
    *iOffset_a = (ival << 4) ;
    break ;

  case IHEX_TYPE_EOF:		/* End-of-File */
    if( (iLength != 0) || (iAddress != 0) ) return( IHEX_BADREC ) ;
    iret = IHEX_EOF ;
    break ;

  case IHEX_TYPE_SEGST:		/* These are valid but ignored */
  case IHEX_TYPE_LINST:
    for( ii=0 ; ii < iLength ; ii++ ) getHex(2) ;
    break ;
  }

  /* Get the checksum and verify that the entire record was correct.
   */
  if( -1 == (ival = getHex(2)) ) return( IHEX_BADREC ) ;  
  if( iHexCheckSum_c ) return( IHEX_CHECKSUM ) ;
  return( iret ) ;
}

/* ======================================================================
 * Internal routine to write two or four characters that will form
 * part of a Hex record.
 */
static void putHex( UINT4 ival_a, SINT4 ichars_a, FILE *file_a )
{
  SINT4 ii ;

  for( ii=1 ; ii <= ichars_a ; ii++ ) {
    SINT4 idigit = 0xF & (ival_a >> (4 * (ichars_a - ii))) ;
    fputc( "0123456789ABCDEF"[idigit], file_a ) ;
  }
  iHexCheckSum_c += (UINT1)( 0xFF & ival_a ) ;
  if( ichars_a == 4 ) iHexCheckSum_c += (UINT1)( 0xFF & (ival_a >> 8) ) ;
}

/* ======================================================================
 * Internal routine to try to extract a Hex value from the next ICHARS
 * (up to four) characters.  Returns -1 if no value could be found.
 */
static SINT4 getHex( SINT4 ichars_a )
{
  SINT4 ival=0, ii ;

  for( ii=0 ; ii < ichars_a ; ii++ ) {
    char idigit, ch = *sHexRecordPtr_c++ ; ival <<= 4 ;

    if(      (ch >= '0') && (ch <= '9') ) idigit = (ch - '0') ;
    else if( (ch >= 'a') && (ch <= 'f') ) idigit = (ch - 'a') + 10 ;
    else if( (ch >= 'A') && (ch <= 'F') ) idigit = (ch - 'A') + 10 ;
    else return( -1 ) ;
    ival += (SINT4)idigit ;
  }
  iHexCheckSum_c += (UINT1)( 0xFF & (ival + (ival >> 8)) ) ;
  return( ival ) ;
}
