Use libevdev for monitoring GPIO state change

Change-Id: I962af3034586f027e6ba74387dcda6ef0cf3672e
Signed-off-by: Vishwanatha Subbanna <vishwa@linux.vnet.ibm.com>
diff --git a/Makefile.am b/Makefile.am
index 93273d8..3e55c5b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -9,9 +9,12 @@
 
 phosphor_gpio_monitor_LDFLAGS = $(SYSTEMD_LIBS) \
                                 $(SDBUSPLUS_LIBS) \
-                                $(PHOSPHOR_LOGGING_LIBS)
-phosphor_gpio_monitor_CFLAGS = $(SYSTEMD_CFLAGS) \
-                               $(SDBUSPLUS_CFLAGS) \
-                               $(PHOSPHOR_LOGGING_CFLAGS)
+                                $(PHOSPHOR_LOGGING_LIBS) \
+                                $(LIBEVDEV_LIBS)
+
+phosphor_gpio_monitor_CXXFLAGS = $(SYSTEMD_CFLAGS) \
+                                 $(SDBUSPLUS_CFLAGS) \
+                                 $(PHOSPHOR_LOGGING_CFLAGS) \
+                                 $(LIBEVDEV_CFLAGS)
 
 SUBDIRS = test
diff --git a/configure.ac b/configure.ac
index 694d2b3..4a05a22 100644
--- a/configure.ac
+++ b/configure.ac
@@ -22,6 +22,7 @@
 PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging],, [AC_MSG_ERROR([Could not find phosphor-logging...openbmc/phosphor-logging package required])])
 PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus],, [AC_MSG_ERROR([Could not find sdbusplus...openbmc/sdbusplus package required])])
 PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces],, [AC_MSG_ERROR([Could not find phosphor-dbus-interfaces...openbmc/phosphor-dbus-interfaces package required])])
+PKG_CHECK_MODULES([LIBEVDEV], [libevdev],, [AC_MSG_ERROR([Could not find libevdev...libevdev package required])])
 
 # Check/set gtest specific functions.
 AX_PTHREAD([GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=1"],[GTEST_CPPFLAGS="-DGTEST_HAS_PTHREAD=0"])
diff --git a/monitor.cpp b/monitor.cpp
index 99eed03..dfd9987 100644
--- a/monitor.cpp
+++ b/monitor.cpp
@@ -62,37 +62,96 @@
     }
 }
 
+// Initializes the event device with the fd
+void Monitor::initEvDev()
+{
+    if (device)
+    {
+        // Init can be done only once per device
+        return;
+    }
+
+    struct libevdev* evdev = nullptr;
+    auto rc = libevdev_new_from_fd((fd)(), &evdev);
+    if (rc < 0)
+    {
+        log<level::ERR>("Failed to initialize evdev");
+        throw std::runtime_error("Failed to initialize evdev");
+    }
+
+    // Packing in the unique_ptr
+    device.reset(evdev);
+    evdev = nullptr;
+}
+
 // Callback handler when there is an activity on the FD
 int Monitor::processEvents(sd_event_source* es, int fd,
                            uint32_t revents, void* userData)
 {
     log<level::INFO>("GPIO line altered");
     auto monitor = static_cast<Monitor*>(userData);
-    // TODO : Need a way to check if the GPIO state change is what we wanted
-    return monitor->analyzeEvent();
+
+    // Initialize libevdev for this. Doing it here enables
+    // gtest to use this infrastructure on arbitrary device
+    // than /dev/input/
+    monitor->initEvDev();
+    monitor->analyzeEvent();
+    return 0;
 }
 
 // Analyzes the GPIO event
-int Monitor::analyzeEvent()
+void Monitor::analyzeEvent()
 {
-    if(!target.empty())
+    // Data returned
+    struct input_event ev{};
+    int rc = 0;
+
+    // While testing, observed that not having a loop here was leading
+    // into events being missed.
+    while (rc >= 0)
     {
-        auto bus = sdbusplus::bus::new_default();
-        auto method = bus.new_method_call(SYSTEMD_SERVICE,
-                                          SYSTEMD_ROOT,
-                                          SYSTEMD_INTERFACE,
-                                          "StartUnit");
-        method.append(target);
-        method.append("replace");
+        // Wait until no more events are available on the device.
+        rc = libevdev_next_event(device.get(),
+                                 LIBEVDEV_READ_FLAG_NORMAL, &ev);
+        if (rc < 0)
+        {
+            // There was an error waiting for events, mostly that there are no
+            // events to be read.. So continue waiting...
+            return;
+        };
 
-        // If there is any error, an exception would be thrown from here.
-        bus.call_noreply(method);
-    }
+        if (rc == LIBEVDEV_READ_STATUS_SUCCESS)
+        {
+            if (ev.type == EV_SYN && ev.code == SYN_REPORT)
+            {
+                continue;
+            }
+            else if (ev.code == key && ev.value == polarity)
+            {
+                // If the code/value is what we are interested in, declare done.
+                // User supplied systemd unit
+                if (!target.empty())
+                {
+                    auto bus = sdbusplus::bus::new_default();
+                    auto method = bus.new_method_call(SYSTEMD_SERVICE,
+                                                      SYSTEMD_ROOT,
+                                                      SYSTEMD_INTERFACE,
+                                                      "StartUnit");
+                    method.append(target);
+                    method.append("replace");
 
-    // This marks the completion of handling the checkstop and app can exit
-    complete = true;
+                    bus.call_noreply(method);
+                }
 
-    return 0;
+                // This marks the completion of handling the gpio assertion
+                // and the app can exit
+                complete = true;
+                return;
+            }
+        }
+    };
+
+    return;
 }
 
 } // namespace gpio
diff --git a/monitor.hpp b/monitor.hpp
index 34803c1..68bf447 100644
--- a/monitor.hpp
+++ b/monitor.hpp
@@ -3,6 +3,7 @@
 #include <unistd.h>
 #include <string>
 #include <linux/input.h>
+#include <libevdev/libevdev.h>
 #include <systemd/sd-event.h>
 #include <sdbusplus/bus.hpp>
 #include "file.hpp"
@@ -31,6 +32,16 @@
 };
 using EventSourcePtr = std::unique_ptr<sd_event_source, EventSourceDeleter>;
 
+/* Need a custom deleter for freeing up evdev struct */
+struct EvdevDeleter
+{
+    void operator()(struct libevdev* device) const
+    {
+        libevdev_free(device);
+    }
+};
+using EvdevPtr = std::unique_ptr<struct libevdev, EvdevDeleter>;
+
 /** @class Monitor
  *  @brief Responsible for catching GPIO state change
  *  condition and taking actions
@@ -118,6 +129,9 @@
         /** @brief File descriptor manager */
         FileDescriptor fd;
 
+        /** event structure */
+        EvdevPtr device;
+
         /** @brief Completion indicator */
         bool complete = false;
 
@@ -127,11 +141,11 @@
         /** @brief attaches FD to events and sets up callback handler */
         void registerCallback();
 
-        /** @brief Analyzes the GPIO event and starts configured target
-         *
-         *  @return - For now, returns zero
-         */
-        int analyzeEvent();
+        /** @brief Analyzes the GPIO event and starts configured target */
+        void analyzeEvent();
+
+        /** @brief Initializes evdev handle with the fd */
+        void initEvDev();
 };
 
 } // namespace gpio
diff --git a/test/Makefile.am b/test/Makefile.am
index db82844..5bd15e7 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,12 +1,13 @@
 AM_CPPFLAGS = -I$(top_srcdir)
+AM_CXXFLAGS = $(LIBEVDEV_CFLAGS)
 
 # Run all 'check' test programs
 TESTS = $(check_PROGRAMS)
 
 # # Build/add utest to test suite
 check_PROGRAMS = utest
-utest_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS) $(SDBUSPLUS_CFLAGS) $(PHOSPHOR_DBUS_INTERFACES_CFLAGS)
+utest_CPPFLAGS = -Igtest $(GTEST_CPPFLAGS) $(AM_CPPFLAGS) $(PHOSPHOR_LOGGING_CFLAGS) $(LIBEVDEV_CFLAGS)
 utest_CXXFLAGS = $(PTHREAD_CFLAGS)
-utest_LDFLAGS = -lgtest_main -lgtest $(PTHREAD_LIBS) $(OESDK_TESTCASE_FLAGS) $(SYSTEMD_LIBS) $(SDBUSPLUS_LIBS) $(PHOSPHOR_DBUS_INTERFACES_LIBS)
+utest_LDFLAGS = -lgtest_main -lgtest $(PTHREAD_LIBS) $(OESDK_TESTCASE_FLAGS) $(SYSTEMD_LIBS) $(PHOSPHOR_LOGGING_LIBS) $(LIBEVDEV_LIBS)
 utest_SOURCES = utest.cpp
 utest_LDADD = $(top_builddir)/monitor.o