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