/*	$NetBSD: eehandlers.c,v 1.19 2019/10/05 23:30:22 mrg Exp $	*/

/*-
 * Copyright (c) 1996 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Jason R. Thorpe.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <util.h>
#include <sys/inttypes.h>

#include <machine/eeprom.h>

#include "defs.h"

extern	char *path_eeprom;
extern	int eval;
extern	int update_checksums;
extern	int ignore_checksum;
extern	int fix_checksum;
extern	int cksumfail;
extern	u_short writecount;

static	char err_str[BUFSIZE];

static	void badval(const struct keytabent *, char *);
static	int doio(const struct keytabent *, u_char *, ssize_t, int);

static	u_char ee_checksum(u_char *, size_t);
static	void ee_hwupdate(const struct keytabent *, char *);
static	void ee_num8(const struct keytabent *, char *);
static	void ee_num16(const struct keytabent *, char *);
static	void ee_screensize(const struct keytabent *, char *);
static	void ee_truefalse(const struct keytabent *, char *);
static	void ee_bootdev(const struct keytabent *, char *);
static	void ee_kbdtype(const struct keytabent *, char *);
static	void ee_constype(const struct keytabent *, char *);
static	void ee_diagpath(const struct keytabent *, char *);
static	void ee_banner(const struct keytabent *, char *);
static	void ee_notsupp(const struct keytabent *, char *);

static const struct	keytabent eekeytab[] = {
	{ "hwupdate",		0x10,	ee_hwupdate },
	{ "memsize",		0x14,	ee_num8 },
	{ "memtest",		0x15,	ee_num8 },
	{ "scrsize",		0x16,	ee_screensize },
	{ "watchdog_reboot",	0x17,	ee_truefalse },
	{ "default_boot",	0x18,	ee_truefalse },
	{ "bootdev",		0x19,	ee_bootdev },
	{ "kbdtype",		0x1e,	ee_kbdtype },
	{ "console",		0x1f,	ee_constype },
	{ "keyclick",		0x21,	ee_truefalse },
	{ "diagdev",		0x22,	ee_bootdev },
	{ "diagpath",		0x28,	ee_diagpath },
	{ "columns",		0x50,	ee_num8 },
	{ "rows",		0x51,	ee_num8 },
	{ "ttya_use_baud",	0x58,	ee_truefalse },
	{ "ttya_baud",		0x59,	ee_num16 },
	{ "ttya_no_rtsdtr",	0x5b,	ee_truefalse },
	{ "ttyb_use_baud",	0x60,	ee_truefalse },
	{ "ttyb_baud",		0x61,	ee_num16 },
	{ "ttyb_no_rtsdtr",	0x63,	ee_truefalse },
	{ "banner",		0x68,	ee_banner },
	{ "secure",		0,	ee_notsupp },
	{ "bad_login",		0,	ee_notsupp },
	{ "password",		0,	ee_notsupp },
	{ NULL,			0,	ee_notsupp },
};

#define BARF(kt) {							\
	badval((kt), arg);						\
	++eval;								\
	return;								\
}

#define FAILEDREAD(kt) {						\
	warnx("%s", err_str);						\
	warnx("failed to read field `%s'", (kt)->kt_keyword);		\
	++eval;								\
	return;								\
}

#define FAILEDWRITE(kt) {						\
	warnx("%s", err_str);						\
	warnx("failed to update field `%s'", (kt)->kt_keyword);		\
	++eval;								\
	return;								\
}

void
ee_action(char *keyword, char *arg)
{
	const struct keytabent *ktent;

	for (ktent = eekeytab; ktent->kt_keyword != NULL; ++ktent) {
		if (strcmp(ktent->kt_keyword, keyword) == 0) {
			(*ktent->kt_handler)(ktent, arg);
			return; 
		}
	}

	warnx("unknown keyword %s", keyword);
	++eval;
}

void
ee_dump(void)
{
	const struct keytabent *ktent;

	for (ktent = eekeytab; ktent->kt_keyword != NULL; ++ktent)
		(*ktent->kt_handler)(ktent, NULL);
}

static void
ee_hwupdate(const struct keytabent *ktent, char *arg)
{
	uint32_t hwtime;
	time_t t;
	char *cp, *cp2;

	if (arg) {
		if ((strcmp(arg, "now") == 0) ||
		    (strcmp(arg, "today") == 0)) {
			if ((t = time(NULL)) == (time_t)(-1)) {
				warnx("can't get current time");
				++eval;
				return;
			}
		} else
			if ((t = parsedate(arg, NULL, NULL)) == (time_t)(-1))
				BARF(ktent);
		hwtime = (uint32_t)t;	/* XXX 32 bit time_t on hardware */
		if (hwtime != t)
			warnx("time overflow");

		if (doio(ktent, (u_char *)&hwtime, sizeof(hwtime), IO_WRITE))
			FAILEDWRITE(ktent);
	} else {
		if (doio(ktent, (u_char *)&hwtime, sizeof(hwtime), IO_READ))
			FAILEDREAD(ktent);
		t = (time_t)hwtime;	/* XXX 32 bit time_t on hardware */
	}

	cp = ctime(&t);
	if (cp != NULL && (cp2 = strrchr(cp, '\n')) != NULL)
		*cp2 = '\0';

	printf("%s=%" PRId64, ktent->kt_keyword, (int64_t)t);
	if (cp != NULL)
		printf(" (%s)", cp);
	printf("\n");
}

static void
ee_num8(const struct keytabent *ktent, char *arg)
{
	u_char num8 = 0;
	u_int num32;
	int i;

	if (arg) {
		for (i = 0; i < (int)strlen(arg) - 1; ++i)
			if (!isdigit((unsigned char)arg[i]))
				BARF(ktent);
		num32 = atoi(arg);
		if (num32 > 0xff)
			BARF(ktent);
		num8 += num32;
		if (doio(ktent, &num8, sizeof(num8), IO_WRITE))
			FAILEDWRITE(ktent);
	} else
		if (doio(ktent, &num8, sizeof(num8), IO_READ))
			FAILEDREAD(ktent);

	printf("%s=%d\n", ktent->kt_keyword, num8);
}

static void
ee_num16(const struct keytabent *ktent, char *arg)
{
	u_int16_t num16 = 0;
	u_int num32;
	int i;

	if (arg) {
		for (i = 0; i < (int)strlen(arg) - 1; ++i)
			if (!isdigit((unsigned char)arg[i]))
				BARF(ktent);
		num32 = atoi(arg);
		if (num32 > 0xffff)
			BARF(ktent);
		num16 += num32;
		if (doio(ktent, (u_char *)&num16, sizeof(num16), IO_WRITE))
			FAILEDWRITE(ktent);
	} else
		if (doio(ktent, (u_char *)&num16, sizeof(num16), IO_READ))
			FAILEDREAD(ktent);

	printf("%s=%d\n", ktent->kt_keyword, num16);
}

static	const struct strvaltabent scrsizetab[] = {
	{ "1152x900",		EE_SCR_1152X900 },
	{ "1024x1024",		EE_SCR_1024X1024 },
	{ "1600x1280",		EE_SCR_1600X1280 },
	{ "1440x1440",		EE_SCR_1440X1440 },
	{ NULL,			0 },
};

static void
ee_screensize(const struct keytabent *ktent, char *arg)
{
	const struct strvaltabent *svp;
	u_char scsize;

	if (arg) {
		for (svp = scrsizetab; svp->sv_str != NULL; ++svp)
			if (strcmp(svp->sv_str, arg) == 0)
				break;
		if (svp->sv_str == NULL)
			BARF(ktent);
		
		scsize = svp->sv_val;
		if (doio(ktent, &scsize, sizeof(scsize), IO_WRITE))
			FAILEDWRITE(ktent);
	} else {
		if (doio(ktent, &scsize, sizeof(scsize), IO_READ))
			FAILEDREAD(ktent);

		for (svp = scrsizetab; svp->sv_str != NULL; ++svp)
			if (svp->sv_val == scsize)
				break;
		if (svp->sv_str == NULL) {
			warnx("unknown %s value %d", ktent->kt_keyword,
			    scsize);
			return;
		}
	}
	printf("%s=%s\n", ktent->kt_keyword, svp->sv_str);
}

static	const struct strvaltabent truthtab[] = {
	{ "true",		EE_TRUE },
	{ "false",		EE_FALSE },
	{ NULL,			0 },
};

static void
ee_truefalse(const struct keytabent *ktent, char *arg)
{
	const struct strvaltabent *svp;
	u_char truth;

	if (arg) {
		for (svp = truthtab; svp->sv_str != NULL; ++svp)
			if (strcmp(svp->sv_str, arg) == 0)
				break;
		if (svp->sv_str == NULL)
			BARF(ktent);

		truth = svp->sv_val;
		if (doio(ktent, &truth, sizeof(truth), IO_WRITE))
			FAILEDWRITE(ktent);
	} else {
		if (doio(ktent, &truth, sizeof(truth), IO_READ))
			FAILEDREAD(ktent);

		for (svp = truthtab; svp->sv_str != NULL; ++svp)
			if (svp->sv_val == truth)
				break;
		if (svp->sv_str == NULL) {
			warnx("unknown truth value 0x%x for %s", truth,
			    ktent->kt_keyword);
			return;
		}
	}
	printf("%s=%s\n", ktent->kt_keyword, svp->sv_str);
}

static void
ee_bootdev(const struct keytabent *ktent, char *arg)
{
	u_char dev[5];
	int i;
	size_t arglen;
	char *cp;

	if (arg) {
		/*
		 * The format of the string we accept is the following:
		 *	cc(n,n,n)
		 * where:
		 *	c -- an alphabetical character [a-z]
		 *	n -- a number in hexadecimal, between 0 and ff,
		 *	     with no leading `0x'.
		 */
		arglen = strlen(arg);
		if (arglen < 9 || arglen > 12 || arg[2] != '(' ||
		     arg[arglen - 1] != ')')
			BARF(ktent);

		/* Handle the first 2 letters. */
		for (i = 0; i < 2; ++i) {
			if (arg[i] < 'a' || arg[i] > 'z')
				BARF(ktent);
			dev[i] = (u_char)arg[i];
		}

		/* Handle the 3 `0x'-less hex values. */
		cp = &arg[3];
		for (i = 2; i < 5; ++i) {
			if (*cp == '\0')
				BARF(ktent);

			if (*cp >= '0' && *cp <= '9')
				dev[i] = *cp++ - '0';
			else if (*cp >= 'a' && *cp <= 'f')
				dev[i] = 10 + (*cp++ - 'a');
			else
				BARF(ktent);

			/* Deal with a second digit. */
			if (*cp >= '0' && *cp <= '9') {
				dev[i] <<= 4;
				dev[i] &= 0xf0;
				dev[i] += *cp++ - '0';
			} else if (*cp >= 'a' && *cp <= 'f') {
				dev[i] <<= 4;
				dev[i] &= 0xf0;
				dev[i] += 10 + (*cp++ - 'a');
			}

			/* Ensure we have the correct delimiter. */
			if ((*cp == ',' && i < 4) || (*cp == ')' && i == 4)) {
				++cp;
				continue;
			} else
				BARF(ktent);
		}
		if (doio(ktent, (u_char *)&dev[0], sizeof(dev), IO_WRITE))
			FAILEDWRITE(ktent);
	} else
		if (doio(ktent, (u_char *)&dev[0], sizeof(dev), IO_READ))
			FAILEDREAD(ktent);

	printf("%s=%c%c(%x,%x,%x)\n", ktent->kt_keyword, dev[0],
	     dev[1], dev[2], dev[3], dev[4]);
}

static void
ee_kbdtype(const struct keytabent *ktent, char *arg)
{
	u_char kbd = 0;
	u_int kbd2;
	int i;

	if (arg) {
		for (i = 0; i < (int)strlen(arg) - 1; ++i)
			if (!isdigit((unsigned char)arg[i]))
				BARF(ktent);
		kbd2 = atoi(arg);
		if (kbd2 > 0xff)
			BARF(ktent);
		kbd += kbd2;
		if (doio(ktent, &kbd, sizeof(kbd), IO_WRITE))
			FAILEDWRITE(ktent);
	} else
		if (doio(ktent, &kbd, sizeof(kbd), IO_READ))
			FAILEDREAD(ktent);

	printf("%s=%d (%s)\n", ktent->kt_keyword, kbd, kbd ? "other" : "Sun");
}

static	const struct strvaltabent constab[] = {
	{ "b&w",		EE_CONS_BW },
	{ "ttya",		EE_CONS_TTYA },
	{ "ttyb",		EE_CONS_TTYB },
	{ "color",		EE_CONS_COLOR },
	{ "p4opt",		EE_CONS_P4OPT },
	{ NULL,			0 },
};

static void
ee_constype(const struct keytabent *ktent, char *arg)
{
	const struct strvaltabent *svp;
	u_char cons;

	if (arg) {
		for (svp = constab; svp->sv_str != NULL; ++svp)
			if (strcmp(svp->sv_str, arg) == 0)
				break;
		if (svp->sv_str == NULL)
			BARF(ktent);

		cons = svp->sv_val;
		if (doio(ktent, &cons, sizeof(cons), IO_WRITE))
			FAILEDWRITE(ktent);
	} else {
		if (doio(ktent, &cons, sizeof(cons), IO_READ))
			FAILEDREAD(ktent);

		for (svp = constab; svp->sv_str != NULL; ++svp)
			if (svp->sv_val == cons)
				break;
		if (svp->sv_str == NULL) {
			warnx("unknown type 0x%x for %s", cons,
			    ktent->kt_keyword);
			return;
		}
	}
	printf("%s=%s\n", ktent->kt_keyword, svp->sv_str);
		
}

static void
ee_diagpath(const struct keytabent *ktent, char *arg)
{
	char path[40];

	memset(path, 0, sizeof(path));
	if (arg) {
		if (strlen(arg) > sizeof(path))
			BARF(ktent);
		memcpy(path, arg, sizeof path);
		if (doio(ktent, (u_char *)&path[0], sizeof(path), IO_WRITE))
			FAILEDWRITE(ktent);
	} else
		if (doio(ktent, (u_char *)&path[0], sizeof(path), IO_READ))
			FAILEDREAD(ktent);

	printf("%s=%s\n", ktent->kt_keyword, path);
}

static void
ee_banner(const struct keytabent *ktent, char *arg)
{
	char string[80];
	u_char enable;
	struct keytabent kt;

	kt.kt_keyword = "enable_banner";
	kt.kt_offset = EE_BANNER_ENABLE_LOC;
	kt.kt_handler = ee_notsupp;

	memset(string, '\0', sizeof(string));
	if (arg) {
		if (strlen(arg) > sizeof(string))
			BARF(ktent);
		if (*arg != '\0') {
			enable = EE_TRUE;
			memcpy(string, arg, sizeof string);
			if (doio(ktent, (u_char *)string,
			    sizeof(string), IO_WRITE))
				FAILEDWRITE(ktent);
		} else {
			enable = EE_FALSE;
			if (doio(ktent, (u_char *)string,
			    sizeof(string), IO_READ))
				FAILEDREAD(ktent);
		}

		if (doio(&kt, &enable, sizeof(enable), IO_WRITE))
			FAILEDWRITE(&kt);
	} else {
		if (doio(ktent, (u_char *)string, sizeof(string), IO_READ))
			FAILEDREAD(ktent);
		if (doio(&kt, &enable, sizeof(enable), IO_READ))
			FAILEDREAD(&kt);
	}
	printf("%s=%s (%s)\n", ktent->kt_keyword, string,
	    enable == EE_TRUE ? "enabled" : "disabled");
}

/* ARGSUSED */
static void
ee_notsupp(const struct keytabent *ktent, char *arg)
{

	warnx("field `%s' not yet supported", ktent->kt_keyword);
}

static void
badval(const struct keytabent *ktent, char *arg)
{

	warnx("inappropriate value `%s' for field `%s'", arg,
	    ktent->kt_keyword);
}

static int
doio(const struct keytabent *ktent, u_char *buf, ssize_t len, int wr)
{
	int fd, rval = 0;
	u_char *buf2;

	buf2 = (u_char *)calloc(1, len);
	if (buf2 == NULL) {
		strncpy(err_str, "memory allocation failed", sizeof err_str);
		return (1);
	}

	fd = open(path_eeprom, wr == IO_WRITE ? O_RDWR : O_RDONLY, 0640);
	if (fd < 0) {
		(void)snprintf(err_str, sizeof err_str, "open: %s: %s", path_eeprom,
		    strerror(errno));
		free(buf2);
		return (1);
	}

	if (lseek(fd, (off_t)ktent->kt_offset, SEEK_SET) < (off_t)0) {
		(void)snprintf(err_str, sizeof err_str, "lseek: %s: %s",
		    path_eeprom, strerror(errno));
		rval = 1;
		goto done;
	}

	if (read(fd, buf2, len) != len) {
		(void)snprintf(err_str, sizeof err_str, "read: %s: %s",
		    path_eeprom, strerror(errno));
		return (1);
	}

	if (wr == IO_WRITE) {
		if (memcmp(buf, buf2, len) == 0)
			goto done;

		if (lseek(fd, (off_t)ktent->kt_offset, SEEK_SET) < (off_t)0) {
			(void)snprintf(err_str, sizeof err_str, "lseek: %s: %s",
			    path_eeprom, strerror(errno));
			rval = 1;
			goto done;
		}

		++update_checksums;
		if (write(fd, buf, len) < 0) {
			(void)snprintf(err_str, sizeof err_str, "write: %s: %s",
			    path_eeprom, strerror(errno));
			rval = 1;
			goto done;
		}
	} else
		memmove(buf, buf2, len);

 done:
	free(buf2);
	(void)close(fd);
	return (rval);
}

/*
 * Read from eeLastHwUpdate to just before eeReserved.  Calculate
 * a checksum, and deposit 3 copies of it sequentially starting at
 * eeChecksum[0].  Increment the write count, and deposit 3 copies
 * of it sequentially starting at eeWriteCount[0].
 */
void
ee_updatechecksums(void)
{
	struct keytabent kt;
	u_char checkme[EE_SIZE - EE_HWUPDATE_LOC];
	u_char checksum;
	int i;

	kt.kt_keyword = "eeprom contents";
	kt.kt_offset = EE_HWUPDATE_LOC;
	kt.kt_handler = ee_notsupp;

	if (doio(&kt, checkme, sizeof(checkme), IO_READ)) {
		cksumfail = 1;
		FAILEDREAD(&kt);
	}

	checksum = ee_checksum(checkme, sizeof(checkme));

	kt.kt_keyword = "eeprom checksum";
	for (i = 0; i < 4; ++i) {
		kt.kt_offset = EE_CKSUM_LOC + (i * sizeof(checksum));
		if (doio(&kt, &checksum, sizeof(checksum), IO_WRITE)) {
			cksumfail = 1;
			FAILEDWRITE(&kt);
		}
	}

	kt.kt_keyword = "eeprom writecount";
	for (i = 0; i < 4; ++i) {
		kt.kt_offset = EE_WC_LOC + (i * sizeof(writecount));
		if (doio(&kt, (u_char *)&writecount, sizeof(writecount),
		    IO_WRITE)) {
			cksumfail = 1;
			FAILEDWRITE(&kt);
		}
	}
}

void
ee_verifychecksums(void)
{
	struct keytabent kt;
	u_char checkme[EE_SIZE - EE_HWUPDATE_LOC];
	u_char checksum, ochecksum[3];
	u_short owritecount[3];

	/*
	 * Verify that the EEPROM's write counts match, and update the
	 * global copy for use later.
	 */
	kt.kt_keyword = "eeprom writecount";
	kt.kt_offset = EE_WC_LOC;
	kt.kt_handler = ee_notsupp;
	
	if (doio(&kt, (u_char *)&owritecount, sizeof(owritecount), IO_READ)) {
		cksumfail = 1;
		FAILEDREAD(&kt);
	}

	if (owritecount[0] != owritecount[1] ||
	    owritecount[0] != owritecount[2]) {
		warnx("eeprom writecount mismatch %s",
		    ignore_checksum ? "(ignoring)" :
		    (fix_checksum ? "(fixing)" : ""));

		if (!ignore_checksum && !fix_checksum) {
			cksumfail = 1;
			return;
		}

		writecount = MAXIMUM(owritecount[0], owritecount[1]);
		writecount = MAXIMUM(writecount, owritecount[2]);
	} else
		writecount = owritecount[0];

	/*
	 * Verify that the EEPROM's checksums match and are correct.
	 */
	kt.kt_keyword = "eeprom checksum";
	kt.kt_offset = EE_CKSUM_LOC;

	if (doio(&kt, ochecksum, sizeof(ochecksum), IO_READ)) {
		cksumfail = 1;
		FAILEDREAD(&kt);
	}

	if (ochecksum[0] != ochecksum[1] ||
	    ochecksum[0] != ochecksum[2]) {
		warnx("eeprom checksum mismatch %s",
		    ignore_checksum ? "(ignoring)" :
		    (fix_checksum ? "(fixing)" : ""));

		if (!ignore_checksum && !fix_checksum) {
			cksumfail = 1;
			return;
		}
	}

	kt.kt_keyword = "eeprom contents";
	kt.kt_offset = EE_HWUPDATE_LOC;

	if (doio(&kt, checkme, sizeof(checkme), IO_READ)) {
		cksumfail = 1;
		FAILEDREAD(&kt);
	}

	checksum = ee_checksum(checkme, sizeof(checkme));

	if (ochecksum[0] != checksum) {
		warnx("eeprom checksum incorrect %s",
		    ignore_checksum ? "(ignoring)" :
		    (fix_checksum ? "(fixing)" : ""));

		if (!ignore_checksum && !fix_checksum) {
			cksumfail = 1;
			return;
		}
	}

	if (fix_checksum)
		ee_updatechecksums();
}

static u_char
ee_checksum(u_char *area, size_t len)
{
	u_char sum = 0;

	while (len--)
		sum += *area++;

	return (0x100 - sum);
}
