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"] = 130 "/redfish/v1/Chassis/" + 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"] = 195 powerCap * std::pow(10, scale); 196 } 197 } 198 199 using Mapper = dbus::utility::MapperGetSubTreePathsResponse; 200 inline void afterGetChassis( 201 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 202 const boost::system::error_code& ec2, const Mapper& chassisPaths) 203 { 204 if (ec2) 205 { 206 BMCWEB_LOG_ERROR("Power Limit GetSubTreePaths handler Dbus error {}", 207 ec2); 208 return; 209 } 210 211 bool found = false; 212 for (const std::string& chassis : chassisPaths) 213 { 214 size_t len = std::string::npos; 215 size_t lastPos = chassis.rfind('/'); 216 if (lastPos == std::string::npos) 217 { 218 continue; 219 } 220 221 if (lastPos == chassis.size() - 1) 222 { 223 size_t end = lastPos; 224 lastPos = chassis.rfind('/', lastPos - 1); 225 if (lastPos == std::string::npos) 226 { 227 continue; 228 } 229 230 len = end - (lastPos + 1); 231 } 232 233 std::string interfaceChassisName = chassis.substr(lastPos + 1, len); 234 if (interfaceChassisName == sensorAsyncResp->chassisId) 235 { 236 found = true; 237 break; 238 } 239 } 240 241 if (!found) 242 { 243 BMCWEB_LOG_DEBUG("Power Limit not present for {}", 244 sensorAsyncResp->chassisId); 245 return; 246 } 247 248 sdbusplus::asio::getAllProperties( 249 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 250 "/xyz/openbmc_project/control/host0/power_cap", 251 "xyz.openbmc_project.Control.Power.Cap", 252 [sensorAsyncResp](const boost::system::error_code& ec, 253 const dbus::utility::DBusPropertiesMap& properties 254 255 ) { afterPowerCapSettingGet(sensorAsyncResp, ec, properties); }); 256 } 257 258 inline void 259 handleChassisPowerGet(App& app, const crow::Request& req, 260 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 261 const std::string& chassisName) 262 { 263 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 264 { 265 return; 266 } 267 asyncResp->res.jsonValue["PowerControl"] = nlohmann::json::array(); 268 269 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 270 asyncResp, chassisName, sensors::dbus::powerPaths, 271 sensors::node::power); 272 273 getChassisData(sensorAsyncResp); 274 275 // This callback verifies that the power limit is only provided 276 // for the chassis that implements the Chassis inventory item. 277 // This prevents things like power supplies providing the 278 // chassis power limit 279 280 constexpr std::array<std::string_view, 2> interfaces = { 281 "xyz.openbmc_project.Inventory.Item.Board", 282 "xyz.openbmc_project.Inventory.Item.Chassis"}; 283 284 dbus::utility::getSubTreePaths( 285 "/xyz/openbmc_project/inventory", 0, interfaces, 286 std::bind_front(afterGetChassis, sensorAsyncResp)); 287 } 288 289 inline void 290 handleChassisPowerPatch(App& app, const crow::Request& req, 291 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 292 const std::string& chassisName) 293 { 294 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 295 { 296 return; 297 } 298 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 299 asyncResp, chassisName, sensors::dbus::powerPaths, 300 sensors::node::power); 301 302 std::optional<std::vector<nlohmann::json::object_t>> voltageCollections; 303 std::optional<std::vector<nlohmann::json::object_t>> powerCtlCollections; 304 305 if (!json_util::readJsonPatch(req, sensorAsyncResp->asyncResp->res, 306 "PowerControl", powerCtlCollections, 307 "Voltages", voltageCollections)) 308 { 309 return; 310 } 311 312 if (powerCtlCollections) 313 { 314 redfish::chassis_utils::getValidChassisPath( 315 sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId, 316 std::bind_front(afterGetChassisPath, sensorAsyncResp, 317 *powerCtlCollections)); 318 } 319 if (voltageCollections) 320 { 321 std::unordered_map<std::string, std::vector<nlohmann::json::object_t>> 322 allCollections; 323 allCollections.emplace("Voltages", std::move(*voltageCollections)); 324 setSensorsOverride(sensorAsyncResp, allCollections); 325 } 326 } 327 328 inline void requestRoutesPower(App& app) 329 { 330 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 331 .privileges(redfish::privileges::getPower) 332 .methods(boost::beast::http::verb::get)( 333 std::bind_front(handleChassisPowerGet, std::ref(app))); 334 335 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 336 .privileges(redfish::privileges::patchPower) 337 .methods(boost::beast::http::verb::patch)( 338 std::bind_front(handleChassisPowerPatch, std::ref(app))); 339 } 340 341 } // namespace redfish 342