diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -588,6 +588,7 @@ vtnet.4 \ watchdog.4 \ ${_wbwd.4} \ + ${_wdatwd.4} \ wg.4 \ witness.4 \ wlan.4 \ @@ -850,6 +851,7 @@ _vmd.4= vmd.4 _vmx.4= vmx.4 _wbwd.4= wbwd.4 +_wdatwd.4= wdatwd.4 _wpi.4= wpi.4 _xen.4= xen.4 _xnb.4= xnb.4 diff --git a/share/man/man4/wdatwd.4 b/share/man/man4/wdatwd.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/wdatwd.4 @@ -0,0 +1,91 @@ +.\"- +.\" Copyright (c) 2022 Tetsuya Uemura +.\" +.\" 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. +.\" +.Dd November 18, 2022 +.Dt WDATWD 4 +.Os +.Sh NAME +.Nm wdatwd +.Nd device driver for the ACPI WDAT based watchdog interrupt timer +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device wdatwd" +.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 +wdatwd_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides +.Xr watchdog 4 +support for the watchdog interrupt timer in ACPI WDAT (Watchdog Action Table). +.Pp +Since WDAT itself is an abstraction for the real hardware such as ICH WDT, it +must be noted that only one driver can be used at a time, either the real +hardware specific driver or this driver. +.Sh SYSCTL VARIABLES +The following read-only +.Xr sysctl 8 +variables are available: +.Bl -tag -width indent +.It Va dev.wdatwd.%d.running +The status of the watchdog timer. 0 if not running, or 1 if running. +.It Va dev.wdatwd.%d.timeout +The current value of the watchdog timeout in millisecond. +This can be 0 on some systems, and the zero value means that the default +timeout is used. +.It Va dev.wdatwd.%d.timeout_configurable +Whether the timeout is configurable or not. +It is 0 if configurable or any positive value if not. +.It Va dev.wdatwd.%d.timeout_default +The default value of the watchdog timeout in millisecond if any. +.El +.Sh SEE ALSO +.Xr ichwd 4 , +.Xr watchdog 4 , +.Xr watchdog 8 , +.Xr watchdogd 8 , +.Xr watchdog 9 +.Rs +.%T Hardware Watchdog Timers Design Specification +.%R Requirements for Hardware Watchdog Timers Supported by Microsoft(R) Windows Vista(R) and Microsoft Windows Server(R) 2008 Operating Systems +.%A Microsoft Corporation +.%U http://msdn.microsoft.com/en-us/windows/hardware/gg463320.aspx +.%D 2006 +.Re +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Tetsuya Uemura Aq Mt t_uemura@macome.co.jp +of MACOME, Corporation. diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64 --- a/sys/conf/files.amd64 +++ b/sys/conf/files.amd64 @@ -369,6 +369,7 @@ dev/viawd/viawd.c optional viawd dev/vmd/vmd.c optional vmd | vmd_bus dev/wbwd/wbwd.c optional wbwd +dev/wdatwd/wdatwd.c optional wdatwd dev/p2sb/p2sb.c optional p2sb pci dev/p2sb/lewisburg_gpiocm.c optional lbggpiocm p2sb dev/p2sb/lewisburg_gpio.c optional lbggpio lbggpiocm diff --git a/sys/conf/files.i386 b/sys/conf/files.i386 --- a/sys/conf/files.i386 +++ b/sys/conf/files.i386 @@ -65,6 +65,7 @@ dev/vmd/vmd.c optional vmd dev/acpi_support/acpi_wmi_if.m standard dev/wbwd/wbwd.c optional wbwd +dev/wdatwd/wdatwd.c optional wdatwd i386/acpica/acpi_machdep.c optional acpi i386/acpica/acpi_wakeup.c optional acpi acpi_wakecode.o optional acpi \ diff --git a/sys/dev/wdatwd/wdatwd.c b/sys/dev/wdatwd/wdatwd.c new file mode 100644 --- /dev/null +++ b/sys/dev/wdatwd/wdatwd.c @@ -0,0 +1,846 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2022 Tetsuya Uemura + * + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_acpi.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/* + * Resource entry. Every instruction has the corresponding ACPI GAS but two or + * more instructions may access the same or adjacent register region(s). So we + * need to merge all the specified resources. + * + * res Resource when allocated. + * start Region start address. + * end Region end address + 1. + * rid Resource rid assigned when allocated. + * type ACPI resource type, SYS_RES_IOPORT or SYS_RES_MEMORY. + * link Next/previous resource entry. + */ +struct wdat_res { + struct resource *res; + uint64_t start; + uint64_t end; + int rid; + int type; + TAILQ_ENTRY(wdat_res) link; +}; + +/* + * Instruction entry. Every instruction itself is actually a single register + * read or write (and subsequent bit operation(s)). + * 0 or more instructions are tied to every watchdog action and once an action + * is kicked, the corresponding entries run sequentially. + * + * entry Permanent copy of ACPI_WDAT_ENTRY entry (sub-table). + * next Next instruction entry. + */ +struct wdat_instr { + ACPI_WDAT_ENTRY entry; + STAILQ_ENTRY(wdat_instr) next; +}; + +/* + * dev Watchdog device. + * wdat ACPI WDAT table, can be accessed until AcpiPutTable(). + * default_timeout BIOS configured watchdog ticks to fire. + * timeout User configured timeout in millisecond or 0 if isn't set. + * max Max. supported watchdog ticks to be set. + * min Min. supported watchdog ticks to be set. + * period Milliseconds per watchdog tick. + * running True if this watchdog is running or false if stopped. + * stop_in_sleep False if this watchdog keeps counting down during sleep. + * ev_tag Tag for EVENTHANDLER_*(). + * action Array of watchdog instruction sets, each indexed by action. + */ +struct wdatwd_softc { + device_t dev; + ACPI_TABLE_WDAT *wdat; + uint64_t default_timeout; + uint64_t timeout; + u_int max; + u_int min; + u_int period; + bool running; + bool stop_in_sleep; + eventhandler_tag ev_tag; + STAILQ_HEAD(, wdat_instr) action[ACPI_WDAT_ACTION_RESERVED]; + TAILQ_HEAD(res_head, wdat_res) res; +}; + +#define WDATWD_VERBOSE_PRINTF(dev, ...) \ + do { \ + if (bootverbose) \ + device_printf(dev, __VA_ARGS__); \ + } while (0) + +/* + * Do requested action. + */ +static int +wdatwd_action(const struct wdatwd_softc *sc, const u_int action, const uint64_t val, uint64_t *ret) +{ + struct wdat_instr *wdat; + const char *rw = NULL; + ACPI_STATUS status; + + if (STAILQ_EMPTY(&sc->action[action])) { + WDATWD_VERBOSE_PRINTF(sc->dev, + "action not supported: 0x%02x\n", action); + return (EOPNOTSUPP); + } + + STAILQ_FOREACH(wdat, &sc->action[action], next) { + ACPI_GENERIC_ADDRESS *gas = &wdat->entry.RegisterRegion; + uint64_t x, y; + + switch (wdat->entry.Instruction + & ~ACPI_WDAT_PRESERVE_REGISTER) { + case ACPI_WDAT_READ_VALUE: + status = AcpiRead(&x, gas); + if (ACPI_FAILURE(status)) { + rw = "AcpiRead"; + goto fail; + } + x >>= gas->BitOffset; + x &= wdat->entry.Mask; + *ret = (x == wdat->entry.Value) ? 1 : 0; + break; + case ACPI_WDAT_READ_COUNTDOWN: + status = AcpiRead(&x, gas); + if (ACPI_FAILURE(status)) { + rw = "AcpiRead"; + goto fail; + } + x >>= gas->BitOffset; + x &= wdat->entry.Mask; + *ret = x; + break; + case ACPI_WDAT_WRITE_VALUE: + x = wdat->entry.Value & wdat->entry.Mask; + x <<= gas->BitOffset; + if (wdat->entry.Instruction + & ACPI_WDAT_PRESERVE_REGISTER) { + status = AcpiRead(&y, gas); + if (ACPI_FAILURE(status)) { + rw = "AcpiRead"; + goto fail; + } + y &= ~(wdat->entry.Mask << gas->BitOffset); + x |= y; + } + status = AcpiWrite(x, gas); + if (ACPI_FAILURE(status)) { + rw = "AcpiWrite"; + goto fail; + } + break; + case ACPI_WDAT_WRITE_COUNTDOWN: + x = val & wdat->entry.Mask; + x <<= gas->BitOffset; + if (wdat->entry.Instruction + & ACPI_WDAT_PRESERVE_REGISTER) { + status = AcpiRead(&y, gas); + if (ACPI_FAILURE(status)) { + rw = "AcpiRead"; + goto fail; + } + y &= ~(wdat->entry.Mask << gas->BitOffset); + x |= y; + } + status = AcpiWrite(x, gas); + if (ACPI_FAILURE(status)) { + rw = "AcpiWrite"; + goto fail; + } + break; + default: + return (EINVAL); + } + } + + return (0); + +fail: + device_printf(sc->dev, "action: 0x%02x, %s() returned: %d\n", + action, rw, status); + return (ENXIO); +} + +/* + * Reset the watchdog countdown. + */ +static int +wdatwd_reset_countdown(const struct wdatwd_softc *sc) +{ + return wdatwd_action(sc, ACPI_WDAT_RESET, 0, NULL); +} + +/* + * Set the watchdog countdown value. In WDAT specification, this is optional. + */ +static int +wdatwd_set_countdown(struct wdatwd_softc *sc, u_int cmd) +{ + uint64_t timeout; + int e; + + cmd &= WD_INTERVAL; + timeout = ((uint64_t) 1 << cmd) / 1000000 / sc->period; + if (timeout > sc->max) + timeout = sc->max; + else if (timeout < sc->min) + timeout = sc->min; + + e = wdatwd_action(sc, ACPI_WDAT_SET_COUNTDOWN, timeout, NULL); + if (e == 0) + sc->timeout = timeout * sc->period; + + return (e); +} + +/* + * Get the watchdog current countdown value. + */ +static int +wdatwd_get_current_countdown(const struct wdatwd_softc *sc, uint64_t *timeout) +{ + return wdatwd_action(sc, ACPI_WDAT_GET_CURRENT_COUNTDOWN, 0, timeout); +} + +/* + * Get the watchdog countdown value the watchdog is configured to fire. + */ +static int +wdatwd_get_countdown(const struct wdatwd_softc *sc, uint64_t *timeout) +{ + return wdatwd_action(sc, ACPI_WDAT_GET_COUNTDOWN, 0, timeout); +} + +/* + * Set the watchdog to running state. + */ +static int +wdatwd_set_running(struct wdatwd_softc *sc) +{ + int e; + + e = wdatwd_action(sc, ACPI_WDAT_SET_RUNNING_STATE, 0, NULL); + if (e == 0) + sc->running = true; + return (e); +} + +/* + * Set the watchdog to stopped state. + */ +static int +wdatwd_set_stop(struct wdatwd_softc *sc) +{ + int e; + + e = wdatwd_action(sc, ACPI_WDAT_SET_STOPPED_STATE, 0, NULL); + if (e == 0) + sc->running = false; + return (e); +} + +/* + * Clear the watchdog's boot status if the current boot was caused by the + * watchdog firing. + */ +static int +wdatwd_clear_status(const struct wdatwd_softc *sc) +{ + return wdatwd_action(sc, ACPI_WDAT_SET_STATUS, 0, NULL); +} + +/* + * Set the watchdog to reboot when it is fired. + */ +static int +wdatwd_set_reboot(const struct wdatwd_softc *sc) +{ + return wdatwd_action(sc, ACPI_WDAT_SET_REBOOT, 0, NULL); +} + +/* + * Watchdog event handler. + */ +static void +wdatwd_event(void *private, u_int cmd, int *error) +{ + struct wdatwd_softc *sc = private; + uint64_t cur[2], cnt[2]; + bool run[2]; + + if (bootverbose) { + run[0] = sc->running; + if (wdatwd_get_countdown(sc, &cnt[0]) != 0) + cnt[0] = 0; + if (wdatwd_get_current_countdown(sc, &cur[0]) != 0) + cur[0] = 0; + } + + if ((cmd & WD_INTERVAL) == 0) + wdatwd_set_stop(sc); + else { + if (!sc->running) { + /* ACPI_WDAT_SET_COUNTDOWN may not be implemented. */ + wdatwd_set_countdown(sc, cmd); + wdatwd_set_running(sc); + /* + * In the first wdatwd_event() call, it sets the + * watchdog timeout to a considerably larger value such + * as 137 seconds, then kicks the watchdog to start + * counting down. Weirdly though, on a Dell R210 BIOS + * 1.12.0, a supplemental reset action must be + * triggered for the newly set timeout value to take + * effect. Without it, the watchdog fires 2.4 seconds + * after starting, where 2.4 seconds is its initially + * set timeout. This failure scenario is seen by first + * starting watchdogd(8) without wdatwd registered then + * kldload it. In steady state, watchdogd pats the + * watchdog every 10 or so seconds which is much longer + * than 2.4 seconds timeout. + */ + } + wdatwd_reset_countdown(sc); + } + + if (bootverbose) { + run[1] = sc->running; + if (wdatwd_get_countdown(sc, &cnt[1]) != 0) + cnt[1] = 0; + if (wdatwd_get_current_countdown(sc, &cur[1]) != 0) + cur[1] = 0; + WDATWD_VERBOSE_PRINTF(sc->dev, "cmd: %u, sc->running: " + "%d -> %d, cnt: %lu -> %lu, cur: %lu -> %lu\n", cmd, + run[0], run[1], cnt[0], cnt[1], cur[0], cur[1]); + } + + return; +} + +static ssize_t +wdat_set_action(struct wdatwd_softc *sc, ACPI_WDAT_ENTRY *addr, ssize_t remaining) +{ + ACPI_WDAT_ENTRY *entry = addr; + struct wdat_instr *wdat; + + if (remaining < sizeof(ACPI_WDAT_ENTRY)) + return (-EINVAL); + + /* Skip actions beyond specification. */ + if (entry->Action < nitems(sc->action)) { + wdat = malloc(sizeof(*wdat), M_DEVBUF, M_WAITOK | M_ZERO); + wdat->entry = *entry; + STAILQ_INSERT_TAIL(&sc->action[entry->Action], wdat, next); + } + return sizeof(ACPI_WDAT_ENTRY); +} + +/* + * Transform every ACPI_WDAT_ENTRY to wdat_instr by calling wdat_set_action(). + */ +static void +wdat_parse_action_table(struct wdatwd_softc *sc) +{ + ACPI_TABLE_WDAT *wdat = sc->wdat; + ssize_t remaining, consumed; + char *cp; + + remaining = wdat->Header.Length - sizeof(ACPI_TABLE_WDAT); + while (remaining > 0) { + cp = (char *)wdat + wdat->Header.Length - remaining; + consumed = wdat_set_action(sc, (ACPI_WDAT_ENTRY *)cp, + remaining); + if (consumed < 0) { + device_printf(sc->dev, "inconsistent WDAT table.\n"); + break; + } + remaining -= consumed; + } +} + +/* + * Decode the given GAS rr and set its type, start and end (actually end + 1) + * in the newly malloc()'ed res. + */ +static struct wdat_res * +wdat_alloc_region(ACPI_GENERIC_ADDRESS *rr) +{ + struct wdat_res *res; + + if (rr->AccessWidth < 1 || rr->AccessWidth > 4) + return (NULL); + + res = malloc(sizeof(*res), + M_DEVBUF, M_WAITOK | M_ZERO); + if (res != NULL) { + res->start = rr->Address; + res->end = res->start + (1 << (rr->AccessWidth - 1)); + res->type = rr->SpaceId; + } + return (res); +} + +#define OVERLAP_NONE 0x0 // no overlap. +#define OVERLAP_SUBSET 0x1 // res2 is fully covered by res1. +#define OVERLAP_START 0x2 // the start of res2 is overlaped. +#define OVERLAP_END 0x4 // the end of res2 is overlapped. + +/* + * Compare the given res1 and res2, and one of the above OVERLAP_* constant, or + * in case res2 is larger than res1 at both the start and the end, + * OVERLAP_START | OVERLAP_END, is returned. + */ +static int +wdat_compare_region(const struct wdat_res *res1, const struct wdat_res *res2) +{ + int overlap; + + /* + * a) both have different resource type. == OVERLAP_NONE + * b) res2 and res1 have no overlap. == OVERLAP_NONE + * c) res2 is fully covered by res1. == OVERLAP_SUBSET + * d) res2 and res1 overlap partially. == OVERLAP_START or + * OVERLAP_END + * e) res2 fully covers res1. == OVERLAP_START | OVERLAP_END + */ + overlap = 0; + + if (res1->type != res2->type || res1->start > res2->end + || res1->end < res2->start) + overlap |= OVERLAP_NONE; + else { + if (res1->start <= res2->start && res1->end >= res2->end) + overlap |= OVERLAP_SUBSET; + if (res1->start > res2->start) + overlap |= OVERLAP_START; + if (res1->end < res2->end) + overlap |= OVERLAP_END; + } + + return (overlap); +} + +/* + * Try to merge the given newres with the existing sc->res. + */ +static void +wdat_merge_region(struct wdatwd_softc *sc, struct wdat_res *newres) +{ + struct wdat_res *res1, *res2, *res_safe, *res_itr; + int overlap; + + if (TAILQ_EMPTY(&sc->res)) { + TAILQ_INSERT_HEAD(&sc->res, newres, link); + return; + } + + overlap = OVERLAP_NONE; + + TAILQ_FOREACH_SAFE(res1, &sc->res, link, res_safe) { + overlap = wdat_compare_region(res1, newres); + + /* Try next res if newres isn't mergeable. */ + if (overlap == OVERLAP_NONE) + continue; + + /* This res fully covers newres. */ + if (overlap == OVERLAP_SUBSET) + break; + + /* Newres extends the existing res res1 to lower. */ + if ((overlap & OVERLAP_START)) { + res1->start = newres->start; + res_itr = res1; + /* Try to merge more res if possible. */ + while ((res2 = TAILQ_PREV(res_itr, res_head, link))) { + if (res1->type != res2->type) { + res_itr = res2; + continue; + } else if (res1->start <= res2->end) { + res1->start = res2->start; + TAILQ_REMOVE(&sc->res, res2, link); + free(res2, M_DEVBUF); + } else + break; + } + } + /* Newres extends the existing res res1 to upper. */ + if ((overlap & OVERLAP_END)) { + res1->end = newres->end; + res_itr = res1; + /* Try to merge more res if possible. */ + while ((res2 = TAILQ_NEXT(res_itr, link))) { + if (res1->type != res2->type) { + res_itr = res2; + continue; + } else if (res1->end >= res2->start) { + res1->end = res2->end; + TAILQ_REMOVE(&sc->res, res2, link); + free(res2, M_DEVBUF); + } else + break; + } + } + break; + } + + /* + * If newres extends the existing res, newres must be free()'ed. + * Otherwise insert newres into sc->res at appropriate position + * (the lowest address region appears first). + */ + if (overlap > OVERLAP_NONE) + free(newres, M_DEVBUF); + else { + TAILQ_FOREACH(res1, &sc->res, link) { + if (newres->type != res1->type) + continue; + if (newres->start < res1->start) { + TAILQ_INSERT_BEFORE(res1, newres, link); + break; + } + } + if (res1 == NULL) + TAILQ_INSERT_TAIL(&sc->res, newres, link); + } +} + +/* + * Release the already allocated resource. + */ +static void +wdat_release_resource(device_t dev) +{ + struct wdatwd_softc *sc; + struct wdat_instr *wdat; + struct wdat_res *res; + int i; + + sc = device_get_softc(dev); + + TAILQ_FOREACH(res, &sc->res, link) + if (res->res != NULL) { + bus_release_resource(dev, res->type, + res->rid, res->res); + bus_delete_resource(dev, res->type, res->rid); + res->res = NULL; + } + + for (i = 0; i < nitems(sc->action); ++i) + while (!STAILQ_EMPTY(&sc->action[i])) { + wdat = STAILQ_FIRST(&sc->action[i]); + STAILQ_REMOVE_HEAD(&sc->action[i], next); + free(wdat, M_DEVBUF); + } + + while (!TAILQ_EMPTY(&sc->res)) { + res = TAILQ_FIRST(&sc->res); + TAILQ_REMOVE(&sc->res, res, link); + free(res, M_DEVBUF); + } +} + +static int +wdatwd_probe(device_t dev) +{ + ACPI_TABLE_WDAT *wdat; + ACPI_STATUS status; + + /* Without WDAT table we have nothing to do. */ + status = AcpiGetTable(ACPI_SIG_WDAT, 0, (ACPI_TABLE_HEADER **)&wdat); + if (ACPI_FAILURE(status)) + return (ENXIO); + + /* Try to allocate one resource and assume wdatwd is already attached + * if it fails. */ + { + int type, rid = 0; + struct resource *res; + + if (acpi_bus_alloc_gas(dev, &type, &rid, + &((ACPI_WDAT_ENTRY *)(wdat + 1))->RegisterRegion, + &res, 0)) + return (ENXIO); + bus_release_resource(dev, type, rid, res); + bus_delete_resource(dev, type, rid); + } + + WDATWD_VERBOSE_PRINTF(dev, "Flags: 0x%x, TimerPeriod: %d ms/cnt, " + "MaxCount: %d cnt (%d ms), MinCount: %d cnt (%d ms)\n", + (int)wdat->Flags, (int)wdat->TimerPeriod, + (int)wdat->MaxCount, (int)(wdat->MaxCount * wdat->TimerPeriod), + (int)wdat->MinCount, (int)(wdat->MinCount * wdat->TimerPeriod)); + /* WDAT timer consistency. */ + if ((wdat->TimerPeriod < 1) || (wdat->MinCount > wdat->MaxCount)) { + device_printf(dev, "inconsistent timer variables.\n"); + return (EINVAL); + } + + AcpiPutTable((ACPI_TABLE_HEADER *)wdat); + + device_set_desc(dev, "ACPI WDAT Watchdog Interface"); + return (BUS_PROBE_DEFAULT); +} + +static int +wdatwd_attach(device_t dev) +{ + struct wdatwd_softc *sc; + struct wdat_instr *wdat; + struct wdat_res *res; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + ACPI_STATUS status; + int e, i, rid; + + sc = device_get_softc(dev); + sc->dev = dev; + + for (i = 0; i < nitems(sc->action); ++i) + STAILQ_INIT(&sc->action[i]); + + /* Search and parse WDAT table. */ + status = AcpiGetTable(ACPI_SIG_WDAT, 0, + (ACPI_TABLE_HEADER **)&sc->wdat); + if (ACPI_FAILURE(status)) + return (ENXIO); + + /* Parse watchdog variables. */ + sc->period = sc->wdat->TimerPeriod; + sc->max = sc->wdat->MaxCount; + sc->min = sc->wdat->MinCount; + sc->stop_in_sleep = (sc->wdat->Flags & ACPI_WDAT_STOPPED) + ? true : false; + /* Parse defined watchdog actions. */ + wdat_parse_action_table(sc); + + AcpiPutTable((ACPI_TABLE_HEADER *)sc->wdat); + + /* Verbose logging. */ + if (bootverbose) { + for (i = 0; i < nitems(sc->action); ++i) + STAILQ_FOREACH(wdat, &sc->action[i], next) { + WDATWD_VERBOSE_PRINTF(dev, "action: 0x%02x, " + "%s %s at 0x%lx (%d bit(s), offset %d bit(s))\n", + i, + wdat->entry.RegisterRegion.SpaceId + == ACPI_ADR_SPACE_SYSTEM_MEMORY + ? "mem" + : wdat->entry.RegisterRegion.SpaceId + == ACPI_ADR_SPACE_SYSTEM_IO + ? "io " + : "???", + wdat->entry.RegisterRegion.AccessWidth == 1 + ? "byte " + : wdat->entry.RegisterRegion.AccessWidth == 2 + ? "word " + : wdat->entry.RegisterRegion.AccessWidth == 3 + ? "dword" + : wdat->entry.RegisterRegion.AccessWidth == 4 + ? "qword" + : "undef", + wdat->entry.RegisterRegion.Address, + wdat->entry.RegisterRegion.BitWidth, + wdat->entry.RegisterRegion.BitOffset); + } + } + + /* Canonicalize the requested resources. */ + TAILQ_INIT(&sc->res); + for (i = 0; i < nitems(sc->action); ++i) + STAILQ_FOREACH(wdat, &sc->action[i], next) { + res = wdat_alloc_region(&wdat->entry.RegisterRegion); + if (res == NULL) + goto fail; + wdat_merge_region(sc, res); + } + + /* Resource allocation. */ + rid = 0; + TAILQ_FOREACH(res, &sc->res, link) { + switch (res->type) { + case ACPI_ADR_SPACE_SYSTEM_MEMORY: + res->type = SYS_RES_MEMORY; + break; + case ACPI_ADR_SPACE_SYSTEM_IO: + res->type = SYS_RES_IOPORT; + break; + default: + goto fail; + } + + res->rid = rid++; + bus_set_resource(dev, res->type, res->rid, + res->start, res->end - res->start); + res->res = bus_alloc_resource_any( + dev, res->type, &res->rid, RF_ACTIVE); + if (res->res == NULL) { + bus_delete_resource(dev, res->type, res->rid); + device_printf(dev, "%s at 0x%lx (%ld byte(s)): " + "alloc' failed\n", + res->type == SYS_RES_MEMORY ? "mem" : "io ", + res->start, res->end - res->start); + goto fail; + } + WDATWD_VERBOSE_PRINTF(dev, "%s at 0x%lx (%ld byte(s)): " + "alloc'ed\n", + res->type == SYS_RES_MEMORY ? "mem" : "io ", + res->start, res->end - res->start); + } + + /* Initialize the watchdog hardware. */ + if (wdatwd_set_stop(sc) != 0) + goto fail; + if ((e = wdatwd_clear_status(sc)) && e != EOPNOTSUPP) + goto fail; + if ((e = wdatwd_set_reboot(sc)) && e != EOPNOTSUPP) + goto fail; + if ((e = wdatwd_get_countdown(sc, &sc->default_timeout)) + && e != EOPNOTSUPP) + goto fail; + WDATWD_VERBOSE_PRINTF(dev, "initialized.\n"); + + /* Some sysctls. Most of them should go to WDATWD_VERBOSE_PRINTF(). */ + sctx = device_get_sysctl_ctx(dev); + soid = device_get_sysctl_tree(dev); + SYSCTL_ADD_U64(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "timeout_default", CTLFLAG_RD, SYSCTL_NULL_U64_PTR, + sc->default_timeout * sc->period, + "The default watchdog timeout in millisecond."); + SYSCTL_ADD_BOOL(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "timeout_configurable", CTLFLAG_RD, SYSCTL_NULL_BOOL_PTR, + STAILQ_EMPTY(&sc->action[ACPI_WDAT_SET_COUNTDOWN]) ? false : true, + "Whether the watchdog timeout is configurable or not."); + SYSCTL_ADD_U64(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "timeout", CTLFLAG_RD, &sc->timeout, 0, + "The current watchdog timeout in millisecond. " + "If 0, the default timeout is used."); + SYSCTL_ADD_BOOL(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "running", CTLFLAG_RD, &sc->running, 0, + "Whether the watchdog timer is running or not."); + + sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, wdatwd_event, sc, + EVENTHANDLER_PRI_ANY); + WDATWD_VERBOSE_PRINTF(dev, "watchdog registered.\n"); + + return (0); + +fail: + wdat_release_resource(dev); + + return (ENXIO); +} + +static int +wdatwd_detach(device_t dev) +{ + struct wdatwd_softc *sc; + int e; + + sc = device_get_softc(dev); + + EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); + e = wdatwd_set_stop(sc); + wdat_release_resource(dev); + + return (e); +} + +static int +wdatwd_suspend(device_t dev) +{ + struct wdatwd_softc *sc; + + sc = device_get_softc(dev); + + if (!sc->stop_in_sleep) + return (0); + + return wdatwd_set_stop(sc); +} + +static int +wdatwd_resume(device_t dev) +{ + struct wdatwd_softc *sc; + + sc = device_get_softc(dev); + + if (!sc->stop_in_sleep) + return (0); + + return (wdatwd_reset_countdown(sc) || wdatwd_set_running(sc)); +} + +static device_method_t wdatwd_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, wdatwd_probe), + DEVMETHOD(device_attach, wdatwd_attach), + DEVMETHOD(device_detach, wdatwd_detach), + DEVMETHOD(device_shutdown, wdatwd_detach), + DEVMETHOD(device_suspend, wdatwd_suspend), + DEVMETHOD(device_resume, wdatwd_resume), + DEVMETHOD_END +}; + +static driver_t wdatwd_driver = { + "wdatwd", + wdatwd_methods, + sizeof(struct wdatwd_softc), +}; + +DRIVER_MODULE(wdatwd, acpi, wdatwd_driver, 0, 0); +MODULE_DEPEND(wdatwd, acpi, 1, 1, 1); diff --git a/sys/modules/Makefile b/sys/modules/Makefile --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -395,6 +395,7 @@ vr \ vte \ ${_wbwd} \ + ${_wdatwd} \ wlan \ wlan_acl \ wlan_amrr \ @@ -706,6 +707,7 @@ _splash= splash _syscons= syscons _wbwd= wbwd +_wdatwd= wdatwd _aac= aac _aacraid= aacraid diff --git a/sys/modules/wdatwd/Makefile b/sys/modules/wdatwd/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/wdatwd/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/wdatwd + +KMOD= wdatwd +SRCS= wdatwd.c +SRCS+= opt_acpi.h acpi_if.h device_if.h bus_if.h + +.include