summaryrefslogtreecommitdiffstats
path: root/mnsl/mns_timer.c
blob: 16040cc0a77bafee72ee33c8ae9694ab484b3d72 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
/*
 * MOST NetServices "Light" V3.2.7.0.1796 MultiInstance Patch
 *
 * Copyright (C) 2015 Microchip Technology Germany II GmbH & Co. KG
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * You may also obtain this software under a propriety license from Microchip.
 * Please contact Microchip for further information.
 *
 */

/*!
 * \file
 * \brief Implementation of the timer management module.
 *
 * \cond MNS_INTERNAL_DOC
 * \addtogroup G_TIMER
 * @{
 */

/*------------------------------------------------------------------------------------------------*/
/* Includes                                                                                       */
/*------------------------------------------------------------------------------------------------*/
#include "mns_timer.h"
#include "mns_misc.h"
#include "mns_trace.h"

/*------------------------------------------------------------------------------------------------*/
/* Service parameters                                                                             */
/*------------------------------------------------------------------------------------------------*/
/*! Priority of the TM service used by scheduler */
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." */
/*! Main event for the TM service */
static const Srv_Event_t TM_EVENT_UPDATE_TIMERS = 1U;

/*------------------------------------------------------------------------------------------------*/
/* Internal prototypes                                                                            */
/*------------------------------------------------------------------------------------------------*/
static void Tm_Service(void *self);
static void Tm_UpdateTimers(CTimerManagement *self);
static bool Tm_HandleElapsedTimer(CTimerManagement *self);
static bool Tm_UpdateTimersAdd(void *c_timer_ptr, void *n_timer_ptr);
static void Tm_SetTimerInternal(CTimerManagement *self,
                                CTimer *timer_ptr,
                                Tm_Handler_t handler_fptr,
                                void *args_ptr,
                                uint16_t elapse,
                                uint16_t period);

/*------------------------------------------------------------------------------------------------*/
/* Implementation of class CTimerManagement                                                       */
/*------------------------------------------------------------------------------------------------*/
/*! \brief Constructor of the timer management class.
 *  \param self        Instance pointer
 *  \param scd         Scheduler instance
 *  \param init_ptr    Reference to the initialization data
 */
void Tm_Ctor(CTimerManagement *self, CScheduler *scd, const Tm_InitData_t *init_ptr)
{
    MISC_MEM_SET(self, 0, sizeof(*self));
    self->mns_inst_id = init_ptr->mns_inst_id;
    /* Initialize subjects and add observers */
    Ssub_Ctor(&self->get_tick_count_subject, self->mns_inst_id);
    (void)Ssub_AddObserver(&self->get_tick_count_subject,
                           init_ptr->get_tick_count_obs_ptr);
    if(init_ptr->set_application_timer_obs_ptr != NULL)
    {
        self->delayed_tm_service_enabled = true;
        Ssub_Ctor(&self->set_application_timer_subject, self->mns_inst_id);
        (void)Ssub_AddObserver(&self->set_application_timer_subject,
                               init_ptr->set_application_timer_obs_ptr);
    }
    /* Initialize timer management service */
    Srv_Ctor(&self->tm_srv, TM_SRV_PRIO, self, &Tm_Service);
    /* Add timer management service to scheduler */
    (void)Scd_AddService(scd, &self->tm_srv);
}

/*! \brief Service function of the timer management.
 *  \param self    Instance pointer
 */
static void Tm_Service(void *self)
{
    CTimerManagement *self_ = (CTimerManagement *)self;
    Srv_Event_t event_mask;

    Srv_GetEvent(&self_->tm_srv, &event_mask);

    if(TM_EVENT_UPDATE_TIMERS == (event_mask & TM_EVENT_UPDATE_TIMERS))     /* Is event pending? */
    {
        Srv_ClearEvent(&self_->tm_srv, TM_EVENT_UPDATE_TIMERS);
        Tm_UpdateTimers(self_); 
    }
}

/*! \brief If event TM_EVENT_UPDATE_TIMERS is set this function is called. Handles the update
 *         of the timer list. If a timer has expired the corresponding callback function is
 *         executed. If the expired timer is a periodic timer, the timer will be set again.
 *  \param self    Instance pointer
 */
static void Tm_UpdateTimers(CTimerManagement *self)
{
    uint16_t current_tick_count;
    Ssub_Notify(&self->get_tick_count_subject, &current_tick_count, false);

    if(self->timer_list.head != NULL)      /* At least one timer is running? */
    {
        bool continue_loop = true;
        /* Calculate time difference between the current and the last TM service run */
        uint16_t tick_count_diff = (uint16_t)(current_tick_count - self->last_tick_count);
        /* Save current tick count for next service run */
        self->last_tick_count = current_tick_count;

        /* Loop while timer list is not empty */
        while((self->timer_list.head != NULL) && (continue_loop!= false))
        {
            /* Is not first timer in list elapsed yet? */
            if(tick_count_diff <= ((CTimer *)self->timer_list.head->data_ptr)->delta)
            {
                /* Update delta of first timer in list */
                ((CTimer *)self->timer_list.head->data_ptr)->delta -= tick_count_diff;
                tick_count_diff = 0U;
            }
            else    /* At least first timer in list elapsed */
            {
                /* Update tick count difference for next timer in list */
                tick_count_diff -= ((CTimer *)self->timer_list.head->data_ptr)->delta;
                /* First timer elapsed */
                ((CTimer *)self->timer_list.head->data_ptr)->delta = 0U;
            }

            /* First timer in list elapsed? */
            if(0U == ((CTimer *)self->timer_list.head->data_ptr)->delta)
            {
                /* Handle elapsed timer */
                continue_loop = Tm_HandleElapsedTimer(self);
            }
            else    /* No elapsed timer in list. */
            {
                /* First timer in list updated! Set trigger to inform application (see 
                   Tm_CheckForNextService()) and stop TM service. */
                self->set_service_timer = true;
                continue_loop = false;
            }
        }
    }
}

/*! \brief  This function is called if the first timer in list is elapsed. The timer handler 
 *          callback function is invoked. If the timer is a periodic timer it is wound up again.
 *  \param  self    Instance pointer
 *  \return \c true if the next timer must be check.
 *  \return \c false if the wound up timer (periodic timer) is new head of timer list
 */
static bool Tm_HandleElapsedTimer(CTimerManagement *self)
{
    bool ret_val = true;

    CDlNode *node = self->timer_list.head;
    /* Reset flag to be able to check if timer object has changed within handler 
        callback function */
    ((CTimer *)node->data_ptr)->changed = false;
    /* Call timer handler callback function */
    ((CTimer *)node->data_ptr)->handler_fptr(((CTimer *)node->data_ptr)->args_ptr);

    /* Timer object hasn't changed within handler callback function? */
    if(false == ((CTimer *)node->data_ptr)->changed)
    {
        /* Remove current timer from list */
        (void)Dl_Remove(&self->timer_list, node);
        /* Mark timer as unused */
        ((CTimer *)node->data_ptr)->in_use = false;
        /* Is current timer a periodic timer? */
        if(((CTimer *)node->data_ptr)->period > 0U)
        {
            /* Reload current timer */
            Tm_SetTimerInternal(self,
                                ((CTimer *)node->data_ptr),
                                ((CTimer *)node->data_ptr)->handler_fptr,
                                ((CTimer *)node->data_ptr)->args_ptr,
                                ((CTimer *)node->data_ptr)->period,
                                ((CTimer *)node->data_ptr)->period);

            if(node == self->timer_list.head)  /* Is current timer new head of list? */
            {
                /* Set trigger to inform application (see Tm_CheckForNextService()) and
                   stop TM service. */
                self->set_service_timer = true;
                ret_val = false;
            }
        }
    }

    return ret_val;
}

/*! \brief Calls an application callback function to inform the application that the MNS must be 
 *         serviced not later than the passed time period. If the timer list is empty a possible
 *         running application timer will be stopped. This function is called at the end of
 *         Mns_Service().
 *  \param self    Instance pointer
 */
void Tm_CheckForNextService(CTimerManagement *self)
{
    if(self->delayed_tm_service_enabled != false)
    {
        uint16_t current_tick_count;
        Ssub_Notify(&self->get_tick_count_subject, &current_tick_count, false);
        /* Has head of timer list changed? */
        if(self->set_service_timer != false)
        {
            uint16_t new_time;
            uint16_t diff = current_tick_count - self->last_tick_count;
            self->set_service_timer = false;
            /* Timer expired since last TM service? */
            if(diff >= ((CTimer *)self->timer_list.head->data_ptr)->delta)
            {
                new_time = 1U;  /* Return minimum value */
            }
            else
            {
                /* Calculate new timeout */
                new_time = (uint16_t)(((CTimer *)self->timer_list.head->data_ptr)->delta - diff);
            }
            /* Inform the application that the MNS must be serviced not later than the passed
               time period. */
            Ssub_Notify(&self->set_application_timer_subject, &new_time, false);
        }
    }
    else
    {
        Tm_TriggerService(self);    /* Application timer not implemented -> Retrigger TM */
    }
}

/*! \brief Helper function to set the TM service event.
 *  \details  This function is used by the application to trigger a service call of the Timer 
 *            Management if the application timer has expired.
 *  \param self            Instance pointer
 */
void Tm_TriggerService(CTimerManagement *self)
{
    if(self->timer_list.head != NULL)      /* At least one timer is running? */
    {
        Srv_SetEvent(&self->tm_srv, TM_EVENT_UPDATE_TIMERS);
    }
}

/*! \brief Helper function to stop the TM service.
 *  \param self            Instance pointer
 */
void Tm_StopService(CTimerManagement *self)
{
    uint16_t new_time = 0U;

    /* Clear probable running application timer */
    Ssub_Notify(&self->set_application_timer_subject, &new_time, false);

    /* Reset the service timer. Not necessary ?  */
    self->set_service_timer = false;

    /* Clear the timer head queue to prevent any event to be set */
    self->timer_list.head = NULL;
}

/*! \brief Creates a new timer. The timer expires at the specified elapse time and then after 
 *         every specified period. When the timer expires the specified callback function is
 *         called.
 *  \param self            Instance pointer
 *  \param timer_ptr       Reference to the timer object
 *  \param handler_fptr    Callback function which is called when the timer expires
 *  \param args_ptr        Reference to an optional parameter which is passed to the specified
 *                         callback function
 *  \param elapse          The elapse value before the timer expires for the first time, in
 *                         milliseconds
 *  \param period          The period of the timer, in milliseconds. If this parameter is zero, the
 *                         timer is signaled once. If the parameter is greater than zero, the timer
 *                         is periodic.
 */
void Tm_SetTimer(CTimerManagement *self,
                 CTimer *timer_ptr,
                 Tm_Handler_t handler_fptr,
                 void *args_ptr,
                 uint16_t elapse,
                 uint16_t period)
{
    (void)Tm_ClearTimer(self, timer_ptr);       /* Clear timer if running */
    /* Call the internal method to set the new timer (-> does not trigger TM service!) */
    Tm_SetTimerInternal(self, timer_ptr, handler_fptr, args_ptr, elapse, period);
    Tm_TriggerService(self);                    /* New timer added -> trigger timer list update */
}

/*! \brief This function contains the internal part when adding a new timer. The function is
 *         called within Tm_SetTimer() and within Tm_UpdateTimers().
 *  \param self            Instance pointer
 *  \param timer_ptr       Reference to the timer object
 *  \param handler_fptr    Callback function which is called when the timer expires
 *  \param args_ptr        Reference to an optional parameter which is passed to the specified
 *                         callback function
 *  \param elapse          The elapse value before the timer expires for the first time, in
 *                         milliseconds
 *  \param period          The period of the timer, in milliseconds. If this parameter is zero, the
 *                         timer is signaled once. If the parameter is greater than zero, the timer
 *                         is periodic.
 */
static void Tm_SetTimerInternal(CTimerManagement *self,
                                CTimer *timer_ptr,
                                Tm_Handler_t handler_fptr,
                                void *args_ptr,
                                uint16_t elapse,
                                uint16_t period)
{
    uint16_t current_tick_count;
    Ssub_Notify(&self->get_tick_count_subject, &current_tick_count, false);
 
    /* Save timer specific values */
    timer_ptr->changed = true;                  /* Flag is needed by Tm_UpdateTimers() */
    timer_ptr->in_use = true;
    timer_ptr->handler_fptr = handler_fptr;
    timer_ptr->args_ptr = args_ptr;
    timer_ptr->elapse = elapse;
    timer_ptr->period = period;
    timer_ptr->delta = elapse;

    /* Create back link to be able to point from node to timer object */
    timer_ptr->node.data_ptr = (void *)timer_ptr;

    if(NULL == self->timer_list.head)           /* Is timer list empty? */
    {
        Dl_InsertHead(&self->timer_list, &timer_ptr->node);    /* Add first timer to list */
        /* Save current tick count */
        Ssub_Notify(&self->get_tick_count_subject, &self->last_tick_count, false);
    }
    else                                        /* Timer list is not empty */
    {
        CDlNode *result_ptr = NULL;

        /* Set delta value in relation to last saved tick count (last TM service) */
        timer_ptr->delta += (uint16_t)(current_tick_count - self->last_tick_count);

        /* Search slot where new timer must be inserted. Update delta of new timer
           and delta of the following timer in the list. */
        result_ptr = Dl_Foreach(&self->timer_list, &Tm_UpdateTimersAdd, (void *)timer_ptr);
 
        if(NULL != result_ptr)                  /* Slot found? */
        {
            /* Insert new timer at found position */
            Dl_InsertBefore(&self->timer_list, result_ptr, &timer_ptr->node);
        }
        else                                    /* No slot found -> Insert as last node */
        {
            /* Add new timer to end of list */
            Dl_InsertTail(&self->timer_list, &timer_ptr->node);
        }
    }
}

/*! \brief     Removes the specified timer from the timer list.
 *  \param     self        Instance pointer
 *  \param     timer_ptr   Reference to the timer object
 *  \attention Make sure that for a timer object Tm_SetTimer() is called before Tm_ClearTimer()
 *             is called!
 */
void Tm_ClearTimer(CTimerManagement *self, CTimer *timer_ptr)
{
    if(timer_ptr->in_use != false)          /* Is timer currently in use? */
    {
        timer_ptr->changed = true;          /* Flag is needed by Tm_UpdateTimers() */

        if(NULL != timer_ptr->node.next)    /* Has deleted timer a follower? */
        {
            /* Adjust delta of following timer */
            ((CTimer *)timer_ptr->node.next->data_ptr)->delta += timer_ptr->delta;
        }

        (void)Dl_Remove(&self->timer_list, &timer_ptr->node);
        timer_ptr->in_use = false;

        Tm_TriggerService(self);            /* Timer removed -> trigger timer list update */
    }
}

/*! \brief  Used by Tm_SetTimer() to find the slot where the new timer must be inserted.
 *  \param  c_timer_ptr Reference to current timer processed by foreach loop
 *  \param  n_timer_ptr Reference to new timer
 *  \return \c true: Slot found, stop foreach loop
 *  \return \c false: Slot not found, continue foreach loop
 */
static bool Tm_UpdateTimersAdd(void *c_timer_ptr, void *n_timer_ptr)
{
    CTimer *current_timer_ptr = (CTimer *)c_timer_ptr;
    CTimer *new_timer_ptr = (CTimer *)n_timer_ptr;
    bool ret_val;

    /* Is current timer lesser than new timer? */
    if(current_timer_ptr->delta <= new_timer_ptr->delta)
    {
        /* Update delta of new timer and continue foreach loop */
        new_timer_ptr->delta -= current_timer_ptr->delta;
        ret_val = false;
    }
    else                                                    /* Slot found! */
    {
        /* Correct delta of current timer and stop foreach loop */
        current_timer_ptr->delta -= new_timer_ptr->delta;
        ret_val = true;
    }

    return ret_val;
}


/*------------------------------------------------------------------------------------------------*/
/* Implementation of class CTimer                                                                 */
/*------------------------------------------------------------------------------------------------*/
/*! \brief Constructor of the Timer class.
 *  \param self        Instance pointer
 */
void T_Ctor(CTimer *self)
{
    MISC_MEM_SET(self, 0, sizeof(*self));
}

/*! \brief  Returns the status of the given timer.
 *  \param  self        Instance pointer
 *  \return \c true if the timer is currently in use
 *  \return \c false if the timer is not currently in use
 */
bool T_IsTimerInUse(CTimer *self)
{
    return self->in_use;
}

/*!
 * @}
 * \endcond
 */

/*------------------------------------------------------------------------------------------------*/
/* End of file                                                                                    */
/*------------------------------------------------------------------------------------------------*/