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/gpio_presence_manager.cpp b/src/gpio-presence/gpio_presence_manager.cpp
new file mode 100644
index 0000000..856408e
--- /dev/null
+++ b/src/gpio-presence/gpio_presence_manager.cpp
@@ -0,0 +1,260 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2022-2024.
+ * All rights reserved. SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "gpio_presence_manager.hpp"
+
+#include "device_presence.hpp"
+
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <gpiod.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/async/timer.hpp>
+#include <sdbusplus/message/native_types.hpp>
+#include <xyz/openbmc_project/Configuration/GPIODeviceDetect/client.hpp>
+#include <xyz/openbmc_project/Configuration/GPIODeviceDetect/common.hpp>
+
+#include <memory>
+#include <ranges>
+#include <string>
+#include <utility>
+
+PHOSPHOR_LOG2_USING;
+
+namespace gpio_presence
+{
+
+GPIOPresenceManager::GPIOPresenceManager(sdbusplus::async::context& ctx) :
+ ctx(ctx), manager(ctx, "/"),
+ configProvider(
+ ConfigProvider(ctx, sdbusplus::common::xyz::openbmc_project::
+ configuration::GPIODeviceDetect::interface))
+{}
+
+auto GPIOPresenceManager::start() -> void
+{
+ ctx.spawn(initialize());
+}
+
+auto GPIOPresenceManager::getPresence(const std::string& name) -> bool
+{
+ if (!presenceMap.contains(name))
+ {
+ return false;
+ }
+ return presenceMap.at(name)->isPresent();
+}
+
+auto GPIOPresenceManager::initialize() -> sdbusplus::async::task<void>
+{
+ co_await configProvider.initialize(
+ std::bind_front(&GPIOPresenceManager::addConfigHandler, this),
+ std::bind_front(&GPIOPresenceManager::removeConfig, this));
+}
+
+auto GPIOPresenceManager::setupBusName() const -> std::string
+{
+ debug("requesting dbus name {NAME}", "NAME", service);
+
+ ctx.request_name(service);
+ return service;
+}
+
+auto GPIOPresenceManager::addConfig(const sdbusplus::message::object_path& obj,
+ std::unique_ptr<DevicePresence> config)
+ -> void
+{
+ debug("adding configuration for {NAME}", "NAME", obj);
+ presenceMap.insert_or_assign(obj, std::move(config));
+
+ debug("found valid configuration at object path {OBJPATH}", "OBJPATH", obj);
+
+ auto gpioConfigs = presenceMap[obj]->gpioPolarity;
+
+ // populate fdios
+ for (auto& [gpioName, _] : gpioConfigs)
+ {
+ if (gpioLines.contains(gpioName))
+ {
+ continue;
+ }
+
+ try
+ {
+ gpioLines[gpioName] = gpiod::find_line(gpioName);
+ }
+ catch (std::exception& e)
+ {
+ error("gpiod::find_line failed: {ERROR}", "ERROR", e);
+ return;
+ }
+
+ gpiod::line_request lineConfig;
+ lineConfig.consumer = "gpio-presence";
+ lineConfig.request_type = gpiod::line_request::EVENT_BOTH_EDGES |
+ gpiod::line_request::DIRECTION_INPUT;
+
+ int lineFd = -1;
+ try
+ {
+ gpioLines[gpioName].request(lineConfig);
+
+ lineFd = gpioLines[gpioName].event_get_fd();
+ }
+ catch (std::exception& e)
+ {
+ error("{ERROR}", "ERROR", e);
+ return;
+ }
+ if (lineFd < 0)
+ {
+ error("could not get event fd for gpio '{NAME}'", "NAME", gpioName);
+ return;
+ }
+
+ if (!fdios.contains(gpioName))
+ {
+ fdios.insert(
+ {gpioName,
+ std::make_unique<sdbusplus::async::fdio>(ctx, lineFd)});
+
+ ctx.spawn(readGPIOAsyncEvent(gpioName));
+ }
+ }
+}
+
+auto GPIOPresenceManager::addConfigHandler(sdbusplus::message::object_path obj)
+ -> void
+{
+ // NOLINTBEGIN(performance-unnecessary-value-param)
+ ctx.spawn(addConfigFromDbusAsync(obj));
+ // NOLINTEND(performance-unnecessary-value-param)
+}
+
+// NOLINTBEGIN(performance-unnecessary-value-param)
+auto GPIOPresenceManager::addConfigFromDbusAsync(
+ const sdbusplus::message::object_path obj) -> sdbusplus::async::task<void>
+// NOLINTEND(performance-unnecessary-value-param)
+{
+ auto props = co_await sdbusplus::client::xyz::openbmc_project::
+ configuration::GPIODeviceDetect<>(ctx)
+ .service("xyz.openbmc_project.EntityManager")
+ .path(obj.str)
+ .properties();
+
+ if (props.presence_pin_names.size() != props.presence_pin_values.size())
+ {
+ error(
+ "presence pin names and presence pin values have different sizes");
+ co_return;
+ }
+
+ auto devicePresence = std::make_unique<DevicePresence>(
+ ctx, props.presence_pin_names, props.presence_pin_values, props.name,
+ gpioState);
+
+ if (devicePresence)
+ {
+ addConfig(obj, std::move(devicePresence));
+ }
+}
+
+auto GPIOPresenceManager::removeConfig(const std::string& objPath) -> void
+{
+ if (!presenceMap.contains(objPath))
+ {
+ return;
+ }
+
+ debug("erasing configuration for object path {OBJPATH}", "OBJPATH",
+ objPath);
+ presenceMap.erase(objPath);
+
+ std::set<std::string> gpiosNeeded;
+
+ for (const auto& config : std::views::values(presenceMap))
+ {
+ for (const auto& gpio : std::views::keys(config->gpioPolarity))
+ {
+ gpiosNeeded.insert(gpio);
+ }
+ }
+
+ auto ks = std::views::keys(gpioLines);
+ std::set<std::string> trackedGPIOs{ks.begin(), ks.end()};
+
+ for (const auto& trackedGPIO : trackedGPIOs)
+ {
+ if (gpiosNeeded.contains(trackedGPIO))
+ {
+ continue;
+ }
+
+ gpioLines[trackedGPIO].release();
+
+ gpioLines.erase(trackedGPIO);
+ fdios.erase(fdios.find(trackedGPIO));
+ }
+}
+
+auto GPIOPresenceManager::updatePresence(const std::string& gpioLine,
+ bool state) -> void
+{
+ gpioState.insert_or_assign(gpioLine, state);
+
+ debug("GPIO line {GPIO_NAME} went {GPIO_LEVEL}", "GPIO_NAME", gpioLine,
+ "GPIO_LEVEL", (state) ? "high" : "low");
+
+ for (const auto& config : std::views::values(presenceMap))
+ {
+ config->updateGPIOPresence(gpioLine);
+ }
+}
+
+auto GPIOPresenceManager::readGPIOAsyncEvent(std::string gpioLine)
+ -> sdbusplus::async::task<void>
+{
+ debug("Watching gpio events for {LINENAME}", "LINENAME", gpioLine);
+
+ if (!fdios.contains(gpioLine))
+ {
+ error("fdio for {LINENAME} not found", "LINENAME", gpioLine);
+ co_return;
+ }
+
+ const auto& fdio = fdios[gpioLine];
+
+ try
+ {
+ const int lineValue = gpioLines[gpioLine].get_value();
+
+ updatePresence(gpioLine, lineValue == gpiod::line_event::RISING_EDGE);
+ }
+ catch (std::exception& e)
+ {
+ error("Failed to read GPIO line {LINENAME}", "LINENAME", gpioLine);
+ error("{ERROR}", "ERROR", e);
+ co_return;
+ }
+
+ while (!ctx.stop_requested())
+ {
+ co_await fdio->next();
+
+ debug("Received gpio event for {LINENAME}", "LINENAME", gpioLine);
+
+ gpioLines[gpioLine].event_read();
+
+ auto lineValue = gpioLines[gpioLine].get_value();
+
+ if (lineValue < 0)
+ {
+ error("Failed to read GPIO line {LINENAME}", "LINENAME", gpioLine);
+ }
+
+ updatePresence(gpioLine, lineValue == gpiod::line_event::RISING_EDGE);
+ }
+}
+
+} // namespace gpio_presence