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     sdbusplus::message::object_path newStateObjPath;
202     std::string newStateUnit{};
203     std::string newStateResult{};
204 
205     // Read the msg and populate each variable
206     try
207     {
208         // newStateID is a throwaway that is needed in order to read the
209         // parameters that are useful out of the dbus message
210         uint32_t newStateID{};
211         msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
212     }
213     catch (const SdBusError& e)
214     {
215         log<level::ERR>("Error in state change - bad encoding",
216                         entry("ERROR=%s", e.what()),
217                         entry("REPLY_SIG=%s", msg.get_signature()));
218         return 0;
219     }
220 
221     if ((newStateUnit == CHASSIS_STATE_POWEROFF_TGT) &&
222         (newStateResult == "done") && (!stateActive(CHASSIS_STATE_POWERON_TGT)))
223     {
224         log<level::INFO>("Received signal that power OFF is complete");
225         this->currentPowerState(server::Chassis::PowerState::Off);
226         this->setStateChangeTime();
227     }
228     else if ((newStateUnit == CHASSIS_STATE_POWERON_TGT) &&
229              (newStateResult == "done") &&
230              (stateActive(CHASSIS_STATE_POWERON_TGT)))
231     {
232         log<level::INFO>("Received signal that power ON is complete");
233         this->currentPowerState(server::Chassis::PowerState::On);
234         this->setStateChangeTime();
235     }
236 
237     return 0;
238 }
239 
240 Chassis::Transition Chassis::requestedPowerTransition(Transition value)
241 {
242 
243     log<level::INFO>("Change to Chassis Requested Power State",
244                      entry("CHASSIS_REQUESTED_POWER_STATE=%s",
245                            convertForMessage(value).c_str()));
246     executeTransition(value);
247     return server::Chassis::requestedPowerTransition(value);
248 }
249 
250 Chassis::PowerState Chassis::currentPowerState(PowerState value)
251 {
252     PowerState chassisPowerState;
253     log<level::INFO>("Change to Chassis Power State",
254                      entry("CHASSIS_CURRENT_POWER_STATE=%s",
255                            convertForMessage(value).c_str()));
256 
257     chassisPowerState = server::Chassis::currentPowerState(value);
258     pOHTimer.setEnabled(chassisPowerState == PowerState::On);
259     return chassisPowerState;
260 }
261 
262 uint32_t Chassis::pOHCounter(uint32_t value)
263 {
264     if (value != pOHCounter())
265     {
266         ChassisInherit::pOHCounter(value);
267         serializePOH();
268     }
269     return pOHCounter();
270 }
271 
272 void Chassis::pOHCallback()
273 {
274     if (ChassisInherit::currentPowerState() == PowerState::On)
275     {
276         pOHCounter(pOHCounter() + 1);
277     }
278 }
279 
280 void Chassis::restorePOHCounter()
281 {
282     uint32_t counter;
283     if (!deserializePOH(POH_COUNTER_PERSIST_PATH, counter))
284     {
285         // set to default value
286         pOHCounter(0);
287     }
288     else
289     {
290         pOHCounter(counter);
291     }
292 }
293 
294 fs::path Chassis::serializePOH(const fs::path& path)
295 {
296     std::ofstream os(path.c_str(), std::ios::binary);
297     cereal::JSONOutputArchive oarchive(os);
298     oarchive(pOHCounter());
299     return path;
300 }
301 
302 bool Chassis::deserializePOH(const fs::path& path, uint32_t& pOHCounter)
303 {
304     try
305     {
306         if (fs::exists(path))
307         {
308             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
309             cereal::JSONInputArchive iarchive(is);
310             iarchive(pOHCounter);
311             return true;
312         }
313         return false;
314     }
315     catch (cereal::Exception& e)
316     {
317         log<level::ERR>(e.what());
318         fs::remove(path);
319         return false;
320     }
321     catch (const fs::filesystem_error& e)
322     {
323         return false;
324     }
325 
326     return false;
327 }
328 
329 void Chassis::startPOHCounter()
330 {
331     auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path();
332     fs::create_directories(dir);
333 
334     try
335     {
336         auto event = sdeventplus::Event::get_default();
337         bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
338         event.loop();
339     }
340     catch (const sdeventplus::SdEventError& e)
341     {
342         log<level::ERR>("Error occurred during the sdeventplus loop",
343                         entry("ERROR=%s", e.what()));
344         phosphor::logging::commit<InternalFailure>();
345     }
346 }
347 
348 void Chassis::serializeStateChangeTime()
349 {
350     fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
351     std::ofstream os(path.c_str(), std::ios::binary);
352     cereal::JSONOutputArchive oarchive(os);
353 
354     oarchive(ChassisInherit::lastStateChangeTime(),
355              ChassisInherit::currentPowerState());
356 }
357 
358 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state)
359 {
360     fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
361 
362     try
363     {
364         if (fs::exists(path))
365         {
366             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
367             cereal::JSONInputArchive iarchive(is);
368             iarchive(time, state);
369             return true;
370         }
371     }
372     catch (std::exception& e)
373     {
374         log<level::ERR>(e.what());
375         fs::remove(path);
376     }
377 
378     return false;
379 }
380 
381 void Chassis::restoreChassisStateChangeTime()
382 {
383     uint64_t time;
384     PowerState state;
385 
386     if (!deserializeStateChangeTime(time, state))
387     {
388         ChassisInherit::lastStateChangeTime(0);
389     }
390     else
391     {
392         ChassisInherit::lastStateChangeTime(time);
393     }
394 }
395 
396 void Chassis::setStateChangeTime()
397 {
398     using namespace std::chrono;
399     uint64_t lastTime;
400     PowerState lastState;
401 
402     auto now =
403         duration_cast<milliseconds>(system_clock::now().time_since_epoch())
404             .count();
405 
406     // If power is on when the BMC is rebooted, this function will get called
407     // because sysStateChange() runs.  Since the power state didn't change
408     // in this case, neither should the state change time, so check that
409     // the power state actually did change here.
410     if (deserializeStateChangeTime(lastTime, lastState))
411     {
412         if (lastState == ChassisInherit::currentPowerState())
413         {
414             return;
415         }
416     }
417 
418     ChassisInherit::lastStateChangeTime(now);
419     serializeStateChangeTime();
420 }
421 
422 } // namespace manager
423 } // namespace state
424 } // namespace phosphor
425