GCC Code Coverage Report


Directory: ./
File: non-gpl/BBShiftString/BBShiftString.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 887 1075 82.5%
Functions: 49 55 89.1%
Branches: 793 1478 53.7%

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 <pthread.h>
19 #include <sched.h>
20 #include <sys/mman.h>
21 #include <sys/wait.h>
22 #include <time.h>
23 #include <unistd.h>
24
25 #include <sys/wait.h>
26 #include <arm_neon.h>
27 #include <cstring>
28 #include <tuple>
29
30 #include <chrono>
31
32 // FPP includes
33 #include "../../Sequence.h"
34 #include "../../channeloutput/channeloutputthread.h"
35 #include "../../Warnings.h"
36 #include "../../channeloutput/ChannelOutputSetup.h"
37 #include "../../common.h"
38 #include "../../log.h"
39
40 #include "BBShiftString.h"
41 #include "../CapeUtils/CapeUtils.h"
42 #include "../../pru/SMEMRing.hp"
43
44 #include "../../overlays/PixelOverlay.h"
45
46 #include "channeloutput/stringtesters/PixelStringTester.h"
47 #include "util/BBBUtils.h"
48
49 #include "Plugin.h"
50 class BBShiftStringPlugin : public FPPPlugins::Plugin, public FPPPlugins::ChannelOutputPlugin {
51 public:
52 28 BBShiftStringPlugin() :
53
2/4
✓ Branch 0 taken 28 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
84 FPPPlugins::Plugin("BBShiftString") {
54 28 }
55 50 virtual ChannelOutput* createChannelOutput(unsigned int startChannel, unsigned int channelCount) override {
56
2/6
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 50 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
100 return new BBShiftStringOutput(startChannel, channelCount);
57 }
58 };
59
60 extern "C" {
61 28 FPPPlugins::Plugin* createPlugin() {
62
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 BBShiftStringPlugin();
63 }
64 }
65
66 #ifdef PLATFORM_BBB
67 static const std::vector<std::string> PRU0_DATA_PINS = { "P9-31", "P9-29", "P9-30", "P9-28", "P9-92", "P9-27", "P9-91", "P9-25" };
68 static const std::vector<std::string> PRU0_CTRL_PINS = { "P8-12", "P8-11" };
69 static const std::vector<std::string> PRU1_DATA_PINS = { "P8-45", "P8-46", "P8-43", "P8-44", "P8-41", "P8-42", "P8-39", "P8-40" };
70 static const std::vector<std::string> PRU1_CTRL_PINS = { "P8-28", "P8-30" };
71 static const std::string PRU1_ENABLE_PIN = "P8-27";
72 #else
73 static const std::vector<std::string> PRU0_DATA_PINS = {};
74 static const std::vector<std::string> PRU0_CTRL_PINS = {};
75 static const std::vector<std::string> PRU1_DATA_PINS = { "P2-02", "P2-04", "P2-06", "P2-08", "P2-20", "P1-20", "P2-24", "P2-33" };
76 static const std::vector<std::string> PRU1_CTRL_PINS = { "P2-17", "P2-18" };
77 static const std::string PRU1_ENABLE_PIN = "P2-22";
78 #endif
79
80 static const std::vector<std::string> PRU_CTRL_PINS[2] = { PRU0_CTRL_PINS, PRU1_CTRL_PINS };
81 static const std::vector<std::string> PRU_DATA_PINS[2] = { PRU0_DATA_PINS, PRU1_DATA_PINS };
82
83 // r30 bit numbers of the clock/latch pins, matching the compile-time
84 // defaults in BBShiftString.asm (CONTROL_BIT_BASE + CLOCK_PIN/LATCH_PIN);
85 // a cape's pruPinConfig clockPin/latchPin names override them and the
86 // resolved values are published to the firmware at PINCFG_OFFSET
87 #ifdef PLATFORM_BBB
88 static const int DEFAULT_CLOCK_BIT[2] = { 15, 10 };
89 static const int DEFAULT_LATCH_BIT[2] = { 14, 11 };
90 #else
91 static const int DEFAULT_CLOCK_BIT[2] = { 19, 19 };
92 static const int DEFAULT_LATCH_BIT[2] = { 16, 16 };
93 #endif
94 // must match PINCFG_OFFSET in BBShiftString.asm
95 #define PINCFG_OFFSET 0x1FE8
96
97 50 BBShiftStringOutput::BBShiftStringOutput(unsigned int startChannel, unsigned int channelCount) :
98
2/8
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
50 ChannelOutput(startChannel, channelCount) {
99
1/2
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
50 LogDebug(VB_CHANNELOUT, "BBShiftStringOutput::BBShiftStringOutput(%u, %u)\n",
100 startChannel, channelCount);
101
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
50 }
102
103 /*
104 *
105 */
106
2/4
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
✗ Branch 3 not taken.
700 BBShiftStringOutput::~BBShiftStringOutput() {
107 100 LogDebug(VB_CHANNELOUT, "BBShiftStringOutput::~BBShiftStringOutput()\n");
108 // idempotent; Close() normally does this, but an output torn down without
109 // one must not leave its frame rate warning stranded in the UI
110 100 setFrameRateWarning(false);
111 100 clearBudgetWarning();
112 100 BBBPru::ddrRelease("BBShiftString");
113 100 m_pumpRunning = false;
114
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 49 times.
100 if (m_pumpThread.joinable()) {
115 2 m_pumpThread.join();
116 }
117
2/2
✓ Branch 0 taken 310 times.
✓ Branch 1 taken 50 times.
720 for (auto a : m_strings) {
118
1/2
✓ Branch 0 taken 310 times.
✗ Branch 1 not taken.
620 delete a;
119 }
120 100 m_strings.clear();
121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 50 times.
100 if (m_pru0.pru) {
122 ✗ delete m_pru0.pru;
123 }
124
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 48 times.
100 if (m_pru1.pru) {
125
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 delete m_pru1.pru;
126 }
127
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 32 times.
100 if (falconV5Support) {
128
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
36 delete falconV5Support;
129 }
130
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 32 times.
100 if (m_fv5PacketMem) {
131 36 free(m_fv5PacketMem);
132 }
133
4/4
✓ Branch 0 taken 100 times.
✓ Branch 1 taken 50 times.
✓ Branch 2 taken 100 times.
✓ Branch 3 taken 50 times.
900 }
134
135 // Inverted lines and Falcon smart receivers do not mix, but only for one of
136 // the two reasons a line can be inverted.
137 //
138 // A chain's first port carries the config packet and the receiver's processor
139 // watches that line, so an inverted head is refused whatever set it. Protocol
140 // inversion (TM18xx) is meant to reach the pixels, so it passes through the
141 // receiver and is refused for every port of the chain.
142 //
143 // Cape declared inversion on a non-head port is left alone: it exists to undo
144 // a backwards wired differential pair, so the receiver already sees a correct
145 // signal. That is the one shipped use - a batch of K16A-B boards with the
146 // second line of the first differential group reversed - and it has to keep
147 // working.
148 // T0, T1 and the end of the bit cell are single latch instants shared by every
149 // port on the PRU, so the controller can only run one bit cell at a time. Take
150 // it from the ports that actually have pixels, and if they disagree, say so
151 // loudly and name the odd ones out rather than driving them at a timing their
152 // part cannot decode.
153 //
154 // Mixing cannot be papered over by picking the slower cell: a ws281x part fed a
155 // 2000ns one-pulse reads it as garbage, so there is no safe common value.
156 49 void BBShiftStringOutput::resolveTiming(const Json::Value& config) {
157 49 std::map<PixelString::Timing, std::vector<int>, TimingLess> used;
158
2/2
✓ Branch 0 taken 310 times.
✓ Branch 1 taken 49 times.
359 for (int i = 0; i < (int)m_strings.size(); i++) {
159
2/2
✓ Branch 0 taken 304 times.
✓ Branch 1 taken 6 times.
310 if (m_strings[i]->m_outputBytes > 0) {
160
6/12
✓ Branch 0 taken 304 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 304 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 304 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 304 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 304 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 304 times.
✗ Branch 11 not taken.
608 used[PixelString::protocolTiming(config["outputs"][i]["protocol"].asString())]
161
1/2
✓ Branch 0 taken 304 times.
✗ Branch 1 not taken.
304 .push_back(m_strings[i]->m_portNumber + 1);
162 }
163 }
164
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (used.empty()) {
165 ✗ return;
166 }
167 // the cell the most ports want wins
168 49 auto best = used.begin();
169
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 49 times.
98 for (auto it = used.begin(); it != used.end(); ++it) {
170
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (it->second.size() > best->second.size()) {
171 ✗ best = it;
172 }
173 }
174 49 m_t0Ns = best->first.t0Ns;
175 49 m_t1Ns = best->first.t1Ns;
176 49 m_lowNs = best->first.periodNs;
177
178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (used.size() > 1) {
179 ✗ std::string odd;
180 ✗ for (auto& [t, ports] : used) {
181 ✗ if (t == best->first) {
182 ✗ continue;
183 }
184 ✗ for (int p : ports) {
185 ✗ odd += (odd.empty() ? "" : ", ") + std::to_string(p);
186 }
187 }
188 ✗ LogErr(VB_CHANNELOUT, "Ports on one controller must share a bit timing; using %d/%d/%dns. Port(s) %s want a different one and will not work\n",
189 m_t0Ns, m_t1Ns, m_lowNs, odd.c_str());
190 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: port(s) " + odd +
191 ✗ " use a pixel protocol whose timing differs from the rest of the controller");
192
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 46 times.
49 } else if (!(best->first == PixelString::Timing{ 320, 750, 1120 })) {
193
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 LogInfo(VB_CHANNELOUT, "BBShiftString: bit timing %d/%d/%dns\n", m_t0Ns, m_t1Ns, m_lowNs);
194 }
195
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 }
196
197 49 void BBShiftStringOutput::demoteInvertedReceiverChains() {
198 49 int x = 0;
199
2/2
✓ Branch 0 taken 255 times.
✓ Branch 1 taken 49 times.
304 while (x < m_strings.size()) {
200 255 PixelString* head = m_strings[x++];
201
2/2
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 232 times.
255 if (!head->m_isSmartReceiver ||
202
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 19 times.
23 (head->smartReceiverType != PixelString::ReceiverType::FalconV5 &&
203
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 head->smartReceiverType != PixelString::ReceiverType::FalconV4)) {
204 255 continue;
205 }
206
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
38 std::vector<PixelString*> chain{ head };
207
6/6
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 55 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 55 times.
✓ Branch 5 taken 19 times.
74 for (int i = 0; i < 3 && x < m_strings.size(); ++i) {
208
1/2
✓ Branch 0 taken 55 times.
✗ Branch 1 not taken.
55 chain.push_back(m_strings[x++]);
209 }
210
211 19 std::string why;
212
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (head->m_isInverted) {
213 ✗ why = "its first port is inverted";
214 } else {
215
2/2
✓ Branch 0 taken 74 times.
✓ Branch 1 taken 19 times.
93 for (auto* p : chain) {
216
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
74 if (p->m_protocolInverted) {
217 ✗ why = "port " + std::to_string(p->m_portNumber + 1) + " uses an inverted protocol";
218 ✗ break;
219 }
220 }
221 }
222
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19 if (why.empty()) {
223 19 continue;
224 }
225
226 ✗ LogWarn(VB_CHANNELOUT, "Falcon smart receivers on port %d disabled: %s\n",
227 head->m_portNumber + 1, why.c_str());
228 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: smart receivers on port " +
229 ✗ std::to_string(head->m_portNumber + 1) +
230 ✗ " disabled, " + why);
231 ✗ head->smartReceiverType = PixelString::ReceiverType::None;
232 ✗ head->m_isSmartReceiver = false;
233
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 19 times.
38 }
234 49 }
235
236 81 void BBShiftStringOutput::createOutputLengths(FrameData& d, const std::string& pfx) {
237 // One command table record is the high mask for every string slot, then
238 // the low mask for every slot, then the two byte channel offset at which
239 // the next record takes over. Each slot contributes one byte (a bitmask
240 // of the eight data pins), so a record carries 2 * stringsPerPin mask
241 // bytes - 16 at 8 deep, 32 at 16 deep - and BYTES_FOR_MASKS in the
242 // firmware has to agree. The 16 deep firmware loads the high half into
243 // scratchpad bank 11 and the low half into bank 12; the 8 deep one loads
244 // both straight into r21-r24.
245 union {
246 uint16_t r[2];
247 uint8_t b[4];
248 } r45[2 * MAX_STRINGS_PER_PIN / 4];
249
250
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 64 times.
81 if (!d.pruData) {
251 17 return;
252 }
253 // registers spanned by one mask half; the low half starts at r45[nRegs]
254 64 const int nRegs = m_stringsPerPin / 4;
255
2/2
✓ Branch 0 taken 260 times.
✓ Branch 1 taken 64 times.
324 for (int x = 0; x < 2 * nRegs; x++) {
256 260 r45[x].r[0] = 0;
257 260 r45[x].r[1] = 0;
258 }
259 64 std::map<int, std::vector<std::tuple<int, int, GPIOCommand, bool>>> sizes;
260 64 int bmask = 0x1;
261
2/2
✓ Branch 0 taken 512 times.
✓ Branch 1 taken 64 times.
576 for (int y = 0; y < MAX_PINS_PER_PRU; ++y) {
262
2/2
✓ Branch 0 taken 4160 times.
✓ Branch 1 taken 512 times.
4672 for (int x = 0; x < m_stringsPerPin; ++x) {
263 4160 int pc = d.stringMap[y][x];
264
2/2
✓ Branch 0 taken 382 times.
✓ Branch 1 taken 3778 times.
4160 if (pc >= 0) {
265
2/2
✓ Branch 0 taken 361 times.
✓ Branch 1 taken 382 times.
743 for (auto& a : m_strings[pc]->m_gpioCommands) {
266
2/4
✓ Branch 0 taken 361 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 361 times.
✗ Branch 3 not taken.
361 sizes[a.channelOffset].push_back(std::make_tuple(y, x, a, m_strings[pc]->m_isInverted));
267 }
268 382 int breg = x % 4;
269 382 int idx = x / 4;
270
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 382 times.
382 if (m_strings[pc]->m_isInverted) {
271 ✗ r45[idx + nRegs].b[breg] |= bmask;
272 } else {
273 382 r45[idx].b[breg] |= bmask;
274 }
275 }
276 }
277 512 bmask <<= 1;
278 }
279
280 // need to use pru->memcpyToPRU so we'll use a temporary here
281 // and it also needs to be 64 byte aligned
282 // the tail of the table is the between-packets mask slot, see BBShiftStringDefs.hp
283 64 const int maxEntries = BBSS_COMMAND_TABLE_ENTRIES - BBSS_PACKET2_MASKS_ENTRIES;
284 // one record is the mask halves plus the two byte offset that introduces
285 // the next one; 9 entries at 8 deep, 17 at 16 deep
286 64 const int entriesPerRecord = 1 + 4 * nRegs;
287 64 uint8_t* buffer = (uint8_t*)malloc(maxEntries * sizeof(uint16_t) + 256);
288 64 uintptr_t ptr = (uintptr_t)buffer;
289 64 ptr += 64 - (ptr % 64);
290 64 uint16_t* commandTable = (uint16_t*)ptr;
291
292 64 int curCommandTable = 0;
293 190 auto emitMasks = [&]() {
294
2/2
✓ Branch 0 taken 772 times.
✓ Branch 1 taken 190 times.
962 for (int r = 0; r < 2 * nRegs; ++r) {
295 772 commandTable[curCommandTable++] = r45[r].r[0];
296 772 commandTable[curCommandTable++] = r45[r].r[1];
297 }
298 254 };
299 64 emitMasks();
300
301 64 auto i = sizes.begin();
302
2/2
✓ Branch 0 taken 65 times.
✓ Branch 1 taken 63 times.
128 while (i != sizes.end()) {
303
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 64 times.
65 if (i->first == GPIO_CMD_AFTER_FIRST_PACKET) {
304 // not a data byte position; applied to the parked masks below
305 1 break;
306 }
307 64 uint16_t min = i->first & 0xFFFF;
308
2/2
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 2 times.
64 if (min <= d.maxStringLen) {
309 // the table lives in the PRU's data RAM, just below the ring
310 // config words - always leave room for this record and the
311 // terminator. A 16 deep cape can reach this: twice the strings
312 // and near twice the record size.
313
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 if (curCommandTable + entriesPerRecord + 1 > maxEntries) {
314 ✗ LogErr(VB_CHANNELOUT, "%s: GPIO command table full at channel offset %d; later commands dropped\n",
315 pfx.c_str(), i->first);
316 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: too many smart receiver GPIO commands to fit the PRU command table");
317 ✗ break;
318 }
319 62 commandTable[curCommandTable++] = min;
320
2/2
✓ Branch 0 taken 352 times.
✓ Branch 1 taken 62 times.
414 for (auto& t : i->second) {
321 352 auto [y, x, cmd, inverted] = t;
322
323 352 int reg = (x / 4);
324 352 int breg = x % 4;
325 352 uint8_t mask = 0x1 << y;
326
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 352 times.
352 if (cmd.type) {
327 ✗ if (inverted) {
328 ✗ r45[reg].b[breg] &= ~mask;
329 } else {
330 ✗ r45[reg].b[breg] |= mask;
331 }
332 } else {
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 352 times.
352 if (inverted) {
334 ✗ r45[reg].b[breg] |= mask;
335 } else {
336 352 r45[reg].b[breg] &= ~mask;
337 }
338 }
339 352 }
340 62 emitMasks();
341 }
342 64 i++;
343 }
344 64 commandTable[curCommandTable++] = 0xFFFF;
345 // round up to nearest 64 byte boundary
346 64 int len = (curCommandTable) * 2;
347 64 len += 64 - (len % 64);
348
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 d.pru->memcpyToPRU((uint8_t*)&d.pruData->commandTable[0], (uint8_t*)&commandTable[0], len);
349
350 // Park the masks the firmware loads in the packet phase (after the first
351 // packet, or for the whole phase of a listen frame): the end-of-frame
352 // masks (r45 has every record applied), less the send-only chain heads,
353 // whose line goes low after their config packet and never carries a
354 // second packet or precedes the bus turnaround. Always written, so the
355 // load is harmless when no such chain exists.
356
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 auto p2 = sizes.find(GPIO_CMD_AFTER_FIRST_PACKET);
357
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 63 times.
64 if (p2 != sizes.end()) {
358
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 for (auto& t : p2->second) {
359 1 auto [y, x, cmd, inverted] = t;
360 1 int reg = (x / 4);
361 1 int breg = x % 4;
362 1 uint8_t mask = 0x1 << y;
363
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if ((cmd.type != 0) != inverted) {
364 ✗ r45[reg].b[breg] |= mask;
365 } else {
366 1 r45[reg].b[breg] &= ~mask;
367 }
368 1 }
369 }
370 64 curCommandTable = 0;
371 64 emitMasks();
372 // one mask set is 2 * stringsPerPin bytes, the slot is 2 * ENTRIES bytes
373 static_assert(2 * MAX_STRINGS_PER_PIN <= 2 * BBSS_PACKET2_MASKS_ENTRIES, "between-packets mask slot too small");
374 64 d.pru->memcpyToPRU((uint8_t*)&d.pruData->commandTable[BBSS_COMMAND_TABLE_ENTRIES - BBSS_PACKET2_MASKS_ENTRIES],
375
1/2
✓ Branch 0 taken 64 times.
✗ Branch 1 not taken.
64 (uint8_t*)&commandTable[0], curCommandTable * 2);
376 64 free(buffer);
377 64 }
378
379 /*
380 *
381 */
382 50 int BBShiftStringOutput::Init(Json::Value config) {
383
1/2
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
50 LogDebug(VB_CHANNELOUT, "BBShiftStringOutput::Init(JSON)\n");
384 50 std::string v;
385
386
2/4
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
✗ Branch 3 not taken.
50 m_subType = config["subType"].asString();
387
2/4
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
✗ Branch 3 not taken.
50 m_channelCount = config["channelCount"].asInt();
388
389
1/2
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
50 Json::Value root;
390
2/4
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 50 times.
50 if (!CapeUtils::INSTANCE.getStringConfig(m_subType, root)) {
391 ✗ LogErr(VB_CHANNELOUT, "Could not read pin configuration for %s\n", m_subType.c_str());
392 ✗ return 0;
393 }
394
395 // The cape's string config has to be one written for this driver. Here an
396 // output is a place in the cape's shift register chains (numeric "pru",
397 // "pin" and "index"); on a BBB48String cape it is a header pin name
398 // ("pin": "P8-08"). Reading a pin name as an int throws out of jsoncpp and
399 // reaches the user as a bare "Value is not convertible to Int.", so name the
400 // real problem instead: the config asked for the wrong driver for this cape.
401 // Configs like that do arrive - xLights 2026.16 pointed an early K16A-B at
402 // this driver, a revision whose eeprom predates it and so is still a
403 // BBB48String pinout (and names no "driver" at all, hence the default
404 // below; later revisions of the same cape do belong here).
405
2/4
✓ Branch 0 taken 50 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 50 times.
✗ Branch 3 not taken.
50 const int capeOutputCount = root["outputs"].size();
406
2/2
✓ Branch 0 taken 855 times.
✓ Branch 1 taken 49 times.
904 for (int i = 0; i < capeOutputCount; i++) {
407 // a string "pin" is the unambiguous BBB48String signature; a missing
408 // one is left to the null-reads-as-0 behaviour the shift capes have
409 // always relied on for their unused fields
410
6/10
✓ Branch 0 taken 855 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 855 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 855 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 855 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 854 times.
✓ Branch 9 taken 1 times.
855 if (!root["outputs"][i]["pin"].isString()) {
411 854 continue;
412 }
413
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", "BBB48String").asString();
414
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "Cape %s is not a BBShiftString cape - its string configuration names header pins, so it needs the %s output type\n",
415 m_subType.c_str(), capeDriver.c_str());
416
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, "BBShiftString: " + m_subType + " needs the " + capeDriver +
417
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 " output type - open the Pixel Strings page and save to correct it");
418 1 return 0;
419 1 }
420
421 // A combo cape shares the PRUSS with a panel driver: this side must not
422 // clear the shared RAM or the other PRU's memory on a restart, and the
423 // FalconV5 listener (a PRU0 program whose capture area overlaps the
424 // panel ring) is unavailable. (Also honored from the output config for
425 // bench testing.)
426
6/12
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 49 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 49 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 49 times.
49 m_sharedPRUSS = root["sharedPRUSS"].asBool() || config["sharedPRUSS"].asBool();
427
428 // How deep the cape's shift register chains are. Absent means 8, the
429 // layout every cape has shipped with so far; 16 doubles the strings one
430 // PRU can drive and selects the SHIFT16 firmware.
431 // (Also honored from the output config, so a new cape can be brought up
432 // before its eeprom is finalized.)
433 49 m_stringsPerPin = 8;
434
7/10
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 48 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 48 times.
✓ Branch 8 taken 1 times.
✓ Branch 9 taken 48 times.
49 if (root.isMember("stringsPerPin") || config.isMember("stringsPerPin")) {
435
4/12
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
1 int spp = config.isMember("stringsPerPin") ? config["stringsPerPin"].asInt() : root["stringsPerPin"].asInt();
436
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (spp != 8 && spp != 16) {
437 ✗ LogErr(VB_CHANNELOUT, "Cape declares stringsPerPin %d; only 8 and 16 are supported, using 8\n", spp);
438 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: unsupported stringsPerPin, falling back to 8");
439 } else {
440 1 m_stringsPerPin = spp;
441 }
442 }
443 #ifdef PLATFORM_BBB
444 ✗ if (m_stringsPerPin != 8) {
445 // At 200MHz the three shift phases of a 16 deep chain come to 1045ns
446 // against a 1120ns bit budget, leaving no room for the data block
447 // read - and the AM335x already reaches 128 strings using both PRUs.
448 ✗ LogErr(VB_CHANNELOUT, "16 strings per pin requires the AM62x; using 8\n");
449 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: 16 strings per pin is not supported on this SBC");
450 ✗ m_stringsPerPin = 8;
451 }
452 #endif
453
454 // Default pin sets, overridable per PRU by the cape for combo pinouts.
455 // The cape only ever names header pins (P2-02 style); the mapping to
456 // r30 bit numbers happens here via the per-platform pin table, so a new
457 // SBC in the same form factor can reuse the same capes and eeproms
458 // (exactly how the PocketBeagle2 picked up PocketBeagle1 capes). The
459 // resolved clock/latch bits are published to the firmware at runtime.
460
2/2
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 49 times.
147 for (int p = 0; p < 2; p++) {
461
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 m_dataPins[p] = PRU_DATA_PINS[p];
462
1/2
✓ Branch 0 taken 98 times.
✗ Branch 1 not taken.
98 m_ctrlPins[p] = PRU_CTRL_PINS[p];
463 98 m_clockBit[p] = DEFAULT_CLOCK_BIT[p];
464 98 m_latchBit[p] = DEFAULT_LATCH_BIT[p];
465 98 m_pinNamesOverridden[p] = false;
466 }
467
8/14
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 49 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 50 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 49 times.
50 for (auto const& pc : root["pruPinConfig"]) {
468
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 int p = pc["pru"].asInt();
469
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (p < 0 || p > 1) {
470 ✗ continue;
471 }
472
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 if (pc.isMember("dataPins")) {
473 1 m_dataPins[p].clear();
474
7/12
✓ 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 2 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 2 times.
✓ Branch 11 taken 1 times.
3 for (auto const& pin : pc["dataPins"]) {
475
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 m_dataPins[p].push_back(pin.asString());
476 }
477 1 m_pinNamesOverridden[p] = true;
478 }
479 1 m_ctrlPins[p].clear();
480
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
3 for (auto const& nm : { "clockPin", "latchPin" }) {
481
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 2 times.
2 if (pc.isMember(nm)) {
482 ✗ std::string pinName = pc[nm].asString();
483 ✗ m_ctrlPins[p].push_back(pinName);
484 ✗ const BBBPinCapabilities* bp = (const BBBPinCapabilities*)(PinCapabilities::getPinByName(pinName).ptr());
485 // pruPin() returns uint8_t; the unmapped value (-1) needs
486 // the sign restored
487 ✗ int bit = bp ? (int8_t)bp->pruPin(p) : -1;
488 ✗ if (bit < 0) {
489 ✗ LogErr(VB_CHANNELOUT, "%s %s has no known PRU%d r30 bit\n", nm, pinName.c_str(), p);
490 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: unusable control pin " + pinName);
491 ✗ } else if (strcmp(nm, "clockPin") == 0) {
492 ✗ m_clockBit[p] = bit;
493 } else {
494 ✗ m_latchBit[p] = bit;
495 }
496 ✗ }
497 }
498 }
499
500 49 int maxStringLen = 0;
501 49 bool hasV5SR = false;
502 49 bool hasFalconSR = false;
503
4/6
✓ Branch 0 taken 359 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 359 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 310 times.
✓ Branch 5 taken 49 times.
359 for (int i = 0; i < config["outputs"].size(); i++) {
504
3/6
✓ Branch 0 taken 310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 310 times.
✗ Branch 5 not taken.
310 Json::Value s = config["outputs"][i];
505
3/8
✓ Branch 0 taken 310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 310 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 310 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
310 PixelString* newString = new PixelString(true);
506
507
5/10
✓ Branch 0 taken 310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 310 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 310 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 310 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 310 times.
310 if (!newString->Init(s, &root["outputs"][i])) {
508 ✗ return 0;
509 }
510
511
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 261 times.
310 if (newString->m_outputBytes > maxStringLen) {
512 49 maxStringLen = newString->m_outputBytes;
513 }
514
515
1/2
✓ Branch 0 taken 310 times.
✗ Branch 1 not taken.
310 m_strings.push_back(newString);
516
1/2
✓ Branch 0 taken 310 times.
✗ Branch 1 not taken.
310 }
517
518
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 resolveTiming(config);
519
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 demoteInvertedReceiverChains();
520
521
2/2
✓ Branch 0 taken 310 times.
✓ Branch 1 taken 49 times.
359 for (auto& a : m_strings) {
522 310 hasV5SR |= a->smartReceiverType == PixelString::ReceiverType::FalconV5;
523
3/4
✓ Branch 0 taken 310 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 22 times.
✓ Branch 3 taken 288 times.
310 hasFalconSR |= a->smartReceiverType == PixelString::ReceiverType::FalconV4 || a->smartReceiverType == PixelString::ReceiverType::FalconV5;
524 }
525
526
2/4
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 int retVal = ChannelOutput::Init(config);
527
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (retVal == 0) {
528 ✗ return 0;
529 }
530
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 if (maxStringLen == 0) {
531 ✗ LogErr(VB_CHANNELOUT, "No pixels configured in any string\n");
532 ✗ return 1;
533 }
534
535
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 m_licensedOutputs = CapeUtils::INSTANCE.getLicensedOutputs();
536
537
2/4
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 config["base"] = root;
538
539 // The full bidirectional Falcon V5 protocol needs the cape's PRU0
540 // listener support (and sole ownership of the PRUSS - the listener is a
541 // PRU0 program whose capture area overlaps the panel ring half of the
542 // shared RAM). Without that, V5 receivers still work as send-only V4:
543 // the config packet goes out, queries and responses do not.
544
4/6
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✓ Branch 3 taken 31 times.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
49 supportsV5Listeners = root.isMember("falconV5ListenerConfig") && !m_sharedPRUSS;
545
4/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 17 times.
49 if (hasV5SR && !supportsV5Listeners) {
546
2/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 LogWarn(VB_CHANNELOUT, "Falcon V5 (bidirectional) smart receivers are not supported on this cape%s; falling back to send-only V4 handling\n",
547 m_sharedPRUSS ? " (shared PRUSS)" : "");
548
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
5 for (auto& a : m_strings) {
549
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
4 if (a->smartReceiverType == PixelString::ReceiverType::FalconV5) {
550 1 a->smartReceiverType = PixelString::ReceiverType::FalconV4;
551 }
552 }
553 1 hasV5SR = false;
554 }
555 49 m_hasBidirSR = hasV5SR;
556
557 // On a cape that wires the receiver enable line to PRU1, that pin gates
558 // the whole differential bus, so it has to be configured whenever the
559 // cape has it - not just for Falcon receivers, and not just for smart
560 // receivers at all. Left unconfigured, nothing downstream of it gets
561 // data, dumb strings included. Configuring it also commits us to
562 // actually running PRU1 (see the maxStringLen bump below): the firmware
563 // parks the pin in its disabled state, so a halted PRU1 holds it there.
564 49 m_usesEnablePin = supportsV5Listeners;
565
566 49 int curRecPort = -1;
567
2/2
✓ Branch 0 taken 310 times.
✓ Branch 1 taken 49 times.
359 for (int x = 0; x < m_strings.size(); x++) {
568
6/6
✓ Branch 0 taken 88 times.
✓ Branch 1 taken 222 times.
✓ Branch 2 taken 70 times.
✓ Branch 3 taken 18 times.
✓ Branch 4 taken 19 times.
✓ Branch 5 taken 291 times.
380 if (curRecPort == -1 && (m_strings[x]->smartReceiverType == PixelString::ReceiverType::FalconV5 ||
569
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 69 times.
70 m_strings[x]->smartReceiverType == PixelString::ReceiverType::FalconV4)) {
570 19 curRecPort = 0;
571 }
572
6/6
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 304 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 2 times.
✓ Branch 4 taken 308 times.
✓ Branch 5 taken 2 times.
310 if (m_strings[x]->m_outputBytes > 0 || curRecPort >= 0) {
573
6/6
✓ Branch 0 taken 67 times.
✓ Branch 1 taken 241 times.
✓ Branch 2 taken 66 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 66 times.
✓ Branch 5 taken 242 times.
308 if (curRecPort == -1 && m_strings[x]->smartReceiverType != PixelString::ReceiverType::None) {
574 66 curRecPort = m_strings[x]->m_portNumber % 4;
575 }
576 // need to output this pin, configure it
577
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 307 times.
308 if (x >= capeOutputCount) {
578 // the loop above only vetted the outputs the cape declares;
579 // a config with more ports than that has nowhere to send them
580 // (and a bare operator[] here would read a null entry as
581 // pru 0 / pin 0 / stage 0, colliding with a real port)
582
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",
583 x + 1, capeOutputCount, m_subType.c_str());
584
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, "BBShiftString: output " + std::to_string(x + 1) +
585
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 " is past the end of the cape's pinout");
586 1 continue;
587 }
588
4/8
✓ Branch 0 taken 307 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 307 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 307 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 307 times.
✗ Branch 7 not taken.
307 int pru = root["outputs"][x]["pru"].asInt();
589
4/8
✓ Branch 0 taken 307 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 307 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 307 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 307 times.
✗ Branch 7 not taken.
307 int pin = root["outputs"][x]["pin"].asInt();
590
4/8
✓ Branch 0 taken 307 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 307 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 307 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 307 times.
✗ Branch 7 not taken.
307 int pinIdx = root["outputs"][x]["index"].asInt();
591
4/4
✓ Branch 0 taken 306 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 305 times.
307 if (pru < 0 || pru > 1) {
592 // m_dataPins/m_ctrlPins/m_pinNamesOverridden are all [2]; a
593 // cape pinout written for a different driver can name a pru
594 // outside that range and would index straight past them
595
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogErr(VB_CHANNELOUT, "Output %d names pru %d but only pru 0 and 1 exist\n", x, pru);
596
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, "BBShiftString: output " + std::to_string(x) + " names an invalid pru");
597 2 continue;
598 }
599
2/4
✓ Branch 0 taken 305 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 305 times.
305 if (pinIdx < 0 || pinIdx >= m_stringsPerPin) {
600 ✗ LogErr(VB_CHANNELOUT, "Output %d has shift stage index %d but the cape declares %d strings per pin\n",
601 x, pinIdx, m_stringsPerPin);
602 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: output " + std::to_string(x) + " shift stage index out of range");
603 ✗ continue;
604 }
605
2/2
✓ Branch 0 taken 606 times.
✓ Branch 1 taken 305 times.
911 for (auto& a : m_ctrlPins[pru]) {
606
3/4
✓ Branch 0 taken 606 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96 times.
✓ Branch 3 taken 510 times.
606 if (m_usedPins.find(a) == m_usedPins.end()) {
607
3/6
✓ Branch 0 taken 96 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 96 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 96 times.
✗ Branch 5 not taken.
96 m_usedPins[a] = "pru" + std::to_string(pru) + "out";
608 }
609 }
610
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 305 times.
305 if (pin >= (int)m_dataPins[pru].size()) {
611 ✗ LogErr(VB_CHANNELOUT, "Output %d references pru %d data pin %d but only %d exist\n",
612 x, pru, pin, (int)m_dataPins[pru].size());
613 ✗ continue;
614 }
615 305 const std::string& pinName = m_dataPins[pru][pin];
616
3/4
✓ Branch 0 taken 305 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 63 times.
✓ Branch 3 taken 242 times.
305 if (m_usedPins.find(pinName) == m_usedPins.end()) {
617
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.
63 m_usedPins[pinName] = "pru" + std::to_string(pru) + "out";
618 }
619 // with the default pin sets the cape's pin index is the r30 bit;
620 // cape-named pins resolve through the platform pin table
621 305 int bit = pin;
622
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 303 times.
305 if (m_pinNamesOverridden[pru]) {
623
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 const BBBPinCapabilities* bp = (const BBBPinCapabilities*)(PinCapabilities::getPinByName(pinName).ptr());
624
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 bit = bp ? (int8_t)bp->pruPin(pru) : -1;
625
3/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
2 if (bit < 0 || bit >= MAX_PINS_PER_PRU) {
626
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "Data pin %s maps to PRU%d r30 bit %d; the firmware supports bits 0-%d\n",
627 pinName.c_str(), pru, bit, MAX_PINS_PER_PRU - 1);
628
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: unusable data pin " + pinName);
629 1 continue;
630 }
631 }
632
633 // printf("pru: %d pin: %d idx: %d\n", pru, pin, pinIdx);
634
5/6
✓ Branch 0 taken 304 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 300 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 300 times.
✓ Branch 5 taken 4 times.
304 if (x >= m_licensedOutputs && m_strings[x]->m_outputBytes > 0) {
635 // apply limit
636 300 int pixels = 50;
637 300 int chanCount = 0;
638
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 300 times.
600 for (auto& a : m_strings[x]->m_virtualStrings) {
639
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 300 times.
300 if (pixels < a.pixelCount) {
640 ✗ a.pixelCount = pixels;
641 }
642 300 pixels -= a.pixelCount;
643
1/2
✓ Branch 0 taken 300 times.
✗ Branch 1 not taken.
300 chanCount += a.pixelCount * a.channelsPerNode();
644 }
645
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 274 times.
300 if (m_strings[x]->m_isSmartReceiver) {
646 26 chanCount = 0;
647 }
648
1/2
✓ Branch 0 taken 300 times.
✗ Branch 1 not taken.
300 m_strings[x]->setPixelDataChannels(chanCount);
649 }
650
651
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 304 times.
304 if (pru == 0) {
652 ✗ m_pru0.stringMap[bit][pinIdx] = x;
653 ✗ m_pru0.maxStringLen = std::max(m_pru0.maxStringLen, m_strings[x]->m_outputBytes);
654 } else {
655 304 m_pru1.stringMap[bit][pinIdx] = x;
656 304 m_pru1.maxStringLen = std::max(m_pru1.maxStringLen, m_strings[x]->m_outputBytes);
657 }
658
2/2
✓ Branch 0 taken 303 times.
✓ Branch 1 taken 1 times.
304 if (curRecPort >= 0) {
659
2/2
✓ Branch 0 taken 72 times.
✓ Branch 1 taken 231 times.
303 if (++curRecPort == 4) {
660 72 curRecPort = -1;
661 }
662 }
663 }
664 }
665
4/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 16 times.
49 if (m_usesEnablePin && m_pru1.maxStringLen <= m_pru0.maxStringLen) {
666 // pru1 controls the enable/reading mux pins so it has to output more pixels
667 // than pru0 so it knows pru0 is done (and so it is started at all)
668 2 m_pru1.maxStringLen = m_pru0.maxStringLen + 1;
669 }
670
671 49 int maxLen = std::max(m_pru0.maxStringLen, m_pru1.maxStringLen);
672
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 if (maxLen > 0) {
673 // m_lowNs is the programmed bit cell from resolveTiming(); the bit loop
674 // carries ~130ns/bit of instruction time outside the waits. Measured
675 // on a K32-Max at two cells: ws281x 1120 -> 1245-1252ns achieved,
676 // ucs1903 2500 -> 2619ns achieved.
677 //
678 // Every frame pays the firmware's fixed 300us reset. A Falcon
679 // receiver chain also pays the packet phase: with one attached the
680 // measured overhead was ~1700us (#2855), so the other 1400us applies
681 // only then. Without one the reset is all there is - the next frame
682 // is staged while the reset runs, and 300 px at 100fps measured a
683 // 110fps ceiling, where charging 1700us here warned at 93.
684 49 int bitNs = m_lowNs + 130;
685
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 31 times.
49 m_frameTimeUs = (int)((maxLen * 8LL * bitNs) / 1000) + 300 + (hasFalconSR ? 1400 : 0);
686
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 LogInfo(VB_CHANNELOUT, "BBShiftString: longest string %d bytes at %dns/bit -> %.1fms/frame, sustainable ceiling ~%.4g fps\n",
687 maxLen, bitNs, m_frameTimeUs / 1000.0, 1000000.0 / m_frameTimeUs);
688 }
689
690
4/4
✓ Branch 0 taken 48 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 47 times.
49 if (!StartPRU()) {
691 1 return 0;
692 }
693
694 #ifdef PLATFORM_BBB
695 // give each area two chunks (frame flipping) of DDR memory, from the
696 // shared region allocator so other outputs on the region cannot
697 // overlap us; the FalconV5 packet area rides at the end
698 ✗ m_pru0.frameSize = stringsPerPru() * std::max(2400, m_pru0.maxStringLen);
699 // leave a full memory page between to avoid conflicts
700 ✗ int offset0 = ((m_pru0.frameSize / 4096) + 2) * 4096;
701 ✗ m_pru1.frameSize = stringsPerPru() * std::max(2400, m_pru1.maxStringLen);
702 ✗ int offset1 = ((m_pru1.frameSize / 4096) + 2) * 4096;
703 ✗ size_t v5Size = hasFalconSR ? 128 * 1024 : 0;
704 ✗ uint32_t ddrPhys = 0;
705 ✗ uint8_t* start = BBBPru::ddrAlloc("BBShiftString", 2 * offset0 + 2 * offset1 + v5Size, ddrPhys);
706 ✗ if (!start) {
707 ✗ LogErr(VB_CHANNELOUT, "BBShiftString: no room in the PRU DDR region\n");
708 ✗ AddOutputConfigWarning(20, "BBShiftString: no room in the PRU DDR region - reduce string lengths or other outputs' buffers");
709 ✗ return 0;
710 }
711 ✗ m_pru0.curData = start;
712 ✗ m_pru0.lastData = m_pru0.curData + offset0;
713
714 ✗ m_pru0.formattedData = (uint8_t*)calloc(1, m_pru0.frameSize);
715
716 ✗ m_pru1.curData = m_pru0.lastData + offset0;
717 ✗ m_pru1.lastData = m_pru1.curData + offset1;
718
719 ✗ m_pru1.formattedData = (uint8_t*)calloc(1, m_pru1.frameSize);
720 #else
721 // AM62x: the frame flipping buffers live in normal cached memory; the
722 // pump thread streams them into the PRU shared memory ring
723 47 m_pru0.frameSize = stringsPerPru() * std::max(2400, m_pru0.maxStringLen);
724 47 m_pru0.curData = (uint8_t*)calloc(1, m_pru0.frameSize);
725 47 m_pru0.lastData = (uint8_t*)calloc(1, m_pru0.frameSize);
726 47 m_pru0.heapData = true;
727 47 m_pru0.formattedData = (uint8_t*)calloc(1, m_pru0.frameSize);
728
729 47 m_pru1.frameSize = stringsPerPru() * std::max(2400, m_pru1.maxStringLen);
730 47 m_pru1.curData = (uint8_t*)calloc(1, m_pru1.frameSize);
731 47 m_pru1.lastData = (uint8_t*)calloc(1, m_pru1.frameSize);
732 47 m_pru1.heapData = true;
733 47 m_pru1.formattedData = (uint8_t*)calloc(1, m_pru1.frameSize);
734 #endif
735
736
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 29 times.
47 if (m_usesEnablePin) {
737 // without this the enable line stays unconfigured and nothing
738 // behind it - receiver port 1, dumb strings - gets data
739
4/8
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 18 times.
✗ Branch 7 not taken.
90 PinCapabilities::getPinByName(PRU1_ENABLE_PIN).configPin("pru1out", true, "BBShiftString-Enable");
740 }
741
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 29 times.
47 if (hasFalconSR) {
742 #ifdef PLATFORM_BBB
743 ✗ setupFalconV5Support(root, m_pru1.lastData + offset1);
744 #else
745 // each config packet occupies 64 * stringsPerPru() bytes per PRU per
746 // repeat, so the packet area scales with the chain depth
747 18 m_fv5PacketMem = (uint8_t*)calloc(1, (size_t)128 * 1024 * (m_stringsPerPin / 8));
748
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
18 setupFalconV5Support(root, m_fv5PacketMem);
749 #endif
750 }
751
752
3/4
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 29 times.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
46 if (falconV5Support && m_frameTimeUs > 0) {
753 // A query frame costs every pin the packet phase and the listen
754 // window on top of the pixel data: 69us of lead-in, 57 bytes of 10
755 // cells at 1130ns, 35us of listener lead-in and a window of 500us,
756 // extended by 1.7ms when the receiver answers - about 2.9ms with a
757 // receiver on the chain. Spread over the frames that carry one so
758 // the ceiling (and the warning) is honest for a receiver config: at
759 // 768px per port and 40fps the string data alone fits, the queries
760 // do not, and without this term the drops came with no warning.
761
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 float share = falconV5Support->queryFrameShare();
762
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1 times.
17 if (share > 0.0f) {
763 16 m_frameTimeUs += (int)(2900.0f * share);
764
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 LogInfo(VB_CHANNELOUT, "BBShiftString: FalconV5 queries on %.0f%% of frames -> %.1fms/frame, sustainable ceiling ~%.4g fps\n",
765 share * 100.0f, m_frameTimeUs / 1000.0, 1000000.0 / m_frameTimeUs);
766 }
767 }
768
769 // flag the virtual strings whose channel map is a plain run so prepData
770 // can walk the channel data directly instead of through the map
771
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 m_vsAffine.resize(m_strings.size());
772
2/2
✓ Branch 0 taken 306 times.
✓ Branch 1 taken 46 times.
352 for (size_t s = 0; s < m_strings.size(); s++) {
773
1/2
✓ Branch 0 taken 306 times.
✗ Branch 1 not taken.
306 m_vsAffine[s].resize(m_strings[s]->m_virtualStrings.size());
774
2/2
✓ Branch 0 taken 306 times.
✓ Branch 1 taken 306 times.
612 for (size_t v = 0; v < m_strings[s]->m_virtualStrings.size(); v++) {
775 306 auto& vs = m_strings[s]->m_virtualStrings[v];
776 306 bool affine = true;
777
2/2
✓ Branch 0 taken 27852 times.
✓ Branch 1 taken 306 times.
28158 for (int i = 0; i + 3 < vs.chMapCount; i++) {
778
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27852 times.
27852 if (vs.chMap[i + 3] != vs.chMap[i] + 3) {
779 ✗ affine = false;
780 ✗ break;
781 }
782 }
783 306 m_vsAffine[s][v] = affine;
784 }
785 }
786
787
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 PixelString::AutoCreateOverlayModels(m_strings, m_autoCreatedModelNames);
788 46 return retVal;
789 52 }
790
791 // Bit timing, as PRU cycle counts, published where the firmware's per-bit
792 // waits read them (TIMING_*_OFFSET in BBShiftString.asm - the unused "buffer"
793 // words, which have to be in the low 256 bytes because LBCO's offset field is
794 // 8 bit). The firmware spins on the marker before its first frame, so the
795 // three counts must be written before it.
796 //
797 // The waits count from a clock reset at the start of the bit, and the macro
798 // spends a few instructions between reading the counter and entering its
799 // delay loop, so the target is reduced by that overhead here rather than on
800 // the PRU. The immediate form compensated 3 (LDI+MAX+SUB); the runtime form
801 // reads through LBCO instead of LDI, which is slower.
802 constexpr int TIMING_OVERHEAD_CYCLES = 5;
803
804 // ns per PRU cycle: 200MHz on the AM335x, 250MHz on the AM62x
805 #ifdef PLATFORM_BBB
806 constexpr int PRU_NS_PER_CYCLE = 5;
807 #else
808 constexpr int PRU_NS_PER_CYCLE = 4;
809 #endif
810
811 141 static uint32_t timingCycles(int ns) {
812 141 int c = ns / PRU_NS_PER_CYCLE - TIMING_OVERHEAD_CYCLES;
813 141 return (uint32_t)std::max(1, c);
814 }
815
816 47 static void publishTiming(BBBPru* pru, int t0ns, int t1ns, int lowns) {
817 47 volatile uint32_t* p = (volatile uint32_t*)(pru->data_ram + 16);
818 47 p[0] = timingCycles(t0ns); // TIMING_T0_OFFSET
819 47 p[1] = timingCycles(t1ns); // TIMING_T1_OFFSET
820 47 p[2] = timingCycles(lowns); // TIMING_LOW_OFFSET
821 47 __sync_synchronize();
822 47 p[3] = 0xA5A5A5A5; // TIMING_MAGIC_OFFSET, written last
823 47 __sync_synchronize();
824 47 }
825
826 // Publish the resolved clock/latch r30 bit numbers to the firmware (see
827 // PINCFG_OFFSET in BBShiftString.asm; the 0xA5 marker distinguishes a real
828 // config from cleared memory). Must land after run() - the firmware load
829 // clears the data RAM - and the firmware rereads it while waiting for the
830 // first frame command.
831 47 static void publishPinConfig(BBBPru* pru, int clockBit, int latchBit) {
832 47 uint32_t v = (uint32_t)(clockBit & 0xFF) | (((uint32_t)(latchBit & 0xFF)) << 8) | (0xA5u << 24);
833 47 *(volatile uint32_t*)(pru->data_ram + PINCFG_OFFSET) = v;
834 47 __sync_synchronize();
835 47 }
836
837 // A 16 deep chain shifts twice as many bytes per phase, keeps its output masks
838 // in the PRU scratchpad and stretches T0H, so it gets its own firmware image
839 // rather than a runtime switch (see SHIFT16 in BBShiftString.asm).
840 48 static std::string pruFirmware(int pru, int stringsPerPin) {
841
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
48 std::string f = "/opt/fpp/src/non-gpl/BBShiftString/BBShiftString_pru" + std::to_string(pru);
842
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 47 times.
48 if (stringsPerPin == 16) {
843
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 f += "_16";
844 }
845
1/2
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
96 return f + ".out";
846 48 }
847
848 49 int BBShiftStringOutput::StartPRU() {
849 49 m_curFrame = 0;
850 49 m_bpOffered = 0;
851 49 m_bpDeclined = 0;
852 49 m_bpLastDeclined = false;
853 49 m_bpWindowStart = {};
854
2/2
✓ Branch 0 taken 159 times.
✓ Branch 1 taken 49 times.
208 for (auto& a : m_usedPins) {
855
3/6
✓ Branch 0 taken 159 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 159 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 159 times.
✗ Branch 5 not taken.
477 PinCapabilities::getPinByName(a.first).configPin(a.second, true, "BBShiftString");
856 }
857
858 #ifdef PLATFORM_BBB
859 ✗ constexpr bool mapShared = false;
860 #else
861 49 constexpr bool mapShared = true;
862
863 // Where each PRU's data ring sits in the 32KB shared RAM. Halving it is
864 // required when both PRUs output strings, or when a panel driver owns the
865 // other half of a combo cape. A sole string PRU can have the rest,
866 // starting above the FalconV5 listener's capture area if a listener will
867 // run on the other PRU (the listener captures from the base upwards).
868 //
869 // 16 deep needs this: it drains the ring at 12.8MB/s, so a 16320 byte half
870 // is only 1.27ms of buffer against 1.60ms from the V5 layout (the listener
871 // reservation cannot shrink - see SMEMRing.hp).
872 49 uint32_t ringBase[2] = { SMEM_RING_SPLIT0_BASE, SMEM_RING_SPLIT1_BASE };
873 49 uint32_t ringSize[2] = { SMEM_RING_SPLIT_SIZE, SMEM_RING_SPLIT_SIZE };
874
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
49 const bool bothPrus = m_pru0.maxStringLen && m_pru1.maxStringLen;
875
4/4
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 31 times.
✓ Branch 2 taken 17 times.
✓ Branch 3 taken 1 times.
49 const bool willListen = supportsV5Listeners && m_hasBidirSR;
876
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 48 times.
49 if (m_stringsPerPin == 16) {
877
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
1 if (bothPrus || m_sharedPRUSS) {
878 // 16 deep already fills a PRU's pins; needing both means the cape
879 // is asking for something the shared RAM cannot feed.
880 ✗ LogErr(VB_CHANNELOUT, "BBShiftString: 16 strings per pin needs sole use of one PRU%s\n",
881 m_sharedPRUSS ? " and cannot share the PRUSS with a panel driver" : "");
882 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: 16 strings per pin requires all strings on one PRU");
883 } else {
884
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 int p = m_pru1.maxStringLen ? 1 : 0;
885
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (willListen && p == 0) {
886 // the listener is a PRU0 program; strings must leave it free
887 ✗ LogErr(VB_CHANNELOUT, "BBShiftString: FalconV5 listeners need PRU0, so the strings must be on PRU1\n");
888 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: FalconV5 listeners require the strings on PRU1");
889 }
890
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 ringBase[p] = willListen ? SMEM_RING_V5_BASE : SMEM_RING_DEFAULT_BASE;
891
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 ringSize[p] = willListen ? SMEM_RING_V5_SIZE : SMEM_RING_DEFAULT_SIZE;
892 }
893 }
894
3/4
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 32 times.
✓ Branch 2 taken 49 times.
✗ Branch 3 not taken.
49 LogDebug(VB_CHANNELOUT, "BBShiftString: ring pru0 %X/%d pru1 %X/%d (listener %d)\n",
895 ringBase[0], ringSize[0], ringBase[1], ringSize[1], willListen ? 1 : 0);
896 #endif
897
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 if (m_pru1.maxStringLen) {
898
5/8
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 48 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
49 m_pru1.pru = new BBBPru(1, mapShared, false);
899 48 m_pru1.pruData = (BBShiftStringData*)m_pru1.pru->data_ram;
900
4/6
✓ Branch 0 taken 48 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 48 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 47 times.
48 if (!m_pru1.pru->run(pruFirmware(1, m_stringsPerPin), !m_sharedPRUSS)) {
901
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogErr(VB_CHANNELOUT, "BBShiftString: Unable to start PRU1. May require a reboot.\n");
902
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: Unable to start PRU1. May require a reboot.");
903 1 return 0;
904 }
905 47 publishPinConfig(m_pru1.pru, m_clockBit[1], m_latchBit[1]);
906
1/2
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
47 publishTiming(m_pru1.pru, m_t0Ns, m_t1Ns, m_lowNs);
907 #ifndef PLATFORM_BBB
908 // the firmware polls for the ring location; the upper half of the
909 // shared RAM keeps clear of the FalconV5 listener capture area
910
1/2
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
47 m_pru1.ring.attach(m_pru1.pru, ringBase[1], ringSize[1], true);
911 #endif
912
2/4
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
✗ Branch 3 not taken.
94 createOutputLengths(m_pru1, "pru1");
913
1/2
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
47 std::this_thread::sleep_for(std::chrono::milliseconds(10));
914 }
915
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 47 times.
47 if (m_pru0.maxStringLen) {
916 ✗ m_pru0.pru = new BBBPru(0, mapShared, false);
917 ✗ m_pru0.pruData = (BBShiftStringData*)m_pru0.pru->data_ram;
918 ✗ if (!m_pru0.pru->run(pruFirmware(0, m_stringsPerPin), !m_sharedPRUSS)) {
919 ✗ LogErr(VB_CHANNELOUT, "BBShiftString: Unable to start PRU0. May require a reboot.\n");
920 ✗ AddOutputConfigWarning(UNKNOWN_WARNING_ID, "BBShiftString: Unable to start PRU0. May require a reboot.");
921 ✗ return 0;
922 }
923 ✗ publishPinConfig(m_pru0.pru, m_clockBit[0], m_latchBit[0]);
924 ✗ publishTiming(m_pru0.pru, m_t0Ns, m_t1Ns, m_lowNs);
925 #ifndef PLATFORM_BBB
926 ✗ m_pru0.ring.attach(m_pru0.pru, ringBase[0], ringSize[0], true);
927 #endif
928 ✗ createOutputLengths(m_pru0, "pru0");
929 }
930 #ifndef PLATFORM_BBB
931
4/8
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 47 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 47 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 47 times.
✗ Branch 7 not taken.
47 if (!m_pumpThread.joinable() && (m_pru0.pru || m_pru1.pru)) {
932 47 m_pumpRunning = true;
933
1/2
✓ Branch 0 taken 47 times.
✗ Branch 1 not taken.
47 m_pumpThread = std::thread(&BBShiftStringOutput::runPumpThread, this);
934 }
935 #endif
936 47 return 1;
937 }
938 46 void BBShiftStringOutput::StopPRU(bool wait) {
939 // Send the stop command
940
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (m_pru0.pru) {
941 ✗ m_pru0.pruData->response = 0;
942 ✗ m_pru0.pruData->command = 0xFFFF;
943 }
944
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (m_pru1.pru) {
945 46 m_pru1.pruData->response = 0;
946 46 m_pru1.pruData->command = 0xFFFF;
947 }
948 46 __asm__ __volatile__("" ::
949 : "memory");
950
951
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (m_pru1.pru) {
952 46 int cnt = 0;
953
7/8
✓ Branch 0 taken 871 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 838 times.
✓ Branch 3 taken 33 times.
✓ Branch 4 taken 825 times.
✓ Branch 5 taken 13 times.
✓ Branch 6 taken 825 times.
✓ Branch 7 taken 46 times.
871 while (wait && cnt < 25 && m_pru1.pruData->response != 0xFFFF) {
954
1/2
✓ Branch 0 taken 825 times.
✗ Branch 1 not taken.
825 std::this_thread::sleep_for(std::chrono::milliseconds(1));
955 825 cnt++;
956 825 __asm__ __volatile__("" ::
957 : "memory");
958 }
959 }
960
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (m_pru0.pru) {
961 ✗ int cnt = 0;
962 ✗ while (wait && cnt < 25 && m_pru0.pruData->response != 0xFFFF) {
963 ✗ std::this_thread::sleep_for(std::chrono::milliseconds(1));
964 ✗ cnt++;
965 ✗ __asm__ __volatile__("" ::
966 : "memory");
967 }
968 }
969 // stop the pump before the PRU memory mappings go away
970 46 m_pumpRunning = false;
971
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (m_pumpThread.joinable()) {
972 46 m_pumpThread.join();
973 }
974
975
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 if (m_pru1.pru) {
976 46 m_pru1.pru->stop();
977
1/2
✓ Branch 0 taken 46 times.
✗ Branch 1 not taken.
46 delete m_pru1.pru;
978 46 m_pru1.pru = nullptr;
979 }
980
981
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 46 times.
46 if (m_pru0.pru) {
982 ✗ m_pru0.pru->stop();
983 ✗ delete m_pru0.pru;
984 ✗ m_pru0.pru = nullptr;
985 }
986 46 }
987 /*
988 *
989 */
990 46 int BBShiftStringOutput::Close(void) {
991 46 LogDebug(VB_CHANNELOUT, "BBShiftStringOutput::Close()\n");
992
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 46 times.
346 for (auto& n : m_autoCreatedModelNames) {
993
1/2
✓ Branch 0 taken 300 times.
✗ Branch 1 not taken.
300 PixelOverlayManager::INSTANCE.removeAutoOverlayModel(n);
994 }
995 46 StopPRU();
996 46 setFrameRateWarning(false);
997 46 clearBudgetWarning();
998
2/2
✓ Branch 0 taken 150 times.
✓ Branch 1 taken 46 times.
196 for (auto& a : m_usedPins) {
999
2/4
✓ Branch 0 taken 150 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 150 times.
✗ Branch 3 not taken.
150 PinCapabilities::getPinByName(a.first).releasePin();
1000 }
1001
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 29 times.
46 if (m_usesEnablePin) {
1002 17 PinCapabilities::getPinByName(PRU1_ENABLE_PIN).releasePin();
1003 }
1004 46 return ChannelOutput::Close();
1005 }
1006
1007 92 void BBShiftStringOutput::GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) {
1008 92 PixelString* ps = NULL;
1009
2/2
✓ Branch 0 taken 612 times.
✓ Branch 1 taken 92 times.
704 for (int s = 0; s < m_strings.size(); s++) {
1010 612 ps = m_strings[s];
1011
1012
2/2
✓ Branch 0 taken 612 times.
✓ Branch 1 taken 612 times.
1224 for (auto& vs : ps->m_virtualStrings) {
1013 612 int min = FPPD_MAX_CHANNELS;
1014 612 int max = -1;
1015
1016 612 int* map = vs.chMap;
1017
2/2
✓ Branch 0 taken 57504 times.
✓ Branch 1 taken 612 times.
58116 for (int c = 0; c < vs.chMapCount; c++) {
1018 57504 int ch = map[c];
1019
1/2
✓ Branch 0 taken 57504 times.
✗ Branch 1 not taken.
57504 if (ch < FPPD_MAX_CHANNELS) {
1020 57504 min = std::min(min, ch);
1021 57504 max = std::max(max, ch);
1022 }
1023 }
1024
2/2
✓ Branch 0 taken 600 times.
✓ Branch 1 taken 12 times.
612 if (min < max) {
1025
1/2
✓ Branch 0 taken 600 times.
✗ Branch 1 not taken.
600 addRange(min, max);
1026 }
1027 }
1028 }
1029 92 }
1030 124 void BBShiftStringOutput::OverlayTestData(unsigned char* channelData, int cycleNum, float percentOfCycle, int testType, const Json::Value& config) {
1031 124 m_testCycle = cycleNum;
1032 124 m_testType = testType;
1033 124 m_testPercent = percentOfCycle;
1034
1035 // We won't overlay the data here because we could have multiple strings
1036 // pointing at the same channel range so a per-port test cannot
1037 // be done via channel ranges. We'll record the test information and use
1038 // that in prepData
1039 124 }
1040
1041 // 8x8 byte transpose: in, rows are 8 consecutive bytes of 8 strings; out,
1042 // rows are the 8 strings' bytes for 8 consecutive positions
1043 1121744 static inline void transpose8x8(uint8x8_t r[8]) {
1044 1121744 uint8x8x2_t t0 = vtrn_u8(r[0], r[1]);
1045 1121744 uint8x8x2_t t1 = vtrn_u8(r[2], r[3]);
1046 1121744 uint8x8x2_t t2 = vtrn_u8(r[4], r[5]);
1047 1121744 uint8x8x2_t t3 = vtrn_u8(r[6], r[7]);
1048 1121744 uint16x4x2_t s0 = vtrn_u16(vreinterpret_u16_u8(t0.val[0]), vreinterpret_u16_u8(t1.val[0]));
1049 1121744 uint16x4x2_t s1 = vtrn_u16(vreinterpret_u16_u8(t0.val[1]), vreinterpret_u16_u8(t1.val[1]));
1050 1121744 uint16x4x2_t s2 = vtrn_u16(vreinterpret_u16_u8(t2.val[0]), vreinterpret_u16_u8(t3.val[0]));
1051 1121744 uint16x4x2_t s3 = vtrn_u16(vreinterpret_u16_u8(t2.val[1]), vreinterpret_u16_u8(t3.val[1]));
1052 1121744 uint32x2x2_t u0 = vtrn_u32(vreinterpret_u32_u16(s0.val[0]), vreinterpret_u32_u16(s2.val[0]));
1053 1121744 uint32x2x2_t u1 = vtrn_u32(vreinterpret_u32_u16(s1.val[0]), vreinterpret_u32_u16(s3.val[0]));
1054 1121744 uint32x2x2_t u2 = vtrn_u32(vreinterpret_u32_u16(s0.val[1]), vreinterpret_u32_u16(s2.val[1]));
1055 1121744 uint32x2x2_t u3 = vtrn_u32(vreinterpret_u32_u16(s1.val[1]), vreinterpret_u32_u16(s3.val[1]));
1056 1121744 r[0] = vreinterpret_u8_u32(u0.val[0]);
1057 1121744 r[1] = vreinterpret_u8_u32(u1.val[0]);
1058 1121744 r[2] = vreinterpret_u8_u32(u2.val[0]);
1059 1121744 r[3] = vreinterpret_u8_u32(u3.val[0]);
1060 1121744 r[4] = vreinterpret_u8_u32(u0.val[1]);
1061 1121744 r[5] = vreinterpret_u8_u32(u1.val[1]);
1062 1121744 r[6] = vreinterpret_u8_u32(u2.val[1]);
1063 1121744 r[7] = vreinterpret_u8_u32(u3.val[1]);
1064 1121744 }
1065
1066 // Transpose one 8x8 block of the interleaved frame: eight source bytes, one
1067 // per data pin, taken inStride apart, become eight bit planes outStride apart.
1068 // SH selects which bit of the pixel byte this plane carries; the caller emits
1069 // SH 0 (the LSB, clocked out last) into the highest numbered plane. Within a
1070 // plane, lane i is the byte destined for shift register stage i.
1071 //
1072 // The reference implementation this replaces, per pixel byte:
1073 // for (int x = 0; x < 8; x++) {
1074 // iframe[7-x] = (buf[0] >> x) & 0x0101010101010101ULL;
1075 // iframe[7-x] |= ((buf[1] >> x) & mask) << 1;
1076 // ... one term per data pin ...
1077 // }
1078 // On 32 bit arm each of those 64 bit shifts is three instructions, hence NEON.
1079 template<int SH>
1080 19093680 static inline void bitFlipPlane(const uint8x8_t* buf, uint8_t* out) {
1081 uint8x8_t b[8];
1082 // vshr_n_u8 requires 1 <= n <= 8, so the LSB plane takes the bytes as they
1083 // are; vsli_n_u8 preserves the low bits of its first operand either way
1084 if constexpr (SH == 0) {
1085
2/2
✓ Branch 0 taken 9546840 times.
✓ Branch 1 taken 1193355 times.
21480390 for (int k = 0; k < 8; ++k) {
1086 19093680 b[k] = buf[k];
1087 }
1088 } else {
1089
2/2
✓ Branch 0 taken 66827880 times.
✓ Branch 1 taken 8353485 times.
150362730 for (int k = 0; k < 8; ++k) {
1090
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
267311520 b[k] = vshr_n_u8(buf[k], SH);
1091 }
1092 }
1093
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
19093680 uint8x8_t tmp = vsli_n_u8(b[0], b[1], 1);
1094
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
19093680 tmp = vsli_n_u8(tmp, b[2], 2);
1095
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
19093680 tmp = vsli_n_u8(tmp, b[3], 3);
1096
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
19093680 tmp = vsli_n_u8(tmp, b[4], 4);
1097
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
19093680 tmp = vsli_n_u8(tmp, b[5], 5);
1098
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
19093680 tmp = vsli_n_u8(tmp, b[6], 6);
1099
0/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
38187360 vst1_u8(out, vsli_n_u8(tmp, b[7], 7));
1100 19093680 }
1101
1102 template<int SPP>
1103 46414 void BBShiftStringOutput::bitFlipDataT(uint8_t* stringChannelData, uint8_t* bitSwapped, size_t len) {
1104 46414 constexpr int NSTR = MAX_PINS_PER_PRU * SPP;
1105 46414 uint8_t* iframe = bitSwapped;
1106
2/2
✓ Branch 0 taken 1184355 times.
✓ Branch 1 taken 23207 times.
2415124 for (size_t p = 0; p < len; p++) {
1107 // one 8x8 transpose per group of eight shift register stages; a 16
1108 // deep chain is just two of them, landing in the two halves of each
1109 // plane
1110
2/2
✓ Branch 0 taken 1193355 times.
✓ Branch 1 taken 1184355 times.
4755420 for (int h = 0; h < SPP / 8; ++h) {
1111 2386710 const int off = h * 8;
1112 uint8x8_t buf[8];
1113
2/2
✓ Branch 0 taken 9546840 times.
✓ Branch 1 taken 1193355 times.
21480390 for (int k = 0; k < 8; ++k) {
1114
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
38187360 buf[k] = vld1_u8(&stringChannelData[k * SPP + off]);
1115 }
1116
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2386710 bitFlipPlane<0>(buf, &iframe[7 * SPP + off]);
1117
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2386710 bitFlipPlane<1>(buf, &iframe[6 * SPP + off]);
1118
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2386710 bitFlipPlane<2>(buf, &iframe[5 * SPP + off]);
1119
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2386710 bitFlipPlane<3>(buf, &iframe[4 * SPP + off]);
1120
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2386710 bitFlipPlane<4>(buf, &iframe[3 * SPP + off]);
1121
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2386710 bitFlipPlane<5>(buf, &iframe[2 * SPP + off]);
1122
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2386710 bitFlipPlane<6>(buf, &iframe[1 * SPP + off]);
1123
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
2386710 bitFlipPlane<7>(buf, &iframe[0 * SPP + off]);
1124 }
1125 2368710 iframe += NSTR;
1126 2368710 stringChannelData += NSTR;
1127 }
1128 46414 }
1129
1130 305 void BBShiftStringOutput::bitFlipData(uint8_t* stringChannelData, uint8_t* bitSwapped, size_t len) {
1131
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 305 times.
305 if (m_stringsPerPin == 16) {
1132 ✗ bitFlipDataT<16>(stringChannelData, bitSwapped, len);
1133 } else {
1134 305 bitFlipDataT<8>(stringChannelData, bitSwapped, len);
1135 }
1136 305 }
1137
1138 template<int SPP>
1139 39928 void BBShiftStringOutput::prepDataT(FrameData& d, unsigned char* channelData) {
1140
2/2
✓ Branch 0 taken 9982 times.
✓ Branch 1 taken 9982 times.
39928 if (d.maxStringLen == 0) {
1141 19964 return;
1142 }
1143 19964 PixelStringTester* tester = nullptr;
1144
3/4
✓ Branch 0 taken 121 times.
✓ Branch 1 taken 9861 times.
✓ Branch 2 taken 121 times.
✗ Branch 3 not taken.
19964 if (m_testType && m_testCycle >= 0) {
1145
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 121 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
242 if (m_testType == 999 && falconV5Support && m_testCycle == 0) {
1146 ✗ falconV5Support->sendCountPixelPackets();
1147 }
1148
1/2
✓ Branch 0 taken 121 times.
✗ Branch 1 not taken.
242 tester = PixelStringTester::getPixelStringTester(m_testType);
1149 // null for a test type no string tester implements; the strings then
1150 // just send their normal data
1151
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 121 times.
242 if (tester) {
1152 ✗ tester->prepareTestData(m_testCycle, m_testPercent);
1153 }
1154 }
1155 19964 constexpr int NSTR = MAX_PINS_PER_PRU * SPP;
1156 // per string slot: either a fully prepared buffer (test mode) or the
1157 // PixelString whose virtual strings are rendered on the fly per tile,
1158 // with a cursor tracking where in the virtual string list the next tile
1159 // continues
1160 struct SlotSrc {
1161 const uint8_t* buf = nullptr;
1162 PixelString* ps = nullptr;
1163 const uint8_t* affine = nullptr;
1164 uint32_t len = 0;
1165 uint32_t vsIdx = 0;
1166 uint32_t vsOff = 0;
1167 uint8_t pad = 0;
1168 19964 } slots[MAX_PINS_PER_PRU][SPP];
1169 19964 uint32_t newMax = d.maxStringLen;
1170
2/2
✓ Branch 0 taken 79856 times.
✓ Branch 1 taken 9982 times.
179676 for (int y = 0; y < MAX_PINS_PER_PRU; ++y) {
1171
2/2
✓ Branch 0 taken 648448 times.
✓ Branch 1 taken 79856 times.
1456608 for (int x = 0; x < SPP; ++x) {
1172 1296896 int idx = d.stringMap[y][x];
1173
2/2
✓ Branch 0 taken 39063 times.
✓ Branch 1 taken 609385 times.
1296896 if (idx != -1) {
1174 78126 PixelString* ps = m_strings[idx];
1175 78126 uint32_t newLen = ps->m_outputBytes;
1176 78126 SlotSrc& sl = slots[y][x];
1177
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39063 times.
78126 if (tester) {
1178 ✗ sl.buf = tester->createTestData(ps, m_testCycle, m_testPercent, channelData, newLen);
1179 } else {
1180 78126 sl.ps = ps;
1181 78126 sl.affine = m_vsAffine[idx].data();
1182 }
1183 78126 sl.len = newLen;
1184 // Past the end of a string the port is parked at its idle
1185 // level, which an inverted line holds high. The data phase
1186 // still shifts whatever is in the buffer, so padding a short
1187 // inverted port with zeros would drop the line mid bit.
1188
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 39063 times.
78126 sl.pad = ps->m_isInverted ? 0xFF : 0x00;
1189 78126 newMax = std::max(newMax, newLen);
1190 }
1191 }
1192 }
1193 19964 d.outputStringLen = newMax;
1194
1195 // Interleave and bit flip the string data in tiles that stay in the L1
1196 // cache. Interleaving the whole frame at once writes a byte every 64
1197 // bytes which forces every cache line of the frame to be re-fetched for
1198 // every string once the frame no longer fits in cache. The brightness/
1199 // gamma application is fused into the tile fill so the string bytes are
1200 // never staged in a full-size intermediate buffer, and virtual strings
1201 // whose channel map is a simple run (map[i+3] == map[i]+3, the normal
1202 // non-grouped case) skip the per-channel map indirection.
1203 19964 constexpr uint32_t TILE = 64;
1204 uint8_t col[SPP][TILE];
1205 uint8_t tile[TILE * NSTR];
1206
2/2
✓ Branch 0 taken 22902 times.
✓ Branch 1 taken 9982 times.
65768 for (uint32_t p0 = 0; p0 < newMax; p0 += TILE) {
1207 45804 const uint32_t n = std::min(TILE, newMax - p0);
1208 45804 const uint32_t nFull = n & ~7;
1209
2/2
✓ Branch 0 taken 183216 times.
✓ Branch 1 taken 22902 times.
412236 for (int y = 0; y < MAX_PINS_PER_PRU; ++y) {
1210
2/2
✓ Branch 0 taken 1475328 times.
✓ Branch 1 taken 183216 times.
3317088 for (int x = 0; x < SPP; ++x) {
1211 2950656 SlotSrc& sl = slots[y][x];
1212
2/2
✓ Branch 0 taken 93735 times.
✓ Branch 1 taken 1381593 times.
2950656 uint32_t avail = sl.len > p0 ? std::min(n, sl.len - p0) : 0;
1213 2950656 uint32_t p = 0;
1214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1475328 times.
2950656 if (sl.buf) {
1215 ✗ memcpy(col[x], sl.buf + p0, avail);
1216 ✗ p = avail;
1217
2/2
✓ Branch 0 taken 94793 times.
✓ Branch 1 taken 1380535 times.
2950656 } else if (sl.ps) {
1218 // A protocol preamble (TM1814's C1/C2 current words) is
1219 // constant and leads the port's stream, so it is consumed
1220 // out of the first tile before the virtual strings start.
1221 189586 const auto& pre = sl.ps->m_preamble;
1222
4/6
✓ Branch 0 taken 93735 times.
✓ Branch 1 taken 1058 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 93735 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 94793 times.
189586 while (p < avail && (p0 + p) < pre.size()) {
1223 ✗ col[x][p] = pre[p0 + p];
1224 ✗ ++p;
1225 }
1226 189586 auto& vstrings = sl.ps->m_virtualStrings;
1227
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 94793 times.
189586 if (sl.ps->m_bytesPerChannel == 2) {
1228 // 16 bit part: two bytes per channel, most significant
1229 // first. vsOff counts wire bytes here rather than
1230 // channels, so the channel index is vsOff >> 1. Kept
1231 // as its own loop so the 8 bit path below is untouched.
1232 ✗ while (p < avail && sl.vsIdx < vstrings.size()) {
1233 ✗ auto& vs = vstrings[sl.vsIdx];
1234 ✗ uint32_t vsBytes = (uint32_t)vs.chMapCount * 2;
1235 ✗ uint32_t m = std::min(avail - p, vsBytes - sl.vsOff);
1236 ✗ const uint16_t* br = vs.brightnessMap16;
1237 ✗ const int* mp = vs.chMap;
1238 ✗ for (uint32_t k = 0; k < m; ++k) {
1239 ✗ uint32_t wb = sl.vsOff + k;
1240 ✗ uint16_t v = br[channelData[mp[wb >> 1]]];
1241 ✗ col[x][p++] = (wb & 1) ? (uint8_t)v : (uint8_t)(v >> 8);
1242 }
1243 ✗ sl.vsOff += m;
1244 ✗ if (sl.vsOff >= vsBytes) {
1245 ✗ sl.vsOff = 0;
1246 ✗ sl.vsIdx++;
1247 }
1248 }
1249 ✗ if (p < n) {
1250 ✗ memset(&col[x][p], sl.pad, n - p);
1251 }
1252 ✗ continue;
1253 ✗ }
1254
5/6
✓ Branch 0 taken 93735 times.
✓ Branch 1 taken 94793 times.
✓ Branch 2 taken 93735 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 93735 times.
✓ Branch 5 taken 94793 times.
377056 while (p < avail && sl.vsIdx < vstrings.size()) {
1255 187470 auto& vs = vstrings[sl.vsIdx];
1256 187470 uint32_t m = std::min(avail - p, (uint32_t)vs.chMapCount - sl.vsOff);
1257 187470 const uint8_t* br = vs.brightnessMap;
1258 187470 const int* mp = vs.chMap;
1259
1/2
✓ Branch 0 taken 93735 times.
✗ Branch 1 not taken.
187470 if (sl.affine[sl.vsIdx]) {
1260 187470 uint32_t c = sl.vsOff % 3;
1261 187470 int32_t base = (int32_t)(sl.vsOff - c);
1262
2/2
✓ Branch 0 taken 4752036 times.
✓ Branch 1 taken 93735 times.
9691542 for (uint32_t k = 0; k < m; ++k) {
1263 9504072 col[x][p++] = br[channelData[mp[c] + base]];
1264
2/2
✓ Branch 0 taken 1584012 times.
✓ Branch 1 taken 3168024 times.
9504072 if (++c == 3) {
1265 3168024 c = 0;
1266 3168024 base += 3;
1267 }
1268 }
1269 } else {
1270 ✗ const int* mo = mp + sl.vsOff;
1271 ✗ for (uint32_t k = 0; k < m; ++k) {
1272 ✗ col[x][p++] = br[channelData[mo[k]]];
1273 }
1274 }
1275 187470 sl.vsOff += m;
1276
2/2
✓ Branch 0 taken 38005 times.
✓ Branch 1 taken 55730 times.
187470 if (sl.vsOff >= (uint32_t)vs.chMapCount) {
1277 76010 sl.vsOff = 0;
1278 76010 sl.vsIdx++;
1279 }
1280 }
1281 }
1282
2/2
✓ Branch 0 taken 1381593 times.
✓ Branch 1 taken 93735 times.
2950656 if (p < n) {
1283 2763186 memset(&col[x][p], sl.pad, n - p);
1284 }
1285 }
1286 366432 uint32_t g = 0;
1287
2/2
✓ Branch 0 taken 1113344 times.
✓ Branch 1 taken 183216 times.
2593120 for (; g < nFull; g += 8) {
1288 // transpose8x8 handles eight strings at a time, so a 16 deep
1289 // chain needs one pass per half, each landing in its own half
1290 // of the pin's slot range
1291
2/2
✓ Branch 0 taken 1121744 times.
✓ Branch 1 taken 1113344 times.
4470176 for (int h = 0; h < SPP / 8; ++h) {
1292 uint8x8_t r[8];
1293
2/2
✓ Branch 0 taken 8973952 times.
✓ Branch 1 taken 1121744 times.
20191392 for (int x = 0; x < 8; ++x) {
1294
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
35895808 r[x] = vld1_u8(&col[h * 8 + x][g]);
1295 }
1296 2243488 transpose8x8(r);
1297
2/2
✓ Branch 0 taken 8973952 times.
✓ Branch 1 taken 1121744 times.
20191392 for (int i = 0; i < 8; i++) {
1298
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
17947904 vst1_u8(&tile[(g + i) * NSTR + y * SPP + h * 8], r[i]);
1299 }
1300 }
1301 }
1302
2/2
✓ Branch 0 taken 429008 times.
✓ Branch 1 taken 183216 times.
1224448 for (uint32_t p = g; p < n; ++p) {
1303
2/2
✓ Branch 0 taken 3470464 times.
✓ Branch 1 taken 429008 times.
7798944 for (int x = 0; x < SPP; ++x) {
1304 6940928 tile[p * NSTR + y * SPP + x] = col[x][p];
1305 }
1306 }
1307 }
1308
1/2
✓ Branch 0 taken 22902 times.
✗ Branch 1 not taken.
45804 bitFlipDataT<SPP>(tile, d.formattedData + (size_t)p0 * NSTR, n);
1309 }
1310 }
1311
1312 // The firmware counts the frames it had to re-seat onto the published frame
1313 // start (see CONT_DATA in BBShiftString.asm). In a healthy system this stays
1314 // zero for the life of the output; anything else means the two sides disagreed
1315 // about a frame's length and the resync covered for it, at the cost of one
1316 // glitched frame.
1317 186 void BBShiftStringOutput::reportRingResync(FrameData& d, int pru) {
1318 #ifndef PLATFORM_BBB
1319
2/2
✓ Branch 0 taken 93 times.
✓ Branch 1 taken 93 times.
186 if (!d.pruData) {
1320 93 return;
1321 }
1322 93 uint32_t n = *(volatile uint32_t*)((uint8_t*)d.pruData + SMEM_RING_RESYNC_OFFSET);
1323
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 92 times.
93 if (n != d.resyncCount) {
1324 1 LogWarn(VB_CHANNELOUT, "BBShiftString: PRU%d ring read position re-seated %u time(s) (%u total); a frame was glitched but the output is aligned\n",
1325 pru, n - d.resyncCount, n);
1326 1 d.resyncCount = n;
1327 }
1328 #endif
1329 ✗ }
1330
1331 19964 void BBShiftStringOutput::prepData(FrameData& d, unsigned char* channelData) {
1332
2/2
✓ Branch 0 taken 300 times.
✓ Branch 1 taken 19664 times.
19964 if (m_stringsPerPin == 16) {
1333 300 prepDataT<16>(d, channelData);
1334 } else {
1335 19664 prepDataT<8>(d, channelData);
1336 }
1337 19964 }
1338
1339 9982 void BBShiftStringOutput::PrepData(unsigned char* channelData) {
1340 9982 LogExcess(VB_CHANNELOUT, "BBShiftStringOutput::PrepData(%p)\n", channelData);
1341
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9982 times.
9982 if (!hasStrings()) {
1342 ✗ return;
1343 }
1344 // auto start = std::chrono::high_resolution_clock::now();
1345 9982 m_curFrame++;
1346
1347 // the firmware heals a ring misalignment on its own, but it should never
1348 // have to - surface it rather than let it stay silent
1349
2/2
✓ Branch 0 taken 93 times.
✓ Branch 1 taken 9889 times.
9982 if ((m_curFrame % 100) == 0) {
1350 93 reportRingResync(m_pru0, 0);
1351 93 reportRingResync(m_pru1, 1);
1352 }
1353
1354 9982 prepData(m_pru0, channelData);
1355 9982 prepData(m_pru1, channelData);
1356 9982 m_testCycle = -1;
1357
1358
6/6
✓ Branch 0 taken 9078 times.
✓ Branch 1 taken 904 times.
✓ Branch 2 taken 539 times.
✓ Branch 3 taken 8539 times.
✓ Branch 4 taken 489 times.
✓ Branch 5 taken 50 times.
9982 if (m_pru1.curV5ConfigPacket > FIRST_LOOPING_CONFIG_PACKET && falconV5Support && m_pru1.v5_config_packets[m_pru1.curV5ConfigPacket] == nullptr) {
1359 489 std::vector<std::array<uint8_t, 64>> packets;
1360
1/2
✓ Branch 0 taken 489 times.
✗ Branch 1 not taken.
489 packets.resize(m_strings.size());
1361
2/2
✓ Branch 0 taken 6276 times.
✓ Branch 1 taken 489 times.
6765 for (auto& p : packets) {
1362 6276 memset(&p[0], 0, 64);
1363 }
1364 489 bool listen = false;
1365
3/4
✓ Branch 0 taken 489 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
✓ Branch 3 taken 249 times.
489 if (falconV5Support->generateDynamicPacket(packets, listen)) {
1366
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 applySendOnlyConfigPackets(packets);
1367
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 120 times.
240 if (m_pru0.dynamicPacketInfo == &m_pru0.dynamicPacketInfo1) {
1368 120 m_pru0.dynamicPacketInfo = &m_pru0.dynamicPacketInfo2;
1369 120 m_pru1.dynamicPacketInfo = &m_pru1.dynamicPacketInfo2;
1370 } else {
1371 120 m_pru0.dynamicPacketInfo = &m_pru0.dynamicPacketInfo1;
1372 120 m_pru1.dynamicPacketInfo = &m_pru1.dynamicPacketInfo1;
1373 }
1374
1/2
✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
240 encodeFalconV5Packet(packets, m_pru0.dynamicPacketInfo->data, m_pru1.dynamicPacketInfo->data);
1375 240 m_pru0.dynamicPacketInfo->listen = listen;
1376 240 m_pru1.dynamicPacketInfo->listen = listen;
1377 240 m_pru0.v5_config_packets[m_pru0.curV5ConfigPacket] = m_pru0.dynamicPacketInfo;
1378 240 m_pru1.v5_config_packets[m_pru1.curV5ConfigPacket] = m_pru1.dynamicPacketInfo;
1379 }
1380 489 }
1381
1382 /*
1383 uint32_t *iframe = (uint32_t*)m_pru1.curData;
1384 for (int x = 0; x < 3; x++) {
1385 for (int y = 0; y < 8; y++) {
1386 printf("%8X ", iframe[y]);
1387 }
1388 printf("\n");
1389 iframe += 8;
1390 }
1391 printf("\n");
1392 */
1393 // auto finish = std::chrono::high_resolution_clock::now();
1394 // auto total =std::chrono::duration_cast<std::chrono::nanoseconds>(finish-start).count();
1395 // printf("Total: %lld\n", total);
1396 }
1397
1398 5314 void BBShiftStringOutput::sendData(FrameData& d) {
1399
2/2
✓ Branch 0 taken 2649 times.
✓ Branch 1 taken 2665 times.
5314 if (d.outputStringLen) {
1400 #ifdef PLATFORM_BBB
1401 // only the bytes the PRU will consume need to be copied/flushed
1402 ✗ uint32_t bytes = d.outputStringLen * stringsPerPru();
1403 ✗ if (bytes > d.frameSize) {
1404 ✗ bytes = d.frameSize;
1405 }
1406 ✗ memcpy(d.curData, d.formattedData, bytes);
1407 // make sure memory is flushed
1408 ✗ msync(d.curData, bytes, MS_SYNC | MS_INVALIDATE);
1409 ✗ __builtin___clear_cache(d.curData, d.curData + bytes);
1410
1411 ✗ d.pruData->address_dma = (d.pru->ddr_addr + (d.curData - d.pru->ddr));
1412 ✗ if (d.v5_config_packets[d.curV5ConfigPacket]) {
1413 ✗ d.pruData->address_dma_packet = (d.pru->ddr_addr + (d.v5_config_packets[d.curV5ConfigPacket]->data - d.pru->ddr));
1414 } else {
1415 ✗ d.pruData->address_dma_packet = 0;
1416 }
1417 ✗ std::swap(d.lastData, d.curData);
1418 #else
1419 // the pump thread streams the frame (and any FalconV5 packet data)
1420 // into the shared memory ring in exactly the order the firmware
1421 // consumes blocks; the PRU consumes one 64 byte block per byte of
1422 // string data
1423 2649 uint32_t bytes = d.outputStringLen * stringsPerPru();
1424
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2649 times.
2649 if (bytes > d.frameSize) {
1425 ✗ bytes = d.frameSize;
1426 }
1427 2649 memcpy(d.curData, d.formattedData, bytes);
1428 2649 d.pendingFrame.frameData = d.curData;
1429 2649 d.pendingFrame.frameBytes = bytes;
1430 2649 auto* pi = d.v5_config_packets[d.curV5ConfigPacket];
1431
3/4
✓ Branch 0 taken 299 times.
✓ Branch 1 taken 2350 times.
✓ Branch 2 taken 299 times.
✗ Branch 3 not taken.
2649 if (pi && pi->data) {
1432 299 d.pendingFrame.packetData = pi->data;
1433 299 d.pendingFrame.packetBytes = 57 * stringsPerPru() * pi->len;
1434 } else {
1435 2350 d.pendingFrame.packetData = nullptr;
1436 2350 d.pendingFrame.packetBytes = 0;
1437 }
1438 2649 std::swap(d.lastData, d.curData);
1439 #endif
1440 }
1441 5314 }
1442
1443 // Persistent UI warning for "the sequence rate is past what these strings can
1444 // clock out". The flag is per output so Close() can retract it; a warning left
1445 // behind by an output that no longer exists has nothing left to clear it.
1446 98 void BBShiftStringOutput::setFrameRateWarning(bool on) {
1447 static const std::string BP_WARN =
1448
4/8
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 70 times.
✓ Branch 2 taken 28 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
154 "Sequence frame rate is higher than the configured pixel strings can output; frames are being dropped";
1449
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 4 times.
98 if (on == m_bpWarned) {
1450 94 return;
1451 }
1452
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 if (on) {
1453
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 WarningHolder::AddWarning(63, BP_WARN);
1454 } else {
1455
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
4 WarningHolder::RemoveWarning(63, BP_WARN);
1456 }
1457 4 m_bpWarned = on;
1458 }
1459
1460 // Predictive companion to the measured warning above, run whenever the
1461 // refresh rate changes. Sequence.cpp publishes the sequence's rate before the
1462 // first frame reaches SendData(), so an over-budget configuration is flagged
1463 // immediately instead of after a minute of measured drops - and a rate that
1464 // changes mid-playlist (sequences at different frame rates back to back, where
1465 // the output thread never restarts) is caught on the next frame.
1466 34 void BBShiftStringOutput::checkFrameRateBudget(float rate) {
1467
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 34 times.
34 if (m_frameTimeUs <= 0) {
1468 ✗ return;
1469 }
1470 34 float ceiling = 1000000.0f / m_frameTimeUs;
1471 // 5% grace: covers the overhead constants' own uncertainty and keeps a
1472 // config a hair past budget - absorbed invisibly by the back-pressure
1473 // gate - quiet
1474
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 33 times.
34 if (rate > ceiling * 1.05f) {
1475 char buf[192];
1476 1 snprintf(buf, sizeof(buf),
1477 "Sequence frame rate (%.4g fps) is higher than the configured pixel strings can output (~%.4g fps); frames will be dropped",
1478 rate, ceiling);
1479
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 std::string msg = buf;
1480
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 if (msg != m_budgetWarnText) {
1481
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 clearBudgetWarning();
1482
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 LogWarn(VB_CHANNELOUT, "BBShiftString: %s\n", msg.c_str());
1483
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 WarningHolder::AddWarning(63, msg);
1484
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_budgetWarnText = msg;
1485 }
1486 1 } else {
1487 33 clearBudgetWarning();
1488 }
1489 }
1490
1491 130 void BBShiftStringOutput::clearBudgetWarning() {
1492
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 129 times.
130 if (!m_budgetWarnText.empty()) {
1493
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 WarningHolder::RemoveWarning(63, m_budgetWarnText);
1494 1 m_budgetWarnText.clear();
1495 }
1496 130 }
1497
1498 9931 int BBShiftStringOutput::SendData(unsigned char* channelData) {
1499 9931 LogExcess(VB_CHANNELOUT, "BBShiftStringOutput::SendData(%p)\n", channelData);
1500
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9931 times.
9931 if (!hasStrings()) {
1501 ✗ return 0;
1502 }
1503
1504 9931 float bwRate = GetChannelOutputRefreshRate();
1505
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 9897 times.
9931 if (bwRate != m_lastBudgetRate) {
1506 34 m_lastBudgetRate = bwRate;
1507 34 checkFrameRateBudget(bwRate);
1508 }
1509
1510
2/2
✓ Branch 0 taken 568 times.
✓ Branch 1 taken 9363 times.
9931 if (falconV5Support) {
1511 568 falconV5Support->processListenerData();
1512 }
1513
1514 #ifndef PLATFORM_BBB
1515 // ---- back-pressure gate --------------------------------------------------
1516 // pumpFrameData() only snapshots pendingFrame once the firmware is ready for
1517 // another frame (command == 0 && ring drained). A frame staged before that
1518 // point is simply overwritten and never rendered. When the requested frame
1519 // rate exceeds what the configured string length can physically clock out,
1520 // that surplus of never-rendered frames is what eventually leaves the PRU
1521 // ring misaligned (#2855). Declining the frame while the previous one is
1522 // still pending drives the surplus to zero at whatever rate the hardware
1523 // actually sustains - frame dropping at the source, with no fixed divisor
1524 // to mis-tune, and configurations already inside their budget are untouched
1525 // (nothing is pending when the next frame arrives, so nothing is declined).
1526 //
1527 // It is also what keeps the two-phase write of pendingFrame safe: sendData()
1528 // fills the byte counts and the block below fills the command, and only a
1529 // frame the pump has already taken may be restaged, so the pump can never
1530 // snapshot one phase of one frame with the other phase of the next. Ditto
1531 // the FalconV5 packet cursor - curV5ConfigPacket only advances for a frame
1532 // that will actually be streamed, where before, over budget, better than
1533 // half the receiver's config and dynamic packets were marked consumed and
1534 // never sent.
1535 //
1536 // AM335x never reaches this: it has no ring and no pump, every frame
1537 // restates its own DDR address and length, and pendingSeq is never bumped.
1538 //
1539 // Cannot wedge: we only decline while pendingSeq != pumpedSeq, so
1540 // pumpFrameData() still enters its busy branch and its 250ms watchdog
1541 // still runs. A PRU is only party to the gate if the pump actually
1542 // services it (pump thread condition below) - otherwise its pendingSeq
1543 // would climb against a pumpedSeq nothing advances and every frame,
1544 // including the other PRU's, would be declined forever.
1545 //
1546 // A declined frame is not retried, so a one-shot frame can land late: the
1547 // end-of-sequence blank is offered three times (the forced output, then
1548 // twice more as onceMore unwinds), the last of those BridgeLightDelay
1549 // (E131BridgingInterval, 50ms default) after the one before it, which is
1550 // past the drain time of any frame this gate declines. Set that interval
1551 // below a frame's clocking time and blanking would be delayed further.
1552 {
1553 9931 bool taken = true;
1554
3/6
✓ Branch 0 taken 9931 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9931 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 9931 times.
✗ Branch 5 not taken.
9931 if (m_pru1.pru && m_pru1.ring.attached()) {
1555
1/2
✓ Branch 0 taken 9931 times.
✗ Branch 1 not taken.
19862 taken = taken && (m_pru1.pendingSeq.load(std::memory_order_acquire) ==
1556
2/2
✓ Branch 0 taken 2657 times.
✓ Branch 1 taken 7274 times.
19862 m_pru1.pumpedSeq.load(std::memory_order_acquire));
1557 }
1558
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 9931 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 9931 times.
9931 if (m_pru0.pru && m_pru0.ring.attached()) {
1559 ✗ taken = taken && (m_pru0.pendingSeq.load(std::memory_order_acquire) ==
1560 ✗ m_pru0.pumpedSeq.load(std::memory_order_acquire));
1561 }
1562 9931 ++m_bpOffered;
1563
2/2
✓ Branch 0 taken 7274 times.
✓ Branch 1 taken 2657 times.
9931 if (!taken) {
1564 7274 ++m_bpDeclined;
1565 }
1566 // report on a wall-clock window rather than a frame count so the
1567 // hysteresis reacts at the same speed whatever the sequence rate
1568 9931 auto now = std::chrono::steady_clock::now();
1569
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 9899 times.
9931 if (m_bpWindowStart.time_since_epoch().count() == 0) {
1570 32 m_bpWindowStart = now;
1571
4/6
✓ Branch 0 taken 9899 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 9899 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 9897 times.
9899 } else if ((now - m_bpWindowStart) >= std::chrono::seconds(60)) {
1572
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (m_bpDeclined) {
1573
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 LogWarn(VB_CHANNELOUT,
1574 "BBShiftString: back-pressure gate declined %u of %u frames (%.1f%%)\n",
1575 m_bpDeclined, m_bpOffered, 100.0 * m_bpDeclined / m_bpOffered);
1576 ✗ } else if (m_bpLastDeclined) {
1577 // A clean window is the normal state, so only the transition
1578 // back to it is worth a line; one per window at the default
1579 // level filled the log on every healthy output.
1580 ✗ LogInfo(VB_CHANNELOUT, "BBShiftString: back-pressure gate declined no frames of %u (recovered)\n",
1581 m_bpOffered);
1582 }
1583 2 m_bpLastDeclined = m_bpDeclined != 0;
1584 // Surface a persistent UI warning once the sequence rate is clearly
1585 // beyond what the strings can output; clear it again when the rate
1586 // drops back (e.g. a different sequence starts). The thresholds
1587 // are hysteresis: on at >=10% dropped, off again below 2%.
1588
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (m_bpDeclined * 10 >= m_bpOffered) {
1589
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 setFrameRateWarning(true);
1590 ✗ } else if (m_bpDeclined * 50 <= m_bpOffered) {
1591 ✗ setFrameRateWarning(false);
1592 }
1593 2 m_bpOffered = 0;
1594 2 m_bpDeclined = 0;
1595 2 m_bpWindowStart = now;
1596 }
1597
2/2
✓ Branch 0 taken 7274 times.
✓ Branch 1 taken 2657 times.
9931 if (!taken) {
1598 7274 return m_channelCount;
1599 }
1600 }
1601 // --------------------------------------------------------------------------
1602 #endif
1603
1604 2657 sendData(m_pru0);
1605 2657 sendData(m_pru1);
1606 // make sure memory is flushed before command is set to 1
1607 2657 __asm__ __volatile__("" ::
1608 : "memory");
1609
1610 // Send the start command. A port set with no data to send must not get a
1611 // command at all: sendData() above leaves pendingFrame empty in that case,
1612 // so the command would name a frame the pump never streams. The length is
1613 // also not enough on its own to make it a no-op - a zero length still ORs
1614 // in the custom-length flag below, and the firmware's render loop reads its
1615 // first block before testing the count, so a "zero byte" frame still eats a
1616 // ring block the ARM never wrote. That block offsets every later frame by
1617 // one byte per string for as long as the output lives, and because both
1618 // sides still move the same number of blocks per frame the ring keeps
1619 // reaching empty and drained() never notices. A freshly Init()ed output
1620 // hits this on a config reload: PrepareChannelData() and SendChannelData()
1621 // are separate walks of the output list, so the replacement can pick up its
1622 // first SendData() before its first PrepData() has set outputStringLen.
1623
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2657 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2657 if (m_pru0.pruData && m_pru0.outputStringLen) {
1624 ✗ uint32_t c = m_pru0.outputStringLen;
1625 ✗ if (m_pru0.v5_config_packets[m_pru0.curV5ConfigPacket]) {
1626 ✗ c |= m_pru0.v5_config_packets[m_pru0.curV5ConfigPacket]->getCommandFlags();
1627 ✗ if (m_pru0.v5_config_packets[m_pru0.curV5ConfigPacket] == m_pru0.dynamicPacketInfo) {
1628 ✗ m_pru0.v5_config_packets[m_pru0.curV5ConfigPacket] = nullptr;
1629 }
1630 }
1631 ✗ if (m_pru0.outputStringLen != m_pru0.maxStringLen) {
1632 ✗ c |= 0x10000; // flag that the output len is custom and ignore the off config
1633 }
1634 ✗ m_pru0.curV5ConfigPacket++;
1635 ✗ if (m_pru0.curV5ConfigPacket == NUM_CONFIG_PACKETS) {
1636 ✗ m_pru0.curV5ConfigPacket = FIRST_LOOPING_CONFIG_PACKET;
1637 }
1638 #ifdef PLATFORM_BBB
1639 ✗ m_pru0.pruData->command = c;
1640 #else
1641 // the pump thread writes the command once the PRU has taken the
1642 // previous one; the gate above guarantees the previous one is gone,
1643 // so this can never overwrite a frame the pump has yet to snapshot
1644 ✗ m_pru0.pendingFrame.command = c;
1645 ✗ m_pru0.pendingSeq.fetch_add(1, std::memory_order_release);
1646 #endif
1647 }
1648
3/4
✓ Branch 0 taken 2657 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2649 times.
✓ Branch 3 taken 8 times.
2657 if (m_pru1.pruData && m_pru1.outputStringLen) {
1649 2649 uint32_t c = m_pru1.outputStringLen;
1650
2/2
✓ Branch 0 taken 299 times.
✓ Branch 1 taken 2350 times.
2649 if (m_pru1.v5_config_packets[m_pru1.curV5ConfigPacket]) {
1651 299 c |= m_pru1.v5_config_packets[m_pru1.curV5ConfigPacket]->getCommandFlags();
1652
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 59 times.
299 if (m_pru1.v5_config_packets[m_pru1.curV5ConfigPacket] == m_pru1.dynamicPacketInfo) {
1653 240 m_pru1.v5_config_packets[m_pru1.curV5ConfigPacket] = nullptr;
1654 }
1655 }
1656
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2649 times.
2649 if (m_pru1.outputStringLen != m_pru1.maxStringLen) {
1657 ✗ c |= 0x10000; // flag that the output len is custom and ignore the off config
1658 }
1659 2649 m_pru1.curV5ConfigPacket++;
1660
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 2628 times.
2649 if (m_pru1.curV5ConfigPacket == NUM_CONFIG_PACKETS) {
1661 21 m_pru1.curV5ConfigPacket = FIRST_LOOPING_CONFIG_PACKET;
1662 }
1663 #ifdef PLATFORM_BBB
1664 ✗ m_pru1.pruData->command = c;
1665 #else
1666 2649 m_pru1.pendingFrame.command = c;
1667 2649 m_pru1.pendingSeq.fetch_add(1, std::memory_order_release);
1668 #endif
1669 }
1670 2657 __asm__ __volatile__("" ::
1671 : "memory");
1672
1673 2657 return m_channelCount;
1674 }
1675
1676 #ifndef PLATFORM_BBB
1677 // Stream the pending frame into the shared memory ring; returns true if any
1678 // progress was made. The full frame does not fit in the ring, so the PRU is
1679 // started as soon as a frame is taken and the data streams ~2.5ms ahead of
1680 // the output.
1681 560002 bool BBShiftStringOutput::pumpFrameData(FrameData& d) {
1682
1/2
✓ Branch 0 taken 560002 times.
✗ Branch 1 not taken.
560002 if (!d.pumpActive) {
1683 560002 uint32_t seq = d.pendingSeq.load(std::memory_order_acquire);
1684 // The command word says how many blocks to render but not WHERE the
1685 // frame starts - the firmware just reads on from wherever it stopped.
1686 // So a command may only be issued when the ring is empty, which is the
1687 // only moment the read pointer provably sits on this frame's first
1688 // byte. Without this the two channels drift apart by whatever was
1689 // still buffered when the first command went out, and every frame
1690 // after that renders that many bytes into the previous one. Streaming
1691 // ahead within a frame is unaffected (that is the loop below).
1692
2/2
✓ Branch 0 taken 308674 times.
✓ Branch 1 taken 251328 times.
1120004 if (seq == d.pumpedSeq.load(std::memory_order_relaxed)) {
1693 308674 return false;
1694 }
1695
5/6
✓ Branch 0 taken 2638 times.
✓ Branch 1 taken 248690 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 2638 times.
✓ Branch 4 taken 248690 times.
✓ Branch 5 taken 2638 times.
251328 if (d.pruData->command != 0 || !d.ring.drained()) {
1696 // The firmware has not finished with what it was already given.
1697 // That clears inside a frame time in normal operation; if it does
1698 // not, it is starved on a frame whose data never arrived (or on a
1699 // command that went missing), and waiting on the ring alone would
1700 // leave the output dead for good. Re-seat on its read pointer and
1701 // carry on rather than wedge.
1702 248690 uint32_t rendered = *(volatile uint32_t*)((uint8_t*)d.pruData + SMEM_RING_FRAMES_OFFSET);
1703 248690 auto now = std::chrono::steady_clock::now();
1704
5/6
✓ Branch 0 taken 248690 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 513 times.
✓ Branch 3 taken 248177 times.
✓ Branch 4 taken 513 times.
✓ Branch 5 taken 248177 times.
248690 if (rendered != d.renderedFrames || d.stalledSince.time_since_epoch().count() == 0) {
1705 513 d.renderedFrames = rendered;
1706 513 d.stalledSince = now;
1707
4/6
✓ Branch 0 taken 248177 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 248177 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 502 times.
✓ Branch 5 taken 247675 times.
248177 } else if ((now - d.stalledSince) > std::chrono::milliseconds(250)) {
1708
1/2
✓ Branch 0 taken 502 times.
✗ Branch 1 not taken.
502 LogWarn(VB_CHANNELOUT, "BBShiftString: firmware rendered no frame for 250ms (command 0x%X, %u bytes unread); resynchronizing the ring\n",
1709 (unsigned)d.pruData->command, d.ring.usedBytes());
1710 502 d.pruData->command = 0;
1711 502 d.ring.seekToConsumer();
1712 502 d.stalledSince = now;
1713 }
1714 248690 return false;
1715 }
1716 2638 d.stalledSince = {};
1717 // Snapshot the newest pending frame; retry if SendData raced us.
1718 // pumpedSeq is published only once the copy is known good, never
1719 // before it: the back-pressure gate in SendData() treats
1720 // pumpedSeq == pendingSeq as its licence to restage, so publishing
1721 // first would invite the output thread to rewrite pendingFrame while
1722 // this copy is still reading it.
1723 while (true) {
1724 2638 uint32_t snapped = seq;
1725 2638 d.activeFrame = d.pendingFrame;
1726 2638 seq = d.pendingSeq.load(std::memory_order_acquire);
1727
1/2
✓ Branch 0 taken 2638 times.
✗ Branch 1 not taken.
2638 if (seq == snapped) {
1728 2638 d.pumpedSeq.store(snapped, std::memory_order_release);
1729 2638 break;
1730 }
1731 ✗ }
1732 2638 d.activeOff = 0;
1733 2638 d.pumpActive = true;
1734 // Publish where in the ring this frame starts before the command that
1735 // makes the firmware act on it - nothing has been written for this
1736 // frame yet, so the write position is its first byte. The firmware
1737 // takes its read position from this and zeroes it, so the two only
1738 // ever move together. This is what keeps a one-off disagreement about
1739 // a frame's length from becoming permanent: the drain gate above
1740 // cannot catch that on its own, because a frame offset by a whole
1741 // number of blocks empties the ring exactly like one offset by none.
1742 2638 d.pruData->address_dma = d.ring.writePos();
1743 2638 __sync_synchronize();
1744 2638 d.pruData->command = d.activeFrame.command;
1745 }
1746 2638 uint32_t total = d.activeFrame.frameBytes + d.activeFrame.packetBytes;
1747 2638 bool progress = false;
1748
2/2
✓ Branch 0 taken 2935 times.
✓ Branch 1 taken 2638 times.
5573 while (d.activeOff < total) {
1749 const uint8_t* src;
1750 uint32_t remaining;
1751
2/2
✓ Branch 0 taken 2638 times.
✓ Branch 1 taken 297 times.
2935 if (d.activeOff < d.activeFrame.frameBytes) {
1752 2638 src = d.activeFrame.frameData + d.activeOff;
1753 2638 remaining = d.activeFrame.frameBytes - d.activeOff;
1754 } else {
1755 297 src = d.activeFrame.packetData + (d.activeOff - d.activeFrame.frameBytes);
1756 297 remaining = total - d.activeOff;
1757 }
1758 2935 uint32_t n = d.ring.write(src, remaining);
1759
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2935 times.
2935 if (n == 0) {
1760 ✗ break;
1761 }
1762 2935 d.activeOff += n;
1763 2935 progress = true;
1764 }
1765
1/2
✓ Branch 0 taken 2638 times.
✗ Branch 1 not taken.
2638 if (d.activeOff == total) {
1766 2638 d.pumpActive = false;
1767 }
1768 2638 return progress;
1769 }
1770
1771 47 void BBShiftStringOutput::runPumpThread() {
1772 struct sched_param sp;
1773 47 sp.sched_priority = 50;
1774 47 int err = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp);
1775
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 47 times.
47 if (err) {
1776 ✗ LogWarn(VB_CHANNELOUT, "BBShiftString: could not set pump thread to SCHED_FIFO: %s\n", strerror(err));
1777 }
1778
2/2
✓ Branch 0 taken 560002 times.
✓ Branch 1 taken 47 times.
560049 while (m_pumpRunning) {
1779 560002 bool p = false;
1780
2/6
✗ Branch 0 not taken.
✓ Branch 1 taken 560002 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 560002 times.
560002 if (m_pru0.pru && m_pru0.ring.attached()) {
1781 ✗ p |= pumpFrameData(m_pru0);
1782 }
1783
3/6
✓ Branch 0 taken 560002 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 560002 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 560002 times.
✗ Branch 5 not taken.
560002 if (m_pru1.pru && m_pru1.ring.attached()) {
1784
1/2
✓ Branch 0 taken 560002 times.
✗ Branch 1 not taken.
560002 p |= pumpFrameData(m_pru1);
1785 }
1786
2/2
✓ Branch 0 taken 88030 times.
✓ Branch 1 taken 471972 times.
560002 if (falconV5Support) {
1787 // take a finished receiver reply out of the listener's shared RAM
1788 // as soon as it is published, before the next listen window can
1789 // overwrite it; the output thread decodes it on its next frame
1790
1/2
✓ Branch 0 taken 88030 times.
✗ Branch 1 not taken.
88030 falconV5Support->stageListenerData();
1791 }
1792
2/2
✓ Branch 0 taken 557364 times.
✓ Branch 1 taken 2638 times.
560002 if (!p) {
1793 557364 struct timespec ts = { 0, 500000 };
1794
1/2
✓ Branch 0 taken 557364 times.
✗ Branch 1 not taken.
557364 nanosleep(&ts, nullptr);
1795 }
1796 }
1797 47 }
1798 #endif
1799
1800 /*
1801 *
1802 */
1803 49 void BBShiftStringOutput::DumpConfig(void) {
1804 49 LogDebug(VB_CHANNELOUT, "BBShiftStringOutput::DumpConfig()\n");
1805 49 LogDebug(VB_CHANNELOUT, " type : %s\n", m_subType.c_str());
1806 49 LogDebug(VB_CHANNELOUT, " strings : %d\n", m_strings.size());
1807 49 LogDebug(VB_CHANNELOUT, " longest string: %d channels\n",
1808 std::max(m_pru0.maxStringLen, m_pru1.maxStringLen));
1809
1810
2/2
✓ Branch 0 taken 310 times.
✓ Branch 1 taken 49 times.
359 for (int i = 0; i < m_strings.size(); i++) {
1811 310 LogDebug(VB_CHANNELOUT, " string #%02d\n", i);
1812 310 m_strings[i]->DumpConfig();
1813 }
1814
1815 49 ChannelOutput::DumpConfig();
1816 49 }
1817 33 void BBShiftStringOutput::StartingOutput() {
1818 33 m_pru1.curV5ConfigPacket = 0;
1819 33 m_pru0.curV5ConfigPacket = 0;
1820 33 }
1821 33 void BBShiftStringOutput::StoppingOutput() {
1822
2/2
✓ Branch 0 taken 3366 times.
✓ Branch 1 taken 33 times.
3399 for (int y = 0; y < NUM_CONFIG_PACKETS; ++y) {
1823
2/4
✓ Branch 0 taken 3366 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 3366 times.
3366 if (m_pru1.v5_config_packets[y] == &m_pru1.dynamicPacketInfo2 || m_pru1.v5_config_packets[y] == &m_pru1.dynamicPacketInfo1) {
1824 ✗ m_pru1.v5_config_packets[y] = nullptr;
1825 }
1826
3/4
✓ Branch 0 taken 3366 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 3365 times.
3366 if (m_pru0.v5_config_packets[y] == &m_pru0.dynamicPacketInfo2 || m_pru0.v5_config_packets[y] == &m_pru0.dynamicPacketInfo1) {
1827 1 m_pru0.v5_config_packets[y] = nullptr;
1828 }
1829 }
1830 33 m_pru1.curV5ConfigPacket = 0;
1831 33 m_pru0.curV5ConfigPacket = 0;
1832 33 }
1833 constexpr int numPacketTypes = 12;
1834 // No inversion pass here: config packets only exist for a chain's first port,
1835 // and demoteInvertedReceiverChains() has already dropped any chain whose head
1836 // is inverted, so a packet never needs flipping.
1837 305 void BBShiftStringOutput::encodeFalconV5Packet(std::vector<std::array<uint8_t, 64>>& packets, uint8_t* memLocPru0, uint8_t* memLocPru1) {
1838 // zeroed: slots with no string mapped to them are still clocked out on
1839 // their shift stage, and must send zeros rather than old stack contents
1840 305 std::array<uint8_t, 57 * MAX_PINS_PER_PRU * MAX_STRINGS_PER_PIN> pru0Data = {};
1841 305 std::array<uint8_t, 57 * MAX_PINS_PER_PRU * MAX_STRINGS_PER_PIN> pru1Data = {};
1842
2/2
✓ Branch 0 taken 2440 times.
✓ Branch 1 taken 305 times.
2745 for (int y = 0; y < MAX_PINS_PER_PRU; ++y) {
1843 2440 uint8_t pinMask = 1 << y;
1844
2/2
✓ Branch 0 taken 19520 times.
✓ Branch 1 taken 2440 times.
21960 for (int x = 0; x < m_stringsPerPin; ++x) {
1845 19520 int idx = m_pru0.stringMap[y][x];
1846
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19520 times.
19520 if (idx != -1) {
1847 ✗ uint8_t* frame = &pru0Data[x + (y * m_stringsPerPin)];
1848 ✗ for (int p = 0; p < 57; p++) {
1849 ✗ uint8_t b = packets[idx][p];
1850 ✗ b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
1851 ✗ *frame = b;
1852 ✗ frame += stringsPerPru();
1853 }
1854 }
1855 19520 idx = m_pru1.stringMap[y][x];
1856
2/2
✓ Branch 0 taken 4148 times.
✓ Branch 1 taken 15372 times.
19520 if (idx != -1) {
1857 4148 uint8_t* frame = &pru1Data[x + (y * m_stringsPerPin)];
1858
2/2
✓ Branch 0 taken 236436 times.
✓ Branch 1 taken 4148 times.
240584 for (int p = 0; p < 57; p++) {
1859 236436 uint8_t b = packets[idx][p];
1860 236436 b = ((b * 0x0802LU & 0x22110LU) | (b * 0x8020LU & 0x88440LU)) * 0x10101LU >> 16;
1861 236436 *frame = b;
1862 236436 frame += stringsPerPru();
1863 }
1864 }
1865 }
1866 }
1867 305 size_t pLen = 57 * stringsPerPru();
1868
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 305 times.
305 if (m_pru0.maxStringLen) {
1869 ✗ bitFlipData(&pru0Data[0], memLocPru0, 57);
1870 }
1871
1/2
✓ Branch 0 taken 305 times.
✗ Branch 1 not taken.
305 if (m_pru1.maxStringLen) {
1872
1/2
✓ Branch 0 taken 305 times.
✗ Branch 1 not taken.
305 bitFlipData(&pru1Data[0], memLocPru1, 57);
1873 }
1874 305 }
1875
1876 304 void BBShiftStringOutput::applySendOnlyConfigPackets(std::vector<std::array<uint8_t, 64>>& packets) {
1877
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 304 times.
304 for (auto& [port, packet] : m_sendOnlyConfigPackets) {
1878 ✗ if (port >= 0 && port < (int)packets.size()) {
1879 ✗ packets[port] = packet;
1880 }
1881 }
1882 304 }
1883
1884 18 void BBShiftStringOutput::setupFalconV5Support(const Json::Value& root, uint8_t* memLoc) {
1885
3/8
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 18 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
18 falconV5Support = new FalconV5Support();
1886 18 m_sendOnlyConfigPackets.clear();
1887
3/4
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
18 if (supportsV5Listeners && m_hasBidirSR) {
1888
3/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 1 times.
17 falconV5Support->addListeners(root["falconV5ListenerConfig"]);
1889 }
1890
1891 17 int max = std::max(m_pru0.maxStringLen, m_pru1.maxStringLen);
1892 17 int x = 0;
1893
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 17 times.
43 while (x < m_strings.size()) {
1894 26 int p = x;
1895 26 PixelString* p1 = m_strings[x++];
1896
5/6
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 17 times.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
26 if (p1->m_isSmartReceiver && (p1->smartReceiverType == PixelString::ReceiverType::FalconV5 || p1->smartReceiverType == PixelString::ReceiverType::FalconV4)) {
1897 // V4 chains are send-only: the config packet goes out, but no
1898 // queries are scheduled and no responses are listened for
1899 18 bool sendOnly = p1->smartReceiverType == PixelString::ReceiverType::FalconV4;
1900 18 int grp = 0;
1901 18 int mux = 0;
1902
5/8
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 17 times.
✓ Branch 7 taken 1 times.
18 if (root["outputs"][p].isMember("falconV5Listener")) {
1903
4/8
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 17 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 17 times.
✗ Branch 7 not taken.
17 grp = root["outputs"][p]["falconV5Listener"].asInt();
1904 }
1905
5/8
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 18 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 18 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✓ Branch 7 taken 17 times.
18 if (root["outputs"][p].isMember("falconV5ListenerMux")) {
1906
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.
1 mux = root["outputs"][p]["falconV5ListenerMux"].asInt();
1907 }
1908
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 PixelString* p2 = x < m_strings.size() ? m_strings[x++] : nullptr;
1909
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 PixelString* p3 = x < m_strings.size() ? m_strings[x++] : nullptr;
1910
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 PixelString* p4 = x < m_strings.size() ? m_strings[x++] : nullptr;
1911
1912
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 falconV5Support->addReceiverChain(p1, p2, p3, p4, grp, mux, sendOnly);
1913 // need to keep the ports on. With v5, all ports need to be kept on
1914 // and must have edges that are aligned. Need to turn OFF the 2-4 ports during
1915 // the config packet
1916 18 p1->m_gpioCommands.clear();
1917
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 17 times.
18 if (sendOnly) {
1918 // a V4 chain gets its config packet in the packet frames that
1919 // end in the frame's low reset, and its line goes low right
1920 // after it; in a frame that opens a listen window the line
1921 // stays low for the whole packet phase
1922
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 p1->m_gpioCommands.emplace_back(1, GPIO_CMD_AFTER_FIRST_PACKET, 0, 0);
1923 }
1924
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if (p2) {
1925 18 p2->m_gpioCommands.clear();
1926
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 p2->m_gpioCommands.emplace_back(2, max, 0, 0);
1927 }
1928
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if (p3) {
1929 18 p3->m_gpioCommands.clear();
1930
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 p3->m_gpioCommands.emplace_back(3, max, 0, 0);
1931 }
1932
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 if (p4) {
1933 18 p4->m_gpioCommands.clear();
1934
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 p4->m_gpioCommands.emplace_back(4, max, 0, 0);
1935 }
1936 }
1937 }
1938
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 if (!falconV5Support->getReceiverChains().empty()) {
1939 // we have receiver chains, generate standard packets
1940
2/2
✓ Branch 0 taken 51 times.
✓ Branch 1 taken 17 times.
68 for (int x = 0; x < 3; x++) {
1941 51 std::vector<std::array<uint8_t, 64>> packets;
1942 51 std::vector<std::array<uint8_t, 64>> packets2;
1943
1/2
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
51 packets.resize(m_strings.size());
1944
1/2
✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
51 packets2.resize(m_strings.size());
1945
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 51 times.
291 for (auto& p : packets) {
1946 240 memset(&p[0], 0, 64);
1947 }
1948
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 51 times.
291 for (auto& p : packets2) {
1949 240 memset(&p[0], 0, 64);
1950 }
1951
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 34 times.
51 int len = (x == 1) ? 2 : 1;
1952 51 int idx = x;
1953 51 bool listen = false;
1954 51 bool any = false;
1955
2/2
✓ Branch 0 taken 54 times.
✓ Branch 1 taken 51 times.
105 for (auto& rc : falconV5Support->getReceiverChains()) {
1956 54 int port = rc->getPixelStrings()[0]->m_portNumber;
1957
4/8
✓ Branch 0 taken 54 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 54 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 54 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 54 times.
54 if (port < 0 || port >= (int)packets.size() || port >= (int)packets2.size()) {
1958 2 continue; // a port past the strings would write past the packets
1959 }
1960
6/6
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 34 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 52 times.
54 if (x > 0 && rc->isSendOnly()) {
1961 // V4 receivers never get number/fuse/query packets; their
1962 // slot in those frames is filled by applySendOnlyConfigPackets
1963 2 continue;
1964 }
1965 52 any = true;
1966
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 34 times.
52 if (x == 0) {
1967
1/2
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
18 rc->generateConfigPacket(&packets[port][0]);
1968
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 17 times.
18 if (rc->isSendOnly()) {
1969
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 m_sendOnlyConfigPackets.emplace_back(port, packets[port]);
1970 }
1971
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 17 times.
34 } else if (x == 1) {
1972
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 rc->generateNumberPackets(&packets[port][0], &packets2[port][0]);
1973
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 } else if (x == 2) {
1974
1/2
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
17 rc->generateSetFusesPacket(&packets[port][0], 1);
1975 }
1976 }
1977
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 49 times.
51 if (!any) {
1978 2 continue;
1979 }
1980
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 17 times.
49 if (x > 0) {
1981
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 applySendOnlyConfigPackets(packets);
1982
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 applySendOnlyConfigPackets(packets2);
1983 }
1984
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 if (m_pru0.v5_config_packets[idx] == nullptr) {
1985
2/4
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
49 m_pru0.v5_config_packets[idx] = new FalconV5PacketInfo(len, memLoc, listen);
1986 // 64 to keep on 4K memory alignment
1987 49 memLoc += 64 * stringsPerPru() * len;
1988
2/4
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 49 times.
49 m_pru1.v5_config_packets[idx] = new FalconV5PacketInfo(len, memLoc, listen);
1989 49 memLoc += 64 * stringsPerPru() * len;
1990
1991
1/2
✓ Branch 0 taken 49 times.
✗ Branch 1 not taken.
49 encodeFalconV5Packet(packets, m_pru0.v5_config_packets[idx]->data, m_pru1.v5_config_packets[idx]->data);
1992
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 33 times.
49 if (len == 2) {
1993 16 size_t pLen = 57 * stringsPerPru();
1994
1/2
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
16 encodeFalconV5Packet(packets2, m_pru0.v5_config_packets[idx]->data + pLen, m_pru1.v5_config_packets[idx]->data + pLen);
1995 }
1996
1997
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 32 times.
49 if (idx == 0) {
1998 // config packet, output in other spots
1999 17 idx += 12;
2000
2/2
✓ Branch 0 taken 136 times.
✓ Branch 1 taken 17 times.
153 while (idx < NUM_CONFIG_PACKETS) {
2001
2/4
✓ Branch 0 taken 136 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 136 times.
136 m_pru0.v5_config_packets[idx] = new FalconV5PacketInfo(len, m_pru0.v5_config_packets[0]->data, listen);
2002
2/4
✓ Branch 0 taken 136 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 136 times.
136 m_pru1.v5_config_packets[idx] = new FalconV5PacketInfo(len, m_pru1.v5_config_packets[0]->data, listen);
2003 136 idx += 12;
2004 }
2005
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
32 } else if (idx == 1) {
2006 // number packet, resend once every loop
2007 16 int nidx = NUM_CONFIG_PACKETS - 1;
2008
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 while (m_pru0.v5_config_packets[nidx] != nullptr) {
2009 ✗ --nidx;
2010 }
2011
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
16 m_pru0.v5_config_packets[nidx] = new FalconV5PacketInfo(len, m_pru0.v5_config_packets[idx]->data, listen);
2012
2/4
✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
16 m_pru1.v5_config_packets[nidx] = new FalconV5PacketInfo(len, m_pru1.v5_config_packets[idx]->data, listen);
2013 }
2014 }
2015
4/4
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 49 times.
✓ Branch 3 taken 2 times.
53 }
2016 }
2017 // Reserve space for the dynamic packets. The len must be set as well:
2018 // the AM62x pump streams 57*64*len packet bytes into the ring, and a
2019 // dynamic packet whose command flags said "packet present" but whose
2020 // len was 0 starves the PRU in its ring wait (the AM335x path reads by
2021 // DDR address and never noticed).
2022 17 m_pru0.dynamicPacketInfo1.data = memLoc;
2023 17 m_pru0.dynamicPacketInfo1.len = 1;
2024 17 memLoc += 64 * stringsPerPru();
2025 17 m_pru0.dynamicPacketInfo2.data = memLoc;
2026 17 m_pru0.dynamicPacketInfo2.len = 1;
2027 17 memLoc += 64 * stringsPerPru();
2028 17 m_pru1.dynamicPacketInfo1.data = memLoc;
2029 17 m_pru1.dynamicPacketInfo1.len = 1;
2030 17 memLoc += 64 * stringsPerPru();
2031 17 m_pru1.dynamicPacketInfo2.data = memLoc;
2032 17 m_pru1.dynamicPacketInfo2.len = 1;
2033 17 memLoc += 64 * stringsPerPru();
2034
2035 17 m_pru0.dynamicPacketInfo = &m_pru0.dynamicPacketInfo1;
2036 17 m_pru1.dynamicPacketInfo = &m_pru1.dynamicPacketInfo1;
2037
2038
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
34 createOutputLengths(m_pru0, "pru0");
2039
2/4
✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
34 createOutputLengths(m_pru1, "pru1");
2040 17 }
2041