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