]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
e96d6be7 | 2 | |
e96d6be7 LP |
3 | #include <errno.h> |
4 | #include <fcntl.h> | |
cf0fbc49 | 5 | #include <sys/ioctl.h> |
a8fbdf54 | 6 | #include <syslog.h> |
e96d6be7 LP |
7 | #include <unistd.h> |
8 | #include <linux/watchdog.h> | |
9 | ||
3ffd4af2 | 10 | #include "fd-util.h" |
cf0fbc49 | 11 | #include "log.h" |
e4c98db3 | 12 | #include "string-util.h" |
a8fbdf54 | 13 | #include "time-util.h" |
cf0fbc49 | 14 | #include "watchdog.h" |
e96d6be7 LP |
15 | |
16 | static int watchdog_fd = -1; | |
e4c98db3 | 17 | static char *watchdog_device = NULL; |
3a43da28 | 18 | static usec_t watchdog_timeout = USEC_INFINITY; |
c5f8a179 | 19 | static usec_t watchdog_last_ping = USEC_INFINITY; |
e96d6be7 LP |
20 | |
21 | static int update_timeout(void) { | |
22 | int r; | |
23 | ||
24 | if (watchdog_fd < 0) | |
25 | return 0; | |
26 | ||
3a43da28 | 27 | if (watchdog_timeout == USEC_INFINITY) |
e96d6be7 LP |
28 | return 0; |
29 | else if (watchdog_timeout == 0) { | |
30 | int flags; | |
31 | ||
32 | flags = WDIOS_DISABLECARD; | |
33 | r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); | |
4a62c710 MS |
34 | if (r < 0) |
35 | return log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); | |
e96d6be7 LP |
36 | } else { |
37 | int sec, flags; | |
38 | char buf[FORMAT_TIMESPAN_MAX]; | |
39 | ||
be6b0c21 | 40 | sec = (int) DIV_ROUND_UP(watchdog_timeout, USEC_PER_SEC); |
e96d6be7 | 41 | r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec); |
4a62c710 MS |
42 | if (r < 0) |
43 | return log_warning_errno(errno, "Failed to set timeout to %is: %m", sec); | |
e96d6be7 LP |
44 | |
45 | watchdog_timeout = (usec_t) sec * USEC_PER_SEC; | |
2fa4092c | 46 | log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0)); |
e96d6be7 LP |
47 | |
48 | flags = WDIOS_ENABLECARD; | |
49 | r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); | |
14f494c7 JD |
50 | if (r < 0) { |
51 | /* ENOTTY means the watchdog is always enabled so we're fine */ | |
52 | log_full(errno == ENOTTY ? LOG_DEBUG : LOG_WARNING, | |
53 | "Failed to enable hardware watchdog: %m"); | |
54 | if (errno != ENOTTY) | |
55 | return -errno; | |
56 | } | |
e96d6be7 LP |
57 | |
58 | r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); | |
4a62c710 MS |
59 | if (r < 0) |
60 | return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); | |
c5f8a179 AP |
61 | |
62 | watchdog_last_ping = now(clock_boottime_or_monotonic()); | |
e96d6be7 LP |
63 | } |
64 | ||
65 | return 0; | |
66 | } | |
67 | ||
68 | static int open_watchdog(void) { | |
69 | struct watchdog_info ident; | |
70 | ||
71 | if (watchdog_fd >= 0) | |
72 | return 0; | |
73 | ||
e4c98db3 EJ |
74 | watchdog_fd = open(watchdog_device ?: "/dev/watchdog", |
75 | O_WRONLY|O_CLOEXEC); | |
e96d6be7 LP |
76 | if (watchdog_fd < 0) |
77 | return -errno; | |
78 | ||
79 | if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0) | |
80 | log_info("Hardware watchdog '%s', version %x", | |
81 | ident.identity, | |
82 | ident.firmware_version); | |
83 | ||
84 | return update_timeout(); | |
85 | } | |
86 | ||
e4c98db3 | 87 | int watchdog_set_device(char *path) { |
1fedf138 ZJS |
88 | int r; |
89 | ||
90 | r = free_and_strdup(&watchdog_device, path); | |
91 | if (r < 0) | |
92 | return r; | |
93 | ||
94 | if (r > 0) /* watchdog_device changed */ | |
95 | watchdog_fd = safe_close(watchdog_fd); | |
96 | ||
97 | return r; | |
e4c98db3 EJ |
98 | } |
99 | ||
e96d6be7 | 100 | int watchdog_set_timeout(usec_t *usec) { |
56bcbfa5 | 101 | int r; |
e96d6be7 LP |
102 | |
103 | watchdog_timeout = *usec; | |
104 | ||
105 | /* If we didn't open the watchdog yet and didn't get any | |
106 | * explicit timeout value set, don't do anything */ | |
3a43da28 | 107 | if (watchdog_fd < 0 && watchdog_timeout == USEC_INFINITY) |
e96d6be7 LP |
108 | return 0; |
109 | ||
110 | if (watchdog_fd < 0) | |
56bcbfa5 | 111 | r = open_watchdog(); |
e96d6be7 | 112 | else |
56bcbfa5 | 113 | r = update_timeout(); |
e96d6be7 LP |
114 | |
115 | *usec = watchdog_timeout; | |
56bcbfa5 MO |
116 | |
117 | return r; | |
e96d6be7 LP |
118 | } |
119 | ||
c5f8a179 AP |
120 | usec_t watchdog_runtime_wait(void) { |
121 | usec_t rtwait; | |
122 | usec_t ntime; | |
123 | ||
124 | if (!timestamp_is_set(watchdog_timeout)) | |
125 | return USEC_INFINITY; | |
126 | ||
86b52a39 | 127 | /* Sleep half the watchdog timeout since the last successful ping at most */ |
c5f8a179 AP |
128 | if (timestamp_is_set(watchdog_last_ping)) { |
129 | ntime = now(clock_boottime_or_monotonic()); | |
130 | assert(ntime >= watchdog_last_ping); | |
131 | rtwait = usec_sub_unsigned(watchdog_last_ping + (watchdog_timeout / 2), ntime); | |
132 | } else | |
133 | rtwait = watchdog_timeout / 2; | |
134 | ||
135 | return rtwait; | |
136 | } | |
137 | ||
e96d6be7 | 138 | int watchdog_ping(void) { |
c5f8a179 | 139 | usec_t ntime; |
e96d6be7 LP |
140 | int r; |
141 | ||
c5f8a179 AP |
142 | ntime = now(clock_boottime_or_monotonic()); |
143 | ||
144 | /* Never ping earlier than watchdog_timeout/4 and try to ping | |
145 | * by watchdog_timeout/2 plus scheduling latencies the latest */ | |
146 | if (timestamp_is_set(watchdog_last_ping)) { | |
147 | assert(ntime >= watchdog_last_ping); | |
148 | if ((ntime - watchdog_last_ping) < (watchdog_timeout / 4)) | |
149 | return 0; | |
150 | } | |
151 | ||
e96d6be7 LP |
152 | if (watchdog_fd < 0) { |
153 | r = open_watchdog(); | |
154 | if (r < 0) | |
155 | return r; | |
156 | } | |
157 | ||
158 | r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); | |
4a62c710 MS |
159 | if (r < 0) |
160 | return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); | |
e96d6be7 | 161 | |
c5f8a179 AP |
162 | watchdog_last_ping = ntime; |
163 | ||
e96d6be7 LP |
164 | return 0; |
165 | } | |
166 | ||
167 | void watchdog_close(bool disarm) { | |
168 | int r; | |
169 | ||
170 | if (watchdog_fd < 0) | |
171 | return; | |
172 | ||
173 | if (disarm) { | |
174 | int flags; | |
175 | ||
176 | /* Explicitly disarm it */ | |
177 | flags = WDIOS_DISABLECARD; | |
178 | r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); | |
179 | if (r < 0) | |
56f64d95 | 180 | log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); |
e96d6be7 LP |
181 | |
182 | /* To be sure, use magic close logic, too */ | |
183 | for (;;) { | |
184 | static const char v = 'V'; | |
185 | ||
186 | if (write(watchdog_fd, &v, 1) > 0) | |
187 | break; | |
188 | ||
189 | if (errno != EINTR) { | |
56f64d95 | 190 | log_error_errno(errno, "Failed to disarm watchdog timer: %m"); |
e96d6be7 LP |
191 | break; |
192 | } | |
193 | } | |
194 | } | |
195 | ||
03e334a1 | 196 | watchdog_fd = safe_close(watchdog_fd); |
e96d6be7 | 197 | } |