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::chassisSubNodeToString( 273 sensor_utils::ChassisSubNode::powerNode)); 274 275 getChassisData(sensorAsyncResp); 276 277 // This callback verifies that the power limit is only provided 278 // for the chassis that implements the Chassis inventory item. 279 // This prevents things like power supplies providing the 280 // chassis power limit 281 282 constexpr std::array<std::string_view, 2> interfaces = { 283 "xyz.openbmc_project.Inventory.Item.Board", 284 "xyz.openbmc_project.Inventory.Item.Chassis"}; 285 286 dbus::utility::getSubTreePaths( 287 "/xyz/openbmc_project/inventory", 0, interfaces, 288 std::bind_front(afterGetChassis, sensorAsyncResp)); 289 } 290 291 inline void 292 handleChassisPowerPatch(App& app, const crow::Request& req, 293 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 294 const std::string& chassisName) 295 { 296 if (!redfish::setUpRedfishRoute(app, req, asyncResp)) 297 { 298 return; 299 } 300 auto sensorAsyncResp = std::make_shared<SensorsAsyncResp>( 301 asyncResp, chassisName, sensors::dbus::powerPaths, 302 sensor_utils::chassisSubNodeToString( 303 sensor_utils::ChassisSubNode::powerNode)); 304 305 std::optional<std::vector<nlohmann::json::object_t>> voltageCollections; 306 std::optional<std::vector<nlohmann::json::object_t>> powerCtlCollections; 307 308 if (!json_util::readJsonPatch(req, sensorAsyncResp->asyncResp->res, 309 "PowerControl", powerCtlCollections, 310 "Voltages", voltageCollections)) 311 { 312 return; 313 } 314 315 if (powerCtlCollections) 316 { 317 redfish::chassis_utils::getValidChassisPath( 318 sensorAsyncResp->asyncResp, sensorAsyncResp->chassisId, 319 std::bind_front(afterGetChassisPath, sensorAsyncResp, 320 *powerCtlCollections)); 321 } 322 if (voltageCollections) 323 { 324 std::unordered_map<std::string, std::vector<nlohmann::json::object_t>> 325 allCollections; 326 allCollections.emplace("Voltages", std::move(*voltageCollections)); 327 setSensorsOverride(sensorAsyncResp, allCollections); 328 } 329 } 330 331 inline void requestRoutesPower(App& app) 332 { 333 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 334 .privileges(redfish::privileges::getPower) 335 .methods(boost::beast::http::verb::get)( 336 std::bind_front(handleChassisPowerGet, std::ref(app))); 337 338 BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/Power/") 339 .privileges(redfish::privileges::patchPower) 340 .methods(boost::beast::http::verb::patch)( 341 std::bind_front(handleChassisPowerPatch, std::ref(app))); 342 } 343 344 } // namespace redfish 345