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::exception::SdBusError;
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 
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
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
63 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
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
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
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 signal = bus.new_signal(
138                 objPath.data(), "xyz.openbmc_project.Watchdog", "Timeout");
139             signal.append(convertForMessage(action).c_str());
140             signal.signal_send();
141         }
142         catch (const SdBusError& e)
143         {
144             log<level::ERR>("watchdog: failed to send timeout signal",
145                             entry("ERROR=%s", e.what()));
146         }
147 
148         try
149         {
150             auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
151                                               SYSTEMD_INTERFACE, "StartUnit");
152             method.append(target->second);
153             method.append("replace");
154 
155             bus.call_noreply(method);
156         }
157         catch (const SdBusError& e)
158         {
159             log<level::ERR>("watchdog: Failed to start unit",
160                             entry("TARGET=%s", target->second.c_str()),
161                             entry("ERROR=%s", e.what()));
162             commit<InternalFailure>();
163         }
164     }
165 
166     tryFallbackOrDisable();
167 }
168 
169 void Watchdog::tryFallbackOrDisable()
170 {
171     // We only re-arm the watchdog if we were already enabled and have
172     // a possible fallback
173     if (fallback && (fallback->always || this->enabled()))
174     {
175         auto interval_ms = fallback->interval;
176         timer.restart(milliseconds(interval_ms));
177         log<level::INFO>("watchdog: falling back",
178                          entry("INTERVAL=%llu", interval_ms));
179     }
180     else if (timerEnabled())
181     {
182         timer.setEnabled(false);
183 
184         log<level::INFO>("watchdog: disabled");
185     }
186 
187     // Make sure we accurately reflect our enabled state to the
188     // dbus interface.
189     WatchdogInherits::enabled(false);
190 }
191 
192 } // namespace watchdog
193 } // namespace phosphor
194