xref: /openbmc/phosphor-state-manager/bmc_state_manager.cpp (revision 9f38152abf80aba2f0177cb55729ff107f54947d)
1 #include "config.h"
2 
3 #include "bmc_state_manager.hpp"
4 
5 #include "utils.hpp"
6 #include "xyz/openbmc_project/Common/error.hpp"
7 
8 #include <gpiod.h>
9 
10 #include <phosphor-logging/elog-errors.hpp>
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/exception.hpp>
13 
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::server::xyz::openbmc_project::state;
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 
getUnitState(const std::string & unitToCheck)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_t& 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_t& 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 
discoverInitialState()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 
executeTransition(const Transition tranReq)119 void BMC::executeTransition(const Transition tranReq)
120 {
121     // HardReboot does not shutdown any services and immediately transitions
122     // into the reboot process
123     if (server::BMC::Transition::HardReboot == tranReq)
124     {
125         // Put BMC state not NotReady when issuing a BMC reboot
126         // and stop monitoring for state changes
127         this->currentBMCState(BMCState::NotReady);
128         this->stateSignal.reset();
129 
130         auto method = this->bus.new_method_call(
131             SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Reboot");
132         try
133         {
134             this->bus.call(method);
135         }
136         catch (const sdbusplus::exception_t& e)
137         {
138             info("Error in HardReboot: {ERROR}", "ERROR", e);
139         }
140     }
141     else
142     {
143         // Check to make sure it can be found
144         auto iter = SYSTEMD_TABLE.find(tranReq);
145         if (iter == SYSTEMD_TABLE.end())
146         {
147             return;
148         }
149 
150         const auto& sysdUnit = iter->second;
151 
152         auto method = this->bus.new_method_call(
153             SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "StartUnit");
154         // The only valid transition is reboot and that
155         // needs to be irreversible once started
156 
157         method.append(sysdUnit, "replace-irreversibly");
158 
159         // Put BMC state not NotReady when issuing a BMC reboot
160         // and stop monitoring for state changes
161         this->currentBMCState(BMCState::NotReady);
162         this->stateSignal.reset();
163 
164         try
165         {
166             this->bus.call(method);
167         }
168         catch (const sdbusplus::exception_t& e)
169         {
170             info("Error in StartUnit - replace-irreversibly: {ERROR}", "ERROR",
171                  e);
172         }
173     }
174     return;
175 }
176 
bmcStateChange(sdbusplus::message_t & msg)177 int BMC::bmcStateChange(sdbusplus::message_t& msg)
178 {
179     uint32_t newStateID{};
180     sdbusplus::message::object_path newStateObjPath;
181     std::string newStateUnit{};
182     std::string newStateResult{};
183 
184     // Read the msg and populate each variable
185     msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
186 
187     if ((newStateUnit == obmcQuiesceTarget) && (newStateResult == signalDone))
188     {
189         error("BMC has entered BMC_QUIESCED state");
190         this->currentBMCState(BMCState::Quiesced);
191 
192         // There is no getting out of Quiesced once entered (other then BMC
193         // reboot) so stop watching for signals
194         auto method =
195             this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
196                                       SYSTEMD_INTERFACE, "Unsubscribe");
197 
198         try
199         {
200             this->bus.call(method);
201         }
202         catch (const sdbusplus::exception_t& e)
203         {
204             info("Error in Unsubscribe: {ERROR}", "ERROR", e);
205         }
206 
207         // disable the system state change object as well
208         this->stateSignal.reset();
209 
210         return 0;
211     }
212 
213     // Caught the signal that indicates the BMC is now BMC_READY
214     if ((newStateUnit == obmcStandbyTarget) && (newStateResult == signalDone))
215     {
216         info("BMC_READY");
217         this->currentBMCState(BMCState::Ready);
218     }
219 
220     return 0;
221 }
222 
requestedBMCTransition(Transition value)223 BMC::Transition BMC::requestedBMCTransition(Transition value)
224 {
225     info("Setting the RequestedBMCTransition field to "
226          "{REQUESTED_BMC_TRANSITION}",
227          "REQUESTED_BMC_TRANSITION", value);
228 
229 #ifdef CHECK_FWUPDATE_BEFORE_DO_TRANSITION
230     /*
231      * Do not do transition when the any firmware being updated
232      */
233     if ((server::BMC::Transition::Reboot == value) &&
234         (phosphor::state::manager::utils::isFirmwareUpdating(this->bus)))
235     {
236         info("Firmware being updated, reject the transition request");
237         throw sdbusplus::xyz::openbmc_project::Common::Error::Unavailable();
238     }
239 #endif // CHECK_FWUPDATE_BEFORE_DO_TRANSITION
240 
241     executeTransition(value);
242     return server::BMC::requestedBMCTransition(value);
243 }
244 
currentBMCState(BMCState value)245 BMC::BMCState BMC::currentBMCState(BMCState value)
246 {
247     info("Setting the BMCState field to {CURRENT_BMC_STATE}",
248          "CURRENT_BMC_STATE", value);
249 
250     return server::BMC::currentBMCState(value);
251 }
252 
lastRebootCause(RebootCause value)253 BMC::RebootCause BMC::lastRebootCause(RebootCause value)
254 {
255     info("Setting the RebootCause field to {LAST_REBOOT_CAUSE}",
256          "LAST_REBOOT_CAUSE", value);
257 
258     return server::BMC::lastRebootCause(value);
259 }
260 
updateLastRebootTime()261 void BMC::updateLastRebootTime()
262 {
263     using namespace std::chrono;
264     struct sysinfo info;
265 
266     auto rc = sysinfo(&info);
267     assert(rc == 0);
268     // Since uptime is in seconds, also get the current time in seconds.
269     auto now = time_point_cast<seconds>(system_clock::now());
270     auto rebootTimeTs = now - seconds(info.uptime);
271     rebootTime =
272         duration_cast<milliseconds>(rebootTimeTs.time_since_epoch()).count();
273     server::BMC::lastRebootTime(rebootTime);
274 }
275 
lastRebootTime() const276 uint64_t BMC::lastRebootTime() const
277 {
278     return rebootTime;
279 }
280 
discoverLastRebootCause()281 void BMC::discoverLastRebootCause()
282 {
283     uint64_t bootReason = 0;
284     std::ifstream file;
285     const auto* bootstatusPath = "/sys/class/watchdog/watchdog0/bootstatus";
286 
287     file.exceptions(std::ifstream::failbit | std::ifstream::badbit |
288                     std::ifstream::eofbit);
289 
290     try
291     {
292         file.open(bootstatusPath);
293         file >> bootReason;
294     }
295     catch (const std::exception& e)
296     {
297         auto rc = errno;
298         error("Failed to read sysfs file {FILE} with errno {ERRNO}", "FILE",
299               bootstatusPath, "ERRNO", rc);
300     }
301 
302     switch (bootReason)
303     {
304         case WDIOF_EXTERN1:
305             this->lastRebootCause(RebootCause::Software);
306             return;
307         case WDIOF_CARDRESET:
308             this->lastRebootCause(RebootCause::Watchdog);
309             return;
310         default:
311             this->lastRebootCause(RebootCause::POR);
312             // Continue below to see if more details can be found
313             // on reason for reboot
314             break;
315     }
316 
317     // If the above code could not detect a reason, look for a the
318     // reset-cause-pinhole gpio to see if it is the reason for the reboot
319     auto gpioval =
320         phosphor::state::manager::utils::getGpioValue("reset-cause-pinhole");
321 
322     // A 0 indicates a pinhole reset occurred
323     if (0 == gpioval)
324     {
325         info("The BMC reset was caused by a pinhole reset");
326         this->lastRebootCause(RebootCause::PinholeReset);
327 
328         // Generate log telling user a pinhole reset has occurred
329         const std::string errorMsg = "xyz.openbmc_project.State.PinholeReset";
330         phosphor::state::manager::utils::createError(
331             this->bus, errorMsg,
332             sdbusplus::server::xyz::openbmc_project::logging::Entry::Level::
333                 Notice);
334         return;
335     }
336 
337     // If we still haven't found a reason, see if we lost AC power
338     // Note that a pinhole reset will remove AC power to the chassis
339     // on some systems so we always want to look for the pinhole reset
340     // first as that would be the main reason AC power was lost.
341     size_t chassisId = 0;
342     if (phosphor::state::manager::utils::checkACLoss(chassisId))
343     {
344         this->lastRebootCause(RebootCause::POR);
345     }
346 
347     return;
348 }
349 
350 } // namespace manager
351 } // namespace state
352 } // namespace phosphor
353