/***************************************************************************
    copyright            : (C) by 2003-2004 Stefano Barbato
    email                : stefano@codesink.org
    
    Copyright (C) 2011 by Kris Rusocki <kszysiu@gmail.com>
    - usage/strings cleanup
    - misc cleanup: use "static" keyword where appropriate
    - misc cleanup: have confirm_action() return void
    - support custom input and output files
    - support user-defined write cycle time
    - ensure that stdin is a terminal when operating w/o -f
    - do not allow reading data from a terminal w/o -f
    - perform complete input validation before taking action
    - use dedicated exit code when opening I2C device fails

    $Id: eeprog.c,v 1.28 2004/02/29 11:06:41 tat Exp $
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

/*
  v 1.0 By Winson 2018.11.5
  Create first version
  This demo for LX-2031 PAC only.
  Write/Read eeprom.
*/


#include <stdio.h>
#include <fcntl.h>
#include <getopt.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdint.h>
#include "lx_2031.h"
#include "msw.h"

#define VERSION 	"0.7.7-tear12"
#define ENV_DEV		"EEPROG_DEV"
#define ENV_I2C_ADDR	"EEPROG_I2C_ADDR"

static int g_quiet;

#define usage_if(a) do { do_usage_if( a , __LINE__); } while(0);
static void do_usage_if(int b, int line)
{
	const static char *eeprog_usage = 
"eeprog " VERSION ", a 24Cxx EEPROM reader/writer\n"
"Copyright (c) 2003-2004 by Stefano Barbato - All rights reserved.\n"
"Copyright (c) 2011 by Kris Rusocki - All rights reserved.\n"
"Usage:\n"
"\teeprog [-fqxd] -r addr[:count] [-o file]\n" 
"\teeprog [-fqd] -w addr [-i file] [-t tWC]\n" 
"\teeprog -h\n"
"\n"
"  Actions: \n"
"	-r addr[:count]	Read [count] (1 if omitted) bytes from [addr]\n" 
"			and print them to the standard output (or file\n"
"			specified by -o)\n"
"	-w addr		Write stdin (or file specified by -i) starting\n"
"			at address [addr] of the EEPROM\n"
"	-h		Print this help\n"
"	-v,     	output SDK version and exit\n"
"	-s,     	support LinPAC platform\n"

"  Options: \n"
"	-i file		Read input from [file] (for use with -w)\n"
"	-o file		Write output to [file] (for use with -r)\n"
"	-x		Set hex output mode\n" 
"	-d		Dummy mode, display what *would* have been done\n" 
"	-f		Disable warnings and don't ask confirmation\n"
"	-q		Quiet mode\n"
"	-t tWC		Define chip's write cycle time to [tWC] miliseconds\n"
"\n"
"	Examples\n"
"	1- read 64 bytes from the EEPROM starting at address 123 (decimal) \n"
"		eeprog -f -r 123:64 \n"
"	2- prints the hex codes of the first 32 bytes at address 0x22 \n"
"		eeprog -xf -r 0x22:0x20\n"
"	3- write file data to EEPROM at address 0x00\n"
"	        eeprog -f -i /etc/timezone -w 0x00 -t 3 \n"
"	4- write the current timestamp at address 0x200 of the EEPROM \n"
"		date | eeprog -f -w 0x200 -t 5 \n"
"	5- write data to EEPROM by stdin at address 0x63 of the EEPROM(EOF to exit) \n"
"	        eeprog -f -w 0x63 -t 3 \n";

	if(!b)
		return;
	fprintf(stderr, "%s\n[line %d]\n", eeprog_usage, line);
	exit(1);
}


#define die_if3(a, msg, code) do { do_die_if( a , msg, __LINE__, code); } while(0)
#define die_if(a, msg) die_if3(a, msg, 1)
static void do_die_if(int b, char* msg, int line, int exitcode)
{
	if(!b)
		return;
	fprintf(stderr, "Error at line %d: %s\n", line, msg);
	//fprintf(stderr, "	sysmsg: %s\n", strerror(errno));
	exit(exitcode);
}

#define print_info(args...) do { if(!g_quiet) fprintf(stderr, args); } while(0)

static int parse_arg(char *arg, int* paddr, int *psize)
{
	char *end;
	unsigned int out_paddr, out_psize;

	if(arg[0] == '\0')
		return -1;

	out_paddr = strtol(arg, &end, 0);
	if(*end == '\0')
	{
		if(paddr)
			*paddr = out_paddr;
		return 1;
	}

	if(*end != ':')
		return -1;

	if(end[1] == '\0')
		return -1;

	out_psize = strtol(end + 1, &end, 0);
	if(*end == '\0')
	{
		if (paddr)
			*paddr = out_paddr;
		if (psize)
			*psize = out_psize;
		return 2;
	}

	return -1;
}

static void confirm_action()
{
	fprintf(stderr, 
	"\n"
	"____________________________WARNING____________________________\n"
	"Erroneously writing to a system EEPROM (like DIMM SPD modules)\n"
	"may break your system.  In such event, it will NOT boot anymore\n"
	"and you may not be able to fix it.\n"
	"\n"
	"Reading from 8bit EEPROMs (like that in your DIMM) while using\n"
	"-16 option can also UNEXPECTEDLY write to them so be sure to\n"
	"use -16 option ONLY when required.\n"
	"\n"
	"Use -f to disable this warning message\n"
	"\n"
	"Press ENTER to continue or hit CTRL-C to exit\n"
	"\n"
	);

	getchar();
}

static int read_from_eeprom(struct eeprom *e, FILE *fp, int addr, int size, int hex)
{
	int ch, i;
	for(i = 0; i < size; ++i, ++addr)
	{
		die_if((ch = eeprom_read_byte(e, addr)) < 0, "read error");
		if(hex)
		{
			if( (i % 16) == 0 ) 
				fprintf(fp, "\n %.4x|  ", addr);
			else if( (i % 8) == 0 ) 
				fprintf(fp, "  ");
			fprintf(fp, "%.2x ", ch);
		} else 
			putc(ch, fp);
	}
	if(hex)
		fprintf(fp, "\n\n");
	fflush(fp);
	return 0;
}

static int write_to_eeprom(struct eeprom *e, FILE *fp, int addr)
{
	int c;

	while((c = fgetc(fp)) != EOF)
	{
		print_info(".");
		fflush(stdout);
		die_if(eeprom_write_byte(e, addr++, c), "write error");
	}
	print_info("\n\n");
	return 0;
}

int main(int argc, char** argv)
{
	struct eeprom e;
	int ret, op, i2c_addr, memaddr, size, want_hex, dummy, force, sixteen, write_cycle_time;

	char *input_file, *output_file;
	FILE *input_fp, *output_fp;

	char *device, *arg = 0, *i2c_addr_s;
	int eeprom_type = 0;

	op = want_hex = dummy = force = sixteen = write_cycle_time = 0;
	size = 1; // default
	g_quiet = 0;

	input_file = output_file = NULL;
	input_fp = stdin;
	output_fp = stdout;

	while((ret = getopt(argc, argv, "1:8fr:qhw:xdt:i:o:sv")) != -1)
	{
		switch(ret)
		{
		case 'x':
			want_hex++;
			break;
		case 'd':
			dummy++;
			break;
		case 'f':
			force++;
			break;
		case 'q':
			g_quiet++;
			break;
		case 'h':
			usage_if(1);
			break;
		case 't':
			die_if(parse_arg(optarg, &write_cycle_time, NULL) != 1 || write_cycle_time < 0, "-t -- invalid argument");
			break;
		case 'i':
			input_file = optarg;
			break;
		case 'o':
			output_file = optarg;
			break;

		case 'v':
			printf("SDK version = %s \n",GetSDKversion());
			return 0;
		case 's':
			printf("Support LX-2031\n");
			return 0;
		default:
			die_if(op != 0, "Both read and write requested"); 
			arg = optarg;
			op = ret;
		}
	}

	if(!eeprom_type)
		eeprom_type = EEPROM_TYPE_16BIT_ADDR; // default

	usage_if(op == 0); // no switches 
	// set device and i2c_addr reading from cmdline or env
	device = i2c_addr_s = 0;

	device = "/dev/i2c-0";
	i2c_addr_s = "0x50";

	usage_if(!device || !i2c_addr_s);
	die_if(parse_arg(i2c_addr_s, &i2c_addr, NULL) != 1 || i2c_addr < 0, "I2C address -- invalid argument");
	ret = parse_arg(arg, &memaddr, &size);
	die_if(op == 'r' && (ret == -1 || memaddr < 0 || size < 0), "-r -- invalid argument");
	die_if(op == 'w' && (ret != 1 || memaddr < 0), "-w -- invalid argument");

	//print_info("eeprog %s, a 24Cxx EEPROM reader/writer\n", VERSION);
	//print_info("Copyright (c) 2003-2004 by Stefano Barbato - All rights reserved.\n");
	//print_info("Copyright (c) 2011 by Kris Rusocki - All rights reserved.\n");
	//print_info("  Bus: %s, Address: 0x%02x, Mode: %dbit\n", 
	//		device, i2c_addr, 
	//		(eeprom_type == EEPROM_TYPE_8BIT_ADDR ? 8 : 16) );
	if(op == 'r')
	{
		print_info("  Operation: read %d bytes from offset %d, Output file: %s\n",
				size, memaddr, output_file ? output_file : "<stdout>");
	} else {
		print_info("  Operation: write at offset %d, Input file: %s\n",
				memaddr, input_file ? input_file : "<stdin>");
		if(write_cycle_time != 0)
			print_info("  Write cycle time: %d milliseconds\n", write_cycle_time);
	}

	if(dummy)
	{
		fprintf(stderr, "Dummy mode selected, nothing done.\n");
		return 0;
	}

	if (input_file) {
		die_if((input_fp = fopen(input_file, "rb")) == NULL,
			"unable to open input file "
			"(check that the file exists and that it's readable)");
	} else {
		input_file = "<stdin>";
	}

	if (output_file) {
		die_if((output_fp = fopen(output_file, "wb")) == NULL,
			"unable to create output file "
			"(check that you have permissions to write the file)");
	} else {
		output_file = "<stdout>";
	}

	die_if3(eeprom_open(device, i2c_addr, eeprom_type, write_cycle_time, &e) < 0, 
			"unable to open eeprom device file "
			"(check that the file exists and that it's readable)",
			2);
	switch(op)
	{
	case 'r':
		if(force == 0) {
			/* Confirmation must come from a terminal */
			die_if(isatty(0) == 0,
				"stdin is not a terminal"
			);
			confirm_action();
		}
		print_info("  Reading %d bytes from 0x%x\n", size, memaddr);
		read_from_eeprom(&e, output_fp, memaddr, size, want_hex);
		break;
	case 'w':
		if(force == 0) {
			/* Don't read data from a terminal */
			die_if(isatty(fileno(input_fp)) == 1,
				"refusing to read data from a terminal\n"
				"\n"
				"Use -i to provide input file or -f to force."
			);

			/* Confirmation must come from a terminal */
			die_if(isatty(0) == 0,
				"stdin is not a terminal"
				"\n"
				"Use -f to force."
			);
			confirm_action();
		}
		print_info("  Writing %s starting at address 0x%x ending by EOF\n",
			input_file, memaddr);

		eeprom_disable_wp();
		write_to_eeprom(&e, input_fp, memaddr);
		eeprom_enable_wp();
		break;
	default:
		usage_if(1);
		exit(1);
	}
	eeprom_close(&e);

	return 0;
}
