diff options
-rw-r--r-- | share/man/man4/wbwd.4 | 117 | ||||
-rw-r--r-- | sys/amd64/conf/GENERIC.hints | 1 | ||||
-rw-r--r-- | sys/amd64/conf/NOTES | 2 | ||||
-rw-r--r-- | sys/boot/forth/loader.conf | 1 | ||||
-rw-r--r-- | sys/conf/files.amd64 | 1 | ||||
-rw-r--r-- | sys/conf/files.i386 | 1 | ||||
-rw-r--r-- | sys/dev/wbwd/wbwd.c | 721 | ||||
-rw-r--r-- | sys/i386/conf/GENERIC.hints | 1 | ||||
-rw-r--r-- | sys/i386/conf/NOTES | 2 | ||||
-rw-r--r-- | sys/modules/Makefile | 3 | ||||
-rw-r--r-- | sys/modules/wbwd/Makefile | 9 |
11 files changed, 859 insertions, 0 deletions
diff --git a/share/man/man4/wbwd.4 b/share/man/man4/wbwd.4 new file mode 100644 index 0000000..2b24fab --- /dev/null +++ b/share/man/man4/wbwd.4 @@ -0,0 +1,117 @@ +.\"- +.\" Copyright (c) 2012 Bjoern A. Zeeb <bz@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 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$ +.\" +.Dd March 6, 2012 +.Dt wbwd 4 +.Os +.Sh NAME +.Nm wbwd +.Nd device driver for watchdog timer found on Winbond Super I/O chips +.Sh SYNOPSIS +To compile this driver into the kernel, place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device wbwd" +.Ed +.Pp +Alternatively, to load the driver as a module at boot time, place the following +line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +wbwd_load="YES" +.Ed +.Pp +In +.Pa /boot/device.hints : +.Cd hint.wbwd.0.at="isa" +.Sh DESCRIPTION +The +.Nm +driver provides +.Xr watchdog 4 +support for the watchdog interrupt timer present on at least the following +Winbond Super I/O chips: +.Pp +.Bl -bullet -compact +.It +83627HF/F/HG/G Rev. G +.It +83627HF/F/HG/G Rev. J +.It +83627HF/F/HG/G Rev. UD-A +.It +83627DHG IC ver. 5 +.El +.Sh SYSCTL VARIABLES +The +.Nm +driver provides the following options as +.Xr sysctl 8 +variables. +.Bl -tag -width "xxxxxx" +.It Va dev.wbwd.0.timeout_override +This variable allows to program the timer to a value independent on the one +provided by the +.Xr watchdog 4 +framework while still relying on the regular updates from e.g. +.Xr watchdogd 8 . +This is particularly useful if your system provides multiple watchdogs and +you want them to fire in a special sequence to trigger an NMI after a shorter +period than the reset timeout for example. +The value set must not be lower than the sleep time of +.Xr watchdogd 8 . +A value of 0 disables this feature and the timeout value provided by +.Xr watchdog 4 +will be used. +.It Va dev.wbwd.0.debug_verbose +If set this sysctl will tell the driver to log its current state before and +after the timer reset on each invocation from +.Xr watchdog 9 +to the kernel message buffer for debugging. +.It Va dev.wbwd.0.debug +This read-only value gives the state of some registers on last update. +.El +.Pp +The +.Nm +driver also provides further sysctl options that are hidden by default. +See the source code for more information. +.Sh SEE ALSO +.Xr watchdog 4 , +.Xr device.hints 5 , +.Xr watchdog 8 , +.Xr watchdogd 8 , +.Xr watchdog 9 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 10.0 . +.Sh AUTHORS +.An -nosplit +This manual page was written by +.An Bjoern A. Zeeb Aq bz@FreeBSD.org . diff --git a/sys/amd64/conf/GENERIC.hints b/sys/amd64/conf/GENERIC.hints index 763b3eb..eacbbe8 100644 --- a/sys/amd64/conf/GENERIC.hints +++ b/sys/amd64/conf/GENERIC.hints @@ -30,3 +30,4 @@ hint.atrtc.0.irq="8" hint.attimer.0.at="isa" hint.attimer.0.port="0x40" hint.attimer.0.irq="0" +hint.wbwd.0.at="isa" diff --git a/sys/amd64/conf/NOTES b/sys/amd64/conf/NOTES index 7c72055..929d76b 100644 --- a/sys/amd64/conf/NOTES +++ b/sys/amd64/conf/NOTES @@ -465,10 +465,12 @@ device tpm # ichwd: Intel ICH watchdog timer # amdsbwd: AMD SB7xx watchdog timer # viawd: VIA south bridge watchdog timer +# wbwd: Winbond watchdog timer # device ichwd device amdsbwd device viawd +device wbwd # # Temperature sensors: diff --git a/sys/boot/forth/loader.conf b/sys/boot/forth/loader.conf index a6a7785..a5d044d 100644 --- a/sys/boot/forth/loader.conf +++ b/sys/boot/forth/loader.conf @@ -488,6 +488,7 @@ vpd_load="NO" # Vital Product Data kernel interface vpo_load="NO" # Parallel to SCSI interface driver amdtemp_load="NO" # AMD K8/K10/K11 temperature monitor tpm_load="NO" # Trusted Platform Module +wbwd_load="NO" # Winbond watchdog ############################################################## ### ACPI settings ########################################## diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 index e8c7947..8ab1cbc 100644 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -270,6 +270,7 @@ dev/tpm/tpm_acpi.c optional tpm acpi dev/tpm/tpm_isa.c optional tpm isa dev/uart/uart_cpu_amd64.c optional uart dev/viawd/viawd.c optional viawd +dev/wbwd/wbwd.c optional wbwd dev/wpi/if_wpi.c optional wpi dev/isci/isci.c optional isci dev/isci/isci_controller.c optional isci diff --git a/sys/conf/files.i386 b/sys/conf/files.i386 index 3056a87..5064f0d 100644 --- a/sys/conf/files.i386 +++ b/sys/conf/files.i386 @@ -247,6 +247,7 @@ dev/uart/uart_cpu_i386.c optional uart dev/viawd/viawd.c optional viawd dev/acpica/acpi_if.m standard dev/acpi_support/acpi_wmi_if.m standard +dev/wbwd/wbwd.c optional wbwd dev/wpi/if_wpi.c optional wpi dev/isci/isci.c optional isci dev/isci/isci_controller.c optional isci diff --git a/sys/dev/wbwd/wbwd.c b/sys/dev/wbwd/wbwd.c new file mode 100644 index 0000000..f6bf5a2 --- /dev/null +++ b/sys/dev/wbwd/wbwd.c @@ -0,0 +1,721 @@ +/*- + * Copyright (c) 2011 Sandvine Incorporated ULC. + * 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 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. + */ +/* + * Support for Winbond watchdog. + * + * With minor abstractions it might be possible to add support for other + * different Winbond Super I/O chips as well. Winbond seems to have four + * different types of chips, four different ways to get into extended config + * mode. + * + * Note: there is no serialization between the debugging sysctl handlers and + * the watchdog functions and possibly others poking the registers at the same + * time. For that at least possibly interfering sysctls are hidden by default. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/eventhandler.h> +#include <sys/lock.h> +#include <sys/module.h> +#include <sys/rman.h> +#include <sys/sbuf.h> +#include <sys/sysctl.h> +#include <sys/watchdog.h> + +#include <isa/isavar.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +/* + * Global registers. + */ +#define WB_DEVICE_ID_REG 0x20 /* Device ID */ +#define WB_DEVICE_REV_REG 0x21 /* Device revision */ +#define WB_CR26 0x26 /* Bit6: HEFRAS (base port selector) */ + +/* LDN selection. */ +#define WB_LDN_REG 0x07 +#define WB_LDN_REG_LDN8 0x08 /* GPIO 2, Watchdog */ + +/* + * LDN8 (GPIO 2, Watchdog) specific registers and options. + */ +/* CR30: LDN8 activation control. */ +#define WB_LDN8_CR30 0x30 +#define WB_LDN8_CR30_ACTIVE 0x01 /* 1: LD active */ + +/* CRF5: Watchdog scale, P20. Mapped to reg_1. */ +#define WB_LDN8_CRF5 0xF5 +#define WB_LDN8_CRF5_SCALE 0x08 /* 0: 1s, 1: 60s */ +#define WB_LDN8_CRF5_KEYB_P20 0x04 /* 1: keyb P20 forces timeout */ + +/* CRF6: Watchdog Timeout (0 == off). Mapped to reg_timeout. */ +#define WB_LDN8_CRF6 0xF6 + +/* CRF7: Watchdog mouse, keyb, force, .. Mapped to reg_2. */ +#define WB_LDN8_CRF7 0xF7 +#define WB_LDN8_CRF7_MOUSE 0x80 /* 1: mouse irq resets wd timer */ +#define WB_LDN8_CRF7_KEYB 0x40 /* 1: keyb irq resets wd timer */ +#define WB_LDN8_CRF7_FORCE 0x20 /* 1: force timeout (self-clear) */ +#define WB_LDN8_CRF7_TS 0x10 /* 0: counting, 1: fired */ +#define WB_LDN8_CRF7_IRQS 0x0f /* irq source for watchdog, 2 == SMI */ +#define WB_LDN8_CRF7_CLEAR_MASK \ + (WB_LDN8_CRF7_MOUSE|WB_LDN8_CRF7_KEYB|WB_LDN8_CRF7_TS|WB_LDN8_CRF7_IRQS) + +#define write_efir_1(sc, value) \ + bus_space_write_1((sc)->bst, (sc)->bsh, 0, (value)) +#define read_efir_1(sc) \ + bus_space_read_1((sc)->bst, (sc)->bsh, 0) +#define write_efdr_1(sc, value) \ + bus_space_write_1((sc)->bst, (sc)->bsh, 1, (value)) +#define read_efdr_1(sc) \ + bus_space_read_1((sc)->bst, (sc)->bsh, 1) + +struct wb_softc { + device_t dev; + struct resource *portres; + bus_space_tag_t bst; + bus_space_handle_t bsh; + int rid; + eventhandler_tag ev_tag; + int (*ext_cfg_enter_f)(struct wb_softc *); + void (*ext_cfg_exit_f)(struct wb_softc *); + int debug_verbose; + + /* + * Special feature to let the watchdog fire at a different + * timeout as set by watchdog(4) but still use that API to + * re-load it periodically. + */ + unsigned int timeout_override; + + /* + * Space to save current state temporary and for sysctls. + * We want to know the timeout value and usually need two + * additional registers for options. Do not name them by + * register as these might be different by chip. + */ + uint8_t reg_timeout; + uint8_t reg_1; + uint8_t reg_2; +}; + +static int ext_cfg_enter_0x87_0x87(struct wb_softc *); +static void ext_cfg_exit_0xaa(struct wb_softc *); + +struct winbond_superio_cfg { + uint8_t efer; /* and efir */ + int (*ext_cfg_enter_f)(struct wb_softc *); + void (*ext_cfg_exit_f)(struct wb_softc *); +} probe_addrs[] = { + { + .efer = 0x2e, + .ext_cfg_enter_f = ext_cfg_enter_0x87_0x87, + .ext_cfg_exit_f = ext_cfg_exit_0xaa, + }, + { + .efer = 0x4e, + .ext_cfg_enter_f = ext_cfg_enter_0x87_0x87, + .ext_cfg_exit_f = ext_cfg_exit_0xaa, + }, +}; + +struct winbond_vendor_device_id { + uint16_t vendor_id; + uint8_t device_id; + uint8_t device_rev; + const char * descr; +} wb_devs[] = { + { + .vendor_id = 0x5ca3, + .device_id = 0x52, + .device_rev = 0x17, + .descr = "Winbond 83627HF/F/HG/G Rev. G", + }, + { + .vendor_id = 0x5ca3, + .device_id = 0x52, + .device_rev = 0x3a, + .descr = "Winbond 83627HF/F/HG/G Rev. J", + }, + { + .vendor_id = 0x5ca3, + .device_id = 0x52, + .device_rev = 0x41, + .descr = "Winbond 83627HF/F/HG/G Rev. UD-A", + }, + { + .vendor_id = 0x5ca3, + .device_id = 0xa0, + .device_rev = 0x25, + .descr = "Winbond 83627DHG IC ver. 5", + }, +}; + +/* + * Return the watchdog related registers as we last read them. This will + * usually not give the current timeout or state on whether the watchdog + * fired. + */ +static int +sysctl_wb_debug(SYSCTL_HANDLER_ARGS) +{ + struct wb_softc *sc; + struct sbuf sb; + int error; + + sc = arg1; + + sbuf_new_for_sysctl(&sb, NULL, 64, req); + + sbuf_printf(&sb, "LDN8 (GPIO2, Watchdog): "); + sbuf_printf(&sb, "CRF5 0x%02x ", sc->reg_1); + sbuf_printf(&sb, "CRF6 0x%02x ", sc->reg_timeout); + sbuf_printf(&sb, "CRF7 0x%02x ", sc->reg_2); + + sbuf_trim(&sb); + error = sbuf_finish(&sb); + sbuf_delete(&sb); + return (error); +} + +/* + * Read the current values before returning them. Given this might poke + * the registers the same time as the watchdog, this sysctl handler should + * be marked CTLFLAG_SKIP to not show up by default. + */ +static int +sysctl_wb_debug_current(SYSCTL_HANDLER_ARGS) +{ + struct wb_softc *sc; + + sc = arg1; + + /* + * Enter extended function mode in case someone else has been + * poking on the registers. We will not leave it though. + */ + if ((*sc->ext_cfg_enter_f)(sc) != 0) + return (ENXIO); + + /* Watchdog is configured as part of LDN 8 (GPIO Port2, Watchdog). */ + write_efir_1(sc, WB_LDN_REG); + write_efdr_1(sc, WB_LDN_REG_LDN8); + + write_efir_1(sc, WB_LDN8_CRF5); + sc->reg_1 = read_efdr_1(sc); + write_efir_1(sc, WB_LDN8_CRF6); + sc->reg_timeout = read_efdr_1(sc); + write_efir_1(sc, WB_LDN8_CRF7); + sc->reg_2 = read_efdr_1(sc); + + return (sysctl_wb_debug(oidp, arg1, arg2, req)); +} + +/* + * Sysctl handlers to force a watchdog timeout or to test the NMI functionality + * works as expetced. + * For testing we could set a test_nmi flag in the softc that, in case of NMI, a + * callback function from trap.c could check whether we fired and not report the + * timeout but clear the flag for the sysctl again. This is interesting given a + * lot of boards have jumpers to change the action on watchdog timeout or + * disable the watchdog completely. + * XXX-BZ notyet: currently no general infrastructure exists to do this. + */ +static int +sysctl_wb_force_test_nmi(SYSCTL_HANDLER_ARGS) +{ + struct wb_softc *sc; + int error, test, val; + + sc = arg1; + test = arg2; + +#ifdef notyet + val = sc->test_nmi; +#else + val = 0; +#endif + error = sysctl_handle_int(oidp, &val, 0, req); + if (error || !req->newptr) + return (error); + +#ifdef notyet + /* Manually clear the test for a value of 0 and do nothing else. */ + if (test && val == 0) { + sc->test_nmi = 0; + return (0); + } +#endif + + /* + * Enter extended function mode in case someone else has been + * poking on the registers. We will not leave it though. + */ + if ((*sc->ext_cfg_enter_f)(sc) != 0) + return (ENXIO); + +#ifdef notyet + /* + * If we are testing the NMI functionality, set the flag before + * forcing the timeout. + */ + if (test) + sc->test_nmi = 1; +#endif + + /* Watchdog is configured as part of LDN 8 (GPIO Port2, Watchdog). */ + write_efir_1(sc, WB_LDN_REG); + write_efdr_1(sc, WB_LDN_REG_LDN8); + + /* Force watchdog to fire. */ + write_efir_1(sc, WB_LDN8_CRF7); + sc->reg_2 = read_efdr_1(sc); + sc->reg_2 |= WB_LDN8_CRF7_FORCE; + + write_efir_1(sc, WB_LDN8_CRF7); + write_efdr_1(sc, sc->reg_2); + + return (0); +} + +/* + * Print current watchdog state. + * + * Note: it is the responsibility of the caller to update the registers + * upfront. + */ +static void +wb_print_state(struct wb_softc *sc, const char *msg) +{ + + device_printf(sc->dev, "%s%sWatchdog %sabled. %s" + "Scaling by %ds, timer at %d (%s=%ds%s). " + "CRF5 0x%02x CRF7 0x%02x\n", + (msg != NULL) ? msg : "", (msg != NULL) ? ": " : "", + (sc->reg_timeout > 0x00) ? "en" : "dis", + (sc->reg_2 & WB_LDN8_CRF7_TS) ? "Watchdog fired. " : "", + (sc->reg_1 & WB_LDN8_CRF5_SCALE) ? 60 : 1, + sc->reg_timeout, + (sc->reg_timeout > 0x00) ? "<" : "", + sc->reg_timeout * ((sc->reg_1 & WB_LDN8_CRF5_SCALE) ? 60 : 1), + (sc->reg_timeout > 0x00) ? " left" : "", + sc->reg_1, sc->reg_2); +} + +/* + * Functions to enter and exit extended function mode. Possibly shared + * between different chips. + */ +static int +ext_cfg_enter_0x87_0x87(struct wb_softc *sc) +{ + + /* + * Enable extended function mode. + * Winbond does not allow us to validate so always return success. + */ + write_efir_1(sc, 0x87); + write_efir_1(sc, 0x87); + + return (0); +} + +static void +ext_cfg_exit_0xaa(struct wb_softc *sc) +{ + + write_efir_1(sc, 0xaa); +} + +/* + * (Re)load the watchdog counter depending on timeout. A timeout of 0 will + * disable the watchdog. + */ +static int +wb_set_watchdog(struct wb_softc *sc, unsigned int timeout) +{ + + if (sc->debug_verbose) + wb_print_state(sc, "Before watchdog counter (re)load"); + + /* + * Enter extended function mode in case someone else has been + * poking on the registers. We will not leave it though. + */ + if ((*sc->ext_cfg_enter_f)(sc) != 0) + return (ENXIO); + + /* Watchdog is configured as part of LDN 8 (GPIO Port2, Watchdog) */ + write_efir_1(sc, WB_LDN_REG); + write_efdr_1(sc, WB_LDN_REG_LDN8); + + /* Disable and validate or arm/reset watchdog. */ + if (timeout == 0) { + /* Disable watchdog. */ + write_efir_1(sc, WB_LDN8_CRF6); + write_efdr_1(sc, 0x00); + + /* Re-check. */ + write_efir_1(sc, WB_LDN8_CRF6); + sc->reg_timeout = read_efdr_1(sc); + + if (sc->reg_timeout != 0x00) { + device_printf(sc->dev, "Failed to disable watchdog: " + "0x%02x.\n", sc->reg_timeout); + return (EIO); + } + + } else { + /* + * In case an override is set, let it override. It may lead + * to strange results as we do not check the input of the sysctl. + */ + if (sc->timeout_override > 0) + timeout = sc->timeout_override; + + /* Make sure we support the requested timeout. */ + if (timeout > 255 * 60) + return (EINVAL); + + /* Read current scaling factor. */ + write_efir_1(sc, WB_LDN8_CRF5); + sc->reg_1 = read_efdr_1(sc); + + if (timeout > 255) { + /* Set scaling factor to 60s. */ + sc->reg_1 |= WB_LDN8_CRF5_SCALE; + sc->reg_timeout = (timeout / 60); + if (timeout % 60) + sc->reg_timeout++; + } else { + /* Set scaling factor to 1s. */ + sc->reg_1 &= ~WB_LDN8_CRF5_SCALE; + sc->reg_timeout = timeout; + } + + /* In case we fired before we need to clear to fire again. */ + write_efir_1(sc, WB_LDN8_CRF7); + sc->reg_2 = read_efdr_1(sc); + if (sc->reg_2 & WB_LDN8_CRF7_TS) { + sc->reg_2 &= ~WB_LDN8_CRF7_TS; + write_efir_1(sc, WB_LDN8_CRF7); + write_efdr_1(sc, sc->reg_2); + } + + /* Write back scaling factor. */ + write_efir_1(sc, WB_LDN8_CRF5); + write_efdr_1(sc, sc->reg_1); + + /* Set timer and arm/reset the watchdog. */ + write_efir_1(sc, WB_LDN8_CRF6); + write_efdr_1(sc, sc->reg_timeout); + } + + if (sc->debug_verbose) + wb_print_state(sc, "After watchdog counter (re)load"); + + return (0); +} + +/* + * watchdog(9) EVENTHANDLER function implementation to (re)load the counter + * with the given timeout or disable the watchdog. + */ +static void +wb_watchdog_fn(void *private, u_int cmd, int *error) +{ + struct wb_softc *sc; + unsigned int timeout; + int e; + + sc = private; + KASSERT(sc != NULL, ("%s: watchdog handler function called without " + "softc.", __func__)); + + cmd &= WD_INTERVAL; + if (cmd > 0 && cmd <= 63) { + /* Reset (and arm) watchdog. */ + timeout = ((uint64_t)1 << cmd) / 1000000000; + if (timeout == 0) + timeout = 1; + e = wb_set_watchdog(sc, timeout); + if (e == 0) { + if (error != NULL) + *error = 0; + } else { + /* On error, try to make sure the WD is disabled. */ + wb_set_watchdog(sc, 0); + } + + } else { + /* Disable watchdog. */ + e = wb_set_watchdog(sc, 0); + if (e != 0 && cmd == 0 && error != NULL) { + /* Failed to disable watchdog. */ + *error = EOPNOTSUPP; + } + } +} + +/* + * Probe/attach the Winbond Super I/O chip. + * + * Initial abstraction to possibly support more chips: + * - Iterate over the well known base ports, try to enable extended function + * mode and read and match the device ID and device revision. Unfortunately + * the Vendor ID is in the hardware monitoring section accessible by different + * base ports only. + * - Also HEFRAS, which would tell use the base port, is only accessible after + * entering extended function mode, for which the base port is needed. + * At least check HEFRAS to match the current base port we are probing. + * - On match set the description, remember functions to enter/exit extended + * function mode as well as the base port. + */ +static int +wb_probe_enable(device_t dev, int probe) +{ + struct wb_softc *sc; + int error, found, i, j; + uint8_t dev_id, dev_rev, cr26; + + sc = device_get_softc(dev); + bzero(sc, sizeof(*sc)); + sc->dev = dev; + + error = ENXIO; + for (i = 0; i < sizeof(probe_addrs) / sizeof(*probe_addrs); i++) { + + /* Allocate bus resources for IO index/data register access. */ + sc->portres = bus_alloc_resource(dev, SYS_RES_IOPORT, &sc->rid, + probe_addrs[i].efer, probe_addrs[i].efer + 1, 2, RF_ACTIVE); + if (sc->portres == NULL) + continue; + sc->bst = rman_get_bustag(sc->portres); + sc->bsh = rman_get_bushandle(sc->portres); + + found = 0; + error = (*probe_addrs[i].ext_cfg_enter_f)(sc); + if (error != 0) + goto cleanup; + + /* Identify the SuperIO chip. */ + write_efir_1(sc, WB_DEVICE_ID_REG); + dev_id = read_efdr_1(sc); + write_efir_1(sc, WB_DEVICE_REV_REG); + dev_rev = read_efdr_1(sc); + write_efir_1(sc, WB_CR26); + cr26 = read_efdr_1(sc); + + /* HEFRAS of 0 means EFER at 0x2e, 1 means EFER at 0x4e. */ + if (((cr26 & 0x40) == 0x00 && probe_addrs[i].efer != 0x2e) || + ((cr26 & 0x40) == 0x40 && probe_addrs[i].efer != 0x4e)) { + device_printf(dev, "HEFRAS and EFER do not align: EFER " + "0x%02x DevID 0x%02x DevRev 0x%02x CR26 0x%02x\n", + probe_addrs[i].efer, dev_id, dev_rev, cr26); + goto cleanup; + } + + for (j = 0; j < sizeof(wb_devs) / sizeof(*wb_devs); j++) { + if (wb_devs[j].device_id == dev_id && + wb_devs[j].device_rev == dev_rev) { + if (probe) + device_set_desc(dev, wb_devs[j].descr); + found++; + break; + } + } + if (probe && found && bootverbose) + device_printf(dev, "%s EFER 0x%02x ID 0x%02x Rev 0x%02x" + " CR26 0x%02x (probing)\n", device_get_desc(dev), + probe_addrs[i].efer, dev_id, dev_rev, cr26); +cleanup: + if (probe || !found) { + (*probe_addrs[i].ext_cfg_exit_f)(sc); + + (void) bus_release_resource(dev, SYS_RES_IOPORT, sc->rid, + sc->portres); + } + + /* + * Stop probing if have successfully identified the SuperIO. + * Remember the extended function mode enter/exit functions + * for operations. + */ + if (found) { + sc->ext_cfg_enter_f = probe_addrs[i].ext_cfg_enter_f; + sc->ext_cfg_exit_f = probe_addrs[i].ext_cfg_exit_f; + error = BUS_PROBE_DEFAULT; + break; + } else + error = ENXIO; + } + + return (error); +} + +static int +wb_probe(device_t dev) +{ + + /* Make sure we do not claim some ISA PNP device. */ + if (isa_get_logicalid(dev) != 0) + return (ENXIO); + + return (wb_probe_enable(dev, 1)); +} + +static int +wb_attach(device_t dev) +{ + struct wb_softc *sc; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + unsigned long timeout; + int error; + + error = wb_probe_enable(dev, 0); + if (error > 0) + return (ENXIO); + + sc = device_get_softc(dev); + KASSERT(sc->ext_cfg_enter_f != NULL && sc->ext_cfg_exit_f != NULL, + ("%s: successfull probe result but not setup correctly", __func__)); + + /* Watchdog is configured as part of LDN 8 (GPIO Port2, Watchdog). */ + write_efir_1(sc, WB_LDN_REG); + write_efdr_1(sc, WB_LDN_REG_LDN8); + + /* Make sure LDN8 is enabled (Do we need to? Also affects GPIO). */ + write_efir_1(sc, WB_LDN8_CR30); + write_efdr_1(sc, WB_LDN8_CR30_ACTIVE); + + /* Read the current watchdog configuration. */ + write_efir_1(sc, WB_LDN8_CRF5); + sc->reg_1 = read_efdr_1(sc); + write_efir_1(sc, WB_LDN8_CRF6); + sc->reg_timeout = read_efdr_1(sc); + write_efir_1(sc, WB_LDN8_CRF7); + sc->reg_2 = read_efdr_1(sc); + + /* Print current state if bootverbose or watchdog already enabled. */ + if (bootverbose || (sc->reg_timeout > 0x00)) + wb_print_state(sc, "Before watchdog attach"); + + /* + * Clear a previous watchdog timeout event (if (still) set). + * Disable all all interrupt reset sources (defaults). + */ + sc->reg_1 &= ~(WB_LDN8_CRF5_KEYB_P20); + write_efir_1(sc, WB_LDN8_CRF5); + write_efir_1(sc, sc->reg_1); + + sc->reg_2 &= ~WB_LDN8_CRF7_CLEAR_MASK; + write_efir_1(sc, WB_LDN8_CRF7); + write_efdr_1(sc, sc->reg_2); + + /* Read global timeout override tunable, Add per device sysctls. */ + if (TUNABLE_ULONG_FETCH("hw.wbwd.timeout_override", &timeout)) { + if (timeout > 0) + sc->timeout_override = timeout; + } + sctx = device_get_sysctl_ctx(dev); + soid = device_get_sysctl_tree(dev); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "timeout_override", CTLFLAG_RW, &sc->timeout_override, 0, + "Timeout in seconds overriding default watchdog timeout"); + SYSCTL_ADD_INT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "debug_verbose", CTLFLAG_RW, &sc->debug_verbose, 0, + "Enables extra debugging information"); + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "debug", + CTLTYPE_STRING|CTLFLAG_RD, sc, 0, sysctl_wb_debug, "A", + "Selected register information from last change by driver"); + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "debug_current", + CTLTYPE_STRING|CTLFLAG_RD|CTLFLAG_SKIP, sc, 0, + sysctl_wb_debug_current, "A", + "Selected register information (may interfere)"); + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, "force_timeout", + CTLTYPE_INT|CTLFLAG_RW|CTLFLAG_SKIP, sc, 0, + sysctl_wb_force_test_nmi, "I", "Enable to force watchdog to fire."); + + /* Register watchdog. */ + sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, wb_watchdog_fn, sc, + 0); + + if (bootverbose) + wb_print_state(sc, "After watchdog attach"); + + return (0); +} + +static int +wb_detach(device_t dev) +{ + struct wb_softc *sc; + + sc = device_get_softc(dev); + + /* Unregister and stop the watchdog if running. */ + if (sc->ev_tag) + EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); + wb_set_watchdog(sc, 0); + + /* Disable extended function mode. */ + (*sc->ext_cfg_exit_f)(sc); + + /* Cleanup resources. */ + (void) bus_release_resource(dev, SYS_RES_IOPORT, sc->rid, sc->portres); + + /* Bus subroutines take care of sysctls already. */ + + return (0); +} + +static device_method_t wb_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, wb_probe), + DEVMETHOD(device_attach, wb_attach), + DEVMETHOD(device_detach, wb_detach), + + { 0, 0 } +}; + +static driver_t wb_isa_driver = { + "wbwd", + wb_methods, + sizeof(struct wb_softc) +}; + +static devclass_t wb_devclass; + +DRIVER_MODULE(wb, isa, wb_isa_driver, wb_devclass, NULL, NULL); diff --git a/sys/i386/conf/GENERIC.hints b/sys/i386/conf/GENERIC.hints index dc827a8..98c906d 100644 --- a/sys/i386/conf/GENERIC.hints +++ b/sys/i386/conf/GENERIC.hints @@ -38,3 +38,4 @@ hint.atrtc.0.irq="8" hint.attimer.0.at="isa" hint.attimer.0.port="0x40" hint.attimer.0.irq="0" +hint.wbwd.0.at="isa" diff --git a/sys/i386/conf/NOTES b/sys/i386/conf/NOTES index 0cd0080..93a35d3 100644 --- a/sys/i386/conf/NOTES +++ b/sys/i386/conf/NOTES @@ -838,10 +838,12 @@ hint.pcf.0.irq="5" # ichwd: Intel ICH watchdog timer # amdsbwd: AMD SB7xx watchdog timer # viawd: VIA south bridge watchdog timer +# wbwd: Winbond watchdog timer # device ichwd device amdsbwd device viawd +device wbwd # # Temperature sensors: diff --git a/sys/modules/Makefile b/sys/modules/Makefile index 0cf237f..c2ddfe4 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -333,6 +333,7 @@ SUBDIR= ${_3dfx} \ vx \ ${_vxge} \ wb \ + ${_wbwd} \ ${_wi} \ wlan \ wlan_acl \ @@ -510,6 +511,7 @@ _stg= stg _streams= streams _svr4= svr4 _vxge= vxge +_wbwd= wbwd _wi= wi _xe= xe .if ${MK_ZFS} != "no" || defined(ALL_MODULES) @@ -704,6 +706,7 @@ _viawd= viawd _virtio= virtio _vxge= vxge _x86bios= x86bios +_wbwd= wbwd _wi= wi _wpi= wpi .if ${MK_SOURCELESS_UCODE} != "no" diff --git a/sys/modules/wbwd/Makefile b/sys/modules/wbwd/Makefile new file mode 100644 index 0000000..e727302 --- /dev/null +++ b/sys/modules/wbwd/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../dev/wbwd + +KMOD= wbwd +SRCS= wbwd.c +SRCS+= bus_if.h device_if.h isa_if.h + +.include <bsd.kmod.mk> |