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