Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  *  SBC8360 Watchdog driver
0004  *
0005  *  (c) Copyright 2005 Webcon, Inc.
0006  *
0007  *  Based on ib700wdt.c, which is based on advantechwdt.c which is based
0008  *  on acquirewdt.c which is based on wdt.c.
0009  *
0010  *  (c) Copyright 2001 Charles Howes <chowes@vsol.net>
0011  *
0012  *  Based on advantechwdt.c which is based on acquirewdt.c which
0013  *  is based on wdt.c.
0014  *
0015  *  (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl>
0016  *
0017  *  Based on acquirewdt.c which is based on wdt.c.
0018  *  Original copyright messages:
0019  *
0020  *  (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
0021  *                      All Rights Reserved.
0022  *
0023  *  Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
0024  *  warranty for any of this software. This material is provided
0025  *  "AS-IS" and at no charge.
0026  *
0027  *  (c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
0028  *
0029  *  14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
0030  *       Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
0031  *       Added timeout module option to override default
0032  *
0033  */
0034 
0035 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0036 
0037 #include <linux/module.h>
0038 #include <linux/types.h>
0039 #include <linux/miscdevice.h>
0040 #include <linux/watchdog.h>
0041 #include <linux/ioport.h>
0042 #include <linux/delay.h>
0043 #include <linux/notifier.h>
0044 #include <linux/fs.h>
0045 #include <linux/reboot.h>
0046 #include <linux/init.h>
0047 #include <linux/spinlock.h>
0048 #include <linux/moduleparam.h>
0049 #include <linux/io.h>
0050 #include <linux/uaccess.h>
0051 
0052 
0053 static unsigned long sbc8360_is_open;
0054 static char expect_close;
0055 
0056 /*
0057  *
0058  * Watchdog Timer Configuration
0059  *
0060  * The function of the watchdog timer is to reset the system automatically
0061  * and is defined at I/O port 0120H and 0121H.  To enable the watchdog timer
0062  * and allow the system to reset, write appropriate values from the table
0063  * below to I/O port 0120H and 0121H.  To disable the timer, write a zero
0064  * value to I/O port 0121H for the system to stop the watchdog function.
0065  *
0066  * The following describes how the timer should be programmed (according to
0067  * the vendor documentation)
0068  *
0069  * Enabling Watchdog:
0070  * MOV AX,000AH (enable, phase I)
0071  * MOV DX,0120H
0072  * OUT DX,AX
0073  * MOV AX,000BH (enable, phase II)
0074  * MOV DX,0120H
0075  * OUT DX,AX
0076  * MOV AX,000nH (set multiplier n, from 1-4)
0077  * MOV DX,0120H
0078  * OUT DX,AX
0079  * MOV AX,000mH (set base timer m, from 0-F)
0080  * MOV DX,0121H
0081  * OUT DX,AX
0082  *
0083  * Reset timer:
0084  * MOV AX,000mH (same as set base timer, above)
0085  * MOV DX,0121H
0086  * OUT DX,AX
0087  *
0088  * Disabling Watchdog:
0089  * MOV AX,0000H (a zero value)
0090  * MOV DX,0120H
0091  * OUT DX,AX
0092  *
0093  * Watchdog timeout configuration values:
0094  *      N
0095  *  M | 1   2   3   4
0096  *  --|----------------------------------
0097  *  0 | 0.5s    5s  50s 100s
0098  *  1 | 1s  10s 100s    200s
0099  *  2 | 1.5s    15s 150s    300s
0100  *  3 | 2s  20s 200s    400s
0101  *  4 | 2.5s    25s 250s    500s
0102  *  5 | 3s  30s 300s    600s
0103  *  6 | 3.5s    35s 350s    700s
0104  *  7 | 4s  40s 400s    800s
0105  *  8 | 4.5s    45s 450s    900s
0106  *  9 | 5s  50s 500s    1000s
0107  *  A | 5.5s    55s 550s    1100s
0108  *  B | 6s  60s 600s    1200s
0109  *  C | 6.5s    65s 650s    1300s
0110  *  D | 7s  70s 700s    1400s
0111  *  E | 7.5s    75s 750s    1500s
0112  *  F | 8s  80s 800s    1600s
0113  *
0114  * Another way to say the same things is:
0115  *  For N=1, Timeout = (M+1) * 0.5s
0116  *  For N=2, Timeout = (M+1) * 5s
0117  *  For N=3, Timeout = (M+1) * 50s
0118  *  For N=4, Timeout = (M+1) * 100s
0119  *
0120  */
0121 
0122 static int wd_times[64][2] = {
0123     {0, 1},         /* 0  = 0.5s */
0124     {1, 1},         /* 1  = 1s   */
0125     {2, 1},         /* 2  = 1.5s */
0126     {3, 1},         /* 3  = 2s   */
0127     {4, 1},         /* 4  = 2.5s */
0128     {5, 1},         /* 5  = 3s   */
0129     {6, 1},         /* 6  = 3.5s */
0130     {7, 1},         /* 7  = 4s   */
0131     {8, 1},         /* 8  = 4.5s */
0132     {9, 1},         /* 9  = 5s   */
0133     {0xA, 1},       /* 10 = 5.5s */
0134     {0xB, 1},       /* 11 = 6s   */
0135     {0xC, 1},       /* 12 = 6.5s */
0136     {0xD, 1},       /* 13 = 7s   */
0137     {0xE, 1},       /* 14 = 7.5s */
0138     {0xF, 1},       /* 15 = 8s   */
0139     {0, 2},         /* 16 = 5s  */
0140     {1, 2},         /* 17 = 10s */
0141     {2, 2},         /* 18 = 15s */
0142     {3, 2},         /* 19 = 20s */
0143     {4, 2},         /* 20 = 25s */
0144     {5, 2},         /* 21 = 30s */
0145     {6, 2},         /* 22 = 35s */
0146     {7, 2},         /* 23 = 40s */
0147     {8, 2},         /* 24 = 45s */
0148     {9, 2},         /* 25 = 50s */
0149     {0xA, 2},       /* 26 = 55s */
0150     {0xB, 2},       /* 27 = 60s */
0151     {0xC, 2},       /* 28 = 65s */
0152     {0xD, 2},       /* 29 = 70s */
0153     {0xE, 2},       /* 30 = 75s */
0154     {0xF, 2},       /* 31 = 80s */
0155     {0, 3},         /* 32 = 50s  */
0156     {1, 3},         /* 33 = 100s */
0157     {2, 3},         /* 34 = 150s */
0158     {3, 3},         /* 35 = 200s */
0159     {4, 3},         /* 36 = 250s */
0160     {5, 3},         /* 37 = 300s */
0161     {6, 3},         /* 38 = 350s */
0162     {7, 3},         /* 39 = 400s */
0163     {8, 3},         /* 40 = 450s */
0164     {9, 3},         /* 41 = 500s */
0165     {0xA, 3},       /* 42 = 550s */
0166     {0xB, 3},       /* 43 = 600s */
0167     {0xC, 3},       /* 44 = 650s */
0168     {0xD, 3},       /* 45 = 700s */
0169     {0xE, 3},       /* 46 = 750s */
0170     {0xF, 3},       /* 47 = 800s */
0171     {0, 4},         /* 48 = 100s */
0172     {1, 4},         /* 49 = 200s */
0173     {2, 4},         /* 50 = 300s */
0174     {3, 4},         /* 51 = 400s */
0175     {4, 4},         /* 52 = 500s */
0176     {5, 4},         /* 53 = 600s */
0177     {6, 4},         /* 54 = 700s */
0178     {7, 4},         /* 55 = 800s */
0179     {8, 4},         /* 56 = 900s */
0180     {9, 4},         /* 57 = 1000s */
0181     {0xA, 4},       /* 58 = 1100s */
0182     {0xB, 4},       /* 59 = 1200s */
0183     {0xC, 4},       /* 60 = 1300s */
0184     {0xD, 4},       /* 61 = 1400s */
0185     {0xE, 4},       /* 62 = 1500s */
0186     {0xF, 4}        /* 63 = 1600s */
0187 };
0188 
0189 #define SBC8360_ENABLE 0x120
0190 #define SBC8360_BASETIME 0x121
0191 
0192 static int timeout = 27;
0193 static int wd_margin = 0xB;
0194 static int wd_multiplier = 2;
0195 static bool nowayout = WATCHDOG_NOWAYOUT;
0196 
0197 module_param(timeout, int, 0);
0198 MODULE_PARM_DESC(timeout, "Index into timeout table (0-63) (default=27 (60s))");
0199 module_param(nowayout, bool, 0);
0200 MODULE_PARM_DESC(nowayout,
0201          "Watchdog cannot be stopped once started (default="
0202                 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0203 
0204 /*
0205  *  Kernel methods.
0206  */
0207 
0208 /* Activate and pre-configure watchdog */
0209 static void sbc8360_activate(void)
0210 {
0211     /* Enable the watchdog */
0212     outb(0x0A, SBC8360_ENABLE);
0213     msleep_interruptible(100);
0214     outb(0x0B, SBC8360_ENABLE);
0215     msleep_interruptible(100);
0216     /* Set timeout multiplier */
0217     outb(wd_multiplier, SBC8360_ENABLE);
0218     msleep_interruptible(100);
0219     /* Nothing happens until first sbc8360_ping() */
0220 }
0221 
0222 /* Kernel pings watchdog */
0223 static void sbc8360_ping(void)
0224 {
0225     /* Write the base timer register */
0226     outb(wd_margin, SBC8360_BASETIME);
0227 }
0228 
0229 /* stop watchdog */
0230 static void sbc8360_stop(void)
0231 {
0232     /* De-activate the watchdog */
0233     outb(0, SBC8360_ENABLE);
0234 }
0235 
0236 /* Userspace pings kernel driver, or requests clean close */
0237 static ssize_t sbc8360_write(struct file *file, const char __user *buf,
0238                  size_t count, loff_t *ppos)
0239 {
0240     if (count) {
0241         if (!nowayout) {
0242             size_t i;
0243 
0244             /* In case it was set long ago */
0245             expect_close = 0;
0246 
0247             for (i = 0; i != count; i++) {
0248                 char c;
0249                 if (get_user(c, buf + i))
0250                     return -EFAULT;
0251                 if (c == 'V')
0252                     expect_close = 42;
0253             }
0254         }
0255         sbc8360_ping();
0256     }
0257     return count;
0258 }
0259 
0260 static int sbc8360_open(struct inode *inode, struct file *file)
0261 {
0262     if (test_and_set_bit(0, &sbc8360_is_open))
0263         return -EBUSY;
0264     if (nowayout)
0265         __module_get(THIS_MODULE);
0266 
0267     /* Activate and ping once to start the countdown */
0268     sbc8360_activate();
0269     sbc8360_ping();
0270     return stream_open(inode, file);
0271 }
0272 
0273 static int sbc8360_close(struct inode *inode, struct file *file)
0274 {
0275     if (expect_close == 42)
0276         sbc8360_stop();
0277     else
0278         pr_crit("SBC8360 device closed unexpectedly.  SBC8360 will not stop!\n");
0279 
0280     clear_bit(0, &sbc8360_is_open);
0281     expect_close = 0;
0282     return 0;
0283 }
0284 
0285 /*
0286  *  Notifier for system down
0287  */
0288 
0289 static int sbc8360_notify_sys(struct notifier_block *this, unsigned long code,
0290                   void *unused)
0291 {
0292     if (code == SYS_DOWN || code == SYS_HALT)
0293         sbc8360_stop(); /* Disable the SBC8360 Watchdog */
0294 
0295     return NOTIFY_DONE;
0296 }
0297 
0298 /*
0299  *  Kernel Interfaces
0300  */
0301 
0302 static const struct file_operations sbc8360_fops = {
0303     .owner = THIS_MODULE,
0304     .llseek = no_llseek,
0305     .write = sbc8360_write,
0306     .open = sbc8360_open,
0307     .release = sbc8360_close,
0308 };
0309 
0310 static struct miscdevice sbc8360_miscdev = {
0311     .minor = WATCHDOG_MINOR,
0312     .name = "watchdog",
0313     .fops = &sbc8360_fops,
0314 };
0315 
0316 /*
0317  *  The SBC8360 needs to learn about soft shutdowns in order to
0318  *  turn the timebomb registers off.
0319  */
0320 
0321 static struct notifier_block sbc8360_notifier = {
0322     .notifier_call = sbc8360_notify_sys,
0323 };
0324 
0325 static int __init sbc8360_init(void)
0326 {
0327     int res;
0328     unsigned long int mseconds = 60000;
0329 
0330     if (timeout < 0 || timeout > 63) {
0331         pr_err("Invalid timeout index (must be 0-63)\n");
0332         res = -EINVAL;
0333         goto out;
0334     }
0335 
0336     if (!request_region(SBC8360_ENABLE, 1, "SBC8360")) {
0337         pr_err("ENABLE method I/O %X is not available\n",
0338                SBC8360_ENABLE);
0339         res = -EIO;
0340         goto out;
0341     }
0342     if (!request_region(SBC8360_BASETIME, 1, "SBC8360")) {
0343         pr_err("BASETIME method I/O %X is not available\n",
0344                SBC8360_BASETIME);
0345         res = -EIO;
0346         goto out_nobasetimereg;
0347     }
0348 
0349     res = register_reboot_notifier(&sbc8360_notifier);
0350     if (res) {
0351         pr_err("Failed to register reboot notifier\n");
0352         goto out_noreboot;
0353     }
0354 
0355     res = misc_register(&sbc8360_miscdev);
0356     if (res) {
0357         pr_err("failed to register misc device\n");
0358         goto out_nomisc;
0359     }
0360 
0361     wd_margin = wd_times[timeout][0];
0362     wd_multiplier = wd_times[timeout][1];
0363 
0364     if (wd_multiplier == 1)
0365         mseconds = (wd_margin + 1) * 500;
0366     else if (wd_multiplier == 2)
0367         mseconds = (wd_margin + 1) * 5000;
0368     else if (wd_multiplier == 3)
0369         mseconds = (wd_margin + 1) * 50000;
0370     else if (wd_multiplier == 4)
0371         mseconds = (wd_margin + 1) * 100000;
0372 
0373     /* My kingdom for the ability to print "0.5 seconds" in the kernel! */
0374     pr_info("Timeout set at %ld ms\n", mseconds);
0375 
0376     return 0;
0377 
0378 out_nomisc:
0379     unregister_reboot_notifier(&sbc8360_notifier);
0380 out_noreboot:
0381     release_region(SBC8360_BASETIME, 1);
0382 out_nobasetimereg:
0383     release_region(SBC8360_ENABLE, 1);
0384 out:
0385     return res;
0386 }
0387 
0388 static void __exit sbc8360_exit(void)
0389 {
0390     misc_deregister(&sbc8360_miscdev);
0391     unregister_reboot_notifier(&sbc8360_notifier);
0392     release_region(SBC8360_ENABLE, 1);
0393     release_region(SBC8360_BASETIME, 1);
0394 }
0395 
0396 module_init(sbc8360_init);
0397 module_exit(sbc8360_exit);
0398 
0399 MODULE_AUTHOR("Ian E. Morgan <imorgan@webcon.ca>");
0400 MODULE_DESCRIPTION("SBC8360 watchdog driver");
0401 MODULE_LICENSE("GPL");
0402 MODULE_VERSION("1.01");
0403 
0404 /* end of sbc8360.c */