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
31126370Sphk#include <sys/cdefs.h>
32126370Sphk__FBSDID("$FreeBSD$");
33126370Sphk
34126370Sphk#include <sys/param.h>
35247405Salfred#include <sys/types.h>
36126370Sphk#include <sys/systm.h>
37126370Sphk#include <sys/conf.h>
38126370Sphk#include <sys/uio.h>
39126370Sphk#include <sys/kernel.h>
40126370Sphk#include <sys/malloc.h>
41126370Sphk#include <sys/module.h>
42253719Salfred#include <sys/sysctl.h>
43247405Salfred#include <sys/syslog.h>
44126370Sphk#include <sys/watchdog.h>
45126370Sphk#include <sys/bus.h>
46126370Sphk#include <machine/bus.h>
47126370Sphk
48247405Salfred#include <sys/syscallsubr.h> /* kern_clock_gettime() */
49247405Salfred
50247405Salfredstatic int wd_set_pretimeout(int newtimeout, int disableiftoolong);
51247405Salfredstatic void wd_timeout_cb(void *arg);
52247405Salfred
53247405Salfredstatic struct callout wd_pretimeo_handle;
54247405Salfredstatic int wd_pretimeout;
55247405Salfredstatic int wd_pretimeout_act = WD_SOFT_LOG;
56247405Salfred
57247405Salfredstatic struct callout wd_softtimeo_handle;
58247405Salfredstatic int wd_softtimer;	/* true = use softtimer instead of hardware
59247405Salfred				   watchdog */
60247405Salfredstatic int wd_softtimeout_act = WD_SOFT_LOG;	/* action for the software timeout */
61247405Salfred
62130585Sphkstatic struct cdev *wd_dev;
63247405Salfredstatic volatile u_int wd_last_u;    /* last timeout value set by kern_do_pat */
64253719Salfredstatic u_int wd_last_u_sysctl;    /* last timeout value set by kern_do_pat */
65253719Salfredstatic u_int wd_last_u_sysctl_secs;    /* wd_last_u in seconds */
66126370Sphk
67253719SalfredSYSCTL_NODE(_hw, OID_AUTO, watchdog, CTLFLAG_RD, 0, "Main watchdog device");
68253719SalfredSYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u, CTLFLAG_RD,
69253719Salfred    &wd_last_u_sysctl, 0, "Watchdog last update time");
70253719SalfredSYSCTL_UINT(_hw_watchdog, OID_AUTO, wd_last_u_secs, CTLFLAG_RD,
71253719Salfred    &wd_last_u_sysctl_secs, 0, "Watchdog last update time");
72253719Salfred
73247405Salfredstatic int wd_lastpat_valid = 0;
74247405Salfredstatic time_t wd_lastpat = 0;	/* when the watchdog was last patted */
75247405Salfred
76253719Salfredstatic void
77253719Salfredpow2ns_to_ts(int pow2ns, struct timespec *ts)
78253719Salfred{
79253719Salfred	uint64_t ns;
80253719Salfred
81253719Salfred	ns = 1ULL << pow2ns;
82253719Salfred	ts->tv_sec = ns / 1000000000ULL;
83253719Salfred	ts->tv_nsec = ns % 1000000000ULL;
84253719Salfred}
85253719Salfred
86253719Salfredstatic int
87253719Salfredpow2ns_to_ticks(int pow2ns)
88253719Salfred{
89253719Salfred	struct timeval tv;
90253719Salfred	struct timespec ts;
91253719Salfred
92253719Salfred	pow2ns_to_ts(pow2ns, &ts);
93253719Salfred	TIMESPEC_TO_TIMEVAL(&tv, &ts);
94253719Salfred	return (tvtohz(&tv));
95253719Salfred}
96253719Salfred
97253719Salfredstatic int
98253719Salfredseconds_to_pow2ns(int seconds)
99253719Salfred{
100253719Salfred	uint64_t power;
101253719Salfred	uint64_t ns;
102253719Salfred	uint64_t shifted;
103253719Salfred
104253719Salfred	ns = ((uint64_t)seconds) * 1000000000ULL;
105253719Salfred	power = flsll(ns);
106253719Salfred	shifted = 1ULL << power;
107253719Salfred	if (shifted <= ns) {
108253719Salfred		power++;
109253719Salfred	}
110253719Salfred	return (power);
111253719Salfred}
112253719Salfred
113253719Salfred
114247405Salfredint
115247405Salfredwdog_kern_pat(u_int utim)
116221121Sattilio{
117221121Sattilio	int error;
118221121Sattilio
119221121Sattilio	if ((utim & WD_LASTVAL) != 0 && (utim & WD_INTERVAL) > 0)
120221121Sattilio		return (EINVAL);
121221121Sattilio
122221121Sattilio	if ((utim & WD_LASTVAL) != 0) {
123247405Salfred		/*
124247405Salfred		 * if WD_LASTVAL is set, fill in the bits for timeout
125247405Salfred		 * from the saved value in wd_last_u.
126247405Salfred		 */
127221121Sattilio		MPASS((wd_last_u & ~WD_INTERVAL) == 0);
128221121Sattilio		utim &= ~WD_LASTVAL;
129221121Sattilio		utim |= wd_last_u;
130247405Salfred	} else {
131247405Salfred		/*
132247405Salfred		 * Otherwise save the new interval.
133247405Salfred		 * This can be zero (to disable the watchdog)
134247405Salfred		 */
135221121Sattilio		wd_last_u = (utim & WD_INTERVAL);
136253719Salfred		wd_last_u_sysctl = wd_last_u;
137253719Salfred		wd_last_u_sysctl_secs = pow2ns_to_ticks(wd_last_u) / hz;
138247405Salfred	}
139221121Sattilio	if ((utim & WD_INTERVAL) == WD_TO_NEVER) {
140221121Sattilio		utim = 0;
141221121Sattilio
142221121Sattilio		/* Assume all is well; watchdog signals failure. */
143221121Sattilio		error = 0;
144221121Sattilio	} else {
145221121Sattilio		/* Assume no watchdog available; watchdog flags success */
146221121Sattilio		error = EOPNOTSUPP;
147221121Sattilio	}
148247405Salfred	if (wd_softtimer) {
149247405Salfred		if (utim == 0) {
150247405Salfred			callout_stop(&wd_softtimeo_handle);
151247405Salfred		} else {
152247405Salfred			(void) callout_reset(&wd_softtimeo_handle,
153253719Salfred			    pow2ns_to_ticks(utim), wd_timeout_cb, "soft");
154247405Salfred		}
155247405Salfred		error = 0;
156247405Salfred	} else {
157247405Salfred		EVENTHANDLER_INVOKE(watchdog_list, utim, &error);
158247405Salfred	}
159247405Salfred	wd_set_pretimeout(wd_pretimeout, true);
160247405Salfred	/*
161247405Salfred	 * If we were able to arm/strobe the watchdog, then
162247405Salfred	 * update the last time it was strobed for WDIOC_GETTIMELEFT
163247405Salfred	 */
164247405Salfred	if (!error) {
165247405Salfred		struct timespec ts;
166247405Salfred
167247405Salfred		error = kern_clock_gettime(curthread /* XXX */,
168247405Salfred		    CLOCK_MONOTONIC_FAST, &ts);
169247405Salfred		if (!error) {
170247405Salfred			wd_lastpat = ts.tv_sec;
171247405Salfred			wd_lastpat_valid = 1;
172247405Salfred		}
173247405Salfred	}
174221121Sattilio	return (error);
175221121Sattilio}
176221121Sattilio
177221121Sattiliostatic int
178247405Salfredwd_valid_act(int act)
179126370Sphk{
180247405Salfred
181247405Salfred	if ((act & ~(WD_SOFT_MASK)) != 0)
182247405Salfred		return false;
183247405Salfred	return true;
184247405Salfred}
185247405Salfred
186247405Salfredstatic int
187247405Salfredwd_ioctl_patpat(caddr_t data)
188247405Salfred{
189126370Sphk	u_int u;
190126370Sphk
191126370Sphk	u = *(u_int *)data;
192221121Sattilio	if (u & ~(WD_ACTIVE | WD_PASSIVE | WD_LASTVAL | WD_INTERVAL))
193126370Sphk		return (EINVAL);
194126370Sphk	if ((u & (WD_ACTIVE | WD_PASSIVE)) == (WD_ACTIVE | WD_PASSIVE))
195126370Sphk		return (EINVAL);
196221121Sattilio	if ((u & (WD_ACTIVE | WD_PASSIVE)) == 0 && ((u & WD_INTERVAL) > 0 ||
197221121Sattilio	    (u & WD_LASTVAL) != 0))
198167950Sn_hibma		return (EINVAL);
199165260Sn_hibma	if (u & WD_PASSIVE)
200165260Sn_hibma		return (ENOSYS);	/* XXX Not implemented yet */
201221121Sattilio	u &= ~(WD_ACTIVE | WD_PASSIVE);
202221121Sattilio
203247405Salfred	return (wdog_kern_pat(u));
204126370Sphk}
205126370Sphk
206247405Salfredstatic int
207247405Salfredwd_get_time_left(struct thread *td, time_t *remainp)
208221121Sattilio{
209247405Salfred	struct timespec ts;
210247405Salfred	int error;
211221121Sattilio
212247405Salfred	error = kern_clock_gettime(td, CLOCK_MONOTONIC_FAST, &ts);
213247405Salfred	if (error)
214247405Salfred		return (error);
215247405Salfred	if (!wd_lastpat_valid)
216247405Salfred		return (ENOENT);
217247405Salfred	*remainp = ts.tv_sec - wd_lastpat;
218247405Salfred	return (0);
219221121Sattilio}
220221121Sattilio
221247405Salfredstatic void
222247405Salfredwd_timeout_cb(void *arg)
223221121Sattilio{
224247405Salfred	const char *type = arg;
225221121Sattilio
226247405Salfred#ifdef DDB
227247405Salfred	if ((wd_pretimeout_act & WD_SOFT_DDB)) {
228247405Salfred		char kdb_why[80];
229247405Salfred		snprintf(kdb_why, sizeof(buf), "watchdog %s timeout", type);
230247405Salfred		kdb_backtrace();
231247405Salfred		kdb_enter(KDB_WHY_WATCHDOG, kdb_why);
232247405Salfred	}
233247405Salfred#endif
234247405Salfred	if ((wd_pretimeout_act & WD_SOFT_LOG))
235247405Salfred		log(LOG_EMERG, "watchdog %s-timeout, WD_SOFT_LOG", type);
236247405Salfred	if ((wd_pretimeout_act & WD_SOFT_PRINTF))
237247405Salfred		printf("watchdog %s-timeout, WD_SOFT_PRINTF\n", type);
238247405Salfred	if ((wd_pretimeout_act & WD_SOFT_PANIC))
239247405Salfred		panic("watchdog %s-timeout, WD_SOFT_PANIC set", type);
240247405Salfred}
241221121Sattilio
242247405Salfred/*
243247405Salfred * Called to manage timeouts.
244247405Salfred * newtimeout needs to be in the range of 0 to actual watchdog timeout.
245247405Salfred * if 0, we disable the pre-timeout.
246247405Salfred * otherwise we set the pre-timeout provided it's not greater than the
247247405Salfred * current actual watchdog timeout.
248247405Salfred */
249247405Salfredstatic int
250247405Salfredwd_set_pretimeout(int newtimeout, int disableiftoolong)
251247405Salfred{
252247405Salfred	u_int utime;
253253719Salfred	struct timespec utime_ts;
254253719Salfred	int timeout_ticks;
255247405Salfred
256247405Salfred	utime = wdog_kern_last_timeout();
257253719Salfred	pow2ns_to_ts(utime, &utime_ts);
258247405Salfred	/* do not permit a pre-timeout >= than the timeout. */
259253719Salfred	if (newtimeout >= utime_ts.tv_sec) {
260247405Salfred		/*
261247405Salfred		 * If 'disableiftoolong' then just fall through
262247405Salfred		 * so as to disable the pre-watchdog
263247405Salfred		 */
264247405Salfred		if (disableiftoolong)
265247405Salfred			newtimeout = 0;
266247405Salfred		else
267247405Salfred			return EINVAL;
268247405Salfred	}
269247405Salfred
270247405Salfred	/* disable the pre-timeout */
271247405Salfred	if (newtimeout == 0) {
272247405Salfred		wd_pretimeout = 0;
273247405Salfred		callout_stop(&wd_pretimeo_handle);
274247405Salfred		return 0;
275247405Salfred	}
276247405Salfred
277253719Salfred	timeout_ticks = pow2ns_to_ticks(utime) - (hz*newtimeout);
278253719Salfred#if 0
279253719Salfred	printf("wd_set_pretimeout: "
280253719Salfred	    "newtimeout: %d, "
281253719Salfred	    "utime: %d -> utime_ticks: %d, "
282253719Salfred	    "hz*newtimeout: %d, "
283253719Salfred	    "timeout_ticks: %d -> sec: %d\n",
284253719Salfred	    newtimeout,
285253719Salfred	    utime, pow2ns_to_ticks(utime),
286253719Salfred	    hz*newtimeout,
287253719Salfred	    timeout_ticks, timeout_ticks / hz);
288253719Salfred#endif
289253719Salfred
290247405Salfred	/* We determined the value is sane, so reset the callout */
291253719Salfred	(void) callout_reset(&wd_pretimeo_handle,
292253719Salfred	    timeout_ticks,
293247405Salfred	    wd_timeout_cb, "pre-timeout");
294247405Salfred	wd_pretimeout = newtimeout;
295247405Salfred	return 0;
296221121Sattilio}
297221121Sattilio
298247405Salfredstatic int
299247405Salfredwd_ioctl(struct cdev *dev __unused, u_long cmd, caddr_t data,
300247405Salfred    int flags __unused, struct thread *td)
301247405Salfred{
302247405Salfred	u_int u;
303247405Salfred	time_t timeleft;
304247405Salfred	int error;
305247405Salfred
306247405Salfred	error = 0;
307247405Salfred
308247405Salfred	switch (cmd) {
309247405Salfred	case WDIOC_SETSOFT:
310247405Salfred		u = *(int *)data;
311247405Salfred		/* do nothing? */
312247405Salfred		if (u == wd_softtimer)
313247405Salfred			break;
314247405Salfred		/* If there is a pending timeout disallow this ioctl */
315247405Salfred		if (wd_last_u != 0) {
316247405Salfred			error = EINVAL;
317247405Salfred			break;
318247405Salfred		}
319247405Salfred		wd_softtimer = u;
320247405Salfred		break;
321247405Salfred	case WDIOC_SETSOFTTIMEOUTACT:
322247405Salfred		u = *(int *)data;
323247405Salfred		if (wd_valid_act(u)) {
324247405Salfred			wd_softtimeout_act = u;
325247405Salfred		} else {
326247405Salfred			error = EINVAL;
327247405Salfred		}
328247405Salfred		break;
329247405Salfred	case WDIOC_SETPRETIMEOUTACT:
330247405Salfred		u = *(int *)data;
331247405Salfred		if (wd_valid_act(u)) {
332247405Salfred			wd_pretimeout_act = u;
333247405Salfred		} else {
334247405Salfred			error = EINVAL;
335247405Salfred		}
336247405Salfred		break;
337247405Salfred	case WDIOC_GETPRETIMEOUT:
338247405Salfred		*(int *)data = (int)wd_pretimeout;
339247405Salfred		break;
340247405Salfred	case WDIOC_SETPRETIMEOUT:
341247405Salfred		error = wd_set_pretimeout(*(int *)data, false);
342247405Salfred		break;
343247405Salfred	case WDIOC_GETTIMELEFT:
344247405Salfred		error = wd_get_time_left(td, &timeleft);
345247405Salfred		if (error)
346247405Salfred			break;
347247405Salfred		*(int *)data = (int)timeleft;
348247405Salfred		break;
349247405Salfred	case WDIOC_SETTIMEOUT:
350247405Salfred		u = *(u_int *)data;
351253719Salfred		error = wdog_kern_pat(seconds_to_pow2ns(u));
352247405Salfred		break;
353247405Salfred	case WDIOC_GETTIMEOUT:
354247405Salfred		u = wdog_kern_last_timeout();
355247405Salfred		*(u_int *)data = u;
356247405Salfred		break;
357247405Salfred	case WDIOCPATPAT:
358247405Salfred		error = wd_ioctl_patpat(data);
359247405Salfred		break;
360247405Salfred	default:
361247405Salfred		error = ENOIOCTL;
362247405Salfred		break;
363247405Salfred	}
364247405Salfred	return (error);
365247405Salfred}
366247405Salfred
367247405Salfred/*
368247405Salfred * Return the last timeout set, this is NOT the seconds from NOW until timeout,
369247405Salfred * rather it is the amount of seconds passed to WDIOCPATPAT/WDIOC_SETTIMEOUT.
370247405Salfred */
371247405Salfredu_int
372247405Salfredwdog_kern_last_timeout(void)
373247405Salfred{
374247405Salfred
375247405Salfred	return (wd_last_u);
376247405Salfred}
377247405Salfred
378126370Sphkstatic struct cdevsw wd_cdevsw = {
379126370Sphk	.d_version =	D_VERSION,
380126370Sphk	.d_ioctl =	wd_ioctl,
381126370Sphk	.d_name =	"watchdog",
382126370Sphk};
383126370Sphk
384126370Sphkstatic int
385126370Sphkwatchdog_modevent(module_t mod __unused, int type, void *data __unused)
386126370Sphk{
387126370Sphk	switch(type) {
388126370Sphk	case MOD_LOAD:
389247405Salfred		callout_init(&wd_pretimeo_handle, true);
390247405Salfred		callout_init(&wd_softtimeo_handle, true);
391126370Sphk		wd_dev = make_dev(&wd_cdevsw, 0,
392126370Sphk		    UID_ROOT, GID_WHEEL, 0600, _PATH_WATCHDOG);
393126370Sphk		return 0;
394126370Sphk	case MOD_UNLOAD:
395247405Salfred		callout_stop(&wd_pretimeo_handle);
396247405Salfred		callout_stop(&wd_softtimeo_handle);
397247405Salfred		callout_drain(&wd_pretimeo_handle);
398247405Salfred		callout_drain(&wd_softtimeo_handle);
399126370Sphk		destroy_dev(wd_dev);
400126370Sphk		return 0;
401126370Sphk	case MOD_SHUTDOWN:
402126370Sphk		return 0;
403126370Sphk	default:
404126370Sphk		return EOPNOTSUPP;
405126370Sphk	}
406126370Sphk}
407126370Sphk
408126370SphkDEV_MODULE(watchdog, watchdog_modevent, NULL);
409