Add base GPIO polling class for the new architecture

New architecture is intended to improve flexibility for which
signals are monitored in a system and how they are handled.

New monitoring flow handles interrupts storms better by using
the interrupt to notify of changes and start a polling loop to
get the actual status.

Change-Id: Iec27560af7a2f4c1649d80bb23c4fecf2c2f1339
Signed-off-by: Jason M. Bills <jason.m.bills@intel.com>
diff --git a/include/error_monitors/base_gpio_poll_monitor.hpp b/include/error_monitors/base_gpio_poll_monitor.hpp
new file mode 100644
index 0000000..eee430f
--- /dev/null
+++ b/include/error_monitors/base_gpio_poll_monitor.hpp
@@ -0,0 +1,256 @@
+/*
+// Copyright (c) 2021 Intel Corporation
+//
+// 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.
+*/
+#pragma once
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <error_monitors/base_monitor.hpp>
+#include <gpiod.hpp>
+#include <host_error_monitor.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <iostream>
+
+namespace host_error_monitor::base_gpio_poll_monitor
+{
+static constexpr bool debug = false;
+
+enum class AssertValue
+{
+    lowAssert = 0,
+    highAssert = 1,
+};
+
+class BaseGPIOPollMonitor : public host_error_monitor::base_monitor::BaseMonitor
+{
+    AssertValue assertValue;
+    size_t pollingTimeMs;
+    size_t timeoutMs;
+
+    boost::asio::steady_timer pollingTimer;
+    std::chrono::steady_clock::time_point timeoutTime;
+
+    gpiod::line line;
+    boost::asio::posix::stream_descriptor event;
+
+    virtual void logEvent()
+    {}
+
+    bool requestEvents()
+    {
+        line = gpiod::find_line(signalName);
+        if (!line)
+        {
+            std::cerr << "Failed to find the " << signalName << " line\n";
+            return false;
+        }
+
+        try
+        {
+            line.request(
+                {"host-error-monitor", gpiod::line_request::EVENT_BOTH_EDGES});
+        }
+        catch (std::exception&)
+        {
+            std::cerr << "Failed to request events for " << signalName << "\n";
+            return false;
+        }
+
+        int lineFd = line.event_get_fd();
+        if (lineFd < 0)
+        {
+            std::cerr << "Failed to get " << signalName << " fd\n";
+            return false;
+        }
+
+        event.assign(lineFd);
+
+        return true;
+    }
+
+    bool asserted()
+    {
+        if constexpr (debug)
+        {
+            std::cerr << "Checking " << signalName << " state\n";
+        }
+
+        if (hostIsOff())
+        {
+            if constexpr (debug)
+            {
+                std::cerr << "Host is off\n";
+            }
+            return false;
+        }
+
+        return (line.get_value() == static_cast<int>(assertValue));
+    }
+
+  public:
+    virtual void assertHandler()
+    {
+        std::cerr << signalName << " asserted for " << std::to_string(timeoutMs)
+                  << " ms\n";
+        logEvent();
+    }
+
+    virtual void deassertHandler()
+    {}
+
+  private:
+    void flushEvents()
+    {
+        if constexpr (debug)
+        {
+            std::cerr << "Flushing " << signalName << " events\n";
+        }
+
+        while (true)
+        {
+            try
+            {
+                line.event_read();
+            }
+            catch (std::system_error)
+            {
+                break;
+            }
+        }
+    }
+
+    void waitForEvent()
+    {
+        if constexpr (debug)
+        {
+            std::cerr << "Wait for " << signalName << "\n";
+        }
+
+        event.async_wait(
+            boost::asio::posix::stream_descriptor::wait_read,
+            [this](const boost::system::error_code ec) {
+                if (ec)
+                {
+                    // operation_aborted is expected if wait is canceled.
+                    if (ec != boost::asio::error::operation_aborted)
+                    {
+                        std::cerr << signalName
+                                  << " wait error: " << ec.message() << "\n";
+                    }
+                    return;
+                }
+
+                if constexpr (debug)
+                {
+                    std::cerr << signalName << " event ready\n";
+                }
+
+                startPolling();
+            });
+    }
+
+  public:
+    virtual void startPolling()
+    {
+        timeoutTime = std::chrono::steady_clock::now() +
+                      std::chrono::duration<int, std::milli>(timeoutMs);
+        poll();
+    }
+
+  private:
+    void poll()
+    {
+        if constexpr (debug)
+        {
+            std::cerr << "Polling " << signalName << "\n";
+        }
+
+        flushEvents();
+
+        if (!asserted())
+        {
+            if constexpr (debug)
+            {
+                std::cerr << signalName << " not asserted\n";
+            }
+
+            deassertHandler();
+            waitForEvent();
+            return;
+        }
+        if constexpr (debug)
+        {
+            std::cerr << signalName << " asserted\n";
+        }
+
+        if (std::chrono::steady_clock::now() > timeoutTime)
+        {
+            assertHandler();
+            waitForEvent();
+            return;
+        }
+
+        pollingTimer.expires_after(std::chrono::milliseconds(pollingTimeMs));
+        pollingTimer.async_wait([this](const boost::system::error_code ec) {
+            if (ec)
+            {
+                // operation_aborted is expected if timer is canceled before
+                // completion.
+                if (ec != boost::asio::error::operation_aborted)
+                {
+                    std::cerr << signalName
+                              << " polling async_wait failed: " << ec.message()
+                              << "\n";
+                }
+                return;
+            }
+            poll();
+        });
+    }
+
+  public:
+    BaseGPIOPollMonitor(boost::asio::io_service& io,
+                        std::shared_ptr<sdbusplus::asio::connection> conn,
+                        const std::string& signalName, AssertValue assertValue,
+                        size_t pollingTimeMs, size_t timeoutMs) :
+        BaseMonitor(io, conn, signalName),
+        pollingTimer(io), event(io), assertValue(assertValue),
+        pollingTimeMs(pollingTimeMs), timeoutMs(timeoutMs)
+    {
+        if (!requestEvents())
+        {
+            return;
+        }
+        event.non_blocking(true);
+        valid = true;
+    }
+
+    void hostOn() override
+    {
+        event.cancel();
+        startPolling();
+    }
+
+    size_t getTimeoutMs()
+    {
+        return timeoutMs;
+    }
+
+    void setTimeoutMs(size_t newTimeoutMs)
+    {
+        timeoutMs = newTimeoutMs;
+    }
+};
+} // namespace host_error_monitor::base_gpio_poll_monitor