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