/* ------------------------------------------------------------------------- */
/* lib/srec.c                                                                */
/*                                                                           */
/* s-record parser for the Miniboot project.                                 */
/*                                                                           */
/* Copyright (C) 2000, 2001, 2002 Magnus Damm <damm@opensource.se>           */
/*                                                                           */
/* This program is free software; you can redistribute it and/or             */
/* modify it under the terms of the GNU General Public License as            */
/* published by the Free Software Foundation; either version 2 of            */
/* the License, or (at your option) any later version.                       */
/*                                                                           */
/* This program is distributed in the hope that it will be useful,           */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             */
/* GNU General Public License for more details.                              */
/*                                                                           */
/* You should have received a copy of the GNU General Public License         */
/* along with this program; if not, write to the Free Software               */
/* Foundation, Inc., 59 Temple Place, Suite 330, Boston,                     */
/* MA 02111-1307 USA                                                         */
/* ------------------------------------------------------------------------- */

#include <stdio.h>
#include <unistd.h>

typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;

extern int verbose;

/* return values stolen from Miniboot lib/flash.h */

#define FLASH_OK                     0
#define FLASH_ERROR                  1
#define FLASH_BAD_ADDRESS            2
#define FLASH_BAD_DEVICE_ID          3
#define FLASH_BAD_MANUFACTURER_ID    4
#define FLASH_ERASE_ERROR            5
#define FLASH_PROGRAM_ERROR          6
#define FLASH_SECTOR_PROTECTED_ERROR 7

/* we don't have any flash, but we use the code as if we had a 8-bit flash.
 * instead of writing to the flash we send a write command to the board... 
 */

#define FLASH_IS_8BIT 
#include "boot_cmds.h"

static unsigned int last_address = 0;
static int global_serial_fd; /* not a nice solution, what the heck.. */




static int set_address(u8 *address)
{
	unsigned int a = (unsigned int) address;

	if ((last_address == 0) || (a != last_address)) {

		if (boot_address_set(global_serial_fd, a) < 0) {
			return -1;
		}
	}

	return 0;
}

int board_flash_program(u8 *address, u8 data)
{
	if (set_address(address) < 0) {
		return -1;
	}

	if (boot_write_n(global_serial_fd, 8, &data) < 0) {
		return -1;
	}

	last_address = (unsigned int)address + 1;

	return 0;
}

int board_flash_verify(u8 *address, u8 data)
{
	unsigned char buf;

	if (set_address(address) < 0) {
		return -1;
	}

	if (boot_read_n(global_serial_fd, 8, &buf) < 0) {
		return -1;
	}

	if (buf != data) {
		return -1;
	}

	last_address = (unsigned int)address + 1;

	return 0;
}

int flash_verbose(int status)
{
	if (status < 0) {
		return -1;
	}

	return 0;
}

/* ------------------------------------------------------------------------- */

#define SREC_TIMEOUT 30   /* seconds */

#define SREC_STATE_S       0
#define SREC_STATE_TYPE    1
#define SREC_STATE_SIZE0   2
#define SREC_STATE_SIZE1   3
#define SREC_STATE_ADDR0   4
#define SREC_STATE_ADDRN   5 
#define SREC_STATE_DATA0   6
#define SREC_STATE_DATAN   7
#define SREC_STATE_CHKSUM0 8
#define SREC_STATE_CHKSUM1 9

#define SREC_ERR_NO_ERROR     0
#define SREC_ERR_FINISHED     1
#define SREC_ERR_BAD_CHAR     2
#define SREC_ERR_BAD_RECORD   3
#define SREC_ERR_BAD_CHECKSUM 4
#define SREC_ERR_BAD_STATE    5
#define SREC_ERR_FLASH_ERROR  6
#define SREC_ERR_VERBOSE_ERROR 7

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static int ascii2int(char c)
{
  if ((c >= '0') && (c <= '9'))
    return c - '0';

  if ((c >= 'A') && (c <= 'F'))
    return (c - 'A') + 10 ;

  if ((c >= 'a') && (c <= 'f'))
    return (c - 'a') + 10 ;

  return -1;
}
    
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

static u8 srec_do_checksum_u32(u8 checksum, u32 data)
{
  for (; data; data >>= 8)
    checksum += data & 0xff;

  return checksum;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void srec_parse(int file_fd, int serial_port_fd, int do_verify)
{
  char c;
  int i;
  int state = SREC_STATE_S;
  int address_width = 0;  /* in bytes, 0 for ignore line */
  int charsleft, tmp, tmp2, datanibbles;
  u32 address;
  u8 checksum;
  int err = SREC_ERR_NO_ERROR;
  int no_records = 0;
  int no_chars = 0;
  int last_record = 0;

  global_serial_fd = serial_port_fd;
  
 /* warning fix */

  charsleft = tmp = tmp2 = address = checksum = 0;

  do {

    if (read(file_fd, &c, 1) != 1) {
      break;
    }

    no_chars++;

    i = ascii2int(c);  
 
    switch(state) {

    case SREC_STATE_S:
      if ((c == 'S') || (c == 's'))
	state++;
      else
	if ((c != ' ') && (c != '\t') && (c != '\r') && (c != '\n'))
	  err = SREC_ERR_BAD_CHAR;
      
      break;
      
    case SREC_STATE_TYPE:
      switch(c)	{
      case '1': address_width = 2; break;
      case '2': address_width = 3; break;
      case '3': address_width = 4; break;
	
	/* these records parsed as end of file */
	
      case '7':
      case '8':
      case '9': last_record = 1; /* fall through */

	/* these records are ignored */

      case '5':
      case '0': address_width = 0; break;
	
      default: err = SREC_ERR_BAD_CHAR;
      }
      state++;
      break;
      
    case SREC_STATE_SIZE0:
      if (i < 0)
	err = SREC_ERR_BAD_CHAR;
      else {
	tmp = i;
	state++;
      }
      break;
      
    case SREC_STATE_SIZE1:
      if (i < 0)
	err = SREC_ERR_BAD_CHAR;
      else {
	checksum = (tmp << 4) | i;
	charsleft = checksum << 1;

	if (charsleft < ((address_width << 1) + 2)) {
	  err = SREC_ERR_BAD_RECORD;
	  break;
	}

	if (address_width)
	    state++;         /* parse address */
	else {
	  tmp = tmp2 = 0;
	  state = SREC_STATE_DATAN;
	}
      }
      break;
      
    case SREC_STATE_ADDR0:
      address = 0;
      tmp = (address_width << 1); /* address chars left */
      state++;

      /* fall through */
      
    case SREC_STATE_ADDRN:
      if (i < 0)
	err = SREC_ERR_BAD_CHAR;
      else {
	address <<= 4;
	address |= i;
	
	tmp--;
	charsleft--;

	if (tmp <= 0) {
	  checksum = srec_do_checksum_u32(checksum, address);

	  if (charsleft <= 2) {
	    state = SREC_STATE_CHKSUM0;
	  }
	  else {
	    state++;
	  }
	}
	
      }
      break;
      
    case SREC_STATE_DATA0:

#if defined(FLASH_IS_32BIT)
      tmp2 = (address & 0x03) << 1;
      address &= ~0x03;
#elif defined(FLASH_IS_16BIT)
      tmp2 = (address & 0x01) << 1;
      address &= ~0x01;
#elif defined(FLASH_IS_8BIT)
      tmp2 = 0;
#else
#error FLASH not supported/configured
#endif
      tmp = 0;
      datanibbles = 0;
      state++;

      /* fall through */
    
    case SREC_STATE_DATAN:
      if (i < 0) {
	err = SREC_ERR_BAD_CHAR;
	break;
      }
      else {

#if defined(FLASH_IS_32BIT)

	/* if address is on a 32-bit boundary - write word of data */

	if ((tmp2 == 8) || (charsleft <= 2)) {

	  /* first of all: add bytes to checksum.     */
	  /* this works because of the checksum code, */
	  /* non-used bytes are set to 0 and does not */
	  /* affect the checksum...                   */

	  checksum = srec_do_checksum_u32(checksum, tmp);

	  /* when we are approaching the checksum part of the line */
	  /* we should flush down our cache to flash..             */
					    
	  tmp <<= ((8 - tmp2) << 2);

	  if (address_width) {
	    u32 data, mask;
	    int stat;

	    if (datanibbles < 8) {
	      stat = board_flash_read((u32 *) address, &data);

	      if (flash_verbose(stat) < 0) {
		err = SREC_ERR_FLASH_ERROR;
		break;
	      }

	      mask = (1 << (datanibbles << 2)) - 1;
	      mask <<= ((8 - tmp2) << 2);

	      tmp |= (data & ~mask);
	    }

	    stat = board_flash_program((u32 *) address, tmp);

	    if (flash_verbose(stat) < 0) {
	      err = SREC_ERR_FLASH_ERROR;
	      break;
	    }

	  }

	  address += 4;
	  tmp2 = tmp = 0;
	  datanibbles = 0;
	}

#elif defined(FLASH_IS_16BIT)

	/* if address is on a 16-bit boundary - write word of data */

	if ((tmp2 == 4) || (charsleft <= 2)) {

	  /* first of all: add bytes to checksum.     */
	  /* this works because of the checksum code, */
	  /* non-used bytes are set to 0 and does not */
	  /* affect the checksum...                   */

	  checksum = srec_do_checksum_u32(checksum, tmp);

	  /* when we are approaching the checksum part of the line */
	  /* we should flush down our cache to flash..             */
					    
	  tmp <<= ((4 - tmp2) << 2);

	  if (address_width) {
	    u16 data, mask;
	    int stat;

	    if (datanibbles < 4) {
	      stat = board_flash_read((u16 *) address, &data);

	      if (flash_verbose(stat) < 0) {
		err = SREC_ERR_FLASH_ERROR;
		break;
	      }

	      mask = (1 << (datanibbles << 2)) - 1;
	      mask <<= ((4 - tmp2) << 2);

	      tmp |= (data & ~mask);
	    }

	    stat = board_flash_program((u16 *) address, tmp);

	    if (flash_verbose(stat) < 0) {
	      err = SREC_ERR_FLASH_ERROR;
	      break;
	    }
	  }

	  address += 2;
	  tmp2 = tmp = 0;
	  datanibbles = 0;
	}

#elif defined(FLASH_IS_8BIT)

	/* if address is on a 8-bit boundary - write word of data */

	if ((tmp2 == 2) || (charsleft <= 2)) {

	  /* first of all: add bytes to checksum.     */

	  checksum = srec_do_checksum_u32(checksum, tmp);

	  if (address_width) {
	    int stat;

	    if (do_verify) {
		    stat = board_flash_verify((u8 *) address, tmp);
		    if (flash_verbose(stat) < 0) {
		            err = SREC_ERR_VERBOSE_ERROR;
			    break;
		    }
	    }
	    else {
		    stat = board_flash_program((u8 *) address, tmp);
		    if (flash_verbose(stat) < 0) {
			    err = SREC_ERR_FLASH_ERROR;
			    break;
		    }
	    }
	  }

	  address++;
	  tmp2 = tmp = 0;
	  datanibbles = 0;
	}
#endif

	if (charsleft > 2) {  /* if not reached checksum */

	  tmp <<= 4;
	  tmp |= i;
	  tmp2++;
	  datanibbles++;

	  charsleft--;
	
	  break;
	}
      }

      state++;

      /* fall through */
      
    case SREC_STATE_CHKSUM0:
      if (i < 0)
	err = SREC_ERR_BAD_CHAR;
      else {

	tmp = i;
	state++;
      }
      break;
      
    case SREC_STATE_CHKSUM1:
      if (i < 0)
	err = SREC_ERR_BAD_CHAR;
      else {
	tmp2 = ((tmp << 4) | i) & 0xff;
	checksum = ~checksum;

	if (checksum != tmp2)
	  err = SREC_ERR_BAD_CHECKSUM;
	else {
          if (verbose) {
            printf(".");
	    fflush(stdout);
	  }
	  no_records++;
	  state = SREC_STATE_S;
	  if (last_record)
            err = SREC_ERR_FINISHED;	  
	}
      }
      break;
    default:
      err = SREC_ERR_BAD_STATE;
    }

  } while (err == SREC_ERR_NO_ERROR);
 
  if (verbose) {
    printf("\n");
  }

  switch(err) {

  case SREC_ERR_FINISHED:
    if (verbose) {
      printf("Done.");
    }
    break;

  case SREC_ERR_NO_ERROR:
    fprintf(stderr, "Timeout!");
    break;
  case SREC_ERR_BAD_CHAR:
    fprintf(stderr, "Error: Char not supported.");
    break;
  case SREC_ERR_BAD_RECORD:
    fprintf(stderr, "Error: Bad record.");
    break;
  case SREC_ERR_BAD_CHECKSUM:
    fprintf(stderr, "Error: Record has bad checksum.");
    break;
  case SREC_ERR_BAD_STATE:
    fprintf(stderr, "Error: Bad state.");
    break;
  case SREC_ERR_FLASH_ERROR:
    fprintf(stderr, "Error: Unable to write to flash.");
    break;
  case SREC_ERR_VERBOSE_ERROR:
    fprintf(stderr, "Error: Verify failed - Bad data at address 0x%08x.",
	   last_address);
    break;
  }

  if (verbose) {
    printf("\n%u chars / %u records %s.\n", no_chars, no_records,
	  do_verify ? "verified" : "downloaded");
  }
  
  return;
}

/* ------------------------------------------------------------------------- */

