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