/*- * 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: %llu -> %llu, cur: %llu -> %llu\n", cmd, run[0], run[1], (unsigned long long) cnt[0], (unsigned long long) cnt[1], (unsigned long long)cur[0], (unsigned long long)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%llx (%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", (unsigned long long ) 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%llx (%lld byte(s)): " "alloc' failed\n", res->type == SYS_RES_MEMORY ? "mem" : "io ", (unsigned long long )res->start, (unsigned long long )(res->end - res->start)); goto fail; } WDATWD_VERBOSE_PRINTF(dev, "%s at 0x%llx (%lld byte(s)): " "alloc'ed\n", res->type == SYS_RES_MEMORY ? "mem" : "io ", (unsigned long long )res->start, (unsigned long long) (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);