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(ObjectMapper::default_service,
232                                       ObjectMapper::instance_path,
233                                       ObjectMapper::interface, "GetSubTree");
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                 PropertyMap properties;
274                 response.read(properties);
275 
276                 if (std::get<uint>(properties["Type"]) != TYPE_UPS)
277                 {
278                     info("UPower device {OBJ_PATH} is not a UPS device",
279                          "OBJ_PATH", path);
280                     continue;
281                 }
282 
283                 if (!std::get<bool>(properties["IsPresent"]))
284                 {
285                     // There is a UPS detected but it is not officially
286                     // "present" yet. Monitor it for state change.
287                     info("UPower device {OBJ_PATH} is not present", "OBJ_PATH",
288                          path);
289                     continue;
290                 }
291 
292                 if (std::get<uint>(properties["State"]) == STATE_FULLY_CHARGED)
293                 {
294                     info("UPS is fully charged");
295                 }
296                 else
297                 {
298                     info("UPS is not fully charged: {UPS_STATE}", "UPS_STATE",
299                          std::get<uint>(properties["State"]));
300                     server::Chassis::currentPowerStatus(
301                         PowerStatus::UninterruptiblePowerSupply);
302                     return false;
303                 }
304 
305                 if (std::get<uint>(properties["BatteryLevel"]) ==
306                     BATTERY_LVL_FULL)
307                 {
308                     info("UPS Battery Level is Full");
309                     // Only one UPS per system, we've found it and it's all
310                     // good so exit function
311                     return true;
312                 }
313                 else
314                 {
315                     info("UPS Battery Level is Low: {UPS_BAT_LEVEL}",
316                          "UPS_BAT_LEVEL",
317                          std::get<uint>(properties["BatteryLevel"]));
318                     server::Chassis::currentPowerStatus(
319                         PowerStatus::UninterruptiblePowerSupply);
320                     return false;
321                 }
322             }
323             catch (const sdbusplus::exception_t& e)
324             {
325                 error("Error reading UPS property, error: {ERROR}, "
326                       "service: {SERVICE} path: {PATH}",
327                       "ERROR", e, "SERVICE", service, "PATH", path);
328                 throw;
329             }
330         }
331     }
332     return true;
333 }
334 
determineStatusOfPSUPower()335 bool Chassis::determineStatusOfPSUPower()
336 {
337     // Find all implementations of the PowerSystemInputs interface
338     auto mapper = bus.new_method_call(ObjectMapper::default_service,
339                                       ObjectMapper::instance_path,
340                                       ObjectMapper::interface, "GetSubTree");
341 
342     mapper.append("/", 0,
343                   std::vector<std::string>(
344                       {decoratorServer::PowerSystemInputs::interface}));
345 
346     std::map<std::string, std::map<std::string, std::vector<std::string>>>
347         mapperResponse;
348 
349     try
350     {
351         auto mapperResponseMsg = bus.call(mapper);
352         mapperResponseMsg.read(mapperResponse);
353     }
354     catch (const sdbusplus::exception_t& e)
355     {
356         error("Error in mapper GetSubTree call for PowerSystemInputs: {ERROR}",
357               "ERROR", e);
358         throw;
359     }
360 
361     for (const auto& [path, services] : mapperResponse)
362     {
363         for (const auto& serviceIter : services)
364         {
365             const std::string& service = serviceIter.first;
366 
367             try
368             {
369                 auto method = bus.new_method_call(service.c_str(), path.c_str(),
370                                                   PROPERTY_INTERFACE, "GetAll");
371                 method.append(decoratorServer::PowerSystemInputs::interface);
372 
373                 auto response = bus.call(method);
374                 using Property = std::string;
375                 using Value = std::variant<std::string>;
376                 using PropertyMap = std::map<Property, Value>;
377                 PropertyMap properties;
378                 response.read(properties);
379 
380                 auto statusStr = std::get<std::string>(properties["Status"]);
381                 auto status =
382                     decoratorServer::PowerSystemInputs::convertStatusFromString(
383                         statusStr);
384 
385                 if (status == decoratorServer::PowerSystemInputs::Status::Fault)
386                 {
387                     info("Power System Inputs status is in Fault state");
388                     server::Chassis::currentPowerStatus(PowerStatus::BrownOut);
389                     return false;
390                 }
391             }
392             catch (const sdbusplus::exception_t& e)
393             {
394                 error(
395                     "Error reading Power System Inputs property, error: {ERROR}, "
396                     "service: {SERVICE} path: {PATH}",
397                     "ERROR", e, "SERVICE", service, "PATH", path);
398                 throw;
399             }
400         }
401     }
402     return true;
403 }
404 
uPowerChangeEvent(sdbusplus::message_t & msg)405 void Chassis::uPowerChangeEvent(sdbusplus::message_t& msg)
406 {
407     debug("UPS Property Change Event Triggered");
408     std::string statusInterface;
409     std::map<std::string, std::variant<uint, bool>> msgData;
410     msg.read(statusInterface, msgData);
411 
412     // If the change is to any of the properties we are interested in, then call
413     // determineStatusOfPower(), which looks at all the power-related
414     // interfaces, to see if a power status change is needed
415     auto propertyMap = msgData.find("IsPresent");
416     if (propertyMap != msgData.end())
417     {
418         info("UPS presence changed to {UPS_PRES_INFO}", "UPS_PRES_INFO",
419              std::get<bool>(propertyMap->second));
420         determineStatusOfPower();
421         return;
422     }
423 
424     propertyMap = msgData.find("State");
425     if (propertyMap != msgData.end())
426     {
427         info("UPS State changed to {UPS_STATE}", "UPS_STATE",
428              std::get<uint>(propertyMap->second));
429         determineStatusOfPower();
430         return;
431     }
432 
433     propertyMap = msgData.find("BatteryLevel");
434     if (propertyMap != msgData.end())
435     {
436         info("UPS BatteryLevel changed to {UPS_BAT_LEVEL}", "UPS_BAT_LEVEL",
437              std::get<uint>(propertyMap->second));
438         determineStatusOfPower();
439         return;
440     }
441     return;
442 }
443 
powerSysInputsChangeEvent(sdbusplus::message_t & msg)444 void Chassis::powerSysInputsChangeEvent(sdbusplus::message_t& msg)
445 {
446     debug("Power System Inputs Property Change Event Triggered");
447     std::string statusInterface;
448     std::map<std::string, std::variant<std::string>> msgData;
449     msg.read(statusInterface, msgData);
450 
451     // If the change is to any of the properties we are interested in, then call
452     // determineStatusOfPower(), which looks at all the power-related
453     // interfaces, to see if a power status change is needed
454     auto propertyMap = msgData.find("Status");
455     if (propertyMap != msgData.end())
456     {
457         info("Power System Inputs status changed to {POWER_SYS_INPUT_STATUS}",
458              "POWER_SYS_INPUT_STATUS",
459              std::get<std::string>(propertyMap->second));
460         determineStatusOfPower();
461         return;
462     }
463     return;
464 }
465 
startUnit(const std::string & sysdUnit)466 void Chassis::startUnit(const std::string& sysdUnit)
467 {
468     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
469                                             SYSTEMD_INTERFACE, "StartUnit");
470 
471     method.append(sysdUnit);
472     method.append("replace");
473 
474     this->bus.call_noreply(method);
475 
476     return;
477 }
478 
restartUnit(const std::string & sysdUnit)479 void Chassis::restartUnit(const std::string& sysdUnit)
480 {
481     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
482                                             SYSTEMD_INTERFACE, "RestartUnit");
483 
484     method.append(sysdUnit);
485     method.append("replace");
486 
487     this->bus.call_noreply(method);
488 
489     return;
490 }
491 
stateActive(const std::string & target)492 bool Chassis::stateActive(const std::string& target)
493 {
494     std::variant<std::string> currentState;
495     sdbusplus::message::object_path unitTargetPath;
496 
497     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
498                                             SYSTEMD_INTERFACE, "GetUnit");
499 
500     method.append(target);
501 
502     try
503     {
504         auto result = this->bus.call(method);
505         result.read(unitTargetPath);
506     }
507     catch (const sdbusplus::exception_t& e)
508     {
509         error("Error in GetUnit call: {ERROR}", "ERROR", e);
510         return false;
511     }
512 
513     method = this->bus.new_method_call(
514         SYSTEMD_SERVICE,
515         static_cast<const std::string&>(unitTargetPath).c_str(),
516         SYSTEMD_PROPERTY_IFACE, "Get");
517 
518     method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState");
519 
520     try
521     {
522         auto result = this->bus.call(method);
523         result.read(currentState);
524     }
525     catch (const sdbusplus::exception_t& e)
526     {
527         error("Error in ActiveState Get: {ERROR}", "ERROR", e);
528         return false;
529     }
530 
531     const auto& currentStateStr = std::get<std::string>(currentState);
532     return currentStateStr == ACTIVE_STATE ||
533            currentStateStr == ACTIVATING_STATE;
534 }
535 
sysStateChange(sdbusplus::message_t & msg)536 int Chassis::sysStateChange(sdbusplus::message_t& msg)
537 {
538     sdbusplus::message::object_path newStateObjPath;
539     std::string newStateUnit{};
540     std::string newStateResult{};
541 
542     // Read the msg and populate each variable
543     try
544     {
545         // newStateID is a throwaway that is needed in order to read the
546         // parameters that are useful out of the dbus message
547         uint32_t newStateID{};
548         msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
549     }
550     catch (const sdbusplus::exception_t& e)
551     {
552         error("Error in state change - bad encoding: {ERROR} {REPLY_SIG}",
553               "ERROR", e, "REPLY_SIG", msg.get_signature());
554         return 0;
555     }
556 
557     if ((newStateUnit == std::format(CHASSIS_STATE_POWEROFF_TGT_FMT, id)) &&
558         (newStateResult == "done") &&
559         (!stateActive(systemdTargetTable[Transition::On])))
560     {
561         info("Received signal that power OFF is complete");
562         this->currentPowerState(server::Chassis::PowerState::Off);
563         this->setStateChangeTime();
564     }
565     else if ((newStateUnit == systemdTargetTable[Transition::On]) &&
566              (newStateResult == "done") &&
567              (stateActive(systemdTargetTable[Transition::On])))
568     {
569         info("Received signal that power ON is complete");
570         this->currentPowerState(server::Chassis::PowerState::On);
571         this->setStateChangeTime();
572 
573         // Remove temporary file which is utilized for scenarios where the
574         // BMC is rebooted while the chassis power is still on.
575         // This file is used to indicate to chassis related systemd services
576         // that the chassis is already on and they should skip running.
577         // Once the chassis state is back to on we can clear this file.
578         auto chassisFile = std::format(CHASSIS_ON_FILE, 0);
579         if (std::filesystem::exists(chassisFile))
580         {
581             std::filesystem::remove(chassisFile);
582         }
583     }
584 
585     return 0;
586 }
587 
requestedPowerTransition(Transition value)588 Chassis::Transition Chassis::requestedPowerTransition(Transition value)
589 {
590     info("Change to Chassis Requested Power State: {REQ_POWER_TRAN}",
591          "REQ_POWER_TRAN", value);
592 #if ONLY_ALLOW_BOOT_WHEN_BMC_READY
593     if ((value != Transition::Off) && (!utils::isBmcReady(this->bus)))
594     {
595         info("BMC State is not Ready so no chassis on operations allowed");
596         throw sdbusplus::xyz::openbmc_project::State::Chassis::Error::
597             BMCNotReady();
598     }
599 #endif
600 
601 #ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION
602     /*
603      * Do not do transition when the any firmware being updated
604      */
605     if ((value != Transition::Off) &&
606         (phosphor::state::manager::utils::isFirmwareUpdating(this->bus)))
607     {
608         info("Firmware being updated, reject the transition request");
609         throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
610     }
611 #endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION
612 
613     startUnit(systemdTargetTable.find(value)->second);
614     return server::Chassis::requestedPowerTransition(value);
615 }
616 
currentPowerState(PowerState value)617 Chassis::PowerState Chassis::currentPowerState(PowerState value)
618 {
619     PowerState chassisPowerState;
620     info("Change to Chassis Power State: {CUR_POWER_STATE}", "CUR_POWER_STATE",
621          value);
622 
623     chassisPowerState = server::Chassis::currentPowerState(value);
624     if (chassisPowerState == PowerState::On)
625     {
626         pohTimer.resetRemaining();
627     }
628     return chassisPowerState;
629 }
630 
pohCounter(uint32_t value)631 uint32_t Chassis::pohCounter(uint32_t value)
632 {
633     if (value != pohCounter())
634     {
635         ChassisInherit::pohCounter(value);
636         serializePOH();
637     }
638     return pohCounter();
639 }
640 
pohCallback()641 void Chassis::pohCallback()
642 {
643     if (ChassisInherit::currentPowerState() == PowerState::On)
644     {
645         pohCounter(pohCounter() + 1);
646     }
647 }
648 
restorePOHCounter()649 void Chassis::restorePOHCounter()
650 {
651     uint32_t counter;
652     if (!deserializePOH(counter))
653     {
654         // set to default value
655         pohCounter(0);
656     }
657     else
658     {
659         pohCounter(counter);
660     }
661 }
662 
serializePOH()663 fs::path Chassis::serializePOH()
664 {
665     fs::path path{std::format(POH_COUNTER_PERSIST_PATH, id)};
666     std::ofstream os(path.c_str(), std::ios::binary);
667     cereal::JSONOutputArchive oarchive(os);
668     oarchive(pohCounter());
669     return path;
670 }
671 
deserializePOH(uint32_t & pohCounter)672 bool Chassis::deserializePOH(uint32_t& pohCounter)
673 {
674     fs::path path{std::format(POH_COUNTER_PERSIST_PATH, id)};
675     try
676     {
677         if (fs::exists(path))
678         {
679             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
680             cereal::JSONInputArchive iarchive(is);
681             iarchive(pohCounter);
682             return true;
683         }
684         return false;
685     }
686     catch (const cereal::Exception& e)
687     {
688         error("deserialize exception: {ERROR}", "ERROR", e);
689         fs::remove(path);
690         return false;
691     }
692     catch (const fs::filesystem_error& e)
693     {
694         return false;
695     }
696 
697     return false;
698 }
699 
startPOHCounter()700 void Chassis::startPOHCounter()
701 {
702     auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path();
703     fs::create_directories(dir);
704 
705     try
706     {
707         auto event = sdeventplus::Event::get_default();
708         bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
709         event.loop();
710     }
711     catch (const sdeventplus::SdEventError& e)
712     {
713         error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR",
714               e);
715         phosphor::logging::commit<InternalFailure>();
716     }
717 }
718 
serializeStateChangeTime()719 void Chassis::serializeStateChangeTime()
720 {
721     fs::path path{std::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)};
722     std::ofstream os(path.c_str(), std::ios::binary);
723     cereal::JSONOutputArchive oarchive(os);
724 
725     oarchive(ChassisInherit::lastStateChangeTime(),
726              ChassisInherit::currentPowerState());
727 }
728 
deserializeStateChangeTime(uint64_t & time,PowerState & state)729 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state)
730 {
731     fs::path path{std::format(CHASSIS_STATE_CHANGE_PERSIST_PATH, id)};
732 
733     try
734     {
735         if (fs::exists(path))
736         {
737             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
738             cereal::JSONInputArchive iarchive(is);
739             iarchive(time, state);
740             return true;
741         }
742     }
743     catch (const std::exception& e)
744     {
745         error("deserialize exception: {ERROR}", "ERROR", e);
746         fs::remove(path);
747     }
748 
749     return false;
750 }
751 
restoreChassisStateChangeTime()752 void Chassis::restoreChassisStateChangeTime()
753 {
754     uint64_t time;
755     PowerState state;
756 
757     if (!deserializeStateChangeTime(time, state))
758     {
759         ChassisInherit::lastStateChangeTime(0);
760     }
761     else
762     {
763         ChassisInherit::lastStateChangeTime(time);
764     }
765 }
766 
setStateChangeTime()767 void Chassis::setStateChangeTime()
768 {
769     using namespace std::chrono;
770     uint64_t lastTime;
771     PowerState lastState;
772 
773     auto now =
774         duration_cast<milliseconds>(system_clock::now().time_since_epoch())
775             .count();
776 
777     // If power is on when the BMC is rebooted, this function will get called
778     // because sysStateChange() runs.  Since the power state didn't change
779     // in this case, neither should the state change time, so check that
780     // the power state actually did change here.
781     if (deserializeStateChangeTime(lastTime, lastState))
782     {
783         if (lastState == ChassisInherit::currentPowerState())
784         {
785             return;
786         }
787     }
788 
789     ChassisInherit::lastStateChangeTime(now);
790     serializeStateChangeTime();
791 }
792 
standbyVoltageRegulatorFault()793 bool Chassis::standbyVoltageRegulatorFault()
794 {
795     bool regulatorFault = false;
796 
797     // find standby voltage regulator fault via gpiog
798 
799     auto gpioval = utils::getGpioValue("regulator-standby-faulted");
800 
801     if (-1 == gpioval)
802     {
803         error("Failed reading regulator-standby-faulted GPIO");
804     }
805 
806     if (1 == gpioval)
807     {
808         info("Detected standby voltage regulator fault");
809         regulatorFault = true;
810     }
811 
812     return regulatorFault;
813 }
814 
815 } // namespace manager
816 } // namespace state
817 } // namespace phosphor
818