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 #include <sys/sysinfo.h>
8 
9 #include <phosphor-logging/elog-errors.hpp>
10 #include <phosphor-logging/lg2.hpp>
11 #include <sdbusplus/exception.hpp>
12 
13 #include <cassert>
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::xyz::openbmc_project::State::server;
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 
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::exception& e)
63     {
64         error("Error in GetUnit call: {ERROR}", "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 obmc-standby.target active or inactive?
80         result.read(currentState);
81     }
82     catch (const sdbusplus::exception::exception& 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 
93     // First look to see if the BMC quiesce target is active
94     auto currentStateStr = getUnitState(obmcQuiesceTarget);
95     if (currentStateStr == activeState)
96     {
97         info("Setting the BMCState field to BMC_QUIESCED");
98         this->currentBMCState(BMCState::Quiesced);
99         return;
100     }
101 
102     // If not quiesced, then check standby target
103     currentStateStr = getUnitState(obmcStandbyTarget);
104     if (currentStateStr == activeState)
105     {
106         info("Setting the BMCState field to BMC_READY");
107         this->currentBMCState(BMCState::Ready);
108     }
109     else
110     {
111         info("Setting the BMCState field to BMC_NOTREADY");
112         this->currentBMCState(BMCState::NotReady);
113     }
114 
115     return;
116 }
117 
118 void BMC::subscribeToSystemdSignals()
119 {
120     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
121                                             SYSTEMD_INTERFACE, "Subscribe");
122 
123     try
124     {
125         this->bus.call(method);
126     }
127     catch (const sdbusplus::exception::exception& e)
128     {
129         error("Failed to subscribe to systemd signals: {ERROR}", "ERROR", e);
130         elog<InternalFailure>();
131     }
132 
133     return;
134 }
135 
136 void BMC::executeTransition(const Transition tranReq)
137 {
138     // HardReboot does not shutdown any services and immediately transitions
139     // into the reboot process
140     if (server::BMC::Transition::HardReboot == tranReq)
141     {
142         auto method = this->bus.new_method_call(
143             SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "Reboot");
144         try
145         {
146             this->bus.call(method);
147         }
148         catch (const sdbusplus::exception::exception& e)
149         {
150             info("Error in HardReboot: {ERROR}", "ERROR", e);
151         }
152     }
153     else
154     {
155         // Check to make sure it can be found
156         auto iter = SYSTEMD_TABLE.find(tranReq);
157         if (iter == SYSTEMD_TABLE.end())
158             return;
159 
160         const auto& sysdUnit = iter->second;
161 
162         auto method = this->bus.new_method_call(
163             SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH, SYSTEMD_INTERFACE, "StartUnit");
164         // The only valid transition is reboot and that
165         // needs to be irreversible once started
166 
167         method.append(sysdUnit, "replace-irreversibly");
168 
169         try
170         {
171             this->bus.call(method);
172         }
173         catch (const sdbusplus::exception::exception& e)
174         {
175             info("Error in StartUnit - replace-irreversibly: {ERROR}", "ERROR",
176                  e);
177         }
178     }
179     return;
180 }
181 
182 int BMC::bmcStateChange(sdbusplus::message::message& msg)
183 {
184     uint32_t newStateID{};
185     sdbusplus::message::object_path newStateObjPath;
186     std::string newStateUnit{};
187     std::string newStateResult{};
188 
189     // Read the msg and populate each variable
190     msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
191 
192     if ((newStateUnit == obmcQuiesceTarget) && (newStateResult == signalDone))
193     {
194         error("BMC has entered BMC_QUIESCED state");
195         this->currentBMCState(BMCState::Quiesced);
196 
197         // There is no getting out of Quiesced once entered (other then BMC
198         // reboot) so stop watching for signals
199         auto method =
200             this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
201                                       SYSTEMD_INTERFACE, "Unsubscribe");
202 
203         try
204         {
205             this->bus.call(method);
206             this->stateSignal.release();
207         }
208         catch (const sdbusplus::exception::exception& e)
209         {
210             info("Error in Unsubscribe: {ERROR}", "ERROR", e);
211         }
212 
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 
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     executeTransition(value);
233     return server::BMC::requestedBMCTransition(value);
234 }
235 
236 BMC::BMCState BMC::currentBMCState(BMCState value)
237 {
238 
239     info("Setting the BMCState field to {CURRENT_BMC_STATE}",
240          "CURRENT_BMC_STATE", value);
241 
242     return server::BMC::currentBMCState(value);
243 }
244 
245 BMC::RebootCause BMC::lastRebootCause(RebootCause value)
246 {
247     info("Setting the RebootCause field to {LAST_REBOOT_CAUSE}",
248          "LAST_REBOOT_CAUSE", value);
249 
250     return server::BMC::lastRebootCause(value);
251 }
252 
253 uint64_t BMC::lastRebootTime() const
254 {
255     using namespace std::chrono;
256     struct sysinfo info;
257 
258     auto rc = sysinfo(&info);
259     assert(rc == 0);
260 
261     // Since uptime is in seconds, also get the current time in seconds.
262     auto now = time_point_cast<seconds>(system_clock::now());
263     auto rebootTime = now - seconds(info.uptime);
264 
265     return duration_cast<milliseconds>(rebootTime.time_since_epoch()).count();
266 }
267 
268 void BMC::discoverLastRebootCause()
269 {
270     uint64_t bootReason = 0;
271     std::ifstream file;
272     auto bootstatusPath = "/sys/class/watchdog/watchdog0/bootstatus";
273 
274     file.exceptions(std::ifstream::failbit | std::ifstream::badbit |
275                     std::ifstream::eofbit);
276 
277     try
278     {
279         file.open(bootstatusPath);
280         file >> bootReason;
281     }
282     catch (const std::exception& e)
283     {
284         auto rc = errno;
285         error("Failed to read sysfs file {FILE} with errno {ERRNO}", "FILE",
286               bootstatusPath, "ERRNO", rc);
287     }
288 
289     switch (bootReason)
290     {
291         case WDIOF_EXTERN1:
292             this->lastRebootCause(RebootCause::Watchdog);
293             return;
294         case WDIOF_CARDRESET:
295             this->lastRebootCause(RebootCause::POR);
296             return;
297         default:
298             this->lastRebootCause(RebootCause::Unknown);
299             // Continue below to see if more details can be found
300             // on reason for reboot
301             break;
302     }
303 
304     // If the above code could not detect a reason, look for a the
305     // reset-cause-pinhole gpio to see if it is the reason for the reboot
306     auto gpioval =
307         phosphor::state::manager::utils::getGpioValue("reset-cause-pinhole");
308 
309     if (1 == 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::xyz::openbmc_project::Logging::server::Entry::Level::
319                 Notice);
320     }
321 
322     return;
323 }
324 
325 } // namespace manager
326 } // namespace state
327 } // namespace phosphor
328