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