Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /* drivers/char/watchdog/scx200_wdt.c
0003 
0004    National Semiconductor SCx200 Watchdog support
0005 
0006    Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>
0007 
0008    Some code taken from:
0009    National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver
0010    (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>
0011 
0012 
0013    The author(s) of this software shall not be held liable for damages
0014    of any nature resulting due to the use of this software. This
0015    software is provided AS-IS with no warranties. */
0016 
0017 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0018 
0019 #include <linux/module.h>
0020 #include <linux/moduleparam.h>
0021 #include <linux/init.h>
0022 #include <linux/miscdevice.h>
0023 #include <linux/watchdog.h>
0024 #include <linux/notifier.h>
0025 #include <linux/reboot.h>
0026 #include <linux/fs.h>
0027 #include <linux/ioport.h>
0028 #include <linux/scx200.h>
0029 #include <linux/uaccess.h>
0030 #include <linux/io.h>
0031 
0032 #define DEBUG
0033 
0034 MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");
0035 MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");
0036 MODULE_LICENSE("GPL");
0037 
0038 static int margin = 60;     /* in seconds */
0039 module_param(margin, int, 0);
0040 MODULE_PARM_DESC(margin, "Watchdog margin in seconds");
0041 
0042 static bool nowayout = WATCHDOG_NOWAYOUT;
0043 module_param(nowayout, bool, 0);
0044 MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");
0045 
0046 static u16 wdto_restart;
0047 static char expect_close;
0048 static unsigned long open_lock;
0049 static DEFINE_SPINLOCK(scx_lock);
0050 
0051 /* Bits of the WDCNFG register */
0052 #define W_ENABLE 0x00fa     /* Enable watchdog */
0053 #define W_DISABLE 0x0000    /* Disable watchdog */
0054 
0055 /* The scaling factor for the timer, this depends on the value of W_ENABLE */
0056 #define W_SCALE (32768/1024)
0057 
0058 static void scx200_wdt_ping(void)
0059 {
0060     spin_lock(&scx_lock);
0061     outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);
0062     spin_unlock(&scx_lock);
0063 }
0064 
0065 static void scx200_wdt_update_margin(void)
0066 {
0067     pr_info("timer margin %d seconds\n", margin);
0068     wdto_restart = margin * W_SCALE;
0069 }
0070 
0071 static void scx200_wdt_enable(void)
0072 {
0073     pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);
0074 
0075     spin_lock(&scx_lock);
0076     outw(0, scx200_cb_base + SCx200_WDT_WDTO);
0077     outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
0078     outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
0079     spin_unlock(&scx_lock);
0080 
0081     scx200_wdt_ping();
0082 }
0083 
0084 static void scx200_wdt_disable(void)
0085 {
0086     pr_debug("disabling watchdog timer\n");
0087 
0088     spin_lock(&scx_lock);
0089     outw(0, scx200_cb_base + SCx200_WDT_WDTO);
0090     outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);
0091     outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);
0092     spin_unlock(&scx_lock);
0093 }
0094 
0095 static int scx200_wdt_open(struct inode *inode, struct file *file)
0096 {
0097     /* only allow one at a time */
0098     if (test_and_set_bit(0, &open_lock))
0099         return -EBUSY;
0100     scx200_wdt_enable();
0101 
0102     return stream_open(inode, file);
0103 }
0104 
0105 static int scx200_wdt_release(struct inode *inode, struct file *file)
0106 {
0107     if (expect_close != 42)
0108         pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");
0109     else if (!nowayout)
0110         scx200_wdt_disable();
0111     expect_close = 0;
0112     clear_bit(0, &open_lock);
0113 
0114     return 0;
0115 }
0116 
0117 static int scx200_wdt_notify_sys(struct notifier_block *this,
0118                       unsigned long code, void *unused)
0119 {
0120     if (code == SYS_HALT || code == SYS_POWER_OFF)
0121         if (!nowayout)
0122             scx200_wdt_disable();
0123 
0124     return NOTIFY_DONE;
0125 }
0126 
0127 static struct notifier_block scx200_wdt_notifier = {
0128     .notifier_call = scx200_wdt_notify_sys,
0129 };
0130 
0131 static ssize_t scx200_wdt_write(struct file *file, const char __user *data,
0132                      size_t len, loff_t *ppos)
0133 {
0134     /* check for a magic close character */
0135     if (len) {
0136         size_t i;
0137 
0138         scx200_wdt_ping();
0139 
0140         expect_close = 0;
0141         for (i = 0; i < len; ++i) {
0142             char c;
0143             if (get_user(c, data + i))
0144                 return -EFAULT;
0145             if (c == 'V')
0146                 expect_close = 42;
0147         }
0148 
0149         return len;
0150     }
0151 
0152     return 0;
0153 }
0154 
0155 static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,
0156                             unsigned long arg)
0157 {
0158     void __user *argp = (void __user *)arg;
0159     int __user *p = argp;
0160     static const struct watchdog_info ident = {
0161         .identity = "NatSemi SCx200 Watchdog",
0162         .firmware_version = 1,
0163         .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
0164                         WDIOF_MAGICCLOSE,
0165     };
0166     int new_margin;
0167 
0168     switch (cmd) {
0169     case WDIOC_GETSUPPORT:
0170         if (copy_to_user(argp, &ident, sizeof(ident)))
0171             return -EFAULT;
0172         return 0;
0173     case WDIOC_GETSTATUS:
0174     case WDIOC_GETBOOTSTATUS:
0175         if (put_user(0, p))
0176             return -EFAULT;
0177         return 0;
0178     case WDIOC_KEEPALIVE:
0179         scx200_wdt_ping();
0180         return 0;
0181     case WDIOC_SETTIMEOUT:
0182         if (get_user(new_margin, p))
0183             return -EFAULT;
0184         if (new_margin < 1)
0185             return -EINVAL;
0186         margin = new_margin;
0187         scx200_wdt_update_margin();
0188         scx200_wdt_ping();
0189         fallthrough;
0190     case WDIOC_GETTIMEOUT:
0191         if (put_user(margin, p))
0192             return -EFAULT;
0193         return 0;
0194     default:
0195         return -ENOTTY;
0196     }
0197 }
0198 
0199 static const struct file_operations scx200_wdt_fops = {
0200     .owner = THIS_MODULE,
0201     .llseek = no_llseek,
0202     .write = scx200_wdt_write,
0203     .unlocked_ioctl = scx200_wdt_ioctl,
0204     .compat_ioctl   = compat_ptr_ioctl,
0205     .open = scx200_wdt_open,
0206     .release = scx200_wdt_release,
0207 };
0208 
0209 static struct miscdevice scx200_wdt_miscdev = {
0210     .minor = WATCHDOG_MINOR,
0211     .name = "watchdog",
0212     .fops = &scx200_wdt_fops,
0213 };
0214 
0215 static int __init scx200_wdt_init(void)
0216 {
0217     int r;
0218 
0219     pr_debug("NatSemi SCx200 Watchdog Driver\n");
0220 
0221     /* check that we have found the configuration block */
0222     if (!scx200_cb_present())
0223         return -ENODEV;
0224 
0225     if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,
0226                 SCx200_WDT_SIZE,
0227                 "NatSemi SCx200 Watchdog")) {
0228         pr_warn("watchdog I/O region busy\n");
0229         return -EBUSY;
0230     }
0231 
0232     scx200_wdt_update_margin();
0233     scx200_wdt_disable();
0234 
0235     r = register_reboot_notifier(&scx200_wdt_notifier);
0236     if (r) {
0237         pr_err("unable to register reboot notifier\n");
0238         release_region(scx200_cb_base + SCx200_WDT_OFFSET,
0239                 SCx200_WDT_SIZE);
0240         return r;
0241     }
0242 
0243     r = misc_register(&scx200_wdt_miscdev);
0244     if (r) {
0245         unregister_reboot_notifier(&scx200_wdt_notifier);
0246         release_region(scx200_cb_base + SCx200_WDT_OFFSET,
0247                 SCx200_WDT_SIZE);
0248         return r;
0249     }
0250 
0251     return 0;
0252 }
0253 
0254 static void __exit scx200_wdt_cleanup(void)
0255 {
0256     misc_deregister(&scx200_wdt_miscdev);
0257     unregister_reboot_notifier(&scx200_wdt_notifier);
0258     release_region(scx200_cb_base + SCx200_WDT_OFFSET,
0259                SCx200_WDT_SIZE);
0260 }
0261 
0262 module_init(scx200_wdt_init);
0263 module_exit(scx200_wdt_cleanup);