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