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