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