1 /** 2 * Copyright © 2021 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #include "power_control.hpp" 18 19 #include "config_file_parser.hpp" 20 #include "format_utils.hpp" 21 #include "types.hpp" 22 #include "ucd90160_device.hpp" 23 #include "ucd90320_device.hpp" 24 #include "utility.hpp" 25 26 #include <exception> 27 #include <format> 28 #include <functional> 29 #include <map> 30 #include <span> 31 #include <stdexcept> 32 #include <thread> 33 #include <utility> 34 35 namespace phosphor::power::sequencer 36 { 37 38 const std::string powerOnTimeoutError = 39 "xyz.openbmc_project.Power.Error.PowerOnTimeout"; 40 41 const std::string powerOffTimeoutError = 42 "xyz.openbmc_project.Power.Error.PowerOffTimeout"; 43 44 const std::string shutdownError = "xyz.openbmc_project.Power.Error.Shutdown"; 45 46 PowerControl::PowerControl(sdbusplus::bus_t& bus, 47 const sdeventplus::Event& event) : 48 PowerObject{bus, POWER_OBJ_PATH, PowerObject::action::defer_emit}, 49 bus{bus}, services{bus}, 50 pgoodWaitTimer{event, std::bind(&PowerControl::onFailureCallback, this)}, 51 powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime}, 52 timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval} 53 { 54 // Obtain dbus service name 55 bus.request_name(POWER_IFACE); 56 57 compatSysTypesFinder = std::make_unique<util::CompatibleSystemTypesFinder>( 58 bus, std::bind_front(&PowerControl::compatibleSystemTypesFound, this)); 59 60 deviceFinder = std::make_unique<DeviceFinder>( 61 bus, std::bind_front(&PowerControl::deviceFound, this)); 62 63 setUpGpio(); 64 } 65 66 int PowerControl::getPgood() const 67 { 68 return pgood; 69 } 70 71 int PowerControl::getPgoodTimeout() const 72 { 73 return timeout.count(); 74 } 75 76 int PowerControl::getState() const 77 { 78 return state; 79 } 80 81 void PowerControl::onFailureCallback() 82 { 83 services.logInfoMsg("After onFailure wait"); 84 85 onFailure(false); 86 87 // Power good has failed, call for chassis hard power off 88 auto method = bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT, 89 util::SYSTEMD_INTERFACE, "StartUnit"); 90 method.append(util::POWEROFF_TARGET); 91 method.append("replace"); 92 bus.call_noreply(method); 93 } 94 95 void PowerControl::onFailure(bool wasTimeOut) 96 { 97 std::string error; 98 std::map<std::string, std::string> additionalData{}; 99 100 // Check if pgood fault occurred on rail monitored by power sequencer device 101 if (device) 102 { 103 try 104 { 105 error = device->findPgoodFault(services, powerSupplyError, 106 additionalData); 107 } 108 catch (const std::exception& e) 109 { 110 services.logErrorMsg(e.what()); 111 additionalData.emplace("ERROR", e.what()); 112 } 113 } 114 115 // If fault was not isolated to a voltage rail, select a more generic error 116 if (error.empty()) 117 { 118 if (!powerSupplyError.empty()) 119 { 120 error = powerSupplyError; 121 } 122 else if (wasTimeOut) 123 { 124 error = powerOnTimeoutError; 125 } 126 else 127 { 128 error = shutdownError; 129 } 130 } 131 132 services.logError(error, Entry::Level::Critical, additionalData); 133 134 if (!wasTimeOut) 135 { 136 services.createBMCDump(); 137 } 138 } 139 140 void PowerControl::pollPgood() 141 { 142 if (inStateTransition) 143 { 144 // In transition between power on and off, check for timeout 145 const auto now = std::chrono::steady_clock::now(); 146 if (now > pgoodTimeoutTime) 147 { 148 services.logErrorMsg(std::format( 149 "Power state transition timeout, state: {}", state)); 150 inStateTransition = false; 151 152 if (state) 153 { 154 // Time out powering on 155 onFailure(true); 156 } 157 else 158 { 159 // Time out powering off 160 std::map<std::string, std::string> additionalData{}; 161 services.logError(powerOffTimeoutError, Entry::Level::Critical, 162 additionalData); 163 } 164 165 failureFound = true; 166 return; 167 } 168 } 169 170 int pgoodState = pgoodLine.get_value(); 171 if (pgoodState != pgood) 172 { 173 // Power good has changed since last read 174 pgood = pgoodState; 175 if (pgoodState == 0) 176 { 177 emitPowerLostSignal(); 178 } 179 else 180 { 181 emitPowerGoodSignal(); 182 // Clear any errors on the transition to power on 183 powerSupplyError.clear(); 184 failureFound = false; 185 } 186 emitPropertyChangedSignal("pgood"); 187 } 188 if (pgoodState == state) 189 { 190 // Power good matches requested state 191 inStateTransition = false; 192 } 193 else if (!inStateTransition && (pgoodState == 0) && !failureFound) 194 { 195 // Not in power off state, not changing state, and power good is off 196 services.logErrorMsg("Chassis pgood failure"); 197 pgoodWaitTimer.restartOnce(std::chrono::seconds(7)); 198 failureFound = true; 199 } 200 } 201 202 void PowerControl::setPgoodTimeout(int t) 203 { 204 if (timeout.count() != t) 205 { 206 timeout = std::chrono::seconds(t); 207 emitPropertyChangedSignal("pgood_timeout"); 208 } 209 } 210 211 void PowerControl::setPowerSupplyError(const std::string& error) 212 { 213 powerSupplyError = error; 214 } 215 216 void PowerControl::setState(int s) 217 { 218 if (state == s) 219 { 220 services.logInfoMsg( 221 std::format("Power already at requested state: {}", state)); 222 return; 223 } 224 if (s == 0) 225 { 226 // Wait for two seconds when powering down. This is to allow host and 227 // other BMC applications time to complete power off processing 228 std::this_thread::sleep_for(std::chrono::seconds(2)); 229 } 230 else 231 { 232 // If minimum power off time has not passed, wait 233 if (powerOnAllowedTime > std::chrono::steady_clock::now()) 234 { 235 services.logInfoMsg(std::format( 236 "Waiting {} seconds until power on allowed", 237 std::chrono::duration_cast<std::chrono::seconds>( 238 powerOnAllowedTime - std::chrono::steady_clock::now()) 239 .count())); 240 } 241 std::this_thread::sleep_until(powerOnAllowedTime); 242 } 243 244 services.logInfoMsg(std::format("setState: {}", s)); 245 services.logInfoMsg(std::format("Powering chassis {}", (s ? "on" : "off"))); 246 powerControlLine.request( 247 {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0}); 248 powerControlLine.set_value(s); 249 powerControlLine.release(); 250 251 if (s == 0) 252 { 253 // Set a minimum amount of time to wait before next power on 254 powerOnAllowedTime = std::chrono::steady_clock::now() + 255 minimumPowerOffTime; 256 } 257 258 pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout; 259 inStateTransition = true; 260 state = s; 261 emitPropertyChangedSignal("state"); 262 } 263 264 void PowerControl::compatibleSystemTypesFound( 265 const std::vector<std::string>& types) 266 { 267 // If we don't already have compatible system types 268 if (compatibleSystemTypes.empty()) 269 { 270 std::string typesStr = format_utils::toString(std::span{types}); 271 services.logInfoMsg( 272 std::format("Compatible system types found: {}", typesStr)); 273 274 // Store compatible system types 275 compatibleSystemTypes = types; 276 277 // Load config file and create device object if possible 278 loadConfigFileAndCreateDevice(); 279 } 280 } 281 282 void PowerControl::deviceFound(const DeviceProperties& properties) 283 { 284 // If we don't already have device properties 285 if (!deviceProperties) 286 { 287 services.logInfoMsg(std::format( 288 "Power sequencer device found: type={}, name={}, bus={:d}, address={:#02x}", 289 properties.type, properties.name, properties.bus, 290 properties.address)); 291 292 // Store device properties 293 deviceProperties = properties; 294 295 // Load config file and create device object if possible 296 loadConfigFileAndCreateDevice(); 297 } 298 } 299 300 void PowerControl::setUpGpio() 301 { 302 const std::string powerControlLineName = "power-chassis-control"; 303 const std::string pgoodLineName = "power-chassis-good"; 304 305 pgoodLine = gpiod::find_line(pgoodLineName); 306 if (!pgoodLine) 307 { 308 std::string errorString{"GPIO line name not found: " + pgoodLineName}; 309 services.logErrorMsg(errorString); 310 throw std::runtime_error(errorString); 311 } 312 powerControlLine = gpiod::find_line(powerControlLineName); 313 if (!powerControlLine) 314 { 315 std::string errorString{"GPIO line name not found: " + 316 powerControlLineName}; 317 services.logErrorMsg(errorString); 318 throw std::runtime_error(errorString); 319 } 320 321 pgoodLine.request( 322 {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0}); 323 int pgoodState = pgoodLine.get_value(); 324 pgood = pgoodState; 325 state = pgoodState; 326 services.logInfoMsg(std::format("Pgood state: {}", pgoodState)); 327 } 328 329 void PowerControl::loadConfigFileAndCreateDevice() 330 { 331 // If compatible system types and device properties have been found 332 if (!compatibleSystemTypes.empty() && deviceProperties) 333 { 334 // Find the JSON configuration file 335 std::filesystem::path configFile = findConfigFile(); 336 if (!configFile.empty()) 337 { 338 // Parse the JSON configuration file 339 std::vector<std::unique_ptr<Rail>> rails; 340 if (parseConfigFile(configFile, rails)) 341 { 342 // Create the power sequencer device object 343 createDevice(std::move(rails)); 344 } 345 } 346 } 347 } 348 349 std::filesystem::path PowerControl::findConfigFile() 350 { 351 // Find config file for current system based on compatible system types 352 std::filesystem::path configFile; 353 if (!compatibleSystemTypes.empty()) 354 { 355 try 356 { 357 configFile = config_file_parser::find(compatibleSystemTypes); 358 if (!configFile.empty()) 359 { 360 services.logInfoMsg(std::format( 361 "JSON configuration file found: {}", configFile.string())); 362 } 363 } 364 catch (const std::exception& e) 365 { 366 services.logErrorMsg(std::format( 367 "Unable to find JSON configuration file: {}", e.what())); 368 } 369 } 370 return configFile; 371 } 372 373 bool PowerControl::parseConfigFile(const std::filesystem::path& configFile, 374 std::vector<std::unique_ptr<Rail>>& rails) 375 { 376 // Parse JSON configuration file 377 bool wasParsed{false}; 378 try 379 { 380 rails = config_file_parser::parse(configFile); 381 wasParsed = true; 382 } 383 catch (const std::exception& e) 384 { 385 services.logErrorMsg(std::format( 386 "Unable to parse JSON configuration file: {}", e.what())); 387 } 388 return wasParsed; 389 } 390 391 void PowerControl::createDevice(std::vector<std::unique_ptr<Rail>> rails) 392 { 393 // Create power sequencer device based on device properties 394 if (deviceProperties) 395 { 396 try 397 { 398 if (deviceProperties->type == UCD90160Device::deviceName) 399 { 400 device = std::make_unique<UCD90160Device>( 401 std::move(rails), services, deviceProperties->bus, 402 deviceProperties->address); 403 } 404 else if (deviceProperties->type == UCD90320Device::deviceName) 405 { 406 device = std::make_unique<UCD90320Device>( 407 std::move(rails), services, deviceProperties->bus, 408 deviceProperties->address); 409 } 410 else 411 { 412 throw std::runtime_error{std::format( 413 "Unsupported device type: {}", deviceProperties->type)}; 414 } 415 services.logInfoMsg(std::format( 416 "Power sequencer device created: {}", device->getName())); 417 } 418 catch (const std::exception& e) 419 { 420 services.logErrorMsg( 421 std::format("Unable to create device object: {}", e.what())); 422 } 423 } 424 } 425 426 } // namespace phosphor::power::sequencer 427