BMC:  Fan control for Idle Power saver

Read IPS active from sysfs and push onto dBus IPS properties
Tested: enable/disable IPS on WEB, occ instrospect ips
Signed-off-by: Sheldon Bailey <baileysh@us.ibm.com>
Change-Id: I12935a4c9f82bf004b1311e477f73ed8b1401a91
diff --git a/occ_device.hpp b/occ_device.hpp
index 3c11d13..5b66bf5 100644
--- a/occ_device.hpp
+++ b/occ_device.hpp
@@ -6,6 +6,7 @@
 #include "occ_events.hpp"
 #include "occ_ffdc.hpp"
 #include "occ_presence.hpp"
+#include "powermode.hpp"
 
 #include <org/open_power/OCC/Device/error.hpp>
 
@@ -45,7 +46,11 @@
      *  @param[in] instance - OCC instance number
      */
     Device(EventPtr& event, const fs::path& path, Manager& manager,
-           Status& status, unsigned int instance = 0) :
+           Status& status,
+#ifdef POWER10
+           std::unique_ptr<powermode::PowerMode>& powerModeRef,
+#endif
+           unsigned int instance = 0) :
         config(getPathBack(path)),
         devPath(path), instance(instance), statusObject(status),
         managerObject(manager),
@@ -78,6 +83,10 @@
         throttleMemTemp(event, path / "occ_mem_throttle",
                         std::bind(std::mem_fn(&Device::throttleMemTempCallback),
                                   this, std::placeholders::_1))
+#ifdef POWER10
+        ,
+        pmode(powerModeRef)
+#endif
     {
         // Nothing to do here
     }
@@ -129,6 +138,13 @@
             throttleProcTemp.addWatch(poll);
         }
 
+#ifdef POWER10
+        if (master())
+        {
+            pmode->addIpsWatch(poll);
+        }
+#endif
+
         throttleProcPower.addWatch(poll);
         throttleMemTemp.addWatch(poll);
 
@@ -164,6 +180,12 @@
         throttleMemTemp.removeWatch();
         throttleProcPower.removeWatch();
         throttleProcTemp.removeWatch();
+#ifdef POWER10
+        if (master())
+        {
+            pmode->removeIpsWatch();
+        }
+#endif
     }
 
     /** @brief Starts to watch how many OCCs are present on the master */
@@ -229,6 +251,11 @@
     Error throttleProcPower;
     Error throttleMemTemp;
 
+#ifdef POWER10
+    /** @brief OCC PowerMode object */
+    std::unique_ptr<powermode::PowerMode>& pmode;
+#endif
+
     /** @brief file writer to achieve bind and unbind
      *
      *  @param[in] filename - Name of file to be written
diff --git a/occ_manager.cpp b/occ_manager.cpp
index f0c59bb..c5038de 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -247,7 +247,12 @@
     {
         // Create the power mode object
         pmode = std::make_unique<powermode::PowerMode>(
-            *this, powermode::PMODE_PATH, powermode::PIPS_PATH);
+            *this, powermode::PMODE_PATH, powermode::PIPS_PATH
+#ifdef POWER10
+            ,
+            event
+#endif
+        );
     }
 #endif
 
diff --git a/occ_status.hpp b/occ_status.hpp
index ef8fad0..9ae8fe7 100644
--- a/occ_status.hpp
+++ b/occ_status.hpp
@@ -100,7 +100,11 @@
                fs::path(DEV_PATH) /
                    fs::path(sysfsName + "." + std::to_string(instance + 1)),
 #endif
-               managerRef, *this, instance),
+               managerRef, *this,
+#ifdef POWER10
+               powerModeRef,
+#endif
+               instance),
         hostControlSignal(
             utils::getBus(),
             sdbusRule::type::signal() + sdbusRule::member("CommandComplete") +
diff --git a/powermode.cpp b/powermode.cpp
index ed17096..f091088 100644
--- a/powermode.cpp
+++ b/powermode.cpp
@@ -1,9 +1,15 @@
 #include "powermode.hpp"
 
+#include "elog-errors.hpp"
+
+#include <fcntl.h>
 #include <fmt/core.h>
+#include <sys/ioctl.h>
 
 #include <com/ibm/Host/Target/server.hpp>
+#include <org/open_power/OCC/Device/error.hpp>
 #include <phosphor-logging/log.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
 #include <xyz/openbmc_project/Control/Power/Mode/server.hpp>
 
 #include <cassert>
@@ -19,6 +25,7 @@
 
 using namespace phosphor::logging;
 using namespace std::literals::string_literals;
+using namespace sdbusplus::org::open_power::OCC::Device::Error;
 
 using Mode = sdbusplus::xyz::openbmc_project::Control::Power::server::Mode;
 
@@ -867,6 +874,194 @@
     return updateDbusIPS(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime);
 }
 
+#ifdef POWER10
+
+// Starts to watch for IPS active state changes.
+bool PowerMode::openIpsFile()
+{
+    bool rc = true;
+    fd = open(ipsStatusFile.c_str(), O_RDONLY | O_NONBLOCK);
+    const int open_errno = errno;
+    if (fd < 0)
+    {
+        log<level::ERR>(fmt::format("openIpsFile Error({})={} : File={}",
+                                    open_errno, strerror(open_errno),
+                                    ipsStatusFile.c_str())
+                            .c_str());
+
+        close(fd);
+
+        using namespace sdbusplus::org::open_power::OCC::Device::Error;
+        report<OpenFailure>(
+            phosphor::logging::org::open_power::OCC::Device::OpenFailure::
+                CALLOUT_ERRNO(open_errno),
+            phosphor::logging::org::open_power::OCC::Device::OpenFailure::
+                CALLOUT_DEVICE_PATH(ipsStatusFile.c_str()));
+
+        // We are no longer watching the error
+        active(false);
+
+        watching = false;
+        rc = false;
+        // NOTE: this will leave the system not reporting IPS active state to
+        // Fan Controls, Until an APP reload, or IPL and we will attempt again.
+    }
+    return rc;
+}
+
+// Starts to watch for IPS active state changes.
+void PowerMode::addIpsWatch(bool poll)
+{
+    // open file and register callback on file if we are not currently watching,
+    // and if poll=true, and if we are the master.
+    if ((!watching) && poll)
+    {
+        //  Open the file
+        if (openIpsFile())
+        {
+            // register the callback handler which sets 'watching'
+            registerIpsStatusCallBack();
+        }
+    }
+}
+
+// Stops watching for IPS active state changes.
+void PowerMode::removeIpsWatch()
+{
+    //  NOTE: we want to remove event, close file, and IPS active false no
+    //  matter what the 'watching' flags is set to.
+
+    // We are no longer watching the error
+    active(false);
+
+    watching = false;
+
+    // Close file
+    close(fd);
+
+    // clears sourcePtr in the event source.
+    eventSource.reset();
+}
+
+// Attaches the FD to event loop and registers the callback handler
+void PowerMode::registerIpsStatusCallBack()
+{
+    decltype(eventSource.get()) sourcePtr = nullptr;
+
+    auto r = sd_event_add_io(event.get(), &sourcePtr, fd, EPOLLPRI | EPOLLERR,
+                             ipsStatusCallBack, this);
+    if (r < 0)
+    {
+        log<level::ERR>(fmt::format("sd_event_add_io: Error({})={} : File={}",
+                                    r, strerror(-r), ipsStatusFile.c_str())
+                            .c_str());
+
+        using InternalFailure =
+            sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+        report<InternalFailure>();
+
+        removeIpsWatch();
+        // NOTE: this will leave the system not reporting IPS active state to
+        // Fan Controls, Until an APP reload, or IPL and we will attempt again.
+    }
+    else
+    {
+        // puts sourcePtr in the event source.
+        eventSource.reset(sourcePtr);
+        // Set we are watching the error
+        watching = true;
+    }
+}
+
+// Static function to redirect to non static analyze event function to be
+// able to read file and push onto dBus.
+int PowerMode::ipsStatusCallBack(sd_event_source* /*es*/, int /*fd*/,
+                                 uint32_t /*revents*/, void* userData)
+{
+    auto pmode = static_cast<PowerMode*>(userData);
+    pmode->analyzeIpsEvent();
+    return 0;
+}
+
+// Function to Read SysFs file change on IPS state and push on dBus.
+void PowerMode::analyzeIpsEvent()
+{
+    // Need to seek to START, else the poll returns immediately telling
+    // there is data to be read. if not done this floods the system.
+    auto r = lseek(fd, 0, SEEK_SET);
+    const int open_errno = errno;
+    if (r < 0)
+    {
+        // NOTE: upon file access error we can not just re-open file, we have to
+        // remove and add to watch.
+        removeIpsWatch();
+        addIpsWatch(true);
+    }
+
+    // if we are 'watching' that is the file seek, or the re-open passed.. we
+    // can read the data
+    if (watching)
+    {
+        // This file gets created when polling OCCs. A value or length of 0 is
+        // deemed success. That means we would disable IPS active on dbus.
+        char data;
+        bool ipsState = false;
+        const auto len = read(fd, &data, sizeof(data));
+        const int readErrno = errno;
+        if (len <= 0)
+        {
+            removeIpsWatch();
+
+            log<level::ERR>(
+                fmt::format("IPS state Read Error({})={} : File={} : len={}",
+                            readErrno, strerror(readErrno),
+                            ipsStatusFile.c_str(), len)
+                    .c_str());
+
+            report<ReadFailure>(
+                phosphor::logging::org::open_power::OCC::Device::ReadFailure::
+                    CALLOUT_ERRNO(readErrno),
+                phosphor::logging::org::open_power::OCC::Device::ReadFailure::
+                    CALLOUT_DEVICE_PATH(ipsStatusFile.c_str()));
+
+            // NOTE: this will leave the system not reporting IPS active state
+            // to Fan Controls, Until an APP reload, or IPL and we will attempt
+            // again.
+        }
+        else
+        {
+            // Data returned in ASCII.
+            // convert to integer. atoi()
+            // from OCC_P10_FW_Interfaces spec
+            //          Bit 6: IPS active   1 indicates enabled.
+            //          Bit 7: IPS enabled. 1 indicates enabled.
+            //      mask off bit 6 --> & 0x02
+            // Shift left one bit and store as bool. >> 1
+            ipsState = static_cast<bool>(((atoi(&data)) & 0x2) >> 1);
+        }
+
+        // This will only set IPS active dbus if different than current.
+        active(ipsState);
+    }
+    else
+    {
+        removeIpsWatch();
+
+        // If the Retry did not get to "watching = true" we already have an
+        // error log, just post trace.
+        log<level::ERR>(fmt::format("Retry on File seek Error({})={} : File={}",
+                                    open_errno, strerror(open_errno),
+                                    ipsStatusFile.c_str())
+                            .c_str());
+
+        // NOTE: this will leave the system not reporting IPS active state to
+        // Fan Controls, Until an APP reload, or IPL and we will attempt again.
+    }
+
+    return;
+}
+#endif
+
 } // namespace powermode
 
 } // namespace occ
diff --git a/powermode.hpp b/powermode.hpp
index d15eea7..dbd68eb 100644
--- a/powermode.hpp
+++ b/powermode.hpp
@@ -39,6 +39,7 @@
 constexpr auto PIPS_PATH = "/xyz/openbmc_project/control/host0/power_ips";
 constexpr auto PIPS_INTERFACE =
     "xyz.openbmc_project.Control.Power.IdlePowerSaver";
+constexpr auto IPS_ACTIVE_PROP = "Active";
 constexpr auto IPS_ENABLED_PROP = "Enabled";
 constexpr auto IPS_ENTER_UTIL = "EnterUtilizationPercent";
 constexpr auto IPS_ENTER_TIME = "EnterDwellTime";
@@ -233,7 +234,12 @@
      * @param[in] ipsPath - Idle Power Saver dbus path
      */
     explicit PowerMode(const Manager& managerRef, const char* modePath,
-                       const char* ipsPath) :
+                       const char* ipsPath
+#ifdef POWER10
+                       ,
+                       EventPtr& event
+#endif
+                       ) :
         ModeInterface(utils::getBus(), modePath, false),
         IpsInterface(utils::getBus(), ipsPath, false), manager(managerRef),
         pmodeMatch(utils::getBus(),
@@ -250,6 +256,10 @@
                 "/xyz/openbmc_project/inventory", PMODE_DEFAULT_INTERFACE),
             [this](auto& msg) { this->defaultsReady(msg); }),
         masterOccSet(false), masterActive(false)
+#ifdef POWER10
+        ,
+        event(event)
+#endif
     {
         // restore Power Mode to DBus
         SysPwrMode currentMode;
@@ -309,6 +319,18 @@
         masterActive = isActive;
     };
 
+#ifdef POWER10
+    /** @brief Starts to monitor for IPS active state change conditions
+     *
+     *  @param[in] poll - Indicates whether or not the IPS state file should
+     *                    actually be read for changes.
+     */
+    void addIpsWatch(bool poll = true);
+
+    /** @brief Removes IPS active watch */
+    void removeIpsWatch();
+#endif
+
   private:
     /** @brief OCC manager object */
     const Manager& manager;
@@ -339,6 +361,19 @@
     /** @brief True when the master OCC is active */
     bool masterActive;
 
+#ifdef POWER10
+    /** @brief IPS status data filename to read */
+    const fs::path ipsStatusFile = std::filesystem::path{OCC_HWMON_PATH} /
+                                   std::filesystem::path{OCC_MASTER_NAME} /
+                                   "occ_ips_status";
+
+    /** @brief Current state of error watching */
+    bool watching = false;
+
+    /** @brief register for the callback from the POLL IPS changed event */
+    void registerIpsStatusCallBack();
+#endif
+
     /** @brief Callback for pmode setting changes
      *
      * Process change and inform OCC
@@ -435,6 +470,34 @@
      * @return true if restore was successful
      */
     bool useDefaultIPSParms();
+
+#ifdef POWER10
+    /** @brief callback for the POLL IPS changed event
+     *
+     *  @param[in] es       - Populated event source
+     *  @param[in] fd       - Associated File descriptor
+     *  @param[in] revents  - Type of event
+     *  @param[in] userData - User data that was passed during registration
+     */
+    static int ipsStatusCallBack(sd_event_source* es, int fd, uint32_t revents,
+                                 void* userData);
+
+    /** @brief Opens the IPS file and populates fd */
+    bool openIpsFile();
+
+    /** @brief sd_event wrapped in unique_ptr */
+    EventPtr& event;
+
+    /** @brief event source wrapped in unique_ptr */
+    EventSourcePtr eventSource;
+
+    /** @brief When the ips status event is received, analyzes it */
+    virtual void analyzeIpsEvent();
+
+  protected:
+    /** @brief File descriptor to watch for errors */
+    int fd = -1;
+#endif
 };
 
 } // namespace powermode