1126370Sphk/*-
2126370Sphk * Copyright (c) 2004 Poul-Henning Kamp
3247405Salfred * Copyright (c) 2013 iXsystems.com,
4247405Salfred *               author: Alfred Perlstein <alfred@freebsd.org>
5247405Salfred *
6126370Sphk * All rights reserved.
7126370Sphk *
8126370Sphk * Redistribution and use in source and binary forms, with or without
9126370Sphk * modification, are permitted provided that the following conditions
10126370Sphk * are met:
11126370Sphk * 1. Redistributions of source code must retain the above copyright
12126370Sphk *    notice, this list of conditions and the following disclaimer
13126370Sphk *    in this position and unchanged.
14126370Sphk * 2. Redistributions in binary form must reproduce the above copyright
15126370Sphk *    notice, this list of conditions and the following disclaimer in the
16126370Sphk *    documentation and/or other materials provided with the distribution.
17126370Sphk *
18126370Sphk * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
19126370Sphk * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20126370Sphk * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21126370Sphk * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22126370Sphk * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23126370Sphk * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24126370Sphk * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25126370Sphk * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26126370Sphk * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27126370Sphk * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28126370Sphk *
29126370Sphk */
30126370Sphk
31279413Srstone#include "opt_ddb.h"
32279413Srstone
33126370Sphk#include <sys/cdefs.h>
34126370Sphk__FBSDID("$FreeBSD: releng/11.0/sys/dev/watchdog/watchdog.c 283291 2015-05-22 17:05:21Z jkim $");
35126370Sphk
36126370Sphk#include <sys/param.h>
37247405Salfred#include <sys/types.h>
38126370Sphk#include <sys/systm.h>
39126370Sphk#include <sys/conf.h>
40126370Sphk#include <sys/uio.h>
41126370Sphk#include <sys/kernel.h>
42279413Srstone#include <sys/kdb.h>
43126370Sphk#include <sys/malloc.h>
44126370Sphk#include <sys/module.h>
45253719Salfred#include <sys/sysctl.h>
46247405Salfred#include <sys/syslog.h>
47126370Sphk#include <sys/watchdog.h>
48126370Sphk#include <sys/bus.h>
49126370Sphk#include <machine/bus.h>
50126370Sphk
51247405Salfred#include <sys/syscallsubr.h> /* kern_clock_gettime() */
52247405Salfred
53247405Salfredstatic int wd_set_pretimeout(int newtimeout, int disableiftoolong);
54247405Salfredstatic void wd_timeout_cb(void *arg);
55247405Salfred
56247405Salfredstatic struct callout wd_pretimeo_handle;
57247405Salfredstatic int wd_pretimeout;
58247405Salfredstatic int wd_pretimeout_act = WD_SOFT_LOG;
59247405Salfred
60247405Salfredstatic struct callout wd_softtimeo_handle;
61247405Salfredstatic int wd_softtimer;	/* true = use softtimer instead of hardware
62247405Salfred				   watchdog */
63247405Salfredstatic int wd_softtimeout_act = WD_SOFT_LOG;	/* action for the software timeout */
64247405Salfred
65130585Sphkstatic struct cdev *wd_dev;
66247405Salfredstatic volatile u_int wd_last_u;    /* last timeout value set by kern_do_pat */
67253719Salfredstatic u_int wd_last_u_sysctl;    /* last timeout value set by kern_do_pat */
68253719Salfredstatic u_int wd_last_u_sysctl_secs;    /* wd_last_u in seconds */
69126370Sphk
70253719SalfredSYSCTL_NODE(_hw, OID_AUTO, watchdog, CTLFLAG_RD, 0, "Main watchdog device");
71253719SalfredSYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u, CTLFLAG_RD,
72253719Salfred    &wd_last_u_sysctl, 0, "Watchdog last update time");
73253719SalfredSYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u_secs, CTLFLAG_RD,
74253719Salfred    &wd_last_u_sysctl_secs, 0, "Watchdog last update time");
75253719Salfred
76247405Salfredstatic int wd_lastpat_valid = 0;
77247405Salfredstatic time_t wd_lastpat = 0;	/* when the watchdog was last patted */
78247405Salfred
79253719Salfredstatic void
80253719Salfredpow2ns_to_ts(int pow2ns, struct timespec *ts)
81253719Salfred{
82253719Salfred	uint64_t ns;
83253719Salfred
84253719Salfred	ns = 1ULL << pow2ns;
85253719Salfred	ts->tv_sec = ns / 1000000000ULL;
86253719Salfred	ts->tv_nsec = ns % 1000000000ULL;
87253719Salfred}
88253719Salfred
89253719Salfredstatic int
90253719Salfredpow2ns_to_ticks(int pow2ns)
91253719Salfred{
92253719Salfred	struct timeval tv;
93253719Salfred	struct timespec ts;
94253719Salfred
95253719Salfred	pow2ns_to_ts(pow2ns, &ts);
96253719Salfred	TIMESPEC_TO_TIMEVAL(&tv, &ts);
97253719Salfred	return (tvtohz(&tv));
98253719Salfred}
99253719Salfred
100253719Salfredstatic int
101253719Salfredseconds_to_pow2ns(int seconds)
102253719Salfred{
103253719Salfred	uint64_t power;
104253719Salfred	uint64_t ns;
105253719Salfred	uint64_t shifted;
106253719Salfred
107253719Salfred	ns = ((uint64_t)seconds) * 1000000000ULL;
108253719Salfred	power = flsll(ns);
109253719Salfred	shifted = 1ULL << power;
110253719Salfred	if (shifted <= ns) {
111253719Salfred		power++;
112253719Salfred	}
113253719Salfred	return (power);
114253719Salfred}
115253719Salfred
116253719Salfred
117247405Salfredint
118247405Salfredwdog_kern_pat(u_int utim)
119221121Sattilio{
120221121Sattilio	int error;
121221121Sattilio
122221121Sattilio	if ((utim & WD_LASTVAL) != 0 && (utim & WD_INTERVAL) > 0)
123221121Sattilio		return (EINVAL);
124221121Sattilio
125221121Sattilio	if ((utim & WD_LASTVAL) != 0) {
126247405Salfred		/*
127247405Salfred		 * if WD_LASTVAL is set, fill in the bits for timeout
128247405Salfred		 * from the saved value in wd_last_u.
129247405Salfred		 */
130221121Sattilio		MPASS((wd_last_u & ~WD_INTERVAL) == 0);
131221121Sattilio		utim &= ~WD_LASTVAL;
132221121Sattilio		utim |= wd_last_u;
133247405Salfred	} else {
134247405Salfred		/*
135247405Salfred		 * Otherwise save the new interval.
136247405Salfred		 * This can be zero (to disable the watchdog)
137247405Salfred		 */
138221121Sattilio		wd_last_u = (utim & WD_INTERVAL);
139253719Salfred		wd_last_u_sysctl = wd_last_u;
140253719Salfred		wd_last_u_sysctl_secs = pow2ns_to_ticks(wd_last_u) / hz;
141247405Salfred	}
142221121Sattilio	if ((utim & WD_INTERVAL) == WD_TO_NEVER) {
143221121Sattilio		utim = 0;
144221121Sattilio
145221121Sattilio		/* Assume all is well; watchdog signals failure. */
146221121Sattilio		error = 0;
147221121Sattilio	} else {
148221121Sattilio		/* Assume no watchdog available; watchdog flags success */
149221121Sattilio		error = EOPNOTSUPP;
150221121Sattilio	}
151247405Salfred	if (wd_softtimer) {
152247405Salfred		if (utim == 0) {
153247405Salfred			callout_stop(&wd_softtimeo_handle);
154247405Salfred		} else {
155247405Salfred			(void) callout_reset(&wd_softtimeo_handle,
156253719Salfred			    pow2ns_to_ticks(utim), wd_timeout_cb, "soft");
157247405Salfred		}
158247405Salfred		error = 0;
159247405Salfred	} else {
160247405Salfred		EVENTHANDLER_INVOKE(watchdog_list, utim, &error);
161247405Salfred	}
162247405Salfred	wd_set_pretimeout(wd_pretimeout, true);
163247405Salfred	/*
164247405Salfred	 * If we were able to arm/strobe the watchdog, then
165247405Salfred	 * update the last time it was strobed for WDIOC_GETTIMELEFT
166247405Salfred	 */
167247405Salfred	if (!error) {
168247405Salfred		struct timespec ts;
169247405Salfred
170247405Salfred		error = kern_clock_gettime(curthread /* XXX */,
171247405Salfred		    CLOCK_MONOTONIC_FAST, &ts);
172247405Salfred		if (!error) {
173247405Salfred			wd_lastpat = ts.tv_sec;
174247405Salfred			wd_lastpat_valid = 1;
175247405Salfred		}
176247405Salfred	}
177221121Sattilio	return (error);
178221121Sattilio}
179221121Sattilio
180221121Sattiliostatic int
181247405Salfredwd_valid_act(int act)
182126370Sphk{
183247405Salfred
184247405Salfred	if ((act & ~(WD_SOFT_MASK)) != 0)
185247405Salfred		return false;
186247405Salfred	return true;
187247405Salfred}
188247405Salfred
189247405Salfredstatic int
190247405Salfredwd_ioctl_patpat(caddr_t data)
191247405Salfred{
192126370Sphk	u_int u;
193126370Sphk
194126370Sphk	u = *(u_int *)data;
195221121Sattilio	if (u & ~(WD_ACTIVE | WD_PASSIVE | WD_LASTVAL | WD_INTERVAL))
196126370Sphk		return (EINVAL);
197126370Sphk	if ((u & (WD_ACTIVE | WD_PASSIVE)) == (WD_ACTIVE | WD_PASSIVE))
198126370Sphk		return (EINVAL);
199221121Sattilio	if ((u & (WD_ACTIVE | WD_PASSIVE)) == 0 && ((u & WD_INTERVAL) > 0 ||
200221121Sattilio	    (u & WD_LASTVAL) != 0))
201167950Sn_hibma		return (EINVAL);
202165260Sn_hibma	if (u & WD_PASSIVE)
203165260Sn_hibma		return (ENOSYS);	/* XXX Not implemented yet */
204221121Sattilio	u &= ~(WD_ACTIVE | WD_PASSIVE);
205221121Sattilio
206247405Salfred	return (wdog_kern_pat(u));
207126370Sphk}
208126370Sphk
209247405Salfredstatic int
210247405Salfredwd_get_time_left(struct thread *td, time_t *remainp)
211221121Sattilio{
212247405Salfred	struct timespec ts;
213247405Salfred	int error;
214221121Sattilio
215247405Salfred	error = kern_clock_gettime(td, CLOCK_MONOTONIC_FAST, &ts);
216247405Salfred	if (error)
217247405Salfred		return (error);
218247405Salfred	if (!wd_lastpat_valid)
219247405Salfred		return (ENOENT);
220247405Salfred	*remainp = ts.tv_sec - wd_lastpat;
221247405Salfred	return (0);
222221121Sattilio}
223221121Sattilio
224247405Salfredstatic void
225247405Salfredwd_timeout_cb(void *arg)
226221121Sattilio{
227247405Salfred	const char *type = arg;
228221121Sattilio
229247405Salfred#ifdef DDB
230247405Salfred	if ((wd_pretimeout_act & WD_SOFT_DDB)) {
231247405Salfred		char kdb_why[80];
232261495Sed		snprintf(kdb_why, sizeof(kdb_why), "watchdog %s timeout", type);
233247405Salfred		kdb_backtrace();
234247405Salfred		kdb_enter(KDB_WHY_WATCHDOG, kdb_why);
235247405Salfred	}
236247405Salfred#endif
237247405Salfred	if ((wd_pretimeout_act & WD_SOFT_LOG))
238247405Salfred		log(LOG_EMERG, "watchdog %s-timeout, WD_SOFT_LOG", type);
239247405Salfred	if ((wd_pretimeout_act & WD_SOFT_PRINTF))
240247405Salfred		printf("watchdog %s-timeout, WD_SOFT_PRINTF\n", type);
241247405Salfred	if ((wd_pretimeout_act & WD_SOFT_PANIC))
242247405Salfred		panic("watchdog %s-timeout, WD_SOFT_PANIC set", type);
243247405Salfred}
244221121Sattilio
245247405Salfred/*
246247405Salfred * Called to manage timeouts.
247247405Salfred * newtimeout needs to be in the range of 0 to actual watchdog timeout.
248247405Salfred * if 0, we disable the pre-timeout.
249247405Salfred * otherwise we set the pre-timeout provided it's not greater than the
250247405Salfred * current actual watchdog timeout.
251247405Salfred */
252247405Salfredstatic int
253247405Salfredwd_set_pretimeout(int newtimeout, int disableiftoolong)
254247405Salfred{
255247405Salfred	u_int utime;
256253719Salfred	struct timespec utime_ts;
257253719Salfred	int timeout_ticks;
258247405Salfred
259247405Salfred	utime = wdog_kern_last_timeout();
260253719Salfred	pow2ns_to_ts(utime, &utime_ts);
261247405Salfred	/* do not permit a pre-timeout >= than the timeout. */
262253719Salfred	if (newtimeout >= utime_ts.tv_sec) {
263247405Salfred		/*
264247405Salfred		 * If 'disableiftoolong' then just fall through
265247405Salfred		 * so as to disable the pre-watchdog
266247405Salfred		 */
267247405Salfred		if (disableiftoolong)
268247405Salfred			newtimeout = 0;
269247405Salfred		else
270247405Salfred			return EINVAL;
271247405Salfred	}
272247405Salfred
273247405Salfred	/* disable the pre-timeout */
274247405Salfred	if (newtimeout == 0) {
275247405Salfred		wd_pretimeout = 0;
276247405Salfred		callout_stop(&wd_pretimeo_handle);
277247405Salfred		return 0;
278247405Salfred	}
279247405Salfred
280253719Salfred	timeout_ticks = pow2ns_to_ticks(utime) - (hz*newtimeout);
281253719Salfred#if 0
282253719Salfred	printf("wd_set_pretimeout: "
283253719Salfred	    "newtimeout: %d, "
284253719Salfred	    "utime: %d -> utime_ticks: %d, "
285253719Salfred	    "hz*newtimeout: %d, "
286253719Salfred	    "timeout_ticks: %d -> sec: %d\n",
287253719Salfred	    newtimeout,
288253719Salfred	    utime, pow2ns_to_ticks(utime),
289253719Salfred	    hz*newtimeout,
290253719Salfred	    timeout_ticks, timeout_ticks / hz);
291253719Salfred#endif
292253719Salfred
293247405Salfred	/* We determined the value is sane, so reset the callout */
294253719Salfred	(void) callout_reset(&wd_pretimeo_handle,
295253719Salfred	    timeout_ticks,
296247405Salfred	    wd_timeout_cb, "pre-timeout");
297247405Salfred	wd_pretimeout = newtimeout;
298247405Salfred	return 0;
299221121Sattilio}
300221121Sattilio
301247405Salfredstatic int
302247405Salfredwd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
303247405Salfred    int flags __unused, struct thread *td)
304247405Salfred{
305247405Salfred	u_int u;
306247405Salfred	time_t timeleft;
307247405Salfred	int error;
308247405Salfred
309247405Salfred	error = 0;
310247405Salfred
311247405Salfred	switch (cmd) {
312247405Salfred	case WDIOC_SETSOFT:
313247405Salfred		u = *(int *)data;
314247405Salfred		/* do nothing? */
315247405Salfred		if (u == wd_softtimer)
316247405Salfred			break;
317247405Salfred		/* If there is a pending timeout disallow this ioctl */
318247405Salfred		if (wd_last_u != 0) {
319247405Salfred			error = EINVAL;
320247405Salfred			break;
321247405Salfred		}
322247405Salfred		wd_softtimer = u;
323247405Salfred		break;
324247405Salfred	case WDIOC_SETSOFTTIMEOUTACT:
325247405Salfred		u = *(int *)data;
326247405Salfred		if (wd_valid_act(u)) {
327247405Salfred			wd_softtimeout_act = u;
328247405Salfred		} else {
329247405Salfred			error = EINVAL;
330247405Salfred		}
331247405Salfred		break;
332247405Salfred	case WDIOC_SETPRETIMEOUTACT:
333247405Salfred		u = *(int *)data;
334247405Salfred		if (wd_valid_act(u)) {
335247405Salfred			wd_pretimeout_act = u;
336247405Salfred		} else {
337247405Salfred			error = EINVAL;
338247405Salfred		}
339247405Salfred		break;
340247405Salfred	case WDIOC_GETPRETIMEOUT:
341247405Salfred		*(int *)data = (int)wd_pretimeout;
342247405Salfred		break;
343247405Salfred	case WDIOC_SETPRETIMEOUT:
344247405Salfred		error = wd_set_pretimeout(*(int *)data, false);
345247405Salfred		break;
346247405Salfred	case WDIOC_GETTIMELEFT:
347247405Salfred		error = wd_get_time_left(td, &timeleft);
348247405Salfred		if (error)
349247405Salfred			break;
350247405Salfred		*(int *)data = (int)timeleft;
351247405Salfred		break;
352247405Salfred	case WDIOC_SETTIMEOUT:
353247405Salfred		u = *(u_int *)data;
354253719Salfred		error = wdog_kern_pat(seconds_to_pow2ns(u));
355247405Salfred		break;
356247405Salfred	case WDIOC_GETTIMEOUT:
357247405Salfred		u = wdog_kern_last_timeout();
358247405Salfred		*(u_int *)data = u;
359247405Salfred		break;
360247405Salfred	case WDIOCPATPAT:
361247405Salfred		error = wd_ioctl_patpat(data);
362247405Salfred		break;
363247405Salfred	default:
364247405Salfred		error = ENOIOCTL;
365247405Salfred		break;
366247405Salfred	}
367247405Salfred	return (error);
368247405Salfred}
369247405Salfred
370247405Salfred/*
371247405Salfred * Return the last timeout set, this is NOT the seconds from NOW until timeout,
372247405Salfred * rather it is the amount of seconds passed to WDIOCPATPAT/WDIOC_SETTIMEOUT.
373247405Salfred */
374247405Salfredu_int
375247405Salfredwdog_kern_last_timeout(void)
376247405Salfred{
377247405Salfred
378247405Salfred	return (wd_last_u);
379247405Salfred}
380247405Salfred
381126370Sphkstatic struct cdevsw wd_cdevsw = {
382126370Sphk	.d_version =	D_VERSION,
383126370Sphk	.d_ioctl =	wd_ioctl,
384126370Sphk	.d_name =	"watchdog",
385126370Sphk};
386126370Sphk
387126370Sphkstatic int
388126370Sphkwatchdog_modevent(module_t mod __unused, int type, void *data __unused)
389126370Sphk{
390126370Sphk	switch(type) {
391126370Sphk	case MOD_LOAD:
392283291Sjkim		callout_init(&wd_pretimeo_handle, 1);
393283291Sjkim		callout_init(&wd_softtimeo_handle, 1);
394126370Sphk		wd_dev = make_dev(&wd_cdevsw, 0,
395126370Sphk		    UID_ROOT, GID_WHEEL, 0600, _PATH_WATCHDOG);
396126370Sphk		return 0;
397126370Sphk	case MOD_UNLOAD:
398247405Salfred		callout_stop(&wd_pretimeo_handle);
399247405Salfred		callout_stop(&wd_softtimeo_handle);
400247405Salfred		callout_drain(&wd_pretimeo_handle);
401247405Salfred		callout_drain(&wd_softtimeo_handle);
402126370Sphk		destroy_dev(wd_dev);
403126370Sphk		return 0;
404126370Sphk	case MOD_SHUTDOWN:
405126370Sphk		return 0;
406126370Sphk	default:
407126370Sphk		return EOPNOTSUPP;
408126370Sphk	}
409126370Sphk}
410126370Sphk
411126370SphkDEV_MODULE(watchdog, watchdog_modevent, NULL);
412