The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/dev/wdatwd/wdatwd.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

    1 /*-
    2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
    3  *
    4  * Copyright (c) 2022 Tetsuya Uemura <t_uemura@macome.co.jp>
    5  *
    6  * Redistribution and use in source and binary forms, with or without
    7  * modification, are permitted provided that the following conditions
    8  * are met:
    9  * 1. Redistributions of source code must retain the above copyright
   10  *    notice, this list of conditions and the following disclaimer.
   11  * 2. Redistributions in binary form must reproduce the above copyright
   12  *    notice, this list of conditions and the following disclaimer in the
   13  *    documentation and/or other materials provided with the distribution.
   14  *
   15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
   16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   25  * SUCH DAMAGE.
   26  */
   27 
   28 #include <sys/cdefs.h>
   29 __FBSDID("$FreeBSD$");
   30 
   31 #include "opt_acpi.h"
   32 
   33 #include <sys/param.h>
   34 #include <sys/systm.h>
   35 #include <sys/bus.h>
   36 #include <sys/callout.h>
   37 #include <sys/eventhandler.h>
   38 #include <sys/interrupt.h>
   39 #include <sys/kernel.h>
   40 #include <sys/malloc.h>
   41 #include <sys/module.h>
   42 #include <sys/queue.h>
   43 #include <sys/rman.h>
   44 #include <sys/sysctl.h>
   45 #include <sys/watchdog.h>
   46 #include <vm/vm.h>
   47 #include <vm/pmap.h>
   48 
   49 #include <contrib/dev/acpica/include/acpi.h>
   50 #include <contrib/dev/acpica/include/accommon.h>
   51 #include <contrib/dev/acpica/include/aclocal.h>
   52 #include <contrib/dev/acpica/include/actables.h>
   53 
   54 #include <dev/acpica/acpivar.h>
   55 
   56 /*
   57  * Resource entry. Every instruction has the corresponding ACPI GAS but two or
   58  * more instructions may access the same or adjacent register region(s). So we
   59  * need to merge all the specified resources.
   60  *
   61  * res   Resource when allocated.
   62  * start Region start address.
   63  * end   Region end address + 1.
   64  * rid   Resource rid assigned when allocated.
   65  * type  ACPI resource type, SYS_RES_IOPORT or SYS_RES_MEMORY.
   66  * link  Next/previous resource entry.
   67  */
   68 struct wdat_res {
   69         struct resource         *res;
   70         uint64_t                start;
   71         uint64_t                end;
   72         int                     rid;
   73         int                     type;
   74         TAILQ_ENTRY(wdat_res)   link;
   75 };
   76 
   77 /*
   78  * Instruction entry. Every instruction itself is actually a single register
   79  * read or write (and subsequent bit operation(s)).
   80  * 0 or more instructions are tied to every watchdog action and once an action
   81  * is kicked, the corresponding entries run sequentially.
   82  *
   83  * entry Permanent copy of ACPI_WDAT_ENTRY entry (sub-table).
   84  * next  Next instruction entry.
   85  */
   86 struct wdat_instr {
   87         ACPI_WDAT_ENTRY         entry;
   88         STAILQ_ENTRY(wdat_instr) next;
   89 };
   90 
   91 /*
   92  * dev             Watchdog device.
   93  * wdat            ACPI WDAT table, can be accessed until AcpiPutTable().
   94  * default_timeout BIOS configured watchdog ticks to fire.
   95  * timeout         User configured timeout in millisecond or 0 if isn't set.
   96  * max             Max. supported watchdog ticks to be set.
   97  * min             Min. supported watchdog ticks to be set.
   98  * period          Milliseconds per watchdog tick.
   99  * running         True if this watchdog is running or false if stopped.
  100  * stop_in_sleep   False if this watchdog keeps counting down during sleep.
  101  * ev_tag          Tag for EVENTHANDLER_*().
  102  * action          Array of watchdog instruction sets, each indexed by action.
  103  */
  104 struct wdatwd_softc {
  105         device_t                dev;
  106         ACPI_TABLE_WDAT         *wdat;
  107         uint64_t                default_timeout;
  108         uint64_t                timeout;
  109         u_int                   max;
  110         u_int                   min;
  111         u_int                   period;
  112         bool                    running;
  113         bool                    stop_in_sleep;
  114         eventhandler_tag        ev_tag;
  115         STAILQ_HEAD(, wdat_instr) action[ACPI_WDAT_ACTION_RESERVED];
  116         TAILQ_HEAD(res_head, wdat_res) res;
  117 };
  118 
  119 #define WDATWD_VERBOSE_PRINTF(dev, ...)                                 \
  120         do {                                                            \
  121                 if (bootverbose)                                        \
  122                         device_printf(dev, __VA_ARGS__);                \
  123         } while (0)
  124 
  125 /*
  126  * Do requested action.
  127  */
  128 static int
  129 wdatwd_action(const struct wdatwd_softc *sc, const u_int action, const uint64_t val, uint64_t *ret)
  130 {
  131         struct wdat_instr       *wdat;
  132         const char              *rw = NULL;
  133         ACPI_STATUS             status;
  134 
  135         if (STAILQ_EMPTY(&sc->action[action])) {
  136                 WDATWD_VERBOSE_PRINTF(sc->dev,
  137                     "action not supported: 0x%02x\n", action);
  138                 return (EOPNOTSUPP);
  139         }
  140 
  141         STAILQ_FOREACH(wdat, &sc->action[action], next) {
  142                 ACPI_GENERIC_ADDRESS    *gas = &wdat->entry.RegisterRegion;
  143                 uint64_t                x, y;
  144 
  145                 switch (wdat->entry.Instruction
  146                     & ~ACPI_WDAT_PRESERVE_REGISTER) {
  147                     case ACPI_WDAT_READ_VALUE:
  148                         status = AcpiRead(&x, gas);
  149                         if (ACPI_FAILURE(status)) {
  150                                 rw = "AcpiRead";
  151                                 goto fail;
  152                         }
  153                         x >>= gas->BitOffset;
  154                         x &= wdat->entry.Mask;
  155                         *ret = (x == wdat->entry.Value) ? 1 : 0;
  156                         break;
  157                     case ACPI_WDAT_READ_COUNTDOWN:
  158                         status = AcpiRead(&x, gas);
  159                         if (ACPI_FAILURE(status)) {
  160                                 rw = "AcpiRead";
  161                                 goto fail;
  162                         }
  163                         x >>= gas->BitOffset;
  164                         x &= wdat->entry.Mask;
  165                         *ret = x;
  166                         break;
  167                     case ACPI_WDAT_WRITE_VALUE:
  168                         x = wdat->entry.Value & wdat->entry.Mask;
  169                         x <<= gas->BitOffset;
  170                         if (wdat->entry.Instruction
  171                             & ACPI_WDAT_PRESERVE_REGISTER) {
  172                                 status = AcpiRead(&y, gas);
  173                                 if (ACPI_FAILURE(status)) {
  174                                         rw = "AcpiRead";
  175                                         goto fail;
  176                                 }
  177                                 y &= ~(wdat->entry.Mask << gas->BitOffset);
  178                                 x |= y;
  179                         }
  180                         status = AcpiWrite(x, gas);
  181                         if (ACPI_FAILURE(status)) {
  182                                 rw = "AcpiWrite";
  183                                 goto fail;
  184                         }
  185                         break;
  186                     case ACPI_WDAT_WRITE_COUNTDOWN:
  187                         x = val & wdat->entry.Mask;
  188                         x <<= gas->BitOffset;
  189                         if (wdat->entry.Instruction
  190                             & ACPI_WDAT_PRESERVE_REGISTER) {
  191                                 status = AcpiRead(&y, gas);
  192                                 if (ACPI_FAILURE(status)) {
  193                                         rw = "AcpiRead";
  194                                         goto fail;
  195                                 }
  196                                 y &= ~(wdat->entry.Mask << gas->BitOffset);
  197                                 x |= y;
  198                         }
  199                         status = AcpiWrite(x, gas);
  200                         if (ACPI_FAILURE(status)) {
  201                                 rw = "AcpiWrite";
  202                                 goto fail;
  203                         }
  204                         break;
  205                     default:
  206                         return (EINVAL);
  207                 }
  208         }
  209 
  210         return (0);
  211 
  212 fail:
  213         device_printf(sc->dev, "action: 0x%02x, %s() returned: %d\n",
  214             action, rw, status);
  215         return (ENXIO);
  216 }
  217 
  218 /*
  219  * Reset the watchdog countdown.
  220  */
  221 static int
  222 wdatwd_reset_countdown(const struct wdatwd_softc *sc)
  223 {
  224         return wdatwd_action(sc, ACPI_WDAT_RESET, 0, NULL);
  225 }
  226 
  227 /*
  228  * Set the watchdog countdown value. In WDAT specification, this is optional.
  229  */
  230 static int
  231 wdatwd_set_countdown(struct wdatwd_softc *sc, u_int cmd)
  232 {
  233         uint64_t                timeout;
  234         int                     e;
  235 
  236         cmd &= WD_INTERVAL;
  237         timeout = ((uint64_t) 1 << cmd) / 1000000 / sc->period;
  238         if (timeout > sc->max)
  239                 timeout = sc->max;
  240         else if (timeout < sc->min)
  241                 timeout = sc->min;
  242 
  243         e = wdatwd_action(sc, ACPI_WDAT_SET_COUNTDOWN, timeout, NULL);
  244         if (e == 0)
  245                 sc->timeout = timeout * sc->period;
  246 
  247         return (e);
  248 }
  249 
  250 /*
  251  * Get the watchdog current countdown value.
  252  */
  253 static int
  254 wdatwd_get_current_countdown(const struct wdatwd_softc *sc, uint64_t *timeout)
  255 {
  256         return wdatwd_action(sc, ACPI_WDAT_GET_CURRENT_COUNTDOWN, 0, timeout);
  257 }
  258 
  259 /*
  260  * Get the watchdog countdown value the watchdog is configured to fire.
  261  */
  262 static int
  263 wdatwd_get_countdown(const struct wdatwd_softc *sc, uint64_t *timeout)
  264 {
  265         return wdatwd_action(sc, ACPI_WDAT_GET_COUNTDOWN, 0, timeout);
  266 }
  267 
  268 /*
  269  * Set the watchdog to running state.
  270  */
  271 static int
  272 wdatwd_set_running(struct wdatwd_softc *sc)
  273 {
  274         int                     e;
  275 
  276         e = wdatwd_action(sc, ACPI_WDAT_SET_RUNNING_STATE, 0, NULL);
  277         if (e == 0)
  278                 sc->running = true;
  279         return (e);
  280 }
  281 
  282 /*
  283  * Set the watchdog to stopped state.
  284  */
  285 static int
  286 wdatwd_set_stop(struct wdatwd_softc *sc)
  287 {
  288         int                     e;
  289 
  290         e = wdatwd_action(sc, ACPI_WDAT_SET_STOPPED_STATE, 0, NULL);
  291         if (e == 0)
  292                 sc->running = false;
  293         return (e);
  294 }
  295 
  296 /*
  297  * Clear the watchdog's boot status if the current boot was caused by the
  298  * watchdog firing.
  299  */
  300 static int
  301 wdatwd_clear_status(const struct wdatwd_softc *sc)
  302 {
  303         return wdatwd_action(sc, ACPI_WDAT_SET_STATUS, 0, NULL);
  304 }
  305 
  306 /*
  307  * Set the watchdog to reboot when it is fired.
  308  */
  309 static int
  310 wdatwd_set_reboot(const struct wdatwd_softc *sc)
  311 {
  312         return wdatwd_action(sc, ACPI_WDAT_SET_REBOOT, 0, NULL);
  313 }
  314 
  315 /*
  316  * Watchdog event handler.
  317  */
  318 static void
  319 wdatwd_event(void *private, u_int cmd, int *error)
  320 {
  321         struct wdatwd_softc     *sc = private;
  322         uint64_t                cur[2], cnt[2];
  323         bool                    run[2];
  324 
  325         if (bootverbose) {
  326                 run[0] = sc->running;
  327                 if (wdatwd_get_countdown(sc, &cnt[0]) != 0) 
  328                         cnt[0] = 0;
  329                 if (wdatwd_get_current_countdown(sc, &cur[0]) != 0)
  330                         cur[0] = 0;
  331         }
  332 
  333         if ((cmd & WD_INTERVAL) == 0)
  334                 wdatwd_set_stop(sc);
  335         else {
  336                 if (!sc->running) {
  337                         /* ACPI_WDAT_SET_COUNTDOWN may not be implemented. */
  338                         wdatwd_set_countdown(sc, cmd);
  339                         wdatwd_set_running(sc);
  340                         /*
  341                          * In the first wdatwd_event() call, it sets the
  342                          * watchdog timeout to a considerably larger value such
  343                          * as 137 seconds, then kicks the watchdog to start
  344                          * counting down. Weirdly though, on a Dell R210 BIOS
  345                          * 1.12.0, a supplemental reset action must be
  346                          * triggered for the newly set timeout value to take
  347                          * effect. Without it, the watchdog fires 2.4 seconds
  348                          * after starting, where 2.4 seconds is its initially
  349                          * set timeout. This failure scenario is seen by first
  350                          * starting watchdogd(8) without wdatwd registered then
  351                          * kldload it. In steady state, watchdogd pats the
  352                          * watchdog every 10 or so seconds which is much longer
  353                          * than 2.4 seconds timeout.
  354                          */
  355                 }
  356                 wdatwd_reset_countdown(sc);
  357         }
  358 
  359         if (bootverbose) {
  360                 run[1] = sc->running;
  361                 if (wdatwd_get_countdown(sc, &cnt[1]) != 0)
  362                         cnt[1] = 0;
  363                 if (wdatwd_get_current_countdown(sc, &cur[1]) != 0)
  364                         cur[1] = 0;
  365                 WDATWD_VERBOSE_PRINTF(sc->dev, "cmd: %u, sc->running: "
  366                     "%d -> %d, cnt: %llu -> %llu, cur: %llu -> %llu\n", cmd,
  367                                       run[0], run[1], 
  368                                       (unsigned long long) cnt[0],
  369                                       (unsigned long long) cnt[1],
  370                                       (unsigned long long)cur[0],
  371                                       (unsigned long long)cur[1]);
  372         }
  373 
  374         return;
  375 }
  376 
  377 static ssize_t
  378 wdat_set_action(struct wdatwd_softc *sc, ACPI_WDAT_ENTRY *addr, ssize_t remaining)
  379 {
  380         ACPI_WDAT_ENTRY         *entry = addr;
  381         struct wdat_instr       *wdat;
  382 
  383         if (remaining < sizeof(ACPI_WDAT_ENTRY))
  384                 return (-EINVAL);
  385 
  386         /* Skip actions beyond specification. */
  387         if (entry->Action < nitems(sc->action)) {
  388                 wdat = malloc(sizeof(*wdat), M_DEVBUF, M_WAITOK | M_ZERO);
  389                 wdat->entry = *entry;
  390                 STAILQ_INSERT_TAIL(&sc->action[entry->Action], wdat, next);
  391         }
  392         return sizeof(ACPI_WDAT_ENTRY);
  393 }
  394 
  395 /*
  396  * Transform every ACPI_WDAT_ENTRY to wdat_instr by calling wdat_set_action().
  397  */
  398 static void
  399 wdat_parse_action_table(struct wdatwd_softc *sc)
  400 {
  401         ACPI_TABLE_WDAT         *wdat = sc->wdat;
  402         ssize_t                 remaining, consumed;
  403         char                    *cp;
  404 
  405         remaining = wdat->Header.Length - sizeof(ACPI_TABLE_WDAT);
  406         while (remaining > 0) {
  407                 cp = (char *)wdat + wdat->Header.Length - remaining;
  408                 consumed = wdat_set_action(sc, (ACPI_WDAT_ENTRY *)cp,
  409                     remaining);
  410                 if (consumed < 0) {
  411                         device_printf(sc->dev, "inconsistent WDAT table.\n");
  412                         break;
  413                 }
  414                         remaining -= consumed;
  415         }
  416 }
  417 
  418 /*
  419  * Decode the given GAS rr and set its type, start and end (actually end + 1)
  420  * in the newly malloc()'ed res.
  421  */
  422 static struct wdat_res *
  423 wdat_alloc_region(ACPI_GENERIC_ADDRESS *rr)
  424 {
  425         struct wdat_res *res;
  426 
  427         if (rr->AccessWidth < 1 || rr->AccessWidth > 4)
  428                 return (NULL);
  429 
  430         res = malloc(sizeof(*res),
  431             M_DEVBUF, M_WAITOK | M_ZERO);
  432         if (res != NULL) {
  433                 res->start = rr->Address;
  434                 res->end   = res->start + (1 << (rr->AccessWidth - 1));
  435                 res->type  = rr->SpaceId;
  436         }
  437         return (res);
  438 }
  439 
  440 #define OVERLAP_NONE    0x0 // no overlap.
  441 #define OVERLAP_SUBSET  0x1 // res2 is fully covered by res1.
  442 #define OVERLAP_START   0x2 // the start of res2 is overlaped.
  443 #define OVERLAP_END     0x4 // the end of res2 is overlapped.
  444 
  445 /*
  446  * Compare the given res1 and res2, and one of the above OVERLAP_* constant, or
  447  * in case res2 is larger than res1 at both the start and the end,
  448  * OVERLAP_START | OVERLAP_END, is returned.
  449  */
  450 static int
  451 wdat_compare_region(const struct wdat_res *res1, const struct wdat_res *res2)
  452 {
  453         int overlap;
  454 
  455         /*
  456          * a) both have different resource type. == OVERLAP_NONE
  457          * b) res2 and res1 have no overlap.     == OVERLAP_NONE
  458          * c) res2 is fully covered by res1.     == OVERLAP_SUBSET
  459          * d) res2 and res1 overlap partially.   == OVERLAP_START or
  460          *                                          OVERLAP_END
  461          * e) res2 fully covers res1.            == OVERLAP_START | OVERLAP_END
  462          */
  463         overlap = 0;
  464 
  465         if (res1->type != res2->type || res1->start > res2->end
  466             || res1->end < res2->start)
  467                 overlap |= OVERLAP_NONE;
  468         else {
  469                 if (res1->start <= res2->start && res1->end >= res2->end)
  470                         overlap |= OVERLAP_SUBSET;
  471                 if (res1->start > res2->start)
  472                         overlap |= OVERLAP_START;
  473                 if (res1->end < res2->end)
  474                         overlap |= OVERLAP_END;
  475         }
  476 
  477         return (overlap);
  478 }
  479 
  480 /*
  481  * Try to merge the given newres with the existing sc->res.
  482  */
  483 static void
  484 wdat_merge_region(struct wdatwd_softc *sc, struct wdat_res *newres)
  485 {
  486         struct wdat_res         *res1, *res2, *res_safe, *res_itr;
  487         int                     overlap;
  488 
  489         if (TAILQ_EMPTY(&sc->res)) {
  490                 TAILQ_INSERT_HEAD(&sc->res, newres, link);
  491                 return;
  492         }
  493 
  494         overlap = OVERLAP_NONE;
  495 
  496         TAILQ_FOREACH_SAFE(res1, &sc->res, link, res_safe) {
  497                 overlap = wdat_compare_region(res1, newres);
  498 
  499                 /* Try next res if newres isn't mergeable. */
  500                 if (overlap == OVERLAP_NONE)
  501                         continue;
  502 
  503                 /* This res fully covers newres. */
  504                 if (overlap == OVERLAP_SUBSET)
  505                         break;
  506 
  507                 /* Newres extends the existing res res1 to lower. */
  508                 if ((overlap & OVERLAP_START)) {
  509                         res1->start = newres->start;
  510                         res_itr = res1;
  511                         /* Try to merge more res if possible. */
  512                         while ((res2 = TAILQ_PREV(res_itr, res_head, link))) {
  513                                 if (res1->type != res2->type) {
  514                                         res_itr = res2;
  515                                         continue;
  516                                 } else if (res1->start <= res2->end) {
  517                                         res1->start = res2->start;
  518                                         TAILQ_REMOVE(&sc->res, res2, link);
  519                                         free(res2, M_DEVBUF);
  520                                 } else
  521                                         break;
  522                         }
  523                 }
  524                 /* Newres extends the existing res res1 to upper. */
  525                 if ((overlap & OVERLAP_END)) {
  526                         res1->end = newres->end;
  527                         res_itr = res1;
  528                         /* Try to merge more res if possible. */
  529                         while ((res2 = TAILQ_NEXT(res_itr, link))) {
  530                                 if (res1->type != res2->type) {
  531                                         res_itr = res2;
  532                                         continue;
  533                                 } else if (res1->end >= res2->start) {
  534                                         res1->end = res2->end;
  535                                         TAILQ_REMOVE(&sc->res, res2, link);
  536                                         free(res2, M_DEVBUF);
  537                                 } else
  538                                         break;
  539                         }
  540                 }
  541                 break;
  542         }
  543 
  544         /*
  545          * If newres extends the existing res, newres must be free()'ed.
  546          * Otherwise insert newres into sc->res at appropriate position
  547          * (the lowest address region appears first).
  548          */
  549         if (overlap > OVERLAP_NONE)
  550                 free(newres, M_DEVBUF);
  551         else {
  552                 TAILQ_FOREACH(res1, &sc->res, link) {
  553                         if (newres->type != res1->type)
  554                                 continue;
  555                         if (newres->start < res1->start) {
  556                                 TAILQ_INSERT_BEFORE(res1, newres, link);
  557                                 break;
  558                         }
  559                 }
  560                 if (res1 == NULL)
  561                         TAILQ_INSERT_TAIL(&sc->res, newres, link);
  562         }
  563 }
  564 
  565 /*
  566  * Release the already allocated resource.
  567  */
  568 static void
  569 wdat_release_resource(device_t dev)
  570 {
  571         struct wdatwd_softc     *sc;
  572         struct wdat_instr       *wdat;
  573         struct wdat_res         *res;
  574         int                     i;
  575 
  576         sc = device_get_softc(dev);
  577 
  578         TAILQ_FOREACH(res, &sc->res, link)
  579                 if (res->res != NULL) {
  580                         bus_release_resource(dev, res->type,
  581                             res->rid, res->res);
  582                         bus_delete_resource(dev, res->type, res->rid);
  583                         res->res = NULL;
  584                 }
  585 
  586         for (i = 0; i < nitems(sc->action); ++i)
  587                 while (!STAILQ_EMPTY(&sc->action[i])) {
  588                         wdat = STAILQ_FIRST(&sc->action[i]);
  589                         STAILQ_REMOVE_HEAD(&sc->action[i], next);
  590                         free(wdat, M_DEVBUF);
  591                 }
  592 
  593         while (!TAILQ_EMPTY(&sc->res)) {
  594                 res = TAILQ_FIRST(&sc->res);
  595                 TAILQ_REMOVE(&sc->res, res, link);
  596                 free(res, M_DEVBUF);
  597         }
  598 }
  599 
  600 static int
  601 wdatwd_probe(device_t dev)
  602 {
  603         ACPI_TABLE_WDAT         *wdat;
  604         ACPI_STATUS             status;
  605 
  606         /* Without WDAT table we have nothing to do. */
  607         status = AcpiGetTable(ACPI_SIG_WDAT, 0, (ACPI_TABLE_HEADER **)&wdat);
  608         if (ACPI_FAILURE(status))
  609                 return (ENXIO);
  610 
  611         /* Try to allocate one resource and assume wdatwd is already attached
  612          * if it fails. */
  613         {
  614                 int             type, rid = 0;
  615                 struct resource *res;
  616 
  617                 if (acpi_bus_alloc_gas(dev, &type, &rid,
  618                     &((ACPI_WDAT_ENTRY *)(wdat + 1))->RegisterRegion,
  619                     &res, 0))
  620                         return (ENXIO);
  621                 bus_release_resource(dev, type, rid, res);
  622                 bus_delete_resource(dev, type, rid);
  623         }
  624 
  625         WDATWD_VERBOSE_PRINTF(dev, "Flags: 0x%x, TimerPeriod: %d ms/cnt, "
  626             "MaxCount: %d cnt (%d ms), MinCount: %d cnt (%d ms)\n",
  627             (int)wdat->Flags, (int)wdat->TimerPeriod,
  628             (int)wdat->MaxCount, (int)(wdat->MaxCount * wdat->TimerPeriod),
  629             (int)wdat->MinCount, (int)(wdat->MinCount * wdat->TimerPeriod));
  630         /* WDAT timer consistency. */
  631         if ((wdat->TimerPeriod < 1) || (wdat->MinCount > wdat->MaxCount)) {
  632                 device_printf(dev, "inconsistent timer variables.\n");
  633                 return (EINVAL);
  634         }
  635 
  636         AcpiPutTable((ACPI_TABLE_HEADER *)wdat);
  637 
  638         device_set_desc(dev, "ACPI WDAT Watchdog Interface");
  639         return (BUS_PROBE_DEFAULT);
  640 }
  641 
  642 static int
  643 wdatwd_attach(device_t dev)
  644 {
  645         struct wdatwd_softc     *sc;
  646         struct wdat_instr       *wdat;
  647         struct wdat_res         *res;
  648         struct sysctl_ctx_list  *sctx;
  649         struct sysctl_oid       *soid;
  650         ACPI_STATUS             status;
  651         int                     e, i, rid;
  652 
  653         sc = device_get_softc(dev);
  654         sc->dev = dev;
  655 
  656         for (i = 0; i < nitems(sc->action); ++i)
  657                 STAILQ_INIT(&sc->action[i]);
  658 
  659         /* Search and parse WDAT table. */
  660         status = AcpiGetTable(ACPI_SIG_WDAT, 0,
  661             (ACPI_TABLE_HEADER **)&sc->wdat);
  662         if (ACPI_FAILURE(status))
  663                 return (ENXIO);
  664 
  665         /* Parse watchdog variables. */
  666         sc->period = sc->wdat->TimerPeriod;
  667         sc->max = sc->wdat->MaxCount;
  668         sc->min = sc->wdat->MinCount;
  669         sc->stop_in_sleep = (sc->wdat->Flags & ACPI_WDAT_STOPPED)
  670             ? true : false;
  671         /* Parse defined watchdog actions. */
  672         wdat_parse_action_table(sc);
  673 
  674         AcpiPutTable((ACPI_TABLE_HEADER *)sc->wdat);
  675 
  676         /* Verbose logging. */
  677         if (bootverbose) {
  678                 for (i = 0; i < nitems(sc->action); ++i)
  679                         STAILQ_FOREACH(wdat, &sc->action[i], next) {
  680                                 WDATWD_VERBOSE_PRINTF(dev, "action: 0x%02x, "
  681                                     "%s %s at 0x%llx (%d bit(s), offset %d bit(s))\n",
  682                                     i,
  683                                     wdat->entry.RegisterRegion.SpaceId
  684                                         == ACPI_ADR_SPACE_SYSTEM_MEMORY
  685                                         ? "mem"
  686                                         : wdat->entry.RegisterRegion.SpaceId
  687                                             == ACPI_ADR_SPACE_SYSTEM_IO
  688                                             ? "io "
  689                                             : "???",
  690                                     wdat->entry.RegisterRegion.AccessWidth == 1
  691                                         ? "byte "
  692                                         : wdat->entry.RegisterRegion.AccessWidth == 2
  693                                             ? "word "
  694                                             : wdat->entry.RegisterRegion.AccessWidth == 3
  695                                                 ? "dword"
  696                                                 : wdat->entry.RegisterRegion.AccessWidth == 4
  697                                                     ? "qword"
  698                                                     : "undef",
  699                                     (unsigned long long )
  700                                     wdat->entry.RegisterRegion.Address,
  701                                     wdat->entry.RegisterRegion.BitWidth,
  702                                     wdat->entry.RegisterRegion.BitOffset);
  703                 }
  704         }
  705 
  706         /* Canonicalize the requested resources. */
  707         TAILQ_INIT(&sc->res);
  708         for (i = 0; i < nitems(sc->action); ++i)
  709                 STAILQ_FOREACH(wdat, &sc->action[i], next) {
  710                         res = wdat_alloc_region(&wdat->entry.RegisterRegion);
  711                         if (res == NULL)
  712                                 goto fail;
  713                         wdat_merge_region(sc, res);
  714                 }
  715 
  716         /* Resource allocation. */
  717         rid = 0;
  718         TAILQ_FOREACH(res, &sc->res, link) {
  719                 switch (res->type) {
  720                     case ACPI_ADR_SPACE_SYSTEM_MEMORY:
  721                         res->type = SYS_RES_MEMORY;
  722                         break;
  723                     case ACPI_ADR_SPACE_SYSTEM_IO:
  724                         res->type = SYS_RES_IOPORT;
  725                         break;
  726                     default:
  727                         goto fail;
  728                 }
  729 
  730                 res->rid = rid++;
  731                 bus_set_resource(dev, res->type, res->rid,
  732                     res->start, res->end - res->start);
  733                 res->res = bus_alloc_resource_any(
  734                     dev, res->type, &res->rid, RF_ACTIVE);
  735                 if (res->res == NULL) {
  736                         bus_delete_resource(dev, res->type, res->rid);
  737                         device_printf(dev, "%s at 0x%llx (%lld byte(s)): "
  738                             "alloc' failed\n",
  739                             res->type == SYS_RES_MEMORY ? "mem" : "io ",
  740                             (unsigned long long )res->start,
  741                             (unsigned long long )(res->end - res->start));
  742                         goto fail;
  743                 }
  744                 WDATWD_VERBOSE_PRINTF(dev, "%s at 0x%llx (%lld byte(s)): "
  745                     "alloc'ed\n",
  746                     res->type == SYS_RES_MEMORY ? "mem" : "io ",
  747                     (unsigned long long )res->start,
  748                     (unsigned long long) (res->end - res->start));
  749         }
  750 
  751         /* Initialize the watchdog hardware. */
  752         if (wdatwd_set_stop(sc) != 0)
  753                 goto fail;
  754         if ((e = wdatwd_clear_status(sc)) && e != EOPNOTSUPP)
  755                 goto fail;
  756         if ((e = wdatwd_set_reboot(sc)) && e != EOPNOTSUPP)
  757                 goto fail;
  758         if ((e = wdatwd_get_countdown(sc, &sc->default_timeout))
  759             && e != EOPNOTSUPP)
  760                 goto fail;
  761         WDATWD_VERBOSE_PRINTF(dev, "initialized.\n");
  762 
  763         /* Some sysctls. Most of them should go to WDATWD_VERBOSE_PRINTF(). */
  764         sctx = device_get_sysctl_ctx(dev);
  765         soid = device_get_sysctl_tree(dev);
  766         SYSCTL_ADD_U64(sctx, SYSCTL_CHILDREN(soid), OID_AUTO,
  767             "timeout_default", CTLFLAG_RD, SYSCTL_NULL_U64_PTR,
  768             sc->default_timeout * sc->period,
  769             "The default watchdog timeout in millisecond.");
  770         SYSCTL_ADD_BOOL(sctx, SYSCTL_CHILDREN(soid), OID_AUTO,
  771             "timeout_configurable", CTLFLAG_RD, SYSCTL_NULL_BOOL_PTR,
  772             STAILQ_EMPTY(&sc->action[ACPI_WDAT_SET_COUNTDOWN]) ? false : true,
  773             "Whether the watchdog timeout is configurable or not.");
  774         SYSCTL_ADD_U64(sctx, SYSCTL_CHILDREN(soid), OID_AUTO,
  775             "timeout", CTLFLAG_RD, &sc->timeout, 0,
  776             "The current watchdog timeout in millisecond. "
  777             "If 0, the default timeout is used.");
  778         SYSCTL_ADD_BOOL(sctx, SYSCTL_CHILDREN(soid), OID_AUTO,
  779             "running", CTLFLAG_RD, &sc->running, 0,
  780             "Whether the watchdog timer is running or not.");
  781 
  782         sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, wdatwd_event, sc,
  783             EVENTHANDLER_PRI_ANY);
  784         WDATWD_VERBOSE_PRINTF(dev, "watchdog registered.\n");
  785 
  786         return (0);
  787 
  788 fail:
  789         wdat_release_resource(dev);
  790 
  791         return (ENXIO);
  792 }
  793 
  794 static int
  795 wdatwd_detach(device_t dev)
  796 {
  797         struct wdatwd_softc     *sc;
  798         int                     e;
  799 
  800         sc = device_get_softc(dev);
  801 
  802         EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag);
  803         e = wdatwd_set_stop(sc);
  804         wdat_release_resource(dev);
  805 
  806         return (e);
  807 }
  808 
  809 static int
  810 wdatwd_suspend(device_t dev)
  811 {
  812         struct wdatwd_softc     *sc;
  813 
  814         sc = device_get_softc(dev);
  815 
  816         if (!sc->stop_in_sleep)
  817                 return (0);
  818 
  819         return wdatwd_set_stop(sc);
  820 }
  821 
  822 static int
  823 wdatwd_resume(device_t dev)
  824 {
  825         struct wdatwd_softc     *sc;
  826 
  827         sc = device_get_softc(dev);
  828 
  829         if (!sc->stop_in_sleep)
  830                 return (0);
  831 
  832         return (wdatwd_reset_countdown(sc) || wdatwd_set_running(sc));
  833 }
  834 
  835 static device_method_t wdatwd_methods[] = {
  836         /* Device interface */
  837         DEVMETHOD(device_probe, wdatwd_probe),
  838         DEVMETHOD(device_attach, wdatwd_attach),
  839         DEVMETHOD(device_detach, wdatwd_detach),
  840         DEVMETHOD(device_shutdown, wdatwd_detach),
  841         DEVMETHOD(device_suspend, wdatwd_suspend),
  842         DEVMETHOD(device_resume, wdatwd_resume),
  843         DEVMETHOD_END
  844 };
  845 
  846 static driver_t wdatwd_driver = {
  847         "wdatwd",
  848         wdatwd_methods,
  849         sizeof(struct wdatwd_softc),
  850 };
  851 
  852 DRIVER_MODULE(wdatwd, acpi, wdatwd_driver, 0, 0);
  853 MODULE_DEPEND(wdatwd, acpi, 1, 1, 1);

Cache object: 4b831bc649ff605461371af72e6b2b8c


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.