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