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