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