xref: /openbmc/entity-manager/src/gpio-presence/gpio_presence_manager.cpp (revision 533d49096d1f45e8389f2349aef2b655945add52)
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 
18 #include <memory>
19 #include <ranges>
20 #include <string>
21 #include <utility>
22 
23 PHOSPHOR_LOG2_USING;
24 
25 namespace gpio_presence
26 {
27 
GPIOPresenceManager(sdbusplus::async::context & ctx)28 GPIOPresenceManager::GPIOPresenceManager(sdbusplus::async::context& ctx) :
29     ctx(ctx), manager(ctx, "/"),
30     configProvider(
31         ConfigProvider(ctx, sdbusplus::common::xyz::openbmc_project::
32                                 configuration::GPIODeviceDetect::interface))
33 {}
34 
start()35 auto GPIOPresenceManager::start() -> void
36 {
37     ctx.spawn(initialize());
38 }
39 
getPresence(const std::string & name)40 auto GPIOPresenceManager::getPresence(const std::string& name) -> bool
41 {
42     if (!presenceMap.contains(name))
43     {
44         return false;
45     }
46     return presenceMap.at(name)->isPresent();
47 }
48 
initialize()49 auto GPIOPresenceManager::initialize() -> sdbusplus::async::task<void>
50 {
51     co_await configProvider.initialize(
52         std::bind_front(&GPIOPresenceManager::addConfigHandler, this),
53         std::bind_front(&GPIOPresenceManager::removeConfig, this));
54 }
55 
setupBusName() const56 auto GPIOPresenceManager::setupBusName() const -> std::string
57 {
58     debug("requesting dbus name {NAME}", "NAME", service);
59 
60     ctx.request_name(service);
61     return service;
62 }
63 
addConfig(const sdbusplus::message::object_path & obj,std::unique_ptr<DevicePresence> config)64 auto GPIOPresenceManager::addConfig(const sdbusplus::message::object_path& obj,
65                                     std::unique_ptr<DevicePresence> config)
66     -> void
67 {
68     debug("adding configuration for {NAME}", "NAME", obj);
69     presenceMap.insert_or_assign(obj, std::move(config));
70 
71     debug("found valid configuration at object path {OBJPATH}", "OBJPATH", obj);
72 
73     auto gpioConfigs = presenceMap[obj]->gpioPolarity;
74 
75     // populate fdios
76     for (auto& [gpioName, _] : gpioConfigs)
77     {
78         if (gpioLines.contains(gpioName))
79         {
80             continue;
81         }
82 
83         try
84         {
85             gpioLines[gpioName] = gpiod::find_line(gpioName);
86         }
87         catch (std::exception& e)
88         {
89             error("gpiod::find_line failed: {ERROR}", "ERROR", e);
90             return;
91         }
92 
93         gpiod::line_request lineConfig;
94         lineConfig.consumer = "gpio-presence";
95         lineConfig.request_type = gpiod::line_request::EVENT_BOTH_EDGES |
96                                   gpiod::line_request::DIRECTION_INPUT;
97 
98         int lineFd = -1;
99         try
100         {
101             gpioLines[gpioName].request(lineConfig);
102 
103             lineFd = gpioLines[gpioName].event_get_fd();
104         }
105         catch (std::exception& e)
106         {
107             error("{ERROR}", "ERROR", e);
108             return;
109         }
110         if (lineFd < 0)
111         {
112             error("could not get event fd for gpio '{NAME}'", "NAME", gpioName);
113             return;
114         }
115 
116         if (!fdios.contains(gpioName))
117         {
118             fdios.insert(
119                 {gpioName,
120                  std::make_unique<sdbusplus::async::fdio>(ctx, lineFd)});
121 
122             ctx.spawn(readGPIOAsyncEvent(gpioName));
123         }
124     }
125 }
126 
addConfigHandler(sdbusplus::message::object_path obj)127 auto GPIOPresenceManager::addConfigHandler(sdbusplus::message::object_path obj)
128     -> void
129 {
130     // NOLINTBEGIN(performance-unnecessary-value-param)
131     ctx.spawn(addConfigFromDbusAsync(obj));
132     // NOLINTEND(performance-unnecessary-value-param)
133 }
134 
135 // NOLINTBEGIN(performance-unnecessary-value-param)
addConfigFromDbusAsync(const sdbusplus::message::object_path obj)136 auto GPIOPresenceManager::addConfigFromDbusAsync(
137     const sdbusplus::message::object_path obj) -> sdbusplus::async::task<void>
138 // NOLINTEND(performance-unnecessary-value-param)
139 {
140     auto props = co_await sdbusplus::client::xyz::openbmc_project::
141                      configuration::GPIODeviceDetect<>(ctx)
142                          .service("xyz.openbmc_project.EntityManager")
143                          .path(obj.str)
144                          .properties();
145 
146     if (props.presence_pin_names.size() != props.presence_pin_values.size())
147     {
148         error(
149             "presence pin names and presence pin values have different sizes");
150         co_return;
151     }
152 
153     auto devicePresence = std::make_unique<DevicePresence>(
154         ctx, props.presence_pin_names, props.presence_pin_values, props.name,
155         gpioState);
156 
157     if (devicePresence)
158     {
159         addConfig(obj, std::move(devicePresence));
160     }
161 }
162 
removeConfig(const std::string & objPath)163 auto GPIOPresenceManager::removeConfig(const std::string& objPath) -> void
164 {
165     if (!presenceMap.contains(objPath))
166     {
167         return;
168     }
169 
170     debug("erasing configuration for object path {OBJPATH}", "OBJPATH",
171           objPath);
172     presenceMap.erase(objPath);
173 
174     std::set<std::string> gpiosNeeded;
175 
176     for (const auto& config : std::views::values(presenceMap))
177     {
178         for (const auto& gpio : std::views::keys(config->gpioPolarity))
179         {
180             gpiosNeeded.insert(gpio);
181         }
182     }
183 
184     auto ks = std::views::keys(gpioLines);
185     std::set<std::string> trackedGPIOs{ks.begin(), ks.end()};
186 
187     for (const auto& trackedGPIO : trackedGPIOs)
188     {
189         if (gpiosNeeded.contains(trackedGPIO))
190         {
191             continue;
192         }
193 
194         gpioLines[trackedGPIO].release();
195 
196         gpioLines.erase(trackedGPIO);
197         fdios.erase(fdios.find(trackedGPIO));
198     }
199 }
200 
updatePresence(const std::string & gpioLine,bool state)201 auto GPIOPresenceManager::updatePresence(const std::string& gpioLine,
202                                          bool state) -> void
203 {
204     gpioState.insert_or_assign(gpioLine, state);
205 
206     debug("GPIO line {GPIO_NAME} went {GPIO_LEVEL}", "GPIO_NAME", gpioLine,
207           "GPIO_LEVEL", (state) ? "high" : "low");
208 
209     for (const auto& config : std::views::values(presenceMap))
210     {
211         config->updateGPIOPresence(gpioLine);
212     }
213 }
214 
readGPIOAsyncEvent(std::string gpioLine)215 auto GPIOPresenceManager::readGPIOAsyncEvent(std::string gpioLine)
216     -> sdbusplus::async::task<void>
217 {
218     debug("Watching gpio events for {LINENAME}", "LINENAME", gpioLine);
219 
220     if (!fdios.contains(gpioLine))
221     {
222         error("fdio for {LINENAME} not found", "LINENAME", gpioLine);
223         co_return;
224     }
225 
226     const auto& fdio = fdios[gpioLine];
227 
228     try
229     {
230         const int lineValue = gpioLines[gpioLine].get_value();
231 
232         updatePresence(gpioLine, lineValue == gpiod::line_event::RISING_EDGE);
233     }
234     catch (std::exception& e)
235     {
236         error("Failed to read GPIO line {LINENAME}", "LINENAME", gpioLine);
237         error("{ERROR}", "ERROR", e);
238         co_return;
239     }
240 
241     while (!ctx.stop_requested())
242     {
243         co_await fdio->next();
244 
245         debug("Received gpio event for {LINENAME}", "LINENAME", gpioLine);
246 
247         // event_read() does not clear the EPOLLIN flag immediately; it is
248         // cleared during the subsequent epoll check. Therefore, call event_wait
249         // with a zero timeout to ensure that event_read() is only invoked when
250         // an event is available, preventing it from blocking.
251         if (!gpioLines[gpioLine].event_wait(std::chrono::milliseconds(0)))
252         {
253             continue;
254         }
255 
256         gpioLines[gpioLine].event_read();
257 
258         auto lineValue = gpioLines[gpioLine].get_value();
259 
260         if (lineValue < 0)
261         {
262             error("Failed to read GPIO line {LINENAME}", "LINENAME", gpioLine);
263         }
264 
265         updatePresence(gpioLine, lineValue == gpiod::line_event::RISING_EDGE);
266     }
267 }
268 
269 } // namespace gpio_presence
270