]>
Commit | Line | Data |
---|---|---|
43316044 WVS |
1 | /* |
2 | * watchdog_dev.c | |
3 | * | |
4 | * (c) Copyright 2008-2011 Alan Cox <alan@lxorguk.ukuu.org.uk>, | |
5 | * All Rights Reserved. | |
6 | * | |
7 | * (c) Copyright 2008-2011 Wim Van Sebroeck <wim@iguana.be>. | |
8 | * | |
9 | * | |
10 | * This source code is part of the generic code that can be used | |
11 | * by all the watchdog timer drivers. | |
12 | * | |
13 | * This part of the generic code takes care of the following | |
14 | * misc device: /dev/watchdog. | |
15 | * | |
16 | * Based on source code of the following authors: | |
17 | * Matt Domsch <Matt_Domsch@dell.com>, | |
18 | * Rob Radez <rob@osinvestor.com>, | |
19 | * Rusty Lynch <rusty@linux.co.intel.com> | |
20 | * Satyam Sharma <satyam@infradead.org> | |
21 | * Randy Dunlap <randy.dunlap@oracle.com> | |
22 | * | |
23 | * This program is free software; you can redistribute it and/or | |
24 | * modify it under the terms of the GNU General Public License | |
25 | * as published by the Free Software Foundation; either version | |
26 | * 2 of the License, or (at your option) any later version. | |
27 | * | |
28 | * Neither Alan Cox, CymruNet Ltd., Wim Van Sebroeck nor Iguana vzw. | |
29 | * admit liability nor provide warranty for any of this software. | |
30 | * This material is provided "AS-IS" and at no charge. | |
31 | */ | |
32 | ||
33 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
34 | ||
b4ffb190 | 35 | #include <linux/cdev.h> /* For character device */ |
43316044 | 36 | #include <linux/errno.h> /* For the -ENODEV/... values */ |
43316044 | 37 | #include <linux/fs.h> /* For file operations */ |
43316044 | 38 | #include <linux/init.h> /* For __init/__exit/... */ |
664a3923 | 39 | #include <linux/jiffies.h> /* For timeout functions */ |
b4ffb190 | 40 | #include <linux/kernel.h> /* For printk/panic/... */ |
b4ffb190 GR |
41 | #include <linux/miscdevice.h> /* For handling misc devices */ |
42 | #include <linux/module.h> /* For module stuff/... */ | |
43 | #include <linux/mutex.h> /* For mutexes */ | |
44 | #include <linux/slab.h> /* For memory functions */ | |
45 | #include <linux/types.h> /* For standard types (like size_t) */ | |
46 | #include <linux/watchdog.h> /* For watchdog specific items */ | |
664a3923 | 47 | #include <linux/workqueue.h> /* For workqueue */ |
43316044 WVS |
48 | #include <linux/uaccess.h> /* For copy_to_user/put_user/... */ |
49 | ||
6cfb5aa8 | 50 | #include "watchdog_core.h" |
ff84136c | 51 | #include "watchdog_pretimeout.h" |
09a46e73 | 52 | |
b4ffb190 GR |
53 | /* |
54 | * struct watchdog_core_data - watchdog core internal data | |
44499b4f | 55 | * @dev: The watchdog's internal device |
b4ffb190 GR |
56 | * @cdev: The watchdog's Character device. |
57 | * @wdd: Pointer to watchdog device. | |
58 | * @lock: Lock for watchdog core. | |
59 | * @status: Watchdog core internal status bits. | |
60 | */ | |
61 | struct watchdog_core_data { | |
44499b4f | 62 | struct device dev; |
b4ffb190 GR |
63 | struct cdev cdev; |
64 | struct watchdog_device *wdd; | |
65 | struct mutex lock; | |
664a3923 | 66 | unsigned long last_keepalive; |
15013ad8 | 67 | unsigned long last_hw_keepalive; |
664a3923 | 68 | struct delayed_work work; |
b4ffb190 GR |
69 | unsigned long status; /* Internal status bits */ |
70 | #define _WDOG_DEV_OPEN 0 /* Opened ? */ | |
71 | #define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */ | |
90b826f1 | 72 | #define _WDOG_KEEPALIVE 2 /* Did we receive a keepalive ? */ |
b4ffb190 GR |
73 | }; |
74 | ||
45f5fed3 AC |
75 | /* the dev_t structure to store the dynamically allocated watchdog devices */ |
76 | static dev_t watchdog_devt; | |
b4ffb190 GR |
77 | /* Reference to watchdog device behind /dev/watchdog */ |
78 | static struct watchdog_core_data *old_wd_data; | |
43316044 | 79 | |
664a3923 GR |
80 | static struct workqueue_struct *watchdog_wq; |
81 | ||
2501b015 SR |
82 | static bool handle_boot_enabled = |
83 | IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED); | |
84 | ||
664a3923 GR |
85 | static inline bool watchdog_need_worker(struct watchdog_device *wdd) |
86 | { | |
87 | /* All variables in milli-seconds */ | |
88 | unsigned int hm = wdd->max_hw_heartbeat_ms; | |
89 | unsigned int t = wdd->timeout * 1000; | |
90 | ||
91 | /* | |
92 | * A worker to generate heartbeat requests is needed if all of the | |
93 | * following conditions are true. | |
94 | * - Userspace activated the watchdog. | |
95 | * - The driver provided a value for the maximum hardware timeout, and | |
96 | * thus is aware that the framework supports generating heartbeat | |
97 | * requests. | |
98 | * - Userspace requests a longer timeout than the hardware can handle. | |
3fbfe926 RV |
99 | * |
100 | * Alternatively, if userspace has not opened the watchdog | |
101 | * device, we take care of feeding the watchdog if it is | |
102 | * running. | |
664a3923 | 103 | */ |
3fbfe926 RV |
104 | return (hm && watchdog_active(wdd) && t > hm) || |
105 | (t && !watchdog_active(wdd) && watchdog_hw_running(wdd)); | |
664a3923 GR |
106 | } |
107 | ||
108 | static long watchdog_next_keepalive(struct watchdog_device *wdd) | |
109 | { | |
110 | struct watchdog_core_data *wd_data = wdd->wd_data; | |
111 | unsigned int timeout_ms = wdd->timeout * 1000; | |
112 | unsigned long keepalive_interval; | |
113 | unsigned long last_heartbeat; | |
114 | unsigned long virt_timeout; | |
115 | unsigned int hw_heartbeat_ms; | |
116 | ||
117 | virt_timeout = wd_data->last_keepalive + msecs_to_jiffies(timeout_ms); | |
3fbfe926 | 118 | hw_heartbeat_ms = min_not_zero(timeout_ms, wdd->max_hw_heartbeat_ms); |
664a3923 GR |
119 | keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2); |
120 | ||
ee142889 GR |
121 | if (!watchdog_active(wdd)) |
122 | return keepalive_interval; | |
123 | ||
664a3923 GR |
124 | /* |
125 | * To ensure that the watchdog times out wdd->timeout seconds | |
126 | * after the most recent ping from userspace, the last | |
127 | * worker ping has to come in hw_heartbeat_ms before this timeout. | |
128 | */ | |
129 | last_heartbeat = virt_timeout - msecs_to_jiffies(hw_heartbeat_ms); | |
130 | return min_t(long, last_heartbeat - jiffies, keepalive_interval); | |
131 | } | |
132 | ||
133 | static inline void watchdog_update_worker(struct watchdog_device *wdd) | |
134 | { | |
135 | struct watchdog_core_data *wd_data = wdd->wd_data; | |
136 | ||
137 | if (watchdog_need_worker(wdd)) { | |
138 | long t = watchdog_next_keepalive(wdd); | |
139 | ||
140 | if (t > 0) | |
141 | mod_delayed_work(watchdog_wq, &wd_data->work, t); | |
142 | } else { | |
143 | cancel_delayed_work(&wd_data->work); | |
144 | } | |
145 | } | |
146 | ||
147 | static int __watchdog_ping(struct watchdog_device *wdd) | |
148 | { | |
15013ad8 GR |
149 | struct watchdog_core_data *wd_data = wdd->wd_data; |
150 | unsigned long earliest_keepalive = wd_data->last_hw_keepalive + | |
151 | msecs_to_jiffies(wdd->min_hw_heartbeat_ms); | |
664a3923 GR |
152 | int err; |
153 | ||
15013ad8 GR |
154 | if (time_is_after_jiffies(earliest_keepalive)) { |
155 | mod_delayed_work(watchdog_wq, &wd_data->work, | |
156 | earliest_keepalive - jiffies); | |
157 | return 0; | |
158 | } | |
159 | ||
160 | wd_data->last_hw_keepalive = jiffies; | |
161 | ||
664a3923 GR |
162 | if (wdd->ops->ping) |
163 | err = wdd->ops->ping(wdd); /* ping the watchdog */ | |
164 | else | |
165 | err = wdd->ops->start(wdd); /* restart watchdog */ | |
166 | ||
167 | watchdog_update_worker(wdd); | |
168 | ||
169 | return err; | |
170 | } | |
171 | ||
43316044 WVS |
172 | /* |
173 | * watchdog_ping: ping the watchdog. | |
bc794ac3 | 174 | * @wdd: the watchdog device to ping |
43316044 | 175 | * |
b4ffb190 GR |
176 | * The caller must hold wd_data->lock. |
177 | * | |
43316044 WVS |
178 | * If the watchdog has no own ping operation then it needs to be |
179 | * restarted via the start operation. This wrapper function does | |
180 | * exactly that. | |
234445b4 | 181 | * We only ping when the watchdog device is running. |
43316044 WVS |
182 | */ |
183 | ||
bc794ac3 | 184 | static int watchdog_ping(struct watchdog_device *wdd) |
43316044 | 185 | { |
664a3923 | 186 | struct watchdog_core_data *wd_data = wdd->wd_data; |
e907df32 | 187 | |
ee142889 | 188 | if (!watchdog_active(wdd) && !watchdog_hw_running(wdd)) |
b4ffb190 | 189 | return 0; |
7a879824 | 190 | |
90b826f1 GR |
191 | set_bit(_WDOG_KEEPALIVE, &wd_data->status); |
192 | ||
664a3923 GR |
193 | wd_data->last_keepalive = jiffies; |
194 | return __watchdog_ping(wdd); | |
195 | } | |
7a879824 | 196 | |
c013b65a RV |
197 | static bool watchdog_worker_should_ping(struct watchdog_core_data *wd_data) |
198 | { | |
199 | struct watchdog_device *wdd = wd_data->wdd; | |
200 | ||
201 | return wdd && (watchdog_active(wdd) || watchdog_hw_running(wdd)); | |
202 | } | |
203 | ||
664a3923 GR |
204 | static void watchdog_ping_work(struct work_struct *work) |
205 | { | |
206 | struct watchdog_core_data *wd_data; | |
664a3923 GR |
207 | |
208 | wd_data = container_of(to_delayed_work(work), struct watchdog_core_data, | |
209 | work); | |
210 | ||
211 | mutex_lock(&wd_data->lock); | |
c013b65a RV |
212 | if (watchdog_worker_should_ping(wd_data)) |
213 | __watchdog_ping(wd_data->wdd); | |
664a3923 | 214 | mutex_unlock(&wd_data->lock); |
234445b4 WVS |
215 | } |
216 | ||
217 | /* | |
218 | * watchdog_start: wrapper to start the watchdog. | |
bc794ac3 | 219 | * @wdd: the watchdog device to start |
234445b4 | 220 | * |
b4ffb190 GR |
221 | * The caller must hold wd_data->lock. |
222 | * | |
234445b4 WVS |
223 | * Start the watchdog if it is not active and mark it active. |
224 | * This function returns zero on success or a negative errno code for | |
225 | * failure. | |
226 | */ | |
227 | ||
bc794ac3 | 228 | static int watchdog_start(struct watchdog_device *wdd) |
234445b4 | 229 | { |
664a3923 GR |
230 | struct watchdog_core_data *wd_data = wdd->wd_data; |
231 | unsigned long started_at; | |
b4ffb190 | 232 | int err; |
e907df32 | 233 | |
bc794ac3 | 234 | if (watchdog_active(wdd)) |
b4ffb190 | 235 | return 0; |
234445b4 | 236 | |
90b826f1 GR |
237 | set_bit(_WDOG_KEEPALIVE, &wd_data->status); |
238 | ||
664a3923 | 239 | started_at = jiffies; |
ee142889 GR |
240 | if (watchdog_hw_running(wdd) && wdd->ops->ping) |
241 | err = wdd->ops->ping(wdd); | |
242 | else | |
243 | err = wdd->ops->start(wdd); | |
664a3923 | 244 | if (err == 0) { |
bc794ac3 | 245 | set_bit(WDOG_ACTIVE, &wdd->status); |
664a3923 GR |
246 | wd_data->last_keepalive = started_at; |
247 | watchdog_update_worker(wdd); | |
248 | } | |
7a879824 | 249 | |
7a879824 | 250 | return err; |
234445b4 WVS |
251 | } |
252 | ||
253 | /* | |
254 | * watchdog_stop: wrapper to stop the watchdog. | |
bc794ac3 | 255 | * @wdd: the watchdog device to stop |
234445b4 | 256 | * |
b4ffb190 GR |
257 | * The caller must hold wd_data->lock. |
258 | * | |
234445b4 WVS |
259 | * Stop the watchdog if it is still active and unmark it active. |
260 | * This function returns zero on success or a negative errno code for | |
261 | * failure. | |
7e192b9c | 262 | * If the 'nowayout' feature was set, the watchdog cannot be stopped. |
234445b4 WVS |
263 | */ |
264 | ||
bc794ac3 | 265 | static int watchdog_stop(struct watchdog_device *wdd) |
234445b4 | 266 | { |
ee142889 | 267 | int err = 0; |
e907df32 | 268 | |
bc794ac3 | 269 | if (!watchdog_active(wdd)) |
b4ffb190 | 270 | return 0; |
7e192b9c | 271 | |
bc794ac3 | 272 | if (test_bit(WDOG_NO_WAY_OUT, &wdd->status)) { |
0254e953 GR |
273 | pr_info("watchdog%d: nowayout prevents watchdog being stopped!\n", |
274 | wdd->id); | |
b4ffb190 | 275 | return -EBUSY; |
7e192b9c | 276 | } |
234445b4 | 277 | |
3c10bbde GR |
278 | if (wdd->ops->stop) { |
279 | clear_bit(WDOG_HW_RUNNING, &wdd->status); | |
d0684c8a | 280 | err = wdd->ops->stop(wdd); |
3c10bbde | 281 | } else { |
d0684c8a | 282 | set_bit(WDOG_HW_RUNNING, &wdd->status); |
3c10bbde | 283 | } |
d0684c8a | 284 | |
664a3923 | 285 | if (err == 0) { |
bc794ac3 | 286 | clear_bit(WDOG_ACTIVE, &wdd->status); |
ee142889 | 287 | watchdog_update_worker(wdd); |
664a3923 | 288 | } |
7a879824 | 289 | |
7a879824 HG |
290 | return err; |
291 | } | |
292 | ||
293 | /* | |
294 | * watchdog_get_status: wrapper to get the watchdog status | |
bc794ac3 | 295 | * @wdd: the watchdog device to get the status from |
b4ffb190 GR |
296 | * |
297 | * The caller must hold wd_data->lock. | |
7a879824 HG |
298 | * |
299 | * Get the watchdog's status flags. | |
300 | */ | |
301 | ||
b4ffb190 | 302 | static unsigned int watchdog_get_status(struct watchdog_device *wdd) |
7a879824 | 303 | { |
90b826f1 GR |
304 | struct watchdog_core_data *wd_data = wdd->wd_data; |
305 | unsigned int status; | |
7a879824 | 306 | |
90b826f1 GR |
307 | if (wdd->ops->status) |
308 | status = wdd->ops->status(wdd); | |
309 | else | |
310 | status = wdd->bootstatus & (WDIOF_CARDRESET | | |
311 | WDIOF_OVERHEAT | | |
312 | WDIOF_FANFAULT | | |
313 | WDIOF_EXTERN1 | | |
314 | WDIOF_EXTERN2 | | |
315 | WDIOF_POWERUNDER | | |
316 | WDIOF_POWEROVER); | |
317 | ||
318 | if (test_bit(_WDOG_ALLOW_RELEASE, &wd_data->status)) | |
319 | status |= WDIOF_MAGICCLOSE; | |
320 | ||
321 | if (test_and_clear_bit(_WDOG_KEEPALIVE, &wd_data->status)) | |
322 | status |= WDIOF_KEEPALIVEPING; | |
323 | ||
324 | return status; | |
7a879824 HG |
325 | } |
326 | ||
327 | /* | |
328 | * watchdog_set_timeout: set the watchdog timer timeout | |
bc794ac3 | 329 | * @wdd: the watchdog device to set the timeout for |
7a879824 | 330 | * @timeout: timeout to set in seconds |
b4ffb190 GR |
331 | * |
332 | * The caller must hold wd_data->lock. | |
7a879824 HG |
333 | */ |
334 | ||
bc794ac3 | 335 | static int watchdog_set_timeout(struct watchdog_device *wdd, |
7a879824 HG |
336 | unsigned int timeout) |
337 | { | |
fb32e9b9 GR |
338 | int err = 0; |
339 | ||
340 | if (!(wdd->info->options & WDIOF_SETTIMEOUT)) | |
7a879824 HG |
341 | return -EOPNOTSUPP; |
342 | ||
bc794ac3 | 343 | if (watchdog_timeout_invalid(wdd, timeout)) |
7a879824 HG |
344 | return -EINVAL; |
345 | ||
df044e02 | 346 | if (wdd->ops->set_timeout) { |
fb32e9b9 | 347 | err = wdd->ops->set_timeout(wdd, timeout); |
df044e02 | 348 | } else { |
fb32e9b9 | 349 | wdd->timeout = timeout; |
df044e02 WS |
350 | /* Disable pretimeout if it doesn't fit the new timeout */ |
351 | if (wdd->pretimeout >= wdd->timeout) | |
352 | wdd->pretimeout = 0; | |
353 | } | |
fb32e9b9 | 354 | |
664a3923 GR |
355 | watchdog_update_worker(wdd); |
356 | ||
fb32e9b9 | 357 | return err; |
7a879824 HG |
358 | } |
359 | ||
df044e02 WS |
360 | /* |
361 | * watchdog_set_pretimeout: set the watchdog timer pretimeout | |
362 | * @wdd: the watchdog device to set the timeout for | |
363 | * @timeout: pretimeout to set in seconds | |
364 | */ | |
365 | ||
366 | static int watchdog_set_pretimeout(struct watchdog_device *wdd, | |
367 | unsigned int timeout) | |
368 | { | |
369 | int err = 0; | |
370 | ||
371 | if (!(wdd->info->options & WDIOF_PRETIMEOUT)) | |
372 | return -EOPNOTSUPP; | |
373 | ||
374 | if (watchdog_pretimeout_invalid(wdd, timeout)) | |
375 | return -EINVAL; | |
376 | ||
377 | if (wdd->ops->set_pretimeout) | |
378 | err = wdd->ops->set_pretimeout(wdd, timeout); | |
379 | else | |
380 | wdd->pretimeout = timeout; | |
381 | ||
382 | return err; | |
383 | } | |
384 | ||
7a879824 HG |
385 | /* |
386 | * watchdog_get_timeleft: wrapper to get the time left before a reboot | |
bc794ac3 | 387 | * @wdd: the watchdog device to get the remaining time from |
7a879824 HG |
388 | * @timeleft: the time that's left |
389 | * | |
b4ffb190 GR |
390 | * The caller must hold wd_data->lock. |
391 | * | |
7a879824 HG |
392 | * Get the time before a watchdog will reboot (if not pinged). |
393 | */ | |
394 | ||
bc794ac3 | 395 | static int watchdog_get_timeleft(struct watchdog_device *wdd, |
7a879824 HG |
396 | unsigned int *timeleft) |
397 | { | |
7a879824 | 398 | *timeleft = 0; |
b4ffb190 | 399 | |
bc794ac3 | 400 | if (!wdd->ops->get_timeleft) |
7a879824 HG |
401 | return -EOPNOTSUPP; |
402 | ||
bc794ac3 | 403 | *timeleft = wdd->ops->get_timeleft(wdd); |
7a879824 | 404 | |
b4ffb190 | 405 | return 0; |
7a879824 HG |
406 | } |
407 | ||
33b71126 PA |
408 | #ifdef CONFIG_WATCHDOG_SYSFS |
409 | static ssize_t nowayout_show(struct device *dev, struct device_attribute *attr, | |
410 | char *buf) | |
411 | { | |
412 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
413 | ||
414 | return sprintf(buf, "%d\n", !!test_bit(WDOG_NO_WAY_OUT, &wdd->status)); | |
415 | } | |
416 | static DEVICE_ATTR_RO(nowayout); | |
417 | ||
418 | static ssize_t status_show(struct device *dev, struct device_attribute *attr, | |
419 | char *buf) | |
420 | { | |
421 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
b4ffb190 GR |
422 | struct watchdog_core_data *wd_data = wdd->wd_data; |
423 | unsigned int status; | |
33b71126 | 424 | |
b4ffb190 GR |
425 | mutex_lock(&wd_data->lock); |
426 | status = watchdog_get_status(wdd); | |
427 | mutex_unlock(&wd_data->lock); | |
33b71126 | 428 | |
90b826f1 | 429 | return sprintf(buf, "0x%x\n", status); |
33b71126 PA |
430 | } |
431 | static DEVICE_ATTR_RO(status); | |
432 | ||
433 | static ssize_t bootstatus_show(struct device *dev, | |
434 | struct device_attribute *attr, char *buf) | |
435 | { | |
436 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
437 | ||
438 | return sprintf(buf, "%u\n", wdd->bootstatus); | |
439 | } | |
440 | static DEVICE_ATTR_RO(bootstatus); | |
441 | ||
442 | static ssize_t timeleft_show(struct device *dev, struct device_attribute *attr, | |
443 | char *buf) | |
444 | { | |
445 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
b4ffb190 | 446 | struct watchdog_core_data *wd_data = wdd->wd_data; |
33b71126 PA |
447 | ssize_t status; |
448 | unsigned int val; | |
449 | ||
b4ffb190 | 450 | mutex_lock(&wd_data->lock); |
33b71126 | 451 | status = watchdog_get_timeleft(wdd, &val); |
b4ffb190 | 452 | mutex_unlock(&wd_data->lock); |
33b71126 PA |
453 | if (!status) |
454 | status = sprintf(buf, "%u\n", val); | |
455 | ||
456 | return status; | |
457 | } | |
458 | static DEVICE_ATTR_RO(timeleft); | |
459 | ||
460 | static ssize_t timeout_show(struct device *dev, struct device_attribute *attr, | |
461 | char *buf) | |
462 | { | |
463 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
464 | ||
465 | return sprintf(buf, "%u\n", wdd->timeout); | |
466 | } | |
467 | static DEVICE_ATTR_RO(timeout); | |
468 | ||
df044e02 WS |
469 | static ssize_t pretimeout_show(struct device *dev, |
470 | struct device_attribute *attr, char *buf) | |
471 | { | |
472 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
473 | ||
474 | return sprintf(buf, "%u\n", wdd->pretimeout); | |
475 | } | |
476 | static DEVICE_ATTR_RO(pretimeout); | |
477 | ||
33b71126 PA |
478 | static ssize_t identity_show(struct device *dev, struct device_attribute *attr, |
479 | char *buf) | |
480 | { | |
481 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
482 | ||
483 | return sprintf(buf, "%s\n", wdd->info->identity); | |
484 | } | |
485 | static DEVICE_ATTR_RO(identity); | |
486 | ||
487 | static ssize_t state_show(struct device *dev, struct device_attribute *attr, | |
488 | char *buf) | |
489 | { | |
490 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
491 | ||
492 | if (watchdog_active(wdd)) | |
493 | return sprintf(buf, "active\n"); | |
494 | ||
495 | return sprintf(buf, "inactive\n"); | |
496 | } | |
497 | static DEVICE_ATTR_RO(state); | |
498 | ||
89873a71 VZ |
499 | static ssize_t pretimeout_available_governors_show(struct device *dev, |
500 | struct device_attribute *attr, char *buf) | |
501 | { | |
502 | return watchdog_pretimeout_available_governors_get(buf); | |
503 | } | |
504 | static DEVICE_ATTR_RO(pretimeout_available_governors); | |
505 | ||
ff84136c VZ |
506 | static ssize_t pretimeout_governor_show(struct device *dev, |
507 | struct device_attribute *attr, | |
508 | char *buf) | |
509 | { | |
510 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
511 | ||
512 | return watchdog_pretimeout_governor_get(wdd, buf); | |
513 | } | |
53f96cee VZ |
514 | |
515 | static ssize_t pretimeout_governor_store(struct device *dev, | |
516 | struct device_attribute *attr, | |
517 | const char *buf, size_t count) | |
518 | { | |
519 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
520 | int ret = watchdog_pretimeout_governor_set(wdd, buf); | |
521 | ||
522 | if (!ret) | |
523 | ret = count; | |
524 | ||
525 | return ret; | |
526 | } | |
527 | static DEVICE_ATTR_RW(pretimeout_governor); | |
ff84136c | 528 | |
33b71126 PA |
529 | static umode_t wdt_is_visible(struct kobject *kobj, struct attribute *attr, |
530 | int n) | |
531 | { | |
532 | struct device *dev = container_of(kobj, struct device, kobj); | |
533 | struct watchdog_device *wdd = dev_get_drvdata(dev); | |
534 | umode_t mode = attr->mode; | |
535 | ||
90b826f1 | 536 | if (attr == &dev_attr_timeleft.attr && !wdd->ops->get_timeleft) |
33b71126 | 537 | mode = 0; |
df044e02 WS |
538 | else if (attr == &dev_attr_pretimeout.attr && |
539 | !(wdd->info->options & WDIOF_PRETIMEOUT)) | |
540 | mode = 0; | |
89873a71 VZ |
541 | else if ((attr == &dev_attr_pretimeout_governor.attr || |
542 | attr == &dev_attr_pretimeout_available_governors.attr) && | |
ff84136c VZ |
543 | (!(wdd->info->options & WDIOF_PRETIMEOUT) || |
544 | !IS_ENABLED(CONFIG_WATCHDOG_PRETIMEOUT_GOV))) | |
545 | mode = 0; | |
33b71126 PA |
546 | |
547 | return mode; | |
548 | } | |
549 | static struct attribute *wdt_attrs[] = { | |
550 | &dev_attr_state.attr, | |
551 | &dev_attr_identity.attr, | |
552 | &dev_attr_timeout.attr, | |
df044e02 | 553 | &dev_attr_pretimeout.attr, |
33b71126 PA |
554 | &dev_attr_timeleft.attr, |
555 | &dev_attr_bootstatus.attr, | |
556 | &dev_attr_status.attr, | |
557 | &dev_attr_nowayout.attr, | |
ff84136c | 558 | &dev_attr_pretimeout_governor.attr, |
89873a71 | 559 | &dev_attr_pretimeout_available_governors.attr, |
33b71126 PA |
560 | NULL, |
561 | }; | |
562 | ||
563 | static const struct attribute_group wdt_group = { | |
564 | .attrs = wdt_attrs, | |
565 | .is_visible = wdt_is_visible, | |
566 | }; | |
567 | __ATTRIBUTE_GROUPS(wdt); | |
568 | #else | |
569 | #define wdt_groups NULL | |
570 | #endif | |
571 | ||
7a879824 HG |
572 | /* |
573 | * watchdog_ioctl_op: call the watchdog drivers ioctl op if defined | |
bc794ac3 | 574 | * @wdd: the watchdog device to do the ioctl on |
7a879824 HG |
575 | * @cmd: watchdog command |
576 | * @arg: argument pointer | |
b4ffb190 GR |
577 | * |
578 | * The caller must hold wd_data->lock. | |
7a879824 HG |
579 | */ |
580 | ||
bc794ac3 | 581 | static int watchdog_ioctl_op(struct watchdog_device *wdd, unsigned int cmd, |
7a879824 HG |
582 | unsigned long arg) |
583 | { | |
bc794ac3 | 584 | if (!wdd->ops->ioctl) |
7a879824 HG |
585 | return -ENOIOCTLCMD; |
586 | ||
b4ffb190 | 587 | return wdd->ops->ioctl(wdd, cmd, arg); |
43316044 WVS |
588 | } |
589 | ||
590 | /* | |
591 | * watchdog_write: writes to the watchdog. | |
592 | * @file: file from VFS | |
593 | * @data: user address of data | |
594 | * @len: length of data | |
595 | * @ppos: pointer to the file offset | |
596 | * | |
597 | * A write to a watchdog device is defined as a keepalive ping. | |
017cf080 | 598 | * Writing the magic 'V' sequence allows the next close to turn |
7e192b9c | 599 | * off the watchdog (if 'nowayout' is not set). |
43316044 WVS |
600 | */ |
601 | ||
602 | static ssize_t watchdog_write(struct file *file, const char __user *data, | |
603 | size_t len, loff_t *ppos) | |
604 | { | |
b4ffb190 GR |
605 | struct watchdog_core_data *wd_data = file->private_data; |
606 | struct watchdog_device *wdd; | |
607 | int err; | |
43316044 WVS |
608 | size_t i; |
609 | char c; | |
610 | ||
611 | if (len == 0) | |
612 | return 0; | |
613 | ||
017cf080 WVS |
614 | /* |
615 | * Note: just in case someone wrote the magic character | |
616 | * five months ago... | |
617 | */ | |
b4ffb190 | 618 | clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status); |
017cf080 WVS |
619 | |
620 | /* scan to see whether or not we got the magic character */ | |
43316044 WVS |
621 | for (i = 0; i != len; i++) { |
622 | if (get_user(c, data + i)) | |
623 | return -EFAULT; | |
017cf080 | 624 | if (c == 'V') |
b4ffb190 | 625 | set_bit(_WDOG_ALLOW_RELEASE, &wd_data->status); |
43316044 WVS |
626 | } |
627 | ||
628 | /* someone wrote to us, so we send the watchdog a keepalive ping */ | |
b4ffb190 GR |
629 | |
630 | err = -ENODEV; | |
631 | mutex_lock(&wd_data->lock); | |
632 | wdd = wd_data->wdd; | |
633 | if (wdd) | |
634 | err = watchdog_ping(wdd); | |
635 | mutex_unlock(&wd_data->lock); | |
636 | ||
5ef79663 AU |
637 | if (err < 0) |
638 | return err; | |
43316044 WVS |
639 | |
640 | return len; | |
641 | } | |
642 | ||
2fa03560 WVS |
643 | /* |
644 | * watchdog_ioctl: handle the different ioctl's for the watchdog device. | |
645 | * @file: file handle to the device | |
646 | * @cmd: watchdog command | |
647 | * @arg: argument pointer | |
648 | * | |
649 | * The watchdog API defines a common set of functions for all watchdogs | |
650 | * according to their available features. | |
651 | */ | |
652 | ||
653 | static long watchdog_ioctl(struct file *file, unsigned int cmd, | |
654 | unsigned long arg) | |
655 | { | |
b4ffb190 | 656 | struct watchdog_core_data *wd_data = file->private_data; |
2fa03560 | 657 | void __user *argp = (void __user *)arg; |
b4ffb190 | 658 | struct watchdog_device *wdd; |
2fa03560 WVS |
659 | int __user *p = argp; |
660 | unsigned int val; | |
234445b4 | 661 | int err; |
2fa03560 | 662 | |
b4ffb190 GR |
663 | mutex_lock(&wd_data->lock); |
664 | ||
665 | wdd = wd_data->wdd; | |
666 | if (!wdd) { | |
667 | err = -ENODEV; | |
668 | goto out_ioctl; | |
669 | } | |
670 | ||
7a879824 HG |
671 | err = watchdog_ioctl_op(wdd, cmd, arg); |
672 | if (err != -ENOIOCTLCMD) | |
b4ffb190 | 673 | goto out_ioctl; |
78d88fc0 | 674 | |
2fa03560 WVS |
675 | switch (cmd) { |
676 | case WDIOC_GETSUPPORT: | |
b4ffb190 | 677 | err = copy_to_user(argp, wdd->info, |
2fa03560 | 678 | sizeof(struct watchdog_info)) ? -EFAULT : 0; |
b4ffb190 | 679 | break; |
2fa03560 | 680 | case WDIOC_GETSTATUS: |
b4ffb190 GR |
681 | val = watchdog_get_status(wdd); |
682 | err = put_user(val, p); | |
683 | break; | |
2fa03560 | 684 | case WDIOC_GETBOOTSTATUS: |
b4ffb190 GR |
685 | err = put_user(wdd->bootstatus, p); |
686 | break; | |
234445b4 | 687 | case WDIOC_SETOPTIONS: |
b4ffb190 GR |
688 | if (get_user(val, p)) { |
689 | err = -EFAULT; | |
690 | break; | |
691 | } | |
234445b4 WVS |
692 | if (val & WDIOS_DISABLECARD) { |
693 | err = watchdog_stop(wdd); | |
694 | if (err < 0) | |
b4ffb190 | 695 | break; |
234445b4 | 696 | } |
b4ffb190 | 697 | if (val & WDIOS_ENABLECARD) |
234445b4 | 698 | err = watchdog_start(wdd); |
b4ffb190 | 699 | break; |
c2dc00e4 | 700 | case WDIOC_KEEPALIVE: |
b4ffb190 GR |
701 | if (!(wdd->info->options & WDIOF_KEEPALIVEPING)) { |
702 | err = -EOPNOTSUPP; | |
703 | break; | |
704 | } | |
705 | err = watchdog_ping(wdd); | |
706 | break; | |
014d694e | 707 | case WDIOC_SETTIMEOUT: |
b4ffb190 GR |
708 | if (get_user(val, p)) { |
709 | err = -EFAULT; | |
710 | break; | |
711 | } | |
7a879824 | 712 | err = watchdog_set_timeout(wdd, val); |
014d694e | 713 | if (err < 0) |
b4ffb190 | 714 | break; |
014d694e WVS |
715 | /* If the watchdog is active then we send a keepalive ping |
716 | * to make sure that the watchdog keep's running (and if | |
717 | * possible that it takes the new timeout) */ | |
5ef79663 AU |
718 | err = watchdog_ping(wdd); |
719 | if (err < 0) | |
b4ffb190 | 720 | break; |
014d694e WVS |
721 | /* Fall */ |
722 | case WDIOC_GETTIMEOUT: | |
723 | /* timeout == 0 means that we don't know the timeout */ | |
b4ffb190 GR |
724 | if (wdd->timeout == 0) { |
725 | err = -EOPNOTSUPP; | |
726 | break; | |
727 | } | |
728 | err = put_user(wdd->timeout, p); | |
729 | break; | |
fd7b673c | 730 | case WDIOC_GETTIMELEFT: |
7a879824 | 731 | err = watchdog_get_timeleft(wdd, &val); |
b4ffb190 GR |
732 | if (err < 0) |
733 | break; | |
734 | err = put_user(val, p); | |
735 | break; | |
df044e02 WS |
736 | case WDIOC_SETPRETIMEOUT: |
737 | if (get_user(val, p)) { | |
738 | err = -EFAULT; | |
739 | break; | |
740 | } | |
741 | err = watchdog_set_pretimeout(wdd, val); | |
742 | break; | |
743 | case WDIOC_GETPRETIMEOUT: | |
744 | err = put_user(wdd->pretimeout, p); | |
745 | break; | |
2fa03560 | 746 | default: |
b4ffb190 GR |
747 | err = -ENOTTY; |
748 | break; | |
2fa03560 | 749 | } |
b4ffb190 GR |
750 | |
751 | out_ioctl: | |
752 | mutex_unlock(&wd_data->lock); | |
753 | return err; | |
2fa03560 WVS |
754 | } |
755 | ||
43316044 | 756 | /* |
45f5fed3 | 757 | * watchdog_open: open the /dev/watchdog* devices. |
43316044 WVS |
758 | * @inode: inode of device |
759 | * @file: file handle to device | |
760 | * | |
45f5fed3 | 761 | * When the /dev/watchdog* device gets opened, we start the watchdog. |
43316044 WVS |
762 | * Watch out: the /dev/watchdog device is single open, so we make sure |
763 | * it can only be opened once. | |
764 | */ | |
765 | ||
766 | static int watchdog_open(struct inode *inode, struct file *file) | |
767 | { | |
b4ffb190 | 768 | struct watchdog_core_data *wd_data; |
45f5fed3 | 769 | struct watchdog_device *wdd; |
8de0b2b8 | 770 | bool hw_running; |
b4ffb190 | 771 | int err; |
45f5fed3 AC |
772 | |
773 | /* Get the corresponding watchdog device */ | |
774 | if (imajor(inode) == MISC_MAJOR) | |
b4ffb190 | 775 | wd_data = old_wd_data; |
45f5fed3 | 776 | else |
b4ffb190 GR |
777 | wd_data = container_of(inode->i_cdev, struct watchdog_core_data, |
778 | cdev); | |
43316044 WVS |
779 | |
780 | /* the watchdog is single open! */ | |
b4ffb190 | 781 | if (test_and_set_bit(_WDOG_DEV_OPEN, &wd_data->status)) |
43316044 WVS |
782 | return -EBUSY; |
783 | ||
b4ffb190 GR |
784 | wdd = wd_data->wdd; |
785 | ||
43316044 WVS |
786 | /* |
787 | * If the /dev/watchdog device is open, we don't want the module | |
788 | * to be unloaded. | |
789 | */ | |
8de0b2b8 GR |
790 | hw_running = watchdog_hw_running(wdd); |
791 | if (!hw_running && !try_module_get(wdd->ops->owner)) { | |
b4ffb190 GR |
792 | err = -EBUSY; |
793 | goto out_clear; | |
794 | } | |
43316044 | 795 | |
234445b4 | 796 | err = watchdog_start(wdd); |
43316044 WVS |
797 | if (err < 0) |
798 | goto out_mod; | |
799 | ||
b4ffb190 | 800 | file->private_data = wd_data; |
45f5fed3 | 801 | |
8de0b2b8 | 802 | if (!hw_running) |
44499b4f | 803 | get_device(&wd_data->dev); |
e907df32 | 804 | |
43316044 WVS |
805 | /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ |
806 | return nonseekable_open(inode, file); | |
807 | ||
808 | out_mod: | |
b4ffb190 GR |
809 | module_put(wd_data->wdd->ops->owner); |
810 | out_clear: | |
811 | clear_bit(_WDOG_DEV_OPEN, &wd_data->status); | |
43316044 WVS |
812 | return err; |
813 | } | |
814 | ||
44499b4f | 815 | static void watchdog_core_data_release(struct device *dev) |
b4ffb190 GR |
816 | { |
817 | struct watchdog_core_data *wd_data; | |
818 | ||
44499b4f | 819 | wd_data = container_of(dev, struct watchdog_core_data, dev); |
b4ffb190 GR |
820 | |
821 | kfree(wd_data); | |
822 | } | |
823 | ||
43316044 | 824 | /* |
45f5fed3 AC |
825 | * watchdog_release: release the watchdog device. |
826 | * @inode: inode of device | |
827 | * @file: file handle to device | |
43316044 | 828 | * |
017cf080 | 829 | * This is the code for when /dev/watchdog gets closed. We will only |
7e192b9c WVS |
830 | * stop the watchdog when we have received the magic char (and nowayout |
831 | * was not set), else the watchdog will keep running. | |
43316044 WVS |
832 | */ |
833 | ||
834 | static int watchdog_release(struct inode *inode, struct file *file) | |
835 | { | |
b4ffb190 GR |
836 | struct watchdog_core_data *wd_data = file->private_data; |
837 | struct watchdog_device *wdd; | |
017cf080 | 838 | int err = -EBUSY; |
d1ed3ba4 | 839 | bool running; |
017cf080 | 840 | |
b4ffb190 GR |
841 | mutex_lock(&wd_data->lock); |
842 | ||
843 | wdd = wd_data->wdd; | |
844 | if (!wdd) | |
845 | goto done; | |
846 | ||
017cf080 WVS |
847 | /* |
848 | * We only stop the watchdog if we received the magic character | |
7e192b9c WVS |
849 | * or if WDIOF_MAGICCLOSE is not set. If nowayout was set then |
850 | * watchdog_stop will fail. | |
017cf080 | 851 | */ |
fcf95670 HP |
852 | if (!test_bit(WDOG_ACTIVE, &wdd->status)) |
853 | err = 0; | |
b4ffb190 | 854 | else if (test_and_clear_bit(_WDOG_ALLOW_RELEASE, &wd_data->status) || |
fcf95670 | 855 | !(wdd->info->options & WDIOF_MAGICCLOSE)) |
017cf080 | 856 | err = watchdog_stop(wdd); |
43316044 | 857 | |
017cf080 | 858 | /* If the watchdog was not stopped, send a keepalive ping */ |
234445b4 | 859 | if (err < 0) { |
0254e953 | 860 | pr_crit("watchdog%d: watchdog did not stop!\n", wdd->id); |
43316044 WVS |
861 | watchdog_ping(wdd); |
862 | } | |
863 | ||
ee142889 | 864 | watchdog_update_worker(wdd); |
664a3923 | 865 | |
43316044 | 866 | /* make sure that /dev/watchdog can be re-opened */ |
b4ffb190 | 867 | clear_bit(_WDOG_DEV_OPEN, &wd_data->status); |
e907df32 | 868 | |
b4ffb190 | 869 | done: |
d1ed3ba4 | 870 | running = wdd && watchdog_hw_running(wdd); |
b4ffb190 | 871 | mutex_unlock(&wd_data->lock); |
ee142889 GR |
872 | /* |
873 | * Allow the owner module to be unloaded again unless the watchdog | |
874 | * is still running. If the watchdog is still running, it can not | |
875 | * be stopped, and its driver must not be unloaded. | |
876 | */ | |
d1ed3ba4 GR |
877 | if (!running) { |
878 | module_put(wd_data->cdev.owner); | |
44499b4f | 879 | put_device(&wd_data->dev); |
ee142889 | 880 | } |
43316044 WVS |
881 | return 0; |
882 | } | |
883 | ||
884 | static const struct file_operations watchdog_fops = { | |
885 | .owner = THIS_MODULE, | |
886 | .write = watchdog_write, | |
2fa03560 | 887 | .unlocked_ioctl = watchdog_ioctl, |
43316044 WVS |
888 | .open = watchdog_open, |
889 | .release = watchdog_release, | |
890 | }; | |
891 | ||
892 | static struct miscdevice watchdog_miscdev = { | |
893 | .minor = WATCHDOG_MINOR, | |
894 | .name = "watchdog", | |
895 | .fops = &watchdog_fops, | |
896 | }; | |
897 | ||
44499b4f KH |
898 | static struct class watchdog_class = { |
899 | .name = "watchdog", | |
900 | .owner = THIS_MODULE, | |
901 | .dev_groups = wdt_groups, | |
902 | }; | |
903 | ||
43316044 | 904 | /* |
32ecc639 | 905 | * watchdog_cdev_register: register watchdog character device |
bc794ac3 | 906 | * @wdd: watchdog device |
43316044 | 907 | * |
32ecc639 | 908 | * Register a watchdog character device including handling the legacy |
45f5fed3 AC |
909 | * /dev/watchdog node. /dev/watchdog is actually a miscdevice and |
910 | * thus we set it up like that. | |
43316044 WVS |
911 | */ |
912 | ||
44499b4f | 913 | static int watchdog_cdev_register(struct watchdog_device *wdd) |
43316044 | 914 | { |
b4ffb190 | 915 | struct watchdog_core_data *wd_data; |
32ecc639 | 916 | int err; |
45f5fed3 | 917 | |
b4ffb190 GR |
918 | wd_data = kzalloc(sizeof(struct watchdog_core_data), GFP_KERNEL); |
919 | if (!wd_data) | |
920 | return -ENOMEM; | |
b4ffb190 GR |
921 | mutex_init(&wd_data->lock); |
922 | ||
923 | wd_data->wdd = wdd; | |
924 | wdd->wd_data = wd_data; | |
925 | ||
664a3923 GR |
926 | if (!watchdog_wq) |
927 | return -ENODEV; | |
928 | ||
929 | INIT_DELAYED_WORK(&wd_data->work, watchdog_ping_work); | |
930 | ||
bc794ac3 | 931 | if (wdd->id == 0) { |
b4ffb190 | 932 | old_wd_data = wd_data; |
bc794ac3 | 933 | watchdog_miscdev.parent = wdd->parent; |
45f5fed3 AC |
934 | err = misc_register(&watchdog_miscdev); |
935 | if (err != 0) { | |
936 | pr_err("%s: cannot register miscdev on minor=%d (err=%d).\n", | |
bc794ac3 | 937 | wdd->info->identity, WATCHDOG_MINOR, err); |
45f5fed3 AC |
938 | if (err == -EBUSY) |
939 | pr_err("%s: a legacy watchdog module is probably present.\n", | |
bc794ac3 | 940 | wdd->info->identity); |
b4ffb190 GR |
941 | old_wd_data = NULL; |
942 | kfree(wd_data); | |
45f5fed3 AC |
943 | return err; |
944 | } | |
43316044 WVS |
945 | } |
946 | ||
44499b4f KH |
947 | device_initialize(&wd_data->dev); |
948 | wd_data->dev.devt = MKDEV(MAJOR(watchdog_devt), wdd->id); | |
949 | wd_data->dev.class = &watchdog_class; | |
950 | wd_data->dev.parent = wdd->parent; | |
951 | wd_data->dev.groups = wdd->groups; | |
952 | wd_data->dev.release = watchdog_core_data_release; | |
953 | dev_set_drvdata(&wd_data->dev, wdd); | |
954 | dev_set_name(&wd_data->dev, "watchdog%d", wdd->id); | |
955 | ||
45f5fed3 | 956 | /* Fill in the data structures */ |
b4ffb190 | 957 | cdev_init(&wd_data->cdev, &watchdog_fops); |
45f5fed3 AC |
958 | |
959 | /* Add the device */ | |
44499b4f | 960 | err = cdev_device_add(&wd_data->cdev, &wd_data->dev); |
45f5fed3 AC |
961 | if (err) { |
962 | pr_err("watchdog%d unable to add device %d:%d\n", | |
bc794ac3 GR |
963 | wdd->id, MAJOR(watchdog_devt), wdd->id); |
964 | if (wdd->id == 0) { | |
45f5fed3 | 965 | misc_deregister(&watchdog_miscdev); |
b4ffb190 | 966 | old_wd_data = NULL; |
44499b4f | 967 | put_device(&wd_data->dev); |
45f5fed3 | 968 | } |
ee142889 | 969 | return err; |
43316044 | 970 | } |
ee142889 | 971 | |
44499b4f KH |
972 | wd_data->cdev.owner = wdd->ops->owner; |
973 | ||
15013ad8 GR |
974 | /* Record time of most recent heartbeat as 'just before now'. */ |
975 | wd_data->last_hw_keepalive = jiffies - 1; | |
976 | ||
ee142889 GR |
977 | /* |
978 | * If the watchdog is running, prevent its driver from being unloaded, | |
979 | * and schedule an immediate ping. | |
980 | */ | |
981 | if (watchdog_hw_running(wdd)) { | |
d4c69aa4 | 982 | __module_get(wdd->ops->owner); |
44499b4f | 983 | get_device(&wd_data->dev); |
d4c69aa4 | 984 | if (handle_boot_enabled) |
2501b015 | 985 | queue_delayed_work(watchdog_wq, &wd_data->work, 0); |
d4c69aa4 | 986 | else |
2501b015 | 987 | pr_info("watchdog%d running and kernel based pre-userspace handler disabled\n", |
d4c69aa4 | 988 | wdd->id); |
ee142889 GR |
989 | } |
990 | ||
991 | return 0; | |
43316044 WVS |
992 | } |
993 | ||
994 | /* | |
32ecc639 | 995 | * watchdog_cdev_unregister: unregister watchdog character device |
43316044 WVS |
996 | * @watchdog: watchdog device |
997 | * | |
32ecc639 GR |
998 | * Unregister watchdog character device and if needed the legacy |
999 | * /dev/watchdog device. | |
43316044 WVS |
1000 | */ |
1001 | ||
32ecc639 | 1002 | static void watchdog_cdev_unregister(struct watchdog_device *wdd) |
43316044 | 1003 | { |
b4ffb190 | 1004 | struct watchdog_core_data *wd_data = wdd->wd_data; |
e907df32 | 1005 | |
44499b4f | 1006 | cdev_device_del(&wd_data->cdev, &wd_data->dev); |
bc794ac3 | 1007 | if (wdd->id == 0) { |
45f5fed3 | 1008 | misc_deregister(&watchdog_miscdev); |
b4ffb190 | 1009 | old_wd_data = NULL; |
43316044 | 1010 | } |
b4ffb190 | 1011 | |
bb292ac1 GR |
1012 | if (watchdog_active(wdd) && |
1013 | test_bit(WDOG_STOP_ON_UNREGISTER, &wdd->status)) { | |
1014 | watchdog_stop(wdd); | |
1015 | } | |
1016 | ||
58c36101 WS |
1017 | mutex_lock(&wd_data->lock); |
1018 | wd_data->wdd = NULL; | |
1019 | wdd->wd_data = NULL; | |
1020 | mutex_unlock(&wd_data->lock); | |
1021 | ||
664a3923 GR |
1022 | cancel_delayed_work_sync(&wd_data->work); |
1023 | ||
44499b4f | 1024 | put_device(&wd_data->dev); |
43316044 | 1025 | } |
45f5fed3 | 1026 | |
32ecc639 GR |
1027 | /* |
1028 | * watchdog_dev_register: register a watchdog device | |
1029 | * @wdd: watchdog device | |
1030 | * | |
1031 | * Register a watchdog device including handling the legacy | |
1032 | * /dev/watchdog node. /dev/watchdog is actually a miscdevice and | |
1033 | * thus we set it up like that. | |
1034 | */ | |
1035 | ||
1036 | int watchdog_dev_register(struct watchdog_device *wdd) | |
1037 | { | |
32ecc639 GR |
1038 | int ret; |
1039 | ||
44499b4f | 1040 | ret = watchdog_cdev_register(wdd); |
32ecc639 GR |
1041 | if (ret) |
1042 | return ret; | |
1043 | ||
ff84136c | 1044 | ret = watchdog_register_pretimeout(wdd); |
a91deea5 | 1045 | if (ret) |
ff84136c | 1046 | watchdog_cdev_unregister(wdd); |
ff84136c | 1047 | |
32ecc639 GR |
1048 | return ret; |
1049 | } | |
1050 | ||
1051 | /* | |
1052 | * watchdog_dev_unregister: unregister a watchdog device | |
1053 | * @watchdog: watchdog device | |
1054 | * | |
1055 | * Unregister watchdog device and if needed the legacy | |
1056 | * /dev/watchdog device. | |
1057 | */ | |
1058 | ||
1059 | void watchdog_dev_unregister(struct watchdog_device *wdd) | |
1060 | { | |
ff84136c | 1061 | watchdog_unregister_pretimeout(wdd); |
b4ffb190 | 1062 | watchdog_cdev_unregister(wdd); |
32ecc639 GR |
1063 | } |
1064 | ||
45f5fed3 AC |
1065 | /* |
1066 | * watchdog_dev_init: init dev part of watchdog core | |
1067 | * | |
1068 | * Allocate a range of chardev nodes to use for watchdog devices | |
1069 | */ | |
1070 | ||
32ecc639 | 1071 | int __init watchdog_dev_init(void) |
45f5fed3 | 1072 | { |
906d7a5c PA |
1073 | int err; |
1074 | ||
664a3923 GR |
1075 | watchdog_wq = alloc_workqueue("watchdogd", |
1076 | WQ_HIGHPRI | WQ_MEM_RECLAIM, 0); | |
1077 | if (!watchdog_wq) { | |
1078 | pr_err("Failed to create watchdog workqueue\n"); | |
1079 | return -ENOMEM; | |
1080 | } | |
1081 | ||
906d7a5c PA |
1082 | err = class_register(&watchdog_class); |
1083 | if (err < 0) { | |
1084 | pr_err("couldn't register class\n"); | |
138913cb | 1085 | goto err_register; |
906d7a5c PA |
1086 | } |
1087 | ||
1088 | err = alloc_chrdev_region(&watchdog_devt, 0, MAX_DOGS, "watchdog"); | |
1089 | if (err < 0) { | |
45f5fed3 | 1090 | pr_err("watchdog: unable to allocate char dev region\n"); |
138913cb | 1091 | goto err_alloc; |
906d7a5c PA |
1092 | } |
1093 | ||
32ecc639 | 1094 | return 0; |
138913cb WY |
1095 | |
1096 | err_alloc: | |
1097 | class_unregister(&watchdog_class); | |
1098 | err_register: | |
1099 | destroy_workqueue(watchdog_wq); | |
1100 | return err; | |
45f5fed3 AC |
1101 | } |
1102 | ||
1103 | /* | |
1104 | * watchdog_dev_exit: exit dev part of watchdog core | |
1105 | * | |
1106 | * Release the range of chardev nodes used for watchdog devices | |
1107 | */ | |
1108 | ||
1109 | void __exit watchdog_dev_exit(void) | |
1110 | { | |
1111 | unregister_chrdev_region(watchdog_devt, MAX_DOGS); | |
906d7a5c | 1112 | class_unregister(&watchdog_class); |
664a3923 | 1113 | destroy_workqueue(watchdog_wq); |
45f5fed3 | 1114 | } |
2501b015 SR |
1115 | |
1116 | module_param(handle_boot_enabled, bool, 0444); | |
1117 | MODULE_PARM_DESC(handle_boot_enabled, | |
1118 | "Watchdog core auto-updates boot enabled watchdogs before userspace takes over (default=" | |
1119 | __MODULE_STRING(IS_ENABLED(CONFIG_WATCHDOG_HANDLE_BOOT_ENABLED)) ")"); |