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