GCC Code Coverage Report


Directory: ./
File: non-gpl/BBB48String/BBB48String.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 561 666 84.2%
Functions: 23 26 88.5%
Branches: 549 1076 51.0%

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 CC-BY-ND as described in the
10 * included LICENSE.CC-BY-ND file. This file may be modified for
11 * personal use, but modified copies MAY NOT be redistributed in any form.
12 */
13
14 #include "fpp-pch.h"
15
16 #include "fpp-json.h"
17
18 #include <sys/wait.h>
19 #include <unistd.h>
20
21 #include <arm_neon.h>
22
23 #include <algorithm>
24
25 #include <fstream>
26
27 #define BBB_PRU 1
28
29 // #define PRINT_STATS
30
31 // FPP includes
32 #include "../../Sequence.h"
33 #include "../FalconV5Support/FalconV5Support.h"
34 #include "../../Warnings.h"
35 #include "../../channeloutput/ChannelOutputSetup.h"
36 #include "../../common.h"
37 #include "../../log.h"
38
39 #include "BBB48String.h"
40 #include "../CapeUtils/CapeUtils.h"
41 #include "channeloutput/stringtesters/PixelStringTester.h"
42 #include "util/BBBUtils.h"
43
44 #include "../../overlays/PixelOverlay.h"
45
46 #include "Plugin.h"
47 class BBB48StringPlugin : public FPPPlugins::Plugin,
48 public FPPPlugins::ChannelOutputPlugin {
49 public:
50
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
75 BBB48StringPlugin() : FPPPlugins::Plugin("BBB48String") {}
51 virtual ChannelOutput*
52 28 createChannelOutput(unsigned int startChannel,
53 unsigned int channelCount) override {
54
2/6
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
56 return new BBB48StringOutput(startChannel, channelCount);
55 }
56 };
57
58 extern "C" {
59
2/6
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
50 FPPPlugins::Plugin* createPlugin() { return new BBB48StringPlugin(); }
60 }
61
62 /*
63 *
64 */
65 28 BBB48StringOutput::BBB48StringOutput(unsigned int startChannel,
66 28 unsigned int channelCount) : ChannelOutput(startChannel, channelCount),
67 28 m_curFrame(0),
68 28 m_pru(NULL),
69 28 m_pruData(NULL),
70 28 m_pru0(NULL),
71 28 m_pru0Data(NULL),
72 28 m_stallCount(0) {
73
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 LogDebug(VB_CHANNELOUT, "BBB48StringOutput::BBB48StringOutput(%u, %u)\n",
74 startChannel, channelCount);
75 28 }
76
77 /*
78 *
79 */
80 112 BBB48StringOutput::~BBB48StringOutput() {
81 56 LogDebug(VB_CHANNELOUT, "BBB48StringOutput::~BBB48StringOutput()\n");
82
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 28 times.
382 for (auto a : m_strings) {
83
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
326 delete a;
84 }
85 56 m_strings.clear();
86
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
56 if (m_pru)
87 ✗ delete m_pru;
88
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 28 times.
56 if (m_pru0)
89 ✗ delete m_pru0;
90
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 26 times.
56 if (falconV5Support)
91
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 delete falconV5Support;
92 112 }
93
94 29 static void compilePRUCode(const char* program,
95 const std::vector<std::string>& sargs,
96 const std::vector<std::string>& args1) {
97
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 std::vector<std::string> args = sargs;
98
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 args.insert(args.end(), args1.begin(), args1.end());
99
2/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
29 CompilePRUProgram(program, args);
100 29 }
101
102 25 static void compilePRUCode(const std::vector<std::string>& sargs,
103 const std::vector<std::string>& args0,
104 const std::vector<std::string>& args1, bool split) {
105 25 compilePRUCode("/opt/fpp/src/non-gpl/BBB48String/compileWS2811x.sh", sargs,
106 args1);
107
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
25 if (split) {
108 4 compilePRUCode("/opt/fpp/src/non-gpl/BBB48String/compileWS2811x_gpio0.sh",
109 sargs, args0);
110 }
111 25 }
112
113 54 inline int mapSize(int max, int maxString) {
114
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 54 times.
54 if (maxString > max) {
115 ✗ maxString = max;
116 }
117 54 int newHeight = maxString;
118 // make sure it's a multiple of 4 to keep things aligned in memory
119
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 31 times.
54 if (maxString == 0) {
120 23 return 0;
121
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 5 times.
31 } else if (maxString <= 4) {
122 26 newHeight = 4;
123
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
5 } else if (maxString <= 8) {
124 2 newHeight = 8;
125
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 2 times.
3 } else if (maxString <= 12) {
126 1 newHeight = 12;
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (maxString <= 16) {
128 ✗ newHeight = 16;
129
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (maxString <= 20) {
130 ✗ newHeight = 20;
131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (maxString <= 24) {
132 ✗ newHeight = 24;
133
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (maxString <= 28) {
134 ✗ newHeight = 28;
135
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (maxString <= 32) {
136 ✗ newHeight = 32;
137
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (maxString <= 36) {
138 ✗ newHeight = 36;
139
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (maxString <= 40) {
140 ✗ newHeight = 40;
141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 } else if (maxString <= 44) {
142 ✗ newHeight = 44;
143 } else {
144 2 newHeight = 48;
145 }
146 31 return newHeight;
147 }
148
149 50 void BBB48StringOutput::createOutputLengths(FrameData& d,
150 const std::string& pfx,
151 std::vector<std::string>& args) {
152
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 29 times.
50 if (d.gpioStringMap.empty()) {
153 21 return;
154 }
155
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 std::ofstream outputFile;
156
3/6
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
29 outputFile.open("/tmp/OutputLengths" + pfx + ".hp",
157 std::ofstream::out | std::ofstream::trunc);
158
159 29 std::map<int, std::vector<GPIOCommand>> sizes;
160
2/2
✓ Branch 0 taken 132 times.
✓ Branch 1 taken 29 times.
161 for (int x : d.gpioStringMap) {
161
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 68 times.
132 if (x != -1) {
162 64 int pc = m_strings[x]->m_outputBytes;
163
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 4 times.
64 if (pc != 0) {
164
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 60 times.
116 for (auto& a : m_strings[x]->m_gpioCommands) {
165
2/4
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
56 sizes[a.channelOffset].push_back(a);
166 }
167 }
168 }
169 }
170
171 // Only offsets inside the frame get a check: the firmware turns every
172 // line off when the frame ends, so a command at or past the longest
173 // string never has anything to do. The last key is not necessarily the
174 // longest string's own end - a Falcon receiver chain has no end of
175 // string command, and a license clamped string keeps commands past its
176 // new length - so the chain is terminated explicitly rather than by
177 // looking one key past the end of the map.
178 29 std::vector<int> offsets;
179
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 29 times.
62 for (auto& s : sizes) {
180
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 28 times.
33 if (s.first < d.maxStringLen) {
181
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 offsets.push_back(s.first);
182 }
183 }
184
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 29 times.
34 for (size_t k = 0; k < offsets.size(); k++) {
185 5 int min = offsets[k];
186
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 taken 5 times.
✗ Branch 7 not taken.
5 outputFile << "\nCHECK_" << pfx << std::to_string(min) << ":\n";
187
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (min <= 255) {
188 5 outputFile << " QBNE skip_" << pfx << std::to_string(min)
189
6/12
✓ 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.
5 << ", cur_data, " << std::to_string(min) << "\n";
190 } else {
191 ✗ if (min <= 0xFFFF) {
192 ✗ outputFile << " LDI r8, " << std::to_string(min) << "\n";
193 } else {
194 ✗ outputFile << " LDI32 r8, " << std::to_string(min) << "\n";
195 }
196 ✗ outputFile << " QBNE skip_" << pfx << std::to_string(min)
197 ✗ << ", cur_data, r8\n";
198 }
199
200
3/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 5 times.
10 for (auto& cmd : sizes[min]) {
201 5 int y = cmd.port;
202
1/2
✓ Branch 0 taken 11 times.
✗ Branch 1 not taken.
11 for (int x = 0; x < d.gpioStringMap.size(); x++) {
203
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 6 times.
11 if (d.gpioStringMap[x] == y) {
204 5 y = x;
205 5 break;
206 }
207 }
208
209 5 std::string o = std::to_string(y + 1);
210
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (cmd.type) {
211 outputFile << " SET GPIO_MASK(o" << o << "_gpio), GPIO_MASK(o"
212 ✗ << o << "_gpio), o" << o << "_pin\n";
213 } else {
214 outputFile << " CLR GPIO_MASK(o" << o << "_gpio), GPIO_MASK(o"
215
7/14
✓ 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.
5 << o << "_gpio), o" << o << "_pin\n";
216 }
217 5 }
218
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 std::string next = "NO_PIXELS_CHECK";
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (k + 1 < offsets.size()) {
220 ✗ next = "CHECK_" + pfx + std::to_string(offsets[k + 1]);
221 }
222
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 outputFile << " LDI next_check, $CODE(" << next << ")\n";
223
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 taken 5 times.
✗ Branch 7 not taken.
5 outputFile << "skip_" << pfx << std::to_string(min) << ":\n RET\n";
224 5 }
225
226
2/2
✓ Branch 0 taken 24 times.
✓ Branch 1 taken 5 times.
29 if (offsets.empty()) {
227
1/2
✓ Branch 0 taken 24 times.
✗ Branch 1 not taken.
24 args.emplace_back("-DFIRST_CHECK=NO_PIXELS_CHECK");
228 } else {
229
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 args.push_back("-DFIRST_CHECK=CHECK_" + pfx + std::to_string(offsets[0]));
230 }
231
1/2
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
29 outputFile.close();
232 29 }
233
234 27 void BBB48StringOutput::groupFalconV4Chains() {
235 // Group Falcon smart receiver ports into 4-port chains. Only the
236 // send-only V4 protocol is possible here: these capes have no listener
237 // hardware, so a V5 selection falls back to V4 handling. Done before
238 // the outputs are mapped so a chain whose first port has no pixels still
239 // gets a lane to carry its receiver's config packet.
240 27 int x = 0;
241
2/2
✓ Branch 0 taken 157 times.
✓ Branch 1 taken 27 times.
184 while (x < m_strings.size()) {
242 157 int p = x;
243 157 PixelString* p1 = m_strings[x++];
244
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 155 times.
157 if (!p1->m_isSmartReceiver ||
245
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 (p1->smartReceiverType != PixelString::ReceiverType::FalconV4 &&
246 ✗ p1->smartReceiverType != PixelString::ReceiverType::FalconV5)) {
247 155 continue;
248 }
249
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (p1->smartReceiverType == PixelString::ReceiverType::FalconV5) {
250 ✗ LogWarn(VB_CHANNELOUT, "Falcon V5 (bidirectional) is not supported on BBB48String; using send-only V4 handling for output %d\n", p + 1);
251 ✗ p1->smartReceiverType = PixelString::ReceiverType::FalconV4;
252 }
253
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 PixelString* p2 = x < m_strings.size() ? m_strings[x++] : nullptr;
254
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 PixelString* p3 = x < m_strings.size() ? m_strings[x++] : nullptr;
255
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 PixelString* p4 = x < m_strings.size() ? m_strings[x++] : nullptr;
256
257
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (p1->m_isInverted) {
258 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: Falcon smart receivers are not supported on inverted output " + std::to_string(p + 1));
259 ✗ continue;
260 }
261
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!falconV5Support) {
262
3/8
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
2 falconV5Support = new FalconV5Support();
263 }
264
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 falconV5Support->addReceiverChain(p1, p2, p3, p4, 0, 0, true);
265
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 m_falconChainHeads.push_back(p);
266 // the four lines carry continuous pixel data - none of the v1/v2
267 // style mid-frame gpio toggling
268 2 p1->m_gpioCommands.clear();
269
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (p2) {
270 2 p2->m_gpioCommands.clear();
271 }
272
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (p3) {
273 2 p3->m_gpioCommands.clear();
274 }
275
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (p4) {
276 2 p4->m_gpioCommands.clear();
277 }
278 }
279 27 }
280
281 4 void BBB48StringOutput::setupFalconV4Lanes(FrameData& d, const std::vector<std::array<uint8_t, 64>>& packets,
282 Json::Value& root, std::vector<std::string>& args) {
283 // The chain heads this PRU drives get their packet bytes in their lane
284 // (bytes reversed: the firmware clocks MSB first, the wire wants the
285 // UART LSB first) and their pin in the FALCON_MASK_GPIOn the firmware
286 // toggles. Every other lane stays zero and its line stays low.
287 4 d.falconPacketLanes.clear();
288 4 uint32_t masks[4] = { 0, 0, 0, 0 };
289 4 int numStrings = d.gpioStringMap.size();
290
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
8 std::vector<uint8_t> lanes(57 * numStrings, 0);
291
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
20 for (int lane = 0; lane < numStrings; lane++) {
292 16 int idx = d.gpioStringMap[lane];
293
7/8
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 10 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 8 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 14 times.
✓ Branch 7 taken 2 times.
16 if (idx < 0 || std::find(m_falconChainHeads.begin(), m_falconChainHeads.end(), idx) == m_falconChainHeads.end()) {
294 14 continue;
295 }
296
5/10
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ 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 not taken.
✓ Branch 8 taken 2 times.
✗ Branch 9 not taken.
2 const PinCapabilities& pin = PinCapabilities::getPinByName(root["outputs"][idx]["pin"].asString());
297
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 masks[pin.mappedGPIOIdx()] |= 1 << pin.mappedGPIO();
298
2/2
✓ Branch 0 taken 114 times.
✓ Branch 1 taken 2 times.
116 for (int b = 0; b < 57; b++) {
299 114 uint8_t v = packets[idx][b];
300 114 v = ((v * 0x0802LU & 0x22110LU) | (v * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
301 114 lanes[b * numStrings + lane] = v;
302 }
303 }
304
6/8
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 2 times.
✗ Branch 7 not taken.
4 if (!masks[0] && !masks[1] && !masks[2] && !masks[3]) {
305 2 return;
306 }
307 2 d.falconPacketLanes = std::move(lanes);
308
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 args.push_back("-DFALCONV4");
309
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
10 for (int g = 0; g < 4; g++) {
310
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if (masks[g]) {
311 char buf[40];
312 2 snprintf(buf, sizeof(buf), "-DFALCON_MASK_GPIO%d=0x%08X", g, masks[g]);
313
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 args.push_back(buf);
314 }
315 }
316
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 }
317
318 25 void BBB48StringOutput::setupFalconV4Support(Json::Value& root, std::vector<std::string>& args1, std::vector<std::string>& args0) {
319 // The packet content is static for a configuration, so it is generated
320 // once (after any license clamp has settled the pixel counts) and handed
321 // to whichever PRU drives each chain head: GPIO0 heads may be on the
322 // other PRU.
323
5/6
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 23 times.
✓ Branch 5 taken 2 times.
25 if (!falconV5Support || falconV5Support->getReceiverChains().empty()) {
324 23 return;
325 }
326 2 std::vector<std::array<uint8_t, 64>> packets;
327
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 packets.resize(m_strings.size());
328
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 2 times.
12 for (auto& pk : packets) {
329 10 memset(&pk[0], 0, 64);
330 }
331
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 for (auto& rc : falconV5Support->getReceiverChains()) {
332 2 int port = rc->getPixelStrings()[0]->m_portNumber;
333
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2 times.
2 if (port < 0 || port >= (int)packets.size()) {
334 ✗ continue; // a port past the strings would write past the packets
335 }
336
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 rc->generateConfigPacket(&packets[port][0]);
337 }
338
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 setupFalconV4Lanes(m_gpioData, packets, root, args1);
339
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 setupFalconV4Lanes(m_gpio0Data, packets, root, args0);
340 2 }
341
342 /*
343 *
344 */
345 28 int BBB48StringOutput::Init(Json::Value config) {
346
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 LogDebug(VB_CHANNELOUT, "BBB48StringOutput::Init(JSON)\n");
347
348
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 m_subType = config["subType"].asString();
349
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 m_channelCount = config["channelCount"].asInt();
350 28 m_gpio0Data.copyToPru = false;
351
352
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 std::string verPostf = "";
353
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 Json::Value root;
354
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 28 times.
28 if (!CapeUtils::INSTANCE.getStringConfig(m_subType, root)) {
355 // might have the version number on it
356 ✗ if (config["pinoutVersion"].asString() == "2.x") {
357 ✗ verPostf = "-v2";
358 }
359 ✗ if (config["pinoutVersion"].asString() == "3.x") {
360 ✗ verPostf = "-v3";
361 }
362 ✗ if (!CapeUtils::INSTANCE.getStringConfig(m_subType + verPostf, root)) {
363 ✗ LogErr(VB_CHANNELOUT, "Could not read pin configuration for %s%s\n",
364 m_subType.c_str(), verPostf.c_str());
365 ✗ AddOutputConfigWarning(
366 ✗ 13, "BBB48String: Could not read pin configuration for " + m_subType +
367 ✗ verPostf);
368 ✗ return 0;
369 }
370 }
371
372
1/2
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
28 m_licensedOutputs = CapeUtils::INSTANCE.getLicensedOutputs();
373
374 // The mirror of the check in BBShiftString: this driver drives a header
375 // pin per output, so every output the cape declares has to name one. A
376 // shift-register cape's pinout gives a shift stage instead and its
377 // integer "pin" would come back through asString() as a pin name that
378 // matches nothing, leaving every port silently unconfigured.
379
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
28 const int capeOutputCount = root["outputs"].size();
380
2/2
✓ Branch 0 taken 212 times.
✓ Branch 1 taken 27 times.
239 for (int i = 0; i < capeOutputCount; i++) {
381
6/10
✓ Branch 0 taken 212 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 212 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 212 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 212 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 211 times.
✓ Branch 9 taken 1 times.
212 if (!root["outputs"][i]["pin"].isIntegral()) {
382 211 continue;
383 }
384
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 std::string capeDriver = root.get("driver", "BBShiftString").asString();
385
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "Cape %s is not a BBB48String cape - its string configuration gives shift register stages, so it needs the %s output type\n",
386 m_subType.c_str(), capeDriver.c_str());
387
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 taken 1 times.
✗ Branch 7 not taken.
2 AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: " + m_subType + " needs the " + capeDriver +
388
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 " output type - open the Pixel Strings page and save to correct it");
389 1 return 0;
390 1 }
391
392
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 config["base"] = root;
393
394 27 int maxStringLen = 0;
395
4/6
✓ Branch 0 taken 190 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 190 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✓ Branch 5 taken 27 times.
190 for (int i = 0; i < config["outputs"].size(); i++) {
396
3/6
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✗ Branch 5 not taken.
163 Json::Value s = config["outputs"][i];
397
3/8
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 163 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
163 PixelString* newString = new PixelString(true);
398
399
5/10
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 163 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 163 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 163 times.
163 if (!newString->Init(s, &root["outputs"][i]))
400 ✗ return 0;
401
402 // This PRU drives the pins directly and has no per-port idle level, so
403 // an inverted protocol would put inverted data on a line that still
404 // idles low - worse than not supporting it. Undo the inversion and
405 // drive the port as a normal ws281x string. The preamble has to go
406 // with it: those bytes are meaningless to a ws281x part and would
407 // offset every pixel on the port. (Cape declared inversion never
408 // reaches here: no cape using this driver sets it.)
409 // The fill loop here emits one byte per channel, so a 16 bit part
410 // would come out at half length with the wrong bytes. Refuse it and
411 // drive the port 8 bit rather than produce garbage.
412
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
163 if (newString->m_bytesPerChannel != 1) {
413 ✗ LogWarn(VB_CHANNELOUT, "16 bit protocols are not supported on this cape; output %d will be driven as 8 bit\n", i + 1);
414 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: 16 bit protocols are not supported on this cape, output " + std::to_string(i + 1));
415 ✗ int pre = (int)newString->m_preamble.size();
416 ✗ newString->m_outputBytes = (newString->m_outputBytes - pre) / newString->m_bytesPerChannel + pre;
417 ✗ newString->m_bytesPerChannel = 1;
418 ✗ for (auto& vs : newString->m_virtualStrings) {
419 ✗ vs.brightnessMap16 = nullptr;
420 }
421 }
422
423
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
163 if (newString->m_protocolInverted) {
424 ✗ LogWarn(VB_CHANNELOUT, "Inverted protocols are not supported on this cape; output %d will be driven as ws2811\n", i + 1);
425 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: inverted protocols are not supported on this cape, output " + std::to_string(i + 1));
426 ✗ newString->invertOutput();
427 ✗ newString->dropPreamble();
428 ✗ newString->m_protocolInverted = false;
429 }
430
431
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 134 times.
163 if (newString->m_outputBytes > maxStringLen) {
432 29 maxStringLen = newString->m_outputBytes;
433 }
434
435
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 m_strings.push_back(newString);
436
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 }
437
438
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 int retVal = ChannelOutput::Init(config);
439
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 if (retVal == 0) {
440 ✗ return 0;
441 }
442
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
27 if (maxStringLen == 0) {
443 ✗ LogErr(VB_CHANNELOUT, "No pixels configured in any string\n");
444 ✗ return 1;
445 }
446
447 27 std::vector<std::string> args;
448
449 27 std::vector<std::string> split1args;
450
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 split1args.push_back("-DRUNNING_ON_PRU" + std::to_string(BBB_PRU ? 1 : 0));
451 27 std::vector<std::string> split0args;
452
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 split0args.push_back("-DRUNNING_ON_PRU" + std::to_string(BBB_PRU ? 0 : 1));
453
454
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 27 times.
27 if (config.isMember("pixelTiming")) {
455 ✗ int pixelTiming = config["pixelTiming"].asInt();
456 ✗ if (pixelTiming) {
457 ✗ args.push_back("-DPIXELTYPE_SLOW");
458 }
459 }
460
461
1/2
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
27 groupFalconV4Chains();
462
463 27 std::vector<int> allStringMap;
464 27 std::string outputList;
465 27 int allMax = 0;
466
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 27 times.
190 for (int x = 0; x < m_strings.size(); x++) {
467
1/2
✓ Branch 0 taken 163 times.
✗ Branch 1 not taken.
163 bool chainHead = std::find(m_falconChainHeads.begin(), m_falconChainHeads.end(), x) != m_falconChainHeads.end();
468
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 163 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 163 times.
✗ Branch 5 not taken.
163 if (m_strings[x]->m_outputBytes > 0 || chainHead) {
469
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 162 times.
163 if (x >= capeOutputCount) {
470
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "Output %d is past the %d the %s cape declares\n",
471 x + 1, capeOutputCount, m_subType.c_str());
472
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: output " + std::to_string(x + 1) +
473
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 " is past the end of the cape's pinout");
474 1 continue;
475 }
476 // need to output this pin, configure it
477
4/8
✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 162 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 162 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 162 times.
✗ Branch 7 not taken.
162 std::string pinName = root["outputs"][x]["pin"].asString();
478
1/2
✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
162 const PinCapabilities& pin = PinCapabilities::getPinByName(pinName);
479
1/2
✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
162 m_usedGPIOS.push_back(pinName);
480
3/6
✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 162 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 162 times.
✗ Branch 5 not taken.
324 pin.configPin("gpio", true, "BBB48String-" + std::to_string(x + 1));
481
1/2
✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
162 allStringMap.push_back(x);
482
5/6
✓ Branch 0 taken 126 times.
✓ Branch 1 taken 36 times.
✓ Branch 2 taken 126 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 126 times.
✓ Branch 5 taken 36 times.
162 if (x >= m_licensedOutputs && m_strings[x]->m_outputBytes > 0) {
483 // apply limit
484 126 int pixels = 50;
485 126 int chanCount = 0;
486
2/2
✓ Branch 0 taken 126 times.
✓ Branch 1 taken 126 times.
252 for (auto& a : m_strings[x]->m_virtualStrings) {
487
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 126 times.
126 if (pixels < a.pixelCount) {
488 ✗ a.pixelCount = pixels;
489 ✗ if (outputList != "") {
490 ✗ outputList += ", ";
491 }
492 ✗ outputList += std::to_string(x + 1);
493 }
494 126 pixels -= a.pixelCount;
495
1/2
✓ Branch 0 taken 126 times.
✗ Branch 1 not taken.
126 chanCount += a.pixelCount * a.channelsPerNode();
496 }
497
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 122 times.
126 if (m_strings[x]->m_isSmartReceiver) {
498 4 chanCount = 0;
499
3/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 times.
4 if (outputList != "") {
500
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 outputList += ", ";
501 }
502
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 outputList += std::to_string(x + 1);
503 }
504
1/2
✓ Branch 0 taken 126 times.
✗ Branch 1 not taken.
126 m_strings[x]->setPixelDataChannels(chanCount);
505 }
506 162 allMax = std::max(allMax, m_strings[x]->m_outputBytes);
507
3/4
✓ Branch 0 taken 162 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 157 times.
162 if (pin.mappedGPIOIdx() == 0) {
508
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 m_gpio0Data.gpioStringMap.push_back(x);
509 5 m_gpio0Data.maxStringLen =
510 5 std::max(m_gpio0Data.maxStringLen, m_strings[x]->m_outputBytes);
511 } else {
512
1/2
✓ Branch 0 taken 157 times.
✗ Branch 1 not taken.
157 m_gpioData.gpioStringMap.push_back(x);
513 157 m_gpioData.maxStringLen =
514 157 std::max(m_gpioData.maxStringLen, m_strings[x]->m_outputBytes);
515 }
516 162 }
517 }
518
519
3/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 26 times.
27 if (outputList != "") {
520
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string warning = "BBB48String is licensed for ";
521
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 warning += std::to_string(m_licensedOutputs);
522 warning += " outputs, but one or more outputs is configured for more than "
523
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 "50 pixels. Output(s): ";
524
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 warning += outputList;
525
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 AddOutputConfigWarning(UNKNOWN_WARNING_ID, warning);
526
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_CHANNELOUT, "WARNING: %s\n", warning.c_str());
527 1 }
528
529
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 26 times.
27 if (m_gpioData.gpioStringMap.empty()) {
530
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_gpioData.gpioStringMap = m_gpio0Data.gpioStringMap;
531 1 m_gpioData.maxStringLen = m_gpio0Data.maxStringLen;
532 1 m_gpio0Data.maxStringLen = 0;
533 1 m_gpio0Data.gpioStringMap.clear();
534 }
535 27 bool hasSerial = true;
536
3/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 23 times.
27 if (config.isMember("serialInUse")) {
537
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 hasSerial = config["serialInUse"].asBool();
538 }
539
540
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 4 times.
27 if (!m_gpio0Data.maxStringLen) {
541 // no GPIO0 output so no need for the second PRU to be used
542 23 hasSerial = true;
543 }
544 27 bool canSplit = !hasSerial;
545
3/4
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
27 if (hasSerial && m_gpio0Data.maxStringLen &&
546 ✗ ((m_gpio0Data.maxStringLen + m_gpioData.maxStringLen) < 2000)) {
547 // if there is plenty of time to output the GPIO0 stuff
548 // after the other GPIO's, let's do that
549 ✗ args.push_back("-DSPLIT_GPIO0");
550 ✗ args.push_back(
551 ✗ "-DGPIO0OUTPUTS=" +
552 ✗ std::to_string(mapSize(48, m_gpio0Data.gpioStringMap.size())));
553
554 ✗ m_gpio0Data.gpioStringMap.insert(m_gpio0Data.gpioStringMap.end(),
555 m_gpioData.gpioStringMap.begin(),
556 m_gpioData.gpioStringMap.end());
557 ✗ m_gpioData.gpioStringMap = m_gpio0Data.gpioStringMap;
558 ✗ m_gpio0Data.gpioStringMap.clear();
559 ✗ m_gpioData.maxStringLen =
560 ✗ std::max(m_gpioData.maxStringLen, m_gpio0Data.maxStringLen);
561 ✗ m_gpio0Data.maxStringLen = 0;
562 ✗ canSplit = false;
563 }
564
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 4 times.
27 if (!canSplit) {
565
1/2
✓ Branch 0 taken 23 times.
✗ Branch 1 not taken.
23 m_gpioData.gpioStringMap = allStringMap;
566 23 m_gpioData.maxStringLen = allMax;
567 23 m_gpio0Data.gpioStringMap.clear();
568 }
569
570
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 int i = mapSize(root["outputs"].size(), m_gpioData.gpioStringMap.size());
571
2/2
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 27 times.
83 while (m_gpioData.gpioStringMap.size() < i) {
572
1/2
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
56 m_gpioData.gpioStringMap.push_back(-1);
573 }
574
2/4
✓ Branch 0 taken 27 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
✗ Branch 3 not taken.
27 i = mapSize(root["outputs"].size(), m_gpio0Data.gpioStringMap.size());
575
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 27 times.
39 while (m_gpio0Data.gpioStringMap.size() < i) {
576
1/2
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
12 m_gpio0Data.gpioStringMap.push_back(-1);
577 }
578 // The firmware loads one byte per output into r10-r21; past 48 the load
579 // runs on into the GPIO bank address registers and every later GPIO
580 // store goes to an address made of pixel data.
581
5/6
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 25 times.
27 if (m_gpioData.gpioStringMap.size() > 48 || m_gpio0Data.gpioStringMap.size() > 48) {
582
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogErr(VB_CHANNELOUT, "%s uses %d outputs on one PRU, more than the 48 the firmware supports\n",
583 m_subType.c_str(), (int)std::max(m_gpioData.gpioStringMap.size(), m_gpio0Data.gpioStringMap.size()));
584
3/6
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: " + m_subType + " uses more than 48 outputs on one PRU");
585 2 return 0;
586 }
587
588
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 setupFalconV4Support(root, split1args, split0args);
589
590
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 std::string v = "-DOUTPUTS=";
591
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
25 v += std::to_string(m_gpioData.gpioStringMap.size());
592
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 split1args.push_back(v);
593
594
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 std::ofstream outputFile;
595
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 outputFile.open("/tmp/PinConfiguration.hp",
596 std::ofstream::out | std::ofstream::trunc);
597
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 25 times.
141 for (int x = 0; x < m_gpioData.gpioStringMap.size(); x++) {
598 116 int idx = m_gpioData.gpioStringMap[x];
599
2/2
✓ Branch 0 taken 60 times.
✓ Branch 1 taken 56 times.
116 if (idx >= 0) {
600 const PinCapabilities& pin =
601
5/10
✓ 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.
✓ Branch 8 taken 60 times.
✗ Branch 9 not taken.
60 PinCapabilities::getPinByName(root["outputs"][idx]["pin"].asString());
602 ✗ outputFile << "#define o" << std::to_string(x + 1) << "_gpio "
603
6/12
✓ 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.
✓ Branch 8 taken 60 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 60 times.
✗ Branch 11 not taken.
60 << std::to_string(pin.mappedGPIOIdx()) << "\n";
604 60 outputFile << "#define o" << std::to_string(x + 1) << "_pin "
605
6/12
✓ 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.
✓ Branch 8 taken 60 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 60 times.
✗ Branch 11 not taken.
60 << std::to_string(pin.mappedGPIO()) << "\n\n";
606 } else {
607
2/4
✓ Branch 0 taken 56 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
56 split1args.push_back("-DNOOUT" + std::to_string(x + 1));
608 }
609 }
610
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 outputFile.close();
611
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
25 if (m_gpio0Data.gpioStringMap.size() > 0) {
612
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 split1args.push_back("-DDDRONLY");
613 4 m_gpioData.copyToPru = false;
614 4 m_gpio0Data.copyToPru = true;
615
616
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 std::string v = "-DOUTPUTS=";
617
1/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4 v += std::to_string(m_gpio0Data.gpioStringMap.size());
618
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 split0args.push_back(v);
619
620
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 outputFile.open("/tmp/PinConfigurationGPIO0.hp",
621 std::ofstream::out | std::ofstream::trunc);
622
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
20 for (int x = 0; x < m_gpio0Data.gpioStringMap.size(); x++) {
623 16 int idx = m_gpio0Data.gpioStringMap[x];
624
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
16 if (idx >= 0) {
625
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 const PinCapabilities& pin = PinCapabilities::getPinByName(
626
4/8
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
8 root["outputs"][idx]["pin"].asString());
627 ✗ outputFile << "#define o" << std::to_string(x + 1) << "_gpio "
628
6/12
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
4 << std::to_string(pin.mappedGPIOIdx()) << "\n";
629 4 outputFile << "#define o" << std::to_string(x + 1) << "_pin "
630
6/12
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 4 times.
✗ Branch 11 not taken.
4 << std::to_string(pin.mappedGPIO()) << "\n\n";
631 } else {
632
2/4
✓ Branch 0 taken 12 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
✗ Branch 3 not taken.
12 split0args.push_back("-DNOOUT" + std::to_string(x + 1));
633 }
634 }
635
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 outputFile.close();
636 4 }
637
638 #ifdef PRINT_STATS
639 args.push_back("-DRECORD_STATS");
640 #endif
641
642
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
50 createOutputLengths(m_gpioData, "", split1args);
643
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
50 createOutputLengths(m_gpio0Data, "GPIO0_", split0args);
644
645
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 compilePRUCode(args, split0args, split1args, !hasSerial);
646
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 25 times.
25 if (!StartPRU(!hasSerial)) {
647 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: Could not start PRU");
648 ✗ return 0;
649 }
650
651 // give each area two chunks (frame flipping) of DDR memory, from the
652 // shared region allocator so other outputs on the region (BBBMatrix
653 // panels, BBBSerial - e.g. the K8-B-Scroller runs all three) cannot
654 // overlap us
655 // each with room for the Falcon V4 config packet appended after the
656 // frame data
657 28 m_gpioData.frameSize =
658 25 m_gpioData.gpioStringMap.size() * std::max(2400, m_gpioData.maxStringLen);
659 25 m_gpioData.frameSize += m_gpioData.falconPacketLanes.size();
660 // leave a full memory page between to avoid conflicts
661 25 int offset = ((m_gpioData.frameSize / 4096) + 2) * 4096;
662 25 m_gpio0Data.frameSize = m_gpio0Data.gpioStringMap.size() *
663 25 std::max(2400, m_gpio0Data.maxStringLen);
664 25 m_gpio0Data.frameSize += m_gpio0Data.falconPacketLanes.size();
665 25 int offset0 = ((m_gpio0Data.frameSize / 4096) + 2) * 4096;
666 25 uint32_t ddrPhys = 0;
667
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
25 uint8_t* ddrBase = BBBPru::ddrAlloc("BBB48String", 2 * offset + 2 * offset0, ddrPhys);
668
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 if (!ddrBase) {
669 ✗ LogErr(VB_CHANNELOUT, "BBB48String: no room in the PRU DDR region\n");
670 ✗ AddOutputConfigWarning(20, "BBB48String: no room in the PRU DDR region - reduce string lengths or other outputs' buffers");
671 ✗ return 0;
672 }
673 25 m_gpioData.curData = ddrBase;
674 25 m_gpioData.lastData = m_gpioData.curData + offset;
675
676 25 m_gpio0Data.curData = m_gpioData.lastData + offset;
677 25 m_gpio0Data.lastData = m_gpio0Data.curData + offset0;
678
679 // flag the virtual strings whose channel map is a plain run so prepData
680 // can walk the channel data directly instead of through the map
681
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 m_vsAffine.resize(m_strings.size());
682
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 25 times.
90 for (size_t s = 0; s < m_strings.size(); s++) {
683
1/2
✓ Branch 0 taken 65 times.
✗ Branch 1 not taken.
65 m_vsAffine[s].resize(m_strings[s]->m_virtualStrings.size());
684
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 65 times.
130 for (size_t v = 0; v < m_strings[s]->m_virtualStrings.size(); v++) {
685 65 auto& vs = m_strings[s]->m_virtualStrings[v];
686 65 bool affine = true;
687
2/2
✓ Branch 0 taken 1854 times.
✓ Branch 1 taken 65 times.
1919 for (int i = 0; i + 3 < vs.chMapCount; i++) {
688
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1854 times.
1854 if (vs.chMap[i + 3] != vs.chMap[i] + 3) {
689 ✗ affine = false;
690 ✗ break;
691 }
692 }
693 65 m_vsAffine[s][v] = affine;
694 }
695 }
696
697
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 PixelString::AutoCreateOverlayModels(m_strings, m_autoCreatedModelNames);
698 25 return retVal;
699 28 }
700
701 25 int BBB48StringOutput::StartPRU(bool both) {
702 25 m_curFrame = 0;
703 25 int pruNumber = BBB_PRU;
704
705 // The firmware is assembled per configuration just before this, so a
706 // missing program is a build failure (the assembler's errors are in the
707 // fppd output), not a PRU that needs a reboot. Like the others here it
708 // is a config warning: it comes down when the outputs are next loaded.
709
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
50 std::vector<std::string> programs = { "/tmp/FalconWS281x.out" };
710
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
25 if (both) {
711
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
8 programs.push_back("/tmp/FalconWS281x_gpio0.out");
712 }
713
2/2
✓ Branch 0 taken 29 times.
✓ Branch 1 taken 25 times.
54 for (auto& fw : programs) {
714
2/4
✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
29 if (!FileExists(fw)) {
715 ✗ LogErr(VB_CHANNELOUT, "BBB48String: PRU firmware %s failed to build\n", fw.c_str());
716 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBB48String: PRU firmware failed to build for this configuration");
717 ✗ return 0;
718 }
719 }
720 // run() is told not to clear PRU memory. The firmware zeroes its own
721 // control block (address, command, response) before it first reads the
722 // command, and reads nothing else until a non-zero command arrives.
723 // SendData() writes that command only after the frame's address, and on
724 // the first frame a full copy of its pixel data into the PRU memories, so
725 // a stale word left from an earlier run can neither start output nor be
726 // sent as pixel data. Skipping the clear also keeps this from touching
727 // the PRUSS while both cores are offline (crash e80ad9caf7).
728
3/8
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 25 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
25 m_pru = new BBBPru(pruNumber, true, true);
729 25 m_pruData = (BBB48StringData*)m_pru->data_ram;
730
3/6
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 25 times.
75 if (!m_pru->run("/tmp/FalconWS281x.out", true, false)) {
731 ✗ LogErr(VB_CHANNELOUT,
732 "BBB48String: Unable to start PRU. May require a reboot.\n");
733 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID,
734 "BBB48String: Unable to start PRU. May require a reboot.");
735 ✗ return 0;
736 }
737 25 m_pruData->command = 0;
738 25 m_pruData->address_dma = m_pru->ddr_addr;
739
740
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
25 if (both) {
741
3/8
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
4 m_pru0 = new BBBPru(!pruNumber, true, true);
742 4 m_pru0Data = (BBB48StringData*)m_pru0->data_ram;
743
3/6
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 4 times.
12 if (!m_pru0->run("/tmp/FalconWS281x_gpio0.out", true, false)) {
744 ✗ LogErr(VB_CHANNELOUT,
745 "BBB48String: Unable to start PRU0. May require a reboot.\n");
746 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID,
747 "BBB48String: Unable to start PRU0. May require a reboot.");
748 ✗ return 0;
749 }
750 4 m_pru0Data->command = 0;
751 4 m_pru0Data->address_dma = m_pru0->ddr_addr;
752 }
753
754 25 return 1;
755 25 }
756 25 void BBB48StringOutput::StopPRU(bool wait) {
757 // Send the stop command
758 25 m_pruData->response = 0;
759 25 m_pruData->command = 0xFFFF;
760
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
25 if (m_pru0Data) {
761 4 m_pru0Data->response = 0;
762 4 m_pru0Data->command = 0xFFFF;
763 }
764 25 __asm__ __volatile__("" ::: "memory");
765
766 // Wait for BOTH cores to acknowledge before stopping EITHER. Stopping a
767 // core through remoteproc can leave the PRUSS data RAM unreachable, and a
768 // read of it then takes an external abort (SIGBUS) rather than returning
769 // a value - which is what happened when PRU0's response was polled after
770 // PRU1 had already been stopped. So every access to PRU RAM here finishes
771 // while both cores are still up, and nothing touches it afterwards.
772 25 int cnt = 0;
773
6/8
✓ Branch 0 taken 650 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 625 times.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 625 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 625 times.
✓ Branch 7 taken 25 times.
650 while (wait && cnt < 25 && m_pruData->response != 0xFFFF) {
774
1/2
✓ Branch 0 taken 625 times.
✗ Branch 1 not taken.
625 std::this_thread::sleep_for(std::chrono::milliseconds(1));
775 625 cnt++;
776 625 __asm__ __volatile__("" ::: "memory");
777 }
778
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
25 if (m_pru0) {
779 4 cnt = 0;
780
6/8
✓ Branch 0 taken 104 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 100 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 100 times.
✓ Branch 7 taken 4 times.
104 while (wait && cnt < 25 && m_pru0Data->response != 0xFFFF) {
781
1/2
✓ Branch 0 taken 100 times.
✗ Branch 1 not taken.
100 std::this_thread::sleep_for(std::chrono::milliseconds(1));
782 100 cnt++;
783 100 __asm__ __volatile__("" ::: "memory");
784 }
785 }
786
787 25 m_pru->stop();
788
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25 delete m_pru;
789
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
25 if (m_pru0) {
790 4 m_pru0->stop();
791
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 delete m_pru0;
792 }
793 25 m_pru = NULL;
794 25 m_pru0 = NULL;
795 25 }
796 /*
797 *
798 */
799 25 int BBB48StringOutput::Close(void) {
800 25 LogDebug(VB_CHANNELOUT, "BBB48StringOutput::Close()\n");
801
2/4
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
✗ Branch 3 not taken.
25 BBBPru::ddrRelease("BBB48String");
802
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 25 times.
89 for (auto& n : m_autoCreatedModelNames) {
803
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 PixelOverlayManager::INSTANCE.removeAutoOverlayModel(n);
804 }
805
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 25 times.
✗ Branch 5 not taken.
25 if (!m_gpioData.gpioStringMap.empty() || !m_gpio0Data.gpioStringMap.empty()) {
806 25 StopPRU();
807 }
808 25 return ChannelOutput::Close();
809 }
810
811 50 void BBB48StringOutput::GetRequiredChannelRanges(
812 const std::function<void(int, int)>& addRange) {
813 50 PixelString* ps = NULL;
814
2/2
✓ Branch 0 taken 130 times.
✓ Branch 1 taken 50 times.
180 for (int s = 0; s < m_strings.size(); s++) {
815 130 ps = m_strings[s];
816 130 int inCh = 0;
817 130 int min = FPPD_MAX_CHANNELS;
818 130 int max = -1;
819
2/2
✓ Branch 0 taken 3810 times.
✓ Branch 1 taken 130 times.
3940 for (int p = 0; p < ps->m_outputBytes; p++) {
820 3810 int ch = ps->m_outputMap[inCh++];
821
1/2
✓ Branch 0 taken 3810 times.
✗ Branch 1 not taken.
3810 if (ch < FPPD_MAX_CHANNELS) {
822 3810 min = std::min(min, ch);
823 3810 max = std::max(max, ch);
824 }
825 }
826
2/2
✓ Branch 0 taken 122 times.
✓ Branch 1 taken 8 times.
130 if (min < max) {
827
1/2
✓ Branch 0 taken 122 times.
✗ Branch 1 not taken.
122 addRange(min, max);
828 }
829 }
830 50 }
831 ✗ void BBB48StringOutput::OverlayTestData(unsigned char* channelData,
832 int cycleNum, float percentOfCycle,
833 int testType,
834 const Json::Value& config) {
835 ✗ m_testCycle = cycleNum;
836 ✗ m_testType = testType;
837 ✗ m_testPercent = percentOfCycle;
838
839 // We won't overlay the data here because we could have multiple strings
840 // pointing at the same channel range so a per-port test cannot
841 // be done via channel ranges. We'll record the test information and use
842 // that in prepData
843 ✗ }
844
845 // 8x8 byte transpose: in, rows are 8 consecutive bytes of 8 strings; out,
846 // rows are the 8 strings' bytes for 8 consecutive positions. (Same helper
847 // as BBShiftString.)
848 54 static inline void transpose8x8(uint8x8_t r[8]) {
849 54 uint8x8x2_t t0 = vtrn_u8(r[0], r[1]);
850 54 uint8x8x2_t t1 = vtrn_u8(r[2], r[3]);
851 54 uint8x8x2_t t2 = vtrn_u8(r[4], r[5]);
852 54 uint8x8x2_t t3 = vtrn_u8(r[6], r[7]);
853 162 uint16x4x2_t s0 = vtrn_u16(vreinterpret_u16_u8(t0.val[0]), vreinterpret_u16_u8(t1.val[0]));
854 162 uint16x4x2_t s1 = vtrn_u16(vreinterpret_u16_u8(t0.val[1]), vreinterpret_u16_u8(t1.val[1]));
855 162 uint16x4x2_t s2 = vtrn_u16(vreinterpret_u16_u8(t2.val[0]), vreinterpret_u16_u8(t3.val[0]));
856 162 uint16x4x2_t s3 = vtrn_u16(vreinterpret_u16_u8(t2.val[1]), vreinterpret_u16_u8(t3.val[1]));
857 162 uint32x2x2_t u0 = vtrn_u32(vreinterpret_u32_u16(s0.val[0]), vreinterpret_u32_u16(s2.val[0]));
858 162 uint32x2x2_t u1 = vtrn_u32(vreinterpret_u32_u16(s1.val[0]), vreinterpret_u32_u16(s3.val[0]));
859 162 uint32x2x2_t u2 = vtrn_u32(vreinterpret_u32_u16(s0.val[1]), vreinterpret_u32_u16(s2.val[1]));
860 162 uint32x2x2_t u3 = vtrn_u32(vreinterpret_u32_u16(s1.val[1]), vreinterpret_u32_u16(s3.val[1]));
861 54 r[0] = vreinterpret_u8_u32(u0.val[0]);
862 54 r[1] = vreinterpret_u8_u32(u1.val[0]);
863 54 r[2] = vreinterpret_u8_u32(u2.val[0]);
864 54 r[3] = vreinterpret_u8_u32(u3.val[0]);
865 54 r[4] = vreinterpret_u8_u32(u0.val[1]);
866 54 r[5] = vreinterpret_u8_u32(u1.val[1]);
867 54 r[6] = vreinterpret_u8_u32(u2.val[1]);
868 54 r[7] = vreinterpret_u8_u32(u3.val[1]);
869 54 }
870
871 1024 void BBB48StringOutput::prepData(FrameData& d, unsigned char* channelData) {
872 1024 PixelStringTester* tester = nullptr;
873
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1024 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1024 if (m_testType && m_testCycle >= 0) {
874 ✗ tester = PixelStringTester::getPixelStringTester(m_testType);
875 // null for a test type no string tester implements; the strings then
876 // just send their normal data
877 ✗ if (tester) {
878 ✗ tester->prepareTestData(m_testCycle, m_testPercent);
879 }
880 }
881 1024 const int numStrings = d.gpioStringMap.size();
882 // per string slot: either a fully prepared buffer (test mode) or the
883 // PixelString whose virtual strings are rendered on the fly per tile,
884 // with a cursor tracking where in the virtual string list the next tile
885 // continues. (Same scheme as BBShiftString::prepData.)
886 struct SlotSrc {
887 const uint8_t* buf = nullptr;
888 PixelString* ps = nullptr;
889 const uint8_t* affine = nullptr;
890 uint32_t len = 0;
891 uint32_t vsIdx = 0;
892 uint32_t vsOff = 0;
893 };
894
1/2
✓ Branch 0 taken 1024 times.
✗ Branch 1 not taken.
2048 std::vector<SlotSrc> slots(numStrings);
895 1024 uint32_t newMaxLen = 0;
896
2/2
✓ Branch 0 taken 2240 times.
✓ Branch 1 taken 1024 times.
3264 for (int s = 0; s < numStrings; s++) {
897 2240 int idx = d.gpioStringMap[s];
898
2/2
✓ Branch 0 taken 1120 times.
✓ Branch 1 taken 1120 times.
2240 if (idx >= 0) {
899 1120 PixelString* ps = m_strings[idx];
900 1120 uint32_t newLen = ps->m_outputBytes;
901 1120 SlotSrc& sl = slots[s];
902
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1120 times.
1120 if (tester) {
903 ✗ sl.buf = tester->createTestData(ps, m_testCycle, m_testPercent,
904 channelData, newLen);
905 } else {
906 1120 sl.ps = ps;
907 1120 sl.affine = m_vsAffine[idx].data();
908 }
909 1120 sl.len = newLen;
910 1120 newMaxLen = std::max(newLen, newMaxLen);
911 }
912 }
913 1024 d.outputStringLen = newMaxLen;
914
915 // Interleave the string data in tiles that stay in the L1 cache instead
916 // of writing a byte every numStrings bytes across the whole frame, with
917 // the brightness/gamma application fused into the tile fill. Virtual
918 // strings whose channel map is a simple run skip the per-channel map
919 // indirection. Unmapped slots and the tail of strings shorter than the
920 // longest now output zeros instead of stale buffer content.
921 1024 uint8_t* out = d.curData;
922 1024 constexpr uint32_t TILE = 64;
923 uint8_t col[8][TILE];
924
2/2
✓ Branch 0 taken 536 times.
✓ Branch 1 taken 1024 times.
1560 for (uint32_t p0 = 0; p0 < newMaxLen; p0 += TILE) {
925 536 const uint32_t n = std::min(TILE, newMaxLen - p0);
926 536 const uint32_t nFull = n & ~7;
927
2/2
✓ Branch 0 taken 542 times.
✓ Branch 1 taken 536 times.
1078 for (int s0 = 0; s0 < numStrings; s0 += 8) {
928 542 const int ns = std::min(8, numStrings - s0);
929
2/2
✓ Branch 0 taken 2240 times.
✓ Branch 1 taken 542 times.
2782 for (int x = 0; x < ns; ++x) {
930 2240 SlotSrc& sl = slots[s0 + x];
931
2/2
✓ Branch 0 taken 1096 times.
✓ Branch 1 taken 1144 times.
2240 uint32_t avail = sl.len > p0 ? std::min(n, sl.len - p0) : 0;
932 2240 uint32_t p = 0;
933
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2240 times.
2240 if (sl.buf) {
934 ✗ memcpy(col[x], sl.buf + p0, avail);
935 ✗ p = avail;
936
2/2
✓ Branch 0 taken 1120 times.
✓ Branch 1 taken 1120 times.
2240 } else if (sl.ps) {
937 1120 auto& vstrings = sl.ps->m_virtualStrings;
938
5/6
✓ Branch 0 taken 1096 times.
✓ Branch 1 taken 1120 times.
✓ Branch 2 taken 1096 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1096 times.
✓ Branch 5 taken 1120 times.
2216 while (p < avail && sl.vsIdx < vstrings.size()) {
939 1096 auto& vs = vstrings[sl.vsIdx];
940 1096 uint32_t m = std::min(avail - p, (uint32_t)vs.chMapCount - sl.vsOff);
941 1096 const uint8_t* br = vs.brightnessMap;
942 1096 const int* mp = vs.chMap;
943
1/2
✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
1096 if (sl.affine[sl.vsIdx]) {
944 1096 uint32_t c = sl.vsOff % 3;
945 1096 int32_t base = (int32_t)(sl.vsOff - c);
946
2/2
✓ Branch 0 taken 36900 times.
✓ Branch 1 taken 1096 times.
37996 for (uint32_t k = 0; k < m; ++k) {
947 36900 col[x][p++] = br[channelData[mp[c] + base]];
948
2/2
✓ Branch 0 taken 12300 times.
✓ Branch 1 taken 24600 times.
36900 if (++c == 3) {
949 12300 c = 0;
950 12300 base += 3;
951 }
952 }
953 } else {
954 ✗ const int* mo = mp + sl.vsOff;
955 ✗ for (uint32_t k = 0; k < m; ++k) {
956 ✗ col[x][p++] = br[channelData[mo[k]]];
957 }
958 }
959 1096 sl.vsOff += m;
960
1/2
✓ Branch 0 taken 1096 times.
✗ Branch 1 not taken.
1096 if (sl.vsOff >= (uint32_t)vs.chMapCount) {
961 1096 sl.vsOff = 0;
962 1096 sl.vsIdx++;
963 }
964 }
965 }
966
2/2
✓ Branch 0 taken 1290 times.
✓ Branch 1 taken 950 times.
2240 if (p < n) {
967 1290 memset(&col[x][p], 0, n - p);
968 }
969 }
970 542 uint32_t g = 0;
971
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 524 times.
542 if (ns == 8) {
972
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 18 times.
72 for (; g < nFull; g += 8) {
973 uint8x8_t r[8];
974
2/2
✓ Branch 0 taken 432 times.
✓ Branch 1 taken 54 times.
486 for (int x = 0; x < 8; ++x) {
975
1/2
✓ Branch 0 taken 432 times.
✗ Branch 1 not taken.
864 r[x] = vld1_u8(&col[x][g]);
976 }
977 54 transpose8x8(r);
978
2/2
✓ Branch 0 taken 432 times.
✓ Branch 1 taken 54 times.
486 for (int i = 0; i < 8; i++) {
979
1/2
✓ Branch 0 taken 432 times.
✗ Branch 1 not taken.
432 vst1_u8(&out[(size_t)(p0 + g + i) * numStrings + s0], r[i]);
980 }
981 }
982 }
983
2/2
✓ Branch 0 taken 19902 times.
✓ Branch 1 taken 542 times.
20444 for (uint32_t p = g; p < n; ++p) {
984
2/2
✓ Branch 0 taken 79896 times.
✓ Branch 1 taken 19902 times.
99798 for (int x = 0; x < ns; ++x) {
985 79896 out[(size_t)(p0 + p) * numStrings + s0 + x] = col[x][p];
986 }
987 }
988 }
989 }
990 1024 }
991
992 /*
993 *
994 */
995 512 void BBB48StringOutput::PrepData(unsigned char* channelData) {
996 512 LogExcess(VB_CHANNELOUT, "BBB48StringOutput::PrepData(%p)\n", channelData);
997
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 512 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 512 times.
512 if (m_gpioData.gpioStringMap.empty() && m_gpio0Data.gpioStringMap.empty()) {
998 ✗ return;
999 }
1000
1001 512 m_curFrame++;
1002
1003 #ifdef PRINT_STATS
1004 int max = 0;
1005 for (int x = 0; x < MAX_WS2811_TIMINGS; x++) {
1006 if (max < m_pruData->timings[x]) {
1007 max = m_pruData->timings[x];
1008 }
1009 }
1010 if (max > 300 || (m_curFrame % 10) == 1) {
1011 for (int x = 0; x < MAX_WS2811_TIMINGS;) {
1012 printf("%8X ", m_pruData->timings[x]);
1013 ++x;
1014 if ((x) % 16 == 0) {
1015 printf("\n");
1016 }
1017 }
1018 printf("\n%d: max %d\n", m_curFrame, max);
1019 }
1020 #endif
1021 512 prepData(m_gpioData, channelData);
1022 512 prepData(m_gpio0Data, channelData);
1023 512 m_testCycle = -1;
1024
1025
6/6
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 500 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 510 times.
512 if (!m_falconChainHeads.empty() && (m_curFrame % 12) == 1) {
1026 // append the config packet after the frame data; the firmware sends
1027 // it when the command has the packet bit set
1028
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 for (FrameData* d : { &m_gpioData, &m_gpio0Data }) {
1029
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (!d->falconPacketLanes.empty()) {
1030 4 memcpy(d->curData + d->outputStringLen * d->gpioStringMap.size(),
1031 2 d->falconPacketLanes.data(), d->falconPacketLanes.size());
1032 }
1033 }
1034 2 m_sendFalconPacket = true;
1035 }
1036 }
1037
1038 486 void BBB48StringOutput::sendData(FrameData& d, uint32_t* dptr) {
1039 486 bool doSwap = false;
1040 // compare everything the copy below would place in PRU memory - the
1041 // firmware reads those bytes from there rather than DDR, so a change
1042 // past the compared range would leave it sending the old data
1043 486 uint32_t pruCopySize = std::min(d.frameSize, (uint32_t)(7624 + 7624 + m_pru->shared_ram_size));
1044
2/2
✓ Branch 0 taken 470 times.
✓ Branch 1 taken 16 times.
486 if (d.copyToPru &&
1045
4/4
✓ Branch 0 taken 449 times.
✓ Branch 1 taken 21 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 423 times.
470 (m_curFrame == 1 || memcmp(d.lastData, d.curData, pruCopySize))) {
1046 // don't copy to PRU memory unless really needed to avoid bus contention
1047
1048 // copy first 7.5K into PRU mem directly
1049 47 int fullsize = d.frameSize;
1050 47 int mx = d.frameSize;
1051
1/2
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
47 if (mx > (8 * 1024 - 512)) {
1052 47 mx = 8 * 1024 - 512;
1053 }
1054
1055 // first 7.5K to main PRU ram
1056 47 m_pru->memcpyToPRU(m_pru->data_ram + 512, d.curData, mx);
1057 47 fullsize -= 7624;
1058
1/2
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
47 if (fullsize > 0) {
1059 47 int outsize = fullsize;
1060
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 38 times.
47 if (outsize > (8 * 1024 - 512)) {
1061 9 outsize = 8 * 1024 - 512;
1062 }
1063 // second 7.5K to other PRU ram
1064 47 m_pru->memcpyToPRU(m_pru->other_data_ram + 512, d.curData + 7624,
1065 outsize);
1066 47 fullsize -= 7624;
1067 }
1068
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 38 times.
47 if (fullsize > 0) {
1069 9 int outsize = fullsize;
1070
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 if (outsize > m_pru->shared_ram_size) {
1071 1 outsize = m_pru->shared_ram_size;
1072 }
1073 9 m_pru->memcpyToPRU(m_pru->shared_ram, d.curData + 7624 + 7624, outsize);
1074 }
1075 }
1076
1077 // Map
1078 486 size_t offset = d.curData - m_pru->ddr;
1079 486 *dptr = (m_pru->ddr_addr + offset);
1080 486 std::swap(d.lastData, d.curData);
1081 486 }
1082
1083 470 int BBB48StringOutput::SendData(unsigned char* channelData) {
1084 470 LogExcess(VB_CHANNELOUT, "BBB48StringOutput::SendData(%p)\n", channelData);
1085
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 470 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 470 times.
470 if (m_gpioData.gpioStringMap.empty() && m_gpio0Data.gpioStringMap.empty()) {
1086 ✗ return 0;
1087 }
1088
1089 470 sendData(m_gpioData, &m_pruData->address_dma);
1090
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 454 times.
470 if (m_pru0Data) {
1091 16 sendData(m_gpio0Data, &m_pru0Data->address_dma);
1092 }
1093
1094 // make sure memory is flushed before command is set to 1
1095 470 __asm__ __volatile__("" ::: "memory");
1096
1097 // Send the start command
1098 470 uint32_t flag = 0;
1099
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 470 times.
470 if (m_testType == 999) {
1100 // pixel counting needs the GPIO commands and such turned off
1101 ✗ flag = 0x8000;
1102 }
1103 // bit 16: a Falcon V4 config packet follows the frame data, for the
1104 // PRUs that drive a chain head
1105 470 uint32_t flag0 = flag;
1106
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 468 times.
470 if (m_sendFalconPacket) {
1107
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!m_gpioData.falconPacketLanes.empty()) {
1108 2 flag |= 0x10000;
1109 }
1110
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!m_gpio0Data.falconPacketLanes.empty()) {
1111 ✗ flag0 |= 0x10000;
1112 }
1113 2 m_sendFalconPacket = false;
1114 }
1115
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 454 times.
470 if (m_pru0Data) {
1116 16 m_pru0Data->command = m_gpio0Data.outputStringLen | flag0;
1117 }
1118 470 m_pruData->command = m_gpioData.outputStringLen | flag;
1119
1120 470 return m_channelCount;
1121 }
1122
1123 /*
1124 *
1125 */
1126 27 void BBB48StringOutput::DumpConfig(void) {
1127 27 LogDebug(VB_CHANNELOUT, "BBB48StringOutput::DumpConfig()\n");
1128 27 LogDebug(VB_CHANNELOUT, " type : %s\n", m_subType.c_str());
1129 27 LogDebug(VB_CHANNELOUT, " strings : %d\n", m_strings.size());
1130 27 LogDebug(VB_CHANNELOUT, " longest string: %d channels\n",
1131 std::max(m_gpio0Data.maxStringLen, m_gpioData.maxStringLen));
1132
1133
2/2
✓ Branch 0 taken 163 times.
✓ Branch 1 taken 27 times.
190 for (int i = 0; i < m_strings.size(); i++) {
1134 163 LogDebug(VB_CHANNELOUT, " string #%02d\n", i);
1135 163 m_strings[i]->DumpConfig();
1136 }
1137
1138 27 ChannelOutput::DumpConfig();
1139 27 }
1140