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