1 /**************************************************************************/
2 /* */
3 /* Copyright (c) Microsoft Corporation. All rights reserved. */
4 /* */
5 /* This software is licensed under the Microsoft Software License */
6 /* Terms for Microsoft Azure RTOS. Full text of the license can be */
7 /* found in the LICENSE file at https://aka.ms/AzureRTOS_EULA */
8 /* and in the root directory of this software. */
9 /* */
10 /**************************************************************************/
11
12
13 /**************************************************************************/
14 /**************************************************************************/
15 /** */
16 /** ThreadX Component */
17 /** */
18 /** Low Power Timer Management */
19 /** */
20 /**************************************************************************/
21 /**************************************************************************/
22
23 #define TX_SOURCE_CODE
24
25
26 /* Include necessary system files. */
27
28 #include "tx_api.h"
29 #include "tx_timer.h"
30 #include "tx_low_power.h"
31
32
33 /* Define low power global variables. */
34
35 /* Flag to determine if we've entered low power mode or not. */
36 UINT tx_low_power_entered;
37
38
39 /**************************************************************************/
40 /* */
41 /* FUNCTION RELEASE */
42 /* */
43 /* tx_low_power_enter PORTABLE C */
44 /* 6.0 */
45 /* AUTHOR */
46 /* */
47 /* William E. Lamie, Microsoft Corporation */
48 /* */
49 /* DESCRIPTION */
50 /* */
51 /* This function is the low power entry function. This function is */
52 /* assumed to be called from the idle loop of tx_thread_schedule. It */
53 /* is important to note that if an interrupt managed by ThreadX occurs */
54 /* anywhere where interrupts are enabled in this function, the entire */
55 /* processing of this function is discarded and the function won't be */
56 /* re-entered until the idle loop in tx_thread_schedule is executed */
57 /* again. */
58 /* */
59 /* INPUT */
60 /* */
61 /* None */
62 /* */
63 /* OUTPUT */
64 /* */
65 /* None */
66 /* */
67 /* CALLS */
68 /* */
69 /* tx_timer_get_next Get next timer expiration */
70 /* */
71 /* CALLED BY */
72 /* */
73 /* _tx_thread_schedule Thread scheduling loop */
74 /* */
75 /* RELEASE HISTORY */
76 /* */
77 /* DATE NAME DESCRIPTION */
78 /* */
79 /* 05-19-2020 William E. Lamie Initial Version 6.0 */
80 /* */
81 /**************************************************************************/
tx_low_power_enter(VOID)82 VOID tx_low_power_enter(VOID)
83 {
84
85 TX_INTERRUPT_SAVE_AREA
86 ULONG tx_low_power_next_expiration;
87 ULONG any_expired;
88
89
90 /* The below macro is user-defined code to determine
91 if low power mode is beneficial for the application.
92 Reasons for not entering low power mode include
93 the overhead associated with entering and exiting low power mode
94 outweighs the savings given when the next interrupt is expected.
95 In addition, the application might also be in a state where
96 responsiveness is more important than power savings. In such
97 situations, using a "reduced power mode" might make more sense.
98 In any case, if low power mode is not desired, simply return at
99 this point in the code. */
100 #ifdef TX_LOW_POWER_USER_CHECK
101 TX_LOW_POWER_USER_CHECK;
102 #endif
103
104 /* Disable interrupts while we prepare for low power mode. */
105 TX_DISABLE
106
107 /* At this point, we want to enter low power mode, since nothing
108 meaningful is going on in the system. However, in order to keep
109 the ThreadX timer services accurate, we must first determine the
110 next ThreadX timer expiration in terms of ticks. This is
111 accomplished via the tx_timer_get_next API. */
112 any_expired = tx_timer_get_next(&tx_low_power_next_expiration);
113
114 /* There are two possibilities:
115 1: A ThreadX timer is active. tx_timer_get_next returns TX_TRUE.
116 Program the hardware timer source such that the next timer
117 interrupt is equal to: tx_low_power_next_expiration*tick_frequency.
118 In most applications, the tick_frequency is 10ms, but this is
119 completely application specific in ThreadX, typically set up
120 in tx_low_level_initialize.
121 2: There are no ThreadX timers active. tx_timer_get_next returns TX_FALSE.
122 If you don't care about maintaining the ThreadX system clock, you can simply
123 sleep forever (until an interrupt wakes you up).
124 If you do want to maintain the ThreadX system clock,
125 program the hardware timer so you can keep track of elapsed time. */
126 #ifdef TX_LOW_POWER_USER_TIMER_SETUP
127 TX_LOW_POWER_USER_TIMER_SETUP(any_expired, tx_low_power_next_expiration);
128 #endif
129
130
131 /* Set the flag indicating that low power has been entered. This
132 flag is checked in tx_low_power_exit to determine if the logic
133 used to adjust the ThreadX time is required. */
134 tx_low_power_entered = TX_TRUE;
135
136 /* Re-enable interrupts before low power mode is entered. */
137 TX_RESTORE
138
139 /* User code to enter low power mode. */
140 #ifdef TX_LOW_POWER_USER_ENTER
141 TX_LOW_POWER_USER_ENTER;
142 #endif
143
144 /* If the low power code returns, this routine returns to the
145 tx_thread_schedule loop. */
146 }
147
148
149 /**************************************************************************/
150 /* */
151 /* FUNCTION RELEASE */
152 /* */
153 /* tx_low_power_exit PORTABLE C */
154 /* 6.0 */
155 /* AUTHOR */
156 /* */
157 /* William E. Lamie, Microsoft Corporation */
158 /* */
159 /* DESCRIPTION */
160 /* */
161 /* This function is the low power exit function. This function must */
162 /* be called from any interrupt that can wakeup the processor from */
163 /* low power mode. If nothing needs to be done, this function simply */
164 /* returns. */
165 /* */
166 /* INPUT */
167 /* */
168 /* None */
169 /* */
170 /* OUTPUT */
171 /* */
172 /* None */
173 /* */
174 /* CALLS */
175 /* */
176 /* tx_time_increment Update the ThreadX timer */
177 /* */
178 /* CALLED BY */
179 /* */
180 /* ISRs Front-end of Interrupt */
181 /* Service Routines */
182 /* */
183 /* RELEASE HISTORY */
184 /* */
185 /* DATE NAME DESCRIPTION */
186 /* */
187 /* 05-19-2020 William E. Lamie Initial Version 6.0 */
188 /* */
189 /**************************************************************************/
tx_low_power_exit(VOID)190 VOID tx_low_power_exit(VOID)
191 {
192 ULONG tx_low_power_adjust_ticks;
193
194 /* Determine if the interrupt occurred in low power mode. */
195 if (tx_low_power_entered)
196 {
197
198 /* Yes, low power mode was interrupted. */
199
200 /* Clear the low power entered flag. */
201 tx_low_power_entered = TX_FALSE;
202
203 /* User code to exit low power mode and reprogram the
204 timer to the desired interrupt frequency. */
205 #ifdef TX_LOW_POWER_USER_EXIT
206 TX_LOW_POWER_USER_EXIT;
207 #endif
208
209 /* User code to determine how many timer ticks (interrupts) that
210 the ThreadX time should be incremented to properly adjust
211 for the time in low power mode. The result is assumed to be
212 placed in tx_low_power_adjust_ticks. */
213 #ifdef TX_LOW_POWER_USER_TIMER_ADJUST
214 tx_low_power_adjust_ticks = TX_LOW_POWER_USER_TIMER_ADJUST;
215 #else
216 tx_low_power_adjust_ticks = (ULONG)0;
217 #endif
218
219 /* Determine if the ThreadX timer needs incrementing. */
220 if (tx_low_power_adjust_ticks)
221 {
222
223 /* Yes, the ThreadX time must be incremented. Call tx_time_increment
224 to accomplish this. */
225 tx_time_increment(tx_low_power_adjust_ticks);
226 }
227 }
228 }
229
230
231 /**************************************************************************/
232 /* */
233 /* FUNCTION RELEASE */
234 /* */
235 /* tx_timer_get_next PORTABLE C */
236 /* 6.0 */
237 /* AUTHOR */
238 /* */
239 /* William E. Lamie, Microsoft Corporation */
240 /* */
241 /* DESCRIPTION */
242 /* */
243 /* This function calculates the next expiration time minus 1 tick for */
244 /* the currently active ThreadX timers. If no timer is active, this */
245 /* routine will return a value of TX_FALSE and the next ticks value */
246 /* will be set to zero. */
247 /* */
248 /* INPUT */
249 /* */
250 /* next_timer_tick_ptr Pointer to destination for next */
251 /* timer expiration value */
252 /* */
253 /* OUTPUT */
254 /* */
255 /* TX_TRUE (1) At least one timer is active */
256 /* TX_FALSE (0) No timers are currently active */
257 /* */
258 /* CALLS */
259 /* */
260 /* None */
261 /* */
262 /* CALLED BY */
263 /* */
264 /* tx_low_power_enter */
265 /* */
266 /* RELEASE HISTORY */
267 /* */
268 /* DATE NAME DESCRIPTION */
269 /* */
270 /* 05-19-2020 William E. Lamie Initial Version 6.0 */
271 /* */
272 /**************************************************************************/
tx_timer_get_next(ULONG * next_timer_tick_ptr)273 ULONG tx_timer_get_next(ULONG *next_timer_tick_ptr)
274 {
275
276 TX_INTERRUPT_SAVE_AREA
277
278 TX_TIMER_INTERNAL **timer_list_head;
279 TX_TIMER_INTERNAL *next_timer;
280 UINT i;
281 ULONG calculated_time;
282 ULONG expiration_time = (ULONG) 0xFFFFFFFF;
283
284
285 /* Disable interrupts. */
286 TX_DISABLE
287
288 /* Look at the next timer entry. */
289 timer_list_head = _tx_timer_current_ptr;
290
291 /* Loop through the timer list, looking for the first non-NULL
292 value to signal an active timer. */
293 for (i = (UINT)0; i < TX_TIMER_ENTRIES; i++)
294 {
295
296 /* Now determine if there is an active timer in this slot. */
297 if (*timer_list_head)
298 {
299
300 /* Setup the pointer to the expiration list. */
301 next_timer = *timer_list_head;
302
303 /* Loop through the timers active for this relative time slot (determined by i). */
304 do
305 {
306
307 /* Determine if the remaining time is larger than the list. */
308 if (next_timer -> tx_timer_internal_remaining_ticks > TX_TIMER_ENTRIES)
309 {
310
311 /* Calculate the expiration time. */
312 calculated_time = next_timer -> tx_timer_internal_remaining_ticks - (TX_TIMER_ENTRIES - i);
313 }
314 else
315 {
316
317 /* Calculate the expiration time, which is simply the number of entries in this case. */
318 calculated_time = i;
319 }
320
321 /* Determine if a new minimum expiration time is present. */
322 if (expiration_time > calculated_time)
323 {
324
325 /* Yes, a new minimum expiration time is present - remember it! */
326 expiration_time = calculated_time;
327 }
328
329 /* Move to the next entry in the timer list. */
330 next_timer = next_timer -> tx_timer_internal_active_next;
331
332 } while (next_timer != *timer_list_head);
333 }
334
335 /* This timer entry is NULL, so just move to the next one. */
336 timer_list_head++;
337
338 /* Check for timer list wrap condition. */
339 if (timer_list_head >= _tx_timer_list_end)
340 {
341
342 /* Wrap to the beginning of the list. */
343 timer_list_head = _tx_timer_list_start;
344 }
345 }
346
347 /* Restore interrupts. */
348 TX_RESTORE
349
350 /* Determine if an active timer was found. */
351 if (expiration_time != 0xFFFFFFFF)
352 {
353
354 /* Yes, an active timer was found. */
355 *next_timer_tick_ptr = expiration_time;
356 return(TX_TRUE);
357 }
358 else
359 {
360
361 /* No active timer was found. */
362 *next_timer_tick_ptr = 0;
363 return(TX_FALSE);
364 }
365 }
366
367
368 /**************************************************************************/
369 /* */
370 /* FUNCTION RELEASE */
371 /* */
372 /* tx_time_increment PORTABLE C */
373 /* 6.0 */
374 /* AUTHOR */
375 /* */
376 /* William E. Lamie, Microsoft Corporation */
377 /* */
378 /* DESCRIPTION */
379 /* */
380 /* This function increments the current time by a specified value. */
381 /* The value was derived by the application by calling the */
382 /* tx_timer_get_next function prior to this call, which was right */
383 /* before the processor was put in sleep mode. */
384 /* */
385 /* INPUT */
386 /* */
387 /* time_increment The amount of time to catch up on */
388 /* */
389 /* OUTPUT */
390 /* */
391 /* None */
392 /* */
393 /* CALLS */
394 /* */
395 /* _tx_timer_system_activate Timer activate service */
396 /* */
397 /* CALLED BY */
398 /* */
399 /* tx_low_power_exit */
400 /* */
401 /* RELEASE HISTORY */
402 /* */
403 /* DATE NAME DESCRIPTION */
404 /* */
405 /* 05-19-2020 William E. Lamie Initial Version 6.0 */
406 /* */
407 /**************************************************************************/
tx_time_increment(ULONG time_increment)408 VOID tx_time_increment(ULONG time_increment)
409 {
410
411 TX_INTERRUPT_SAVE_AREA
412 UINT i;
413 TX_TIMER_INTERNAL **timer_list_head;
414 TX_TIMER_INTERNAL *next_timer;
415 TX_TIMER_INTERNAL *temp_list_head;
416
417
418 /* Determine if there is any time increment. */
419 if (time_increment == 0)
420 {
421
422 /* Nothing to do, just return. */
423 return;
424 }
425
426 /* Disable interrupts. */
427 TX_DISABLE
428
429 /* Adjust the system clock. */
430 _tx_timer_system_clock = _tx_timer_system_clock + time_increment;
431
432 /* Adjust the time slice variable. */
433 if (_tx_timer_time_slice)
434 {
435
436 /* Decrement the time-slice variable. */
437 if (_tx_timer_time_slice > time_increment)
438 _tx_timer_time_slice = _tx_timer_time_slice - time_increment;
439 else
440 _tx_timer_time_slice = 1;
441 }
442
443 /* Calculate the proper place to position the timer. */
444 timer_list_head = _tx_timer_current_ptr;
445
446 /* Setup the temporary list pointer. */
447 temp_list_head = TX_NULL;
448
449 /* Loop to pull all timers off the timer structure and put on the
450 the temporary list head. */
451 for (i = 0; i < TX_TIMER_ENTRIES; i++)
452 {
453
454 /* Determine if there is a timer list in this entry. */
455 if (*timer_list_head)
456 {
457
458 /* Walk the list and update all the relative times to actual times. */
459
460 /* Setup the pointer to the expiration list. */
461 next_timer = *timer_list_head;
462
463 /* Loop through the timers active for this relative time slot (determined by i). */
464 do
465 {
466
467 /* Determine if the remaining time is larger than the list. */
468 if (next_timer -> tx_timer_internal_remaining_ticks > TX_TIMER_ENTRIES)
469 {
470
471 /* Calculate the actual expiration time. */
472 next_timer -> tx_timer_internal_remaining_ticks =
473 next_timer -> tx_timer_internal_remaining_ticks - (TX_TIMER_ENTRIES - i) + 1;
474 }
475 else
476 {
477
478 /* Calculate the expiration time, which is simply the number of entries in this
479 case. */
480 next_timer -> tx_timer_internal_remaining_ticks = i + 1;
481 }
482
483 /* Move to the next entry in the timer list. */
484 next_timer = next_timer -> tx_timer_internal_active_next;
485
486 } while (next_timer != *timer_list_head);
487
488 /* NULL terminate the current timer list. */
489 ((*timer_list_head) -> tx_timer_internal_active_previous) -> tx_timer_internal_active_next = TX_NULL;
490
491 /* Yes, determine if the temporary list is NULL. */
492 if (temp_list_head == TX_NULL)
493 {
494
495 /* First item on the list. Move the entire
496 linked list. */
497 temp_list_head = *timer_list_head;
498 }
499 else
500 {
501
502 /* No, the temp list already has timers on it. Link the next
503 timer list to the end. */
504 (temp_list_head -> tx_timer_internal_active_previous) -> tx_timer_internal_active_next = *timer_list_head;
505
506 /* Now update the previous to the new list's previous timer pointer. */
507 temp_list_head -> tx_timer_internal_active_previous = (*timer_list_head) -> tx_timer_internal_active_previous;
508 }
509
510 /* Now clear the current timer head pointer. */
511 *timer_list_head = TX_NULL;
512 }
513
514 /* Move to next timer entry. */
515 timer_list_head++;
516
517 /* Determine if a wrap around condition has occurred. */
518 if (timer_list_head >= _tx_timer_list_end)
519 {
520
521 /* Wrap from the beginning of the list. */
522 timer_list_head = _tx_timer_list_start;
523 }
524 }
525
526 /* Set the current timer pointer to the beginning of the list. */
527 _tx_timer_current_ptr = _tx_timer_list_start;
528
529 /* Loop to update and reinsert all the timers in the list. */
530 while (temp_list_head)
531 {
532
533 /* Pickup the next timer to update and reinsert. */
534 next_timer = temp_list_head;
535
536 /* Move the temp list head pointer to the next pointer. */
537 temp_list_head = next_timer -> tx_timer_internal_active_next;
538
539 /* Determine if the remaining time is greater than the time increment
540 value - this is the normal case. */
541 if (next_timer -> tx_timer_internal_remaining_ticks > time_increment)
542 {
543
544 /* Decrement the elapsed time. */
545 next_timer -> tx_timer_internal_remaining_ticks = next_timer -> tx_timer_internal_remaining_ticks - time_increment;
546 }
547 else
548 {
549
550 /* Simply set the expiration value to expire on the next tick. */
551 next_timer -> tx_timer_internal_remaining_ticks = 1;
552 }
553
554 /* Now clear the timer list head pointer for the timer activate function to work properly. */
555 next_timer -> tx_timer_internal_list_head = TX_NULL;
556
557 /* Now re-insert the timer into the list. */
558 _tx_timer_system_activate(next_timer);
559 }
560
561 /* Restore interrupts. */
562 TX_RESTORE
563 }
564