linux/drivers/watchdog/sb_wdog.c
<<
>>
Prefs
   1/*
   2 * Watchdog driver for SiByte SB1 SoCs
   3 *
   4 * Copyright (C) 2007 OnStor, Inc. * Andrew Sharp <andy.sharp@lsi.com>
   5 *
   6 * This driver is intended to make the second of two hardware watchdogs
   7 * on the Sibyte 12XX and 11XX SoCs available to the user.  There are two
   8 * such devices available on the SoC, but it seems that there isn't an
   9 * enumeration class for watchdogs in Linux like there is for RTCs.
  10 * The second is used rather than the first because it uses IRQ 1,
  11 * thereby avoiding all that IRQ 0 problematic nonsense.
  12 *
  13 * I have not tried this driver on a 1480 processor; it might work
  14 * just well enough to really screw things up.
  15 *
  16 * It is a simple timer, and there is an interrupt that is raised the
  17 * first time the timer expires.  The second time it expires, the chip
  18 * is reset and there is no way to redirect that NMI.  Which could
  19 * be problematic in some cases where this chip is sitting on the HT
  20 * bus and has just taken responsibility for providing a cache block.
  21 * Since the reset can't be redirected to the external reset pin, it is
  22 * possible that other HT connected processors might hang and not reset.
  23 * For Linux, a soft reset would probably be even worse than a hard reset.
  24 * There you have it.
  25 *
  26 * The timer takes 23 bits of a 64 bit register (?) as a count value,
  27 * and decrements the count every microsecond, for a max value of
  28 * 0x7fffff usec or about 8.3ish seconds.
  29 *
  30 * This watchdog borrows some user semantics from the softdog driver,
  31 * in that if you close the fd, it leaves the watchdog running, unless
  32 * you previously wrote a 'V' to the fd, in which case it disables
  33 * the watchdog when you close the fd like some other drivers.
  34 *
  35 * Based on various other watchdog drivers, which are probably all
  36 * loosely based on something Alan Cox wrote years ago.
  37 *
  38 *      (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
  39 *                                              All Rights Reserved.
  40 *
  41 *      This program is free software; you can redistribute it and/or
  42 *      modify it under the terms of the GNU General Public License
  43 *      version 1 or 2 as published by the Free Software Foundation.
  44 *
  45 */
  46
  47#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  48
  49#include <linux/module.h>
  50#include <linux/io.h>
  51#include <linux/uaccess.h>
  52#include <linux/fs.h>
  53#include <linux/reboot.h>
  54#include <linux/miscdevice.h>
  55#include <linux/watchdog.h>
  56#include <linux/interrupt.h>
  57
  58#include <asm/sibyte/sb1250.h>
  59#include <asm/sibyte/sb1250_regs.h>
  60#include <asm/sibyte/sb1250_int.h>
  61#include <asm/sibyte/sb1250_scd.h>
  62
  63static DEFINE_SPINLOCK(sbwd_lock);
  64
  65/*
  66 * set the initial count value of a timer
  67 *
  68 * wdog is the iomem address of the cfg register
  69 */
  70void sbwdog_set(char __iomem *wdog, unsigned long t)
  71{
  72        spin_lock(&sbwd_lock);
  73        __raw_writeb(0, wdog);
  74        __raw_writeq(t & 0x7fffffUL, wdog - 0x10);
  75        spin_unlock(&sbwd_lock);
  76}
  77
  78/*
  79 * cause the timer to [re]load it's initial count and start counting
  80 * all over again
  81 *
  82 * wdog is the iomem address of the cfg register
  83 */
  84void sbwdog_pet(char __iomem *wdog)
  85{
  86        spin_lock(&sbwd_lock);
  87        __raw_writeb(__raw_readb(wdog) | 1, wdog);
  88        spin_unlock(&sbwd_lock);
  89}
  90
  91static unsigned long sbwdog_gate; /* keeps it to one thread only */
  92static char __iomem *kern_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_0));
  93static char __iomem *user_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_1));
  94static unsigned long timeout = 0x7fffffUL;      /* useconds: 8.3ish secs. */
  95static int expect_close;
  96
  97static const struct watchdog_info ident = {
  98        .options        = WDIOF_CARDRESET | WDIOF_SETTIMEOUT |
  99                                        WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
 100        .identity       = "SiByte Watchdog",
 101};
 102
 103/*
 104 * Allow only a single thread to walk the dog
 105 */
 106static int sbwdog_open(struct inode *inode, struct file *file)
 107{
 108        nonseekable_open(inode, file);
 109        if (test_and_set_bit(0, &sbwdog_gate))
 110                return -EBUSY;
 111        __module_get(THIS_MODULE);
 112
 113        /*
 114         * Activate the timer
 115         */
 116        sbwdog_set(user_dog, timeout);
 117        __raw_writeb(1, user_dog);
 118
 119        return 0;
 120}
 121
 122/*
 123 * Put the dog back in the kennel.
 124 */
 125static int sbwdog_release(struct inode *inode, struct file *file)
 126{
 127        if (expect_close == 42) {
 128                __raw_writeb(0, user_dog);
 129                module_put(THIS_MODULE);
 130        } else {
 131                pr_crit("%s: Unexpected close, not stopping watchdog!\n",
 132                        ident.identity);
 133                sbwdog_pet(user_dog);
 134        }
 135        clear_bit(0, &sbwdog_gate);
 136        expect_close = 0;
 137
 138        return 0;
 139}
 140
 141/*
 142 * 42 - the answer
 143 */
 144static ssize_t sbwdog_write(struct file *file, const char __user *data,
 145                        size_t len, loff_t *ppos)
 146{
 147        int i;
 148
 149        if (len) {
 150                /*
 151                 * restart the timer
 152                 */
 153                expect_close = 0;
 154
 155                for (i = 0; i != len; i++) {
 156                        char c;
 157
 158                        if (get_user(c, data + i))
 159                                return -EFAULT;
 160                        if (c == 'V')
 161                                expect_close = 42;
 162                }
 163                sbwdog_pet(user_dog);
 164        }
 165
 166        return len;
 167}
 168
 169static long sbwdog_ioctl(struct file *file, unsigned int cmd,
 170                                                unsigned long arg)
 171{
 172        int ret = -ENOTTY;
 173        unsigned long time;
 174        void __user *argp = (void __user *)arg;
 175        int __user *p = argp;
 176
 177        switch (cmd) {
 178        case WDIOC_GETSUPPORT:
 179                ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 180                break;
 181
 182        case WDIOC_GETSTATUS:
 183        case WDIOC_GETBOOTSTATUS:
 184                ret = put_user(0, p);
 185                break;
 186
 187        case WDIOC_KEEPALIVE:
 188                sbwdog_pet(user_dog);
 189                ret = 0;
 190                break;
 191
 192        case WDIOC_SETTIMEOUT:
 193                ret = get_user(time, p);
 194                if (ret)
 195                        break;
 196
 197                time *= 1000000;
 198                if (time > 0x7fffffUL) {
 199                        ret = -EINVAL;
 200                        break;
 201                }
 202                timeout = time;
 203                sbwdog_set(user_dog, timeout);
 204                sbwdog_pet(user_dog);
 205
 206        case WDIOC_GETTIMEOUT:
 207                /*
 208                 * get the remaining count from the ... count register
 209                 * which is 1*8 before the config register
 210                 */
 211                ret = put_user((u32)__raw_readq(user_dog - 8) / 1000000, p);
 212                break;
 213        }
 214        return ret;
 215}
 216
 217/*
 218 *      Notifier for system down
 219 */
 220static int sbwdog_notify_sys(struct notifier_block *this, unsigned long code,
 221                                                                void *erf)
 222{
 223        if (code == SYS_DOWN || code == SYS_HALT) {
 224                /*
 225                 * sit and sit
 226                 */
 227                __raw_writeb(0, user_dog);
 228                __raw_writeb(0, kern_dog);
 229        }
 230
 231        return NOTIFY_DONE;
 232}
 233
 234static const struct file_operations sbwdog_fops = {
 235        .owner          = THIS_MODULE,
 236        .llseek         = no_llseek,
 237        .write          = sbwdog_write,
 238        .unlocked_ioctl = sbwdog_ioctl,
 239        .open           = sbwdog_open,
 240        .release        = sbwdog_release,
 241};
 242
 243static struct miscdevice sbwdog_miscdev = {
 244        .minor          = WATCHDOG_MINOR,
 245        .name           = "watchdog",
 246        .fops           = &sbwdog_fops,
 247};
 248
 249static struct notifier_block sbwdog_notifier = {
 250        .notifier_call  = sbwdog_notify_sys,
 251};
 252
 253/*
 254 * interrupt handler
 255 *
 256 * doesn't do a whole lot for user, but oh so cleverly written so kernel
 257 * code can use it to re-up the watchdog, thereby saving the kernel from
 258 * having to create and maintain a timer, just to tickle another timer,
 259 * which is just so wrong.
 260 */
 261irqreturn_t sbwdog_interrupt(int irq, void *addr)
 262{
 263        unsigned long wd_init;
 264        char *wd_cfg_reg = (char *)addr;
 265        u8 cfg;
 266
 267        cfg = __raw_readb(wd_cfg_reg);
 268        wd_init = __raw_readq(wd_cfg_reg - 8) & 0x7fffff;
 269
 270        /*
 271         * if it's the second watchdog timer, it's for those users
 272         */
 273        if (wd_cfg_reg == user_dog)
 274                pr_crit("%s in danger of initiating system reset "
 275                        "in %ld.%01ld seconds\n",
 276                        ident.identity,
 277                        wd_init / 1000000, (wd_init / 100000) % 10);
 278        else
 279                cfg |= 1;
 280
 281        __raw_writeb(cfg, wd_cfg_reg);
 282
 283        return IRQ_HANDLED;
 284}
 285
 286static int __init sbwdog_init(void)
 287{
 288        int ret;
 289
 290        /*
 291         * register a reboot notifier
 292         */
 293        ret = register_reboot_notifier(&sbwdog_notifier);
 294        if (ret) {
 295                pr_err("%s: cannot register reboot notifier (err=%d)\n",
 296                       ident.identity, ret);
 297                return ret;
 298        }
 299
 300        /*
 301         * get the resources
 302         */
 303
 304        ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED,
 305                ident.identity, (void *)user_dog);
 306        if (ret) {
 307                pr_err("%s: failed to request irq 1 - %d\n",
 308                       ident.identity, ret);
 309                goto out;
 310        }
 311
 312        ret = misc_register(&sbwdog_miscdev);
 313        if (ret == 0) {
 314                pr_info("%s: timeout is %ld.%ld secs\n",
 315                        ident.identity,
 316                        timeout / 1000000, (timeout / 100000) % 10);
 317                return 0;
 318        }
 319        free_irq(1, (void *)user_dog);
 320out:
 321        unregister_reboot_notifier(&sbwdog_notifier);
 322
 323        return ret;
 324}
 325
 326static void __exit sbwdog_exit(void)
 327{
 328        misc_deregister(&sbwdog_miscdev);
 329        free_irq(1, (void *)user_dog);
 330        unregister_reboot_notifier(&sbwdog_notifier);
 331}
 332
 333module_init(sbwdog_init);
 334module_exit(sbwdog_exit);
 335
 336MODULE_AUTHOR("Andrew Sharp <andy.sharp@lsi.com>");
 337MODULE_DESCRIPTION("SiByte Watchdog");
 338
 339module_param(timeout, ulong, 0);
 340MODULE_PARM_DESC(timeout,
 341      "Watchdog timeout in microseconds (max/default 8388607 or 8.3ish secs)");
 342
 343MODULE_LICENSE("GPL");
 344MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 345
 346/*
 347 * example code that can be put in a platform code area to utilize the
 348 * first watchdog timer for the kernels own purpose.
 349
 350void platform_wd_setup(void)
 351{
 352        int ret;
 353
 354        ret = request_irq(1, sbwdog_interrupt, IRQF_SHARED,
 355                "Kernel Watchdog", IOADDR(A_SCD_WDOG_CFG_0));
 356        if (ret) {
 357                pr_crit("Watchdog IRQ zero(0) failed to be requested - %d\n", ret);
 358        }
 359}
 360
 361
 362 */
 363