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