xref: /openbmc/bmcweb/redfish-core/lib/power.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 // SPDX-FileCopyrightText: Copyright 2018 Ampere Computing LLC
5 
6 #pragma once
7 
8 #include "app.hpp"
9 #include "dbus_utility.hpp"
10 #include "generated/enums/power.hpp"
11 #include "query.hpp"
12 #include "registries/privilege_registry.hpp"
13 #include "sensors.hpp"
14 #include "utils/chassis_utils.hpp"
15 #include "utils/json_utils.hpp"
16 #include "utils/sensor_utils.hpp"
17 
18 #include <sdbusplus/asio/property.hpp>
19 
20 #include <array>
21 #include <string>
22 #include <string_view>
23 #include <vector>
24 
25 namespace redfish
26 {
27 
28 inline void afterGetPowerCapEnable(
29     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
30     uint32_t valueToSet, const boost::system::error_code& ec,
31     bool powerCapEnable)
32 {
33     if (ec)
34     {
35         messages::internalError(sensorsAsyncResp->asyncResp->res);
36         BMCWEB_LOG_ERROR("powerCapEnable Get handler: Dbus error {}", ec);
37         return;
38     }
39     if (!powerCapEnable)
40     {
41         messages::actionNotSupported(
42             sensorsAsyncResp->asyncResp->res,
43             "Setting LimitInWatts when PowerLimit feature is disabled");
44         BMCWEB_LOG_ERROR("PowerLimit feature is disabled ");
45         return;
46     }
47 
48     setDbusProperty(sensorsAsyncResp->asyncResp, "PowerControl",
49                     "xyz.openbmc_project.Settings",
50                     sdbusplus::message::object_path(
51                         "/xyz/openbmc_project/control/host0/power_cap"),
52                     "xyz.openbmc_project.Control.Power.Cap", "PowerCap",
53                     valueToSet);
54 }
55 
56 inline void afterGetChassisPath(
57     const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp,
58     std::vector<nlohmann::json::object_t>& powerControlCollections,
59     const std::optional<std::string>& chassisPath)
60 {
61     if (!chassisPath)
62     {
63         BMCWEB_LOG_WARNING("Don't find valid chassis path ");
64         messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, "Chassis",
65                                    sensorsAsyncResp->chassisId);
66         return;
67     }
68 
69     if (powerControlCollections.size() != 1)
70     {
71         BMCWEB_LOG_WARNING("Don't support multiple hosts at present ");
72         messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, "Power",
73                                    "PowerControl");
74         return;
75     }
76 
77     auto& item = powerControlCollections[0];
78 
79     std::optional<uint32_t> value;
80     if (!json_util::readJsonObject( //
81             item, sensorsAsyncResp->asyncResp->res, //
82             "PowerLimit/LimitInWatts", value //
83             ))
84     {
85         return;
86     }
87     if (!value)
88     {
89         return;
90     }
91     dbus::utility::getProperty<bool>(
92         "xyz.openbmc_project.Settings",
93         "/xyz/openbmc_project/control/host0/power_cap",
94         "xyz.openbmc_project.Control.Power.Cap", "PowerCapEnable",
95         std::bind_front(afterGetPowerCapEnable, sensorsAsyncResp, *value));
96 }
97 
98 inline void afterPowerCapSettingGet(
99     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
100     const boost::system::error_code& ec,
101     const dbus::utility::DBusPropertiesMap& properties)
102 {
103     if (ec)
104     {
105         messages::internalError(sensorAsyncResp->asyncResp->res);
106         BMCWEB_LOG_ERROR("Power Limit GetAll handler: Dbus error {}", ec);
107         return;
108     }
109 
110     nlohmann::json& tempArray =
111         sensorAsyncResp->asyncResp->res.jsonValue["PowerControl"];
112 
113     // Put multiple "sensors" into a single PowerControl, 0,
114     // so only create the first one
115     if (tempArray.empty())
116     {
117         // Mandatory properties odata.id and MemberId
118         // A warning without a odata.type
119         nlohmann::json::object_t powerControl;
120         powerControl["@odata.type"] = "#Power.v1_0_0.PowerControl";
121         powerControl["@odata.id"] =
122             "/redfish/v1/Chassis/" + sensorAsyncResp->chassisId +
123             "/Power#/PowerControl/0";
124         powerControl["Name"] = "Chassis Power Control";
125         powerControl["MemberId"] = "0";
126         tempArray.emplace_back(std::move(powerControl));
127     }
128 
129     nlohmann::json& sensorJson = tempArray.back();
130     bool enabled = false;
131     double powerCap = 0.0;
132     int64_t scale = 0;
133 
134     for (const std::pair<std::string, dbus::utility::DbusVariantType>&
135              property : properties)
136     {
137         if (property.first == "Scale")
138         {
139             const int64_t* i = std::get_if<int64_t>(&property.second);
140 
141             if (i != nullptr)
142             {
143                 scale = *i;
144             }
145         }
146         else if (property.first == "PowerCap")
147         {
148             const double* d = std::get_if<double>(&property.second);
149             const int64_t* i = std::get_if<int64_t>(&property.second);
150             const uint32_t* u = std::get_if<uint32_t>(&property.second);
151 
152             if (d != nullptr)
153             {
154                 powerCap = *d;
155             }
156             else if (i != nullptr)
157             {
158                 powerCap = static_cast<double>(*i);
159             }
160             else if (u != nullptr)
161             {
162                 powerCap = *u;
163             }
164         }
165         else if (property.first == "PowerCapEnable")
166         {
167             const bool* b = std::get_if<bool>(&property.second);
168 
169             if (b != nullptr)
170             {
171                 enabled = *b;
172             }
173         }
174     }
175 
176     // LimitException is Mandatory attribute as per OCP
177     // Baseline Profile - v1.0.0, so currently making it
178     // "NoAction" as default value to make it OCP Compliant.
179     sensorJson["PowerLimit"]["LimitException"] =
180         power::PowerLimitException::NoAction;
181 
182     if (enabled)
183     {
184         // Redfish specification indicates PowerLimit should
185         // be null if the limit is not enabled.
186         sensorJson["PowerLimit"]["LimitInWatts"] =
187             powerCap * std::pow(10, scale);
188     }
189 }
190 
191 using Mapper = dbus::utility::MapperGetSubTreePathsResponse;
192 inline void afterGetChassis(
193     const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
194     const boost::system::error_code& ec2, const Mapper& chassisPaths)
195 {
196     if (ec2)
197     {
198         BMCWEB_LOG_ERROR("Power Limit GetSubTreePaths handler Dbus error {}",
199                          ec2);
200         return;
201     }
202 
203     bool found = false;
204     for (const std::string& chassis : chassisPaths)
205     {
206         size_t len = std::string::npos;
207         size_t lastPos = chassis.rfind('/');
208         if (lastPos == std::string::npos)
209         {
210             continue;
211         }
212 
213         if (lastPos == chassis.size() - 1)
214         {
215             size_t end = lastPos;
216             lastPos = chassis.rfind('/', lastPos - 1);
217             if (lastPos == std::string::npos)
218             {
219                 continue;
220             }
221 
222             len = end - (lastPos + 1);
223         }
224 
225         std::string interfaceChassisName = chassis.substr(lastPos + 1, len);
226         if (interfaceChassisName == sensorAsyncResp->chassisId)
227         {
228             found = true;
229             break;
230         }
231     }
232 
233     if (!found)
234     {
235         BMCWEB_LOG_DEBUG("Power Limit not present for {}",
236                          sensorAsyncResp->chassisId);
237         return;
238     }
239 
240     dbus::utility::getAllProperties(
241         "xyz.openbmc_project.Settings",
242         "/xyz/openbmc_project/control/host0/power_cap",
243         "xyz.openbmc_project.Control.Power.Cap",
244         [sensorAsyncResp](const boost::system::error_code& ec,
245                           const dbus::utility::DBusPropertiesMap& properties
246 
247         ) { afterPowerCapSettingGet(sensorAsyncResp, ec, properties); });
248 }
249 
250 inline void
251     handleChassisPowerGet(App& app, const crow::Request& req,
252                           const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
253                           const std::string& chassisName)
254 {
255     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
256     {
257         return;
258     }
259     asyncResp->res.jsonValue["PowerControl"] = nlohmann::json::array();
260 
261     auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
262         asyncResp, chassisName, sensors::dbus::powerPaths,
263         sensor_utils::chassisSubNodeToString(
264             sensor_utils::ChassisSubNode::powerNode));
265 
266     getChassisData(sensorAsyncResp);
267 
268     // This callback verifies that the power limit is only provided
269     // for the chassis that implements the Chassis inventory item.
270     // This prevents things like power supplies providing the
271     // chassis power limit
272 
273     constexpr std::array<std::string_view, 2> interfaces = {
274         "xyz.openbmc_project.Inventory.Item.Board",
275         "xyz.openbmc_project.Inventory.Item.Chassis"};
276 
277     dbus::utility::getSubTreePaths(
278         "/xyz/openbmc_project/inventory", 0, interfaces,
279         std::bind_front(afterGetChassis, sensorAsyncResp));
280 }
281 
282 inline void
283     handleChassisPowerPatch(App& app, const crow::Request& req,
284                             const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
285                             const std::string& chassisName)
286 {
287     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
288     {
289         return;
290     }
291     auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
292         asyncResp, chassisName, sensors::dbus::powerPaths,
293         sensor_utils::chassisSubNodeToString(
294             sensor_utils::ChassisSubNode::powerNode));
295 
296     std::optional<std::vector<nlohmann::json::object_t>> voltageCollections;
297     std::optional<std::vector<nlohmann::json::object_t>> powerCtlCollections;
298 
299     if (!json_util::readJsonPatch( //
300             req, sensorAsyncResp->asyncResp->res, //
301             "PowerControl", powerCtlCollections, //
302             "Voltages", voltageCollections //
303             ))
304     {
305         return;
306     }
307 
308     if (powerCtlCollections)
309     {
310         redfish::chassis_utils::getValidChassisPath(
311             sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId,
312             std::bind_front(afterGetChassisPath, sensorAsyncResp,
313                             *powerCtlCollections));
314     }
315     if (voltageCollections)
316     {
317         std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>
318             allCollections;
319         allCollections.emplace("Voltages", std::move(*voltageCollections));
320         setSensorsOverride(sensorAsyncResp, allCollections);
321     }
322 }
323 
324 inline void requestRoutesPower(App& app)
325 {
326     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/")
327         .privileges(redfish::privileges::getPower)
328         .methods(boost::beast::http::verb::get)(
329             std::bind_front(handleChassisPowerGet, std::ref(app)));
330 
331     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/")
332         .privileges(redfish::privileges::patchPower)
333         .methods(boost::beast::http::verb::patch)(
334             std::bind_front(handleChassisPowerPatch, std::ref(app)));
335 }
336 
337 } // namespace redfish
338