akaros/kern/drivers/dev/watchdog.c
<<
>>
Prefs
   1/* Copyright (c) 2020 Google Inc
   2 * Barret Rhoden <brho@cs.berkeley.edu>
   3 * See LICENSE for details.
   4 *
   5 * #watchdog
   6 */
   7
   8#include <ns.h>
   9#include <kmalloc.h>
  10#include <string.h>
  11#include <assert.h>
  12#include <error.h>
  13
  14#include <stdio.h>
  15#include <arch/console.h>
  16
  17/* The usage of the HPET is so hokey that I don't want it in a header in
  18 * include/ */
  19#include "../timers/hpet.h"
  20
  21/* Primitive ktask control.  Probably want better general support for this
  22 * stuff, maybe including rendez or something to kick us out of a sleep.
  23 * kthread_usleep() has a built-in rendez already, so it's almost there. */
  24struct wd_ctl {
  25        bool should_exit;
  26};
  27
  28/* lock-protected invariants
  29 * ----------
  30 * creation and manipulation of the hpet_timer ht
  31 *
  32 * if enabled is set:
  33 *      - cur_wd is set (the ktask responsible for updating the hpet)
  34 *      - timeout is set once and unchanged
  35 *      - there may be an old ktask with their own ctl, but it is set to
  36 *      should_exit.
  37 *      - ht was already created and initialized
  38 * if disabled:
  39 *      - global cur_wd is NULL
  40 *      - timeout is zero
  41 *      - any previously running ktask's should_exit is true
  42 *
  43 * on the edges:
  44 * ----------
  45 * disabled->enabled: ktask is kicked, it'll turn on the timer
  46 * enabled->disabled: ktask is told to die, we turn off the timer
  47 */
  48static spinlock_t lock = SPINLOCK_INITIALIZER;
  49static bool enabled;
  50static struct wd_ctl *cur_wd;
  51static uint64_t timeout;
  52static struct hpet_timer *ht;
  53
  54struct dev watchdog_devtab;
  55
  56static char *devname(void)
  57{
  58        return watchdog_devtab.name;
  59}
  60
  61enum {
  62        Qdir,
  63        Qctl,
  64};
  65
  66static struct dirtab wd_dir[] = {
  67        {".", {Qdir, 0, QTDIR}, 0, DMDIR | 0555},
  68        {"ctl", {Qctl, 0, QTFILE}, 0, 0666},
  69};
  70
  71static struct chan *wd_attach(char *spec)
  72{
  73        return devattach(devname(), spec);
  74}
  75
  76static struct walkqid *wd_walk(struct chan *c, struct chan *nc, char **name,
  77                               unsigned int nname)
  78{
  79        return devwalk(c, nc, name, nname, wd_dir, ARRAY_SIZE(wd_dir),
  80                       devgen);
  81}
  82
  83static size_t wd_stat(struct chan *c, uint8_t *db, size_t n)
  84{
  85        return devstat(c, db, n, wd_dir, ARRAY_SIZE(wd_dir), devgen);
  86}
  87
  88static struct chan *wd_open(struct chan *c, int omode)
  89{
  90        return devopen(c, omode, wd_dir, ARRAY_SIZE(wd_dir), devgen);
  91}
  92
  93static void wd_close(struct chan *c)
  94{
  95        if (!(c->flag & COPEN))
  96                return;
  97}
  98
  99static size_t wd_read(struct chan *c, void *ubuf, size_t n, off64_t offset)
 100{
 101        switch (c->qid.path) {
 102        case Qdir:
 103                return devdirread(c, ubuf, n, wd_dir, ARRAY_SIZE(wd_dir),
 104                                  devgen);
 105        case Qctl:
 106                if (READ_ONCE(enabled))
 107                        return readstr(offset, ubuf, n, "on");
 108                else
 109                        return readstr(offset, ubuf, n, "off");
 110        default:
 111                panic("Bad Qid %p!", c->qid.path);
 112        }
 113        return -1;
 114}
 115
 116/* do_nmi_work() call this directly.  We don't have IRQ handlers for NMIs, and
 117 * this will get called on *every* NMI, since we're basically muxing in SW. */
 118void __watchdog_nmi_handler(struct hw_trapframe *hw_tf)
 119{
 120        /* It's not enough to check 'enabled', since we get the spurious IRQ at
 121         * some point after we call hpet_timer_enable().  We could attempt to
 122         * deal with this by enabling the timer, waiting a bit in case the IRQ
 123         * fires (which it might not, so we don't know how long to wait), and
 124         * *then* setting enabled.  With barriers.  Fun. */
 125        if (!READ_ONCE(enabled))
 126                return;
 127        if (hpet_check_spurious_64(ht))
 128                return;
 129
 130        /* This is real hokey, and could easily trigger another deadlock. */
 131        panic_skip_console_lock = true;
 132        panic_skip_print_lock = true;
 133        print_trapframe(hw_tf);
 134        backtrace_hwtf(hw_tf);
 135
 136        printk("Watchdog forcing a reboot in 10 sec!\n");
 137        udelay(10000000);
 138
 139        reboot();
 140}
 141
 142/* Attempts to set up a timer.  Returns 0 on failure.  Returns the actual
 143 * timeout to use.  i.e. if we're limited by the timer's reach. */
 144static uint64_t __init_timer_once(uint64_t sec_timeout)
 145{
 146        uint64_t max;
 147
 148        if (!ht) {
 149                ht = hpet_get_magic_timer();
 150                if (!ht)
 151                        return 0;
 152                /* NMI mode.  Vector is ignored, but passing 2 for clarity.  If
 153                 * you try a regular vector/IRQ, you'll need to hook up an
 154                 * irq_handler.  (EOIs, handlers, etc). */
 155                hpet_magic_timer_setup(ht, 2, 0x4);
 156        }
 157        /* We use a 64 bit counter, so the reach32 is a little excessive.
 158         * However, we need some limit to avoid wraparound.  Might as well use
 159         * the 32 bit one, in case we ever sort out the HPET spurious crap. */
 160        max = ht->hpb->reach32 / 2;
 161        if (max < sec_timeout) {
 162                trace_printk("Watchdog request for %d, throttled to %d\n",
 163                             sec_timeout, max);
 164                return max;
 165        }
 166        return sec_timeout;
 167}
 168
 169static void __shutoff_timer(void)
 170{
 171        hpet_timer_disable(ht);
 172}
 173
 174static void __increment_timer(uint64_t two_x_timeout)
 175{
 176        hpet_timer_increment_comparator(ht, two_x_timeout * 1000000000);
 177        hpet_timer_enable(ht);
 178}
 179
 180/* Our job is to kick the watchdog by periodically adjusting the interrupt
 181 * deadline in the timer into the future.  When we execute, we set it for
 182 * 2 * timeout more time, based on whatever it is at - not based on our runtime.
 183 * We'll sleep for timeout.  If we get delayed by another timeout and fail to
 184 * reset it, the IRQ will fire and we'll reboot.  Technically we could be held
 185 * up for 2 * timeout before kicking, but we were held up for at least one
 186 * timeout.
 187 *
 188 * It's mostly OK to have multiple of these ktasks running - that can happen if
 189 * you do multiple off-ons quickly.  (i.e. start a new one before the old one
 190 * had a chance to shut down).  Each thread has its own control structure, so
 191 * that's fine.  They will stop (if instructed) before doing anything.  These
 192 * threads will sit around though, until their timeout.  We don't have any easy
 193 * support for kicking a ktask to make it wake up faster. */
 194static void wd_ktask(void *arg)
 195{
 196        struct wd_ctl *ctl = arg;
 197        uint64_t sleep_usec;
 198
 199        while (1) {
 200                spin_lock(&lock);
 201                if (ctl->should_exit) {
 202                        spin_unlock(&lock);
 203                        break;
 204                }
 205                if (!timeout) {
 206                        /* We should have been told to exit already. */
 207                        warn("WD saw timeout == 0!");
 208                        spin_unlock(&lock);
 209                        break;
 210                }
 211                __increment_timer(timeout * 2);
 212                sleep_usec = timeout * 1000000;
 213                spin_unlock(&lock);
 214                kthread_usleep(sleep_usec);
 215        }
 216        kfree(ctl);
 217}
 218
 219#define WD_CTL_USAGE "on SEC_TIMEOUT | off"
 220
 221static void wd_ctl_cmd(struct chan *c, struct cmdbuf *cb)
 222{
 223        struct wd_ctl *ctl;
 224        unsigned long sec_timeout;
 225
 226        if (cb->nf < 1)
 227                error(EFAIL, WD_CTL_USAGE);
 228
 229        if (!strcmp(cb->f[0], "on")) {
 230                if (cb->nf < 2)
 231                        error(EFAIL, WD_CTL_USAGE);
 232                sec_timeout = strtoul(cb->f[1], 0, 0);
 233                if (!sec_timeout)
 234                        error(EFAIL, "need a non-zero timeout");
 235                ctl = kzmalloc(sizeof(struct wd_ctl), MEM_WAIT);
 236                spin_lock(&lock);
 237                if (enabled) {
 238                        spin_unlock(&lock);
 239                        kfree(ctl);
 240                        error(EFAIL, "watchdog already running; stop it first");
 241                }
 242                sec_timeout = __init_timer_once(sec_timeout);
 243                if (!sec_timeout) {
 244                        spin_unlock(&lock);
 245                        kfree(ctl);
 246                        error(EFAIL, "unable to get an appropriate timer");
 247                }
 248                timeout = sec_timeout;
 249                WRITE_ONCE(enabled, true);
 250                cur_wd = ctl;
 251                ktask("watchdog", wd_ktask, cur_wd);
 252                spin_unlock(&lock);
 253        } else if (!strcmp(cb->f[0], "off")) {
 254                spin_lock(&lock);
 255                if (!enabled) {
 256                        spin_unlock(&lock);
 257                        error(EFAIL, "watchdog was not on");
 258                }
 259                WRITE_ONCE(enabled, false);
 260                timeout = 0;
 261                cur_wd->should_exit = true;
 262                cur_wd = NULL;
 263                __shutoff_timer();
 264                spin_unlock(&lock);
 265        } else {
 266                error(EFAIL, WD_CTL_USAGE);
 267        }
 268}
 269
 270static size_t wd_write(struct chan *c, void *ubuf, size_t n, off64_t unused)
 271{
 272        ERRSTACK(1);
 273        struct cmdbuf *cb = parsecmd(ubuf, n);
 274
 275        if (waserror()) {
 276                kfree(cb);
 277                nexterror();
 278        }
 279        switch (c->qid.path) {
 280        case Qctl:
 281                wd_ctl_cmd(c, cb);
 282                break;
 283        default:
 284                error(EFAIL, "Unable to write to %s", devname());
 285        }
 286        kfree(cb);
 287        poperror();
 288        return n;
 289}
 290
 291struct dev watchdog_devtab __devtab = {
 292        .name = "watchdog",
 293        .reset = devreset,
 294        .init = devinit,
 295        .shutdown = devshutdown,
 296        .attach = wd_attach,
 297        .walk = wd_walk,
 298        .stat = wd_stat,
 299        .open = wd_open,
 300        .create = devcreate,
 301        .close = wd_close,
 302        .read = wd_read,
 303        .bread = devbread,
 304        .write = wd_write,
 305        .bwrite = devbwrite,
 306        .remove = devremove,
 307        .wstat = devwstat,
 308        .power = devpower,
 309        .chaninfo = devchaninfo,
 310};
 311