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