xref: /openbmc/phosphor-buttons/src/button_handler.cpp (revision 33eb55003f19ec95a95bfdf2473a1457535c1ee1)
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 }
getHostNumStr(PowerEvent powerEventType,const std::string & objectPath) const237 std::string Handler::getHostNumStr(PowerEvent powerEventType,
238                                    const std::string& objectPath) const
239 {
240     switch (powerEventType)
241     {
242         case PowerEvent::powerReleased:
243         {
244             return objectPath.substr(
245                 std::string(POWER_DBUS_OBJECT_NAME).length() - 1);
246         }
247 
248         case PowerEvent::resetReleased:
249         {
250             return objectPath.substr(
251                 std::string(RESET_DBUS_OBJECT_NAME).length() - 1);
252         }
253 
254         default:
255         {
256             lg2::error("{EVENT} is invalid power event. skipping...", "EVENT",
257                        powerEventType);
258             throw std::runtime_error("Invalid power event type");
259         }
260     }
261 }
getHostSelectorValue()262 size_t Handler::getHostSelectorValue()
263 {
264     auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
265 
266     if (HSService.empty())
267     {
268         lg2::info("Host selector dbus object not available");
269         throw std::invalid_argument("Host selector dbus object not available");
270     }
271 
272     try
273     {
274         auto method = bus.new_method_call(
275             HSService.c_str(), HS_DBUS_OBJECT_NAME, propertyIface, "Get");
276         method.append(hostSelectorIface, "Position");
277         auto result = bus.call(method);
278 
279         auto HSPositionVariant = result.unpack<std::variant<size_t>>();
280 
281         auto position = std::get<size_t>(HSPositionVariant);
282         return position;
283     }
284     catch (const sdbusplus::exception_t& e)
285     {
286         lg2::error("Error reading host selector position: {ERROR}", "ERROR", e);
287         throw;
288     }
289 }
poweredOn(size_t hostNumber) const290 bool Handler::poweredOn(size_t hostNumber) const
291 {
292     auto hostObjectName = HOST_STATE_OBJECT_NAME + std::to_string(hostNumber);
293     auto service = getService(hostObjectName.c_str(), hostIface);
294     auto method = bus.new_method_call(service.c_str(), hostObjectName.c_str(),
295                                       propertyIface, "Get");
296     method.append(hostIface, "CurrentHostState");
297     auto result = bus.call(method);
298 
299     auto state = result.unpack<std::variant<std::string>>();
300 
301     return Host::HostState::Off !=
302            Host::convertHostStateFromString(std::get<std::string>(state));
303 }
304 
handlePowerEvent(PowerEvent powerEventType,const std::string & objectPath,std::chrono::microseconds duration)305 void Handler::handlePowerEvent(PowerEvent powerEventType,
306                                const std::string& objectPath,
307                                std::chrono::microseconds duration)
308 {
309     std::string objPathName;
310     std::string dbusIfaceName;
311     std::string transitionName;
312     std::variant<Host::Transition, Chassis::Transition> transition;
313 
314     size_t hostNumber = 0;
315     std::string hostNumStr;
316     try
317     {
318         hostNumStr = getHostNumStr(powerEventType, objectPath);
319     }
320     catch (const std::exception& e)
321     {
322         lg2::error(
323             "Failed to parse host number for event '{EVENT}' and path '{PATH}': {ERR}",
324             "EVENT", powerEventType, "PATH", objectPath, "ERR", e.what());
325         return;
326     }
327 
328     auto isMultiHostSystem = isMultiHost();
329 
330     if (hostSelectButtonMode)
331     {
332         hostNumber = getHostSelectorValue();
333         lg2::info("Multi-host system detected : {POSITION}", "POSITION",
334                   hostNumber);
335 
336         hostNumStr = std::to_string(hostNumber);
337 
338         // ignore  power and reset button events if BMC is selected.
339         if (isMultiHostSystem && (hostNumber == BMC_POSITION) &&
340             (powerEventType != PowerEvent::powerReleased) &&
341             (duration <= LONG_PRESS_TIME_MS))
342         {
343             lg2::info(
344                 "handlePowerEvent : BMC selected on multi-host system. ignoring power and reset button events...");
345             return;
346         }
347     }
348 
349     switch (powerEventType)
350     {
351         case PowerEvent::powerReleased:
352         {
353             if (!multiPwrBtnActConf.empty())
354             {
355                 for (const auto& iter :
356                      multiPwrBtnActConf[stoi(hostNumStr) - 1])
357                 {
358                     if (duration > std::chrono::milliseconds(iter.first))
359                     {
360                         dbusIfaceName = chassisIface;
361                         transitionName = "RequestedPowerTransition";
362                         objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
363                         transition = iter.second;
364                     }
365                 }
366                 if (!objPathName.empty())
367                 {
368                     break;
369                 }
370                 // If objPathName is still empty, continue to fallback logic
371             }
372 
373             if (duration <= LONG_PRESS_TIME_MS)
374             {
375                 objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
376                 dbusIfaceName = hostIface;
377                 transitionName = "RequestedHostTransition";
378 
379                 transition = Host::Transition::On;
380 
381                 if (poweredOn(hostNumber))
382                 {
383                     transition = Host::Transition::Off;
384                 }
385                 lg2::info("handlePowerEvent : Handle power button press ");
386                 break;
387             }
388             else
389             {
390                 dbusIfaceName = chassisIface;
391                 transitionName = "RequestedPowerTransition";
392                 objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
393                 transition = Chassis::Transition::Off;
394 
395                 /*  multi host system :
396                         hosts (1 to N) - host shutdown
397                         bmc (0) - sled cycle
398                     single host system :
399                         host(0) - host shutdown
400                 */
401                 if (isMultiHostSystem && (hostNumber == BMC_POSITION))
402                 {
403 #if CHASSIS_SYSTEM_RESET_ENABLED
404                     objPathName = CHASSISSYSTEM_STATE_OBJECT_NAME + hostNumStr;
405                     transition = Chassis::Transition::PowerCycle;
406 #else
407                     return;
408 #endif
409                 }
410                 else if (!poweredOn(hostNumber))
411                 {
412                     lg2::info(
413                         "Power is off so ignoring long power button press");
414                     return;
415                 }
416                 lg2::info("handlePowerEvent : handle long power button press");
417                 break;
418             }
419         }
420         case PowerEvent::resetReleased:
421         {
422             objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
423             dbusIfaceName = hostIface;
424             transitionName = "RequestedHostTransition";
425 
426             if (!poweredOn(hostNumber))
427             {
428                 lg2::info("Power is off so ignoring reset button press");
429                 return;
430             }
431 
432             lg2::info("Handling reset button press");
433 #if ENABLE_RESET_BUTTON_DO_WARM_REBOOT
434             transition = Host::Transition::ForceWarmReboot;
435 #else
436             transition = Host::Transition::Reboot;
437 #endif
438             break;
439         }
440         default:
441         {
442             lg2::error("{EVENT} is invalid power event. skipping...", "EVENT",
443                        powerEventType);
444 
445             return;
446         }
447     }
448     auto service = getService(objPathName.c_str(), dbusIfaceName);
449     auto method = bus.new_method_call(service.c_str(), objPathName.c_str(),
450                                       propertyIface, "Set");
451     method.append(dbusIfaceName, transitionName, transition);
452     bus.call(method);
453 }
454 
powerReleased(sdbusplus::message_t & msg)455 void Handler::powerReleased(sdbusplus::message_t& msg)
456 {
457     try
458     {
459         auto time = msg.unpack<uint64_t>();
460 
461         handlePowerEvent(PowerEvent::powerReleased, msg.get_path(),
462                          std::chrono::microseconds(time));
463     }
464     catch (const sdbusplus::exception_t& e)
465     {
466         lg2::error("Failed power state change on a power button press: {ERROR}",
467                    "ERROR", e);
468     }
469 }
470 
resetReleased(sdbusplus::message_t & msg)471 void Handler::resetReleased(sdbusplus::message_t& msg)
472 {
473     try
474     {
475         // No need to calculate duration, set to 0.
476         handlePowerEvent(PowerEvent::resetReleased, msg.get_path(),
477                          std::chrono::microseconds(0));
478     }
479     catch (const sdbusplus::exception_t& e)
480     {
481         lg2::error("Failed power state change on a reset button press: {ERROR}",
482                    "ERROR", e);
483     }
484 }
485 
idReleased(sdbusplus::message_t &)486 void Handler::idReleased(sdbusplus::message_t& /* msg */)
487 {
488     std::string groupPath{ledGroupBasePath};
489     groupPath += ID_LED_GROUP;
490 
491     auto service = getService(groupPath, ledGroupIface);
492 
493     if (service.empty())
494     {
495         lg2::info("No found {GROUP} during ID button press:", "GROUP",
496                   groupPath);
497         return;
498     }
499 
500     try
501     {
502         auto method = bus.new_method_call(service.c_str(), groupPath.c_str(),
503                                           propertyIface, "Get");
504         method.append(ledGroupIface, "Asserted");
505         auto result = bus.call(method);
506 
507         auto state = result.unpack<std::variant<bool>>();
508 
509         state = !std::get<bool>(state);
510 
511         lg2::info(
512             "Changing ID LED group state on ID LED press, GROUP = {GROUP}, STATE = {STATE}",
513             "GROUP", groupPath, "STATE", std::get<bool>(state));
514 
515         method = bus.new_method_call(service.c_str(), groupPath.c_str(),
516                                      propertyIface, "Set");
517 
518         method.append(ledGroupIface, "Asserted", state);
519         result = bus.call(method);
520     }
521     catch (const sdbusplus::exception_t& e)
522     {
523         lg2::error("Error toggling ID LED group on ID button press: {ERROR}",
524                    "ERROR", e);
525     }
526 }
527 
increaseHostSelectorPosition()528 void Handler::increaseHostSelectorPosition()
529 {
530     try
531     {
532         auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
533 
534         if (HSService.empty())
535         {
536             lg2::error("Host selector service not available");
537             return;
538         }
539 
540         auto method =
541             bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
542                                 phosphor::button::propertyIface, "GetAll");
543         method.append(phosphor::button::hostSelectorIface);
544         auto result = bus.call(method);
545         auto properties = result.unpack<
546             std::unordered_map<std::string, std::variant<size_t>>>();
547 
548         auto maxPosition = std::get<size_t>(properties.at("MaxPosition"));
549         auto position = std::get<size_t>(properties.at("Position"));
550 
551         std::variant<size_t> HSPositionVariant =
552             (position < maxPosition) ? (position + 1) : 0;
553 
554         method = bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
555                                      phosphor::button::propertyIface, "Set");
556         method.append(phosphor::button::hostSelectorIface, "Position");
557 
558         method.append(HSPositionVariant);
559         result = bus.call(method);
560     }
561     catch (const sdbusplus::exception_t& e)
562     {
563         lg2::error("Error modifying host selector position : {ERROR}", "ERROR",
564                    e);
565     }
566 }
567 
debugHostSelectorReleased(sdbusplus::message_t &)568 void Handler::debugHostSelectorReleased(sdbusplus::message_t& /* msg */)
569 {
570     try
571     {
572         increaseHostSelectorPosition();
573     }
574     catch (const sdbusplus::exception_t& e)
575     {
576         lg2::error(
577             "Failed power process debug host selector button press : {ERROR}",
578             "ERROR", e);
579     }
580 }
581 
582 } // namespace button
583 } // namespace phosphor
584