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 <gpiod.h>
9 
10 #include <cereal/archives/json.hpp>
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <phosphor-logging/lg2.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 PHOSPHOR_LOG2_USING;
29 
30 // When you see server:: you know we're referencing our base class
31 namespace server = sdbusplus::xyz::openbmc_project::State::server;
32 
33 using namespace phosphor::logging;
34 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
35 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Blackout;
36 using sdbusplus::xyz::openbmc_project::State::Shutdown::Power::Error::Regulator;
37 constexpr auto CHASSIS_STATE_POWEROFF_TGT = "obmc-chassis-poweroff@0.target";
38 constexpr auto CHASSIS_STATE_HARD_POWEROFF_TGT =
39     "obmc-chassis-hard-poweroff@0.target";
40 constexpr auto CHASSIS_STATE_POWERON_TGT = "obmc-chassis-poweron@0.target";
41 
42 constexpr auto ACTIVE_STATE = "active";
43 constexpr auto ACTIVATING_STATE = "activating";
44 
45 /* Map a transition to it's systemd target */
46 const std::map<server::Chassis::Transition, std::string> SYSTEMD_TARGET_TABLE =
47     {
48         // Use the hard off target to ensure we shutdown immediately
49         {server::Chassis::Transition::Off, CHASSIS_STATE_HARD_POWEROFF_TGT},
50         {server::Chassis::Transition::On, CHASSIS_STATE_POWERON_TGT}};
51 
52 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
53 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
54 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
55 
56 constexpr auto SYSTEMD_PROPERTY_IFACE = "org.freedesktop.DBus.Properties";
57 constexpr auto SYSTEMD_INTERFACE_UNIT = "org.freedesktop.systemd1.Unit";
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::exception& e)
68     {
69         error("Failed to subscribe to systemd signals: {ERROR}", "ERROR", e);
70         elog<InternalFailure>();
71     }
72 
73     return;
74 }
75 
76 // TODO - Will be rewritten once sdbusplus client bindings are in place
77 //        and persistent storage design is in place and sdbusplus
78 //        has read property function
79 void Chassis::determineInitialState()
80 {
81     std::variant<int> pgood = -1;
82     auto method = this->bus.new_method_call(
83         "org.openbmc.control.Power", "/org/openbmc/control/power0",
84         "org.freedesktop.DBus.Properties", "Get");
85 
86     method.append("org.openbmc.control.Power", "pgood");
87     try
88     {
89         auto reply = this->bus.call(method);
90         reply.read(pgood);
91 
92         if (std::get<int>(pgood) == 1)
93         {
94             info("Initial Chassis State will be On");
95             server::Chassis::currentPowerState(PowerState::On);
96             server::Chassis::requestedPowerTransition(Transition::On);
97             return;
98         }
99         else
100         {
101             // The system is off.  If we think it should be on then
102             // we probably lost AC while up, so set a new state
103             // change time.
104             uint64_t lastTime;
105             PowerState lastState;
106 
107             if (deserializeStateChangeTime(lastTime, lastState))
108             {
109                 if (lastState == PowerState::On)
110                 {
111                     if (standbyVoltageRegulatorFault())
112                     {
113                         report<Regulator>();
114                     }
115                     else
116                     {
117                         report<Blackout>();
118                     }
119 
120                     setStateChangeTime();
121                 }
122             }
123         }
124     }
125     catch (const sdbusplus::exception::exception& e)
126     {
127         // It's acceptable for the pgood state service to not be available
128         // since it will notify us of the pgood state when it comes up.
129         if (e.name() != nullptr &&
130             strcmp("org.freedesktop.DBus.Error.ServiceUnknown", e.name()) == 0)
131         {
132             goto fail;
133         }
134 
135         // Only log for unexpected error types.
136         error("Error performing call to get pgood: {ERROR}", "ERROR", e);
137         goto fail;
138     }
139 
140 fail:
141     info("Initial Chassis State will be Off");
142     server::Chassis::currentPowerState(PowerState::Off);
143     server::Chassis::requestedPowerTransition(Transition::Off);
144 
145     return;
146 }
147 
148 void Chassis::executeTransition(Transition tranReq)
149 {
150     auto sysdTarget = SYSTEMD_TARGET_TABLE.find(tranReq)->second;
151 
152     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
153                                             SYSTEMD_INTERFACE, "StartUnit");
154 
155     method.append(sysdTarget);
156     method.append("replace");
157 
158     this->bus.call_noreply(method);
159 
160     return;
161 }
162 
163 bool Chassis::stateActive(const std::string& target)
164 {
165     std::variant<std::string> currentState;
166     sdbusplus::message::object_path unitTargetPath;
167 
168     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
169                                             SYSTEMD_INTERFACE, "GetUnit");
170 
171     method.append(target);
172 
173     try
174     {
175         auto result = this->bus.call(method);
176         result.read(unitTargetPath);
177     }
178     catch (const sdbusplus::exception::exception& e)
179     {
180         error("Error in GetUnit call: {ERROR}", "ERROR", e);
181         return false;
182     }
183 
184     method = this->bus.new_method_call(
185         SYSTEMD_SERVICE,
186         static_cast<const std::string&>(unitTargetPath).c_str(),
187         SYSTEMD_PROPERTY_IFACE, "Get");
188 
189     method.append(SYSTEMD_INTERFACE_UNIT, "ActiveState");
190 
191     try
192     {
193         auto result = this->bus.call(method);
194         result.read(currentState);
195     }
196     catch (const sdbusplus::exception::exception& e)
197     {
198         error("Error in ActiveState Get: {ERROR}", "ERROR", e);
199         return false;
200     }
201 
202     const auto& currentStateStr = std::get<std::string>(currentState);
203     return currentStateStr == ACTIVE_STATE ||
204            currentStateStr == ACTIVATING_STATE;
205 }
206 
207 int Chassis::sysStateChange(sdbusplus::message::message& msg)
208 {
209     sdbusplus::message::object_path newStateObjPath;
210     std::string newStateUnit{};
211     std::string newStateResult{};
212 
213     // Read the msg and populate each variable
214     try
215     {
216         // newStateID is a throwaway that is needed in order to read the
217         // parameters that are useful out of the dbus message
218         uint32_t newStateID{};
219         msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
220     }
221     catch (const sdbusplus::exception::exception& e)
222     {
223         error("Error in state change - bad encoding: {ERROR} {REPLY_SIG}",
224               "ERROR", e, "REPLY_SIG", 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         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         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     info("Change to Chassis Requested Power State: {REQ_POWER_TRAN}",
265          "REQ_POWER_TRAN", value);
266     executeTransition(value);
267     return server::Chassis::requestedPowerTransition(value);
268 }
269 
270 Chassis::PowerState Chassis::currentPowerState(PowerState value)
271 {
272     PowerState chassisPowerState;
273     info("Change to Chassis Power State: {CUR_POWER_STATE}", "CUR_POWER_STATE",
274          value);
275 
276     chassisPowerState = server::Chassis::currentPowerState(value);
277     pohTimer.setEnabled(chassisPowerState == PowerState::On);
278     return chassisPowerState;
279 }
280 
281 uint32_t Chassis::pohCounter(uint32_t value)
282 {
283     if (value != pohCounter())
284     {
285         ChassisInherit::pohCounter(value);
286         serializePOH();
287     }
288     return pohCounter();
289 }
290 
291 void Chassis::pohCallback()
292 {
293     if (ChassisInherit::currentPowerState() == PowerState::On)
294     {
295         pohCounter(pohCounter() + 1);
296     }
297 }
298 
299 void Chassis::restorePOHCounter()
300 {
301     uint32_t counter;
302     if (!deserializePOH(POH_COUNTER_PERSIST_PATH, counter))
303     {
304         // set to default value
305         pohCounter(0);
306     }
307     else
308     {
309         pohCounter(counter);
310     }
311 }
312 
313 fs::path Chassis::serializePOH(const fs::path& path)
314 {
315     std::ofstream os(path.c_str(), std::ios::binary);
316     cereal::JSONOutputArchive oarchive(os);
317     oarchive(pohCounter());
318     return path;
319 }
320 
321 bool Chassis::deserializePOH(const fs::path& path, uint32_t& pohCounter)
322 {
323     try
324     {
325         if (fs::exists(path))
326         {
327             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
328             cereal::JSONInputArchive iarchive(is);
329             iarchive(pohCounter);
330             return true;
331         }
332         return false;
333     }
334     catch (const cereal::Exception& e)
335     {
336         error("deserialize exception: {ERROR}", "ERROR", e);
337         fs::remove(path);
338         return false;
339     }
340     catch (const fs::filesystem_error& e)
341     {
342         return false;
343     }
344 
345     return false;
346 }
347 
348 void Chassis::startPOHCounter()
349 {
350     auto dir = fs::path(POH_COUNTER_PERSIST_PATH).parent_path();
351     fs::create_directories(dir);
352 
353     try
354     {
355         auto event = sdeventplus::Event::get_default();
356         bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
357         event.loop();
358     }
359     catch (const sdeventplus::SdEventError& e)
360     {
361         error("Error occurred during the sdeventplus loop: {ERROR}", "ERROR",
362               e);
363         phosphor::logging::commit<InternalFailure>();
364     }
365 }
366 
367 void Chassis::serializeStateChangeTime()
368 {
369     fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
370     std::ofstream os(path.c_str(), std::ios::binary);
371     cereal::JSONOutputArchive oarchive(os);
372 
373     oarchive(ChassisInherit::lastStateChangeTime(),
374              ChassisInherit::currentPowerState());
375 }
376 
377 bool Chassis::deserializeStateChangeTime(uint64_t& time, PowerState& state)
378 {
379     fs::path path{CHASSIS_STATE_CHANGE_PERSIST_PATH};
380 
381     try
382     {
383         if (fs::exists(path))
384         {
385             std::ifstream is(path.c_str(), std::ios::in | std::ios::binary);
386             cereal::JSONInputArchive iarchive(is);
387             iarchive(time, state);
388             return true;
389         }
390     }
391     catch (const std::exception& e)
392     {
393         error("deserialize exception: {ERROR}", "ERROR", e);
394         fs::remove(path);
395     }
396 
397     return false;
398 }
399 
400 void Chassis::restoreChassisStateChangeTime()
401 {
402     uint64_t time;
403     PowerState state;
404 
405     if (!deserializeStateChangeTime(time, state))
406     {
407         ChassisInherit::lastStateChangeTime(0);
408     }
409     else
410     {
411         ChassisInherit::lastStateChangeTime(time);
412     }
413 }
414 
415 void Chassis::setStateChangeTime()
416 {
417     using namespace std::chrono;
418     uint64_t lastTime;
419     PowerState lastState;
420 
421     auto now =
422         duration_cast<milliseconds>(system_clock::now().time_since_epoch())
423             .count();
424 
425     // If power is on when the BMC is rebooted, this function will get called
426     // because sysStateChange() runs.  Since the power state didn't change
427     // in this case, neither should the state change time, so check that
428     // the power state actually did change here.
429     if (deserializeStateChangeTime(lastTime, lastState))
430     {
431         if (lastState == ChassisInherit::currentPowerState())
432         {
433             return;
434         }
435     }
436 
437     ChassisInherit::lastStateChangeTime(now);
438     serializeStateChangeTime();
439 }
440 
441 bool Chassis::standbyVoltageRegulatorFault()
442 {
443     bool regulatorFault = false;
444 
445     // find standby voltage regulator fault via gpio
446     gpiod_line* line = gpiod_line_find("regulator-standby-faulted");
447 
448     if (nullptr != line)
449     {
450         // take ownership of gpio
451         if (0 != gpiod_line_request_input(line, "chassis"))
452         {
453             error("Failed request for regulator-standby-faulted GPIO");
454         }
455         else
456         {
457             // get gpio value
458             auto gpioval = gpiod_line_get_value(line);
459 
460             // release ownership of gpio
461             gpiod_line_close_chip(line);
462 
463             if (-1 == gpioval)
464             {
465                 error("Failed reading regulator-standby-faulted GPIO");
466             }
467 
468             if (1 == gpioval)
469             {
470                 info("Detected standby voltage regulator fault");
471                 regulatorFault = true;
472             }
473         }
474     }
475     return regulatorFault;
476 }
477 
478 } // namespace manager
479 } // namespace state
480 } // namespace phosphor
481