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/include/redfish.hpp b/redfish-core/include/redfish.hpp
index 54d5d0e..5d5eb7b 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -147,6 +147,8 @@
nodes.emplace_back(std::make_unique<ProcessorCollection>(app));
nodes.emplace_back(std::make_unique<Processor>(app));
+ nodes.emplace_back(std::make_unique<OperatingConfigCollection>(app));
+ nodes.emplace_back(std::make_unique<OperatingConfig>(app));
nodes.emplace_back(std::make_unique<MemoryCollection>(app));
nodes.emplace_back(std::make_unique<Memory>(app));
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: