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