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 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 35 auto GPIOPresenceManager::start() -> void 36 { 37 ctx.spawn(initialize()); 38 } 39 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 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 56 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 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 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) 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 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 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 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