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 "sensors.hpp" 20 21 #include <app.hpp> 22 #include <dbus_utility.hpp> 23 #include <query.hpp> 24 #include <registries/privilege_registry.hpp> 25 26 namespace redfish 27 { 28 inline void setPowerCapOverride( 29 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 30 std::vector<nlohmann::json>& powerControlCollections) 31 { 32 auto getChassisPath = [sensorsAsyncResp, powerControlCollections]( 33 const std::optional<std::string>& 34 chassisPath) mutable { 35 if (!chassisPath) 36 { 37 BMCWEB_LOG_ERROR << "Don't find valid chassis path "; 38 messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, 39 "Chassis", sensorsAsyncResp->chassisId); 40 return; 41 } 42 43 if (powerControlCollections.size() != 1) 44 { 45 BMCWEB_LOG_ERROR << "Don't support multiple hosts at present "; 46 messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, 47 "Power", "PowerControl"); 48 return; 49 } 50 51 auto& item = powerControlCollections[0]; 52 53 std::optional<nlohmann::json> powerLimit; 54 if (!json_util::readJson(item, sensorsAsyncResp->asyncResp->res, 55 "PowerLimit", powerLimit)) 56 { 57 return; 58 } 59 if (!powerLimit) 60 { 61 return; 62 } 63 std::optional<uint32_t> value; 64 if (!json_util::readJson(*powerLimit, sensorsAsyncResp->asyncResp->res, 65 "LimitInWatts", value)) 66 { 67 return; 68 } 69 if (!value) 70 { 71 return; 72 } 73 sdbusplus::asio::getProperty<bool>( 74 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 75 "/xyz/openbmc_project/control/host0/power_cap", 76 "xyz.openbmc_project.Control.Power.Cap", "PowerCapEnable", 77 [value, sensorsAsyncResp](const boost::system::error_code ec, 78 bool powerCapEnable) { 79 if (ec) 80 { 81 messages::internalError(sensorsAsyncResp->asyncResp->res); 82 BMCWEB_LOG_ERROR 83 << "powerCapEnable Get handler: Dbus error " << ec; 84 return; 85 } 86 if (!powerCapEnable) 87 { 88 messages::actionNotSupported( 89 sensorsAsyncResp->asyncResp->res, 90 "Setting LimitInWatts when PowerLimit feature is disabled"); 91 BMCWEB_LOG_ERROR << "PowerLimit feature is disabled "; 92 return; 93 } 94 95 crow::connections::systemBus->async_method_call( 96 [sensorsAsyncResp](const boost::system::error_code ec2) { 97 if (ec2) 98 { 99 BMCWEB_LOG_DEBUG << "Power Limit Set: Dbus error: " 100 << ec2; 101 messages::internalError( 102 sensorsAsyncResp->asyncResp->res); 103 return; 104 } 105 sensorsAsyncResp->asyncResp->res.result( 106 boost::beast::http::status::no_content); 107 }, 108 "xyz.openbmc_project.Settings", 109 "/xyz/openbmc_project/control/host0/power_cap", 110 "org.freedesktop.DBus.Properties", "Set", 111 "xyz.openbmc_project.Control.Power.Cap", "PowerCap", 112 std::variant<uint32_t>(*value)); 113 }); 114 }; 115 getValidChassisPath(sensorsAsyncResp, std::move(getChassisPath)); 116 } 117 inline void requestRoutesPower(App& app) 118 { 119 120 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 121 .privileges(redfish::privileges::getPower) 122 .methods( 123 boost::beast::http::verb:: 124 get)([&app](const crow::Request& req, 125 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 126 const std::string& chassisName) { 127 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 128 { 129 return; 130 } 131 asyncResp->res.jsonValue["PowerControl"] = nlohmann::json::array(); 132 133 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 134 asyncResp, chassisName, sensors::dbus::powerPaths, 135 sensors::node::power); 136 137 getChassisData(sensorAsyncResp); 138 139 // This callback verifies that the power limit is only provided 140 // for the chassis that implements the Chassis inventory item. 141 // This prevents things like power supplies providing the 142 // chassis power limit 143 144 using Mapper = dbus::utility::MapperGetSubTreePathsResponse; 145 auto chassisHandler = [sensorAsyncResp]( 146 const boost::system::error_code e, 147 const Mapper& chassisPaths) { 148 if (e) 149 { 150 BMCWEB_LOG_ERROR 151 << "Power Limit GetSubTreePaths handler Dbus error " 152 << e; 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 = 179 chassis.substr(lastPos + 1, 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( 201 sensorAsyncResp->asyncResp->res); 202 BMCWEB_LOG_ERROR 203 << "Power Limit GetAll handler: Dbus error " 204 << ec; 205 return; 206 } 207 208 nlohmann::json& tempArray = 209 sensorAsyncResp->asyncResp->res 210 .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"] = 220 "#Power.v1_0_0.PowerControl"; 221 powerControl["@odata.id"] = 222 "/redfish/v1/Chassis/" + 223 sensorAsyncResp->chassisId + 224 "/Power#/PowerControl/0"; 225 powerControl["Name"] = "Chassis Power Control"; 226 powerControl["MemberId"] = "0"; 227 tempArray.push_back(std::move(powerControl)); 228 } 229 230 nlohmann::json& sensorJson = tempArray.back(); 231 bool enabled = false; 232 double powerCap = 0.0; 233 int64_t scale = 0; 234 235 for (const std::pair<std::string, 236 dbus::utility::DbusVariantType>& 237 property : properties) 238 { 239 if (property.first == "Scale") 240 { 241 const int64_t* i = 242 std::get_if<int64_t>(&property.second); 243 244 if (i != nullptr) 245 { 246 scale = *i; 247 } 248 } 249 else if (property.first == "PowerCap") 250 { 251 const double* d = 252 std::get_if<double>(&property.second); 253 const int64_t* i = 254 std::get_if<int64_t>(&property.second); 255 const uint32_t* u = 256 std::get_if<uint32_t>(&property.second); 257 258 if (d != nullptr) 259 { 260 powerCap = *d; 261 } 262 else if (i != nullptr) 263 { 264 powerCap = static_cast<double>(*i); 265 } 266 else if (u != nullptr) 267 { 268 powerCap = *u; 269 } 270 } 271 else if (property.first == "PowerCapEnable") 272 { 273 const bool* b = 274 std::get_if<bool>(&property.second); 275 276 if (b != nullptr) 277 { 278 enabled = *b; 279 } 280 } 281 } 282 283 nlohmann::json& value = 284 sensorJson["PowerLimit"]["LimitInWatts"]; 285 286 // LimitException is Mandatory attribute as per OCP 287 // Baseline Profile – v1.0.0, so currently making it 288 // "NoAction" as default value to make it OCP Compliant. 289 sensorJson["PowerLimit"]["LimitException"] = "NoAction"; 290 291 if (enabled) 292 { 293 // Redfish specification indicates PowerLimit should 294 // be null if the limit is not enabled. 295 value = powerCap * std::pow(10, scale); 296 } 297 }; 298 299 crow::connections::systemBus->async_method_call( 300 std::move(valueHandler), "xyz.openbmc_project.Settings", 301 "/xyz/openbmc_project/control/host0/power_cap", 302 "org.freedesktop.DBus.Properties", "GetAll", 303 "xyz.openbmc_project.Control.Power.Cap"); 304 }; 305 306 crow::connections::systemBus->async_method_call( 307 std::move(chassisHandler), "xyz.openbmc_project.ObjectMapper", 308 "/xyz/openbmc_project/object_mapper", 309 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 310 "/xyz/openbmc_project/inventory", 0, 311 std::array<const char*, 2>{ 312 "xyz.openbmc_project.Inventory.Item.Board", 313 "xyz.openbmc_project.Inventory.Item.Chassis"}); 314 }); 315 316 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 317 .privileges(redfish::privileges::patchPower) 318 .methods(boost::beast::http::verb::patch)( 319 [&app](const crow::Request& req, 320 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 321 const std::string& chassisName) { 322 if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) 323 { 324 return; 325 } 326 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 327 asyncResp, chassisName, sensors::dbus::powerPaths, 328 sensors::node::power); 329 330 std::optional<std::vector<nlohmann::json>> voltageCollections; 331 std::optional<std::vector<nlohmann::json>> powerCtlCollections; 332 333 if (!json_util::readJsonPatch( 334 req, sensorAsyncResp->asyncResp->res, "PowerControl", 335 powerCtlCollections, "Voltages", voltageCollections)) 336 { 337 return; 338 } 339 340 if (powerCtlCollections) 341 { 342 setPowerCapOverride(sensorAsyncResp, *powerCtlCollections); 343 } 344 if (voltageCollections) 345 { 346 std::unordered_map<std::string, std::vector<nlohmann::json>> 347 allCollections; 348 allCollections.emplace("Voltages", 349 *std::move(voltageCollections)); 350 setSensorsOverride(sensorAsyncResp, allCollections); 351 } 352 }); 353 } 354 355 } // namespace redfish 356