Blame view

drivers/watchdog/xen_wdt.c 7.45 KB
066d6c7f4   Jan Beulich   watchdog: Xen wat...
1
2
3
4
5
6
7
8
9
10
  /*
   *	Xen Watchdog Driver
   *
   *	(c) Copyright 2010 Novell, Inc.
   *
   *	This program is free software; you can redistribute it and/or
   *	modify it under the terms of the GNU General Public License
   *	as published by the Free Software Foundation; either version
   *	2 of the License, or (at your option) any later version.
   */
27c766aaa   Joe Perches   watchdog: Use pr_...
11
  #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
066d6c7f4   Jan Beulich   watchdog: Xen wat...
12
13
  #define DRV_NAME	"wdt"
  #define DRV_VERSION	"0.01"
066d6c7f4   Jan Beulich   watchdog: Xen wat...
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
  
  #include <linux/bug.h>
  #include <linux/errno.h>
  #include <linux/fs.h>
  #include <linux/hrtimer.h>
  #include <linux/kernel.h>
  #include <linux/ktime.h>
  #include <linux/init.h>
  #include <linux/miscdevice.h>
  #include <linux/module.h>
  #include <linux/moduleparam.h>
  #include <linux/platform_device.h>
  #include <linux/spinlock.h>
  #include <linux/uaccess.h>
  #include <linux/watchdog.h>
  #include <xen/xen.h>
  #include <asm/xen/hypercall.h>
  #include <xen/interface/sched.h>
  
  static struct platform_device *platform_device;
  static DEFINE_SPINLOCK(wdt_lock);
  static struct sched_watchdog wdt;
  static __kernel_time_t wdt_expires;
  static bool is_active, expect_release;
  
  #define WATCHDOG_TIMEOUT 60 /* in seconds */
  static unsigned int timeout = WATCHDOG_TIMEOUT;
  module_param(timeout, uint, S_IRUGO);
  MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds "
  	"(default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
  
  static bool nowayout = WATCHDOG_NOWAYOUT;
  module_param(nowayout, bool, S_IRUGO);
  MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
  	"(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
  
  static inline __kernel_time_t set_timeout(void)
  {
  	wdt.timeout = timeout;
  	return ktime_to_timespec(ktime_get()).tv_sec + timeout;
  }
  
  static int xen_wdt_start(void)
  {
  	__kernel_time_t expires;
  	int err;
  
  	spin_lock(&wdt_lock);
  
  	expires = set_timeout();
  	if (!wdt.id)
  		err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
  	else
  		err = -EBUSY;
  	if (err > 0) {
  		wdt.id = err;
  		wdt_expires = expires;
  		err = 0;
  	} else
  		BUG_ON(!err);
  
  	spin_unlock(&wdt_lock);
  
  	return err;
  }
  
  static int xen_wdt_stop(void)
  {
  	int err = 0;
  
  	spin_lock(&wdt_lock);
  
  	wdt.timeout = 0;
  	if (wdt.id)
  		err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
  	if (!err)
  		wdt.id = 0;
  
  	spin_unlock(&wdt_lock);
  
  	return err;
  }
  
  static int xen_wdt_kick(void)
  {
  	__kernel_time_t expires;
  	int err;
  
  	spin_lock(&wdt_lock);
  
  	expires = set_timeout();
  	if (wdt.id)
  		err = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wdt);
  	else
  		err = -ENXIO;
  	if (!err)
  		wdt_expires = expires;
  
  	spin_unlock(&wdt_lock);
  
  	return err;
  }
  
  static int xen_wdt_open(struct inode *inode, struct file *file)
  {
  	int err;
  
  	/* /dev/watchdog can only be opened once */
  	if (xchg(&is_active, true))
  		return -EBUSY;
  
  	err = xen_wdt_start();
  	if (err == -EBUSY)
  		err = xen_wdt_kick();
  	return err ?: nonseekable_open(inode, file);
  }
  
  static int xen_wdt_release(struct inode *inode, struct file *file)
  {
38c484fa1   Jan Beulich   watchdog: xen: do...
133
  	int err = 0;
066d6c7f4   Jan Beulich   watchdog: Xen wat...
134
  	if (expect_release)
38c484fa1   Jan Beulich   watchdog: xen: do...
135
  		err = xen_wdt_stop();
066d6c7f4   Jan Beulich   watchdog: Xen wat...
136
  	else {
27c766aaa   Joe Perches   watchdog: Use pr_...
137
138
  		pr_crit("unexpected close, not stopping watchdog!
  ");
066d6c7f4   Jan Beulich   watchdog: Xen wat...
139
140
  		xen_wdt_kick();
  	}
38c484fa1   Jan Beulich   watchdog: xen: do...
141
  	is_active = err;
066d6c7f4   Jan Beulich   watchdog: Xen wat...
142
  	expect_release = false;
38c484fa1   Jan Beulich   watchdog: xen: do...
143
  	return err;
066d6c7f4   Jan Beulich   watchdog: Xen wat...
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
  }
  
  static ssize_t xen_wdt_write(struct file *file, const char __user *data,
  			     size_t len, loff_t *ppos)
  {
  	/* See if we got the magic character 'V' and reload the timer */
  	if (len) {
  		if (!nowayout) {
  			size_t i;
  
  			/* in case it was set long ago */
  			expect_release = false;
  
  			/* scan to see whether or not we got the magic
  			   character */
  			for (i = 0; i != len; i++) {
  				char c;
  				if (get_user(c, data + i))
  					return -EFAULT;
  				if (c == 'V')
  					expect_release = true;
  			}
  		}
  
  		/* someone wrote to us, we should reload the timer */
  		xen_wdt_kick();
  	}
  	return len;
  }
  
  static long xen_wdt_ioctl(struct file *file, unsigned int cmd,
  			  unsigned long arg)
  {
  	int new_options, retval = -EINVAL;
  	int new_timeout;
  	int __user *argp = (void __user *)arg;
  	static const struct watchdog_info ident = {
  		.options =		WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE,
  		.firmware_version =	0,
  		.identity =		DRV_NAME,
  	};
  
  	switch (cmd) {
  	case WDIOC_GETSUPPORT:
  		return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
  
  	case WDIOC_GETSTATUS:
  	case WDIOC_GETBOOTSTATUS:
  		return put_user(0, argp);
  
  	case WDIOC_SETOPTIONS:
  		if (get_user(new_options, argp))
  			return -EFAULT;
  
  		if (new_options & WDIOS_DISABLECARD)
  			retval = xen_wdt_stop();
  		if (new_options & WDIOS_ENABLECARD) {
  			retval = xen_wdt_start();
  			if (retval == -EBUSY)
  				retval = xen_wdt_kick();
  		}
  		return retval;
  
  	case WDIOC_KEEPALIVE:
  		xen_wdt_kick();
  		return 0;
  
  	case WDIOC_SETTIMEOUT:
  		if (get_user(new_timeout, argp))
  			return -EFAULT;
  		if (!new_timeout)
  			return -EINVAL;
  		timeout = new_timeout;
  		xen_wdt_kick();
  		/* fall through */
  	case WDIOC_GETTIMEOUT:
  		return put_user(timeout, argp);
  
  	case WDIOC_GETTIMELEFT:
  		retval = wdt_expires - ktime_to_timespec(ktime_get()).tv_sec;
  		return put_user(retval, argp);
  	}
  
  	return -ENOTTY;
  }
  
  static const struct file_operations xen_wdt_fops = {
  	.owner =		THIS_MODULE,
  	.llseek =		no_llseek,
  	.write =		xen_wdt_write,
  	.unlocked_ioctl =	xen_wdt_ioctl,
  	.open =			xen_wdt_open,
  	.release =		xen_wdt_release,
  };
  
  static struct miscdevice xen_wdt_miscdev = {
  	.minor =	WATCHDOG_MINOR,
  	.name =		"watchdog",
  	.fops =		&xen_wdt_fops,
  };
2d991a164   Bill Pemberton   watchdog: remove ...
244
  static int xen_wdt_probe(struct platform_device *dev)
066d6c7f4   Jan Beulich   watchdog: Xen wat...
245
246
247
248
249
250
251
252
  {
  	struct sched_watchdog wd = { .id = ~0 };
  	int ret = HYPERVISOR_sched_op(SCHEDOP_watchdog, &wd);
  
  	switch (ret) {
  	case -EINVAL:
  		if (!timeout) {
  			timeout = WATCHDOG_TIMEOUT;
27c766aaa   Joe Perches   watchdog: Use pr_...
253
254
  			pr_info("timeout value invalid, using %d
  ", timeout);
066d6c7f4   Jan Beulich   watchdog: Xen wat...
255
256
257
258
  		}
  
  		ret = misc_register(&xen_wdt_miscdev);
  		if (ret) {
27c766aaa   Joe Perches   watchdog: Use pr_...
259
260
  			pr_err("cannot register miscdev on minor=%d (%d)
  ",
066d6c7f4   Jan Beulich   watchdog: Xen wat...
261
262
263
  			       WATCHDOG_MINOR, ret);
  			break;
  		}
27c766aaa   Joe Perches   watchdog: Use pr_...
264
265
266
  		pr_info("initialized (timeout=%ds, nowayout=%d)
  ",
  			timeout, nowayout);
066d6c7f4   Jan Beulich   watchdog: Xen wat...
267
268
269
  		break;
  
  	case -ENOSYS:
27c766aaa   Joe Perches   watchdog: Use pr_...
270
271
  		pr_info("not supported
  ");
066d6c7f4   Jan Beulich   watchdog: Xen wat...
272
273
274
275
  		ret = -ENODEV;
  		break;
  
  	default:
27c766aaa   Joe Perches   watchdog: Use pr_...
276
277
  		pr_info("bogus return value %d
  ", ret);
066d6c7f4   Jan Beulich   watchdog: Xen wat...
278
279
280
281
282
  		break;
  	}
  
  	return ret;
  }
4b12b896c   Bill Pemberton   watchdog: remove ...
283
  static int xen_wdt_remove(struct platform_device *dev)
066d6c7f4   Jan Beulich   watchdog: Xen wat...
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
  {
  	/* Stop the timer before we leave */
  	if (!nowayout)
  		xen_wdt_stop();
  
  	misc_deregister(&xen_wdt_miscdev);
  
  	return 0;
  }
  
  static void xen_wdt_shutdown(struct platform_device *dev)
  {
  	xen_wdt_stop();
  }
  
  static int xen_wdt_suspend(struct platform_device *dev, pm_message_t state)
  {
83448bf78   Jan Beulich   watchdog: xen: do...
301
302
303
304
305
  	typeof(wdt.id) id = wdt.id;
  	int rc = xen_wdt_stop();
  
  	wdt.id = id;
  	return rc;
066d6c7f4   Jan Beulich   watchdog: Xen wat...
306
307
308
309
  }
  
  static int xen_wdt_resume(struct platform_device *dev)
  {
83448bf78   Jan Beulich   watchdog: xen: do...
310
311
312
  	if (!wdt.id)
  		return 0;
  	wdt.id = 0;
066d6c7f4   Jan Beulich   watchdog: Xen wat...
313
314
315
316
317
  	return xen_wdt_start();
  }
  
  static struct platform_driver xen_wdt_driver = {
  	.probe          = xen_wdt_probe,
82268714b   Bill Pemberton   watchdog: remove ...
318
  	.remove         = xen_wdt_remove,
066d6c7f4   Jan Beulich   watchdog: Xen wat...
319
320
321
322
  	.shutdown       = xen_wdt_shutdown,
  	.suspend        = xen_wdt_suspend,
  	.resume         = xen_wdt_resume,
  	.driver         = {
066d6c7f4   Jan Beulich   watchdog: Xen wat...
323
324
325
326
327
328
329
330
331
332
  		.name   = DRV_NAME,
  	},
  };
  
  static int __init xen_wdt_init_module(void)
  {
  	int err;
  
  	if (!xen_domain())
  		return -ENODEV;
27c766aaa   Joe Perches   watchdog: Use pr_...
333
334
  	pr_info("Xen WatchDog Timer Driver v%s
  ", DRV_VERSION);
066d6c7f4   Jan Beulich   watchdog: Xen wat...
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
  
  	err = platform_driver_register(&xen_wdt_driver);
  	if (err)
  		return err;
  
  	platform_device = platform_device_register_simple(DRV_NAME,
  								  -1, NULL, 0);
  	if (IS_ERR(platform_device)) {
  		err = PTR_ERR(platform_device);
  		platform_driver_unregister(&xen_wdt_driver);
  	}
  
  	return err;
  }
  
  static void __exit xen_wdt_cleanup_module(void)
  {
  	platform_device_unregister(platform_device);
  	platform_driver_unregister(&xen_wdt_driver);
27c766aaa   Joe Perches   watchdog: Use pr_...
354
355
  	pr_info("module unloaded
  ");
066d6c7f4   Jan Beulich   watchdog: Xen wat...
356
357
358
359
360
361
362
363
364
  }
  
  module_init(xen_wdt_init_module);
  module_exit(xen_wdt_cleanup_module);
  
  MODULE_AUTHOR("Jan Beulich <jbeulich@novell.com>");
  MODULE_DESCRIPTION("Xen WatchDog Timer Driver");
  MODULE_VERSION(DRV_VERSION);
  MODULE_LICENSE("GPL");