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