linux/drivers/watchdog/alim7101_wdt.c
<<
>>
Prefs
   1/*
   2 *      ALi M7101 PMU Computer Watchdog Timer driver
   3 *
   4 *      Based on w83877f_wdt.c by Scott Jennings <linuxdrivers@oro.net>
   5 *      and the Cobalt kernel WDT timer driver by Tim Hockin
   6 *                                            <thockin@cobaltnet.com>
   7 *
   8 *      (c)2002 Steve Hill <steve@navaho.co.uk>
   9 *
  10 *  This WDT driver is different from most other Linux WDT
  11 *  drivers in that the driver will ping the watchdog by itself,
  12 *  because this particular WDT has a very short timeout (1.6
  13 *  seconds) and it would be insane to count on any userspace
  14 *  daemon always getting scheduled within that time frame.
  15 *
  16 *  Additions:
  17 *   Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs
  18 *                  found on very old cobalt hardware.
  19 *                  -- Mike Waychison <michael.waychison@sun.com>
  20 */
  21
  22#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  23
  24#include <linux/module.h>
  25#include <linux/moduleparam.h>
  26#include <linux/types.h>
  27#include <linux/timer.h>
  28#include <linux/miscdevice.h>
  29#include <linux/watchdog.h>
  30#include <linux/ioport.h>
  31#include <linux/notifier.h>
  32#include <linux/reboot.h>
  33#include <linux/init.h>
  34#include <linux/fs.h>
  35#include <linux/pci.h>
  36#include <linux/io.h>
  37#include <linux/uaccess.h>
  38
  39
  40#define WDT_ENABLE 0x9C
  41#define WDT_DISABLE 0x8C
  42
  43#define ALI_7101_WDT    0x92
  44#define ALI_7101_GPIO   0x7D
  45#define ALI_7101_GPIO_O 0x7E
  46#define ALI_WDT_ARM     0x01
  47
  48/*
  49 * We're going to use a 1 second timeout.
  50 * If we reset the watchdog every ~250ms we should be safe.  */
  51
  52#define WDT_INTERVAL (HZ/4+1)
  53
  54/*
  55 * We must not require too good response from the userspace daemon.
  56 * Here we require the userspace daemon to send us a heartbeat
  57 * char to /dev/watchdog every 30 seconds.
  58 */
  59
  60#define WATCHDOG_TIMEOUT 30            /* 30 sec default timeout */
  61/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
  62static int timeout = WATCHDOG_TIMEOUT;
  63module_param(timeout, int, 0);
  64MODULE_PARM_DESC(timeout,
  65                "Watchdog timeout in seconds. (1<=timeout<=3600, default="
  66                                __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
  67
  68static int use_gpio; /* Use the pic (for a1d revision alim7101) */
  69module_param(use_gpio, int, 0);
  70MODULE_PARM_DESC(use_gpio,
  71                "Use the gpio watchdog (required by old cobalt boards).");
  72
  73static void wdt_timer_ping(unsigned long);
  74static DEFINE_TIMER(timer, wdt_timer_ping, 0, 1);
  75static unsigned long next_heartbeat;
  76static unsigned long wdt_is_open;
  77static char wdt_expect_close;
  78static struct pci_dev *alim7101_pmu;
  79
  80static bool nowayout = WATCHDOG_NOWAYOUT;
  81module_param(nowayout, bool, 0);
  82MODULE_PARM_DESC(nowayout,
  83                "Watchdog cannot be stopped once started (default="
  84                                __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  85
  86/*
  87 *      Whack the dog
  88 */
  89
  90static void wdt_timer_ping(unsigned long data)
  91{
  92        /* If we got a heartbeat pulse within the WDT_US_INTERVAL
  93         * we agree to ping the WDT
  94         */
  95        char tmp;
  96
  97        if (time_before(jiffies, next_heartbeat)) {
  98                /* Ping the WDT (this is actually a disarm/arm sequence) */
  99                pci_read_config_byte(alim7101_pmu, 0x92, &tmp);
 100                pci_write_config_byte(alim7101_pmu,
 101                                        ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
 102                pci_write_config_byte(alim7101_pmu,
 103                                        ALI_7101_WDT, (tmp | ALI_WDT_ARM));
 104                if (use_gpio) {
 105                        pci_read_config_byte(alim7101_pmu,
 106                                        ALI_7101_GPIO_O, &tmp);
 107                        pci_write_config_byte(alim7101_pmu,
 108                                        ALI_7101_GPIO_O, tmp | 0x20);
 109                        pci_write_config_byte(alim7101_pmu,
 110                                        ALI_7101_GPIO_O, tmp & ~0x20);
 111                }
 112        } else {
 113                pr_warn("Heartbeat lost! Will not ping the watchdog\n");
 114        }
 115        /* Re-set the timer interval */
 116        mod_timer(&timer, jiffies + WDT_INTERVAL);
 117}
 118
 119/*
 120 * Utility routines
 121 */
 122
 123static void wdt_change(int writeval)
 124{
 125        char tmp;
 126
 127        pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp);
 128        if (writeval == WDT_ENABLE) {
 129                pci_write_config_byte(alim7101_pmu,
 130                                        ALI_7101_WDT, (tmp | ALI_WDT_ARM));
 131                if (use_gpio) {
 132                        pci_read_config_byte(alim7101_pmu,
 133                                        ALI_7101_GPIO_O, &tmp);
 134                        pci_write_config_byte(alim7101_pmu,
 135                                        ALI_7101_GPIO_O, tmp & ~0x20);
 136                }
 137
 138        } else {
 139                pci_write_config_byte(alim7101_pmu,
 140                                        ALI_7101_WDT, (tmp & ~ALI_WDT_ARM));
 141                if (use_gpio) {
 142                        pci_read_config_byte(alim7101_pmu,
 143                                        ALI_7101_GPIO_O, &tmp);
 144                        pci_write_config_byte(alim7101_pmu,
 145                                        ALI_7101_GPIO_O, tmp | 0x20);
 146                }
 147        }
 148}
 149
 150static void wdt_startup(void)
 151{
 152        next_heartbeat = jiffies + (timeout * HZ);
 153
 154        /* We must enable before we kick off the timer in case the timer
 155           occurs as we ping it */
 156
 157        wdt_change(WDT_ENABLE);
 158
 159        /* Start the timer */
 160        mod_timer(&timer, jiffies + WDT_INTERVAL);
 161
 162        pr_info("Watchdog timer is now enabled\n");
 163}
 164
 165static void wdt_turnoff(void)
 166{
 167        /* Stop the timer */
 168        del_timer_sync(&timer);
 169        wdt_change(WDT_DISABLE);
 170        pr_info("Watchdog timer is now disabled...\n");
 171}
 172
 173static void wdt_keepalive(void)
 174{
 175        /* user land ping */
 176        next_heartbeat = jiffies + (timeout * HZ);
 177}
 178
 179/*
 180 * /dev/watchdog handling
 181 */
 182
 183static ssize_t fop_write(struct file *file, const char __user *buf,
 184                                                size_t count, loff_t *ppos)
 185{
 186        /* See if we got the magic character 'V' and reload the timer */
 187        if (count) {
 188                if (!nowayout) {
 189                        size_t ofs;
 190
 191                        /* note: just in case someone wrote the magic character
 192                         * five months ago... */
 193                        wdt_expect_close = 0;
 194
 195                        /* now scan */
 196                        for (ofs = 0; ofs != count; ofs++) {
 197                                char c;
 198                                if (get_user(c, buf + ofs))
 199                                        return -EFAULT;
 200                                if (c == 'V')
 201                                        wdt_expect_close = 42;
 202                        }
 203                }
 204                /* someone wrote to us, we should restart timer */
 205                wdt_keepalive();
 206        }
 207        return count;
 208}
 209
 210static int fop_open(struct inode *inode, struct file *file)
 211{
 212        /* Just in case we're already talking to someone... */
 213        if (test_and_set_bit(0, &wdt_is_open))
 214                return -EBUSY;
 215        /* Good, fire up the show */
 216        wdt_startup();
 217        return nonseekable_open(inode, file);
 218}
 219
 220static int fop_close(struct inode *inode, struct file *file)
 221{
 222        if (wdt_expect_close == 42)
 223                wdt_turnoff();
 224        else {
 225                /* wim: shouldn't there be a: del_timer(&timer); */
 226                pr_crit("device file closed unexpectedly. Will not stop the WDT!\n");
 227        }
 228        clear_bit(0, &wdt_is_open);
 229        wdt_expect_close = 0;
 230        return 0;
 231}
 232
 233static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 234{
 235        void __user *argp = (void __user *)arg;
 236        int __user *p = argp;
 237        static const struct watchdog_info ident = {
 238                .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
 239                                                        | WDIOF_MAGICCLOSE,
 240                .firmware_version = 1,
 241                .identity = "ALiM7101",
 242        };
 243
 244        switch (cmd) {
 245        case WDIOC_GETSUPPORT:
 246                return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
 247        case WDIOC_GETSTATUS:
 248        case WDIOC_GETBOOTSTATUS:
 249                return put_user(0, p);
 250        case WDIOC_SETOPTIONS:
 251        {
 252                int new_options, retval = -EINVAL;
 253
 254                if (get_user(new_options, p))
 255                        return -EFAULT;
 256                if (new_options & WDIOS_DISABLECARD) {
 257                        wdt_turnoff();
 258                        retval = 0;
 259                }
 260                if (new_options & WDIOS_ENABLECARD) {
 261                        wdt_startup();
 262                        retval = 0;
 263                }
 264                return retval;
 265        }
 266        case WDIOC_KEEPALIVE:
 267                wdt_keepalive();
 268                return 0;
 269        case WDIOC_SETTIMEOUT:
 270        {
 271                int new_timeout;
 272
 273                if (get_user(new_timeout, p))
 274                        return -EFAULT;
 275                /* arbitrary upper limit */
 276                if (new_timeout < 1 || new_timeout > 3600)
 277                        return -EINVAL;
 278                timeout = new_timeout;
 279                wdt_keepalive();
 280                /* Fall through */
 281        }
 282        case WDIOC_GETTIMEOUT:
 283                return put_user(timeout, p);
 284        default:
 285                return -ENOTTY;
 286        }
 287}
 288
 289static const struct file_operations wdt_fops = {
 290        .owner          =       THIS_MODULE,
 291        .llseek         =       no_llseek,
 292        .write          =       fop_write,
 293        .open           =       fop_open,
 294        .release        =       fop_close,
 295        .unlocked_ioctl =       fop_ioctl,
 296};
 297
 298static struct miscdevice wdt_miscdev = {
 299        .minor  =       WATCHDOG_MINOR,
 300        .name   =       "watchdog",
 301        .fops   =       &wdt_fops,
 302};
 303
 304/*
 305 *      Notifier for system down
 306 */
 307
 308static int wdt_notify_sys(struct notifier_block *this,
 309                                        unsigned long code, void *unused)
 310{
 311        if (code == SYS_DOWN || code == SYS_HALT)
 312                wdt_turnoff();
 313
 314        if (code == SYS_RESTART) {
 315                /*
 316                 * Cobalt devices have no way of rebooting themselves other
 317                 * than getting the watchdog to pull reset, so we restart the
 318                 * watchdog on reboot with no heartbeat
 319                 */
 320                wdt_change(WDT_ENABLE);
 321                pr_info("Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second\n");
 322        }
 323        return NOTIFY_DONE;
 324}
 325
 326/*
 327 *      The WDT needs to learn about soft shutdowns in order to
 328 *      turn the timebomb registers off.
 329 */
 330
 331static struct notifier_block wdt_notifier = {
 332        .notifier_call = wdt_notify_sys,
 333};
 334
 335static void __exit alim7101_wdt_unload(void)
 336{
 337        wdt_turnoff();
 338        /* Deregister */
 339        misc_deregister(&wdt_miscdev);
 340        unregister_reboot_notifier(&wdt_notifier);
 341        pci_dev_put(alim7101_pmu);
 342}
 343
 344static int __init alim7101_wdt_init(void)
 345{
 346        int rc = -EBUSY;
 347        struct pci_dev *ali1543_south;
 348        char tmp;
 349
 350        pr_info("Steve Hill <steve@navaho.co.uk>\n");
 351        alim7101_pmu = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,
 352                NULL);
 353        if (!alim7101_pmu) {
 354                pr_info("ALi M7101 PMU not present - WDT not set\n");
 355                return -EBUSY;
 356        }
 357
 358        /* Set the WDT in the PMU to 1 second */
 359        pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02);
 360
 361        ali1543_south = pci_get_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533,
 362                NULL);
 363        if (!ali1543_south) {
 364                pr_info("ALi 1543 South-Bridge not present - WDT not set\n");
 365                goto err_out;
 366        }
 367        pci_read_config_byte(ali1543_south, 0x5e, &tmp);
 368        pci_dev_put(ali1543_south);
 369        if ((tmp & 0x1e) == 0x00) {
 370                if (!use_gpio) {
 371                        pr_info("Detected old alim7101 revision 'a1d'.  If this is a cobalt board, set the 'use_gpio' module parameter.\n");
 372                        goto err_out;
 373                }
 374                nowayout = 1;
 375        } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) {
 376                pr_info("ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n");
 377                goto err_out;
 378        }
 379
 380        if (timeout < 1 || timeout > 3600) {
 381                /* arbitrary upper limit */
 382                timeout = WATCHDOG_TIMEOUT;
 383                pr_info("timeout value must be 1 <= x <= 3600, using %d\n",
 384                        timeout);
 385        }
 386
 387        rc = register_reboot_notifier(&wdt_notifier);
 388        if (rc) {
 389                pr_err("cannot register reboot notifier (err=%d)\n", rc);
 390                goto err_out;
 391        }
 392
 393        rc = misc_register(&wdt_miscdev);
 394        if (rc) {
 395                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 396                       wdt_miscdev.minor, rc);
 397                goto err_out_reboot;
 398        }
 399
 400        if (nowayout)
 401                __module_get(THIS_MODULE);
 402
 403        pr_info("WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n",
 404                timeout, nowayout);
 405        return 0;
 406
 407err_out_reboot:
 408        unregister_reboot_notifier(&wdt_notifier);
 409err_out:
 410        pci_dev_put(alim7101_pmu);
 411        return rc;
 412}
 413
 414module_init(alim7101_wdt_init);
 415module_exit(alim7101_wdt_unload);
 416
 417static DEFINE_PCI_DEVICE_TABLE(alim7101_pci_tbl) __used = {
 418        { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533) },
 419        { PCI_DEVICE(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101) },
 420        { }
 421};
 422
 423MODULE_DEVICE_TABLE(pci, alim7101_pci_tbl);
 424
 425MODULE_AUTHOR("Steve Hill");
 426MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver");
 427MODULE_LICENSE("GPL");
 428MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
 429