root/drivers/watchdog/scx200_wdt.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. scx200_wdt_ping
  2. scx200_wdt_update_margin
  3. scx200_wdt_enable
  4. scx200_wdt_disable
  5. scx200_wdt_open
  6. scx200_wdt_release
  7. scx200_wdt_notify_sys
  8. scx200_wdt_write
  9. scx200_wdt_ioctl
  10. scx200_wdt_init
  11. scx200_wdt_cleanup

   1 // SPDX-License-Identifier: GPL-2.0-or-later
   2 /* drivers/char/watchdog/scx200_wdt.c
   3 
   4    National Semiconductor SCx200 Watchdog support
   5 
   6    Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
   7 
   8    Some code taken from:
   9    National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
  10    (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
  11 
  12 
  13    The author(s) of this software shall not be held liable for damages
  14    of any nature resulting due to the use of this software. This
  15    software is provided AS-IS with no warranties. */
  16 
  17 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  18 
  19 #include <linux/module.h>
  20 #include <linux/moduleparam.h>
  21 #include <linux/init.h>
  22 #include <linux/miscdevice.h>
  23 #include <linux/watchdog.h>
  24 #include <linux/notifier.h>
  25 #include <linux/reboot.h>
  26 #include <linux/fs.h>
  27 #include <linux/ioport.h>
  28 #include <linux/scx200.h>
  29 #include <linux/uaccess.h>
  30 #include <linux/io.h>
  31 
  32 #define DEBUG
  33 
  34 MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
  35 MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
  36 MODULE_LICENSE("GPL");
  37 
  38 static int margin = 60;         /* in seconds */
  39 module_param(margin, int, 0);
  40 MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
  41 
  42 static bool nowayout = WATCHDOG_NOWAYOUT;
  43 module_param(nowayout, bool, 0);
  44 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
  45 
  46 static u16 wdto_restart;
  47 static char expect_close;
  48 static unsigned long open_lock;
  49 static DEFINE_SPINLOCK(scx_lock);
  50 
  51 /* Bits of the WDCNFG register */
  52 #define W_ENABLE 0x00fa         /* Enable watchdog */
  53 #define W_DISABLE 0x0000        /* Disable watchdog */
  54 
  55 /* The scaling factor for the timer, this depends on the value of W_ENABLE */
  56 #define W_SCALE (32768/1024)
  57 
  58 static void scx200_wdt_ping(void)
  59 {
  60         spin_lock(&scx_lock);
  61         outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
  62         spin_unlock(&scx_lock);
  63 }
  64 
  65 static void scx200_wdt_update_margin(void)
  66 {
  67         pr_info("timer margin %d seconds\n", margin);
  68         wdto_restart = margin * W_SCALE;
  69 }
  70 
  71 static void scx200_wdt_enable(void)
  72 {
  73         pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);
  74 
  75         spin_lock(&scx_lock);
  76         outw(0, scx200_cb_base + SCx200_WDT_WDTO);
  77         outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
  78         outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
  79         spin_unlock(&scx_lock);
  80 
  81         scx200_wdt_ping();
  82 }
  83 
  84 static void scx200_wdt_disable(void)
  85 {
  86         pr_debug("disabling watchdog timer\n");
  87 
  88         spin_lock(&scx_lock);
  89         outw(0, scx200_cb_base + SCx200_WDT_WDTO);
  90         outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
  91         outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
  92         spin_unlock(&scx_lock);
  93 }
  94 
  95 static int scx200_wdt_open(struct inode *inode, struct file *file)
  96 {
  97         /* only allow one at a time */
  98         if (test_and_set_bit(0, &open_lock))
  99                 return -EBUSY;
 100         scx200_wdt_enable();
 101 
 102         return stream_open(inode, file);
 103 }
 104 
 105 static int scx200_wdt_release(struct inode *inode, struct file *file)
 106 {
 107         if (expect_close != 42)
 108                 pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
 109         else if (!nowayout)
 110                 scx200_wdt_disable();
 111         expect_close = 0;
 112         clear_bit(0, &open_lock);
 113 
 114         return 0;
 115 }
 116 
 117 static int scx200_wdt_notify_sys(struct notifier_block *this,
 118                                       unsigned long code, void *unused)
 119 {
 120         if (code == SYS_HALT || code == SYS_POWER_OFF)
 121                 if (!nowayout)
 122                         scx200_wdt_disable();
 123 
 124         return NOTIFY_DONE;
 125 }
 126 
 127 static struct notifier_block scx200_wdt_notifier = {
 128         .notifier_call = scx200_wdt_notify_sys,
 129 };
 130 
 131 static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
 132                                      size_t len, loff_t *ppos)
 133 {
 134         /* check for a magic close character */
 135         if (len) {
 136                 size_t i;
 137 
 138                 scx200_wdt_ping();
 139 
 140                 expect_close = 0;
 141                 for (i = 0; i < len; ++i) {
 142                         char c;
 143                         if (get_user(c, data + i))
 144                                 return -EFAULT;
 145                         if (c == 'V')
 146                                 expect_close = 42;
 147                 }
 148 
 149                 return len;
 150         }
 151 
 152         return 0;
 153 }
 154 
 155 static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
 156                                                         unsigned long arg)
 157 {
 158         void __user *argp = (void __user *)arg;
 159         int __user *p = argp;
 160         static const struct watchdog_info ident = {
 161                 .identity = "NatSemi SCx200 Watchdog",
 162                 .firmware_version = 1,
 163                 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
 164                                                 WDIOF_MAGICCLOSE,
 165         };
 166         int new_margin;
 167 
 168         switch (cmd) {
 169         case WDIOC_GETSUPPORT:
 170                 if (copy_to_user(argp, &ident, sizeof(ident)))
 171                         return -EFAULT;
 172                 return 0;
 173         case WDIOC_GETSTATUS:
 174         case WDIOC_GETBOOTSTATUS:
 175                 if (put_user(0, p))
 176                         return -EFAULT;
 177                 return 0;
 178         case WDIOC_KEEPALIVE:
 179                 scx200_wdt_ping();
 180                 return 0;
 181         case WDIOC_SETTIMEOUT:
 182                 if (get_user(new_margin, p))
 183                         return -EFAULT;
 184                 if (new_margin < 1)
 185                         return -EINVAL;
 186                 margin = new_margin;
 187                 scx200_wdt_update_margin();
 188                 scx200_wdt_ping();
 189                 /* Fall through */
 190         case WDIOC_GETTIMEOUT:
 191                 if (put_user(margin, p))
 192                         return -EFAULT;
 193                 return 0;
 194         default:
 195                 return -ENOTTY;
 196         }
 197 }
 198 
 199 static const struct file_operations scx200_wdt_fops = {
 200         .owner = THIS_MODULE,
 201         .llseek = no_llseek,
 202         .write = scx200_wdt_write,
 203         .unlocked_ioctl = scx200_wdt_ioctl,
 204         .open = scx200_wdt_open,
 205         .release = scx200_wdt_release,
 206 };
 207 
 208 static struct miscdevice scx200_wdt_miscdev = {
 209         .minor = WATCHDOG_MINOR,
 210         .name = "watchdog",
 211         .fops = &scx200_wdt_fops,
 212 };
 213 
 214 static int __init scx200_wdt_init(void)
 215 {
 216         int r;
 217 
 218         pr_debug("NatSemi SCx200 Watchdog Driver\n");
 219 
 220         /* check that we have found the configuration block */
 221         if (!scx200_cb_present())
 222                 return -ENODEV;
 223 
 224         if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
 225                             SCx200_WDT_SIZE,
 226                             "NatSemi SCx200 Watchdog")) {
 227                 pr_warn("watchdog I/O region busy\n");
 228                 return -EBUSY;
 229         }
 230 
 231         scx200_wdt_update_margin();
 232         scx200_wdt_disable();
 233 
 234         r = register_reboot_notifier(&scx200_wdt_notifier);
 235         if (r) {
 236                 pr_err("unable to register reboot notifier\n");
 237                 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
 238                                 SCx200_WDT_SIZE);
 239                 return r;
 240         }
 241 
 242         r = misc_register(&scx200_wdt_miscdev);
 243         if (r) {
 244                 unregister_reboot_notifier(&scx200_wdt_notifier);
 245                 release_region(scx200_cb_base + SCx200_WDT_OFFSET,
 246                                 SCx200_WDT_SIZE);
 247                 return r;
 248         }
 249 
 250         return 0;
 251 }
 252 
 253 static void __exit scx200_wdt_cleanup(void)
 254 {
 255         misc_deregister(&scx200_wdt_miscdev);
 256         unregister_reboot_notifier(&scx200_wdt_notifier);
 257         release_region(scx200_cb_base + SCx200_WDT_OFFSET,
 258                        SCx200_WDT_SIZE);
 259 }
 260 
 261 module_init(scx200_wdt_init);
 262 module_exit(scx200_wdt_cleanup);

/* [<][>][^][v][top][bottom][index][help] */