GCC Code Coverage Report


Directory: ./
File: util/GPIOUtils.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 232 319 72.7%
Functions: 20 24 83.3%
Branches: 261 552 47.3%

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