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(item, sensorsAsyncResp->asyncResp->res,
92 "PowerLimit/LimitInWatts", value))
93 {
94 return;
95 }
96 if (!value)
97 {
98 return;
99 }
100 sdbusplus::asio::getProperty<bool>(
101 *crow::connections::systemBus, "xyz.openbmc_project.Settings",
102 "/xyz/openbmc_project/control/host0/power_cap",
103 "xyz.openbmc_project.Control.Power.Cap", "PowerCapEnable",
104 std::bind_front(afterGetPowerCapEnable, sensorsAsyncResp, *value));
105 }
106
afterPowerCapSettingGet(const std::shared_ptr<SensorsAsyncResp> & sensorAsyncResp,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & properties)107 inline void afterPowerCapSettingGet(
108 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
109 const boost::system::error_code& ec,
110 const dbus::utility::DBusPropertiesMap& properties)
111 {
112 if (ec)
113 {
114 messages::internalError(sensorAsyncResp->asyncResp->res);
115 BMCWEB_LOG_ERROR("Power Limit GetAll handler: Dbus error {}", ec);
116 return;
117 }
118
119 nlohmann::json& tempArray =
120 sensorAsyncResp->asyncResp->res.jsonValue["PowerControl"];
121
122 // Put multiple "sensors" into a single PowerControl, 0,
123 // so only create the first one
124 if (tempArray.empty())
125 {
126 // Mandatory properties odata.id and MemberId
127 // A warning without a odata.type
128 nlohmann::json::object_t powerControl;
129 powerControl["@odata.type"] = "#Power.v1_0_0.PowerControl";
130 powerControl["@odata.id"] =
131 "/redfish/v1/Chassis/" + sensorAsyncResp->chassisId +
132 "/Power#/PowerControl/0";
133 powerControl["Name"] = "Chassis Power Control";
134 powerControl["MemberId"] = "0";
135 tempArray.emplace_back(std::move(powerControl));
136 }
137
138 nlohmann::json& sensorJson = tempArray.back();
139 bool enabled = false;
140 double powerCap = 0.0;
141 int64_t scale = 0;
142
143 for (const std::pair<std::string, dbus::utility::DbusVariantType>&
144 property : properties)
145 {
146 if (property.first == "Scale")
147 {
148 const int64_t* i = std::get_if<int64_t>(&property.second);
149
150 if (i != nullptr)
151 {
152 scale = *i;
153 }
154 }
155 else if (property.first == "PowerCap")
156 {
157 const double* d = std::get_if<double>(&property.second);
158 const int64_t* i = std::get_if<int64_t>(&property.second);
159 const uint32_t* u = std::get_if<uint32_t>(&property.second);
160
161 if (d != nullptr)
162 {
163 powerCap = *d;
164 }
165 else if (i != nullptr)
166 {
167 powerCap = static_cast<double>(*i);
168 }
169 else if (u != nullptr)
170 {
171 powerCap = *u;
172 }
173 }
174 else if (property.first == "PowerCapEnable")
175 {
176 const bool* b = std::get_if<bool>(&property.second);
177
178 if (b != nullptr)
179 {
180 enabled = *b;
181 }
182 }
183 }
184
185 // LimitException is Mandatory attribute as per OCP
186 // Baseline Profile - v1.0.0, so currently making it
187 // "NoAction" as default value to make it OCP Compliant.
188 sensorJson["PowerLimit"]["LimitException"] =
189 power::PowerLimitException::NoAction;
190
191 if (enabled)
192 {
193 // Redfish specification indicates PowerLimit should
194 // be null if the limit is not enabled.
195 sensorJson["PowerLimit"]["LimitInWatts"] =
196 powerCap * std::pow(10, scale);
197 }
198 }
199
200 using Mapper = dbus::utility::MapperGetSubTreePathsResponse;
afterGetChassis(const std::shared_ptr<SensorsAsyncResp> & sensorAsyncResp,const boost::system::error_code & ec2,const Mapper & chassisPaths)201 inline void afterGetChassis(
202 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp,
203 const boost::system::error_code& ec2, const Mapper& chassisPaths)
204 {
205 if (ec2)
206 {
207 BMCWEB_LOG_ERROR("Power Limit GetSubTreePaths handler Dbus error {}",
208 ec2);
209 return;
210 }
211
212 bool found = false;
213 for (const std::string& chassis : chassisPaths)
214 {
215 size_t len = std::string::npos;
216 size_t lastPos = chassis.rfind('/');
217 if (lastPos == std::string::npos)
218 {
219 continue;
220 }
221
222 if (lastPos == chassis.size() - 1)
223 {
224 size_t end = lastPos;
225 lastPos = chassis.rfind('/', lastPos - 1);
226 if (lastPos == std::string::npos)
227 {
228 continue;
229 }
230
231 len = end - (lastPos + 1);
232 }
233
234 std::string interfaceChassisName = chassis.substr(lastPos + 1, len);
235 if (interfaceChassisName == sensorAsyncResp->chassisId)
236 {
237 found = true;
238 break;
239 }
240 }
241
242 if (!found)
243 {
244 BMCWEB_LOG_DEBUG("Power Limit not present for {}",
245 sensorAsyncResp->chassisId);
246 return;
247 }
248
249 sdbusplus::asio::getAllProperties(
250 *crow::connections::systemBus, "xyz.openbmc_project.Settings",
251 "/xyz/openbmc_project/control/host0/power_cap",
252 "xyz.openbmc_project.Control.Power.Cap",
253 [sensorAsyncResp](const boost::system::error_code& ec,
254 const dbus::utility::DBusPropertiesMap& properties
255
256 ) { afterPowerCapSettingGet(sensorAsyncResp, ec, properties); });
257 }
258
259 inline void
handleChassisPowerGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisName)260 handleChassisPowerGet(App& app, const crow::Request& req,
261 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
262 const std::string& chassisName)
263 {
264 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
265 {
266 return;
267 }
268 asyncResp->res.jsonValue["PowerControl"] = nlohmann::json::array();
269
270 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
271 asyncResp, chassisName, sensors::dbus::powerPaths,
272 sensor_utils::chassisSubNodeToString(
273 sensor_utils::ChassisSubNode::powerNode));
274
275 getChassisData(sensorAsyncResp);
276
277 // This callback verifies that the power limit is only provided
278 // for the chassis that implements the Chassis inventory item.
279 // This prevents things like power supplies providing the
280 // chassis power limit
281
282 constexpr std::array<std::string_view, 2> interfaces = {
283 "xyz.openbmc_project.Inventory.Item.Board",
284 "xyz.openbmc_project.Inventory.Item.Chassis"};
285
286 dbus::utility::getSubTreePaths(
287 "/xyz/openbmc_project/inventory", 0, interfaces,
288 std::bind_front(afterGetChassis, sensorAsyncResp));
289 }
290
291 inline void
handleChassisPowerPatch(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisName)292 handleChassisPowerPatch(App& app, const crow::Request& req,
293 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
294 const std::string& chassisName)
295 {
296 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
297 {
298 return;
299 }
300 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>(
301 asyncResp, chassisName, sensors::dbus::powerPaths,
302 sensor_utils::chassisSubNodeToString(
303 sensor_utils::ChassisSubNode::powerNode));
304
305 std::optional<std::vector<nlohmann::json::object_t>> voltageCollections;
306 std::optional<std::vector<nlohmann::json::object_t>> powerCtlCollections;
307
308 if (!json_util::readJsonPatch(req, sensorAsyncResp->asyncResp->res,
309 "PowerControl", powerCtlCollections,
310 "Voltages", voltageCollections))
311 {
312 return;
313 }
314
315 if (powerCtlCollections)
316 {
317 redfish::chassis_utils::getValidChassisPath(
318 sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId,
319 std::bind_front(afterGetChassisPath, sensorAsyncResp,
320 *powerCtlCollections));
321 }
322 if (voltageCollections)
323 {
324 std::unordered_map<std::string, std::vector<nlohmann::json::object_t>>
325 allCollections;
326 allCollections.emplace("Voltages", std::move(*voltageCollections));
327 setSensorsOverride(sensorAsyncResp, allCollections);
328 }
329 }
330
requestRoutesPower(App & app)331 inline void requestRoutesPower(App& app)
332 {
333 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/")
334 .privileges(redfish::privileges::getPower)
335 .methods(boost::beast::http::verb::get)(
336 std::bind_front(handleChassisPowerGet, std::ref(app)));
337
338 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/")
339 .privileges(redfish::privileges::patchPower)
340 .methods(boost::beast::http::verb::patch)(
341 std::bind_front(handleChassisPowerPatch, std::ref(app)));
342 }
343
344 } // namespace redfish
345