brintos

brintos / linux-shallow public Read only

0
0
Text · 14.9 KiB · 97ca500 Raw
615 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 *	SMsC 37B787 Watchdog Timer driver for Linux 2.6.x.x4 *5 *	Based on acquirewdt.c by Alan Cox <alan@lxorguk.ukuu.org.uk>6 *	and some other existing drivers7 *8 *	The authors do NOT admit liability nor provide warranty for9 *	any of this software. This material is provided "AS-IS" in10 *	the hope that it may be useful for others.11 *12 *	(C) Copyright 2003-2006  Sven Anders <anders@anduras.de>13 *14 *  History:15 *	2003 - Created version 1.0 for Linux 2.4.x.16 *	2006 - Ported to Linux 2.6, added nowayout and MAGICCLOSE17 *	       features. Released version 1.118 *19 *  Theory of operation:20 *21 *	A Watchdog Timer (WDT) is a hardware circuit that can22 *	reset the computer system in case of a software fault.23 *	You probably knew that already.24 *25 *	Usually a userspace daemon will notify the kernel WDT driver26 *	via the /dev/watchdog special device file that userspace is27 *	still alive, at regular intervals.  When such a notification28 *	occurs, the driver will usually tell the hardware watchdog29 *	that everything is in order, and that the watchdog should wait30 *	for yet another little while to reset the system.31 *	If userspace fails (RAM error, kernel bug, whatever), the32 *	notifications cease to occur, and the hardware watchdog will33 *	reset the system (causing a reboot) after the timeout occurs.34 *35 * Create device with:36 *  mknod /dev/watchdog c 10 13037 *38 * For an example userspace keep-alive daemon, see:39 *   Documentation/watchdog/wdt.rst40 */41 42#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt43 44#include <linux/module.h>45#include <linux/moduleparam.h>46#include <linux/types.h>47#include <linux/miscdevice.h>48#include <linux/watchdog.h>49#include <linux/delay.h>50#include <linux/fs.h>51#include <linux/ioport.h>52#include <linux/notifier.h>53#include <linux/reboot.h>54#include <linux/init.h>55#include <linux/spinlock.h>56#include <linux/io.h>57#include <linux/uaccess.h>58 59 60/* enable support for minutes as units? */61/* (does not always work correctly, so disabled by default!) */62#define SMSC_SUPPORT_MINUTES63#undef SMSC_SUPPORT_MINUTES64 65#define MAX_TIMEOUT     25566 67#define UNIT_SECOND     068#define UNIT_MINUTE     169 70#define VERSION		"1.1"71 72#define IOPORT		0x3F073#define IOPORT_SIZE     274#define IODEV_NO	875 76static int unit = UNIT_SECOND;	/* timer's unit */77static int timeout = 60;	/* timeout value: default is 60 "units" */78static unsigned long timer_enabled;   /* is the timer enabled? */79 80static char expect_close;       /* is the close expected? */81 82static DEFINE_SPINLOCK(io_lock);/* to guard the watchdog from io races */83 84static bool nowayout = WATCHDOG_NOWAYOUT;85 86/* -- Low level function ----------------------------------------*/87 88/* unlock the IO chip */89 90static inline void open_io_config(void)91{92	outb(0x55, IOPORT);93	mdelay(1);94	outb(0x55, IOPORT);95}96 97/* lock the IO chip */98static inline void close_io_config(void)99{100	outb(0xAA, IOPORT);101}102 103/* select the IO device */104static inline void select_io_device(unsigned char devno)105{106	outb(0x07, IOPORT);107	outb(devno, IOPORT+1);108}109 110/* write to the control register */111static inline void write_io_cr(unsigned char reg, unsigned char data)112{113	outb(reg, IOPORT);114	outb(data, IOPORT+1);115}116 117/* read from the control register */118static inline char read_io_cr(unsigned char reg)119{120	outb(reg, IOPORT);121	return inb(IOPORT+1);122}123 124/* -- Medium level functions ------------------------------------*/125 126static inline void gpio_bit12(unsigned char reg)127{128	/* -- General Purpose I/O Bit 1.2 --129	 * Bit 0,   In/Out: 0 = Output, 1 = Input130	 * Bit 1,   Polarity: 0 = No Invert, 1 = Invert131	 * Bit 2,   Group Enable Intr.: 0 = Disable, 1 = Enable132	 * Bit 3/4, Function select: 00 = GPI/O, 01 = WDT, 10 = P17,133	 *                           11 = Either Edge Triggered Intr. 2134	 * Bit 5/6  (Reserved)135	 * Bit 7,   Output Type: 0 = Push Pull Bit, 1 = Open Drain136	 */137	write_io_cr(0xE2, reg);138}139 140static inline void gpio_bit13(unsigned char reg)141{142	/* -- General Purpose I/O Bit 1.3 --143	 * Bit 0,  In/Out: 0 = Output, 1 = Input144	 * Bit 1,  Polarity: 0 = No Invert, 1 = Invert145	 * Bit 2,  Group Enable Intr.: 0 = Disable, 1 = Enable146	 * Bit 3,  Function select: 0 = GPI/O, 1 = LED147	 * Bit 4-6 (Reserved)148	 * Bit 7,  Output Type: 0 = Push Pull Bit, 1 = Open Drain149	 */150	write_io_cr(0xE3, reg);151}152 153static inline void wdt_timer_units(unsigned char new_units)154{155	/* -- Watchdog timer units --156	 * Bit 0-6 (Reserved)157	 * Bit 7,  WDT Time-out Value Units Select158	 *         (0 = Minutes, 1 = Seconds)159	 */160	write_io_cr(0xF1, new_units);161}162 163static inline void wdt_timeout_value(unsigned char new_timeout)164{165	/* -- Watchdog Timer Time-out Value --166	 * Bit 0-7 Binary coded units (0=Disabled, 1..255)167	 */168	write_io_cr(0xF2, new_timeout);169}170 171static inline void wdt_timer_conf(unsigned char conf)172{173	/* -- Watchdog timer configuration --174	 * Bit 0   Joystick enable: 0* = No Reset, 1 = Reset WDT upon175	 *							Gameport I/O176	 * Bit 1   Keyboard enable: 0* = No Reset, 1 = Reset WDT upon KBD Intr.177	 * Bit 2   Mouse enable: 0* = No Reset, 1 = Reset WDT upon Mouse Intr178	 * Bit 3   Reset the timer179	 *         (Wrong in SMsC documentation? Given as: PowerLED Timout180	 *							Enabled)181	 * Bit 4-7 WDT Interrupt Mapping: (0000* = Disabled,182	 *            0001=IRQ1, 0010=(Invalid), 0011=IRQ3 to 1111=IRQ15)183	 */184	write_io_cr(0xF3, conf);185}186 187static inline void wdt_timer_ctrl(unsigned char reg)188{189	/* -- Watchdog timer control --190	 * Bit 0   Status Bit: 0 = Timer counting, 1 = Timeout occurred191	 * Bit 1   Power LED Toggle: 0 = Disable Toggle, 1 = Toggle at 1 Hz192	 * Bit 2   Force Timeout: 1 = Forces WD timeout event (self-cleaning)193	 * Bit 3   P20 Force Timeout enabled:194	 *          0 = P20 activity does not generate the WD timeout event195	 *          1 = P20 Allows rising edge of P20, from the keyboard196	 *              controller, to force the WD timeout event.197	 * Bit 4   (Reserved)198	 * -- Soft power management --199	 * Bit 5   Stop Counter: 1 = Stop software power down counter200	 *            set via register 0xB8, (self-cleaning)201	 *            (Upon read: 0 = Counter running, 1 = Counter stopped)202	 * Bit 6   Restart Counter: 1 = Restart software power down counter203	 *            set via register 0xB8, (self-cleaning)204	 * Bit 7   SPOFF: 1 = Force software power down (self-cleaning)205	 */206	write_io_cr(0xF4, reg);207}208 209/* -- Higher level functions ------------------------------------*/210 211/* initialize watchdog */212 213static void wb_smsc_wdt_initialize(void)214{215	unsigned char old;216 217	spin_lock(&io_lock);218	open_io_config();219	select_io_device(IODEV_NO);220 221	/* enable the watchdog */222	gpio_bit13(0x08);  /* Select pin 80 = LED not GPIO */223	gpio_bit12(0x0A);  /* Set pin 79 = WDT not224			      GPIO/Output/Polarity=Invert */225	/* disable the timeout */226	wdt_timeout_value(0);227 228	/* reset control register */229	wdt_timer_ctrl(0x00);230 231	/* reset configuration register */232	wdt_timer_conf(0x00);233 234	/* read old (timer units) register */235	old = read_io_cr(0xF1) & 0x7F;236	if (unit == UNIT_SECOND)237		old |= 0x80;	/* set to seconds */238 239	/* set the watchdog timer units */240	wdt_timer_units(old);241 242	close_io_config();243	spin_unlock(&io_lock);244}245 246/* shutdown the watchdog */247 248static void wb_smsc_wdt_shutdown(void)249{250	spin_lock(&io_lock);251	open_io_config();252	select_io_device(IODEV_NO);253 254	/* disable the watchdog */255	gpio_bit13(0x09);256	gpio_bit12(0x09);257 258	/* reset watchdog config register */259	wdt_timer_conf(0x00);260 261	/* reset watchdog control register */262	wdt_timer_ctrl(0x00);263 264	/* disable timeout */265	wdt_timeout_value(0x00);266 267	close_io_config();268	spin_unlock(&io_lock);269}270 271/* set timeout => enable watchdog */272 273static void wb_smsc_wdt_set_timeout(unsigned char new_timeout)274{275	spin_lock(&io_lock);276	open_io_config();277	select_io_device(IODEV_NO);278 279	/* set Power LED to blink, if we enable the timeout */280	wdt_timer_ctrl((new_timeout == 0) ? 0x00 : 0x02);281 282	/* set timeout value */283	wdt_timeout_value(new_timeout);284 285	close_io_config();286	spin_unlock(&io_lock);287}288 289/* get timeout */290 291static unsigned char wb_smsc_wdt_get_timeout(void)292{293	unsigned char set_timeout;294 295	spin_lock(&io_lock);296	open_io_config();297	select_io_device(IODEV_NO);298	set_timeout = read_io_cr(0xF2);299	close_io_config();300	spin_unlock(&io_lock);301 302	return set_timeout;303}304 305/* disable watchdog */306 307static void wb_smsc_wdt_disable(void)308{309	/* set the timeout to 0 to disable the watchdog */310	wb_smsc_wdt_set_timeout(0);311}312 313/* enable watchdog by setting the current timeout */314 315static void wb_smsc_wdt_enable(void)316{317	/* set the current timeout... */318	wb_smsc_wdt_set_timeout(timeout);319}320 321/* reset the timer */322 323static void wb_smsc_wdt_reset_timer(void)324{325	spin_lock(&io_lock);326	open_io_config();327	select_io_device(IODEV_NO);328 329	/* reset the timer */330	wdt_timeout_value(timeout);331	wdt_timer_conf(0x08);332 333	close_io_config();334	spin_unlock(&io_lock);335}336 337/* return, if the watchdog is enabled (timeout is set...) */338 339static int wb_smsc_wdt_status(void)340{341	return (wb_smsc_wdt_get_timeout() == 0) ? 0 : WDIOF_KEEPALIVEPING;342}343 344 345/* -- File operations -------------------------------------------*/346 347/* open => enable watchdog and set initial timeout */348 349static int wb_smsc_wdt_open(struct inode *inode, struct file *file)350{351	/* /dev/watchdog can only be opened once */352 353	if (test_and_set_bit(0, &timer_enabled))354		return -EBUSY;355 356	if (nowayout)357		__module_get(THIS_MODULE);358 359	/* Reload and activate timer */360	wb_smsc_wdt_enable();361 362	pr_info("Watchdog enabled. Timeout set to %d %s\n",363		timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)");364 365	return stream_open(inode, file);366}367 368/* close => shut off the timer */369 370static int wb_smsc_wdt_release(struct inode *inode, struct file *file)371{372	/* Shut off the timer. */373 374	if (expect_close == 42) {375		wb_smsc_wdt_disable();376		pr_info("Watchdog disabled, sleeping again...\n");377	} else {378		pr_crit("Unexpected close, not stopping watchdog!\n");379		wb_smsc_wdt_reset_timer();380	}381 382	clear_bit(0, &timer_enabled);383	expect_close = 0;384	return 0;385}386 387/* write => update the timer to keep the machine alive */388 389static ssize_t wb_smsc_wdt_write(struct file *file, const char __user *data,390				 size_t len, loff_t *ppos)391{392	/* See if we got the magic character 'V' and reload the timer */393	if (len) {394		if (!nowayout) {395			size_t i;396 397			/* reset expect flag */398			expect_close = 0;399 400			/* scan to see whether or not we got the401			   magic character */402			for (i = 0; i != len; i++) {403				char c;404				if (get_user(c, data + i))405					return -EFAULT;406				if (c == 'V')407					expect_close = 42;408			}409		}410 411		/* someone wrote to us, we should reload the timer */412		wb_smsc_wdt_reset_timer();413	}414	return len;415}416 417/* ioctl => control interface */418 419static long wb_smsc_wdt_ioctl(struct file *file,420					unsigned int cmd, unsigned long arg)421{422	int new_timeout;423 424	union {425		struct watchdog_info __user *ident;426		int __user *i;427	} uarg;428 429	static const struct watchdog_info ident = {430		.options =		WDIOF_KEEPALIVEPING |431					WDIOF_SETTIMEOUT |432					WDIOF_MAGICCLOSE,433		.firmware_version =	0,434		.identity =		"SMsC 37B787 Watchdog",435	};436 437	uarg.i = (int __user *)arg;438 439	switch (cmd) {440	case WDIOC_GETSUPPORT:441		return copy_to_user(uarg.ident, &ident, sizeof(ident))442								? -EFAULT : 0;443	case WDIOC_GETSTATUS:444		return put_user(wb_smsc_wdt_status(), uarg.i);445	case WDIOC_GETBOOTSTATUS:446		return put_user(0, uarg.i);447	case WDIOC_SETOPTIONS:448	{449		int options, retval = -EINVAL;450 451		if (get_user(options, uarg.i))452			return -EFAULT;453 454		if (options & WDIOS_DISABLECARD) {455			wb_smsc_wdt_disable();456			retval = 0;457		}458		if (options & WDIOS_ENABLECARD) {459			wb_smsc_wdt_enable();460			retval = 0;461		}462		return retval;463	}464	case WDIOC_KEEPALIVE:465		wb_smsc_wdt_reset_timer();466		return 0;467	case WDIOC_SETTIMEOUT:468		if (get_user(new_timeout, uarg.i))469			return -EFAULT;470		/* the API states this is given in secs */471		if (unit == UNIT_MINUTE)472			new_timeout /= 60;473		if (new_timeout < 0 || new_timeout > MAX_TIMEOUT)474			return -EINVAL;475		timeout = new_timeout;476		wb_smsc_wdt_set_timeout(timeout);477		fallthrough;	/* and return the new timeout */478	case WDIOC_GETTIMEOUT:479		new_timeout = timeout;480		if (unit == UNIT_MINUTE)481			new_timeout *= 60;482		return put_user(new_timeout, uarg.i);483	default:484		return -ENOTTY;485	}486}487 488/* -- Notifier funtions -----------------------------------------*/489 490static int wb_smsc_wdt_notify_sys(struct notifier_block *this,491					unsigned long code, void *unused)492{493	if (code == SYS_DOWN || code == SYS_HALT) {494		/* set timeout to 0, to avoid possible race-condition */495		timeout = 0;496		wb_smsc_wdt_disable();497	}498	return NOTIFY_DONE;499}500 501/* -- Module's structures ---------------------------------------*/502 503static const struct file_operations wb_smsc_wdt_fops = {504	.owner	  = THIS_MODULE,505	.write		= wb_smsc_wdt_write,506	.unlocked_ioctl	= wb_smsc_wdt_ioctl,507	.compat_ioctl	= compat_ptr_ioctl,508	.open		= wb_smsc_wdt_open,509	.release	= wb_smsc_wdt_release,510};511 512static struct notifier_block wb_smsc_wdt_notifier = {513	.notifier_call  = wb_smsc_wdt_notify_sys,514};515 516static struct miscdevice wb_smsc_wdt_miscdev = {517	.minor		= WATCHDOG_MINOR,518	.name		= "watchdog",519	.fops		= &wb_smsc_wdt_fops,520};521 522/* -- Module init functions -------------------------------------*/523 524/* module's "constructor" */525 526static int __init wb_smsc_wdt_init(void)527{528	int ret;529 530	pr_info("SMsC 37B787 watchdog component driver "531		VERSION " initialising...\n");532 533	if (!request_region(IOPORT, IOPORT_SIZE, "SMsC 37B787 watchdog")) {534		pr_err("Unable to register IO port %#x\n", IOPORT);535		ret = -EBUSY;536		goto out_pnp;537	}538 539	/* set new maximum, if it's too big */540	if (timeout > MAX_TIMEOUT)541		timeout = MAX_TIMEOUT;542 543	/* init the watchdog timer */544	wb_smsc_wdt_initialize();545 546	ret = register_reboot_notifier(&wb_smsc_wdt_notifier);547	if (ret) {548		pr_err("Unable to register reboot notifier err = %d\n", ret);549		goto out_io;550	}551 552	ret = misc_register(&wb_smsc_wdt_miscdev);553	if (ret) {554		pr_err("Unable to register miscdev on minor %d\n",555		       WATCHDOG_MINOR);556		goto out_rbt;557	}558 559	/* output info */560	pr_info("Timeout set to %d %s\n",561		timeout, (unit == UNIT_SECOND) ? "second(s)" : "minute(s)");562	pr_info("Watchdog initialized and sleeping (nowayout=%d)...\n",563		nowayout);564out_clean:565	return ret;566 567out_rbt:568	unregister_reboot_notifier(&wb_smsc_wdt_notifier);569 570out_io:571	release_region(IOPORT, IOPORT_SIZE);572 573out_pnp:574	goto out_clean;575}576 577/* module's "destructor" */578 579static void __exit wb_smsc_wdt_exit(void)580{581	/* Stop the timer before we leave */582	if (!nowayout) {583		wb_smsc_wdt_shutdown();584		pr_info("Watchdog disabled\n");585	}586 587	misc_deregister(&wb_smsc_wdt_miscdev);588	unregister_reboot_notifier(&wb_smsc_wdt_notifier);589	release_region(IOPORT, IOPORT_SIZE);590 591	pr_info("SMsC 37B787 watchdog component driver removed\n");592}593 594module_init(wb_smsc_wdt_init);595module_exit(wb_smsc_wdt_exit);596 597MODULE_AUTHOR("Sven Anders <anders@anduras.de>");598MODULE_DESCRIPTION("Driver for SMsC 37B787 watchdog component (Version "599								VERSION ")");600MODULE_LICENSE("GPL");601 602#ifdef SMSC_SUPPORT_MINUTES603module_param(unit, int, 0);604MODULE_PARM_DESC(unit,605		"set unit to use, 0=seconds or 1=minutes, default is 0");606#endif607 608module_param(timeout, int, 0);609MODULE_PARM_DESC(timeout, "range is 1-255 units, default is 60");610 611module_param(nowayout, bool, 0);612MODULE_PARM_DESC(nowayout,613		"Watchdog cannot be stopped once started (default="614				__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");615