////////////////////////////////////////////////////////////////////////
//
//  boot: Pico-2 ram-boot program
//      version 2.0 for Linux/Mac/Windows (August 31, 2009)
//
//  (c) Hideki Kozima (xkozima@myu.ac.jp), subject to GPLv2
//
//  usage: boot --dev /dev/ttyUSB0 file.sr         (Linux)
//         boot --dev /dev/tty.KeySerial1 file.sr  (Mac)
//         boot --dev COM3 file.sr                 (Windows)
//
//  Functions:
//      This program transfers an SR file to RAM on Pico-2.  The SR file
//      should be an executable program of SH2, which will be fed into 
//      the RAM (from 0xffffd800) of SH2 (S7046F).  You need to reset 
//      Pico-2 with BOOT mode (switch #6 and #7 are on) before load.
//

////////////////////////////////////////////////////////////////////////
//
//  serial port interface
//      void  com_init (char *dev);
//      void  com_quit ();
//      void  com_send (unsigned char *data, int n)
//      int   com_rec_byte (unsigned char *data);

#ifdef _WIN32
//  Windows

#include <windows.h>
#include <stdio.h>

HANDLE  ComFH;                          //  file handle

void  com_error (char *func, char *mess)
{
    fprintf(stderr, "*** com_%s: %s\n", func, mess);
    fprintf(stderr, "Press ENTER to exit: "); fflush(stderr);
    getchar();
    exit(1);
}

void  com_init (char *dev)
{
    DCB  dcb;                           //  device settings
    COMMTIMEOUTS  t_out;                //  timeout settings
    int  ret;

    //  open serial port
    ComFH = CreateFile(dev, GENERIC_READ | GENERIC_WRITE, 0, 0,
                       OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    if (ComFH == INVALID_HANDLE_VALUE)
        com_error("init", "cannot CreateFile");

    //  set attribute (baud, etc.)
    FillMemory(&dcb, sizeof(dcb), 0);
    dcb.DCBlength = sizeof(dcb);
    ret = BuildCommDCB("38400,n,8,1", &dcb);
    if (ret == 0)
        com_error("init", "cannot BuildCommDCB");
    dcb.fRtsControl = RTS_CONTROL_DISABLE;
    ret = SetCommState(ComFH, &dcb);
    if (ret == 0)
        com_error("init", "cannot SetCommState");

    //  set timeout (100ms)
    t_out.ReadIntervalTimeout         = MAXDWORD;
    t_out.ReadTotalTimeoutMultiplier  = MAXDWORD;
    t_out.ReadTotalTimeoutConstant    = 500;
    t_out.WriteTotalTimeoutMultiplier = MAXDWORD;
    t_out.WriteTotalTimeoutConstant   = 500;
    ret = SetCommTimeouts(ComFH, &t_out);
    if (ret == 0)
        com_error("init", "cannot SetCommTimeouts");
}

void  com_quit ()
{
    int  ret;

    ret = CloseHandle(ComFH);
    if (ret == 0)
        com_error("quit", "cannot CloseHandle");
}

void  com_send (unsigned char *data, int n)
{
    int  ret;
    DWORD  n_done;

    ret = WriteFile(ComFH, data, n, &n_done, NULL);
    if (ret == 0)
        com_error("send", "cannot WriteFile");
    if ((int) n_done < n) {
        //  still some data to send
        Sleep(10);
        com_send(data + n_done, n - n_done);
    }
}

int  com_rec_byte (unsigned char *data)
{
    int  ret;
    DWORD  n_done;

    //  read data from the port
    ret = ReadFile(ComFH, data, 1, &n_done, NULL);
    if (ret == 0)
        com_error("rec_byte", "cannot ReadFile");

    //  return 1 on success
    //  return 0 on timeout
    return n_done;
}

//  enf of Windows
#else
//  Linux/Mac

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

static int  ComFD = -1;                 //  file descriptor

void  com_error (char *func, char *mess)
{
    fprintf(stderr, "*** com_%s: %s\n", func, mess);
    fprintf(stderr, "Press ENTER to exit: "); fflush(stderr);
    getchar();
    exit(1);
}

void  com_init (char *dev)
{
    static struct termios  termios_data;
    int  ret;

    //  open serial port
    ComFD = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if (ComFD < 0)
        com_error("init", "can't open the port");

    //  set attribute (settings and baud)
    ret = tcgetattr(ComFD, &termios_data);
    termios_data.c_iflag  = 0;
    termios_data.c_oflag  = 0;
    termios_data.c_lflag  = 0;
    termios_data.c_cflag  = CS8 | CREAD | CLOCAL;
    termios_data.c_cc[VMIN]  = 0;       //  "read" byte by byte
    termios_data.c_cc[VTIME] = 5;       //  timeout 500ms
    ret = cfsetspeed(&termios_data, B38400);
    if (ret < 0)
        com_error("init", "cannot set speed");
    ret = tcsetattr(ComFD, TCSANOW, &termios_data);
    if (ret < 0)
        com_error("init", "cannot set attribute");

    //  turn off "NONBOLCK"
    ret = fcntl(ComFD, F_SETFL, 0);
    if (ret < 0)
        com_error("init", "can't fcntl (to unset NONBLOCK)");
  
    //  flush input/output
    ret = tcflush(ComFD, TCIOFLUSH);
    if (ret < 0)
        com_error("init", "cannot flush");
}

void  com_quit ()
{
    int  ret;

    //  close the port
    ret = close(ComFD);
    if (ret < 0)
        com_error("quit", "cannot close the port");
}

void  com_send (unsigned char *data, int n)
{
    int  ret, ret_drain;

    //  write data to the port
    ret = write(ComFD, data, n);
    if (ret < 0)
        com_error("send", "cannot write data to the port");

    //  drain out the write-buffer
    ret_drain = tcdrain(ComFD);
    if (ret_drain < 0)
        com_error("send", "cannot drain the data");

    //  incomplete?
    if (ret < n) {
        //  still some data to send
        usleep(10000);
        com_send(data + ret, n - ret);
    }
}

int  com_rec_byte (unsigned char *data)
{
    int  ret;

    //  read data from the port
    ret = read(ComFD, data, 1);
    if (ret < 0)
        com_error("rec_byte", "cannot read byte from the port");

    //  return 1 on success
    //  return 0 on timeout
    return ret;
}

//  end of Linux/Mac
#endif

//
//  com_send_byte (for Linux/Mac/Windows)

void  com_send_byte (unsigned char data)
{
    com_send(&data, 1);
}

////////////////////////////////////////////////////////////////////////
//
//  boot

#include <string.h>

#if defined(linux)
//  Linux
#define  DEF_DEV  "/dev/ttyUSB0"        //  example
//
#elif defined(__APPLE__) && defined(__MACH__)
//  Mac OS X
#define  DEF_DEV  "/dev/cu.KeySerial1"  //  example
//
#elif defined(_WIN32)
//  Windows
#define  DEF_DEV  "COM3"                //  example
#define  usleep(a)  Sleep((a)/1000)
//
#endif

static void  error_exit (char *message)
{
    fprintf(stderr, "*** boot: %s\n", message);
    exit(1);
}

////////////////////////////////////////////////////////////////////////
//
//  Motorola S format interpreter (for word-address) 
//      head:  S0 <bytes> 0000 <filename...> <checksum>
//      body:  S3 <bytes> <word-address> <data...> <checksum>
//      tail:  S7 <bytes> <entry-address> <checksum>

static int  sr_h2d (char c1, char c2)
{
    int  d1, d2;

         if ('0' <= c1 && c1 <= '9') d1 = c1 - '0';
    else if ('A' <= c1 && c1 <= 'F') d1 = (c1 - 'A') + 10;
    else return  -1;

         if ('0' <= c2 && c2 <= '9') d2 = c2 - '0';
    else if ('A' <= c2 && c2 <= 'F') d2 = (c2 - 'A') + 10;
    else return  -1;

    return  d1*16 + d2;
}

int  sr_readline (char *line, unsigned char *data)
{
    char  s1, s2,  b1, b2,  a1, a2, a3, a4, a5, a6, a7, a8,  c1, c2;
    int  len, i;

    sscanf(line, "%c%c%c%c%c%c%c%c%c%c%c%c", 
                 &s1, &s2, &b1, &b2,  
                 &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8 );
    len = sr_h2d(b1, b2) - 5;

    for (i = 0; i < len; i++) {
        int  val;

        sscanf(line+12+i*2, "%c%c", &c1 ,&c2);
        val = sr_h2d(c1, c2);
        if (val < 0) 
            error_exit("SR: hex data required (SR_readline)");
        data[i] = val;
    }
    return  len;
}

////////////////////////////////////////////////////////////////////////
//
//  main

#define  NEGOMAX  32
#define  MAXSIZE  100000
#define  LINELEN  1000

int  main (int argc, char **argv)
{
    char   *dev = DEF_DEV, 
           *filename = NULL;
    FILE   *file;
    char   line[LINELEN];
    int    length = 0, i, res;
    unsigned char  data, length_hi, length_lo;
    unsigned char  memory[MAXSIZE];

    //  reading options
    //      -d/--dev </dev/ttyS0>  <s-filename>
    if (argc == 1) {
        printf("boot: download a ram-program to Pico-2\n");
        printf("    boot [-d/--dev <dev>] <s-filename>\n");
        printf("        default <dev> : %s\n", dev);
        return  0;
    }
    for (i = 1; i < argc; i++) {
        if (strcmp(argv[i], "-d") == 0 || strcmp(argv[i], "--dev") == 0) {
            if (i == argc - 1)
                error_exit("error in option -d/--dev <dev> (main)");
            dev = argv[i + 1];
            i++;
        }
        else
            filename = argv[i];
    }
    if (! filename)
        error_exit("filename is missing (main)");

    //  read S-data
    printf("Reading s-file (%s) ...", filename); fflush(stdout);
    file = fopen(filename, "r");
    if (file == NULL)
        error_exit("boot: s-file not found (main)");
    while (fgets(line, LINELEN, file)) {
        if (strlen(line) < 6)
            error_exit("boot: format error in s-file (main)");
        if (line[0] != 'S')
            error_exit("boot: line format error in s-file (main)");
        if (line[1] == '0') continue;   //  header (ignore)
        if (line[1] == '7') continue;   //  tailer (ignore)
        if (line[1] == '3')
            length += sr_readline(line, memory+length);
    }
    fclose(file);
    printf(" done. (%d bytes)\n", length);
    if (length > 9984)
        error_exit("boot: program size exceeds the ram size (main)");

    //  open serial port
    com_init(dev);
    printf("Opened serial port (%s at 19200 bps)\n", dev);

    //  negotiation
    //      host: 0x00
    //      pico: ----
    //      host: 0x00
    //      pico: ----
    //      host: 0x00
    //      pico: 0x00
    printf("Negotiating "); fflush(stdout);
    res = 0;
    for (i = 0; i < NEGOMAX; i++) {
        com_send_byte(0x00);
        res = com_rec_byte(&data);
        if (res != 0) break;
        printf("."); fflush(stdout);
    }
    if (res == 0)
        error_exit("boot: negotiation timeout (main)");
    if (data != 0x00)
        error_exit("boot: negotiation failed (main)");
    printf(" done. (0x%02x)\n", data);

    //  acknowledge
    //      host: 0x55
    //      pico: 0xaa
    com_send_byte(0x55);
    com_rec_byte(&data);
    if (data != 0xaa)
        error_exit("boot: acknowledgment failed (main)");
    printf("Acknowledged to start downloading. (0x%02x)\n", data);

    //  send and check byte length
    //      host: length_hi
    //      host: length_lo
    //      pico: length_hi (echo)
    //      pico: length_lo (echo)
    length_hi = length / 256;
    length_lo = length % 256;
    com_send_byte(length_hi);
    com_send_byte(length_lo);
    com_rec_byte(&data);
    if (data != length_hi) 
        error_exit("boot: byte length (high) mismatch (main)");
    com_rec_byte(&data);
    if (data != length_lo)
        error_exit("boot: byte length (low) mismatch (main)");

    //  feed the program to Pico-2's ram!
    //      host: byte
    //      pico: byte (echo)
    printf("Downloading "); fflush(stdout);
    for (i = 0; i < length; i++) {
        com_send_byte(memory[i]);
        com_rec_byte(&data);
        if (memory[i] != data)
            error_exit("boot: echoback data mismatch (main)");
        if (i % 100 == 0) {
            printf("."); fflush(stdout);
        }
    }
    printf(" done.\n");

    //  check if flash is erased
    //      pico: 0xaa (on success) or 0xff (on failure)
    i = 0;
    res = com_rec_byte(&data);
    while (res == 0) {
        if (++i > 10)
            error_exit("boot: flash erase check timeout (main)");
        res = com_rec_byte(&data);
    }
    if (data == 0xff)
        error_exit("boot: flash erase failed (main)");
    else if (data != 0xaa)
        error_exit("boot: flash erase probably failed (main)");
    printf("Flash memory is successfully erased.\n");
    printf("*** Now your Pico-2 starts the program. ***\n");

    //  epilogue
    com_quit();
    printf("Press ENTER to end this program: "); fflush(stdout);
    getchar();

    return  0;
}
