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