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