GCC Code Coverage Report


Directory: ./
File: util/PiGPIOUtils.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 231 314 73.6%
Functions: 23 23 100.0%
Branches: 440 955 46.1%

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 GPL v2 as described in the
10 * included LICENSE.GPL file.
11 */
12
13 #include "fpp-pch.h"
14
15 #include <fcntl.h>
16
17 #include <filesystem>
18 #include <fstream>
19 #include <mutex>
20 #include <map>
21 #include <set>
22
23 #include "PiFaceUtils.h"
24 #include "PiGPIOUtils.h"
25 #include "Warnings.h"
26 #include "common_mini.h"
27 #include "log.h"
28
29 341 static bool isPi5() {
30 126 static bool pi5 = [] {
31
2/4
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
63 std::string model = GetFileContents("/proc/device-tree/model");
32
5/12
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 62 times.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
190 return startsWith(model, "Raspberry Pi 5") ||
33
8/16
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 62 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 62 times.
✓ Branch 10 taken 63 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
128 startsWith(model, "Raspberry Pi Compute Module 5");
34
4/8
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 278 times.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
404 }();
35 341 return pi5;
36 }
37
38 // Kernel 6.18 enabled strict pinmux mode for both pinctrl-rp1 and pinctrl-bcm2835
39 // (raspberrypi/linux "pinctrl: rp1: enable strict pinmux mode", raspberrypi/linux#5870).
40 // A header pin that a peripheral node claims in the device tree -- i2c1 on P1-3/5, spi0
41 // on P1-19/21/23, uart0 on P1-8/10, i2s on P1-12/35/38/40 -- is now reported to gpiolib
42 // as in use by the kernel, and every gpiod request against it fails with EINVAL. Through
43 // FPP 9.x (kernel 6.12) those drivers were non-strict, so re-muxing the pad with
44 // "pinctrl set" and then requesting it worked; on 6.18 it does not, because "pinctrl set"
45 // writes FUNCSEL behind the pinctrl driver's back and leaves the kernel's claim in place.
46 //
47 // The supported way to get the pin back is to make the owning device release it, which
48 // unbinding its driver does. That is safe when nothing has attached to the bus (the common
49 // case for buttons wired straight to the header on a board with no cape), and decidedly
50 // unsafe when something has: a child driver's remove path can wedge in the kernel and take
51 // the whole bus with it. So we unbind only when the owner has no child driver bound other
52 // than the ones known to detach cleanly, and warn with the offending driver named otherwise.
53 //
54 // On a non-strict kernel the debugfs format has no "device " prefix, so findPinMuxOwner()
55 // finds nothing and none of this engages -- which is exactly right, since a non-strict
56 // controller hands the pin over without help.
57 static constexpr const char* PINMUX_DEBUG_DIR = "/sys/kernel/debug/pinctrl";
58
59 struct PinMuxOwner {
60 std::string device; // platform device holding the mux claim, e.g. "1f00074000.i2c"
61 std::string function; // mux function it selected, e.g. "i2c1"
62 bool hog = false;
63 };
64
65 // The pin controllers that drive the 40-pin header. Other pinctrl instances (the bcm2712
66 // "aon" controller, expanders) enumerate overlapping pin numbers and must not be consulted.
67 5 static bool isHeaderPinctrlDir(const std::string& dir) {
68
6/26
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✓ Branch 13 taken 5 times.
✓ Branch 14 taken 5 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 5 times.
✗ Branch 17 not taken.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
20 return endsWith(dir, "-pinctrl-rp1") || endsWith(dir, "-pinctrl-bcm2711") ||
69
3/16
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 5 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 5 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
✗ Branch 15 not taken.
10 endsWith(dir, "-pinctrl-bcm2835");
70 }
71
72 // debugfs files report a size of 0, so they have to be streamed rather than slurped.
73 63 static bool findPinMuxOwner(int gpio, PinMuxOwner& owner) {
74
2/4
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
63 const std::string prefix = "pin " + std::to_string(gpio) + " (";
75 63 std::error_code ec;
76
2/4
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
63 std::filesystem::directory_iterator dir(PINMUX_DEBUG_DIR, ec);
77
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 5 times.
63 if (ec) {
78 // Without debugfs there is no way to find out which device holds a pin, so a
79 // claimed pin can only be reported as unavailable by the failing request itself.
80 static bool warned = false;
81
2/2
✓ Branch 0 taken 35 times.
✓ Branch 1 taken 23 times.
58 if (!warned) {
82 35 warned = true;
83
1/2
✓ Branch 0 taken 35 times.
✗ Branch 1 not taken.
35 LogWarn(VB_GPIO, "%s is not readable; pins claimed by a kernel driver cannot be freed for GPIO use\n",
84 PINMUX_DEBUG_DIR);
85 }
86 58 return false;
87 }
88
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
5 for (const auto& entry : dir) {
89
4/8
✓ 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 not taken.
✓ Branch 7 taken 5 times.
5 if (!isHeaderPinctrlDir(entry.path().filename().string())) {
90 ✗ continue;
91 }
92
3/6
✓ 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.
5 std::ifstream in(entry.path().string() + "/pinmux-pins");
93 5 std::string line;
94
3/6
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 64 times.
✗ Branch 5 not taken.
64 while (std::getline(in, line)) {
95
3/4
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 59 times.
✓ Branch 3 taken 5 times.
64 if (!startsWith(line, prefix)) {
96 59 continue;
97 }
98 // strict format: "pin 2 (gpio2): device 1f00074000.i2c function i2c1 group gpio2"
99 5 size_t dpos = line.find("): device ");
100
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 if (dpos == std::string::npos) {
101 5 return false; // UNCLAIMED, GPIO-owned, or a non-strict kernel
102 }
103
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 std::string rest = line.substr(dpos + 10);
104
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 owner.device = rest.substr(0, rest.find(' '));
105 3 owner.hog = rest.find(" (HOG)") != std::string::npos;
106 3 size_t fpos = rest.find(" function ");
107
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (fpos != std::string::npos) {
108
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 std::string fn = rest.substr(fpos + 10);
109
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 owner.function = fn.substr(0, fn.find(' '));
110 3 }
111 3 return true;
112 3 }
113
4/8
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 5 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 5 times.
20 }
114 ✗ return false;
115 63 }
116
117 // A child that is still driver-bound means the bus is in use; removing the controller then
118 // runs that driver's remove path, which is where an unbind can hang. spidev is the one
119 // child FPP creates itself and is known to detach cleanly.
120 3 static bool findBlockingChildDriver(const std::string& devPath, std::string& blocker) {
121
4/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3 static const std::set<std::string> safeChildDrivers = { "spidev" };
122
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 const std::string ownDriver = devPath + "/driver";
123 3 std::error_code ec;
124 3 auto end = std::filesystem::recursive_directory_iterator();
125 3 for (auto it = std::filesystem::recursive_directory_iterator(
126
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 devPath, std::filesystem::directory_options::skip_permission_denied, ec);
127
2/4
✓ Branch 0 taken 45 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
48 it != end; it.increment(ec)) {
128
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (ec) {
129 ✗ break;
130 }
131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 48 times.
48 if (it.depth() >= 5) {
132 ✗ it.disable_recursion_pending();
133 }
134 48 const std::filesystem::path& p = it->path();
135
12/24
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 45 times.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 3 times.
✓ Branch 10 taken 3 times.
✓ Branch 11 taken 45 times.
✓ Branch 12 taken 48 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 48 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 45 times.
✓ Branch 17 taken 3 times.
✗ Branch 18 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
48 if (p.filename() != "driver" || p.string() == ownDriver) {
136 45 continue;
137 }
138
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 std::filesystem::path target = std::filesystem::read_symlink(p, ec);
139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (ec) {
140 ✗ ec.clear();
141 ✗ continue;
142 }
143
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 std::string driver = target.filename().string();
144
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (!safeChildDrivers.count(driver)) {
145
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 blocker = driver;
146 3 return true;
147 }
148
3/7
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
9 }
149 ✗ return false;
150 3 }
151
152 // Hand a pin back from the peripheral that claimed it in the device tree so FPP can
153 // use it itself - as a GPIO, or re-muxed to another function such as DPI (a pixel
154 // string output on the SPI header pins, say, with spi0 still enabled in config.txt).
155 // "use" names what the caller wants the pin for, for the messages. Returns true if
156 // the pin is usable when it returns.
157 64 static bool releasePinFromKernelDriver(int gpio, const std::string& pinName, const std::string& use = "GPIO") {
158 // configPin() is reachable from the REST API as well as from startup, so the
159 // once-per-pin bookkeeping needs to be serialized.
160 static std::mutex handledLock;
161 // What became of each pin already dealt with this boot: true if it was
162 // freed, false if it couldn't be (and was warned about). A later caller
163 // gets the same answer; this used to say "freed" to every later caller,
164 // even for a pin that is still claimed.
165
3/4
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 39 times.
✗ Branch 3 not taken.
64 static std::map<int, bool> handled;
166
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 std::unique_lock<std::mutex> lock(handledLock);
167
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 auto done = handled.find(gpio);
168
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 63 times.
64 if (done != handled.end()) {
169 1 return done->second;
170 }
171
172 63 PinMuxOwner owner;
173
3/4
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✓ Branch 3 taken 3 times.
63 if (!findPinMuxOwner(gpio, owner)) {
174 60 return true; // unclaimed, or a kernel that doesn't enforce the claim
175 }
176
4/8
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 3 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
3 if (owner.hog || owner.function == "gpio") {
177 // A hog belongs to the pin controller itself, and a pin muxed as the GPIO
178 // function stays requestable under strict mode via .function_is_gpio.
179 ✗ return true;
180 }
181
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 handled[gpio] = false; // until it is shown to be free below
182
183
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 const std::string devPath = "/sys/bus/platform/devices/" + owner.device;
184 3 std::error_code ec;
185
3/6
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
3 std::filesystem::path driverLink = std::filesystem::read_symlink(devPath + "/driver", ec);
186
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (ec) {
187 ✗ std::string w = "Pin " + pinName + " is claimed by " + owner.device + " (" + owner.function +
188 ✗ ") and cannot be used for " + use + "; no driver to unbind";
189 ✗ WarningHolder::AddWarning(51, w);
190 ✗ LogWarn(VB_GPIO, "%s\n", w.c_str());
191 ✗ return false;
192 ✗ }
193
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 std::string driver = driverLink.filename().string();
194
195 3 std::string blocker;
196
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
3 if (findBlockingChildDriver(devPath, blocker)) {
197
5/10
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
6 std::string w = "Pin " + pinName + " is claimed by " + owner.function + " (" + owner.device +
198
2/4
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
6 "), which is in use by the " + blocker +
199
5/10
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
3 " driver. Disable " + owner.function + " in config.txt to use this pin for " + use + ".";
200
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 WarningHolder::AddWarning(51, w);
201
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 LogWarn(VB_GPIO, "%s\n", w.c_str());
202 3 return false;
203 3 }
204
205 ✗ LogInfo(VB_GPIO, "Pin %s is claimed by %s (%s); unbinding %s to free it for %s use\n",
206 pinName.c_str(), owner.function.c_str(), owner.device.c_str(), driver.c_str(), use.c_str());
207 ✗ std::ofstream unbind("/sys/bus/platform/drivers/" + driver + "/unbind");
208 ✗ unbind << owner.device;
209 ✗ unbind.close();
210
211 ✗ PinMuxOwner check;
212 ✗ if (findPinMuxOwner(gpio, check)) {
213 ✗ std::string w = "Pin " + pinName + " is claimed by " + check.function + " (" + check.device +
214 ✗ ") and could not be freed for " + use + " use";
215 ✗ WarningHolder::AddWarning(51, w);
216 ✗ LogWarn(VB_GPIO, "%s\n", w.c_str());
217 ✗ return false;
218 ✗ }
219 ✗ handled[gpio] = true;
220 ✗ return true;
221 64 }
222
223 class PiGPIODCapabilities : public GPIODCapabilities {
224 public:
225 1764 PiGPIODCapabilities(const std::string& n, uint32_t kg) :
226
3/6
✓ Branch 0 taken 1764 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1764 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1764 times.
✗ Branch 5 not taken.
8820 GPIODCapabilities(n, kg) {
227
1/2
✓ Branch 0 taken 1764 times.
✗ Branch 1 not taken.
1764 gpioIdx = getPinctrlRpiChip();
228
1/2
✓ Branch 0 taken 1764 times.
✗ Branch 1 not taken.
1764 gpioName = pinctrlRpiChipName;
229 1764 gpio = kg;
230 1764 }
231
232 7938 virtual ~PiGPIODCapabilities() {
233
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3969 times.
7938 if (dutyFile != nullptr) {
234 ✗ fclose(dutyFile);
235 }
236 7938 }
237
238 1386 PiGPIODCapabilities& setResetMode(const std::string& mode) {
239 1386 resetMode = mode;
240 1386 return *this;
241 }
242 252 PiGPIODCapabilities& setPwm(int p, int sub) {
243 252 GPIODCapabilities::setPwm(p, sub);
244 252 return *this;
245 }
246 126 PiGPIODCapabilities& setUART(const std::string& u, const std::string& um) {
247 126 uart = u;
248 126 uartMode = um;
249 126 return *this;
250 }
251 // The pin is a data line (MISO/MOSI/SCLK) of SPI bus b when muxed to alt. Only
252 // for a bus the device tree enables with no pins claimed (fpp-spi1-nopins), the
253 // same way setUART() pairs with fpp-uart0-nopins.
254 189 PiGPIODCapabilities& setSPI(int b, const std::string& alt) {
255 189 spiBus = b;
256 189 spiMode = alt;
257 189 return *this;
258 }
259 33 virtual void releasePin() const override {
260 char buf[256];
261
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 7 times.
33 if (dpiConfigured) {
262 // A pixel data (or latch) line. Returning it to a plain input leaves
263 // it floating - or on the Pi 5, at "function none" with a weak
264 // pull-down - and the hat's buffer then amplifies whatever noise it
265 // picks up into random colours on the string for the rest of the
266 // shutdown/reboot (issue #2895). Park it as a driven-low output
267 // instead: pinctrl writes the pad registers directly, so this
268 // survives fppd exiting, and low is idle for WS281x. It holds until
269 // the next boot resets the pad; fppd's Init() then muxes the pin back
270 // to DPI only after a blank frame is already in the framebuffer.
271 26 snprintf(buf, 256, "/usr/bin/pinctrl set %d op dl", gpio);
272 26 dpiConfigured = false;
273 } else {
274 7 snprintf(buf, 256, "/usr/bin/pinctrl set %d %s", gpio, resetMode.c_str());
275 }
276
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 system(buf);
277
1/2
✓ Branch 0 taken 33 times.
✗ Branch 1 not taken.
33 GPIODCapabilities::releasePin();
278 33 }
279 64 virtual int configPin(const std::string& mode = "gpio",
280 bool directionOut = true,
281 const std::string& desc = "") const override {
282 // see https://datasheets.raspberrypi.com/rp1/rp1-peripherals.pdf
283 // 1-3: https://elinux.org/RPi_BCM2835_GPIOs
284 // 4/5: https://elinux.org/RPi_BCM2711_GPIOs
285
286 // A data line of a bus the device tree enables with no pins claimed
287 // (fpp-spi1-nopins), so the spi controller never owns it here. Anything
288 // else holding the pin -- i2s on P1-35/38/40 under an audio overlay, say
289 // -- has to let go before a "pinctrl set" to the SPI alt sticks, and a
290 // pin that cannot be freed is refused rather than half-muxed.
291
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 64 times.
64 if (mode == "spi") {
292 ✗ if (spiBus == -1) {
293 ✗ LogWarn(VB_GPIO, "Pin %s has no SPI function\n", name.c_str());
294 ✗ return -1;
295 }
296 ✗ if (!releasePinFromKernelDriver(gpio, name, "SPI")) {
297 ✗ return -1;
298 }
299 char buf[256];
300 ✗ snprintf(buf, 256, "/usr/bin/pinctrl set %d %s", gpio, spiMode.c_str());
301 ✗ system(buf);
302 ✗ return 0;
303 }
304
305 // Under strict pinmux a pin the device tree handed to a peripheral has to be
306 // reclaimed from that driver before any gpiod request on it can succeed, and
307 // before a "pinctrl set" to another alt function sticks: the pinctrl driver
308 // reasserts its claim, so a string output on a pin spi0 owns silently stayed
309 // SPI. See releasePinFromKernelDriver() above.
310
15/28
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 64 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✓ Branch 5 taken 35 times.
✓ Branch 6 taken 29 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 26 times.
✓ Branch 10 taken 3 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 2 times.
✓ Branch 13 taken 1 times.
✓ Branch 14 taken 2 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 2 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 64 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 64 times.
✗ Branch 21 not taken.
✓ Branch 22 taken 64 times.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
✗ Branch 26 not taken.
✗ Branch 27 not taken.
192 if (startsWith(mode, "gpio") || mode == "dpi" || mode == "pwm" || mode == "uart") {
311
11/16
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 38 times.
✓ Branch 4 taken 38 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 37 times.
✓ Branch 8 taken 37 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 35 times.
✓ Branch 12 taken 64 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 64 times.
✗ Branch 15 not taken.
192 releasePinFromKernelDriver(gpio, name, mode == "dpi" ? "DPI" : mode == "pwm" ? "PWM" : mode == "uart" ? "UART" : "GPIO");
312 }
313
314
5/6
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 63 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 63 times.
64 if (mode == "pwm" && pwm != -1) {
315 // alt3 is pwm
316 char buf[256];
317 1 snprintf(buf, 256, "/usr/bin/pinctrl set %d a3", gpio);
318
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 system(buf);
319 1 return 0;
320 }
321
322
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 37 times.
63 if (mode == "dpi") {
323 // Pi 5 (RP1): DPI is alt1, Pi 4 and earlier (BCM2711/BCM2835): DPI is alt2
324
3/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 24 times.
✓ Branch 3 taken 2 times.
26 const char* alt = isPi5() ? "a1" : "a2";
325 char buf[256];
326 26 snprintf(buf, 256, "/usr/bin/pinctrl set %d %s", gpio, alt);
327
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 system(buf);
328 26 dpiConfigured = true;
329 26 return 0;
330 }
331
6/8
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 35 times.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2 times.
✓ Branch 7 taken 35 times.
37 if (mode == "uart" && !uart.empty() && !uartMode.empty()) {
332 char buf[256];
333 2 snprintf(buf, 256, "/usr/bin/pinctrl set %d %s", gpio, uartMode.c_str());
334
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 system(buf);
335 2 return 0;
336 }
337
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 31 times.
35 if (mode == "gpio_od") {
338 // Open-drain (bit-banged I2C): set the pad to GPIO function with the
339 // input buffer + pull-up enabled via pinctrl, then create the
340 // open-drain gpiod request that actually drives/senses the line.
341 char buf[256];
342 4 snprintf(buf, 256, "/usr/bin/pinctrl set %d ip pu", gpio);
343
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 system(buf);
344
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
12 return GPIODCapabilities::configPin("gpio_od", false, desc);
345 }
346 // Pins whose resetMode is an alt-function sentinel ("a0"/"a3" -- the
347 // i2c/uart/spi header pins the device tree hands to a peripheral) come out of
348 // reset muxed away from GPIO, so push the pad back to the gpio function with
349 // pinctrl first. (gpio_pu/gpio_pd are left out: pinctrl's bare direction set
350 // carries no bias, and those pins are fine going straight to the request.)
351 //
352 // This must NOT return early. A gpio* mode only becomes usable once
353 // GPIODCapabilities::configPin() has requested the line: without a live
354 // request setValue()/getValue() are silent no-ops and requestEventFile() has
355 // no lastBias/lastDesc to work from. Returning here instead is a bug that has
356 // now surfaced three times -- P1-35/GPIO19 floating on Pi5 (the "no" sentinel,
357 // #2768), pull-up/down inputs coming up floating on any pin left at the
358 // class-default "a0", and a plain "gpio" OUTPUT on P1-10/GPIO15 (uart0 RX,
359 // which some Pi capes use as a port enable pin) muxing correctly but never
360 // driving, leaving that port permanently dark (#2979).
361
13/24
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 31 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 31 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 31 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 19 times.
✓ Branch 9 taken 12 times.
✓ Branch 10 taken 19 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 11 times.
✓ Branch 13 taken 8 times.
✓ Branch 14 taken 31 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 31 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 5 times.
✓ Branch 19 taken 26 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
104 if (startsWith(mode, "gpio") && mode != "gpio_pu" && mode != "gpio_pd" &&
362
6/8
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 10 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✓ Branch 7 taken 6 times.
11 (resetMode == "a0" || resetMode == "a3")) {
363 char buf[256];
364
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 snprintf(buf, 256, "/usr/bin/pinctrl set %d %s", gpio, directionOut ? "op" : "ip");
365
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 system(buf);
366 }
367
368
3/6
✓ Branch 0 taken 31 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 31 times.
31 if (mode == "pwm" || mode == "uart") {
369 ✗ return 0;
370 }
371 31 return GPIODCapabilities::configPin(mode, directionOut, desc);
372 }
373
374 1 virtual bool supportPWM() const override { return pwm != -1; }
375 1 virtual bool setupPWM(int maxValue = 25500) const override {
376
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (pwm != -1 && dutyFile != nullptr) {
377 // Already set up. Each call opened another duty_cycle stream and
378 // leaked the last; reopening instead would close a FILE* that
379 // setPWMValue() on an output thread may be writing to. The pin
380 // itself is muxed again, though: an output reload closes the old
381 // output first, and its releasePin() put the pin back to its reset
382 // mode, so the new output's PWM never reached it. A caller that
383 // wants another period (SetupExtGPIO vs the GPIO output) gets it.
384 ✗ configPin("pwm", true, "PWM");
385 ✗ if (maxValue != pwmPeriod) {
386 char name[128];
387 // duty_cycle may not exceed period: zero it before a shorter one
388 ✗ fprintf(dutyFile, "0");
389 ✗ fflush(dutyFile);
390 ✗ snprintf(name, sizeof(name), "/sys/class/pwm/pwmchip0/pwm%d/period", subPwm);
391 ✗ FILE* f = fopen(name, "w");
392 ✗ if (f) {
393 ✗ fprintf(f, "%d", maxValue);
394 ✗ fclose(f);
395 ✗ pwmPeriod = maxValue;
396 }
397 }
398 ✗ return true;
399 }
400
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (pwm != -1) {
401
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
4 configPin("pwm", true, "PWM");
402 char dir_name[128];
403
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 FILE* dir = fopen("/sys/class/pwm/pwmchip0/export", "w");
404
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (!dir) {
405 1 return false;
406 }
407 ✗ fprintf(dir, "%d", subPwm);
408 ✗ fclose(dir);
409
410 ✗ snprintf(dir_name, sizeof(dir_name), "/sys/class/pwm/pwmchip0/pwm%d/period", subPwm);
411 ✗ dir = fopen(dir_name, "w");
412 ✗ if (!dir) {
413 ✗ return false;
414 }
415 ✗ fprintf(dir, "%d", maxValue);
416 ✗ fclose(dir);
417 ✗ pwmPeriod = maxValue;
418
419 ✗ snprintf(dir_name, sizeof(dir_name), "/sys/class/pwm/pwmchip0/pwm%d/duty_cycle", subPwm);
420 ✗ dutyFile = fopen(dir_name, "w");
421 ✗ if (!dutyFile) {
422 ✗ return false;
423 }
424 ✗ fprintf(dutyFile, "0");
425 ✗ fflush(dutyFile);
426
427 ✗ snprintf(dir_name, sizeof(dir_name), "/sys/class/pwm/pwmchip0/pwm%d/enable", subPwm);
428 ✗ dir = fopen(dir_name, "w");
429 ✗ if (!dir) {
430 ✗ return false;
431 }
432 ✗ fprintf(dir, "1");
433 ✗ fclose(dir);
434 ✗ return true;
435 }
436 ✗ return false;
437 }
438 69 virtual void setPWMValue(int valueNS) const override {
439 // dutyFile is only opened by a setupPWM() that succeeded; every caller
440 // (the GPIO output, the ExtGPIO command) reaches here regardless.
441
2/4
✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 69 times.
69 if (pwm != -1 && dutyFile) {
442 ✗ fprintf(dutyFile, "%d", valueNS);
443 ✗ fflush(dutyFile);
444 }
445 69 }
446
447 1764 static int getPinctrlRpiChip() {
448
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 1701 times.
1764 if (pinctrlRpiChip == -1) {
449 63 pinctrlRpiChip = 0; // Default fallback
450 #ifdef IS_GPIOD_CXX_V2
451 63 int has54lineChip = -1;
452 63 std::string has54lineChipName;
453
5/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5694 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5754 times.
✓ Branch 7 taken 3 times.
5757 for (const auto& entry : std::filesystem::directory_iterator("/dev/")) {
454
6/10
✓ Branch 0 taken 5754 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5754 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 5754 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 5754 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 75 times.
✓ Branch 9 taken 5679 times.
17262 if (startsWith(entry.path().filename().string(), "gpiochip")) {
455 try {
456
3/6
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 75 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 75 times.
✗ Branch 5 not taken.
75 auto chip = gpiod::chip(entry.path().string());
457
1/2
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
75 auto info = chip.get_info();
458
14/26
✓ Branch 0 taken 75 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 60 times.
✓ Branch 4 taken 15 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 15 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 15 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 15 times.
✓ Branch 12 taken 15 times.
✓ Branch 13 taken 60 times.
✓ Branch 14 taken 15 times.
✓ Branch 15 taken 60 times.
✓ Branch 16 taken 75 times.
✗ Branch 17 not taken.
✓ Branch 18 taken 60 times.
✓ Branch 19 taken 15 times.
✗ Branch 20 not taken.
✗ Branch 21 not taken.
✗ Branch 22 not taken.
✗ Branch 23 not taken.
✗ Branch 24 not taken.
✗ Branch 25 not taken.
75 if (info.label() == "pinctrl-rp1" || info.label() == "pinctrl-bcm2711" || info.label() == "pinctrl-bcm2835") {
459
4/8
✓ Branch 0 taken 60 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 60 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 60 times.
✗ Branch 7 not taken.
60 pinctrlRpiChip = std::stoi(entry.path().filename().string().substr(8));
460 60 pinctrlRpiChipName = info.name();
461 60 return pinctrlRpiChip;
462
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15 } else if (info.num_lines() == 54) {
463 ✗ has54lineChip = std::stoi(entry.path().filename().string().substr(8));
464 ✗ has54lineChipName = info.name();
465 }
466
4/4
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 60 times.
135 } catch (...) {
467 // Chip doesn't exist, continue
468 ✗ }
469 }
470
6/6
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 60 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 60 times.
✓ Branch 4 taken 3 times.
✓ Branch 5 taken 60 times.
183 }
471
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (has54lineChip != -1) {
472 ✗ pinctrlRpiChip = has54lineChip;
473 ✗ pinctrlRpiChipName = has54lineChipName;
474 ✗ return pinctrlRpiChip;
475 }
476 #else
477 for (auto& a : gpiod::make_chip_iter()) {
478 std::string label = a.label();
479 if (label == "pinctrl-rp1" || label == "pinctrl-bcm2711" || label == "pinctrl-bcm2835") {
480 pinctrlRpiChipName = a.name();
481 return pinctrlRpiChip;
482 }
483 pinctrlRpiChip++;
484 }
485 // didn't find it by name, try finding the chip with 54 lines
486 pinctrlRpiChip = 0;
487 for (auto& a : gpiod::make_chip_iter()) {
488 if (a.num_lines() == 54) {
489 pinctrlRpiChipName = a.name();
490 return pinctrlRpiChip;
491 }
492 pinctrlRpiChip++;
493 }
494 #endif
495
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 60 times.
63 }
496 1704 return pinctrlRpiChip;
497 }
498 mutable FILE* dutyFile = nullptr;
499 mutable int pwmPeriod = 0; // ns, as last written to the period file
500 static int pinctrlRpiChip;
501 static std::string pinctrlRpiChipName;
502 std::string resetMode = "a0";
503 mutable bool dpiConfigured = false; // muxed for DPI; released as output-low, see releasePin()
504 std::string uartMode;
505 int spiBus = -1; // logical SPI bus this pin is a data line of, -1 if none
506 std::string spiMode;
507 };
508 int PiGPIODCapabilities::pinctrlRpiChip = -1;
509 std::string PiGPIODCapabilities::pinctrlRpiChipName;
510 static std::vector<PiGPIODCapabilities> PI_PINS;
511
512 63 void PiGPIOPinProvider::Init() {
513
4/6
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
126 std::string ipOrNo = isPi5() ? "no" : "ip";
514
4/6
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
126 std::string i2c = isPi5() ? "a3" : "a0";
515 // SPI1 sits on the I2S pins; RP1 puts it on alt0 where the older SoCs use alt4
516
4/6
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 62 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
189 std::string spi1 = isPi5() ? "a0" : "a4";
517
4/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-3", 2).setResetMode(i2c));
518
4/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-5", 3).setResetMode(i2c));
519
3/6
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-7", 4));
520
9/16
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 62 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 63 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 63 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 63 times.
✗ Branch 15 not taken.
378 PI_PINS.push_back(PiGPIODCapabilities("P1-8", 14).setUART("ttyAMA0-tx", isPi5() ? "a4" : "a0"));
521
9/16
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 62 times.
✓ Branch 9 taken 1 times.
✓ Branch 10 taken 63 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 63 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 63 times.
✗ Branch 15 not taken.
378 PI_PINS.push_back(PiGPIODCapabilities("P1-10", 15).setUART("ttyAMA0-rx", isPi5() ? "a4" : "a0"));
522
4/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-11", 17).setResetMode(ipOrNo));
523
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-12", 18).setPwm(0, 0).setResetMode(ipOrNo));
524
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-13", 27).setPwm(0, 1).setResetMode(ipOrNo));
525
4/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-15", 22).setResetMode(ipOrNo));
526
4/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-16", 23).setResetMode(ipOrNo));
527
4/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-18", 24).setResetMode(ipOrNo));
528
3/6
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-19", 10));
529
3/6
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-21", 9));
530
4/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-22", 25).setResetMode(ipOrNo));
531
3/6
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-23", 11));
532
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-24", 8).setResetMode("op"));
533
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-26", 7).setResetMode("op"));
534
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-27", 0).setResetMode("ip"));
535
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-28", 1).setResetMode("ip"));
536
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-29", 5).setResetMode("ip"));
537
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-31", 6).setResetMode("ip"));
538
6/12
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 63 times.
✗ Branch 11 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-32", 12).setResetMode("ip").setPwm(0, 0));
539
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-33", 13).setResetMode("ip"));
540
6/12
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 63 times.
✗ Branch 11 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-35", 19).setResetMode(ipOrNo).setPwm(0, 1).setSPI(1, spi1));
541
4/8
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-36", 16).setResetMode(ipOrNo));
542
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
252 PI_PINS.push_back(PiGPIODCapabilities("P1-37", 26).setResetMode("ip"));
543
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
126 PI_PINS.push_back(PiGPIODCapabilities("P1-38", 20).setResetMode(ipOrNo).setSPI(1, spi1));
544
5/10
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 63 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 63 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 63 times.
✗ Branch 9 not taken.
63 PI_PINS.push_back(PiGPIODCapabilities("P1-40", 21).setResetMode(ipOrNo).setSPI(1, spi1));
545
546
1/2
✓ Branch 0 taken 63 times.
✗ Branch 1 not taken.
63 PiFacePinCapabilities::Init();
547 63 }
548 183 const PinCapabilities& PiGPIOPinProvider::getPinByName(const std::string& name) {
549
2/2
✓ Branch 0 taken 2873 times.
✓ Branch 1 taken 12 times.
2885 for (auto& a : PI_PINS) {
550
2/2
✓ Branch 0 taken 171 times.
✓ Branch 1 taken 2702 times.
2873 if (a.name == name) {
551 171 return a;
552 }
553 }
554 12 return PiFacePinCapabilities::getPinByName(name);
555 }
556 3 const PinCapabilities& PiGPIOPinProvider::getPinByGPIO(int chip, int gpio) {
557
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (chip == 0) {
558
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 for (auto& a : PI_PINS) {
559
3/4
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 45 times.
48 if (a.gpioIdx == chip && a.gpio == gpio) {
560 3 return a;
561 }
562 }
563 }
564 ✗ return PiFacePinCapabilities::getPinByGPIO(chip, gpio);
565 }
566
567 56 std::vector<std::string> PiGPIOPinProvider::getPinNames() {
568 56 std::vector<std::string> ret;
569
2/2
✓ Branch 0 taken 1568 times.
✓ Branch 1 taken 56 times.
1624 for (auto& a : PI_PINS) {
570
1/2
✓ Branch 0 taken 1568 times.
✗ Branch 1 not taken.
1568 ret.push_back(a.name);
571 }
572
1/2
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
56 PiFacePinCapabilities::getPinNames(ret);
573 56 return ret;
574 ✗ }
575 2 const PinCapabilities& PiGPIOPinProvider::getPinByUART(const std::string& n) {
576
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 for (auto& a : PI_PINS) {
577
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if (a.uart == n) {
578 2 return a;
579 }
580 }
581 ✗ return PiFacePinCapabilities::getPinByUART(n);
582 }
583