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