/* $Id: eeprom.c,v 1.1 1999/10/11 17:24:28 jj Exp $
 * eeprom.c: OpenPROM set utility.
 *
 * Copyright (C) 1999 Jakub Jelinek (jakub@redhat.com)
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/sysmacros.h>
#include <dirent.h>
#include <pwd.h>

#include <asm/openpromio.h>

#ifndef OPROMSETCUR
#define OPROMSETCUR     0x20004FF0
#define OPROMPCI2NODE   0x20004FF1
#define OPROMPATH2NODE  0x20004FF2
#endif

const char * program_name = "eeprom";

int prom = 0;
int readonly;
int verbose = 0;
char *promdev = "/dev/openprom";
char *device;
int promfd;
int promvers;
int p1275;
int prom_root_node, prom_current_node;
#define MAX_PROP	128
#define MAX_VAL		(4096-128-4)
char buf[4096];
#define DECL_OP(size) struct openpromio *op = (struct openpromio *)buf; op->oprom_size = (size)
void (*prom_walk_callback)(int node);
char prom_path[1024];
char regstr[40];

static int prom_getsibling(int node)
{
	DECL_OP(sizeof(int));
	
	if (node == -1) return 0;
	*(int *)op->oprom_array = node;
	if (ioctl (promfd, OPROMNEXT, op) < 0)
		return 0;
	prom_current_node = *(int *)op->oprom_array;
	return *(int *)op->oprom_array;
}

static int prom_getchild(int node)
{
	DECL_OP(sizeof(int));
	
	if (!node || node == -1) return 0;
	*(int *)op->oprom_array = node;
	if (ioctl (promfd, OPROMCHILD, op) < 0)
		return 0;
	prom_current_node = *(int *)op->oprom_array;
	return *(int *)op->oprom_array;
}

static char *prom_getproperty(char *prop, int *lenp)
{
	DECL_OP(MAX_VAL);
	
	strcpy (op->oprom_array, prop);
	if (ioctl (promfd, OPROMGETPROP, op) < 0)
		return 0;
	if (lenp) *lenp = op->oprom_size;
	return op->oprom_array;
}

static int prom_searchsiblings(char *name)
{
	char *prop;
	int len;
	
	for (;;) {
		if (!(prop = prom_getproperty("name", &len)))
			return 0;
		prop[len] = 0;
		if (!strcmp(prop, name))
			return prom_current_node;
		if (!prom_getsibling(prom_current_node))
			return 0;
	}
}

static char *prom_getopt(char *var, int *lenp)
{
	DECL_OP(MAX_VAL);

	strcpy (op->oprom_array, var);
	if (ioctl (promfd, OPROMGETOPT, op) < 0)
		return 0;
	if (lenp) *lenp = op->oprom_size;
	return op->oprom_array;
}

static int prom_setopt(char *var, char *value)
{
	DECL_OP(MAX_VAL);

	strcpy (op->oprom_array, var);
	strcpy (op->oprom_array + strlen (var) + 1, value);
	return ioctl (promfd, OPROMSETOPT, op);
}

static int prom_pci2node(int bus, int devfn)
{
	DECL_OP(2*sizeof(int));
    
	((int *)op->oprom_array)[0] = bus;
	((int *)op->oprom_array)[1] = devfn;
	if (ioctl (promfd, OPROMPCI2NODE, op) < 0)
		return 0;
	prom_current_node = *(int *)op->oprom_array;
	return *(int *)op->oprom_array;
}

#define PW_TYPE_SBUS	1
#define PW_TYPE_PCI	2
#define PW_TYPE_EBUS	3

static void prom_walk(char *path, int parent, int node, int type)
{
	int nextnode;
	int len, ntype = type;
	char *prop;

	prop = prom_getproperty("name", &len);
	if (prop && len > 0) {
		if ((!strcmp(prop, "sbus") || !strcmp(prop, "sbi")) && !type)
			ntype = PW_TYPE_SBUS;
		else if (!strcmp(prop, "ebus") && type == PW_TYPE_PCI)
			ntype = PW_TYPE_EBUS;
		else if (!strcmp(prop, "pci") && !type)
			ntype = PW_TYPE_PCI;
	}
	*path = '/';
	strcpy (path + 1, prop);
	prop = prom_getproperty("reg", &len);
	if (prop && len >= 4) {
		unsigned int *reg = (unsigned int *)prop;
		int cnt = 0;
		if (!p1275 || (type == PW_TYPE_SBUS))
			sprintf (regstr, "@%x,%x", reg[0], reg[1]);
		else if (type == PW_TYPE_PCI) {
			if ((reg[0] >> 8) & 7)
				sprintf (regstr, "@%x,%x", (reg[0] >> 11) & 0x1f, (reg[0] >> 8) & 7);
			else
				sprintf (regstr, "@%x", (reg[0] >> 11) & 0x1f);
		} else if (len == 4)
			sprintf (regstr, "@%x", reg[0]);
		else {
			unsigned int regs[2];

			/* Things get more complicated on UPA. If upa-portid exists,
			   then address is @upa-portid,second-int-in-reg, otherwise
			   it is @first-int-in-reg/16,second-int-in-reg (well, probably
			   upa-portid always exists, but just to be safe). */
			memcpy (regs, reg, sizeof(regs));
			prop = prom_getproperty("upa-portid", &len);
			if (prop && len == 4) {
				reg = (unsigned int *)prop;
				sprintf (regstr, "@%x,%x", reg[0], regs[1]); 
			} else
				sprintf (regstr, "@%x,%x", regs[0] >> 4, regs[1]);
		}
		for (nextnode = prom_getchild(parent); nextnode; nextnode = prom_getsibling(nextnode)) {
			prop = prom_getproperty("name", &len);
			if (prop && len > 0 && !strcmp (path + 1, prop))
				cnt++;
		}
		if (cnt > 1)
			strcat (path, regstr);
	}

	prom_walk_callback(node);

	nextnode = prom_getchild(node);
	if (nextnode)
		prom_walk(strchr (path, 0), node, nextnode, ntype);
	nextnode = prom_getsibling(node);
	if (nextnode)
		prom_walk(path, parent, nextnode, type);
}

#define SDSK_TYPE_IDE	1
#define SDSK_TYPE_SD	2
#define SDSK_TYPE_PLN	3
#define SDSK_TYPE_FC	4

static struct sdsk_disk {
	unsigned int prom_node;
	unsigned int type, host, hi, mid, lo;
	unsigned char *prom_name;
} *hd = NULL, *sd = NULL;
static int hdlen, sdlen;

static void scan_walk_callback(int node)
{
	int nextnode;
	char *prop;
	int len, disk;
	static int v0ctrl = 0;

	for (disk = 0; disk < hdlen + sdlen; disk++) {
		if (hd[disk].prom_node == node) {
			switch (hd[disk].type) {
			case SDSK_TYPE_IDE:
				for (nextnode = prom_getchild(node); nextnode; nextnode = prom_getsibling(nextnode)) {
					prop = prom_getproperty("name", &len);
					if (prop && len > 0 && (!strcmp (prop, "ata") || !strcmp (prop, "disk")))
						break;
				}
				if (!nextnode)
					continue;
				if (prop[0] == 'a')
					sprintf (prop, "/ata@%x,0/cmdk@%x,0", hd[disk].hi, hd[disk].lo);
				else
					sprintf (prop, "/disk@%x,0", hd[disk].hi * 2 + hd[disk].lo);
				break;
			case SDSK_TYPE_SD:
				for (nextnode = prom_getchild(node); nextnode; nextnode = prom_getsibling(nextnode)) {
					prop = prom_getproperty("compatible", &len);
					if (prop && len > 0 && !strcmp (prop, "sd"))
						break;
					prop = prom_getproperty("name", &len);
					if (prop && len > 0 && (!strcmp (prop, "sd") || !strcmp (prop, "disk")))
						break;
				}
				if (!nextnode || hd[disk].hi)
					continue;
				if (promvers) {
					char name[1024];
					prop = prom_getproperty("name", &len);
					if (prop && len > 0)
						strcpy (name, prop);
					else
						strcpy (name, "sd");
					if (!prop)
						prop = ((struct openpromio *)buf)->oprom_array;
					sprintf (prop, "/%s@%x,%x", name, hd[disk].mid, hd[disk].lo);
				} else
					sprintf (prop, "sd(%d,%d,", v0ctrl, hd[disk].mid);
				break;
			case SDSK_TYPE_PLN:
				prop = ((struct openpromio *)buf)->oprom_array;
				sprintf (prop, "/SUNW,pln@%x,%x/SUNW,ssd@%x,%x",
						 hd[disk].lo & 0xf0000000, hd[disk].lo & 0xffffff,
						 hd[disk].hi, hd[disk].mid);
				break;
			case SDSK_TYPE_FC:
				prop = ((struct openpromio *)buf)->oprom_array;
				sprintf (prop, "/sf@0,0/ssd@w%08x%08x,%x", hd[disk].hi, hd[disk].mid, hd[disk].lo);
				break;
			default:
				continue;
			}
			hd[disk].prom_name = malloc (strlen (prom_path) + strlen(prop) + 3);
			if (!hd[disk].prom_name)
				continue;
			if (promvers)
				strcpy (hd[disk].prom_name, prom_path);
			else
				hd[disk].prom_name[0] = '\0';
			strcat (hd[disk].prom_name, prop);
		}
	}
	v0ctrl++;
}

static int scan_ide(void)
{
	DIR * dir;
	char path[80];
	char buffer[512];
	int fd, i, disk;
	struct dirent * ent;
	int pci_bus, pci_devfn;

	if (access("/proc/ide", R_OK)) return 0;

	if (!(dir = opendir("/proc/ide"))) {
		return 1;
	}

	while ((ent = readdir(dir))) {
		if (ent->d_name[0] == 'h' && ent->d_name[1] == 'd' &&
			ent->d_name[2] >= 'a' && ent->d_name[2] <= 'z' &&
			ent->d_name[3] == '\0') {
			disk = ent->d_name[2] - 'a';
			if (disk >= hdlen) {
				hd = (struct sdsk_disk *)realloc(hd, ((disk&~3)+4)*sizeof(struct sdsk_disk));
				memset (hd + hdlen, 0, ((disk&~3)+4-hdlen)*sizeof(struct sdsk_disk));
				hdlen = (disk&~3)+4;
			}
			for (i = (disk & ~3); i <= (disk | 3); i++) {
				if (hd[i].type)
					break;
			}
			if (i > (disk | 3)) {
				sprintf(path, "/proc/ide/%s", ent->d_name);
				if (readlink(path, buffer, 512) < 5)
					continue;
				if (strncmp(buffer, "ide", 3) ||
					!isdigit(buffer[3]) ||
					buffer[4] != '/')
					continue;
				buffer[4] = 0;
				sprintf(path, "/proc/ide/%s/config", buffer);
				if ((fd = open(path, O_RDONLY)) < 0)
					continue;
				i = read(fd, buffer, 50);
				close(fd);
				if (i < 50) continue;
				if (sscanf (buffer, "pci bus %x device %x ",
							&pci_bus, &pci_devfn) != 2)
						continue;
				hd[disk].prom_node = prom_pci2node (pci_bus, pci_devfn);
			} else
					hd[disk].prom_node = hd[i].prom_node;
			hd[disk].type = SDSK_TYPE_IDE;
			hd[disk].hi = (disk & 2) >> 1;
			hd[disk].lo = (disk & 1);
		}
	}

	closedir(dir);

	return 0;
}

static int scan_scsi(void)
{
	FILE *f;
	DIR * dir, *dirhba;
	struct dirent * ent, *enthba;
	struct stat st;
	char * p, * q;
	char buf[512];
	char path[128];
	int disk = 0;
	int host, channel, id, lun;
	int prom_node, pci_bus, pci_devfn;

	if (access("/proc/scsi/scsi", R_OK)) {
		return 0;
	}

	f = fopen("/proc/scsi/scsi", "r");
	if (f == NULL) return 1;

	if (fgets(buf, sizeof(buf), f) == NULL) {
		fclose(f);
		return 1;
	}
	if (!strcmp(buf, "Attached devices: none\n")) {
		fclose(f);
		return 0;
	}

	while (fgets(buf, sizeof(buf), f) != NULL) {
		if (sscanf(buf, "Host: scsi%d Channel: %d Id: %d Lun: %d\n",
				   &host, &channel, &id, &lun) != 4)
			break;
		if (fgets(buf, sizeof(buf), f) == NULL)
			break;
		if (strncmp(buf, "  Vendor:", 9))
			break;
		if (fgets(buf, sizeof(buf), f) == NULL)
			break;
		if (strncmp(buf, "  Type:   ", 10))
			break;
		if (!strncmp(buf+10, "Direct-Access", 13)) {
			if (disk >= sdlen) {
				hd = (struct sdsk_disk *)
					 realloc(hd, (hdlen+(disk&~3)+4)*sizeof(struct sdsk_disk));
				sd = hd + hdlen;
				memset (sd + sdlen, 0,
						((disk&~3)+4-sdlen)*sizeof(struct sdsk_disk));
				sdlen = (disk&~3)+4;
			}
			sd[disk].type = SDSK_TYPE_SD;
			sd[disk].host = host;
			sd[disk].hi = channel;
			sd[disk].mid = id;
			sd[disk].lo = lun;
			disk++;
		}
	}
	fclose (f);

	if (!(dir = opendir("/proc/scsi"))) {
		if (!hdlen && hd) {
			free(hd);
			hd = NULL;
		}
		sd = NULL;
		sdlen = 0;
		return 1;
	}

	while ((ent = readdir(dir))) {
		if (!strcmp (ent->d_name, "scsi") || ent->d_name[0] == '.')
			continue;
		sprintf (path, "/proc/scsi/%s", ent->d_name);
		if (stat (path, &st) < 0 || !S_ISDIR (st.st_mode))
			continue;
		if (!(dirhba = opendir(path)))
			continue;

		while ((enthba = readdir(dirhba))) {
			if (enthba->d_name[0] == '.')
				continue;
			host = atoi(enthba->d_name);
			sprintf (path, "/proc/scsi/%s/%s", ent->d_name, enthba->d_name);
			f = fopen (path, "r");
			if (f == NULL) continue;

			if (!strcmp (ent->d_name, "esp") ||
				!strcmp (ent->d_name, "qlogicpti") ||
				!strcmp (ent->d_name, "fcal"))
				p = "PROM node";
			else if (!strcmp (ent->d_name, "pluto"))
				p = "serial ";
			else
				p = "PCI bus";
			while (fgets (buf, sizeof(buf), f) != NULL) {
				q = strstr (buf, p);
				if (q == NULL) continue;
				prom_node = 0;
				switch (p[1]) {
				case 'R':
					if (sscanf (q, "PROM node %x", &prom_node) == 1)
						q = NULL;
					break;
				case 'e':
					if (sscanf (q, "serial 000000%x %*dx%*d on soc%*d port %x PROM node %x",
								&id, &lun, &prom_node) == 3 &&
						lun >= 10 && lun <= 11) {
						q = NULL;
					}
					break;
				case 'C':
					if (sscanf (q, "PCI bus %x device %x", &pci_bus, &pci_devfn) == 2) {
						q = NULL;
						prom_node = prom_pci2node (pci_bus, pci_devfn);
					}
					break;
				}
				if (q == NULL) {
					for (disk = 0; disk < sdlen; disk++)
						if (sd[disk].host == host && sd[disk].type) {
							sd[disk].prom_node = prom_node;
							if (p[1] == 'e') {
								sd[disk].type = SDSK_TYPE_PLN;
								sd[disk].lo = (lun << 28) | id;
							} else if (!strcmp (ent->d_name, "fcal"))
								sd[disk].type = SDSK_TYPE_FC;
						}
				}
			}
			if (!strcmp (ent->d_name, "fcal")) {
				while (fgets (buf, sizeof(buf), f) != NULL) {
					unsigned long long ll;
					if (sscanf (buf, " [AL-PA: %*x, Id: %d, Port WWN: %Lx, Node WWN: ", &id, &ll) == 2) {
						for (disk = 0; disk < sdlen; disk++)
						if (sd[disk].host == host && sd[disk].mid == id) {
							sd[disk].hi = ll >> 32;
							sd[disk].mid = ll;
						}
					}
				}
			}
			fclose(f);
		}
		closedir(dirhba);
	}
	closedir(dir);
	return 0;
}

static int get_prom_ver(void)
{
	FILE *f = fopen ("/proc/cpuinfo","r");
	int ver = 0;
	char buffer[1024];
	char *p;

	if (f) {
		while (fgets (buffer, 1024, f)) {
			if (!strncmp (buffer, "promlib", 7)) {
				p = strstr (buffer, "Version ");
				if (p) {
					p += 8;
					if (*p == '0' || (*p >= '2' && *p <= '3')) {
						ver = *p - '0';
					}
				}
				break;
			}
		}
		fclose(f);
	}
	return ver;
}

static inline int print_device(void)
{
	char devbuf[40];
	struct stat st;
	int found = 0;
	int part = -1;
	int diskno = -1;
	
	if (stat (device, &st) < 0) {
		if (strlen (device) < 20 && !strchr (device, '/')) {
			sprintf (devbuf, "/dev/%s", device);
			if (stat (devbuf, &st) >= 0)
				found = 1;
		}
	} else
		found = 1;
	if (!found || !S_ISBLK (st.st_mode)) {
		fprintf (stderr, "Cannot find device %s\n", device);
		return 1;
	}
	switch (major (st.st_rdev)) {
	case 3: diskno = 0; break;
	case 22: diskno = 2; break;
	case 33: diskno = 4; break;
	case 34: diskno = 6; break;
	case 8:
	case 65:
	case 66:
	case 67:
	case 68:
	case 69:
	case 70:
	case 71:
		diskno = hdlen + ((major (st.st_rdev) & 7) << 4) +
			 (minor (st.st_rdev) >> 4);
		part = minor (st.st_rdev) & 15;
		break;
	}
	if (diskno != -1 && part == -1) {
		part = (minor (st.st_rdev) & 63) + 64;
		if (minor (st.st_rdev) & 64) diskno++;
	}
	if (diskno != -1) {
		promvers = get_prom_ver();
		scan_ide();
		scan_scsi();
		prom_walk_callback = scan_walk_callback;
		prom_walk(prom_path, prom_root_node, prom_getchild (prom_root_node), 0);
		if (part & 64) {
			if (diskno >= hdlen) diskno = -1;
		} else {
			diskno += hdlen;
			if (diskno >= hdlen + sdlen)
				diskno = -1;
		}
		part &= 63;
		if (part > 8) diskno = -1;
		if (!part) part = 3;
		if (diskno != -1 && !hd[diskno].prom_name) diskno = -1;
		if (diskno != -1) {
			if (!promvers)
				printf ("%s%d)\n", hd[diskno].prom_name, part - 1);
			else
				printf ("%s:%c\n", hd[diskno].prom_name, part + 'a' - 1);
		}
	}
	if (diskno == -1) {
		fprintf (stderr, "Could not find PROM path for device %s\n", device); 
		return 1;
	}
	return 0;
}

static inline void do_eeprom_init(void)
{
	struct utsname u;
	int ret;

	promfd = open(promdev, O_RDWR);
	if (promfd == -1) {
		readonly = 1;
		promfd = open(promdev, O_RDONLY);
	}
	if (promfd == -1) {
		fprintf(stderr, "Cannot open %s\n", promdev);
		exit(1);
	}
	prom_root_node = prom_getsibling(0);
	if (!prom_root_node) {
		fprintf(stderr, "Cannot find root node\n");
		exit(1);
	}

	if (!uname (&u) && !strcmp (u.machine, "sparc64"))
		p1275 = 1;
}

static int print_option(char *opt)
{
	int len, i;
	char *value;

	value = prom_getopt(opt, &len);
	if (!value || len <= 0)
		printf ("%s: data not available.\n", opt);
	else if (!strcmp (opt, "oem-logo")) {
		printf ("oem-logo=\n");
		for (i = 0; i < len; i++) {
		    printf ("%02x", (unsigned char)value[i]);
		    if ((i & 31) == 31 || i == len - 1)
			printf ("\n");
		    else if ((i & 3) == 3)
			printf (".");
		}
	} else
		printf ("%s=%s\n", opt, value);
	return !value;
}

static void print_all_options(void)
{
	char buf2[4096];
	struct openpromio *op = (struct openpromio *)buf2; 
	int len, string;
	char *prop;

	*(int *)op->oprom_array = 0;
		
	for (;;) {
		
		op->oprom_size = MAX_PROP;
		if (ioctl(promfd, OPROMNXTOPT, op) < 0) {
			fprintf(stderr, "OPROMNXTOPT failed\n");
			exit(2);
		}
		if (!op->oprom_size) break;

		print_option(op->oprom_array);
	}
}

static int prom_set_option(char *opt, char *value)
{
	if (!strcmp (opt, "security-mode")) {
		char *sec;
		int oldmode = 0;
		int len;
		if (strcmp (value, "none") && strcmp (value, "full") && strcmp (value, "command")) {
			fprintf(stderr, "Invalid security mode, mode unchanged.\n");
			return 8;
		}
		sec = prom_getopt("security-mode", &len);
		if (sec && len > 0 && (!strcmp (sec, "full") || !strcmp (sec, "command")))
			oldmode = 1;
		if (strcmp (value, "none") && !oldmode) {
			len = prom_set_option("security-password", NULL);
			if (len) return len;
		}
	}
	if (!strcmp (opt, "oem-logo")) {
		fprintf(stderr, "Setting oem-logo is not supported by this version of eeprom\n");
		return 16;
	} else if (!strcmp (opt, "security-password")) {
		char buf[128], *sec, *pass, mode[10];
		int len;
		sec = prom_getopt("security-mode", &len);
		if (sec && len > 0 && (!strcmp (sec, "full") || !strcmp (sec, "command") ||
		    (!value && !strcmp (sec, "none")))) {
			strcpy (mode, sec);
			/* Hack: all Linux kernels up to know have debugging of OPROMSET
			   enabled, which is not very good for changing password.
			   Fortunately, it is limited to first 10 sets. */
			for (len = 0; len < 10; len++) {
				if (prom_setopt ("security-mode", mode) < 0) {
					fprintf(stderr, "Could not set security mode\n");
					return 4;
				}
			}
			for (;;) {
				pass = getpass("Changing PROM password:\nNew password:");
				if (!pass[0]) {
					printf("Password unchanged.\n");
					return 64;
				}
				if (strlen (pass) < 6) {
					memset(pass, 0, strlen(pass));
					printf("Please use a longer password.\n");
					continue;
				}
				strncpy(buf, pass, 127);
				buf[127] = 0;
				pass = getpass("Retype new password:");
				if (strcmp (buf, pass)) {
					memset(buf, 0, 128);
					memset(pass, 0, strlen(pass));
					printf("Mismatch - password unchanged.\n");
					return 64;
				}
				memset(pass, 0, strlen(pass));
				if (prom_setopt (opt, buf) < 0) {
					memset(buf, 0, 128);
					fprintf(stderr, "Could not change PROM password\n");
					return 4;
				}
				memset(buf, 0, 128);
				break;
			}
		} else {
			fprintf(stderr, "Not in secure mode\n");
			return 32;
		}
	} else {
		if (prom_setopt (opt, value) < 0) {
			fprintf(stderr, "%s: Invalid property\n", opt);
			return 4;
		}
	}
	return 0;
}
		
static void usage(void) __attribute__((noreturn));

static void usage(void)
{
	fprintf (stderr, "Usage: %s [-] [-h] [-f device] [-d device] [param [=value] [...]]\n", program_name);
	exit (1);
}

int main(int argc, char **argv)
{
	char c;
	int i, ret = 0;
	int hyphens = 0;
	static struct option long_options[] = {
	  { "device", 1, 0, 'd' },
	  { "promdev", 1, 0, 'f' },
	  { "help", 0, 0, 'h' },
	  { NULL, 0, 0, '\0' }
	};

	if (argc && *argv)
		program_name = *argv;
	while ((c = getopt_long (argc, argv, "d:f:h", long_options, 0)) != EOF)
		switch (c) {
		case 'd':
			device = optarg;
			break;
		case 'f':
			promdev = optarg;
			break;
		case 'h':
		default:
			usage ();
		}

	do_eeprom_init();
	if (device) {
		if (optind < argc)
			usage ();
		ret = print_device();
	} else if (optind >= argc)
		print_all_options();
	else for (i = optind; i < argc; i++) {
		char *p;

		if (!strcmp (argv[i], "-") && !hyphens) {
			char buf[512], *p;

			hyphens = 1;
			while (fgets (buf, 512, stdin)) {
				p = strchr (buf, '\n');
				if (p) *p = 0;
				p = strchr (buf, '=');
				if (!p)
					ret |= print_option(buf);
				else {
					if (readonly) {
						if (readonly == 1)
							fprintf(stderr, "%s: running in read-only mode\n", program_name);
						readonly = 2;
						ret |= 4;
						continue;
					}
					*p = 0;
					ret |= prom_set_option(buf, p + 1);
				}
			}
			continue;
		}
		p = strchr(argv[i], '=');
		if (!p)
			ret |= print_option(argv[i]);
		else {
			if (readonly) {
				if (readonly == 1)
					fprintf(stderr, "%s: running in read-only mode\n", program_name);
				readonly = 2;
				ret |= 4;
				continue;
			}
			*p = 0;
			ret |= prom_set_option(argv[i], p + 1);
		}
	}

	close(promfd);
	return ret;
}
