xref: /openbmc/phosphor-gpio-monitor/presence/gpio_presence.cpp (revision 75ff16717de9a7b3beeda9f3cace9456cad98156)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 
4 #include "gpio_presence.hpp"
5 
6 #include "xyz/openbmc_project/Common/error.hpp"
7 
8 #include <fcntl.h>
9 #include <libevdev/libevdev.h>
10 
11 #include <phosphor-logging/elog-errors.hpp>
12 #include <phosphor-logging/elog.hpp>
13 #include <phosphor-logging/lg2.hpp>
14 
15 #include <fstream>
16 
17 namespace phosphor
18 {
19 namespace gpio
20 {
21 namespace presence
22 {
23 
24 using namespace phosphor::logging;
25 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
26 
27 constexpr auto INVENTORY_PATH = "/xyz/openbmc_project/inventory";
28 constexpr auto INVENTORY_INTF = "xyz.openbmc_project.Inventory.Manager";
29 
30 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
31 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
32 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
33 
getService(const std::string & path,const std::string & interface,sdbusplus::bus_t & bus)34 std::string getService(const std::string& path, const std::string& interface,
35                        sdbusplus::bus_t& bus)
36 {
37     auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
38                                           MAPPER_INTERFACE, "GetObject");
39 
40     mapperCall.append(path);
41     mapperCall.append(std::vector<std::string>({interface}));
42 
43     std::map<std::string, std::vector<std::string>> mapperResponse;
44     try
45     {
46         auto mapperResponseMsg = bus.call(mapperCall);
47         mapperResponseMsg.read(mapperResponse);
48     }
49     catch (const sdbusplus::exception_t& e)
50     {
51         lg2::error(
52             "Error in mapper call to get service name, path: {PATH}, interface: {INTERFACE}, error: {ERROR}",
53             "PATH", path, "INTERFACE", interface, "ERROR", e);
54         elog<InternalFailure>();
55     }
56 
57     return mapperResponse.begin()->first;
58 }
59 
determinePresence()60 void Presence::determinePresence()
61 {
62     auto present = false;
63     auto value = static_cast<int>(0);
64     auto fetch_rc =
65         libevdev_fetch_event_value(devicePtr.get(), EV_KEY, key, &value);
66     if (0 == fetch_rc)
67     {
68         lg2::error("Device does not support event type, key: {KEYCODE}",
69                    "KEYCODE", key);
70         elog<InternalFailure>();
71         return;
72     }
73     if (value > 0)
74     {
75         present = true;
76     }
77 
78     updateInventory(present);
79 }
80 
81 // Callback handler when there is an activity on the FD
processEvents(sd_event_source *,int,uint32_t,void * userData)82 int Presence::processEvents(sd_event_source*, int, uint32_t, void* userData)
83 {
84     auto presence = static_cast<Presence*>(userData);
85 
86     presence->analyzeEvent();
87     return 0;
88 }
89 
90 // Analyzes the GPIO event
analyzeEvent()91 void Presence::analyzeEvent()
92 {
93     // Data returned
94     struct input_event ev{};
95     int rc = 0;
96 
97     // While testing, observed that not having a loop here was leading
98     // into events being missed.
99     while (rc >= 0)
100     {
101         // Wait until no more events are available on the device.
102         rc = libevdev_next_event(devicePtr.get(), LIBEVDEV_READ_FLAG_NORMAL,
103                                  &ev);
104         if (rc < 0)
105         {
106             // There was an error waiting for events, mostly that there are no
107             // events to be read.. So continue waiting...
108             return;
109         }
110 
111         if (rc == LIBEVDEV_READ_STATUS_SUCCESS)
112         {
113             if (ev.type == EV_SYN && ev.code == SYN_REPORT)
114             {
115                 continue;
116             }
117             else if (ev.code == key)
118             {
119                 auto present = false;
120                 if (ev.value > 0)
121                 {
122                     present = true;
123                     std::this_thread::sleep_for(
124                         std::chrono::milliseconds(delay));
125                     bindOrUnbindDrivers(present);
126                     updateInventory(present);
127                 }
128                 else
129                 {
130                     updateInventory(present);
131                     bindOrUnbindDrivers(present);
132                 }
133             }
134         }
135     }
136 
137     return;
138 }
139 
getObjectMap(bool present)140 Presence::ObjectMap Presence::getObjectMap(bool present)
141 {
142     ObjectMap invObj;
143     InterfaceMap invIntf;
144     PropertyMap invProp;
145 
146     invProp.emplace("Present", present);
147     invProp.emplace("PrettyName", name);
148     invIntf.emplace("xyz.openbmc_project.Inventory.Item", std::move(invProp));
149     // Add any extra interfaces we want to associate with the inventory item
150     for (auto& iface : ifaces)
151     {
152         invIntf.emplace(iface, PropertyMap());
153     }
154     invObj.emplace(std::move(inventory), std::move(invIntf));
155 
156     return invObj;
157 }
158 
updateInventory(bool present)159 void Presence::updateInventory(bool present)
160 {
161     ObjectMap invObj = getObjectMap(present);
162 
163     lg2::info(
164         "Updating inventory present property value to {PRESENT}, path: {PATH}",
165         "PRESENT", present, "PATH", inventory);
166 
167     auto invService = getService(INVENTORY_PATH, INVENTORY_INTF, bus);
168 
169     // Update inventory
170     auto invMsg = bus.new_method_call(invService.c_str(), INVENTORY_PATH,
171                                       INVENTORY_INTF, "Notify");
172     invMsg.append(std::move(invObj));
173     try
174     {
175         auto invMgrResponseMsg = bus.call(invMsg);
176     }
177     catch (const sdbusplus::exception_t& e)
178     {
179         lg2::error(
180             "Error in inventory manager call to update inventory: {ERROR}",
181             "ERROR", e);
182         elog<InternalFailure>();
183     }
184 }
185 
bindOrUnbindDrivers(bool present)186 void Presence::bindOrUnbindDrivers(bool present)
187 {
188     auto action = (present) ? "bind" : "unbind";
189 
190     for (auto& driver : drivers)
191     {
192         auto path = std::get<pathField>(driver) / action;
193         auto device = std::get<deviceField>(driver);
194 
195         if (present)
196         {
197             lg2::info("Binding a {DEVICE} driver: {PATH}", "DEVICE", device,
198                       "PATH", path);
199         }
200         else
201         {
202             lg2::info("Unbinding a {DEVICE} driver: {PATH}", "DEVICE", device,
203                       "PATH", path);
204         }
205 
206         std::ofstream file;
207 
208         file.exceptions(std::ofstream::failbit | std::ofstream::badbit |
209                         std::ofstream::eofbit);
210 
211         try
212         {
213             file.open(path);
214             file << device;
215             file.close();
216         }
217         catch (const std::exception& e)
218         {
219             lg2::error(
220                 "Failed binding or unbinding a {DEVICE} after a card was removed or added, path: {PATH}, error: {ERROR}",
221                 "DEVICE", device, "PATH", path, "ERROR", e);
222         }
223     }
224 }
225 
226 } // namespace presence
227 } // namespace gpio
228 } // namespace phosphor
229