Add support for Redfish OperatingConfig resources

- Update Processor GET handler to look for a matching
  CurrentOperatingConfig interface on D-Bus.
- Add OperatingConfig node and implement GET handler to look for
  matching OperatingConfig interface on D-Bus.
- Add OperatingConfigCollection node and implement GET handler to look
  for all OperatingConfig interfaces on D-Bus under the given cpu.

Tested:
 - Ran Redfish Service Validator and verified no errors or warnings on
   Processor, OperatingConfig, and OperatingConfigCollection.
 - Browsed OperatingConfig links and resources in browser, confirmed
   nonexistent configs returned 404 and didn't crash bmcweb.
 - Killed D-Bus provider service and verified bmcweb didn't crash and
   still served Processor as before, and passed service validator.

Change-Id: Iab94b7fd49a9462cb0eca6f8ea0754f5fb241053
Signed-off-by: Jonathan Doman <jonathan.doman@intel.com>
diff --git a/redfish-core/lib/processor.hpp b/redfish-core/lib/processor.hpp
index 82c01f9..e85b616 100644
--- a/redfish-core/lib/processor.hpp
+++ b/redfish-core/lib/processor.hpp
@@ -19,6 +19,8 @@
 
 #include <boost/container/flat_map.hpp>
 #include <node.hpp>
+#include <sdbusplus/message/native_types.hpp>
+#include <sdbusplus/utility/dedup_variant.hpp>
 #include <utils/collection.hpp>
 #include <utils/json_utils.hpp>
 
@@ -380,6 +382,168 @@
         service, objPath, "org.freedesktop.DBus.Properties", "GetAll", "");
 }
 
+// OperatingConfig D-Bus Types
+using TurboProfileProperty = std::vector<std::tuple<uint32_t, size_t>>;
+using BaseSpeedPrioritySettingsProperty =
+    std::vector<std::tuple<uint32_t, std::vector<uint32_t>>>;
+// uint32_t and size_t may or may not be the same type, requiring a dedup'd
+// variant
+using OperatingConfigProperties = std::vector<std::pair<
+    std::string,
+    sdbusplus::utility::dedup_variant<uint32_t, size_t, TurboProfileProperty,
+                                      BaseSpeedPrioritySettingsProperty>>>;
+
+/**
+ * Fill out the HighSpeedCoreIDs in a Processor resource from the given
+ * OperatingConfig D-Bus property.
+ *
+ * @param[in,out]   aResp               Async HTTP response.
+ * @param[in]       baseSpeedSettings   Full list of base speed priority groups,
+ *                                      to use to determine the list of high
+ *                                      speed cores.
+ */
+inline void highSpeedCoreIdsHandler(
+    const std::shared_ptr<AsyncResp>& aResp,
+    const BaseSpeedPrioritySettingsProperty& baseSpeedSettings)
+{
+    // The D-Bus property does not indicate which bucket is the "high
+    // priority" group, so let's discern that by looking for the one with
+    // highest base frequency.
+    auto highPriorityGroup = baseSpeedSettings.cend();
+    uint32_t highestBaseSpeed = 0;
+    for (auto it = baseSpeedSettings.cbegin(); it != baseSpeedSettings.cend();
+         ++it)
+    {
+        const uint32_t baseFreq = std::get<uint32_t>(*it);
+        if (baseFreq > highestBaseSpeed)
+        {
+            highestBaseSpeed = baseFreq;
+            highPriorityGroup = it;
+        }
+    }
+
+    nlohmann::json& jsonCoreIds = aResp->res.jsonValue["HighSpeedCoreIDs"];
+    jsonCoreIds = nlohmann::json::array();
+
+    // There may not be any entries in the D-Bus property, so only populate
+    // if there was actually something there.
+    if (highPriorityGroup != baseSpeedSettings.cend())
+    {
+        jsonCoreIds = std::get<std::vector<uint32_t>>(*highPriorityGroup);
+    }
+}
+
+/**
+ * Fill out OperatingConfig related items in a Processor resource by requesting
+ * data from the given D-Bus object.
+ *
+ * @param[in,out]   aResp       Async HTTP response.
+ * @param[in]       cpuId       CPU D-Bus name.
+ * @param[in]       service     D-Bus service to query.
+ * @param[in]       objPath     D-Bus object to query.
+ */
+inline void getCpuConfigData(const std::shared_ptr<AsyncResp>& aResp,
+                             const std::string& cpuId,
+                             const std::string& service,
+                             const std::string& objPath)
+{
+    BMCWEB_LOG_INFO << "Getting CPU operating configs for " << cpuId;
+
+    // First, GetAll CurrentOperatingConfig properties on the object
+    crow::connections::systemBus->async_method_call(
+        [aResp, cpuId, service](
+            const boost::system::error_code ec,
+            const std::vector<
+                std::pair<std::string,
+                          std::variant<sdbusplus::message::object_path, bool>>>&
+                properties) {
+            if (ec)
+            {
+                BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", "
+                                   << ec.message();
+                messages::internalError(aResp->res);
+                return;
+            }
+
+            nlohmann::json& json = aResp->res.jsonValue;
+
+            for (const auto& [dbusPropName, variantVal] : properties)
+            {
+                if (dbusPropName == "AppliedConfig")
+                {
+                    const sdbusplus::message::object_path* dbusPathWrapper =
+                        std::get_if<sdbusplus::message::object_path>(
+                            &variantVal);
+                    if (dbusPathWrapper == nullptr)
+                    {
+                        continue;
+                    }
+
+                    const std::string& dbusPath = dbusPathWrapper->str;
+                    std::string uri = "/redfish/v1/Systems/system/Processors/" +
+                                      cpuId + "/OperatingConfigs";
+                    json["OperatingConfigs"] = {{"@odata.id", uri}};
+
+                    // Reuse the D-Bus config object name for the Redfish
+                    // URI
+                    size_t baseNamePos = dbusPath.rfind('/');
+                    if (baseNamePos == std::string::npos ||
+                        baseNamePos == (dbusPath.size() - 1))
+                    {
+                        // If the AppliedConfig was somehow not a valid path,
+                        // skip adding any more properties, since everything
+                        // else is tied to this applied config.
+                        messages::internalError(aResp->res);
+                        break;
+                    }
+                    uri += '/';
+                    uri += dbusPath.substr(baseNamePos + 1);
+                    json["AppliedOperatingConfig"] = {{"@odata.id", uri}};
+
+                    // Once we found the current applied config, queue another
+                    // request to read the base freq core ids out of that
+                    // config.
+                    crow::connections::systemBus->async_method_call(
+                        [aResp](
+                            const boost::system::error_code ec,
+                            const std::variant<
+                                BaseSpeedPrioritySettingsProperty>& property) {
+                            if (ec)
+                            {
+                                BMCWEB_LOG_WARNING
+                                    << "D-Bus Property Get error: " << ec;
+                                messages::internalError(aResp->res);
+                                return;
+                            }
+                            auto baseSpeedList =
+                                std::get_if<BaseSpeedPrioritySettingsProperty>(
+                                    &property);
+                            if (baseSpeedList != nullptr)
+                            {
+                                highSpeedCoreIdsHandler(aResp, *baseSpeedList);
+                            }
+                        },
+                        service, dbusPath, "org.freedesktop.DBus.Properties",
+                        "Get",
+                        "xyz.openbmc_project.Inventory.Item.Cpu."
+                        "OperatingConfig",
+                        "BaseSpeedPrioritySettings");
+                }
+                else if (dbusPropName == "BaseSpeedPriorityEnabled")
+                {
+                    const bool* state = std::get_if<bool>(&variantVal);
+                    if (state != nullptr)
+                    {
+                        json["BaseSpeedPriorityState"] =
+                            *state ? "Enabled" : "Disabled";
+                    }
+                }
+            }
+        },
+        service, objPath, "org.freedesktop.DBus.Properties", "GetAll",
+        "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig");
+}
+
 inline void getProcessorData(std::shared_ptr<AsyncResp> aResp,
                              const std::string& processorId)
 {
@@ -434,6 +598,13 @@
                             getAcceleratorDataByService(
                                 aResp, processorId, serviceName, objectPath);
                         }
+                        else if (interface ==
+                                 "xyz.openbmc_project.Control.Processor."
+                                 "CurrentOperatingConfig")
+                        {
+                            getCpuConfigData(aResp, processorId, serviceName,
+                                             objectPath);
+                        }
                     }
                 }
                 return;
@@ -446,13 +617,288 @@
         "/xyz/openbmc_project/object_mapper",
         "xyz.openbmc_project.ObjectMapper", "GetSubTree",
         "/xyz/openbmc_project/inventory", 0,
-        std::array<const char*, 4>{
+        std::array<const char*, 5>{
             "xyz.openbmc_project.Inventory.Decorator.Asset",
             "xyz.openbmc_project.Inventory.Decorator.Revision",
             "xyz.openbmc_project.Inventory.Item.Cpu",
-            "xyz.openbmc_project.Inventory.Item.Accelerator"});
+            "xyz.openbmc_project.Inventory.Item.Accelerator",
+            "xyz.openbmc_project.Control.Processor.CurrentOperatingConfig"});
 }
 
+/**
+ * Request all the properties for the given D-Bus object and fill out the
+ * related entries in the Redfish OperatingConfig response.
+ *
+ * @param[in,out]   aResp       Async HTTP response.
+ * @param[in]       service     D-Bus service name to query.
+ * @param[in]       objPath     D-Bus object to query.
+ */
+inline void getOperatingConfigData(const std::shared_ptr<AsyncResp>& aResp,
+                                   const std::string& service,
+                                   const std::string& objPath)
+{
+    crow::connections::systemBus->async_method_call(
+        [aResp](boost::system::error_code ec,
+                const OperatingConfigProperties& properties) {
+            if (ec)
+            {
+                BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", "
+                                   << ec.message();
+                messages::internalError(aResp->res);
+                return;
+            }
+
+            nlohmann::json& json = aResp->res.jsonValue;
+            for (const auto& [key, variant] : properties)
+            {
+                if (key == "AvailableCoreCount")
+                {
+                    const size_t* cores = std::get_if<size_t>(&variant);
+                    if (cores != nullptr)
+                    {
+                        json["TotalAvailableCoreCount"] = *cores;
+                    }
+                }
+                else if (key == "BaseSpeed")
+                {
+                    const uint32_t* speed = std::get_if<uint32_t>(&variant);
+                    if (speed != nullptr)
+                    {
+                        json["BaseSpeedMHz"] = *speed;
+                    }
+                }
+                else if (key == "MaxJunctionTemperature")
+                {
+                    const uint32_t* temp = std::get_if<uint32_t>(&variant);
+                    if (temp != nullptr)
+                    {
+                        json["MaxJunctionTemperatureCelsius"] = *temp;
+                    }
+                }
+                else if (key == "MaxSpeed")
+                {
+                    const uint32_t* speed = std::get_if<uint32_t>(&variant);
+                    if (speed != nullptr)
+                    {
+                        json["MaxSpeedMHz"] = *speed;
+                    }
+                }
+                else if (key == "PowerLimit")
+                {
+                    const uint32_t* tdp = std::get_if<uint32_t>(&variant);
+                    if (tdp != nullptr)
+                    {
+                        json["TDPWatts"] = *tdp;
+                    }
+                }
+                else if (key == "TurboProfile")
+                {
+                    const auto* turboList =
+                        std::get_if<TurboProfileProperty>(&variant);
+                    if (turboList == nullptr)
+                    {
+                        continue;
+                    }
+
+                    nlohmann::json& turboArray = json["TurboProfile"];
+                    turboArray = nlohmann::json::array();
+                    for (const auto& [turboSpeed, coreCount] : *turboList)
+                    {
+                        turboArray.push_back({{"ActiveCoreCount", coreCount},
+                                              {"MaxSpeedMHz", turboSpeed}});
+                    }
+                }
+                else if (key == "BaseSpeedPrioritySettings")
+                {
+                    const auto* baseSpeedList =
+                        std::get_if<BaseSpeedPrioritySettingsProperty>(
+                            &variant);
+                    if (baseSpeedList == nullptr)
+                    {
+                        continue;
+                    }
+
+                    nlohmann::json& baseSpeedArray =
+                        json["BaseSpeedPrioritySettings"];
+                    baseSpeedArray = nlohmann::json::array();
+                    for (const auto& [baseSpeed, coreList] : *baseSpeedList)
+                    {
+                        baseSpeedArray.push_back(
+                            {{"CoreCount", coreList.size()},
+                             {"CoreIDs", coreList},
+                             {"BaseSpeedMHz", baseSpeed}});
+                    }
+                }
+            }
+        },
+        service, objPath, "org.freedesktop.DBus.Properties", "GetAll",
+        "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig");
+}
+
+class OperatingConfigCollection : public Node
+{
+  public:
+    OperatingConfigCollection(App& app) :
+        Node(app,
+             "/redfish/v1/Systems/system/Processors/<str>/OperatingConfigs/",
+             std::string())
+    {
+        // Defined by Redfish spec privilege registry
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+    }
+
+  private:
+    void doGet(crow::Response& res, const crow::Request& req,
+               const std::vector<std::string>& params) override
+    {
+        if (params.size() != 1)
+        {
+            messages::internalError(res);
+            res.end();
+            return;
+        }
+
+        const std::string& cpuName = params[0];
+        res.jsonValue["@odata.type"] =
+            "#OperatingConfigCollection.OperatingConfigCollection";
+        res.jsonValue["@odata.id"] = req.url;
+        res.jsonValue["Name"] = "Operating Config Collection";
+
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        // First find the matching CPU object so we know how to constrain our
+        // search for related Config objects.
+        crow::connections::systemBus->async_method_call(
+            [asyncResp, cpuName](const boost::system::error_code ec,
+                                 const std::vector<std::string>& objects) {
+                if (ec)
+                {
+                    BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", "
+                                       << ec.message();
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+
+                for (const std::string& object : objects)
+                {
+                    if (!boost::ends_with(object, cpuName))
+                    {
+                        continue;
+                    }
+
+                    // Not expected that there will be multiple matching CPU
+                    // objects, but if there are just use the first one.
+
+                    // Use the common search routine to construct the Collection
+                    // of all Config objects under this CPU.
+                    collection_util::getCollectionMembers(
+                        asyncResp,
+                        "/redfish/v1/Systems/system/Processors/" + cpuName +
+                            "/OperatingConfigs",
+                        {"xyz.openbmc_project.Inventory.Item.Cpu."
+                         "OperatingConfig"},
+                        object.c_str());
+                    return;
+                }
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths",
+            "/xyz/openbmc_project/inventory", 0,
+            std::array<const char*, 1>{"xyz.openbmc_project.Control.Processor."
+                                       "CurrentOperatingConfig"});
+    }
+};
+
+class OperatingConfig : public Node
+{
+  public:
+    OperatingConfig(App& app) :
+        Node(app,
+             "/redfish/v1/Systems/system/Processors/<str>/OperatingConfigs/"
+             "<str>/",
+             std::string(), std::string())
+    {
+        // Defined by Redfish spec privilege registry
+        entityPrivileges = {
+            {boost::beast::http::verb::get, {{"Login"}}},
+            {boost::beast::http::verb::head, {{"Login"}}},
+            {boost::beast::http::verb::patch, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::put, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}},
+            {boost::beast::http::verb::post, {{"ConfigureComponents"}}}};
+    }
+
+  private:
+    void doGet(crow::Response& res, const crow::Request& req,
+               const std::vector<std::string>& params) override
+    {
+        if (params.size() != 2)
+        {
+            messages::internalError(res);
+            res.end();
+            return;
+        }
+
+        const std::string& cpuName = params[0];
+        const std::string& configName = params[1];
+
+        auto asyncResp = std::make_shared<AsyncResp>(res);
+
+        // Ask for all objects implementing OperatingConfig so we can search for
+        // one with a matching name
+        crow::connections::systemBus->async_method_call(
+            [asyncResp, cpuName, configName,
+             reqUrl{req.url}](boost::system::error_code ec,
+                              const MapperGetSubTreeResponse& subtree) {
+                if (ec)
+                {
+                    BMCWEB_LOG_WARNING << "D-Bus error: " << ec << ", "
+                                       << ec.message();
+                    messages::internalError(asyncResp->res);
+                    return;
+                }
+                const std::string expectedEnding = cpuName + '/' + configName;
+                for (const auto& [objectPath, serviceMap] : subtree)
+                {
+                    // Ignore any configs without matching cpuX/configY
+                    if (!boost::ends_with(objectPath, expectedEnding) ||
+                        serviceMap.empty())
+                    {
+                        continue;
+                    }
+
+                    nlohmann::json& json = asyncResp->res.jsonValue;
+                    json["@odata.type"] =
+                        "#OperatingConfig.v1_0_0.OperatingConfig";
+                    json["@odata.id"] = reqUrl;
+                    json["Name"] = "Processor Profile";
+                    json["Id"] = configName;
+
+                    // Just use the first implementation of the object - not
+                    // expected that there would be multiple matching services
+                    getOperatingConfigData(asyncResp, serviceMap.begin()->first,
+                                           objectPath);
+                    return;
+                }
+                messages::resourceNotFound(asyncResp->res, "OperatingConfig",
+                                           configName);
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTree",
+            "/xyz/openbmc_project/inventory", 0,
+            std::array<const char*, 1>{
+                "xyz.openbmc_project.Inventory.Item.Cpu.OperatingConfig"});
+    }
+};
+
 class ProcessorCollection : public Node
 {
   public: