1 /*
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>
17 #include <xyz/openbmc_project/Inventory/Decorator/Compatible/client.hpp>
18
19 #include <memory>
20 #include <ranges>
21 #include <string>
22 #include <utility>
23
24 PHOSPHOR_LOG2_USING;
25
26 namespace gpio_presence
27 {
28 const std::string entityManagerBusName = "xyz.openbmc_project.EntityManager";
29
GPIOPresenceManager(sdbusplus::async::context & ctx)30 GPIOPresenceManager::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
start()37 auto GPIOPresenceManager::start() -> void
38 {
39 ctx.spawn(initialize());
40 }
41
getPresence(const std::string & name)42 auto 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
initialize()51 auto 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
setupBusName() const58 auto 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
addConfig(const sdbusplus::message::object_path & obj,std::unique_ptr<DevicePresence> config)66 auto 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 // Re-add: restore presence from cached GPIO state.
78 for (const auto& [gpioName, _] : gpioConfigs)
79 {
80 if (gpioState.contains(gpioName))
81 {
82 presenceMap[obj]->updateGPIOPresence(gpioName);
83 }
84 }
85
86 // populate fdios
87 for (auto& [gpioName, _] : gpioConfigs)
88 {
89 if (gpioLines.contains(gpioName))
90 {
91 continue;
92 }
93
94 try
95 {
96 gpioLines[gpioName] = gpiod::find_line(gpioName);
97 }
98 catch (std::exception& e)
99 {
100 error("gpiod::find_line failed: {ERROR}", "ERROR", e);
101 return;
102 }
103
104 gpiod::line_request lineConfig;
105 lineConfig.consumer = "gpio-presence";
106 lineConfig.request_type = gpiod::line_request::EVENT_BOTH_EDGES |
107 gpiod::line_request::DIRECTION_INPUT;
108
109 int lineFd = -1;
110 try
111 {
112 gpioLines[gpioName].request(lineConfig);
113
114 lineFd = gpioLines[gpioName].event_get_fd();
115 }
116 catch (std::exception& e)
117 {
118 error("{NAME}: {ERROR}", "NAME", gpioName, "ERROR", e);
119 return;
120 }
121 if (lineFd < 0)
122 {
123 error("could not get event fd for gpio '{NAME}'", "NAME", gpioName);
124 return;
125 }
126
127 if (!fdios.contains(gpioName))
128 {
129 fdios.insert(
130 {gpioName,
131 std::make_unique<sdbusplus::async::fdio>(ctx, lineFd)});
132
133 ctx.spawn(readGPIOAsyncEvent(gpioName));
134 }
135 }
136 }
137
addConfigHandler(sdbusplus::message::object_path obj)138 auto GPIOPresenceManager::addConfigHandler(sdbusplus::message::object_path obj)
139 -> void
140 {
141 // NOLINTBEGIN(performance-unnecessary-value-param)
142 ctx.spawn(addConfigFromDbusAsync(obj));
143 // NOLINTEND(performance-unnecessary-value-param)
144 }
145
146 // NOLINTBEGIN(performance-unnecessary-value-param)
addConfigFromDbusAsync(const sdbusplus::message::object_path obj)147 auto GPIOPresenceManager::addConfigFromDbusAsync(
148 const sdbusplus::message::object_path obj) -> sdbusplus::async::task<void>
149 // NOLINTEND(performance-unnecessary-value-param)
150 {
151 auto props = co_await sdbusplus::client::xyz::openbmc_project::
152 configuration::GPIODeviceDetect<>(ctx)
153 .service(entityManagerBusName)
154 .path(obj.str)
155 .properties();
156
157 if (props.presence_pin_names.size() != props.presence_pin_values.size())
158 {
159 error(
160 "presence pin names and presence pin values have different sizes");
161 co_return;
162 }
163
164 const auto parentInvCompatible = co_await getParentInventoryCompatible(obj);
165
166 auto devicePresence = std::make_unique<DevicePresence>(
167 ctx, props.presence_pin_names, props.presence_pin_values, props.name,
168 gpioState, parentInvCompatible);
169
170 if (devicePresence)
171 {
172 addConfig(obj, std::move(devicePresence));
173 }
174 }
175
getParentInventoryCompatible(const sdbusplus::message::object_path & obj)176 auto GPIOPresenceManager::getParentInventoryCompatible(
177 const sdbusplus::message::object_path& obj)
178 -> sdbusplus::async::task<std::vector<std::string>>
179 {
180 const auto parentInvObjPath = obj.parent_path();
181
182 auto clientCompatible = sdbusplus::client::xyz::openbmc_project::inventory::
183 decorator::Compatible<>(ctx)
184 .service(entityManagerBusName)
185 .path(parentInvObjPath.str);
186
187 try
188 {
189 auto parentCompatibleHardware = co_await clientCompatible.names();
190 lg2::debug(
191 "Found 'Compatible' decorator on parent inventory path of {PATH}",
192 "PATH", obj);
193 co_return parentCompatibleHardware;
194 }
195 catch (std::exception& e)
196 {
197 // pass, since it is an optional interface
198 lg2::debug("Did not find interface {INTF} on path {PATH}", "INTF",
199 sdbusplus::common::xyz::openbmc_project::inventory::
200 decorator::Compatible::interface,
201 "PATH", parentInvObjPath);
202 co_return {};
203 }
204 }
205
removeConfig(const std::string & objPath)206 auto GPIOPresenceManager::removeConfig(const std::string& objPath) -> void
207 {
208 if (!presenceMap.contains(objPath))
209 {
210 return;
211 }
212
213 debug("erasing configuration for object path {OBJPATH}", "OBJPATH",
214 objPath);
215 presenceMap.erase(objPath);
216
217 std::set<std::string> gpiosNeeded;
218
219 for (const auto& config : std::views::values(presenceMap))
220 {
221 for (const auto& gpio : std::views::keys(config->gpioPolarity))
222 {
223 gpiosNeeded.insert(gpio);
224 }
225 }
226
227 auto ks = std::views::keys(gpioLines);
228 std::set<std::string> trackedGPIOs{ks.begin(), ks.end()};
229
230 for (const auto& trackedGPIO : trackedGPIOs)
231 {
232 if (gpiosNeeded.contains(trackedGPIO))
233 {
234 continue;
235 }
236
237 gpioLines[trackedGPIO].release();
238
239 gpioLines.erase(trackedGPIO);
240 fdios.erase(fdios.find(trackedGPIO));
241 }
242 }
243
updatePresence(const std::string & gpioLine,bool state)244 auto GPIOPresenceManager::updatePresence(const std::string& gpioLine,
245 bool state) -> void
246 {
247 gpioState.insert_or_assign(gpioLine, state);
248
249 debug("GPIO line {GPIO_NAME} went {GPIO_LEVEL}", "GPIO_NAME", gpioLine,
250 "GPIO_LEVEL", (state) ? "high" : "low");
251
252 for (const auto& config : std::views::values(presenceMap))
253 {
254 config->updateGPIOPresence(gpioLine);
255 }
256 }
257
readGPIOAsyncEvent(std::string gpioLine)258 auto GPIOPresenceManager::readGPIOAsyncEvent(std::string gpioLine)
259 -> sdbusplus::async::task<void>
260 {
261 debug("Watching gpio events for {LINENAME}", "LINENAME", gpioLine);
262
263 if (!fdios.contains(gpioLine))
264 {
265 error("fdio for {LINENAME} not found", "LINENAME", gpioLine);
266 co_return;
267 }
268
269 const auto& fdio = fdios[gpioLine];
270
271 try
272 {
273 const int lineValue = gpioLines[gpioLine].get_value();
274
275 updatePresence(gpioLine, lineValue == gpiod::line_event::RISING_EDGE);
276 }
277 catch (std::exception& e)
278 {
279 error("Failed to read GPIO line {LINENAME}", "LINENAME", gpioLine);
280 error("{ERROR}", "ERROR", e);
281 co_return;
282 }
283
284 while (!ctx.stop_requested())
285 {
286 co_await fdio->next();
287
288 debug("Received gpio event for {LINENAME}", "LINENAME", gpioLine);
289
290 // event_read() does not clear the EPOLLIN flag immediately; it is
291 // cleared during the subsequent epoll check. Therefore, call event_wait
292 // with a zero timeout to ensure that event_read() is only invoked when
293 // an event is available, preventing it from blocking.
294 if (!gpioLines[gpioLine].event_wait(std::chrono::milliseconds(0)))
295 {
296 continue;
297 }
298
299 gpioLines[gpioLine].event_read();
300
301 auto lineValue = gpioLines[gpioLine].get_value();
302
303 if (lineValue < 0)
304 {
305 error("Failed to read GPIO line {LINENAME}", "LINENAME", gpioLine);
306 }
307
308 updatePresence(gpioLine, lineValue == gpiod::line_event::RISING_EDGE);
309 }
310 }
311
312 } // namespace gpio_presence
313