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