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 <phosphor-logging/elog-errors.hpp>
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/bus.hpp>
13 #include <sdbusplus/exception.hpp>
14 #include <sdeventplus/event.hpp>
15 #include <sdeventplus/exception.hpp>
16 
17 #include <filesystem>
18 #include <fstream>
19 
20 namespace phosphor
21 {
22 namespace state
23 {
24 namespace manager
25 {
26 
27 PHOSPHOR_LOG2_USING;
28 
29 // When you see server:: you know we're referencing our base class
30 namespace server = sdbusplus::xyz::openbmc_project::State::server;
31 
32 using namespace phosphor::logging;
33 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
34 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout;
35 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Regulator;
36 constexpr auto CHASSIS_STATE_POWEROFF_TGT = "obmc-chassis-poweroff@0.target";
37 constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT =
38     "obmc-chassis-hard-poweroff@0.target";
39 constexpr auto CHASSIS_STATE_POWERON_TGT = "obmc-chassis-poweron@0.target";
40 
41 constexpr auto ACTIVE_STATE = "active";
42 constexpr auto ACTIVATING_STATE = "activating";
43 
44 // Details at https://upower.freedesktop.org/docs/Device.html
45 constexpr uint TYPE_UPS = 3;
46 constexpr uint STATE_FULLY_CHARGED = 4;
47 constexpr uint BATTERY_LVL_FULL = 8;
48 
49 /* Map a transition to it's systemd target */
50 const std::map<server::Chassis::Transition, std::string> SYSTEMD_TARGET_TABLE =
51     {
52         // Use the hard off target to ensure we shutdown immediately
53         {server::Chassis::Transition::Off, CHASSIS_STATE_HARD_POWEROFF_TGT},
54         {server::Chassis::Transition::On, CHASSIS_STATE_POWERON_TGT}};
55 
56 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
57 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
58 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
59 
60 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
61 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit";
62 
63 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
64 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
65 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
66 constexpr auto UPOWER_INTERFACE = "org.freedesktop.UPower.Device";
67 constexpr auto PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties";
68 
69 void Chassis::subscribeToSystemdSignals()
70 {
71     try
72     {
73         auto method = this->bus.new_method_call(
74             SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Subscribe");
75         this->bus.call(method);
76     }
77     catch (const sdbusplus::exception::exception& e)
78     {
79         error("Failed to subscribe to systemd signals: {ERROR}", "ERROR", e);
80         elog<InternalFailure>();
81     }
82 
83     return;
84 }
85 
86 // TODO - Will be rewritten once sdbusplus client bindings are in place
87 //        and persistent storage design is in place and sdbusplus
88 //        has read property function
89 void Chassis::determineInitialState()
90 {
91 
92     // Monitor for any properties changed signals on UPower device path
93     uPowerPropChangeSignal = std::make_unique<sdbusplus::bus::match_t>(
94         bus,
95         sdbusplus::bus::match::rules::propertiesChangedNamespace(
96             "/org/freedesktop/UPower", UPOWER_INTERFACE),
97         [this](auto& msg) { this->uPowerChangeEvent(msg); });
98 
99     determineStatusOfPower();
100 
101     std::variant<int> pgood = -1;
102     auto method = this->bus.new_method_call(
103         "org.openbmc.control.Power", "/org/openbmc/control/power0",
104         "org.freedesktop.DBus.Properties", "Get");
105 
106     method.append("org.openbmc.control.Power", "pgood");
107     try
108     {
109         auto reply = this->bus.call(method);
110         reply.read(pgood);
111 
112         if (std::get<int>(pgood) == 1)
113         {
114             info("Initial Chassis State will be On");
115             server::Chassis::currentPowerState(PowerState::On);
116             server::Chassis::requestedPowerTransition(Transition::On);
117             return;
118         }
119         else
120         {
121             // The system is off.  If we think it should be on then
122             // we probably lost AC while up, so set a new state
123             // change time.
124             uint64_t lastTime;
125             PowerState lastState;
126 
127             if (deserializeStateChangeTime(lastTime, lastState))
128             {
129                 // If power was on before the BMC reboot and the reboot reason
130                 // was not a pinhole reset, log an error
131                 if (lastState == PowerState::On)
132                 {
133                     info(
134                         "Chassis power was on before the BMC reboot and it is off now");
135                     setStateChangeTime();
136 
137                     if (phosphor::state::manager::utils::getGpioValue(
138                             "reset-cause-pinhole") != 1)
139                     {
140                         if (standbyVoltageRegulatorFault())
141                         {
142                             report<Regulator>();
143                         }
144                         else
145                         {
146                             report<Blackout>();
147                         }
148                     }
149                 }
150             }
151         }
152     }
153     catch (const sdbusplus::exception::exception& e)
154     {
155         // It's acceptable for the pgood state service to not be available
156         // since it will notify us of the pgood state when it comes up.
157         if (e.name() != nullptr &&
158             strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0)
159         {
160             goto fail;
161         }
162 
163         // Only log for unexpected error types.
164         error("Error performing call to get pgood: {ERROR}", "ERROR", e);
165         goto fail;
166     }
167 
168 fail:
169     info("Initial Chassis State will be Off");
170     server::Chassis::currentPowerState(PowerState::Off);
171     server::Chassis::requestedPowerTransition(Transition::Off);
172 
173     return;
174 }
175 
176 void Chassis::determineStatusOfPower()
177 {
178     // Default PowerStatus to good
179     server::Chassis::currentPowerStatus(PowerStatus::Good);
180 
181     // Find all implementations of the UPower interface
182     auto mapper = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
183                                       MAPPER_INTERFACE, "GetSubTree");
184 
185     mapper.append("/", 0, std::vector<std::string>({UPOWER_INTERFACE}));
186 
187     std::map<std::string, std::map<std::string, std::vector<std::string>>>
188         mapperResponse;
189 
190     try
191     {
192         auto mapperResponseMsg = bus.call(mapper);
193         mapperResponseMsg.read(mapperResponse);
194     }
195     catch (const sdbusplus::exception::exception& e)
196     {
197         error("Error in mapper GetSubTree call for UPS: {ERROR}", "ERROR", e);
198         throw;
199     }
200 
201     if (mapperResponse.empty())
202     {
203         debug("No UPower devices found in system");
204     }
205 
206     // Iterate through all returned Upower interfaces and look for UPS's
207     for (const auto& [path, services] : mapperResponse)
208     {
209         for (const auto& serviceIter : services)
210         {
211             const std::string& service = serviceIter.first;
212 
213             try
214             {
215                 auto method = bus.new_method_call(service.c_str(), path.c_str(),
216                                                   PROPERTY_INTERFACE, "GetAll");
217                 method.append(UPOWER_INTERFACE);
218 
219                 auto response = bus.call(method);
220                 using Property = std::string;
221                 using Value = std::variant<bool, uint>;
222                 using PropertyMap = std::map<Property, Value>;
223                 PropertyMap properties;
224                 response.read(properties);
225 
226                 if (std::get<uint>(properties["Type"]) != TYPE_UPS)
227                 {
228                     info("UPower device {OBJ_PATH} is not a UPS device",
229                          "OBJ_PATH", path);
230                     continue;
231                 }
232 
233                 if (std::get<bool>(properties["IsPresent"]) != true)
234                 {
235                     // There is a UPS detected but it is not officially
236                     // "present" yet. Monitor it for state change.
237                     info("UPower device {OBJ_PATH} is not present", "OBJ_PATH",
238                          path);
239                     continue;
240                 }
241 
242                 if (std::get<uint>(properties["State"]) == STATE_FULLY_CHARGED)
243                 {
244                     info("UPS is fully charged");
245                 }
246                 else
247                 {
248                     info("UPS is not fully charged: {UPS_STATE}", "UPS_STATE",
249                          std::get<uint>(properties["State"]));
250                     server::Chassis::currentPowerStatus(
251                         PowerStatus::UninterruptiblePowerSupply);
252                     return;
253                 }
254 
255                 if (std::get<uint>(properties["BatteryLevel"]) ==
256                     BATTERY_LVL_FULL)
257                 {
258                     info("UPS Battery Level is Full");
259                     // Only one UPS per system, we've found it and it's all
260                     // good so exit function
261                     return;
262                 }
263                 else
264                 {
265                     info("UPS Battery Level is Low: {UPS_BAT_LEVEL}",
266                          "UPS_BAT_LEVEL",
267                          std::get<uint>(properties["BatteryLevel"]));
268                     server::Chassis::currentPowerStatus(
269                         PowerStatus::UninterruptiblePowerSupply);
270                     return;
271                 }
272             }
273             catch (const sdbusplus::exception::exception& e)
274             {
275                 error("Error reading UPS property, error: {ERROR}, "
276                       "service: {SERVICE} path: {PATH}",
277                       "ERROR", e, "SERVICE", service, "PATH", path);
278                 throw;
279             }
280         }
281     }
282     return;
283 }
284 
285 void Chassis::uPowerChangeEvent(sdbusplus::message::message& msg)
286 {
287     debug("UPS Property Change Event Triggered");
288     std::string statusInterface;
289     std::map<std::string, std::variant<uint, bool>> msgData;
290     msg.read(statusInterface, msgData);
291 
292     // If the change is to any of the three properties we are interested in
293     // then call determineStatusOfPower() to see if a power status change
294     // is needed
295     auto propertyMap = msgData.find("IsPresent");
296     if (propertyMap != msgData.end())
297     {
298         info("UPS presence changed to {UPS_PRES_INFO}", "UPS_PRES_INFO",
299              std::get<bool>(propertyMap->second));
300         determineStatusOfPower();
301         return;
302     }
303 
304     propertyMap = msgData.find("State");
305     if (propertyMap != msgData.end())
306     {
307         info("UPS State changed to {UPS_STATE}", "UPS_STATE",
308              std::get<uint>(propertyMap->second));
309         determineStatusOfPower();
310         return;
311     }
312 
313     propertyMap = msgData.find("BatteryLevel");
314     if (propertyMap != msgData.end())
315     {
316         info("UPS BatteryLevel changed to {UPS_BAT_LEVEL}", "UPS_BAT_LEVEL",
317              std::get<uint>(propertyMap->second));
318         determineStatusOfPower();
319         return;
320     }
321     return;
322 }
323 
324 void Chassis::executeTransition(Transition tranReq)
325 {
326     auto sysdTarget = SYSTEMD_TARGET_TABLE.find(tranReq)->second;
327 
328     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
329                                             SYSTEMD_INTERFACE, "StartUnit");
330 
331     method.append(sysdTarget);
332     method.append("replace");
333 
334     this->bus.call_noreply(method);
335 
336     return;
337 }
338 
339 bool Chassis::stateActive(const std::string& target)
340 {
341     std::variant<std::string> currentState;
342     sdbusplus::message::object_path unitTargetPath;
343 
344     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
345                                             SYSTEMD_INTERFACE, "GetUnit");
346 
347     method.append(target);
348 
349     try
350     {
351         auto result = this->bus.call(method);
352         result.read(unitTargetPath);
353     }
354     catch (const sdbusplus::exception::exception& e)
355     {
356         error("Error in GetUnit call: {ERROR}", "ERROR", e);
357         return false;
358     }
359 
360     method = this->bus.new_method_call(
361         SYSTEMD_SERVICE,
362         static_cast<const std::string&>(unitTargetPath).c_str(),
363         SYSTEMD_PROPERTY_IFACE, "Get");
364 
365     method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState");
366 
367     try
368     {
369         auto result = this->bus.call(method);
370         result.read(currentState);
371     }
372     catch (const sdbusplus::exception::exception& e)
373     {
374         error("Error in ActiveState Get: {ERROR}", "ERROR", e);
375         return false;
376     }
377 
378     const auto& currentStateStr = std::get<std::string>(currentState);
379     return currentStateStr == ACTIVE_STATE ||
380            currentStateStr == ACTIVATING_STATE;
381 }
382 
383 int Chassis::sysStateChange(sdbusplus::message::message& msg)
384 {
385     sdbusplus::message::object_path newStateObjPath;
386     std::string newStateUnit{};
387     std::string newStateResult{};
388 
389     // Read the msg and populate each variable
390     try
391     {
392         // newStateID is a throwaway that is needed in order to read the
393         // parameters that are useful out of the dbus message
394         uint32_t newStateID{};
395         msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
396     }
397     catch (const sdbusplus::exception::exception& e)
398     {
399         error("Error in state change - bad encoding: {ERROR} {REPLY_SIG}",
400               "ERROR", e, "REPLY_SIG", msg.get_signature());
401         return 0;
402     }
403 
404     if ((newStateUnit == CHASSIS_STATE_POWEROFF_TGT) &&
405         (newStateResult == "done") && (!stateActive(CHASSIS_STATE_POWERON_TGT)))
406     {
407         info("Received signal that power OFF is complete");
408         this->currentPowerState(server::Chassis::PowerState::Off);
409         this->setStateChangeTime();
410     }
411     else if ((newStateUnit == CHASSIS_STATE_POWERON_TGT) &&
412              (newStateResult == "done") &&
413              (stateActive(CHASSIS_STATE_POWERON_TGT)))
414     {
415         info("Received signal that power ON is complete");
416         this->currentPowerState(server::Chassis::PowerState::On);
417         this->setStateChangeTime();
418 
419         // Remove temporary file which is utilized for scenarios where the
420         // BMC is rebooted while the chassis power is still on.
421         // This file is used to indicate to chassis related systemd services
422         // that the chassis is already on and they should skip running.
423         // Once the chassis state is back to on we can clear this file.
424         auto size = std::snprintf(nullptr, 0, CHASSIS_ON_FILE, 0);
425         size++; // null
426         std::unique_ptr<char[]> chassisFile(new char[size]);
427         std::snprintf(chassisFile.get(), size, CHASSIS_ON_FILE, 0);
428         if (std::filesystem::exists(chassisFile.get()))
429         {
430             std::filesystem::remove(chassisFile.get());
431         }
432     }
433 
434     return 0;
435 }
436 
437 Chassis::Transition Chassis::requestedPowerTransition(Transition value)
438 {
439 
440     info("Change to Chassis Requested Power State: {REQ_POWER_TRAN}",
441          "REQ_POWER_TRAN", value);
442     executeTransition(value);
443     return server::Chassis::requestedPowerTransition(value);
444 }
445 
446 Chassis::PowerState Chassis::currentPowerState(PowerState value)
447 {
448     PowerState chassisPowerState;
449     info("Change to Chassis Power State: {CUR_POWER_STATE}", "CUR_POWER_STATE",
450          value);
451 
452     chassisPowerState = server::Chassis::currentPowerState(value);
453     pohTimer.setEnabled(chassisPowerState == PowerState::On);
454     return chassisPowerState;
455 }
456 
457 uint32_t Chassis::pohCounter(uint32_t value)
458 {
459     if (value != pohCounter())
460     {
461         ChassisInherit::pohCounter(value);
462         serializePOH();
463     }
464     return pohCounter();
465 }
466 
467 void Chassis::pohCallback()
468 {
469     if (ChassisInherit::currentPowerState() == PowerState::On)
470     {
471         pohCounter(pohCounter() + 1);
472     }
473 }
474 
475 void Chassis::restorePOHCounter()
476 {
477     uint32_t counter;
478     if (!deserializePOH(POH_COUNTER_PERSIST_PATH, counter))
479     {
480         // set to default value
481         pohCounter(0);
482     }
483     else
484     {
485         pohCounter(counter);
486     }
487 }
488 
489 fs::path Chassis::serializePOH(const fs::path& path)
490 {
491     std::ofstream os(path.c_str(), std::ios::binary);
492     cereal::JSONOutputArchive oarchive(os);
493     oarchive(pohCounter());
494     return path;
495 }
496 
497 bool Chassis::deserializePOH(const fs::path& path, uint32_t& pohCounter)
498 {
499     try
500     {
501         if (fs::exists(path))
502         {
503             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
504             cereal::JSONInputArchive iarchive(is);
505             iarchive(pohCounter);
506             return true;
507         }
508         return false;
509     }
510     catch (const cereal::Exception& e)
511     {
512         error("deserialize exception: {ERROR}", "ERROR", e);
513         fs::remove(path);
514         return false;
515     }
516     catch (const fs::filesystem_error& e)
517     {
518         return false;
519     }
520 
521     return false;
522 }
523 
524 void Chassis::startPOHCounter()
525 {
526     auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path();
527     fs::create_directories(dir);
528 
529     try
530     {
531         auto event = sdeventplus::Event::get_default();
532         bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
533         event.loop();
534     }
535     catch (const sdeventplus::SdEventError& e)
536     {
537         error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR",
538               e);
539         phosphor::logging::commit<InternalFailure>();
540     }
541 }
542 
543 void Chassis::serializeStateChangeTime()
544 {
545     fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
546     std::ofstream os(path.c_str(), std::ios::binary);
547     cereal::JSONOutputArchive oarchive(os);
548 
549     oarchive(ChassisInherit::lastStateChangeTime(),
550              ChassisInherit::currentPowerState());
551 }
552 
553 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state)
554 {
555     fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
556 
557     try
558     {
559         if (fs::exists(path))
560         {
561             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
562             cereal::JSONInputArchive iarchive(is);
563             iarchive(time, state);
564             return true;
565         }
566     }
567     catch (const std::exception& e)
568     {
569         error("deserialize exception: {ERROR}", "ERROR", e);
570         fs::remove(path);
571     }
572 
573     return false;
574 }
575 
576 void Chassis::restoreChassisStateChangeTime()
577 {
578     uint64_t time;
579     PowerState state;
580 
581     if (!deserializeStateChangeTime(time, state))
582     {
583         ChassisInherit::lastStateChangeTime(0);
584     }
585     else
586     {
587         ChassisInherit::lastStateChangeTime(time);
588     }
589 }
590 
591 void Chassis::setStateChangeTime()
592 {
593     using namespace std::chrono;
594     uint64_t lastTime;
595     PowerState lastState;
596 
597     auto now =
598         duration_cast<milliseconds>(system_clock::now().time_since_epoch())
599             .count();
600 
601     // If power is on when the BMC is rebooted, this function will get called
602     // because sysStateChange() runs.  Since the power state didn't change
603     // in this case, neither should the state change time, so check that
604     // the power state actually did change here.
605     if (deserializeStateChangeTime(lastTime, lastState))
606     {
607         if (lastState == ChassisInherit::currentPowerState())
608         {
609             return;
610         }
611     }
612 
613     ChassisInherit::lastStateChangeTime(now);
614     serializeStateChangeTime();
615 }
616 
617 bool Chassis::standbyVoltageRegulatorFault()
618 {
619     bool regulatorFault = false;
620 
621     // find standby voltage regulator fault via gpiog
622 
623     auto gpioval = utils::getGpioValue("regulator-standby-faulted");
624 
625     if (-1 == gpioval)
626     {
627         error("Failed reading regulator-standby-faulted GPIO");
628     }
629 
630     if (1 == gpioval)
631     {
632         info("Detected standby voltage regulator fault");
633         regulatorFault = true;
634     }
635 
636     return regulatorFault;
637 }
638 
639 } // namespace manager
640 } // namespace state
641 } // namespace phosphor
642