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
This commit is contained in:
Takanori Watanabe 2023-01-04 00:16:36 +09:00
parent a0c50eda29
commit 99aeb219ca
7 changed files with 952 additions and 0 deletions

View File

@ -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

91
share/man/man4/wdatwd.4 Normal file
View File

@ -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.

View File

@ -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

View File

@ -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 \

846
sys/dev/wdatwd/wdatwd.c Normal file
View File

@ -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
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);

View File

@ -395,6 +395,7 @@ SUBDIR= \
vr \
vte \
${_wbwd} \
${_wdatwd} \
wlan \
wlan_acl \
wlan_amrr \
@ -706,6 +707,7 @@ _speaker= speaker
_splash= splash
_syscons= syscons
_wbwd= wbwd
_wdatwd= wdatwd
_aac= aac
_aacraid= aacraid

View File

@ -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>