Bare-metal:  post complete GPIO monitor

This creates a service that monitors the post complete gpio, it
reenabled ipmi when gpio is deasserted. And at the startup it checks the
gpio and disable ipmi if it is asserted.

Tested:
1. reset host, ipmi is re-enabled
2. reboot bmc, ipmi is disabled after bmc booting up.

Change-Id: If52c72ce57e10f6efbb94fd3f0bdcb7655b48d61
Signed-off-by: John Wedig <johnwedig@google.com>
Signed-off-by: Yuxiao Zhang <yuxiaozhang@google.com>
diff --git a/.gitignore b/.gitignore
index 9dc3fde..4691b58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,6 +7,7 @@
 !subprojects/metrics-ipmi-blobs/
 !subprojects/ncsid/
 !subprojects/nemora-postd/
+!subprojects/bare-metal-host-monitor/
 !subprojects/googletest.wrap
 !subprojects/phosphor-dbus-interfaces.wrap
 !subprojects/phosphor-logging.wrap
diff --git a/bare-metal-host-monitor b/bare-metal-host-monitor
new file mode 120000
index 0000000..1356767
--- /dev/null
+++ b/bare-metal-host-monitor
@@ -0,0 +1 @@
+subprojects/bare-metal-host-monitor
\ No newline at end of file
diff --git a/subprojects/bare-metal-host-monitor/host-gpio-monitor.service.in b/subprojects/bare-metal-host-monitor/host-gpio-monitor.service.in
new file mode 100644
index 0000000..3da5f1d
--- /dev/null
+++ b/subprojects/bare-metal-host-monitor/host-gpio-monitor.service.in
@@ -0,0 +1,10 @@
+[Unit]
+Description=gBMC bare-metal GPIO  monitoring
+After=phosphor-ipmi-host.service
+
+[Service]
+Restart=always
+ExecStart=@@BIN@ host_gpio_monitor
+
+[Install]
+WantedBy=multi-user.target
diff --git a/subprojects/bare-metal-host-monitor/host_gpio_monitor.cpp b/subprojects/bare-metal-host-monitor/host_gpio_monitor.cpp
new file mode 100644
index 0000000..889ab93
--- /dev/null
+++ b/subprojects/bare-metal-host-monitor/host_gpio_monitor.cpp
@@ -0,0 +1,144 @@
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/steady_timer.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/property.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+const constexpr char* OperatingSystemService =
+    "xyz.openbmc_project.State.OperatingSystem";
+const constexpr char* OperatingSystemPath = "/xyz/openbmc_project/state/os";
+const constexpr char* OperatingSystemStatusInterface =
+    "xyz.openbmc_project.State.OperatingSystem.Status";
+const constexpr char* OperatingSystemStateProperty = "OperatingSystemState";
+const constexpr char* OperatingSystemStateStandby =
+    "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Standby";
+const constexpr char* OperatingSystemStateInactive =
+    "xyz.openbmc_project.State.OperatingSystem.Status.OSStatus.Inactive";
+const constexpr char* BareMetalActiveTarget = "gbmc-bare-metal-active.target";
+
+const constexpr char* SystemdService = "org.freedesktop.systemd1";
+const constexpr char* SystemdManagerObject = "/org/freedesktop/systemd1";
+const constexpr char* SystemdManagerInterface =
+    "org.freedesktop.systemd1.Manager";
+
+void setUnitStatus(sdbusplus::asio::connection& bus, bool status)
+{
+    auto method = bus.new_method_call(SystemdService, SystemdManagerObject,
+                                      SystemdManagerInterface,
+                                      status ? "StartUnit" : "StopUnit");
+    method.append(BareMetalActiveTarget, "replace");
+
+    bus.call(method);
+}
+
+/* This only gets called once on startup. */
+void checkPostComplete(sdbusplus::asio::connection& bus,
+                       const std::string& state, bool action)
+{
+    sdbusplus::asio::getProperty<std::string>(
+        bus, OperatingSystemService, OperatingSystemPath,
+        OperatingSystemStatusInterface, OperatingSystemStateProperty,
+        [&](const boost::system::error_code& ec,
+            const std::string& postCompleteState) {
+        if (ec)
+        {
+            lg2::error("Error when checking Post Complete GPIO state");
+            return;
+        }
+
+        lg2::info("Post Complete state is {STATE}", "STATE", postCompleteState);
+        /*
+         * If state is Standby, enable the bare-metal-active systemd
+         * target.
+         * If state is Inactive, no-op cause IPMI is enabled by default.
+         */
+        if (postCompleteState == state)
+        {
+            setUnitStatus(bus, action);
+        }
+    });
+}
+
+/* This only gets called once on startup. */
+void checkPostCompleteStartup(sdbusplus::asio::connection& bus)
+{
+    checkPostComplete(bus, OperatingSystemStateStandby, true);
+}
+
+/* Gets called when a GPIO state change is detected. */
+void checkPostCompleteEvent(sdbusplus::asio::connection& bus)
+{
+    checkPostComplete(bus, OperatingSystemStateInactive, false);
+}
+
+int main()
+{
+    try
+    {
+        /* Setup connection to dbus. */
+        boost::asio::io_context io;
+        auto conn = sdbusplus::asio::connection(io);
+
+        /* check IPMI status at startup */
+        checkPostCompleteStartup(conn);
+        /*
+         * Set up an event handler to process Post Complete GPIO state changes.
+         */
+        boost::asio::steady_timer filterTimer(io);
+
+        auto match = std::make_unique<sdbusplus::bus::match_t>(
+            static_cast<sdbusplus::bus_t&>(conn),
+            std::format(
+                "type='signal',member='PropertiesChanged',path_namespace='"
+                "/xyz/openbmc_project/state/os',arg0namespace='{}'",
+                OperatingSystemStatusInterface),
+            [&](sdbusplus::message_t& message) {
+            if (message.is_method_error())
+            {
+                lg2::error("eventHandler callback method error");
+                return;
+            }
+
+            /*
+             * This implicitly cancels the timer, if it's already pending.
+             * If there's a burst of events within a short period, we want
+             * to handle them all at once. So, we will wait this long for no
+             * more events to occur, before processing them.
+             */
+            filterTimer.expires_from_now(std::chrono::seconds(1));
+
+            filterTimer.async_wait([&](const boost::system::error_code& ec) {
+                if (ec == boost::asio::error::operation_aborted)
+                {
+                    /* we were canceled */
+                    return;
+                }
+                if (ec)
+                {
+                    lg2::error("timer error");
+                    return;
+                }
+
+                /*
+                 * Stop the bare metal active target if the post complete got
+                 * deasserted.
+                 */
+                checkPostCompleteEvent(conn);
+            });
+        });
+
+        io.run();
+        return 0;
+    }
+    catch (const std::exception& e)
+    {
+        lg2::error(e.what(), "REDFISH_MESSAGE_ID",
+                   std::string("OpenBMC.1.0.ServiceException"));
+
+        return 2;
+    }
+    return 1;
+}
diff --git a/subprojects/bare-metal-host-monitor/meson.build b/subprojects/bare-metal-host-monitor/meson.build
new file mode 100644
index 0000000..dfed5eb
--- /dev/null
+++ b/subprojects/bare-metal-host-monitor/meson.build
@@ -0,0 +1,36 @@
+project(
+  'host_gpio_monitor',
+  'cpp',
+  version: '0.1',
+  meson_version: '>=1.1.1',
+  default_options: [
+    'warning_level=3',
+    'werror=true',
+    'cpp_std=c++23',
+  ],
+)
+
+executable(
+  'host_gpio_monitor',
+  'host_gpio_monitor.cpp',
+  implicit_include_directories: false,
+  dependencies:
+  [
+    dependency('stdplus'),
+    dependency('phosphor-logging'),
+  ],
+  install: true,
+  install_dir: get_option('libexecdir'),
+)
+
+systemd = dependency('systemd')
+systemunitdir = systemd.get_variable('systemdsystemunitdir')
+
+libexecdir = get_option('prefix') / get_option('libexecdir')
+
+configure_file(
+  configuration: {'BIN': libexecdir / 'host_gpio_monitor'},
+  input: 'host-gpio-monitor.service.in',
+  output: 'host-gpio-monitor.service',
+  install_mode: 'rw-r--r--',
+  install_dir: systemunitdir)