--- /dev/null	2022-11-19 17:31:08.863965000 +0900
+++ sys/dev/wdatwd/wdatwd.c	2022-11-17 13:07:02.586417178 +0900
@@ -0,0 +1,810 @@
+/*-
+ * 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>
+
+static devclass_t wdatwd_devclass;
+
+/* 
+ * 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, wdatwd_devclass, 0, 0);
+MODULE_DEPEND(wdatwd, acpi, 1, 1, 1);
