/*      $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);
}