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