| 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 |