| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 3 | * 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 | #include "fpp-pch.h" | ||
| 14 | |||
| 15 | #include "fpp-json.h" | ||
| 16 | |||
| 17 | #include <unistd.h> | ||
| 18 | |||
| 19 | #include "FalconV5Support.h" | ||
| 20 | |||
| 21 | #include <stdexcept> | ||
| 22 | #include "OutputMonitor.h" | ||
| 23 | #include "Warnings.h" | ||
| 24 | #include "common.h" | ||
| 25 | #include "channeloutput/PixelString.h" | ||
| 26 | #include "channeloutput/serialutil.h" | ||
| 27 | #include "util/GPIOUtils.h" | ||
| 28 | |||
| 29 | #include "util/BBBPruUtils.h" | ||
| 30 | #include "pru/SMEMRing.hp" | ||
| 31 | #include "util/BBBUtils.h" | ||
| 32 | #include <sys/wait.h> | ||
| 33 | #include <arm_neon.h> | ||
| 34 | |||
| 35 | extern "C" { | ||
| 36 | bool encodeFalconV5Packet(const Json::Value& description, uint8_t* packet); | ||
| 37 | bool decodeFalconV5Packet(uint8_t* packet, Json::Value& result); | ||
| 38 | } | ||
| 39 | |||
| 40 | class FalconV5Listener { | ||
| 41 | public: | ||
| 42 | 17 | FalconV5Listener(const Json::Value& config) { | |
| 43 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | pin = config["pin"].asString(); |
| 44 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | const PinCapabilities& p = PinCapabilities::getPinByName(pin); |
| 45 | // the cape config comes from the cape EEPROM; a pin this board does | ||
| 46 | // not have fails the output (Init() catches this) instead of being | ||
| 47 | // dereferenced as null below | ||
| 48 |
3/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 16 times.
|
17 | if (p.ptr() == nullptr) { |
| 49 |
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 | throw std::runtime_error("FalconV5 listener pin '" + pin + "' does not exist on this board"); |
| 50 | } | ||
| 51 |
3/6✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 16 times.
✗ Branch 5 not taken.
|
64 | p.configPin("pru0in", false, "FalconV5 Listener"); |
| 52 | |||
| 53 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | const BBBPinCapabilities* pc = (const BBBPinCapabilities*)p.ptr(); |
| 54 | 16 | offset = pc->pruPin(0); | |
| 55 | 17 | } | |
| 56 | 16 | ~FalconV5Listener() { | |
| 57 | 16 | const PinCapabilities& p = PinCapabilities::getPinByName(pin); | |
| 58 | 16 | p.releasePin(); | |
| 59 | 16 | } | |
| 60 | std::string pin; | ||
| 61 | int offset = 0; | ||
| 62 | }; | ||
| 63 | |||
| 64 | // structure of the data at the start of the PRU ram | ||
| 65 | // that the pru program expects to see | ||
| 66 | typedef struct { | ||
| 67 | // 0xFFFF to abort | ||
| 68 | volatile uint32_t command; | ||
| 69 | // bytes captured in the last completed listen window. Written by the | ||
| 70 | // listener only when the string PRU closes the window, never while a | ||
| 71 | // capture is in progress; 'captures' is bumped right after it, so a | ||
| 72 | // changed count means a new, complete length is there to read. | ||
| 73 | volatile uint32_t length; | ||
| 74 | volatile uint32_t captures; | ||
| 75 | // 1 from the moment a listen window opens until the capture is published. | ||
| 76 | // The ARM reads it before and after copying the buffer: a window can open | ||
| 77 | // while the ARM is still reading the previous capture (the frame tick | ||
| 78 | // lands inside the next window whenever a frame overran its period), and | ||
| 79 | // the listener then overwrites the buffer from the start, splicing the | ||
| 80 | // new reply's head onto the old reply's tail. Two replies scrambled with | ||
| 81 | // different random keys decode into garbage that still parses. | ||
| 82 | volatile uint32_t busy; | ||
| 83 | } __attribute__((__packed__)) FalconV5PRUData; | ||
| 84 | |||
| 85 | class PRUControl { | ||
| 86 | public: | ||
| 87 | 16 | PRUControl() { | |
| 88 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | const std::string filename = "/opt/fpp/src/non-gpl/FalconV5Support/FalconV5PRUListener_pru0.out"; |
| 89 | |||
| 90 |
3/8✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
16 | pru = new BBBPru(0, true, false); |
| 91 | 16 | pruData = (FalconV5PRUData*)pru->data_ram; | |
| 92 | 16 | data = pru->shared_ram; | |
| 93 | // never clear the shared RAM here: on the AM62x the string PRU's | ||
| 94 | // data ring lives there and is already attached and live by the | ||
| 95 | // time the listener starts - zeroing its counters desyncs the ring | ||
| 96 | // and wedges the string PRU in its ring wait. The listener's own | ||
| 97 | // state lives in PRU0's data RAM (still cleared above) and the | ||
| 98 | // capture bytes are rewritten before each capture completes. | ||
| 99 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | pru->run(filename, false); |
| 100 | 16 | } | |
| 101 | 16 | ~PRUControl() { | |
| 102 | 16 | pruData->command = 0xFFFF; | |
| 103 | 16 | __asm__ __volatile__("" :: | |
| 104 | : "memory"); | ||
| 105 | 16 | pru->stop(); | |
| 106 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | delete pru; |
| 107 | 16 | } | |
| 108 | |||
| 109 | BBBPru* pru = nullptr; | ||
| 110 | FalconV5PRUData* pruData = nullptr; | ||
| 111 | uint8_t* data = nullptr; | ||
| 112 | }; | ||
| 113 | |||
| 114 | 20 | FalconV5Support::FalconV5Support() { | |
| 115 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | OutputMonitor::INSTANCE.setSmartReceiverEventCallback([this](int port, int index, const std::string& cmd) { |
| 116 | 279 | PendingToggle pt; | |
| 117 | 279 | pt.port = (int16_t)port; | |
| 118 | 279 | pt.index = (uint8_t)index; | |
| 119 |
2/4✓ Branch 0 taken 279 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 279 times.
✗ Branch 3 not taken.
|
279 | if (cmd == "ToggleOutput") { |
| 120 | 279 | pt.cmd = ToggleCommand::Toggle; | |
| 121 | ✗ | } else if (cmd == "ResetOutput") { | |
| 122 | ✗ | pt.cmd = ToggleCommand::Reset; | |
| 123 | } else { | ||
| 124 | ✗ | return; | |
| 125 | } | ||
| 126 | 279 | pendingToggle.store(pt); | |
| 127 | }); | ||
| 128 | 20 | } | |
| 129 | 20 | FalconV5Support::~FalconV5Support() { | |
| 130 | 20 | OutputMonitor::INSTANCE.setSmartReceiverEventCallback(nullptr); | |
| 131 |
2/2✓ Branch 0 taken 20 times.
✓ Branch 1 taken 20 times.
|
40 | for (auto rc : receiverChains) { |
| 132 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | delete rc; |
| 133 | } | ||
| 134 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 20 times.
|
20 | for (auto& p : muxPins) { |
| 135 | ✗ | p->releasePin(); | |
| 136 | } | ||
| 137 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 20 times.
|
36 | for (auto l : listeners) { |
| 138 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | delete l; |
| 139 | } | ||
| 140 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 4 times.
|
20 | if (pru) { |
| 141 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | delete pru; |
| 142 | } | ||
| 143 | 20 | listeners.clear(); | |
| 144 | 20 | receiverChains.clear(); | |
| 145 | 20 | } | |
| 146 | 17 | void FalconV5Support::addListeners(const Json::Value& config) { | |
| 147 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 16 times.
|
33 | for (int x = 0; x < config["listenerPins"].size(); x++) { |
| 148 |
8/14✓ 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 16 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 16 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 16 times.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
|
18 | listeners.push_back(new FalconV5Listener(config["listenerPins"][x])); |
| 149 | } | ||
| 150 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | if (!listeners.empty()) { |
| 151 |
2/6✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
16 | pru = new PRUControl(); |
| 152 | } | ||
| 153 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | for (int x = 0; x < config["muxPins"].size(); x++) { |
| 154 | ✗ | std::string p = config["muxPins"][x].asString(); | |
| 155 | ✗ | if (PinCapabilities::getPinByName(p).ptr() == nullptr) { | |
| 156 | ✗ | throw std::runtime_error("FalconV5 mux pin '" + p + "' does not exist on this board"); | |
| 157 | } | ||
| 158 | ✗ | muxPins.push_back(PinCapabilities::getPinByName(p).ptr()); | |
| 159 | ✗ | muxPins.back()->configPin("gpio", true, "FalconV5 Mux Pin"); | |
| 160 | ✗ | muxPins.back()->setValue(0); | |
| 161 | ✗ | } | |
| 162 | 16 | } | |
| 163 | |||
| 164 | ✗ | static inline void maskBit(int len, int bit, uint8_t* s, uint8_t* d) { | |
| 165 | ✗ | uint8_t m1 = 1 << bit; | |
| 166 | ✗ | const uint8x8_t bitmask = { m1, m1, m1, m1, m1, m1, m1, m1 }; | |
| 167 | ✗ | int idx = 0; | |
| 168 | ✗ | while (idx < len) { | |
| 169 | ✗ | uint8x8_t work = vld1_u8(s); // load 8 bytes | |
| 170 | ✗ | uint8x8_t msk = vand_u8(work, bitmask); | |
| 171 | vst1_u8(d, msk); | ||
| 172 | ✗ | s += 8; | |
| 173 | ✗ | d += 8; | |
| 174 | ✗ | idx += 8; | |
| 175 | } | ||
| 176 | ✗ | } | |
| 177 | ✗ | static void printDataBuf(int len, uint8_t* data) { | |
| 178 | ✗ | printf("Len: %d \n", len); | |
| 179 | ✗ | for (int x = 0; x < 40; x++) | |
| 180 | ✗ | printf("%02X %s", data[x], (x % 5 != 4 ? "" : " ")); | |
| 181 | ✗ | printf("\n"); | |
| 182 | ✗ | for (int x = 0; x < 40; x++) | |
| 183 | ✗ | printf("%02X %s", data[x + 40], (x % 5 != 4 ? "" : " ")); | |
| 184 | ✗ | printf("\n"); | |
| 185 | ✗ | for (int x = 0; x < 40; x++) | |
| 186 | ✗ | printf("%02X %s", data[x + 80], (x % 5 != 4 ? "" : " ")); | |
| 187 | ✗ | printf("\n\n"); | |
| 188 | ✗ | } | |
| 189 | ✗ | static uint8_t readByte(uint8_t* data, int& pos) { | |
| 190 | ✗ | constexpr int superSample = 5; | |
| 191 | ✗ | constexpr int sampleOffset = 2; | |
| 192 | // find start bit | ||
| 193 | ✗ | uint8_t ret = 0; | |
| 194 | ✗ | while (data[pos] != 0) { | |
| 195 | ✗ | ++pos; | |
| 196 | } | ||
| 197 | ✗ | pos += sampleOffset + superSample; | |
| 198 | ✗ | if (data[pos]) | |
| 199 | ✗ | ret |= 0x1; | |
| 200 | ✗ | pos += superSample; | |
| 201 | ✗ | if (data[pos]) | |
| 202 | ✗ | ret |= 0x2; | |
| 203 | ✗ | pos += superSample; | |
| 204 | ✗ | if (data[pos]) | |
| 205 | ✗ | ret |= 0x4; | |
| 206 | ✗ | pos += superSample; | |
| 207 | ✗ | if (data[pos]) | |
| 208 | ✗ | ret |= 0x8; | |
| 209 | ✗ | pos += superSample; | |
| 210 | ✗ | if (data[pos]) | |
| 211 | ✗ | ret |= 0x10; | |
| 212 | ✗ | pos += superSample; | |
| 213 | ✗ | if (data[pos]) | |
| 214 | ✗ | ret |= 0x20; | |
| 215 | ✗ | pos += superSample; | |
| 216 | ✗ | if (data[pos]) | |
| 217 | ✗ | ret |= 0x40; | |
| 218 | ✗ | pos += superSample; | |
| 219 | ✗ | if (data[pos]) | |
| 220 | ✗ | ret |= 0x80; | |
| 221 | ✗ | pos += superSample; // should be in the stop bit now | |
| 222 | ✗ | return ret; | |
| 223 | } | ||
| 224 | |||
| 225 | 17 | float FalconV5Support::queryFrameShare() const { | |
| 226 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 16 times.
|
17 | if (!pru) { |
| 227 | 1 | return 0.0f; | |
| 228 | } | ||
| 229 | 16 | int receivers = 0; | |
| 230 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
|
32 | for (auto c : maxCount) { |
| 231 | 16 | receivers += c; | |
| 232 | } | ||
| 233 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
|
16 | if (receivers == 0) { |
| 234 | ✗ | return 0.0f; | |
| 235 | } | ||
| 236 | 16 | return (float)receivers / (float)(receivers + maxCount.size()); | |
| 237 | } | ||
| 238 | |||
| 239 | 88450 | void FalconV5Support::stageListenerData() { | |
| 240 |
2/2✓ Branch 0 taken 15168 times.
✓ Branch 1 taken 73282 times.
|
88450 | if (!pru) { |
| 241 | 88450 | return; | |
| 242 | } | ||
| 243 | // The listener publishes the length and bumps the capture count only | ||
| 244 | // when the string PRU closes the window, and holds 'busy' from the | ||
| 245 | // moment a window opens until then. A window can open while we are | ||
| 246 | // still copying the previous capture (the frame tick lands inside the | ||
| 247 | // next window whenever a frame overran its period), and the listener | ||
| 248 | // then overwrites the buffer from the start, splicing the new reply's | ||
| 249 | // head onto the old reply's tail - two replies scrambled with different | ||
| 250 | // random keys, which decode into garbage that still parses. So: count | ||
| 251 | // first, copy, then busy and count again; anything that moved means the | ||
| 252 | // copy is a splice and is dropped. The next query repeats the question. | ||
| 253 | // both the pump thread and the output thread may call this | ||
| 254 |
1/2✓ Branch 0 taken 73282 times.
✗ Branch 1 not taken.
|
73282 | std::unique_lock<std::mutex> lock(stageLock); |
| 255 | 73282 | uint32_t busy0 = pru->pruData->busy; | |
| 256 | 73282 | uint32_t captures = pru->pruData->captures; | |
| 257 |
2/2✓ Branch 0 taken 73042 times.
✓ Branch 1 taken 240 times.
|
73282 | if (captures == lastCaptures) { |
| 258 | 73042 | return; | |
| 259 | } | ||
| 260 | 240 | lastCaptures = captures; | |
| 261 | 240 | uint32_t len = pru->pruData->length; | |
| 262 | // the listener stops storing at its shared RAM reservation; on the | ||
| 263 | // AM62x the string PRU's ring starts immediately above it | ||
| 264 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
|
240 | if (len > SMEM_RING_LISTENER_BYTES) { |
| 265 | ✗ | len = SMEM_RING_LISTENER_BYTES; | |
| 266 | } | ||
| 267 |
1/2✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
|
240 | if (!len) { |
| 268 | 240 | return; | |
| 269 | } | ||
| 270 | ✗ | if (stageBuf.size() < SMEM_RING_LISTENER_BYTES + 8) { | |
| 271 | ✗ | stageBuf.resize(SMEM_RING_LISTENER_BYTES + 8); | |
| 272 | ✗ | workBuf.resize(SMEM_RING_LISTENER_BYTES + 8); | |
| 273 | } | ||
| 274 | // copy exactly len: the listener publishes whole 32 byte stores, so len | ||
| 275 | // is a multiple of the 8 bytes maskBit() reads at a time, and the clamp | ||
| 276 | // above keeps it inside the capture reservation. Reading past it ran off | ||
| 277 | // the end of the AM335x's 12KB shared RAM. | ||
| 278 | ✗ | pru->pru->memcpyToPRU(&stageBuf[0], pru->data, len); | |
| 279 | ✗ | uint32_t busy1 = pru->pruData->busy; | |
| 280 | ✗ | uint32_t captures2 = pru->pruData->captures; | |
| 281 | ✗ | if (busy0 || busy1 || captures2 != captures) { | |
| 282 | ✗ | lastCaptures = captures2; | |
| 283 | ✗ | ++tornCaptures; | |
| 284 | ✗ | LogExcess(VB_CHANNELOUT, "FalconV5: listener capture %u overwritten while being read, dropped (%u so far)\n", captures, tornCaptures); | |
| 285 | ✗ | return; | |
| 286 | } | ||
| 287 | ✗ | stageLen = len; | |
| 288 | ✗ | stageValid = true; | |
| 289 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 73282 times.
|
73282 | } |
| 290 | |||
| 291 | 568 | void FalconV5Support::processListenerData() { | |
| 292 |
2/2✓ Branch 0 taken 148 times.
✓ Branch 1 taken 420 times.
|
568 | if (!pru) { |
| 293 | 148 | return; | |
| 294 | } | ||
| 295 | // no pump thread polling for us (AM335x), or it has not looked yet | ||
| 296 | 420 | stageListenerData(); | |
| 297 | 420 | uint32_t len = 0; | |
| 298 | { | ||
| 299 |
1/2✓ Branch 0 taken 420 times.
✗ Branch 1 not taken.
|
420 | std::unique_lock<std::mutex> lock(stageLock); |
| 300 |
1/2✓ Branch 0 taken 420 times.
✗ Branch 1 not taken.
|
420 | if (!stageValid) { |
| 301 | 420 | return; | |
| 302 | } | ||
| 303 | ✗ | std::swap(stageBuf, workBuf); | |
| 304 | ✗ | len = stageLen; | |
| 305 | ✗ | stageValid = false; | |
| 306 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 420 times.
|
420 | } |
| 307 | ✗ | uint8_t* buf = &workBuf[0]; | |
| 308 | ✗ | for (auto& l : listeners) { | |
| 309 | // readByte() walks up to a byte cell past 'len' while finishing the | ||
| 310 | // last byte, so the sample buffer carries a zeroed tail for it | ||
| 311 | uint8_t data[SMEM_RING_LISTENER_BYTES + 64]; | ||
| 312 | uint8_t packet[1024]; | ||
| 313 | ✗ | maskBit(len, l->offset, buf, data); | |
| 314 | ✗ | memset(data + len, 0, 64); | |
| 315 | ✗ | int pidx = 0; | |
| 316 | ✗ | int pos = 0; | |
| 317 | ✗ | while (pos < len) { | |
| 318 | ✗ | packet[pidx++] = readByte(data, pos); | |
| 319 | } | ||
| 320 | ✗ | if (pidx > 1) { | |
| 321 | // printf("L: %d\n", l->offset); | ||
| 322 | // printDataBuf(len, data); | ||
| 323 | // printDataBuf(pidx, packet); | ||
| 324 | ✗ | Json::Value json; | |
| 325 | ✗ | if (pidx != 0 && decodeFalconV5Packet(packet, json)) { | |
| 326 | ✗ | int port = json["port"].asInt(); | |
| 327 | // printf("Port: %d Index: %d\n", port, json["index"].asInt()); | ||
| 328 | // printf("%s\n", SaveJsonToString(json, " ").c_str()); | ||
| 329 | ✗ | for (auto& rc : receiverChains) { | |
| 330 | ✗ | if (rc->getPixelStrings()[0]->m_portNumber == port) { | |
| 331 | ✗ | rc->handleQueryResponse(json); | |
| 332 | } | ||
| 333 | } | ||
| 334 | } | ||
| 335 | ✗ | } | |
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | ✗ | void FalconV5Support::setCurrentMux(int i) { | |
| 340 | ✗ | curMux = i; | |
| 341 | ✗ | for (auto& p : muxPins) { | |
| 342 | ✗ | int d = i & 0x1; | |
| 343 | ✗ | p->setValue(d); | |
| 344 | ✗ | i >>= 1; | |
| 345 | } | ||
| 346 | ✗ | } | |
| 347 | 20 | FalconV5Support::ReceiverChain::ReceiverChain(PixelString* p1, PixelString* p2, PixelString* p3, PixelString* p4, int grp, int m, bool so) : | |
| 348 | 20 | group(grp), mux(m), numReceivers(1), sendOnly(so) { | |
| 349 | 20 | strings[0] = p1; | |
| 350 | 20 | strings[1] = p2; | |
| 351 | 20 | strings[2] = p3; | |
| 352 | 20 | strings[3] = p4; | |
| 353 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 20 times.
|
100 | for (auto& s : strings) { |
| 354 | // a chain head in the last three strings has fewer than four | ||
| 355 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 80 times.
|
80 | if (s == nullptr) { |
| 356 | ✗ | continue; | |
| 357 | } | ||
| 358 | 80 | uint32_t srCount = 1; | |
| 359 |
2/2✓ Branch 0 taken 80 times.
✓ Branch 1 taken 80 times.
|
160 | for (auto& vs : s->m_virtualStrings) { |
| 360 |
2/4✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 80 times.
|
80 | if (vs.isFalconV5SmartReceiver()) { |
| 361 | ✗ | srCount++; | |
| 362 | } | ||
| 363 | } | ||
| 364 | 80 | numReceivers = std::max(numReceivers, srCount); | |
| 365 | } | ||
| 366 | 20 | } | |
| 367 | 20 | FalconV5Support::ReceiverChain* FalconV5Support::addReceiverChain(PixelString* p1, PixelString* p2, PixelString* p3, PixelString* p4, int group, int m, bool sendOnly) { | |
| 368 | // the mux comes from the cape config (falconV5ListenerMux); a negative | ||
| 369 | // one compares as huge against the sizes below and grew maxCount until | ||
| 370 | // memory ran out | ||
| 371 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 19 times.
|
20 | if (m < 0) { |
| 372 | 1 | m = 0; | |
| 373 | } | ||
| 374 |
3/8✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 20 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
|
20 | FalconV5Support::ReceiverChain* rc = new FalconV5Support::ReceiverChain(p1, p2, p3, p4, group, m, sendOnly); |
| 375 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | receiverChains.push_back(rc); |
| 376 | |||
| 377 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 17 times.
|
20 | if (sendOnly) { |
| 378 | // V4 chains only get the config packet: keep them out of the query | ||
| 379 | // scheduling entirely so no query/listen frames are generated | ||
| 380 | 3 | return rc; | |
| 381 | } | ||
| 382 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1 times.
|
17 | if (m >= queryData.size()) { |
| 383 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | queryData.resize(m + 1); |
| 384 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 16 times.
|
32 | while (m >= maxCount.size()) { |
| 385 |
1/2✓ Branch 0 taken 16 times.
✗ Branch 1 not taken.
|
16 | maxCount.push_back(0); |
| 386 | } | ||
| 387 | } | ||
| 388 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | queryData[m][group].push_back(rc); |
| 389 | |||
| 390 | 17 | int newM = 0; | |
| 391 |
2/2✓ Branch 0 taken 17 times.
✓ Branch 1 taken 17 times.
|
34 | for (auto& g : queryData[m]) { |
| 392 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 17 times.
|
35 | for (auto& rc : g.second) { |
| 393 | 18 | newM += rc->getReceiverCount(); | |
| 394 | } | ||
| 395 | } | ||
| 396 | 17 | maxCount[m] = std::max(maxCount[m], newM); | |
| 397 | 17 | return rc; | |
| 398 | } | ||
| 399 | |||
| 400 | 80 | static bool generateConfigJSONForPixelString(const PixelString* p, Json::Value& config) { | |
| 401 |
5/6✓ Branch 0 taken 80 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 60 times.
✓ Branch 3 taken 20 times.
✓ Branch 4 taken 9 times.
✓ Branch 5 taken 51 times.
|
80 | if (p && (p->smartReceiverType == PixelString::ReceiverType::FalconV5 || p->smartReceiverType == PixelString::ReceiverType::FalconV4)) { |
| 402 |
1/2✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
|
29 | Json::Value v; |
| 403 |
3/6✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
|
29 | v["startChannels"].append(0); |
| 404 | |||
| 405 | 29 | int srCount = 0; | |
| 406 | 29 | int curChanCount = 0; | |
| 407 |
2/2✓ Branch 0 taken 29 times.
✓ Branch 1 taken 29 times.
|
58 | for (auto& vs : p->m_virtualStrings) { |
| 408 | 29 | curChanCount += vs.chMapCount; | |
| 409 |
2/4✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 29 times.
|
29 | if (vs.isFalconV5SmartReceiver()) { |
| 410 | ✗ | srCount++; | |
| 411 | ✗ | v["startChannels"].append(curChanCount); | |
| 412 | } | ||
| 413 | } | ||
| 414 | 29 | srCount++; | |
| 415 |
3/6✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 29 times.
✗ Branch 5 not taken.
|
29 | v["startChannels"].append(curChanCount); |
| 416 |
2/4✓ Branch 0 taken 29 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
✗ Branch 3 not taken.
|
29 | config["ports"].append(v); |
| 417 | 29 | return true; | |
| 418 | 29 | } | |
| 419 |
1/2✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
|
51 | Json::Value v; |
| 420 |
3/6✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 51 times.
✗ Branch 5 not taken.
|
51 | v["startChannels"].append(0); |
| 421 |
2/4✓ Branch 0 taken 51 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 51 times.
✗ Branch 3 not taken.
|
51 | config["ports"].append(v); |
| 422 | 51 | return false; | |
| 423 | 51 | } | |
| 424 | 20 | bool FalconV5Support::ReceiverChain::generateConfigPacket(uint8_t* packet) const { | |
| 425 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | Json::Value config; |
| 426 |
2/4✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
|
20 | config["type"] = "config"; |
| 427 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | bool hasSR = generateConfigJSONForPixelString(strings[0], config); |
| 428 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | hasSR |= generateConfigJSONForPixelString(strings[1], config); |
| 429 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | hasSR |= generateConfigJSONForPixelString(strings[2], config); |
| 430 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | hasSR |= generateConfigJSONForPixelString(strings[3], config); |
| 431 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (hasSR) { |
| 432 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | return encodeFalconV5Packet(config, packet); |
| 433 | } | ||
| 434 | ✗ | return false; | |
| 435 | 20 | } | |
| 436 | 240 | bool FalconV5Support::ReceiverChain::generateQueryPacket(uint8_t* packet, int receiver) const { | |
| 437 |
1/2✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
|
240 | Json::Value config; |
| 438 |
2/4✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
|
240 | config["type"] = "query"; |
| 439 |
2/4✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 240 times.
✗ Branch 3 not taken.
|
240 | config["receiver"] = receiver; |
| 440 |
1/2✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
|
480 | return encodeFalconV5Packet(config, packet); |
| 441 | 240 | } | |
| 442 | ✗ | bool FalconV5Support::ReceiverChain::generateToggleEFusePacket(uint8_t* packet, int receiver, int port) const { | |
| 443 | ✗ | Json::Value config; | |
| 444 | ✗ | config["type"] = "toggleFuse"; | |
| 445 | ✗ | config["receiver"] = receiver; | |
| 446 | ✗ | config["port"] = port; | |
| 447 | ✗ | return encodeFalconV5Packet(config, packet); | |
| 448 | ✗ | } | |
| 449 | |||
| 450 | 17 | bool FalconV5Support::ReceiverChain::generateNumberPackets(uint8_t* packet, uint8_t* packet2) const { | |
| 451 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | Json::Value config; |
| 452 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | config["type"] = "number"; |
| 453 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | config["port"] = strings[0]->m_portNumber; |
| 454 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | config["phase"] = 1; |
| 455 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | if (encodeFalconV5Packet(config, packet)) { |
| 456 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | config["phase"] = 2; |
| 457 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | return encodeFalconV5Packet(config, packet2); |
| 458 | } | ||
| 459 | ✗ | return false; | |
| 460 | 17 | } | |
| 461 | ✗ | bool FalconV5Support::ReceiverChain::generateResetEFusePacket(uint8_t* packet, int receiver, int port) const { | |
| 462 | ✗ | Json::Value config; | |
| 463 | ✗ | config["type"] = "resetFuses"; | |
| 464 | ✗ | config["receiver"] = receiver; | |
| 465 | ✗ | config["port"] = port; | |
| 466 | ✗ | return encodeFalconV5Packet(config, packet); | |
| 467 | ✗ | } | |
| 468 | |||
| 469 | 17 | bool FalconV5Support::ReceiverChain::generateSetFusesPacket(uint8_t* packet, bool on) const { | |
| 470 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
17 | Json::Value config; |
| 471 |
2/4✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 17 times.
✗ Branch 3 not taken.
|
17 | config["type"] = "setFuses"; |
| 472 |
3/6✓ 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.
|
17 | config["state"] = on ? 1 : 0; |
| 473 |
1/2✓ Branch 0 taken 17 times.
✗ Branch 1 not taken.
|
34 | return encodeFalconV5Packet(config, packet); |
| 474 | 17 | } | |
| 475 | |||
| 476 | ✗ | bool FalconV5Support::ReceiverChain::generateResetFusesPacket(uint8_t* packet) const { | |
| 477 | ✗ | Json::Value config; | |
| 478 | ✗ | config["type"] = "resetFuses"; | |
| 479 | ✗ | return encodeFalconV5Packet(config, packet); | |
| 480 | ✗ | } | |
| 481 | 240 | bool FalconV5Support::ReceiverChain::generateQueryPacket(uint8_t* packet) { | |
| 482 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
|
240 | if (curReceiverQuery == numReceivers) { |
| 483 | ✗ | curReceiverQuery = 0; | |
| 484 | } | ||
| 485 | 240 | return generateQueryPacket(packet, curReceiverQuery++); | |
| 486 | } | ||
| 487 | ✗ | bool FalconV5Support::ReceiverChain::generatePixelCountPacket(uint8_t* packet) const { | |
| 488 | ✗ | Json::Value config; | |
| 489 | ✗ | config["type"] = "pixelCount"; | |
| 490 | ✗ | return encodeFalconV5Packet(config, packet); | |
| 491 | ✗ | } | |
| 492 | |||
| 493 | ✗ | void FalconV5Support::ReceiverChain::handleQueryResponse(Json::Value& json) { | |
| 494 | try { | ||
| 495 | ✗ | int index = json["index"].asInt(); | |
| 496 | ✗ | int port = json["port"].asInt(); | |
| 497 | ✗ | int dial = json["dial"].asInt(); | |
| 498 | // decoded from a reply on the wire; a receiver has at most 4 ports | ||
| 499 | ✗ | int numPorts = std::clamp(json["numPorts"].asInt(), 0, 4); | |
| 500 | ✗ | int id = json["id"].asInt(); | |
| 501 | // printf("Resp: Idx: %d Port: %d dial: %d NP: %d ID: %d\n", index, port, dial, numPorts, id); | ||
| 502 | ✗ | for (int x = 0; x < numPorts; x++) { | |
| 503 | ✗ | OutputMonitor::INSTANCE.setSmartReceiverInfo(port + x % 4, index, | |
| 504 | ✗ | json["ports"][x]["fuseOn"].asBool(), | |
| 505 | ✗ | json["ports"][x]["fuseBlown"].asBool(), | |
| 506 | ✗ | json["ports"][x]["current"].asInt(), | |
| 507 | ✗ | json["ports"][x]["pixelCount"].asInt()); | |
| 508 | } | ||
| 509 | ✗ | } catch (std::exception& e) { | |
| 510 | ✗ | LogWarn(VB_CHANNELOUT, "Error (%s) handling response JSON: %s\n", e.what(), SaveJsonToString(json, " ").c_str()); | |
| 511 | ✗ | } | |
| 512 | ✗ | } | |
| 513 | |||
| 514 | 489 | bool FalconV5Support::generateDynamicPacket(std::vector<std::array<uint8_t, 64>>& packets, bool& listen) { | |
| 515 |
2/2✓ Branch 0 taken 129 times.
✓ Branch 1 taken 360 times.
|
489 | if (maxCount.empty()) { |
| 516 | 129 | return false; | |
| 517 | } | ||
| 518 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 240 times.
|
360 | if (curCount == maxCount[curMux]) { |
| 519 | // finished this mux, but we need to wait a frame before changing muxes | ||
| 520 | 120 | ++curCount; | |
| 521 | 120 | return false; | |
| 522 |
2/2✓ Branch 0 taken 119 times.
✓ Branch 1 taken 121 times.
|
240 | } else if (curCount > maxCount[curMux]) { |
| 523 | 119 | curCount = 0; | |
| 524 | 119 | int newMux = curMux + 1; | |
| 525 |
1/2✓ Branch 0 taken 119 times.
✗ Branch 1 not taken.
|
119 | if (newMux >= maxCount.size()) { |
| 526 | 119 | newMux = 0; | |
| 527 | 119 | triggerPixelCount = false; | |
| 528 | } | ||
| 529 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
|
119 | if (newMux != curMux) { |
| 530 | ✗ | setCurrentMux(newMux); | |
| 531 | } | ||
| 532 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 119 times.
|
119 | if (maxCount[newMux] == 0) { |
| 533 | ✗ | curCount++; | |
| 534 | ✗ | return false; | |
| 535 | } | ||
| 536 | } | ||
| 537 | 240 | curCount++; | |
| 538 | // Snapshot the pending toggle once for this pass so every chain sees the | ||
| 539 | // same port/index/command triple. | ||
| 540 | 240 | PendingToggle toggle = pendingToggle.load(); | |
| 541 |
2/2✓ Branch 0 taken 240 times.
✓ Branch 1 taken 240 times.
|
480 | for (auto& g : queryData[curMux]) { |
| 542 | 240 | auto rc = g.second.front(); | |
| 543 |
2/2✓ Branch 0 taken 239 times.
✓ Branch 1 taken 1 times.
|
240 | if (!rc->hasMoreQueries()) { |
| 544 | 239 | rc->resetQueryCount(); | |
| 545 | 239 | g.second.pop_front(); | |
| 546 |
1/2✓ Branch 0 taken 239 times.
✗ Branch 1 not taken.
|
239 | g.second.push_back(rc); |
| 547 | 239 | rc = g.second.front(); | |
| 548 | } | ||
| 549 | 240 | int rcP = rc->getPixelStrings().front()->m_portNumber; | |
| 550 |
3/6✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 240 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 240 times.
|
240 | if (rcP < 0 || rcP >= (int)packets.size()) { |
| 551 | ✗ | continue; // a port past the strings would write past the packets | |
| 552 | } | ||
| 553 |
1/6✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
|
240 | if (toggle.cmd != ToggleCommand::None && rcP <= toggle.port && (rcP + 4) > toggle.port) { |
| 554 | ✗ | if (toggle.cmd == ToggleCommand::Toggle) { | |
| 555 | ✗ | rc->generateToggleEFusePacket(&packets[rcP][0], toggle.index, toggle.port % 4); | |
| 556 | } else { | ||
| 557 | ✗ | rc->generateResetEFusePacket(&packets[rcP][0], toggle.index, toggle.port % 4); | |
| 558 | } | ||
| 559 | // Clear only the toggle we just acted on: a compare_exchange keeps | ||
| 560 | // a request that arrived since the snapshot from being dropped. | ||
| 561 | // 'expected' is a copy because a failed exchange writes through it. | ||
| 562 | ✗ | PendingToggle expected = toggle; | |
| 563 | ✗ | pendingToggle.compare_exchange_strong(expected, PendingToggle()); | |
| 564 | ✗ | toggle = PendingToggle(); | |
| 565 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 240 times.
|
240 | } else if (triggerPixelCount) { |
| 566 | ✗ | rc->generatePixelCountPacket(&packets[rcP][0]); | |
| 567 | ✗ | listen = false; | |
| 568 | } else { | ||
| 569 |
1/2✓ Branch 0 taken 240 times.
✗ Branch 1 not taken.
|
240 | rc->generateQueryPacket(&packets[rcP][0]); |
| 570 | 240 | listen = true; | |
| 571 | } | ||
| 572 | } | ||
| 573 | 240 | return true; | |
| 574 | } | ||
| 575 | |||
| 576 | ✗ | void FalconV5Support::sendCountPixelPackets() { | |
| 577 | ✗ | setCurrentMux(0); | |
| 578 | ✗ | curCount = 0; | |
| 579 | ✗ | triggerPixelCount = true; | |
| 580 | ✗ | } | |
| 581 |