blob: 46bbca7d6dce1b811e6bca95568907b4f40c1f75 [file] [log] [blame]
Alexander Hansen8c4b1d92024-11-04 14:06:24 +01001/*
2 * SPDX-FileCopyrightText: Copyright (c) 2022-2024.
3 * All rights reserved. SPDX-License-Identifier: Apache-2.0
4 */
5
6#include "gpio_presence_manager.hpp"
7
8#include "device_presence.hpp"
9
10#include <boost/asio/posix/stream_descriptor.hpp>
11#include <gpiod.hpp>
12#include <phosphor-logging/lg2.hpp>
13#include <sdbusplus/async/timer.hpp>
14#include <sdbusplus/message/native_types.hpp>
15#include <xyz/openbmc_project/Configuration/GPIODeviceDetect/client.hpp>
16#include <xyz/openbmc_project/Configuration/GPIODeviceDetect/common.hpp>
Alexander Hansen92d79812025-08-22 14:34:22 +020017#include <xyz/openbmc_project/Inventory/Decorator/Compatible/client.hpp>
Alexander Hansen8c4b1d92024-11-04 14:06:24 +010018
19#include <memory>
20#include <ranges>
21#include <string>
22#include <utility>
23
24PHOSPHOR_LOG2_USING;
25
26namespace gpio_presence
27{
Alexander Hansen92d79812025-08-22 14:34:22 +020028const std::string entityManagerBusName = "xyz.openbmc_project.EntityManager";
Alexander Hansen8c4b1d92024-11-04 14:06:24 +010029
30GPIOPresenceManager::GPIOPresenceManager(sdbusplus::async::context& ctx) :
31 ctx(ctx), manager(ctx, "/"),
32 configProvider(
33 ConfigProvider(ctx, sdbusplus::common::xyz::openbmc_project::
34 configuration::GPIODeviceDetect::interface))
35{}
36
37auto GPIOPresenceManager::start() -> void
38{
39 ctx.spawn(initialize());
40}
41
42auto GPIOPresenceManager::getPresence(const std::string& name) -> bool
43{
44 if (!presenceMap.contains(name))
45 {
46 return false;
47 }
48 return presenceMap.at(name)->isPresent();
49}
50
51auto GPIOPresenceManager::initialize() -> sdbusplus::async::task<void>
52{
53 co_await configProvider.initialize(
54 std::bind_front(&GPIOPresenceManager::addConfigHandler, this),
55 std::bind_front(&GPIOPresenceManager::removeConfig, this));
56}
57
58auto GPIOPresenceManager::setupBusName() const -> std::string
59{
60 debug("requesting dbus name {NAME}", "NAME", service);
61
62 ctx.request_name(service);
63 return service;
64}
65
66auto GPIOPresenceManager::addConfig(const sdbusplus::message::object_path& obj,
67 std::unique_ptr<DevicePresence> config)
68 -> void
69{
70 debug("adding configuration for {NAME}", "NAME", obj);
71 presenceMap.insert_or_assign(obj, std::move(config));
72
73 debug("found valid configuration at object path {OBJPATH}", "OBJPATH", obj);
74
75 auto gpioConfigs = presenceMap[obj]->gpioPolarity;
76
77 // populate fdios
78 for (auto& [gpioName, _] : gpioConfigs)
79 {
80 if (gpioLines.contains(gpioName))
81 {
82 continue;
83 }
84
85 try
86 {
87 gpioLines[gpioName] = gpiod::find_line(gpioName);
88 }
89 catch (std::exception& e)
90 {
91 error("gpiod::find_line failed: {ERROR}", "ERROR", e);
92 return;
93 }
94
95 gpiod::line_request lineConfig;
96 lineConfig.consumer = "gpio-presence";
97 lineConfig.request_type = gpiod::line_request::EVENT_BOTH_EDGES |
98 gpiod::line_request::DIRECTION_INPUT;
99
100 int lineFd = -1;
101 try
102 {
103 gpioLines[gpioName].request(lineConfig);
104
105 lineFd = gpioLines[gpioName].event_get_fd();
106 }
107 catch (std::exception& e)
108 {
109 error("{ERROR}", "ERROR", e);
110 return;
111 }
112 if (lineFd < 0)
113 {
114 error("could not get event fd for gpio '{NAME}'", "NAME", gpioName);
115 return;
116 }
117
118 if (!fdios.contains(gpioName))
119 {
120 fdios.insert(
121 {gpioName,
122 std::make_unique<sdbusplus::async::fdio>(ctx, lineFd)});
123
124 ctx.spawn(readGPIOAsyncEvent(gpioName));
125 }
126 }
127}
128
129auto GPIOPresenceManager::addConfigHandler(sdbusplus::message::object_path obj)
130 -> void
131{
132 // NOLINTBEGIN(performance-unnecessary-value-param)
133 ctx.spawn(addConfigFromDbusAsync(obj));
134 // NOLINTEND(performance-unnecessary-value-param)
135}
136
137// NOLINTBEGIN(performance-unnecessary-value-param)
138auto GPIOPresenceManager::addConfigFromDbusAsync(
139 const sdbusplus::message::object_path obj) -> sdbusplus::async::task<void>
140// NOLINTEND(performance-unnecessary-value-param)
141{
142 auto props = co_await sdbusplus::client::xyz::openbmc_project::
143 configuration::GPIODeviceDetect<>(ctx)
Alexander Hansen92d79812025-08-22 14:34:22 +0200144 .service(entityManagerBusName)
Alexander Hansen8c4b1d92024-11-04 14:06:24 +0100145 .path(obj.str)
146 .properties();
147
148 if (props.presence_pin_names.size() != props.presence_pin_values.size())
149 {
150 error(
151 "presence pin names and presence pin values have different sizes");
152 co_return;
153 }
154
Alexander Hansen92d79812025-08-22 14:34:22 +0200155 const auto parentInvCompatible = co_await getParentInventoryCompatible(obj);
156
Alexander Hansen8c4b1d92024-11-04 14:06:24 +0100157 auto devicePresence = std::make_unique<DevicePresence>(
158 ctx, props.presence_pin_names, props.presence_pin_values, props.name,
Alexander Hansen92d79812025-08-22 14:34:22 +0200159 gpioState, parentInvCompatible);
Alexander Hansen8c4b1d92024-11-04 14:06:24 +0100160
161 if (devicePresence)
162 {
163 addConfig(obj, std::move(devicePresence));
164 }
165}
166
Alexander Hansen92d79812025-08-22 14:34:22 +0200167auto GPIOPresenceManager::getParentInventoryCompatible(
168 const sdbusplus::message::object_path& obj)
169 -> sdbusplus::async::task<std::vector<std::string>>
170{
171 const auto parentInvObjPath = obj.parent_path();
172
173 auto clientCompatible = sdbusplus::client::xyz::openbmc_project::inventory::
174 decorator::Compatible<>(ctx)
175 .service(entityManagerBusName)
176 .path(parentInvObjPath.str);
177
178 try
179 {
180 auto parentCompatibleHardware = co_await clientCompatible.names();
181 lg2::debug(
182 "Found 'Compatible' decorator on parent inventory path of {PATH}",
183 "PATH", obj);
184 co_return parentCompatibleHardware;
185 }
186 catch (std::exception& e)
187 {
188 // pass, since it is an optional interface
189 lg2::debug("Did not find interface {INTF} on path {PATH}", "INTF",
190 sdbusplus::common::xyz::openbmc_project::inventory::
191 decorator::Compatible::interface,
192 "PATH", parentInvObjPath);
193 co_return {};
194 }
195}
196
Alexander Hansen8c4b1d92024-11-04 14:06:24 +0100197auto GPIOPresenceManager::removeConfig(const std::string& objPath) -> void
198{
199 if (!presenceMap.contains(objPath))
200 {
201 return;
202 }
203
204 debug("erasing configuration for object path {OBJPATH}", "OBJPATH",
205 objPath);
206 presenceMap.erase(objPath);
207
208 std::set<std::string> gpiosNeeded;
209
210 for (const auto& config : std::views::values(presenceMap))
211 {
212 for (const auto& gpio : std::views::keys(config->gpioPolarity))
213 {
214 gpiosNeeded.insert(gpio);
215 }
216 }
217
218 auto ks = std::views::keys(gpioLines);
219 std::set<std::string> trackedGPIOs{ks.begin(), ks.end()};
220
221 for (const auto& trackedGPIO : trackedGPIOs)
222 {
223 if (gpiosNeeded.contains(trackedGPIO))
224 {
225 continue;
226 }
227
228 gpioLines[trackedGPIO].release();
229
230 gpioLines.erase(trackedGPIO);
231 fdios.erase(fdios.find(trackedGPIO));
232 }
233}
234
235auto GPIOPresenceManager::updatePresence(const std::string& gpioLine,
236 bool state) -> void
237{
238 gpioState.insert_or_assign(gpioLine, state);
239
240 debug("GPIO line {GPIO_NAME} went {GPIO_LEVEL}", "GPIO_NAME", gpioLine,
241 "GPIO_LEVEL", (state) ? "high" : "low");
242
243 for (const auto& config : std::views::values(presenceMap))
244 {
245 config->updateGPIOPresence(gpioLine);
246 }
247}
248
249auto GPIOPresenceManager::readGPIOAsyncEvent(std::string gpioLine)
250 -> sdbusplus::async::task<void>
251{
252 debug("Watching gpio events for {LINENAME}", "LINENAME", gpioLine);
253
254 if (!fdios.contains(gpioLine))
255 {
256 error("fdio for {LINENAME} not found", "LINENAME", gpioLine);
257 co_return;
258 }
259
260 const auto& fdio = fdios[gpioLine];
261
262 try
263 {
264 const int lineValue = gpioLines[gpioLine].get_value();
265
266 updatePresence(gpioLine, lineValue == gpiod::line_event::RISING_EDGE);
267 }
268 catch (std::exception& e)
269 {
270 error("Failed to read GPIO line {LINENAME}", "LINENAME", gpioLine);
271 error("{ERROR}", "ERROR", e);
272 co_return;
273 }
274
275 while (!ctx.stop_requested())
276 {
277 co_await fdio->next();
278
279 debug("Received gpio event for {LINENAME}", "LINENAME", gpioLine);
280
eldin.lee533d4902025-07-30 20:01:24 +0800281 // event_read() does not clear the EPOLLIN flag immediately; it is
282 // cleared during the subsequent epoll check. Therefore, call event_wait
283 // with a zero timeout to ensure that event_read() is only invoked when
284 // an event is available, preventing it from blocking.
285 if (!gpioLines[gpioLine].event_wait(std::chrono::milliseconds(0)))
286 {
287 continue;
288 }
289
Alexander Hansen8c4b1d92024-11-04 14:06:24 +0100290 gpioLines[gpioLine].event_read();
291
292 auto lineValue = gpioLines[gpioLine].get_value();
293
294 if (lineValue < 0)
295 {
296 error("Failed to read GPIO line {LINENAME}", "LINENAME", gpioLine);
297 }
298
299 updatePresence(gpioLine, lineValue == gpiod::line_event::RISING_EDGE);
300 }
301}
302
303} // namespace gpio_presence