brintos

brintos / linux-shallow public Read only

0
0
Text · 6.5 KiB · 21a1f0b Raw
302 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 *	NANO7240 SBC Watchdog device driver4 *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 ": " fmt11 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		0x44328#define SBC7240_DISABLE_PORT		0x04329#define SBC7240_SET_TIMEOUT_PORT	SBC7240_ENABLE_PORT30#define SBC7240_MAGIC_CHAR		'V'31 32#define SBC7240_TIMEOUT		3033#define SBC7240_MAX_TIMEOUT		25534static 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		045#define SBC7240_ENABLED_STATUS_BIT	146#define SBC7240_EXPECT_CLOSE_STATUS_BIT	247static unsigned long wdt_status;48 49/*50 * Utility routines51 */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 handling93 */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	.write = fop_write,209	.open = fop_open,210	.release = fop_close,211	.unlocked_ioctl = fop_ioctl,212	.compat_ioctl = compat_ptr_ioctl,213};214 215static struct miscdevice wdt_miscdev = {216	.minor = WATCHDOG_MINOR,217	.name = "watchdog",218	.fops = &wdt_fops,219};220 221/*222 *	Notifier for system down223 */224 225static int wdt_notify_sys(struct notifier_block *this, unsigned long code,226			  void *unused)227{228	if (code == SYS_DOWN || code == SYS_HALT)229		wdt_disable();230	return NOTIFY_DONE;231}232 233static struct notifier_block wdt_notifier = {234	.notifier_call = wdt_notify_sys,235};236 237static void __exit sbc7240_wdt_unload(void)238{239	pr_info("Removing watchdog\n");240	misc_deregister(&wdt_miscdev);241 242	unregister_reboot_notifier(&wdt_notifier);243	release_region(SBC7240_ENABLE_PORT, 1);244}245 246static int __init sbc7240_wdt_init(void)247{248	int rc = -EBUSY;249 250	if (!request_region(SBC7240_ENABLE_PORT, 1, "SBC7240 WDT")) {251		pr_err("I/O address 0x%04x already in use\n",252		       SBC7240_ENABLE_PORT);253		rc = -EIO;254		goto err_out;255	}256 257	/* The IO port 0x043 used to disable the watchdog258	 * is already claimed by the system timer, so we259	 * can't request_region() it ...*/260 261	if (timeout < 1 || timeout > SBC7240_MAX_TIMEOUT) {262		timeout = SBC7240_TIMEOUT;263		pr_info("timeout value must be 1<=x<=%d, using %d\n",264			SBC7240_MAX_TIMEOUT, timeout);265	}266	wdt_set_timeout(timeout);267	wdt_disable();268 269	rc = register_reboot_notifier(&wdt_notifier);270	if (rc) {271		pr_err("cannot register reboot notifier (err=%d)\n", rc);272		goto err_out_region;273	}274 275	rc = misc_register(&wdt_miscdev);276	if (rc) {277		pr_err("cannot register miscdev on minor=%d (err=%d)\n",278		       wdt_miscdev.minor, rc);279		goto err_out_reboot_notifier;280	}281 282	pr_info("Watchdog driver for SBC7240 initialised (nowayout=%d)\n",283		nowayout);284 285	return 0;286 287err_out_reboot_notifier:288	unregister_reboot_notifier(&wdt_notifier);289err_out_region:290	release_region(SBC7240_ENABLE_PORT, 1);291err_out:292	return rc;293}294 295module_init(sbc7240_wdt_init);296module_exit(sbc7240_wdt_unload);297 298MODULE_AUTHOR("Gilles Gigan");299MODULE_DESCRIPTION("Watchdog device driver for single board"300		   " computers EPIC Nano 7240 from iEi");301MODULE_LICENSE("GPL");302