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