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