/*
 * Program to read/write a Hyundai HY93C46 EEPROM onboard an
 * ethernet controller
 *
 * Moshen Chan <mchan@cobalt.com>
 * May 19, 2000
 *
 * Tim Hockin <thockin@sun.com>
 * Aug 01, 2001
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <sys/io.h>
#include "enet_eeprom.h"

static void usage(char *progname);
static struct eeprom_driver *config_driver(char *drivername);
static int do_read_all(void);
static int do_read_mac(void);
static int do_read_wol(void);
static int do_read_sopass(void);
static int do_write_mac(int argc, char *argv[]);
static int do_write_wol(int argc, char *argv[]);
static int do_write_sopass(int argc, char *argv[]);
static int do_write_default(void);
static int do_write_all(int argc, char *argv[]);
void print_eeprom(unsigned short *eeprom);
static void print_mac(unsigned char *mac);
static void print_wol(unsigned long wol);
static int parse_wol_options(char *options, unsigned long *wol);
static int write_eeprom(void);
static int read_eeprom(void);

static struct eeprom_driver *drv;
static unsigned short *eeprom;
static unsigned short ioaddr;
static int pretty_print;

int main(int argc, char *argv[]) 
{
    int ch;
    int  h = 0, p = 0, d = 0, r = 0, w = 0;
    char *progname;
    char *port, *device, *mode;
    int ret = 0;

    progname = argv[0];
    port = device = mode = NULL;

    while ((ch = getopt(argc, argv, "hPp:d:r:w:")) != -1) {
	switch (ch) {
	    case 'h':
		h = 1;
		break;
		
	    case 'P':
		pretty_print = 1;
		break;
		
	    case 'p':
		port = optarg;
		p = 1;
		break;

	    case 'd':
		device = optarg;
		d = 1;
		break;

	    case 'r':
		r = 1;
		mode = optarg;
		break;

	    case 'w':
		mode = optarg;
		w = 1;
		break;
	}	
    }

    argc -= optind;
    argv += optind;

    /* fall back on a couple environment variables for -p and -d */
    if (!p) {
	port = getenv("ENET_PORT");
	if (port) {
		p = 1;
	}
    }
    if (!d) {
	device = getenv("ENET_DEV");
	if (device) {
		d = 1;
	}
    }

    /* note that only ONE of -r or -w is allowed */
    if (!(p && d && (r ^ w )) || h) {
	usage(progname);
	return 1;
    }

    /* get access rights */
    if (iopl(3) < 0) {
        fprintf(stderr, "no permission to open ports\n");
        exit(1);
    }

    /* convert port string */
    ioaddr = strtoul(port, NULL, 16);
 
    /* pick a driver to use */
    drv = config_driver(device);
    if (!drv) {
	fprintf(stderr, "unknown device type %s\n", device);
	exit(1);
    }

    /* do the requested operation */
    if (r) {
	if (!strcmp(mode,"all")) {
	    ret = do_read_all();
	} else if (!strcmp(mode, "mac")) {
	    ret = do_read_mac();
	} else if (!strcmp(mode, "wol")) {
	    ret = do_read_wol();
	} else if (!strcmp(mode, "sopass")) {
	    ret = do_read_sopass();
	} else {
	    fprintf(stderr, "not a valid read mode\n");
	    ret = -1;
	}
    } else if (w) {
	if (!strcmp(mode, "all")) {
	    ret = do_write_all(argc, argv);
	} else if (!strcmp(mode, "mac") ) {
	    ret = do_write_mac(argc, argv);
	} else if (!strcmp(mode, "wol")) {
	    ret = do_write_wol(argc, argv);
	} else if (!strcmp(mode, "sopass")) {
	    ret = do_write_sopass(argc, argv);
	} else if (!strcmp(mode, "default")) {
	    ret = do_write_default();
	} else {
	    fprintf(stderr, "not a valid write mode\n");
	    ret = -1;
	}
    }

    return (ret ? 1 : 0);
}

static void 
usage(char *progname) 
{
    printf(
	"usage: %s [-P] -p port -d device [-W wakepass] (-r|-w) mode [data]\n", 
		progname);
    printf(
	"options:\n"
	"  -h           show help and exit\n"
	"  -P           Pretty Print the output\n"
	"  -p port      ioport of the NIC (look in /proc/pci)\n"
	"  -d device    type of NIC.  Valid device types:\n"
	"                 natsemi   - Natsemi DP83815\n"
	"                 eeprom100 - Intel EtherExpress Pro 100\n"
	"  -r mode      reads from eeprom acording to mode\n"
	"  -w mode      writes to eeprom acording to mode\n"
	"\n"
    );
    printf(
	"Valid read/write modes:\n"
	"  all     - read/wite entire eeprom (no checksum calculation!).\n"
	"            Data is the contents of the eeprom in 16 bit hex words.\n"
	"            Length is eeprom specific.\n"
	"  mac     - read/write mac address.  Data is 6 hex bytes \n"
	"            representing the mac address.\n"
	"  wol     - read/wite wake on lan properties.  Data is a string\n"
	"            consisting of one or more of the following characters\n"
	"             p - wake on phy interrupt\n"
	"             u - wake on unicast packet\n"
	"             m - wake on multicast packet\n"
	"             b - wake on broadcast packet\n"
	"             a - wake on ARP\n"
	"             g - wake on MagicPacket(tm)\n"
	"             s - enable SecureOn(tm) password for MagicPacket(tm)\n"
	"             d - disable\n"
	"  sopass  - read/write MagicPacket(tm) SecureOn(tm) password.  Data\n"
	"            is 6 hex bytes representing the password\n"
	"  default - write default values and current mac address.  No data\n"
	"            needed.\n"
	"\n"
    );
    printf("Examples:\n");
    printf("\t%s -p 0xdb00 -d eepro100 -r all\n", progname);
    printf("\t%s -p 0xdb00 -d eepro100 -w mac 00 10 e0 01 0b 7f\n", progname);
    printf("\t%s -p 0xdb00 -d natsemi -w default\n", progname);
    printf("\t%s -p 0xdb00 -d natsemi -w wol pg\n", progname);
    printf("\t%s -p 0xdb00 -d natsemi -w sopass 00 11 22 33 44 55\n", progname);
}

static struct eeprom_driver * 
config_driver(char *driver_name)
{
    /* we only know NatSemi DP83815(MacPhyter) and Intel EEPRO100 */
    if (!strcmp(driver_name, "natsemi")) {
	return &natsemi_driver;
    } else if (!strcmp(driver_name, "eepro100")) {
	return &eepro100_driver;
    } else {
	return NULL;
    }
}

static int 
do_read_all(void)
{
    if (read_eeprom() < 0) {
	    return -1;
    }
    print_eeprom(eeprom);
    return 0;
}

static int 
do_read_mac(void)
{
    unsigned char mac[6];

    if (!drv->read_mac) {
	    fprintf(stderr, "read_mac is not supported for this device\n");
	    return -1;
    }
    if (read_eeprom() < 0) {
	    return -1;
    }
    if (drv->read_mac(eeprom, mac) < 0) {
	    return -1;
    }
    print_mac(mac);
    return 0;
}

static int 
do_read_wol(void)
{
    unsigned long wol;

    if (!drv->read_wol) {
	    fprintf(stderr, "read_wol is not supported for this device\n");
	    return -1;
    }
    if (read_eeprom() < 0) {
	    return -1;
    }
    if (drv->read_wol(eeprom, &wol) < 0) {
	    return -1;
    }
    print_wol(wol);
    return 0;
}

static int 
do_write_mac(int argc, char *argv[])
{
    unsigned char mac[6];
    int i;

    if (argc != 6) {
	fprintf(stderr, "must specify 6 bytes in MAC address\n");
	return -1;
    }

    for (i = 0; i < 6; i++) {
	mac[i] = (unsigned char)strtoul(argv[i], NULL, 16);
    }

    if (!drv->write_mac) {
	    fprintf(stderr, "write_mac is not supported for this device\n");
	    return -1;
    }
    if (read_eeprom() < 0) {
	    return -1;
    }
    if (drv->write_mac(eeprom, mac) < 0) {
	    return -1;
    }
    if (drv->update_checksum(eeprom) < 0) {
	    return -1;
    }

    if (write_eeprom() < 0) {
	    return -1;
    }

    return 0;
}

static int 
do_write_wol(int argc, char *argv[])
{
    unsigned long wolbits;
   
    if (argc == 0) {
	fprintf(stderr, "no WOL info specified\n");
	return -1;
    }

    if (parse_wol_options(*argv, &wolbits) < 0) {
	return -1;
    }

    if (!drv->write_wol) {
	    fprintf(stderr, "write_wol is not supported for this device\n");
	    return -1;
    }
    if (read_eeprom() < 0) {
	    return -1;
    }
    if (drv->write_wol(eeprom, wolbits) < 0) {
	    return -1;
    }
    if (drv->update_checksum(eeprom) < 0) {
	    return -1;
    }
    
    if (write_eeprom() < 0) {
	    return -1;
    }

    return 0;
}

static int 
do_read_sopass(void)
{
    unsigned char pass[6];

    if (!drv->read_sopass) {
	    fprintf(stderr, "read_sopass is not supported for this device\n");
	    return -1;
    }
    if (read_eeprom() < 0) {
	    return -1;
    }
    if (drv->read_sopass(eeprom, pass) < 0) {
	    return -1;
    }
    print_mac(pass);
    return 0;
}

static int 
do_write_sopass(int argc, char *argv[])
{
    unsigned char sopass[6];
    int i;

    if (argc != 6) {
	fprintf(stderr, "must specify 6 bytes in password\n");
	return -1;
    }

    for (i = 0; i < argc; i++) {
	sopass[i] = (unsigned char)strtoul(argv[i], NULL, 16);
    }

    if (!drv->write_sopass) {
	    fprintf(stderr, "write_sopass is not supported for this device\n");
	    return -1;
    }
    if (read_eeprom() < 0) {
	    return -1;
    }
    if (drv->write_sopass(eeprom, sopass) < 0) {
	    return -1;
    }
    if (drv->update_checksum(eeprom) < 0) {
	    return -1;
    }

    if (write_eeprom() < 0) {
	    return -1;
    }

    return 0;
}

static int 
do_write_default(void)
{
    unsigned char mac[6];

    if (!drv->load_default) {
	    fprintf(stderr, "write_default is not supported for this device\n");
	    return -1;
    }
    if (read_eeprom() < 0) {
	    return -1;
    }
    if (drv->read_mac(eeprom, mac) < 0) {
	    return -1;
    }
    if (drv->load_default(eeprom) < 0) {
	    return -1;
    }
    if (drv->write_mac(eeprom, mac) < 0) {
	    return -1;
    }
    if (drv->update_checksum(eeprom) < 0) {
	    return -1;
    }
    
    if (write_eeprom() < 0) {
	    return -1;
    }

    return 0;
}

static int 
do_write_all(int argc, char *argv[])
{
    int size = drv->get_size();
    int i;
    int ret=0;

    if (argc != size) {
	fprintf(stderr, "please specify %d (16 bit)words to write in hex\n", 
		size);
	return -1;
    }
    
    if (!drv->write_word) {
	    fprintf(stderr, "write_word is not supported for this device\n");
	    return -1;
    }
    for (i = 0; i < size; i++) {
	unsigned short s;
	s = (unsigned short)strtoul(argv[i], NULL, 16);
	if (drv->write_word(ioaddr, s, i) < 0) {
	    ret = -1;
	    fprintf(stderr, "word write failed\n");
	}
    }

    return ret;
}

void 
print_eeprom(unsigned short *eeprom)
{
    int size = drv->get_size();
    int i;
    int space=0;

    if (pretty_print) {
	printf("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f");
	for (i = 0 ; i < size ; i++) {
	    if (!(i%0x8))
		printf( "\n%02x:", i*2 );
	    printf(" %02x %02x", eeprom[i] & 0xff, eeprom[i] >> 8);
	}
	printf("\n");
    } else {
	for (i = 0 ; i < size ; i++) {
	    printf("%s%04x", (space)?" ":"", eeprom[i]);
	    space = 1;
	}
    }
    printf("\n");
}

static void 
print_mac(unsigned char *mac)
{
	int space =0;
	int i;

	for (i = 0; i < 6; i++) {
		if (pretty_print) {
			printf("%s%02x", (space)?":":"", mac[i]);
		} else { 
			printf("%s%02x", (space)?" ":"", mac[i]);
		}
		space = 1;
	}
	printf("\n");
}

static void 
print_wol(unsigned long wol)
{
    if (pretty_print) {
	printf("Wake on: \n");
	if (wol & WAKE_ON_PHY)
	    printf("\tPHY Interrupt\n");
	if (wol & WAKE_ON_UCAST)
	    printf("\tUnicast Packet\n");
	if (wol & WAKE_ON_MCAST)
	    printf("\tMulticast Packet\n");
	if (wol & WAKE_ON_BCAST)
	    printf("\tBroadcast Packet\n");
	if (wol & WAKE_ON_ARP)
	    printf("\tARP\n");
	if (wol & WAKE_ON_MAGIC) {
	    printf("\tMagicPacket(tm)");
	    if (wol & WAKE_ON_SOMAGIC)
	        printf(" with SecureOn(tm)");
	    printf("\n");
	}
	if (!wol)
	    printf("\tDisabled\n");
    } else {
	if (wol & WAKE_ON_PHY)
	    printf("WAKE_ON_PHY\n");
	if (wol & WAKE_ON_UCAST)
	    printf("WAKE_ON_UCAST\n");
	if (wol & WAKE_ON_MCAST)
	    printf("WAKE_ON_MCAST\n");
	if (wol & WAKE_ON_BCAST)
	    printf("WAKE_ON_BCAST\n");
	if (wol & WAKE_ON_ARP)
	    printf("WAKE_ON_ARP\n");
	if (wol & WAKE_ON_MAGIC)
	    printf("WAKE_ON_MAGIC\n");
	if (wol & WAKE_ON_SOMAGIC)
	    printf("WAKE_ON_SOMAGIC\n");
	if (!wol)
	    printf("DISABLED\n");
    }
}

static int 
parse_wol_options(char *options, unsigned long *wol)
{
    *wol = 0;
    while (*options) {
	switch (*options) {
	    case 'p':
		*wol |= WAKE_ON_PHY;
		break;
		
	    case 'u':
		*wol |= WAKE_ON_UCAST;
		break;
		
	    case 'm':
		*wol |= WAKE_ON_MCAST;
		break;
		
	    case 'b':
		*wol |= WAKE_ON_BCAST;
		break;
			    
	    case 'a':
		*wol |= WAKE_ON_ARP;
		break;
		
	    case 'g':
		*wol |= WAKE_ON_MAGIC;
		break;

	    case 's':
		*wol |= WAKE_ON_SOMAGIC;
		break;

	    case 'd':
		*wol = 0;
		break;
		
	    default:
		fprintf(stderr, "unknown wake option '%c'\n", *options);
		return -1;
	}
	options++;
    }
    return 0;
}

static int
write_eeprom(void)
{
    if (!drv->write_eeprom) {
	    fprintf(stderr, "write_eeprom is not supported for this device\n");
	    return -1;
    }
    if (drv->write_eeprom(ioaddr, eeprom) < 0) {
	fprintf(stderr, "EEPROM write failed\n");
	return -1;
    }
    return 0;
}

static int
read_eeprom(void)
{
    eeprom = malloc(sizeof(*eeprom) * drv->get_size());
    if (!eeprom) {
        perror("malloc()");
        return -1;
    }
    if (drv->read_eeprom(ioaddr, eeprom) < 0) {
	fprintf(stderr, "EEPROM read failed\n");
        return -1;
    }
    return 0;
}
    

