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')