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