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