Merge pull request #2 from ronan22/master
[apps/agl-service-unicens.git] / ucs2-lib / src / ucs_timer.c
1 /*------------------------------------------------------------------------------------------------*/
2 /* UNICENS V2.1.0-3491                                                                            */
3 /* Copyright (c) 2017 Microchip Technology Germany II GmbH & Co. KG.                              */
4 /*                                                                                                */
5 /* This program is free software: you can redistribute it and/or modify                           */
6 /* it under the terms of the GNU General Public License as published by                           */
7 /* the Free Software Foundation, either version 2 of the License, or                              */
8 /* (at your option) any later version.                                                            */
9 /*                                                                                                */
10 /* This program is distributed in the hope that it will be useful,                                */
11 /* but WITHOUT ANY WARRANTY; without even the implied warranty of                                 */
12 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                                  */
13 /* GNU General Public License for more details.                                                   */
14 /*                                                                                                */
15 /* You should have received a copy of the GNU General Public License                              */
16 /* along with this program.  If not, see <http://www.gnu.org/licenses/>.                          */
17 /*                                                                                                */
18 /* You may also obtain this software under a propriety license from Microchip.                    */
19 /* Please contact Microchip for further information.                                              */
20 /*------------------------------------------------------------------------------------------------*/
21
22 /*!
23  * \file
24  * \brief Implementation of the timer management module.
25  *
26  * \cond UCS_INTERNAL_DOC
27  * \addtogroup G_TIMER
28  * @{
29  */
30
31 /*------------------------------------------------------------------------------------------------*/
32 /* Includes                                                                                       */
33 /*------------------------------------------------------------------------------------------------*/
34 #include "ucs_timer.h"
35 #include "ucs_misc.h"
36 #include "ucs_trace.h"
37
38 /*------------------------------------------------------------------------------------------------*/
39 /* Service parameters                                                                             */
40 /*------------------------------------------------------------------------------------------------*/
41 /*! Priority of the TM service used by scheduler */
42 static const uint8_t TM_SRV_PRIO = 255U;    /* parasoft-suppress  MISRA2004-8_7 "Value shall be part of the module, not part of a function." */
43 /*! Main event for the TM service */
44 static const Srv_Event_t TM_EVENT_UPDATE_TIMERS = 1U;
45
46 /*------------------------------------------------------------------------------------------------*/
47 /* Internal prototypes                                                                            */
48 /*------------------------------------------------------------------------------------------------*/
49 static void Tm_Service(void *self);
50 static void Tm_UpdateTimers(CTimerManagement *self);
51 static bool Tm_HandleElapsedTimer(CTimerManagement *self);
52 static bool Tm_UpdateTimersAdd(void *c_timer_ptr, void *n_timer_ptr);
53 static void Tm_SetTimerInternal(CTimerManagement *self,
54                                 CTimer *timer_ptr,
55                                 Tm_Handler_t handler_fptr,
56                                 void *args_ptr,
57                                 uint16_t elapse,
58                                 uint16_t period);
59
60 /*------------------------------------------------------------------------------------------------*/
61 /* Implementation of class CTimerManagement                                                       */
62 /*------------------------------------------------------------------------------------------------*/
63 /*! \brief Constructor of the timer management class.
64  *  \param self        Instance pointer
65  *  \param scd         Scheduler instance
66  *  \param init_ptr    Reference to the initialization data
67  *  \param  ucs_user_ptr User reference that needs to be passed in every callback function
68  */
69 void Tm_Ctor(CTimerManagement *self, CScheduler *scd, const Tm_InitData_t *init_ptr, void * ucs_user_ptr)
70 {
71     MISC_MEM_SET(self, 0, sizeof(*self));
72     self->ucs_user_ptr = ucs_user_ptr;
73     /* Initialize subjects and add observers */
74     Ssub_Ctor(&self->get_tick_count_subject, self->ucs_user_ptr);
75     (void)Ssub_AddObserver(&self->get_tick_count_subject,
76                            init_ptr->get_tick_count_obs_ptr);
77     if(init_ptr->set_application_timer_obs_ptr != NULL)
78     {
79         self->delayed_tm_service_enabled = true;
80         Ssub_Ctor(&self->set_application_timer_subject, self->ucs_user_ptr);
81         (void)Ssub_AddObserver(&self->set_application_timer_subject,
82                                init_ptr->set_application_timer_obs_ptr);
83     }
84     /* Initialize timer management service */
85     Srv_Ctor(&self->tm_srv, TM_SRV_PRIO, self, &Tm_Service);
86     /* Add timer management service to scheduler */
87     (void)Scd_AddService(scd, &self->tm_srv);
88 }
89
90 /*! \brief Service function of the timer management.
91  *  \param self    Instance pointer
92  */
93 static void Tm_Service(void *self)
94 {
95     CTimerManagement *self_ = (CTimerManagement *)self;
96     Srv_Event_t event_mask;
97
98     Srv_GetEvent(&self_->tm_srv, &event_mask);
99
100     if(TM_EVENT_UPDATE_TIMERS == (event_mask & TM_EVENT_UPDATE_TIMERS))     /* Is event pending? */
101     {
102         Srv_ClearEvent(&self_->tm_srv, TM_EVENT_UPDATE_TIMERS);
103         Tm_UpdateTimers(self_); 
104     }
105 }
106
107 /*! \brief If event TM_EVENT_UPDATE_TIMERS is set this function is called. Handles the update
108  *         of the timer list. If a timer has expired the corresponding callback function is
109  *         executed. If the expired timer is a periodic timer, the timer will be set again.
110  *  \param self    Instance pointer
111  */
112 static void Tm_UpdateTimers(CTimerManagement *self)
113 {
114     uint16_t current_tick_count;
115     Ssub_Notify(&self->get_tick_count_subject, &current_tick_count, false);
116
117     if(self->timer_list.head != NULL)      /* At least one timer is running? */
118     {
119         bool continue_loop = true;
120         /* Calculate time difference between the current and the last TM service run */
121         uint16_t tick_count_diff = (uint16_t)(current_tick_count - self->last_tick_count);
122         /* Save current tick count for next service run */
123         self->last_tick_count = current_tick_count;
124
125         /* Loop while timer list is not empty */
126         while((self->timer_list.head != NULL) && (continue_loop!= false))
127         {
128             /* Is not first timer in list elapsed yet? */
129             if(tick_count_diff <= ((CTimer *)self->timer_list.head->data_ptr)->delta)
130             {
131                 /* Update delta of first timer in list */
132                 ((CTimer *)self->timer_list.head->data_ptr)->delta -= tick_count_diff;
133                 tick_count_diff = 0U;
134             }
135             else    /* At least first timer in list elapsed */
136             {
137                 /* Update tick count difference for next timer in list */
138                 tick_count_diff -= ((CTimer *)self->timer_list.head->data_ptr)->delta;
139                 /* First timer elapsed */
140                 ((CTimer *)self->timer_list.head->data_ptr)->delta = 0U;
141             }
142
143             /* First timer in list elapsed? */
144             if(0U == ((CTimer *)self->timer_list.head->data_ptr)->delta)
145             {
146                 /* Handle elapsed timer */
147                 continue_loop = Tm_HandleElapsedTimer(self);
148             }
149             else    /* No elapsed timer in list. */
150             {
151                 /* First timer in list updated! Set trigger to inform application (see 
152                    Tm_CheckForNextService()) and stop TM service. */
153                 self->set_service_timer = true;
154                 continue_loop = false;
155             }
156         }
157     }
158 }
159
160 /*! \brief  This function is called if the first timer in list is elapsed. The timer handler 
161  *          callback function is invoked. If the timer is a periodic timer it is wound up again.
162  *  \param  self    Instance pointer
163  *  \return \c true if the next timer must be check.
164  *  \return \c false if the wound up timer (periodic timer) is new head of timer list
165  */
166 static bool Tm_HandleElapsedTimer(CTimerManagement *self)
167 {
168     bool ret_val = true;
169
170     CDlNode *node = self->timer_list.head;
171     /* Reset flag to be able to check if timer object has changed within handler 
172         callback function */
173     ((CTimer *)node->data_ptr)->changed = false;
174     /* Call timer handler callback function */
175     ((CTimer *)node->data_ptr)->handler_fptr(((CTimer *)node->data_ptr)->args_ptr);
176
177     /* Timer object hasn't changed within handler callback function? */
178     if(false == ((CTimer *)node->data_ptr)->changed)
179     {
180         /* Remove current timer from list */
181         (void)Dl_Remove(&self->timer_list, node);
182         /* Mark timer as unused */
183         ((CTimer *)node->data_ptr)->in_use = false;
184         /* Is current timer a periodic timer? */
185         if(((CTimer *)node->data_ptr)->period > 0U)
186         {
187             /* Reload current timer */
188             Tm_SetTimerInternal(self,
189                                 ((CTimer *)node->data_ptr),
190                                 ((CTimer *)node->data_ptr)->handler_fptr,
191                                 ((CTimer *)node->data_ptr)->args_ptr,
192                                 ((CTimer *)node->data_ptr)->period,
193                                 ((CTimer *)node->data_ptr)->period);
194
195             if(node == self->timer_list.head)  /* Is current timer new head of list? */
196             {
197                 /* Set trigger to inform application (see Tm_CheckForNextService()) and
198                    stop TM service. */
199                 self->set_service_timer = true;
200                 ret_val = false;
201             }
202         }
203     }
204
205     return ret_val;
206 }
207
208 /*! \brief Calls an application callback function to inform the application that the UCS must be 
209  *         serviced not later than the passed time period. If the timer list is empty a possible
210  *         running application timer will be stopped. This function is called at the end of
211  *         Ucs_Service().
212  *  \param self    Instance pointer
213  */
214 void Tm_CheckForNextService(CTimerManagement *self)
215 {
216     if(self->delayed_tm_service_enabled != false)
217     {
218         uint16_t current_tick_count;
219         Ssub_Notify(&self->get_tick_count_subject, &current_tick_count, false);
220         /* Has head of timer list changed? */
221         if(self->set_service_timer != false)
222         {
223             uint16_t new_time;
224             uint16_t diff = current_tick_count - self->last_tick_count;
225             self->set_service_timer = false;
226             if (self->timer_list.head != NULL)
227             {
228                 /* Timer expired since last TM service? */
229                 if(diff >= ((CTimer *)self->timer_list.head->data_ptr)->delta)
230                 {
231                     new_time = 1U;  /* Return minimum value */
232                 }
233                 else
234                 {
235                     /* Calculate new timeout */
236                     new_time = (uint16_t)(((CTimer *)self->timer_list.head->data_ptr)->delta - diff);
237                 }
238                 /* Inform the application that the UCS must be serviced not later than the passed
239                    time period. */
240                 Ssub_Notify(&self->set_application_timer_subject, &new_time, false);
241             }
242         }
243     }
244     else
245     {
246         Tm_TriggerService(self);    /* Application timer not implemented -> Retrigger TM */
247     }
248 }
249
250 /*! \brief Helper function to set the TM service event.
251  *  \details  This function is used by the application to trigger a service call of the Timer 
252  *            Management if the application timer has expired.
253  *  \param self            Instance pointer
254  */
255 void Tm_TriggerService(CTimerManagement *self)
256 {
257     if(self->timer_list.head != NULL)      /* At least one timer is running? */
258     {
259         Srv_SetEvent(&self->tm_srv, TM_EVENT_UPDATE_TIMERS);
260     }
261 }
262
263 /*! \brief Helper function to stop the TM service.
264  *  \param self            Instance pointer
265  */
266 void Tm_StopService(CTimerManagement *self)
267 {
268     uint16_t new_time = 0U;
269
270     /* Clear probable running application timer */
271     Ssub_Notify(&self->set_application_timer_subject, &new_time, false);
272
273     /* Reset the service timer. Not necessary ?  */
274     self->set_service_timer = false;
275
276     /* Clear the timer head queue to prevent any event to be set */
277     self->timer_list.head = NULL;
278 }
279
280 /*! \brief Creates a new timer. The timer expires at the specified elapse time and then after 
281  *         every specified period. When the timer expires the specified callback function is
282  *         called.
283  *  \param self            Instance pointer
284  *  \param timer_ptr       Reference to the timer object
285  *  \param handler_fptr    Callback function which is called when the timer expires
286  *  \param args_ptr        Reference to an optional parameter which is passed to the specified
287  *                         callback function
288  *  \param elapse          The elapse value before the timer expires for the first time, in
289  *                         milliseconds
290  *  \param period          The period of the timer, in milliseconds. If this parameter is zero, the
291  *                         timer is signaled once. If the parameter is greater than zero, the timer
292  *                         is periodic.
293  */
294 void Tm_SetTimer(CTimerManagement *self,
295                  CTimer *timer_ptr,
296                  Tm_Handler_t handler_fptr,
297                  void *args_ptr,
298                  uint16_t elapse,
299                  uint16_t period)
300 {
301     (void)Tm_ClearTimer(self, timer_ptr);       /* Clear timer if running */
302     /* Call the internal method to set the new timer (-> does not trigger TM service!) */
303     Tm_SetTimerInternal(self, timer_ptr, handler_fptr, args_ptr, elapse, period);
304     Tm_TriggerService(self);                    /* New timer added -> trigger timer list update */
305 }
306
307 /*! \brief This function contains the internal part when adding a new timer. The function is
308  *         called within Tm_SetTimer() and within Tm_UpdateTimers().
309  *  \param self            Instance pointer
310  *  \param timer_ptr       Reference to the timer object
311  *  \param handler_fptr    Callback function which is called when the timer expires
312  *  \param args_ptr        Reference to an optional parameter which is passed to the specified
313  *                         callback function
314  *  \param elapse          The elapse value before the timer expires for the first time, in
315  *                         milliseconds
316  *  \param period          The period of the timer, in milliseconds. If this parameter is zero, the
317  *                         timer is signaled once. If the parameter is greater than zero, the timer
318  *                         is periodic.
319  */
320 static void Tm_SetTimerInternal(CTimerManagement *self,
321                                 CTimer *timer_ptr,
322                                 Tm_Handler_t handler_fptr,
323                                 void *args_ptr,
324                                 uint16_t elapse,
325                                 uint16_t period)
326 {
327     uint16_t current_tick_count;
328     Ssub_Notify(&self->get_tick_count_subject, &current_tick_count, false);
329
330     /* Save timer specific values */
331     timer_ptr->changed = true;                  /* Flag is needed by Tm_UpdateTimers() */
332     timer_ptr->in_use = true;
333     timer_ptr->handler_fptr = handler_fptr;
334     timer_ptr->args_ptr = args_ptr;
335     timer_ptr->elapse = elapse;
336     timer_ptr->period = period;
337     timer_ptr->delta = elapse;
338
339     /* Create back link to be able to point from node to timer object */
340     timer_ptr->node.data_ptr = (void *)timer_ptr;
341
342     if(self->timer_list.head == NULL)            /* Is timer list empty? */
343     {
344         Dl_InsertHead(&self->timer_list, &timer_ptr->node);    /* Add first timer to list */
345         /* Save current tick count */
346         Ssub_Notify(&self->get_tick_count_subject, &self->last_tick_count, false);
347     }
348     else                                        /* Timer list is not empty */
349     {
350         CDlNode *result_ptr = NULL;
351
352         /* Set delta value in relation to last saved tick count (last TM service) */
353         timer_ptr->delta += (uint16_t)(current_tick_count - self->last_tick_count);
354
355         /* Search slot where new timer must be inserted. Update delta of new timer
356            and delta of the following timer in the list. */
357         result_ptr = Dl_Foreach(&self->timer_list, &Tm_UpdateTimersAdd, (void *)timer_ptr);
358
359         if(result_ptr != NULL)                   /* Slot found? */
360         {
361             /* Insert new timer at found position */
362             Dl_InsertBefore(&self->timer_list, result_ptr, &timer_ptr->node);
363         }
364         else                                    /* No slot found -> Insert as last node */
365         {
366             /* Add new timer to end of list */
367             Dl_InsertTail(&self->timer_list, &timer_ptr->node);
368         }
369     }
370 }
371
372 /*! \brief     Removes the specified timer from the timer list.
373  *  \param     self        Instance pointer
374  *  \param     timer_ptr   Reference to the timer object
375  *  \attention Make sure that for a timer object Tm_SetTimer() is called before Tm_ClearTimer()
376  *             is called!
377  */
378 void Tm_ClearTimer(CTimerManagement *self, CTimer *timer_ptr)
379 {
380     if(timer_ptr->in_use != false)          /* Is timer currently in use? */
381     {
382         timer_ptr->changed = true;          /* Flag is needed by Tm_UpdateTimers() */
383
384         if(timer_ptr->node.next != NULL)     /* Has deleted timer a follower? */
385         {
386             /* Adjust delta of following timer */
387             ((CTimer *)timer_ptr->node.next->data_ptr)->delta += timer_ptr->delta;
388         }
389
390         (void)Dl_Remove(&self->timer_list, &timer_ptr->node);
391         timer_ptr->in_use = false;
392
393         Tm_TriggerService(self);            /* Timer removed -> trigger timer list update */
394     }
395 }
396
397 /*! \brief  Used by Tm_SetTimer() to find the slot where the new timer must be inserted.
398  *  \param  c_timer_ptr Reference to current timer processed by foreach loop
399  *  \param  n_timer_ptr Reference to new timer
400  *  \return \c true: Slot found, stop foreach loop
401  *  \return \c false: Slot not found, continue foreach loop
402  */
403 static bool Tm_UpdateTimersAdd(void *c_timer_ptr, void *n_timer_ptr)
404 {
405     CTimer *current_timer_ptr = (CTimer *)c_timer_ptr;
406     CTimer *new_timer_ptr = (CTimer *)n_timer_ptr;
407     bool ret_val;
408
409     /* Is current timer lesser than new timer? */
410     if(current_timer_ptr->delta <= new_timer_ptr->delta)
411     {
412         /* Update delta of new timer and continue foreach loop */
413         new_timer_ptr->delta -= current_timer_ptr->delta;
414         ret_val = false;
415     }
416     else                                                    /* Slot found! */
417     {
418         /* Correct delta of current timer and stop foreach loop */
419         current_timer_ptr->delta -= new_timer_ptr->delta;
420         ret_val = true;
421     }
422
423     return ret_val;
424 }
425
426
427 /*------------------------------------------------------------------------------------------------*/
428 /* Implementation of class CTimer                                                                 */
429 /*------------------------------------------------------------------------------------------------*/
430 /*! \brief Constructor of the Timer class.
431  *  \param self        Instance pointer
432  */
433 void T_Ctor(CTimer *self)
434 {
435     MISC_MEM_SET(self, 0, sizeof(*self));
436 }
437
438 /*! \brief  Returns the status of the given timer.
439  *  \param  self        Instance pointer
440  *  \return \c true if the timer is currently in use
441  *  \return \c false if the timer is not currently in use
442  */
443 bool T_IsTimerInUse(CTimer *self)
444 {
445     return self->in_use;
446 }
447
448 /*!
449  * @}
450  * \endcond
451  */
452
453 /*------------------------------------------------------------------------------------------------*/
454 /* End of file                                                                                    */
455 /*------------------------------------------------------------------------------------------------*/
456