Use Idle Power Saver parameters from DBus

Allows users to enable and update the IPS parameters instead of
using hardcoded values.

Change-Id: I9010c4b4d3dbdf130a4a778f71c87279681a9f1a
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/occ_manager.cpp b/occ_manager.cpp
index ea2020c..b83feb3 100644
--- a/occ_manager.cpp
+++ b/occ_manager.cpp
@@ -159,6 +159,12 @@
         pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
             *statusObjects.front());
     }
+    // Create the idle power saver monitor object for master occ (0)
+    if (!pips)
+    {
+        pips = std::make_unique<open_power::occ::powermode::PowerIPS>(
+            *statusObjects.front());
+    }
 #endif
 }
 
@@ -235,6 +241,8 @@
 #ifdef POWER10
     pmode = std::make_unique<open_power::occ::powermode::PowerMode>(
         *statusObjects.front());
+    pips = std::make_unique<open_power::occ::powermode::PowerIPS>(
+        *statusObjects.front());
 #endif
 }
 #endif
diff --git a/occ_manager.hpp b/occ_manager.hpp
index 43cc32f..5b97abc 100644
--- a/occ_manager.hpp
+++ b/occ_manager.hpp
@@ -147,6 +147,9 @@
 #ifdef POWER10
     /** @brief Power mode monitor and notification object */
     std::unique_ptr<open_power::occ::powermode::PowerMode> pmode;
+
+    /** @brief Idle Power Saver monitor and notification object */
+    std::unique_ptr<open_power::occ::powermode::PowerIPS> pips;
 #endif
 
     /** @brief sbdbusplus match objects */
diff --git a/occ_status.cpp b/occ_status.cpp
index ab6b9d1..b1893fd 100644
--- a/occ_status.cpp
+++ b/occ_status.cpp
@@ -172,11 +172,6 @@
         fs::path(DEV_PATH) /
         fs::path(sysfsName + "." + std::to_string(instance + 1)) / "occ_state";
 
-    log<level::DEBUG>(
-        fmt::format("Status::readOccState: reading OCC{} state from {}",
-                    instance, filename.c_str())
-            .c_str());
-
     std::ifstream file(filename, std::ios::in);
     const int open_errno = errno;
     if (file)
@@ -272,6 +267,124 @@
     return pmode;
 }
 
+// Get the requested power mode
+bool Status::getIPSParms(uint8_t& enterUtil, uint16_t& enterTime,
+                         uint8_t& exitUtil, uint16_t& exitTime)
+{
+    using namespace open_power::occ::powermode;
+    // Defaults:
+    bool ipsEnabled = false; // Disabled
+    enterUtil = 8;           // Enter Utilization (8%)
+    enterTime = 240;         // Enter Delay Time (240s)
+    exitUtil = 12;           // Exit Utilization (12%)
+    exitTime = 10;           // Exit Delay Time (10s)
+
+    std::map<std::string, std::variant<bool, uint8_t, uint64_t>>
+        ipsProperties{};
+
+    // Get all IPS properties from DBus
+    try
+    {
+        auto& bus = utils::getBus();
+        auto service = utils::getService(PIPS_PATH, PIPS_INTERFACE);
+        auto method =
+            bus.new_method_call(service.c_str(), PIPS_PATH,
+                                "org.freedesktop.DBus.Properties", "GetAll");
+        method.append(PIPS_INTERFACE);
+        auto reply = bus.call(method);
+        reply.read(ipsProperties);
+    }
+    catch (const sdbusplus::exception::exception& e)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "Unable to read Idle Power Saver parameters so it will be disabled: {}",
+                e.what())
+                .c_str());
+        return ipsEnabled;
+    }
+
+    auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP);
+    if (ipsEntry != ipsProperties.end())
+    {
+        ipsEnabled = std::get<bool>(ipsEntry->second);
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("Status::getIPSParms could not find property: {}",
+                        IPS_ENABLED_PROP)
+                .c_str());
+    }
+
+    ipsEntry = ipsProperties.find(IPS_ENTER_UTIL);
+    if (ipsEntry != ipsProperties.end())
+    {
+        enterUtil = std::get<uint8_t>(ipsEntry->second);
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("Status::getIPSParms could not find property: {}",
+                        IPS_ENTER_UTIL)
+                .c_str());
+    }
+
+    ipsEntry = ipsProperties.find(IPS_ENTER_TIME);
+    if (ipsEntry != ipsProperties.end())
+    {
+        std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
+        enterTime =
+            std::chrono::duration_cast<std::chrono::seconds>(ms).count();
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("Status::getIPSParms could not find property: {}",
+                        IPS_ENTER_TIME)
+                .c_str());
+    }
+
+    ipsEntry = ipsProperties.find(IPS_EXIT_UTIL);
+    if (ipsEntry != ipsProperties.end())
+    {
+        exitUtil = std::get<uint8_t>(ipsEntry->second);
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("Status::getIPSParms could not find property: {}",
+                        IPS_EXIT_UTIL)
+                .c_str());
+    }
+
+    ipsEntry = ipsProperties.find(IPS_EXIT_TIME);
+    if (ipsEntry != ipsProperties.end())
+    {
+        std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
+        exitTime = std::chrono::duration_cast<std::chrono::seconds>(ms).count();
+    }
+    else
+    {
+        log<level::ERR>(
+            fmt::format("Status::getIPSParms could not find property: {}",
+                        IPS_EXIT_TIME)
+                .c_str());
+    }
+
+    if (enterUtil > exitUtil)
+    {
+        log<level::ERR>(
+            fmt::format(
+                "ERROR: Idle Power Saver Enter Utilization ({}%) is > Exit Utilization ({}%) - using Exit for both",
+                enterUtil, exitUtil)
+                .c_str());
+        enterUtil = exitUtil;
+    }
+
+    return ipsEnabled;
+}
+
 // Special processing that needs to happen once the OCCs change to ACTIVE state
 void Status::occsWentActive()
 {
@@ -325,6 +438,7 @@
     if (VALID_POWER_MODE_SETTING(newMode))
     {
         std::vector<std::uint8_t> cmd, rsp;
+        cmd.reserve(9);
         cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
         cmd.push_back(0x00); // Data Length (2 bytes)
         cmd.push_back(0x06);
@@ -344,18 +458,15 @@
         {
             if (rsp.size() == 5)
             {
-                if (RspStatus::SUCCESS == RspStatus(rsp[2]))
-                {
-                    log<level::DEBUG>(
-                        "Status::sendModeChange: - Mode change completed successfully");
-                }
-                else
+                if (RspStatus::SUCCESS != RspStatus(rsp[2]))
                 {
                     log<level::ERR>(
                         fmt::format(
                             "Status::sendModeChange: SET MODE failed with status 0x{:02X}",
                             rsp[2])
                             .c_str());
+                    dump_hex(rsp);
+                    status = CmdStatus::FAILURE;
                 }
             }
             else
@@ -363,14 +474,15 @@
                 log<level::ERR>(
                     "Status::sendModeChange: INVALID SET MODE response");
                 dump_hex(rsp);
+                status = CmdStatus::FAILURE;
             }
         }
         else
         {
             if (status == CmdStatus::OPEN_FAILURE)
             {
-                log<level::WARNING>(
-                    "Status::sendModeChange: OCC not active yet");
+                log<level::INFO>("Status::sendModeChange: OCC not active yet");
+                status = CmdStatus::SUCCESS;
             }
             else
             {
@@ -385,6 +497,7 @@
                 "Status::sendModeChange: Unable to set power mode to {}",
                 newMode)
                 .c_str());
+        status = CmdStatus::FAILURE;
     }
 
     return status;
@@ -412,42 +525,49 @@
         return CmdStatus::SUCCESS;
     }
 
-    std::vector<std::uint8_t> cmd, rsp;
-    cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
-    cmd.push_back(0x00); // Data Length (2 bytes)
-    cmd.push_back(0x09);
-    // Data:
-    cmd.push_back(0x11); // Config Format: IPS Settings
-    cmd.push_back(0x00); // Version
-    cmd.push_back(0x00); // IPS Enable: disabled
-    cmd.push_back(0x00); // Enter Delay Time (240s)
-    cmd.push_back(0xF0); //
-    cmd.push_back(0x08); // Enter Utilization (8%)
-    cmd.push_back(0x00); // Exit Delay Time (10s)
-    cmd.push_back(0x0A); //
-    cmd.push_back(0x0C); // Exit Utilization (12%)
+    uint8_t enterUtil, exitUtil;
+    uint16_t enterTime, exitTime;
+    const bool ipsEnabled =
+        getIPSParms(enterUtil, enterTime, exitUtil, exitTime);
+
     log<level::INFO>(
         fmt::format(
-            "Status::sendIpsData: SET_CFG_DATA[IPS] command to OCC{} ({} bytes)",
-            instance, cmd.size())
+            "Idle Power Saver Parameters: enabled:{}, enter:{}%/{}s, exit:{}%/{}s",
+            ipsEnabled, enterUtil, enterTime, exitUtil, exitTime)
             .c_str());
+
+    std::vector<std::uint8_t> cmd, rsp;
+    cmd.reserve(12);
+    cmd.push_back(uint8_t(CmdType::SET_CONFIG_DATA));
+    cmd.push_back(0x00);               // Data Length (2 bytes)
+    cmd.push_back(0x09);               //
+    cmd.push_back(0x11);               // Config Format: IPS Settings
+    cmd.push_back(0x00);               // Version
+    cmd.push_back(ipsEnabled ? 1 : 0); // IPS Enable
+    cmd.push_back(enterTime >> 8);     // Enter Delay Time
+    cmd.push_back(enterTime & 0xFF);   //
+    cmd.push_back(enterUtil);          // Enter Utilization
+    cmd.push_back(exitTime >> 8);      // Exit Delay Time
+    cmd.push_back(exitTime & 0xFF);    //
+    cmd.push_back(exitUtil);           // Exit Utilization
+    log<level::INFO>(fmt::format("Status::sendIpsData: SET_CFG_DATA[IPS] "
+                                 "command to OCC{} ({} bytes)",
+                                 instance, cmd.size())
+                         .c_str());
     status = occCmd.send(cmd, rsp);
     if (status == CmdStatus::SUCCESS)
     {
         if (rsp.size() == 5)
         {
-            if (RspStatus::SUCCESS == RspStatus(rsp[2]))
-            {
-                log<level::DEBUG>(
-                    "Status::sendIpsData: - SET_CFG_DATA[IPS] completed successfully");
-            }
-            else
+            if (RspStatus::SUCCESS != RspStatus(rsp[2]))
             {
                 log<level::ERR>(
                     fmt::format(
                         "Status::sendIpsData: SET_CFG_DATA[IPS] failed with status 0x{:02X}",
                         rsp[2])
                         .c_str());
+                dump_hex(rsp);
+                status = CmdStatus::FAILURE;
             }
         }
         else
@@ -455,13 +575,15 @@
             log<level::ERR>(
                 "Status::sendIpsData: INVALID SET_CFG_DATA[IPS] response");
             dump_hex(rsp);
+            status = CmdStatus::FAILURE;
         }
     }
     else
     {
         if (status == CmdStatus::OPEN_FAILURE)
         {
-            log<level::WARNING>("Status::sendIpsData: OCC not active yet");
+            log<level::INFO>("Status::sendIpsData: OCC not active yet");
+            status = CmdStatus::SUCCESS;
         }
         else
         {
diff --git a/occ_status.hpp b/occ_status.hpp
index 5856ee9..ceff8f3 100644
--- a/occ_status.hpp
+++ b/occ_status.hpp
@@ -171,6 +171,11 @@
      *  @return SUCCESS on success
      */
     CmdStatus sendModeChange();
+
+    /** @brief Send Idle Power Saver config data to the master OCC
+     *  @return SUCCESS on success
+     */
+    CmdStatus sendIpsData();
 #endif // POWER10
 
   private:
@@ -241,10 +246,11 @@
      */
     SysPwrMode getMode();
 
-    /** @brief Send Idle Power Saver config data to the master OCC
-     *  @return SUCCESS on success
+    /** @brief Get the Idle Power Saver properties
+     * @return true if IPS is enabled
      */
-    CmdStatus sendIpsData();
+    bool getIPSParms(uint8_t& enterUtil, uint16_t& enterTime, uint8_t& exitUtil,
+                     uint16_t& exitTime);
 #endif // POWER10
 
     /** @brief Override the sensor name with name from the definition.
diff --git a/powermode.cpp b/powermode.cpp
index 32af335..a78c37e 100644
--- a/powermode.cpp
+++ b/powermode.cpp
@@ -21,7 +21,7 @@
 {
     if (!occStatus.occActive())
     {
-        // Nothing to  do
+        // Nothing to do
         return;
     }
 
@@ -81,6 +81,79 @@
     return pmode;
 }
 
+void PowerIPS::ipsChanged(sdbusplus::message::message& msg)
+{
+    if (!occStatus.occActive())
+    {
+        // Nothing to do
+        return;
+    }
+
+    bool parmsChanged = false;
+    std::string interface;
+    std::map<std::string, std::variant<bool, uint8_t, uint64_t>>
+        ipsProperties{};
+    msg.read(interface, ipsProperties);
+
+    auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP);
+    if (ipsEntry != ipsProperties.end())
+    {
+        const auto ipsEnabled = std::get<bool>(ipsEntry->second);
+        log<level::INFO>(
+            fmt::format("Idle Power Saver change: Enabled={}", ipsEnabled)
+                .c_str());
+        parmsChanged = true;
+    }
+    ipsEntry = ipsProperties.find(IPS_ENTER_UTIL);
+    if (ipsEntry != ipsProperties.end())
+    {
+        const auto enterUtil = std::get<uint8_t>(ipsEntry->second);
+        log<level::INFO>(
+            fmt::format("Idle Power Saver change: Enter Util={}%", enterUtil)
+                .c_str());
+        parmsChanged = true;
+    }
+    ipsEntry = ipsProperties.find(IPS_ENTER_TIME);
+    if (ipsEntry != ipsProperties.end())
+    {
+        std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
+        const auto enterTime =
+            std::chrono::duration_cast<std::chrono::seconds>(ms).count();
+        log<level::INFO>(
+            fmt::format("Idle Power Saver change: Enter Time={}sec", enterTime)
+                .c_str());
+        parmsChanged = true;
+    }
+    ipsEntry = ipsProperties.find(IPS_EXIT_UTIL);
+    if (ipsEntry != ipsProperties.end())
+    {
+        const auto exitUtil = std::get<uint8_t>(ipsEntry->second);
+        log<level::INFO>(
+            fmt::format("Idle Power Saver change: Exit Util={}%", exitUtil)
+                .c_str());
+        parmsChanged = true;
+    }
+    ipsEntry = ipsProperties.find(IPS_EXIT_TIME);
+    if (ipsEntry != ipsProperties.end())
+    {
+        std::chrono::milliseconds ms(std::get<uint64_t>(ipsEntry->second));
+        const auto exitTime =
+            std::chrono::duration_cast<std::chrono::seconds>(ms).count();
+        log<level::INFO>(
+            fmt::format("Idle Power Saver change: Exit Time={}sec", exitTime)
+                .c_str());
+        parmsChanged = true;
+    }
+
+    if (parmsChanged)
+    {
+        // Trigger mode change to OCC
+        occStatus.sendIpsData();
+    }
+
+    return;
+}
+
 } // namespace powermode
 
 } // namespace occ
diff --git a/powermode.hpp b/powermode.hpp
index fd7d466..c9d3f0e 100644
--- a/powermode.hpp
+++ b/powermode.hpp
@@ -21,6 +21,15 @@
 constexpr auto PMODE_INTERFACE = "xyz.openbmc_project.Control.Power.Mode";
 constexpr auto POWER_MODE_PROP = "PowerMode";
 
+constexpr auto PIPS_PATH = "/xyz/openbmc_project/control/host0/power_ips";
+constexpr auto PIPS_INTERFACE =
+    "xyz.openbmc_project.Control.Power.IdlePowerSaver";
+constexpr auto IPS_ENABLED_PROP = "Enabled";
+constexpr auto IPS_ENTER_UTIL = "EnterUtilizationPercent";
+constexpr auto IPS_ENTER_TIME = "EnterDwellTime";
+constexpr auto IPS_EXIT_UTIL = "ExitUtilizationPercent";
+constexpr auto IPS_EXIT_TIME = "ExitDwellTime";
+
 /** @brief Convert power mode string to OCC SysPwrMode value
  *
  * @param[in] i_modeString - power mode string
@@ -72,6 +81,42 @@
     sdbusplus::bus::match_t pmodeMatch;
 };
 
+class PowerIPS
+{
+  public:
+    /** @brief PowerIPS object to inform occ of changes to Idle Power Saver
+     * parms
+     *
+     * This object will monitor for changes to the Idle Power Saver settings.
+     * If a change is detected, and the occ is active, then this object will
+     * notify the OCC of the change.
+     *
+     * @param[in] occStatus - The occ status object
+     */
+    PowerIPS(Status& occStatus) :
+        occStatus(occStatus),
+        ipsMatch(utils::getBus(),
+                 sdbusplus::bus::match::rules::propertiesChanged(
+                     PIPS_PATH, PIPS_INTERFACE),
+                 [this](auto& msg) { this->ipsChanged(msg); }){};
+
+  private:
+    /** @brief Callback for IPS setting changes
+     *
+     * Process change and inform OCC
+     *
+     * @param[in]  msg       - Data associated with IPS change signal
+     *
+     */
+    void ipsChanged(sdbusplus::message::message& msg);
+
+    /* @brief OCC Status object */
+    Status& occStatus;
+
+    /** @brief Used to subscribe to dbus IPS property changes **/
+    sdbusplus::bus::match_t ipsMatch;
+};
+
 } // namespace powermode
 
 } // namespace occ