1 #include "config.h"
2 
3 #include "chassis_state_manager.hpp"
4 
5 #include "xyz/openbmc_project/Common/error.hpp"
6 #include "xyz/openbmc_project/State/Shutdown/Power/error.hpp"
7 
8 #include <cereal/archives/json.hpp>
9 #include <phosphor-logging/elog-errors.hpp>
10 #include <phosphor-logging/log.hpp>
11 #include <sdbusplus/bus.hpp>
12 #include <sdbusplus/exception.hpp>
13 #include <sdeventplus/event.hpp>
14 #include <sdeventplus/exception.hpp>
15 
16 #include <experimental/filesystem>
17 #include <fstream>
18 
19 namespace phosphor
20 {
21 namespace state
22 {
23 namespace manager
24 {
25 
26 // When you see server:: you know we're referencing our base class
27 namespace server = sdbusplus::xyz::openbmc_project::State::server;
28 
29 using namespace phosphor::logging;
30 using sdbusplus::exception::SdBusError;
31 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
32 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout;
33 constexpr auto CHASSIS_STATE_POWEROFF_TGT = "obmc-chassis-poweroff@0.target";
34 constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT =
35     "obmc-chassis-hard-poweroff@0.target";
36 constexpr auto CHASSIS_STATE_POWERON_TGT = "obmc-chassis-poweron@0.target";
37 
38 constexpr auto ACTIVE_STATE = "active";
39 constexpr auto ACTIVATING_STATE = "activating";
40 
41 /* Map a transition to it's systemd target */
42 const std::map<server::Chassis::Transition, std::string> SYSTEMD_TARGET_TABLE =
43     {
44         // Use the hard off target to ensure we shutdown immediately
45         {server::Chassis::Transition::Off, CHASSIS_STATE_HARD_POWEROFF_TGT},
46         {server::Chassis::Transition::On, CHASSIS_STATE_POWERON_TGT}};
47 
48 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
49 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
50 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
51 
52 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
53 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit";
54 
55 void Chassis::subscribeToSystemdSignals()
56 {
57     try
58     {
59         auto method = this->bus.new_method_call(
60             SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Subscribe");
61         this->bus.call(method);
62     }
63     catch (const sdbusplus::exception::SdBusError& ex)
64     {
65         log<level::ERR>("Failed to subscribe to systemd signals",
66                         entry("ERR=%s", ex.what()));
67         elog<InternalFailure>();
68     }
69 
70     return;
71 }
72 
73 // TODO - Will be rewritten once sdbusplus client bindings are in place
74 //        and persistent storage design is in place and sdbusplus
75 //        has read property function
76 void Chassis::determineInitialState()
77 {
78     std::variant<int> pgood = -1;
79     auto method = this->bus.new_method_call(
80         "org.openbmc.control.Power", "/org/openbmc/control/power0",
81         "org.freedesktop.DBus.Properties", "Get");
82 
83     method.append("org.openbmc.control.Power", "pgood");
84     try
85     {
86         auto reply = this->bus.call(method);
87         reply.read(pgood);
88 
89         if (std::get<int>(pgood) == 1)
90         {
91             log<level::INFO>("Initial Chassis State will be On",
92                              entry("CHASSIS_CURRENT_POWER_STATE=%s",
93                                    convertForMessage(PowerState::On).c_str()));
94             server::Chassis::currentPowerState(PowerState::On);
95             server::Chassis::requestedPowerTransition(Transition::On);
96             return;
97         }
98         else
99         {
100             // The system is off.  If we think it should be on then
101             // we probably lost AC while up, so set a new state
102             // change time.
103             uint64_t lastTime;
104             PowerState lastState;
105 
106             if (deserializeStateChangeTime(lastTime, lastState))
107             {
108                 if (lastState == PowerState::On)
109                 {
110                     report<Blackout>();
111                     setStateChangeTime();
112                 }
113             }
114         }
115     }
116     catch (const SdBusError& e)
117     {
118         // It's acceptable for the pgood state service to not be available
119         // since it will notify us of the pgood state when it comes up.
120         if (e.name() != nullptr &&
121             strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0)
122         {
123             goto fail;
124         }
125 
126         // Only log for unexpected error types.
127         log<level::ERR>("Error performing call to get pgood",
128                         entry("ERROR=%s", e.what()));
129         goto fail;
130     }
131 
132 fail:
133     log<level::INFO>("Initial Chassis State will be Off",
134                      entry("CHASSIS_CURRENT_POWER_STATE=%s",
135                            convertForMessage(PowerState::Off).c_str()));
136     server::Chassis::currentPowerState(PowerState::Off);
137     server::Chassis::requestedPowerTransition(Transition::Off);
138 
139     return;
140 }
141 
142 void Chassis::executeTransition(Transition tranReq)
143 {
144     auto sysdTarget = SYSTEMD_TARGET_TABLE.find(tranReq)->second;
145 
146     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
147                                             SYSTEMD_INTERFACE, "StartUnit");
148 
149     method.append(sysdTarget);
150     method.append("replace");
151 
152     this->bus.call_noreply(method);
153 
154     return;
155 }
156 
157 bool Chassis::stateActive(const std::string& target)
158 {
159     std::variant<std::string> currentState;
160     sdbusplus::message::object_path unitTargetPath;
161 
162     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
163                                             SYSTEMD_INTERFACE, "GetUnit");
164 
165     method.append(target);
166 
167     try
168     {
169         auto result = this->bus.call(method);
170         result.read(unitTargetPath);
171     }
172     catch (const SdBusError& e)
173     {
174         log<level::ERR>("Error in GetUnit call", entry("ERROR=%s", e.what()));
175         return false;
176     }
177 
178     method = this->bus.new_method_call(
179         SYSTEMD_SERVICE,
180         static_cast<const std::string&>(unitTargetPath).c_str(),
181         SYSTEMD_PROPERTY_IFACE, "Get");
182 
183     method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState");
184 
185     try
186     {
187         auto result = this->bus.call(method);
188         result.read(currentState);
189     }
190     catch (const SdBusError& e)
191     {
192         log<level::ERR>("Error in ActiveState Get",
193                         entry("ERROR=%s", e.what()));
194         return false;
195     }
196 
197     const auto& currentStateStr = std::get<std::string>(currentState);
198     return currentStateStr == ACTIVE_STATE ||
199            currentStateStr == ACTIVATING_STATE;
200 }
201 
202 int Chassis::sysStateChange(sdbusplus::message::message& msg)
203 {
204     sdbusplus::message::object_path newStateObjPath;
205     std::string newStateUnit{};
206     std::string newStateResult{};
207 
208     // Read the msg and populate each variable
209     try
210     {
211         // newStateID is a throwaway that is needed in order to read the
212         // parameters that are useful out of the dbus message
213         uint32_t newStateID{};
214         msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
215     }
216     catch (const SdBusError& e)
217     {
218         log<level::ERR>("Error in state change - bad encoding",
219                         entry("ERROR=%s", e.what()),
220                         entry("REPLY_SIG=%s", msg.get_signature()));
221         return 0;
222     }
223 
224     if ((newStateUnit == CHASSIS_STATE_POWEROFF_TGT) &&
225         (newStateResult == "done") && (!stateActive(CHASSIS_STATE_POWERON_TGT)))
226     {
227         log<level::INFO>("Received signal that power OFF is complete");
228         this->currentPowerState(server::Chassis::PowerState::Off);
229         this->setStateChangeTime();
230     }
231     else if ((newStateUnit == CHASSIS_STATE_POWERON_TGT) &&
232              (newStateResult == "done") &&
233              (stateActive(CHASSIS_STATE_POWERON_TGT)))
234     {
235         log<level::INFO>("Received signal that power ON is complete");
236         this->currentPowerState(server::Chassis::PowerState::On);
237         this->setStateChangeTime();
238     }
239 
240     return 0;
241 }
242 
243 Chassis::Transition Chassis::requestedPowerTransition(Transition value)
244 {
245 
246     log<level::INFO>("Change to Chassis Requested Power State",
247                      entry("CHASSIS_REQUESTED_POWER_STATE=%s",
248                            convertForMessage(value).c_str()));
249     executeTransition(value);
250     return server::Chassis::requestedPowerTransition(value);
251 }
252 
253 Chassis::PowerState Chassis::currentPowerState(PowerState value)
254 {
255     PowerState chassisPowerState;
256     log<level::INFO>("Change to Chassis Power State",
257                      entry("CHASSIS_CURRENT_POWER_STATE=%s",
258                            convertForMessage(value).c_str()));
259 
260     chassisPowerState = server::Chassis::currentPowerState(value);
261     pOHTimer.setEnabled(chassisPowerState == PowerState::On);
262     return chassisPowerState;
263 }
264 
265 uint32_t Chassis::pOHCounter(uint32_t value)
266 {
267     if (value != pOHCounter())
268     {
269         ChassisInherit::pOHCounter(value);
270         serializePOH();
271     }
272     return pOHCounter();
273 }
274 
275 void Chassis::pOHCallback()
276 {
277     if (ChassisInherit::currentPowerState() == PowerState::On)
278     {
279         pOHCounter(pOHCounter() + 1);
280     }
281 }
282 
283 void Chassis::restorePOHCounter()
284 {
285     uint32_t counter;
286     if (!deserializePOH(POH_COUNTER_PERSIST_PATH, counter))
287     {
288         // set to default value
289         pOHCounter(0);
290     }
291     else
292     {
293         pOHCounter(counter);
294     }
295 }
296 
297 fs::path Chassis::serializePOH(const fs::path& path)
298 {
299     std::ofstream os(path.c_str(), std::ios::binary);
300     cereal::JSONOutputArchive oarchive(os);
301     oarchive(pOHCounter());
302     return path;
303 }
304 
305 bool Chassis::deserializePOH(const fs::path& path, uint32_t& pOHCounter)
306 {
307     try
308     {
309         if (fs::exists(path))
310         {
311             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
312             cereal::JSONInputArchive iarchive(is);
313             iarchive(pOHCounter);
314             return true;
315         }
316         return false;
317     }
318     catch (cereal::Exception& e)
319     {
320         log<level::ERR>(e.what());
321         fs::remove(path);
322         return false;
323     }
324     catch (const fs::filesystem_error& e)
325     {
326         return false;
327     }
328 
329     return false;
330 }
331 
332 void Chassis::startPOHCounter()
333 {
334     auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path();
335     fs::create_directories(dir);
336 
337     try
338     {
339         auto event = sdeventplus::Event::get_default();
340         bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
341         event.loop();
342     }
343     catch (const sdeventplus::SdEventError& e)
344     {
345         log<level::ERR>("Error occurred during the sdeventplus loop",
346                         entry("ERROR=%s", e.what()));
347         phosphor::logging::commit<InternalFailure>();
348     }
349 }
350 
351 void Chassis::serializeStateChangeTime()
352 {
353     fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
354     std::ofstream os(path.c_str(), std::ios::binary);
355     cereal::JSONOutputArchive oarchive(os);
356 
357     oarchive(ChassisInherit::lastStateChangeTime(),
358              ChassisInherit::currentPowerState());
359 }
360 
361 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state)
362 {
363     fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
364 
365     try
366     {
367         if (fs::exists(path))
368         {
369             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
370             cereal::JSONInputArchive iarchive(is);
371             iarchive(time, state);
372             return true;
373         }
374     }
375     catch (std::exception& e)
376     {
377         log<level::ERR>(e.what());
378         fs::remove(path);
379     }
380 
381     return false;
382 }
383 
384 void Chassis::restoreChassisStateChangeTime()
385 {
386     uint64_t time;
387     PowerState state;
388 
389     if (!deserializeStateChangeTime(time, state))
390     {
391         ChassisInherit::lastStateChangeTime(0);
392     }
393     else
394     {
395         ChassisInherit::lastStateChangeTime(time);
396     }
397 }
398 
399 void Chassis::setStateChangeTime()
400 {
401     using namespace std::chrono;
402     uint64_t lastTime;
403     PowerState lastState;
404 
405     auto now =
406         duration_cast<milliseconds>(system_clock::now().time_since_epoch())
407             .count();
408 
409     // If power is on when the BMC is rebooted, this function will get called
410     // because sysStateChange() runs.  Since the power state didn't change
411     // in this case, neither should the state change time, so check that
412     // the power state actually did change here.
413     if (deserializeStateChangeTime(lastTime, lastState))
414     {
415         if (lastState == ChassisInherit::currentPowerState())
416         {
417             return;
418         }
419     }
420 
421     ChassisInherit::lastStateChangeTime(now);
422     serializeStateChangeTime();
423 }
424 
425 } // namespace manager
426 } // namespace state
427 } // namespace phosphor
428