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