GCC Code Coverage Report


Directory: mod/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 100.0% 91 / 0 / 91
Functions: 100.0% 6 / 0 / 6
Branches: 88.1% 37 / 0 / 42

debug_sdr/debug_sdr.c
Line Branch Exec Source
1 /**
2 ******************************************************************************
3 * @file : debug_sdr.c
4 * @brief : Interfaces for debugging SDR embedded firmware.
5 ******************************************************************************
6 * @copyright
7 *
8 * Copyright (c) 2026 Sun Devil Rocketry.
9 * All rights reserved.
10 *
11 * This software is licensed under terms that can be found in the LICENSE file
12 * in the root directory of this software component.
13 * If no LICENSE file comes with this software, it is covered under the
14 * BSD-3-Clause.
15 *
16 * https://opensource.org/license/bsd-3-clause
17 *
18 ******************************************************************************
19 @verbatim
20 ==============================================================================
21 ##### Integration Guide #####
22 ==============================================================================
23 [..]
24 (+) Provide an async debug write callback using debug_init(). If your debug
25 write callback is not asynchronous, you risk blocking in the middle of
26 a real-time event or otherwise hiding the true performance of the system.
27 (+) Ensure you call debug_init() in your initialization sequence before ever
28 logging to it. This can be initialized as soon as the debug write interface
29 is initialized, so consider HAL -> debug write intf -> debug module -> rest
30 of init to ensure you get useful logging throughout.
31 (+) Register debug_callback_handler (the callback handler) to your async write
32 function's callback handler.
33 (+) Optionally, register an overflow handler callback to handle circular buffer
34 overflows. This takes the full, constructed message that triggered the
35 overflow as an argument. Pass NULL to use the default handler.
36 (+) Do not use the _debug functions directly! Use the macros so that you have
37 protection against release builds.
38 (+) If you want to log in release builds, use the error_sdr logging functions.
39 ******************************************************************************
40 @endverbatim
41 */
42
43 /* Includes ------------------------------------------------------------------*/
44 #include <stdio.h>
45
46 #include "main.h"
47 #include "debug_sdr.h"
48 #include "error_sdr.h"
49 #include "timer.h"
50
51 /* Static Variables ----------------------------------------------------------*/
52 static DEBUG_HANDLE debug_handle; /* This module may be conditionally compiled
53 for debug mode only. Holding the handle
54 internally helps avoid issues in debug
55 versus release since we aren't passing
56 around this giant buffer for no reason. */
57
58
59 /* Private function prototypes -----------------------------------------------*/
60 static void default_overflow_handler(const char* trigger_msg, size_t len);
61 static DEBUG_STATUS start_tx(void);
62
63 /* Procedures ----------------------------------------------------------------*/
64
65 /**
66 * @brief Initializes the debug module
67 *
68 * @details This function should be called before any other in the
69 * debug module, as it initializes pointers and buffers to their
70 * correct values.
71 *
72 * @param write_function Callback to the function that writes over the debug
73 * interface.
74 * @param overflow_function Callback to the function that handles the buffer
75 * running out of space.
76 *
77 * Pass (void*)NULL as the overflow callback if you want to use
78 * the default overflow handler.
79 *
80 * @return The status of the debug module.
81 */
82 5 DEBUG_STATUS debug_init
83 (
84 debug_write_callback write_function,
85 overflow_callback overflow_function
86 )
87 {
88 /* Begin with an invariant check */
89
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 time.
5 assert_return( write_function, DEBUG_FAIL ); /* exit if nullptr */
90
91 /* Initialize the internal debug handle */
92
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 memset( &debug_handle, 0, sizeof( DEBUG_HANDLE ) );
93
94 /* Set the callbacks */
95 4 debug_handle.write_function = write_function; /* already null checked */
96
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 3 times.
4 if( overflow_function == 0 )
97 {
98 1 debug_handle.overflow_function = default_overflow_handler;
99 }
100 else
101 {
102 3 debug_handle.overflow_function = overflow_function;
103 }
104
105 /* Set the circular buffer's pointers */
106 4 debug_handle.buf_put_head = debug_handle.tx_buf;
107 4 debug_handle.buf_tx_head = debug_handle.tx_buf;
108 4 debug_handle.buffer_size_cnt = 0;
109
110 /* Clear the transmitting flag */
111 4 debug_handle.transmitting = false;
112
113 4 return DEBUG_OK;
114
115 } /* debug_init */
116
117 /**
118 * @brief Reset the circular buffer.
119 */
120 1 void debug_clear
121 (
122 void
123 )
124 {
125 /* Reset the circular buffer's pointers */
126 1 debug_handle.buf_put_head = debug_handle.tx_buf;
127 1 debug_handle.buf_tx_head = debug_handle.tx_buf;
128 1 debug_handle.buffer_size_cnt = 0;
129 1 } /* debug_clear */
130
131
132 /**
133 * @brief Add to the debug buffer.
134 *
135 * @note Use the macros unless you know what you're doing!
136 *
137 * @param message A C-string with the message you want to send.
138 * @param len The length of the message you want to send.
139 * @param log_level The severity of the debug message.
140 *
141 * @return The status of the debug module.
142 */
143 11 DEBUG_STATUS debug_log
144 (
145 const char* message,
146 size_t len,
147 DEBUG_LEVEL log_level
148 )
149 {
150 /* Local Variables */
151 11 char temp_msg[DEBUG_MSG_MAX_LEN];
152 11 size_t true_len = 0;
153
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 time.
11 memset(temp_msg, 0, DEBUG_MSG_MAX_LEN);
154
155 /* Begin with an invariant check */
156
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 time.
11 assert_return( message, DEBUG_FAIL ); /* exit if nullptr */
157
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 1 time.
10 assert_return( len <= DEBUG_MSG_USER_MAX_LEN, DEBUG_FAIL ); /* exit if msg too long */
158
159 /* Construct the message to be logged */
160 9 SYSTEM_TIME time = get_system_time();
161
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
9 true_len = snprintf(temp_msg, DEBUG_MSG_MAX_LEN, "[%02d:%02d:%02d.%03d ", (time.hours % 24), time.mins, time.secs, time.millis);
162
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 1 time.
9 switch( log_level )
163 {
164 2 case LOG_LVL_INFO:
165 2 true_len += snprintf( (temp_msg+true_len), DEBUG_MSG_MAX_LEN - true_len, "INFO]: " );
166 2 break;
167 3 case LOG_LVL_WARN:
168 3 true_len += snprintf( (temp_msg+true_len), DEBUG_MSG_MAX_LEN - true_len, "WARN]: " );
169 3 break;
170 3 case LOG_LVL_ERROR:
171 3 true_len += snprintf( (temp_msg+true_len), DEBUG_MSG_MAX_LEN - true_len, "ERROR]: " );
172 3 break;
173 default:
174 return DEBUG_FAIL;
175 }
176
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 strncpy(temp_msg+true_len, message, len);
177 8 true_len += len;
178 8 temp_msg[true_len] = '\n';
179 8 true_len++;
180
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8 assert_return( true_len <= DEBUG_MSG_MAX_LEN, DEBUG_FAIL );
181
182 /* Check the overflow condition */
183
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if( debug_handle.buffer_size_cnt + true_len > DEBUG_INTF_BUF_SIZE )
184 {
185 2 debug_handle.overflow_function( temp_msg, true_len );
186 2 return DEBUG_OVERFLOW;
187 }
188
189 /* Put the message in the buffer */
190 6 size_t space_to_end = debug_handle.tx_buf + DEBUG_INTF_BUF_SIZE - debug_handle.buf_put_head;
191
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if( true_len <= space_to_end )
192 {
193 4 memset( debug_handle.buf_put_head, 0, true_len );
194 4 memcpy( debug_handle.buf_put_head, temp_msg, true_len );
195 }
196 else
197 {
198 /* two copies to handle wraparound */
199 2 memset( debug_handle.buf_put_head, 0, space_to_end );
200 2 memset( debug_handle.tx_buf, 0, true_len - space_to_end );
201 2 memcpy( debug_handle.buf_put_head, temp_msg, space_to_end );
202 2 memcpy( debug_handle.tx_buf, temp_msg + space_to_end, true_len - space_to_end );
203 }
204 6 debug_handle.buffer_size_cnt += true_len;
205 /* Gross pointer arithmetic to set the buffer put head */
206 6 debug_handle.buf_put_head = debug_handle.tx_buf + ((debug_handle.buf_put_head - debug_handle.tx_buf) + true_len) % DEBUG_INTF_BUF_SIZE;
207
208 /* Kick off a transmission if one's not already going */
209
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 time.
6 if( !debug_handle.transmitting )
210 {
211 5 return start_tx();
212 }
213 else
214 {
215 return DEBUG_OK;
216 }
217
218 } /* debug_log */
219
220
221 /**
222 * @brief The function to call after a write completes.
223 */
224 5 void debug_callback_handler
225 (
226 void
227 )
228 {
229 5 debug_handle.transmitting = false;
230
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 4 times.
5 if( debug_handle.buffer_size_cnt > 0 )
231 {
232 1 start_tx();
233 }
234
235 5 } /* debug_callback_handler */
236
237
238 /**
239 * @brief Start a transmission over the debug interface.
240 *
241 * @return The status of the debug module.
242 */
243 6 static DEBUG_STATUS start_tx
244 (
245 void
246 )
247 {
248 /* Local variables */
249 6 size_t msg_len = 0;
250 6 uint8_t* tx_addr = debug_handle.buf_tx_head;
251 6 size_t space_to_end = debug_handle.tx_buf + DEBUG_INTF_BUF_SIZE - tx_addr;
252 6 static uint8_t staging_buf[DEBUG_MSG_MAX_LEN];
253 6 memset( staging_buf, 0, DEBUG_MSG_MAX_LEN );
254
255 /* Get the length of the string to be written */
256
1/2
✓ Branch 0 taken 212 times.
✗ Branch 1 not taken.
212 for( uint16_t i = 0; i < DEBUG_MSG_MAX_LEN; i++ )
257 {
258 212 uint8_t* search_addr = debug_handle.tx_buf + ((tx_addr - debug_handle.tx_buf) + i) % DEBUG_INTF_BUF_SIZE;
259
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 206 times.
212 if( (char)*(search_addr) == '\n' )
260 {
261 6 msg_len = i + 1;
262 6 break;
263 }
264 }
265
266 /* Check invariants */
267 6 assert_return( msg_len != 0, DEBUG_FAIL );
268
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 assert_return( msg_len <= DEBUG_MSG_MAX_LEN, DEBUG_FAIL );
269
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 assert_return( msg_len <= debug_handle.buffer_size_cnt, DEBUG_FAIL );
270
271 /* Kick off write */
272
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2 times.
6 if( msg_len <= space_to_end )
273 {
274 4 memcpy( staging_buf, tx_addr, msg_len );
275 4 memset( tx_addr, 0, msg_len );
276 }
277 else
278 {
279 /* Message wraps: write in two halves */
280 2 memcpy( staging_buf, tx_addr, space_to_end );
281 2 memset( tx_addr, 0, space_to_end );
282 2 memcpy( staging_buf + space_to_end, debug_handle.tx_buf, msg_len - space_to_end );
283 2 memset( debug_handle.tx_buf, 0, msg_len - space_to_end );
284 }
285
286 /* Kick off write */
287 6 debug_handle.transmitting = true;
288 6 debug_handle.buf_tx_head = debug_handle.tx_buf + ((debug_handle.buf_tx_head - debug_handle.tx_buf) + msg_len) % DEBUG_INTF_BUF_SIZE;
289 6 debug_handle.buffer_size_cnt -= msg_len;
290 6 debug_handle.write_function( staging_buf, msg_len );
291
292 6 return DEBUG_OK;
293
294 } /* start_tx */
295
296
297 /**
298 * @brief The default callback for a buffer overflow event.
299 *
300 * @param trigger_msg The message that overflowed the buffer.
301 * @param len The length of the trigger message.
302 */
303 1 static void default_overflow_handler
304 (
305 const char* trigger_msg,
306 size_t len
307 )
308 {
309 1 const char* msg = "Debug buffer overflowed!";
310 /* Clear the buffer and put a new message */
311 1 debug_clear();
312 1 debug_log(msg, strlen( msg ), LOG_LVL_WARN);
313
314 1 } /* default_overflow_handler */
315