monitor: Fill in EpowPowerOff action

This action does the following:

1) Starts a service mode timer, which would allow the system to be
   serviced before anything happens.
2) On the expiration of that timer, it will:
   a) Set the thermal fault alert D-Bus property.  This will be used
      to send an EPOW alert to the host on IBM systems.
   b) Start the meltdown timer.
3) On the expiration of the meltdown timer, a hard power off will
   occur.  This timer cannot be canceled even if fans start behaving.

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I9434699b816b23b68c6d9d1e97283b4ab9befe4f
diff --git a/monitor/power_interface.hpp b/monitor/power_interface.hpp
index 1a339c5..e0a802d 100644
--- a/monitor/power_interface.hpp
+++ b/monitor/power_interface.hpp
@@ -1,5 +1,7 @@
 #pragma once
 
+#include "types.hpp"
+
 namespace phosphor::fan::monitor
 {
 
@@ -28,6 +30,13 @@
      * @brief Perform a hard power off
      */
     virtual void hardPowerOff() = 0;
+
+    /**
+     * @brief Sets the thermal alert D-Bus property
+     *
+     * @param[in] alert - The alert value
+     */
+    virtual void thermalAlert(bool alert) = 0;
 };
 
 /**
@@ -38,7 +47,7 @@
 class PowerInterface : public PowerInterfaceBase
 {
   public:
-    PowerInterface() = default;
+    PowerInterface() = delete;
     ~PowerInterface() = default;
     PowerInterface(const PowerInterface&) = delete;
     PowerInterface& operator=(const PowerInterface&) = delete;
@@ -46,6 +55,15 @@
     PowerInterface& operator=(PowerInterface&&) = delete;
 
     /**
+     * @brief Constructor
+     *
+     * @param[in] ThermalAlertObject& - The thermal alert D-Bus object
+     */
+    explicit PowerInterface(ThermalAlertObject& alertObject) :
+        _alert(alertObject)
+    {}
+
+    /**
      * @brief Perform a soft power off
      */
     void softPowerOff() override;
@@ -54,6 +72,22 @@
      * @brief Perform a hard power off
      */
     void hardPowerOff() override;
+
+    /**
+     * @brief Sets the thermal alert D-Bus property
+     *
+     * @param[in] alert - The alert value
+     */
+    void thermalAlert(bool alert) override
+    {
+        _alert.enabled(alert);
+    }
+
+  private:
+    /**
+     * @brief Reference to the thermal alert D-Bus object
+     */
+    ThermalAlertObject& _alert;
 };
 
 } // namespace phosphor::fan::monitor
diff --git a/monitor/power_off_action.hpp b/monitor/power_off_action.hpp
index f815a3b..f0e7531 100644
--- a/monitor/power_off_action.hpp
+++ b/monitor/power_off_action.hpp
@@ -302,9 +302,14 @@
 /**
  * @class EpowPowerOff
  *
- * Still TODO, but has a cancelable service mode delay followed
- * by an uncancelable meltdown delay followed by a hard power off, with
- * some sort of EPOW alert in there as well.
+ * This class is derived from the PowerOffAction class and does the following:
+ * 1) On start, the service mode timer is started.  This timer can be
+ *    canceled if the cause is no longer satisfied (fans work again).
+ * 2) When this timer expires:
+ *   a) The thermal alert D-Bus property is set, this can be used as
+ *      an EPOW alert to the host that a power off is imminent.
+ *   b) The meltdown timer is started.  This timer cannot be canceled,
+ *      and on expiration a hard power off occurs.
  */
 class EpowPowerOff : public PowerOffAction
 {
@@ -316,28 +321,133 @@
     EpowPowerOff(EpowPowerOff&&) = delete;
     EpowPowerOff& operator=(EpowPowerOff&&) = delete;
 
+    /**
+     * @brief Constructor
+     *
+     * @param[in] serviceModeDelay - The service mode timeout.
+     * @param[in] meltdownDelay - The meltdown delay timeout.
+     * @param[in] powerInterface - The object to use to do the power off
+     * @param[in] func - A function to call right before the power
+     *                   off occurs (after the delay).  May be
+     *                   empty if no function is necessary.
+     */
     EpowPowerOff(uint32_t serviceModeDelay, uint32_t meltdownDelay,
                  std::shared_ptr<PowerInterfaceBase> powerInterface,
                  PrePowerOffFunc func) :
         PowerOffAction("EPOW Power Off: " + std::to_string(serviceModeDelay) +
                            "s/" + std::to_string(meltdownDelay) + "s",
                        powerInterface, func),
-        _serviceModeDelay(serviceModeDelay), _meltdownDelay(meltdownDelay)
+        _serviceModeDelay(serviceModeDelay), _meltdownDelay(meltdownDelay),
+        _serviceModeTimer(
+            _event,
+            std::bind(std::mem_fn(&EpowPowerOff::serviceModeTimerExpired),
+                      this)),
+        _meltdownTimer(
+            _event,
+            std::bind(std::mem_fn(&EpowPowerOff::meltdownTimerExpired), this))
     {}
 
+    /**
+     * @brief Starts the service mode timer.
+     */
     void start() override
     {
-        // TODO
+        getLogger().log(
+            fmt::format("Action {}: Starting service mode timer", name()));
+
+        _serviceModeTimer.restartOnce(_serviceModeDelay);
     }
 
-    bool cancel(bool) override
+    /**
+     * @brief Called when the service mode timer expires.
+     *
+     * Sets the thermal alert D-Bus property and starts the
+     * meltdown timer.
+     */
+    void serviceModeTimerExpired()
     {
-        // TODO
+        getLogger().log(fmt::format(
+            "Action {}: Service mode timer expired, starting meltdown timer",
+            name()));
+
+        _powerIface->thermalAlert(true);
+        _meltdownTimer.restartOnce(_meltdownDelay);
+    }
+
+    /**
+     * @brief Called when the meltdown timer expires.
+     *
+     * Executes a hard power off.
+     */
+    void meltdownTimerExpired()
+    {
+        getLogger().log(fmt::format(
+            "Action {}: Meltdown timer expired, executing hard power off",
+            name()));
+
+        if (_prePowerOffFunc)
+        {
+            _prePowerOffFunc();
+        }
+
+        _powerIface->hardPowerOff();
+    }
+
+    /**
+     * @brief Attempts to cancel the action
+     *
+     * The service mode timer can be canceled.  The meltdown
+     * timer cannot.
+     *
+     * @param[in] force - To force the cancel (like if the
+     *                    system powers off).
+     *
+     * @return bool - If the cancel was successful
+     */
+    bool cancel(bool force) override
+    {
+        if (_serviceModeTimer.isEnabled())
+        {
+            _serviceModeTimer.setEnabled(false);
+        }
+
+        if (_meltdownTimer.isEnabled())
+        {
+            if (force)
+            {
+                _meltdownTimer.setEnabled(false);
+            }
+            else
+            {
+                getLogger().log("Cannot cancel running meltdown timer");
+                return false;
+            }
+        }
         return true;
     }
 
   private:
+    /**
+     * @brief The number of seconds to wait until starting the uncancelable
+     *        meltdown timer.
+     */
     std::chrono::seconds _serviceModeDelay;
+
+    /**
+     * @brief The number of seconds to wait after the service mode
+     *        timer expires before a hard power off will occur.
+     */
     std::chrono::seconds _meltdownDelay;
+
+    /**
+     * @brief The service mode timer.
+     */
+    sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic>
+        _serviceModeTimer;
+
+    /**
+     * @brief The meltdown timer.
+     */
+    sdeventplus::utility::Timer<sdeventplus::ClockId::Monotonic> _meltdownTimer;
 };
 } // namespace phosphor::fan::monitor
diff --git a/monitor/system.cpp b/monitor/system.cpp
index dbb9b73..4c30f9f 100644
--- a/monitor/system.cpp
+++ b/monitor/system.cpp
@@ -189,7 +189,7 @@
 {
 #ifdef MONITOR_USE_JSON
     std::shared_ptr<PowerInterfaceBase> powerInterface =
-        std::make_shared<PowerInterface>();
+        std::make_shared<PowerInterface>(_thermalAlert);
 
     PowerOffAction::PrePowerOffFunc func =
         std::bind(std::mem_fn(&System::logShutdownError), this);
diff --git a/monitor/test/mock_power_interface.hpp b/monitor/test/mock_power_interface.hpp
index c070182..6801751 100644
--- a/monitor/test/mock_power_interface.hpp
+++ b/monitor/test/mock_power_interface.hpp
@@ -12,6 +12,7 @@
   public:
     MOCK_METHOD(void, softPowerOff, (), (override));
     MOCK_METHOD(void, hardPowerOff, (), (override));
+    MOCK_METHOD(void, thermalAlert, (bool), (override));
 };
 
 } // namespace phosphor::fan::monitor