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