diff --git a/sys/conf/files.i386 b/sys/conf/files.i386 index 7ee353fb4185..3e1a2b395377 100644 --- a/sys/conf/files.i386 +++ b/sys/conf/files.i386 @@ -137,6 +137,7 @@ dev/fb/fb.c optional vga dev/fb/splash.c optional splash dev/fb/vga.c optional vga dev/fe/if_fe_isa.c optional fe isa +dev/ichwd/ichwd.c optional ichwd dev/if_ndis/if_ndis.c optional ndis dev/if_ndis/if_ndis_pccard.c optional ndis card dev/if_ndis/if_ndis_pccard.c optional ndis pccard diff --git a/sys/dev/ichwd/ichwd.c b/sys/dev/ichwd/ichwd.c new file mode 100644 index 000000000000..0321ca1fa86a --- /dev/null +++ b/sys/dev/ichwd/ichwd.c @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2004 Texas A&M University + * All rights reserved. + * + * Developer: Wm. Daryl Hawkins + * + * 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 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 AUTHOR 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. + */ + +/* + * Intel ICH Watchdog Timer (WDT) driver + * + * Originally developed by Wm. Daryl Hawkins of Texas A&M + * Heavily modified by + * + * This is a tricky one. The ICH WDT can't be treated as a regular PCI + * device as it's actually an integrated function of the ICH LPC interface + * bridge. Detection is also awkward, because we can only infer the + * presence of the watchdog timer from the fact that the machine has an + * ICH chipset, or, on ACPI 2.x systems, by the presence of the 'WDDT' + * ACPI table (although this driver does not support the ACPI detection + * method). + * + * There is one slight problem on non-ACPI or ACPI 1.x systems: we have no + * way of knowing if the WDT is permanently disabled (either by the BIOS + * or in hardware). + * + * The WDT is programmed through I/O registers in the ACPI I/O space. + * Intel swears it's always at offset 0x60, so we use that. + * + * For details about the ICH WDT, see Intel Application Note AP-725 + * (document no. 292273-001). The WDT is also described in the individual + * chipset datasheets, e.g. Intel82801EB ICH5 / 82801ER ICH5R Datasheet + * (document no. 252516-001) sections 9.10 and 9.11. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +static struct ichwd_device ichwd_devices[] = { + { VENDORID_INTEL, DEVICEID_82801AA, "Intel 82801AA watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801AB, "Intel 82801AB watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801BA, "Intel 82801BA watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801BAM, "Intel 82801BAM watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801CA, "Intel 82801CA watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801CAM, "Intel 82801CAM watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801DB, "Intel 82801DB watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801DBM, "Intel 82801DBM watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801E, "Intel 82801E watchdog timer" }, + { VENDORID_INTEL, DEVICEID_82801EBR, "Intel 82801EB/ER watchdog timer" }, + { 0, 0, NULL }, +}; + +static devclass_t ichwd_devclass; + +#define ichwd_read_1(sc, off) \ + bus_space_read_1((sc)->smi_bst, (sc)->smi_bsh, (off)) +#define ichwd_read_2(sc, off) \ + bus_space_read_2((sc)->smi_bst, (sc)->smi_bsh, (off)) +#define ichwd_read_4(sc, off) \ + bus_space_read_4((sc)->smi_bst, (sc)->smi_bsh, (off)) + +#define ichwd_write_1(sc, off, val) \ + bus_space_write_1((sc)->smi_bst, (sc)->smi_bsh, (off), (val)) +#define ichwd_write_2(sc, off, val) \ + bus_space_write_2((sc)->smi_bst, (sc)->smi_bsh, (off), (val)) +#define ichwd_write_4(sc, off, val) \ + bus_space_write_4((sc)->smi_bst, (sc)->smi_bsh, (off), (val)) + +static __inline void +ichwd_intr_enable(struct ichwd_softc *sc) +{ + ichwd_write_4(sc, SMI_EN, ichwd_read_4(sc, SMI_EN) | SMI_TCO_EN); +} + +static __inline void +ichwd_intr_disable(struct ichwd_softc *sc) +{ + ichwd_write_4(sc, SMI_EN, ichwd_read_4(sc, SMI_EN) & ~SMI_TCO_EN); +} + +static __inline void +ichwd_sts_reset(struct ichwd_softc *sc) +{ + ichwd_write_2(sc, TCO1_STS, TCO_TIMEOUT); + ichwd_write_2(sc, TCO2_STS, TCO_BOOT_STS); + ichwd_write_2(sc, TCO2_STS, TCO_SECOND_TO_STS); +} + +static __inline void +ichwd_tmr_enable(struct ichwd_softc *sc) +{ + uint16_t cnt; + + cnt = ichwd_read_2(sc, TCO1_CNT) & TCO_CNT_PRESERVE; + ichwd_write_2(sc, TCO1_CNT, cnt & ~TCO_TMR_HALT); + sc->active = 1; + if (bootverbose) + device_printf(sc->device, "timer enabled\n"); +} + +static __inline void +ichwd_tmr_disable(struct ichwd_softc *sc) +{ + uint16_t cnt; + + cnt = ichwd_read_2(sc, TCO1_CNT) & TCO_CNT_PRESERVE; + ichwd_write_2(sc, TCO1_CNT, cnt | TCO_TMR_HALT); + sc->active = 0; + if (bootverbose) + device_printf(sc->device, "timer disabled\n"); +} + +static __inline void +ichwd_tmr_reload(struct ichwd_softc *sc) +{ + ichwd_write_1(sc, TCO_RLD, 1); + if (bootverbose) + device_printf(sc->device, "timer reloaded\n"); +} + +static __inline void +ichwd_tmr_set(struct ichwd_softc *sc, uint8_t timeout) +{ + ichwd_write_1(sc, TCO_TMR, timeout); + sc->timeout = timeout; + if (bootverbose) + device_printf(sc->device, "timeout set to %u ticks\n", timeout); +} + +/* + * Watchdog event handler. + */ +static void +ichwd_event(void *arg, unsigned int cmd, int *error) +{ + struct ichwd_softc *sc = arg; + unsigned int timeout; + + cmd &= WD_INTERVAL; + + /* disable / enable */ + if (cmd == 0) { + if (sc->active) + ichwd_tmr_disable(sc); + *error = 0; + return; + } + if (!sc->active) + ichwd_tmr_enable(sc); + + /* convert from power-of-to-ns to WDT ticks */ + if (cmd >= 64) { + *error = EINVAL; + return; + } + timeout = ((uint64_t)1 << cmd) / ICHWD_TICK; + if (timeout < ICHWD_MIN_TIMEOUT || timeout > ICHWD_MAX_TIMEOUT) { + *error = EINVAL; + return; + } + + /* set new initial value */ + if (timeout != sc->timeout) + ichwd_tmr_set(sc, timeout); + + /* reload */ + ichwd_tmr_reload(sc); + + *error = 0; + return; +} + +static int +ichwd_probe(device_t dev) +{ + (void)dev; + return (0); +} + +static unsigned long pmbase; + +/* + * Look for an ICH LPC interface bridge. If one is found, register an + * ichwd device. There can be only one. + */ +static void +ichwd_identify(driver_t *driver, device_t parent) +{ + struct ichwd_device *id; + device_t ich = NULL; + device_t dev; + + printf("%s()\n", __func__); + + /* look for an ICH LPC interface bridge */ + for (id = ichwd_devices; id->desc != NULL; ++id) + if ((ich = pci_find_device(id->vendor, id->device)) != NULL) + break; + if (ich == NULL) + return; + + if (bootverbose) + printf("%s(): found ICH chipset: %s\n", __func__, id->desc); + + /* get for ACPI base address */ + pmbase = pci_read_config(ich, ICH_PMBASE, 2) & ICH_PMBASE_MASK; + if (pmbase == 0) { + if (bootverbose) + printf("%s(): ICH PMBASE register is empty\n", + __func__); + return; + } + + /* try to clear the NO_REBOOT bit */ + pci_write_config(ich, ICH_GEN_STA, 0x00, 1); + if (pci_read_config(ich, ICH_GEN_STA, 1) & ICH_GEN_STA_NO_REBOOT) { + if (bootverbose) + printf("%s(): ICH WDT present but disabled\n", + __func__); + return; + } + + /* good, add child to bus */ + if ((dev = device_find_child(parent, driver->name, 0)) == NULL) + dev = BUS_ADD_CHILD(parent, 0, driver->name, -1); + device_set_desc_copy(dev, id->desc); + device_set_driver(dev, driver); +} + +static int +ichwd_attach(device_t dev) +{ + struct ichwd_softc *sc; + + device_printf(dev, "attaching\n"); + + sc = device_get_softc(dev); + sc->device = dev; + + /* allocate I/O register space */ + sc->smi_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->smi_rid, + pmbase + SMI_BASE, pmbase + SMI_BASE + SMI_LEN - 1, SMI_LEN, + RF_ACTIVE|RF_SHAREABLE); + if (sc->smi_res == NULL) { + device_printf(dev, "unable to reserve SMI registers\n"); + goto fail; + } + sc->smi_bst = rman_get_bustag(sc->smi_res); + sc->smi_bsh = rman_get_bushandle(sc->smi_res); + sc->tco_res = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->tco_rid, + pmbase + TCO_BASE, pmbase + TCO_BASE + TCO_LEN - 1, TCO_LEN, + RF_ACTIVE|RF_SHAREABLE); + if (sc->tco_res == NULL) { + device_printf(dev, "unable to reserve TCO registers\n"); + goto fail; + } + sc->tco_bst = rman_get_bustag(sc->tco_res); + sc->tco_bsh = rman_get_bushandle(sc->tco_res); + + /* reset the watchdog status registers */ + ichwd_sts_reset(sc); + + /* make sure the WDT starts out inactive */ + ichwd_tmr_disable(sc); + + /* register the watchdog event handler */ + sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, ichwd_event, sc, 0); + + /* enable watchdog timeout interrupts */ + ichwd_intr_enable(sc); + + return (0); + fail: + sc = device_get_softc(dev); + if (sc->tco_res != NULL) + bus_release_resource(dev, SYS_RES_IOPORT, + sc->tco_rid, sc->tco_res); + if (sc->smi_res != NULL) + bus_release_resource(dev, SYS_RES_IOPORT, + sc->smi_rid, sc->smi_res); + return (ENXIO); +} + +static int +ichwd_detach(device_t dev) +{ + struct ichwd_softc *sc; + + device_printf(dev, "detaching\n"); + + sc = device_get_softc(dev); + + /* halt the watchdog timer */ + if (sc->active) + ichwd_tmr_disable(sc); + + /* disable watchdog timeout interrupts */ + ichwd_intr_disable(sc); + + /* deregister event handler */ + if (sc->ev_tag != NULL) + EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); + sc->ev_tag = NULL; + + /* reset the watchdog status registers */ + ichwd_sts_reset(sc); + + /* deallocate I/O register space */ + bus_release_resource(dev, SYS_RES_IOPORT, sc->tco_rid, sc->tco_res); + bus_release_resource(dev, SYS_RES_IOPORT, sc->smi_rid, sc->smi_res); + + return (0); +} + +static device_method_t ichwd_methods[] = { + DEVMETHOD(device_identify, ichwd_identify), + DEVMETHOD(device_probe, ichwd_probe), + DEVMETHOD(device_attach, ichwd_attach), + DEVMETHOD(device_detach, ichwd_detach), + {0,0} +}; + +static driver_t ichwd_driver = { + "ichwd", + ichwd_methods, + sizeof(struct ichwd_softc), +}; + +static int +ichwd_modevent(module_t mode, int type, void *data) +{ + int error = 0; + + switch (type) { + case MOD_LOAD: + printf("ichwd module loaded\n"); + break; + case MOD_UNLOAD: + printf("ichwd module unloaded\n"); + break; + case MOD_SHUTDOWN: + printf("ichwd module shutting down\n"); + break; + } + return (error); +} + +DRIVER_MODULE(ichwd, nexus, ichwd_driver, ichwd_devclass, ichwd_modevent, NULL); +/* + * this doesn't seem to work, though I can't figure out why. + * currently not a big issue since watchdog is standard. +MODULE_DEPEND(ichwd, watchdog, 1, 1, 1); + */ diff --git a/sys/dev/ichwd/ichwd.h b/sys/dev/ichwd/ichwd.h new file mode 100644 index 000000000000..ab0e117fbdf8 --- /dev/null +++ b/sys/dev/ichwd/ichwd.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2004 Texas A&M University + * All rights reserved. + * + * Developer: Wm. Daryl Hawkins + * + * 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 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 AUTHOR 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. + * + * $FreeBSD$ + */ + +#ifndef _ICHWD_H_ +#define _ICHWD_H_ + +struct ichwd_device { + uint16_t vendor; + uint16_t device; + char *desc; +}; + +struct ichwd_softc { + device_t device; + + int active; + unsigned int timeout; + + int smi_rid; + struct resource *smi_res; + bus_space_tag_t smi_bst; + bus_space_handle_t smi_bsh; + + int tco_rid; + struct resource *tco_res; + bus_space_tag_t tco_bst; + bus_space_handle_t tco_bsh; + + eventhandler_tag ev_tag; +}; + +#define VENDORID_INTEL 0x8086 +#define DEVICEID_82801AA 0x2410 +#define DEVICEID_82801AB 0x2420 +#define DEVICEID_82801BA 0x2440 +#define DEVICEID_82801BAM 0x244c +#define DEVICEID_82801CA 0x2480 +#define DEVICEID_82801CAM 0x248c +#define DEVICEID_82801DB 0x24c0 +#define DEVICEID_82801DBM 0x24cc +#define DEVICEID_82801E 0x2450 +#define DEVICEID_82801EBR 0x24d0 + +/* ICH LPC Interface Bridge Registers */ +#define ICH_GEN_STA 0xd4 +#define ICH_GEN_STA_NO_REBOOT 0x02 +#define ICH_PMBASE 0x40 /* ACPI base address register */ +#define ICH_PMBASE_MASK 0x7f80 /* bits 7-15 */ + +/* register names and locations (relative to PMBASE) */ +#define SMI_BASE 0x30 /* base address for SMI registers */ +#define SMI_LEN 0x08 +#define SMI_EN 0x00 /* SMI Control and Enable Register */ +#define SMI_STS 0x04 /* SMI Status Register */ +#define TCO_BASE 0x60 /* base address for TCO registers */ +#define TCO_LEN 0x0a +#define TCO_RLD 0x00 /* TCO Reload and Current Value */ +#define TCO_TMR 0x01 /* TCO Timer Initial Value */ +#define TCO_DAT_IN 0x02 /* TCO Data In (DO NOT USE) */ +#define TCO_DAT_OUT 0x03 /* TCO Data Out (DO NOT USE) */ +#define TCO1_STS 0x04 /* TCO Status 1 */ +#define TCO2_STS 0x06 /* TCO Status 2 */ +#define TCO1_CNT 0x08 /* TCO Control 1 */ + +/* bit definitions for SMI_EN and SMI_STS */ +#define SMI_TCO_EN 0x2000 +#define SMI_TCO_STS 0x2000 + +/* timer value mask for TCO_RLD and TCO_TMR */ +#define TCO_TIMER_MASK 0x1f + +/* status bits for TCO1_STS */ +#define TCO_TIMEOUT 0x08 /* timed out */ +#define TCO_INT_STS 0x04 /* data out (DO NOT USE) */ +#define TCO_SMI_STS 0x02 /* data in (DO NOT USE) */ + +/* status bits for TCO2_STS */ +#define TCO_BOOT_STS 0x04 /* failed to come out of reset */ +#define TCO_SECOND_TO_STS 0x02 /* ran down twice */ + +/* control bits for TCO1_CNT */ +#define TCO_TMR_HALT 0x0800 /* clear to enable WDT */ +#define TCO_CNT_PRESERVE 0x0200 /* preserve these bits */ + +/* approximate length in nanoseconds of one WDT tick */ +#define ICHWD_TICK 600000000 + +/* minimum / maximum timeout in WDT ticks */ +#define ICHWD_MIN_TIMEOUT 2 +#define ICHWD_MAX_TIMEOUT 63 + +#endif diff --git a/sys/modules/Makefile b/sys/modules/Makefile index e4e501c010f6..e3e06cfd3cc4 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -83,6 +83,7 @@ SUBDIR= ${_3dfx} \ ${_hme} \ ${_i2c} \ ${_ibcs2} \ + ${_ichwd} \ ${_ida} \ ${_idt} \ ${_ie} \ @@ -300,6 +301,7 @@ _fe= fe _hfa= hfa _i2c= i2c _ibcs2= ibcs2 +_ichwd= ichwd _ie= ie _linprocfs= linprocfs _linux= linux diff --git a/sys/modules/ichwd/Makefile b/sys/modules/ichwd/Makefile new file mode 100644 index 000000000000..880ec82a28ef --- /dev/null +++ b/sys/modules/ichwd/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../dev/ichwd + +KMOD= ichwd +SRCS= ichwd.c device_if.h bus_if.h pci_if.h + +.include