Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 3 Jan 2023 15:35:39 GMT
From:      Takanori Watanabe <takawata@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 99aeb219cad9 - main - wdatwd: Add support for ACPI WDAT based watchdog timer.
Message-ID:  <202301031535.303FZdXe018764@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by takawata:

URL: https://cgit.FreeBSD.org/src/commit/?id=99aeb219cad9a5a0347ca3ffef963bb614c99eb9

commit 99aeb219cad9a5a0347ca3ffef963bb614c99eb9
Author:     Takanori Watanabe <takawata@FreeBSD.org>
AuthorDate: 2023-01-03 15:16:36 +0000
Commit:     Takanori Watanabe <takawata@FreeBSD.org>
CommitDate: 2023-01-03 15:36:24 +0000

    wdatwd: Add support for ACPI WDAT based watchdog timer.
    
    Simply said, WDAT is an abstraction for the real WDT hardware. For
    instance, to add a newer generation WDT to ichwd(4), one must know the
    detailed hardware registers, etc..
    
    With WDAT, the necessary IO accesses to operate the WDT are comprehensively
    described in it and no hardware knowledge is required.
    
    With this driver, the WDT on Advantech ARK-1124C, Dell R210 and Dell R240 are
    detected and operated flawlessly.
    * While R210 is also supported by ichwd(4), others are not supported yet.
    
    The unfortunate thing is that not all systems have WDAT defined.
    
    Submitted by: t_uemura at macome.co.jp
    Reviewed by: hrs
    Differential Revision: https://reviews.freebsd.org/D37493
---
 share/man/man4/Makefile     |   2 +
 share/man/man4/wdatwd.4     |  91 +++++
 sys/conf/files.amd64        |   1 +
 sys/conf/files.i386         |   1 +
 sys/dev/wdatwd/wdatwd.c     | 846 ++++++++++++++++++++++++++++++++++++++++++++
 sys/modules/Makefile        |   2 +
 sys/modules/wdatwd/Makefile |   9 +
 7 files changed, 952 insertions(+)

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



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202301031535.303FZdXe018764>