]> git.sur5r.net Git - freertos/blob - FreeRTOS/Demo/Common/Minimal/blocktim.c
9abf824fc8bf988f6caae6a54c26d25df2a1b67f
[freertos] / FreeRTOS / Demo / Common / Minimal / blocktim.c
1 /*\r
2  * FreeRTOS Kernel V10.2.1\r
3  * Copyright (C) 2019 Amazon.com, Inc. or its affiliates.  All Rights Reserved.\r
4  *\r
5  * Permission is hereby granted, free of charge, to any person obtaining a copy of\r
6  * this software and associated documentation files (the "Software"), to deal in\r
7  * the Software without restriction, including without limitation the rights to\r
8  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\r
9  * the Software, and to permit persons to whom the Software is furnished to do so,\r
10  * subject to the following conditions:\r
11  *\r
12  * The above copyright notice and this permission notice shall be included in all\r
13  * copies or substantial portions of the Software.\r
14  *\r
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\r
17  * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\r
18  * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\r
19  * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\r
20  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
21  *\r
22  * http://www.FreeRTOS.org\r
23  * http://aws.amazon.com/freertos\r
24  *\r
25  * 1 tab == 4 spaces!\r
26  */\r
27 \r
28 /*\r
29  * This file contains some test scenarios that ensure tasks do not exit queue\r
30  * send or receive functions prematurely.  A description of the tests is\r
31  * included within the code.\r
32  */\r
33 \r
34 /* Kernel includes. */\r
35 #include "FreeRTOS.h"\r
36 #include "task.h"\r
37 #include "queue.h"\r
38 \r
39 /* Demo includes. */\r
40 #include "blocktim.h"\r
41 \r
42 /* Task priorities and stack sizes.  Allow these to be overridden. */\r
43 #ifndef bktPRIMARY_PRIORITY\r
44         #define bktPRIMARY_PRIORITY             ( configMAX_PRIORITIES - 3 )\r
45 #endif\r
46 \r
47 #ifndef bktSECONDARY_PRIORITY\r
48         #define bktSECONDARY_PRIORITY   ( configMAX_PRIORITIES - 4 )\r
49 #endif\r
50 \r
51 #ifndef bktBLOCK_TIME_TASK_STACK_SIZE\r
52         #define bktBLOCK_TIME_TASK_STACK_SIZE configMINIMAL_STACK_SIZE\r
53 #endif\r
54 \r
55 /* Task behaviour. */\r
56 #define bktQUEUE_LENGTH                         ( 5 )\r
57 #define bktSHORT_WAIT                           pdMS_TO_TICKS( ( TickType_t ) 20 )\r
58 #define bktPRIMARY_BLOCK_TIME           ( 10 )\r
59 #define bktALLOWABLE_MARGIN                     ( 15 )\r
60 #define bktTIME_TO_BLOCK                        ( 175 )\r
61 #define bktDONT_BLOCK                           ( ( TickType_t ) 0 )\r
62 #define bktRUN_INDICATOR                        ( ( UBaseType_t ) 0x55 )\r
63 \r
64 /* In case the demo does not have software timers enabled, as this file uses\r
65 the configTIMER_TASK_PRIORITY setting. */\r
66 #ifndef configTIMER_TASK_PRIORITY\r
67         #define configTIMER_TASK_PRIORITY ( configMAX_PRIORITIES - 1 )\r
68 #endif\r
69 \r
70 /*-----------------------------------------------------------*/\r
71 \r
72 /*\r
73  * The two test tasks.  Their behaviour is commented within the functions.\r
74  */\r
75 static void vPrimaryBlockTimeTestTask( void *pvParameters );\r
76 static void vSecondaryBlockTimeTestTask( void *pvParameters );\r
77 \r
78 /*\r
79  * Very basic tests to verify the block times are as expected.\r
80  */\r
81 static void prvBasicDelayTests( void );\r
82 \r
83 /*-----------------------------------------------------------*/\r
84 \r
85 /* The queue on which the tasks block. */\r
86 static QueueHandle_t xTestQueue;\r
87 \r
88 /* Handle to the secondary task is required by the primary task for calls\r
89 to vTaskSuspend/Resume(). */\r
90 static TaskHandle_t xSecondary;\r
91 \r
92 /* Used to ensure that tasks are still executing without error. */\r
93 static volatile BaseType_t xPrimaryCycles = 0, xSecondaryCycles = 0;\r
94 static volatile BaseType_t xErrorOccurred = pdFALSE;\r
95 \r
96 /* Provides a simple mechanism for the primary task to know when the\r
97 secondary task has executed. */\r
98 static volatile UBaseType_t xRunIndicator;\r
99 \r
100 /*-----------------------------------------------------------*/\r
101 \r
102 void vCreateBlockTimeTasks( void )\r
103 {\r
104         /* Create the queue on which the two tasks block. */\r
105         xTestQueue = xQueueCreate( bktQUEUE_LENGTH, sizeof( BaseType_t ) );\r
106 \r
107         if( xTestQueue != NULL )\r
108         {\r
109                 /* vQueueAddToRegistry() adds the queue to the queue registry, if one\r
110                 is in use.  The queue registry is provided as a means for kernel aware\r
111                 debuggers to locate queues and has no purpose if a kernel aware\r
112                 debugger is not being used.  The call to vQueueAddToRegistry() will be\r
113                 removed by the pre-processor if configQUEUE_REGISTRY_SIZE is not\r
114                 defined or is defined to be less than 1. */\r
115                 vQueueAddToRegistry( xTestQueue, "Block_Time_Queue" );\r
116 \r
117                 /* Create the two test tasks. */\r
118                 xTaskCreate( vPrimaryBlockTimeTestTask, "BTest1", bktBLOCK_TIME_TASK_STACK_SIZE, NULL, bktPRIMARY_PRIORITY, NULL );\r
119                 xTaskCreate( vSecondaryBlockTimeTestTask, "BTest2", bktBLOCK_TIME_TASK_STACK_SIZE, NULL, bktSECONDARY_PRIORITY, &xSecondary );\r
120         }\r
121 }\r
122 /*-----------------------------------------------------------*/\r
123 \r
124 static void vPrimaryBlockTimeTestTask( void *pvParameters )\r
125 {\r
126 BaseType_t xItem, xData;\r
127 TickType_t xTimeWhenBlocking;\r
128 TickType_t xTimeToBlock, xBlockedTime;\r
129 \r
130         ( void ) pvParameters;\r
131 \r
132         for( ;; )\r
133         {\r
134                 /*********************************************************************\r
135                 Test 0\r
136 \r
137                 Basic vTaskDelay() and vTaskDelayUntil() tests. */\r
138                 prvBasicDelayTests();\r
139 \r
140 \r
141                 /*********************************************************************\r
142                 Test 1\r
143 \r
144                 Simple block time wakeup test on queue receives. */\r
145                 for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ )\r
146                 {\r
147                         /* The queue is empty. Attempt to read from the queue using a block\r
148                         time.  When we wake, ensure the delta in time is as expected. */\r
149                         xTimeToBlock = ( TickType_t ) ( bktPRIMARY_BLOCK_TIME << xItem );\r
150 \r
151                         xTimeWhenBlocking = xTaskGetTickCount();\r
152 \r
153                         /* We should unblock after xTimeToBlock having not received\r
154                         anything on the queue. */\r
155                         if( xQueueReceive( xTestQueue, &xData, xTimeToBlock ) != errQUEUE_EMPTY )\r
156                         {\r
157                                 xErrorOccurred = pdTRUE;\r
158                         }\r
159 \r
160                         /* How long were we blocked for? */\r
161                         xBlockedTime = xTaskGetTickCount() - xTimeWhenBlocking;\r
162 \r
163                         if( xBlockedTime < xTimeToBlock )\r
164                         {\r
165                                 /* Should not have blocked for less than we requested. */\r
166                                 xErrorOccurred = pdTRUE;\r
167                         }\r
168 \r
169                         if( xBlockedTime > ( xTimeToBlock + bktALLOWABLE_MARGIN ) )\r
170                         {\r
171                                 /* Should not have blocked for longer than we requested,\r
172                                 although we would not necessarily run as soon as we were\r
173                                 unblocked so a margin is allowed. */\r
174                                 xErrorOccurred = pdTRUE;\r
175                         }\r
176                 }\r
177 \r
178                 /*********************************************************************\r
179                 Test 2\r
180 \r
181                 Simple block time wakeup test on queue sends.\r
182 \r
183                 First fill the queue.  It should be empty so all sends should pass. */\r
184                 for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ )\r
185                 {\r
186                         if( xQueueSend( xTestQueue, &xItem, bktDONT_BLOCK ) != pdPASS )\r
187                         {\r
188                                 xErrorOccurred = pdTRUE;\r
189                         }\r
190 \r
191                         #if configUSE_PREEMPTION == 0\r
192                                 taskYIELD();\r
193                         #endif\r
194                 }\r
195 \r
196                 for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ )\r
197                 {\r
198                         /* The queue is full. Attempt to write to the queue using a block\r
199                         time.  When we wake, ensure the delta in time is as expected. */\r
200                         xTimeToBlock = ( TickType_t ) ( bktPRIMARY_BLOCK_TIME << xItem );\r
201 \r
202                         xTimeWhenBlocking = xTaskGetTickCount();\r
203 \r
204                         /* We should unblock after xTimeToBlock having not received\r
205                         anything on the queue. */\r
206                         if( xQueueSend( xTestQueue, &xItem, xTimeToBlock ) != errQUEUE_FULL )\r
207                         {\r
208                                 xErrorOccurred = pdTRUE;\r
209                         }\r
210 \r
211                         /* How long were we blocked for? */\r
212                         xBlockedTime = xTaskGetTickCount() - xTimeWhenBlocking;\r
213 \r
214                         if( xBlockedTime < xTimeToBlock )\r
215                         {\r
216                                 /* Should not have blocked for less than we requested. */\r
217                                 xErrorOccurred = pdTRUE;\r
218                         }\r
219 \r
220                         if( xBlockedTime > ( xTimeToBlock + bktALLOWABLE_MARGIN ) )\r
221                         {\r
222                                 /* Should not have blocked for longer than we requested,\r
223                                 although we would not necessarily run as soon as we were\r
224                                 unblocked so a margin is allowed. */\r
225                                 xErrorOccurred = pdTRUE;\r
226                         }\r
227                 }\r
228 \r
229                 /*********************************************************************\r
230                 Test 3\r
231 \r
232                 Wake the other task, it will block attempting to post to the queue.\r
233                 When we read from the queue the other task will wake, but before it\r
234                 can run we will post to the queue again.  When the other task runs it\r
235                 will find the queue still full, even though it was woken.  It should\r
236                 recognise that its block time has not expired and return to block for\r
237                 the remains of its block time.\r
238 \r
239                 Wake the other task so it blocks attempting to post to the already\r
240                 full queue. */\r
241                 xRunIndicator = 0;\r
242                 vTaskResume( xSecondary );\r
243 \r
244                 /* We need to wait a little to ensure the other task executes. */\r
245                 while( xRunIndicator != bktRUN_INDICATOR )\r
246                 {\r
247                         /* The other task has not yet executed. */\r
248                         vTaskDelay( bktSHORT_WAIT );\r
249                 }\r
250                 /* Make sure the other task is blocked on the queue. */\r
251                 vTaskDelay( bktSHORT_WAIT );\r
252                 xRunIndicator = 0;\r
253 \r
254                 for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ )\r
255                 {\r
256                         /* Now when we make space on the queue the other task should wake\r
257                         but not execute as this task has higher priority. */\r
258                         if( xQueueReceive( xTestQueue, &xData, bktDONT_BLOCK ) != pdPASS )\r
259                         {\r
260                                 xErrorOccurred = pdTRUE;\r
261                         }\r
262 \r
263                         /* Now fill the queue again before the other task gets a chance to\r
264                         execute.  If the other task had executed we would find the queue\r
265                         full ourselves, and the other task have set xRunIndicator. */\r
266                         if( xQueueSend( xTestQueue, &xItem, bktDONT_BLOCK ) != pdPASS )\r
267                         {\r
268                                 xErrorOccurred = pdTRUE;\r
269                         }\r
270 \r
271                         if( xRunIndicator == bktRUN_INDICATOR )\r
272                         {\r
273                                 /* The other task should not have executed. */\r
274                                 xErrorOccurred = pdTRUE;\r
275                         }\r
276 \r
277                         /* Raise the priority of the other task so it executes and blocks\r
278                         on the queue again. */\r
279                         vTaskPrioritySet( xSecondary, bktPRIMARY_PRIORITY + 2 );\r
280 \r
281                         /* The other task should now have re-blocked without exiting the\r
282                         queue function. */\r
283                         if( xRunIndicator == bktRUN_INDICATOR )\r
284                         {\r
285                                 /* The other task should not have executed outside of the\r
286                                 queue function. */\r
287                                 xErrorOccurred = pdTRUE;\r
288                         }\r
289 \r
290                         /* Set the priority back down. */\r
291                         vTaskPrioritySet( xSecondary, bktSECONDARY_PRIORITY );\r
292                 }\r
293 \r
294                 /* Let the other task timeout.  When it unblockes it will check that it\r
295                 unblocked at the correct time, then suspend itself. */\r
296                 while( xRunIndicator != bktRUN_INDICATOR )\r
297                 {\r
298                         vTaskDelay( bktSHORT_WAIT );\r
299                 }\r
300                 vTaskDelay( bktSHORT_WAIT );\r
301                 xRunIndicator = 0;\r
302 \r
303 \r
304                 /*********************************************************************\r
305                 Test 4\r
306 \r
307                 As per test 3 - but with the send and receive the other way around.\r
308                 The other task blocks attempting to read from the queue.\r
309 \r
310                 Empty the queue.  We should find that it is full. */\r
311                 for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ )\r
312                 {\r
313                         if( xQueueReceive( xTestQueue, &xData, bktDONT_BLOCK ) != pdPASS )\r
314                         {\r
315                                 xErrorOccurred = pdTRUE;\r
316                         }\r
317                 }\r
318 \r
319                 /* Wake the other task so it blocks attempting to read from  the\r
320                 already empty queue. */\r
321                 vTaskResume( xSecondary );\r
322 \r
323                 /* We need to wait a little to ensure the other task executes. */\r
324                 while( xRunIndicator != bktRUN_INDICATOR )\r
325                 {\r
326                         vTaskDelay( bktSHORT_WAIT );\r
327                 }\r
328                 vTaskDelay( bktSHORT_WAIT );\r
329                 xRunIndicator = 0;\r
330 \r
331                 for( xItem = 0; xItem < bktQUEUE_LENGTH; xItem++ )\r
332                 {\r
333                         /* Now when we place an item on the queue the other task should\r
334                         wake but not execute as this task has higher priority. */\r
335                         if( xQueueSend( xTestQueue, &xItem, bktDONT_BLOCK ) != pdPASS )\r
336                         {\r
337                                 xErrorOccurred = pdTRUE;\r
338                         }\r
339 \r
340                         /* Now empty the queue again before the other task gets a chance to\r
341                         execute.  If the other task had executed we would find the queue\r
342                         empty ourselves, and the other task would be suspended. */\r
343                         if( xQueueReceive( xTestQueue, &xData, bktDONT_BLOCK ) != pdPASS )\r
344                         {\r
345                                 xErrorOccurred = pdTRUE;\r
346                         }\r
347 \r
348                         if( xRunIndicator == bktRUN_INDICATOR )\r
349                         {\r
350                                 /* The other task should not have executed. */\r
351                                 xErrorOccurred = pdTRUE;\r
352                         }\r
353 \r
354                         /* Raise the priority of the other task so it executes and blocks\r
355                         on the queue again. */\r
356                         vTaskPrioritySet( xSecondary, bktPRIMARY_PRIORITY + 2 );\r
357 \r
358                         /* The other task should now have re-blocked without exiting the\r
359                         queue function. */\r
360                         if( xRunIndicator == bktRUN_INDICATOR )\r
361                         {\r
362                                 /* The other task should not have executed outside of the\r
363                                 queue function. */\r
364                                 xErrorOccurred = pdTRUE;\r
365                         }\r
366                         vTaskPrioritySet( xSecondary, bktSECONDARY_PRIORITY );\r
367                 }\r
368 \r
369                 /* Let the other task timeout.  When it unblockes it will check that it\r
370                 unblocked at the correct time, then suspend itself. */\r
371                 while( xRunIndicator != bktRUN_INDICATOR )\r
372                 {\r
373                         vTaskDelay( bktSHORT_WAIT );\r
374                 }\r
375                 vTaskDelay( bktSHORT_WAIT );\r
376 \r
377                 xPrimaryCycles++;\r
378         }\r
379 }\r
380 /*-----------------------------------------------------------*/\r
381 \r
382 static void vSecondaryBlockTimeTestTask( void *pvParameters )\r
383 {\r
384 TickType_t xTimeWhenBlocking, xBlockedTime;\r
385 BaseType_t xData;\r
386 \r
387         ( void ) pvParameters;\r
388 \r
389         for( ;; )\r
390         {\r
391                 /*********************************************************************\r
392                 Test 0, 1 and 2\r
393 \r
394                 This task does not participate in these tests. */\r
395                 vTaskSuspend( NULL );\r
396 \r
397                 /*********************************************************************\r
398                 Test 3\r
399 \r
400                 The first thing we do is attempt to read from the queue.  It should be\r
401                 full so we block.  Note the time before we block so we can check the\r
402                 wake time is as per that expected. */\r
403                 xTimeWhenBlocking = xTaskGetTickCount();\r
404 \r
405                 /* We should unblock after bktTIME_TO_BLOCK having not sent anything to\r
406                 the queue. */\r
407                 xData = 0;\r
408                 xRunIndicator = bktRUN_INDICATOR;\r
409                 if( xQueueSend( xTestQueue, &xData, bktTIME_TO_BLOCK ) != errQUEUE_FULL )\r
410                 {\r
411                         xErrorOccurred = pdTRUE;\r
412                 }\r
413 \r
414                 /* How long were we inside the send function? */\r
415                 xBlockedTime = xTaskGetTickCount() - xTimeWhenBlocking;\r
416 \r
417                 /* We should not have blocked for less time than bktTIME_TO_BLOCK. */\r
418                 if( xBlockedTime < bktTIME_TO_BLOCK )\r
419                 {\r
420                         xErrorOccurred = pdTRUE;\r
421                 }\r
422 \r
423                 /* We should of not blocked for much longer than bktALLOWABLE_MARGIN\r
424                 either.  A margin is permitted as we would not necessarily run as\r
425                 soon as we unblocked. */\r
426                 if( xBlockedTime > ( bktTIME_TO_BLOCK + bktALLOWABLE_MARGIN ) )\r
427                 {\r
428                         xErrorOccurred = pdTRUE;\r
429                 }\r
430 \r
431                 /* Suspend ready for test 3. */\r
432                 xRunIndicator = bktRUN_INDICATOR;\r
433                 vTaskSuspend( NULL );\r
434 \r
435                 /*********************************************************************\r
436         Test 4\r
437 \r
438                 As per test three, but with the send and receive reversed. */\r
439                 xTimeWhenBlocking = xTaskGetTickCount();\r
440 \r
441                 /* We should unblock after bktTIME_TO_BLOCK having not received\r
442                 anything on the queue. */\r
443                 xRunIndicator = bktRUN_INDICATOR;\r
444                 if( xQueueReceive( xTestQueue, &xData, bktTIME_TO_BLOCK ) != errQUEUE_EMPTY )\r
445                 {\r
446                         xErrorOccurred = pdTRUE;\r
447                 }\r
448 \r
449                 xBlockedTime = xTaskGetTickCount() - xTimeWhenBlocking;\r
450 \r
451                 /* We should not have blocked for less time than bktTIME_TO_BLOCK. */\r
452                 if( xBlockedTime < bktTIME_TO_BLOCK )\r
453                 {\r
454                         xErrorOccurred = pdTRUE;\r
455                 }\r
456 \r
457                 /* We should of not blocked for much longer than bktALLOWABLE_MARGIN\r
458                 either.  A margin is permitted as we would not necessarily run as soon\r
459                 as we unblocked. */\r
460                 if( xBlockedTime > ( bktTIME_TO_BLOCK + bktALLOWABLE_MARGIN ) )\r
461                 {\r
462                         xErrorOccurred = pdTRUE;\r
463                 }\r
464 \r
465                 xRunIndicator = bktRUN_INDICATOR;\r
466 \r
467                 xSecondaryCycles++;\r
468         }\r
469 }\r
470 /*-----------------------------------------------------------*/\r
471 \r
472 static void prvBasicDelayTests( void )\r
473 {\r
474 TickType_t xPreTime, xPostTime, x, xLastUnblockTime, xExpectedUnblockTime;\r
475 const TickType_t xPeriod = 75, xCycles = 5, xAllowableMargin = ( bktALLOWABLE_MARGIN >> 1 );\r
476 \r
477         /* Temporarily increase priority so the timing is more accurate, but not so\r
478         high as to disrupt the timer tests. */\r
479         vTaskPrioritySet( NULL, configTIMER_TASK_PRIORITY - 1 );\r
480 \r
481         /* Crude check to too see that vTaskDelay() blocks for the expected\r
482         period. */\r
483         xPreTime = xTaskGetTickCount();\r
484         vTaskDelay( bktTIME_TO_BLOCK );\r
485         xPostTime = xTaskGetTickCount();\r
486 \r
487         /* The priority is higher, so the allowable margin is halved when compared\r
488         to the other tests in this file. */\r
489         if( ( xPostTime - xPreTime ) > ( bktTIME_TO_BLOCK + xAllowableMargin ) )\r
490         {\r
491                 xErrorOccurred = pdTRUE;\r
492         }\r
493 \r
494         /* Now crude tests to check the vTaskDelayUntil() functionality. */\r
495         xPostTime = xTaskGetTickCount();\r
496         xLastUnblockTime = xPostTime;\r
497 \r
498         for( x = 0; x < xCycles; x++ )\r
499         {\r
500                 /* Calculate the next expected unblock time from the time taken before\r
501                 this loop was entered. */\r
502                 xExpectedUnblockTime = xPostTime + ( x * xPeriod );\r
503 \r
504                 vTaskDelayUntil( &xLastUnblockTime, xPeriod );\r
505 \r
506                 if( ( xTaskGetTickCount() - xExpectedUnblockTime ) > ( bktTIME_TO_BLOCK + xAllowableMargin ) )\r
507                 {\r
508                         xErrorOccurred = pdTRUE;\r
509                 }\r
510 \r
511                 xPrimaryCycles++;\r
512         }\r
513 \r
514         /* Reset to the original task priority ready for the other tests. */\r
515         vTaskPrioritySet( NULL, bktPRIMARY_PRIORITY );\r
516 }\r
517 /*-----------------------------------------------------------*/\r
518 \r
519 BaseType_t xAreBlockTimeTestTasksStillRunning( void )\r
520 {\r
521 static BaseType_t xLastPrimaryCycleCount = 0, xLastSecondaryCycleCount = 0;\r
522 BaseType_t xReturn = pdPASS;\r
523 \r
524         /* Have both tasks performed at least one cycle since this function was\r
525         last called? */\r
526         if( xPrimaryCycles == xLastPrimaryCycleCount )\r
527         {\r
528                 xReturn = pdFAIL;\r
529         }\r
530 \r
531         if( xSecondaryCycles == xLastSecondaryCycleCount )\r
532         {\r
533                 xReturn = pdFAIL;\r
534         }\r
535 \r
536         if( xErrorOccurred == pdTRUE )\r
537         {\r
538                 xReturn = pdFAIL;\r
539         }\r
540 \r
541         xLastSecondaryCycleCount = xSecondaryCycles;\r
542         xLastPrimaryCycleCount = xPrimaryCycles;\r
543 \r
544         return xReturn;\r
545 }\r