brintos

brintos / linux-shallow public Read only

0
0
Text · 6.1 KiB · 4dd8549 Raw
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