diff options
author | sobomax <sobomax@FreeBSD.org> | 2006-08-01 22:19:01 +0000 |
---|---|---|
committer | sobomax <sobomax@FreeBSD.org> | 2006-08-01 22:19:01 +0000 |
commit | a2e1257dac349364bf2162e7c5d187107fcd8725 (patch) | |
tree | aba24c38e600543d1256c6e33ef0efd99bf3900f /sys/dev/powermac_nvram/powermac_nvram.c | |
parent | a152234cf9a52766984ecb9a96cc1e5740ae9d78 (diff) | |
download | FreeBSD-src-a2e1257dac349364bf2162e7c5d187107fcd8725.zip FreeBSD-src-a2e1257dac349364bf2162e7c5d187107fcd8725.tar.gz |
Add device to access and modify Open Firmware NVRAM settings in
PowerPC-based Apple's machines and small utility to do it from
userland modelled after the similar utility in Darwin/OSX.
Only tested on 1.25GHz G4 Mac Mini.
MFC after: 1 month
Diffstat (limited to 'sys/dev/powermac_nvram/powermac_nvram.c')
-rw-r--r-- | sys/dev/powermac_nvram/powermac_nvram.c | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/sys/dev/powermac_nvram/powermac_nvram.c b/sys/dev/powermac_nvram/powermac_nvram.c new file mode 100644 index 0000000..e131d6a --- /dev/null +++ b/sys/dev/powermac_nvram/powermac_nvram.c @@ -0,0 +1,423 @@ +/* + * Copyright (c) 2006 Maxim Sobolev <sobomax@FreeBSD.org> + * All rights reserved. + * + * 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 AUTHOR ``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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/uio.h> + +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_pci.h> + +#include <machine/bus.h> +#include <machine/md_var.h> +#include <machine/nexusvar.h> +#include <machine/resource.h> + +#include <sys/rman.h> + +#include <powerpc/ofw/ofw_pci.h> +#include <dev/powermac_nvram/powermac_nvramvar.h> + +#include <vm/vm.h> +#include <vm/pmap.h> + +/* + * Device interface. + */ +static int powermac_nvram_probe(device_t); +static int powermac_nvram_attach(device_t); +static int powermac_nvram_detach(device_t); + +/* Helper functions */ +static int powermac_nvram_check(void *data); +static int chrp_checksum(int sum, uint8_t *, uint8_t *); +static uint32_t adler_checksum(uint8_t *, int); +static int erase_bank(device_t, uint8_t *); +static int write_bank(device_t, uint8_t *, uint8_t *); + +/* + * Driver methods. + */ +static device_method_t powermac_nvram_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, powermac_nvram_probe), + DEVMETHOD(device_attach, powermac_nvram_attach), + DEVMETHOD(device_detach, powermac_nvram_detach), + + { 0, 0 } +}; + +static driver_t powermac_nvram_driver = { + "powermac_nvram", + powermac_nvram_methods, + sizeof(struct powermac_nvram_softc) +}; + +static devclass_t powermac_nvram_devclass; + +DRIVER_MODULE(powermac_nvram, nexus, powermac_nvram_driver, powermac_nvram_devclass, 0, 0); + +/* + * Cdev methods. + */ + +#define NVRAM_UNIT(dev) minor(dev) +#define NVRAM_SOFTC(unit) ((struct powermac_nvram_softc *) \ + devclass_get_softc(powermac_nvram_devclass, unit)) + +static d_open_t powermac_nvram_open; +static d_close_t powermac_nvram_close; +static d_read_t powermac_nvram_read; +static d_write_t powermac_nvram_write; + +static struct cdevsw powermac_nvram_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_NEEDGIANT, + .d_open = powermac_nvram_open, + .d_close = powermac_nvram_close, + .d_read = powermac_nvram_read, + .d_write = powermac_nvram_write, + .d_name = "powermac_nvram", +}; + +static int +powermac_nvram_probe(device_t dev) +{ + char *type, *compatible; + + type = nexus_get_device_type(dev); + compatible = nexus_get_compatible(dev); + + if (type == NULL || compatible == NULL) + return ENXIO; + + if (strcmp(type, "nvram") != 0 || strcmp(compatible, "amd-0137") != 0) + return ENXIO; + + device_set_desc(dev, "Apple NVRAM"); + return 0; +} + +static int +powermac_nvram_attach(device_t dev) +{ + struct powermac_nvram_softc *sc; + phandle_t node; + u_int32_t reg[2]; + int gen0, gen1; + + node = nexus_get_node(dev); + sc = device_get_softc(dev); + + if (OF_getprop(node, "reg", reg, sizeof(reg)) < 8) + return ENXIO; + + sc->sc_dev = dev; + sc->sc_node = node; + + sc->sc_bank0 = (vm_offset_t)pmap_mapdev(reg[0], NVRAM_SIZE * 2); + sc->sc_bank1 = sc->sc_bank0 + NVRAM_SIZE; + + gen0 = powermac_nvram_check((void *)sc->sc_bank0); + gen1 = powermac_nvram_check((void *)sc->sc_bank1); + + if (gen0 == -1 && gen1 == -1) { + if ((void *)sc->sc_bank0 != NULL) + pmap_unmapdev(sc->sc_bank0, NVRAM_SIZE * 2); + device_printf(dev, "both banks appear to be corrupt\n"); + return ENXIO; + } + device_printf(dev, "bank0 generation %d, bank1 generation %d\n", + gen0, gen1); + + sc->sc_bank = (gen0 > gen1) ? sc->sc_bank0 : sc->sc_bank1; + bcopy((void *)sc->sc_bank, (void *)sc->sc_data, NVRAM_SIZE); + + sc->sc_cdev = make_dev(&powermac_nvram_cdevsw, 0, 0, 0, 0600, + "powermac_nvram"); + + return 0; +} + +static int +powermac_nvram_detach(device_t dev) +{ + struct powermac_nvram_softc *sc; + + sc = device_get_softc(dev); + + if ((void *)sc->sc_bank0 != NULL) + pmap_unmapdev(sc->sc_bank0, NVRAM_SIZE * 2); + + if (sc->sc_cdev != NULL) + destroy_dev(sc->sc_cdev); + + return 0; +} + +static int +powermac_nvram_open(struct cdev *dev, int flags, int fmt, struct thread *td) +{ + struct powermac_nvram_softc *sc; + + sc = NVRAM_SOFTC(NVRAM_UNIT(dev)); + if (sc->sc_isopen) + return EBUSY; + sc->sc_isopen = 1; + sc->sc_rpos = sc->sc_wpos = 0; + return 0; +} + +static int +powermac_nvram_close(struct cdev *dev, int fflag, int devtype, struct thread *td) +{ + struct powermac_nvram_softc *sc; + struct core99_header *header; + vm_offset_t bank; + + sc = NVRAM_SOFTC(NVRAM_UNIT(dev)); + + if (sc->sc_wpos != sizeof(sc->sc_data)) { + /* Short write, restore in-memory copy */ + bcopy((void *)sc->sc_bank, (void *)sc->sc_data, NVRAM_SIZE); + sc->sc_isopen = 0; + return 0; + } + + header = (struct core99_header *)sc->sc_data; + + header->generation = ((struct core99_header *)sc->sc_bank)->generation; + header->generation++; + header->chrp_header.signature = CORE99_SIGNATURE; + + header->adler_checksum = + adler_checksum((uint8_t *)&(header->generation), + NVRAM_SIZE - offsetof(struct core99_header, generation)); + header->chrp_header.chrp_checksum = chrp_checksum(header->chrp_header.signature, + (uint8_t *)&(header->chrp_header.length), + (uint8_t *)&(header->adler_checksum)); + + bank = (sc->sc_bank == sc->sc_bank0) ? sc->sc_bank1 : sc->sc_bank0; + if (erase_bank(sc->sc_dev, (uint8_t *)bank) != 0 || + write_bank(sc->sc_dev, (uint8_t *)bank, sc->sc_data) != 0) { + sc->sc_isopen = 0; + return ENOSPC; + } + sc->sc_bank = bank; + sc->sc_isopen = 0; + return 0; +} + +static int +powermac_nvram_read(struct cdev *dev, struct uio *uio, int ioflag) +{ + int rv, amnt, data_available; + struct powermac_nvram_softc *sc; + + sc = NVRAM_SOFTC(NVRAM_UNIT(dev)); + + rv = 0; + while (uio->uio_resid > 0) { + data_available = sizeof(sc->sc_data) - sc->sc_rpos; + if (data_available > 0) { + amnt = MIN(uio->uio_resid, data_available); + rv = uiomove((void *)(sc->sc_data + sc->sc_rpos), + amnt, uio); + if (rv != 0) + break; + sc->sc_rpos += amnt; + } else { + break; + } + } + return rv; +} + +static int +powermac_nvram_write(struct cdev *dev, struct uio *uio, int ioflag) +{ + int rv, amnt, data_available; + struct powermac_nvram_softc *sc; + + sc = NVRAM_SOFTC(NVRAM_UNIT(dev)); + + if (sc->sc_wpos >= sizeof(sc->sc_data)) + return EINVAL; + + rv = 0; + while (uio->uio_resid > 0) { + data_available = sizeof(sc->sc_data) - sc->sc_wpos; + if (data_available > 0) { + amnt = MIN(uio->uio_resid, data_available); + rv = uiomove((void *)(sc->sc_data + sc->sc_wpos), + amnt, uio); + if (rv != 0) + break; + sc->sc_wpos += amnt; + } else { + break; + } + } + return rv; +} + +static int +powermac_nvram_check(void *data) +{ + struct core99_header *header; + + header = (struct core99_header *)data; + + if (header->chrp_header.signature != CORE99_SIGNATURE) + return -1; + if (header->chrp_header.chrp_checksum != + chrp_checksum(header->chrp_header.signature, + (uint8_t *)&(header->chrp_header.length), + (uint8_t *)&(header->adler_checksum))) + return -1; + if (header->adler_checksum != + adler_checksum((uint8_t *)&(header->generation), + NVRAM_SIZE - offsetof(struct core99_header, generation))) + return -1; + return header->generation; +} + +static int +chrp_checksum(int sum, uint8_t *data, uint8_t *end) +{ + + for (; data < end; data++) + sum += data[0]; + while (sum > 0xff) + sum = (sum & 0xff) + (sum >> 8); + return sum; +} + +static uint32_t +adler_checksum(uint8_t *data, int len) +{ + uint32_t low, high; + int i; + + low = 1; + high = 0; + for (i = 0; i < len; i++) { + if ((i % 5000) == 0) { + high %= 65521UL; + high %= 65521UL; + } + low += data[i]; + high += low; + } + low %= 65521UL; + high %= 65521UL; + + return (high << 16) | low; +} + +#define OUTB_DELAY(a, v) outb(a, v); DELAY(1); + +static int +wait_operation_complete(uint8_t *bank) +{ + int i; + + for (i = 1000000; i != 0; i--) + if ((inb(bank) ^ inb(bank)) == 0) + return 0; + return -1; +} + +static int +erase_bank(device_t dev, uint8_t *bank) +{ + unsigned int i; + + /* Unlock 1 */ + OUTB_DELAY(bank + 0x555, 0xaa); + /* Unlock 2 */ + OUTB_DELAY(bank + 0x2aa, 0x55); + + /* Sector-Erase */ + OUTB_DELAY(bank + 0x555, 0x80); + OUTB_DELAY(bank + 0x555, 0xaa); + OUTB_DELAY(bank + 0x2aa, 0x55); + OUTB_DELAY(bank, 0x30); + + if (wait_operation_complete(bank) != 0) { + device_printf(dev, "flash erase timeout\n"); + return -1; + } + + /* Reset */ + OUTB_DELAY(bank, 0xf0); + + for (i = 0; i < NVRAM_SIZE; i++) { + if (bank[i] != 0xff) { + device_printf(dev, "flash erase has failed\n"); + return -1; + } + } + return 0; +} + +static int +write_bank(device_t dev, uint8_t *bank, uint8_t *data) +{ + unsigned int i; + + for (i = 0; i < NVRAM_SIZE; i++) { + /* Unlock 1 */ + OUTB_DELAY(bank + 0x555, 0xaa); + /* Unlock 2 */ + OUTB_DELAY(bank + 0x2aa, 0x55); + + /* Write single word */ + OUTB_DELAY(bank + 0x555, 0xa0); + OUTB_DELAY(bank + i, data[i]); + if (wait_operation_complete(bank) != 0) { + device_printf(dev, "flash write timeout\n"); + return -1; + } + } + + /* Reset */ + OUTB_DELAY(bank, 0xf0); + + for (i = 0; i < NVRAM_SIZE; i++) { + if (bank[i] != data[i]) { + device_printf(dev, "flash write has failed\n"); + return -1; + } + } + return 0; +} |