xref: /freebsd/sys/dev/watchdog/watchdog.c (revision 5686c6c38a3e1cc78804eaf5f880bda23dcf592f)
1 /*-
2  * Copyright (c) 2004 Poul-Henning Kamp
3  * Copyright (c) 2013 iXsystems.com,
4  *               author: Alfred Perlstein <alfred@freebsd.org>
5  *
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer
13  *    in this position and unchanged.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  */
30 
31 #include <sys/cdefs.h>
32 __FBSDID("$FreeBSD$");
33 
34 #include <sys/param.h>
35 #include <sys/types.h>
36 #include <sys/systm.h>
37 #include <sys/conf.h>
38 #include <sys/uio.h>
39 #include <sys/kernel.h>
40 #include <sys/malloc.h>
41 #include <sys/module.h>
42 #include <sys/syslog.h>
43 #include <sys/watchdog.h>
44 #include <sys/bus.h>
45 #include <machine/bus.h>
46 
47 #include <sys/syscallsubr.h> /* kern_clock_gettime() */
48 
49 static int wd_set_pretimeout(int newtimeout, int disableiftoolong);
50 static void wd_timeout_cb(void *arg);
51 
52 static struct callout wd_pretimeo_handle;
53 static int wd_pretimeout;
54 static int wd_pretimeout_act = WD_SOFT_LOG;
55 
56 static struct callout wd_softtimeo_handle;
57 static int wd_softtimer;	/* true = use softtimer instead of hardware
58 				   watchdog */
59 static int wd_softtimeout_act = WD_SOFT_LOG;	/* action for the software timeout */
60 
61 static struct cdev *wd_dev;
62 static volatile u_int wd_last_u;    /* last timeout value set by kern_do_pat */
63 
64 static int wd_lastpat_valid = 0;
65 static time_t wd_lastpat = 0;	/* when the watchdog was last patted */
66 
67 int
68 wdog_kern_pat(u_int utim)
69 {
70 	int error;
71 
72 	if ((utim & WD_LASTVAL) != 0 && (utim & WD_INTERVAL) > 0)
73 		return (EINVAL);
74 
75 	if ((utim & WD_LASTVAL) != 0) {
76 		/*
77 		 * if WD_LASTVAL is set, fill in the bits for timeout
78 		 * from the saved value in wd_last_u.
79 		 */
80 		MPASS((wd_last_u & ~WD_INTERVAL) == 0);
81 		utim &= ~WD_LASTVAL;
82 		utim |= wd_last_u;
83 	} else {
84 		/*
85 		 * Otherwise save the new interval.
86 		 * This can be zero (to disable the watchdog)
87 		 */
88 		wd_last_u = (utim & WD_INTERVAL);
89 	}
90 	if ((utim & WD_INTERVAL) == WD_TO_NEVER) {
91 		utim = 0;
92 
93 		/* Assume all is well; watchdog signals failure. */
94 		error = 0;
95 	} else {
96 		/* Assume no watchdog available; watchdog flags success */
97 		error = EOPNOTSUPP;
98 	}
99 	if (wd_softtimer) {
100 		if (utim == 0) {
101 			callout_stop(&wd_softtimeo_handle);
102 		} else {
103 			(void) callout_reset(&wd_softtimeo_handle,
104 			    hz*utim, wd_timeout_cb, "soft");
105 		}
106 		error = 0;
107 	} else {
108 		EVENTHANDLER_INVOKE(watchdog_list, utim, &error);
109 	}
110 	wd_set_pretimeout(wd_pretimeout, true);
111 	/*
112 	 * If we were able to arm/strobe the watchdog, then
113 	 * update the last time it was strobed for WDIOC_GETTIMELEFT
114 	 */
115 	if (!error) {
116 		struct timespec ts;
117 
118 		error = kern_clock_gettime(curthread /* XXX */,
119 		    CLOCK_MONOTONIC_FAST, &ts);
120 		if (!error) {
121 			wd_lastpat = ts.tv_sec;
122 			wd_lastpat_valid = 1;
123 		}
124 	}
125 	return (error);
126 }
127 
128 static int
129 wd_valid_act(int act)
130 {
131 
132 	if ((act & ~(WD_SOFT_MASK)) != 0)
133 		return false;
134 	return true;
135 }
136 
137 static int
138 wd_ioctl_patpat(caddr_t data)
139 {
140 	u_int u;
141 
142 	u = *(u_int *)data;
143 	if (u & ~(WD_ACTIVE | WD_PASSIVE | WD_LASTVAL | WD_INTERVAL))
144 		return (EINVAL);
145 	if ((u & (WD_ACTIVE | WD_PASSIVE)) == (WD_ACTIVE | WD_PASSIVE))
146 		return (EINVAL);
147 	if ((u & (WD_ACTIVE | WD_PASSIVE)) == 0 && ((u & WD_INTERVAL) > 0 ||
148 	    (u & WD_LASTVAL) != 0))
149 		return (EINVAL);
150 	if (u & WD_PASSIVE)
151 		return (ENOSYS);	/* XXX Not implemented yet */
152 	u &= ~(WD_ACTIVE | WD_PASSIVE);
153 
154 	return (wdog_kern_pat(u));
155 }
156 
157 static int
158 wd_get_time_left(struct thread *td, time_t *remainp)
159 {
160 	struct timespec ts;
161 	int error;
162 
163 	error = kern_clock_gettime(td, CLOCK_MONOTONIC_FAST, &ts);
164 	if (error)
165 		return (error);
166 	if (!wd_lastpat_valid)
167 		return (ENOENT);
168 	*remainp = ts.tv_sec - wd_lastpat;
169 	return (0);
170 }
171 
172 static void
173 wd_timeout_cb(void *arg)
174 {
175 	const char *type = arg;
176 
177 #ifdef DDB
178 	if ((wd_pretimeout_act & WD_SOFT_DDB)) {
179 		char kdb_why[80];
180 		snprintf(kdb_why, sizeof(buf), "watchdog %s timeout", type);
181 		kdb_backtrace();
182 		kdb_enter(KDB_WHY_WATCHDOG, kdb_why);
183 	}
184 #endif
185 	if ((wd_pretimeout_act & WD_SOFT_LOG))
186 		log(LOG_EMERG, "watchdog %s-timeout, WD_SOFT_LOG", type);
187 	if ((wd_pretimeout_act & WD_SOFT_PRINTF))
188 		printf("watchdog %s-timeout, WD_SOFT_PRINTF\n", type);
189 	if ((wd_pretimeout_act & WD_SOFT_PANIC))
190 		panic("watchdog %s-timeout, WD_SOFT_PANIC set", type);
191 }
192 
193 /*
194  * Called to manage timeouts.
195  * newtimeout needs to be in the range of 0 to actual watchdog timeout.
196  * if 0, we disable the pre-timeout.
197  * otherwise we set the pre-timeout provided it's not greater than the
198  * current actual watchdog timeout.
199  */
200 static int
201 wd_set_pretimeout(int newtimeout, int disableiftoolong)
202 {
203 	u_int utime;
204 
205 	utime = wdog_kern_last_timeout();
206 	/* do not permit a pre-timeout >= than the timeout. */
207 	if (newtimeout >= utime) {
208 		/*
209 		 * If 'disableiftoolong' then just fall through
210 		 * so as to disable the pre-watchdog
211 		 */
212 		if (disableiftoolong)
213 			newtimeout = 0;
214 		else
215 			return EINVAL;
216 	}
217 
218 	/* disable the pre-timeout */
219 	if (newtimeout == 0) {
220 		wd_pretimeout = 0;
221 		callout_stop(&wd_pretimeo_handle);
222 		return 0;
223 	}
224 
225 	/* We determined the value is sane, so reset the callout */
226 	(void) callout_reset(&wd_pretimeo_handle, hz*(utime - newtimeout),
227 	    wd_timeout_cb, "pre-timeout");
228 	wd_pretimeout = newtimeout;
229 	return 0;
230 }
231 
232 static int
233 wd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
234     int flags __unused, struct thread *td)
235 {
236 	u_int u;
237 	time_t timeleft;
238 	int error;
239 
240 	error = 0;
241 
242 	switch (cmd) {
243 	case WDIOC_SETSOFT:
244 		u = *(int *)data;
245 		/* do nothing? */
246 		if (u == wd_softtimer)
247 			break;
248 		/* If there is a pending timeout disallow this ioctl */
249 		if (wd_last_u != 0) {
250 			error = EINVAL;
251 			break;
252 		}
253 		wd_softtimer = u;
254 		break;
255 	case WDIOC_SETSOFTTIMEOUTACT:
256 		u = *(int *)data;
257 		if (wd_valid_act(u)) {
258 			wd_softtimeout_act = u;
259 		} else {
260 			error = EINVAL;
261 		}
262 		break;
263 	case WDIOC_SETPRETIMEOUTACT:
264 		u = *(int *)data;
265 		if (wd_valid_act(u)) {
266 			wd_pretimeout_act = u;
267 		} else {
268 			error = EINVAL;
269 		}
270 		break;
271 	case WDIOC_GETPRETIMEOUT:
272 		*(int *)data = (int)wd_pretimeout;
273 		break;
274 	case WDIOC_SETPRETIMEOUT:
275 		error = wd_set_pretimeout(*(int *)data, false);
276 		break;
277 	case WDIOC_GETTIMELEFT:
278 		error = wd_get_time_left(td, &timeleft);
279 		if (error)
280 			break;
281 		*(int *)data = (int)timeleft;
282 		break;
283 	case WDIOC_SETTIMEOUT:
284 		u = *(u_int *)data;
285 		error = wdog_kern_pat(u);
286 		break;
287 	case WDIOC_GETTIMEOUT:
288 		u = wdog_kern_last_timeout();
289 		*(u_int *)data = u;
290 		break;
291 	case WDIOCPATPAT:
292 		error = wd_ioctl_patpat(data);
293 		break;
294 	default:
295 		error = ENOIOCTL;
296 		break;
297 	}
298 	return (error);
299 }
300 
301 /*
302  * Return the last timeout set, this is NOT the seconds from NOW until timeout,
303  * rather it is the amount of seconds passed to WDIOCPATPAT/WDIOC_SETTIMEOUT.
304  */
305 u_int
306 wdog_kern_last_timeout(void)
307 {
308 
309 	return (wd_last_u);
310 }
311 
312 static struct cdevsw wd_cdevsw = {
313 	.d_version =	D_VERSION,
314 	.d_ioctl =	wd_ioctl,
315 	.d_name =	"watchdog",
316 };
317 
318 static int
319 watchdog_modevent(module_t mod __unused, int type, void *data __unused)
320 {
321 	switch(type) {
322 	case MOD_LOAD:
323 		callout_init(&wd_pretimeo_handle, true);
324 		callout_init(&wd_softtimeo_handle, true);
325 		wd_dev = make_dev(&wd_cdevsw, 0,
326 		    UID_ROOT, GID_WHEEL, 0600, _PATH_WATCHDOG);
327 		return 0;
328 	case MOD_UNLOAD:
329 		callout_stop(&wd_pretimeo_handle);
330 		callout_stop(&wd_softtimeo_handle);
331 		callout_drain(&wd_pretimeo_handle);
332 		callout_drain(&wd_softtimeo_handle);
333 		destroy_dev(wd_dev);
334 		return 0;
335 	case MOD_SHUTDOWN:
336 		return 0;
337 	default:
338 		return EOPNOTSUPP;
339 	}
340 }
341 
342 DEV_MODULE(watchdog, watchdog_modevent, NULL);
343