1 /*
2 * Trace Recorder for Tracealyzer v4.5.1
3 * Copyright 2021 Percepio AB
4 * www.percepio.com
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 *
8 * The SAFERTOS specific parts of the trace recorder
9 */
10
11 #define KERNEL_SOURCE_FILE
12
13 #include "SafeRTOS_API.h"
14 #include "trcInternalBuffer.h"
15
16 #if (configUSE_TRACE_FACILITY == 1 && !defined(TRC_USE_TRACEALYZER_RECORDER))
17 #error Trace Recorder: You need to include trcRecorder.h at the end of your SafeRTOSConfig.h!
18 #endif
19
20 #if (defined(TRC_USE_TRACEALYZER_RECORDER) && TRC_USE_TRACEALYZER_RECORDER == 1)
21
22 #if (configUSE_TICKLESS_IDLE != 0 && (TRC_HWTC_TYPE == TRC_OS_TIMER_INCR || TRC_HWTC_TYPE == TRC_OS_TIMER_DECR))
23 /*
24 The below error message is to alert you on the following issue:
25
26 The hardware port selected in trcConfig.h uses a periodic interrupt timer for the
27 timestamping, probably the same timer as used by SafeRTOS for the tick interrupt,
28 e.g. SysTick on ARM Cortex-M.
29
30 When using tickless idle, the recorder needs an independent time source in order to
31 correctly record the length of the idle time, like a free-running counter.
32
33 You may override this warning by defining the TRC_CFG_ACKNOWLEDGE_TICKLESS_IDLE_WARNING
34 macro in your trcConfig.h file. But then the time scale may be incorrect during
35 tickless idle periods.
36
37 To get this correct, set up a hardware timer as a free-running counter, set the hardware
38 port in trcConfig.h to TRC_HARDWARE_PORT_APPLICATION_DEFINED and define the HWTC macros
39 accordingly (see trcHardwarePort.h for details).
40
41 For ARM Cortex-M3, M4 and M7 MCUs this is not an issue, since the recorder uses the
42 DWT cycle counter for timestamping when available.
43 */
44
45 #ifndef TRC_CFG_ACKNOWLEDGE_TICKLESS_IDLE_WARNING
46 #error Trace Recorder: This timestamping mode is not recommended with Tickless Idle.
47 #endif
48 #endif
49
50 /*******************************************************************************
51 * prvTraceIsSchedulerSuspended
52 *
53 * Returns true if the RTOS scheduler currently is disabled, thus preventing any
54 * task-switches from occurring. Only called from vTraceStoreISREnd.
55 ******************************************************************************/
prvTraceIsSchedulerSuspended()56 unsigned char prvTraceIsSchedulerSuspended()
57 {
58 return xTaskIsSchedulerSuspended() == pdTRUE;
59 }
60
prvTraceGetQueueType(void * handle)61 uint8_t prvTraceGetQueueType(void* handle)
62 {
63 // This is either declared in header file in FreeRTOS 8 and later, or as extern above
64 return (uint8_t)uxQueueGetQueueType(handle);
65 }
66
prvGetCurrentTaskHandle()67 void* prvGetCurrentTaskHandle()
68 {
69 return xTaskGetCurrentTaskHandle();
70 }
71
prvGetTaskNumber(void * pxObject)72 uint32_t prvGetTaskNumber(void* pxObject)
73 {
74 return uxTaskGetTaskNumber(pxObject);
75 }
76
prvGetQueueNumber(void * pxObject)77 uint32_t prvGetQueueNumber(void* pxObject)
78 {
79 return uxQueueGetQueueNumber(pxObject);
80 }
81
82 #if ((pdKERNEL_MAJOR_VERSION == 5 && pdKERNEL_MINOR_VERSION >= 10) || pdKERNEL_MAJOR_VERSION > 5)
prvGetTimerNumber(void * pxObject)83 uint32_t prvGetTimerNumber(void* pxObject)
84 {
85 /* There is no uxTimerGetTimerNumber function */
86 return ((timerControlBlockType*)pxObject)->uxTimerNumber;
87 }
88 #endif /* ((pdKERNEL_MAJOR_VERSION == 5 && pdKERNEL_MINOR_VERSION >= 10) || pdKERNEL_MAJOR_VERSION > 5) */
89
90 #if ((pdKERNEL_MAJOR_VERSION == 5 && pdKERNEL_MINOR_VERSION >= 10) || pdKERNEL_MAJOR_VERSION > 5)
prvGetEventGroupNumber(void * pxObject)91 uint32_t prvGetEventGroupNumber(void* pxObject)
92 {
93 return uxTaskGetEventGroupNumber(pxObject);
94 }
95 #endif /* ((pdKERNEL_MAJOR_VERSION == 5 && pdKERNEL_MINOR_VERSION >= 10) || pdKERNEL_MAJOR_VERSION > 5) */
96
97 #if (TRC_CFG_RECORDER_MODE == TRC_RECORDER_MODE_STREAMING)
98
99 static void* pCurrentTCB = NULL;
100 static portTaskHandleType HandleTzCtrl = 0; /* TzCtrl task TCB */
101
102 //#pragma data_alignment=TRC_CFG_CTRL_TASK_STACK_SIZE
103 static portInt8Type unalignedStackTzCtrl[TRC_CFG_CTRL_TASK_STACK_SIZE + 0x10] = { 0 }; /* Allocate more than necessary */
104 static xTCB tcbTzCtrl = { 0 };
105
106 /* Monitored by TzCtrl task, that give warnings as User Events */
107 extern volatile uint32_t NoRoomForSymbol;
108 extern volatile uint32_t NoRoomForObjectData;
109 extern volatile uint32_t LongestSymbolName;
110 extern volatile uint32_t MaxBytesTruncated;
111
112 /* User Event Channel for giving warnings regarding NoRoomForSymbol etc. */
113 traceString trcWarningChannel = 0;
114
115 TRC_STREAM_PORT_ALLOCATE_FIELDS()
116
117 /* Called by TzCtrl task periodically (Normally every 100 ms) */
118 static void prvCheckRecorderStatus(void);
119
120 /* The TzCtrl task - receives commands from Tracealyzer (start/stop) */
121 static void TzCtrl( void *pvParameters );
122
123 #if ((pdKERNEL_MAJOR_VERSION == 5 && pdKERNEL_MINOR_VERSION > 10) || pdKERNEL_MAJOR_VERSION > 5)
124 xTaskParameters TzCtrlParameters =
125 {
126 TzCtrl,
127 "TzCtrl",
128 &tcbTzCtrl,
129 0,
130 TRC_CFG_CTRL_TASK_STACK_SIZE,
131 NULL,
132 TRC_CFG_CTRL_TASK_PRIORITY,
133 0,
134 {
135 mpuPRIVILEGED_TASK,
136 {
137 { 0, 0UL, 0UL, 0UL },
138 { 0, 0UL, 0UL, 0UL },
139 { 0, 0UL, 0UL, 0UL }
140 }
141 }
142 };
143 #else /* ((pdKERNEL_MAJOR_VERSION == 5 && pdKERNEL_MINOR_VERSION > 10) || pdKERNEL_MAJOR_VERSION > 5) */
144 xTaskParameters TzCtrlParameters =
145 {
146 TzCtrl,
147 "TzCtrl",
148 &tcbTzCtrl,
149 0,
150 TRC_CFG_CTRL_TASK_STACK_SIZE,
151 NULL,
152 TRC_CFG_CTRL_TASK_PRIORITY,
153 {
154 mpuPRIVILEGED_TASK,
155 {
156 { 0, 0UL, 0UL, 0UL },
157 { 0, 0UL, 0UL, 0UL },
158 { 0, 0UL, 0UL, 0UL }
159 }
160 }
161 };
162 #endif /* ((pdKERNEL_MAJOR_VERSION == 5 && pdKERNEL_MINOR_VERSION > 10) || pdKERNEL_MAJOR_VERSION > 5) */
163
164 /*******************************************************************************
165 * vTraceEnable
166 *
167 * Function that enables the tracing and creates the control task. It will halt
168 * execution until a Start command has been received if haltUntilStart is true.
169 *
170 ******************************************************************************/
vTraceEnable(int startOption)171 void vTraceEnable(int startOption)
172 {
173 int32_t bytes = 0;
174 int32_t status;
175 TracealyzerCommandType msg;
176 extern uint32_t RecorderEnabled;
177
178 /* Make sure recorder data is initialized */
179 vTraceInitialize();
180
181 if (HandleTzCtrl == 0)
182 {
183 TRC_STREAM_PORT_INIT();
184
185 /* The #WFR channel means "Warnings from Recorder" and
186 * is used to store warnings and errors from the recorder.
187 * The abbreviation #WFR is used instead of the longer full name,
188 * to avoid truncation by small slots in the symbol table.
189 * This is translated in Tracealyzer and shown as the full name,
190 * "Warnings from Recorder".
191 *
192 * Note: Requires that TRC_CFG_INCLUDE_USER_EVENTS is 1. */
193 trcWarningChannel = xTraceRegisterString("#WFR");
194
195 /* Creates the TzCtrl task - receives trace commands (start, stop, ...) */
196 TzCtrlParameters.pcStackBuffer = (void*)((((uint32_t)unalignedStackTzCtrl) + 0xF) & ~0xF); /* Align the stack pointer we will use to 16-bytes. It's OK, we allocated 16 bytes more than we needed. */
197 xTaskCreate( &TzCtrlParameters, &HandleTzCtrl );
198 if (HandleTzCtrl == NULL)
199 {
200 prvTraceError(PSF_ERROR_TZCTRLTASK_NOT_CREATED);
201 }
202 }
203
204 if (startOption == TRC_START_AWAIT_HOST)
205 {
206 /* We keep trying to read commands until the recorder has been started */
207 do
208 {
209 bytes = 0;
210
211 status = TRC_STREAM_PORT_READ_DATA(&msg, sizeof(TracealyzerCommandType), (int32_t*)&bytes);
212
213 if (status != 0)
214 {
215 prvTraceWarning(PSF_WARNING_STREAM_PORT_READ);
216 }
217
218 if ((status == 0) && (bytes == sizeof(TracealyzerCommandType)))
219 {
220 if (prvIsValidCommand(&msg))
221 {
222 if (msg.cmdCode == CMD_SET_ACTIVE && msg.param1 == 1)
223 {
224 /* On start, init and reset the timestamping */
225 TRC_PORT_SPECIFIC_INIT();
226 }
227
228 prvProcessCommand(&msg);
229 }
230 }
231 }
232 while (RecorderEnabled == 0);
233 }
234 else if (startOption == TRC_START)
235 {
236 /* We start streaming directly - this assumes that the interface is ready! */
237 TRC_PORT_SPECIFIC_INIT();
238
239 msg.cmdCode = CMD_SET_ACTIVE;
240 msg.param1 = 1;
241 prvProcessCommand(&msg);
242 }
243 else if (startOption == TRC_INIT)
244 {
245 /* On TRC_INIT */
246 TRC_PORT_SPECIFIC_INIT();
247 }
248 }
249
250 /*******************************************************************************
251 * prvIsNewTCB
252 *
253 * Tells if this task is already executing, or if there has been a task-switch.
254 * Assumed to be called within a trace hook in kernel context.
255 ******************************************************************************/
prvIsNewTCB(void * pNewTCB)256 uint32_t prvIsNewTCB(void* pNewTCB)
257 {
258 if (pCurrentTCB != pNewTCB)
259 {
260 pCurrentTCB = pNewTCB;
261 return 1;
262 }
263 return 0;
264 }
265
266 /*******************************************************************************
267 * prvCheckRecorderStatus
268 *
269 * Called by TzCtrl task periodically (every 100 ms - seems reasonable).
270 * Checks a number of diagnostic variables and give warnings as user events,
271 * in most cases including a suggested solution.
272 ******************************************************************************/
prvCheckRecorderStatus(void)273 static void prvCheckRecorderStatus(void)
274 {
275 if (NoRoomForSymbol > 0)
276 {
277 prvTraceWarning(PSF_WARNING_SYMBOL_TABLE_SLOTS);
278 NoRoomForSymbol = 0;
279 }
280
281 if (NoRoomForObjectData > 0)
282 {
283 prvTraceWarning(PSF_WARNING_OBJECT_DATA_SLOTS);
284 NoRoomForObjectData = 0;
285 }
286
287 if (LongestSymbolName > (TRC_CFG_SYMBOL_MAX_LENGTH))
288 {
289 prvTraceWarning(PSF_WARNING_SYMBOL_MAX_LENGTH);
290 LongestSymbolName = 0;
291 }
292
293 if (MaxBytesTruncated > 0)
294 {
295 prvTraceWarning(PSF_WARNING_STRING_TOO_LONG);
296 MaxBytesTruncated = 0;
297 }
298 }
299
300 /*******************************************************************************
301 * TzCtrl
302 *
303 * Task for receiving commands from Tracealyzer and for recorder diagnostics.
304 *
305 ******************************************************************************/
TzCtrl(void * pvParameters)306 static void TzCtrl( void *pvParameters )
307 {
308 TracealyzerCommandType msg;
309 int32_t bytes = 0;
310 int32_t status = 0;
311 (void)pvParameters;
312
313 while (1)
314 {
315 do
316 {
317 /* Listen for new commands */
318 bytes = 0;
319 status = TRC_STREAM_PORT_READ_DATA(&msg, sizeof(TracealyzerCommandType), (int32_t*)&bytes);
320
321 if (status != 0)
322 {
323 /* The connection has failed, stop tracing */
324 vTraceStop();
325 }
326
327 if ((status == 0) && (bytes == sizeof(TracealyzerCommandType)))
328 {
329 if (prvIsValidCommand(&msg))
330 {
331 prvProcessCommand(&msg); /* Start or Stop currently... */
332 }
333 }
334
335 /* If the internal buffer is disabled, the COMMIT macro instead sends the data directly
336 from the "event functions" (using TRC_STREAM_PORT_WRITE_DATA). */
337 #if (TRC_STREAM_PORT_USE_INTERNAL_BUFFER == 1)
338 /* If there is a buffer page, this sends it to the streaming interface using TRC_STREAM_PORT_WRITE_DATA. */
339 bytes = prvPagedEventBufferTransfer();
340 #endif
341
342 /* If there was data sent or received (bytes != 0), loop around and repeat, if there is more data to send or receive.
343 Otherwise, step out of this loop and sleep for a while. */
344
345 } while (bytes != 0);
346
347 prvCheckRecorderStatus();
348
349 xTaskDelay(TRC_CFG_CTRL_TASK_DELAY); /* 10ms */
350 }
351 }
352
353 #endif /*(TRC_CFG_RECORDER_MODE == TRC_RECORDER_MODE_STREAMING)*/
354
355
356 #if (TRC_CFG_RECORDER_MODE == TRC_RECORDER_MODE_SNAPSHOT)
357
358 /******************************************************************************
359 * TraceQueueClassTable
360 * Translates a SAFERTOS QueueType into trace object classes (TRACE_CLASS_).
361 * Has one entry for each QueueType, gives TRACE_CLASS ID.
362 ******************************************************************************/
363 traceObjectClass TraceQueueClassTable[3] = {
364 TRACE_CLASS_QUEUE,
365 TRACE_CLASS_SEMAPHORE,
366 TRACE_CLASS_MUTEX
367 };
368
369 /******************************************************************************
370 * vTraceEnable(int startOption) - snapshot mode
371 *
372 * Initializes and optionally starts the trace, depending on the start option.
373 * To use the trace recorder, the startup must call vTraceEnable before any RTOS
374 * calls are made (including "create" calls). Three start options are provided:
375 *
376 * TRC_START: Starts the tracing directly. In snapshot mode this allows for
377 * starting the trace at any point in your code, assuming vTraceEnable(TRC_INIT)
378 * has been called in the startup.
379 * Can also be used for streaming without Tracealyzer control, e.g. to a local
380 * flash file system (assuming such a "stream port", see trcStreamingPort.h).
381 *
382 * TRC_INIT: Initializes the trace recorder, but does not start the tracing.
383 * In snapshot mode, this must be followed by a vTraceEnable(TRC_START) sometime
384 * later.
385 *
386 * Usage examples, in snapshot mode:
387 *
388 * Snapshot trace, from startup:
389 * <board init>
390 * vTraceEnable(TRC_START);
391 * <RTOS init>
392 *
393 * Snapshot trace, from a later point:
394 * <board init>
395 * vTraceEnable(TRC_INIT);
396 * <RTOS init>
397 * ...
398 * vTraceEnable(TRC_START); // e.g., in task context, at some relevant event
399 *
400 *
401 * Note: See other implementation of vTraceEnable in trcStreamingRecorder.c
402 ******************************************************************************/
vTraceEnable(int startOption)403 void vTraceEnable(int startOption)
404 {
405 vTraceInitialize();
406
407 if (startOption == TRC_START)
408 {
409 prvTraceInitTimestamps();
410
411 vTraceStart();
412 }
413 else if (startOption == TRC_START_AWAIT_HOST)
414 {
415 prvTraceError("vTraceEnable(TRC_START_AWAIT_HOST) not allowed in Snapshot mode");
416 }
417 else if (startOption != TRC_INIT)
418 {
419 prvTraceError("Unexpected argument to vTraceEnable (snapshot mode)");
420 }
421 }
422
423 /* Initialization of the object property table */
vTraceInitObjectPropertyTable()424 void vTraceInitObjectPropertyTable()
425 {
426 RecorderDataPtr->ObjectPropertyTable.NumberOfObjectClasses = TRACE_NCLASSES;
427 RecorderDataPtr->ObjectPropertyTable.NumberOfObjectsPerClass[0] = TRC_CFG_NQUEUE;
428 RecorderDataPtr->ObjectPropertyTable.NumberOfObjectsPerClass[1] = TRC_CFG_NTASK;
429 RecorderDataPtr->ObjectPropertyTable.NumberOfObjectsPerClass[2] = TRC_CFG_NISR;
430 RecorderDataPtr->ObjectPropertyTable.NumberOfObjectsPerClass[3] = TRC_CFG_NTIMER;
431 RecorderDataPtr->ObjectPropertyTable.NumberOfObjectsPerClass[4] = TRC_CFG_NEVENTGROUP;
432 RecorderDataPtr->ObjectPropertyTable.NumberOfObjectsPerClass[5] = TRC_CFG_NSEMAPHORE;
433 RecorderDataPtr->ObjectPropertyTable.NumberOfObjectsPerClass[6] = TRC_CFG_NMUTEX;
434 RecorderDataPtr->ObjectPropertyTable.NameLengthPerClass[0] = TRC_CFG_NAME_LEN_QUEUE;
435 RecorderDataPtr->ObjectPropertyTable.NameLengthPerClass[1] = TRC_CFG_NAME_LEN_TASK;
436 RecorderDataPtr->ObjectPropertyTable.NameLengthPerClass[2] = TRC_CFG_NAME_LEN_ISR;
437 RecorderDataPtr->ObjectPropertyTable.NameLengthPerClass[3] = TRC_CFG_NAME_LEN_TIMER;
438 RecorderDataPtr->ObjectPropertyTable.NameLengthPerClass[4] = TRC_CFG_NAME_LEN_EVENTGROUP;
439 RecorderDataPtr->ObjectPropertyTable.NameLengthPerClass[5] = TRC_CFG_NAME_LEN_SEMAPHORE;
440 RecorderDataPtr->ObjectPropertyTable.NameLengthPerClass[6] = TRC_CFG_NAME_LEN_MUTEX;
441 RecorderDataPtr->ObjectPropertyTable.TotalPropertyBytesPerClass[0] = PropertyTableSizeQueue;
442 RecorderDataPtr->ObjectPropertyTable.TotalPropertyBytesPerClass[1] = PropertyTableSizeTask;
443 RecorderDataPtr->ObjectPropertyTable.TotalPropertyBytesPerClass[2] = PropertyTableSizeISR;
444 RecorderDataPtr->ObjectPropertyTable.TotalPropertyBytesPerClass[3] = PropertyTableSizeTimer;
445 RecorderDataPtr->ObjectPropertyTable.TotalPropertyBytesPerClass[4] = PropertyTableSizeEventGroup;
446 RecorderDataPtr->ObjectPropertyTable.TotalPropertyBytesPerClass[5] = PropertyTableSizeSemaphore;
447 RecorderDataPtr->ObjectPropertyTable.TotalPropertyBytesPerClass[6] = PropertyTableSizeMutex;
448 RecorderDataPtr->ObjectPropertyTable.StartIndexOfClass[0] = StartIndexQueue;
449 RecorderDataPtr->ObjectPropertyTable.StartIndexOfClass[1] = StartIndexTask;
450 RecorderDataPtr->ObjectPropertyTable.StartIndexOfClass[2] = StartIndexISR;
451 RecorderDataPtr->ObjectPropertyTable.StartIndexOfClass[3] = StartIndexTimer;
452 RecorderDataPtr->ObjectPropertyTable.StartIndexOfClass[4] = StartIndexEventGroup;
453 RecorderDataPtr->ObjectPropertyTable.StartIndexOfClass[5] = StartIndexSemaphore;
454 RecorderDataPtr->ObjectPropertyTable.StartIndexOfClass[6] = StartIndexMutex;
455 RecorderDataPtr->ObjectPropertyTable.ObjectPropertyTableSizeInBytes = TRACE_OBJECT_TABLE_SIZE;
456 }
457
458 /* Initialization of the handle mechanism, see e.g, prvTraceGetObjectHandle */
vTraceInitObjectHandleStack()459 void vTraceInitObjectHandleStack()
460 {
461 uint32_t i = 0;
462
463 objectHandleStacks.indexOfNextAvailableHandle[0] = objectHandleStacks.lowestIndexOfClass[0] = 0;
464 objectHandleStacks.indexOfNextAvailableHandle[1] = objectHandleStacks.lowestIndexOfClass[1] = TRC_CFG_NQUEUE;
465 objectHandleStacks.indexOfNextAvailableHandle[2] = objectHandleStacks.lowestIndexOfClass[2] = TRC_CFG_NQUEUE + TRC_CFG_NTASK;
466 objectHandleStacks.indexOfNextAvailableHandle[3] = objectHandleStacks.lowestIndexOfClass[3] = TRC_CFG_NQUEUE + TRC_CFG_NTASK + TRC_CFG_NISR;
467 objectHandleStacks.indexOfNextAvailableHandle[4] = objectHandleStacks.lowestIndexOfClass[4] = TRC_CFG_NQUEUE + TRC_CFG_NTASK + TRC_CFG_NISR + TRC_CFG_NTIMER;
468
469 objectHandleStacks.highestIndexOfClass[0] = TRC_CFG_NQUEUE - 1;
470 objectHandleStacks.highestIndexOfClass[1] = TRC_CFG_NQUEUE + TRC_CFG_NTASK - 1;
471 objectHandleStacks.highestIndexOfClass[2] = TRC_CFG_NQUEUE + TRC_CFG_NTASK + TRC_CFG_NISR - 1;
472 objectHandleStacks.highestIndexOfClass[3] = TRC_CFG_NQUEUE + TRC_CFG_NTASK + TRC_CFG_NISR + TRC_CFG_NTIMER - 1;
473 objectHandleStacks.highestIndexOfClass[4] = TRC_CFG_NQUEUE + TRC_CFG_NTASK + TRC_CFG_NISR + TRC_CFG_NTIMER + TRC_CFG_NEVENTGROUP - 1;
474
475 for (i = 0; i < TRACE_NCLASSES; i++)
476 {
477 objectHandleStacks.handleCountWaterMarksOfClass[i] = 0;
478 }
479
480 for (i = 0; i < TRACE_KERNEL_OBJECT_COUNT; i++)
481 {
482 objectHandleStacks.objectHandles[i] = 0;
483 }
484 }
485
486 /* Returns the "Not enough handles" error message for this object class */
pszTraceGetErrorNotEnoughHandles(traceObjectClass objectclass)487 const char* pszTraceGetErrorNotEnoughHandles(traceObjectClass objectclass)
488 {
489 switch(objectclass)
490 {
491 case TRACE_CLASS_TASK:
492 return "Not enough TASK handles - increase TRC_CFG_NTASK in trcSnapshotConfig.h";
493 case TRACE_CLASS_ISR:
494 return "Not enough ISR handles - increase TRC_CFG_NISR in trcSnapshotConfig.h";
495 case TRACE_CLASS_QUEUE:
496 return "Not enough QUEUE handles - increase TRC_CFG_NQUEUE in trcSnapshotConfig.h";
497 case TRACE_CLASS_TIMER:
498 return "Not enough TIMER handles - increase TRC_CFG_NTIMER in trcSnapshotConfig.h";
499 case TRACE_CLASS_EVENTGROUP:
500 return "Not enough EVENTGROUP handles - increase TRC_CFG_NEVENTGROUP in trcSnapshotConfig.h";
501 default:
502 return "pszTraceGetErrorHandles: Invalid objectclass!";
503 }
504 }
505
506 #endif /* Snapshot mode */
507
508 #endif /*(TRC_USE_TRACEALYZER_RECORDER == 1)*/
509