Add gpio-presence-sensor

This is to implement the gpio based hw inventory design [1].

There is a new meson option 'gpio-presence' to enable/disable the
daemon.

Summary of the functionality:

- fetch configuration from EM, according to the configuration interface
- the D-Bus interface is
  xyz.openbmc_project.Configuration.GPIODeviceDetect
- the configuration represents devices for which presence can be
  detected based on gpio values.
- watch gpios for changes
- add/remove the xyz.openbmc_project.Inventory.Source.DevicePresence
  interface on the object path based on gpio values.

References:
[1] https://github.com/openbmc/docs/blob/master/designs/inventory/gpio-based-hardware-inventory.md
[2] https://www.kernel.org/doc/html/latest/admin-guide/gpio/gpio-sim.html

Tested: using linux gpio-sim facility, see below

1. create a fake gpio via [2]
2. configure gpio-presence-sensor as per [1]
3. run the gpio-presence-sensor
4. change the value of the gpio previously configured
5. there should be log output (at debug level)
6. the dbus interfaces exposed should appear/disappear as per [1]

Change-Id: I4cf039b583247581aa5c6c6c59e7fc41ced0bb85
Signed-off-by: Alexander Hansen <alexander.hansen@9elements.com>
diff --git a/src/gpio-presence/config_provider.cpp b/src/gpio-presence/config_provider.cpp
new file mode 100644
index 0000000..7948802
--- /dev/null
+++ b/src/gpio-presence/config_provider.cpp
@@ -0,0 +1,149 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2022-2024. All rights
+ * reserved. SPDX-License-Identifier: Apache-2.0
+ */
+#include "config_provider.hpp"
+
+#include <boost/container/flat_map.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async/match.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <xyz/openbmc_project/ObjectMapper/client.hpp>
+
+#include <ranges>
+#include <string>
+
+PHOSPHOR_LOG2_USING;
+
+using VariantType =
+    std::variant<std::vector<std::string>, std::string, int64_t, uint64_t,
+                 double, int32_t, uint32_t, int16_t, uint16_t, uint8_t, bool>;
+using ConfigMap = boost::container::flat_map<std::string, VariantType>;
+using ConfigData = boost::container::flat_map<std::string, ConfigMap>;
+
+namespace gpio_presence
+{
+
+ConfigProvider::ConfigProvider(sdbusplus::async::context& ctx,
+                               const std::string& interface) :
+    interface(interface), ctx(ctx)
+{}
+
+auto ConfigProvider::initialize(AddedCallback addConfig,
+                                RemovedCallback removeConfig)
+    -> sdbusplus::async::task<void>
+{
+    ctx.spawn(handleInterfacesAdded(addConfig));
+    ctx.spawn(handleInterfacesRemoved(removeConfig));
+
+    co_await getConfig(addConfig);
+}
+
+auto ConfigProvider::getConfig(AddedCallback addConfig)
+    -> sdbusplus::async::task<void>
+{
+    auto client = sdbusplus::client::xyz::openbmc_project::ObjectMapper<>(ctx)
+                      .service("xyz.openbmc_project.ObjectMapper")
+                      .path("/xyz/openbmc_project/object_mapper");
+
+    debug("calling 'GetSubTree' to find instances of {INTF}", "INTF",
+          interface);
+
+    using SubTreeType =
+        std::map<std::string, std::map<std::string, std::vector<std::string>>>;
+    SubTreeType res = {};
+
+    try
+    {
+        std::vector<std::string> interfaces = {interface};
+        res = co_await client.get_sub_tree("/xyz/openbmc_project/inventory", 0,
+                                           interfaces);
+    }
+    catch (std::exception& e)
+    {
+        error("Failed GetSubTree call for configuration interface: {ERR}",
+              "ERR", e);
+    }
+
+    if (res.empty())
+    {
+        co_return;
+    }
+
+    // call the user callback for all the device that is already available
+    for (auto& [path, serviceInterfaceMap] : res)
+    {
+        for (const auto& service :
+             std::ranges::views::keys(serviceInterfaceMap))
+        {
+            debug("found configuration interface at {SERVICE} {PATH} {INTF}",
+                  "SERVICE", service, "PATH", path, "INTF", interface);
+
+            addConfig(path);
+        }
+    }
+}
+
+namespace rules_intf = sdbusplus::bus::match::rules;
+
+const auto senderRule = rules_intf::sender("xyz.openbmc_project.EntityManager");
+
+auto ConfigProvider::handleInterfacesAdded(AddedCallback addConfig)
+    -> sdbusplus::async::task<void>
+{
+    debug("setting up dbus match for interfaces added");
+
+    sdbusplus::async::match addedMatch(
+        ctx, rules_intf::interfacesAdded() + senderRule);
+
+    while (!ctx.stop_requested())
+    {
+        auto [objPath, intfMap] =
+            co_await addedMatch
+                .next<sdbusplus::message::object_path, ConfigData>();
+
+        debug("Detected interface added on {OBJPATH}", "OBJPATH", objPath);
+
+        if (!std::ranges::contains(std::views::keys(intfMap), interface))
+        {
+            continue;
+        }
+
+        try
+        {
+            addConfig(objPath);
+        }
+        catch (std::exception& e)
+        {
+            error("Incomplete or invalid config found: {ERR}", "ERR", e);
+        }
+    }
+};
+
+auto ConfigProvider::handleInterfacesRemoved(RemovedCallback removeConfig)
+    -> sdbusplus::async::task<void>
+{
+    debug("setting up dbus match for interfaces removed");
+
+    sdbusplus::async::match removedMatch(
+        ctx, rules_intf::interfacesRemoved() + senderRule);
+
+    while (!ctx.stop_requested())
+    {
+        auto [objectPath, interfaces] =
+            co_await removedMatch.next<sdbusplus::message::object_path,
+                                       std::vector<std::string>>();
+
+        if (!std::ranges::contains(interfaces, interface))
+        {
+            continue;
+        }
+
+        debug("Detected interface {INTF} removed on {OBJPATH}", "INTF",
+              interface, "OBJPATH", objectPath);
+
+        removeConfig(objectPath);
+    }
+};
+
+} // namespace gpio_presence