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