xref: /openbmc/entity-manager/src/gpio-presence/gpio_presence_manager.cpp (revision c689762bbde322171310f72793ee52af000eb50b)
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 
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 
37 auto GPIOPresenceManager::start() -> void
38 {
39     ctx.spawn(initialize());
40 }
41 
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 
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 
58 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 
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 
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)
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 
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 
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 
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 
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