263 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/* drivers/char/watchdog/scx200_wdt.c3 4 National Semiconductor SCx200 Watchdog support5 6 Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com>7 8 Some code taken from:9 National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver10 (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>11 12 13 The author(s) of this software shall not be held liable for damages14 of any nature resulting due to the use of this software. This15 software is provided AS-IS with no warranties. */16 17#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt18 19#include <linux/module.h>20#include <linux/moduleparam.h>21#include <linux/init.h>22#include <linux/miscdevice.h>23#include <linux/watchdog.h>24#include <linux/notifier.h>25#include <linux/reboot.h>26#include <linux/fs.h>27#include <linux/ioport.h>28#include <linux/scx200.h>29#include <linux/uaccess.h>30#include <linux/io.h>31 32#define DEBUG33 34MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>");35MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver");36MODULE_LICENSE("GPL");37 38static int margin = 60; /* in seconds */39module_param(margin, int, 0);40MODULE_PARM_DESC(margin, "Watchdog margin in seconds");41 42static bool nowayout = WATCHDOG_NOWAYOUT;43module_param(nowayout, bool, 0);44MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");45 46static u16 wdto_restart;47static char expect_close;48static unsigned long open_lock;49static DEFINE_SPINLOCK(scx_lock);50 51/* Bits of the WDCNFG register */52#define W_ENABLE 0x00fa /* Enable watchdog */53#define W_DISABLE 0x0000 /* Disable watchdog */54 55/* The scaling factor for the timer, this depends on the value of W_ENABLE */56#define W_SCALE (32768/1024)57 58static void scx200_wdt_ping(void)59{60 spin_lock(&scx_lock);61 outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO);62 spin_unlock(&scx_lock);63}64 65static void scx200_wdt_update_margin(void)66{67 pr_info("timer margin %d seconds\n", margin);68 wdto_restart = margin * W_SCALE;69}70 71static void scx200_wdt_enable(void)72{73 pr_debug("enabling watchdog timer, wdto_restart = %d\n", wdto_restart);74 75 spin_lock(&scx_lock);76 outw(0, scx200_cb_base + SCx200_WDT_WDTO);77 outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);78 outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG);79 spin_unlock(&scx_lock);80 81 scx200_wdt_ping();82}83 84static void scx200_wdt_disable(void)85{86 pr_debug("disabling watchdog timer\n");87 88 spin_lock(&scx_lock);89 outw(0, scx200_cb_base + SCx200_WDT_WDTO);90 outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS);91 outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG);92 spin_unlock(&scx_lock);93}94 95static int scx200_wdt_open(struct inode *inode, struct file *file)96{97 /* only allow one at a time */98 if (test_and_set_bit(0, &open_lock))99 return -EBUSY;100 scx200_wdt_enable();101 102 return stream_open(inode, file);103}104 105static int scx200_wdt_release(struct inode *inode, struct file *file)106{107 if (expect_close != 42)108 pr_warn("watchdog device closed unexpectedly, will not disable the watchdog timer\n");109 else if (!nowayout)110 scx200_wdt_disable();111 expect_close = 0;112 clear_bit(0, &open_lock);113 114 return 0;115}116 117static int scx200_wdt_notify_sys(struct notifier_block *this,118 unsigned long code, void *unused)119{120 if (code == SYS_HALT || code == SYS_POWER_OFF)121 if (!nowayout)122 scx200_wdt_disable();123 124 return NOTIFY_DONE;125}126 127static struct notifier_block scx200_wdt_notifier = {128 .notifier_call = scx200_wdt_notify_sys,129};130 131static ssize_t scx200_wdt_write(struct file *file, const char __user *data,132 size_t len, loff_t *ppos)133{134 /* check for a magic close character */135 if (len) {136 size_t i;137 138 scx200_wdt_ping();139 140 expect_close = 0;141 for (i = 0; i < len; ++i) {142 char c;143 if (get_user(c, data + i))144 return -EFAULT;145 if (c == 'V')146 expect_close = 42;147 }148 149 return len;150 }151 152 return 0;153}154 155static long scx200_wdt_ioctl(struct file *file, unsigned int cmd,156 unsigned long arg)157{158 void __user *argp = (void __user *)arg;159 int __user *p = argp;160 static const struct watchdog_info ident = {161 .identity = "NatSemi SCx200 Watchdog",162 .firmware_version = 1,163 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |164 WDIOF_MAGICCLOSE,165 };166 int new_margin;167 168 switch (cmd) {169 case WDIOC_GETSUPPORT:170 if (copy_to_user(argp, &ident, sizeof(ident)))171 return -EFAULT;172 return 0;173 case WDIOC_GETSTATUS:174 case WDIOC_GETBOOTSTATUS:175 if (put_user(0, p))176 return -EFAULT;177 return 0;178 case WDIOC_KEEPALIVE:179 scx200_wdt_ping();180 return 0;181 case WDIOC_SETTIMEOUT:182 if (get_user(new_margin, p))183 return -EFAULT;184 if (new_margin < 1)185 return -EINVAL;186 margin = new_margin;187 scx200_wdt_update_margin();188 scx200_wdt_ping();189 fallthrough;190 case WDIOC_GETTIMEOUT:191 if (put_user(margin, p))192 return -EFAULT;193 return 0;194 default:195 return -ENOTTY;196 }197}198 199static const struct file_operations scx200_wdt_fops = {200 .owner = THIS_MODULE,201 .write = scx200_wdt_write,202 .unlocked_ioctl = scx200_wdt_ioctl,203 .compat_ioctl = compat_ptr_ioctl,204 .open = scx200_wdt_open,205 .release = scx200_wdt_release,206};207 208static struct miscdevice scx200_wdt_miscdev = {209 .minor = WATCHDOG_MINOR,210 .name = "watchdog",211 .fops = &scx200_wdt_fops,212};213 214static int __init scx200_wdt_init(void)215{216 int r;217 218 pr_debug("NatSemi SCx200 Watchdog Driver\n");219 220 /* check that we have found the configuration block */221 if (!scx200_cb_present())222 return -ENODEV;223 224 if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET,225 SCx200_WDT_SIZE,226 "NatSemi SCx200 Watchdog")) {227 pr_warn("watchdog I/O region busy\n");228 return -EBUSY;229 }230 231 scx200_wdt_update_margin();232 scx200_wdt_disable();233 234 r = register_reboot_notifier(&scx200_wdt_notifier);235 if (r) {236 pr_err("unable to register reboot notifier\n");237 release_region(scx200_cb_base + SCx200_WDT_OFFSET,238 SCx200_WDT_SIZE);239 return r;240 }241 242 r = misc_register(&scx200_wdt_miscdev);243 if (r) {244 unregister_reboot_notifier(&scx200_wdt_notifier);245 release_region(scx200_cb_base + SCx200_WDT_OFFSET,246 SCx200_WDT_SIZE);247 return r;248 }249 250 return 0;251}252 253static void __exit scx200_wdt_cleanup(void)254{255 misc_deregister(&scx200_wdt_miscdev);256 unregister_reboot_notifier(&scx200_wdt_notifier);257 release_region(scx200_cb_base + SCx200_WDT_OFFSET,258 SCx200_WDT_SIZE);259}260 261module_init(scx200_wdt_init);262module_exit(scx200_wdt_cleanup);263