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