GPIO Monitor with multiple lines and libgpiod

Added a new GPIO monitor (phosphor-multi-gpio-monitor) which monitors
multiple gpio lines based on their line name or offset. GPIO details
need to be defined in a JSON file and needs to be passed to this daemon.

This uses libgpiod for accessing gpio lines and also uses CLI11 for
parsing input parameter.

Signed-off-by: Vijay Khemka <vijaykhemka@fb.com>
Change-Id: I843e6df8c1159888f2ca628d1f69c1d2294d29d6
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..df55110
--- /dev/null
+++ b/README.md
@@ -0,0 +1,64 @@
+# phosphor-gpio-monitor
+
+This daemon accepts a command line parameter for monitoring single gpio
+line and take action if requested. This implementation uses GPIO keys
+and only supports monitoring single GPIO line, for multiple lines,
+user has to run this daemon seperately for each gpio line.
+
+# phosphor-multi-gpio-monitor
+
+This daemon accepts command line parameter as a well-defined GPIO
+configuration file in json format to monitor list of gpios from
+config file and take action defined in config based on gpio state
+change. It uses libgpiod library.
+
+# Difference
+New implementation (phosphor-multi-gpio-monitor) provides multiple gpio
+line monitoring in single instance of phosphor-multi-gpio-monitor running.
+It is very easy to add list of gpios into JSON config file and it also
+supports of GPIO line by name defined in kernel.
+
+## Configuration
+
+There is a phosphor-multi-gpio-monitor.json file that defines details of
+GPIOs which is required to be monitored. This file can be replaced with
+a platform specific configuration file via bbappend.
+
+Following are fields in json file
+1. Name: Name of gpio for reference.
+2. LineName: this is the line name defined in device tree for specific gpio
+3. GpioNum: GPIO offset, this field is optional if LineName is defined.
+4. ChipId: This is device name either offset ("0") or complete gpio device
+           ("gpiochip0"). This field is not required if LineName is defined.
+5. EventMon: Event of gpio to be monitored. This can be "FALLING", "RISING"
+             OR "BOTH". Default value for this is "BOTH".
+6. Target: This is an optional systemd service which will get started after
+           triggering event. A journal entry will be added for every event
+           occurs irrespective of this definition.
+7. Continue: This is a optional flag and if it is defined as true then this
+             gpio will be monitored continously. If not defined then
+             monitoring of this gpio will stop after first event.
+
+## Sample config file
+
+[
+	{
+		"Name": "PowerButton",
+		"LineName": "POWER_BUTTON",
+		"GpioNum": 34,
+		"ChipId": "gpiochip0",
+		"EventMon": "BOTH",
+		"Continue": true
+	},
+	{
+		"Name": "PowerGood",
+		"LineName": "PS_PWROK",
+		"EventMon": "FALLING",
+		"Continue": false
+	},
+	{
+		"Name": "SystemReset",
+		"GpioNum": 46,
+		"ChipId": "0"
+	}
+]
diff --git a/gpioMon.cpp b/gpioMon.cpp
new file mode 100644
index 0000000..2ab079a
--- /dev/null
+++ b/gpioMon.cpp
@@ -0,0 +1,123 @@
+/**
+ * Copyright © 2019 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gpioMon.hpp"
+
+#include <phosphor-logging/log.hpp>
+#include <sdbusplus/bus.hpp>
+
+namespace phosphor
+{
+namespace gpio
+{
+
+/* systemd service to kick start a target. */
+constexpr auto SYSTEMD_SERVICE = "org.freedesktop.systemd1";
+constexpr auto SYSTEMD_ROOT = "/org/freedesktop/systemd1";
+constexpr auto SYSTEMD_INTERFACE = "org.freedesktop.systemd1.Manager";
+
+using namespace phosphor::logging;
+
+void GpioMonitor::scheduleEventHandler()
+{
+
+    gpioEventDescriptor.async_wait(
+        boost::asio::posix::stream_descriptor::wait_read,
+        [this](const boost::system::error_code& ec) {
+            if (ec)
+            {
+                std::string msg = gpioLineMsg + "event handler error" +
+                                  std::string(ec.message());
+                log<level::ERR>(msg.c_str());
+                return;
+            }
+            gpioEventHandler();
+        });
+}
+
+void GpioMonitor::gpioEventHandler()
+{
+    gpiod_line_event gpioLineEvent;
+
+    if (gpiod_line_event_read_fd(gpioEventDescriptor.native_handle(),
+                                 &gpioLineEvent) < 0)
+    {
+        log<level::ERR>("Failed to read gpioLineEvent from fd",
+                        entry("GPIO_LINE=%s", gpioLineMsg.c_str()));
+        return;
+    }
+
+    std::string logMessage =
+        gpioLineMsg + (gpioLineEvent.event_type == GPIOD_LINE_EVENT_RISING_EDGE
+                           ? " Asserted"
+                           : " Deasserted");
+
+    log<level::INFO>(logMessage.c_str());
+
+    /* Execute the target if it is defined. */
+    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");
+
+        bus.call_noreply(method);
+    }
+
+    /* if not required to continue monitoring then return */
+    if (!continueAfterEvent)
+    {
+        return;
+    }
+
+    /* Schedule a wait event */
+    scheduleEventHandler();
+}
+
+int GpioMonitor::requestGPIOEvents()
+{
+
+    /* Request an event to monitor for respected gpio line */
+    if (gpiod_line_request(gpioLine, &gpioConfig, 0) < 0)
+    {
+        log<level::ERR>("Failed to request gpioLineEvent",
+                        entry("GPIO_LINE=%s", gpioLineMsg.c_str()));
+        return -1;
+    }
+
+    int gpioLineFd = gpiod_line_event_get_fd(gpioLine);
+    if (gpioLineFd < 0)
+    {
+        log<level::ERR>("Failed to get fd for gpioLineEvent",
+                        entry("GPIO_LINE=%s", gpioLineMsg.c_str()));
+        return -1;
+    }
+
+    std::string logMsg = gpioLineMsg + " monitoring started";
+    log<level::INFO>(logMsg.c_str());
+
+    /* Assign line fd to descriptor for monitoring */
+    gpioEventDescriptor.assign(gpioLineFd);
+
+    /* Schedule a wait event */
+    scheduleEventHandler();
+
+    return 0;
+}
+} // namespace gpio
+} // namespace phosphor
diff --git a/gpioMon.hpp b/gpioMon.hpp
new file mode 100644
index 0000000..b07bb40
--- /dev/null
+++ b/gpioMon.hpp
@@ -0,0 +1,80 @@
+#pragma once
+
+#include <gpiod.h>
+
+#include <boost/asio/io_service.hpp>
+#include <boost/asio/posix/stream_descriptor.hpp>
+
+namespace phosphor
+{
+namespace gpio
+{
+
+/** @class GpioMonitor
+ *  @brief Responsible for catching GPIO state change
+ *  condition and starting systemd targets.
+ */
+class GpioMonitor
+{
+  public:
+    GpioMonitor() = delete;
+    ~GpioMonitor() = default;
+    GpioMonitor(const GpioMonitor&) = delete;
+    GpioMonitor& operator=(const GpioMonitor&) = delete;
+    GpioMonitor(GpioMonitor&&) = delete;
+    GpioMonitor& operator=(GpioMonitor&&) = delete;
+
+    /** @brief Constructs GpioMonitor object.
+     *
+     *  @param[in] line        - GPIO line from libgpiod
+     *  @param[in] config      - configuration of line with event
+     *  @param[in] io          - io service
+     *  @param[in] target      - systemd unit to be started on GPIO
+     *                           value change
+     *  @param[in] lineMsg     - GPIO line message to be used for log
+     *  @param[in] continueRun - Whether to continue after event occur
+     */
+    GpioMonitor(gpiod_line* line, gpiod_line_request_config& config,
+                boost::asio::io_service& io, const std::string& target,
+                const std::string& lineMsg, bool continueRun) :
+        gpioLine(line),
+        gpioConfig(config), gpioEventDescriptor(io), target(target),
+        gpioLineMsg(lineMsg), continueAfterEvent(continueRun)
+    {
+        requestGPIOEvents();
+    };
+
+  private:
+    /** @brief GPIO line */
+    gpiod_line* gpioLine;
+
+    /** @brief GPIO line configuration */
+    gpiod_line_request_config gpioConfig;
+
+    /** @brief GPIO event descriptor */
+    boost::asio::posix::stream_descriptor gpioEventDescriptor;
+
+    /** @brief Systemd unit to be started when the condition is met */
+    const std::string target;
+
+    /** @brief GPIO line name message */
+    std::string gpioLineMsg;
+
+    /** @brief If the monitor should continue after event */
+    bool continueAfterEvent;
+
+    /** @brief register handler for gpio event
+     *
+     *  @return  - 0 on success and -1 otherwise
+     */
+    int requestGPIOEvents();
+
+    /** @brief Schedule an event handler for GPIO event to trigger */
+    void scheduleEventHandler();
+
+    /** @brief Handle the GPIO event and starts configured target */
+    void gpioEventHandler();
+};
+
+} // namespace gpio
+} // namespace phosphor
diff --git a/gpioMonMain.cpp b/gpioMonMain.cpp
new file mode 100644
index 0000000..4b8b052
--- /dev/null
+++ b/gpioMonMain.cpp
@@ -0,0 +1,183 @@
+/**
+ * Copyright © 2019 Facebook
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gpioMon.hpp"
+
+#include <CLI/CLI.hpp>
+#include <boost/asio/io_service.hpp>
+#include <fstream>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/log.hpp>
+
+using namespace phosphor::logging;
+
+namespace phosphor
+{
+namespace gpio
+{
+
+std::map<std::string, int> polarityMap = {
+    /**< Only watch falling edge events. */
+    {"FALLING", GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE},
+    /**< Only watch rising edge events. */
+    {"RISING", GPIOD_LINE_REQUEST_EVENT_RISING_EDGE},
+    /**< Monitor both types of events. */
+    {"BOTH", GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES}};
+
+}
+} // namespace phosphor
+
+int main(int argc, char** argv)
+{
+
+    boost::asio::io_service io;
+
+    CLI::App app{"Monitor GPIO line for requested state change"};
+
+    std::string gpioFileName;
+
+    /* Add an input option */
+    app.add_option("-c,--config", gpioFileName, "Name of config json file")
+        ->required()
+        ->check(CLI::ExistingFile);
+
+    /* Parse input parameter */
+    try
+    {
+        app.parse(argc, argv);
+    }
+    catch (CLI::Error& e)
+    {
+        return app.exit(e);
+    }
+
+    /* Get list of gpio config details from json file */
+    std::ifstream file(gpioFileName);
+    if (!file)
+    {
+        log<level::ERR>("GPIO monitor config file not found",
+                        entry("GPIO_MON_FILE=%s", gpioFileName.c_str()));
+        return -1;
+    }
+
+    nlohmann::json gpioMonObj;
+    file >> gpioMonObj;
+    file.close();
+
+    std::vector<std::unique_ptr<phosphor::gpio::GpioMonitor>> gpios;
+
+    for (auto& obj : gpioMonObj)
+    {
+
+        /* GPIO Line message */
+        std::string lineMsg = "GPIO Line ";
+
+        /* GPIO line */
+        gpiod_line* line = NULL;
+
+        /* Log message string */
+        std::string errMsg;
+
+        /* GPIO line configuration, default to monitor both edge */
+        struct gpiod_line_request_config config
+        {
+            "gpio_monitor", GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES, 0
+        };
+
+        /* flag to monitor */
+        bool flag = false;
+
+        /* target to start */
+        std::string target;
+
+        if (obj.find("LineName") == obj.end())
+        {
+            /* If there is no line Name defined then gpio num nd chip
+             * id must be defined. GpioNum is integer mapping to the
+             * GPIO key configured by the kernel
+             */
+            if (obj.find("GpioNum") == obj.end() ||
+                obj.find("ChipId") == obj.end())
+            {
+                log<level::ERR>(
+                    "Failed to find line name or gpio number",
+                    entry("GPIO_JSON_FILE_NAME=%s", gpioFileName.c_str()));
+                return -1;
+            }
+
+            std::string chipIdStr = obj["ChipId"];
+            int gpioNum = obj["GpioNum"];
+
+            lineMsg += std::to_string(gpioNum);
+
+            /* Get the GPIO line */
+            line = gpiod_line_get(chipIdStr.c_str(), gpioNum);
+        }
+        else
+        {
+            /* Find the GPIO line */
+            std::string lineName = obj["LineName"];
+            lineMsg += lineName;
+            line = gpiod_line_find(lineName.c_str());
+        }
+
+        if (line == NULL)
+        {
+            errMsg = "Failed to find the " + lineMsg;
+            log<level::ERR>(errMsg.c_str());
+            return -1;
+        }
+
+        /* Get event to be monitored, if it is not defined then
+         * Both rising falling edge will be monitored.
+         */
+        if (obj.find("EventMon") != obj.end())
+        {
+            std::string eventStr = obj["EventMon"];
+            auto findEvent = phosphor::gpio::polarityMap.find(eventStr);
+            if (findEvent == phosphor::gpio::polarityMap.end())
+            {
+                errMsg = "Incorrect GPIO monitor event defined " + lineMsg;
+                log<level::ERR>(errMsg.c_str());
+                return -1;
+            }
+
+            config.request_type = findEvent->second;
+        }
+
+        /* Get flag if monitoring needs to continue after first event */
+        if (obj.find("Continue") != obj.end())
+        {
+            flag = obj["Continue"];
+        }
+
+        /* Parse out target argument. It is fine if the user does not
+         * pass this if they are not interested in calling into any target
+         * on meeting a condition.
+         */
+        if (obj.find("Target") != obj.end())
+        {
+            target = obj["Target"];
+        }
+
+        /* Create a monitor object and let it do all the rest */
+        gpios.push_back(std::make_unique<phosphor::gpio::GpioMonitor>(
+            line, config, io, target, lineMsg, flag));
+    }
+    io.run();
+
+    return 0;
+}
diff --git a/meson.build b/meson.build
index 0f4f937..0f94b29 100644
--- a/meson.build
+++ b/meson.build
@@ -15,11 +15,16 @@
 cppfs = meson.get_compiler('cpp').find_library('stdc++fs')
 libevdev = dependency('libevdev')
 libsystemd = dependency('libsystemd')
+libgpiod = dependency('libgpiod')
 phosphor_dbus_interfaces = dependency('phosphor-dbus-interfaces')
 phosphor_logging = dependency('phosphor-logging')
 sdbusplus = dependency('sdbusplus')
 systemd = dependency('systemd')
 
+boost_args = ['-DBOOST_ASIO_DISABLE_THREADS',
+              '-DBOOST_ERROR_CODE_HEADER_ONLY',
+              '-DBOOST_SYSTEM_NO_DEPRECATED']
+
 systemd_system_unit_dir = systemd.get_pkgconfig_variable(
     'systemdsystemunitdir',
     define_variable: ['prefix', get_option('prefix')])
@@ -80,6 +85,19 @@
     ],
 )
 
+executable(
+   'phosphor-multi-gpio-monitor',
+   'gpioMonMain.cpp',
+   'gpioMon.cpp',
+   dependencies: [
+        phosphor_logging,
+        sdbusplus,
+        libgpiod,
+    ],
+    cpp_args: boost_args,
+    install: true,
+)
+
 subdir('gpio-util')
 subdir('presence')
 subdir('test')