linux/drivers/watchdog/sbc7240_wdt.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 *      NANO7240 SBC Watchdog device driver
   4 *
   5 *      Based on w83877f.c by Scott Jennings,
   6 *
   7 *      (c) Copyright 2007  Gilles GIGAN <gilles.gigan@jcu.edu.au>
   8 */
   9
  10#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  11
  12#include <linux/fs.h>
  13#include <linux/init.h>
  14#include <linux/ioport.h>
  15#include <linux/jiffies.h>
  16#include <linux/module.h>
  17#include <linux/moduleparam.h>
  18#include <linux/miscdevice.h>
  19#include <linux/notifier.h>
  20#include <linux/reboot.h>
  21#include <linux/types.h>
  22#include <linux/watchdog.h>
  23#include <linux/io.h>
  24#include <linux/uaccess.h>
  25#include <linux/atomic.h>
  26
  27#define SBC7240_ENABLE_PORT             0x443
  28#define SBC7240_DISABLE_PORT            0x043
  29#define SBC7240_SET_TIMEOUT_PORT        SBC7240_ENABLE_PORT
  30#define SBC7240_MAGIC_CHAR              'V'
  31
  32#define SBC7240_TIMEOUT         30
  33#define SBC7240_MAX_TIMEOUT             255
  34static int timeout = SBC7240_TIMEOUT;   /* in seconds */
  35module_param(timeout, int, 0);
  36MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<="
  37                 __MODULE_STRING(SBC7240_MAX_TIMEOUT) ", default="
  38                 __MODULE_STRING(SBC7240_TIMEOUT) ")");
  39
  40static bool nowayout = WATCHDOG_NOWAYOUT;
  41module_param(nowayout, bool, 0);
  42MODULE_PARM_DESC(nowayout, "Disable watchdog when closing device file");
  43
  44#define SBC7240_OPEN_STATUS_BIT         0
  45#define SBC7240_ENABLED_STATUS_BIT      1
  46#define SBC7240_EXPECT_CLOSE_STATUS_BIT 2
  47static unsigned long wdt_status;
  48
  49/*
  50 * Utility routines
  51 */
  52
  53static void wdt_disable(void)
  54{
  55        /* disable the watchdog */
  56        if (test_and_clear_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
  57                inb_p(SBC7240_DISABLE_PORT);
  58                pr_info("Watchdog timer is now disabled\n");
  59        }
  60}
  61
  62static void wdt_enable(void)
  63{
  64        /* enable the watchdog */
  65        if (!test_and_set_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status)) {
  66                inb_p(SBC7240_ENABLE_PORT);
  67                pr_info("Watchdog timer is now enabled\n");
  68        }
  69}
  70
  71static int wdt_set_timeout(int t)
  72{
  73        if (t < 1 || t > SBC7240_MAX_TIMEOUT) {
  74                pr_err("timeout value must be 1<=x<=%d\n", SBC7240_MAX_TIMEOUT);
  75                return -1;
  76        }
  77        /* set the timeout */
  78        outb_p((unsigned)t, SBC7240_SET_TIMEOUT_PORT);
  79        timeout = t;
  80        pr_info("timeout set to %d seconds\n", t);
  81        return 0;
  82}
  83
  84/* Whack the dog */
  85static inline void wdt_keepalive(void)
  86{
  87        if (test_bit(SBC7240_ENABLED_STATUS_BIT, &wdt_status))
  88                inb_p(SBC7240_ENABLE_PORT);
  89}
  90
  91/*
  92 * /dev/watchdog handling
  93 */
  94static ssize_t fop_write(struct file *file, const char __user *buf,
  95                         size_t count, loff_t *ppos)
  96{
  97        size_t i;
  98        char c;
  99
 100        if (count) {
 101                if (!nowayout) {
 102                        clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
 103                                &wdt_status);
 104
 105                        /* is there a magic char ? */
 106                        for (i = 0; i != count; i++) {
 107                                if (get_user(c, buf + i))
 108                                        return -EFAULT;
 109                                if (c == SBC7240_MAGIC_CHAR) {
 110                                        set_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT,
 111                                                &wdt_status);
 112                                        break;
 113                                }
 114                        }
 115                }
 116
 117                wdt_keepalive();
 118        }
 119
 120        return count;
 121}
 122
 123static int fop_open(struct inode *inode, struct file *file)
 124{
 125        if (test_and_set_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status))
 126                return -EBUSY;
 127
 128        wdt_enable();
 129
 130        return stream_open(inode, file);
 131}
 132
 133static int fop_close(struct inode *inode, struct file *file)
 134{
 135        if (test_and_clear_bit(SBC7240_EXPECT_CLOSE_STATUS_BIT, &wdt_status)
 136            || !nowayout) {
 137                wdt_disable();
 138        } else {
 139                pr_crit("Unexpected close, not stopping watchdog!\n");
 140                wdt_keepalive();
 141        }
 142
 143        clear_bit(SBC7240_OPEN_STATUS_BIT, &wdt_status);
 144        return 0;
 145}
 146
 147static const struct watchdog_info ident = {
 148        .options = WDIOF_KEEPALIVEPING|
 149                   WDIOF_SETTIMEOUT|
 150                   WDIOF_MAGICCLOSE,
 151        .firmware_version = 1,
 152        .identity = "SBC7240",
 153};
 154
 155
 156static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
 157{
 158        switch (cmd) {
 159        case WDIOC_GETSUPPORT:
 160                return copy_to_user((void __user *)arg, &ident, sizeof(ident))
 161                                                 ? -EFAULT : 0;
 162        case WDIOC_GETSTATUS:
 163        case WDIOC_GETBOOTSTATUS:
 164                return put_user(0, (int __user *)arg);
 165        case WDIOC_SETOPTIONS:
 166        {
 167                int options;
 168                int retval = -EINVAL;
 169
 170                if (get_user(options, (int __user *)arg))
 171                        return -EFAULT;
 172
 173                if (options & WDIOS_DISABLECARD) {
 174                        wdt_disable();
 175                        retval = 0;
 176                }
 177
 178                if (options & WDIOS_ENABLECARD) {
 179                        wdt_enable();
 180                        retval = 0;
 181                }
 182
 183                return retval;
 184        }
 185        case WDIOC_KEEPALIVE:
 186                wdt_keepalive();
 187                return 0;
 188        case WDIOC_SETTIMEOUT:
 189        {
 190                int new_timeout;
 191
 192                if (get_user(new_timeout, (int __user *)arg))
 193                        return -EFAULT;
 194
 195                if (wdt_set_timeout(new_timeout))
 196                        return -EINVAL;
 197        }
 198                fallthrough;
 199        case WDIOC_GETTIMEOUT:
 200                return put_user(timeout, (int __user *)arg);
 201        default:
 202                return -ENOTTY;
 203        }
 204}
 205
 206static const struct file_operations wdt_fops = {
 207        .owner = THIS_MODULE,
 208        .llseek = no_llseek,
 209        .write = fop_write,
 210        .open = fop_open,
 211        .release = fop_close,
 212        .unlocked_ioctl = fop_ioctl,
 213        .compat_ioctl = compat_ptr_ioctl,
 214};
 215
 216static struct miscdevice wdt_miscdev = {
 217        .minor = WATCHDOG_MINOR,
 218        .name = "watchdog",
 219        .fops = &wdt_fops,
 220};
 221
 222/*
 223 *      Notifier for system down
 224 */
 225
 226static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
 227                          void *unused)
 228{
 229        if (code == SYS_DOWN || code == SYS_HALT)
 230                wdt_disable();
 231        return NOTIFY_DONE;
 232}
 233
 234static struct notifier_block wdt_notifier = {
 235        .notifier_call = wdt_notify_sys,
 236};
 237
 238static void __exit sbc7240_wdt_unload(void)
 239{
 240        pr_info("Removing watchdog\n");
 241        misc_deregister(&wdt_miscdev);
 242
 243        unregister_reboot_notifier(&wdt_notifier);
 244        release_region(SBC7240_ENABLE_PORT, 1);
 245}
 246
 247static int __init sbc7240_wdt_init(void)
 248{
 249        int rc = -EBUSY;
 250
 251        if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) {
 252                pr_err("I/O address 0x%04x already in use\n",
 253                       SBC7240_ENABLE_PORT);
 254                rc = -EIO;
 255                goto err_out;
 256        }
 257
 258        /* The IO port 0x043 used to disable the watchdog
 259         * is already claimed by the system timer, so we
 260         * can't request_region() it ...*/
 261
 262        if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) {
 263                timeout = SBC7240_TIMEOUT;
 264                pr_info("timeout value must be 1<=x<=%d, using %d\n",
 265                        SBC7240_MAX_TIMEOUT, timeout);
 266        }
 267        wdt_set_timeout(timeout);
 268        wdt_disable();
 269
 270        rc = register_reboot_notifier(&wdt_notifier);
 271        if (rc) {
 272                pr_err("cannot register reboot notifier (err=%d)\n", rc);
 273                goto err_out_region;
 274        }
 275
 276        rc = misc_register(&wdt_miscdev);
 277        if (rc) {
 278                pr_err("cannot register miscdev on minor=%d (err=%d)\n",
 279                       wdt_miscdev.minor, rc);
 280                goto err_out_reboot_notifier;
 281        }
 282
 283        pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n",
 284                nowayout);
 285
 286        return 0;
 287
 288err_out_reboot_notifier:
 289        unregister_reboot_notifier(&wdt_notifier);
 290err_out_region:
 291        release_region(SBC7240_ENABLE_PORT, 1);
 292err_out:
 293        return rc;
 294}
 295
 296module_init(sbc7240_wdt_init);
 297module_exit(sbc7240_wdt_unload);
 298
 299MODULE_AUTHOR("Gilles Gigan");
 300MODULE_DESCRIPTION("Watchdog device driver for single board"
 301                   " computers EPIC Nano 7240 from iEi");
 302MODULE_LICENSE("GPL");
 303