xref: /openbmc/phosphor-buttons/src/button_handler.cpp (revision fbd3bd5d584e83f9f5febf5a457c39d76882ed2b)
1 #include "button_handler.hpp"
2 
3 #include "config.hpp"
4 #include "gpio.hpp"
5 #include "power_button_profile_factory.hpp"
6 
7 #include <phosphor-logging/lg2.hpp>
8 #include <xyz/openbmc_project/Chassis/Buttons/Power/server.hpp>
9 #include <xyz/openbmc_project/State/Chassis/server.hpp>
10 #include <xyz/openbmc_project/State/Host/server.hpp>
11 
12 #include <fstream>
13 #include <iostream>
14 #include <string>
15 
16 namespace phosphor
17 {
18 namespace button
19 {
20 
21 namespace sdbusRule = sdbusplus::bus::match::rules;
22 using namespace sdbusplus::xyz::openbmc_project::State::server;
23 using namespace sdbusplus::xyz::openbmc_project::Chassis::Buttons::server;
24 using GetSubTreePathsType = std::vector<std::string>;
25 
26 const std::map<std::string, Chassis::Transition> chassisPwrCtls = {
27     {"chassis-on", Chassis::Transition::On},
28     {"chassis-off", Chassis::Transition::Off},
29     {"chassis-cycle", Chassis::Transition::PowerCycle}};
30 
31 constexpr auto chassisIface = "xyz.openbmc_project.State.Chassis";
32 constexpr auto hostIface = "xyz.openbmc_project.State.Host";
33 constexpr auto powerButtonIface = "xyz.openbmc_project.Chassis.Buttons.Power";
34 constexpr auto idButtonIface = "xyz.openbmc_project.Chassis.Buttons.ID";
35 constexpr auto resetButtonIface = "xyz.openbmc_project.Chassis.Buttons.Reset";
36 constexpr auto ledGroupIface = "xyz.openbmc_project.Led.Group";
37 constexpr auto ledGroupBasePath = "/xyz/openbmc_project/led/groups/";
38 constexpr auto hostSelectorIface =
39     "xyz.openbmc_project.Chassis.Buttons.HostSelector";
40 constexpr auto debugHostSelectorIface =
41     "xyz.openbmc_project.Chassis.Buttons.Button";
42 
43 constexpr auto propertyIface = "org.freedesktop.DBus.Properties";
44 constexpr auto mapperIface = "xyz.openbmc_project.ObjectMapper";
45 
46 constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper";
47 constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
48 constexpr auto BMC_POSITION = 0;
49 
50 std::vector<std::map<uint16_t, Chassis::Transition>> multiPwrBtnActConf;
51 
Handler(sdbusplus::bus_t & bus)52 Handler::Handler(sdbusplus::bus_t& bus) : bus(bus)
53 {
54     /* So far, there are two modes for multi-host power control
55     - host select button mode, e.g.: Yosemite V2
56     only one power button with host select switch,
57     which's interface for handling target host,
58     in the case, hostSelectButtonMode = true
59     - multi power button mode, e.g.: Greatlakes
60     each slot/sled has its own power button,
61     in the case, hostSelectButtonMode = false */
62 
63     std::ifstream gpios{gpioDefFile};
64     auto configDefJson = nlohmann::json::parse(gpios, nullptr, true);
65     nlohmann::json gpioDefs = configDefJson["gpio_definitions"];
66 
67     bool isHostSelectorExists =
68         !getService(HS_DBUS_OBJECT_NAME, hostSelectorIface).empty();
69 
70     for (const auto& gpioConfig : gpioDefs)
71     {
72         if (gpioConfig.contains("multi-action"))
73         {
74             std::map<uint16_t, Chassis::Transition> mapEntry;
75             const auto& multiActCfg = gpioConfig["multi-action"];
76             for (const auto& ActCfg : multiActCfg)
77             {
78                 auto chassisPwrCtl = chassisPwrCtls.find(ActCfg["action"]);
79                 if (chassisPwrCtl != chassisPwrCtls.end())
80                 {
81                     auto duration = ActCfg["duration"].get<uint16_t>();
82                     mapEntry[duration] = chassisPwrCtl->second;
83                 }
84                 else
85                 {
86                     lg2::error("unknown power button action");
87                 }
88             }
89             multiPwrBtnActConf.emplace_back(mapEntry);
90         }
91 
92         if (gpioConfig.value("name", "") == "HOST_SELECTOR")
93         {
94             virtualButton = gpioConfig.value("virtual_button", false);
95             lg2::debug("Change virtualButton flag to: {VIRTUAL}", "VIRTUAL",
96                        virtualButton);
97         }
98     }
99 
100     hostSelectButtonMode = isHostSelectorExists && !virtualButton;
101     lg2::debug("hostSelectButtonMode: {MODE}", "MODE", hostSelectButtonMode);
102 
103     try
104     {
105         static const int depth = 1;
106         GetSubTreePathsType powerButtonSubTreePaths;
107         auto method = bus.new_method_call(mapperService, mapperObjPath,
108                                           mapperIface, "GetSubTreePaths");
109         method.append("/xyz/openbmc_project/Chassis/Buttons", depth,
110                       GetSubTreePathsType({powerButtonIface}));
111         auto reply = bus.call(method);
112         reply.read(powerButtonSubTreePaths);
113         if (!powerButtonSubTreePaths.empty())
114         {
115             lg2::info("Starting power button handler");
116 
117             // Check for a custom handler
118             powerButtonProfile =
119                 PowerButtonProfileFactory::instance().createProfile(bus);
120 
121             // Fallback: register for all power buttons found
122             if (!powerButtonProfile)
123             {
124                 for (const auto& path : powerButtonSubTreePaths)
125                 {
126                     lg2::debug("Registering power button handler for: {PATH}",
127                                "PATH", path);
128                     std::unique_ptr<sdbusplus::bus::match_t>
129                         multiPowerReleaseMatch =
130                             std::make_unique<sdbusplus::bus::match_t>(
131                                 bus,
132                                 sdbusRule::type::signal() +
133                                     sdbusRule::member("Released") +
134                                     sdbusRule::path(path) +
135                                     sdbusRule::interface(powerButtonIface),
136                                 std::bind(std::mem_fn(&Handler::powerReleased),
137                                           this, std::placeholders::_1));
138                     multiPowerButtonReleased.emplace_back(
139                         std::move(multiPowerReleaseMatch));
140                 }
141             }
142         }
143     }
144     catch (const sdbusplus::exception_t& e)
145     {
146         lg2::error("Error creating power button handlers: '{ERROR_MESSAGE}'",
147                    "ERROR_MESSAGE", e.what());
148     }
149 
150     try
151     {
152         if (!getService(ID_DBUS_OBJECT_NAME, idButtonIface).empty())
153         {
154             lg2::info("Registering ID button handler");
155             idButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
156                 bus,
157                 sdbusRule::type::signal() + sdbusRule::member("Released") +
158                     sdbusRule::path(ID_DBUS_OBJECT_NAME) +
159                     sdbusRule::interface(idButtonIface),
160                 std::bind(std::mem_fn(&Handler::idReleased), this,
161                           std::placeholders::_1));
162         }
163     }
164     catch (const sdbusplus::exception_t& e)
165     {
166         // The button wasn't implemented
167     }
168 
169     try
170     {
171         if (!getService(RESET_DBUS_OBJECT_NAME, resetButtonIface).empty())
172         {
173             lg2::info("Registering reset button handler");
174             resetButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
175                 bus,
176                 sdbusRule::type::signal() + sdbusRule::member("Released") +
177                     sdbusRule::path(RESET_DBUS_OBJECT_NAME) +
178                     sdbusRule::interface(resetButtonIface),
179                 std::bind(std::mem_fn(&Handler::resetReleased), this,
180                           std::placeholders::_1));
181         }
182     }
183     catch (const sdbusplus::exception_t& e)
184     {
185         // The button wasn't implemented
186     }
187     try
188     {
189         if (!getService(DBG_HS_DBUS_OBJECT_NAME, debugHostSelectorIface)
190                  .empty())
191         {
192             lg2::info("Registering debug host selector button handler");
193             debugHSButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
194                 bus,
195                 sdbusRule::type::signal() + sdbusRule::member("Released") +
196                     sdbusRule::path(DBG_HS_DBUS_OBJECT_NAME) +
197                     sdbusRule::interface(debugHostSelectorIface),
198                 std::bind(std::mem_fn(&Handler::debugHostSelectorReleased),
199                           this, std::placeholders::_1));
200         }
201     }
202     catch (const sdbusplus::exception_t& e)
203     {
204         // The button wasn't implemented
205     }
206 }
isMultiHost()207 bool Handler::isMultiHost()
208 {
209     if (numberOfChassis() != 1)
210     {
211         return true;
212     }
213     else
214     {
215         return (hostSelectButtonMode);
216     }
217 }
getService(const std::string & path,const std::string & interface) const218 std::string Handler::getService(const std::string& path,
219                                 const std::string& interface) const
220 {
221     auto method = bus.new_method_call(mapperService, mapperObjPath, mapperIface,
222                                       "GetObject");
223     method.append(path, std::vector{interface});
224     try
225     {
226         auto result = bus.call(method);
227         auto objectData =
228             result.unpack<std::map<std::string, std::vector<std::string>>>();
229 
230         return objectData.begin()->first;
231     }
232     catch (const sdbusplus::exception_t& e)
233     {
234         return std::string();
235     }
236 }
getHostSelectorValue()237 size_t Handler::getHostSelectorValue()
238 {
239     auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
240 
241     if (HSService.empty())
242     {
243         lg2::info("Host selector dbus object not available");
244         throw std::invalid_argument("Host selector dbus object not available");
245     }
246 
247     try
248     {
249         auto method = bus.new_method_call(
250             HSService.c_str(), HS_DBUS_OBJECT_NAME, propertyIface, "Get");
251         method.append(hostSelectorIface, "Position");
252         auto result = bus.call(method);
253 
254         auto HSPositionVariant = result.unpack<std::variant<size_t>>();
255 
256         auto position = std::get<size_t>(HSPositionVariant);
257         return position;
258     }
259     catch (const sdbusplus::exception_t& e)
260     {
261         lg2::error("Error reading host selector position: {ERROR}", "ERROR", e);
262         throw;
263     }
264 }
poweredOn(size_t hostNumber) const265 bool Handler::poweredOn(size_t hostNumber) const
266 {
267     auto hostObjectName = HOST_STATE_OBJECT_NAME + std::to_string(hostNumber);
268     auto service = getService(hostObjectName.c_str(), hostIface);
269     auto method = bus.new_method_call(service.c_str(), hostObjectName.c_str(),
270                                       propertyIface, "Get");
271     method.append(hostIface, "CurrentHostState");
272     auto result = bus.call(method);
273 
274     auto state = result.unpack<std::variant<std::string>>();
275 
276     return Host::HostState::Off !=
277            Host::convertHostStateFromString(std::get<std::string>(state));
278 }
279 
handlePowerEvent(PowerEvent powerEventType,const std::string & objectPath,std::chrono::microseconds duration)280 void Handler::handlePowerEvent(PowerEvent powerEventType,
281                                const std::string& objectPath,
282                                std::chrono::microseconds duration)
283 {
284     std::string objPathName;
285     std::string dbusIfaceName;
286     std::string transitionName;
287     std::variant<Host::Transition, Chassis::Transition> transition;
288 
289     size_t hostNumber = 0;
290     std::string hostNumStr =
291         objectPath.substr(std::string(POWER_DBUS_OBJECT_NAME).length());
292     auto isMultiHostSystem = isMultiHost();
293 
294     if (hostSelectButtonMode)
295     {
296         hostNumber = getHostSelectorValue();
297         lg2::info("Multi-host system detected : {POSITION}", "POSITION",
298                   hostNumber);
299 
300         hostNumStr = std::to_string(hostNumber);
301 
302         // ignore  power and reset button events if BMC is selected.
303         if (isMultiHostSystem && (hostNumber == BMC_POSITION) &&
304             (powerEventType != PowerEvent::powerReleased) &&
305             (duration <= LONG_PRESS_TIME_MS))
306         {
307             lg2::info(
308                 "handlePowerEvent : BMC selected on multi-host system. ignoring power and reset button events...");
309             return;
310         }
311     }
312 
313     switch (powerEventType)
314     {
315         case PowerEvent::powerReleased:
316         {
317             for (const auto& iter : multiPwrBtnActConf[stoi(hostNumStr) - 1])
318             {
319                 if (duration > std::chrono::milliseconds(iter.first))
320                 {
321                     dbusIfaceName = chassisIface;
322                     transitionName = "RequestedPowerTransition";
323                     objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
324                     transition = iter.second;
325                 }
326             }
327             break;
328 
329             if (duration <= LONG_PRESS_TIME_MS)
330             {
331                 objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
332                 dbusIfaceName = hostIface;
333                 transitionName = "RequestedHostTransition";
334 
335                 transition = Host::Transition::On;
336 
337                 if (poweredOn(hostNumber))
338                 {
339                     transition = Host::Transition::Off;
340                 }
341                 lg2::info("handlePowerEvent : Handle power button press ");
342                 break;
343             }
344             else
345             {
346                 dbusIfaceName = chassisIface;
347                 transitionName = "RequestedPowerTransition";
348                 objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
349                 transition = Chassis::Transition::Off;
350 
351                 /*  multi host system :
352                         hosts (1 to N) - host shutdown
353                         bmc (0) - sled cycle
354                     single host system :
355                         host(0) - host shutdown
356                 */
357                 if (isMultiHostSystem && (hostNumber == BMC_POSITION))
358                 {
359 #if CHASSIS_SYSTEM_RESET_ENABLED
360                     objPathName = CHASSISSYSTEM_STATE_OBJECT_NAME + hostNumStr;
361                     transition = Chassis::Transition::PowerCycle;
362 #else
363                     return;
364 #endif
365                 }
366                 else if (!poweredOn(hostNumber))
367                 {
368                     lg2::info(
369                         "Power is off so ignoring long power button press");
370                     return;
371                 }
372                 lg2::info("handlePowerEvent : handle long power button press");
373                 break;
374             }
375         }
376         case PowerEvent::resetReleased:
377         {
378             objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
379             dbusIfaceName = hostIface;
380             transitionName = "RequestedHostTransition";
381 
382             if (!poweredOn(hostNumber))
383             {
384                 lg2::info("Power is off so ignoring reset button press");
385                 return;
386             }
387 
388             lg2::info("Handling reset button press");
389 #ifdef ENABLE_RESET_BUTTON_DO_WARM_REBOOT
390             transition = Host::Transition::ForceWarmReboot;
391 #else
392             transition = Host::Transition::Reboot;
393 #endif
394             break;
395         }
396         default:
397         {
398             lg2::error("{EVENT} is invalid power event. skipping...", "EVENT",
399                        powerEventType);
400 
401             return;
402         }
403     }
404     auto service = getService(objPathName.c_str(), dbusIfaceName);
405     auto method = bus.new_method_call(service.c_str(), objPathName.c_str(),
406                                       propertyIface, "Set");
407     method.append(dbusIfaceName, transitionName, transition);
408     bus.call(method);
409 }
410 
powerReleased(sdbusplus::message_t & msg)411 void Handler::powerReleased(sdbusplus::message_t& msg)
412 {
413     try
414     {
415         auto time = msg.unpack<uint64_t>();
416 
417         handlePowerEvent(PowerEvent::powerReleased, msg.get_path(),
418                          std::chrono::microseconds(time));
419     }
420     catch (const sdbusplus::exception_t& e)
421     {
422         lg2::error("Failed power state change on a power button press: {ERROR}",
423                    "ERROR", e);
424     }
425 }
426 
resetReleased(sdbusplus::message_t & msg)427 void Handler::resetReleased(sdbusplus::message_t& msg)
428 {
429     try
430     {
431         // No need to calculate duration, set to 0.
432         handlePowerEvent(PowerEvent::resetReleased, msg.get_path(),
433                          std::chrono::microseconds(0));
434     }
435     catch (const sdbusplus::exception_t& e)
436     {
437         lg2::error("Failed power state change on a reset button press: {ERROR}",
438                    "ERROR", e);
439     }
440 }
441 
idReleased(sdbusplus::message_t &)442 void Handler::idReleased(sdbusplus::message_t& /* msg */)
443 {
444     std::string groupPath{ledGroupBasePath};
445     groupPath += ID_LED_GROUP;
446 
447     auto service = getService(groupPath, ledGroupIface);
448 
449     if (service.empty())
450     {
451         lg2::info("No found {GROUP} during ID button press:", "GROUP",
452                   groupPath);
453         return;
454     }
455 
456     try
457     {
458         auto method = bus.new_method_call(service.c_str(), groupPath.c_str(),
459                                           propertyIface, "Get");
460         method.append(ledGroupIface, "Asserted");
461         auto result = bus.call(method);
462 
463         auto state = result.unpack<std::variant<bool>>();
464 
465         state = !std::get<bool>(state);
466 
467         lg2::info(
468             "Changing ID LED group state on ID LED press, GROUP = {GROUP}, STATE = {STATE}",
469             "GROUP", groupPath, "STATE", std::get<bool>(state));
470 
471         method = bus.new_method_call(service.c_str(), groupPath.c_str(),
472                                      propertyIface, "Set");
473 
474         method.append(ledGroupIface, "Asserted", state);
475         result = bus.call(method);
476     }
477     catch (const sdbusplus::exception_t& e)
478     {
479         lg2::error("Error toggling ID LED group on ID button press: {ERROR}",
480                    "ERROR", e);
481     }
482 }
483 
increaseHostSelectorPosition()484 void Handler::increaseHostSelectorPosition()
485 {
486     try
487     {
488         auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
489 
490         if (HSService.empty())
491         {
492             lg2::error("Host selector service not available");
493             return;
494         }
495 
496         auto method =
497             bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
498                                 phosphor::button::propertyIface, "GetAll");
499         method.append(phosphor::button::hostSelectorIface);
500         auto result = bus.call(method);
501         auto properties = result.unpack<
502             std::unordered_map<std::string, std::variant<size_t>>>();
503 
504         auto maxPosition = std::get<size_t>(properties.at("MaxPosition"));
505         auto position = std::get<size_t>(properties.at("Position"));
506 
507         std::variant<size_t> HSPositionVariant =
508             (position < maxPosition) ? (position + 1) : 0;
509 
510         method = bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
511                                      phosphor::button::propertyIface, "Set");
512         method.append(phosphor::button::hostSelectorIface, "Position");
513 
514         method.append(HSPositionVariant);
515         result = bus.call(method);
516     }
517     catch (const sdbusplus::exception_t& e)
518     {
519         lg2::error("Error modifying host selector position : {ERROR}", "ERROR",
520                    e);
521     }
522 }
523 
debugHostSelectorReleased(sdbusplus::message_t &)524 void Handler::debugHostSelectorReleased(sdbusplus::message_t& /* msg */)
525 {
526     try
527     {
528         increaseHostSelectorPosition();
529     }
530     catch (const sdbusplus::exception_t& e)
531     {
532         lg2::error(
533             "Failed power process debug host selector button press : {ERROR}",
534             "ERROR", e);
535     }
536 }
537 
538 } // namespace button
539 } // namespace phosphor
540