Add a missing include of an options header.
[freebsd.git] / sys / dev / watchdog / watchdog.c
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 "opt_ddb.h"
32
33 #include <sys/cdefs.h>
34 __FBSDID("$FreeBSD$");
35
36 #include <sys/param.h>
37 #include <sys/types.h>
38 #include <sys/systm.h>
39 #include <sys/conf.h>
40 #include <sys/uio.h>
41 #include <sys/kernel.h>
42 #include <sys/kdb.h>
43 #include <sys/malloc.h>
44 #include <sys/module.h>
45 #include <sys/sysctl.h>
46 #include <sys/syslog.h>
47 #include <sys/watchdog.h>
48 #include <sys/bus.h>
49 #include <machine/bus.h>
50
51 #include <sys/syscallsubr.h> /* kern_clock_gettime() */
52
53 static int wd_set_pretimeout(int newtimeout, int disableiftoolong);
54 static void wd_timeout_cb(void *arg);
55
56 static struct callout wd_pretimeo_handle;
57 static int wd_pretimeout;
58 static int wd_pretimeout_act = WD_SOFT_LOG;
59
60 static struct callout wd_softtimeo_handle;
61 static int wd_softtimer;        /* true = use softtimer instead of hardware
62                                    watchdog */
63 static int wd_softtimeout_act = WD_SOFT_LOG;    /* action for the software timeout */
64
65 static struct cdev *wd_dev;
66 static volatile u_int wd_last_u;    /* last timeout value set by kern_do_pat */
67 static u_int wd_last_u_sysctl;    /* last timeout value set by kern_do_pat */
68 static u_int wd_last_u_sysctl_secs;    /* wd_last_u in seconds */
69
70 SYSCTL_NODE(_hw, OID_AUTO, watchdog, CTLFLAG_RD, 0, "Main watchdog device");
71 SYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u, CTLFLAG_RD,
72     &wd_last_u_sysctl, 0, "Watchdog last update time");
73 SYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u_secs, CTLFLAG_RD,
74     &wd_last_u_sysctl_secs, 0, "Watchdog last update time");
75
76 static int wd_lastpat_valid = 0;
77 static time_t wd_lastpat = 0;   /* when the watchdog was last patted */
78
79 static void
80 pow2ns_to_ts(int pow2ns, struct timespec *ts)
81 {
82         uint64_t ns;
83
84         ns = 1ULL << pow2ns;
85         ts->tv_sec = ns / 1000000000ULL;
86         ts->tv_nsec = ns % 1000000000ULL;
87 }
88
89 static int
90 pow2ns_to_ticks(int pow2ns)
91 {
92         struct timeval tv;
93         struct timespec ts;
94
95         pow2ns_to_ts(pow2ns, &ts);
96         TIMESPEC_TO_TIMEVAL(&tv, &ts);
97         return (tvtohz(&tv));
98 }
99
100 static int
101 seconds_to_pow2ns(int seconds)
102 {
103         uint64_t power;
104         uint64_t ns;
105         uint64_t shifted;
106
107         ns = ((uint64_t)seconds) * 1000000000ULL;
108         power = flsll(ns);
109         shifted = 1ULL << power;
110         if (shifted <= ns) {
111                 power++;
112         }
113         return (power);
114 }
115
116
117 int
118 wdog_kern_pat(u_int utim)
119 {
120         int error;
121
122         if ((utim & WD_LASTVAL) != 0 && (utim & WD_INTERVAL) > 0)
123                 return (EINVAL);
124
125         if ((utim & WD_LASTVAL) != 0) {
126                 /*
127                  * if WD_LASTVAL is set, fill in the bits for timeout
128                  * from the saved value in wd_last_u.
129                  */
130                 MPASS((wd_last_u & ~WD_INTERVAL) == 0);
131                 utim &= ~WD_LASTVAL;
132                 utim |= wd_last_u;
133         } else {
134                 /*
135                  * Otherwise save the new interval.
136                  * This can be zero (to disable the watchdog)
137                  */
138                 wd_last_u = (utim & WD_INTERVAL);
139                 wd_last_u_sysctl = wd_last_u;
140                 wd_last_u_sysctl_secs = pow2ns_to_ticks(wd_last_u) / hz;
141         }
142         if ((utim & WD_INTERVAL) == WD_TO_NEVER) {
143                 utim = 0;
144
145                 /* Assume all is well; watchdog signals failure. */
146                 error = 0;
147         } else {
148                 /* Assume no watchdog available; watchdog flags success */
149                 error = EOPNOTSUPP;
150         }
151         if (wd_softtimer) {
152                 if (utim == 0) {
153                         callout_stop(&wd_softtimeo_handle);
154                 } else {
155                         (void) callout_reset(&wd_softtimeo_handle,
156                             pow2ns_to_ticks(utim), wd_timeout_cb, "soft");
157                 }
158                 error = 0;
159         } else {
160                 EVENTHANDLER_INVOKE(watchdog_list, utim, &error);
161         }
162         wd_set_pretimeout(wd_pretimeout, true);
163         /*
164          * If we were able to arm/strobe the watchdog, then
165          * update the last time it was strobed for WDIOC_GETTIMELEFT
166          */
167         if (!error) {
168                 struct timespec ts;
169
170                 error = kern_clock_gettime(curthread /* XXX */,
171                     CLOCK_MONOTONIC_FAST, &ts);
172                 if (!error) {
173                         wd_lastpat = ts.tv_sec;
174                         wd_lastpat_valid = 1;
175                 }
176         }
177         return (error);
178 }
179
180 static int
181 wd_valid_act(int act)
182 {
183
184         if ((act & ~(WD_SOFT_MASK)) != 0)
185                 return false;
186         return true;
187 }
188
189 static int
190 wd_ioctl_patpat(caddr_t data)
191 {
192         u_int u;
193
194         u = *(u_int *)data;
195         if (u & ~(WD_ACTIVE | WD_PASSIVE | WD_LASTVAL | WD_INTERVAL))
196                 return (EINVAL);
197         if ((u & (WD_ACTIVE | WD_PASSIVE)) == (WD_ACTIVE | WD_PASSIVE))
198                 return (EINVAL);
199         if ((u & (WD_ACTIVE | WD_PASSIVE)) == 0 && ((u & WD_INTERVAL) > 0 ||
200             (u & WD_LASTVAL) != 0))
201                 return (EINVAL);
202         if (u & WD_PASSIVE)
203                 return (ENOSYS);        /* XXX Not implemented yet */
204         u &= ~(WD_ACTIVE | WD_PASSIVE);
205
206         return (wdog_kern_pat(u));
207 }
208
209 static int
210 wd_get_time_left(struct thread *td, time_t *remainp)
211 {
212         struct timespec ts;
213         int error;
214
215         error = kern_clock_gettime(td, CLOCK_MONOTONIC_FAST, &ts);
216         if (error)
217                 return (error);
218         if (!wd_lastpat_valid)
219                 return (ENOENT);
220         *remainp = ts.tv_sec - wd_lastpat;
221         return (0);
222 }
223
224 static void
225 wd_timeout_cb(void *arg)
226 {
227         const char *type = arg;
228
229 #ifdef DDB
230         if ((wd_pretimeout_act & WD_SOFT_DDB)) {
231                 char kdb_why[80];
232                 snprintf(kdb_why, sizeof(kdb_why), "watchdog %s timeout", type);
233                 kdb_backtrace();
234                 kdb_enter(KDB_WHY_WATCHDOG, kdb_why);
235         }
236 #endif
237         if ((wd_pretimeout_act & WD_SOFT_LOG))
238                 log(LOG_EMERG, "watchdog %s-timeout, WD_SOFT_LOG", type);
239         if ((wd_pretimeout_act & WD_SOFT_PRINTF))
240                 printf("watchdog %s-timeout, WD_SOFT_PRINTF\n", type);
241         if ((wd_pretimeout_act & WD_SOFT_PANIC))
242                 panic("watchdog %s-timeout, WD_SOFT_PANIC set", type);
243 }
244
245 /*
246  * Called to manage timeouts.
247  * newtimeout needs to be in the range of 0 to actual watchdog timeout.
248  * if 0, we disable the pre-timeout.
249  * otherwise we set the pre-timeout provided it's not greater than the
250  * current actual watchdog timeout.
251  */
252 static int
253 wd_set_pretimeout(int newtimeout, int disableiftoolong)
254 {
255         u_int utime;
256         struct timespec utime_ts;
257         int timeout_ticks;
258
259         utime = wdog_kern_last_timeout();
260         pow2ns_to_ts(utime, &utime_ts);
261         /* do not permit a pre-timeout >= than the timeout. */
262         if (newtimeout >= utime_ts.tv_sec) {
263                 /*
264                  * If 'disableiftoolong' then just fall through
265                  * so as to disable the pre-watchdog
266                  */
267                 if (disableiftoolong)
268                         newtimeout = 0;
269                 else
270                         return EINVAL;
271         }
272
273         /* disable the pre-timeout */
274         if (newtimeout == 0) {
275                 wd_pretimeout = 0;
276                 callout_stop(&wd_pretimeo_handle);
277                 return 0;
278         }
279
280         timeout_ticks = pow2ns_to_ticks(utime) - (hz*newtimeout);
281 #if 0
282         printf("wd_set_pretimeout: "
283             "newtimeout: %d, "
284             "utime: %d -> utime_ticks: %d, "
285             "hz*newtimeout: %d, "
286             "timeout_ticks: %d -> sec: %d\n",
287             newtimeout,
288             utime, pow2ns_to_ticks(utime),
289             hz*newtimeout,
290             timeout_ticks, timeout_ticks / hz);
291 #endif
292
293         /* We determined the value is sane, so reset the callout */
294         (void) callout_reset(&wd_pretimeo_handle,
295             timeout_ticks,
296             wd_timeout_cb, "pre-timeout");
297         wd_pretimeout = newtimeout;
298         return 0;
299 }
300
301 static int
302 wd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
303     int flags __unused, struct thread *td)
304 {
305         u_int u;
306         time_t timeleft;
307         int error;
308
309         error = 0;
310
311         switch (cmd) {
312         case WDIOC_SETSOFT:
313                 u = *(int *)data;
314                 /* do nothing? */
315                 if (u == wd_softtimer)
316                         break;
317                 /* If there is a pending timeout disallow this ioctl */
318                 if (wd_last_u != 0) {
319                         error = EINVAL;
320                         break;
321                 }
322                 wd_softtimer = u;
323                 break;
324         case WDIOC_SETSOFTTIMEOUTACT:
325                 u = *(int *)data;
326                 if (wd_valid_act(u)) {
327                         wd_softtimeout_act = u;
328                 } else {
329                         error = EINVAL;
330                 }
331                 break;
332         case WDIOC_SETPRETIMEOUTACT:
333                 u = *(int *)data;
334                 if (wd_valid_act(u)) {
335                         wd_pretimeout_act = u;
336                 } else {
337                         error = EINVAL;
338                 }
339                 break;
340         case WDIOC_GETPRETIMEOUT:
341                 *(int *)data = (int)wd_pretimeout;
342                 break;
343         case WDIOC_SETPRETIMEOUT:
344                 error = wd_set_pretimeout(*(int *)data, false);
345                 break;
346         case WDIOC_GETTIMELEFT:
347                 error = wd_get_time_left(td, &timeleft);
348                 if (error)
349                         break;
350                 *(int *)data = (int)timeleft;
351                 break;
352         case WDIOC_SETTIMEOUT:
353                 u = *(u_int *)data;
354                 error = wdog_kern_pat(seconds_to_pow2ns(u));
355                 break;
356         case WDIOC_GETTIMEOUT:
357                 u = wdog_kern_last_timeout();
358                 *(u_int *)data = u;
359                 break;
360         case WDIOCPATPAT:
361                 error = wd_ioctl_patpat(data);
362                 break;
363         default:
364                 error = ENOIOCTL;
365                 break;
366         }
367         return (error);
368 }
369
370 /*
371  * Return the last timeout set, this is NOT the seconds from NOW until timeout,
372  * rather it is the amount of seconds passed to WDIOCPATPAT/WDIOC_SETTIMEOUT.
373  */
374 u_int
375 wdog_kern_last_timeout(void)
376 {
377
378         return (wd_last_u);
379 }
380
381 static struct cdevsw wd_cdevsw = {
382         .d_version =    D_VERSION,
383         .d_ioctl =      wd_ioctl,
384         .d_name =       "watchdog",
385 };
386
387 static int
388 watchdog_modevent(module_t mod __unused, int type, void *data __unused)
389 {
390         switch(type) {
391         case MOD_LOAD:
392                 callout_init(&wd_pretimeo_handle, true);
393                 callout_init(&wd_softtimeo_handle, true);
394                 wd_dev = make_dev(&wd_cdevsw, 0,
395                     UID_ROOT, GID_WHEEL, 0600, _PATH_WATCHDOG);
396                 return 0;
397         case MOD_UNLOAD:
398                 callout_stop(&wd_pretimeo_handle);
399                 callout_stop(&wd_softtimeo_handle);
400                 callout_drain(&wd_pretimeo_handle);
401                 callout_drain(&wd_softtimeo_handle);
402                 destroy_dev(wd_dev);
403                 return 0;
404         case MOD_SHUTDOWN:
405                 return 0;
406         default:
407                 return EOPNOTSUPP;
408         }
409 }
410
411 DEV_MODULE(watchdog, watchdog_modevent, NULL);