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