1 #include "button_handler.hpp"
2
3 #include "config.hpp"
4 #include "power_button_profile_factory.hpp"
5
6 #include <phosphor-logging/lg2.hpp>
7 #include <xyz/openbmc_project/State/Chassis/server.hpp>
8 #include <xyz/openbmc_project/State/Host/server.hpp>
9 namespace phosphor
10 {
11 namespace button
12 {
13
14 namespace sdbusRule = sdbusplus::bus::match::rules;
15 using namespace sdbusplus::xyz::openbmc_project::State::server;
16
17 constexpr auto chassisIface = "xyz.openbmc_project.State.Chassis";
18 constexpr auto hostIface = "xyz.openbmc_project.State.Host";
19 constexpr auto powerButtonIface = "xyz.openbmc_project.Chassis.Buttons.Power";
20 constexpr auto idButtonIface = "xyz.openbmc_project.Chassis.Buttons.ID";
21 constexpr auto resetButtonIface = "xyz.openbmc_project.Chassis.Buttons.Reset";
22 constexpr auto ledGroupIface = "xyz.openbmc_project.Led.Group";
23 constexpr auto ledGroupBasePath = "/xyz/openbmc_project/led/groups/";
24 constexpr auto hostSelectorIface =
25 "xyz.openbmc_project.Chassis.Buttons.HostSelector";
26 constexpr auto debugHostSelectorIface =
27 "xyz.openbmc_project.Chassis.Buttons.Button";
28
29 constexpr auto propertyIface = "org.freedesktop.DBus.Properties";
30 constexpr auto mapperIface = "xyz.openbmc_project.ObjectMapper";
31
32 constexpr auto mapperObjPath = "/xyz/openbmc_project/object_mapper";
33 constexpr auto mapperService = "xyz.openbmc_project.ObjectMapper";
34 constexpr auto BMC_POSITION = 0;
35
Handler(sdbusplus::bus_t & bus)36 Handler::Handler(sdbusplus::bus_t& bus) : bus(bus)
37 {
38 try
39 {
40 if (!getService(POWER_DBUS_OBJECT_NAME, powerButtonIface).empty())
41 {
42 lg2::info("Starting power button handler");
43
44 // Check for a custom handler
45 powerButtonProfile =
46 PowerButtonProfileFactory::instance().createProfile(bus);
47
48 if (!powerButtonProfile)
49 {
50 powerButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
51 bus,
52 sdbusRule::type::signal() + sdbusRule::member("Released") +
53 sdbusRule::path(POWER_DBUS_OBJECT_NAME) +
54 sdbusRule::interface(powerButtonIface),
55 std::bind(std::mem_fn(&Handler::powerReleased), this,
56 std::placeholders::_1));
57 }
58 }
59 }
60 catch (const sdbusplus::exception_t& e)
61 {
62 // The button wasn't implemented
63 }
64
65 try
66 {
67 if (!getService(ID_DBUS_OBJECT_NAME, idButtonIface).empty())
68 {
69 lg2::info("Registering ID button handler");
70 idButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
71 bus,
72 sdbusRule::type::signal() + sdbusRule::member("Released") +
73 sdbusRule::path(ID_DBUS_OBJECT_NAME) +
74 sdbusRule::interface(idButtonIface),
75 std::bind(std::mem_fn(&Handler::idReleased), this,
76 std::placeholders::_1));
77 }
78 }
79 catch (const sdbusplus::exception_t& e)
80 {
81 // The button wasn't implemented
82 }
83
84 try
85 {
86 if (!getService(RESET_DBUS_OBJECT_NAME, resetButtonIface).empty())
87 {
88 lg2::info("Registering reset button handler");
89 resetButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
90 bus,
91 sdbusRule::type::signal() + sdbusRule::member("Released") +
92 sdbusRule::path(RESET_DBUS_OBJECT_NAME) +
93 sdbusRule::interface(resetButtonIface),
94 std::bind(std::mem_fn(&Handler::resetReleased), this,
95 std::placeholders::_1));
96 }
97 }
98 catch (const sdbusplus::exception_t& e)
99 {
100 // The button wasn't implemented
101 }
102 try
103 {
104 if (!getService(DBG_HS_DBUS_OBJECT_NAME, debugHostSelectorIface)
105 .empty())
106 {
107 lg2::info("Registering debug host selector button handler");
108 debugHSButtonReleased = std::make_unique<sdbusplus::bus::match_t>(
109 bus,
110 sdbusRule::type::signal() + sdbusRule::member("Released") +
111 sdbusRule::path(DBG_HS_DBUS_OBJECT_NAME) +
112 sdbusRule::interface(debugHostSelectorIface),
113 std::bind(std::mem_fn(&Handler::debugHostSelectorReleased),
114 this, std::placeholders::_1));
115 }
116 }
117 catch (const sdbusplus::exception_t& e)
118 {
119 // The button wasn't implemented
120 }
121 }
isMultiHost()122 bool Handler::isMultiHost()
123 {
124 // return true in case host selector object is available
125 return (!getService(HS_DBUS_OBJECT_NAME, hostSelectorIface).empty());
126 }
getService(const std::string & path,const std::string & interface) const127 std::string Handler::getService(const std::string& path,
128 const std::string& interface) const
129 {
130 auto method = bus.new_method_call(mapperService, mapperObjPath, mapperIface,
131 "GetObject");
132 method.append(path, std::vector{interface});
133 try
134 {
135 auto result = bus.call(method);
136 std::map<std::string, std::vector<std::string>> objectData;
137 result.read(objectData);
138 return objectData.begin()->first;
139 }
140 catch (const sdbusplus::exception_t& e)
141 {
142 return std::string();
143 }
144 }
getHostSelectorValue()145 size_t Handler::getHostSelectorValue()
146 {
147 auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
148
149 if (HSService.empty())
150 {
151 lg2::info("Host selector dbus object not available");
152 throw std::invalid_argument("Host selector dbus object not available");
153 }
154
155 try
156 {
157 auto method = bus.new_method_call(
158 HSService.c_str(), HS_DBUS_OBJECT_NAME, propertyIface, "Get");
159 method.append(hostSelectorIface, "Position");
160 auto result = bus.call(method);
161
162 std::variant<size_t> HSPositionVariant;
163 result.read(HSPositionVariant);
164
165 auto position = std::get<size_t>(HSPositionVariant);
166 return position;
167 }
168 catch (const sdbusplus::exception_t& e)
169 {
170 lg2::error("Error reading host selector position: {ERROR}", "ERROR", e);
171 throw;
172 }
173 }
poweredOn(size_t hostNumber) const174 bool Handler::poweredOn(size_t hostNumber) const
175 {
176 auto hostObjectName = HOST_STATE_OBJECT_NAME + std::to_string(hostNumber);
177 auto service = getService(hostObjectName.c_str(), hostIface);
178 auto method = bus.new_method_call(service.c_str(), hostObjectName.c_str(),
179 propertyIface, "Get");
180 method.append(hostIface, "CurrentHostState");
181 auto result = bus.call(method);
182
183 std::variant<std::string> state;
184 result.read(state);
185
186 return Host::HostState::Off !=
187 Host::convertHostStateFromString(std::get<std::string>(state));
188 }
189
handlePowerEvent(PowerEvent powerEventType,std::chrono::microseconds duration)190 void Handler::handlePowerEvent(PowerEvent powerEventType,
191 std::chrono::microseconds duration)
192 {
193 std::string objPathName;
194 std::string dbusIfaceName;
195 std::string transitionName;
196 std::variant<Host::Transition, Chassis::Transition> transition;
197
198 size_t hostNumber = 0;
199 auto isMultiHostSystem = isMultiHost();
200 if (isMultiHostSystem)
201 {
202 hostNumber = getHostSelectorValue();
203 lg2::info("Multi-host system detected : {POSITION}", "POSITION",
204 hostNumber);
205 }
206
207 std::string hostNumStr = std::to_string(hostNumber);
208
209 // ignore power and reset button events if BMC is selected.
210 if (isMultiHostSystem && (hostNumber == BMC_POSITION) &&
211 (powerEventType != PowerEvent::powerReleased) &&
212 (duration <= LONG_PRESS_TIME_MS))
213 {
214 lg2::info(
215 "handlePowerEvent : BMC selected on multi-host system. ignoring power and reset button events...");
216 return;
217 }
218
219 switch (powerEventType)
220 {
221 case PowerEvent::powerReleased:
222 {
223 if (duration <= LONG_PRESS_TIME_MS)
224 {
225 objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
226 dbusIfaceName = hostIface;
227 transitionName = "RequestedHostTransition";
228
229 transition = Host::Transition::On;
230
231 if (poweredOn(hostNumber))
232 {
233 transition = Host::Transition::Off;
234 }
235 lg2::info("handlePowerEvent : Handle power button press ");
236
237 break;
238 }
239 else
240 {
241 dbusIfaceName = chassisIface;
242 transitionName = "RequestedPowerTransition";
243 objPathName = CHASSIS_STATE_OBJECT_NAME + hostNumStr;
244 transition = Chassis::Transition::Off;
245
246 /* multi host system :
247 hosts (1 to N) - host shutdown
248 bmc (0) - sled cycle
249 single host system :
250 host(0) - host shutdown
251 */
252 if (isMultiHostSystem && (hostNumber == BMC_POSITION))
253 {
254 #if CHASSIS_SYSTEM_RESET_ENABLED
255 objPathName = CHASSISSYSTEM_STATE_OBJECT_NAME + hostNumStr;
256 transition = Chassis::Transition::PowerCycle;
257 #else
258 return;
259 #endif
260 }
261 else if (!poweredOn(hostNumber))
262 {
263 lg2::info(
264 "Power is off so ignoring long power button press");
265 return;
266 }
267 lg2::info("handlePowerEvent : handle long power button press");
268 break;
269 }
270 }
271 case PowerEvent::resetReleased:
272 {
273 objPathName = HOST_STATE_OBJECT_NAME + hostNumStr;
274 dbusIfaceName = hostIface;
275 transitionName = "RequestedHostTransition";
276
277 if (!poweredOn(hostNumber))
278 {
279 lg2::info("Power is off so ignoring reset button press");
280 return;
281 }
282
283 lg2::info("Handling reset button press");
284 #ifdef ENABLE_RESET_BUTTON_DO_WARM_REBOOT
285 transition = Host::Transition::ForceWarmReboot;
286 #else
287 transition = Host::Transition::Reboot;
288 #endif
289 break;
290 }
291 default:
292 {
293 lg2::error("{EVENT} is invalid power event. skipping...", "EVENT",
294 powerEventType);
295
296 return;
297 }
298 }
299 auto service = getService(objPathName.c_str(), dbusIfaceName);
300 auto method = bus.new_method_call(service.c_str(), objPathName.c_str(),
301 propertyIface, "Set");
302 method.append(dbusIfaceName, transitionName, transition);
303 bus.call(method);
304 }
powerReleased(sdbusplus::message_t & msg)305 void Handler::powerReleased(sdbusplus::message_t& msg)
306 {
307 try
308 {
309 uint64_t time;
310 msg.read(time);
311
312 handlePowerEvent(PowerEvent::powerReleased,
313 std::chrono::microseconds(time));
314 }
315 catch (const sdbusplus::exception_t& e)
316 {
317 lg2::error("Failed power state change on a power button press: {ERROR}",
318 "ERROR", e);
319 }
320 }
321
resetReleased(sdbusplus::message_t &)322 void Handler::resetReleased(sdbusplus::message_t& /* msg */)
323 {
324 try
325 {
326 // No need to calculate duration, set to 0.
327 handlePowerEvent(PowerEvent::resetReleased,
328 std::chrono::microseconds(0));
329 }
330 catch (const sdbusplus::exception_t& e)
331 {
332 lg2::error("Failed power state change on a reset button press: {ERROR}",
333 "ERROR", e);
334 }
335 }
336
idReleased(sdbusplus::message_t &)337 void Handler::idReleased(sdbusplus::message_t& /* msg */)
338 {
339 std::string groupPath{ledGroupBasePath};
340 groupPath += ID_LED_GROUP;
341
342 auto service = getService(groupPath, ledGroupIface);
343
344 if (service.empty())
345 {
346 lg2::info("No found {GROUP} during ID button press:", "GROUP",
347 groupPath);
348 return;
349 }
350
351 try
352 {
353 auto method = bus.new_method_call(service.c_str(), groupPath.c_str(),
354 propertyIface, "Get");
355 method.append(ledGroupIface, "Asserted");
356 auto result = bus.call(method);
357
358 std::variant<bool> state;
359 result.read(state);
360
361 state = !std::get<bool>(state);
362
363 lg2::info(
364 "Changing ID LED group state on ID LED press, GROUP = {GROUP}, STATE = {STATE}",
365 "GROUP", groupPath, "STATE", std::get<bool>(state));
366
367 method = bus.new_method_call(service.c_str(), groupPath.c_str(),
368 propertyIface, "Set");
369
370 method.append(ledGroupIface, "Asserted", state);
371 result = bus.call(method);
372 }
373 catch (const sdbusplus::exception_t& e)
374 {
375 lg2::error("Error toggling ID LED group on ID button press: {ERROR}",
376 "ERROR", e);
377 }
378 }
379
increaseHostSelectorPosition()380 void Handler::increaseHostSelectorPosition()
381 {
382 try
383 {
384 auto HSService = getService(HS_DBUS_OBJECT_NAME, hostSelectorIface);
385
386 if (HSService.empty())
387 {
388 lg2::error("Host selector service not available");
389 return;
390 }
391
392 auto method =
393 bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
394 phosphor::button::propertyIface, "GetAll");
395 method.append(phosphor::button::hostSelectorIface);
396 auto result = bus.call(method);
397 std::unordered_map<std::string, std::variant<size_t>> properties;
398 result.read(properties);
399
400 auto maxPosition = std::get<size_t>(properties.at("MaxPosition"));
401 auto position = std::get<size_t>(properties.at("Position"));
402
403 std::variant<size_t> HSPositionVariant =
404 (position < maxPosition) ? (position + 1) : 0;
405
406 method = bus.new_method_call(HSService.c_str(), HS_DBUS_OBJECT_NAME,
407 phosphor::button::propertyIface, "Set");
408 method.append(phosphor::button::hostSelectorIface, "Position");
409
410 method.append(HSPositionVariant);
411 result = bus.call(method);
412 }
413 catch (const sdbusplus::exception_t& e)
414 {
415 lg2::error("Error modifying host selector position : {ERROR}", "ERROR",
416 e);
417 }
418 }
419
debugHostSelectorReleased(sdbusplus::message_t &)420 void Handler::debugHostSelectorReleased(sdbusplus::message_t& /* msg */)
421 {
422 try
423 {
424 increaseHostSelectorPosition();
425 }
426 catch (const sdbusplus::exception_t& e)
427 {
428 lg2::error(
429 "Failed power process debug host selector button press : {ERROR}",
430 "ERROR", e);
431 }
432 }
433
434 } // namespace button
435 } // namespace phosphor
436