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 "generated/enums/power.hpp" 22 #include "query.hpp" 23 #include "registries/privilege_registry.hpp" 24 #include "sensors.hpp" 25 #include "utils/chassis_utils.hpp" 26 #include "utils/json_utils.hpp" 27 #include "utils/sensor_utils.hpp" 28 29 #include <sdbusplus/asio/property.hpp> 30 31 #include <array> 32 #include <string> 33 #include <string_view> 34 #include <vector> 35 36 namespace redfish 37 { 38 39 inline void afterGetPowerCapEnable( 40 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 41 uint32_t valueToSet, const boost::system::error_code& ec, 42 bool powerCapEnable) 43 { 44 if (ec) 45 { 46 messages::internalError(sensorsAsyncResp->asyncResp->res); 47 BMCWEB_LOG_ERROR("powerCapEnable Get handler: Dbus error {}", ec); 48 return; 49 } 50 if (!powerCapEnable) 51 { 52 messages::actionNotSupported( 53 sensorsAsyncResp->asyncResp->res, 54 "Setting LimitInWatts when PowerLimit feature is disabled"); 55 BMCWEB_LOG_ERROR("PowerLimit feature is disabled "); 56 return; 57 } 58 59 setDbusProperty(sensorsAsyncResp->asyncResp, "PowerControl", 60 "xyz.openbmc_project.Settings", 61 sdbusplus::message::object_path( 62 "/xyz/openbmc_project/control/host0/power_cap"), 63 "xyz.openbmc_project.Control.Power.Cap", "PowerCap", 64 valueToSet); 65 } 66 67 inline void afterGetChassisPath( 68 const std::shared_ptr<SensorsAsyncResp>& sensorsAsyncResp, 69 std::vector<nlohmann::json::object_t>& powerControlCollections, 70 const std::optional<std::string>& chassisPath) 71 { 72 if (!chassisPath) 73 { 74 BMCWEB_LOG_WARNING("Don't find valid chassis path "); 75 messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, "Chassis", 76 sensorsAsyncResp->chassisId); 77 return; 78 } 79 80 if (powerControlCollections.size() != 1) 81 { 82 BMCWEB_LOG_WARNING("Don't support multiple hosts at present "); 83 messages::resourceNotFound(sensorsAsyncResp->asyncResp->res, "Power", 84 "PowerControl"); 85 return; 86 } 87 88 auto& item = powerControlCollections[0]; 89 90 std::optional<uint32_t> value; 91 if (!json_util::readJsonObject(item, sensorsAsyncResp->asyncResp->res, 92 "PowerLimit/LimitInWatts", value)) 93 { 94 return; 95 } 96 if (!value) 97 { 98 return; 99 } 100 sdbusplus::asio::getProperty<bool>( 101 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 102 "/xyz/openbmc_project/control/host0/power_cap", 103 "xyz.openbmc_project.Control.Power.Cap", "PowerCapEnable", 104 std::bind_front(afterGetPowerCapEnable, sensorsAsyncResp, *value)); 105 } 106 107 inline void afterPowerCapSettingGet( 108 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 109 const boost::system::error_code& ec, 110 const dbus::utility::DBusPropertiesMap& properties) 111 { 112 if (ec) 113 { 114 messages::internalError(sensorAsyncResp->asyncResp->res); 115 BMCWEB_LOG_ERROR("Power Limit GetAll handler: Dbus error {}", ec); 116 return; 117 } 118 119 nlohmann::json& tempArray = 120 sensorAsyncResp->asyncResp->res.jsonValue["PowerControl"]; 121 122 // Put multiple "sensors" into a single PowerControl, 0, 123 // so only create the first one 124 if (tempArray.empty()) 125 { 126 // Mandatory properties odata.id and MemberId 127 // A warning without a odata.type 128 nlohmann::json::object_t powerControl; 129 powerControl["@odata.type"] = "#Power.v1_0_0.PowerControl"; 130 powerControl["@odata.id"] = 131 "/redfish/v1/Chassis/" + sensorAsyncResp->chassisId + 132 "/Power#/PowerControl/0"; 133 powerControl["Name"] = "Chassis Power Control"; 134 powerControl["MemberId"] = "0"; 135 tempArray.emplace_back(std::move(powerControl)); 136 } 137 138 nlohmann::json& sensorJson = tempArray.back(); 139 bool enabled = false; 140 double powerCap = 0.0; 141 int64_t scale = 0; 142 143 for (const std::pair<std::string, dbus::utility::DbusVariantType>& 144 property : properties) 145 { 146 if (property.first == "Scale") 147 { 148 const int64_t* i = std::get_if<int64_t>(&property.second); 149 150 if (i != nullptr) 151 { 152 scale = *i; 153 } 154 } 155 else if (property.first == "PowerCap") 156 { 157 const double* d = std::get_if<double>(&property.second); 158 const int64_t* i = std::get_if<int64_t>(&property.second); 159 const uint32_t* u = std::get_if<uint32_t>(&property.second); 160 161 if (d != nullptr) 162 { 163 powerCap = *d; 164 } 165 else if (i != nullptr) 166 { 167 powerCap = static_cast<double>(*i); 168 } 169 else if (u != nullptr) 170 { 171 powerCap = *u; 172 } 173 } 174 else if (property.first == "PowerCapEnable") 175 { 176 const bool* b = std::get_if<bool>(&property.second); 177 178 if (b != nullptr) 179 { 180 enabled = *b; 181 } 182 } 183 } 184 185 // LimitException is Mandatory attribute as per OCP 186 // Baseline Profile - v1.0.0, so currently making it 187 // "NoAction" as default value to make it OCP Compliant. 188 sensorJson["PowerLimit"]["LimitException"] = 189 power::PowerLimitException::NoAction; 190 191 if (enabled) 192 { 193 // Redfish specification indicates PowerLimit should 194 // be null if the limit is not enabled. 195 sensorJson["PowerLimit"]["LimitInWatts"] = 196 powerCap * std::pow(10, scale); 197 } 198 } 199 200 using Mapper = dbus::utility::MapperGetSubTreePathsResponse; 201 inline void afterGetChassis( 202 const std::shared_ptr<SensorsAsyncResp>& sensorAsyncResp, 203 const boost::system::error_code& ec2, const Mapper& chassisPaths) 204 { 205 if (ec2) 206 { 207 BMCWEB_LOG_ERROR("Power Limit GetSubTreePaths handler Dbus error {}", 208 ec2); 209 return; 210 } 211 212 bool found = false; 213 for (const std::string& chassis : chassisPaths) 214 { 215 size_t len = std::string::npos; 216 size_t lastPos = chassis.rfind('/'); 217 if (lastPos == std::string::npos) 218 { 219 continue; 220 } 221 222 if (lastPos == chassis.size() - 1) 223 { 224 size_t end = lastPos; 225 lastPos = chassis.rfind('/', lastPos - 1); 226 if (lastPos == std::string::npos) 227 { 228 continue; 229 } 230 231 len = end - (lastPos + 1); 232 } 233 234 std::string interfaceChassisName = chassis.substr(lastPos + 1, len); 235 if (interfaceChassisName == sensorAsyncResp->chassisId) 236 { 237 found = true; 238 break; 239 } 240 } 241 242 if (!found) 243 { 244 BMCWEB_LOG_DEBUG("Power Limit not present for {}", 245 sensorAsyncResp->chassisId); 246 return; 247 } 248 249 sdbusplus::asio::getAllProperties( 250 *crow::connections::systemBus, "xyz.openbmc_project.Settings", 251 "/xyz/openbmc_project/control/host0/power_cap", 252 "xyz.openbmc_project.Control.Power.Cap", 253 [sensorAsyncResp](const boost::system::error_code& ec, 254 const dbus::utility::DBusPropertiesMap& properties 255 256 ) { afterPowerCapSettingGet(sensorAsyncResp, ec, properties); }); 257 } 258 259 inline void 260 handleChassisPowerGet(App& app, const crow::Request& req, 261 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 262 const std::string& chassisName) 263 { 264 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 265 { 266 return; 267 } 268 asyncResp->res.jsonValue["PowerControl"] = nlohmann::json::array(); 269 270 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 271 asyncResp, chassisName, sensors::dbus::powerPaths, 272 sensor_utils::powerNode); 273 274 getChassisData(sensorAsyncResp); 275 276 // This callback verifies that the power limit is only provided 277 // for the chassis that implements the Chassis inventory item. 278 // This prevents things like power supplies providing the 279 // chassis power limit 280 281 constexpr std::array<std::string_view, 2> interfaces = { 282 "xyz.openbmc_project.Inventory.Item.Board", 283 "xyz.openbmc_project.Inventory.Item.Chassis"}; 284 285 dbus::utility::getSubTreePaths( 286 "/xyz/openbmc_project/inventory", 0, interfaces, 287 std::bind_front(afterGetChassis, sensorAsyncResp)); 288 } 289 290 inline void 291 handleChassisPowerPatch(App& app, const crow::Request& req, 292 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 293 const std::string& chassisName) 294 { 295 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 296 { 297 return; 298 } 299 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 300 asyncResp, chassisName, sensors::dbus::powerPaths, 301 sensor_utils::powerNode); 302 303 std::optional<std::vector<nlohmann::json::object_t>> voltageCollections; 304 std::optional<std::vector<nlohmann::json::object_t>> powerCtlCollections; 305 306 if (!json_util::readJsonPatch(req, sensorAsyncResp->asyncResp->res, 307 "PowerControl", powerCtlCollections, 308 "Voltages", voltageCollections)) 309 { 310 return; 311 } 312 313 if (powerCtlCollections) 314 { 315 redfish::chassis_utils::getValidChassisPath( 316 sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId, 317 std::bind_front(afterGetChassisPath, sensorAsyncResp, 318 *powerCtlCollections)); 319 } 320 if (voltageCollections) 321 { 322 std::unordered_map<std::string, std::vector<nlohmann::json::object_t>> 323 allCollections; 324 allCollections.emplace("Voltages", std::move(*voltageCollections)); 325 setSensorsOverride(sensorAsyncResp, allCollections); 326 } 327 } 328 329 inline void requestRoutesPower(App& app) 330 { 331 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 332 .privileges(redfish::privileges::getPower) 333 .methods(boost::beast::http::verb::get)( 334 std::bind_front(handleChassisPowerGet, std::ref(app))); 335 336 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 337 .privileges(redfish::privileges::patchPower) 338 .methods(boost::beast::http::verb::patch)( 339 std::bind_front(handleChassisPowerPatch, std::ref(app))); 340 } 341 342 } // namespace redfish 343