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