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