1 #include "config.h" 2 3 #include "chassis_state_manager.hpp" 4 5 #include "utils.hpp" 6 #include "xyz/openbmc_project/Common/error.hpp" 7 #include "xyz/openbmc_project/State/Shutdown/Power/error.hpp" 8 9 #include <fmt/format.h> 10 #include <fmt/printf.h> 11 12 #include <cereal/archives/json.hpp> 13 #include <phosphor-logging/elog-errors.hpp> 14 #include <phosphor-logging/lg2.hpp> 15 #include <sdbusplus/bus.hpp> 16 #include <sdbusplus/exception.hpp> 17 #include <sdeventplus/event.hpp> 18 #include <sdeventplus/exception.hpp> 19 #include <xyz/openbmc_project/State/Decorator/PowerSystemInputs/server.hpp> 20 21 #include <filesystem> 22 #include <fstream> 23 24 namespace phosphor 25 { 26 namespace state 27 { 28 namespace manager 29 { 30 31 PHOSPHOR_LOG2_USING; 32 33 // When you see server:: you know we're referencing our base class 34 namespace server = sdbusplus::xyz::openbmc_project::State::server; 35 namespace decoratorServer = 36 sdbusplus::xyz::openbmc_project::State::Decorator::server; 37 38 using namespace phosphor::logging; 39 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; 40 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout; 41 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Regulator; 42 constexpr auto CHASSIS_STATE_POWEROFF_TGT_FMT = 43 "obmc-chassis-poweroff@{}.target"; 44 constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT_FMT = 45 "obmc-chassis-hard-poweroff@{}.target"; 46 constexpr auto CHASSIS_STATE_POWERON_TGT_FMT = "obmc-chassis-poweron@{}.target"; 47 constexpr auto CHASSIS_BLACKOUT_TGT_FMT = "obmc-chassis-blackout@{}.target"; 48 constexpr auto AUTO_POWER_RESTORE_SVC_FMT = 49 "phosphor-discover-system-state@{}.service"; 50 constexpr auto ACTIVE_STATE = "active"; 51 constexpr auto ACTIVATING_STATE = "activating"; 52 53 // Details at https://upower.freedesktop.org/docs/Device.html 54 constexpr uint TYPE_UPS = 3; 55 constexpr uint STATE_FULLY_CHARGED = 4; 56 constexpr uint BATTERY_LVL_FULL = 8; 57 58 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1"; 59 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1"; 60 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager"; 61 62 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties"; 63 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit"; 64 65 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper"; 66 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper"; 67 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper"; 68 constexpr auto UPOWER_INTERFACE = "org.freedesktop.UPower.Device"; 69 constexpr auto POWERSYSINPUTS_INTERFACE = 70 "xyz.openbmc_project.State.Decorator.PowerSystemInputs"; 71 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"; 72 73 void Chassis::subscribeToSystemdSignals() 74 { 75 try 76 { 77 auto method = this->bus.new_method_call( 78 SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Subscribe"); 79 this->bus.call(method); 80 } 81 catch (const sdbusplus::exception_t& e) 82 { 83 error("Failed to subscribe to systemd signals: {ERROR}", "ERROR", e); 84 elog<InternalFailure>(); 85 } 86 87 return; 88 } 89 90 void Chassis::createSystemdTargetTable() 91 { 92 systemdTargetTable = { 93 // Use the hard off target to ensure we shutdown immediately 94 {Transition::Off, fmt::format(CHASSIS_STATE_HARD_POWEROFF_TGT_FMT, id)}, 95 {Transition::On, fmt::format(CHASSIS_STATE_POWERON_TGT_FMT, id)}}; 96 } 97 98 // TODO - Will be rewritten once sdbusplus client bindings are in place 99 // and persistent storage design is in place and sdbusplus 100 // has read property function 101 void Chassis::determineInitialState() 102 { 103 // Monitor for any properties changed signals on UPower device path 104 uPowerPropChangeSignal = std::make_unique<sdbusplus::bus::match_t>( 105 bus, 106 sdbusplus::bus::match::rules::propertiesChangedNamespace( 107 "/org/freedesktop/UPower", UPOWER_INTERFACE), 108 [this](auto& msg) { this->uPowerChangeEvent(msg); }); 109 110 // Monitor for any properties changed signals on PowerSystemInputs 111 powerSysInputsPropChangeSignal = std::make_unique<sdbusplus::bus::match_t>( 112 bus, 113 sdbusplus::bus::match::rules::propertiesChangedNamespace( 114 fmt::format( 115 "/xyz/openbmc_project/power/power_supplies/chassis{}/psus", id), 116 POWERSYSINPUTS_INTERFACE), 117 [this](auto& msg) { this->powerSysInputsChangeEvent(msg); }); 118 119 determineStatusOfPower(); 120 121 std::variant<int> pgood = -1; 122 auto method = this->bus.new_method_call( 123 "org.openbmc.control.Power", "/org/openbmc/control/power0", 124 "org.freedesktop.DBus.Properties", "Get"); 125 126 method.append("org.openbmc.control.Power", "pgood"); 127 try 128 { 129 auto reply = this->bus.call(method); 130 reply.read(pgood); 131 132 if (std::get<int>(pgood) == 1) 133 { 134 info("Initial Chassis State will be On"); 135 server::Chassis::currentPowerState(PowerState::On); 136 server::Chassis::requestedPowerTransition(Transition::On); 137 return; 138 } 139 else 140 { 141 // The system is off. If we think it should be on then 142 // we probably lost AC while up, so set a new state 143 // change time. 144 uint64_t lastTime; 145 PowerState lastState; 146 147 if (deserializeStateChangeTime(lastTime, lastState)) 148 { 149 // If power was on before the BMC reboot and the reboot reason 150 // was not a pinhole reset, log an error 151 if (lastState == PowerState::On) 152 { 153 info( 154 "Chassis power was on before the BMC reboot and it is off now"); 155 156 // Reset host sensors since system is off now 157 // Ensure Power Leds are off. 158 startUnit(fmt::format(CHASSIS_BLACKOUT_TGT_FMT, id)); 159 160 setStateChangeTime(); 161 // Generate file indicating AC loss occurred 162 std::string chassisLostPowerFileFmt = 163 fmt::sprintf(CHASSIS_LOST_POWER_FILE, id); 164 fs::create_directories(BASE_FILE_DIR); 165 fs::path chassisPowerLossFile{chassisLostPowerFileFmt}; 166 std::ofstream outfile(chassisPowerLossFile); 167 outfile.close(); 168 169 // 0 indicates pinhole reset. 1 is NOT pinhole reset 170 if (phosphor::state::manager::utils::getGpioValue( 171 "reset-cause-pinhole") != 0) 172 { 173 if (standbyVoltageRegulatorFault()) 174 { 175 report<Regulator>(); 176 } 177 else 178 { 179 report<Blackout>(Entry::Level::Critical); 180 } 181 } 182 else 183 { 184 info("Pinhole reset"); 185 } 186 } 187 } 188 } 189 } 190 catch (const sdbusplus::exception_t& e) 191 { 192 // It's acceptable for the pgood state service to not be available 193 // since it will notify us of the pgood state when it comes up. 194 if (e.name() != nullptr && 195 strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0) 196 { 197 goto fail; 198 } 199 200 // Only log for unexpected error types. 201 error("Error performing call to get pgood: {ERROR}", "ERROR", e); 202 goto fail; 203 } 204 205 fail: 206 info("Initial Chassis State will be Off"); 207 server::Chassis::currentPowerState(PowerState::Off); 208 server::Chassis::requestedPowerTransition(Transition::Off); 209 210 return; 211 } 212 213 void Chassis::determineStatusOfPower() 214 { 215 auto initialPowerStatus = server::Chassis::currentPowerStatus(); 216 217 bool powerGood = determineStatusOfUPSPower(); 218 if (!powerGood) 219 { 220 return; 221 } 222 223 powerGood = determineStatusOfPSUPower(); 224 if (powerGood) 225 { 226 // All checks passed, set power status to good 227 server::Chassis::currentPowerStatus(PowerStatus::Good); 228 229 // If power status transitioned from bad to good and chassis power is 230 // off then call Auto Power Restart to see if the system should auto 231 // power on now that power status is good 232 if ((initialPowerStatus != PowerStatus::Good) && 233 (server::Chassis::currentPowerState() == PowerState::Off)) 234 { 235 info("power status transitioned from {START_PWR_STATE} to Good and " 236 "chassis power is off, calling APR", 237 "START_PWR_STATE", initialPowerStatus); 238 restartUnit(fmt::format(AUTO_POWER_RESTORE_SVC_FMT, this->id)); 239 } 240 } 241 } 242 243 bool Chassis::determineStatusOfUPSPower() 244 { 245 // Find all implementations of the UPower interface 246 auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, 247 MAPPER_INTERFACE, "GetSubTree"); 248 249 mapper.append("/", 0, std::vector<std::string>({UPOWER_INTERFACE})); 250 251 std::map<std::string, std::map<std::string, std::vector<std::string>>> 252 mapperResponse; 253 254 try 255 { 256 auto mapperResponseMsg = bus.call(mapper); 257 mapperResponseMsg.read(mapperResponse); 258 } 259 catch (const sdbusplus::exception_t& e) 260 { 261 error("Error in mapper GetSubTree call for UPS: {ERROR}", "ERROR", e); 262 throw; 263 } 264 265 if (mapperResponse.empty()) 266 { 267 debug("No UPower devices found in system"); 268 } 269 270 // Iterate through all returned Upower interfaces and look for UPS's 271 for (const auto& [path, services] : mapperResponse) 272 { 273 for (const auto& serviceIter : services) 274 { 275 const std::string& service = serviceIter.first; 276 277 try 278 { 279 auto method = bus.new_method_call(service.c_str(), path.c_str(), 280 PROPERTY_INTERFACE, "GetAll"); 281 method.append(UPOWER_INTERFACE); 282 283 auto response = bus.call(method); 284 using Property = std::string; 285 using Value = std::variant<bool, uint>; 286 using PropertyMap = std::map<Property, Value>; 287 PropertyMap properties; 288 response.read(properties); 289 290 if (std::get<uint>(properties["Type"]) != TYPE_UPS) 291 { 292 info("UPower device {OBJ_PATH} is not a UPS device", 293 "OBJ_PATH", path); 294 continue; 295 } 296 297 if (std::get<bool>(properties["IsPresent"]) != true) 298 { 299 // There is a UPS detected but it is not officially 300 // "present" yet. Monitor it for state change. 301 info("UPower device {OBJ_PATH} is not present", "OBJ_PATH", 302 path); 303 continue; 304 } 305 306 if (std::get<uint>(properties["State"]) == STATE_FULLY_CHARGED) 307 { 308 info("UPS is fully charged"); 309 } 310 else 311 { 312 info("UPS is not fully charged: {UPS_STATE}", "UPS_STATE", 313 std::get<uint>(properties["State"])); 314 server::Chassis::currentPowerStatus( 315 PowerStatus::UninterruptiblePowerSupply); 316 return false; 317 } 318 319 if (std::get<uint>(properties["BatteryLevel"]) == 320 BATTERY_LVL_FULL) 321 { 322 info("UPS Battery Level is Full"); 323 // Only one UPS per system, we've found it and it's all 324 // good so exit function 325 return true; 326 } 327 else 328 { 329 info("UPS Battery Level is Low: {UPS_BAT_LEVEL}", 330 "UPS_BAT_LEVEL", 331 std::get<uint>(properties["BatteryLevel"])); 332 server::Chassis::currentPowerStatus( 333 PowerStatus::UninterruptiblePowerSupply); 334 return false; 335 } 336 } 337 catch (const sdbusplus::exception_t& e) 338 { 339 error("Error reading UPS property, error: {ERROR}, " 340 "service: {SERVICE} path: {PATH}", 341 "ERROR", e, "SERVICE", service, "PATH", path); 342 throw; 343 } 344 } 345 } 346 return true; 347 } 348 349 bool Chassis::determineStatusOfPSUPower() 350 { 351 // Find all implementations of the PowerSystemInputs interface 352 auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH, 353 MAPPER_INTERFACE, "GetSubTree"); 354 355 mapper.append("/", 0, std::vector<std::string>({POWERSYSINPUTS_INTERFACE})); 356 357 std::map<std::string, std::map<std::string, std::vector<std::string>>> 358 mapperResponse; 359 360 try 361 { 362 auto mapperResponseMsg = bus.call(mapper); 363 mapperResponseMsg.read(mapperResponse); 364 } 365 catch (const sdbusplus::exception_t& e) 366 { 367 error("Error in mapper GetSubTree call for PowerSystemInputs: {ERROR}", 368 "ERROR", e); 369 throw; 370 } 371 372 for (const auto& [path, services] : mapperResponse) 373 { 374 for (const auto& serviceIter : services) 375 { 376 const std::string& service = serviceIter.first; 377 378 try 379 { 380 auto method = bus.new_method_call(service.c_str(), path.c_str(), 381 PROPERTY_INTERFACE, "GetAll"); 382 method.append(POWERSYSINPUTS_INTERFACE); 383 384 auto response = bus.call(method); 385 using Property = std::string; 386 using Value = std::variant<std::string>; 387 using PropertyMap = std::map<Property, Value>; 388 PropertyMap properties; 389 response.read(properties); 390 391 auto statusStr = std::get<std::string>(properties["Status"]); 392 auto status = 393 decoratorServer::PowerSystemInputs::convertStatusFromString( 394 statusStr); 395 396 if (status == decoratorServer::PowerSystemInputs::Status::Fault) 397 { 398 info("Power System Inputs status is in Fault state"); 399 server::Chassis::currentPowerStatus(PowerStatus::BrownOut); 400 return false; 401 } 402 } 403 catch (const sdbusplus::exception_t& e) 404 { 405 error( 406 "Error reading Power System Inputs property, error: {ERROR}, " 407 "service: {SERVICE} path: {PATH}", 408 "ERROR", e, "SERVICE", service, "PATH", path); 409 throw; 410 } 411 } 412 } 413 return true; 414 } 415 416 void Chassis::uPowerChangeEvent(sdbusplus::message_t& msg) 417 { 418 debug("UPS Property Change Event Triggered"); 419 std::string statusInterface; 420 std::map<std::string, std::variant<uint, bool>> msgData; 421 msg.read(statusInterface, msgData); 422 423 // If the change is to any of the properties we are interested in, then call 424 // determineStatusOfPower(), which looks at all the power-related 425 // interfaces, to see if a power status change is needed 426 auto propertyMap = msgData.find("IsPresent"); 427 if (propertyMap != msgData.end()) 428 { 429 info("UPS presence changed to {UPS_PRES_INFO}", "UPS_PRES_INFO", 430 std::get<bool>(propertyMap->second)); 431 determineStatusOfPower(); 432 return; 433 } 434 435 propertyMap = msgData.find("State"); 436 if (propertyMap != msgData.end()) 437 { 438 info("UPS State changed to {UPS_STATE}", "UPS_STATE", 439 std::get<uint>(propertyMap->second)); 440 determineStatusOfPower(); 441 return; 442 } 443 444 propertyMap = msgData.find("BatteryLevel"); 445 if (propertyMap != msgData.end()) 446 { 447 info("UPS BatteryLevel changed to {UPS_BAT_LEVEL}", "UPS_BAT_LEVEL", 448 std::get<uint>(propertyMap->second)); 449 determineStatusOfPower(); 450 return; 451 } 452 return; 453 } 454 455 void Chassis::powerSysInputsChangeEvent(sdbusplus::message_t& msg) 456 { 457 debug("Power System Inputs Property Change Event Triggered"); 458 std::string statusInterface; 459 std::map<std::string, std::variant<std::string>> msgData; 460 msg.read(statusInterface, msgData); 461 462 // If the change is to any of the properties we are interested in, then call 463 // determineStatusOfPower(), which looks at all the power-related 464 // interfaces, to see if a power status change is needed 465 auto propertyMap = msgData.find("Status"); 466 if (propertyMap != msgData.end()) 467 { 468 info("Power System Inputs status changed to {POWER_SYS_INPUT_STATUS}", 469 "POWER_SYS_INPUT_STATUS", 470 std::get<std::string>(propertyMap->second)); 471 determineStatusOfPower(); 472 return; 473 } 474 return; 475 } 476 477 void Chassis::startUnit(const std::string& sysdUnit) 478 { 479 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 480 SYSTEMD_INTERFACE, "StartUnit"); 481 482 method.append(sysdUnit); 483 method.append("replace"); 484 485 this->bus.call_noreply(method); 486 487 return; 488 } 489 490 void Chassis::restartUnit(const std::string& sysdUnit) 491 { 492 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 493 SYSTEMD_INTERFACE, "RestartUnit"); 494 495 method.append(sysdUnit); 496 method.append("replace"); 497 498 this->bus.call_noreply(method); 499 500 return; 501 } 502 503 bool Chassis::stateActive(const std::string& target) 504 { 505 std::variant<std::string> currentState; 506 sdbusplus::message::object_path unitTargetPath; 507 508 auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, 509 SYSTEMD_INTERFACE, "GetUnit"); 510 511 method.append(target); 512 513 try 514 { 515 auto result = this->bus.call(method); 516 result.read(unitTargetPath); 517 } 518 catch (const sdbusplus::exception_t& e) 519 { 520 error("Error in GetUnit call: {ERROR}", "ERROR", e); 521 return false; 522 } 523 524 method = this->bus.new_method_call( 525 SYSTEMD_SERVICE, 526 static_cast<const std::string&>(unitTargetPath).c_str(), 527 SYSTEMD_PROPERTY_IFACE, "Get"); 528 529 method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState"); 530 531 try 532 { 533 auto result = this->bus.call(method); 534 result.read(currentState); 535 } 536 catch (const sdbusplus::exception_t& e) 537 { 538 error("Error in ActiveState Get: {ERROR}", "ERROR", e); 539 return false; 540 } 541 542 const auto& currentStateStr = std::get<std::string>(currentState); 543 return currentStateStr == ACTIVE_STATE || 544 currentStateStr == ACTIVATING_STATE; 545 } 546 547 int Chassis::sysStateChange(sdbusplus::message_t& msg) 548 { 549 sdbusplus::message::object_path newStateObjPath; 550 std::string newStateUnit{}; 551 std::string newStateResult{}; 552 553 // Read the msg and populate each variable 554 try 555 { 556 // newStateID is a throwaway that is needed in order to read the 557 // parameters that are useful out of the dbus message 558 uint32_t newStateID{}; 559 msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult); 560 } 561 catch (const sdbusplus::exception_t& e) 562 { 563 error("Error in state change - bad encoding: {ERROR} {REPLY_SIG}", 564 "ERROR", e, "REPLY_SIG", msg.get_signature()); 565 return 0; 566 } 567 568 if ((newStateUnit == fmt::format(CHASSIS_STATE_POWEROFF_TGT_FMT, id)) && 569 (newStateResult == "done") && 570 (!stateActive(systemdTargetTable[Transition::On]))) 571 { 572 info("Received signal that power OFF is complete"); 573 this->currentPowerState(server::Chassis::PowerState::Off); 574 this->setStateChangeTime(); 575 } 576 else if ((newStateUnit == systemdTargetTable[Transition::On]) && 577 (newStateResult == "done") && 578 (stateActive(systemdTargetTable[Transition::On]))) 579 { 580 info("Received signal that power ON is complete"); 581 this->currentPowerState(server::Chassis::PowerState::On); 582 this->setStateChangeTime(); 583 584 // Remove temporary file which is utilized for scenarios where the 585 // BMC is rebooted while the chassis power is still on. 586 // This file is used to indicate to chassis related systemd services 587 // that the chassis is already on and they should skip running. 588 // Once the chassis state is back to on we can clear this file. 589 auto size = std::snprintf(nullptr, 0, CHASSIS_ON_FILE, 0); 590 size++; // null 591 std::unique_ptr<char[]> chassisFile(new char[size]); 592 std::snprintf(chassisFile.get(), size, CHASSIS_ON_FILE, 0); 593 if (std::filesystem::exists(chassisFile.get())) 594 { 595 std::filesystem::remove(chassisFile.get()); 596 } 597 } 598 599 return 0; 600 } 601 602 Chassis::Transition Chassis::requestedPowerTransition(Transition value) 603 { 604 info("Change to Chassis Requested Power State: {REQ_POWER_TRAN}", 605 "REQ_POWER_TRAN", value); 606 startUnit(systemdTargetTable.find(value)->second); 607 return server::Chassis::requestedPowerTransition(value); 608 } 609 610 Chassis::PowerState Chassis::currentPowerState(PowerState value) 611 { 612 PowerState chassisPowerState; 613 info("Change to Chassis Power State: {CUR_POWER_STATE}", "CUR_POWER_STATE", 614 value); 615 616 chassisPowerState = server::Chassis::currentPowerState(value); 617 if (chassisPowerState == PowerState::On) 618 { 619 pohTimer.resetRemaining(); 620 } 621 return chassisPowerState; 622 } 623 624 uint32_t Chassis::pohCounter(uint32_t value) 625 { 626 if (value != pohCounter()) 627 { 628 ChassisInherit::pohCounter(value); 629 serializePOH(); 630 } 631 return pohCounter(); 632 } 633 634 void Chassis::pohCallback() 635 { 636 if (ChassisInherit::currentPowerState() == PowerState::On) 637 { 638 pohCounter(pohCounter() + 1); 639 } 640 } 641 642 void Chassis::restorePOHCounter() 643 { 644 uint32_t counter; 645 if (!deserializePOH(counter)) 646 { 647 // set to default value 648 pohCounter(0); 649 } 650 else 651 { 652 pohCounter(counter); 653 } 654 } 655 656 fs::path Chassis::serializePOH() 657 { 658 fs::path path{fmt::format(POH_COUNTER_PERSIST_PATH, id)}; 659 std::ofstream os(path.c_str(), std::ios::binary); 660 cereal::JSONOutputArchive oarchive(os); 661 oarchive(pohCounter()); 662 return path; 663 } 664 665 bool Chassis::deserializePOH(uint32_t& pohCounter) 666 { 667 fs::path path{fmt::format(POH_COUNTER_PERSIST_PATH, id)}; 668 try 669 { 670 if (fs::exists(path)) 671 { 672 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 673 cereal::JSONInputArchive iarchive(is); 674 iarchive(pohCounter); 675 return true; 676 } 677 return false; 678 } 679 catch (const cereal::Exception& e) 680 { 681 error("deserialize exception: {ERROR}", "ERROR", e); 682 fs::remove(path); 683 return false; 684 } 685 catch (const fs::filesystem_error& e) 686 { 687 return false; 688 } 689 690 return false; 691 } 692 693 void Chassis::startPOHCounter() 694 { 695 auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path(); 696 fs::create_directories(dir); 697 698 try 699 { 700 auto event = sdeventplus::Event::get_default(); 701 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL); 702 event.loop(); 703 } 704 catch (const sdeventplus::SdEventError& e) 705 { 706 error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR", 707 e); 708 phosphor::logging::commit<InternalFailure>(); 709 } 710 } 711 712 void Chassis::serializeStateChangeTime() 713 { 714 fs::path path{fmt::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)}; 715 std::ofstream os(path.c_str(), std::ios::binary); 716 cereal::JSONOutputArchive oarchive(os); 717 718 oarchive(ChassisInherit::lastStateChangeTime(), 719 ChassisInherit::currentPowerState()); 720 } 721 722 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state) 723 { 724 fs::path path{fmt::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)}; 725 726 try 727 { 728 if (fs::exists(path)) 729 { 730 std::ifstream is(path.c_str(), std::ios::in | std::ios::binary); 731 cereal::JSONInputArchive iarchive(is); 732 iarchive(time, state); 733 return true; 734 } 735 } 736 catch (const std::exception& e) 737 { 738 error("deserialize exception: {ERROR}", "ERROR", e); 739 fs::remove(path); 740 } 741 742 return false; 743 } 744 745 void Chassis::restoreChassisStateChangeTime() 746 { 747 uint64_t time; 748 PowerState state; 749 750 if (!deserializeStateChangeTime(time, state)) 751 { 752 ChassisInherit::lastStateChangeTime(0); 753 } 754 else 755 { 756 ChassisInherit::lastStateChangeTime(time); 757 } 758 } 759 760 void Chassis::setStateChangeTime() 761 { 762 using namespace std::chrono; 763 uint64_t lastTime; 764 PowerState lastState; 765 766 auto now = 767 duration_cast<milliseconds>(system_clock::now().time_since_epoch()) 768 .count(); 769 770 // If power is on when the BMC is rebooted, this function will get called 771 // because sysStateChange() runs. Since the power state didn't change 772 // in this case, neither should the state change time, so check that 773 // the power state actually did change here. 774 if (deserializeStateChangeTime(lastTime, lastState)) 775 { 776 if (lastState == ChassisInherit::currentPowerState()) 777 { 778 return; 779 } 780 } 781 782 ChassisInherit::lastStateChangeTime(now); 783 serializeStateChangeTime(); 784 } 785 786 bool Chassis::standbyVoltageRegulatorFault() 787 { 788 bool regulatorFault = false; 789 790 // find standby voltage regulator fault via gpiog 791 792 auto gpioval = utils::getGpioValue("regulator-standby-faulted"); 793 794 if (-1 == gpioval) 795 { 796 error("Failed reading regulator-standby-faulted GPIO"); 797 } 798 799 if (1 == gpioval) 800 { 801 info("Detected standby voltage regulator fault"); 802 regulatorFault = true; 803 } 804 805 return regulatorFault; 806 } 807 808 } // namespace manager 809 } // namespace state 810 } // namespace phosphor 811