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