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