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