/* upload.c - send data to the sh1wh board by Matt Debergalis */

/***
    implements sh1wh upload protocol, described below:

    the binary format is as follows, byte by byte:
    
    ...0xFF SZ3 SZ2 SZ1 SZ0 0x00 0xFF 0x00 0xFF [D0 ... DN] 0x00 0xDE
       0xAD 0x00
    
    explanations:
    
    ...0xFF is the first byte we care about. all other bytes until
       0xFF is sent are ignored (this helps insure ASCII garbage gets
       flushed from all FIFOs before send begins).
    
    SZ3-SZ0 is the size of the binary file data sequence, given MSB
       first.  The SZ bytes makes up a 32-bit number = 4 GBytes, which
       is as large as the entire SH-1 address space. Famous last
       words: 4 GBytes should be plenty.
    
    0xFF 0x00 0xFF 0x00 -- preamble sequence.
    
    D0-DN -- data, where N is the number represented in the SZ
       field. One byte of data uploaded gives an SZ = 0 (D0 - D0),
       1024 bytes is SZ = 1023.
    
    0x00 0xDE 0xAD 0x00 -- postamble sequence. This lets us know if we
       skipped any bytes, or if we overran or miscounted somehow. It's
       not foolproof, but its useful for reference. An error message
       is kicked out to the serial port if any errors are encountered
       in the download process.
***/

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

static int baud = 38400;
static char *serial_device = "/dev/ttyS0";
static int serial_fd;

extern char *optarg;
extern int optind;

void setup_serial(void);
void usage(char *);

int
main(int argc, char *argv[])
{
        struct stat sb;
        unsigned char buf[512];
        int f;
        int len;
        int size;
        char *file;
        char *progname;
        char c;

        /* parse command line */
        progname = argv[0];
        while ((c = getopt(argc, argv, "b:p:")) != -1) {
                switch (c) {
                case 'b':
                        baud = atoi(optarg);
                        printf("baud: %d\n", baud);
                        break;
                case 'p':
                        serial_device = strdup(optarg);
                        break;
                default:
                        usage(progname);
                }
        }
        argc -= optind;
        argv += optind;

        if (argc == 1)
                file = strdup(argv[0]);
        else
                usage(progname);

        /* configure serial connection */
        setup_serial();

        /* load in file */
        f = open(file, O_RDONLY);
        if (f == -1) {
                fprintf(stderr, "missing input file %s\n", file);
                exit(1);
        }

        fstat(f, &sb);
        size = sb.st_size ;

        /* send preamble */
        buf[0] = 0xff;

        buf[1] = size >> 24;
        buf[2] = (size >> 16) & 0xff;
        buf[3] = (size >> 8) & 0xff;
        buf[4] = size & 0xff;

        buf[5] = 0xff;
        buf[6] = 0x00;
        buf[7] = 0xff;
        buf[8] = 0x00;

        write(serial_fd, buf, 9);

        /* send file */
        while ((len = read(f, buf, 512)))
                write(serial_fd, buf, len);

        /* send postamble */
        buf[0] = 0x00;
        buf[1] = 0xDE;
        buf[2] = 0xAD;
        buf[3] = 0x00;

        write(serial_fd, buf, 4);

        close(f);
        close(serial_fd);

        return 0;
}

void
setup_serial(void)
{
        struct termios term;

        /* open the port */
        serial_fd=open(serial_device, O_WRONLY);

	if (serial_fd == -1) {
		fprintf(stderr, "unable to open %s\n", serial_device);
		exit(1);
	}

        /* if writing to a file, don't frob terminal settings */
        if (isatty(serial_fd)) {
                /* get, modify, and save terminal characteristics */
                tcgetattr(serial_fd, &term);
                term.c_cflag=CS8; /* 8N1 */
                term.c_cflag |= (CREAD | /* enable receiver */
                                 HUPCL | /* lower modem lines on last close */
                                 CLOCAL); /* ignore modem status lines */
                term.c_oflag = 0; /* turn off all output processing */
                term.c_iflag = IGNBRK | IGNPAR; /* ignore break, parity errors
                                                   also no flow control since XON,
                                                   XOFF unset */
                term.c_lflag = 0; /* turn off canonical mode, sig generation, echo */
                
                /* set baud both ways */
                cfsetispeed(&term, baud);
                cfsetospeed(&term, baud);
                
                /* set attribs */
                tcsetattr(serial_fd, TCSANOW, &term);
        }
}

void
usage(char *progname)
{
        printf("usage: %s [-p device] [-b baudrate] infile\n", progname);
        exit(1);
}

