GCC Code Coverage Report


Directory: lib/fprime-samd/fprime-samd/
File: Drv/UsartDriver/UsartDriver.cpp
Date: 2026-09-26 00:05:09
Exec Total Coverage
Lines: 0 184 0.0%
Functions: 0 12 0.0%
Branches: 0 251 0.0%

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 ✗ UsartDriver ::UsartDriver(const char* const compName)
28 : UsartDriverComponentBase(compName),
29 ✗ m_sercom(SercomKind::SERCOM_0),
30 ✗ m_active_rx(RxDmaBufferID::INVALID),
31 ✗ m_configured(false),
32 ✗ m_active_processed(0),
33 ✗ m_rxOverflows(0),
34 ✗ m_schedSkips(0),
35 ✗ m_rx_activity(0),
36 ✗ m_last_sched_activity(0),
37 ✗ m_rxBytes(0),
38 ✗ m_txBytes(0) {}
39
40 ✗ UsartDriver ::~UsartDriver() = default;
41
42 ✗ 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 ✗ FW_ASSERT(!this->m_configured, sercom.e);
53
54 // Store configuration
55 ✗ m_sercom = sercom;
56
57 // Configure and enable the SERCOM USART peripheral.
58 ✗ 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 ✗ this->m_active_rx = RxDmaBufferID::A;
63 ✗ this->m_active_processed = 0;
64
65 ✗ this->m_configured = true;
66
67 ✗ if (this->isConnected_ready_OutputPort(0)) {
68 ✗ this->ready_out(0);
69 }
70
71 // Queue the Rx buffer to get the DMA to start reading from the uart
72 ✗ if (this->isConnected_dmaQueueOut_OutputPort(UsartDriver_DmaChannel::RX)) {
73 // Send these buffers to the DMA
74 ✗ this->dmaQueueRxSend(ThinBuffer(this->m_rx[0].data, USART_RX_BUFFER_SIZE));
75 ✗ this->dmaQueueRxSend(ThinBuffer(this->m_rx[1].data, USART_RX_BUFFER_SIZE));
76 ✗ this->dmaRxCircular_out(0);
77 }
78 ✗ }
79
80 // ----------------------------------------------------------------------
81 // Handler implementations for typed input ports
82 // ----------------------------------------------------------------------
83
84 ✗ void UsartDriver ::schedIn_handler(FwIndexType portNum, U32 context) {
85 ✗ 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 ✗ 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 ✗ const U32 activity = this->m_rx_activity;
97 ✗ const bool rxBusy = (activity != this->m_last_sched_activity);
98 ✗ this->m_last_sched_activity = activity;
99
100 ✗ if (rxBusy) {
101 ✗ this->m_schedSkips++;
102 } else {
103 // RX is idle -- safe to suspend and read the in-progress transfer.
104 ✗ auto currentRx = this->dmaRxRead_out(0);
105
106 // Absolute count of bytes filled in the current DMA buffer so far
107 ✗ 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 ✗ 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 ✗ Fw::Success status;
117 {
118 ✗ CriticalSection cs;
119 ✗ status = this->m_queue.enqueue(Signal(SignalKind::RX_BUFFER_PARTIAL, filled));
120 ✗ }
121 ✗ FW_ASSERT(status == Fw::Success::SUCCESS, status);
122 ✗ }
123 ✗ }
124
125 // Emit diagnostics on the sched tick (no added TX load beyond telemetry).
126 ✗ auto time = getTime();
127 ✗ this->tlmWrite_rxOverflows(this->m_rxOverflows, time);
128 ✗ this->tlmWrite_rxBytes(this->m_rxBytes, time);
129 ✗ this->tlmWrite_txBytes(this->m_txBytes, time);
130 ✗ }
131 ✗ }
132
133 ✗ bool UsartDriver ::activeIn_handler(FwIndexType portNum, U32 context) {
134 ✗ 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 ✗ bool didWork = false;
139
140 // Unload the queue
141 for (;;) {
142 ✗ Signal signal;
143 ✗ Fw::Success status;
144
145 // Dequeue an item off the queue
146 {
147 ✗ CriticalSection cs;
148 ✗ if (this->m_queue.isEmpty()) {
149 // No more items
150 ✗ break;
151 } else {
152 ✗ status = this->m_queue.dequeue(signal);
153 }
154 ✗ }
155
156 ✗ FW_ASSERT(status == Fw::Success::SUCCESS, status);
157 ✗ didWork = true;
158
159 ✗ ThinBuffer thinBuffer;
160 ✗ Fw::Buffer thickBuffer;
161 ✗ Fw::Success bufferStatus;
162
163 ✗ switch (signal.kind) {
164 ✗ case SignalKind::TX_BUFFER_OK:
165 ✗ bufferStatus = this->m_tx_queue.dequeue(thinBuffer);
166 ✗ FW_ASSERT(bufferStatus == Fw::Success::SUCCESS);
167 ✗ thickBuffer = thinBuffer.getBuffer();
168 ✗ this->sendReturnOut_out(0, thickBuffer, Drv::ByteStreamStatus::OP_OK);
169 ✗ break;
170 ✗ case SignalKind::RX_BUFFER_DONE:
171 case SignalKind::RX_BUFFER_PARTIAL: {
172 ✗ FW_ASSERT(signal.rx_bytes <= USART_RX_BUFFER_SIZE, this->m_sercom, signal.rx_bytes);
173 ✗ FW_ASSERT(signal.rx_bytes >= this->m_active_processed, this->m_sercom, this->m_active_processed,
174 signal.rx_bytes);
175
176 ✗ 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 ✗ switch (this->m_active_rx) {
179 ✗ case RxDmaBufferID::A:
180 ✗ thickBuffer = Fw::Buffer(this->m_rx[0].data + this->m_active_processed, newBytes);
181 ✗ break;
182 ✗ case RxDmaBufferID::B:
183 ✗ thickBuffer = Fw::Buffer(this->m_rx[1].data + this->m_active_processed, newBytes);
184 ✗ break;
185 ✗ case RxDmaBufferID::INVALID:
186 ✗ break;
187 ✗ default:
188 ✗ FW_ASSERT(false);
189 }
190
191 ✗ switch (signal.kind) {
192 ✗ case SignalKind::RX_BUFFER_PARTIAL:
193 // We got part of a buffer, track the amount of data we received
194 ✗ this->m_active_processed += thickBuffer.getSize();
195 ✗ break;
196 ✗ case SignalKind::RX_BUFFER_DONE:
197 // We got the entire buffer, we need to tick the state machine
198 ✗ switch (this->m_active_rx) {
199 ✗ case RxDmaBufferID::A:
200 // Update all the states to represent the new overall state
201 ✗ this->m_active_rx = RxDmaBufferID::B;
202 ✗ this->m_active_processed = 0;
203 ✗ break;
204 ✗ case RxDmaBufferID::B:
205 // Update all the states to represent the new overall state
206 ✗ this->m_active_rx = RxDmaBufferID::A;
207 ✗ this->m_active_processed = 0;
208 ✗ break;
209 ✗ case RxDmaBufferID::INVALID:
210 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(this->m_active_rx));
211 ✗ default:
212 ✗ FW_ASSERT(false);
213 }
214 ✗ break;
215 ✗ default:
216 ✗ FW_ASSERT(false); // unreachable
217 }
218
219 // Send the data we received to downstream uplink
220 ✗ if (thickBuffer.getSize() > 0) {
221 ✗ this->m_rxBytes += static_cast<U32>(thickBuffer.getSize());
222 ✗ if (this->isConnected_recv_OutputPort(0)) {
223 ✗ this->recv_out(0, thickBuffer, Drv::ByteStreamStatus::OP_OK);
224 }
225 }
226
227 ✗ break;
228 }
229 ✗ default:
230 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(signal.kind));
231 }
232 ✗ }
233
234 ✗ return didWork;
235 }
236
237 // Not configured: nothing to do this tick.
238 ✗ return false;
239 }
240
241 ✗ void UsartDriver ::dmaReplyIn_handler(FwIndexType portNum, const Samd21::Dma::Reply& reply) {
242 ✗ switch (portNum) {
243 ✗ case UsartDriver_DmaChannel::TX:
244 ✗ this->dmaReplyTxIsr(reply);
245 ✗ break;
246 ✗ case UsartDriver_DmaChannel::RX:
247 ✗ this->dmaReplyRxIsr(reply);
248 ✗ break;
249 ✗ default:
250 ✗ FW_ASSERT(false, static_cast<FwAssertArgType>(portNum));
251 }
252 ✗ }
253
254 ✗ 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 ✗ }
259
260 ✗ void UsartDriver ::send_handler(FwIndexType portNum, Fw::Buffer& fwBuffer) {
261 ✗ FW_ASSERT(this->m_configured);
262
263 ✗ auto status = this->m_tx_queue.enqueue(ThinBuffer(fwBuffer));
264 ✗ if (status == Fw::Success::SUCCESS) {
265 ✗ 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 ✗ this->dmaQueueOut_out(
269 ✗ UsartDriver_DmaChannel::TX, SercomUtil::txDmaTrigger(m_sercom), Dma::TransactionType::BEAT,
270 ✗ Dma::Priority::PRIORITY_0, static_cast<U32>(reinterpret_cast<uintptr_t>(fwBuffer.getData())),
271 ✗ 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 ✗ this->sendReturnOut_out(0, fwBuffer, Drv::ByteStreamStatus::SEND_RETRY);
278 }
279 ✗ }
280
281 ✗ Drv::ByteStreamStatus UsartDriver ::sendSync_handler(FwIndexType portNum, Fw::Buffer& sendBuffer) {
282 ✗ UsartHardware::UsartHal::sendSync(this->m_sercom, sendBuffer.getData(), sendBuffer.getSize());
283 ✗ return Drv::ByteStreamStatus::OP_OK;
284 }
285
286 ✗ void UsartDriver ::dmaQueueRxSend(const ThinBuffer& buffer) {
287 ✗ this->dmaQueueOut_out(
288 ✗ UsartDriver_DmaChannel::RX, SercomUtil::rxDmaTrigger(m_sercom), Dma::TransactionType::BEAT,
289 ✗ Dma::Priority::PRIORITY_0, UsartHardware::UsartHal::getDataRegisterAddress(m_sercom),
290 ✗ 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 ✗ }
294
295 ✗ void UsartDriver ::dmaReplyTxIsr(const Samd21::Dma::Reply& reply) {
296 ✗ Fw::Success status;
297 ✗ switch (reply.get_status()) {
298 ✗ case Dma::Status::OK: {
299 ✗ CriticalSection cs;
300 ✗ status = this->m_queue.enqueue(Signal(SignalKind::TX_BUFFER_OK, 0));
301 ✗ }
302 ✗ FW_ASSERT(status == Fw::Success::SUCCESS, status);
303 ✗ 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 ✗ }
312
313 ✗ void UsartDriver ::dmaReplyRxIsr(const Samd21::Dma::Reply& reply) {
314 ✗ Fw::Success status;
315 ✗ switch (reply.get_status()) {
316 ✗ case Dma::Status::OK:
317 // Make sure remaining bytes are consistent with our storage
318 ✗ 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 ✗ 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 ✗ CriticalSection cs;
329 ✗ status = this->m_queue.enqueue(
330 ✗ Signal(SignalKind::RX_BUFFER_DONE,
331 ✗ static_cast<U16>(USART_RX_BUFFER_SIZE - static_cast<U16>(reply.get_remainingBytes()))));
332 ✗ }
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 ✗ FW_ASSERT(status == Fw::Success::SUCCESS, status);
336 ✗ 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 ✗ }
345
346 } // namespace Samd21
347