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