Add phosphor-multi-gpio-presence
The new service works like phosphor-gpio-presence, but uses libgpiod
and can monitor an arbitrary amount of GPIOs.
The driver loading feature hasn't been ported to the new service.
Change-Id: I412345f804208e48eec40ec020b3a0d8f668a34b
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
diff --git a/README.md b/README.md
index 7851995..c8090eb 100644
--- a/README.md
+++ b/README.md
@@ -73,3 +73,56 @@
{ "Name": "SystemReset", "GpioNum": 46, "ChipId": "0" }
]
```
+
+### `phosphor-multi-gpio-presence`
+
+This daemon accepts command line parameter as a well-defined GPIO configuration
+file in json format to monitor list of gpios from config file and sets inventory
+presence as defined in config based on gpio state change. It uses libgpiod
+library.
+
+### Difference
+
+New implementation (phosphor-multi-gpio-presence) provides multiple gpio line
+monitoring in single instance of phosphor-multi-gpio-presence running. It is
+very easy to add list of gpios into JSON config file and it also supports of
+GPIO line by name defined in kernel.
+
+## Configuration
+
+There is a phosphor-multi-gpio-presence.json file that defines details of GPIOs
+which is required to be monitored. This file can be replaced with a platform
+specific configuration file via bbappend.
+
+Following are fields in json file
+
+1. Name: PrettyName of inventory item
+2. LineName: this is the line name defined in device tree for specific gpio
+3. GpioNum: GPIO offset, this field is optional if LineName is defined.
+4. ChipId: This is device name either offset ("0") or complete gpio device
+ ("gpiochip0"). This field is not required if LineName is defined.
+5. Inventory: Object path under inventory that will be created
+6. ExtraInterfaces: [Optional] List of interfaces to associate to inventory item
+7. ActiveLow: [Optional] Object is present on LOW level
+8. Bias: [Optional] Configure a BIAS on the GPIO line, for example PULL_UP
+
+## Sample config file
+
+```json
+[
+ {
+ "Name": "DIMM A0",
+ "LineName": "POWER_BUTTON",
+ "Inventory": "/system/chassis/motherboard/dimm_a0"
+ },
+ {
+ "Name": "Powersupply 0",
+ "ChipId": "0",
+ "GpioNum": 14,
+ "Inventory": "/system/chassis/motherboard/powersupply0",
+ "ActiveLow": true,
+ "Bias": "PULL_UP",
+ "ExtraInterfaces": ["xyz.openbmc_project.Inventory.Item.PowerSupply"]
+ }
+]
+```
diff --git a/meson.build b/meson.build
index 5337496..0148a0c 100644
--- a/meson.build
+++ b/meson.build
@@ -62,6 +62,12 @@
)
fs.copyfile(
+ 'phosphor-multi-gpio-presence.service',
+ install: true,
+ install_dir: systemd_system_unit_dir
+)
+
+fs.copyfile(
'phosphor-gpio-presence@.service',
install: true,
install_dir: systemd_system_unit_dir
@@ -136,6 +142,7 @@
cli11_dep,
libgpiod,
nlohmann_json_dep,
+ phosphor_dbus_interfaces,
phosphor_logging,
sdbusplus,
],
@@ -144,6 +151,7 @@
)
subdir('presence')
+subdir('multi-presence')
build_tests = get_option('tests')
if build_tests.allowed()
diff --git a/multi-presence/gpio_presence.cpp b/multi-presence/gpio_presence.cpp
new file mode 100644
index 0000000..338248f
--- /dev/null
+++ b/multi-presence/gpio_presence.cpp
@@ -0,0 +1,223 @@
+/**
+ * Copyright © 2019 Facebook
+ * Copyright © 2023 9elements GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gpio_presence.hpp"
+
+#include "xyz/openbmc_project/Common/error.hpp"
+
+#include <phosphor-logging/elog-errors.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/lg2.hpp>
+#include <sdbusplus/bus.hpp>
+
+namespace phosphor
+{
+namespace gpio
+{
+
+using namespace phosphor::logging;
+using namespace sdbusplus::xyz::openbmc_project::Common::Error;
+
+constexpr auto INVENTORY_PATH = "/xyz/openbmc_project/inventory";
+constexpr auto INVENTORY_INTF = "xyz.openbmc_project.Inventory.Manager";
+
+constexpr auto MAPPER_BUSNAME = "xyz.openbmc_project.ObjectMapper";
+constexpr auto MAPPER_PATH = "/xyz/openbmc_project/object_mapper";
+constexpr auto MAPPER_INTERFACE = "xyz.openbmc_project.ObjectMapper";
+
+std::string getService(const std::string& path, const std::string& interface,
+ sdbusplus::bus_t& bus)
+{
+ auto mapperCall = bus.new_method_call(MAPPER_BUSNAME, MAPPER_PATH,
+ MAPPER_INTERFACE, "GetObject");
+
+ mapperCall.append(path);
+ mapperCall.append(std::vector<std::string>({interface}));
+
+ std::map<std::string, std::vector<std::string>> mapperResponse;
+ try
+ {
+ auto mapperResponseMsg = bus.call(mapperCall);
+ mapperResponseMsg.read(mapperResponse);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error(
+ "Error in mapper call to get service name, path: {PATH}, interface: {INTERFACE}, error: {ERROR}",
+ "PATH", path, "INTERFACE", interface, "ERROR", e);
+ elog<InternalFailure>();
+ }
+
+ return mapperResponse.begin()->first;
+}
+
+GpioPresence::ObjectMap GpioPresence::getObjectMap(bool present)
+{
+ ObjectMap invObj;
+ InterfaceMap invIntf;
+ PropertyMap invProp;
+
+ invProp.emplace("Present", present);
+ invProp.emplace("PrettyName", name);
+ invIntf.emplace("xyz.openbmc_project.Inventory.Item", std::move(invProp));
+ // Add any extra interfaces we want to associate with the inventory item
+ for (auto& iface : interfaces)
+ {
+ invIntf.emplace(iface, PropertyMap());
+ }
+ invObj.emplace(std::move(inventory), std::move(invIntf));
+
+ return invObj;
+}
+
+void GpioPresence::updateInventory(bool present)
+{
+ ObjectMap invObj = getObjectMap(present);
+
+ lg2::info(
+ "Updating inventory present property value to {PRESENT}, path: {PATH}",
+ "PRESENT", present, "PATH", inventory);
+
+ auto bus = sdbusplus::bus::new_default();
+ auto invService = getService(INVENTORY_PATH, INVENTORY_INTF, bus);
+
+ // Update inventory
+ auto invMsg = bus.new_method_call(invService.c_str(), INVENTORY_PATH,
+ INVENTORY_INTF, "Notify");
+ invMsg.append(std::move(invObj));
+ try
+ {
+ auto invMgrResponseMsg = bus.call(invMsg);
+ }
+ catch (const sdbusplus::exception_t& e)
+ {
+ lg2::error(
+ "Error in inventory manager call to update inventory: {ERROR}",
+ "ERROR", e);
+ elog<InternalFailure>();
+ }
+}
+
+void GpioPresence::scheduleEventHandler()
+{
+ std::string gpio = std::string(gpioLineMsg);
+
+ gpioEventDescriptor.async_wait(
+ boost::asio::posix::stream_descriptor::wait_read,
+ [this, gpio](const boost::system::error_code& ec) {
+ if (ec == boost::asio::error::operation_aborted)
+ {
+ // we were cancelled
+ return;
+ }
+ if (ec)
+ {
+ lg2::error("{GPIO} event handler error: {ERROR}", "GPIO", gpio,
+ "ERROR", ec.message());
+ return;
+ }
+ gpioEventHandler();
+ });
+}
+
+void GpioPresence::cancelEventHandler()
+{
+ gpioEventDescriptor.cancel();
+}
+
+void GpioPresence::gpioEventHandler()
+{
+ gpiod_line_event gpioLineEvent;
+
+ if (gpiod_line_event_read_fd(gpioEventDescriptor.native_handle(),
+ &gpioLineEvent) < 0)
+ {
+ lg2::error("Failed to read {GPIO} from fd", "GPIO", gpioLineMsg);
+ return;
+ }
+
+ if (gpioLineEvent.event_type == GPIOD_LINE_EVENT_RISING_EDGE)
+ {
+ lg2::info("{GPIO} Asserted", "GPIO", gpioLineMsg);
+ }
+ else
+ {
+ lg2::info("{GPIO} Deasserted", "GPIO", gpioLineMsg);
+ }
+ updateInventory(gpioLineEvent.event_type == GPIOD_LINE_EVENT_RISING_EDGE);
+
+ /* Schedule a wait event */
+ scheduleEventHandler();
+}
+
+int GpioPresence::requestGPIOEvents()
+{
+ std::string flags;
+
+ /* Request an event to monitor for respected gpio line */
+ if (gpiod_line_request(gpioLine, &gpioConfig, 0) < 0)
+ {
+ lg2::error("Failed to request {GPIO}: {ERRNO}", "GPIO", gpioLineMsg,
+ "ERRNO", errno);
+ return -1;
+ }
+
+ int gpioLineFd = gpiod_line_event_get_fd(gpioLine);
+ if (gpioLineFd < 0)
+ {
+ lg2::error("Failed to get fd for {GPIO}", "GPIO", gpioLineMsg);
+ return -1;
+ }
+
+ if (gpioConfig.flags & GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE)
+ {
+ flags += " Bias DISABLE";
+ }
+ else if (gpioConfig.flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP)
+ {
+ flags += " Bias PULL_UP";
+ }
+ else if (gpioConfig.flags & GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN)
+ {
+ flags += " Bias PULL_DOWN";
+ }
+
+ if (gpioConfig.flags & GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW)
+ {
+ flags += " ActiveLow";
+ }
+
+ if (!flags.empty())
+ {
+ flags = "[" + flags + "]";
+ }
+
+ lg2::info("{GPIO} {FLAGS} monitoring started", "GPIO", gpioLineMsg, "FLAGS",
+ flags);
+
+ /* Assign line fd to descriptor for monitoring */
+ gpioEventDescriptor.assign(gpioLineFd);
+
+ updateInventory(gpiod_line_get_value(gpioLine));
+
+ /* Schedule a wait event */
+ scheduleEventHandler();
+
+ return 0;
+}
+} // namespace gpio
+} // namespace phosphor
diff --git a/multi-presence/gpio_presence.hpp b/multi-presence/gpio_presence.hpp
new file mode 100644
index 0000000..abc4b63
--- /dev/null
+++ b/multi-presence/gpio_presence.hpp
@@ -0,0 +1,133 @@
+#pragma once
+
+#include <gpiod.h>
+
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/posix/stream_descriptor.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/message.hpp>
+
+#include <cstdlib>
+#include <filesystem>
+#include <map>
+#include <string>
+#include <vector>
+
+static constexpr auto deviceField = 0;
+static constexpr auto pathField = 1;
+using Device = std::string;
+using Path = std::filesystem::path;
+using Driver = std::tuple<Device, Path>;
+using Interface = std::string;
+
+namespace phosphor
+{
+namespace gpio
+{
+
+/** @class GpioPresence
+ * @brief Responsible for catching GPIO state change
+ * condition and updating the inventory presence.
+ */
+class GpioPresence
+{
+ using Property = std::string;
+ using Value = std::variant<bool, std::string>;
+ // Association between property and its value
+ using PropertyMap = std::map<Property, Value>;
+ using Interface = std::string;
+ // Association between interface and the D-Bus property
+ using InterfaceMap = std::map<Interface, PropertyMap>;
+ using Object = sdbusplus::message::object_path;
+ // Association between object and the interface
+ using ObjectMap = std::map<Object, InterfaceMap>;
+
+ public:
+ GpioPresence() = delete;
+ ~GpioPresence() = default;
+ GpioPresence(const GpioPresence&) = delete;
+ GpioPresence& operator=(const GpioPresence&) = delete;
+
+ /** @brief Constructs GpioPresence object.
+ *
+ * @param[in] line - GPIO line from libgpiod
+ * @param[in] config - configuration of line with event
+ * @param[in] io - io service
+ * @param[in] inventory - Object path under inventory that
+ will be created
+ * @param[in] extraInterfaces - List of interfaces to associate to
+ inventory item
+ * @param[in] name - PrettyName of inventory object
+ * @param[in] lineMsg - GPIO line message to be used for log
+ */
+ GpioPresence(gpiod_line* line, gpiod_line_request_config& config,
+ boost::asio::io_context& io, const std::string& inventory,
+ const std::vector<std::string>& extraInterfaces,
+ const std::string& name, const std::string& lineMsg) :
+ gpioLine(line),
+ gpioConfig(config), gpioEventDescriptor(io), inventory(inventory),
+ interfaces(extraInterfaces), name(name), gpioLineMsg(lineMsg)
+ {
+ requestGPIOEvents();
+ };
+
+ GpioPresence(GpioPresence&& old) noexcept :
+ gpioLine(old.gpioLine), gpioConfig(old.gpioConfig),
+ gpioEventDescriptor(old.gpioEventDescriptor.get_executor()),
+ inventory(std::move(old.inventory)),
+ interfaces(std::move(old.interfaces)), name(std::move(old.name)),
+ gpioLineMsg(std::move(old.gpioLineMsg))
+ {
+ old.cancelEventHandler();
+
+ gpioEventDescriptor = std::move(old.gpioEventDescriptor);
+
+ scheduleEventHandler();
+ };
+
+ private:
+ /** @brief GPIO line */
+ gpiod_line* gpioLine;
+
+ /** @brief GPIO line configuration */
+ gpiod_line_request_config gpioConfig;
+
+ /** @brief GPIO event descriptor */
+ boost::asio::posix::stream_descriptor gpioEventDescriptor;
+
+ /** @brief Object path under inventory that will be created */
+ const std::string inventory;
+
+ /** @brief List of interfaces to associate to inventory item */
+ const std::vector<std::string> interfaces;
+
+ /** @brief PrettyName of inventory object */
+ const std::string name;
+
+ /** @brief GPIO line name message */
+ const std::string gpioLineMsg;
+
+ /** @brief register handler for gpio event
+ *
+ * @return - 0 on success and -1 otherwise
+ */
+ int requestGPIOEvents();
+
+ /** @brief Schedule an event handler for GPIO event to trigger */
+ void scheduleEventHandler();
+
+ /** @brief Stop the event handler for GPIO events */
+ void cancelEventHandler();
+
+ /** @brief Handle the GPIO event and starts configured target */
+ void gpioEventHandler();
+
+ /** @brief Returns the object map for the inventory object */
+ ObjectMap getObjectMap(bool present);
+
+ /** @brief Updates the inventory */
+ void updateInventory(bool present);
+};
+
+} // namespace gpio
+} // namespace phosphor
diff --git a/multi-presence/main.cpp b/multi-presence/main.cpp
new file mode 100644
index 0000000..4b631d9
--- /dev/null
+++ b/multi-presence/main.cpp
@@ -0,0 +1,195 @@
+/**
+ * Copyright © 2019 Facebook
+ * Copyright © 2023 9elements GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gpio_presence.hpp"
+
+#include <CLI/CLI.hpp>
+#include <boost/asio/io_context.hpp>
+#include <nlohmann/json.hpp>
+#include <phosphor-logging/lg2.hpp>
+
+#include <fstream>
+
+namespace phosphor
+{
+namespace gpio
+{
+
+const std::map<std::string, int> biasMap = {
+ /**< Set bias as is. */
+ {"AS_IS", 0},
+ /**< Disable bias. */
+ {"DISABLE", GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE},
+ /**< Enable pull-up. */
+ {"PULL_UP", GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP},
+ /**< Enable pull-down. */
+ {"PULL_DOWN", GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN}};
+}
+} // namespace phosphor
+
+int main(int argc, char** argv)
+{
+ boost::asio::io_context io;
+
+ CLI::App app{"Monitor gpio presence status"};
+
+ std::string gpioFileName;
+
+ /* Add an input option */
+ app.add_option("-c,--config", gpioFileName, "Name of config json file")
+ ->required()
+ ->check(CLI::ExistingFile);
+
+ /* Parse input parameter */
+ try
+ {
+ app.parse(argc, argv);
+ }
+ catch (const CLI::Error& e)
+ {
+ return app.exit(e);
+ }
+
+ /* Get list of gpio config details from json file */
+ std::ifstream file(gpioFileName);
+ if (!file)
+ {
+ lg2::error("Failed to open config file: {FILE}", "FILE", gpioFileName);
+ return -1;
+ }
+
+ nlohmann::json gpioMonObj;
+ file >> gpioMonObj;
+ file.close();
+
+ std::vector<phosphor::gpio::GpioPresence> gpios;
+
+ for (auto& obj : gpioMonObj)
+ {
+ /* GPIO Line message */
+ std::string lineMsg = "GPIO Line ";
+
+ /* GPIO line */
+ gpiod_line* line = NULL;
+
+ /* GPIO line configuration, default to monitor both edge */
+ struct gpiod_line_request_config config
+ {
+ "gpio_monitor", GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES, 0
+ };
+
+ /* Pretty name of the inventory object */
+ std::string name;
+
+ /* Object path under inventory that will be created */
+ std::string inventory;
+
+ /* List of interfaces to associate to inventory item */
+ std::vector<std::string> extraInterfaces;
+
+ if (obj.find("LineName") == obj.end())
+ {
+ /* If there is no line Name defined then gpio num nd chip
+ * id must be defined. GpioNum is integer mapping to the
+ * GPIO key configured by the kernel
+ */
+ if (obj.find("GpioNum") == obj.end() ||
+ obj.find("ChipId") == obj.end())
+ {
+ lg2::error("Failed to find line name or gpio number: {FILE}",
+ "FILE", gpioFileName);
+ return -1;
+ }
+
+ std::string chipIdStr = obj["ChipId"].get<std::string>();
+ int gpioNum = obj["GpioNum"].get<int>();
+
+ lineMsg += chipIdStr + " " + std::to_string(gpioNum);
+
+ /* Get the GPIO line */
+ line = gpiod_line_get(chipIdStr.c_str(), gpioNum);
+ }
+ else
+ {
+ /* Find the GPIO line */
+ std::string lineName = obj["LineName"].get<std::string>();
+ lineMsg += lineName;
+ line = gpiod_line_find(lineName.c_str());
+ }
+
+ if (line == NULL)
+ {
+ lg2::error("Failed to find the {GPIO}", "GPIO", lineMsg);
+ return -1;
+ }
+
+ /* Parse out inventory argument. */
+ if (obj.find("Inventory") == obj.end())
+ {
+ lg2::error("{GPIO}: Inventory path not specified", "GPIO", lineMsg);
+ return -1;
+ }
+ else
+ {
+ inventory = obj["Inventory"].get<std::string>();
+ }
+
+ if (obj.find("Name") == obj.end())
+ {
+ lg2::error("{GPIO}: Name path not specified", "GPIO", lineMsg);
+ return -1;
+ }
+ else
+ {
+ name = obj["Name"].get<std::string>();
+ }
+
+ /* Parse optional bias */
+ if (obj.find("Bias") != obj.end())
+ {
+ std::string biasName = obj["Bias"].get<std::string>();
+ auto findBias = phosphor::gpio::biasMap.find(biasName);
+ if (findBias == phosphor::gpio::biasMap.end())
+ {
+ lg2::error("{GPIO}: Bias unknown: {BIAS}", "GPIO", lineMsg,
+ "BIAS", biasName);
+ return -1;
+ }
+
+ config.flags = findBias->second;
+ }
+
+ /* Parse optional active level */
+ if (obj.find("ActiveLow") != obj.end() && obj["ActiveLow"].get<bool>())
+ {
+ config.flags |= GPIOD_LINE_REQUEST_FLAG_ACTIVE_LOW;
+ }
+
+ /* Parse optional extra interfaces */
+ if (obj.find("ExtraInterfaces") != obj.end())
+ {
+ obj.at("ExtraInterfaces").get_to(extraInterfaces);
+ }
+
+ /* Create a monitor object and let it do all the rest */
+ gpios.push_back(phosphor::gpio::GpioPresence(
+ line, config, io, inventory, extraInterfaces, name, lineMsg));
+ }
+ io.run();
+
+ return 0;
+}
diff --git a/multi-presence/meson.build b/multi-presence/meson.build
new file mode 100644
index 0000000..be1f97c
--- /dev/null
+++ b/multi-presence/meson.build
@@ -0,0 +1,16 @@
+executable(
+ 'phosphor-multi-gpio-presence',
+ 'gpio_presence.cpp',
+ 'main.cpp',
+ dependencies: [
+ cli11_dep,
+ libgpiod,
+ nlohmann_json_dep,
+ phosphor_logging,
+ sdbusplus,
+ ],
+ cpp_args: boost_args,
+ include_directories: '..',
+ implicit_include_directories: false,
+ install: true,
+)
\ No newline at end of file
diff --git a/phosphor-multi-gpio-presence.json b/phosphor-multi-gpio-presence.json
new file mode 100644
index 0000000..65b5283
--- /dev/null
+++ b/phosphor-multi-gpio-presence.json
@@ -0,0 +1,16 @@
+[
+ {
+ "Name": "DIMM A0",
+ "LineName": "POWER_BUTTON",
+ "Inventory": "/system/chassis/motherboard/dimm_a0"
+ },
+ {
+ "Name": "Powersupply 0",
+ "ChipId": "0",
+ "GpioNum": 14,
+ "Inventory": "/system/chassis/motherboard/powersupply0",
+ "ActiveLow": true,
+ "Bias": "PULL_UP",
+ "ExtraInterfaces": ["xyz.openbmc_project.Inventory.Item.PowerSupply"]
+ }
+]
diff --git a/phosphor-multi-gpio-presence.service b/phosphor-multi-gpio-presence.service
new file mode 100644
index 0000000..f3c9c1a
--- /dev/null
+++ b/phosphor-multi-gpio-presence.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Phosphor Multi GPIO presence
+Wants=mapper-wait@-xyz-openbmc_project-inventory.service
+After=mapper-wait@-xyz-openbmc_project-inventory.service
+
+[Service]
+Restart=no
+ExecStart=/usr/bin/phosphor-multi-gpio-presence --config /usr/share/phosphor-gpio-monitor/phosphor-multi-gpio-presence.json
+
+[Install]
+RequiredBy=multi-user.target
\ No newline at end of file