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