////////////////////////////////////////////////////////////////////////
//
//  pc-flash: Pico-2 flash memory writer (PC program)
//      version 2.0 for Linux/Mac/Windows (August 31, 2009)
//
//  (c) Hideki Kozima (xkozima@myu.ac.jp), subject to GPLv2
//
//  usage: pc-flash --dev /dev/ttyUSB0 file.sr          (Linux)
//         pc-flash --dev /dev/tty.KeySerial1 file.sr   (Mac)
//         pc-flash --dev COM3 file.sr                  (Windows)
//
//  Functions:
//      This program writes an SR file to the flash memory on Pico-2
//      (SH2).  The SR file should be an executable program of SH2, 
//      which will be baked into the flash memory (from 0x00000000 to 
//      0003ffff).
//
//  Direction:
//      1. Reset Pico-2 with BOOT mode (switch #6 and #7 are on).
//      2. With "pc-boot" program, transfer "pico-flash.sr" to Pico-2.
//             $  pc-boot --dev /dev/ttyUSB0 pico-flash.sr
//      3. Type "pc-flash <target.sr>", and see what happens.
//             $  pc-flash --dev /dev/ttyUSB0 your-program.sr 
//
////////////////////////////////////////////////////////////////////////

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

////////////////////////////////////////////////////////////////////////
//
//  flash

#include <string.h>

#if defined(linux)
//  Linux
#define  LINUX
#define  DEF_DEV  "/dev/ttyUSB0"        //  example
//
#elif defined(__APPLE__) && defined(__MACH__)
//  Mac OS X
#define  MAC_OS_X
#define  DEF_DEV  "/dev/cu.KeySerial1"  //  example
//
#elif defined(_WIN32)
//  Windows
#define  WINDOWS
#define  DEF_DEV  "COM3"                //  example
//
#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:  S1 <bytes> <2-byte-address> <data...> <checksum>
//             S2 <bytes> <3-byte-address>  <data...> <checksum>
//             S3 <bytes> <4-byte-address>  <data...> <checksum>
//      tail:  S7 <bytes> <4-byte-entry-address> <checksum>
//             S8 <bytes> <3-byte-entry-address> <checksum>
//             S9 <bytes> <2-byte-entry-address> <checksum>
//      <bytes> means # of bytes <address> to <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_readline1 (char *line, unsigned int *addr, unsigned char *data)
{
    char  s1, s2,  b1, b2,  a1, a2, a3, a4, c1, c2;
    int  len, i;

    sscanf(line, "%c%c%c%c%c%c%c%c", 
                 &s1, &s2, &b1, &b2,  
                 &a1, &a2, &a3, &a4 );
    len = sr_h2d(b1, b2) - 3;
    *addr = sr_h2d(a1, a2) * 0x100
          + sr_h2d(a3, a4);

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

        sscanf(line+8+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;
}

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

    sscanf(line, "%c%c%c%c%c%c%c%c%c%c", 
                 &s1, &s2, &b1, &b2,  
                 &a1, &a2, &a3, &a4, &a5, &a6 );
    len = sr_h2d(b1, b2) - 4;
    *addr = sr_h2d(a1, a2) * 0x10000 
          + sr_h2d(a3, a4) * 0x100 
          + sr_h2d(a5, a6);

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

        sscanf(line+10+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;
}

int  sr_readline3 (char *line, unsigned int *addr, 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;
    *addr = sr_h2d(a1, a2) * 0x1000000 
          + sr_h2d(a3, a4) * 0x10000 
          + sr_h2d(a5, a6) * 0x100 
          + sr_h2d(a7, a8);

    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  MEMSIZE  262144
#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  ack;
    unsigned char  memory[MEMSIZE];
    unsigned char  memtag[2048];                //  tag for 128-byte blocks

    //  reading options
    //      -d/---dev </dev/ttyS0>  <s-filename>
    if (argc == 1) {
        printf("pc-flash: download an S-file to the flash memory\n");
        printf("    fpc-lash [-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("pc-flash: error in option -d/--dev <dev> (main)");
            dev = argv[i + 1];
            i++;
        }
        else
            filename = argv[i];
    }
    if (! filename)
        error_exit("pc-flash: filename is missing (main)");

    //  clear the memory & memtag
    for (i = 0; i < MEMSIZE; i++)
        memory[i] = 0xff;
    for (i = 0; i < 2048; i++)
        memtag[i] = 0;

    //  read S-data
    printf("Reading s-file (%s) ... ", filename); fflush(stdout);
    file = fopen(filename, "r");
    if (file == NULL)
        error_exit("pc-flash: s-file not found (main)");
    while (fgets(line, LINELEN, file)) {
        unsigned int  len, addr, j;

        if (strlen(line) < 6)
            error_exit("pc-flash: format error in s-file (main)");
        if (line[0] != 'S')
            error_exit("pc-flash: line format error in s-file (main)");
        switch (line[1]) {
            case '1': 
                len = sr_readline1(line, &addr, memory+length);
                break;
            case '2': 
                len = sr_readline2(line, &addr, memory+length);
                break;
            case '3': 
                len = sr_readline3(line, &addr, memory+length);
                break;
            default:
                continue;
        }
        if (addr >= 0x00040000 || addr < 0)
            error_exit("pc-flash: address out of range (main)");
        for (j = 0; j < len; j++)
            memtag[(addr + j) / 128] = 1;
        length += len;
    }
    fclose(file);
    printf(" done (%d bytes)\n", length);

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

    //  write to flash memory
    //      host: <fla-addr1> <byte1> <byte2> ... <byte128>
    //      pico: 0x00
    //      host: <fla-addr2> <byte1> <byte2> ... <byte128>
    //      pico: 0x00
    //            ...
    //      host: <0xffffffff>
    //      pico: 0xff
    printf("Writing flash "); fflush(stdout);
    for (i = 0; i < 2048; i++) {
        if (memtag[i]) {
            unsigned int   addr;
            unsigned char  ack = 0x55;
            int  j, res;

            //  send address
            addr = i * 128;
            com_send_byte((addr >> 24) & 0xff);
            com_send_byte((addr >> 16) & 0xff);
            com_send_byte((addr >>  8) & 0xff);
            com_send_byte( addr        & 0xff);

            //  send data block
            for (j = 0; j < 128; j++)
                com_send_byte(*(memory+addr+j));

            //  receive ack
            res = com_rec_byte(&ack);
            if (res == 0)
                error_exit("pc-flash: acknowledge timeout (main)");
            if (ack != 0x00)
                error_exit("pc-flash: failed in writing data block (main)");

            printf("."); fflush(stdout);
        }
    }
    printf(" done\n");

    //  send end mark and receive ack
    com_send_byte(0xff); com_send_byte(0xff); 
    com_send_byte(0xff); com_send_byte(0xff);
    res = com_rec_byte(&ack);
    if (res == 0)
        error_exit("pc-flash: final acknowledge timeout (main)");
    if (ack != 0xff)
        error_exit("pc-flash: failed in finising flash memory (main)");
    printf("Program has been successfully written!\n"); 

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

    return  0;
}
