GCC Code Coverage Report


Directory: lib/fprime-samd/fprime-samd/Drv/UsartDriver/
File: UsartDriver.cpp
Date: 2026-09-26 00:05:17
Exec Total Coverage
Lines: 161 184 87.5%
Functions: 12 12 100.0%
Branches: 118 135 87.4%

Line Branch Exec Source
1 // ======================================================================
2 // \title UsartDriver.cpp
3 // \author tumbar
4 // \brief cpp file for UsartDriver component implementation class
5 // ======================================================================
6
7 #include "fprime-samd/Drv/UsartDriver/UsartDriver.hpp"
8 #include "Drv/ByteStreamDriverModel/ByteStreamStatusEnumAc.hpp"
9 #include "Fw/Types/Assert.hpp"
10 #include "Fw/Types/SuccessEnumAc.hpp"
11 #include "config/FwAssertArgTypeAliasAc.h"
12 #include "config/FwIndexTypeAliasAc.h"
13 #include "fprime-samd/Drv/Types/CriticalSection.hpp"
14 #include "fprime-samd/Drv/Types/PriorityEnumAc.hpp"
15 #include "fprime-samd/Drv/Types/Sercom.hpp"
16 #include "fprime-samd/Drv/Types/ThinBuffer.hpp"
17 #include "fprime-samd/Drv/UsartDriver/UsartDriverHardware.hpp"
18 #include "fprime-samd/Drv/UsartDriver/UsartDriver_DmaChannelEnumAc.hpp"
19 #include "samd-config/UsartDriverConfig.hpp"
20
21 namespace Samd21 {
22
23 // ----------------------------------------------------------------------
24 // Component construction and destruction
25 // ----------------------------------------------------------------------
26
27 24 UsartDriver ::UsartDriver(const char* const compName)
28 : UsartDriverComponentBase(compName),
29
1/1
✓ Branch 3 taken 24 times.
24 m_sercom(SercomKind::SERCOM_0),
30 24 m_active_rx(RxDmaBufferID::INVALID),
31 24 m_configured(false),
32 24 m_active_processed(0),
33 24 m_rxOverflows(0),
34 24 m_schedSkips(0),
35 24 m_rx_activity(0),
36 24 m_last_sched_activity(0),
37 24 m_rxBytes(0),
38
2/2
✓ Branch 10 taken 24 times.
✓ Branch 20 taken 24 times.
72 m_txBytes(0) {}
39
40 48 UsartDriver ::~UsartDriver() = default;
41
42 21 void UsartDriver ::configure(SercomKind sercom,
43 RxPinOut rx,
44 TxPinOut tx,
45 ClockMode clock,
46 CommunicationMode mode,
47 BaudRate baud_rate,
48 DataOrder data_order,
49 DataBits data_bits,
50 StopBits stop_bits,
51 Parity parity) {
52 21 FW_ASSERT(!this->m_configured, sercom.e);
53
54 // Store configuration
55 21 m_sercom = sercom;
56
57 // Configure and enable the SERCOM USART peripheral.
58
2/2
✓ Branch 2 taken 21 times.
✓ Branch 5 taken 21 times.
21 UsartHardware::UsartHal::configure(sercom, rx, tx, clock, mode, baud_rate, data_order, data_bits, stop_bits,
59 parity);
60
61 // Initialize the RX state
62 21 this->m_active_rx = RxDmaBufferID::A;
63 21 this->m_active_processed = 0;
64
65 21 this->m_configured = true;
66
67
2/2
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 6 times.
21 if (this->isConnected_ready_OutputPort(0)) {
68 15 this->ready_out(0);
69 }
70
71 // Queue the Rx buffer to get the DMA to start reading from the uart
72
1/2
✓ Branch 5 taken 21 times.
✗ Branch 6 not taken.
21 if (this->isConnected_dmaQueueOut_OutputPort(UsartDriver_DmaChannel::RX)) {
73 // Send these buffers to the DMA
74
2/2
✓ Branch 7 taken 21 times.
✓ Branch 11 taken 21 times.
21 this->dmaQueueRxSend(ThinBuffer(this->m_rx[0].data, USART_RX_BUFFER_SIZE));
75
2/2
✓ Branch 7 taken 21 times.
✓ Branch 11 taken 21 times.
21 this->dmaQueueRxSend(ThinBuffer(this->m_rx[1].data, USART_RX_BUFFER_SIZE));
76 21 this->dmaRxCircular_out(0);
77 }
78 21 }
79
80 // ----------------------------------------------------------------------
81 // Handler implementations for typed input ports
82 // ----------------------------------------------------------------------
83
84 7 void UsartDriver ::schedIn_handler(FwIndexType portNum, U32 context) {
85
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 6 times.
✓ Branch 6 taken 1 times.
7 if (this->m_configured) {
86 // Check the SERCOM RX hardware overflow flag. This is a plain register
87 // read (no channel suspend) and is safe to do at any RX rate; it tells us
88 // whether the peripheral silently dropped any bytes since the last tick.
89
3/4
✓ Branch 5 taken 6 times.
✓ Branch 8 taken 6 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 6 times.
6 if (UsartHardware::UsartHal::checkAndClearRxOverflow(this->m_sercom)) {
90 ✗ this->m_rxOverflows++;
91 }
92
93 // High-RX watchdog. dmaReplyRxIsr bumps m_rx_activity on every full-buffer
94 // RX completion. If it advanced since the previous tick, RX is actively
95 // streaming and the partial read below
96 6 const U32 activity = this->m_rx_activity;
97 6 const bool rxBusy = (activity != this->m_last_sched_activity);
98 6 this->m_last_sched_activity = activity;
99
100
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 5 times.
6 if (rxBusy) {
101 1 this->m_schedSkips++;
102 } else {
103 // RX is idle -- safe to suspend and read the in-progress transfer.
104
1/1
✓ Branch 3 taken 5 times.
5 auto currentRx = this->dmaRxRead_out(0);
105
106 // Absolute count of bytes filled in the current DMA buffer so far
107 5 auto filled = static_cast<U16>(USART_RX_BUFFER_SIZE - currentRx.get_btcnt());
108
109 // Only notify if new data has arrived since we last processed this buffer.
110 // Signals carry the absolute high-water mark (not a delta) so that a PARTIAL and
111 // a concurrently-enqueued DONE/PARTIAL cannot double-count the same bytes.
112
2/2
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 1 times.
5 if (filled > this->m_active_processed) {
113 // Notify our queue of the current Rx status.
114 // m_queue is shared with the DMA ISR (dmaReplyIn), so the enqueue must be
115 // protected from a concurrent enqueue happening inside the ISR.
116
1/1
✓ Branch 2 taken 4 times.
4 Fw::Success status;
117 {
118
1/1
✓ Branch 2 taken 4 times.
4 CriticalSection cs;
119
2/2
✓ Branch 7 taken 4 times.
✓ Branch 12 taken 4 times.
4 status = this->m_queue.enqueue(Signal(SignalKind::RX_BUFFER_PARTIAL, filled));
120 4 }
121 4 FW_ASSERT(status == Fw::Success::SUCCESS, status);
122 4 }
123 5 }
124
125 // Emit diagnostics on the sched tick (no added TX load beyond telemetry).
126
1/1
✓ Branch 3 taken 6 times.
6 auto time = getTime();
127
2/2
✓ Branch 6 taken 6 times.
✓ Branch 13 taken 6 times.
6 this->tlmWrite_rxOverflows(this->m_rxOverflows, time);
128
2/2
✓ Branch 6 taken 6 times.
✓ Branch 13 taken 6 times.
6 this->tlmWrite_rxBytes(this->m_rxBytes, time);
129
2/2
✓ Branch 6 taken 6 times.
✓ Branch 13 taken 6 times.
6 this->tlmWrite_txBytes(this->m_txBytes, time);
130 6 }
131 7 }
132
133 10 bool UsartDriver ::activeIn_handler(FwIndexType portNum, U32 context) {
134
3/4
✗ Branch 3 not taken.
✓ Branch 4 taken 10 times.
✓ Branch 5 taken 9 times.
✓ Branch 6 taken 1 times.
10 if (this->m_configured) {
135 // Tracks whether we actually dequeued (and processed) any signal this
136 // tick. Returned to the caller so the cycler knows whether to re-invoke
137 // us (work was done) or let it go idle (queue was empty).
138 9 bool didWork = false;
139
140 // Unload the queue
141 for (;;) {
142 17 Signal signal;
143
1/1
✓ Branch 2 taken 17 times.
17 Fw::Success status;
144
145 // Dequeue an item off the queue
146 {
147
1/1
✓ Branch 2 taken 17 times.
17 CriticalSection cs;
148
3/3
✓ Branch 6 taken 17 times.
✓ Branch 8 taken 9 times.
✓ Branch 9 taken 8 times.
17 if (this->m_queue.isEmpty()) {
149 // No more items
150 9 break;
151 } else {
152
2/2
✓ Branch 4 taken 8 times.
✓ Branch 9 taken 8 times.
8 status = this->m_queue.dequeue(signal);
153 }
154 17 }
155
156 8 FW_ASSERT(status == Fw::Success::SUCCESS, status);
157 8 didWork = true;
158
159
1/1
✓ Branch 2 taken 8 times.
8 ThinBuffer thinBuffer;
160
1/1
✓ Branch 2 taken 8 times.
8 Fw::Buffer thickBuffer;
161
1/1
✓ Branch 2 taken 8 times.
8 Fw::Success bufferStatus;
162
163
2/3
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
8 switch (signal.kind) {
164 1 case SignalKind::TX_BUFFER_OK:
165
2/2
✓ Branch 4 taken 1 times.
✓ Branch 9 taken 1 times.
1 bufferStatus = this->m_tx_queue.dequeue(thinBuffer);
166 1 FW_ASSERT(bufferStatus == Fw::Success::SUCCESS);
167
2/2
✓ Branch 2 taken 1 times.
✓ Branch 7 taken 1 times.
1 thickBuffer = thinBuffer.getBuffer();
168
2/2
✓ Branch 6 taken 1 times.
✓ Branch 10 taken 1 times.
1 this->sendReturnOut_out(0, thickBuffer, Drv::ByteStreamStatus::OP_OK);
169 1 break;
170 7 case SignalKind::RX_BUFFER_DONE:
171 case SignalKind::RX_BUFFER_PARTIAL: {
172 7 FW_ASSERT(signal.rx_bytes <= USART_RX_BUFFER_SIZE, this->m_sercom, signal.rx_bytes);
173 7 FW_ASSERT(signal.rx_bytes >= this->m_active_processed, this->m_sercom, this->m_active_processed,
174 signal.rx_bytes);
175
176 7 U16 newBytes = signal.rx_bytes - this->m_active_processed;
177 // Wrap the newly received region of the current DMA buffer in a Fw::Buffer
178
2/4
✓ Branch 4 taken 5 times.
✓ Branch 5 taken 2 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
7 switch (this->m_active_rx) {
179 5 case RxDmaBufferID::A:
180
2/2
✓ Branch 9 taken 5 times.
✓ Branch 14 taken 5 times.
5 thickBuffer = Fw::Buffer(this->m_rx[0].data + this->m_active_processed, newBytes);
181 5 break;
182 2 case RxDmaBufferID::B:
183
2/2
✓ Branch 9 taken 2 times.
✓ Branch 14 taken 2 times.
2 thickBuffer = Fw::Buffer(this->m_rx[1].data + this->m_active_processed, newBytes);
184 2 break;
185 ✗ case RxDmaBufferID::INVALID:
186 ✗ break;
187 ✗ default:
188 ✗ FW_ASSERT(false);
189 }
190
191
2/3
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
7 switch (signal.kind) {
192 4 case SignalKind::RX_BUFFER_PARTIAL:
193 // We got part of a buffer, track the amount of data we received
194
1/1
✓ Branch 2 taken 4 times.
4 this->m_active_processed += thickBuffer.getSize();
195 4 break;
196 3 case SignalKind::RX_BUFFER_DONE:
197 // We got the entire buffer, we need to tick the state machine
198
2/4
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 1 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
3 switch (this->m_active_rx) {
199 2 case RxDmaBufferID::A:
200 // Update all the states to represent the new overall state
201 2 this->m_active_rx = RxDmaBufferID::B;
202 2 this->m_active_processed = 0;
203 2 break;
204 1 case RxDmaBufferID::B:
205 // Update all the states to represent the new overall state
206 1 this->m_active_rx = RxDmaBufferID::A;
207 1 this->m_active_processed = 0;
208 1 break;
209 ✗ case RxDmaBufferID::INVALID:
210 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(this->m_active_rx));
211 ✗ default:
212 ✗ FW_ASSERT(false);
213 }
214 3 break;
215 ✗ default:
216 ✗ FW_ASSERT(false); // unreachable
217 }
218
219 // Send the data we received to downstream uplink
220
2/3
✓ Branch 2 taken 7 times.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 if (thickBuffer.getSize() > 0) {
221
1/1
✓ Branch 2 taken 7 times.
7 this->m_rxBytes += static_cast<U32>(thickBuffer.getSize());
222
2/3
✓ Branch 5 taken 7 times.
✓ Branch 7 taken 7 times.
✗ Branch 8 not taken.
7 if (this->isConnected_recv_OutputPort(0)) {
223
2/2
✓ Branch 6 taken 7 times.
✓ Branch 10 taken 7 times.
7 this->recv_out(0, thickBuffer, Drv::ByteStreamStatus::OP_OK);
224 }
225 }
226
227 7 break;
228 }
229 ✗ default:
230 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(signal.kind));
231 }
232 25 }
233
234 9 return didWork;
235 }
236
237 // Not configured: nothing to do this tick.
238 1 return false;
239 }
240
241 4 void UsartDriver ::dmaReplyIn_handler(FwIndexType portNum, const Samd21::Dma::Reply& reply) {
242
2/3
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
4 switch (portNum) {
243 1 case UsartDriver_DmaChannel::TX:
244 1 this->dmaReplyTxIsr(reply);
245 1 break;
246 3 case UsartDriver_DmaChannel::RX:
247 3 this->dmaReplyRxIsr(reply);
248 3 break;
249 ✗ default:
250 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(portNum));
251 }
252 4 }
253
254 1 void UsartDriver ::recvReturnIn_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
255 // The downstream process gave us our buffer back
256 // These buffers are always in the DMA, this is a no-op
257 // Do we need overrun detection here?
258 1 }
259
260 12 void UsartDriver ::send_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
261 12 FW_ASSERT(this->m_configured);
262
263
2/2
✓ Branch 6 taken 12 times.
✓ Branch 10 taken 12 times.
12 auto status = this->m_tx_queue.enqueue(ThinBuffer(fwBuffer));
264
2/2
✓ Branch 2 taken 11 times.
✓ Branch 3 taken 1 times.
12 if (status == Fw::Success::SUCCESS) {
265
1/1
✓ Branch 4 taken 11 times.
11 this->m_txBytes += static_cast<U32>(fwBuffer.getSize());
266 // This job has been added to the queue, send it to the DMA
267 // Note: Use DATA.reg to get the actual register address, not the structure address
268
8/8
✓ Branch 6 taken 11 times.
✓ Branch 11 taken 11 times.
✓ Branch 16 taken 11 times.
✓ Branch 20 taken 11 times.
✓ Branch 23 taken 11 times.
✓ Branch 26 taken 11 times.
✓ Branch 31 taken 11 times.
✓ Branch 35 taken 11 times.
55 this->dmaQueueOut_out(
269
2/2
✓ Branch 6 taken 11 times.
✓ Branch 9 taken 11 times.
22 UsartDriver_DmaChannel::TX, SercomUtil::txDmaTrigger(m_sercom), Dma::TransactionType::BEAT,
270
1/1
✓ Branch 4 taken 11 times.
11 Dma::Priority::PRIORITY_0, static_cast<U32>(reinterpret_cast<uintptr_t>(fwBuffer.getData())),
271
1/1
✓ Branch 4 taken 11 times.
11 UsartHardware::UsartHal::getDataRegisterAddress(m_sercom), fwBuffer.getSize(), Dma::BeatSize::BYTE,
272 /* increment source */ true,
273 /* incrementDestination */ false, Dma::AddressIncrementStepSize::SIZE_1, Dma::StepSelection::SOURCE);
274 } else {
275 // We could not add this buffer to the queue
276 // Reply back to the producer with a "retry"
277
2/2
✓ Branch 6 taken 1 times.
✓ Branch 10 taken 1 times.
1 this->sendReturnOut_out(0, fwBuffer, Drv::ByteStreamStatus::SEND_RETRY);
278 }
279 24 }
280
281 1 Drv::ByteStreamStatus UsartDriver ::sendSync_handler(FwIndexType portNum, Fw::Buffer& sendBuffer) {
282
2/2
✓ Branch 13 taken 1 times.
✓ Branch 16 taken 1 times.
1 UsartHardware::UsartHal::sendSync(this->m_sercom, sendBuffer.getData(), sendBuffer.getSize());
283 1 return Drv::ByteStreamStatus::OP_OK;
284 }
285
286 42 void UsartDriver ::dmaQueueRxSend(const ThinBuffer& buffer) {
287
8/8
✓ Branch 6 taken 42 times.
✓ Branch 11 taken 42 times.
✓ Branch 16 taken 42 times.
✓ Branch 20 taken 42 times.
✓ Branch 23 taken 42 times.
✓ Branch 27 taken 42 times.
✓ Branch 32 taken 42 times.
✓ Branch 36 taken 42 times.
168 this->dmaQueueOut_out(
288
2/2
✓ Branch 6 taken 42 times.
✓ Branch 9 taken 42 times.
84 UsartDriver_DmaChannel::RX, SercomUtil::rxDmaTrigger(m_sercom), Dma::TransactionType::BEAT,
289 42 Dma::Priority::PRIORITY_0, UsartHardware::UsartHal::getDataRegisterAddress(m_sercom),
290
2/2
✓ Branch 2 taken 42 times.
✓ Branch 6 taken 42 times.
42 static_cast<U32>(reinterpret_cast<uintptr_t>(buffer.getData())), buffer.getSize(), Dma::BeatSize::BYTE,
291 /* increment source */ false,
292 /* incrementDestination */ true, Dma::AddressIncrementStepSize::SIZE_1, Dma::StepSelection::DESTINATION);
293 42 }
294
295 1 void UsartDriver ::dmaReplyTxIsr(const Samd21::Dma::Reply& reply) {
296
1/1
✓ Branch 2 taken 1 times.
1 Fw::Success status;
297
1/3
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
1 switch (reply.get_status()) {
298 1 case Dma::Status::OK: {
299
1/1
✓ Branch 2 taken 1 times.
1 CriticalSection cs;
300
2/2
✓ Branch 7 taken 1 times.
✓ Branch 12 taken 1 times.
1 status = this->m_queue.enqueue(Signal(SignalKind::TX_BUFFER_OK, 0));
301 1 }
302 1 FW_ASSERT(status == Fw::Success::SUCCESS, status);
303 1 break;
304 ✗ case Dma::Status::BUS_ERROR:
305 // We tried to transmit from a buffer on an invalid address
306 ✗ FW_ASSERT(false, this->m_sercom);
307 break;
308 ✗ default:
309 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(reply.get_status()));
310 }
311 2 }
312
313 3 void UsartDriver ::dmaReplyRxIsr(const Samd21::Dma::Reply& reply) {
314
1/1
✓ Branch 2 taken 3 times.
3 Fw::Success status;
315
1/3
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
3 switch (reply.get_status()) {
316 3 case Dma::Status::OK:
317 // Make sure remaining bytes are consistent with our storage
318 3 FW_ASSERT(reply.get_remainingBytes() <= USART_RX_BUFFER_SIZE, reply.get_remainingBytes());
319 // Signal the high-RX watchdog that a full buffer just completed. schedIn
320 // compares this across ticks to decide whether RX is busy (and thus
321 // whether the suspend-inducing partial read is safe to run).
322 3 this->m_rx_activity++;
323 {
324 // Carry the absolute count of bytes filled in the current buffer (high-water mark)
325 // rather than a delta against m_active_processed. m_active_processed is only mutated
326 // in activeIn_handler, so reading it here (in an ISR) would race; the absolute count
327 // is computed purely from DMA state and the delta is resolved at consume time.
328
1/1
✓ Branch 2 taken 3 times.
3 CriticalSection cs;
329
1/1
✓ Branch 4 taken 3 times.
12 status = this->m_queue.enqueue(
330 9 Signal(SignalKind::RX_BUFFER_DONE,
331
1/1
✓ Branch 7 taken 3 times.
6 static_cast<U16>(USART_RX_BUFFER_SIZE - static_cast<U16>(reply.get_remainingBytes()))));
332 3 }
333 // TODO(tumbar) If we are Rx-ing too fast, this will assert
334 // Maybe a better thing to do is to drop this buffer?
335 3 FW_ASSERT(status == Fw::Success::SUCCESS, status);
336 3 break;
337 ✗ case Dma::Status::BUS_ERROR:
338 // We tried to receive to a buffer on an invalid address
339 ✗ FW_ASSERT(false, this->m_sercom);
340 break;
341 ✗ default:
342 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(reply.get_status()));
343 }
344 6 }
345
346 } // namespace Samd21
347