1/*
2 * IBM Automatic Server Restart driver.
3 *
4 * Copyright (c) 2005 Andrey Panin <pazke@donpac.ru>
5 *
6 * Based on driver written by Pete Reynolds.
7 * Copyright (c) IBM Corporation, 1998-2004.
8 *
9 * This software may be used and distributed according to the terms
10 * of the GNU Public License, incorporated herein by reference.
11 */
12
13#include <linux/fs.h>
14#include <linux/kernel.h>
15#include <linux/slab.h>
16#include <linux/module.h>
17#include <linux/pci.h>
18#include <linux/timer.h>
19#include <linux/miscdevice.h>
20#include <linux/watchdog.h>
21#include <linux/dmi.h>
22
23#include <asm/io.h>
24#include <asm/uaccess.h>
25
26
27enum {
28	ASMTYPE_UNKNOWN,
29	ASMTYPE_TOPAZ,
30	ASMTYPE_JASPER,
31	ASMTYPE_PEARL,
32	ASMTYPE_JUNIPER,
33	ASMTYPE_SPRUCE,
34};
35
36#define PFX "ibmasr: "
37
38#define TOPAZ_ASR_REG_OFFSET	4
39#define TOPAZ_ASR_TOGGLE	0x40
40#define TOPAZ_ASR_DISABLE	0x80
41
42/* PEARL ASR S/W REGISTER SUPERIO PORT ADDRESSES */
43#define PEARL_BASE	0xe04
44#define PEARL_WRITE	0xe06
45#define PEARL_READ	0xe07
46
47#define PEARL_ASR_DISABLE_MASK	0x80	/* bit 7: disable = 1, enable = 0 */
48#define PEARL_ASR_TOGGLE_MASK	0x40	/* bit 6: 0, then 1, then 0 */
49
50/* JASPER OFFSET FROM SIO BASE ADDR TO ASR S/W REGISTERS. */
51#define JASPER_ASR_REG_OFFSET	0x38
52
53#define JASPER_ASR_DISABLE_MASK	0x01	/* bit 0: disable = 1, enable = 0 */
54#define JASPER_ASR_TOGGLE_MASK	0x02	/* bit 1: 0, then 1, then 0 */
55
56#define JUNIPER_BASE_ADDRESS	0x54b	/* Base address of Juniper ASR */
57#define JUNIPER_ASR_DISABLE_MASK 0x01	/* bit 0: disable = 1 enable = 0 */
58#define JUNIPER_ASR_TOGGLE_MASK	0x02	/* bit 1: 0, then 1, then 0 */
59
60#define SPRUCE_BASE_ADDRESS	0x118e	/* Base address of Spruce ASR */
61#define SPRUCE_ASR_DISABLE_MASK	0x01	/* bit 1: disable = 1 enable = 0 */
62#define SPRUCE_ASR_TOGGLE_MASK	0x02	/* bit 0: 0, then 1, then 0 */
63
64
65static int nowayout = WATCHDOG_NOWAYOUT;
66
67static unsigned long asr_is_open;
68static char asr_expect_close;
69
70static unsigned int asr_type, asr_base, asr_length;
71static unsigned int asr_read_addr, asr_write_addr;
72static unsigned char asr_toggle_mask, asr_disable_mask;
73
74static void asr_toggle(void)
75{
76	unsigned char reg = inb(asr_read_addr);
77
78	outb(reg & ~asr_toggle_mask, asr_write_addr);
79	reg = inb(asr_read_addr);
80
81	outb(reg | asr_toggle_mask, asr_write_addr);
82	reg = inb(asr_read_addr);
83
84	outb(reg & ~asr_toggle_mask, asr_write_addr);
85	reg = inb(asr_read_addr);
86}
87
88static void asr_enable(void)
89{
90	unsigned char reg;
91
92	if (asr_type == ASMTYPE_TOPAZ) {
93		/* asr_write_addr == asr_read_addr */
94		reg = inb(asr_read_addr);
95		outb(reg & ~(TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE),
96		     asr_read_addr);
97	} else {
98		/*
99		 * First make sure the hardware timer is reset by toggling
100		 * ASR hardware timer line.
101		 */
102		asr_toggle();
103
104		reg = inb(asr_read_addr);
105		outb(reg & ~asr_disable_mask, asr_write_addr);
106	}
107	reg = inb(asr_read_addr);
108}
109
110static void asr_disable(void)
111{
112	unsigned char reg = inb(asr_read_addr);
113
114	if (asr_type == ASMTYPE_TOPAZ)
115		/* asr_write_addr == asr_read_addr */
116		outb(reg | TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE,
117		     asr_read_addr);
118	else {
119		outb(reg | asr_toggle_mask, asr_write_addr);
120		reg = inb(asr_read_addr);
121
122		outb(reg | asr_disable_mask, asr_write_addr);
123	}
124	reg = inb(asr_read_addr);
125}
126
127static int __init asr_get_base_address(void)
128{
129	unsigned char low, high;
130	const char *type = "";
131
132	asr_length = 1;
133
134	switch (asr_type) {
135	case ASMTYPE_TOPAZ:
136		/* SELECT SuperIO CHIP FOR QUERYING (WRITE 0x07 TO BOTH 0x2E and 0x2F) */
137		outb(0x07, 0x2e);
138		outb(0x07, 0x2f);
139
140		/* SELECT AND READ THE HIGH-NIBBLE OF THE GPIO BASE ADDRESS */
141		outb(0x60, 0x2e);
142		high = inb(0x2f);
143
144		/* SELECT AND READ THE LOW-NIBBLE OF THE GPIO BASE ADDRESS */
145		outb(0x61, 0x2e);
146		low = inb(0x2f);
147
148		asr_base = (high << 16) | low;
149		asr_read_addr = asr_write_addr =
150			asr_base + TOPAZ_ASR_REG_OFFSET;
151		asr_length = 5;
152
153		break;
154
155	case ASMTYPE_JASPER:
156		type = "Jaspers ";
157
158
159/*		spin_lock_irqsave(&pci_config_lock, flags);*/
160
161		/* Select the SuperIO chip in the PCI I/O port register */
162		outl(0x8000f858, 0xcf8);
163
164		/*
165		 * Read the base address for the SuperIO chip.
166		 * Only the lower 16 bits are valid, but the address is word
167		 * aligned so the last bit must be masked off.
168		 */
169		asr_base = inl(0xcfc) & 0xfffe;
170
171/*		spin_unlock_irqrestore(&pci_config_lock, flags);*/
172
173		asr_read_addr = asr_write_addr =
174			asr_base + JASPER_ASR_REG_OFFSET;
175		asr_toggle_mask = JASPER_ASR_TOGGLE_MASK;
176		asr_disable_mask = JASPER_ASR_DISABLE_MASK;
177		asr_length = JASPER_ASR_REG_OFFSET + 1;
178
179		break;
180
181	case ASMTYPE_PEARL:
182		type = "Pearls ";
183		asr_base = PEARL_BASE;
184		asr_read_addr = PEARL_READ;
185		asr_write_addr = PEARL_WRITE;
186		asr_toggle_mask = PEARL_ASR_TOGGLE_MASK;
187		asr_disable_mask = PEARL_ASR_DISABLE_MASK;
188		asr_length = 4;
189		break;
190
191	case ASMTYPE_JUNIPER:
192		type = "Junipers ";
193		asr_base = JUNIPER_BASE_ADDRESS;
194		asr_read_addr = asr_write_addr = asr_base;
195		asr_toggle_mask = JUNIPER_ASR_TOGGLE_MASK;
196		asr_disable_mask = JUNIPER_ASR_DISABLE_MASK;
197		break;
198
199	case ASMTYPE_SPRUCE:
200		type = "Spruce's ";
201		asr_base = SPRUCE_BASE_ADDRESS;
202		asr_read_addr = asr_write_addr = asr_base;
203		asr_toggle_mask = SPRUCE_ASR_TOGGLE_MASK;
204		asr_disable_mask = SPRUCE_ASR_DISABLE_MASK;
205		break;
206	}
207
208	if (!request_region(asr_base, asr_length, "ibmasr")) {
209		printk(KERN_ERR PFX "address %#x already in use\n",
210			asr_base);
211		return -EBUSY;
212	}
213
214	printk(KERN_INFO PFX "found %sASR @ addr %#x\n", type, asr_base);
215
216	return 0;
217}
218
219
220static ssize_t asr_write(struct file *file, const char __user *buf,
221			 size_t count, loff_t *ppos)
222{
223	if (count) {
224		if (!nowayout) {
225			size_t i;
226
227			/* In case it was set long ago */
228			asr_expect_close = 0;
229
230			for (i = 0; i != count; i++) {
231				char c;
232				if (get_user(c, buf + i))
233					return -EFAULT;
234				if (c == 'V')
235					asr_expect_close = 42;
236			}
237		}
238		asr_toggle();
239	}
240	return count;
241}
242
243static int asr_ioctl(struct inode *inode, struct file *file,
244		     unsigned int cmd, unsigned long arg)
245{
246	static const struct watchdog_info ident = {
247		.options =	WDIOF_KEEPALIVEPING |
248				WDIOF_MAGICCLOSE,
249		.identity =	"IBM ASR"
250	};
251	void __user *argp = (void __user *)arg;
252	int __user *p = argp;
253	int heartbeat;
254
255	switch (cmd) {
256		case WDIOC_GETSUPPORT:
257			return copy_to_user(argp, &ident, sizeof(ident)) ?
258				-EFAULT : 0;
259
260		case WDIOC_GETSTATUS:
261		case WDIOC_GETBOOTSTATUS:
262			return put_user(0, p);
263
264		case WDIOC_KEEPALIVE:
265			asr_toggle();
266			return 0;
267
268		/*
269		 * The hardware has a fixed timeout value, so no WDIOC_SETTIMEOUT
270		 * and WDIOC_GETTIMEOUT always returns 256.
271		 */
272		case WDIOC_GETTIMEOUT:
273			heartbeat = 256;
274			return put_user(heartbeat, p);
275
276		case WDIOC_SETOPTIONS: {
277			int new_options, retval = -EINVAL;
278
279			if (get_user(new_options, p))
280				return -EFAULT;
281
282			if (new_options & WDIOS_DISABLECARD) {
283				asr_disable();
284				retval = 0;
285			}
286
287			if (new_options & WDIOS_ENABLECARD) {
288				asr_enable();
289				asr_toggle();
290				retval = 0;
291			}
292
293			return retval;
294		}
295	}
296
297	return -ENOTTY;
298}
299
300static int asr_open(struct inode *inode, struct file *file)
301{
302	if(test_and_set_bit(0, &asr_is_open))
303		return -EBUSY;
304
305	asr_toggle();
306	asr_enable();
307
308	return nonseekable_open(inode, file);
309}
310
311static int asr_release(struct inode *inode, struct file *file)
312{
313	if (asr_expect_close == 42)
314		asr_disable();
315	else {
316		printk(KERN_CRIT PFX "unexpected close, not stopping watchdog!\n");
317		asr_toggle();
318	}
319	clear_bit(0, &asr_is_open);
320	asr_expect_close = 0;
321	return 0;
322}
323
324static const struct file_operations asr_fops = {
325	.owner =	THIS_MODULE,
326	.llseek	=	no_llseek,
327	.write =	asr_write,
328	.ioctl =	asr_ioctl,
329	.open =		asr_open,
330	.release =	asr_release,
331};
332
333static struct miscdevice asr_miscdev = {
334	.minor =	WATCHDOG_MINOR,
335	.name =		"watchdog",
336	.fops =		&asr_fops,
337};
338
339
340struct ibmasr_id {
341	const char *desc;
342	int type;
343};
344
345static struct ibmasr_id __initdata ibmasr_id_table[] = {
346	{ "IBM Automatic Server Restart - eserver xSeries 220", ASMTYPE_TOPAZ },
347	{ "IBM Automatic Server Restart - Machine Type 8673", ASMTYPE_PEARL },
348	{ "IBM Automatic Server Restart - Machine Type 8480", ASMTYPE_JASPER },
349	{ "IBM Automatic Server Restart - Machine Type 8482", ASMTYPE_JUNIPER },
350	{ "IBM Automatic Server Restart - Machine Type 8648", ASMTYPE_SPRUCE },
351	{ NULL }
352};
353
354static int __init ibmasr_init(void)
355{
356	struct ibmasr_id *id;
357	int rc;
358
359	for (id = ibmasr_id_table; id->desc; id++) {
360		if (dmi_find_device(DMI_DEV_TYPE_OTHER, id->desc, NULL)) {
361			asr_type = id->type;
362			break;
363		}
364	}
365
366	if (!asr_type)
367		return -ENODEV;
368
369	rc = asr_get_base_address();
370	if (rc)
371		return rc;
372
373	rc = misc_register(&asr_miscdev);
374	if (rc < 0) {
375		release_region(asr_base, asr_length);
376		printk(KERN_ERR PFX "failed to register misc device\n");
377		return rc;
378	}
379
380	return 0;
381}
382
383static void __exit ibmasr_exit(void)
384{
385	if (!nowayout)
386		asr_disable();
387
388	misc_deregister(&asr_miscdev);
389
390	release_region(asr_base, asr_length);
391}
392
393module_init(ibmasr_init);
394module_exit(ibmasr_exit);
395
396module_param(nowayout, int, 0);
397MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
398
399MODULE_DESCRIPTION("IBM Automatic Server Restart driver");
400MODULE_AUTHOR("Andrey Panin");
401MODULE_LICENSE("GPL");
402MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
403