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