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 // 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
addConfigHandler(sdbusplus::message::object_path obj)129 auto 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)
addConfigFromDbusAsync(const sdbusplus::message::object_path obj)138 auto 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)
144 .service(entityManagerBusName)
145 .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
155 const auto parentInvCompatible = co_await getParentInventoryCompatible(obj);
156
157 auto devicePresence = std::make_unique<DevicePresence>(
158 ctx, props.presence_pin_names, props.presence_pin_values, props.name,
159 gpioState, parentInvCompatible);
160
161 if (devicePresence)
162 {
163 addConfig(obj, std::move(devicePresence));
164 }
165 }
166
getParentInventoryCompatible(const sdbusplus::message::object_path & obj)167 auto 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
removeConfig(const std::string & objPath)197 auto 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
updatePresence(const std::string & gpioLine,bool state)235 auto 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
readGPIOAsyncEvent(std::string gpioLine)249 auto 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
281 // 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
290 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
304