1 #include <cassert>
2 #include <phosphor-logging/log.hpp>
3 #include <phosphor-logging/elog-errors.hpp>
4 #include <sdbusplus/exception.hpp>
5 #include <sys/sysinfo.h>
6 #include "bmc_state_manager.hpp"
7 #include "xyz/openbmc_project/Common/error.hpp"
8 
9 namespace phosphor
10 {
11 namespace state
12 {
13 namespace manager
14 {
15 
16 // When you see server:: you know we're referencing our base class
17 namespace server = sdbusplus::xyz::openbmc_project::State::server;
18 
19 using namespace phosphor::logging;
20 using sdbusplus::exception::SdBusError;
21 using sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
22 
23 constexpr auto obmcStandbyTarget = "multi-user.target";
24 constexpr auto signalDone = "done";
25 constexpr auto activeState = "active";
26 
27 /* Map a transition to it's systemd target */
28 const std::map<server::BMC::Transition, const char*> SYSTEMD_TABLE = {
29     {server::BMC::Transition::Reboot, "reboot.target"}};
30 
31 constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
32 constexpr auto SYSTEMD_OBJ_PATH = "/org/freedesktop/systemd1";
33 constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
34 constexpr auto SYSTEMD_PRP_INTERFACE = "org.freedesktop.DBus.Properties";
35 
36 void BMC::discoverInitialState()
37 {
38     std::variant<std::string> currentState;
39     sdbusplus::message::object_path unitTargetPath;
40 
41     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
42                                             SYSTEMD_INTERFACE, "GetUnit");
43 
44     method.append(obmcStandbyTarget);
45 
46     try
47     {
48         auto result = this->bus.call(method);
49         result.read(unitTargetPath);
50     }
51     catch (const SdBusError& e)
52     {
53         log<level::ERR>("Error in GetUnit call", entry("ERROR=%s", e.what()));
54         return;
55     }
56 
57     method = this->bus.new_method_call(
58         SYSTEMD_SERVICE,
59         static_cast<const std::string&>(unitTargetPath).c_str(),
60         SYSTEMD_PRP_INTERFACE, "Get");
61 
62     method.append("org.freedesktop.systemd1.Unit", "ActiveState");
63 
64     try
65     {
66         auto result = this->bus.call(method);
67 
68         // Is obmc-standby.target active or inactive?
69         result.read(currentState);
70     }
71     catch (const SdBusError& e)
72     {
73         log<level::INFO>("Error in ActiveState Get",
74                          entry("ERROR=%s", e.what()));
75         return;
76     }
77 
78     auto currentStateStr = std::get<std::string>(currentState);
79     if (currentStateStr == activeState)
80     {
81         log<level::INFO>("Setting the BMCState field",
82                          entry("CURRENT_BMC_STATE=%s", "BMC_READY"));
83         this->currentBMCState(BMCState::Ready);
84 
85         // Unsubscribe so we stop processing all other signals
86         method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
87                                            SYSTEMD_INTERFACE, "Unsubscribe");
88         try
89         {
90             this->bus.call(method);
91             this->stateSignal.release();
92         }
93         catch (const SdBusError& e)
94         {
95             log<level::INFO>("Error in Unsubscribe",
96                              entry("ERROR=%s", e.what()));
97         }
98     }
99     else
100     {
101         log<level::INFO>("Setting the BMCState field",
102                          entry("CURRENT_BMC_STATE=%s", "BMC_NOTREADY"));
103         this->currentBMCState(BMCState::NotReady);
104     }
105 
106     return;
107 }
108 
109 void BMC::subscribeToSystemdSignals()
110 {
111     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
112                                             SYSTEMD_INTERFACE, "Subscribe");
113 
114     try
115     {
116         this->bus.call(method);
117     }
118     catch (const SdBusError& e)
119     {
120         log<level::ERR>("Failed to subscribe to systemd signals",
121                         entry("ERR=%s", e.what()));
122         elog<InternalFailure>();
123     }
124 
125     return;
126 }
127 
128 void BMC::executeTransition(const Transition tranReq)
129 {
130     // Check to make sure it can be found
131     auto iter = SYSTEMD_TABLE.find(tranReq);
132     if (iter == SYSTEMD_TABLE.end())
133         return;
134 
135     const auto& sysdUnit = iter->second;
136 
137     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
138                                             SYSTEMD_INTERFACE, "StartUnit");
139     // The only valid transition is reboot and that
140     // needs to be irreversible once started
141     method.append(sysdUnit, "replace-irreversibly");
142 
143     try
144     {
145         this->bus.call(method);
146     }
147     catch (const SdBusError& e)
148     {
149         log<level::INFO>("Error in StartUnit - replace-irreversibly",
150                          entry("ERROR=%s", e.what()));
151     }
152 
153     return;
154 }
155 
156 int BMC::bmcStateChange(sdbusplus::message::message& msg)
157 {
158     uint32_t newStateID{};
159     sdbusplus::message::object_path newStateObjPath;
160     std::string newStateUnit{};
161     std::string newStateResult{};
162 
163     // Read the msg and populate each variable
164     msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
165 
166     // Caught the signal that indicates the BMC is now BMC_READY
167     if ((newStateUnit == obmcStandbyTarget) && (newStateResult == signalDone))
168     {
169         log<level::INFO>("BMC_READY");
170         this->currentBMCState(BMCState::Ready);
171 
172         // Unsubscribe so we stop processing all other signals
173         auto method =
174             this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
175                                       SYSTEMD_INTERFACE, "Unsubscribe");
176 
177         try
178         {
179             this->bus.call(method);
180             this->stateSignal.release();
181         }
182         catch (const SdBusError& e)
183         {
184             log<level::INFO>("Error in Unsubscribe",
185                              entry("ERROR=%s", e.what()));
186         }
187     }
188 
189     return 0;
190 }
191 
192 BMC::Transition BMC::requestedBMCTransition(Transition value)
193 {
194     log<level::INFO>("Setting the RequestedBMCTransition field",
195                      entry("REQUESTED_BMC_TRANSITION=0x%s",
196                            convertForMessage(value).c_str()));
197 
198     executeTransition(value);
199     return server::BMC::requestedBMCTransition(value);
200 }
201 
202 BMC::BMCState BMC::currentBMCState(BMCState value)
203 {
204     log<level::INFO>(
205         "Setting the BMCState field",
206         entry("CURRENT_BMC_STATE=0x%s", convertForMessage(value).c_str()));
207 
208     return server::BMC::currentBMCState(value);
209 }
210 
211 uint64_t BMC::lastRebootTime() const
212 {
213     using namespace std::chrono;
214     struct sysinfo info;
215 
216     auto rc = sysinfo(&info);
217     assert(rc == 0);
218 
219     // Since uptime is in seconds, also get the current time in seconds.
220     auto now = time_point_cast<seconds>(system_clock::now());
221     auto rebootTime = now - seconds(info.uptime);
222 
223     return duration_cast<milliseconds>(rebootTime.time_since_epoch()).count();
224 }
225 
226 } // namespace manager
227 } // namespace state
228 } // namespace phosphor
229