GCC Code Coverage Report


Directory: ./
File: non-gpl/BBShiftString/BBShiftString.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 41 41 100.0%
Functions: 11 11 100.0%
Branches: 27 36 75.0%

Line Branch Exec Source
1 #pragma once
2 /*
3 * This file is part of the Falcon Player (FPP) and is Copyright (C)
4 * 2022 by the Falcon Player Developers.
5 *
6 * The Falcon Player (FPP) is free software, and is covered under
7 * multiple Open Source licenses. Please see the included 'LICENSES'
8 * file for descriptions of what files are covered by each license.
9 *
10 * This source file is covered under the CC-BY-ND as described in the
11 * included LICENSE.CC-BY-ND file. This file may be modified for
12 * personal use, but modified copies MAY NOT be redistributed in any form.
13 */
14
15 #include <atomic>
16 #include <chrono>
17 #include <string>
18 #include "fpp-json-fwd.h"
19 #include <thread>
20 #include <vector>
21
22 #include "../../pru/BBShiftStringDefs.hp"
23 #include "../FalconV5Support/FalconV5Support.h"
24 #include "channeloutput/ChannelOutput.h"
25 #include "channeloutput/PixelString.h"
26 #include "util/BBBPruUtils.h"
27
28 // How deep the cape's shift register chain is, i.e. how many strings hang off
29 // each data pin. 8 is the long standing layout; 16 doubles the strings a
30 // single PRU can drive and is AM62x only - see the SHIFT16 firmware variant in
31 // BBShiftString.asm and the makefile. Buffers and maps are sized for the
32 // maximum; the live depth is m_stringsPerPin, from the cape's "stringsPerPin".
33 constexpr int MAX_STRINGS_PER_PIN = 16;
34 constexpr int MAX_PINS_PER_PRU = 8;
35
36 // when output restarts, there are some packets that need to be sent first
37 // but not when looped around
38 constexpr int FIRST_LOOPING_CONFIG_PACKET = 6;
39 constexpr int NUM_CONFIG_PACKETS = 96 + FIRST_LOOPING_CONFIG_PACKET;
40
41 // structure of the data at the start of the PRU ram
42 // that the pru program expects to see
43 typedef struct {
44 // in the DDR shared with the PRU
45 uint32_t address_dma;
46 uint32_t address_dma_packet;
47
48 // write data length to start, 0xFFFF to abort. will be cleared when started
49 volatile uint32_t command;
50 volatile uint32_t response;
51
52 uint32_t buffer[4]; // need a bit of a buffer
53 uint16_t commandTable[BBSS_COMMAND_TABLE_ENTRIES];
54 } __attribute__((__packed__)) BBShiftStringData;
55
56 // GPIOCommand::channelOffset value for a command that applies in the FalconV5
57 // packet phase rather than at a data byte position: once the first packet has
58 // gone out, and for the whole phase in a frame that opens a listen window.
59 // Larger than any real offset so it sorts last in the command table build.
60 constexpr int GPIO_CMD_AFTER_FIRST_PACKET = 0x7FFFFFFF;
61
62 class BBShiftStringOutput : public ChannelOutput {
63 public:
64 BBShiftStringOutput(unsigned int startChannel, unsigned int channelCount);
65 virtual ~BBShiftStringOutput();
66
67 308 virtual std::string GetOutputType() const {
68
1/2
✓ Branch 0 taken 308 times.
✗ Branch 1 not taken.
616 return "BBB Pixel Strings";
69 }
70
71 virtual int Init(Json::Value config) override;
72 virtual int Close(void) override;
73
74 virtual void StartingOutput() override;
75 virtual void StoppingOutput() override;
76
77 virtual int SendData(unsigned char* channelData) override;
78 virtual void PrepData(unsigned char* channelData) override;
79 virtual void DumpConfig(void) override;
80
81 virtual void GetRequiredChannelRanges(const std::function<void(int, int)>& addRange) override;
82
83 virtual void OverlayTestData(unsigned char* channelData, int cycleNum, float percentOfCycle, int testType, const Json::Value& config) override;
84 124 virtual bool SupportsTesting() const { return true; }
85
86 private:
87 void StopPRU(bool wait = true);
88 int StartPRU();
89
90 std::string m_subType;
91
92 class FalconV5PacketInfo {
93 public:
94 402 FalconV5PacketInfo(int l, uint8_t* d, bool list) :
95 402 len(l), data(d), listen(list) {
96 402 }
97 200 FalconV5PacketInfo() :
98 200 len(0), data(nullptr), listen(false) {
99 200 }
100 601 ~FalconV5PacketInfo() {
101 601 }
102 299 uint32_t getCommandFlags() {
103 299 uint32_t f = 0x20000;
104
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 291 times.
299 if (len == 2) {
105 8 f |= 0x40000;
106 }
107
2/2
✓ Branch 0 taken 240 times.
✓ Branch 1 taken 59 times.
299 if (listen) {
108 240 f |= 0x80000;
109 }
110 299 return f;
111 }
112 int len;
113 uint8_t* data;
114 bool listen = false;
115 };
116
117 class FrameData {
118 public:
119 100 FrameData() {
120
2/2
✓ Branch 0 taken 800 times.
✓ Branch 1 taken 100 times.
900 for (int y = 0; y < MAX_PINS_PER_PRU; ++y) {
121
2/2
✓ Branch 0 taken 12800 times.
✓ Branch 1 taken 800 times.
13600 for (int x = 0; x < MAX_STRINGS_PER_PIN; ++x) {
122 12800 stringMap[y][x] = -1;
123 }
124 }
125
2/2
✓ Branch 0 taken 10200 times.
✓ Branch 1 taken 100 times.
10300 for (int y = 0; y < NUM_CONFIG_PACKETS; ++y) {
126 10200 v5_config_packets[y] = nullptr;
127 }
128 100 }
129 100 ~FrameData() {
130
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 6 times.
100 if (formattedData)
131 94 free(formattedData);
132
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 6 times.
100 if (heapData) {
133
1/2
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
94 if (curData)
134 94 free(curData);
135
1/2
✓ Branch 0 taken 94 times.
✗ Branch 1 not taken.
94 if (lastData)
136 94 free(lastData);
137 }
138
2/2
✓ Branch 0 taken 10200 times.
✓ Branch 1 taken 100 times.
10300 for (int y = 0; y < NUM_CONFIG_PACKETS; ++y) {
139
4/6
✓ Branch 0 taken 401 times.
✓ Branch 1 taken 9799 times.
✓ Branch 2 taken 401 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 401 times.
✗ Branch 5 not taken.
10200 if (v5_config_packets[y] && v5_config_packets[y] != &dynamicPacketInfo2 && v5_config_packets[y] != &dynamicPacketInfo1) {
140
1/2
✓ Branch 0 taken 401 times.
✗ Branch 1 not taken.
401 delete v5_config_packets[y];
141 }
142 }
143 100 }
144 std::array<std::array<int, MAX_STRINGS_PER_PIN>, MAX_PINS_PER_PRU> stringMap;
145 BBBPru* pru = nullptr;
146 BBShiftStringData* pruData = nullptr;
147
148 uint8_t* formattedData = nullptr;
149
150 uint8_t* lastData = nullptr;
151 uint8_t* curData = nullptr;
152 uint32_t frameSize = 0;
153 int maxStringLen = 0;
154 int outputStringLen = 0;
155
156 // AM62x only: the PRU is fed through a ring in the PRU shared RAM by
157 // a pump thread instead of reading DDR (see pru/SMEMRing.hp); the
158 // frame buffers live in normal cached memory. Unused on AM335x.
159 BBBPruSMEMRing ring;
160 bool heapData = false;
161
162 // frame handoff to the pump thread; SendData fills pendingFrame then
163 // bumps pendingSeq, the pump snapshots it when the PRU is ready
164 struct PumpFrame {
165 const uint8_t* frameData = nullptr;
166 uint32_t frameBytes = 0;
167 const uint8_t* packetData = nullptr;
168 uint32_t packetBytes = 0;
169 uint32_t command = 0;
170 } pendingFrame;
171 std::atomic<uint32_t> pendingSeq{ 0 };
172 // last values read back out of the firmware's counters, so only new
173 // events get reported
174 uint32_t renderedFrames = 0;
175 uint32_t resyncCount = 0;
176 // when the firmware first stopped making progress on a pending frame;
177 // zero while it is keeping up
178 std::chrono::steady_clock::time_point stalledSince{};
179 // pump-internal streaming state
180 // atomic: the pump thread writes this while the output thread reads it
181 // in SendData's back-pressure gate. It is published with release only
182 // once the pump's copy of pendingFrame is known good - the gate reads
183 // it as permission to restage, so an early store would hand the output
184 // thread a frame the pump is still reading.
185 std::atomic<uint32_t> pumpedSeq{ 0 };
186 PumpFrame activeFrame;
187 uint32_t activeOff = 0;
188 bool pumpActive = false;
189
190 FalconV5PacketInfo* v5_config_packets[NUM_CONFIG_PACKETS];
191 FalconV5PacketInfo dynamicPacketInfo1;
192 FalconV5PacketInfo dynamicPacketInfo2;
193 FalconV5PacketInfo* dynamicPacketInfo = nullptr;
194 int curV5ConfigPacket = 0;
195 } m_pru0, m_pru1;
196
197 std::vector<PixelString*> m_strings;
198 // per string, per virtual string: nonzero when chMap[i+3] == chMap[i]+3
199 // for the whole map, letting prepData skip the map indirection
200 std::vector<std::vector<uint8_t>> m_vsAffine;
201 std::map<std::string, std::string> m_usedPins;
202
203 // per-PRU pin sets; the platform defaults unless the cape overrides
204 // them by name (combo capes with nonstandard pinouts). The cape only
205 // carries header pin names - the r30 bit numbers are resolved here
206 // from the platform pin table and published to the firmware at runtime
207 std::vector<std::string> m_dataPins[2];
208 std::vector<std::string> m_ctrlPins[2];
209 bool m_pinNamesOverridden[2] = { false, false };
210 int m_clockBit[2] = { 0, 0 };
211 int m_latchBit[2] = { 0, 0 };
212 // sharing the PRUSS with a panel driver on the other PRU: never clear
213 // the shared RAM or the other PRU's memory, no FalconV5 listeners
214 bool m_sharedPRUSS = false;
215
216 // shift register chain depth: strings per data pin, 8 or 16. Everything
217 // the PRU consumes scales with stringsPerPru(): one shift phase clocks
218 // that many bytes, so a frame is outputStringLen * stringsPerPru() bytes.
219 int m_stringsPerPin = 8;
220 239965 int stringsPerPru() const { return MAX_PINS_PER_PRU * m_stringsPerPin; }
221
222 // whether anything at all was mapped onto a PRU. maxStringLen is set
223 // alongside every stringMap entry Init() fills in, so it is zero exactly
224 // when there is no frame to build or send. (stringMap is a std::array,
225 // so its empty() is a compile time false and cannot answer this.)
226
2/4
✓ Branch 0 taken 19913 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 19913 times.
✗ Branch 3 not taken.
19913 bool hasStrings() const { return m_pru0.maxStringLen || m_pru1.maxStringLen; }
227
228 uint32_t m_curFrame = 0;
229 uint32_t m_licensedOutputs = 0;
230
231 // back-pressure gate accounting, touched only on the output thread.
232 // Members rather than function statics so a config reload starts a fresh
233 // window and so a removed output cannot leave its warning stuck in the UI.
234 uint32_t m_bpOffered = 0;
235 uint32_t m_bpDeclined = 0;
236 bool m_bpLastDeclined = false; // previous window declined any frames
237 bool m_bpWarned = false;
238 std::chrono::steady_clock::time_point m_bpWindowStart{};
239 void setFrameRateWarning(bool on);
240
241 // predictive frame-rate budget: computed once in Init() from the longest
242 // string, compared against the sequence rate whenever output starts
243 int m_frameTimeUs = 0;
244 float m_lastBudgetRate = -1.0f;
245 std::string m_budgetWarnText;
246 void checkFrameRateBudget(float rate);
247 void clearBudgetWarning();
248
249 int m_testCycle = -1;
250 int m_testType = 0;
251 float m_testPercent = 0.0f;
252
253 FalconV5Support* falconV5Support = nullptr;
254 std::list<std::string> m_autoCreatedModelNames;
255 bool supportsV5Listeners = false;
256 // any configured Falcon V5 (bidirectional) receivers after the
257 // capability checks; V4 (send-only) chains do not set this
258 bool m_hasBidirSR = false;
259 // the cape wires the enable line to PRU1 and we configured that pin;
260 // this also means PRU1 must be running, see Init()
261 bool m_usesEnablePin = false;
262
263 void prepData(FrameData& d, unsigned char* channelData);
264 void sendData(FrameData& d);
265 void bitFlipData(uint8_t* stringChannelData, uint8_t* bitSwapped, size_t len);
266 // The hot paths are templated on the chain depth and dispatched once per
267 // frame rather than reading m_stringsPerPin in the inner loops, so the 8
268 // deep path keeps the compile time constants (and the codegen) it had.
269 template<int SPP>
270 void prepDataT(FrameData& d, unsigned char* channelData);
271 template<int SPP>
272 void bitFlipDataT(uint8_t* stringChannelData, uint8_t* bitSwapped, size_t len);
273
274 // AM62x shared-memory ring pump (see pru/SMEMRing.hp)
275 std::thread m_pumpThread;
276 std::atomic<bool> m_pumpRunning{ false };
277 uint8_t* m_fv5PacketMem = nullptr;
278 void runPumpThread();
279 bool pumpFrameData(FrameData& d);
280
281 void createOutputLengths(FrameData& d, const std::string& pfx);
282 // report ring resynchronizations the firmware has had to make
283 void reportRingResync(FrameData& d, int pru);
284
285 // Bit cell timing in ns, published to the firmware as cycle counts. These
286 // are the values the firmware used to have compiled in; keeping them here
287 // is what lets a different bit cell (a 400kHz part) be selected without a
288 // separate firmware image.
289 int m_t0Ns = 320;
290 int m_t1Ns = 750;
291 int m_lowNs = 1120;
292
293 // ordering so a Timing can key a map; the values are small and exact
294 struct TimingLess {
295 510 bool operator()(const PixelString::Timing& a, const PixelString::Timing& b) const {
296
1/2
✓ Branch 0 taken 510 times.
✗ Branch 1 not taken.
510 return std::tie(a.t0Ns, a.t1Ns, a.periodNs) < std::tie(b.t0Ns, b.t1Ns, b.periodNs);
297 }
298 };
299 // pick the controller's bit cell from the protocols actually in use
300 void resolveTiming(const Json::Value& config);
301
302 void demoteInvertedReceiverChains();
303
304 void setupFalconV5Support(const Json::Value& root, uint8_t* memLoc);
305 void encodeFalconV5Packet(std::vector<std::array<uint8_t, 64>>& packets, uint8_t* memLocPru0, uint8_t* memLocPru1);
306
307 // The packet phase is clocked out on every pin of the cape at once, so a
308 // send-only (V4) chain is on the bus for every packet frame the V5 chains
309 // need, not just its own config frames. Its slot must carry a valid V4
310 // config packet in every one of them: an all-zero slot is a framed packet
311 // with a token the receiver does not know, and a V4 receiver is specified
312 // to get its config packet regularly after a data frame anyway. The
313 // encoded packet is cached per chain and stamped into every frame.
314 std::vector<std::pair<int, std::array<uint8_t, 64>>> m_sendOnlyConfigPackets;
315 void applySendOnlyConfigPackets(std::vector<std::array<uint8_t, 64>>& packets);
316 };
317