diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -584,6 +584,7 @@ vtnet.4 \ watchdog.4 \ ${_wbwd.4} \ + ${_wdatwd.4} \ wg.4 \ witness.4 \ wlan.4 \ @@ -846,6 +847,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,98 @@ +.\"- +.\" Copyright (c) 2022 Tetsuya Uemura +.\" 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. +.\" +.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 described by ACPI WDAT (Watchdog +Action Table) found on some ACPI implementations. +.Pp +Since WDAT itself is an abstraction for the real hardware such as ICH WDT, +WDAT doesn't require the knowledge about the hardware at all. Thus, +.Nm +for example may support the newer generation hardwares that +.Xr ichwd 4 +driver is yet to support. +.Sh SYSCTL VARIABLES +The following read-only +.Xr sysctl 8 +variables are available: +.Bl -tag -width indent +.It Va dev.wdat.%d.can_set_timeout +0 if WDAT doesn't allow users to set the custom timeout, or any positive +integer if allowed. If 0, the watchdog will fire after its BIOS default timeout +period. +.It Va dev.wdat.%d.default_timeout_ms +If WDAT describes the action to guess the default timeout, it will be +converted to milli-sec order and is set to this variable. +.It Va dev.wdat.%d.running +0 if the watchdog is in stopped state or 1 in running state. +.It Va dev.wdat.%d.timeout_ms +Currently configured timeout period in milli-sec order. +.El +.Sh SEE ALSO +.Xr watchdog 4 , +.Xr ichwd 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 +.Re +.Sh HISTORY +The +.Nm +driver still in-development. +.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 b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3481,6 +3481,7 @@ dev/vt/vt_sysmouse.c optional vt dev/vte/if_vte.c optional vte pci dev/watchdog/watchdog.c standard +dev/wdatwd/wdatwd.c optional wdatwd dev/wg/if_wg.c optional wg \ compile-with "${NORMAL_C} -include $S/dev/wg/compat.h" dev/wg/wg_cookie.c optional wg \ 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,809 @@ +/*- + * 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. + */ + +/* + * ACPI WDAT based watchdog timer driver. The WDAT specification ``Hardware + * Watchdog Timers Design Specification'' was made public by Microsoft and can + * be found at the following location. + * WWW: https://download.microsoft.com/download/a/f/7/af7777e5-7dcd-4800-8a0a-b18336565f5b/HardwareWDTSpec.doc + */ + +#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 specifies the corresponding ACPI GAS but + * more than one instructions access the same or adjacent register region(s) + * so we need to canonicalize/merge all the specified GASs. + * + * type ACPI resource type and later SYS_RES_(IOPORT|MEMORY). + * start Region start address. + * end Region end address + 1. + * rid Resource rid assigned when allocated. + * res Resource when allocated. + * link Next/previous resource entry. + */ +struct wdat_res { + int type; + uint64_t start, end; + int rid; + struct resource *res; + 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 are operated sequencially. + * + * 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(). + * period Number of watchdog ticks per milli-sec. + * max Max. supported watchdog ticks to be set. + * min Min. supported watchdog ticks to be set. + * default_timeout BIOS configured watchdog ticks to fire. + * timeout User set watchdog timeout in ms or 0 if isn't changed. + * stop_in_sleep 0 if this watchdog keeps counting down during sleep. + * running 1 if this watchdog is running or 0 if stopped. + * 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; + unsigned int period; + unsigned int max; + unsigned int min; + uint64_t default_timeout; + uint64_t timeout; + unsigned int stop_in_sleep; + unsigned int running; + 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); + +#define wdatwd_acpi_status(rw) { \ + if (ACPI_FAILURE(status)) {\ + device_printf(sc->dev, \ + "action: 0x%02x, %s() returned: %d\n", \ + action, (rw), status); \ + return (ENXIO); \ + }; \ + } + +/* + * Do requested action. + */ +static int +wdatwd_action(struct wdatwd_softc *sc, unsigned int action, uint64_t val, uint64_t *ret) +{ + struct wdat_instr *wdat; + + 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_STATUS status; + 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); + wdatwd_acpi_status("AcpiRead"); + x >>= gas->BitOffset; + x &= wdat->entry.Mask; + *ret = (x == wdat->entry.Value) ? 1 : 0; + break; + case ACPI_WDAT_READ_COUNTDOWN: + status = AcpiRead(&x, gas); + wdatwd_acpi_status("AcpiRead"); + 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); + wdatwd_acpi_status("AcpiRead"); + y &= ~(wdat->entry.Mask << gas->BitOffset); + x |= y; + } + status = AcpiWrite(x, gas); + wdatwd_acpi_status("AcpiWrite"); + 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); + wdatwd_acpi_status("AcpiRead"); + y &= ~(wdat->entry.Mask << gas->BitOffset); + x |= y; + } + status = AcpiWrite(x, gas); + wdatwd_acpi_status("AcpiWrite"); + break; + default: + return (EINVAL); + } + } + + return (0); +} + +/* + * Reset the watchdog countdown. + */ +static int +wdatwd_reset_countdown(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, unsigned 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; + if (! (e = wdatwd_action( + sc, ACPI_WDAT_SET_COUNTDOWN, timeout, NULL))) { + sc->timeout = timeout * sc->period; + } + + return (e); +} + +/* + * Get the watchdog current countdown value. + */ +static int +wdatwd_get_current_countdown(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(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 = 0; + + e = wdatwd_action(sc, ACPI_WDAT_SET_RUNNING_STATE, 0, NULL); + sc->running = ! e; + return (e); +} + +/* + * Set the watchdog to stopped state. + */ +static int +wdatwd_set_stop(struct wdatwd_softc *sc) +{ + int e = 0; + + e = wdatwd_action(sc, ACPI_WDAT_SET_STOPPED_STATE, 0, NULL); + sc->running = !! e; + return (e); +} + +/* + * Clear the watchdog's boot status if the current boot was caused by the + * watchdog firing. + */ +static int +wdatwd_clear_status(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(struct wdatwd_softc *sc) +{ + return wdatwd_action(sc, ACPI_WDAT_SET_REBOOT, 0, NULL); +} + +/* + * Watchdog event handler. + */ +static void +wdatwd_event(void *private, unsigned int cmd, int *error) +{ + struct wdatwd_softc *sc = private; + int run[2]; + uint64_t cur[2], cnt[2]; + + if (bootverbose) { + run[0] = sc->running; + wdatwd_get_countdown(sc, cnt); + wdatwd_get_current_countdown(sc, cur); + } + + if ((cmd & WD_INTERVAL)) { + if (sc->running) + wdatwd_reset_countdown(sc); + else { + // 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 sec, 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 sec after + * starting, where 2.4 sec 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 sec which is much longer + * than 2.4 sec timeout. */ + wdatwd_reset_countdown(sc); + } + } else + wdatwd_set_stop(sc); + + if (bootverbose) { + run[1] = sc->running; + wdatwd_get_countdown(sc, cnt + 1); + wdatwd_get_current_countdown(sc, cur + 1); + 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 int +wdat_set_action(struct wdatwd_softc *sc, void *addr, int remaining) +{ + ACPI_WDAT_ENTRY *entry = addr; + struct wdat_instr *wdat; + + if (remaining < (int) sizeof(ACPI_WDAT_ENTRY)) + return (-1); + + // Skip actions beyond specification. + if (entry->Action < ACPI_WDAT_ACTION_RESERVED) { + 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; + char *cp; + int remaining, consumed; + + remaining = wdat->Header.Length - sizeof(ACPI_TABLE_WDAT); + while (remaining > 0) { + cp = (char *)wdat + wdat->Header.Length - remaining; + consumed = wdat_set_action(sc, cp, remaining); + if (consumed < 0) + device_printf(sc->dev, "inconsistent WDAT table.\n"); + if (consumed <= 0) + break; + else + remaining -= consumed; + } + + return; +} + +/* + * Compare the given GAS rr against the region start and end + * (actually end + 1). On return, type, start and end are updated as per rr. + * If not NULL, also overlap is updated depending on how rr and start-end + * overlap each other. + */ +static int +wdat_compare_region(ACPI_GENERIC_ADDRESS *rr, int *type, uint64_t *start, uint64_t *end, int *overlap) +{ + uint64_t s, e; + + s = e = rr->Address; + switch (rr->AccessWidth) { + case 1: + e += 1; + break; + case 2: + e += 2; + break; + case 3: + e += 4; + break; + case 4: + e += 8; + break; + default: + return (EINVAL); + } + + /* a) rr is fully covered by start-end. + * b) rr fully covers start-end. + * c) rr and start-end overlap partially. + * d) rr and start-end have no overlap. + * e) both have different resource type. + * f) overlap is set to NULL. + * overlap is a bit-field. bit 1 is for (a), bit 2 and 3 is for (b) and + * (c), where rr extends start to lower then bit 2 is set, and to upper + * bit 3 is set. */ + if (overlap) + *overlap = 0; + + if ((*type != rr->SpaceId) || (overlap == NULL) // (ef) + || (s > *end) || (e < *start)) {} // (d) + else { + if ((s >= *start) && (e <= *end)) // (a) + *overlap = 0x1; + if (s < *start) // (bc) + *overlap = 0x2; + if (e > *end) // (bc) + *overlap += 0x4; + } + + *start = s; + *end = e; + *type = rr->SpaceId; + + return (0); +} + +/* + * Try to merge the given GAS (in wdat) with the existing res in queue. + */ +static void +wdat_merge_resource(struct wdatwd_softc *sc, struct wdat_instr *wdat) +{ + struct wdat_res *res, *r2; + uint64_t s, e; + int overlap, type, found = 0; + + /* Try to merge the given GAS with the existing res list by extending + * one certain res to cover the GAS if such res and GAS overlap each + * other. If no overlapped res found, insert a new res at appropriate + * position. */ + TAILQ_FOREACH(res, &sc->res, link) { + type = res->type; + s = res->start; + e = res->end; + wdat_compare_region(&wdat->entry.RegisterRegion, + &type, &s, &e, &overlap); + // Try next res if GAS isn't mergeable. + if ((type != res->type) || (! overlap)) + continue; + + found = 1; + + // This res fully covers the GAS. + if (overlap == 0x1) + break; + + // This GAS extends the res to lower address (s < res->start). + if ((overlap & 0x2)) { + while ((r2 = TAILQ_PREV(res, res_head, link))) { + if (type != r2->type) + continue; + else if (s <= r2->end) { + s = r2->start; + TAILQ_REMOVE(&sc->res, r2, link); + free(r2, M_DEVBUF); + } else + break; + } + res->start = s; + } + // This GAS extends the res to upper address (e > res->end). + if ((overlap & 0x4)) { + while ((r2 = TAILQ_NEXT(res, link))) { + if (type != r2->type) + continue; + else if (e >= r2->start) { + e = r2->end; + TAILQ_REMOVE(&sc->res, r2, link); + free(r2, M_DEVBUF); + } else + break; + } + res->end = e; + } + break; + } + + // No extendable res at all. Now add new res to the queue. + if (! found) { + res = malloc(sizeof(*res), M_DEVBUF, M_WAITOK | M_ZERO); + res->type = type; + res->start = s; + res->end = e; + + TAILQ_FOREACH(r2, &sc->res, link) { + if (res->type != r2->type) + continue; + if (res->start < r2->start) { + TAILQ_INSERT_BEFORE(r2, res, link); + found = 1; + break; + } + } + if (! found) + TAILQ_INSERT_TAIL(&sc->res, res, link); + } +} + +/* + * Release the already allocated resource. + */ +static void +wdat_release_resource(device_t dev) +{ + struct wdatwd_softc *sc; + struct wdat_res *res; + + sc = device_get_softc(dev); + + TAILQ_FOREACH(res, &sc->res, link) + if (res->res) { + bus_release_resource(dev, res->type, + res->rid, res->res); + bus_delete_resource(dev, res->type, res->rid); + res->res = NULL; + } +} + +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 0; +} + +static int +wdatwd_attach(device_t dev) +{ + struct wdatwd_softc *sc; + ACPI_STATUS status; + int i, rid, e = 0; + struct wdat_instr *wdat; + struct sysctl_ctx_list *sctx; + struct sysctl_oid *soid; + struct wdat_res *res; + + sc = device_get_softc(dev); + sc->dev = dev; + + for (i = 0; i < ACPI_WDAT_ACTION_RESERVED; ++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); + // Parse defined watchdog actions. + wdat_parse_action_table(sc); + + AcpiPutTable((ACPI_TABLE_HEADER *) sc->wdat); + + // Verbose logging. + for (i = 0; i < ACPI_WDAT_ACTION_RESERVED; ++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 < ACPI_WDAT_ACTION_RESERVED; ++i) + STAILQ_FOREACH(wdat, &sc->action[i], next) { + if (TAILQ_EMPTY(&sc->res)) { + res = malloc(sizeof(*res), + M_DEVBUF, M_WAITOK | M_ZERO); + wdat_compare_region( + &wdat->entry.RegisterRegion, + &res->type, &res->start, &res->end, NULL); + TAILQ_INSERT_HEAD(&sc->res, res, link); + } else + wdat_merge_resource(sc, wdat); + } + + // 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: + return (EOPNOTSUPP); + } + + 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); + e = ENOMEM; + 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 (((e = wdatwd_set_stop(sc))) + || ((e = wdatwd_clear_status(sc)) && (e != EOPNOTSUPP)) + || ((e = wdatwd_set_reboot(sc)) && (e != EOPNOTSUPP)) + || ((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_ms", CTLFLAG_RD, &sc->timeout, 0, + "Current watchdog timeout in ms if changed."); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "running", CTLFLAG_RD, &sc->running, 0, + "Watchdog is running now."); + SYSCTL_ADD_U64(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "default_timeout_ms", CTLFLAG_RD, SYSCTL_NULL_U64_PTR, + sc->default_timeout * sc->period, + "BIOS configured watchdog timeout in ms."); + SYSCTL_ADD_UINT(sctx, SYSCTL_CHILDREN(soid), OID_AUTO, + "can_set_timeout", CTLFLAG_RD, SYSCTL_NULL_UINT_PTR, + ! STAILQ_EMPTY(&sc->action[ACPI_WDAT_SET_COUNTDOWN]), + "Watchdog timeout is configurable."); + + sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, wdatwd_event, sc, 0); + 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); + + return sc->stop_in_sleep ? 0 : wdatwd_set_stop(sc); +} + +static int +wdatwd_resume(device_t dev) +{ + struct wdatwd_softc *sc; + int e = 0; + + sc = device_get_softc(dev); + + if (sc->stop_in_sleep) + (e = wdatwd_reset_countdown(sc)) + || (e = wdatwd_set_running(sc)); + + return (e); +} + +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 @@ -397,6 +397,7 @@ vr \ vte \ ${_wbwd} \ + wdatwd \ wlan \ wlan_acl \ wlan_amrr \ 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