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 "chassis.hpp" 20 #include "config_file_parser.hpp" 21 #include "format_utils.hpp" 22 #include "types.hpp" 23 #include "utility.hpp" 24 25 #include <exception> 26 #include <format> 27 #include <functional> 28 #include <span> 29 #include <stdexcept> 30 #include <thread> 31 #include <utility> 32 33 namespace phosphor::power::sequencer 34 { 35 36 const std::string powerOnTimeoutError = 37 "xyz.openbmc_project.Power.Error.PowerOnTimeout"; 38 39 const std::string powerOffTimeoutError = 40 "xyz.openbmc_project.Power.Error.PowerOffTimeout"; 41 42 const std::string shutdownError = "xyz.openbmc_project.Power.Error.Shutdown"; 43 44 PowerControl::PowerControl(sdbusplus::bus_t& bus, 45 const sdeventplus::Event& event) : 46 PowerObject{bus, POWER_OBJ_PATH, PowerObject::action::defer_emit}, bus{bus}, 47 services{bus}, 48 pgoodWaitTimer{event, std::bind(&PowerControl::onFailureCallback, this)}, 49 powerOnAllowedTime{std::chrono::steady_clock::now() + minimumColdStartTime}, 50 timer{event, std::bind(&PowerControl::pollPgood, this), pollInterval} 51 { 52 // Obtain dbus service name 53 bus.request_name(POWER_IFACE); 54 55 compatSysTypesFinder = std::make_unique<util::CompatibleSystemTypesFinder>( 56 bus, std::bind_front(&PowerControl::compatibleSystemTypesFound, this)); 57 58 setUpGpio(); 59 } 60 61 int PowerControl::getPgood() const 62 { 63 return pgood; 64 } 65 66 int PowerControl::getPgoodTimeout() const 67 { 68 return timeout.count(); 69 } 70 71 int PowerControl::getState() const 72 { 73 return state; 74 } 75 76 void PowerControl::onFailureCallback() 77 { 78 services.logInfoMsg("After onFailure wait"); 79 80 onFailure(false); 81 82 // Power good has failed, call for chassis hard power off 83 auto method = bus.new_method_call(util::SYSTEMD_SERVICE, util::SYSTEMD_ROOT, 84 util::SYSTEMD_INTERFACE, "StartUnit"); 85 method.append(util::POWEROFF_TARGET); 86 method.append("replace"); 87 bus.call_noreply(method); 88 } 89 90 void PowerControl::onFailure(bool wasTimeOut) 91 { 92 std::string error; 93 std::map<std::string, std::string> additionalData{}; 94 95 // Check if pgood fault occurred on one of the rails being monitored 96 error = findPgoodFault(additionalData); 97 98 // If fault was not isolated to a voltage rail, select a more generic error 99 if (error.empty()) 100 { 101 if (!powerSupplyError.empty()) 102 { 103 error = powerSupplyError; 104 } 105 else if (wasTimeOut) 106 { 107 error = powerOnTimeoutError; 108 } 109 else 110 { 111 error = shutdownError; 112 } 113 } 114 115 services.logError(error, Entry::Level::Critical, additionalData); 116 117 if (!wasTimeOut) 118 { 119 services.createBMCDump(); 120 } 121 } 122 123 std::string PowerControl::findPgoodFault( 124 std::map<std::string, std::string>& additionalData) 125 { 126 // Note: This code is temporary. It will be replaced when additional 127 // multi-chassis support is implementated in this application. 128 std::string error{}; 129 if (system) 130 { 131 try 132 { 133 for (auto& chassis : system->getChassis()) 134 { 135 for (auto& powerSequencer : chassis->getPowerSequencers()) 136 { 137 error = powerSequencer->findPgoodFault( 138 services, powerSupplyError, additionalData); 139 if (!error.empty()) 140 { 141 return error; 142 } 143 } 144 } 145 } 146 catch (const std::exception& e) 147 { 148 services.logErrorMsg(e.what()); 149 additionalData.emplace("ERROR", e.what()); 150 } 151 } 152 return error; 153 } 154 155 void PowerControl::pollPgood() 156 { 157 if (inStateTransition) 158 { 159 // In transition between power on and off, check for timeout 160 const auto now = std::chrono::steady_clock::now(); 161 if (now > pgoodTimeoutTime) 162 { 163 services.logErrorMsg(std::format( 164 "Power state transition timeout, state: {}", state)); 165 inStateTransition = false; 166 167 if (state) 168 { 169 // Time out powering on 170 onFailure(true); 171 } 172 else 173 { 174 // Time out powering off 175 std::map<std::string, std::string> additionalData{}; 176 services.logError(powerOffTimeoutError, Entry::Level::Critical, 177 additionalData); 178 } 179 180 failureFound = true; 181 return; 182 } 183 } 184 185 int pgoodState = pgoodLine.get_value(); 186 if (pgoodState != pgood) 187 { 188 // Power good has changed since last read 189 pgood = pgoodState; 190 if (pgoodState == 0) 191 { 192 emitPowerLostSignal(); 193 } 194 else 195 { 196 emitPowerGoodSignal(); 197 // Clear any errors on the transition to power on 198 powerSupplyError.clear(); 199 failureFound = false; 200 } 201 emitPropertyChangedSignal("pgood"); 202 } 203 if (pgoodState == state) 204 { 205 // Power good matches requested state 206 inStateTransition = false; 207 } 208 else if (!inStateTransition && (pgoodState == 0) && !failureFound) 209 { 210 // Not in power off state, not changing state, and power good is off 211 services.logErrorMsg("Chassis pgood failure"); 212 pgoodWaitTimer.restartOnce(std::chrono::seconds(7)); 213 failureFound = true; 214 } 215 } 216 217 void PowerControl::setPgoodTimeout(int t) 218 { 219 if (timeout.count() != t) 220 { 221 timeout = std::chrono::seconds(t); 222 emitPropertyChangedSignal("pgood_timeout"); 223 } 224 } 225 226 void PowerControl::setPowerSupplyError(const std::string& error) 227 { 228 powerSupplyError = error; 229 } 230 231 void PowerControl::setState(int s) 232 { 233 if (state == s) 234 { 235 services.logInfoMsg( 236 std::format("Power already at requested state: {}", state)); 237 return; 238 } 239 if (s == 0) 240 { 241 // Wait for two seconds when powering down. This is to allow host and 242 // other BMC applications time to complete power off processing 243 std::this_thread::sleep_for(std::chrono::seconds(2)); 244 } 245 else 246 { 247 // If minimum power off time has not passed, wait 248 if (powerOnAllowedTime > std::chrono::steady_clock::now()) 249 { 250 services.logInfoMsg(std::format( 251 "Waiting {} seconds until power on allowed", 252 std::chrono::duration_cast<std::chrono::seconds>( 253 powerOnAllowedTime - std::chrono::steady_clock::now()) 254 .count())); 255 } 256 std::this_thread::sleep_until(powerOnAllowedTime); 257 } 258 259 services.logInfoMsg(std::format("setState: {}", s)); 260 services.logInfoMsg(std::format("Powering chassis {}", (s ? "on" : "off"))); 261 powerControlLine.request( 262 {"phosphor-power-control", gpiod::line_request::DIRECTION_OUTPUT, 0}); 263 powerControlLine.set_value(s); 264 powerControlLine.release(); 265 266 if (s == 0) 267 { 268 // Set a minimum amount of time to wait before next power on 269 powerOnAllowedTime = 270 std::chrono::steady_clock::now() + minimumPowerOffTime; 271 } 272 273 pgoodTimeoutTime = std::chrono::steady_clock::now() + timeout; 274 inStateTransition = true; 275 state = s; 276 emitPropertyChangedSignal("state"); 277 } 278 279 void PowerControl::compatibleSystemTypesFound( 280 const std::vector<std::string>& types) 281 { 282 // If we don't already have compatible system types 283 if (compatibleSystemTypes.empty()) 284 { 285 std::string typesStr = format_utils::toString(std::span{types}); 286 services.logInfoMsg( 287 std::format("Compatible system types found: {}", typesStr)); 288 289 // Store compatible system types 290 compatibleSystemTypes = types; 291 292 // Load config file that matches one of the compatible system types 293 loadConfigFile(); 294 } 295 } 296 297 void PowerControl::setUpGpio() 298 { 299 const std::string powerControlLineName = "power-chassis-control"; 300 const std::string pgoodLineName = "power-chassis-good"; 301 302 pgoodLine = gpiod::find_line(pgoodLineName); 303 if (!pgoodLine) 304 { 305 std::string errorString{"GPIO line name not found: " + pgoodLineName}; 306 services.logErrorMsg(errorString); 307 throw std::runtime_error(errorString); 308 } 309 powerControlLine = gpiod::find_line(powerControlLineName); 310 if (!powerControlLine) 311 { 312 std::string errorString{ 313 "GPIO line name not found: " + powerControlLineName}; 314 services.logErrorMsg(errorString); 315 throw std::runtime_error(errorString); 316 } 317 318 pgoodLine.request( 319 {"phosphor-power-control", gpiod::line_request::DIRECTION_INPUT, 0}); 320 int pgoodState = pgoodLine.get_value(); 321 pgood = pgoodState; 322 state = pgoodState; 323 services.logInfoMsg(std::format("Pgood state: {}", pgoodState)); 324 } 325 326 void PowerControl::loadConfigFile() 327 { 328 try 329 { 330 std::filesystem::path configFile = findConfigFile(); 331 if (!configFile.empty()) 332 { 333 std::vector<std::unique_ptr<Chassis>> chassis = 334 config_file_parser::parse(configFile, services); 335 system = std::make_unique<System>(std::move(chassis)); 336 } 337 } 338 catch (const std::exception& e) 339 { 340 services.logErrorMsg(std::format( 341 "Unable to parse JSON configuration file: {}", e.what())); 342 } 343 } 344 345 std::filesystem::path PowerControl::findConfigFile() 346 { 347 // Find config file for current system based on compatible system types 348 std::filesystem::path configFile; 349 if (!compatibleSystemTypes.empty()) 350 { 351 try 352 { 353 configFile = config_file_parser::find(compatibleSystemTypes); 354 if (!configFile.empty()) 355 { 356 services.logInfoMsg(std::format( 357 "JSON configuration file found: {}", configFile.string())); 358 } 359 } 360 catch (const std::exception& e) 361 { 362 services.logErrorMsg(std::format( 363 "Unable to find JSON configuration file: {}", e.what())); 364 } 365 } 366 return configFile; 367 } 368 369 } // namespace phosphor::power::sequencer 370