1 #include <libevdev/libevdev.h>
2 #include <fcntl.h>
3 #include <phosphor-logging/elog.hpp>
4 #include <phosphor-logging/log.hpp>
5 #include <phosphor-logging/elog-errors.hpp>
6 #include "xyz/openbmc_project/Common/error.hpp"
7 #include "gpio_presence.hpp"
8 
9 namespace phosphor
10 {
11 namespace gpio
12 {
13 namespace presence
14 {
15 
16 using namespace phosphor::logging;
17 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
18 
19 constexpr auto INVENTORY_PATH = "/xyz/openbmc_project/inventory";
20 constexpr auto INVENTORY_INTF = "xyz.openbmc_project.Inventory.Manager";
21 
22 constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
23 constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
24 constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
25 
26 std::string getService(const std::string& path,
27                        const std::string& interface,
28                        sdbusplus::bus::bus& bus)
29 {
30     auto mapperCall = bus.new_method_call(MAPPER_BUSNAME,
31                                           MAPPER_PATH,
32                                           MAPPER_INTERFACE,
33                                           "GetObject");
34 
35     mapperCall.append(path);
36     mapperCall.append(std::vector<std::string>({interface}));
37 
38     auto mapperResponseMsg = bus.call(mapperCall);
39     if (mapperResponseMsg.is_method_error())
40     {
41         log<level::ERR>("Error in mapper call to get service name",
42                         entry("PATH=%s", path.c_str()),
43                         entry("INTERFACE=%s", interface.c_str()));
44         elog<InternalFailure>();
45     }
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>(
54             "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 // Populate the file descriptor for passed in device
64 int Presence::openDevice()
65 {
66     using namespace phosphor::logging;
67 
68     auto fd = open(path.c_str(), O_RDONLY | O_NONBLOCK);
69     if (fd < 0)
70     {
71         log<level::ERR>("Failed to open device path",
72                         entry("DEVICEPATH=%s", path.c_str()),
73                         entry("ERRNO=%d", errno));
74         elog<InternalFailure>();
75     }
76     return fd;
77 }
78 
79 // Initializes the event device with the fd
80 void Presence::initEvDev()
81 {
82     if (devicePtr)
83     {
84         // Init can be done only once per device
85         return;
86     }
87 
88     struct libevdev* evdev = nullptr;
89     auto rc = libevdev_new_from_fd((fd)(), &evdev);
90     if (rc < 0)
91     {
92         log<level::ERR>("Failed to initialize evdev");
93         elog<InternalFailure>();
94         return;
95     }
96 
97     // Packing in the unique_ptr
98     devicePtr.reset(evdev);
99 }
100 
101 void Presence::determinePresence()
102 {
103     auto present = false;
104     auto value = static_cast<int>(0);
105     auto fetch_rc = libevdev_fetch_event_value(devicePtr.get(), EV_KEY,
106                     key, &value);
107     if (0 == fetch_rc)
108     {
109         log<level::ERR>("Device does not support event type",
110                         entry("KEYCODE=%d", key));
111         elog<InternalFailure>();
112         return;
113     }
114     if (value > 0)
115     {
116         present = true;
117     }
118 
119     updateInventory(present);
120 }
121 
122 // Callback handler when there is an activity on the FD
123 int Presence::processEvents(sd_event_source* es, int fd,
124                             uint32_t revents, void* userData)
125 {
126     auto presence = static_cast<Presence*>(userData);
127 
128     presence->analyzeEvent();
129     return 0;
130 }
131 
132 
133 // Analyzes the GPIO event
134 void Presence::analyzeEvent()
135 {
136 
137     // Data returned
138     struct input_event ev {};
139     int rc = 0;
140 
141     // While testing, observed that not having a loop here was leading
142     // into events being missed.
143     while (rc >= 0)
144     {
145         // Wait until no more events are available on the device.
146         rc = libevdev_next_event(devicePtr.get(),
147                                  LIBEVDEV_READ_FLAG_NORMAL, &ev);
148         if (rc < 0)
149         {
150             // There was an error waiting for events, mostly that there are no
151             // events to be read.. So continue waiting...
152             return;
153         }
154 
155         if (rc == LIBEVDEV_READ_STATUS_SUCCESS)
156         {
157             if (ev.type == EV_SYN && ev.code == SYN_REPORT)
158             {
159                 continue;
160             }
161             else if (ev.code == key)
162             {
163                 auto present = false;
164                 if (ev.value > 0)
165                 {
166                     present = true;
167                 }
168                 updateInventory(present);
169             }
170         }
171     }
172 
173     return;
174 }
175 
176 Presence::ObjectMap Presence::getObjectMap(bool present)
177 {
178     ObjectMap invObj;
179     InterfaceMap invIntf;
180     PropertyMap invProp;
181 
182     invProp.emplace("Present", present);
183     invProp.emplace("PrettyName", name);
184     invIntf.emplace("xyz.openbmc_project.Inventory.Item",
185                     std::move(invProp));
186     invObj.emplace(std::move(inventory), std::move(invIntf));
187 
188     return invObj;
189 }
190 
191 void Presence::updateInventory(bool present)
192 {
193     ObjectMap invObj = getObjectMap(present);
194 
195     log<level::INFO>("Updating inventory present property",
196                      entry("PRESENT=%d", present),
197                      entry("PATH=%s", inventory));
198 
199     auto invService = getService(INVENTORY_PATH, INVENTORY_INTF, bus);
200 
201     // Update inventory
202     auto invMsg = bus.new_method_call(invService.c_str(),
203                                       INVENTORY_PATH,
204                                       INVENTORY_INTF,
205                                       "Notify");
206     invMsg.append(std::move(invObj));
207     auto invMgrResponseMsg = bus.call(invMsg);
208     if (invMgrResponseMsg.is_method_error())
209     {
210         log<level::ERR>(
211             "Error in inventory manager call to update inventory");
212         elog<InternalFailure>();
213     }
214 }
215 
216 // Attaches the FD to event loop and registers the callback handler
217 void Presence::registerCallback()
218 {
219     decltype(eventSource.get()) sourcePtr = nullptr;
220     auto rc = sd_event_add_io(event.get(), &sourcePtr, (fd)(),
221                               EPOLLIN, callbackHandler, this);
222     eventSource.reset(sourcePtr);
223 
224     if (rc < 0)
225     {
226         log<level::ERR>("Failed to register callback handler",
227                         entry("ERROR=%s", strerror(-rc)));
228         elog<InternalFailure>();
229     }
230 }
231 
232 } // namespace presence
233 } // namespace gpio
234 } // namespace phosphor
235 
236