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