Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F153496181
D37493.id113475.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
27 KB
Referenced Files
None
Subscribers
None
D37493.id113475.diff
View Options
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,98 @@
+.\"-
+.\" Copyright (c) 2022 Tetsuya Uemura <t_uemura@macome.co.jp>
+.\" 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.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
@@ -69,6 +69,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,808 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2022 Tetsuya Uemura <t_uemura@macome.co.jp>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_acpi.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/interrupt.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/queue.h>
+#include <sys/rman.h>
+#include <sys/eventhandler.h>
+#include <sys/sysctl.h>
+#include <sys/watchdog.h>
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <contrib/dev/acpica/include/aclocal.h>
+#include <contrib/dev/acpica/include/actables.h>
+
+#include <dev/acpica/acpivar.h>
+
+/*
+ * 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 \
@@ -708,6 +709,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 <bsd.kmod.mk>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Apr 22, 11:34 AM (2 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
31974916
Default Alt Text
D37493.id113475.diff (27 KB)
Attached To
Mode
D37493: Add support for ACPI WDAT based watchdog timer.
Attached
Detach File
Event Timeline
Log In to Comment