]>
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; |
e96d6be7 LP |
19 | |
20 | static int update_timeout(void) { | |
21 | int r; | |
22 | ||
23 | if (watchdog_fd < 0) | |
24 | return 0; | |
25 | ||
3a43da28 | 26 | if (watchdog_timeout == USEC_INFINITY) |
e96d6be7 LP |
27 | return 0; |
28 | else if (watchdog_timeout == 0) { | |
29 | int flags; | |
30 | ||
31 | flags = WDIOS_DISABLECARD; | |
32 | r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); | |
4a62c710 MS |
33 | if (r < 0) |
34 | return log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); | |
e96d6be7 LP |
35 | } else { |
36 | int sec, flags; | |
37 | char buf[FORMAT_TIMESPAN_MAX]; | |
38 | ||
be6b0c21 | 39 | sec = (int) DIV_ROUND_UP(watchdog_timeout, USEC_PER_SEC); |
e96d6be7 | 40 | r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec); |
4a62c710 MS |
41 | if (r < 0) |
42 | return log_warning_errno(errno, "Failed to set timeout to %is: %m", sec); | |
e96d6be7 LP |
43 | |
44 | watchdog_timeout = (usec_t) sec * USEC_PER_SEC; | |
2fa4092c | 45 | log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0)); |
e96d6be7 LP |
46 | |
47 | flags = WDIOS_ENABLECARD; | |
48 | r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); | |
14f494c7 JD |
49 | if (r < 0) { |
50 | /* ENOTTY means the watchdog is always enabled so we're fine */ | |
51 | log_full(errno == ENOTTY ? LOG_DEBUG : LOG_WARNING, | |
52 | "Failed to enable hardware watchdog: %m"); | |
53 | if (errno != ENOTTY) | |
54 | return -errno; | |
55 | } | |
e96d6be7 LP |
56 | |
57 | r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); | |
4a62c710 MS |
58 | if (r < 0) |
59 | return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); | |
e96d6be7 LP |
60 | } |
61 | ||
62 | return 0; | |
63 | } | |
64 | ||
65 | static int open_watchdog(void) { | |
66 | struct watchdog_info ident; | |
67 | ||
68 | if (watchdog_fd >= 0) | |
69 | return 0; | |
70 | ||
e4c98db3 EJ |
71 | watchdog_fd = open(watchdog_device ?: "/dev/watchdog", |
72 | O_WRONLY|O_CLOEXEC); | |
e96d6be7 LP |
73 | if (watchdog_fd < 0) |
74 | return -errno; | |
75 | ||
76 | if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0) | |
77 | log_info("Hardware watchdog '%s', version %x", | |
78 | ident.identity, | |
79 | ident.firmware_version); | |
80 | ||
81 | return update_timeout(); | |
82 | } | |
83 | ||
e4c98db3 EJ |
84 | int watchdog_set_device(char *path) { |
85 | return free_and_strdup(&watchdog_device, path); | |
86 | } | |
87 | ||
e96d6be7 | 88 | int watchdog_set_timeout(usec_t *usec) { |
56bcbfa5 | 89 | int r; |
e96d6be7 LP |
90 | |
91 | watchdog_timeout = *usec; | |
92 | ||
93 | /* If we didn't open the watchdog yet and didn't get any | |
94 | * explicit timeout value set, don't do anything */ | |
3a43da28 | 95 | if (watchdog_fd < 0 && watchdog_timeout == USEC_INFINITY) |
e96d6be7 LP |
96 | return 0; |
97 | ||
98 | if (watchdog_fd < 0) | |
56bcbfa5 | 99 | r = open_watchdog(); |
e96d6be7 | 100 | else |
56bcbfa5 | 101 | r = update_timeout(); |
e96d6be7 LP |
102 | |
103 | *usec = watchdog_timeout; | |
56bcbfa5 MO |
104 | |
105 | return r; | |
e96d6be7 LP |
106 | } |
107 | ||
108 | int watchdog_ping(void) { | |
109 | int r; | |
110 | ||
111 | if (watchdog_fd < 0) { | |
112 | r = open_watchdog(); | |
113 | if (r < 0) | |
114 | return r; | |
115 | } | |
116 | ||
117 | r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); | |
4a62c710 MS |
118 | if (r < 0) |
119 | return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); | |
e96d6be7 LP |
120 | |
121 | return 0; | |
122 | } | |
123 | ||
124 | void watchdog_close(bool disarm) { | |
125 | int r; | |
126 | ||
127 | if (watchdog_fd < 0) | |
128 | return; | |
129 | ||
130 | if (disarm) { | |
131 | int flags; | |
132 | ||
133 | /* Explicitly disarm it */ | |
134 | flags = WDIOS_DISABLECARD; | |
135 | r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); | |
136 | if (r < 0) | |
56f64d95 | 137 | log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); |
e96d6be7 LP |
138 | |
139 | /* To be sure, use magic close logic, too */ | |
140 | for (;;) { | |
141 | static const char v = 'V'; | |
142 | ||
143 | if (write(watchdog_fd, &v, 1) > 0) | |
144 | break; | |
145 | ||
146 | if (errno != EINTR) { | |
56f64d95 | 147 | log_error_errno(errno, "Failed to disarm watchdog timer: %m"); |
e96d6be7 LP |
148 | break; |
149 | } | |
150 | } | |
151 | } | |
152 | ||
03e334a1 | 153 | watchdog_fd = safe_close(watchdog_fd); |
e96d6be7 | 154 | } |