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