gpio-health-monitor: Patch to fix crash issue

This patch fixes a crash that occurred when multiple GPIO lines asserted
simultaneously by refactoring D-Bus handling to use one shared
connection instead of creating new ones for every event. It also prevents
GPIO line handle leaks by adding missing gpiod_line_release calls in the
destructor and relevant error paths.

0003-phosphor-health-gpio-monitor-Use-shared-D-Bus-connec.patch

Tested: details in the patch.

Fusion-Link: fusion2 N/A
Google-Bug-Id: 427175539
Change-Id: Ib72c70c9750d9a528b593ceeba064f0202035c2b
Signed-off-by: Vikram Gara <vikramgara@google.com>
diff --git a/recipes-phosphor/gpio/health-monitor/0003-phosphor-health-gpio-monitor-Use-shared-D-Bus-connec.patch b/recipes-phosphor/gpio/health-monitor/0003-phosphor-health-gpio-monitor-Use-shared-D-Bus-connec.patch
new file mode 100644
index 0000000..3639319
--- /dev/null
+++ b/recipes-phosphor/gpio/health-monitor/0003-phosphor-health-gpio-monitor-Use-shared-D-Bus-connec.patch
@@ -0,0 +1,246 @@
+From 3c878b9f7759b74a310b13ddabb591937e381f47 Mon Sep 17 00:00:00 2001
+From: Vikram Gara <vikramgara@google.com>
+Date: Tue, 2 Sep 2025 22:12:01 +0000
+Subject: [PATCH] phosphor-health-gpio-monitor: Use shared D-Bus connection to
+ fix crash
+
+The previous implementation created a new `boost::asio::io_context` and
+`sdbusplus::asio::connection` within the `gpioEventHandler` for every
+GPIO event. This is inefficient and leads to a crash, likely due to
+resource exhaustion or race conditions.
+
+This patch refactors the D-Bus connection handling to fix the crash
+and improve resource management:
+
+- A single `sdbusplus::asio::connection` is now created as a
+  `std::shared_ptr` in `main()` and is shared across all `GpioMonitor`
+  instances.
+- The `GpioMonitor` class now stores this shared pointer and uses it
+  for making asynchronous D-Bus calls, avoiding the creation of new
+  connections in the event handler.
+- The D-Bus method to update the health status is now only called when
+  the health state actually changes, reducing unnecessary D-Bus traffic.
+
+Additionally, this patch includes resource management improvements:
+- A destructor is added to `GpioMonitor` to ensure `gpiod_line_release`
+  is called, preventing GPIO line handle leaks.
+- A missing `gpiod_line_release` call in an error path within
+  `createHealthObject` is added.
+- Add mutex lock to prevent race condition between main loop and D-Bus
+  signal handler when creating HealthMonitor objects
+
+Tested:
+      Simulated two gpio line assertions at the same time using mimic
+      did not see any crash with the change.
+      http://paste.googleplex.com/4763735394746368#l=199
+Signed-off-by: Vikram Gara <vikramgara@google.com>
+---
+ gpioHealthMon.cpp     | 36 +++++++++++++++++-------------------
+ gpioHealthMon.hpp     | 25 ++++++++++++++++++++-----
+ gpioHealthMonMain.cpp | 18 +++++++++++-------
+ 3 files changed, 48 insertions(+), 31 deletions(-)
+
+diff --git a/gpioHealthMon.cpp b/gpioHealthMon.cpp
+index b7e90d4..99503ff 100644
+--- a/gpioHealthMon.cpp
++++ b/gpioHealthMon.cpp
+@@ -17,7 +17,6 @@
+ #include "gpioHealthMon.hpp"
+ 
+ #include <boost/asio/io_context.hpp>
+-#include <sdbusplus/asio/connection.hpp>
+ #include <sdbusplus/bus.hpp>
+ 
+ #include <iostream>
+@@ -65,24 +64,23 @@ void GpioMonitor::gpioEventHandler()
+         lg2::info("{GPIO} Deasserted", "GPIO", gpioLineMsg);
+         isRising = false;
+     }
+-    currentHealth = (isRising == healthyPolarity);
+-
+-    // Creating io_context
+-    boost::asio::io_context io;
+-
+-    // Creating shared connection
+-    auto conn = sdbusplus::asio::connection(io);
+-
+-    // Making the async D-Bus method call
+-    conn.async_method_call([](boost::system::error_code ec) {
+-		    if (ec)
+-		    {
+-		    lg2::error("error with async_method_call: {ERROR}", "ERROR",
+-				    ec.message());
+-		    return;
+-		    }
+-		    }, phosphor::gpio::DBUS_SERVICE, objectPath, DBUS_INTERFACE,
+-		    "updateHealthStatus", currentHealth);
++
++    bool newHealth = (isRising == healthyPolarity);
++    if (newHealth != currentHealth)
++    {
++        currentHealth = newHealth;
++        // Making the async D-Bus method call
++        conn->async_method_call(
++            [](boost::system::error_code ec) {
++                if (ec)
++                {
++                    lg2::error("error with async_method_call: {ERROR}", "ERROR",
++                               ec.message());
++                }
++            },
++            phosphor::gpio::DBUS_SERVICE, objectPath, DBUS_INTERFACE,
++            "updateHealthStatus", currentHealth);
++    }
+ 
+     /* Schedule a wait event */
+     scheduleEventHandler();
+diff --git a/gpioHealthMon.hpp b/gpioHealthMon.hpp
+index 894a817..72e7c13 100644
+--- a/gpioHealthMon.hpp
++++ b/gpioHealthMon.hpp
+@@ -4,9 +4,11 @@
+ 
+ #include <boost/asio/io_context.hpp>
+ #include <boost/asio/posix/stream_descriptor.hpp>
++#include <sdbusplus/asio/connection.hpp>
+ #include <phosphor-logging/lg2.hpp>
+ 
+ #include <map>
++#include <memory>
+ #include <vector>
+ 
+ namespace phosphor
+@@ -26,7 +28,14 @@ class GpioMonitor
+ {
+   public:
+     GpioMonitor() = delete;
+-    ~GpioMonitor() = default;
++    ~GpioMonitor()
++    {
++        if (gpioLine)
++        {
++            // Release the GPIO line
++            gpiod_line_release(gpioLine);
++        }
++    }
+     GpioMonitor(const GpioMonitor&) = delete;
+     GpioMonitor& operator=(const GpioMonitor&) = delete;
+     GpioMonitor(GpioMonitor&&) = delete;
+@@ -35,18 +44,21 @@ class GpioMonitor
+     /** @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] config          - Configuration of line with event
++     *  @param[in] conn            - D-Bus connection
+      *  @param[in] lineMsg         - GPIO line message to be used for log
+      *  @param[in] objPath         - D-Bus service path for this GPIO
+      *  @param[in] healthyPolarity - Definition of healthy event
+      *  @param[in] currentHealth   - Current GPIO status
++     *  @param[in] entityPath      - Entity path for the GPIO
+      */
+     GpioMonitor(gpiod_line* line, gpiod_line_request_config& config,
+-                boost::asio::io_context& io, const std::string& lineMsg,
++                const std::shared_ptr<sdbusplus::asio::connection>& conn,
++                const std::string& lineMsg,
+                 std::string& objPath, bool& healthyPolarity,
+                 bool& currentHealth, std::string entityPath) :
+-        gpioLine(line), gpioConfig(config), gpioEventDescriptor(io),
++        gpioLine(line), gpioConfig(config),
++        gpioEventDescriptor(conn->get_io_context()), conn(conn),
+         gpioLineMsg(lineMsg), objectPath(objPath),
+         healthyPolarity(healthyPolarity), currentHealth(currentHealth),
+         entityPath(entityPath)
+@@ -69,6 +81,9 @@ class GpioMonitor
+     /** @brief GPIO event descriptor */
+     boost::asio::posix::stream_descriptor gpioEventDescriptor;
+ 
++    /** @brief D-Bus connection. */
++    std::shared_ptr<sdbusplus::asio::connection> conn;
++
+     /** @brief GPIO line name message */
+     std::string gpioLineMsg;
+ 
+diff --git a/gpioHealthMonMain.cpp b/gpioHealthMonMain.cpp
+index 1d84d5d..f26dc75 100644
+--- a/gpioHealthMonMain.cpp
++++ b/gpioHealthMonMain.cpp
+@@ -108,7 +108,8 @@ std::set<std::string> exist;
+ std::mutex handlerMutex;
+ 
+ bool createHealthObject(sdbusplus::asio::connection& conn,
+-                        const std::string& objPath, sdbusplus::bus_t& bus)
++                        const std::string& objPath, sdbusplus::bus_t& bus,
++                        const std::shared_ptr<sdbusplus::asio::connection>& sharedConn)
+ {
+     /* Call GetAll method call */
+     std::vector<std::string> targetInterfaces = {
+@@ -186,8 +187,10 @@ bool createHealthObject(sdbusplus::asio::connection& conn,
+     if (gpioValue < 0)
+     {
+         lg2::error("Failed to read current GPIO value");
++        gpiod_line_release(line);
+         return false;
+     }
++
+     gpiod_line_release(line);
+ 
+     /* Get event to be monitored, if it is not defined then
+@@ -212,7 +215,7 @@ bool createHealthObject(sdbusplus::asio::connection& conn,
+     limit = std::get<uint64_t>(properties["LimitMonitoring"]);
+ 
+     gpios.insert(std::make_unique<phosphor::gpio::GpioMonitor>(
+-        line, config, conn.get_io_context(), lineMsg, gpioObjectPath,
++        line, config, sharedConn, lineMsg, gpioObjectPath,
+         healthyPolarity, currentHealthy, objPath));
+ 
+     dbusObjects.push_back(std::make_unique<GetStatus>(
+@@ -221,20 +224,20 @@ bool createHealthObject(sdbusplus::asio::connection& conn,
+ }
+ 
+ std::unique_ptr<sdbusplus::bus::match_t>
+-    setupPropertiesChangedMatches(sdbusplus::asio::connection& conn, sdbusplus::bus_t& bus)
++    setupPropertiesChangedMatches(const std::shared_ptr<sdbusplus::asio::connection>& conn, sdbusplus::bus_t& bus)
+ {
+     lg2::info("Begin to listen");
+     std::string match_expression =
+         "type='signal',member='PropertiesChanged',path_namespace='/xyz/openbmc_project/inventory',arg0namespace='xyz.openbmc_project.Configuration.HealthMonitor'";
+     auto match = std::make_unique<sdbusplus::bus::match_t>(
+         bus, match_expression,
+-        [&conn, &bus](sdbusplus::message_t& message) {
++        [conn, &bus](sdbusplus::message_t& message) {
+         std::lock_guard<std::mutex> lock(handlerMutex);
+         const auto* path = message.get_path();
+         if (exist.find(path) != exist.end())
+             return;
+         exist.insert(path);
+-        if (createHealthObject(conn, path, bus) == false)
++        if (createHealthObject(*conn, path, bus, conn) == false)
+         {
+             lg2::error("Failed to create health object for {NAME}.", "NAME",
+                        path);
+@@ -310,17 +313,18 @@ int main()
+ 
+     for (const auto& objPath : objPaths)
+     {
++        std::lock_guard<std::mutex> lock(handlerMutex);
+         if (exist.find(objPath) != exist.end())
+             continue;
+         exist.insert(objPath);
+-        if (createHealthObject(*conn, objPath, bus) == false)
++        if (createHealthObject(*conn, objPath, bus, conn) == false)
+         {
+             lg2::error("Failed to create health object for {NAME}.", "NAME",
+                        objPath);
+             continue;
+         }
+     }
+-    auto match = setupPropertiesChangedMatches(*conn, bus);
++    auto match = setupPropertiesChangedMatches(conn, bus);
+     io.run();
+ 
+     return 0;
+-- 
+2.51.0.384.g4c02a37b29-goog
+