Disable Idle Power Saver support in efficiency modes

IPS will not be published on DBUS when the PowerMode is set to one of
the newer efficiency modes:
  EfficiencyFavorPower
  EfficiencyFavorPerformance (OEM only)

This will prevent the Redfish interface from allowing GET/PATCH
commands and provides a way for the GUI to know when to suppress
B
displaying the parameters on the customer GUI.

Testing:
Verified on Rainier.

When in a non-efficiency mode, the Redfish query returns IPS data:
(GET /redfish/v1/Systems/system)
'''
...
    "Id": "system",
    "IdlePowerSaver": {
        "Enabled": true,
        "EnterDwellTimeSeconds": 240,
        "EnterUtilizationPercent": 8,
        "ExitDwellTimeSeconds": 10,
        "ExitUtilizationPercent": 12
    },
    "IndicatorLED": "Off",
...
'''

When in an efficiency mode, the Redfish query does not return IPS data.
'''
...
    "Id": "system",
    "IndicatorLED": "Off",
...
'''

Verified across re-ipls, OCC resets, app restarts.

Change-Id: I45bb0d8e97dab33a1a66c0d791f7bb4848bfce41
Signed-off-by: Chris Cain <cjcain@us.ibm.com>
diff --git a/occ_status.cpp b/occ_status.cpp
index 7d654b9..4b6e276 100644
--- a/occ_status.cpp
+++ b/occ_status.cpp
@@ -251,6 +251,10 @@
 {
     CmdStatus status = CmdStatus::SUCCESS;
 
+    // IPS data will get sent automatically after a mode change if the mode
+    // supports it.
+    pmode->needToSendIPS();
+
     status = pmode->sendModeChange();
     if (status != CmdStatus::SUCCESS)
     {
@@ -263,22 +267,6 @@
         // Disable and reset to try recovering
         deviceError();
     }
-
-    status = pmode->sendIpsData();
-    if (status != CmdStatus::SUCCESS)
-    {
-        log<level::ERR>(
-            std::format(
-                "Status::occsWentActive: Sending Idle Power Save Config data failed with status {}",
-                status)
-                .c_str());
-
-        if (status == CmdStatus::COMM_FAILURE)
-        {
-            // Disable and reset to try recovering
-            deviceError();
-        }
-    }
 }
 
 // Send Ambient and Altitude to the OCC
diff --git a/powermode.cpp b/powermode.cpp
index 776edc4..2179bd8 100644
--- a/powermode.cpp
+++ b/powermode.cpp
@@ -44,14 +44,16 @@
     ((mode == SysPwrMode::SFP) || (mode == SysPwrMode::FFO) ||                 \
      (mode == SysPwrMode::MAX_FREQ) ||                                         \
      (mode == SysPwrMode::NON_DETERMINISTIC))
+// List of all Power Modes that disable IPS
+#define IS_ECO_MODE(mode)                                                      \
+    ((mode == SysPwrMode::EFF_FAVOR_POWER) ||                                  \
+     (mode == SysPwrMode::EFF_FAVOR_PERF))
 
 // Constructor
 PowerMode::PowerMode(const Manager& managerRef, const char* modePath,
                      const char* ipsPath, EventPtr& event) :
     ModeInterface(utils::getBus(), modePath,
                   ModeInterface::action::emit_no_signals),
-    IpsInterface(utils::getBus(), ipsPath,
-                 IpsInterface::action::emit_no_signals),
     manager(managerRef),
     ipsMatch(utils::getBus(),
              sdbusplus::bus::match::rules::propertiesChanged(PIPS_PATH,
@@ -62,7 +64,8 @@
         sdbusplus::bus::match::rules::propertiesChangedNamespace(
             "/xyz/openbmc_project/inventory", PMODE_DEFAULT_INTERFACE),
         [this](auto& msg) { this->defaultsReady(msg); }),
-    masterOccSet(false), masterActive(false), event(event)
+    masterOccSet(false), masterActive(false), ipsObjectPath(ipsPath),
+    event(event)
 {
     // Get supported power modes from entity manager
     if (false == getSupportedModes())
@@ -84,7 +87,7 @@
         // Validate persisted mode is supported
         if (isValidMode(currentMode))
         {
-            // Update power mode on DBus
+            // Update power mode on DBus and create IPS object if allowed
             updateDbusMode(currentMode);
         }
         else
@@ -99,18 +102,46 @@
             initPersistentData();
         }
     }
-
-    uint8_t enterUtil, exitUtil;
-    uint16_t enterTime, exitTime;
-    bool ipsEnabled;
-    // Read the persisted Idle Power Saver parametres
-    if (getIPSParms(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime))
-    {
-        // Update Idle Power Saver parameters on DBus
-        updateDbusIPS(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime);
-    }
 };
 
+void PowerMode::createIpsObject()
+{
+    if (!ipsObject)
+    {
+        log<level::INFO>("createIpsObject: Creating IPS object");
+        ipsObject =
+            std::make_unique<IpsInterface>(utils::getBus(), ipsObjectPath);
+
+        uint8_t enterUtil, exitUtil;
+        uint16_t enterTime, exitTime;
+        bool ipsEnabled;
+        // Read the persisted Idle Power Saver parametres
+        if (getIPSParms(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime))
+        {
+            // Update Idle Power Saver parameters on DBus
+            updateDbusIPS(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime);
+        }
+
+        // Starts watching for IPS state changes.
+        addIpsWatch(true);
+
+        needToSendIpsData = true;
+    }
+}
+
+void PowerMode::removeIpsObject()
+{
+    if (ipsObject)
+    {
+        // Stop watching for IPS state changes.
+        removeIpsWatch();
+
+        log<level::INFO>("removeIpsObject: Deleting IPS object");
+        ipsObject.reset(nullptr);
+    }
+    needToSendIpsData = false;
+}
+
 // Set the Master OCC
 void PowerMode::setMasterOcc(const std::string& masterOccPath)
 {
@@ -323,7 +354,7 @@
         // Save default mode as current mode
         persistedData.updateMode(currentMode, 0);
 
-        // Write default mode to DBus
+        // Write default mode to DBus and create IPS object if allowed
         updateDbusMode(currentMode);
     }
 
@@ -356,7 +387,7 @@
     return true;
 }
 
-// Set the power mode on DBus
+// Set the power mode on DBus and create IPS object if allowed/needed
 bool PowerMode::updateDbusMode(const SysPwrMode newMode)
 {
     if (!isValidMode(newMode))
@@ -399,6 +430,16 @@
     }
     // else return OEM mode
 
+    // Determine if IPS is allowed and create/remove as needed
+    if (IS_ECO_MODE(newMode))
+    {
+        removeIpsObject();
+    }
+    else
+    {
+        createIpsObject();
+    }
+
     ModeInterface::powerMode(dBusMode);
 
     return true;
@@ -409,27 +450,42 @@
 {
     CmdStatus status;
 
-    if (!masterActive || !masterOccSet)
-    {
-        // Nothing to do
-        log<level::DEBUG>("PowerMode::sendModeChange: OCC master not active");
-        return CmdStatus::SUCCESS;
-    }
-
-    if (!isPowerVM())
-    {
-        // Mode change is only supported on PowerVM systems
-        log<level::DEBUG>(
-            "PowerMode::sendModeChange: MODE CHANGE does not get sent on non-PowerVM systems");
-        return CmdStatus::SUCCESS;
-    }
-
     SysPwrMode newMode;
     uint16_t oemModeData = 0;
     getMode(newMode, oemModeData);
 
     if (isValidMode(newMode))
     {
+        if (IS_ECO_MODE(newMode))
+        {
+            // Customer no longer able to enable IPS
+            removeIpsObject();
+        }
+        else
+        {
+            // Customer now able to enable IPS
+            if (!ipsObject)
+            {
+                createIpsObject();
+            }
+        }
+
+        if (!masterActive || !masterOccSet)
+        {
+            // Nothing to do until OCC goes active
+            log<level::DEBUG>(
+                "PowerMode::sendModeChange: OCC master not active");
+            return CmdStatus::SUCCESS;
+        }
+
+        if (!isPowerVM())
+        {
+            // Mode change is only supported on PowerVM systems
+            log<level::DEBUG>(
+                "PowerMode::sendModeChange: MODE CHANGE does not get sent on non-PowerVM systems");
+            return CmdStatus::SUCCESS;
+        }
+
         std::vector<std::uint8_t> cmd, rsp;
         cmd.reserve(9);
         cmd.push_back(uint8_t(CmdType::SET_MODE_AND_STATE));
@@ -451,7 +507,16 @@
         {
             if (rsp.size() == 5)
             {
-                if (RspStatus::SUCCESS != RspStatus(rsp[2]))
+                if (RspStatus::SUCCESS == RspStatus(rsp[2]))
+                {
+                    if (needToSendIpsData)
+                    {
+                        // Successful mode change and IPS is now allowed, so
+                        // send IPS config
+                        sendIpsData();
+                    }
+                }
+                else
                 {
                     log<level::ERR>(
                         std::format(
@@ -492,6 +557,7 @@
     return status;
 }
 
+// Handle IPS changed event (from GUI/Redfish)
 void PowerMode::ipsChanged(sdbusplus::message_t& msg)
 {
     bool parmsChanged = false;
@@ -506,6 +572,13 @@
     uint16_t enterTime, exitTime;
     getIPSParms(ipsEnabled, enterUtil, enterTime, exitUtil, exitTime);
 
+    if (!ipsObject)
+    {
+        log<level::WARNING>(
+            "ipsChanged: Idle Power Saver can not be modified in an ECO power mode");
+        return;
+    }
+
     // Check for any changed data
     auto ipsEntry = ipsProperties.find(IPS_ENABLED_PROP);
     if (ipsEntry != ipsProperties.end())
@@ -628,15 +701,22 @@
                               const uint16_t enterTime, const uint8_t exitUtil,
                               const uint16_t exitTime)
 {
-    // true = skip update signal
-    IpsInterface::setPropertyByName(IPS_ENABLED_PROP, enabled, true);
-    IpsInterface::setPropertyByName(IPS_ENTER_UTIL, enterUtil, true);
-    // Convert time from seconds to ms
-    uint64_t msTime = enterTime * 1000;
-    IpsInterface::setPropertyByName(IPS_ENTER_TIME, msTime, true);
-    IpsInterface::setPropertyByName(IPS_EXIT_UTIL, exitUtil, true);
-    msTime = exitTime * 1000;
-    IpsInterface::setPropertyByName(IPS_EXIT_TIME, msTime, true);
+    if (ipsObject)
+    {
+        // true = skip update signal
+        ipsObject->setPropertyByName(IPS_ENABLED_PROP, enabled, true);
+        ipsObject->setPropertyByName(IPS_ENTER_UTIL, enterUtil, true);
+        // Convert time from seconds to ms
+        uint64_t msTime = enterTime * 1000;
+        ipsObject->setPropertyByName(IPS_ENTER_TIME, msTime, true);
+        ipsObject->setPropertyByName(IPS_EXIT_UTIL, exitUtil, true);
+        msTime = exitTime * 1000;
+        ipsObject->setPropertyByName(IPS_EXIT_TIME, msTime, true);
+    }
+    else
+    {
+        log<level::WARNING>("updateDbusIPS: No IPS object was found");
+    }
 
     return true;
 }
@@ -658,6 +738,14 @@
         return CmdStatus::SUCCESS;
     }
 
+    if (!ipsObject)
+    {
+        // Idle Power Saver data is not available in Eco Modes
+        log<level::INFO>(
+            "PowerMode::sendIpsData: Skipping IPS data due to being in an ECO Power Mode");
+        return CmdStatus::SUCCESS;
+    }
+
     bool ipsEnabled;
     uint8_t enterUtil, exitUtil;
     uint16_t enterTime, exitTime;
@@ -692,7 +780,11 @@
     {
         if (rsp.size() == 5)
         {
-            if (RspStatus::SUCCESS != RspStatus(rsp[2]))
+            if (RspStatus::SUCCESS == RspStatus(rsp[2]))
+            {
+                needToSendIpsData = false;
+            }
+            else
             {
                 log<level::ERR>(
                     std::format(
@@ -1050,7 +1142,10 @@
                 CALLOUT_DEVICE_PATH(ipsStatusFile.c_str()));
 
         // We are no longer watching the error
-        active(false);
+        if (ipsObject)
+        {
+            ipsObject->active(false);
+        }
 
         watching = false;
         rc = false;
@@ -1083,7 +1178,10 @@
     //  matter what the 'watching' flags is set to.
 
     // We are no longer watching the error
-    active(false);
+    if (ipsObject)
+    {
+        ipsObject->active(false);
+    }
 
     watching = false;
 
@@ -1192,7 +1290,10 @@
         }
 
         // This will only set IPS active dbus if different than current.
-        active(ipsState);
+        if (ipsObject)
+        {
+            ipsObject->active(ipsState);
+        }
     }
     else
     {
diff --git a/powermode.hpp b/powermode.hpp
index a9b01de..715499b 100644
--- a/powermode.hpp
+++ b/powermode.hpp
@@ -241,7 +241,7 @@
  *  the power mode to the OCC if the mode is changed while the occ is active.
  */
 
-class PowerMode : public ModeInterface, public IpsInterface
+class PowerMode : public ModeInterface
 {
   public:
     /** @brief PowerMode object to inform occ of changes to mode
@@ -339,6 +339,15 @@
      */
     bool isValidMode(const SysPwrMode mode);
 
+    /** @brief If IPS is supported, set flag indicating need to send IPS data */
+    void needToSendIPS()
+    {
+        if (ipsObject)
+        {
+            needToSendIpsData = true;
+        }
+    }
+
   private:
     /** @brief OCC manager object */
     const Manager& manager;
@@ -385,6 +394,15 @@
     /** @brief Current state of error watching */
     bool watching = false;
 
+    /** @brief Set at IPS object creation and cleared after sending IPS data */
+    bool needToSendIpsData = false;
+
+    /** @brief Object path for IPS on DBUS */
+    const char* ipsObjectPath;
+
+    /** @brief IPS DBUS Object */
+    std::unique_ptr<IpsInterface> ipsObject;
+
     /** @brief register for the callback from the POLL IPS changed event */
     void registerIpsStatusCallBack();
 
@@ -483,6 +501,12 @@
      */
     bool getSupportedModes();
 
+    /** @brief Create IPS DBUS Object */
+    void createIpsObject();
+
+    /** @brief Remove IPS DBUS Object */
+    void removeIpsObject();
+
     /** @brief callback for the POLL IPS changed event
      *
      *  @param[in] es       - Populated event source