1 #include "bmc_state_manager.hpp"
2 
3 #include "utils.hpp"
4 #include "xyz/openbmc_project/Common/error.hpp"
5 
6 #include <gpiod.h>
7 #include <sys/sysinfo.h>
8 
9 #include <phosphor-logging/elog-errors.hpp>
10 #include <phosphor-logging/lg2.hpp>
11 #include <sdbusplus/exception.hpp>
12 
13 #include <cassert>
14 #include <filesystem>
15 #include <fstream>
16 #include <iostream>
17 
18 namespace phosphor
19 {
20 namespace state
21 {
22 namespace manager
23 {
24 
25 PHOSPHOR_LOG2_USING;
26 
27 // When you see server:: you know we're referencing our base class
28 namespace server = sdbusplus::xyz::openbmc_project::State::server;
29 
30 using namespace phosphor::logging;
31 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
32 
33 constexpr auto obmcStandbyTarget = "multi-user.target";
34 constexpr auto signalDone = "done";
35 constexpr auto activeState = "active";
36 
37 /* Map a transition to it's systemd target */
38 const std::map<server::BMC::Transition, const char*> SYSTEMD_TABLE = {
39     {server::BMC::Transition::Reboot, "reboot.target"}};
40 
41 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
42 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
43 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
44 constexpr auto SYSTEMD_PRP_INTERFACE = "org.freedesktop.DBus.Properties";
45 
46 void BMC::discoverInitialState()
47 {
48     std::variant<std::string> currentState;
49     sdbusplus::message::object_path unitTargetPath;
50 
51     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
52                                             SYSTEMD_INTERFACE, "GetUnit");
53 
54     method.append(obmcStandbyTarget);
55 
56     try
57     {
58         auto result = this->bus.call(method);
59         result.read(unitTargetPath);
60     }
61     catch (const sdbusplus::exception::exception& e)
62     {
63         error("Error in GetUnit call: {ERROR}", "ERROR", e);
64         return;
65     }
66 
67     method = this->bus.new_method_call(
68         SYSTEMD_SERVICE,
69         static_cast<const std::string&>(unitTargetPath).c_str(),
70         SYSTEMD_PRP_INTERFACE, "Get");
71 
72     method.append("org.freedesktop.systemd1.Unit", "ActiveState");
73 
74     try
75     {
76         auto result = this->bus.call(method);
77 
78         // Is obmc-standby.target active or inactive?
79         result.read(currentState);
80     }
81     catch (const sdbusplus::exception::exception& e)
82     {
83         info("Error in ActiveState Get: {ERROR}", "ERROR", e);
84         return;
85     }
86 
87     auto currentStateStr = std::get<std::string>(currentState);
88     if (currentStateStr == activeState)
89     {
90         info("Setting the BMCState field to BMC_READY");
91         this->currentBMCState(BMCState::Ready);
92 
93         // Unsubscribe so we stop processing all other signals
94         method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
95                                            SYSTEMD_INTERFACE, "Unsubscribe");
96         try
97         {
98             this->bus.call(method);
99             this->stateSignal.release();
100         }
101         catch (const sdbusplus::exception::exception& e)
102         {
103             info("Error in Unsubscribe: {ERROR}", "ERROR", e);
104         }
105     }
106     else
107     {
108         info("Setting the BMCState field to BMC_NOTREADY");
109         this->currentBMCState(BMCState::NotReady);
110     }
111 
112     return;
113 }
114 
115 void BMC::subscribeToSystemdSignals()
116 {
117     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
118                                             SYSTEMD_INTERFACE, "Subscribe");
119 
120     try
121     {
122         this->bus.call(method);
123     }
124     catch (const sdbusplus::exception::exception& e)
125     {
126         error("Failed to subscribe to systemd signals: {ERROR}", "ERROR", e);
127         elog<InternalFailure>();
128     }
129 
130     return;
131 }
132 
133 void BMC::executeTransition(const Transition tranReq)
134 {
135     // HardReboot does not shutdown any services and immediately transitions
136     // into the reboot process
137     if (server::BMC::Transition::HardReboot == tranReq)
138     {
139         auto method = this->bus.new_method_call(
140             SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Reboot");
141         try
142         {
143             this->bus.call(method);
144         }
145         catch (const sdbusplus::exception::exception& e)
146         {
147             info("Error in HardReboot: {ERROR}", "ERROR", e);
148         }
149     }
150     else
151     {
152         // Check to make sure it can be found
153         auto iter = SYSTEMD_TABLE.find(tranReq);
154         if (iter == SYSTEMD_TABLE.end())
155             return;
156 
157         const auto& sysdUnit = iter->second;
158 
159         auto method = this->bus.new_method_call(
160             SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "StartUnit");
161         // The only valid transition is reboot and that
162         // needs to be irreversible once started
163 
164         method.append(sysdUnit, "replace-irreversibly");
165 
166         try
167         {
168             this->bus.call(method);
169         }
170         catch (const sdbusplus::exception::exception& e)
171         {
172             info("Error in StartUnit - replace-irreversibly: {ERROR}", "ERROR",
173                  e);
174         }
175     }
176     return;
177 }
178 
179 int BMC::bmcStateChange(sdbusplus::message::message& msg)
180 {
181     uint32_t newStateID{};
182     sdbusplus::message::object_path newStateObjPath;
183     std::string newStateUnit{};
184     std::string newStateResult{};
185 
186     // Read the msg and populate each variable
187     msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
188 
189     // Caught the signal that indicates the BMC is now BMC_READY
190     if ((newStateUnit == obmcStandbyTarget) && (newStateResult == signalDone))
191     {
192         info("BMC_READY");
193         this->currentBMCState(BMCState::Ready);
194 
195         // Unsubscribe so we stop processing all other signals
196         auto method =
197             this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
198                                       SYSTEMD_INTERFACE, "Unsubscribe");
199 
200         try
201         {
202             this->bus.call(method);
203             this->stateSignal.release();
204         }
205         catch (const sdbusplus::exception::exception& e)
206         {
207             info("Error in Unsubscribe: {ERROR}", "ERROR", e);
208         }
209     }
210 
211     return 0;
212 }
213 
214 BMC::Transition BMC::requestedBMCTransition(Transition value)
215 {
216     info("Setting the RequestedBMCTransition field to "
217          "{REQUESTED_BMC_TRANSITION}",
218          "REQUESTED_BMC_TRANSITION", value);
219 
220     executeTransition(value);
221     return server::BMC::requestedBMCTransition(value);
222 }
223 
224 BMC::BMCState BMC::currentBMCState(BMCState value)
225 {
226 
227     info("Setting the BMCState field to {CURRENT_BMC_STATE}",
228          "CURRENT_BMC_STATE", value);
229 
230     return server::BMC::currentBMCState(value);
231 }
232 
233 BMC::RebootCause BMC::lastRebootCause(RebootCause value)
234 {
235     info("Setting the RebootCause field to {LAST_REBOOT_CAUSE}",
236          "LAST_REBOOT_CAUSE", value);
237 
238     return server::BMC::lastRebootCause(value);
239 }
240 
241 uint64_t BMC::lastRebootTime() const
242 {
243     using namespace std::chrono;
244     struct sysinfo info;
245 
246     auto rc = sysinfo(&info);
247     assert(rc == 0);
248 
249     // Since uptime is in seconds, also get the current time in seconds.
250     auto now = time_point_cast<seconds>(system_clock::now());
251     auto rebootTime = now - seconds(info.uptime);
252 
253     return duration_cast<milliseconds>(rebootTime.time_since_epoch()).count();
254 }
255 
256 void BMC::discoverLastRebootCause()
257 {
258     uint64_t bootReason = 0;
259     std::ifstream file;
260     auto bootstatusPath = "/sys/class/watchdog/watchdog0/bootstatus";
261 
262     file.exceptions(std::ifstream::failbit | std::ifstream::badbit |
263                     std::ifstream::eofbit);
264 
265     try
266     {
267         file.open(bootstatusPath);
268         file >> bootReason;
269     }
270     catch (const std::exception& e)
271     {
272         auto rc = errno;
273         error("Failed to read sysfs file {FILE} with errno {ERRNO}", "FILE",
274               bootstatusPath, "ERRNO", rc);
275     }
276 
277     switch (bootReason)
278     {
279         case WDIOF_EXTERN1:
280             this->lastRebootCause(RebootCause::Watchdog);
281             return;
282         case WDIOF_CARDRESET:
283             this->lastRebootCause(RebootCause::POR);
284             return;
285         default:
286             this->lastRebootCause(RebootCause::Unknown);
287             // Continue below to see if more details can be found
288             // on reason for reboot
289             break;
290     }
291 
292     // If the above code could not detect a reason, look for a the
293     // reset-cause-pinhole gpio to see if it is the reason for the reboot
294     auto gpioval =
295         phosphor::state::manager::utils::getGpioValue("reset-cause-pinhole");
296 
297     if (1 == gpioval)
298     {
299         info("The BMC reset was caused by a pinhole reset");
300         this->lastRebootCause(RebootCause::PinholeReset);
301 
302         // Generate log telling user a pinhole reset has occurred
303         const std::string errorMsg = "xyz.openbmc_project.State.PinholeReset";
304         phosphor::state::manager::utils::createError(
305             this->bus, errorMsg,
306             sdbusplus::xyz::openbmc_project::Logging::server::Entry::Level::
307                 Notice);
308     }
309 
310     return;
311 }
312 
313 } // namespace manager
314 } // namespace state
315 } // namespace phosphor
316