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