| #include "watchdog.hpp" |
| |
| #include <algorithm> |
| #include <chrono> |
| #include <phosphor-logging/elog.hpp> |
| #include <phosphor-logging/log.hpp> |
| #include <sdbusplus/exception.hpp> |
| #include <string_view> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| |
| namespace phosphor |
| { |
| namespace watchdog |
| { |
| using namespace std::chrono; |
| using namespace std::chrono_literals; |
| using namespace phosphor::logging; |
| |
| using sdbusplus::exception::SdBusError; |
| using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; |
| |
| // systemd service to kick start a target. |
| constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; |
| constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1"; |
| constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; |
| |
| void Watchdog::resetTimeRemaining(bool enableWatchdog) |
| { |
| timeRemaining(interval()); |
| if (enableWatchdog) |
| { |
| enabled(true); |
| } |
| } |
| |
| // Enable or disable watchdog |
| bool Watchdog::enabled(bool value) |
| { |
| if (!value) |
| { |
| // Make sure we accurately reflect our enabled state to the |
| // tryFallbackOrDisable() call |
| WatchdogInherits::enabled(value); |
| |
| // Attempt to fallback or disable our timer if needed |
| tryFallbackOrDisable(); |
| |
| return false; |
| } |
| else if (!this->enabled()) |
| { |
| auto interval_ms = this->interval(); |
| timer.restart(milliseconds(interval_ms)); |
| log<level::INFO>("watchdog: enabled and started", |
| entry("INTERVAL=%llu", interval_ms)); |
| } |
| |
| return WatchdogInherits::enabled(value); |
| } |
| |
| // Get the remaining time before timer expires. |
| // If the timer is disabled, returns 0 |
| uint64_t Watchdog::timeRemaining() const |
| { |
| // timer may have already expired and disabled |
| if (!timerEnabled()) |
| { |
| return 0; |
| } |
| |
| return duration_cast<milliseconds>(timer.getRemaining()).count(); |
| } |
| |
| // Reset the timer to a new expiration value |
| uint64_t Watchdog::timeRemaining(uint64_t value) |
| { |
| if (!timerEnabled()) |
| { |
| // We don't need to update the timer because it is off |
| return 0; |
| } |
| |
| if (this->enabled()) |
| { |
| // Update interval to minInterval if applicable |
| value = std::max(value, minInterval); |
| } |
| else |
| { |
| // Having a timer but not displaying an enabled value means we |
| // are inside of the fallback |
| value = fallback->interval; |
| } |
| |
| // Update new expiration |
| timer.setRemaining(milliseconds(value)); |
| |
| // Update Base class data. |
| return WatchdogInherits::timeRemaining(value); |
| } |
| |
| // Set value of Interval |
| uint64_t Watchdog::interval(uint64_t value) |
| { |
| return WatchdogInherits::interval(std::max(value, minInterval)); |
| } |
| |
| // Optional callback function on timer expiration |
| void Watchdog::timeOutHandler() |
| { |
| Action action = expireAction(); |
| if (!this->enabled()) |
| { |
| action = fallback->action; |
| } |
| |
| expiredTimerUse(currentTimerUse()); |
| |
| auto target = actionTargetMap.find(action); |
| if (target == actionTargetMap.end()) |
| { |
| log<level::INFO>("watchdog: Timed out with no target", |
| entry("ACTION=%s", convertForMessage(action).c_str()), |
| entry("TIMER_USE=%s", |
| convertForMessage(expiredTimerUse()).c_str())); |
| } |
| else |
| { |
| log<level::INFO>( |
| "watchdog: Timed out", |
| entry("ACTION=%s", convertForMessage(action).c_str()), |
| entry("TIMER_USE=%s", convertForMessage(expiredTimerUse()).c_str()), |
| entry("TARGET=%s", target->second.c_str())); |
| |
| try |
| { |
| auto signal = bus.new_signal( |
| objPath.data(), "xyz.openbmc_project.Watchdog", "Timeout"); |
| signal.append(convertForMessage(action).c_str()); |
| signal.signal_send(); |
| } |
| catch (const SdBusError& e) |
| { |
| log<level::ERR>("watchdog: failed to send timeout signal", |
| entry("ERROR=%s", e.what())); |
| } |
| |
| try |
| { |
| auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT, |
| SYSTEMD_INTERFACE, "StartUnit"); |
| method.append(target->second); |
| method.append("replace"); |
| |
| bus.call_noreply(method); |
| } |
| catch (const SdBusError& e) |
| { |
| log<level::ERR>("watchdog: Failed to start unit", |
| entry("TARGET=%s", target->second.c_str()), |
| entry("ERROR=%s", e.what())); |
| commit<InternalFailure>(); |
| } |
| } |
| |
| tryFallbackOrDisable(); |
| } |
| |
| void Watchdog::tryFallbackOrDisable() |
| { |
| // We only re-arm the watchdog if we were already enabled and have |
| // a possible fallback |
| if (fallback && (fallback->always || this->enabled())) |
| { |
| auto interval_ms = fallback->interval; |
| timer.restart(milliseconds(interval_ms)); |
| log<level::INFO>("watchdog: falling back", |
| entry("INTERVAL=%llu", interval_ms)); |
| } |
| else if (timerEnabled()) |
| { |
| timer.setEnabled(false); |
| |
| log<level::INFO>("watchdog: disabled"); |
| } |
| |
| // Make sure we accurately reflect our enabled state to the |
| // dbus interface. |
| WatchdogInherits::enabled(false); |
| } |
| |
| } // namespace watchdog |
| } // namespace phosphor |