1 #include "watchdog.hpp"
2
3 #include <phosphor-logging/elog.hpp>
4 #include <phosphor-logging/log.hpp>
5 #include <sdbusplus/exception.hpp>
6 #include <xyz/openbmc_project/Common/error.hpp>
7
8 #include <algorithm>
9 #include <chrono>
10 #include <string_view>
11
12 namespace phosphor
13 {
14 namespace watchdog
15 {
16 using namespace std::chrono;
17 using namespace std::chrono_literals;
18 using namespace phosphor::logging;
19
20 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
21
22 // systemd service to kick start a target.
23 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
24 constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1";
25 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
26
resetTimeRemaining(bool enableWatchdog)27 void Watchdog::resetTimeRemaining(bool enableWatchdog)
28 {
29 timeRemaining(interval());
30 if (enableWatchdog)
31 {
32 enabled(true);
33 }
34 }
35
36 // Enable or disable watchdog
enabled(bool value)37 bool Watchdog::enabled(bool value)
38 {
39 if (!value)
40 {
41 // Make sure we accurately reflect our enabled state to the
42 // tryFallbackOrDisable() call
43 WatchdogInherits::enabled(value);
44
45 // Attempt to fallback or disable our timer if needed
46 tryFallbackOrDisable();
47
48 return false;
49 }
50 else if (!this->enabled())
51 {
52 auto interval_ms = this->interval();
53 timer.restart(milliseconds(interval_ms));
54 log<level::INFO>("watchdog: enabled and started",
55 entry("INTERVAL=%llu", interval_ms));
56 }
57
58 return WatchdogInherits::enabled(value);
59 }
60
61 // Get the remaining time before timer expires.
62 // If the timer is disabled, returns 0
timeRemaining() const63 uint64_t Watchdog::timeRemaining() const
64 {
65 // timer may have already expired and disabled
66 if (!timerEnabled())
67 {
68 return 0;
69 }
70
71 return duration_cast<milliseconds>(timer.getRemaining()).count();
72 }
73
74 // Reset the timer to a new expiration value
timeRemaining(uint64_t value)75 uint64_t Watchdog::timeRemaining(uint64_t value)
76 {
77 if (!timerEnabled())
78 {
79 // We don't need to update the timer because it is off
80 return 0;
81 }
82
83 if (this->enabled())
84 {
85 // Update interval to minInterval if applicable
86 value = std::max(value, minInterval);
87 }
88 else
89 {
90 // Having a timer but not displaying an enabled value means we
91 // are inside of the fallback
92 value = fallback->interval;
93 }
94
95 // Update new expiration
96 timer.setRemaining(milliseconds(value));
97
98 // Update Base class data.
99 return WatchdogInherits::timeRemaining(value);
100 }
101
102 // Set value of Interval
interval(uint64_t value)103 uint64_t Watchdog::interval(uint64_t value)
104 {
105 return WatchdogInherits::interval(std::max(value, minInterval));
106 }
107
108 // Optional callback function on timer expiration
timeOutHandler()109 void Watchdog::timeOutHandler()
110 {
111 Action action = expireAction();
112 if (!this->enabled())
113 {
114 action = fallback->action;
115 }
116
117 expiredTimerUse(currentTimerUse());
118
119 auto target = actionTargetMap.find(action);
120 if (target == actionTargetMap.end())
121 {
122 log<level::INFO>("watchdog: Timed out with no target",
123 entry("ACTION=%s", convertForMessage(action).c_str()),
124 entry("TIMER_USE=%s",
125 convertForMessage(expiredTimerUse()).c_str()));
126 }
127 else
128 {
129 log<level::INFO>(
130 "watchdog: Timed out",
131 entry("ACTION=%s", convertForMessage(action).c_str()),
132 entry("TIMER_USE=%s", convertForMessage(expiredTimerUse()).c_str()),
133 entry("TARGET=%s", target->second.c_str()));
134
135 try
136 {
137 auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
138 SYSTEMD_INTERFACE, "StartUnit");
139 method.append(target->second);
140 method.append("replace");
141
142 bus.call_noreply(method);
143 }
144 catch (const sdbusplus::exception_t& e)
145 {
146 log<level::ERR>("watchdog: Failed to start unit",
147 entry("TARGET=%s", target->second.c_str()),
148 entry("ERROR=%s", e.what()));
149 commit<InternalFailure>();
150 }
151 }
152 try
153 {
154 auto signal = bus.new_signal(objPath.data(),
155 "xyz.openbmc_project.Watchdog", "Timeout");
156 signal.append(convertForMessage(action).c_str());
157 signal.signal_send();
158 }
159 catch (const sdbusplus::exception_t& e)
160 {
161 log<level::ERR>("watchdog: failed to send timeout signal",
162 entry("ERROR=%s", e.what()));
163 }
164
165 if (exitAfterTimeout)
166 {
167 timer.get_event().exit(0);
168 }
169
170 tryFallbackOrDisable();
171 }
172
tryFallbackOrDisable()173 void Watchdog::tryFallbackOrDisable()
174 {
175 // We only re-arm the watchdog if we were already enabled and have
176 // a possible fallback
177 if (fallback && (fallback->always || this->enabled()))
178 {
179 auto interval_ms = fallback->interval;
180 timer.restart(milliseconds(interval_ms));
181 log<level::INFO>("watchdog: falling back",
182 entry("INTERVAL=%llu", interval_ms));
183 }
184 else if (timerEnabled())
185 {
186 timer.setEnabled(false);
187
188 log<level::INFO>("watchdog: disabled");
189 }
190
191 // Make sure we accurately reflect our enabled state to the
192 // dbus interface.
193 WatchdogInherits::enabled(false);
194 }
195
196 } // namespace watchdog
197 } // namespace phosphor
198