/*
 *  Copyright (c) 2001    MURAMATSU Atsushi
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following condition
 * is met:
 *
 *   Redistributions of any form (source code and/or binary) must
 *   reproduce the above copyright notice, this condition and the
 *   following disclaimer in source code and/or the documentation
 *   provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <setjmp.h>
#include <signal.h>

#define COPYRIGHT "SH-downloader for AP-SH3D-0A Version 0.1b\n\tCopyright(c) 2001, MURAMATSU Atsushi\n\n"

#define FALSE	0
#define TRUE	1

#define BUFFER_SIZE	120
#define FIRST_WAIT	20
#define LINE_WAIT	5

#ifndef SERIAL_DEV
#define SERIAL_DEV "/dev/tty00"
#endif
int serial_FD;

int
comm_init(char *tty_file, int serial_speed)
{
    struct termios term;
    speed_t speed;

    if ((serial_FD = open(tty_file, O_RDWR | O_NONBLOCK)) < 0)
	return FALSE;
    if (tcgetattr(serial_FD, &term) < 0)
	goto error_end;
    term.c_lflag &= ~(ECHO | ICANON | ISIG);
#ifdef	IEXTEN
    term.c_lflag &= ~IEXTEN;
#endif
    term.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    term.c_cflag &= ~(CSIZE | PARENB | CSTOPB);
    term.c_cflag |= CS8 | CLOCAL;
    term.c_oflag &= ~(OPOST);
    term.c_cc[VMIN] = 1;
    term.c_cc[VTIME] = 0;
    switch (serial_speed) {
	case 300:	speed = B300;	break;
	case 1200:	speed = B1200;	break;
	case 2400:	speed = B2400;	break;
	case 4800:	speed = B4800;	break;
	case 9600:	speed = B9600;	break;
	case 19200:	speed = B19200;	break;
	case 38400:	speed = B38400;	break;
	default:	speed = B9600;
    }
    cfsetispeed(&term, speed);
    cfsetospeed(&term, speed);
    if (tcsetattr(serial_FD, TCSANOW, &term) < 0)
	goto error_end;
    return TRUE;

error_end:
    close(serial_FD);
    return FALSE;
}

void
send_data(char *str)
{
    int size = strlen(str);
    int write_size;
    while (size > 0) {
	write_size = write(serial_FD, str, size);
	size -= write_size;
	str += write_size;
    }
}

jmp_buf jumpbuf;
void
alarm_handler()
{
    longjmp(jumpbuf, 1);
}

int
get_return(char *buf, int buf_size, int wait)
{
    int size = 0;
    if (setjmp(jumpbuf)) {
	return FALSE;
    }
    if (wait != 0) {
	signal(SIGALRM, alarm_handler);
	alarm(wait);
    }
    while (1) {
	while (read(serial_FD, buf, 1) != 1);
	if (buf[0] == '\n' || ++size >= buf_size-1)
	    break;
	buf++;
    }
    alarm(0);
    buf[1] = '\0';
    return TRUE;
}

int
send_command(char *cmd)
{
    char buffer[BUFFER_SIZE];
    send_data(cmd);
    if (!get_return(buffer, sizeof(buffer), LINE_WAIT))
	return FALSE;
    if (strcmp(cmd, buffer) != 0)
	return FALSE;
    return TRUE;
}

void
usage()
{
    fprintf(stderr, "usage : sh-downloder [-l serial] [-s speed] file\n\n");
    exit(1);
}

int
main(int argc, char *argv[])
{
    FILE *file;
    char buffer[BUFFER_SIZE];
    char *serial = SERIAL_DEV;
    int serial_speed = 9600;
    long line = 0;

    fprintf(stderr, COPYRIGHT);

    argv++;	/* drop command name */
    while (argc > 1) {
	/* check switch */
	if (argv[0][0] == '-') {
	    switch (argv[0][1]) {
		case 'l':
		    serial = argv[1];
		    argv += 2; argc -= 2;
		    break;

		case 's':
		    serial_speed = atoi(argv[1]);
		    argv += 2; argc -= 2;
		    break;
		    
		default:
		    usage();
	    }
	}
	else
	    break;
    }
    if (argc <= 1)
	usage();
    
    if ((file = fopen(argv[0], "r")) == NULL) {
	fprintf(stderr, "Cannot open \"%s\".\n", argv[0]);
	return 1;
    }
    if (!comm_init(serial, serial_speed)) {
	fprintf(stderr, "Cannot open serial device \"%s\".\n", serial);
	return 1;
    }
    
    fprintf(stderr, "Waiting for target reply... ");
    get_return(buffer, sizeof(buffer), LINE_WAIT); /* skip garbage data */
    send_data("\n");
    if (! get_return(buffer, sizeof(buffer), FIRST_WAIT)) {
	fprintf(stderr, "!!Timeout!!\n\n");
	return 1;
    }
    fprintf(stderr, "OK\n");
    
    fprintf(stderr, "Now send \"%s\" to target ", argv[0]);
    if (! send_command("DL\n")) {
	fprintf(stderr, "\n!!Command Error!!\n\n");
	return 1;
    }
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
	send_data(buffer);
	if (! get_return(buffer, sizeof(buffer), LINE_WAIT)) {
	    fprintf(stderr, "\n!! Serial timeout !!\n\n");
	    return 1;
	}
	if (strcmp(buffer, "x\n") != 0) {
	    fprintf(stderr, "\n!! S-Record file transmit error !!\n\n");
	    return 1;
	}
	if ((line++ % 10) == 0)
	    fprintf(stderr, ".");
    }
    send_data("END\n");
    if (! get_return(buffer, sizeof(buffer), LINE_WAIT)) {
	fprintf(stderr, "\n!! Serial timeout !!\n\n");
	return 1;
    }
    if (strcmp(buffer, "e\n") != 0) {
	fprintf(stderr, "\n!! S-Record file transmit error !!\n\n");
	return 1;
    }
    fprintf(stderr, " OK\n");
    
    fprintf(stderr,
	    "File sending was succeeded, Now writing it to FlashROM...");
    if (!send_command("WR\n")) {
	fprintf(stderr, "\n!!Command Error!!\n\n");
	return 1;
    }
    send_data("\n"); /* This is dummy ... */
    get_return(buffer, sizeof(buffer), 0);
    if (strcmp(buffer, "WRE\n") != 0) {
	fprintf(stderr, "\n!! FlashROM Writing failed !!\n\n");
	return 1;
    }
    fprintf(stderr, " OK\n");
    return 0;
}
