/*
 * flashwrite.c: programming the flash memory on a NetWinder
 * Copyright 1998, 1999  Rebel.com
 *
 * History
 *      Oct 27, 1999 - woody - reinstated interactive file info.
 *	Fixed report for starting offset > 0
 *	Added checking the flash size, close all files on error.
 *
 *	Jun 24, 1999 - Ralph Siemsen <ralphs@netwinder.org>
 *	Added are-you-sure question and override (-yes) option.
 *	More verbose reporting of offset range errors.
 *
 *	May 16, 1999 - Andrew E. Mileski <andrewm@netwinder.org>
 * 	Cleanup and made to safely flash any amount at any offset.
 *
 *	?, 1998 - Woody Suwalski <woody@netwinder.org>
 *	Original writing.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>

#include "nwflash.h"

/* These must be powers of 2 */
#define FLASH_BANK	(64 * 1024)
#define MAX_FLASH	(4 * 1024 * 1024)
#define FLASH_MASK	(FLASH_BANK - 1)

int flashsize(int fd)
{
int size = 0;

	if (lseek(fd, 4*1024*1024, SEEK_SET) > 0)
		size = 4*1024*1024;
	else if (lseek(fd, 1024*1024, SEEK_SET) > 0)
		size = 1024*1024;
	return size;
}		

void fatal_error(void)
{
	fprintf(stderr, "Fatal error!  Try again!\n");
}

int flashwrite(char *datafile, int flash_offset, int base64_ok)
{
	int buf_offset, data_fd, flash_fd, flash_size, rc, written;
	char *buf;

	/* Writing to first 64k only if enabled */
	if (!base64_ok && flash_offset < FLASH_BANK) {
		fprintf(stderr, "Writes to the first 64k not permitted "
			"unless -base64 specified\n");
		return 1; 
	}

	/* Writing to negative offset is illegal */
	if (flash_offset < 0) {
		fprintf(stderr, "Invalid offset\n");
		return 1;
	}


	flash_fd = open("/dev/nwflash", O_RDWR);
	if (flash_fd <= 0) {
		perror("Error opening flash device");
		return 1;
	}

	/* Warn if writing beyond 4MB */
	flash_size = flashsize(flash_fd);
	if (flash_offset >= flash_size) {
		fprintf(stderr, 
			"Error: offset above %dMB > flash size %dMB!\n",
			(flash_offset / 1024) / 1024,
			(flash_size / 1024) / 1024);
		close(flash_fd);
		return 1;
	}
	 

	if (base64_ok)
		ioctl(flash_fd, CMD_WRITE_BASE64K_ENABLE);

	buf = (char *)malloc(FLASH_BANK);
	if (!buf) {
		perror("Can't allocate a buffer");
		close(flash_fd);
		return 1;
	}

	data_fd = open(datafile, O_RDONLY);
	if (data_fd <= 0) {
		perror("Error opening data file");
		close(flash_fd);
		return 1;
	}

	/* Start at the beginning of a bank */
	buf_offset = flash_offset & FLASH_MASK;
	flash_offset -= buf_offset;
	lseek(flash_fd, flash_offset, SEEK_SET);

	/* Make the first bank's data complete */
	if (buf_offset)
	{
		read(flash_fd, buf, buf_offset);
		lseek(flash_fd, flash_offset, SEEK_SET);
	}
	rc = read(data_fd, buf + buf_offset, FLASH_BANK - buf_offset);
	written = rc;

	/* Write complete banks of data */
	do {
		ioctl(flash_fd, CMD_WRITE_ENABLE);
		rc = write(flash_fd, buf, FLASH_BANK);
		ioctl(flash_fd, CMD_WRITE_DISABLE);
		if (rc != FLASH_BANK) {
			fatal_error();
			close(flash_fd);
			close(data_fd);
			return 1;
		}
		flash_offset += FLASH_BANK;
		rc = read(data_fd, buf, FLASH_BANK);
		written += rc;
	} while (rc == FLASH_BANK);

	flash_size = flash_offset + rc;

	/* Write the last incomplete bank (if any) */
	if (rc > 0) {
		lseek(flash_fd, flash_offset + rc, SEEK_SET);
		read(flash_fd, buf + rc, FLASH_BANK - rc);
		lseek(flash_fd, flash_offset, SEEK_SET);
		ioctl(flash_fd, CMD_WRITE_ENABLE);
		rc = write(flash_fd, buf, FLASH_BANK);
		ioctl(flash_fd, CMD_WRITE_DISABLE);
		if (rc != FLASH_BANK) {
			fatal_error();
			return 1;
		}
	}
	close(flash_fd);
	close(data_fd);

	fprintf(stderr, "Successfully wrote %d bytes to flash.\n", written);
	return 0;
}

int main (int argc, char **argv)
{
	int i, rc;
	char *our_name;

	/* Command line args */
	int base64_ok = 0;
	int show_prompt = 1;
	char *datafile = NULL;
	int offset = 0;
	int retry = 1;
	int data_fd;
	int data_size;

	/* Get our executable's name (should be "flashwrite") */
	our_name = (our_name = strrchr(argv[0], '/')) ? our_name + 1: argv[0];

	/* Parse the command line args */
	for (i = 1; i < argc; i++) {
		if (!strcmp(argv[i], "-base64")) {
			base64_ok = 1;
			continue;
		}
		if (!strcmp(argv[i], "-yes")) {
			show_prompt = 0;
			retry = 10;
			continue;
		}
		if (!datafile) {
			datafile = argv[i];
			continue;
		}
		if (!offset) {
			offset = strtol(argv[i], NULL, 0);
			continue;
		}
	}
	if(!datafile) {
		fprintf(stderr, "\n"
			"NetWinder Flash Memory update program,"
			" Rebel.com 1998,1999.\n"
			"Usage: %s datafile [offset]\n"
			"offset is in bytes, or 0xNNN for hex\n"
			"optional flags:\n"
			"  -base64   enables writing to first 64kB\n"
			"  -yes      does not prompt for confirmation\n"
			"\n",
	 		our_name
		);
	   	return 1;
	}

	data_fd = open(datafile, O_RDONLY);
	if (data_fd <= 0) {
		perror("Error opening data file");
		return 1;
	}

	data_size = lseek(data_fd,0,2);
	close(data_fd);

	if (!data_size) {
		perror("Zero length data file");
		return 1;
	}

	if (show_prompt) {
		int ch;
		fprintf(stderr, "\n%s: ready to write into flash memory\n"
			"  file   : \"%s\"\n"
			"  size   : %d bytes\n"
			"  offset : 0x%X.\n\n"
			"Enter Y to continue, or anything else to abort...\n",
			our_name,datafile,data_size,offset);
		ch = getchar();
		if (!((ch == 'y') || (ch == 'Y'))) {
			fprintf(stderr, "Not this time... aborted.\n");
			exit (0);
		}
	}

	fprintf(stderr, "\n"
		"Please wait...\n"
		"Yellow LED - erasing, Red LED - writing, Green LED - verifying"
		"...\n"
	);

	/* Write to the data to the flash */
	while (retry-- && (rc = flashwrite(datafile, offset, base64_ok))) {
		/* Repeat */
	}

	return rc;
}

