GCC Code Coverage Report


Directory: ./
File: util/BBBPruUtils.h
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 17 21 81.0%
Functions: 5 5 100.0%
Branches: 7 16 43.8%

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