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