/* LARTLOAD Utility. 
   This software is freeware - 
   use it, abuse it - just don't blame me if it doesn't work.

   Must be compiles with the ncurses library:
   gcc -Wall -lncurses -o lartload lartload-0.2.c

   R Green  25 Jan 2002
   rik@ee.bath.ac.uk

   R Green  31 Jan 2002
   Version 0.2:  Just took out a few funnies so it compiles ok with -Wall.
                 Also added command line option for terminal speed,
                  as requested by Mr. Riker.
                 Also added -q option, because it looks useful for
                  batch processing and impatient people.
		 Yet more - you can now talk to your lart after all the
                  downloads have completed via a simle (note VERY simple!)
                  terminal emulator.
*/


#define USAGE "\n\
 v0.2  Usage:             \n\
\n\
 lartload [-b[f] file] [-k[f] file] [-r[f] file] [-B]  \n\
          [-Sn] [-d device] [-Tn] [-Dn] [-q]           \n\
\n\
      -b[f] <filename>   download blob to Lart [and flash]      \n\
      -k[f] <filename>   download kernel to Lart [and flash]    \n\
      -r[f] <filename>   download ramdisk to Lart [and flash]   \n\
      -B                 boot system after downloading          \n\
      -Sn                use serial port /dev/ttySn             \n\
      -d <device>        use other device for comms             \n\
      -Tn                use baud rate n for the terminal (default 9600) \n\
      -Dn                use baud rate n for download (default 115200)   \n\
      -q                 quick - no delays or waiting for keypress       \n"


/* BANN1 appears to the left of the screen banner, BANN2 to the right:  */
#define BANN1 "LART download utility v0.2"
#define BANN2 "R Green  31 Jan 2002"


/* A note about colours:
   
   colour pair 1:  Progress window - Standard grey
   colour pair 2:  Terminal window - Green
   colour pair 3:  Error messages  - red
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <ncurses.h>
#include <unistd.h>
#include <stdlib.h>

/* baudrate settings are defined in <asm/termbits.h> which is
   included by <termios.h> */


#define FALSE 0
#define TRUE 1
#define NL 0

#define BUFLEN 256


int readuntil(char *, char *, int);
void setserial(char *);
int download(char *, char *);
int flashit(char *);
void giveup(char *);
int strcmp2(char *, char *, char *);


char *blob = 0;         /* blob file name  */
int bflash = FALSE;     /* flag to say whether to flash blob or not  */

char *kernel = 0;       /* kernel file name  */
int kflash = FALSE;     /* flag to say whether to flash kernel or not  */

char *ramdisk = 0;      /* ramdisk file name  */
int rflash = FALSE;     /* flag to say whether to flash ramdisk or not  */

int boot = FALSE;       /* flag to say whether to boot LART or not  */
int sfd;                /* file descriptor for srial port  */

char sbuf[BUFLEN];                   /* serial input buffer  */
char serialport[50]="/dev/ttyS0";    /* name of serial device  */
char termbaud[10]="9600";            /* terminal baud rate  */
char downbaud[10]="115200";          /* download baud rate  */
int changebaud = TRUE;               /* true is baud rates not the same  */
struct termios oldtio;               /* old serial port settings  */

WINDOW *termwin, *progwin, *bannwin; /* terminal, progress and banner windows */


char emess[50];                      /* error message string  */

int quick = FALSE;                   /* true for no delays  */


int main(int argc, char **argv) {
  int i,  x, y;                      /* various temp variables  */
  int termheight;                    /* terminal window height  */
  char s[200];                       /* banner message buffer   */

  int kok=TRUE, rok=TRUE, bok=TRUE;  /* ok flags  */
  int np=0, nc=0;                    /* number of processes, number sucessful  */


  /* Check for correct arguments and parse:  */

  if (argc < 3) {
    printf(USAGE);
    exit(-1);
  }

  for (i=1; i<(argc-1); i++) {
    if (strcmp(argv[i], "-b")==0)  {blob = argv[i+1]; np++;}
    if (strcmp(argv[i], "-bf")==0)  {blob = argv[i+1]; bflash = TRUE; np+=2;}
    if (strcmp(argv[i], "-k")==0)  {kernel = argv[i+1]; np++;}
    if (strcmp(argv[i], "-kf")==0)  {kernel = argv[i+1]; kflash = TRUE; np+=2;}
    if (strcmp(argv[i], "-r")==0)  {ramdisk = argv[i+1]; np++;}
    if (strcmp(argv[i], "-rf")==0)  {ramdisk = argv[i+1]; rflash =TRUE; np+=2;}
    if (strcmp(argv[i], "d")==0)  strncpy(serialport, argv[i+1], 49);
  }

  for (i=1; i<argc; i++) {
    if (strcmp(argv[i], "-B")==0)  {boot = TRUE; np++;}
    if (strcmp(argv[i], "-S0")==0)  strcpy(serialport, "/dev/ttyS0");
    if (strcmp(argv[i], "-S1")==0)  strcpy(serialport, "/dev/ttyS1");
    if (strcmp(argv[i], "-S2")==0)  strcpy(serialport, "/dev/ttyS2");
    if (strcmp(argv[i], "-S3")==0)  strcpy(serialport, "/dev/ttyS3");
    if (strstr(argv[i], "-T")==argv[i])  strncpy(termbaud, argv[i]+2, 9);
    if (strstr(argv[i], "-D")==argv[i])  strncpy(downbaud, argv[i]+2, 9);
    if (strcmp(argv[i], "-q")==0)  quick = TRUE;
  }

  if (np == 0) {
    printf(USAGE);
    exit(-1);
  }


  /* check to see if we need to keep changing baud rates:  */

  if (strcmp(termbaud, downbaud)==0) changebaud = FALSE;


  /* initialise curses:   */

  initscr();
  raw();          /* let me have all charcaters */
  keypad(stdscr, TRUE);
  noecho();
  cbreak();       /* let me have all characters as they come */


  /* initialise colours:  */

  start_color();  /* give me colours  */
  init_pair(1, COLOR_WHITE, COLOR_BLACK);
  init_pair(2, COLOR_GREEN, COLOR_BLACK);
  init_pair(3, COLOR_RED, COLOR_BLACK);


  /* calculate screen dimensions:  */

  termheight = (LINES*2)/3;


  /* create the windows:  */

  termwin = newwin(termheight, COLS, 0, 0);  /* "terminal" window */
  idlok(termwin, TRUE);       /* necessary for scrolling */
  scrollok(termwin, TRUE);    /* enable window scrolling */
  nodelay(termwin, TRUE);     /* return keybaord data immediately */
  wattron(termwin, COLOR_PAIR(2));
  keypad(termwin, TRUE);      /* give us all characters  */

  progwin = newwin(LINES-termheight-1, COLS, termheight+1, 0);
  idlok(progwin, TRUE);
  scrollok(progwin, TRUE);
  nodelay(progwin, TRUE);
  wattron(progwin, COLOR_PAIR(1));

  bannwin = newwin(1, COLS, termheight, 0);


  /* print the banner message:  */

  wattron(bannwin, A_REVERSE);  /* reverse video */
  memset(s, ' ', COLS);
  *(s+COLS) = NL;
  mvwprintw(bannwin, 0, 0, "%s", s);
  mvwprintw(bannwin, 0, 1, BANN1);
  mvwprintw(bannwin, 0, COLS-strlen(BANN2)-1, BANN2);
  wrefresh(bannwin);


  /* open the serial port:  */

  wprintw(progwin, "Opening serial port: %s...", serialport);
  wrefresh(progwin);

  /* open modem device for reading and writing and not as controlling tty
     because we don't want to get killed if linenoise sends CTRL-C:  */

  sfd = open(serialport, O_RDWR | O_NOCTTY);
  if (sfd <0) giveup("cannot open serial port");
  
  if (quick == FALSE)  sleep(1);

  wprintw(progwin, "done.\n");
  wrefresh(progwin);

  wprintw(progwin, "Setting serial attributes...");
  wrefresh(progwin);


  /* save old serial settings:  */
  
  tcgetattr(sfd, &oldtio);  
  

  /* set the terminal baud rate:  */

  setserial(termbaud);


  if (quick == FALSE)  sleep(1);

  wprintw(progwin, "done.\n");
  wrefresh(progwin);



  /* The user must now reset the LART in order that it's state can
     be tracked:  */

  wattron(progwin, A_BLINK);
  wprintw(progwin, "\nRESET LART NOW");
  wrefresh(progwin);


  /* now read input until the boot loader is ready for input: */

  readuntil("Consider", 0, FALSE);

  wattroff(progwin, A_BLINK);  /* stop it blinking  */
  wprintw(progwin, "\rRESET LART NOW\n\n");
  wrefresh(progwin);

  readuntil("press any key to stop", 0, FALSE);


  /* send a character to stop the autoboot: */

  if (quick == FALSE)  sleep(1);
  wprintw(progwin, "Stopping autoboot...");
  wrefresh(progwin);

  if (write(sfd, "X", 1) < 0) giveup("Serial Tx error");


  /* wait for blob prompt:  */

  readuntil("blob>", 0, FALSE);
  wprintw(progwin, "stopped.\n");
  wrefresh(progwin);


  /* set the download speed:  */

  wprintw(progwin, "Setting download speed...");
  wrefresh(progwin);

  if (write(sfd, "speed ", 6) < 0) giveup("Serial Tx error");
  if (write(sfd, downbaud, strlen(downbaud)) < 0) giveup("Serial Tx error");
  if (write(sfd, "\n", 1) < 0) giveup("Serial Tx error");

  if (readuntil("speed set", "Invalid", FALSE)==2)
    giveup("Lart cannot accept baud rate");

  readuntil ("blob>", 0, FALSE);

  wprintw(progwin, "done.\n");
  wrefresh(progwin);


  /* download blob, flash and/or kernel as requested:  */
  
  if (blob != NL) {
    bok = download("blob", blob);
    if (bok) {
      nc++;
      if (bflash != NL)  if (flashit("blob")) nc++;
    }
  }

  if (kernel != NL) {
    kok = download("kernel", kernel);
    if (kok) {
      nc++;
      if (kflash != NL)  if (flashit("kernel")) nc++;
    }
  }

  if (ramdisk != NL) {
    rok = download("ramdisk", ramdisk);
    if (rok) {
      nc++;
      if (rflash != NL)  if (flashit("ramdisk")) nc++;
    }
  }
  

  /* boot LART if requested and if all other processes ok:  */
  
  if (boot) {
    if (rok && bok && kok) {
      wprintw(progwin, "\nBooting LART...");
      wrefresh(progwin);

      if (write(sfd, "boot\n", 5) < 0) giveup("Serial Tx error");
      nc++;

      wprintw(progwin, "in progress\n");
      wrefresh(progwin);
    }
    else {
      wattron(progwin, COLOR_PAIR(3) | A_BOLD);
      wprintw(progwin,"Not booting due to error in 1 or more processes.\n");
      wrefresh(progwin);
      wattroff(progwin, COLOR_PAIR(3) | A_BOLD);
    }
  }
  

  /* read lart output until the user presses a key */
    
  if (np == nc) wprintw(progwin, "\nALL PROCESSES COMPLETE - ");
  else wprintw(progwin, "\n%d/%d PROCESSES COMPLETED SUCESSFULLY - ", nc, np);
  
  if (quick == FALSE) {
    wattron(progwin, A_BLINK);
    wprintw(progwin, "Press F1 to exit ");
    wrefresh(progwin);
    
    readuntil(0, 0, TRUE);

    getyx(progwin, y, x);
    wattroff(progwin, A_BLINK);
    mvwprintw(progwin, y, x-22, "Press F1 to exit ");
    wrefresh(progwin);
  } 


  /* restore old serial port settings, close windows and exit:  */

  wprintw(progwin, "\nRestoring old serial port settings...");
  wrefresh(progwin);

  tcflush(sfd, TCIFLUSH);
  if (tcsetattr(sfd, TCSANOW, &oldtio)!=0) giveup("Serial setup error");

  wprintw(progwin, "done.\n");
  wrefresh(progwin);


  /* Finally say goodbye to ncurses:  */

  if (quick == FALSE)  sleep(1);

  delwin(progwin);
  delwin(bannwin);
  delwin(termwin);

  endwin();

  return(0);
}





void giveup(char *emess) {

  int x, y;

  wattron(progwin, COLOR_PAIR(3) | A_BOLD);
  wprintw(progwin, "\nERROR: %s\n", emess);
  wattroff(progwin, COLOR_PAIR(3) | A_BOLD);

  wattron(progwin, A_BLINK);
  wprintw(progwin, "Press any key to exit ");
  wrefresh(progwin);

  readuntil(0, 0, TRUE);

  getyx(progwin, y, x);
  wattroff(progwin, A_BLINK);
  mvwprintw(progwin, y, x-22, "Press any key to exit ");
  wrefresh(progwin); 


  delwin(progwin);
  delwin(bannwin);
  delwin(termwin);

  endwin();

  exit(-1);
}



int readuntil(char *compstring, char *compstring2, int key) {

  int res, len;
  static char sbuf2[BUFLEN*2] = "";

  while (TRUE) {   /* loop until terminate */

     res = read(sfd, sbuf, BUFLEN-1); /* reads 0 to BUFLEN-1 chars */
     sbuf[res] = 0;
     wprintw(termwin, "%s", sbuf);
     wrefresh(termwin);

     strcat(sbuf2, sbuf);
     len = strlen(sbuf2);
     if (len > BUFLEN) memmove(sbuf2, sbuf2+(len-BUFLEN), BUFLEN+1);

     if (compstring != 0)
       if (strstr(sbuf2, compstring) != 0) {
	 sbuf2[0] = NL;  /* so that is doesn't get detected twice! */
	 return(1);
       }

     if (compstring2 != 0)
       if (strstr(sbuf2, compstring2) !=0) {
	 sbuf2[0] = NL;  /* ditto  */
	 return(2);
       }

     if (key == TRUE) {
       res = wgetch(termwin);
       if (res != ERR) {
	 if (res == KEY_F(1)) return(0);
	 if (res == KEY_BACKSPACE) res = 8;
	 if (write(sfd, &res, 1) < 0)  giveup("Serial Tx error");
       }
     }
   }
 }



 void setserial(char *baudrate) {
   struct termios newtio;
   bzero(&newtio, sizeof(newtio));  /* clear the structure */


 /* BAUDRATE: Set bps rate.  You could also use cfsetispeed and cfsetospeed.
    CRTSCTS:  Output hardware flow control.
    CS8:      8n1 (8 bits, no parity, 1 stop bit).
    CLOCAL:   local connection, no modem control.
    CREAD:    enable receiving characters. */

   if (strcmp2(baudrate, "1200", "1k2")==0)  newtio.c_cflag = B1200;
   else if (strcmp2(baudrate, "2400", "2k4")==0) newtio.c_cflag = B2400;
   else if (strcmp2(baudrate, "4800", "4k8")==0) newtio.c_cflag = B4800;
   else if (strcmp2(baudrate, "9600", "9k6")==0) newtio.c_cflag = B9600;
   else if (strcmp2(baudrate, "19200", "19k2")==0) newtio.c_cflag = B19200;
   else if (strcmp2(baudrate, "38400", "38k4")==0) newtio.c_cflag = B38400;
   else if (strcmp2(baudrate, "57600", "57k6")==0) newtio.c_cflag = B57600;
   else if (strcmp2(baudrate, "115200", "115k2")==0) newtio.c_cflag = B115200;
   else if (strcmp2(baudrate, "230400", "230k4")==0) newtio.c_cflag = B230400;
   else if (strcmp2(baudrate, "460800", "460k8")==0) newtio.c_cflag = B460800;
   else giveup("Baud rate not supported!");


   newtio.c_cflag |= CS8 | CLOCAL | CREAD;


 /* IGNPAR:   Ignore bytes with parity errors
    ICRNL:    map CR to NL (otherwise a CR on the other computer will
	       not terminate input).
    otherwise make device raw (no other input processing). */

   newtio.c_iflag = IGNPAR;


 /* Raw output: */

   newtio.c_oflag = 0;


 /* ICANON:   Enable canonical input
    disable all echo functionality, and don't send signals to calling program */

   newtio.c_lflag = 0;


 /* Initialise all control characters.
    default values can be found in /usr/include/termios.h, and are given
    in the comments, but we don't need them here: */

   newtio.c_cc[VINTR]	=0;	/* Ctrl-c	*/
   newtio.c_cc[VQUIT]	=0;	/* Ctrl-\	*/
   newtio.c_cc[VERASE]	=0;	/* del		*/
   newtio.c_cc[VKILL]	=0;	/* @ 		*/
   newtio.c_cc[VEOF]	=4;	/* Ctrl-d	*/
   newtio.c_cc[VTIME]	=0;	/* inter-character timer unused */
   newtio.c_cc[VMIN]	=0;	/* blocking read until 1 char arrives */
   newtio.c_cc[VSWTC]	=0;	/* '\0'		*/
   newtio.c_cc[VSTART]	=0;	/* Ctrl-q	*/
   newtio.c_cc[VSTOP]	=0;	/* Ctrl-s	*/
   newtio.c_cc[VSUSP]	=0;	/* Ctrl-z	*/
   newtio.c_cc[VEOL]	=0;	/* '\0'		*/
   newtio.c_cc[VREPRINT]	=0;	/* Ctrl-r	*/
   newtio.c_cc[VDISCARD]	=0;	/* Ctrl-u	*/
   newtio.c_cc[VWERASE]	=0;	/* Ctrl-w	*/
   newtio.c_cc[VLNEXT]	=0;	/* Ctrl-v	*/
   newtio.c_cc[VEOL2]	=0;	/* '\0'		*/


 /* Now clear the modem line and activate the settings for the port:  */

   tcflush(sfd, TCIFLUSH);
   if (tcsetattr(sfd, TCSANOW, &newtio)!=0) giveup("Serial setup error");
 }



int download(char *mode, char *source) {

  int ffd;            /* file descriptor for file  */ 
  int res;            /* result from function calls  */
  int i=1;            /* temp variables  */
  char s[100];        /* temp string  */
  char fbuf[BUFLEN];  /* buffer for file reading  */
  int ok = TRUE;      /* status of the process  */
  struct stat fileinfo;    /* file status structure   */
  long txb = 0;       /* number of bytes transmitted  */


  wprintw(progwin, "Download %s:\n", mode);
  wrefresh(progwin);


  /* open the file: */

  wprintw(progwin, " Source file: %s\n", source);
  wprintw(progwin, " Opening source file...");    
  wrefresh(progwin);

  ffd = open(source, O_RDONLY);

  if (ffd <0) {
    wattron(progwin, COLOR_PAIR(3) | A_BOLD);
    wprintw(progwin, " Cannot open file.  Aborting this process.\n");
    wrefresh(progwin);
    wattroff(progwin, COLOR_PAIR(3) | A_BOLD);
    ok = FALSE;
  }
  else {
    wprintw(progwin, "done.\n");
    wrefresh(progwin);
    fstat(ffd, &fileinfo);
  }


  if (ok) {

    /* make sure the file pointer is at the beginning:  */
    lseek (ffd, 0, SEEK_SET);


    /* send the download command to LART:  */

    strcpy(s, "download ");
    strcat(s, mode);
    strcat(s, "\n");    
    if (write(sfd, s, strlen(s)) < 0)  giveup("Serial Tx error");

    readuntil(" baud.", 0, FALSE);


    /* switch to download baud rate:  */

    if (changebaud == TRUE) {

      wprintw(progwin, " Switching to %s baud...", downbaud);
      wrefresh(progwin);

      if (quick == FALSE)  sleep(1);

      setserial(downbaud);
      wprintw(progwin, "done.\n");
      wrefresh(progwin);
    }


    /* copy the source file to the serial port:  */

    wprintw(progwin, " Downloading now: please wait...");
    wrefresh(progwin);
    

    while (i != NL) {
      i = read (ffd, fbuf, BUFLEN);
      if(i<0)  giveup("File read error");
      
      write(sfd, fbuf, i);
      txb += i;

      wprintw(progwin, "%3d%% \b\b\b\b\b", (100*txb)/fileinfo.st_size);
      wrefresh(progwin);

      res = read(sfd, sbuf, 5);  /* update terminal screen  */
      sbuf[res] = 0;
      wprintw(termwin, "%s", sbuf);
      wrefresh(termwin);
    }

    wprintw(progwin, ">");
    wrefresh(progwin);    

    
    /* now wait for LART to respond:  */
    
    res = readuntil("bytes", "done", FALSE);
    
    if (res==2) {
      wattron(progwin, COLOR_PAIR(3) | A_BOLD);
      wprintw(progwin, " FAILED\n Download failed - timeout expired.\n");
      wrefresh(progwin);
      wattroff(progwin, COLOR_PAIR(3) | A_BOLD);
      ok = FALSE;
    }
    
    else {
      wprintw(progwin, "done.\n");
      wrefresh(progwin);
    }


    /* close source file:  */
    
    if (close(ffd) < 0) {
      wattron(progwin, COLOR_PAIR(3) | A_BOLD);
      wprintw(progwin, " Cannot close file!\n");
      wattroff(progwin, COLOR_PAIR(3) | A_BOLD);
    }
    else {
      wprintw(progwin, " Source file closed.\n");
    }
    wrefresh(progwin);


    /* switch back to terminal baud rate:  */

    if (changebaud == TRUE) {
      wprintw (progwin, " Switching back to %s baud...", termbaud);
      wrefresh(progwin);
    
      setserial(termbaud);
    
      wprintw(progwin, "done.\n");
      wrefresh(progwin);
    }

    if (quick == FALSE)  sleep(1);


    /* send CR and wait for blob prompt again:  */

    write(sfd, "\n", 1);
    readuntil("blob>", 0, FALSE);
  }

  if (ok) return(TRUE);
  else return(FALSE);
}




int flashit(char *mode) {

  char s[100];    /* temp string  */


  wprintw(progwin, "Flash %s:\n", mode);
  wrefresh(progwin);
    

  /* send the flash command to LART:  */

  strcpy(s, "flash ");
  strcat(s, mode);
  strcat(s, "\n");
  if (write(sfd, s, strlen(s)) < 0)  giveup("Serial Tx error");


  /* wait for response:  */

  wprintw(progwin, " Flashing now: please wait...");
  wrefresh(progwin);
  if (quick == FALSE)  sleep(1);

  readuntil("blob>", 0, FALSE);
  if (quick == FALSE)  sleep(1);

  wprintw(progwin, "done.\n");
  wrefresh(progwin);

  return (TRUE);
}



int strcmp2(char *source, char *str1, char *str2) {
  /* this routine is an extension of the standard function strcmp().
     It compares string 'source' with both 'str1' and 'str2', and
     returns 0 if either match, 1 otherwise.
  */

  if (strcmp(source, str1) == 0)  return(0);
  else if (strcmp(source, str2) ==0)  return(0);
  else return(1);
  }






