1 #include "config.h"
2 
3 #include "button_handler.hpp"
4 
5 #include <phosphor-logging/lg2.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 
16 constexpr auto chassisIface = "xyz.openbmc_project.State.Chassis";
17 constexpr auto hostIface = "xyz.openbmc_project.State.Host";
18 constexpr auto powerButtonIface = "xyz.openbmc_project.Chassis.Buttons.Power";
19 constexpr auto idButtonIface = "xyz.openbmc_project.Chassis.Buttons.ID";
20 constexpr auto resetButtonIface = "xyz.openbmc_project.Chassis.Buttons.Reset";
21 constexpr auto ledGroupIface = "xyz.openbmc_project.Led.Group";
22 constexpr auto ledGroupBasePath = "/xyz/openbmc_project/led/groups/";
23 constexpr auto hostSelectorIface =
24     "xyz.openbmc_project.Chassis.Buttons.HostSelector";
25 constexpr auto debugHostSelectorIface =
26     "xyz.openbmc_project.Chassis.Buttons.Button";
27 
28 constexpr auto propertyIface = "org.freedesktop.DBus.Properties";
29 constexpr auto mapperIface = "xyz.openbmc_project.ObjectMapper";
30 
31 constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper";
32 constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
33 constexpr auto BMC_POSITION = 0;
34 
35 Handler::Handler(sdbusplus::bus_t& bus) : bus(bus)
36 {
37     try
38     {
39         if (!getService(POWER_DBUS_OBJECT_NAME, powerButtonIface).empty())
40         {
41             lg2::info("Starting power button handler");
42             powerButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
43                 bus,
44                 sdbusRule::type::signal() + sdbusRule::member("Released") +
45                     sdbusRule::path(POWER_DBUS_OBJECT_NAME) +
46                     sdbusRule::interface(powerButtonIface),
47                 std::bind(std::mem_fn(&Handler::powerReleased), this,
48                           std::placeholders::_1));
49         }
50     }
51     catch (const sdbusplus::exception_t& e)
52     {
53         // The button wasn't implemented
54     }
55 
56     try
57     {
58         if (!getService(ID_DBUS_OBJECT_NAME, idButtonIface).empty())
59         {
60             lg2::info("Registering ID button handler");
61             idButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
62                 bus,
63                 sdbusRule::type::signal() + sdbusRule::member("Released") +
64                     sdbusRule::path(ID_DBUS_OBJECT_NAME) +
65                     sdbusRule::interface(idButtonIface),
66                 std::bind(std::mem_fn(&Handler::idReleased), this,
67                           std::placeholders::_1));
68         }
69     }
70     catch (const sdbusplus::exception_t& e)
71     {
72         // The button wasn't implemented
73     }
74 
75     try
76     {
77         if (!getService(RESET_DBUS_OBJECT_NAME, resetButtonIface).empty())
78         {
79             lg2::info("Registering reset button handler");
80             resetButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
81                 bus,
82                 sdbusRule::type::signal() + sdbusRule::member("Released") +
83                     sdbusRule::path(RESET_DBUS_OBJECT_NAME) +
84                     sdbusRule::interface(resetButtonIface),
85                 std::bind(std::mem_fn(&Handler::resetReleased), this,
86                           std::placeholders::_1));
87         }
88     }
89     catch (const sdbusplus::exception_t& e)
90     {
91         // The button wasn't implemented
92     }
93     try
94     {
95         if (!getService(DBG_HS_DBUS_OBJECT_NAME, debugHostSelectorIface)
96                  .empty())
97         {
98             lg2::info("Registering debug host selector button handler");
99             debugHSButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
100                 bus,
101                 sdbusRule::type::signal() + sdbusRule::member("Released") +
102                     sdbusRule::path(DBG_HS_DBUS_OBJECT_NAME) +
103                     sdbusRule::interface(debugHostSelectorIface),
104                 std::bind(std::mem_fn(&Handler::debugHostSelectorReleased),
105                           this, std::placeholders::_1));
106         }
107     }
108     catch (const sdbusplus::exception_t& e)
109     {
110         // The button wasn't implemented
111     }
112 }
113 bool Handler::isMultiHost()
114 {
115     // return true in case host selector object is available
116     return (!getService(HS_DBUS_OBJECT_NAME, hostSelectorIface).empty());
117 }
118 std::string Handler::getService(const std::string& path,
119                                 const std::string& interface) const
120 {
121     auto method = bus.new_method_call(mapperService, mapperObjPath, mapperIface,
122                                       "GetObject");
123     method.append(path, std::vector{interface});
124     try
125     {
126         auto result = bus.call(method);
127         std::map<std::string, std::vector<std::string>> objectData;
128         result.read(objectData);
129         return objectData.begin()->first;
130     }
131     catch (const sdbusplus::exception_t& e)
132     {
133         return std::string();
134     }
135 }
136 size_t Handler::getHostSelectorValue()
137 {
138     auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
139 
140     if (HSService.empty())
141     {
142         lg2::info("Host selector dbus object not available");
143         throw std::invalid_argument("Host selector dbus object not available");
144     }
145 
146     try
147     {
148         auto method = bus.new_method_call(
149             HSService.c_str(), HS_DBUS_OBJECT_NAME, propertyIface, "Get");
150         method.append(hostSelectorIface, "Position");
151         auto result = bus.call(method);
152 
153         std::variant<size_t> HSPositionVariant;
154         result.read(HSPositionVariant);
155 
156         auto position = std::get<size_t>(HSPositionVariant);
157         return position;
158     }
159     catch (const sdbusplus::exception_t& e)
160     {
161         lg2::error("Error reading host selector position: {ERROR}", "ERROR", e);
162         throw;
163     }
164 }
165 bool Handler::poweredOn(size_t hostNumber) const
166 {
167     auto hostObjectName = HOST_STATE_OBJECT_NAME + std::to_string(hostNumber);
168     auto service = getService(hostObjectName.c_str(), hostIface);
169     auto method = bus.new_method_call(service.c_str(), hostObjectName.c_str(),
170                                       propertyIface, "Get");
171     method.append(hostIface, "CurrentHostState");
172     auto result = bus.call(method);
173 
174     std::variant<std::string> state;
175     result.read(state);
176 
177     return Host::HostState::Off !=
178            Host::convertHostStateFromString(std::get<std::string>(state));
179 }
180 
181 void Handler::handlePowerEvent(PowerEvent powerEventType,
182                                std::chrono::microseconds duration)
183 {
184     std::string objPathName;
185     std::string dbusIfaceName;
186     std::string transitionName;
187     std::variant<Host::Transition, Chassis::Transition> transition;
188     uint64_t durationMs =
189         std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
190 
191     size_t hostNumber = 0;
192     auto isMultiHostSystem = isMultiHost();
193     if (isMultiHostSystem)
194     {
195         hostNumber = getHostSelectorValue();
196         lg2::info("Multi-host system detected : {POSITION}", "POSITION",
197                   hostNumber);
198     }
199 
200     std::string hostNumStr = std::to_string(hostNumber);
201 
202     // ignore  power and reset button events if BMC is selected.
203     if (isMultiHostSystem && (hostNumber == BMC_POSITION) &&
204         (powerEventType != PowerEvent::powerReleased) &&
205         (durationMs <= LONG_PRESS_TIME_MS))
206     {
207         lg2::info(
208             "handlePowerEvent : BMC selected on multi-host system. ignoring power and reset button events...");
209         return;
210     }
211 
212     switch (powerEventType)
213     {
214         case PowerEvent::powerReleased:
215         {
216             if (durationMs <= LONG_PRESS_TIME_MS)
217             {
218                 objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
219                 dbusIfaceName = hostIface;
220                 transitionName = "RequestedHostTransition";
221 
222                 transition = Host::Transition::On;
223 
224                 if (poweredOn(hostNumber))
225                 {
226                     transition = Host::Transition::Off;
227                 }
228                 lg2::info("handlePowerEvent : Handle power button press ");
229 
230                 break;
231             }
232             else
233             {
234                 dbusIfaceName = chassisIface;
235                 transitionName = "RequestedPowerTransition";
236                 objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
237                 transition = Chassis::Transition::Off;
238 
239                 /*  multi host system :
240                         hosts (1 to N) - host shutdown
241                         bmc (0) - sled cycle
242                     single host system :
243                         host(0) - host shutdown
244                 */
245                 if (isMultiHostSystem && (hostNumber == BMC_POSITION))
246                 {
247 #if CHASSIS_SYSTEM_RESET_ENABLED
248                     objPathName = CHASSISSYSTEM_STATE_OBJECT_NAME + hostNumStr;
249                     transition = Chassis::Transition::PowerCycle;
250 #else
251                     return;
252 #endif
253                 }
254                 else if (!poweredOn(hostNumber))
255                 {
256                     lg2::info(
257                         "Power is off so ignoring long power button press");
258                     return;
259                 }
260                 lg2::info("handlePowerEvent : handle long power button press");
261                 break;
262             }
263         }
264         case PowerEvent::resetReleased:
265         {
266             objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
267             dbusIfaceName = hostIface;
268             transitionName = "RequestedHostTransition";
269 
270             if (!poweredOn(hostNumber))
271             {
272                 lg2::info("Power is off so ignoring reset button press");
273                 return;
274             }
275 
276             lg2::info("Handling reset button press");
277             transition = Host::Transition::Reboot;
278             break;
279         }
280         default:
281         {
282             lg2::error("{EVENT} is invalid power event. skipping...", "EVENT",
283                        static_cast<std::underlying_type_t<PowerEvent>>(
284                            powerEventType));
285 
286             return;
287         }
288     }
289     auto service = getService(objPathName.c_str(), dbusIfaceName);
290     auto method = bus.new_method_call(service.c_str(), objPathName.c_str(),
291                                       propertyIface, "Set");
292     method.append(dbusIfaceName, transitionName, transition);
293     bus.call(method);
294 }
295 void Handler::powerReleased(sdbusplus::message_t& msg)
296 {
297     try
298     {
299         uint64_t time;
300         msg.read(time);
301 
302         handlePowerEvent(PowerEvent::powerReleased,
303                          std::chrono::microseconds(time));
304     }
305     catch (const sdbusplus::exception_t& e)
306     {
307         lg2::error("Failed power state change on a power button press: {ERROR}",
308                    "ERROR", e);
309     }
310 }
311 
312 void Handler::resetReleased(sdbusplus::message_t& /* msg */)
313 {
314     try
315     {
316         // No need to calculate duration, set to 0.
317         handlePowerEvent(PowerEvent::resetReleased,
318                          std::chrono::microseconds(0));
319     }
320     catch (const sdbusplus::exception_t& e)
321     {
322         lg2::error("Failed power state change on a reset button press: {ERROR}",
323                    "ERROR", e);
324     }
325 }
326 
327 void Handler::idReleased(sdbusplus::message_t& /* msg */)
328 {
329     std::string groupPath{ledGroupBasePath};
330     groupPath += ID_LED_GROUP;
331 
332     auto service = getService(groupPath, ledGroupIface);
333 
334     if (service.empty())
335     {
336         lg2::info("No found {GROUP} during ID button press:", "GROUP",
337                   groupPath);
338         return;
339     }
340 
341     try
342     {
343         auto method = bus.new_method_call(service.c_str(), groupPath.c_str(),
344                                           propertyIface, "Get");
345         method.append(ledGroupIface, "Asserted");
346         auto result = bus.call(method);
347 
348         std::variant<bool> state;
349         result.read(state);
350 
351         state = !std::get<bool>(state);
352 
353         lg2::info(
354             "Changing ID LED group state on ID LED press, GROUP = {GROUP}, STATE = {STATE}",
355             "GROUP", groupPath, "STATE", std::get<bool>(state));
356 
357         method = bus.new_method_call(service.c_str(), groupPath.c_str(),
358                                      propertyIface, "Set");
359 
360         method.append(ledGroupIface, "Asserted", state);
361         result = bus.call(method);
362     }
363     catch (const sdbusplus::exception_t& e)
364     {
365         lg2::error("Error toggling ID LED group on ID button press: {ERROR}",
366                    "ERROR", e);
367     }
368 }
369 
370 void Handler::increaseHostSelectorPosition()
371 {
372     try
373     {
374         auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
375 
376         if (HSService.empty())
377         {
378             lg2::error("Host selector service not available");
379             return;
380         }
381 
382         auto method =
383             bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
384                                 phosphor::button::propertyIface, "GetAll");
385         method.append(phosphor::button::hostSelectorIface);
386         auto result = bus.call(method);
387         std::unordered_map<std::string, std::variant<size_t>> properties;
388         result.read(properties);
389 
390         auto maxPosition = std::get<size_t>(properties.at("MaxPosition"));
391         auto position = std::get<size_t>(properties.at("Position"));
392 
393         std::variant<size_t> HSPositionVariant =
394             (position < maxPosition) ? (position + 1) : 0;
395 
396         method = bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
397                                      phosphor::button::propertyIface, "Set");
398         method.append(phosphor::button::hostSelectorIface, "Position");
399 
400         method.append(HSPositionVariant);
401         result = bus.call(method);
402     }
403     catch (const sdbusplus::exception_t& e)
404     {
405         lg2::error("Error modifying host selector position : {ERROR}", "ERROR",
406                    e);
407     }
408 }
409 
410 void Handler::debugHostSelectorReleased(sdbusplus::message_t& /* msg */)
411 {
412     try
413     {
414         increaseHostSelectorPosition();
415     }
416     catch (const sdbusplus::exception_t& e)
417     {
418         lg2::error(
419             "Failed power process debug host selector button press : {ERROR}",
420             "ERROR", e);
421     }
422 }
423 
424 } // namespace button
425 } // namespace phosphor
426