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     sdbusplus::message::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 =
79         sdbusplus::message::variant_ns::get<std::string>(currentState);
80     if (currentStateStr == activeState)
81     {
82         log<level::INFO>("Setting the BMCState field",
83                          entry("CURRENT_BMC_STATE=%s", "BMC_READY"));
84         this->currentBMCState(BMCState::Ready);
85 
86         // Unsubscribe so we stop processing all other signals
87         method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
88                                            SYSTEMD_INTERFACE, "Unsubscribe");
89         try
90         {
91             this->bus.call(method);
92             this->stateSignal.release();
93         }
94         catch (const SdBusError& e)
95         {
96             log<level::INFO>("Error in Unsubscribe",
97                              entry("ERROR=%s", e.what()));
98         }
99     }
100     else
101     {
102         log<level::INFO>("Setting the BMCState field",
103                          entry("CURRENT_BMC_STATE=%s", "BMC_NOTREADY"));
104         this->currentBMCState(BMCState::NotReady);
105     }
106 
107     return;
108 }
109 
110 void BMC::subscribeToSystemdSignals()
111 {
112     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
113                                             SYSTEMD_INTERFACE, "Subscribe");
114 
115     try
116     {
117         this->bus.call(method);
118     }
119     catch (const SdBusError& e)
120     {
121         log<level::ERR>("Failed to subscribe to systemd signals",
122                         entry("ERR=%s", e.what()));
123         elog<InternalFailure>();
124     }
125 
126     return;
127 }
128 
129 void BMC::executeTransition(const Transition tranReq)
130 {
131     // Check to make sure it can be found
132     auto iter = SYSTEMD_TABLE.find(tranReq);
133     if (iter == SYSTEMD_TABLE.end())
134         return;
135 
136     const auto& sysdUnit = iter->second;
137 
138     auto method = this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
139                                             SYSTEMD_INTERFACE, "StartUnit");
140     // The only valid transition is reboot and that
141     // needs to be irreversible once started
142     method.append(sysdUnit, "replace-irreversibly");
143 
144     try
145     {
146         this->bus.call(method);
147     }
148     catch (const SdBusError& e)
149     {
150         log<level::INFO>("Error in StartUnit - replace-irreversibly",
151                          entry("ERROR=%s", e.what()));
152     }
153 
154     return;
155 }
156 
157 int BMC::bmcStateChange(sdbusplus::message::message& msg)
158 {
159     uint32_t newStateID{};
160     sdbusplus::message::object_path newStateObjPath;
161     std::string newStateUnit{};
162     std::string newStateResult{};
163 
164     // Read the msg and populate each variable
165     msg.read(newStateID, newStateObjPath, newStateUnit, newStateResult);
166 
167     // Caught the signal that indicates the BMC is now BMC_READY
168     if ((newStateUnit == obmcStandbyTarget) && (newStateResult == signalDone))
169     {
170         log<level::INFO>("BMC_READY");
171         this->currentBMCState(BMCState::Ready);
172 
173         // Unsubscribe so we stop processing all other signals
174         auto method =
175             this->bus.new_method_call(SYSTEMD_SERVICE, SYSTEMD_OBJ_PATH,
176                                       SYSTEMD_INTERFACE, "Unsubscribe");
177 
178         try
179         {
180             this->bus.call(method);
181             this->stateSignal.release();
182         }
183         catch (const SdBusError& e)
184         {
185             log<level::INFO>("Error in Unsubscribe",
186                              entry("ERROR=%s", e.what()));
187         }
188     }
189 
190     return 0;
191 }
192 
193 BMC::Transition BMC::requestedBMCTransition(Transition value)
194 {
195     log<level::INFO>("Setting the RequestedBMCTransition field",
196                      entry("REQUESTED_BMC_TRANSITION=0x%s",
197                            convertForMessage(value).c_str()));
198 
199     executeTransition(value);
200     return server::BMC::requestedBMCTransition(value);
201 }
202 
203 BMC::BMCState BMC::currentBMCState(BMCState value)
204 {
205     log<level::INFO>(
206         "Setting the BMCState field",
207         entry("CURRENT_BMC_STATE=0x%s", convertForMessage(value).c_str()));
208 
209     return server::BMC::currentBMCState(value);
210 }
211 
212 uint64_t BMC::lastRebootTime() const
213 {
214     using namespace std::chrono;
215     struct sysinfo info;
216 
217     auto rc = sysinfo(&info);
218     assert(rc == 0);
219 
220     // Since uptime is in seconds, also get the current time in seconds.
221     auto now = time_point_cast<seconds>(system_clock::now());
222     auto rebootTime = now - seconds(info.uptime);
223 
224     return duration_cast<milliseconds>(rebootTime.time_since_epoch()).count();
225 }
226 
227 } // namespace manager
228 } // namespace state
229 } // namespace phosphor
230