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