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