| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 2013-2022 by the Falcon Player Developers. | ||
| 4 | * | ||
| 5 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 6 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 7 | * file for descriptions of what files are covered by each license. | ||
| 8 | * | ||
| 9 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 10 | * included LICENSE.LGPL file. | ||
| 11 | */ | ||
| 12 | |||
| 13 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include "fpp-json.h" | ||
| 16 | #include "common.h" | ||
| 17 | |||
| 18 | #include <sys/mman.h> | ||
| 19 | #include <sys/types.h> | ||
| 20 | #include <array> | ||
| 21 | #include <fcntl.h> | ||
| 22 | #include <limits> | ||
| 23 | #include <set> | ||
| 24 | #include <thread> | ||
| 25 | #include <unistd.h> // ftruncate -- needed directly for NOPCH builds | ||
| 26 | |||
| 27 | #include "GPIOUtils.h" | ||
| 28 | #include "../Warnings.h" | ||
| 29 | #include "../log.h" | ||
| 30 | #include "commands/Commands.h" | ||
| 31 | |||
| 32 | // No platform information on how to control pins | ||
| 33 | static NoPinCapabilities NULL_PIN_INSTANCE("-none-", 0); | ||
| 34 | |||
| 35 | ✗ | const NoPinCapabilities& NoPinCapabilitiesProvider::getPinByName(const std::string& name) { return NULL_PIN_INSTANCE; } | |
| 36 | ✗ | const NoPinCapabilities& NoPinCapabilitiesProvider::getPinByGPIO(int chip, int gpio) { return NULL_PIN_INSTANCE; } | |
| 37 | ✗ | const NoPinCapabilities& NoPinCapabilitiesProvider::getPinByUART(const std::string& n) { return NULL_PIN_INSTANCE; } | |
| 38 | |||
| 39 | 468 | Json::Value PinCapabilities::toJSON() const { | |
| 40 | 468 | Json::Value ret; | |
| 41 |
5/10✓ Branch 0 taken 468 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 468 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 468 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 468 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 468 times.
✗ Branch 9 not taken.
|
468 | if (name != "" && name != "-none-") { |
| 42 |
2/4✓ Branch 0 taken 468 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 468 times.
✗ Branch 3 not taken.
|
468 | ret["pin"] = name; |
| 43 |
2/4✓ Branch 0 taken 468 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 468 times.
✗ Branch 3 not taken.
|
468 | ret["gpioChip"] = gpioIdx; |
| 44 |
2/4✓ Branch 0 taken 468 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 468 times.
✗ Branch 3 not taken.
|
468 | ret["gpioLine"] = gpio; |
| 45 |
2/2✓ Branch 0 taken 296 times.
✓ Branch 1 taken 172 times.
|
468 | if (gpioIdx == 0) { |
| 46 | // somewhat for compatibility with the old kernel GPIO numbers on the Pi's | ||
| 47 |
2/4✓ Branch 0 taken 296 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 296 times.
✗ Branch 3 not taken.
|
296 | ret["gpio"] = gpio; |
| 48 | } | ||
| 49 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 460 times.
|
468 | if (pwm != -1) { |
| 50 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | ret["pwm"] = pwm; |
| 51 |
2/4✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
|
8 | ret["subPwm"] = subPwm; |
| 52 | } | ||
| 53 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 464 times.
|
468 | if (i2cBus != -1) { |
| 54 |
2/4✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
|
4 | ret["i2c"] = i2cBus; |
| 55 | } | ||
| 56 |
3/4✓ Branch 0 taken 468 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 420 times.
|
468 | if (uart != "") { |
| 57 |
2/4✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
|
48 | ret["uart"] = uart; |
| 58 | } | ||
| 59 |
3/6✓ Branch 0 taken 468 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 468 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 468 times.
✗ Branch 5 not taken.
|
468 | ret["supportsPullUp"] = supportsPullUp(); |
| 60 |
3/6✓ Branch 0 taken 468 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 468 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 468 times.
✗ Branch 5 not taken.
|
468 | ret["supportsPullDown"] = supportsPullDown(); |
| 61 | } | ||
| 62 | 468 | return ret; | |
| 63 | ✗ | } | |
| 64 | |||
| 65 | ✗ | void PinCapabilities::enableOledScreen(int i2cBus, bool enable) { | |
| 66 | // this pin is i2c, we may need to tell fppoled to turn off the display | ||
| 67 | // before we shutdown this pin because once we re-configure, i2c will | ||
| 68 | // be unavailable and the display won't update | ||
| 69 | ✗ | int smfd = shm_open("fppoled", O_CREAT | O_RDWR, 0644); | |
| 70 | ✗ | if (smfd < 0) { | |
| 71 | ✗ | return; | |
| 72 | } | ||
| 73 | ✗ | ftruncate(smfd, 1024); | |
| 74 | ✗ | unsigned int* status = (unsigned int*)mmap(0, 1024, PROT_WRITE | PROT_READ, MAP_SHARED, smfd, 0); | |
| 75 | ✗ | if (status == MAP_FAILED) { | |
| 76 | ✗ | close(smfd); | |
| 77 | ✗ | return; | |
| 78 | } | ||
| 79 | ✗ | if (i2cBus == (int)status[0]) { | |
| 80 | ✗ | if (!enable) { | |
| 81 | // force the display off | ||
| 82 | ✗ | status[2] = 1; | |
| 83 | ✗ | int count = 0; | |
| 84 | ✗ | while (status[1] != 0 && count < 150) { | |
| 85 | ✗ | count++; | |
| 86 | ✗ | std::this_thread::sleep_for(std::chrono::milliseconds(10)); | |
| 87 | } | ||
| 88 | } else { | ||
| 89 | // allow the display to come back on | ||
| 90 | ✗ | status[2] = 0; | |
| 91 | } | ||
| 92 | } | ||
| 93 | ✗ | munmap(status, 1024); | |
| 94 | ✗ | close(smfd); | |
| 95 | } | ||
| 96 | |||
| 97 | // the built in GPIO chips that are handled by the more optimized | ||
| 98 | // platform specific GPIO drivers | ||
| 99 | static const std::set<std::string> PLATFORM_IGNORES{ | ||
| 100 | "pinctrl-bcm2835", // raspberry pi's | ||
| 101 | "pinctrl-bcm2711", // Pi4 | ||
| 102 | "raspberrypi-exp-gpio", | ||
| 103 | "brcmvirt-gpio", | ||
| 104 | "gpio-0-31", // beagles | ||
| 105 | "gpio-32-63", | ||
| 106 | "gpio-64-95", | ||
| 107 | "gpio-96-127", | ||
| 108 | "gpio-brcmstb@107d508500", // Pi5's internal GPIO chips | ||
| 109 | "gpio-brcmstb@107d508520", | ||
| 110 | "gpio-brcmstb@107d517c00", | ||
| 111 | "gpio-brcmstb@107d517c20", | ||
| 112 | "pinctrl-rp1", // Pi5's external GPIO chip | ||
| 113 | "tps65219-gpio", // AM62x | ||
| 114 | "4201000.gpio", | ||
| 115 | "600000.gpio", | ||
| 116 | "601000.gpio" | ||
| 117 | }; | ||
| 118 | // No platform information on how to control pins | ||
| 119 | static std::string PROCESS_NAME = "FPPD"; | ||
| 120 | |||
| 121 | 228 | bool GPIODCapabilities::supportsPullUp() const { | |
| 122 | 228 | return true; | |
| 123 | } | ||
| 124 | 228 | bool GPIODCapabilities::supportsPullDown() const { | |
| 125 | 228 | return true; | |
| 126 | } | ||
| 127 | |||
| 128 | #ifdef HASGPIOD | ||
| 129 | // Indexed by PinCapabilities::gpioIdx unchecked, so the cache covers every | ||
| 130 | // value that field can hold. It used to be 32 entries, and a pin on | ||
| 131 | // gpiochip32 or above (an I2C expander on a board with many chips) indexed | ||
| 132 | // past the end. | ||
| 133 | constexpr int MAX_GPIOD_CHIPS = std::numeric_limits<decltype(PinCapabilities::gpioIdx)>::max() + 1; | ||
| 134 | class GPIOChipHolder { | ||
| 135 | public: | ||
| 136 | static GPIOChipHolder INSTANCE; | ||
| 137 | |||
| 138 | #ifdef IS_GPIOD_CXX_V2 | ||
| 139 | std::array<std::shared_ptr<gpiod::chip>, MAX_GPIOD_CHIPS> chips; | ||
| 140 | #else | ||
| 141 | std::array<gpiod::chip, MAX_GPIOD_CHIPS> chips; | ||
| 142 | #endif | ||
| 143 | }; | ||
| 144 | GPIOChipHolder GPIOChipHolder::INSTANCE; | ||
| 145 | #endif | ||
| 146 | |||
| 147 | 508 | void GPIODCapabilities::releasePin() const { | |
| 148 | 508 | releaseGPIOD(); | |
| 149 | #ifdef HASGPIOD | ||
| 150 | #ifdef IS_GPIOD_CXX_V2 | ||
| 151 | 508 | request = nullptr; | |
| 152 | #else | ||
| 153 | if (line.is_requested()) { | ||
| 154 | line.release(); | ||
| 155 | } | ||
| 156 | #endif | ||
| 157 | #endif | ||
| 158 | 508 | } | |
| 159 | |||
| 160 | 609 | int GPIODCapabilities::configPin(const std::string& mode, | |
| 161 | bool directionOut, | ||
| 162 | const std::string& desc) const { | ||
| 163 | #ifdef HASGPIOD | ||
| 164 | // printf("Configuring %s %d %d %s\n", name.c_str(), gpioIdx, gpio, mode.c_str()); | ||
| 165 | #ifdef IS_GPIOD_CXX_V2 | ||
| 166 |
2/2✓ Branch 0 taken 344 times.
✓ Branch 1 taken 265 times.
|
609 | if (chip == nullptr) { |
| 167 |
2/2✓ Branch 0 taken 116 times.
✓ Branch 1 taken 228 times.
|
344 | if (!GPIOChipHolder::INSTANCE.chips[gpioIdx]) { |
| 168 | try { | ||
| 169 |
1/2✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
|
116 | std::string gn = gpioName; |
| 170 |
2/2✓ Branch 0 taken 94 times.
✓ Branch 1 taken 22 times.
|
116 | if (gn.empty()) { |
| 171 |
1/2✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
|
94 | gn = "gpiochip" + std::to_string(gpioIdx); |
| 172 | } | ||
| 173 |
2/4✓ Branch 0 taken 116 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 116 times.
✗ Branch 3 not taken.
|
116 | GPIOChipHolder::INSTANCE.chips[gpioIdx] = std::make_shared<gpiod::chip>("/dev/" + gn); |
| 174 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
116 | } catch (const std::exception& ex) { |
| 175 | ✗ | std::string w = "Could not open GPIO chip " + (gpioName.empty() ? std::to_string(gpioIdx) : gpioName) + | |
| 176 | ✗ | " (" + ex.what() + ")"; | |
| 177 | ✗ | WarningHolder::AddWarning(51, w); | |
| 178 | ✗ | LogWarn(VB_GPIO, "%s\n", w.c_str()); | |
| 179 | ✗ | return -1; | |
| 180 | ✗ | } | |
| 181 | } | ||
| 182 | 344 | chip = GPIOChipHolder::INSTANCE.chips[gpioIdx]; | |
| 183 | } | ||
| 184 | |||
| 185 |
1/2✓ Branch 0 taken 609 times.
✗ Branch 1 not taken.
|
609 | std::string consumer = PROCESS_NAME; |
| 186 |
2/2✓ Branch 0 taken 404 times.
✓ Branch 1 taken 205 times.
|
609 | if (!desc.empty()) { |
| 187 |
2/4✓ Branch 0 taken 404 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 404 times.
✗ Branch 3 not taken.
|
404 | consumer += "-" + desc; |
| 188 | } | ||
| 189 | |||
| 190 | // Already configured exactly this way: keep the request. Callers | ||
| 191 | // configure on every action (each GPIO command, each LED pulse tick), and | ||
| 192 | // releasing and re-requesting an output drives it low until the caller's | ||
| 193 | // setValue() -- a glitch on a relay that was already on. | ||
| 194 | { | ||
| 195 |
1/2✓ Branch 0 taken 609 times.
✗ Branch 1 not taken.
|
609 | bool wantOpenDrain = (mode == "gpio_od"); |
| 196 |
4/4✓ Branch 0 taken 231 times.
✓ Branch 1 taken 378 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 227 times.
|
609 | bool wantOut = directionOut || wantOpenDrain; |
| 197 | 609 | bool sameBias = true; | |
| 198 |
2/2✓ Branch 0 taken 227 times.
✓ Branch 1 taken 382 times.
|
609 | if (!wantOut) { |
| 199 |
3/4✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 215 times.
|
442 | auto want = mode == "gpio_pu" ? gpiod::line::bias::PULL_UP |
| 200 |
3/4✓ Branch 0 taken 215 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 207 times.
|
215 | : (mode == "gpio_pd" ? gpiod::line::bias::PULL_DOWN : gpiod::line::bias::DISABLED); |
| 201 | 227 | sameBias = (lastBias == want); | |
| 202 | } | ||
| 203 |
10/14✓ Branch 0 taken 103 times.
✓ Branch 1 taken 506 times.
✓ Branch 2 taken 103 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 103 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 103 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 103 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✓ Branch 11 taken 99 times.
✓ Branch 12 taken 4 times.
✓ Branch 13 taken 605 times.
|
712 | if (request && lastRequestType == (wantOut ? 1 : 0) && openDrain == wantOpenDrain && sameBias && |
| 204 | 103 | lastDesc == consumer) { | |
| 205 | 4 | return 0; | |
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 |
1/2✓ Branch 0 taken 605 times.
✗ Branch 1 not taken.
|
605 | openDrain = (mode == "gpio_od"); |
| 210 |
1/2✓ Branch 0 taken 605 times.
✗ Branch 1 not taken.
|
605 | lastDesc = consumer; |
| 211 | |||
| 212 | // Build line settings for the requested mode. For open-drain, withBias | ||
| 213 | // controls whether we request an internal pull-up. | ||
| 214 | 606 | auto makeSettings = [&](bool withBias) { | |
| 215 | 606 | gpiod::line_settings settings; | |
| 216 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 603 times.
|
606 | if (openDrain) { |
| 217 | // Open-drain output for bit-banged I2C: setValue(0) drives low, | ||
| 218 | // setValue(1) releases (high-Z, pulled up). The released line stays | ||
| 219 | // readable, so ACK/read bits work with no direction flipping. | ||
| 220 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | settings.set_direction(gpiod::line::direction::OUTPUT); |
| 221 |
1/2✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
|
3 | settings.set_drive(gpiod::line::drive::OPEN_DRAIN); |
| 222 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (withBias) { |
| 223 |
1/2✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
|
2 | settings.set_bias(gpiod::line::bias::PULL_UP); |
| 224 | 2 | lastBias = gpiod::line::bias::PULL_UP; | |
| 225 | } else { | ||
| 226 | 1 | lastBias = gpiod::line::bias::AS_IS; | |
| 227 | } | ||
| 228 |
2/2✓ Branch 0 taken 376 times.
✓ Branch 1 taken 227 times.
|
603 | } else if (directionOut) { |
| 229 |
1/2✓ Branch 0 taken 376 times.
✗ Branch 1 not taken.
|
376 | settings.set_direction(gpiod::line::direction::OUTPUT); |
| 230 | } else { | ||
| 231 |
1/2✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
|
227 | settings.set_direction(gpiod::line::direction::INPUT); |
| 232 |
3/4✓ Branch 0 taken 227 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 215 times.
|
227 | if (mode == "gpio_pu") { |
| 233 |
1/2✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
|
12 | settings.set_bias(gpiod::line::bias::PULL_UP); |
| 234 | 12 | lastBias = gpiod::line::bias::PULL_UP; | |
| 235 |
3/4✓ Branch 0 taken 215 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 207 times.
|
215 | } else if (mode == "gpio_pd") { |
| 236 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | settings.set_bias(gpiod::line::bias::PULL_DOWN); |
| 237 | 8 | lastBias = gpiod::line::bias::PULL_DOWN; | |
| 238 | } else { | ||
| 239 |
1/2✓ Branch 0 taken 207 times.
✗ Branch 1 not taken.
|
207 | settings.set_bias(gpiod::line::bias::DISABLED); |
| 240 | 207 | lastBias = gpiod::line::bias::DISABLED; | |
| 241 | } | ||
| 242 | } | ||
| 243 | 606 | return settings; | |
| 244 | ✗ | }; | |
| 245 | |||
| 246 | // Open-drain: try with an internal pull-up, then without -- some | ||
| 247 | // controllers (e.g. an i2c GPIO expander exposed as a kernel gpiochip) | ||
| 248 | // don't support bias config and reject the request; an external pull-up | ||
| 249 | // is then required. Other modes are a single attempt. | ||
| 250 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 603 times.
|
605 | int attempts = openDrain ? 2 : 1; |
| 251 |
2/2✓ Branch 0 taken 606 times.
✓ Branch 1 taken 5 times.
|
611 | for (int attempt = 0; attempt < attempts; ++attempt) { |
| 252 | try { | ||
| 253 |
1/2✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
|
606 | gpiod::line_settings settings = makeSettings(attempt == 0); |
| 254 |
1/2✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
|
606 | gpiod::request_builder builder = chip->prepare_request(); |
| 255 |
1/2✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
|
606 | builder.add_line_settings(gpio, settings); |
| 256 | 606 | builder.set_consumer(consumer); | |
| 257 |
2/2✓ Branch 0 taken 99 times.
✓ Branch 1 taken 507 times.
|
606 | if (request) { |
| 258 |
1/2✓ Branch 0 taken 99 times.
✗ Branch 1 not taken.
|
99 | request->release(); |
| 259 | 99 | request = nullptr; | |
| 260 | } | ||
| 261 |
3/4✓ Branch 0 taken 600 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 600 times.
✗ Branch 3 not taken.
|
606 | request = std::make_shared<gpiod::line_request>(builder.do_request()); |
| 262 |
4/4✓ Branch 0 taken 228 times.
✓ Branch 1 taken 372 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 226 times.
|
600 | lastRequestType = (directionOut || openDrain) ? 1 : 0; |
| 263 |
4/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 598 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
600 | if (openDrain && attempt == 1) { |
| 264 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogInfo(VB_GPIO, "Pin %s: open-drain configured without an internal pull-up; an external pull-up is required\n", name.c_str()); |
| 265 | } | ||
| 266 | 600 | break; | |
| 267 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
|
618 | } catch (const std::exception& ex) { |
| 268 |
3/4✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
6 | if (openDrain && attempt == 0) { |
| 269 | 1 | continue; // retry without the (unsupported) pull-up bias | |
| 270 | } | ||
| 271 |
8/16✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 5 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 5 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 5 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 5 times.
✗ Branch 15 not taken.
|
5 | std::string w = "Could not configure pin " + name + "(" + desc + ") as " + mode + " (" + ex.what() + ")"; |
| 272 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | WarningHolder::AddWarning(51, w); |
| 273 |
1/2✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
|
5 | LogWarn(VB_GPIO, "%s\n", w.c_str()); |
| 274 | 5 | lastRequestType = 0; | |
| 275 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
|
6 | } |
| 276 | } | ||
| 277 | #else | ||
| 278 | // printf("Configuring %s %d %d %s\n", name.c_str(), gpioIdx, gpio, mode.c_str()); | ||
| 279 | if (chip == nullptr) { | ||
| 280 | if (!GPIOChipHolder::INSTANCE.chips[gpioIdx]) { | ||
| 281 | if (gpioName.empty()) { | ||
| 282 | std::string n = std::to_string(gpioIdx); | ||
| 283 | GPIOChipHolder::INSTANCE.chips[gpioIdx].open(n, gpiod::chip::OPEN_BY_NUMBER); | ||
| 284 | } else { | ||
| 285 | GPIOChipHolder::INSTANCE.chips[gpioIdx].open(gpioName, gpiod::chip::OPEN_BY_NAME); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | chip = &GPIOChipHolder::INSTANCE.chips[gpioIdx]; | ||
| 289 | line = chip->get_line(gpio); | ||
| 290 | } | ||
| 291 | gpiod::line_request req; | ||
| 292 | req.consumer = PROCESS_NAME; | ||
| 293 | if (!desc.empty()) { | ||
| 294 | req.consumer += "-" + desc; | ||
| 295 | } | ||
| 296 | lastDesc = req.consumer; | ||
| 297 | openDrain = (mode == "gpio_od"); | ||
| 298 | if (openDrain) { | ||
| 299 | req.request_type = gpiod::line_request::DIRECTION_OUTPUT; | ||
| 300 | req.flags |= gpiod::line_request::FLAG_OPEN_DRAIN | gpiod::line_request::FLAG_BIAS_PULL_UP; | ||
| 301 | } else if (directionOut) { | ||
| 302 | req.request_type = gpiod::line_request::DIRECTION_OUTPUT; | ||
| 303 | } else { | ||
| 304 | req.request_type = gpiod::line_request::DIRECTION_INPUT; | ||
| 305 | if (mode == "gpio_pu") { | ||
| 306 | req.flags |= gpiod::line_request::FLAG_BIAS_PULL_UP; | ||
| 307 | } else if (mode == "gpio_pd") { | ||
| 308 | req.flags |= gpiod::line_request::FLAG_BIAS_PULL_DOWN; | ||
| 309 | } else { | ||
| 310 | req.flags |= gpiod::line_request::FLAG_BIAS_DISABLE; | ||
| 311 | } | ||
| 312 | } | ||
| 313 | if (req.request_type != lastRequestType || req.flags != lastRequestFlags) { | ||
| 314 | if (line.is_requested()) { | ||
| 315 | line.release(); | ||
| 316 | } | ||
| 317 | try { | ||
| 318 | line.request(req, 1); | ||
| 319 | lastRequestType = req.request_type; | ||
| 320 | lastRequestFlags = req.flags; | ||
| 321 | } catch (const std::exception& ex) { | ||
| 322 | std::string w = "Could not configure pin " + name + "(" + desc + ") as " + mode + " (" + ex.what() + ")"; | ||
| 323 | WarningHolder::AddWarning(51, w); | ||
| 324 | LogWarn(VB_GPIO, "%s\n", w.c_str()); | ||
| 325 | lastRequestType = 0; | ||
| 326 | lastRequestFlags = 0; | ||
| 327 | } | ||
| 328 | } | ||
| 329 | #endif | ||
| 330 | // -1 when the line could not be requested (the chip open above already | ||
| 331 | // says so): a failed request used to report success, warning and all. | ||
| 332 |
3/4✓ Branch 0 taken 605 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 600 times.
✓ Branch 3 taken 5 times.
|
605 | return isAcquired() ? 0 : -1; |
| 333 | #else | ||
| 334 | return 0; | ||
| 335 | #endif | ||
| 336 | 609 | } | |
| 337 | 539 | void GPIODCapabilities::releaseGPIOD() const { | |
| 338 | #ifdef HASGPIOD | ||
| 339 | #ifdef IS_GPIOD_CXX_V2 | ||
| 340 |
2/2✓ Branch 0 taken 493 times.
✓ Branch 1 taken 46 times.
|
539 | if (request) { |
| 341 | 493 | request->release(); | |
| 342 | 493 | request = nullptr; | |
| 343 | 493 | lastRequestType = 0; | |
| 344 | 493 | lastDesc.clear(); | |
| 345 | } | ||
| 346 | #else | ||
| 347 | if (line.is_requested()) { | ||
| 348 | line.release(); | ||
| 349 | lastRequestType = 0; | ||
| 350 | lastRequestFlags = 0; | ||
| 351 | lastDesc.clear(); | ||
| 352 | } | ||
| 353 | #endif | ||
| 354 | #endif | ||
| 355 | 539 | } | |
| 356 | |||
| 357 | 37 | int GPIODCapabilities::readEventFromFile() const { | |
| 358 | #ifdef HASGPIOD | ||
| 359 | #ifdef IS_GPIOD_CXX_V2 | ||
| 360 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | if (request) { |
| 361 | try { | ||
| 362 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | gpiod::edge_event_buffer buffer(1); |
| 363 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | int count = request->read_edge_events(buffer); |
| 364 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | if (count == 1) { |
| 365 |
1/2✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
|
37 | auto& event = buffer.get_event(0); |
| 366 |
3/4✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 19 times.
|
37 | return event.type() == gpiod::edge_event::event_type::RISING_EDGE ? 1 : 0; |
| 367 | } | ||
| 368 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 37 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
37 | } catch (const std::exception& ex) { |
| 369 | ✗ | LogWarn(VB_GPIO, "Error reading event for pin %s: %s\n", name.c_str(), ex.what()); | |
| 370 | ✗ | } | |
| 371 | } | ||
| 372 | #else | ||
| 373 | struct gpiod_line_event event; | ||
| 374 | if (gpiod_line_event_read_fd(line.event_get_fd(), &event) >= 0) { | ||
| 375 | return event.event_type == GPIOD_LINE_EVENT_RISING_EDGE; | ||
| 376 | } | ||
| 377 | #endif | ||
| 378 | #endif | ||
| 379 | ✗ | return -1; | |
| 380 | } | ||
| 381 | |||
| 382 | 20 | int GPIODCapabilities::requestEventFile(bool risingEdge, bool fallingEdge) const { | |
| 383 | 20 | int fd = -1; | |
| 384 | #ifdef HASGPIOD | ||
| 385 | #ifdef IS_GPIOD_CXX_V2 | ||
| 386 | 20 | LogDebug(VB_GPIO, "requestEventFile V2 for pin %s (chip:%d line:%d device:%s)\n", | |
| 387 | name.c_str(), gpioIdx, gpio, gpioName.c_str()); | ||
| 388 | // Ensure chip is initialized | ||
| 389 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | if (chip == nullptr) { |
| 390 | ✗ | if (!GPIOChipHolder::INSTANCE.chips[gpioIdx]) { | |
| 391 | try { | ||
| 392 | ✗ | std::string gn = gpioName; | |
| 393 | ✗ | if (gn.empty()) { | |
| 394 | ✗ | gn = "gpiochip" + std::to_string(gpioIdx); | |
| 395 | } | ||
| 396 | ✗ | GPIOChipHolder::INSTANCE.chips[gpioIdx] = std::make_shared<gpiod::chip>("/dev/" + gn); | |
| 397 | ✗ | } catch (const std::exception& ex) { | |
| 398 | ✗ | std::string w = "Could not open GPIO chip " + (gpioName.empty() ? std::to_string(gpioIdx) : gpioName) + | |
| 399 | ✗ | " for event request (" + ex.what() + ")"; | |
| 400 | ✗ | WarningHolder::AddWarning(51, w); | |
| 401 | ✗ | LogWarn(VB_GPIO, "%s\n", w.c_str()); | |
| 402 | ✗ | return -1; | |
| 403 | ✗ | } | |
| 404 | } | ||
| 405 | ✗ | chip = GPIOChipHolder::INSTANCE.chips[gpioIdx]; | |
| 406 | } | ||
| 407 | |||
| 408 | try { | ||
| 409 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | std::string desc = lastDesc; |
| 410 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | releaseGPIOD(); |
| 411 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | lastDesc = desc; |
| 412 | |||
| 413 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | if (!chip) { |
| 414 | ✗ | WarningHolder::AddWarning(51, "Could not configure pin " + name + " for events: chip pointer is null"); | |
| 415 | ✗ | return -1; | |
| 416 | } | ||
| 417 | |||
| 418 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | gpiod::line_settings settings; |
| 419 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | settings.set_direction(gpiod::line::direction::INPUT); |
| 420 | |||
| 421 |
2/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
20 | if (risingEdge && fallingEdge) { |
| 422 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | settings.set_edge_detection(gpiod::line::edge::BOTH); |
| 423 | ✗ | } else if (risingEdge) { | |
| 424 | ✗ | settings.set_edge_detection(gpiod::line::edge::RISING); | |
| 425 | ✗ | } else if (fallingEdge) { | |
| 426 | ✗ | settings.set_edge_detection(gpiod::line::edge::FALLING); | |
| 427 | } | ||
| 428 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (lastBias != gpiod::line::bias::UNKNOWN) { |
| 429 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | settings.set_bias(lastBias); |
| 430 | } | ||
| 431 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
20 | std::string consumer = lastDesc.empty() ? PROCESS_NAME : lastDesc; |
| 432 | |||
| 433 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | LogDebug(VB_GPIO, "Building request for pin %s: chip device=%s, line=%d\n", |
| 434 | name.c_str(), gpioName.c_str(), gpio); | ||
| 435 | |||
| 436 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | gpiod::request_builder builder = chip->prepare_request(); |
| 437 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | builder.add_line_settings(gpio, settings); |
| 438 | 20 | builder.set_consumer(consumer); | |
| 439 | |||
| 440 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | LogDebug(VB_GPIO, "Executing do_request() for pin %s\n", name.c_str()); |
| 441 |
3/4✓ Branch 0 taken 19 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
|
20 | request = std::make_shared<gpiod::line_request>(builder.do_request()); |
| 442 | |||
| 443 |
1/2✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
|
19 | fd = request->fd(); |
| 444 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
|
19 | if (fd < 0) { |
| 445 | ✗ | WarningHolder::AddWarning(51, "Could not get event file descriptor for pin " + name); | |
| 446 | ✗ | request = nullptr; | |
| 447 | } | ||
| 448 |
2/4✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
|
24 | } catch (const std::exception& ex) { |
| 449 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogDebug(VB_GPIO, "Pin %s (chip:%d line:%d device:%s) does not support edge detection - will use polling instead. (%s)\n", |
| 450 | name.c_str(), gpioIdx, gpio, gpioName.c_str(), ex.what()); | ||
| 451 | // Re-configure the pin for plain input so getValue() works for polling | ||
| 452 | try { | ||
| 453 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gpiod::line_settings settings; |
| 454 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | settings.set_direction(gpiod::line::direction::INPUT); |
| 455 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (lastBias != gpiod::line::bias::UNKNOWN) { |
| 456 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | settings.set_bias(lastBias); |
| 457 | } | ||
| 458 |
2/4✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | std::string consumer = lastDesc.empty() ? PROCESS_NAME : lastDesc; |
| 459 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | gpiod::request_builder builder = chip->prepare_request(); |
| 460 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | builder.add_line_settings(gpio, settings); |
| 461 | 1 | builder.set_consumer(consumer); | |
| 462 |
2/4✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
|
1 | request = std::make_shared<gpiod::line_request>(builder.do_request()); |
| 463 | 1 | lastRequestType = 2; // Use 2 to indicate input-for-polling (not 0 which means unconfigured) | |
| 464 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | LogInfo(VB_GPIO, "Pin %s re-configured for polling input\n", name.c_str()); |
| 465 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
1 | } catch (const std::exception& ex2) { |
| 466 | ✗ | LogWarn(VB_GPIO, "Could not re-configure pin %s for polling: %s\n", name.c_str(), ex2.what()); | |
| 467 | ✗ | } | |
| 468 | 1 | return -1; // Return -1 to trigger polling fallback | |
| 469 | 1 | } | |
| 470 | #else | ||
| 471 | gpiod::line_request req; | ||
| 472 | req.consumer = lastDesc.empty() ? PROCESS_NAME : lastDesc; | ||
| 473 | req.request_type = lastRequestType; | ||
| 474 | req.flags = lastRequestFlags; | ||
| 475 | if (risingEdge && fallingEdge) { | ||
| 476 | req.request_type |= gpiod::line_request::EVENT_BOTH_EDGES; | ||
| 477 | } else if (risingEdge) { | ||
| 478 | req.request_type |= gpiod::line_request::EVENT_RISING_EDGE; | ||
| 479 | } else if (fallingEdge) { | ||
| 480 | req.request_type |= gpiod::line_request::EVENT_FALLING_EDGE; | ||
| 481 | } | ||
| 482 | bool wasRequested = line.is_requested(); | ||
| 483 | if (wasRequested) { | ||
| 484 | line.release(); | ||
| 485 | } | ||
| 486 | try { | ||
| 487 | line.request(req, 0); | ||
| 488 | } catch (const std::exception& ex) { | ||
| 489 | if (!wasRequested) { | ||
| 490 | WarningHolder::AddWarning(51, "Could not configure pin " + name + " for events (" + ex.what() + ") for edges Rising:" + | ||
| 491 | std::to_string(risingEdge) + " Falling:" + std::to_string(fallingEdge)); | ||
| 492 | } | ||
| 493 | } | ||
| 494 | if (line.is_requested()) { | ||
| 495 | fd = line.event_get_fd(); | ||
| 496 | if (fd < 0) { | ||
| 497 | WarningHolder::AddWarning(51, "Could not get event file descriptor for pin " + name); | ||
| 498 | } | ||
| 499 | } else if (wasRequested) { | ||
| 500 | req.request_type = lastRequestType; | ||
| 501 | line.request(req, 0); | ||
| 502 | if (!line.is_requested()) { | ||
| 503 | WarningHolder::AddWarning(51, "Could not re-request pin " + name + " after failing to request events"); | ||
| 504 | } | ||
| 505 | } else { | ||
| 506 | WarningHolder::AddWarning(51, "Could not request event for pin " + name); | ||
| 507 | } | ||
| 508 | #endif | ||
| 509 | #endif | ||
| 510 | 19 | return fd; | |
| 511 | } | ||
| 512 | |||
| 513 | // A line the kernel handed to a peripheral cannot be requested at all, and under | ||
| 514 | // the strict pinmux of kernel 6.18 that is now permanent for some pins. Say so | ||
| 515 | // plainly, rather than leaving callers to infer it from a value that is really | ||
| 516 | // just the "could not read" default. | ||
| 517 | 634 | bool GPIODCapabilities::isAcquired() const { | |
| 518 | #ifdef HASGPIOD | ||
| 519 | #ifdef IS_GPIOD_CXX_V2 | ||
| 520 | 634 | return request != nullptr; | |
| 521 | #else | ||
| 522 | return line.is_requested(); | ||
| 523 | #endif | ||
| 524 | #else | ||
| 525 | return false; | ||
| 526 | #endif | ||
| 527 | } | ||
| 528 | |||
| 529 | 81 | bool GPIODCapabilities::getValue() const { | |
| 530 | #ifdef HASGPIOD | ||
| 531 |
3/4✓ Branch 0 taken 4 times.
✓ Branch 1 taken 77 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
|
81 | if (lastRequestType == 1 && !openDrain) { // push-pull OUTPUT: value is what we drove |
| 532 | ✗ | return lastValue; | |
| 533 | } | ||
| 534 | // open-drain (or input): read the actual line level so a released open-drain | ||
| 535 | // line reflects what the slave is driving (needed for I2C ACK/read bits). | ||
| 536 | #ifdef IS_GPIOD_CXX_V2 | ||
| 537 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 81 times.
|
81 | if (!request) { |
| 538 | ✗ | return 0; | |
| 539 | } | ||
| 540 | try { | ||
| 541 |
1/2✓ Branch 0 taken 81 times.
✗ Branch 1 not taken.
|
81 | auto v = request->get_value(gpio); |
| 542 | 81 | return v == gpiod::line::value::ACTIVE; | |
| 543 | ✗ | } catch (const std::exception& ex) { | |
| 544 | ✗ | LogWarn(VB_GPIO, "Error getting value for pin %s: %s\n", name.c_str(), ex.what()); | |
| 545 | ✗ | return 0; | |
| 546 | ✗ | } | |
| 547 | #else | ||
| 548 | if (lastRequestType == 0 && !line.is_requested()) { | ||
| 549 | return 0; | ||
| 550 | } | ||
| 551 | // Same contract as the v2 branch above: libgpiod v1's line::get_value() | ||
| 552 | // throws (std::system_error on a failed ioctl, std::logic_error on an empty | ||
| 553 | // or unrequested line handle) and callers of PinCapabilities::getValue() | ||
| 554 | // are not exception-safe, so a GPIO read failure must degrade to a warning. | ||
| 555 | try { | ||
| 556 | return line.get_value(); | ||
| 557 | } catch (const std::exception& ex) { | ||
| 558 | LogWarn(VB_GPIO, "Error getting value for pin %s: %s\n", name.c_str(), ex.what()); | ||
| 559 | return 0; | ||
| 560 | } | ||
| 561 | #endif | ||
| 562 | #else | ||
| 563 | return 0; | ||
| 564 | #endif | ||
| 565 | } | ||
| 566 | 105 | void GPIODCapabilities::setValue(bool i) const { | |
| 567 | #ifdef HASGPIOD | ||
| 568 | 105 | lastValue = i; | |
| 569 | #ifdef IS_GPIOD_CXX_V2 | ||
| 570 |
2/2✓ Branch 0 taken 100 times.
✓ Branch 1 taken 5 times.
|
105 | if (request) { |
| 571 | try { | ||
| 572 |
3/4✓ Branch 0 taken 47 times.
✓ Branch 1 taken 53 times.
✓ Branch 2 taken 100 times.
✗ Branch 3 not taken.
|
100 | request->set_value(gpio, i ? gpiod::line::value::ACTIVE : gpiod::line::value::INACTIVE); |
| 573 | ✗ | } catch (const std::exception& ex) { | |
| 574 | ✗ | LogWarn(VB_GPIO, "Error setting value for pin %s: %s\n", name.c_str(), ex.what()); | |
| 575 | ✗ | } | |
| 576 | } | ||
| 577 | #else | ||
| 578 | // Same contract as the v2 branch above: libgpiod v1's line::set_value() | ||
| 579 | // routes through line_bulk::set_values() and throws (std::system_error on a | ||
| 580 | // failed ioctl, std::logic_error on an empty or unrequested line handle). | ||
| 581 | // configPin()/requestEventFile() only warn when a request fails, leaving the | ||
| 582 | // line unrequested, so the first setValue() on such a pin would otherwise | ||
| 583 | // escape as an uncaught exception and std::terminate fppd. | ||
| 584 | try { | ||
| 585 | line.set_value(i ? 1 : 0); | ||
| 586 | } catch (const std::exception& ex) { | ||
| 587 | LogWarn(VB_GPIO, "Error setting value for pin %s: %s\n", name.c_str(), ex.what()); | ||
| 588 | } | ||
| 589 | #endif | ||
| 590 | #endif | ||
| 591 | 105 | } | |
| 592 | static std::vector<GPIODCapabilities> GPIOD_PINS; | ||
| 593 | static PinCapabilitiesProvider* PIN_PROVIDER = nullptr; | ||
| 594 | |||
| 595 | #ifdef HASGPIOD | ||
| 596 | // Scan the kernel gpiochips and register any lines for chips not already known. | ||
| 597 | // Split out of InitGPIO() so it can run again later: an I2C GPIO expander that a | ||
| 598 | // cape device-tree overlay instantiates can probe a bit after boot, so its chip | ||
| 599 | // (and its named lines) may be missing during the first scan. Re-running picks up | ||
| 600 | // the late chip without disturbing the pins already registered. | ||
| 601 | 1910 | static void registerGPIODChips() { | |
| 602 | try { | ||
| 603 | #ifdef IS_GPIOD_CXX_V2 | ||
| 604 | // Chip devices (e.g. "gpiochip5") we've already registered lines for. | ||
| 605 | 1910 | std::set<std::string> registeredDevices; | |
| 606 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1910 times.
|
1910 | for (const auto& p : GPIOD_PINS) { |
| 607 | ✗ | registeredDevices.insert(p.gpioName); | |
| 608 | } | ||
| 609 | // Rebuild the label-uniqueness set from the already-registered chips so a | ||
| 610 | // duplicate chip label keeps getting the same disambiguating suffix. | ||
| 611 | 1910 | std::set<std::string> found; | |
| 612 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1910 times.
|
1910 | for (const auto& dev : registeredDevices) { |
| 613 | try { | ||
| 614 | ✗ | auto chip = gpiod::chip("/dev/" + dev); | |
| 615 | ✗ | std::string label = chip.get_info().label(); | |
| 616 | ✗ | char ci = 'b'; | |
| 617 | ✗ | std::string ulabel = label; | |
| 618 | ✗ | while (found.find(ulabel) != found.end()) { | |
| 619 | ✗ | ulabel = label + ci; | |
| 620 | ✗ | ci++; | |
| 621 | } | ||
| 622 | ✗ | found.insert(ulabel); | |
| 623 | ✗ | } catch (...) { | |
| 624 | // chip went away; ignore | ||
| 625 | ✗ | } | |
| 626 | } | ||
| 627 | 1910 | std::set<std::string> foundChips; | |
| 628 |
5/8✓ Branch 0 taken 1910 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1910 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 61774 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 61774 times.
✓ Branch 7 taken 1910 times.
|
63684 | for (const auto& entry : std::filesystem::directory_iterator("/dev/")) { |
| 629 |
6/10✓ Branch 0 taken 61774 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 61774 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 61774 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 61774 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 955 times.
✓ Branch 9 taken 60819 times.
|
159401 | if (startsWith(entry.path().filename().string(), "gpiochip")) { |
| 630 |
3/6✓ Branch 0 taken 955 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 955 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 955 times.
✗ Branch 5 not taken.
|
955 | foundChips.insert(entry.path().filename().string()); |
| 631 | } | ||
| 632 | 1910 | } | |
| 633 |
2/2✓ Branch 0 taken 955 times.
✓ Branch 1 taken 1910 times.
|
2865 | for (auto& chipName : foundChips) { |
| 634 |
2/4✓ Branch 0 taken 955 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 955 times.
|
955 | if (registeredDevices.find(chipName) != registeredDevices.end()) { |
| 635 | ✗ | continue; // already registered this chip | |
| 636 | } | ||
| 637 |
3/6✓ Branch 0 taken 955 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 955 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 955 times.
✗ Branch 5 not taken.
|
955 | auto chip = gpiod::chip("/dev/" + chipName); |
| 638 |
1/2✓ Branch 0 taken 955 times.
✗ Branch 1 not taken.
|
955 | auto info = chip.get_info(); |
| 639 |
1/2✓ Branch 0 taken 955 times.
✗ Branch 1 not taken.
|
955 | std::string chipDevice = chipName; |
| 640 | 955 | std::string label = info.label(); | |
| 641 |
2/4✓ Branch 0 taken 955 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 955 times.
✗ Branch 3 not taken.
|
955 | int chipNum = std::stoi(chipDevice.substr(8)); |
| 642 | |||
| 643 |
3/4✓ Branch 0 taken 955 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 158 times.
✓ Branch 3 taken 797 times.
|
955 | if (PLATFORM_IGNORES.find(label) == PLATFORM_IGNORES.end()) { |
| 644 | 158 | char ci = 'b'; | |
| 645 |
3/4✓ Branch 0 taken 235 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 77 times.
✓ Branch 3 taken 158 times.
|
235 | while (found.find(label) != found.end()) { |
| 646 |
2/4✓ Branch 0 taken 77 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 77 times.
✗ Branch 3 not taken.
|
77 | label = chip.get_info().label() + ci; |
| 647 | 77 | ci++; | |
| 648 | } | ||
| 649 |
1/2✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
|
158 | found.insert(label); |
| 650 |
1/2✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
|
158 | int num_lines = chip.get_info().num_lines(); |
| 651 |
1/2✓ Branch 0 taken 158 times.
✗ Branch 1 not taken.
|
158 | LogDebug(VB_GPIO, "Registering chip %s (/dev/%s) as chipIdx=%d with %d lines\n", |
| 652 | label.c_str(), chipDevice.c_str(), chipNum, num_lines); | ||
| 653 |
2/2✓ Branch 0 taken 2684 times.
✓ Branch 1 taken 158 times.
|
2842 | for (int x = 0; x < num_lines; x++) { |
| 654 |
2/4✓ Branch 0 taken 2684 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2684 times.
✗ Branch 3 not taken.
|
2684 | std::string n = label + "-" + std::to_string(x); |
| 655 |
1/2✓ Branch 0 taken 2684 times.
✗ Branch 1 not taken.
|
2684 | auto line = chip.get_line_info(x); |
| 656 | 2684 | std::string plabel = line.name(); | |
| 657 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2676 times.
|
2684 | if (plabel.empty()) { |
| 658 |
1/2✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
|
8 | plabel = n; |
| 659 | } | ||
| 660 |
2/4✓ Branch 0 taken 2684 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2684 times.
✗ Branch 3 not taken.
|
2684 | GPIOD_PINS.push_back(GPIODCapabilities(plabel, x, chipDevice).setGPIO(chipNum, x)); |
| 661 | 2684 | } | |
| 662 | } | ||
| 663 | 955 | } | |
| 664 | #else | ||
| 665 | // libgpiod v1 (legacy): scan once, as before. | ||
| 666 | if (GPIOD_PINS.empty()) { | ||
| 667 | std::set<std::string> found; | ||
| 668 | LogDebug(VB_GPIO, "Using libgpiod C++ API v1\n"); | ||
| 669 | ::gpiod_chip* chip = gpiod_chip_open_by_number(0); | ||
| 670 | if (chip != nullptr) { | ||
| 671 | int chipCount = 0; | ||
| 672 | ::gpiod_chip_close(chip); | ||
| 673 | for (auto& a : gpiod::make_chip_iter()) { | ||
| 674 | std::string name = a.name(); | ||
| 675 | std::string label = a.label(); | ||
| 676 | |||
| 677 | if (PLATFORM_IGNORES.find(label) == PLATFORM_IGNORES.end()) { | ||
| 678 | char i = 'b'; | ||
| 679 | while (found.find(label) != found.end()) { | ||
| 680 | label = a.label() + i; | ||
| 681 | i++; | ||
| 682 | } | ||
| 683 | found.insert(label); | ||
| 684 | for (int x = 0; x < a.num_lines(); x++) { | ||
| 685 | std::string n = label + "-" + std::to_string(x); | ||
| 686 | std::string plabel = a.get_line(x).name(); | ||
| 687 | if (plabel.empty()) { | ||
| 688 | plabel = n; | ||
| 689 | } | ||
| 690 | GPIOD_PINS.push_back(GPIODCapabilities(plabel, x, name).setGPIO(chipCount, x)); | ||
| 691 | } | ||
| 692 | } | ||
| 693 | chipCount++; | ||
| 694 | } | ||
| 695 | } | ||
| 696 | } | ||
| 697 | #endif | ||
| 698 |
0/2✗ Branch 0 not taken.
✗ Branch 1 not taken.
|
1910 | } catch (const std::exception& ex) { |
| 699 | ✗ | LogWarn(VB_GPIO, "Error initializing GPIO: %s\n", ex.what()); | |
| 700 | ✗ | } | |
| 701 | 1910 | } | |
| 702 | #endif | ||
| 703 | |||
| 704 | 1900 | void PinCapabilities::InitGPIO(const std::string& process, PinCapabilitiesProvider* p) { | |
| 705 | 1900 | PROCESS_NAME = process; | |
| 706 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1900 times.
|
1900 | if (p == nullptr) { |
| 707 | ✗ | p = new NoPinCapabilitiesProvider(); | |
| 708 | } | ||
| 709 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1900 times.
|
1900 | if (PIN_PROVIDER != nullptr) { |
| 710 | ✗ | delete PIN_PROVIDER; | |
| 711 | } | ||
| 712 | 1900 | PIN_PROVIDER = p; | |
| 713 | #ifdef HASGPIOD | ||
| 714 | 1900 | registerGPIODChips(); | |
| 715 | #endif | ||
| 716 | 1900 | PIN_PROVIDER->Init(); | |
| 717 | 1900 | } | |
| 718 | |||
| 719 | 10 | void PinCapabilities::RescanGPIO() { | |
| 720 | #ifdef HASGPIOD | ||
| 721 | 10 | registerGPIODChips(); | |
| 722 | #endif | ||
| 723 | 10 | } | |
| 724 | 1901 | std::vector<std::string> PinCapabilities::getPinNames() { | |
| 725 | 1901 | std::vector<std::string> pn = PIN_PROVIDER->getPinNames(); | |
| 726 |
2/2✓ Branch 0 taken 2487 times.
✓ Branch 1 taken 1901 times.
|
4388 | for (auto& a : GPIOD_PINS) { |
| 727 |
1/2✓ Branch 0 taken 2487 times.
✗ Branch 1 not taken.
|
2487 | pn.push_back(a.name); |
| 728 | } | ||
| 729 | 1901 | return pn; | |
| 730 | ✗ | } | |
| 731 | |||
| 732 | 2466 | const PinCapabilities& PinCapabilities::getPinByName(const std::string& n) { | |
| 733 |
2/2✓ Branch 0 taken 21037 times.
✓ Branch 1 taken 2388 times.
|
23425 | for (auto& a : GPIOD_PINS) { |
| 734 |
2/2✓ Branch 0 taken 78 times.
✓ Branch 1 taken 20959 times.
|
21037 | if (n == a.name) { |
| 735 | 78 | return a; | |
| 736 | } | ||
| 737 | } | ||
| 738 | // PIN_PROVIDER is only set once InitGPIO() runs, which must wait until after | ||
| 739 | // cape detection so cape GPIO expanders are registered. Anything that looks | ||
| 740 | // up a pin before then (e.g. fppoled's early display init probing the i2c1 | ||
| 741 | // fallback) gets the null pin rather than dereferencing a null provider -- | ||
| 742 | // configPin() etc. on it are no-ops, so the lookup is safely skipped. | ||
| 743 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2388 times.
|
2388 | if (PIN_PROVIDER == nullptr) { |
| 744 | ✗ | return NULL_PIN_INSTANCE; | |
| 745 | } | ||
| 746 | 2388 | return PIN_PROVIDER->getPinByName(n); | |
| 747 | } | ||
| 748 | 9 | const PinCapabilities& PinCapabilities::getPinByGPIO(int chip, int gpio) { | |
| 749 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | for (auto& a : GPIOD_PINS) { |
| 750 | ✗ | if (a.gpioIdx == chip && a.gpio == gpio) { | |
| 751 | ✗ | return a; | |
| 752 | } | ||
| 753 | } | ||
| 754 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
|
9 | if (PIN_PROVIDER == nullptr) { |
| 755 | ✗ | return NULL_PIN_INSTANCE; | |
| 756 | } | ||
| 757 | 9 | return PIN_PROVIDER->getPinByGPIO(chip, gpio); | |
| 758 | } | ||
| 759 | 73 | const PinCapabilities& PinCapabilities::getPinByUART(const std::string& n) { | |
| 760 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 73 times.
|
73 | if (PIN_PROVIDER == nullptr) { |
| 761 | ✗ | return NULL_PIN_INSTANCE; | |
| 762 | } | ||
| 763 | 73 | return PIN_PROVIDER->getPinByUART(n); | |
| 764 | } | ||
| 765 | |||
| 766 | 7810 | void PinCapabilities::SetMultiPinValue(const std::list<const PinCapabilities*>& pins, int v) { | |
| 767 |
2/2✓ Branch 0 taken 7797 times.
✓ Branch 1 taken 13 times.
|
7810 | if (pins.empty()) { |
| 768 | 7797 | return; | |
| 769 | } | ||
| 770 |
2/2✓ Branch 0 taken 13 times.
✓ Branch 1 taken 13 times.
|
26 | for (auto p : pins) { |
| 771 |
1/2✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
|
13 | p->setValue(v); |
| 772 | } | ||
| 773 | } | ||
| 774 |