/* * 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 . * * 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, ¤t_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, ¤t_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, ¤t_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 */ /*------------------------------------------------------------------------------------------------*/