1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Watchdog Driver Test Program |
4 | * - Tests all ioctls |
5 | * - Tests Magic Close - CONFIG_WATCHDOG_NOWAYOUT |
6 | * - Could be tested against softdog driver on systems that |
7 | * don't have watchdog hardware. |
8 | * - TODO: |
9 | * - Enhance test to add coverage for WDIOC_GETTEMP. |
10 | * |
11 | * Reference: Documentation/watchdog/watchdog-api.rst |
12 | */ |
13 | |
14 | #include <errno.h> |
15 | #include <stdio.h> |
16 | #include <stdlib.h> |
17 | #include <string.h> |
18 | #include <unistd.h> |
19 | #include <fcntl.h> |
20 | #include <signal.h> |
21 | #include <getopt.h> |
22 | #include <sys/ioctl.h> |
23 | #include <linux/types.h> |
24 | #include <linux/watchdog.h> |
25 | |
26 | #define DEFAULT_PING_RATE 1 |
27 | |
28 | int fd; |
29 | const char v = 'V'; |
30 | static const char sopts[] = "bdehp:st:Tn:NLf:i" ; |
31 | static const struct option lopts[] = { |
32 | {"bootstatus" , no_argument, NULL, 'b'}, |
33 | {"disable" , no_argument, NULL, 'd'}, |
34 | {"enable" , no_argument, NULL, 'e'}, |
35 | {"help" , no_argument, NULL, 'h'}, |
36 | {"pingrate" , required_argument, NULL, 'p'}, |
37 | {"status" , no_argument, NULL, 's'}, |
38 | {"timeout" , required_argument, NULL, 't'}, |
39 | {"gettimeout" , no_argument, NULL, 'T'}, |
40 | {"pretimeout" , required_argument, NULL, 'n'}, |
41 | {"getpretimeout" , no_argument, NULL, 'N'}, |
42 | {"gettimeleft" , no_argument, NULL, 'L'}, |
43 | {"file" , required_argument, NULL, 'f'}, |
44 | {"info" , no_argument, NULL, 'i'}, |
45 | {NULL, no_argument, NULL, 0x0} |
46 | }; |
47 | |
48 | /* |
49 | * This function simply sends an IOCTL to the driver, which in turn ticks |
50 | * the PC Watchdog card to reset its internal timer so it doesn't trigger |
51 | * a computer reset. |
52 | */ |
53 | static void keep_alive(void) |
54 | { |
55 | int dummy; |
56 | int ret; |
57 | |
58 | ret = ioctl(fd, WDIOC_KEEPALIVE, &dummy); |
59 | if (!ret) |
60 | printf("." ); |
61 | } |
62 | |
63 | /* |
64 | * The main program. Run the program with "-d" to disable the card, |
65 | * or "-e" to enable the card. |
66 | */ |
67 | |
68 | static void term(int sig) |
69 | { |
70 | int ret = write(fd, &v, 1); |
71 | |
72 | close(fd); |
73 | if (ret < 0) |
74 | printf("\nStopping watchdog ticks failed (%d)...\n" , errno); |
75 | else |
76 | printf("\nStopping watchdog ticks...\n" ); |
77 | exit(0); |
78 | } |
79 | |
80 | static void usage(char *progname) |
81 | { |
82 | printf("Usage: %s [options]\n" , progname); |
83 | printf(" -f, --file\t\tOpen watchdog device file\n" ); |
84 | printf("\t\t\tDefault is /dev/watchdog\n" ); |
85 | printf(" -i, --info\t\tShow watchdog_info\n" ); |
86 | printf(" -s, --status\t\tGet status & supported features\n" ); |
87 | printf(" -b, --bootstatus\tGet last boot status (Watchdog/POR)\n" ); |
88 | printf(" -d, --disable\t\tTurn off the watchdog timer\n" ); |
89 | printf(" -e, --enable\t\tTurn on the watchdog timer\n" ); |
90 | printf(" -h, --help\t\tPrint the help message\n" ); |
91 | printf(" -p, --pingrate=P\tSet ping rate to P seconds (default %d)\n" , |
92 | DEFAULT_PING_RATE); |
93 | printf(" -t, --timeout=T\tSet timeout to T seconds\n" ); |
94 | printf(" -T, --gettimeout\tGet the timeout\n" ); |
95 | printf(" -n, --pretimeout=T\tSet the pretimeout to T seconds\n" ); |
96 | printf(" -N, --getpretimeout\tGet the pretimeout\n" ); |
97 | printf(" -L, --gettimeleft\tGet the time left until timer expires\n" ); |
98 | printf("\n" ); |
99 | printf("Parameters are parsed left-to-right in real-time.\n" ); |
100 | printf("Example: %s -d -t 10 -p 5 -e\n" , progname); |
101 | printf("Example: %s -t 12 -T -n 7 -N\n" , progname); |
102 | } |
103 | |
104 | struct wdiof_status { |
105 | int flag; |
106 | const char *status_str; |
107 | }; |
108 | |
109 | #define WDIOF_NUM_STATUS 8 |
110 | |
111 | static const struct wdiof_status wdiof_status[WDIOF_NUM_STATUS] = { |
112 | {WDIOF_SETTIMEOUT, "Set timeout (in seconds)" }, |
113 | {WDIOF_MAGICCLOSE, "Supports magic close char" }, |
114 | {WDIOF_PRETIMEOUT, "Pretimeout (in seconds), get/set" }, |
115 | {WDIOF_ALARMONLY, "Watchdog triggers a management or other external alarm not a reboot" }, |
116 | {WDIOF_KEEPALIVEPING, "Keep alive ping reply" }, |
117 | {WDIOS_DISABLECARD, "Turn off the watchdog timer" }, |
118 | {WDIOS_ENABLECARD, "Turn on the watchdog timer" }, |
119 | {WDIOS_TEMPPANIC, "Kernel panic on temperature trip" }, |
120 | }; |
121 | |
122 | static void print_status(int flags) |
123 | { |
124 | int wdiof = 0; |
125 | |
126 | if (flags == WDIOS_UNKNOWN) { |
127 | printf("Unknown status error from WDIOC_GETSTATUS\n" ); |
128 | return; |
129 | } |
130 | |
131 | for (wdiof = 0; wdiof < WDIOF_NUM_STATUS; wdiof++) { |
132 | if (flags & wdiof_status[wdiof].flag) |
133 | printf("Support/Status: %s\n" , |
134 | wdiof_status[wdiof].status_str); |
135 | } |
136 | } |
137 | |
138 | #define WDIOF_NUM_BOOTSTATUS 7 |
139 | |
140 | static const struct wdiof_status wdiof_bootstatus[WDIOF_NUM_BOOTSTATUS] = { |
141 | {WDIOF_OVERHEAT, "Reset due to CPU overheat" }, |
142 | {WDIOF_FANFAULT, "Fan failed" }, |
143 | {WDIOF_EXTERN1, "External relay 1" }, |
144 | {WDIOF_EXTERN2, "External relay 2" }, |
145 | {WDIOF_POWERUNDER, "Power bad/power fault" }, |
146 | {WDIOF_CARDRESET, "Card previously reset the CPU" }, |
147 | {WDIOF_POWEROVER, "Power over voltage" }, |
148 | }; |
149 | |
150 | static void print_boot_status(int flags) |
151 | { |
152 | int wdiof = 0; |
153 | |
154 | if (flags == WDIOF_UNKNOWN) { |
155 | printf("Unknown flag error from WDIOC_GETBOOTSTATUS\n" ); |
156 | return; |
157 | } |
158 | |
159 | if (flags == 0) { |
160 | printf("Last boot is caused by: Power-On-Reset\n" ); |
161 | return; |
162 | } |
163 | |
164 | for (wdiof = 0; wdiof < WDIOF_NUM_BOOTSTATUS; wdiof++) { |
165 | if (flags & wdiof_bootstatus[wdiof].flag) |
166 | printf("Last boot is caused by: %s\n" , |
167 | wdiof_bootstatus[wdiof].status_str); |
168 | } |
169 | } |
170 | |
171 | int main(int argc, char *argv[]) |
172 | { |
173 | int flags; |
174 | unsigned int ping_rate = DEFAULT_PING_RATE; |
175 | int ret; |
176 | int c; |
177 | int oneshot = 0; |
178 | char *file = "/dev/watchdog" ; |
179 | struct watchdog_info info; |
180 | int temperature; |
181 | |
182 | setbuf(stdout, NULL); |
183 | |
184 | while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { |
185 | if (c == 'f') |
186 | file = optarg; |
187 | } |
188 | |
189 | fd = open(file, O_WRONLY); |
190 | |
191 | if (fd == -1) { |
192 | if (errno == ENOENT) |
193 | printf("Watchdog device (%s) not found.\n" , file); |
194 | else if (errno == EACCES) |
195 | printf("Run watchdog as root.\n" ); |
196 | else |
197 | printf("Watchdog device open failed %s\n" , |
198 | strerror(errno)); |
199 | exit(-1); |
200 | } |
201 | |
202 | /* |
203 | * Validate that `file` is a watchdog device |
204 | */ |
205 | ret = ioctl(fd, WDIOC_GETSUPPORT, &info); |
206 | if (ret) { |
207 | printf("WDIOC_GETSUPPORT error '%s'\n" , strerror(errno)); |
208 | close(fd); |
209 | exit(ret); |
210 | } |
211 | |
212 | optind = 0; |
213 | |
214 | while ((c = getopt_long(argc, argv, sopts, lopts, NULL)) != -1) { |
215 | switch (c) { |
216 | case 'b': |
217 | flags = 0; |
218 | oneshot = 1; |
219 | ret = ioctl(fd, WDIOC_GETBOOTSTATUS, &flags); |
220 | if (!ret) |
221 | print_boot_status(flags); |
222 | else |
223 | printf("WDIOC_GETBOOTSTATUS error '%s'\n" , strerror(errno)); |
224 | break; |
225 | case 'd': |
226 | flags = WDIOS_DISABLECARD; |
227 | ret = ioctl(fd, WDIOC_SETOPTIONS, &flags); |
228 | if (!ret) |
229 | printf("Watchdog card disabled.\n" ); |
230 | else { |
231 | printf("WDIOS_DISABLECARD error '%s'\n" , strerror(errno)); |
232 | oneshot = 1; |
233 | } |
234 | break; |
235 | case 'e': |
236 | flags = WDIOS_ENABLECARD; |
237 | ret = ioctl(fd, WDIOC_SETOPTIONS, &flags); |
238 | if (!ret) |
239 | printf("Watchdog card enabled.\n" ); |
240 | else { |
241 | printf("WDIOS_ENABLECARD error '%s'\n" , strerror(errno)); |
242 | oneshot = 1; |
243 | } |
244 | break; |
245 | case 'p': |
246 | ping_rate = strtoul(optarg, NULL, 0); |
247 | if (!ping_rate) |
248 | ping_rate = DEFAULT_PING_RATE; |
249 | printf("Watchdog ping rate set to %u seconds.\n" , ping_rate); |
250 | break; |
251 | case 's': |
252 | flags = 0; |
253 | oneshot = 1; |
254 | ret = ioctl(fd, WDIOC_GETSTATUS, &flags); |
255 | if (!ret) |
256 | print_status(flags); |
257 | else |
258 | printf("WDIOC_GETSTATUS error '%s'\n" , strerror(errno)); |
259 | ret = ioctl(fd, WDIOC_GETTEMP, &temperature); |
260 | if (ret) |
261 | printf("WDIOC_GETTEMP: '%s'\n" , strerror(errno)); |
262 | else |
263 | printf("Temperature %d\n" , temperature); |
264 | |
265 | break; |
266 | case 't': |
267 | flags = strtoul(optarg, NULL, 0); |
268 | ret = ioctl(fd, WDIOC_SETTIMEOUT, &flags); |
269 | if (!ret) |
270 | printf("Watchdog timeout set to %u seconds.\n" , flags); |
271 | else { |
272 | printf("WDIOC_SETTIMEOUT error '%s'\n" , strerror(errno)); |
273 | oneshot = 1; |
274 | } |
275 | break; |
276 | case 'T': |
277 | oneshot = 1; |
278 | ret = ioctl(fd, WDIOC_GETTIMEOUT, &flags); |
279 | if (!ret) |
280 | printf("WDIOC_GETTIMEOUT returns %u seconds.\n" , flags); |
281 | else |
282 | printf("WDIOC_GETTIMEOUT error '%s'\n" , strerror(errno)); |
283 | break; |
284 | case 'n': |
285 | flags = strtoul(optarg, NULL, 0); |
286 | ret = ioctl(fd, WDIOC_SETPRETIMEOUT, &flags); |
287 | if (!ret) |
288 | printf("Watchdog pretimeout set to %u seconds.\n" , flags); |
289 | else { |
290 | printf("WDIOC_SETPRETIMEOUT error '%s'\n" , strerror(errno)); |
291 | oneshot = 1; |
292 | } |
293 | break; |
294 | case 'N': |
295 | oneshot = 1; |
296 | ret = ioctl(fd, WDIOC_GETPRETIMEOUT, &flags); |
297 | if (!ret) |
298 | printf("WDIOC_GETPRETIMEOUT returns %u seconds.\n" , flags); |
299 | else |
300 | printf("WDIOC_GETPRETIMEOUT error '%s'\n" , strerror(errno)); |
301 | break; |
302 | case 'L': |
303 | oneshot = 1; |
304 | ret = ioctl(fd, WDIOC_GETTIMELEFT, &flags); |
305 | if (!ret) |
306 | printf("WDIOC_GETTIMELEFT returns %u seconds.\n" , flags); |
307 | else |
308 | printf("WDIOC_GETTIMELEFT error '%s'\n" , strerror(errno)); |
309 | break; |
310 | case 'f': |
311 | /* Handled above */ |
312 | break; |
313 | case 'i': |
314 | /* |
315 | * watchdog_info was obtained as part of file open |
316 | * validation. So we just show it here. |
317 | */ |
318 | oneshot = 1; |
319 | printf("watchdog_info:\n" ); |
320 | printf(" identity:\t\t%s\n" , info.identity); |
321 | printf(" firmware_version:\t%u\n" , |
322 | info.firmware_version); |
323 | print_status(flags: info.options); |
324 | break; |
325 | |
326 | default: |
327 | usage(progname: argv[0]); |
328 | goto end; |
329 | } |
330 | } |
331 | |
332 | if (oneshot) |
333 | goto end; |
334 | |
335 | printf("Watchdog Ticking Away!\n" ); |
336 | |
337 | signal(SIGINT, term); |
338 | |
339 | while (1) { |
340 | keep_alive(); |
341 | sleep(ping_rate); |
342 | } |
343 | end: |
344 | /* |
345 | * Send specific magic character 'V' just in case Magic Close is |
346 | * enabled to ensure watchdog gets disabled on close. |
347 | */ |
348 | ret = write(fd, &v, 1); |
349 | if (ret < 0) |
350 | printf("Stopping watchdog ticks failed (%d)...\n" , errno); |
351 | close(fd); |
352 | return 0; |
353 | } |
354 | |