Systems: Add IdlePowerSave support

Idle power saver is a system wide mode that will reduce the power usage
when the system reaches an idle state.  The system will also exit idle
power save once the performance increases to a specified utilization.

The idle state is defined as when the processor utilization drops to
EnterUtilizationPercent for EnterDwellTimeSeconds.  Once the utilization
increases to ExitUtilizationPercent for ExitDwellTimeSeconds, the system
will return to normal (non-idle) state.

Ran Validator on hardware and all /redfish/v1/Systems/system tests
passed.

Tested: I manually tested retrieving and setting the PowerMode property
on a Rainier system (with good and bad values)

$ curl -s -k -X GET https://$bmc/redfish/v1/Systems/system | grep -A6 IdlePowerSaver
  "IdlePowerSaver": {
    "Enabled": false,
    "EnterDwellTimeSeconds": 240,
    "EnterUtilizationPercent": 8,
    "ExitDwellTimeSeconds": 10,
    "ExitUtilizationPercent": 12
  },

$ curl -k -X PATCH -d '{"IdlePowerSaver":{"Enabled":true}}' https://$bmc/redfish/v1/Systems/system

$ curl -k -X PATCH -d '{"IdlePowerSaver":{"Enabled":false,"EnterUtilizationPercent":8,"EnterDwellTimeSeconds":240,"ExitUtilizationPercent":12,"ExitDwellTimeSeconds":10}}' https://$bmc/redfish/v1/Systems/system

Signed-off-by: Chris Cain <cjcain@us.ibm.com>
Change-Id: Icdc948f133959dce4297ab9774c2e9c8acb620e3
diff --git a/redfish-core/lib/systems.hpp b/redfish-core/lib/systems.hpp
index 2f4c36d..b94612c 100644
--- a/redfish-core/lib/systems.hpp
+++ b/redfish-core/lib/systems.hpp
@@ -2469,6 +2469,347 @@
     }
 }
 
+using ipsPropertiesType =
+    std::vector<std::pair<std::string, dbus::utility::DbusVariantType>>;
+/**
+ * @brief Parse the Idle Power Saver properties into json
+ *
+ * @param[in] aResp     Shared pointer for completing asynchronous calls.
+ * @param[in] properties  IPS property data from DBus.
+ *
+ * @return true if successful
+ */
+bool parseIpsProperties(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+                        ipsPropertiesType& properties)
+{
+    for (const auto& property : properties)
+    {
+        if (property.first == "Enabled")
+        {
+            const bool* state = std::get_if<bool>(&property.second);
+            if (!state)
+            {
+                return false;
+            }
+            aResp->res.jsonValue["IdlePowerSaver"][property.first] = *state;
+        }
+        else if (property.first == "EnterUtilizationPercent")
+        {
+            const uint8_t* util = std::get_if<uint8_t>(&property.second);
+            if (!util)
+            {
+                return false;
+            }
+            aResp->res.jsonValue["IdlePowerSaver"][property.first] = *util;
+        }
+        else if (property.first == "EnterDwellTime")
+        {
+            // Convert Dbus time from milliseconds to seconds
+            const uint64_t* timeMilliseconds =
+                std::get_if<uint64_t>(&property.second);
+            if (!timeMilliseconds)
+            {
+                return false;
+            }
+            const std::chrono::duration<uint64_t, std::milli> ms(
+                *timeMilliseconds);
+            aResp->res.jsonValue["IdlePowerSaver"]["EnterDwellTimeSeconds"] =
+                std::chrono::duration_cast<std::chrono::duration<uint64_t>>(ms)
+                    .count();
+        }
+        else if (property.first == "ExitUtilizationPercent")
+        {
+            const uint8_t* util = std::get_if<uint8_t>(&property.second);
+            if (!util)
+            {
+                return false;
+            }
+            aResp->res.jsonValue["IdlePowerSaver"][property.first] = *util;
+        }
+        else if (property.first == "ExitDwellTime")
+        {
+            // Convert Dbus time from milliseconds to seconds
+            const uint64_t* timeMilliseconds =
+                std::get_if<uint64_t>(&property.second);
+            if (!timeMilliseconds)
+            {
+                return false;
+            }
+            const std::chrono::duration<uint64_t, std::milli> ms(
+                *timeMilliseconds);
+            aResp->res.jsonValue["IdlePowerSaver"]["ExitDwellTimeSeconds"] =
+                std::chrono::duration_cast<std::chrono::duration<uint64_t>>(ms)
+                    .count();
+        }
+        else
+        {
+            BMCWEB_LOG_WARNING << "Unexpected IdlePowerSaver property: "
+                               << property.first;
+        }
+    }
+
+    return true;
+}
+
+/**
+ * @brief Retrieves host watchdog timer properties over DBUS
+ *
+ * @param[in] aResp     Shared pointer for completing asynchronous calls.
+ *
+ * @return None.
+ */
+inline void getIdlePowerSaver(const std::shared_ptr<bmcweb::AsyncResp>& aResp)
+{
+    BMCWEB_LOG_DEBUG << "Get idle power saver parameters";
+
+    // Get IdlePowerSaver object path:
+    crow::connections::systemBus->async_method_call(
+        [aResp](
+            const boost::system::error_code ec,
+            const std::vector<std::pair<
+                std::string,
+                std::vector<std::pair<std::string, std::vector<std::string>>>>>&
+                subtree) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG
+                    << "DBUS response error on Power.IdlePowerSaver GetSubTree "
+                    << ec;
+                messages::internalError(aResp->res);
+                return;
+            }
+            if (subtree.empty())
+            {
+                // This is an optional interface so just return
+                // if there is no instance found
+                BMCWEB_LOG_DEBUG << "No instances found";
+                return;
+            }
+            if (subtree.size() > 1)
+            {
+                // More then one PowerIdlePowerSaver object is not supported and
+                // is an error
+                BMCWEB_LOG_DEBUG << "Found more than 1 system D-Bus "
+                                    "Power.IdlePowerSaver objects: "
+                                 << subtree.size();
+                messages::internalError(aResp->res);
+                return;
+            }
+            if ((subtree[0].first.empty()) || (subtree[0].second.size() != 1))
+            {
+                BMCWEB_LOG_DEBUG << "Power.IdlePowerSaver mapper error!";
+                messages::internalError(aResp->res);
+                return;
+            }
+            const std::string& path = subtree[0].first;
+            const std::string& service = subtree[0].second.begin()->first;
+            if (service.empty())
+            {
+                BMCWEB_LOG_DEBUG
+                    << "Power.IdlePowerSaver service mapper error!";
+                messages::internalError(aResp->res);
+                return;
+            }
+
+            // Valid IdlePowerSaver object found, now read the current values
+            crow::connections::systemBus->async_method_call(
+                [aResp](const boost::system::error_code ec,
+                        ipsPropertiesType& properties) {
+                    if (ec)
+                    {
+                        BMCWEB_LOG_ERROR
+                            << "DBUS response error on IdlePowerSaver GetAll: "
+                            << ec;
+                        messages::internalError(aResp->res);
+                        return;
+                    }
+
+                    if (parseIpsProperties(aResp, properties) == false)
+                    {
+                        messages::internalError(aResp->res);
+                        return;
+                    }
+                },
+                service, path, "org.freedesktop.DBus.Properties", "GetAll",
+                "xyz.openbmc_project.Control.Power.IdlePowerSaver");
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0),
+        std::array<const char*, 1>{
+            "xyz.openbmc_project.Control.Power.IdlePowerSaver"});
+
+    BMCWEB_LOG_DEBUG << "EXIT: Get idle power saver parameters";
+}
+
+/**
+ * @brief Sets Idle Power Saver properties.
+ *
+ * @param[in] aResp      Shared pointer for generating response message.
+ * @param[in] ipsEnable  The IPS Enable value (true/false) from incoming
+ *                       RF request.
+ * @param[in] ipsEnterUtil The utilization limit to enter idle state.
+ * @param[in] ipsEnterTime The time the utilization must be below ipsEnterUtil
+ * before entering idle state.
+ * @param[in] ipsExitUtil The utilization limit when exiting idle state.
+ * @param[in] ipsExitTime The time the utilization must be above ipsExutUtil
+ * before exiting idle state
+ *
+ * @return None.
+ */
+inline void setIdlePowerSaver(const std::shared_ptr<bmcweb::AsyncResp>& aResp,
+                              const std::optional<bool> ipsEnable,
+                              const std::optional<uint8_t> ipsEnterUtil,
+                              const std::optional<uint64_t> ipsEnterTime,
+                              const std::optional<uint8_t> ipsExitUtil,
+                              const std::optional<uint64_t> ipsExitTime)
+{
+    BMCWEB_LOG_DEBUG << "Set idle power saver properties";
+
+    // Get IdlePowerSaver object path:
+    crow::connections::systemBus->async_method_call(
+        [aResp, ipsEnable, ipsEnterUtil, ipsEnterTime, ipsExitUtil,
+         ipsExitTime](
+            const boost::system::error_code ec,
+            const std::vector<std::pair<
+                std::string,
+                std::vector<std::pair<std::string, std::vector<std::string>>>>>&
+                subtree) {
+            if (ec)
+            {
+                BMCWEB_LOG_DEBUG
+                    << "DBUS response error on Power.IdlePowerSaver GetSubTree "
+                    << ec;
+                messages::internalError(aResp->res);
+                return;
+            }
+            if (subtree.empty())
+            {
+                // This is an optional D-Bus object, but user attempted to patch
+                messages::resourceNotFound(aResp->res, "ComputerSystem",
+                                           "IdlePowerSaver");
+                return;
+            }
+            if (subtree.size() > 1)
+            {
+                // More then one PowerIdlePowerSaver object is not supported and
+                // is an error
+                BMCWEB_LOG_DEBUG << "Found more than 1 system D-Bus "
+                                    "Power.IdlePowerSaver objects: "
+                                 << subtree.size();
+                messages::internalError(aResp->res);
+                return;
+            }
+            if ((subtree[0].first.empty()) || (subtree[0].second.size() != 1))
+            {
+                BMCWEB_LOG_DEBUG << "Power.IdlePowerSaver mapper error!";
+                messages::internalError(aResp->res);
+                return;
+            }
+            const std::string& path = subtree[0].first;
+            const std::string& service = subtree[0].second.begin()->first;
+            if (service.empty())
+            {
+                BMCWEB_LOG_DEBUG
+                    << "Power.IdlePowerSaver service mapper error!";
+                messages::internalError(aResp->res);
+                return;
+            }
+
+            // Valid Power IdlePowerSaver object found, now set any values that
+            // need to be updated
+
+            if (ipsEnable)
+            {
+                crow::connections::systemBus->async_method_call(
+                    [aResp](const boost::system::error_code ec) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
+                            messages::internalError(aResp->res);
+                            return;
+                        }
+                    },
+                    service, path, "org.freedesktop.DBus.Properties", "Set",
+                    "xyz.openbmc_project.Control.Power.IdlePowerSaver",
+                    "Enabled", std::variant<bool>(*ipsEnable));
+            }
+            if (ipsEnterUtil)
+            {
+                crow::connections::systemBus->async_method_call(
+                    [aResp](const boost::system::error_code ec) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
+                            messages::internalError(aResp->res);
+                            return;
+                        }
+                    },
+                    service, path, "org.freedesktop.DBus.Properties", "Set",
+                    "xyz.openbmc_project.Control.Power.IdlePowerSaver",
+                    "EnterUtilizationPercent",
+                    std::variant<uint8_t>(*ipsEnterUtil));
+            }
+            if (ipsEnterTime)
+            {
+                // Convert from seconds into milliseconds for DBus
+                const uint64_t timeMilliseconds = *ipsEnterTime * 1000;
+                crow::connections::systemBus->async_method_call(
+                    [aResp](const boost::system::error_code ec) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
+                            messages::internalError(aResp->res);
+                            return;
+                        }
+                    },
+                    service, path, "org.freedesktop.DBus.Properties", "Set",
+                    "xyz.openbmc_project.Control.Power.IdlePowerSaver",
+                    "EnterDwellTime", std::variant<uint64_t>(timeMilliseconds));
+            }
+            if (ipsExitUtil)
+            {
+                crow::connections::systemBus->async_method_call(
+                    [aResp](const boost::system::error_code ec) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
+                            messages::internalError(aResp->res);
+                            return;
+                        }
+                    },
+                    service, path, "org.freedesktop.DBus.Properties", "Set",
+                    "xyz.openbmc_project.Control.Power.IdlePowerSaver",
+                    "ExitUtilizationPercent",
+                    std::variant<uint8_t>(*ipsExitUtil));
+            }
+            if (ipsExitTime)
+            {
+                // Convert from seconds into milliseconds for DBus
+                const uint64_t timeMilliseconds = *ipsExitTime * 1000;
+                crow::connections::systemBus->async_method_call(
+                    [aResp](const boost::system::error_code ec) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_DEBUG << "DBUS response error " << ec;
+                            messages::internalError(aResp->res);
+                            return;
+                        }
+                    },
+                    service, path, "org.freedesktop.DBus.Properties", "Set",
+                    "xyz.openbmc_project.Control.Power.IdlePowerSaver",
+                    "ExitDwellTime", std::variant<uint64_t>(timeMilliseconds));
+            }
+        },
+        "xyz.openbmc_project.ObjectMapper",
+        "/xyz/openbmc_project/object_mapper",
+        "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", int32_t(0),
+        std::array<const char*, 1>{
+            "xyz.openbmc_project.Control.Power.IdlePowerSaver"});
+
+    BMCWEB_LOG_DEBUG << "EXIT: Set idle power saver parameters";
+}
+
 /**
  * SystemsCollection derived class for delivering ComputerSystems Collection
  * Schema
@@ -2682,7 +3023,7 @@
                 get)([](const crow::Request&,
                         const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
             asyncResp->res.jsonValue["@odata.type"] =
-                "#ComputerSystem.v1_15_0.ComputerSystem";
+                "#ComputerSystem.v1_16_0.ComputerSystem";
             asyncResp->res.jsonValue["Name"] = "system";
             asyncResp->res.jsonValue["Id"] = "system";
             asyncResp->res.jsonValue["SystemType"] = "Physical";
@@ -2795,6 +3136,7 @@
 #endif
             getTrustedModuleRequiredToBoot(asyncResp);
             getPowerMode(asyncResp);
+            getIdlePowerSaver(asyncResp);
         });
     BMCWEB_ROUTE(app, "/redfish/v1/Systems/system/")
         .privileges(redfish::privileges::patchComputerSystem)
@@ -2808,12 +3150,14 @@
                 std::optional<std::string> assetTag;
                 std::optional<std::string> powerRestorePolicy;
                 std::optional<std::string> powerMode;
+                std::optional<nlohmann::json> ipsProps;
                 if (!json_util::readJson(
                         req, asyncResp->res, "IndicatorLED", indicatorLed,
                         "LocationIndicatorActive", locationIndicatorActive,
                         "Boot", bootProps, "WatchdogTimer", wdtTimerProps,
                         "PowerRestorePolicy", powerRestorePolicy, "AssetTag",
-                        assetTag, "PowerMode", powerMode))
+                        assetTag, "PowerMode", powerMode, "IdlePowerSaver",
+                        ipsProps))
                 {
                     return;
                 }
@@ -2902,6 +3246,27 @@
                 {
                     setPowerMode(asyncResp, *powerMode);
                 }
+
+                if (ipsProps)
+                {
+                    std::optional<bool> ipsEnable;
+                    std::optional<uint8_t> ipsEnterUtil;
+                    std::optional<uint64_t> ipsEnterTime;
+                    std::optional<uint8_t> ipsExitUtil;
+                    std::optional<uint64_t> ipsExitTime;
+
+                    if (!json_util::readJson(
+                            *ipsProps, asyncResp->res, "Enabled", ipsEnable,
+                            "EnterUtilizationPercent", ipsEnterUtil,
+                            "EnterDwellTimeSeconds", ipsEnterTime,
+                            "ExitUtilizationPercent", ipsExitUtil,
+                            "ExitDwellTimeSeconds", ipsExitTime))
+                    {
+                        return;
+                    }
+                    setIdlePowerSaver(asyncResp, ipsEnable, ipsEnterUtil,
+                                      ipsEnterTime, ipsExitUtil, ipsExitTime);
+                }
             });
 }