
/* 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 -lncurses -o lartload lartload.c

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


#define USAGE "\n\
 Usage:             \n\
\n\
 lartload [-b[f] file] [-k[f] file] [-r[f] file] [-B] [-Sn] [-d device]
\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 sevice for comms             \n"


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


/* A note about colurs:
   
   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>

/* 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(long);
int download(char *, char *);
int flashit(char *);
void giveup(void);


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[]="/dev/ttyS0";      /* name of serial device  */
struct termios oldtio;               /* old serial port settings  */

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


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


int main(int argc, char **argv) {
  int c, res, i, len, 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) strcpy(serialport, argv[i+1]);
  }

  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 (np == 0) {
    printf(USAGE);
    exit(-1);
  }


  /* 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));

  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) { strcpy(emess,serialport); giveup(); }
  
  sleep(1);

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

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


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

  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: */

  sleep(1);
  wprintw(progwin, "Stopping autoboot...");
  wrefresh(progwin);

  res = write(sfd, "X", 1);
  if (res < 0) {
    strcpy(emess, "Serial Tx");
    giveup();
  };


  /* wait for blob prompt: */

  readuntil("blob>", 0, FALSE);
  wprintw(progwin, "stopped.\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);

      res = write(sfd, "boot\n", 5);
      if (res < 0) {
	strcpy(emess, "Serial Tx");
	giveup();
      }
      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);
  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); 


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

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

  tcflush(sfd, TCIFLUSH);
  if (tcsetattr(sfd, TCSANOW, &oldtio)!=0) {
    strcpy(emess, "setup serial");
    giveup();
  }

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


  /* Finally say goodbye to ncurses:  */

  sleep(1);

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

  endwin();

  return(0);
}





void giveup(void) {

  perror(emess);

  sleep(10);

  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)
       if (wgetch(progwin) != ERR) return(0);
   }
 }



 void setserial(long 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 (baudrate == 9600)  newtio.c_cflag = B9600;
   else if (baudrate == 115200)  newtio.c_cflag = B115200;

   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) {
     strcpy(emess, "setup serial");
     giveup();
   }
 }



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

  int ffd;            /* file descriptor for file  */ 
  int res;            /* result from function calls  */
  int i=1,  x;        /* 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, "\nDownload %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");    
    res = write(sfd, s, strlen(s));
    if (res < 0) {
      perror("Serial Tx");
      giveup();
    }

    readuntil("will switch back to 9600 baud.", 0, FALSE);


    /* switch to 115200 baud:  */

    wprintw(progwin, " Switching to 115200 baud...");
    wrefresh(progwin);

    sleep(1);

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


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

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

    while (i != NL) {
      i = read (ffd, fbuf, BUFLEN);
      if(i<0) {
	strcpy(emess, "File read error");
	giveup();
      }
      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 9600 baud  */

    wprintw (progwin, " Switching back to 9600 baud...");
    wrefresh(progwin);
    
    setserial(9600);
    
    wprintw(progwin, "done.\n");
    wrefresh(progwin);

    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  */
  int res;        /* result from functions  */

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

  /* send the flash command to LART:  */

  strcpy(s, "flash ");
  strcat(s, mode);
  strcat(s, "\n");
  res = write(sfd, s, strlen(s));
  if (res < 0) {
    strcpy(emess, "Serial TX");
    giveup();
  }


  /* wait for response:  */

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

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

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

  return (TRUE);
}

