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 #include "utils/chassis_utils.hpp" 21 22 #include <app.hpp> 23 #include <dbus_utility.hpp> 24 #include <query.hpp> 25 #include <registries/privilege_registry.hpp> 26 27 namespace redfish 28 { 29 inline void setPowerCapOverride( 30 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 31 std::vector<nlohmann::json>& powerControlCollections) 32 { 33 auto getChassisPath = 34 [sensorsAsyncResp, powerControlCollections]( 35 const std::optional<std::string>& chassisPath) mutable { 36 if (!chassisPath) 37 { 38 BMCWEB_LOG_ERROR << "Don't find valid chassis path "; 39 messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, 40 "Chassis", sensorsAsyncResp->chassisId); 41 return; 42 } 43 44 if (powerControlCollections.size() != 1) 45 { 46 BMCWEB_LOG_ERROR << "Don't support multiple hosts at present "; 47 messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, 48 "Power", "PowerControl"); 49 return; 50 } 51 52 auto& item = powerControlCollections[0]; 53 54 std::optional<nlohmann::json> powerLimit; 55 if (!json_util::readJson(item, sensorsAsyncResp->asyncResp->res, 56 "PowerLimit", powerLimit)) 57 { 58 return; 59 } 60 if (!powerLimit) 61 { 62 return; 63 } 64 std::optional<uint32_t> value; 65 if (!json_util::readJson(*powerLimit, sensorsAsyncResp->asyncResp->res, 66 "LimitInWatts", value)) 67 { 68 return; 69 } 70 if (!value) 71 { 72 return; 73 } 74 sdbusplus::asio::getProperty<bool>( 75 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 76 "/xyz/openbmc_project/control/host0/power_cap", 77 "xyz.openbmc_project.Control.Power.Cap", "PowerCapEnable", 78 [value, sensorsAsyncResp](const boost::system::error_code ec, 79 bool powerCapEnable) { 80 if (ec) 81 { 82 messages::internalError(sensorsAsyncResp->asyncResp->res); 83 BMCWEB_LOG_ERROR << "powerCapEnable Get handler: Dbus error " 84 << ec; 85 return; 86 } 87 if (!powerCapEnable) 88 { 89 messages::actionNotSupported( 90 sensorsAsyncResp->asyncResp->res, 91 "Setting LimitInWatts when PowerLimit feature is disabled"); 92 BMCWEB_LOG_ERROR << "PowerLimit feature is disabled "; 93 return; 94 } 95 96 crow::connections::systemBus->async_method_call( 97 [sensorsAsyncResp](const boost::system::error_code ec2) { 98 if (ec2) 99 { 100 BMCWEB_LOG_DEBUG << "Power Limit Set: Dbus error: " << ec2; 101 messages::internalError(sensorsAsyncResp->asyncResp->res); 102 return; 103 } 104 sensorsAsyncResp->asyncResp->res.result( 105 boost::beast::http::status::no_content); 106 }, 107 "xyz.openbmc_project.Settings", 108 "/xyz/openbmc_project/control/host0/power_cap", 109 "org.freedesktop.DBus.Properties", "Set", 110 "xyz.openbmc_project.Control.Power.Cap", "PowerCap", 111 std::variant<uint32_t>(*value)); 112 }); 113 }; 114 redfish::chassis_utils::getValidChassisPath(sensorsAsyncResp->asyncResp, 115 sensorsAsyncResp->chassisId, 116 std::move(getChassisPath)); 117 } 118 inline void requestRoutesPower(App& app) 119 { 120 121 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 122 .privileges(redfish::privileges::getPower) 123 .methods(boost::beast::http::verb::get)( 124 [&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)) 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 = 146 [sensorAsyncResp](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 " << e; 152 return; 153 } 154 155 bool found = false; 156 for (const std::string& chassis : chassisPaths) 157 { 158 size_t len = std::string::npos; 159 size_t lastPos = chassis.rfind('/'); 160 if (lastPos == std::string::npos) 161 { 162 continue; 163 } 164 165 if (lastPos == chassis.size() - 1) 166 { 167 size_t end = lastPos; 168 lastPos = chassis.rfind('/', lastPos - 1); 169 if (lastPos == std::string::npos) 170 { 171 continue; 172 } 173 174 len = end - (lastPos + 1); 175 } 176 177 std::string interfaceChassisName = 178 chassis.substr(lastPos + 1, len); 179 if (interfaceChassisName == sensorAsyncResp->chassisId) 180 { 181 found = true; 182 break; 183 } 184 } 185 186 if (!found) 187 { 188 BMCWEB_LOG_DEBUG << "Power Limit not present for " 189 << sensorAsyncResp->chassisId; 190 return; 191 } 192 193 auto valueHandler = 194 [sensorAsyncResp]( 195 const boost::system::error_code ec, 196 const dbus::utility::DBusPropertiesMap& properties) { 197 if (ec) 198 { 199 messages::internalError(sensorAsyncResp->asyncResp->res); 200 BMCWEB_LOG_ERROR 201 << "Power Limit GetAll handler: Dbus error " << ec; 202 return; 203 } 204 205 nlohmann::json& tempArray = 206 sensorAsyncResp->asyncResp->res.jsonValue["PowerControl"]; 207 208 // Put multiple "sensors" into a single PowerControl, 0, 209 // so only create the first one 210 if (tempArray.empty()) 211 { 212 // Mandatory properties odata.id and MemberId 213 // A warning without a odata.type 214 nlohmann::json::object_t powerControl; 215 powerControl["@odata.type"] = "#Power.v1_0_0.PowerControl"; 216 powerControl["@odata.id"] = "/redfish/v1/Chassis/" + 217 sensorAsyncResp->chassisId + 218 "/Power#/PowerControl/0"; 219 powerControl["Name"] = "Chassis Power Control"; 220 powerControl["MemberId"] = "0"; 221 tempArray.push_back(std::move(powerControl)); 222 } 223 224 nlohmann::json& sensorJson = tempArray.back(); 225 bool enabled = false; 226 double powerCap = 0.0; 227 int64_t scale = 0; 228 229 for (const std::pair<std::string, 230 dbus::utility::DbusVariantType>& property : 231 properties) 232 { 233 if (property.first == "Scale") 234 { 235 const int64_t* i = 236 std::get_if<int64_t>(&property.second); 237 238 if (i != nullptr) 239 { 240 scale = *i; 241 } 242 } 243 else if (property.first == "PowerCap") 244 { 245 const double* d = std::get_if<double>(&property.second); 246 const int64_t* i = 247 std::get_if<int64_t>(&property.second); 248 const uint32_t* u = 249 std::get_if<uint32_t>(&property.second); 250 251 if (d != nullptr) 252 { 253 powerCap = *d; 254 } 255 else if (i != nullptr) 256 { 257 powerCap = static_cast<double>(*i); 258 } 259 else if (u != nullptr) 260 { 261 powerCap = *u; 262 } 263 } 264 else if (property.first == "PowerCapEnable") 265 { 266 const bool* b = std::get_if<bool>(&property.second); 267 268 if (b != nullptr) 269 { 270 enabled = *b; 271 } 272 } 273 } 274 275 nlohmann::json& value = 276 sensorJson["PowerLimit"]["LimitInWatts"]; 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 value = powerCap * std::pow(10, scale); 288 } 289 }; 290 291 crow::connections::systemBus->async_method_call( 292 std::move(valueHandler), "xyz.openbmc_project.Settings", 293 "/xyz/openbmc_project/control/host0/power_cap", 294 "org.freedesktop.DBus.Properties", "GetAll", 295 "xyz.openbmc_project.Control.Power.Cap"); 296 }; 297 298 crow::connections::systemBus->async_method_call( 299 std::move(chassisHandler), "xyz.openbmc_project.ObjectMapper", 300 "/xyz/openbmc_project/object_mapper", 301 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", 302 "/xyz/openbmc_project/inventory", 0, 303 std::array<const char*, 2>{ 304 "xyz.openbmc_project.Inventory.Item.Board", 305 "xyz.openbmc_project.Inventory.Item.Chassis"}); 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