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