/* $NetBSD: nvram_pnpbus.c,v 1.22 2019/11/10 21:16:32 chs Exp $ */

/*-
* Copyright (c) 2006 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Tim Rightnour
*
* 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/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: nvram_pnpbus.c,v 1.22 2019/11/10 21:16:32 chs Exp $");

#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/ioctl.h>
#include <sys/conf.h>
#include <sys/kthread.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/bus.h>
#include <sys/intr.h>

#include <machine/isa_machdep.h>
/* clock stuff for motorolla machines */
#include <dev/clock_subr.h>
#include <dev/ic/mk48txxreg.h>

#include <uvm/uvm_extern.h>

#include <machine/residual.h>
#include <machine/nvram.h>

#include <prep/pnpbus/pnpbusvar.h>

#include "opt_nvram.h"

static char *nvramData;
static NVRAM_MAP *nvram;
static char *nvramGEAp;         /* pointer to the GE area */
static char *nvramCAp;          /* pointer to the Config area */
static char *nvramOSAp;         /* pointer to the OSArea */

int prep_clock_mk48txx;

extern char bootpath[256];
extern RESIDUAL resdata;

#define NVRAM_STD_DEV 0

static int      nvram_pnpbus_probe(device_t, cfdata_t, void *);
static void     nvram_pnpbus_attach(device_t, device_t, void *);
uint8_t         prep_nvram_read_val(int);
char            *prep_nvram_next_var(char *);
char            *prep_nvram_find_var(const char *);
char            *prep_nvram_get_var(const char *);
int             prep_nvram_get_var_len(const char *);
int             prep_nvram_count_vars(void);
void            prep_nvram_write_val(int, uint8_t);
uint8_t         mkclock_pnpbus_nvrd(struct mk48txx_softc *, int);
void            mkclock_pnpbus_nvwr(struct mk48txx_softc *, int, uint8_t);

CFATTACH_DECL_NEW(nvram_pnpbus, sizeof(struct nvram_pnpbus_softc),
   nvram_pnpbus_probe, nvram_pnpbus_attach, NULL, NULL);

dev_type_open(prep_nvramopen);
dev_type_ioctl(prep_nvramioctl);
dev_type_close(prep_nvramclose);
dev_type_read(prep_nvramread);

const struct cdevsw nvram_cdevsw = {
       .d_open = prep_nvramopen,
       .d_close = prep_nvramclose,
       .d_read = prep_nvramread,
       .d_write = nowrite,
       .d_ioctl = prep_nvramioctl,
       .d_stop = nostop,
       .d_tty = notty,
       .d_poll = nopoll,
       .d_mmap = nommap,
       .d_kqfilter = nokqfilter,
       .d_discard = nodiscard,
       .d_flag = D_OTHER,
};

extern struct cfdriver nvram_cd;

static int
nvram_pnpbus_probe(device_t parent, cfdata_t match, void *aux)
{
       struct pnpbus_dev_attach_args *pna = aux;
       int ret = 0;

       if (strcmp(pna->pna_devid, "IBM0008") == 0)
               ret = 1;

       if (ret)
               pnpbus_scan(pna, pna->pna_ppc_dev);

       return ret;
}

static void
nvram_pnpbus_attach(device_t parent, device_t self, void *aux)
{
       struct nvram_pnpbus_softc *sc = device_private(self);
       struct pnpbus_dev_attach_args *pna = aux;
       int as_iobase, as_len, data_iobase, data_len, i, nvlen, cur;
       uint8_t *p;
       HEADER prep_nvram_header;

       sc->sc_iot = pna->pna_iot;

       pnpbus_getioport(&pna->pna_res, 0, &as_iobase, &as_len);
       pnpbus_getioport(&pna->pna_res, 1, &data_iobase, &data_len);

       if (pnpbus_io_map(&pna->pna_res, 0, &sc->sc_as, &sc->sc_ash) ||
           pnpbus_io_map(&pna->pna_res, 1, &sc->sc_data, &sc->sc_datah)) {
               aprint_error("nvram: couldn't map registers\n");
               return;
       }

       /* Initialize the nvram header */
       p = (uint8_t *) &prep_nvram_header;
       for (i = 0; i < sizeof(HEADER); i++)
               *p++ = prep_nvram_read_val(i);

       /*
        * now that we have the header, we know how big the NVRAM part on
        * this machine really is.  Malloc space to save a copy.
        */

       nvlen = 1024 * prep_nvram_header.Size;
       nvramData = malloc(nvlen, M_DEVBUF, M_WAITOK);
       p = (uint8_t *) nvramData;

       /*
        * now read the whole nvram in, one chunk at a time, marking down
        * the main start points as we go.
        */
       for (i = 0; i < sizeof(HEADER) && i < nvlen; i++)
               *p++ = prep_nvram_read_val(i);
       nvramGEAp = p;
       cur = i;
       for (; i < cur + prep_nvram_header.GELength && i < nvlen; i++)
               *p++ = prep_nvram_read_val(i);
       nvramOSAp = p;
       cur = i;
       for (; i < cur + prep_nvram_header.OSAreaLength && i < nvlen; i++)
               *p++ = prep_nvram_read_val(i);
       nvramCAp = p;
       cur = i;
       for (; i < cur + prep_nvram_header.ConfigLength && i < nvlen; i++)
               *p++ = prep_nvram_read_val(i);

       /* we should be done here.  umm.. yay? */
       nvram = (NVRAM_MAP *)&nvramData[0];
       aprint_normal("\n");
       aprint_verbose("%s: Read %d bytes from nvram of size %d\n",
           device_xname(self), i, nvlen);

#if defined(NVRAM_DUMP)
       printf("Boot device: %s\n", prep_nvram_get_var("fw-boot-device"));
       printf("Dumping nvram\n");
       for (cur=0; cur < i; cur++) {
               printf("%c", nvramData[cur]);
               if (cur % 70 == 0)
                       printf("\n");
       }
#endif
       strncpy(bootpath, prep_nvram_get_var("fw-boot-device"), 256);


       if (prep_clock_mk48txx == 0)
               return;
       /* otherwise, we have a motorolla clock chip.  Set it up. */
       sc->sc_mksc.sc_model = "mk48t18";
       sc->sc_mksc.sc_year0 = 1900;
       sc->sc_mksc.sc_nvrd = mkclock_pnpbus_nvrd;
       sc->sc_mksc.sc_nvwr = mkclock_pnpbus_nvwr;
       /* copy down the bus space tags */
       sc->sc_mksc.sc_bst = sc->sc_as;
       sc->sc_mksc.sc_bsh = sc->sc_ash;
       sc->sc_mksc.sc_data = sc->sc_data;
       sc->sc_mksc.sc_datah = sc->sc_datah;

       aprint_normal("%s: attaching clock", device_xname(self));
       mk48txx_attach((struct mk48txx_softc *)&sc->sc_mksc);
       aprint_normal("\n");
}

/*
* This function should be called at a high spl only, as it interfaces with
* real hardware.
*/

uint8_t
prep_nvram_read_val(int addr)
{
       struct nvram_pnpbus_softc *sc;

       sc = device_lookup_private(&nvram_cd, NVRAM_STD_DEV);
       if (sc == NULL)
               return 0;

       /* tell the NVRAM what we want */
       bus_space_write_1(sc->sc_as, sc->sc_ash, 0, addr);
       bus_space_write_1(sc->sc_as, sc->sc_ash, 1, addr>>8);

       return bus_space_read_1(sc->sc_data, sc->sc_datah, 0);
}

/*
* This function should be called at a high spl only, as it interfaces with
* real hardware.
*/

void
prep_nvram_write_val(int addr, uint8_t val)
{
       struct nvram_pnpbus_softc *sc;

       sc = device_lookup_private(&nvram_cd, NVRAM_STD_DEV);
       if (sc == NULL)
               return;

       /* tell the NVRAM what we want */
       bus_space_write_1(sc->sc_as, sc->sc_ash, 0, addr);
       bus_space_write_1(sc->sc_as, sc->sc_ash, 1, addr>>8);

       bus_space_write_1(sc->sc_data, sc->sc_datah, 0, val);
}

/* the rest of these should all be called with the lock held */

char *
prep_nvram_next_var(char *name)
{
       char *cp;

       if (name == NULL)
               return NULL;

       cp = name;
       /* skip forward to the first null char */
       while ((cp - nvramGEAp) < nvram->Header.GELength && (*cp != '\0'))
               cp++;
       /* skip nulls */
       while ((cp - nvramGEAp) < nvram->Header.GELength && (*cp == '\0'))
               cp++;
       if ((cp - nvramGEAp) < nvram->Header.GELength)
               return cp;
       else
               return NULL;
}

char *
prep_nvram_find_var(const char *name)
{
       char *cp = nvramGEAp;
       size_t len;

       len = strlen(name);
       while (cp != NULL) {
               if ((strncmp(name, cp, len) == 0) && (cp[len] == '='))
                       return cp;
               cp = prep_nvram_next_var(cp);
       }
       return NULL;
}

char *
prep_nvram_get_var(const char *name)
{
       char *cp = nvramGEAp;
       size_t len;

       if (name == NULL)
               return NULL;
       len = strlen(name);
       while (cp != NULL) {
               if ((strncmp(name, cp, len) == 0) && (cp[len] == '='))
                       return cp+len+1;
               cp = prep_nvram_next_var(cp);
       }
       return NULL;
}

int
prep_nvram_get_var_len(const char *name)
{
       char *cp = nvramGEAp;
       char *ep;
       size_t len;

       if (name == NULL)
               return -1;

       len = strlen(name);
       while (cp != NULL) {
               if ((strncmp(name, cp, len) == 0) && (cp[len] == '='))
                       goto out;
               cp = prep_nvram_next_var(cp);
       }
       return -1;

out:
       ep = cp;
       while (ep != NULL && *ep != '\0')
               ep++;
       return ep-cp;
}

int
prep_nvram_count_vars(void)
{
       char *cp = nvramGEAp;
       int i=0;

       while (cp != NULL) {
               i++;
               cp = prep_nvram_next_var(cp);
       }
       return i;
}

static int
nvramgetstr(int len, char *user, char **cpp)
{
       int error;
       char *cp;

       /* Reject obvious bogus requests */
       if ((u_int)len > (8 * 1024) - 1)
               return ENAMETOOLONG;

       *cpp = cp = malloc(len + 1, M_TEMP, M_WAITOK);
       error = copyin(user, cp, len);
       cp[len] = '\0';
       return error;
}

int
prep_nvramioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l)
{
       int len, error;
       struct pnviocdesc *pnv;
       char *np, *cp, *name;

       pnv = (struct pnviocdesc *)data;
       error = 0;
       cp = name = NULL;

       switch (cmd) {
       case PNVIOCGET:
               if (pnv->pnv_name == NULL)
                       return EINVAL;

               error = nvramgetstr(pnv->pnv_namelen, pnv->pnv_name, &name);
               np = prep_nvram_get_var(name);
               if (np == NULL)
                       return EINVAL;
               len = prep_nvram_get_var_len(name);

               if (len > pnv->pnv_buflen) {
                       error = ENOMEM;
                       break;
               }
               if (len <= 0)
                       break;
               error = copyout(np, pnv->pnv_buf, len);
               pnv->pnv_buflen = len;
               break;

       case PNVIOCGETNEXTNAME:
               /* if the first one is null, we give them the first name */
               if (pnv->pnv_name == NULL) {
                       cp = nvramGEAp;
               } else {
                       error = nvramgetstr(pnv->pnv_namelen, pnv->pnv_name,
                           &name);
                       if (!error) {
                               np = prep_nvram_find_var(name);
                               cp = prep_nvram_next_var(np);
                       }
               }
               if (cp == NULL)
                       error = EINVAL;
               if (error)
                       break;

               np = cp;
               while (*np != '=')
                       np++;
               len = np-cp;
               if (len > pnv->pnv_buflen) {
                       error = ENOMEM;
                       break;
               }
               error = copyout(cp, pnv->pnv_buf, len);
               if (error)
                       break;
               pnv->pnv_buflen = len;
               break;

       case PNVIOCGETNUMGE:
               /* count the GE variables */
               pnv->pnv_num = prep_nvram_count_vars();
               break;
       case PNVIOCSET:
               /* this will require some real work.  Not ready yet */
               return ENOTSUP;

       default:
               return ENOTTY;
       }
       if (name)
               free(name, M_TEMP);
       return error;
}

int
prep_nvramread(dev_t dev, struct uio *uio, int flags)
{
       int size, resid, error;
       u_int c;
       char *rdata;

       error = 0;
       rdata = (char *)&resdata;

       if (uio->uio_rw == UIO_WRITE) {
               uio->uio_resid = 0;
               return 0;
       }

       switch (minor(dev)) {
       case DEV_NVRAM:
               size = nvram->Header.Size * 1024;
               break;
       case DEV_RESIDUAL:
               size = res->ResidualLength;
               break;
       default:
               return ENXIO;
       }
       resid = size;
       if (uio->uio_resid < resid)
               resid = uio->uio_resid;
       while (resid > 0 && error == 0 && uio->uio_offset < size) {
               switch (minor(dev)) {
               case DEV_NVRAM:
                       c = uimin(resid, PAGE_SIZE);
                       error = uiomove(&nvramData[uio->uio_offset], c, uio);
                       break;
               case DEV_RESIDUAL:
                       c = uimin(resid, PAGE_SIZE);
                       error = uiomove(&rdata[uio->uio_offset], c, uio);
                       break;
               default:
                       return ENXIO;
               }
       }
       return error;
}

int
prep_nvramopen(dev_t dev, int flags, int mode, struct lwp *l)
{
       struct nvram_pnpbus_softc *sc;

       sc = device_lookup_private(&nvram_cd, NVRAM_STD_DEV);
       if (sc == NULL)
               return ENODEV;

       if (sc->sc_open)
               return EBUSY;

       sc->sc_open = 1;

       return 0;
}

int
prep_nvramclose(dev_t dev, int flags, int mode, struct lwp *l)
{
       struct nvram_pnpbus_softc *sc;

       sc = device_lookup_private(&nvram_cd, NVRAM_STD_DEV);
       if (sc == NULL)
               return ENODEV;
       sc->sc_open = 0;
       return 0;
}

/* Motorola mk48txx clock routines */
uint8_t
mkclock_pnpbus_nvrd(struct mk48txx_softc *osc, int off)
{
       struct prep_mk48txx_softc *sc = (struct prep_mk48txx_softc *)osc;
       uint8_t datum;
       int s;

#ifdef DEBUG
       aprint_debug("mkclock_pnpbus_nvrd(%d)", off);
#endif
       s = splclock();
       bus_space_write_1(sc->sc_bst, sc->sc_bsh, 0, off & 0xff);
       bus_space_write_1(sc->sc_bst, sc->sc_bsh, 1, off >> 8);
       datum = bus_space_read_1(sc->sc_data, sc->sc_datah, 0);
       splx(s);
#ifdef DEBUG
       aprint_debug(" -> %02x\n", datum);
#endif
       return datum;
}

void
mkclock_pnpbus_nvwr(struct mk48txx_softc *osc, int off, uint8_t datum)
{
       struct prep_mk48txx_softc *sc = (struct prep_mk48txx_softc *)osc;
       int s;

#ifdef DEBUG
       aprint_debug("mkclock_isa_nvwr(%d, %02x)\n", off, datum);
#endif
       s = splclock();
       bus_space_write_1(sc->sc_bst, sc->sc_bsh, 0, off & 0xff);
       bus_space_write_1(sc->sc_bst, sc->sc_bsh, 1, off >> 8);
       bus_space_write_1(sc->sc_data, sc->sc_datah, 0, datum);
       splx(s);
}