| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #pragma once | ||
| 2 | /* | ||
| 3 | * This file is part of the Falcon Player (FPP) and is Copyright (C) | ||
| 4 | * 2013-2022 by the Falcon Player Developers. | ||
| 5 | * | ||
| 6 | * The Falcon Player (FPP) is free software, and is covered under | ||
| 7 | * multiple Open Source licenses. Please see the included 'LICENSES' | ||
| 8 | * file for descriptions of what files are covered by each license. | ||
| 9 | * | ||
| 10 | * This source file is covered under the LGPL v2.1 as described in the | ||
| 11 | * included LICENSE.LGPL file. | ||
| 12 | */ | ||
| 13 | |||
| 14 | #include <inttypes.h> | ||
| 15 | #include <stdio.h> | ||
| 16 | #include <string> | ||
| 17 | #include <vector> | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Mapping of the PRU memory spaces. | ||
| 21 | * | ||
| 22 | * The PRU has a 8K, fast local data RAM that is mapped into ARM memory, | ||
| 23 | * as well as a 12K, fast local data RAM that is shared for both PRU's | ||
| 24 | * as well as slower access to the DDR RAM of the ARM. | ||
| 25 | */ | ||
| 26 | class BBBPru { | ||
| 27 | public: | ||
| 28 | BBBPru(int pru, bool mapShared = false, bool mapOther = false); | ||
| 29 | ~BBBPru(); | ||
| 30 | |||
| 31 | // clearSharedMems: clear the shared RAM and (if mapped) the other | ||
| 32 | // PRU's data RAM after the firmware load. Pass false when another | ||
| 33 | // driver owns the other PRU (a shared panels + strings cape) so a | ||
| 34 | // restart of this output cannot wipe its ring or command state. | ||
| 35 | // clearMems: false skips clearing any PRU memory, before or after the | ||
| 36 | // core starts, for firmware that zeroes its own handshake words before | ||
| 37 | // reading them and only reads data the ARM wrote ahead of the command | ||
| 38 | // that releases it. run() then does not touch PRU memory at all. | ||
| 39 | bool run(const std::string& program, bool clearSharedMems = true, bool clearMems = true); | ||
| 40 | void stop(); | ||
| 41 | |||
| 42 | // Halt/restart the core in place. Unlike stop()/run() these do not go | ||
| 43 | // through remoteproc or reload the firmware image: the program counter | ||
| 44 | // and every register survive, so resume() continues from exactly where | ||
| 45 | // pause() stopped and the data/shared RAM mappings stay usable while the | ||
| 46 | // core is halted. | ||
| 47 | // | ||
| 48 | // Two things to know before pausing: | ||
| 49 | // - the output pins hold whatever the program last drove onto them, so | ||
| 50 | // only pause where the firmware has parked them somewhere safe | ||
| 51 | // - the cycle counter stops, which is usually the point: it saturates | ||
| 52 | // after 2^32 cycles (~17s at 250MHz) and the hardware clears CTR_EN | ||
| 53 | // when it does. RESET_PRU_CLOCK does not recover from that, so any | ||
| 54 | // firmware spinning on GET_PRU_CLOCK would hang forever afterwards. | ||
| 55 | // A core left running but idle for >17s hits this; a paused one does | ||
| 56 | // not. | ||
| 57 | void pause(); | ||
| 58 | void resume(); | ||
| 59 | |||
| 60 | void clearPRUMem(uint8_t* ptr, size_t sz); | ||
| 61 | void memcpyToPRU(uint8_t* dst, uint8_t* src, size_t sz); | ||
| 62 | |||
| 63 | unsigned pru_num; | ||
| 64 | |||
| 65 | uint8_t* data_ram; // PRU data ram in ARM space | ||
| 66 | size_t data_ram_size; // size in bytes of the PRU's data RAM | ||
| 67 | |||
| 68 | uint8_t* shared_ram; // PRU data ram in ARM space | ||
| 69 | size_t shared_ram_size; // size in bytes of the PRU's data RAM | ||
| 70 | |||
| 71 | uint8_t* other_data_ram; // PRU data ram in ARM space | ||
| 72 | size_t other_data_ram_size; // size in bytes of the PRU's data RAM | ||
| 73 | |||
| 74 | uint8_t* ddr; // PRU DMA address (in ARM space) | ||
| 75 | |||
| 76 | // Shared allocator for the PRU reserved DDR region. Multiple outputs | ||
| 77 | // can coexist on it (K8-B-Scroller: BBB48String strings + BBBMatrix | ||
| 78 | // panels + BBBSerial) and historically used hardcoded offsets that | ||
| 79 | // could silently overlap. Allocations are page aligned, first fit | ||
| 80 | // (partial config reloads free and re-request in arbitrary order), and | ||
| 81 | // named so a re-request by the same owner replaces its allocation. | ||
| 82 | // Returns nullptr (and logs) when the region cannot fit the request. | ||
| 83 | static uint8_t* ddrAlloc(const std::string& owner, size_t size, uint32_t& physAddr, size_t minOffset = 0); | ||
| 84 | static void ddrRelease(const std::string& owner); | ||
| 85 | uint32_t ddr_addr; // PRU DMA address (in PRU space) | ||
| 86 | size_t ddr_size; // Size in bytes of the shared space | ||
| 87 | |||
| 88 | uint8_t* sram; // SRAM data ram in ARM space | ||
| 89 | uint32_t sram_addr; // SRAM address (in PRU space) | ||
| 90 | size_t sram_size; // size in bytes of the SRAM | ||
| 91 | |||
| 92 | uint8_t* m4ram; // M4RAM data ram in ARM space | ||
| 93 | uint32_t m4ram_addr; // M4RAM address (in PRU space) | ||
| 94 | size_t m4ram_size; // size in bytes of the M4RAM | ||
| 95 | }; | ||
| 96 | |||
| 97 | /** | ||
| 98 | * ARM-side producer for a ring buffer in the PRU shared RAM, used to stream | ||
| 99 | * output data to a PRU program with short, predictable read latency instead | ||
| 100 | * of having the PRU read DDR (1.1us+ per read on the AM62x, with much larger | ||
| 101 | * spikes). The layout and the data-RAM config handshake are described in | ||
| 102 | * pru/SMEMRing.hp; the firmware polls its data RAM for the ring location, so | ||
| 103 | * attach() must be called after BBBPru::run(). | ||
| 104 | * | ||
| 105 | * Two flow-control conventions are supported, chosen by the firmware: | ||
| 106 | * - counters: the control words hold free-running produced/consumed byte | ||
| 107 | * counts (used when the consumer wants to cache its available window) | ||
| 108 | * - pointers: the control words hold the write/read addresses; equality | ||
| 109 | * means empty, which requires all writes to be multiples of the consumer | ||
| 110 | * block size (64 bytes) | ||
| 111 | * The ring is never filled completely so full and empty stay unambiguous. | ||
| 112 | */ | ||
| 113 | class BBBPruSMEMRing { | ||
| 114 | public: | ||
| 115 | // writes the ring config into the PRU data RAM (the firmware polls for | ||
| 116 | // it) and resets the control words; pru must have been created with | ||
| 117 | // mapShared=true | ||
| 118 | void attach(BBBPru* pru, uint32_t pruBaseAddr, uint32_t ringSize, bool usePointerMode); | ||
| 119 | 569933 | bool attached() const { return ring != nullptr; } | |
| 120 | |||
| 121 | // copies up to len bytes (multiple of 8, from an 8-byte aligned src) | ||
| 122 | // into the ring using aligned 64-bit stores (the mapping is device | ||
| 123 | // memory) and publishes the new write position; returns the number of | ||
| 124 | // bytes written, limited by the available space | ||
| 125 | uint32_t write(const uint8_t* src, uint32_t len); | ||
| 126 | |||
| 127 | // PRU address the next byte will be written at. A producer that hands | ||
| 128 | // its consumer an explicit frame start publishes this one: at a frame | ||
| 129 | // boundary nothing of the new frame has been written yet, so it is that | ||
| 130 | // frame's first byte. | ||
| 131 | 2638 | uint32_t writePos() const { return basePru + writeOff; } | |
| 132 | |||
| 133 | // PRU address the consumer has published as its read position | ||
| 134 | uint32_t readPos() const { return ctrl ? ctrl[1] : basePru; } | ||
| 135 | |||
| 136 | // Re-seat the write position onto the consumer's published read position, | ||
| 137 | // abandoning anything still unread. This is a recovery path, not a | ||
| 138 | // steady-state one: it is for a producer that has given up waiting for a | ||
| 139 | // consumer that will never read what it was handed, where carrying on from | ||
| 140 | // the old write position would leave the ring permanently non-empty and | ||
| 141 | // the output dead. Returns true if it actually had to move. | ||
| 142 | 502 | bool seekToConsumer() { | |
| 143 |
2/4✓ Branch 0 taken 502 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 502 times.
|
502 | if (!ctrl || !pointerMode) { |
| 144 | ✗ | return false; | |
| 145 | } | ||
| 146 | 502 | uint32_t off = (ctrl[1] - basePru) % size; | |
| 147 | // Republish unconditionally. The published write position is the one | ||
| 148 | // the consumer compares against, and it can be wrong on its own - if | ||
| 149 | // this returned early whenever the local offset already agreed with | ||
| 150 | // the consumer, a stale published value would never be corrected and | ||
| 151 | // the ring would read as permanently occupied. | ||
| 152 |
1/4✗ Branch 0 not taken.
✓ Branch 1 taken 502 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
502 | bool moved = off != writeOff || ctrl[0] != basePru + off; |
| 153 | 502 | writeOff = off; | |
| 154 | 502 | __sync_synchronize(); | |
| 155 | 502 | ctrl[0] = basePru + writeOff; | |
| 156 | 502 | return moved; | |
| 157 | } | ||
| 158 | |||
| 159 | // bytes the consumer has not caught up on, from the published positions | ||
| 160 | 502 | uint32_t usedBytes() const { | |
| 161 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 502 times.
|
502 | if (!ctrl) { |
| 162 | ✗ | return 0; | |
| 163 | } | ||
| 164 | // counters: the difference is exact across the wrap, and no modulo | ||
| 165 | // may be applied to it; pointers: both lie inside the ring | ||
| 166 |
1/2✓ Branch 0 taken 502 times.
✗ Branch 1 not taken.
|
502 | return pointerMode ? (ctrl[0] - ctrl[1] + size) % size : ctrl[0] - ctrl[1]; |
| 167 | } | ||
| 168 | |||
| 169 | // true once the consumer has caught up with every byte written; both | ||
| 170 | // conventions publish positions that compare equal only when empty. | ||
| 171 | // Producers that stop on a frame boundary can use this to know the PRU | ||
| 172 | // has consumed exactly what was produced, so the two stay aligned in the | ||
| 173 | // byte stream across a pause. | ||
| 174 | // Compared modulo the ring size: a consumer that publishes its read | ||
| 175 | // pointer before wrapping reports "one past the end", which is the same | ||
| 176 | // position as the base. Comparing raw would call that not-drained | ||
| 177 | // forever and wedge a producer waiting on it. | ||
| 178 | 2638 | bool drained() const { | |
| 179 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2638 times.
|
2638 | if (!ctrl) { |
| 180 | ✗ | return false; | |
| 181 | } | ||
| 182 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2638 times.
|
2638 | if (!pointerMode) { |
| 183 | ✗ | return ctrl[0] == ctrl[1]; | |
| 184 | } | ||
| 185 | 2638 | return ((ctrl[0] - basePru) % size) == ((ctrl[1] - basePru) % size); | |
| 186 | } | ||
| 187 | |||
| 188 | uint32_t size = 0; | ||
| 189 | |||
| 190 | private: | ||
| 191 | volatile uint64_t* ring = nullptr; | ||
| 192 | volatile uint32_t* ctrl = nullptr; | ||
| 193 | uint32_t basePru = 0; | ||
| 194 | uint32_t writeOff = 0; | ||
| 195 | uint32_t produced = 0; | ||
| 196 | bool pointerMode = false; | ||
| 197 | }; | ||
| 198 | |||
| 199 | // Build a PRU program: runs "/bin/bash <script> <args...>" and waits for that | ||
| 200 | // child alone. fppd sets SA_NOCLDWAIT on SIGCHLD, so wait() would block until | ||
| 201 | // every child of fppd had exited (ptp4l, a running script...) and no exit | ||
| 202 | // status survives either way. The script therefore has to delete its outputs | ||
| 203 | // before building, so a failed build leaves nothing for BBBPru::run() to load | ||
| 204 | // instead of the previous configuration's program. | ||
| 205 | void CompilePRUProgram(const std::string& script, const std::vector<std::string>& args); | ||
| 206 |