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