xref: /openbmc/entity-manager/src/gpio-presence/gpio_presence_manager.cpp (revision 0164a72fec126f54714c8de8b53909f40bb2330c)
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