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(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()
620 {
621     fs::path path{fmt::format(POH_COUNTER_PERSIST_PATH, id)};
622     std::ofstream os(path.c_str(), std::ios::binary);
623     cereal::JSONOutputArchive oarchive(os);
624     oarchive(pohCounter());
625     return path;
626 }
627 
628 bool Chassis::deserializePOH(uint32_t& pohCounter)
629 {
630     fs::path path{fmt::format(POH_COUNTER_PERSIST_PATH, id)};
631     try
632     {
633         if (fs::exists(path))
634         {
635             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
636             cereal::JSONInputArchive iarchive(is);
637             iarchive(pohCounter);
638             return true;
639         }
640         return false;
641     }
642     catch (const cereal::Exception& e)
643     {
644         error("deserialize exception: {ERROR}", "ERROR", e);
645         fs::remove(path);
646         return false;
647     }
648     catch (const fs::filesystem_error& e)
649     {
650         return false;
651     }
652 
653     return false;
654 }
655 
656 void Chassis::startPOHCounter()
657 {
658     auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path();
659     fs::create_directories(dir);
660 
661     try
662     {
663         auto event = sdeventplus::Event::get_default();
664         bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
665         event.loop();
666     }
667     catch (const sdeventplus::SdEventError& e)
668     {
669         error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR",
670               e);
671         phosphor::logging::commit<InternalFailure>();
672     }
673 }
674 
675 void Chassis::serializeStateChangeTime()
676 {
677     fs::path path{fmt::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)};
678     std::ofstream os(path.c_str(), std::ios::binary);
679     cereal::JSONOutputArchive oarchive(os);
680 
681     oarchive(ChassisInherit::lastStateChangeTime(),
682              ChassisInherit::currentPowerState());
683 }
684 
685 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state)
686 {
687     fs::path path{fmt::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)};
688 
689     try
690     {
691         if (fs::exists(path))
692         {
693             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
694             cereal::JSONInputArchive iarchive(is);
695             iarchive(time, state);
696             return true;
697         }
698     }
699     catch (const std::exception& e)
700     {
701         error("deserialize exception: {ERROR}", "ERROR", e);
702         fs::remove(path);
703     }
704 
705     return false;
706 }
707 
708 void Chassis::restoreChassisStateChangeTime()
709 {
710     uint64_t time;
711     PowerState state;
712 
713     if (!deserializeStateChangeTime(time, state))
714     {
715         ChassisInherit::lastStateChangeTime(0);
716     }
717     else
718     {
719         ChassisInherit::lastStateChangeTime(time);
720     }
721 }
722 
723 void Chassis::setStateChangeTime()
724 {
725     using namespace std::chrono;
726     uint64_t lastTime;
727     PowerState lastState;
728 
729     auto now =
730         duration_cast<milliseconds>(system_clock::now().time_since_epoch())
731             .count();
732 
733     // If power is on when the BMC is rebooted, this function will get called
734     // because sysStateChange() runs.  Since the power state didn't change
735     // in this case, neither should the state change time, so check that
736     // the power state actually did change here.
737     if (deserializeStateChangeTime(lastTime, lastState))
738     {
739         if (lastState == ChassisInherit::currentPowerState())
740         {
741             return;
742         }
743     }
744 
745     ChassisInherit::lastStateChangeTime(now);
746     serializeStateChangeTime();
747 }
748 
749 bool Chassis::standbyVoltageRegulatorFault()
750 {
751     bool regulatorFault = false;
752 
753     // find standby voltage regulator fault via gpiog
754 
755     auto gpioval = utils::getGpioValue("regulator-standby-faulted");
756 
757     if (-1 == gpioval)
758     {
759         error("Failed reading regulator-standby-faulted GPIO");
760     }
761 
762     if (1 == gpioval)
763     {
764         info("Detected standby voltage regulator fault");
765         regulatorFault = true;
766     }
767 
768     return regulatorFault;
769 }
770 
771 } // namespace manager
772 } // namespace state
773 } // namespace phosphor
774