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/test/test_gpio_presence.cpp b/test/test_gpio_presence.cpp
new file mode 100644
index 0000000..727bc40
--- /dev/null
+++ b/test/test_gpio_presence.cpp
@@ -0,0 +1,160 @@
+#include "gpio-presence/device_presence.hpp"
+#include "gpio-presence/gpio_presence_manager.hpp"
+
+#include <gpiod.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <xyz/openbmc_project/Inventory/Source/DevicePresence/client.hpp>
+
+#include <gtest/gtest.h>
+
+using namespace gpio_presence;
+
+auto requestStop(sdbusplus::async::context& io) -> sdbusplus::async::task<>
+{
+    io.request_stop();
+    co_return;
+}
+
+TEST(GpioPresence, ConstructionSucceeds)
+{
+    sdbusplus::async::context ctx;
+
+    gpio_presence::GPIOPresenceManager s(ctx);
+
+    ctx.spawn(requestStop(ctx));
+    ctx.run();
+}
+
+TEST(GpioPresence, AcceptConfig1Gpio)
+{
+    sdbusplus::async::context ctx;
+
+    gpio_presence::GPIOPresenceManager sensor(ctx);
+
+    std::string name = "cable0";
+    std::string gpioName = "TEST_GPIO";
+
+    std::vector<std::string> gpioNames = {gpioName};
+    std::vector<uint64_t> gpioValues = {0};
+
+    auto c = std::make_unique<gpio_presence::DevicePresence>(
+        ctx, gpioNames, gpioValues, name, sensor.gpioState);
+
+    sensor.addConfig(name, std::move(c));
+
+    sensor.updatePresence(gpioName, false);
+
+    EXPECT_EQ(sensor.getPresence(name), true);
+
+    sensor.updatePresence(gpioName, true);
+
+    EXPECT_EQ(sensor.getPresence(name), false);
+
+    ctx.spawn(requestStop(ctx));
+    ctx.run();
+}
+
+auto testDevicePresentDbus(sdbusplus::async::context& ctx)
+    -> sdbusplus::async::task<>
+{
+    gpio_presence::GPIOPresenceManager sensor(ctx);
+
+    std::string busName = sensor.setupBusName();
+
+    std::string name = "cable0";
+    std::string gpioName = "TEST_GPIO";
+
+    std::vector<std::string> gpioNames = {gpioName};
+    std::vector<uint64_t> gpioValues = {0};
+
+    auto c = std::make_unique<gpio_presence::DevicePresence>(
+        ctx, gpioNames, gpioValues, name, sensor.gpioState);
+
+    sdbusplus::message::object_path objPath = c->getObjPath();
+
+    sensor.addConfig(name, std::move(c));
+
+    sensor.updatePresence(gpioName, false);
+
+    lg2::debug("found obj path {OBJPATH}", "OBJPATH", objPath);
+
+    auto client = sdbusplus::client::xyz::openbmc_project::inventory::source::
+                      DevicePresence<>(ctx)
+                          .service(busName)
+                          .path(objPath.str);
+
+    std::string nameFound = co_await client.name();
+
+    assert(nameFound == "cable0");
+
+    ctx.request_stop();
+
+    co_return;
+}
+
+TEST(GpioPresence, DevicePresentDbus)
+{
+    sdbusplus::async::context ctx;
+    ctx.spawn(testDevicePresentDbus(ctx));
+    ctx.run();
+}
+
+auto testDevicePresentThenDisappearDbus(sdbusplus::async::context& ctx)
+    -> sdbusplus::async::task<>
+{
+    gpio_presence::GPIOPresenceManager sensor(ctx);
+
+    std::string busName = sensor.setupBusName();
+
+    std::string name = "cable0";
+    std::string gpioName = "TEST_GPIO";
+
+    std::vector<std::string> gpioNames = {gpioName};
+    std::vector<uint64_t> gpioValues = {0};
+
+    auto c = std::make_unique<gpio_presence::DevicePresence>(
+        ctx, gpioNames, gpioValues, name, sensor.gpioState);
+
+    sdbusplus::message::object_path objPath = c->getObjPath();
+
+    sensor.addConfig(name, std::move(c));
+
+    sensor.updatePresence(gpioName, false);
+
+    lg2::debug("found obj path {OBJPATH}", "OBJPATH", objPath);
+
+    auto client = sdbusplus::client::xyz::openbmc_project::inventory::source::
+                      DevicePresence<>(ctx)
+                          .service(busName)
+                          .path(objPath.str);
+
+    std::string nameFound = co_await client.name();
+
+    assert(nameFound == "cable0");
+
+    // gpio goes high, cable 0 should disappear
+    sensor.updatePresence(gpioName, true);
+
+    try
+    {
+        co_await client.name();
+        assert(false);
+    }
+    catch (std::exception& _)
+    {
+        // expected, since cable 0 is gone.
+        // have to do something here to shut up clang-tidy
+        std::cout << "" << std::endl;
+    }
+
+    ctx.request_stop();
+
+    co_return;
+}
+
+TEST(GpioPresence, DevicePresentThenDisappearDbus)
+{
+    sdbusplus::async::context ctx;
+    ctx.spawn(testDevicePresentThenDisappearDbus(ctx));
+    ctx.run();
+}