/*
 * This is the GPS-type-independent part of the gpsflash program.
 *
 * Copyright (c) 2005 Chris Kuethe <chris.kuethe@gmail.com>
 */
#include <stdarg.h>
#include "gpsd.h"
#include "gpsflash.h"

/* block size when writing to the serial port. related to FIFO size */
#define WRBLK 128

static char *progname;
static int verbosity = 0;

void gpsd_report(int errlevel, const char *fmt, ... )
/* assemble command in printf(3) style, use stderr or syslog */
{
    if (errlevel <= verbosity) {
	char buf[BUFSIZ];
	va_list ap;

	strcpy(buf, progname);
	strcat(buf, ": ");
	va_start(ap, fmt) ;
	(void)vsnprintf(buf + strlen(buf), sizeof(buf)-strlen(buf), fmt, ap);
	va_end(ap);
	(void)fputs(buf, stdout);
    }
}

static void
usage(void){
    fprintf(stderr, "Usage: %s [-v d] [-n] [-l <loader_file>] -f <firmware_file> {<tty>}\n", progname);
}

int
serialSpeed(int pfd, struct termios *term, int speed){
	int rv;
	int r = 0;

	switch(speed){
#ifdef B115200
	case 115200:
		speed = B115200;
		break;
#endif
#ifdef B57600
	case 57600:
		speed = B57600;
		break;
#endif
	case 38400:
		speed = B38400;
		break;
#ifdef B28800
	case 28800:
		speed = B28800;
		break;
#endif
	case 19200:
		speed = B19200;
		break;
#ifdef B14400
	case 14400:
		speed = B14400;
		break;
#endif
	case 9600:
		speed = B9600;
		break;
	case 4800:
		speed = B9600;
		break;
	default:
		errno = EINVAL;
		return -1;
	}

	/* set UART speed */
	(int)tcgetattr(pfd, term);
	/*@ ignore @*/
	cfsetispeed(term, speed);
	cfsetospeed(term, speed);
	/*@ end @*/
	while (((rv = tcsetattr(pfd, TCSAFLUSH, term)) == -1) && \
	    (errno == EINTR) && (r < 3)) {
		/* retry up to 3 times on EINTR */
		(void)usleep(1000);
		r++;
	}

	if(rv == -1)
		return -1;
	else
		return 0;
}


int
serialConfig(int pfd, struct termios *term, int speed){
	int rv;
	int r = 0;

	/* get the current terminal settings */
	(void)tcgetattr(pfd, term);
	/* set the port into "raw" mode. */
	/*@i@*/cfmakeraw(term);
	term->c_lflag &=~ (ICANON);
	/* Enable serial I/O, ignore modem lines */
	term->c_cflag |= (CLOCAL | CREAD);
	/* No output postprocessing */
	term->c_oflag &=~ (OPOST);
	/* 8 data bits */
	term->c_cflag |= CS8;
	term->c_iflag &=~ (ISTRIP);
	/* No parity */
	term->c_iflag &=~ (INPCK);
	term->c_cflag &=~ (PARENB | PARODD);
	/* 1 Stop bit */
	term->c_cflag &=~ (CSIZE | CSTOPB);
	/* No flow control */
	term->c_iflag &=~ (IXON | IXOFF);
#if defined(CCTS_OFLOW) && defined(CRTS_IFLOW) && defined(MDMBUF)
	term->c_oflag &=~ (CCTS_OFLOW | CRTS_IFLOW | MDMBUF);
#endif
#if defined(CRTSCTS)
	term->c_oflag &=~ (CRTSCTS);
#endif

	/* we'd like to read back at least 2 characters in .2sec */
	/*@i@*/term->c_cc[VMIN] = 2;
	/*@i@*/term->c_cc[VTIME] = 2;

	/* apply all the funky control settings */
	while (((rv = tcsetattr(pfd, TCSAFLUSH, term)) == -1) && \
	    (errno == EINTR) && (r < 3)) {
		/* retry up to 3 times on EINTR */
		(void)usleep(1000);
		r++;
	}

	if(rv == -1)
		return -1;
	
	/* and if that all worked, try change the UART speed */
	return serialSpeed(pfd, term, speed);
}

int
binary_send(int pfd, char *data, size_t ls){
	unsigned char *msg;
	size_t nbr, nbs, nbx;
	ssize_t r;
	static int count;
	double start = timestamp();

	/*@ -compdef @*/
	if((msg = malloc(ls+10)) == NULL){
		return -1; /* oops. bail out */
	}

	fprintf(stderr, "gpsflash: transferring binary... \010");
	count = 0;

	nbr = ls+10; nbs = WRBLK ; nbx = 0;
	while(nbr){
		if(nbr > WRBLK )
			nbs = WRBLK ;
		else
			nbs = nbr;

r0:		if((r = write(pfd, msg+nbx, nbs)) == -1){
			if (errno == EAGAIN){ /* retry */
				(void)tcdrain(pfd); /* wait a moment */
				errno = 0; /* clear errno */
				nbr -= r; /* number bytes remaining */
				nbx += r; /* number bytes sent */
				goto r0;
			} else {
			        (void)free(msg);
				return -1; /* oops. bail out */
			}
		}
		nbr -= r;
		nbx += r;

		(void)fputc("-/|\\"[count % 4], stderr);
		(void)fputc('\010', stderr);
		(void)fflush(stdout);
	}
	/*@ +compdef @*/

	(void)fprintf(stderr, "...done (%2.2f sec).\n", timestamp()-start);

	(void)free(msg);
	return 0;
}


int
srecord_send(int pfd, char *data, size_t len){
	int r, i;
	size_t tl;
	char sendbuf[85], recvbuf[8];
	static int count;
	double start = timestamp();

	/* srecord loading is interactive. send line, get reply */
	/* when sending S-records, check for SA/S5 or SE */

	fprintf(stderr, "gpsflash: transferring S-records... \010");
	count = 0;

	memset(recvbuf, 0, 8);
	i = 0;
	while(strlen(data)){
		/* grab a line of firmware, ignore line endings */
		if ((r = (int)strlen(data))){
			memset(sendbuf,0,85);
			if((r = sscanf(data, "%80s", sendbuf)) == EOF)
				return 0;

			tl = strlen(sendbuf);
			if ((tl < 1) || (tl > 80))
				return -1;

			data += tl;
			len -= tl;

			while((data[0] != 'S') && (data[0] != '\0'))
				data++;

			sendbuf[tl] = '\r';
			sendbuf[tl+1] = '\n';
			tl += 2;

			if ((++i % 1000) == 0)
				printf ("%6d\n", i);

			(void)tcflush(pfd, TCIFLUSH);
			if((r = (int)write(pfd, sendbuf, tl+2)) != (int)tl+2)
				return -1; /* oops. bail out */

			(void)tcdrain(pfd);
			if((r = (int)read(pfd, recvbuf, 7)) == -1)
				return -1; /* oops. bail out */

			if (!((recvbuf[0] == 'S') && ((recvbuf[1] == 'A') || (recvbuf[1] == '5'))))
				return -1; /* oops. bail out */
		}

		(void)fputc("-/|\\"[count % 4], stderr);
		(void)fputc('\010', stderr);
		(void)fflush(stdout);
	}

	(void)fprintf(stderr, "...done (%2.2f sec).\n", timestamp()-start);
	return 0;
}

bool 
expect(int pfd, const char *str, size_t len, time_t timeout)
/* keep reading till we see a specified expect string or time out */
{
    size_t got = 0;
    char ch;
    double start = timestamp();

    gpsd_report(1, "expect(%s, %d)\n", 
		gpsd_hexdump((char *)str, len),
		timeout);

    for (;;) {
	if (read(pfd, &ch, 1) != 1)
	    return false;		/* I/O failed */
	gpsd_report(5, "I see %d: %02x\n", got, (unsigned)(ch & 0xff));
	if (timestamp() - start > (double)timeout)
	    return false;		/* we're timed out */
	else if (got == len)
	    return true;		/* we're done */
	else if (ch == str[got])
	    got++;			/* match continues */
	else
	    got = 0;			/* match fails, retry */
    }
}


#if defined(SIRFII_ENABLE) && defined(BINARY_ENABLE)
/* add new types by adding pointers to their driver blocks to this list */
/*@ -nullassign @*/
static struct flashloader_t *types[] = {&sirf_type, NULL};
/*@ +nullassign @*/
#else
/* add new types by adding pointers to their driver blocks to this list */
/*@ -nullassign @*/
static struct flashloader_t *types[] = {NULL, NULL};
/*@ +nullassign @*/
#endif

int
main(int argc, char **argv){

	int ch;
	int lfd, ffd, pfd;
	size_t ls,  fs;
	bool fflag = false, lflag = false, nflag = false;
	struct stat sb;
	struct flashloader_t *gpstype, **gp;
	char *fname = NULL;
	char *lname = NULL;
	char *port = NULL;
	char *warning;
	struct termios term;
	sigset_t sigset;
	char *firmware = NULL;
	char *loader = NULL;
	char *version = NULL;

	progname = argv[0];

	while ((ch = getopt(argc, argv, "f:l:nVv:")) != -1)
		switch (ch) {
		case 'f':
			fname = optarg;
			fflag = true;
			break;
		case 'l':
			lname = optarg;
			lflag = true;
			break;
		case 'n':
			nflag = true;
			break;
		case 'v':
			verbosity = atoi(optarg);
			break;
		case 'V':
			(void)fprintf(stderr, "SVN ID: $$ \n");
			exit(0);
		default:
			usage();
			exit(0);
			/* NOTREACHED */
		}

	argc -= optind;
	argv += optind;

	/* there is exactly one required argument, a tty device */
	if (argc == 1) 
	    port = argv[0];
	else {
	    usage();
	    exit(0);
	}

	if (!nflag && 
	    (((warning = getenv("I_READ_THE_WARNING")) == NULL) ||
	     (strcmp(warning, "why oh why didn't i take the blue pill") == 0 ))){
	    printf("\nThis program rewrites your receiver's flash ROM.\n");
	    printf("If done improperly this will permanently ruin your\n");
	    printf("receiver. We insist you read the gpsflash manpage\n");
	    printf("before you break something.\n\n");
	    nflag = true;
	}

	if (!nflag) {
	    /* make sure we have meaningful flags */
	    if (!fflag || fname == NULL) {
		usage();
		return 1;
	    }
	}

	/* Open the serial port, blocking is OK */
	if((pfd = open(port, O_RDWR | O_NOCTTY , 0600)) == -1) {
		gpsd_report(0, "open(%s)\n", port);
		return 1;
	}

	/* try to get an identification string out of the firmware */
	gpstype = NULL;
	for (gp = types; *gp; gp++) {
	    gpstype = *gp;
	    gpsd_report(0, "probing for %s\n", gpstype->name);
	    if (gpstype->probe(pfd, &version) == 0)
		break;
	}
	if (gpstype == NULL || version == NULL) {
	    gpsd_report(0, "not a known GPS type\n");
	    return 1;
	}

	/* OK, we have a known type */
	gpsd_report(0, "GPS is %s, version '%s'.\n", gpstype->name, version);
	if (lname == NULL)
	    lname = (char *)gpstype->flashloader;

	if (nflag) {
	    gpsd_report(1, "probe finished.\n");
	    return 0;
	}

	/* there may be a type-specific setup method */
	memset(&term, 0, sizeof(term));
	if(gpstype->port_setup(pfd, &term) == -1) {
		gpsd_report(0, "port_setup()\n");
		return 1;
	}

	gpsd_report(1, "port set up...\n");

	/* Open the loader file */
	if((lfd = open(lname, O_RDONLY, 0444)) == -1) {
	    gpsd_report(0, "open(%s)\n", lname);
	    return 1;
	}

	/* fstat() its file descriptor. Need the size, and avoid races */
	if(fstat(lfd, &sb) == -1) {
	    gpsd_report(0, "fstat(%s)\n", lname);
	    return 1;
	}

	/* minimal sanity check on loader size. also prevents bad malloc() */
	ls = (size_t)sb.st_size;
	if ((ls < gpstype->min_loader_size)||(ls > gpstype->max_loader_size)){
	    gpsd_report(0, "preposterous loader size: %d\n", ls);
	    return 1;
	}

	gpsd_report(1, "passed sanity checks...\n");

	/* malloc a loader buffer */
	if ((loader = malloc(ls)) == NULL) {
	    gpsd_report(0, "malloc(%d)\n", ls);
	    return 1;
	}

	if (read(lfd, loader, ls) != (ssize_t)ls) {
	    (void)free(loader);
	    gpsd_report(0, "read(%d)\n", ls);
	    return 1;
	}

	/* don't care if close fails - kernel will force close on exit() */
	(void)close(lfd);

	gpsd_report(1, "loader read in...\n");

	/* Open the firmware image file */
	/*@ -nullpass @*/
	if((ffd = open(fname, O_RDONLY, 0444)) == -1) {
	    (void)free(loader);
	    gpsd_report(0, "open(%s)]n", fname);
	    return 1;
	}
	if(fstat(ffd, &sb) == -1) {
	    (void)free(loader);
	    gpsd_report(0, "fstat(%s)\n", fname);
	    return 1;
	}

	/* minimal sanity check on firmware size. also prevents bad malloc() */
	fs = (size_t)sb.st_size;
	if ((fs < gpstype->min_firmware_size) || (fs > gpstype->max_firmware_size)){
	    (void)free(loader);
	    gpsd_report(1, "preposterous firmware size: %d\n", fs);
	    return 1;
	}

	/* malloc an image buffer */
	if ((firmware = malloc(fs+1)) == NULL) {
	    (void)free(loader);
	    gpsd_report(0, "malloc(%u)\n", (unsigned)fs);
	    return 1;
	}

	/* get the firmware */
	if (read(ffd, firmware, fs) != (ssize_t)fs) {
	    (void)free(loader);
	    (void)free(firmware);
	    gpsd_report(0, "read(%u)\n", (unsigned)fs);
	    return 1;
	}

	firmware[fs] = '\0';

	/* don't care if close fails - kernel will force close on exit() */
	(void)close(ffd);

	gpsd_report(1, "firmware read in...\n");

	/* did we just read some S-records? */
	if (!((firmware[0] == 'S') && ((firmware[1] >= '0') && (firmware[1] <= '9')))){ /* srec? */
	    (void)free(loader);
	    (void)free(firmware);
	    gpsd_report(0, "%s: not an S-record file\n", fname);
	    return 1;
	}
	/*@ +nullpass @*/

	if(gpstype->version_check(pfd, version, loader, ls, firmware, fs)==-1){
		(void)free(loader);
		(void)free(firmware);
		gpsd_report(0, "version_check()\n");
		return 1;
	}

	gpsd_report(1, "version checked...\n");

	gpsd_report(1, "blocking signals...\n");

	/* once we get here, we are uninterruptable. handle signals */
	(void)sigemptyset(&sigset);
	(void)sigaddset(&sigset, SIGINT);
	(void)sigaddset(&sigset, SIGHUP);
	(void)sigaddset(&sigset, SIGQUIT);
	(void)sigaddset(&sigset, SIGTSTP);
	(void)sigaddset(&sigset, SIGSTOP);
	(void)sigaddset(&sigset, SIGKILL);

	if(sigprocmask(SIG_BLOCK, &sigset, NULL) == -1) {
		(void)free(loader);
		(void)free(firmware);
		gpsd_report(0,"sigprocmask\n");
		return 1;
	}

	/* send the command to begin the update */
	if(gpstype->stage1_command!=NULL && (gpstype->stage1_command(pfd) == -1)) {
		(void)free(loader);
		(void)free(firmware);
		gpsd_report(0, "Stage 1 update command\n");
		return 1;
	}

	gpsd_report(1, "sending loader...\n");

	/* send the bootstrap/flash programmer */
	if(gpstype->loader_send(pfd, &term, loader, ls) == -1) {
		(void)free(loader);
		(void)free(firmware);
		gpsd_report(0, "Loader send\n");
		return 1;
	}
	(void)free(loader);

	gpsd_report(1, "initializing firmware load...\n");

	/* send any command needed to demarcate the two loads */
	if(gpstype->stage2_command!=NULL && (gpstype->stage2_command(pfd) == -1)) {
	    (void)free(firmware);
	    gpsd_report(0, "Stage 2 update command\n");
	    return 1;
	}

	gpsd_report(1, "performing firmware load...\n");

	/* and now, poke the actual firmware over */
	if(gpstype->firmware_send(pfd, firmware, fs) == -1) {
	    (void)free(firmware);
	    gpsd_report(0, "Firmware send\n");
	    return 1;
	}
	(void)free(firmware);

	gpsd_report(1, "finishing firmware load...\n");

	/* send any command needed to finish the firmware load */
	if(gpstype->stage3_command!=NULL && (gpstype->stage3_command(pfd) == -1)) {
	    gpsd_report(0, "Stage 3 update command\n");
	    return 1;
	}

	gpsd_report(1, "unblocking signals...\n");

	if(sigprocmask(SIG_UNBLOCK, &sigset, NULL) == -1) {
		gpsd_report(0,"sigprocmask\n");
		return 1;
	}

	/* type-defined wrapup, take our tty to GPS's post-flash settings */
	if(gpstype->port_wrapup(pfd, &term) == -1) {
		gpsd_report(0, "port_wrapup()\n");
		return 1;
	}

	gpsd_report(1, "finished.\n");

	/* return() from main(), to take advantage of SSP compilers */
	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1