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