From 84f3e76efb5ecba117bea7b0ad477c92042c9829 Mon Sep 17 00:00:00 2001
From: James Feist <james.feist@linux.intel.com>
Date: Mon, 17 Jun 2019 12:00:58 -0700
Subject: [PATCH] Customize phosphor-watchdog for Intel platforms

This patch adds various changes to phosphor-watchdog that are
required for compatibility with Intel platforms.

   1. Add Redfish messages for watchdog timeout and pre-interrupt
   2. Use dbus properties for power control insted of service files
   3. Use host status to enable/disable watchdog
   4. Set preTimeoutInterruptOccurFlag
   5. Assign watchdog cause for correct reset cause reporting
   6. Add NMI Pre-Interrupt support for IPMI watchdog timer.

Signed-off-by: James Feist <james.feist@linux.intel.com>
Signed-off-by: Ren Yu <yux.ren@intel.com>
Signed-off-by: Yong Li <yong.b.li@linux.intel.com>
Signed-off-by: Jason M. Bills <jason.m.bills@linux.intel.com>
Signed-off-by: Johnathan Mantey <johnathanx.mantey@intel.com>
Signed-off-by: Sunita Kumari <sunitax.kumari@intel.com>
Upstream-Status: Pending
---
 src/watchdog.cpp | 230 ++++++++++++++++++++++++++++++++++++++++++++---
 src/watchdog.hpp |  23 ++++-
 2 files changed, 242 insertions(+), 11 deletions(-)

diff --git a/src/watchdog.cpp b/src/watchdog.cpp
index 90a840e..ae9f4e3 100644
--- a/src/watchdog.cpp
+++ b/src/watchdog.cpp
@@ -1,9 +1,12 @@
 #include "watchdog.hpp"
 
+#include <systemd/sd-journal.h>
+
 #include <phosphor-logging/elog.hpp>
 #include <phosphor-logging/log.hpp>
 #include <sdbusplus/exception.hpp>
 #include <xyz/openbmc_project/Common/error.hpp>
+#include <xyz/openbmc_project/State/Host/server.hpp>
 
 #include <algorithm>
 #include <chrono>
@@ -19,10 +22,86 @@ using namespace phosphor::logging;
 
 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";
+const static constexpr char* currentHostState = "CurrentHostState";
+const static constexpr char* hostStatusOff =
+    "xyz.openbmc_project.State.Host.HostState.Off";
+
+const static constexpr char* actionDescription = " due to Watchdog timeout";
+const static constexpr char* hardResetDescription = "Hard Reset - System reset";
+const static constexpr char* powerOffDescription =
+    "Power Down - System power down";
+const static constexpr char* powerCycleDescription =
+    "Power Cycle - System power cycle";
+const static constexpr char* timerExpiredDescription = "Timer expired";
+
+const static constexpr char* preInterruptActionNone =
+    "xyz.openbmc_project.State.Watchdog.PreTimeoutInterruptAction.None";
+
+const static constexpr char* preInterruptDescriptionSMI = "SMI";
+const static constexpr char* preInterruptDescriptionNMI = "NMI";
+const static constexpr char* preInterruptDescriptionMI = "Messaging Interrupt";
+
+const static constexpr char* reservedDescription = "Reserved";
+
+const static constexpr char* timerUseDescriptionBIOSFRB2 = "BIOS FRB2";
+const static constexpr char* timerUseDescriptionBIOSPOST = "BIOS/POST";
+const static constexpr char* timerUseDescriptionOSLoad = "OSLoad";
+const static constexpr char* timerUseDescriptionSMSOS = "SMS/OS";
+const static constexpr char* timerUseDescriptionOEM = "OEM";
+
+namespace restart
+{
+static constexpr const char* busName =
+    "xyz.openbmc_project.Control.Host.RestartCause";
+static constexpr const char* path =
+    "/xyz/openbmc_project/control/host0/restart_cause";
+static constexpr const char* interface =
+    "xyz.openbmc_project.Control.Host.RestartCause";
+static constexpr const char* property = "RequestedRestartCause";
+} // namespace restart
+
+// chassis state manager service
+namespace chassis
+{
+static constexpr const char* busName = "xyz.openbmc_project.State.Chassis";
+static constexpr const char* path = "/xyz/openbmc_project/state/chassis0";
+static constexpr const char* interface = "xyz.openbmc_project.State.Chassis";
+static constexpr const char* request = "RequestedPowerTransition";
+} // namespace chassis
+
+namespace host
+{
+static constexpr const char* busName = "xyz.openbmc_project.State.Host";
+static constexpr const char* path = "/xyz/openbmc_project/state/host0";
+static constexpr const char* interface = "xyz.openbmc_project.State.Host";
+static constexpr const char* request = "RequestedHostTransition";
+} // namespace host
+
+namespace nmi
+{
+static constexpr const char* busName = "xyz.openbmc_project.Control.Host.NMI";
+static constexpr const char* path = "/xyz/openbmc_project/control/host0/nmi";
+static constexpr const char* interface = "xyz.openbmc_project.Control.Host.NMI";
+static constexpr const char* request = "NMI";
+
+} // namespace nmi
+
+void Watchdog::powerStateChangedHandler(
+    const std::map<std::string, std::variant<std::string>>& props)
+{
+    const auto iter = props.find(currentHostState);
+    if (iter != props.end())
+    {
+        const std::string* powerState = std::get_if<std::string>(&iter->second);
+        if (powerState && (*powerState == hostStatusOff))
+        {
+            if (timerEnabled())
+            {
+                enabled(false);
+            }
+        }
+    }
+}
 
 void Watchdog::resetTimeRemaining(bool enableWatchdog)
 {
@@ -108,13 +187,111 @@ uint64_t Watchdog::interval(uint64_t value)
 // Optional callback function on timer expiration
 void Watchdog::timeOutHandler()
 {
+    PreTimeoutInterruptAction preTimeoutInterruptAction = preTimeoutInterrupt();
+    std::string preInterruptActionMessageArgs{};
+
     Action action = expireAction();
+    std::string actionMessageArgs{};
+
+    expiredTimerUse(currentTimerUse());
+
+    TimerUse timeUser = expiredTimerUse();
+    std::string timeUserMessage{};
+
     if (!this->enabled())
     {
         action = fallback->action;
     }
 
-    expiredTimerUse(currentTimerUse());
+    switch (timeUser)
+    {
+        case Watchdog::TimerUse::BIOSFRB2:
+            timeUserMessage = timerUseDescriptionBIOSFRB2;
+            break;
+        case Watchdog::TimerUse::BIOSPOST:
+            timeUserMessage = timerUseDescriptionBIOSPOST;
+            break;
+        case Watchdog::TimerUse::OSLoad:
+            timeUserMessage = timerUseDescriptionOSLoad;
+            break;
+        case Watchdog::TimerUse::SMSOS:
+            timeUserMessage = timerUseDescriptionSMSOS;
+            break;
+        case Watchdog::TimerUse::OEM:
+            timeUserMessage = timerUseDescriptionOEM;
+            break;
+        default:
+            timeUserMessage = reservedDescription;
+            break;
+    }
+
+    switch (action)
+    {
+        case Watchdog::Action::HardReset:
+            actionMessageArgs = std::string(hardResetDescription) +
+                                std::string(actionDescription);
+            break;
+        case Watchdog::Action::PowerOff:
+            actionMessageArgs = std::string(powerOffDescription) +
+                                std::string(actionDescription);
+            break;
+        case Watchdog::Action::PowerCycle:
+            actionMessageArgs = std::string(powerCycleDescription) +
+                                std::string(actionDescription);
+            break;
+        case Watchdog::Action::None:
+            actionMessageArgs = timerExpiredDescription;
+            break;
+        default:
+            actionMessageArgs = reservedDescription;
+            break;
+    }
+
+    // Log into redfish event log
+    sd_journal_send("MESSAGE=IPMIWatchdog: Timed out ACTION=%s",
+                    convertForMessage(action).c_str(), "PRIORITY=%i", LOG_INFO,
+                    "REDFISH_MESSAGE_ID=%s", "OpenBMC.0.1.IPMIWatchdog",
+                    "REDFISH_MESSAGE_ARGS=%s. timer use: %s",
+                    actionMessageArgs.c_str(), timeUserMessage.c_str(), NULL);
+
+    switch (preTimeoutInterruptAction)
+    {
+        case Watchdog::PreTimeoutInterruptAction::SMI:
+            preInterruptActionMessageArgs = preInterruptDescriptionSMI;
+            break;
+        case Watchdog::PreTimeoutInterruptAction::NMI:
+            preInterruptActionMessageArgs = preInterruptDescriptionNMI;
+            break;
+        case Watchdog::PreTimeoutInterruptAction::MI:
+            preInterruptActionMessageArgs = preInterruptDescriptionMI;
+            break;
+        default:
+            preInterruptActionMessageArgs = reservedDescription;
+            break;
+    }
+
+    if (preInterruptActionNone != convertForMessage(preTimeoutInterruptAction))
+    {
+        preTimeoutInterruptOccurFlag(true);
+
+        sd_journal_send("MESSAGE=IPMIWatchdog: Pre Timed out Interrupt=%s",
+                        convertForMessage(preTimeoutInterruptAction).c_str(),
+                        "PRIORITY=%i", LOG_INFO, "REDFISH_MESSAGE_ID=%s",
+                        "OpenBMC.0.1.IPMIWatchdog",
+                        "REDFISH_MESSAGE_ARGS=Timer interrupt - %s due to "
+                        "Watchdog timeout. timer use: %s",
+                        preInterruptActionMessageArgs.c_str(),
+                        timeUserMessage.c_str(), NULL);
+
+        if (preTimeoutInterruptAction ==
+            Watchdog::PreTimeoutInterruptAction::NMI)
+        {
+            sdbusplus::message::message preTimeoutInterruptHandler;
+            preTimeoutInterruptHandler = bus.new_method_call(
+                nmi::busName, nmi::path, nmi::interface, nmi::request);
+            bus.call_noreply(preTimeoutInterruptHandler);
+        }
+    }
 
     auto target = actionTargetMap.find(action);
     if (target == actionTargetMap.end())
@@ -134,12 +311,45 @@ void Watchdog::timeOutHandler()
 
         try
         {
-            auto method = bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_ROOT,
-                                              SYSTEMD_INTERFACE, "StartUnit");
-            method.append(target->second);
-            method.append("replace");
+            sdbusplus::message::message method;
+            if (action == Watchdog::Action::HardReset)
+            {
+                auto method = bus.new_method_call(
+                    restart::busName, restart::path,
+                    "org.freedesktop.DBus.Properties", "Set");
+                method.append(
+                    restart::interface, restart::property,
+                    std::variant<std::string>("xyz.openbmc_project.State.Host."
+                                              "RestartCause.WatchdogTimer"));
+                bus.call_noreply(method);
 
-            bus.call_noreply(method);
+                method = bus.new_method_call(host::busName, host::path,
+                                             "org.freedesktop.DBus.Properties",
+                                             "Set");
+                method.append(host::interface, host::request,
+                              std::variant<std::string>(target->second));
+                bus.call_noreply(method);
+            }
+            else
+            {
+                if (action == Watchdog::Action::PowerCycle)
+                {
+                    auto method = bus.new_method_call(
+                        restart::busName, restart::path,
+                        "org.freedesktop.DBus.Properties", "Set");
+                    method.append(restart::interface, restart::property,
+                                  std::variant<std::string>(
+                                      "xyz.openbmc_project.State.Host."
+                                      "RestartCause.WatchdogTimer"));
+                    bus.call_noreply(method);
+                }
+                method = bus.new_method_call(chassis::busName, chassis::path,
+                                             "org.freedesktop.DBus.Properties",
+                                             "Set");
+                method.append(chassis::interface, chassis::request,
+                              std::variant<std::string>(target->second));
+                bus.call_noreply(method);
+            }
         }
         catch (const sdbusplus::exception_t& e)
         {
diff --git a/src/watchdog.hpp b/src/watchdog.hpp
index da1e05a..0e88b50 100644
--- a/src/watchdog.hpp
+++ b/src/watchdog.hpp
@@ -75,7 +75,18 @@ class Watchdog : public WatchdogInherits
         bus(bus), actionTargetMap(std::move(actionTargetMap)),
         fallback(fallback), minInterval(minInterval),
         timer(event, std::bind(&Watchdog::timeOutHandler, this)),
-        objPath(objPath), exitAfterTimeout(exitAfterTimeout)
+        objPath(objPath), exitAfterTimeout(exitAfterTimeout),
+        powerStateChangedSignal(
+            bus,
+            sdbusplus::bus::match::rules::propertiesChanged(
+                "/xyz/openbmc_project/state/host0",
+                "xyz.openbmc_project.State.Host"),
+            [this](sdbusplus::message::message& msg) {
+                std::string objectName;
+                std::map<std::string, std::variant<std::string>> props;
+                msg.read(objectName, props);
+                powerStateChangedHandler(props);
+            })
     {
         // Use default if passed in otherwise just use default that comes
         // with object
@@ -92,6 +103,12 @@ class Watchdog : public WatchdogInherits
         tryFallbackOrDisable();
     }
 
+    /** @brief Disable watchdog when power status change meet
+     *         the specific requirement
+     */
+    void powerStateChangedHandler(
+        const std::map<std::string, std::variant<std::string>>& props);
+
     /** @brief Resets the TimeRemaining to the configured Interval
      *         Optionally enables the watchdog.
      *
@@ -180,6 +197,10 @@ class Watchdog : public WatchdogInherits
     /** @brief Contained timer object */
     sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> timer;
 
+    /** @brief Optional Callback handler when power status change meet
+     * the specific requirement */
+    sdbusplus::bus::match_t powerStateChangedSignal;
+
     /** @brief Optional Callback handler on timer expirartion */
     void timeOutHandler();
 
-- 
2.25.1

