0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
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;
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
0052 #define W_ENABLE 0x00fa
0053 #define W_DISABLE 0x0000
0054
0055
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
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
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
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);