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 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 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 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; 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 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 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 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