summaryrefslogtreecommitdiffstats
path: root/mnsl/mns_timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'mnsl/mns_timer.c')
-rw-r--r--mnsl/mns_timer.c454
1 files changed, 454 insertions, 0 deletions
diff --git a/mnsl/mns_timer.c b/mnsl/mns_timer.c
new file mode 100644
index 0000000..c28d32b
--- /dev/null
+++ b/mnsl/mns_timer.c
@@ -0,0 +1,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 3 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 */
+/*------------------------------------------------------------------------------------------------*/
+