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