GCC Code Coverage Report


Directory: ./
File: util/BBBPruSerial.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 77 125 61.6%
Functions: 8 12 66.7%
Branches: 55 178 30.9%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2026 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 <atomic>
16 #include <mutex>
17 #include <stdexcept>
18 #include <unistd.h>
19
20 #include "../Warnings.h"
21 #include "../common_mini.h"
22 #include "../log.h"
23 #include "../channeloutput/serialutil.h"
24 #include "../channeloutput/ChannelOutputSetup.h"
25
26 #include "BBBPruSerial.h"
27 #include "BBBPruUtils.h"
28 #include "GPIOUtils.h"
29
30 #ifdef PLATFORM_BB64
31
32 // The PocketBeagle capes this is for run their strings with BBB48String,
33 // which uses PRU1 on the AM62x and only takes PRU0 for pins on the MCU GPIO
34 // controller, which no PocketBeagle 2 header pin is.
35 static constexpr int PRU_NUM = 0;
36 static constexpr uint32_t PRU_HZ = 250000000;
37
38 // Must match PRUSerial.asm. Word offsets into the PRU data RAM.
39 static constexpr int CMD = 0x00 / 4;
40 static constexpr int RESP = 0x04 / 4;
41 static constexpr int HEAD = 0x08 / 4;
42 static constexpr int TAIL = 0x0C / 4;
43 static constexpr int CONFIG = 0x10 / 4;
44 static constexpr int RING = 0x100 / 4;
45 static constexpr uint32_t RING_ENTRIES = 1024;
46 static constexpr uint32_t HOLD_LOW = 0x80000000;
47 static constexpr uint32_t HOLD_HIGH = 0x40000000;
48 static constexpr uint32_t HOLD_MAX = 0x3FFFFFFF;
49
50 // The OUT register of each GPIO bank, indexed by mappedGPIOIdx(); the set and
51 // clear registers follow it. Same table as FalconPRUDefs.hp.
52 static constexpr uint32_t GPIO_OUT_REG[4] = { 0x04201014, 0x0060003C, 0x00600064, 0x00601014 };
53
54 static std::atomic<bool> pruInUse(false);
55
56 class BBBPruSerial : public SerialPortBackend {
57 public:
58 1 BBBPruSerial(BBBPru* p, uint32_t bitCyc, int db, char par, int sb) :
59 1 pru(p),
60 1 ram((volatile uint32_t*)p->data_ram),
61 1 bitCycles(bitCyc),
62 1 dataBits(db),
63 1 parity(par),
64 1 stopBits(sb) {
65 1 }
66 4 ~BBBPruSerial() override {
67 2 ram[CMD] = 0xFF;
68 2 __sync_synchronize();
69
3/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 1 times.
2 for (int i = 0; i < 50 && ram[RESP] != 0xFFFF; i++) {
70 ✗ usleep(1000);
71 }
72 // cleared while the core is still up; its data RAM can be unreachable
73 // once remoteproc has stopped it
74 2 ram[CMD] = 0;
75 2 pru->stop();
76
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
2 delete pru;
77 2 pruInUse = false;
78 4 }
79
80 78 int write(const uint8_t* data, int len, int timeoutMs) override {
81
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 std::lock_guard<std::mutex> l(lock);
82
1/2
✓ Branch 0 taken 78 times.
✗ Branch 1 not taken.
78 uint64_t deadline = GetMonotonicTimeMS() + timeoutMs;
83 78 int sent = 0;
84
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 78 times.
156 while (sent < len) {
85 78 uint32_t room = freeEntries();
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 78 times.
78 if (room == 0) {
87 ✗ publish();
88 ✗ if (GetMonotonicTimeMS() >= deadline) {
89 ✗ break;
90 }
91 ✗ usleep(200);
92 ✗ continue;
93 }
94
5/6
✓ Branch 0 taken 624 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 546 times.
✓ Branch 3 taken 78 times.
✓ Branch 4 taken 546 times.
✓ Branch 5 taken 78 times.
624 while (room-- && sent < len) {
95 546 push(frame(data[sent++]));
96 }
97 }
98 78 publish();
99 78 return sent;
100 78 }
101
102 ✗ void sendBreak(int us) override {
103 ✗ std::lock_guard<std::mutex> l(lock);
104 ✗ uint64_t deadline = GetMonotonicTimeMS() + 100;
105 ✗ while (freeEntries() < 2 && GetMonotonicTimeMS() < deadline) {
106 ✗ usleep(200);
107 }
108 ✗ if (freeEntries() < 2) {
109 ✗ return;
110 }
111 // the mark after it gives the receiver time to see the line come back
112 // up before the next start bit; at least 20us, as a tty break then a
113 // usleep(20) gives (DMX needs 8us)
114 ✗ uint64_t low = (uint64_t)us * (PRU_HZ / 1000000);
115 ✗ uint64_t mark = std::max<uint64_t>(20 * (PRU_HZ / 1000000), 2 * bitCycles);
116 ✗ push(HOLD_LOW | (uint32_t)std::min<uint64_t>(low, HOLD_MAX));
117 ✗ push(HOLD_HIGH | (uint32_t)std::min<uint64_t>(mark, HOLD_MAX));
118 ✗ publish();
119 ✗ }
120
121 ✗ bool waitDrained(int timeoutMs) override {
122 ✗ uint64_t deadline = GetMonotonicTimeMS() + timeoutMs;
123 ✗ while (ram[TAIL] != head) {
124 ✗ if (GetMonotonicTimeMS() >= deadline) {
125 ✗ return false;
126 }
127 ✗ usleep(200);
128 }
129 ✗ return true;
130 }
131
132 private:
133 78 uint32_t freeEntries() const {
134 78 return (ram[TAIL] - head - 1) & (RING_ENTRIES - 1);
135 }
136 546 void push(uint32_t entry) {
137 546 ram[RING + head] = entry;
138 546 head = (head + 1) & (RING_ENTRIES - 1);
139 546 }
140 78 void publish() {
141 // the entries have to be in place before the PRU sees the new head
142 78 __sync_synchronize();
143 78 ram[HEAD] = head;
144 78 }
145 // start bit, data LSB first, parity, stop bits, as one entry
146 546 uint32_t frame(uint8_t b) const {
147 546 uint32_t bits = 0;
148 546 int n = 1; // start bit is the 0 already in bit 0
149 546 uint32_t d = b & ((1u << dataBits) - 1);
150 546 bits |= d << n;
151 546 n += dataBits;
152
2/4
✓ Branch 0 taken 546 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 546 times.
546 if (parity == 'E' || parity == 'O') {
153 ✗ int ones = __builtin_popcount(d);
154 ✗ uint32_t p = (parity == 'E') ? (ones & 1) : !(ones & 1);
155 ✗ bits |= p << n;
156 ✗ n++;
157 }
158
2/2
✓ Branch 0 taken 546 times.
✓ Branch 1 taken 546 times.
1092 for (int s = 0; s < stopBits; s++) {
159 546 bits |= 1u << n++;
160 }
161 546 return ((uint32_t)n << 16) | bits;
162 }
163
164 BBBPru* pru;
165 volatile uint32_t* ram;
166 uint32_t bitCycles;
167 int dataBits;
168 char parity;
169 int stopBits;
170 uint32_t head = 0;
171 std::mutex lock;
172 };
173
174 1 int BBBPruSerialOpen(const PinCapabilities& pin, int baud, const char* mode, const std::string& desc) {
175 ✗ auto fail = [&](const std::string& why) {
176 ✗ std::string w = desc + ": cannot send on " + pin.name + " (" + why + ")";
177 ✗ LogErr(VB_CHANNELOUT, "%s\n", w.c_str());
178 // comes down when the output's config is next loaded, like the
179 // other serial output config errors
180 ✗ AddOutputConfigWarning(26, w);
181 ✗ return -1;
182 ✗ };
183
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.
1 int dataBits = (mode && mode[0] >= '5' && mode[0] <= '8') ? mode[0] - '0' : 8;
184
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 char parity = (mode && mode[0]) ? mode[1] : 'N';
185
4/8
✓ 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.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
1 int stopBits = (mode && mode[0] && mode[1] && mode[2] == '2') ? 2 : 1;
186
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (baud <= 0 || PRU_HZ / baud < 16) {
187 ✗ return fail("unsupported speed " + std::to_string(baud));
188 }
189
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 int gpioIdx = pin.mappedGPIOIdx();
190
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 int bit = pin.mappedGPIO();
191
4/8
✓ 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.
✗ Branch 6 not taken.
✓ Branch 7 taken 1 times.
1 if (gpioIdx < 0 || gpioIdx > 3 || bit < 0 || bit > 31) {
192 ✗ return fail("no GPIO the PRU can drive");
193 }
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (pruInUse.exchange(true)) {
195 ✗ return fail("the PRU transmitter is already used by another serial output");
196 }
197
198
5/10
✓ 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.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
4 CompilePRUProgram("/opt/fpp/src/pru/compilePRUSerial.sh", { "-DRUNNING_ON_PRU" + std::to_string(PRU_NUM) });
199 1 BBBPru* pru = nullptr;
200 try {
201
3/8
✓ 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 not taken.
✗ Branch 7 not taken.
1 pru = new BBBPru(PRU_NUM);
202 ✗ } catch (const std::exception& ex) {
203 ✗ pruInUse = false;
204 ✗ return fail(ex.what());
205 ✗ }
206
3/6
✓ 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.
3 if (!pru->run("/tmp/PRUSerial.out")) {
207 ✗ delete pru;
208 ✗ pruInUse = false;
209 ✗ return fail("could not start PRU" + std::to_string(PRU_NUM));
210 }
211
212 // the line idles high (mark) before the PRU takes it over
213
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 pin.configPin("gpio", true, desc);
214
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 pin.setValue(true);
215
216 1 uint32_t bitCycles = (PRU_HZ + baud / 2) / baud;
217 1 volatile uint32_t* ram = (volatile uint32_t*)pru->data_ram;
218 1 ram[CONFIG + 0] = GPIO_OUT_REG[gpioIdx] + 4; // set
219 1 ram[CONFIG + 1] = GPIO_OUT_REG[gpioIdx] + 8; // clear
220 1 ram[CONFIG + 2] = 1u << bit;
221 1 ram[CONFIG + 3] = bitCycles;
222 1 ram[HEAD] = 0;
223 1 ram[TAIL] = 0;
224 1 __sync_synchronize();
225 1 ram[CMD] = 1;
226
227
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogInfo(VB_CHANNELOUT, "%s: sending on %s from PRU%d at %d baud %s (%u cycles per bit)\n",
228 desc.c_str(), pin.name.c_str(), PRU_NUM, baud, mode, bitCycles);
229
3/8
✓ 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 not taken.
✗ Branch 7 not taken.
1 return SerialAttachBackend(new BBBPruSerial(pru, bitCycles, dataBits, parity, stopBits));
230
1/6
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
1 }
231
232 #else
233
234 ✗ int BBBPruSerialOpen(const PinCapabilities& pin, int baud, const char* mode, const std::string& desc) {
235 ✗ return -1;
236 }
237
238 #endif
239