/* $NetBSD: nvram_pnpbus.c,v 1.21 2018/09/03 16:29:26 riastradh 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 __KERNEL_RCSID(0, "$NetBSD: nvram_pnpbus.c,v 1.21 2018/09/03 16:29:26 riastradh Exp $"); #include #include #include #include #include #include #include #include #include #include #include /* clock stuff for motorolla machines */ #include #include #include #include #include #include #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_NOWAIT); 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); }