Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  *  Acquire Single Board Computer Watchdog Timer driver
0004  *
0005  *  Based on wdt.c. Original copyright messages:
0006  *
0007  *  (c) Copyright 1996 Alan Cox <alan@lxorguk.ukuu.org.uk>,
0008  *                      All Rights Reserved.
0009  *
0010  *  Neither Alan Cox nor CymruNet Ltd. admit liability nor provide
0011  *  warranty for any of this software. This material is provided
0012  *  "AS-IS" and at no charge.
0013  *
0014  *  (c) Copyright 1995    Alan Cox <alan@lxorguk.ukuu.org.uk>
0015  *
0016  *  14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com>
0017  *      Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT
0018  *      Can't add timeout - driver doesn't allow changing value
0019  */
0020 
0021 /*
0022  *  Theory of Operation:
0023  *      The Watch-Dog Timer is provided to ensure that standalone
0024  *      Systems can always recover from catastrophic conditions that
0025  *      caused the CPU to crash. This condition may have occurred by
0026  *      external EMI or a software bug. When the CPU stops working
0027  *      correctly, hardware on the board will either perform a hardware
0028  *      reset (cold boot) or a non-maskable interrupt (NMI) to bring the
0029  *      system back to a known state.
0030  *
0031  *      The Watch-Dog Timer is controlled by two I/O Ports.
0032  *        443 hex   - Read  - Enable or refresh the Watch-Dog Timer
0033  *        043 hex   - Read  - Disable the Watch-Dog Timer
0034  *
0035  *      To enable the Watch-Dog Timer, a read from I/O port 443h must
0036  *      be performed. This will enable and activate the countdown timer
0037  *      which will eventually time out and either reset the CPU or cause
0038  *      an NMI depending on the setting of a jumper. To ensure that this
0039  *      reset condition does not occur, the Watch-Dog Timer must be
0040  *      periodically refreshed by reading the same I/O port 443h.
0041  *      The Watch-Dog Timer is disabled by reading I/O port 043h.
0042  *
0043  *      The Watch-Dog Timer Time-Out Period is set via jumpers.
0044  *      It can be 1, 2, 10, 20, 110 or 220 seconds.
0045  */
0046 
0047 /*
0048  *  Includes, defines, variables, module parameters, ...
0049  */
0050 
0051 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
0052 
0053 /* Includes */
0054 #include <linux/module.h>       /* For module specific items */
0055 #include <linux/moduleparam.h>      /* For new moduleparam's */
0056 #include <linux/types.h>        /* For standard types (like size_t) */
0057 #include <linux/errno.h>        /* For the -ENODEV/... values */
0058 #include <linux/kernel.h>       /* For printk/panic/... */
0059 #include <linux/miscdevice.h>       /* For struct miscdevice */
0060 #include <linux/watchdog.h>     /* For the watchdog specific items */
0061 #include <linux/fs.h>           /* For file operations */
0062 #include <linux/ioport.h>       /* For io-port access */
0063 #include <linux/platform_device.h>  /* For platform_driver framework */
0064 #include <linux/init.h>         /* For __init/__exit/... */
0065 #include <linux/uaccess.h>      /* For copy_to_user/put_user/... */
0066 #include <linux/io.h>           /* For inb/outb/... */
0067 
0068 /* Module information */
0069 #define DRV_NAME "acquirewdt"
0070 #define WATCHDOG_NAME "Acquire WDT"
0071 /* There is no way to see what the correct time-out period is */
0072 #define WATCHDOG_HEARTBEAT 0
0073 
0074 /* internal variables */
0075 /* the watchdog platform device */
0076 static struct platform_device *acq_platform_device;
0077 static unsigned long acq_is_open;
0078 static char expect_close;
0079 
0080 /* module parameters */
0081 /* You must set this - there is no sane way to probe for this board. */
0082 static int wdt_stop = 0x43;
0083 module_param(wdt_stop, int, 0);
0084 MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)");
0085 
0086 /* You must set this - there is no sane way to probe for this board. */
0087 static int wdt_start = 0x443;
0088 module_param(wdt_start, int, 0);
0089 MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)");
0090 
0091 static bool nowayout = WATCHDOG_NOWAYOUT;
0092 module_param(nowayout, bool, 0);
0093 MODULE_PARM_DESC(nowayout,
0094     "Watchdog cannot be stopped once started (default="
0095     __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
0096 
0097 /*
0098  *  Watchdog Operations
0099  */
0100 
0101 static void acq_keepalive(void)
0102 {
0103     /* Write a watchdog value */
0104     inb_p(wdt_start);
0105 }
0106 
0107 static void acq_stop(void)
0108 {
0109     /* Turn the card off */
0110     inb_p(wdt_stop);
0111 }
0112 
0113 /*
0114  *  /dev/watchdog handling
0115  */
0116 
0117 static ssize_t acq_write(struct file *file, const char __user *buf,
0118                         size_t count, loff_t *ppos)
0119 {
0120     /* See if we got the magic character 'V' and reload the timer */
0121     if (count) {
0122         if (!nowayout) {
0123             size_t i;
0124             /* note: just in case someone wrote the magic character
0125                five months ago... */
0126             expect_close = 0;
0127             /* scan to see whether or not we got the
0128                magic character */
0129             for (i = 0; i != count; i++) {
0130                 char c;
0131                 if (get_user(c, buf + i))
0132                     return -EFAULT;
0133                 if (c == 'V')
0134                     expect_close = 42;
0135             }
0136         }
0137         /* Well, anyhow someone wrote to us, we should
0138                 return that favour */
0139         acq_keepalive();
0140     }
0141     return count;
0142 }
0143 
0144 static long acq_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
0145 {
0146     int options, retval = -EINVAL;
0147     void __user *argp = (void __user *)arg;
0148     int __user *p = argp;
0149     static const struct watchdog_info ident = {
0150         .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
0151         .firmware_version = 1,
0152         .identity = WATCHDOG_NAME,
0153     };
0154 
0155     switch (cmd) {
0156     case WDIOC_GETSUPPORT:
0157         return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
0158 
0159     case WDIOC_GETSTATUS:
0160     case WDIOC_GETBOOTSTATUS:
0161         return put_user(0, p);
0162 
0163     case WDIOC_SETOPTIONS:
0164     {
0165         if (get_user(options, p))
0166             return -EFAULT;
0167         if (options & WDIOS_DISABLECARD) {
0168             acq_stop();
0169             retval = 0;
0170         }
0171         if (options & WDIOS_ENABLECARD) {
0172             acq_keepalive();
0173             retval = 0;
0174         }
0175         return retval;
0176     }
0177     case WDIOC_KEEPALIVE:
0178         acq_keepalive();
0179         return 0;
0180 
0181     case WDIOC_GETTIMEOUT:
0182         return put_user(WATCHDOG_HEARTBEAT, p);
0183 
0184     default:
0185         return -ENOTTY;
0186     }
0187 }
0188 
0189 static int acq_open(struct inode *inode, struct file *file)
0190 {
0191     if (test_and_set_bit(0, &acq_is_open))
0192         return -EBUSY;
0193 
0194     if (nowayout)
0195         __module_get(THIS_MODULE);
0196 
0197     /* Activate */
0198     acq_keepalive();
0199     return stream_open(inode, file);
0200 }
0201 
0202 static int acq_close(struct inode *inode, struct file *file)
0203 {
0204     if (expect_close == 42) {
0205         acq_stop();
0206     } else {
0207         pr_crit("Unexpected close, not stopping watchdog!\n");
0208         acq_keepalive();
0209     }
0210     clear_bit(0, &acq_is_open);
0211     expect_close = 0;
0212     return 0;
0213 }
0214 
0215 /*
0216  *  Kernel Interfaces
0217  */
0218 
0219 static const struct file_operations acq_fops = {
0220     .owner      = THIS_MODULE,
0221     .llseek     = no_llseek,
0222     .write      = acq_write,
0223     .unlocked_ioctl = acq_ioctl,
0224     .compat_ioctl   = compat_ptr_ioctl,
0225     .open       = acq_open,
0226     .release    = acq_close,
0227 };
0228 
0229 static struct miscdevice acq_miscdev = {
0230     .minor  = WATCHDOG_MINOR,
0231     .name   = "watchdog",
0232     .fops   = &acq_fops,
0233 };
0234 
0235 /*
0236  *  Init & exit routines
0237  */
0238 
0239 static int __init acq_probe(struct platform_device *dev)
0240 {
0241     int ret;
0242 
0243     if (wdt_stop != wdt_start) {
0244         if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) {
0245             pr_err("I/O address 0x%04x already in use\n", wdt_stop);
0246             ret = -EIO;
0247             goto out;
0248         }
0249     }
0250 
0251     if (!request_region(wdt_start, 1, WATCHDOG_NAME)) {
0252         pr_err("I/O address 0x%04x already in use\n", wdt_start);
0253         ret = -EIO;
0254         goto unreg_stop;
0255     }
0256     ret = misc_register(&acq_miscdev);
0257     if (ret != 0) {
0258         pr_err("cannot register miscdev on minor=%d (err=%d)\n",
0259                WATCHDOG_MINOR, ret);
0260         goto unreg_regions;
0261     }
0262     pr_info("initialized. (nowayout=%d)\n", nowayout);
0263 
0264     return 0;
0265 unreg_regions:
0266     release_region(wdt_start, 1);
0267 unreg_stop:
0268     if (wdt_stop != wdt_start)
0269         release_region(wdt_stop, 1);
0270 out:
0271     return ret;
0272 }
0273 
0274 static int acq_remove(struct platform_device *dev)
0275 {
0276     misc_deregister(&acq_miscdev);
0277     release_region(wdt_start, 1);
0278     if (wdt_stop != wdt_start)
0279         release_region(wdt_stop, 1);
0280 
0281     return 0;
0282 }
0283 
0284 static void acq_shutdown(struct platform_device *dev)
0285 {
0286     /* Turn the WDT off if we have a soft shutdown */
0287     acq_stop();
0288 }
0289 
0290 static struct platform_driver acquirewdt_driver = {
0291     .remove     = acq_remove,
0292     .shutdown   = acq_shutdown,
0293     .driver     = {
0294         .name   = DRV_NAME,
0295     },
0296 };
0297 
0298 static int __init acq_init(void)
0299 {
0300     int err;
0301 
0302     pr_info("WDT driver for Acquire single board computer initialising\n");
0303 
0304     acq_platform_device = platform_device_register_simple(DRV_NAME,
0305                                 -1, NULL, 0);
0306     if (IS_ERR(acq_platform_device))
0307         return PTR_ERR(acq_platform_device);
0308 
0309     err = platform_driver_probe(&acquirewdt_driver, acq_probe);
0310     if (err)
0311         goto unreg_platform_device;
0312     return 0;
0313 
0314 unreg_platform_device:
0315     platform_device_unregister(acq_platform_device);
0316     return err;
0317 }
0318 
0319 static void __exit acq_exit(void)
0320 {
0321     platform_device_unregister(acq_platform_device);
0322     platform_driver_unregister(&acquirewdt_driver);
0323     pr_info("Watchdog Module Unloaded\n");
0324 }
0325 
0326 module_init(acq_init);
0327 module_exit(acq_exit);
0328 
0329 MODULE_AUTHOR("David Woodhouse");
0330 MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver");
0331 MODULE_LICENSE("GPL");