/*
 * SEAShellUtil.c
 * Copyright (C) Seemanta 2008 <seemanta@seemanta.net>
 * 
 * SEAShellUtil.c 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.
 * 
 * SEAShellUtil.c 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, see <http://www.gnu.org/licenses/>.
 */


//This program can be used to use SEAShell in batch mode.

#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

//#define SIMULATION     //This can be turned on to run the program in simulation mode where no EEPROM is actually
                         //burned.

// Command definitions common to SEAShell.asm and SEAShellUtil.c
#define BATCH_MODE           0xFE
#define INTERACTIVE_MODE     0xFD
#define BATCH_ADDRESS_WRITE  0xF9
#define BATCH_WRITE_DATA     0xFC
#define BATCH_READ_DATA      0xFB
#define END_RW_CYCLE         0xFA
#define NEWLINE              0x0D
#define BATCH_ADDRESS_READ   0xF8
#define EOT 0x21

//Definitions needed to talk through the serial port
#define BAUDRATE B9600
#define PROGRAMMER_DEV "/dev/ttyUSB0"


// Some local definitions
#define WRITE_DATA 0x01       //Write mode indicator for function send_init_sequence ()
#define READ_DATA 0x02        //Read mode indicator for function send_init_sequence ()
#define PAGE_SIZE 64          //This indicates the write page size as mentioned in the EEPROM data sheet.
#define READ_SIZE 200          //This indicates how many bytes we read at one shot.
#define EEPROM_SIZE 0x7FFF     //This indicates the size of the serial EEPROM. Change according to EEPROM used.

//Local function definitions
static void read_from_eeprom();
static void write_into_eeprom(FILE *fp);
static void write_byte(unsigned char cmd);
static void get_response(unsigned char wait_char);
static void get_banner();
static void copy_into_file_buff(char *buff);
static void switch_mode(unsigned char mode);
static void send_end_cycle();
static void send_init_sequence(unsigned int index, unsigned char nobytes, unsigned char mode);
static void write_byte_at_address(unsigned int index, unsigned char nobytes);

//Global variables
static unsigned int g_index = 0;
static char *FILE_DATA_BUFF = NULL;
static int file_size = 0;
int serial_fd;

int main(int argc, char *argv[])
{
    struct termios serial_old, serial_new, stdin_new, stdin_old, stdout_old;
    unsigned char mode = 0;
    FILE *fp = NULL;
    //validate arguments
    if((argc != 3) && (argc != 4))
    {
        printf("Improper usage!!\n\rCorrect Usage: %s -r [count]|-w hex_file_to_read_or_write\n\r",argv[0]);
        exit(1);
    }
    if(!strcmp(argv[1],"-r"))
    {
        mode = READ_DATA;  //override default setting in case programming/verification is not requested
        //Opening output file for writing
        if((fp=fopen(argv[3],"w")) == NULL)
        {
            printf("Error: File doesn't exist!!\n\r");
            exit(1);
        }
        file_size = atoi(argv[2]);
        // If user gives size as zero then read complete EEPRROM
        if(file_size == 0)
        {
            file_size = EEPROM_SIZE;
        }
    }
    else
    if(!strcmp(argv[1],"-w"))
    {
        mode = WRITE_DATA;  //override default setting in case programming/verification is not requested
        //Opening input file for reading
        if((fp=fopen(argv[2],"r"))==NULL)
        {
            printf("Error: File doesn't exist!!\n\r");
            exit(1);
        }
    }

#ifndef SIMULATION
    serial_fd = open(PROGRAMMER_DEV, O_RDWR | O_NOCTTY); 
    if (serial_fd < 0)
    {
        perror(PROGRAMMER_DEV);
        exit(1);
    }

    // get the current serial and stdin settings for restoring later
    // also get a working copy for each of them
    tcgetattr(STDIN_FILENO, &stdin_old);
    tcgetattr(STDIN_FILENO, &stdin_new);  //working copy
    tcgetattr(serial_fd, &serial_old );
    tcgetattr(serial_fd, &serial_new );   //working copy
    tcgetattr(STDOUT_FILENO,&stdout_old);  //restoring back
    serial_new.c_cflag = BAUDRATE | CS8 | CLOCAL | CREAD;
    serial_new.c_iflag = IGNPAR;
    serial_new.c_oflag = 0;
    serial_new.c_lflag = 0;
    serial_new.c_cc[VMIN]=1;
    serial_new.c_cc[VTIME]=0;
    tcflush(serial_fd, TCIFLUSH);
    tcsetattr(serial_fd,TCSANOW,&serial_new);
    // next stop echo and buffering for stdin
    stdin_new.c_lflag &= ~(ICANON | ECHO);
#endif
    //First get rid of the banner and the initial prompt. The micro should be RESET in case
    //the banner has not been printed already.
    get_banner();
    if(mode == READ_DATA)                 //proceed further ONLY if programming/verification is requested
    {
        printf("Reading from eeprom...please wait");
        fflush(stdout);
        read_from_eeprom();
        for(g_index = 0 ; g_index < file_size ; g_index++) 
        {
            //uncomment if you wish to know the data read from the serial port for debugging
            //printf("Data in buffer: %c\n\r",FILE_DATA_BUFF[g_index]);
            fputc(FILE_DATA_BUFF[g_index], fp);
        }
        printf("\n\rReading done. Output stored in file \"%s\"\n\r", argv[3]);
        fclose(fp); //closing the file opened for writing.
    }
    else
    if(mode == WRITE_DATA)
    {
        printf("Writing into eeprom from file \"%s\"...please wait", argv[2]);
        write_into_eeprom(fp);
        printf("\n\rWriting done.\n\r");
        fclose(fp);
    }

    close(serial_fd);  //closing the serial port
    if(FILE_DATA_BUFF)
    {
        free(FILE_DATA_BUFF);
    }
#ifndef SIMULATION
    tcsetattr(serial_fd, TCSANOW, &serial_old );   //restoring back
#endif
    return 0;
}

static void read_from_eeprom()
{
    FILE_DATA_BUFF = malloc(file_size);
    unsigned char byte = 0;
    unsigned int index = 0;
    if(!FILE_DATA_BUFF)
    {
        exit(1);
    }
    switch_mode(BATCH_MODE);    //First we must switch SEAShell into batch mode
    for(index = 0; index < file_size; index++)
    {
        if((index%READ_SIZE) == 0)
        {
            send_end_cycle();
            send_init_sequence(index, READ_SIZE, READ_DATA);
            printf(".");      
            fflush(stdout);
        }
	write_byte(BATCH_READ_DATA);
	write_byte(NEWLINE);
        read(serial_fd,&byte,1);
        get_response(EOT);
        //uncomment if you wish to know the data read from the serial port for debugging
        //printf("Read byte at index %d: %c\r\n", index, byte);
        FILE_DATA_BUFF[g_index++] = byte;
    }
    switch_mode(INTERACTIVE_MODE);
    write_byte('s');       //to reset dump counter to 0
    write_byte(NEWLINE);
    return;
}

static void write_into_eeprom(FILE *fp)
{
    long size = 0, index = 0, bytes_remaining = 0, no_bytes_to_write = 0; 
    char inp_line[80]={0};
    char first_time = 1;

    //Create the file buffer first
    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    rewind(fp);
    //Uncomment if you need to know the file size for debugging purposes
    //printf("File size is %ld bytes...\n", size);
    FILE_DATA_BUFF = malloc(size);
    if(!FILE_DATA_BUFF)
    {
        printf("Memory allocation failed, exiting...\n");
        exit(1);
    }    
    while( (fgets(inp_line,80,fp)))
    {
        copy_into_file_buff(inp_line);
    }

    switch_mode(BATCH_MODE);    //First we must switch SEAShell into batch mode
    
    // Here begins the main loop which will write into the eeprom
    first_time = 1;
    bytes_remaining = size;
    no_bytes_to_write = 0;

    for(index = 0; index < size; index++)
    {

        if(first_time)
        {
            first_time = 0;
            send_end_cycle();
            printf(".");
            fflush(stdout);
	    if((bytes_remaining / PAGE_SIZE) > 0)
	    {
		no_bytes_to_write = PAGE_SIZE;
		bytes_remaining = bytes_remaining - PAGE_SIZE;
	    }
	    else
	    {
	        no_bytes_to_write = bytes_remaining;
	    }
            send_init_sequence(index, no_bytes_to_write, WRITE_DATA);
            write_byte_at_address(index, no_bytes_to_write);
        }
        else
        if(((index)%64) == 0)
        {
            send_end_cycle();
            printf(".");
            fflush(stdout);
            if((bytes_remaining / PAGE_SIZE) > 0)
            {
	        no_bytes_to_write = PAGE_SIZE;
                bytes_remaining = bytes_remaining - PAGE_SIZE;
	    }
	    else
	    {
	        no_bytes_to_write = bytes_remaining;
	    }
            send_init_sequence(index, no_bytes_to_write, WRITE_DATA);
            write_byte_at_address(index, no_bytes_to_write);
        }
    }
    switch_mode(INTERACTIVE_MODE);
}

static void send_end_cycle()
{
#ifdef SIMULATION
    printf("==ENDING CYCLE==\n");
    return;
#endif
    write_byte(END_RW_CYCLE);
    write_byte(NEWLINE);
    get_response(EOT);
}

static void switch_mode(unsigned char mode)
{
#ifdef SIMULATION
    return;
#endif
    //Switching into batch mode first
    write_byte(mode);
    write_byte(NEWLINE);
    if(mode == BATCH_MODE)
    {
        get_response(EOT);
        get_response(EOT);
    }
    else
    if(mode == INTERACTIVE_MODE)
    {
      // Do nothing
    }
    else
    {
        assert(0);  //error if mode is anything other than BATCH or INTERACTIVE
    }
}


static void send_init_sequence(unsigned int address, unsigned char length, unsigned char mode)
{
   //Writing address to write
    if(mode == WRITE_DATA)
    {
        write_byte(BATCH_ADDRESS_WRITE);
    }
    else
    if(mode == READ_DATA)
    {
        write_byte(BATCH_ADDRESS_READ);
    }
    write_byte(NEWLINE);
    get_response(EOT);
 
    //Write the address to start writing from
    write_byte((address) & 0xFF);            //Low byte first
    write_byte((address >> 8) & 0xFF);       //High byte next

    //Finally the count of characters we would be writing
    write_byte(length);
    get_response(EOT);
}
    
static void write_byte_at_address(unsigned int index, unsigned char length)
{
    unsigned int idx = 0;
    //Now the actual data that we wish to write
    write_byte(BATCH_WRITE_DATA);
    write_byte(NEWLINE);
    get_response(EOT);
    for(idx=index; idx < index+length; idx++)
    {
        write_byte(FILE_DATA_BUFF[idx]);
#ifdef SIMULATION
        printf("Writing byte: %c at index %d\n",FILE_DATA_BUFF[idx], idx);
#endif
    }
    get_response(EOT);
    send_end_cycle();
    return;
}

static void get_response(unsigned char wait_char)
{
    unsigned char resp = 0x00;
#ifdef SIMULATION
    return;
#endif

    while(read(serial_fd,&resp,1))
    {
        if(resp == wait_char)
        {
            break;
        }
    }
}

static void write_byte(unsigned char cmd)
{
    //uncomment if you wish to know the data written into serial port for debugging
    //printf("Writing data: %X\n\r",cmd);  
#ifndef SIMULATION
    write(serial_fd,&cmd,1);
#endif
}

static void get_banner()
{
#ifndef SIMULATION
    get_response('>');
    get_response(' ');
#endif
}

static void copy_into_file_buff(char *buff)
{
    long i = 0;
    while(*(buff+i) != '\0')
    {
        FILE_DATA_BUFF[g_index++] = *(buff + i++);
    }
}