1 #include "button_handler.hpp"
2 
3 #include "settings.hpp"
4 
5 #include <phosphor-logging/log.hpp>
6 #include <xyz/openbmc_project/State/Chassis/server.hpp>
7 #include <xyz/openbmc_project/State/Host/server.hpp>
8 namespace phosphor
9 {
10 namespace button
11 {
12 
13 namespace sdbusRule = sdbusplus::bus::match::rules;
14 using namespace sdbusplus::xyz::openbmc_project::State::server;
15 using namespace phosphor::logging;
16 
17 constexpr auto chassisIface = "xyz.openbmc_project.State.Chassis";
18 constexpr auto hostIface = "xyz.openbmc_project.State.Host";
19 constexpr auto powerButtonIface = "xyz.openbmc_project.Chassis.Buttons.Power";
20 constexpr auto idButtonIface = "xyz.openbmc_project.Chassis.Buttons.ID";
21 constexpr auto resetButtonIface = "xyz.openbmc_project.Chassis.Buttons.Reset";
22 constexpr auto ledGroupIface = "xyz.openbmc_project.Led.Group";
23 constexpr auto ledGroupBasePath = "/xyz/openbmc_project/led/groups/";
24 constexpr auto hostSelectorIface =
25     "xyz.openbmc_project.Chassis.Buttons.HostSelector";
26 constexpr auto debugHostSelectorIface =
27     "xyz.openbmc_project.Chassis.Buttons.DebugHostSelector";
28 
29 constexpr auto propertyIface = "org.freedesktop.DBus.Properties";
30 constexpr auto mapperIface = "xyz.openbmc_project.ObjectMapper";
31 
32 constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper";
33 constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
34 constexpr auto BMC_POSITION = 0;
35 
36 Handler::Handler(sdbusplus::bus::bus& bus) : bus(bus)
37 {
38     try
39     {
40         if (!getService(POWER_DBUS_OBJECT_NAME, powerButtonIface).empty())
41         {
42             log<level::INFO>("Starting power button handler");
43             powerButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
44                 bus,
45                 sdbusRule::type::signal() + sdbusRule::member("Released") +
46                     sdbusRule::path(POWER_DBUS_OBJECT_NAME) +
47                     sdbusRule::interface(powerButtonIface),
48                 std::bind(std::mem_fn(&Handler::powerPressed), this,
49                           std::placeholders::_1));
50 
51             powerButtonLongPressReleased =
52                 std::make_unique<sdbusplus::bus::match_t>(
53                     bus,
54                     sdbusRule::type::signal() +
55                         sdbusRule::member("PressedLong") +
56                         sdbusRule::path(POWER_DBUS_OBJECT_NAME) +
57                         sdbusRule::interface(powerButtonIface),
58                     std::bind(std::mem_fn(&Handler::longPowerPressed), this,
59                               std::placeholders::_1));
60         }
61     }
62     catch (const sdbusplus::exception::exception& e)
63     {
64         // The button wasn't implemented
65     }
66 
67     try
68     {
69         if (!getService(ID_DBUS_OBJECT_NAME, idButtonIface).empty())
70         {
71             log<level::INFO>("Registering ID button handler");
72             idButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
73                 bus,
74                 sdbusRule::type::signal() + sdbusRule::member("Released") +
75                     sdbusRule::path(ID_DBUS_OBJECT_NAME) +
76                     sdbusRule::interface(idButtonIface),
77                 std::bind(std::mem_fn(&Handler::idPressed), this,
78                           std::placeholders::_1));
79         }
80     }
81     catch (const sdbusplus::exception::exception& e)
82     {
83         // The button wasn't implemented
84     }
85 
86     try
87     {
88         if (!getService(RESET_DBUS_OBJECT_NAME, resetButtonIface).empty())
89         {
90             log<level::INFO>("Registering reset button handler");
91             resetButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
92                 bus,
93                 sdbusRule::type::signal() + sdbusRule::member("Released") +
94                     sdbusRule::path(RESET_DBUS_OBJECT_NAME) +
95                     sdbusRule::interface(resetButtonIface),
96                 std::bind(std::mem_fn(&Handler::resetPressed), this,
97                           std::placeholders::_1));
98         }
99     }
100     catch (const sdbusplus::exception::exception& e)
101     {
102         // The button wasn't implemented
103     }
104 }
105 bool Handler::isMultiHost()
106 {
107     // return true in case host selector object is available
108     return (!getService(HS_DBUS_OBJECT_NAME, hostSelectorIface).empty());
109 }
110 std::string Handler::getService(const std::string& path,
111                                 const std::string& interface) const
112 {
113     auto method = bus.new_method_call(mapperService, mapperObjPath, mapperIface,
114                                       "GetObject");
115     method.append(path, std::vector{interface});
116     auto result = bus.call(method);
117 
118     std::map<std::string, std::vector<std::string>> objectData;
119     result.read(objectData);
120 
121     return objectData.begin()->first;
122 }
123 size_t Handler::getHostSelectorValue()
124 {
125     auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
126 
127     if (HSService.empty())
128     {
129         log<level::INFO>("Host Selector dbus object not available");
130         throw std::invalid_argument("Host selector dbus object not available");
131     }
132 
133     try
134     {
135         auto method = bus.new_method_call(
136             HSService.c_str(), HS_DBUS_OBJECT_NAME, propertyIface, "Get");
137         method.append(hostSelectorIface, "Position");
138         auto result = bus.call(method);
139 
140         std::variant<size_t> HSPositionVariant;
141         result.read(HSPositionVariant);
142 
143         auto position = std::get<size_t>(HSPositionVariant);
144         return position;
145     }
146     catch (const sdbusplus::exception::exception& e)
147     {
148         log<level::ERR>("Error reading Host selector Position",
149                         entry("ERROR=%s", e.what()));
150         throw;
151     }
152 }
153 bool Handler::poweredOn(size_t hostNumber) const
154 {
155     auto chassisObjectName =
156         CHASSIS_STATE_OBJECT_NAME + std::to_string(hostNumber);
157     auto service = getService(chassisObjectName.c_str(), chassisIface);
158     auto method = bus.new_method_call(
159         service.c_str(), chassisObjectName.c_str(), propertyIface, "Get");
160     method.append(chassisIface, "CurrentPowerState");
161     auto result = bus.call(method);
162 
163     std::variant<std::string> state;
164     result.read(state);
165 
166     return Chassis::PowerState::On ==
167            Chassis::convertPowerStateFromString(std::get<std::string>(state));
168 }
169 
170 void Handler::handlePowerEvent(PowerEvent powerEventType)
171 {
172     std::string objPathName;
173     std::string dbusIfaceName;
174     std::string transitionName;
175     std::variant<Host::Transition, Chassis::Transition> transition;
176 
177     size_t hostNumber = 0;
178     auto isMultiHostSystem = isMultiHost();
179     if (isMultiHostSystem)
180     {
181         hostNumber = getHostSelectorValue();
182         log<level::INFO>("Multi host system detected : ",
183                          entry("POSITION=%d", hostNumber));
184     }
185 
186     std::string hostNumStr = std::to_string(hostNumber);
187 
188     // ignore  power and reset button events if BMC is selected.
189     if (isMultiHostSystem && (hostNumber == BMC_POSITION) &&
190         (powerEventType != PowerEvent::longPowerPressed))
191     {
192         log<level::INFO>("handlePowerEvent : BMC selected on multihost system."
193                          "ignoring power and reset button events...");
194         return;
195     }
196 
197     switch (powerEventType)
198     {
199         case PowerEvent::powerPressed:
200         {
201             objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
202             dbusIfaceName = hostIface;
203             transitionName = "RequestedHostTransition";
204 
205             transition = Host::Transition::On;
206 
207             if (poweredOn(hostNumber))
208             {
209                 transition = Host::Transition::Off;
210             }
211             log<level::INFO>("handlePowerEvent : handle power button press ");
212 
213             break;
214         }
215         case PowerEvent::longPowerPressed:
216         {
217             dbusIfaceName = chassisIface;
218             transitionName = "RequestedPowerTransition";
219             objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
220             transition = Chassis::Transition::Off;
221 
222             /*  multi host system :
223                     hosts (1 to N) - host shutdown
224                     bmc (0) - sled cycle
225                 single host system :
226                     host(0) - host shutdown
227             */
228             if (isMultiHostSystem && (hostNumber == BMC_POSITION))
229             {
230 #if CHASSIS_SYSTEM_RESET_ENABLED
231                 objPathName = CHASSISSYSTEM_STATE_OBJECT_NAME + hostNumStr;
232                 transition = Chassis::Transition::PowerCycle;
233 #else
234                 return;
235 #endif
236             }
237             else if (!poweredOn(hostNumber))
238             {
239                 log<level::INFO>(
240                     "Power is off so ignoring long power button press");
241                 return;
242             }
243             log<level::INFO>(
244                 "handlePowerEvent : handle long power button press");
245 
246             break;
247         }
248 
249         case PowerEvent::resetPressed:
250         {
251             objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
252             dbusIfaceName = hostIface;
253             transitionName = "RequestedHostTransition";
254 
255             if (!poweredOn(hostNumber))
256             {
257                 log<level::INFO>("Power is off so ignoring reset button press");
258                 return;
259             }
260 
261             log<level::INFO>("Handling reset button press");
262             transition = Host::Transition::Reboot;
263             break;
264         }
265         default:
266         {
267             log<level::ERR>(
268                 "Invalid power event. skipping...",
269                 entry("EVENT=%d", static_cast<int>(powerEventType)));
270 
271             return;
272         }
273     }
274     auto service = getService(objPathName.c_str(), dbusIfaceName);
275     auto method = bus.new_method_call(service.c_str(), objPathName.c_str(),
276                                       propertyIface, "Set");
277     method.append(dbusIfaceName, transitionName, transition);
278     bus.call(method);
279 }
280 void Handler::powerPressed(sdbusplus::message::message& /* msg */)
281 {
282     try
283     {
284         handlePowerEvent(PowerEvent::powerPressed);
285     }
286     catch (const sdbusplus::exception::exception& e)
287     {
288         log<level::ERR>("Failed power state change on a power button press",
289                         entry("ERROR=%s", e.what()));
290     }
291 }
292 void Handler::longPowerPressed(sdbusplus::message::message& /* msg */)
293 {
294     try
295     {
296         handlePowerEvent(PowerEvent::longPowerPressed);
297     }
298     catch (const sdbusplus::exception::exception& e)
299     {
300         log<level::ERR>("Failed powering off on long power button press",
301                         entry("ERROR=%s", e.what()));
302     }
303 }
304 
305 void Handler::resetPressed(sdbusplus::message::message& /* msg */)
306 {
307     try
308     {
309         handlePowerEvent(PowerEvent::resetPressed);
310     }
311     catch (const sdbusplus::exception::exception& e)
312     {
313         log<level::ERR>("Failed power state change on a reset button press",
314                         entry("ERROR=%s", e.what()));
315     }
316 }
317 
318 void Handler::idPressed(sdbusplus::message::message& /* msg */)
319 {
320     std::string groupPath{ledGroupBasePath};
321     groupPath += ID_LED_GROUP;
322 
323     auto service = getService(groupPath, ledGroupIface);
324 
325     if (service.empty())
326     {
327         log<level::INFO>("No identify LED group found during ID button press",
328                          entry("GROUP=%s", groupPath.c_str()));
329         return;
330     }
331 
332     try
333     {
334         auto method = bus.new_method_call(service.c_str(), groupPath.c_str(),
335                                           propertyIface, "Get");
336         method.append(ledGroupIface, "Asserted");
337         auto result = bus.call(method);
338 
339         std::variant<bool> state;
340         result.read(state);
341 
342         state = !std::get<bool>(state);
343 
344         log<level::INFO>("Changing ID LED group state on ID LED press",
345                          entry("GROUP=%s", groupPath.c_str()),
346                          entry("STATE=%d", std::get<bool>(state)));
347 
348         method = bus.new_method_call(service.c_str(), groupPath.c_str(),
349                                      propertyIface, "Set");
350 
351         method.append(ledGroupIface, "Asserted", state);
352         result = bus.call(method);
353     }
354     catch (const sdbusplus::exception::exception& e)
355     {
356         log<level::ERR>("Error toggling ID LED group on ID button press",
357                         entry("ERROR=%s", e.what()));
358     }
359 }
360 } // namespace button
361 } // namespace phosphor
362