blob: fb3c1e77308059ef725ac4c68d9922cff1a5f94e [file] [log] [blame]
/******************************************************************************
Copyright (c) 2014-2015 Lantiq Deutschland GmbH
Copyright (c) 2015-2016 Lantiq Beteiligungs-GmbH & Co.KG
Copyright 2016, Intel Corporation.
For licensing information, see the file 'LICENSE' in the root folder of
this software module.
******************************************************************************/
/**
\file dxs_timer.c
This file contains the implementation of timers used in DXS driver.
*/
/* ========================================================================= */
/* Includes */
/* ========================================================================= */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#include <signal.h>
#include <semaphore.h>
#include <errno.h>
#include "dxs_timer.h"
/* ========================================================================= */
/* Macro definitions */
/* ========================================================================= */
/** default timeout thread poll time (in milliseconds),
this is the polling time used to check if new entiries/events were added */
#ifndef TIMEOUT_THREAD_POLL_TIME
#define TIMEOUT_THREAD_POLL_TIME 50
#endif
/** get pointer to payload of given entry */
#define list_entry_data(ENTRY) ((void *)((char *)ENTRY + sizeof(struct list_entry)))
/** check if list is empty */
#define is_list_empty(LIST) \
(((LIST)->first_element.next == &(LIST)->first_element) ? DXS_TM_TRUE : DXS_TM_FALSE)
#define foreach_list_entry_safe_ll(PLIST, ENTRY, NEXT_ENTRY) \
for ((ENTRY) = (PLIST)->next, (NEXT_ENTRY) = (ENTRY)->next; \
(ENTRY)->next != (PLIST)->next; \
(ENTRY) = (NEXT_ENTRY), (NEXT_ENTRY) = (ENTRY)->next)
#define foreach_list_entry(PLIST, ENTRY) \
for ((ENTRY) = (PLIST)->first_element.next; \
(ENTRY)->next != (PLIST)->first_element.next; \
(ENTRY) = (ENTRY)->next)
/* ========================================================================= */
/* Type definitions */
/* ========================================================================= */
/** List entry */
struct list_entry {
/** Previous list entry */
struct list_entry *next;
/** Next list entry */
struct list_entry *prev;
};
/** List structure */
struct list {
/** Used list entries, this first element is empty
and can be used only to pint to other elements */
struct list_entry first_element;
/** Size of list entry data */
size_t payload_size;
/** List lock */
sem_t lock;
};
/** timeout descriptor structure */
struct timeout {
/** set DXS_TM_TRUE if handler should be called periodically,
otherwise set DXS_TM_FALSE */
DXS_TM_BOOL_t bPeriodical;
/** timeout argument */
unsigned long arg1;
/** timeout handler */
TIMER_ENTRY handler;
};
/** timeout list entry */
struct timeout_list_entry {
/** Time when the timeout becomes active (in milliseconds) */
time_t timeout_time;
/** Time to wait from setting the timeout, used for periodic events */
time_t time_in;
/** timeout descriptor */
struct timeout timeout;
};
/** timeout control structure */
struct DXS_TM_Context {
/* DXS_TM_TRUE if timers were initialized */
DXS_TM_BOOL_t bTimersInialized;
/** timeout list */
struct list timeout_list;
/** Timeout thread control structure */
pthread_t timeout_thread_ctrl;
};
/* ========================================================================== */
/* Global variables */
/* ========================================================================== */
/** \todo deleting/clean up of timers is missing for user space, it should
be called upon closing the tapi thread or even maybe for dev stop */
/** control structure for timers in user's space */
struct DXS_TM_Context G_timers /* = {.bTimersInialized = DXS_TM_FALSE} */;
static volatile sig_atomic_t timer_thr_exit = 0;
/* ========================================================================== */
/* Local functions */
/* ========================================================================== */
static int32_t DXS_TM_list_init(struct list *list, size_t payload_size);
static void DXS_TM_list_entry_free(struct list *list,
struct list_entry *entry);
static int32_t DXS_TM_lockless_event_remove(struct DXS_TM_Context *context,
struct list_entry *entry_to_remove);
static int32_t DXS_TM_timeout_event_remove(struct DXS_TM_Context *context,
struct list_entry *timer_entry);
static void DXS_TM_timeout_event_stop(struct DXS_TM_Context *context,
struct list_entry *timer_entry);
static struct list_entry *DXS_TM_next_active_event_get(struct DXS_TM_Context *context,
struct timeout *timeout,
time_t * time_to_next_entry);
static void DXS_TM_list_entry_remove(struct list *list,
struct list_entry *entry);
static void DXS_TM_lockless_event_stop(struct DXS_TM_Context *context,
struct list_entry *entry_to_stop);
static void *DXS_TM_timeout_thread_main(void *arg);
static void DXS_TM_list_delete(struct list *list);
static int32_t DXS_TM_timeout_init(struct DXS_TM_Context *context);
static struct list_entry *DXS_TM_event_entry_create(struct DXS_TM_Context *context,
const struct timeout *timeout);
static struct list_entry *DXS_TM_timeout_event_create(struct DXS_TM_Context *context,
TIMER_ENTRY handler,
unsigned long arg1);
static int32_t DXS_TM_timeout_event_start(struct DXS_TM_Context *context,
struct list_entry *timer_entry,
time_t timeout_time, DXS_TM_BOOL_t bPeriodical);
static int32_t DXS_TM_event_entry_start(struct DXS_TM_Context *context,
struct list_entry *timer_entry,
time_t timeout_time,
DXS_TM_BOOL_t bPeriodical);
static int32_t DXS_TM_lockless_event_entry_start(struct DXS_TM_Context *context,
struct list_entry *timer_entry,
time_t timeout_time);
static void DXS_TM_list_entry_add_before(struct list *list,
struct list_entry *entry,
struct list_entry *new_entry);
static void DXS_TM_list_entry_add_tail(struct list *list,
struct list_entry *new_entry);
static void DXS_TM_list_entry_add_after(struct list *list,
struct list_entry *entry,
struct list_entry *new_entry);
/**
Sleep for defined time period.
\param pTVal pointer to timespec structure
\return
- none
*/
static void dxs_nanosleep(struct timespec *pTVal)
{
while (1)
{
int rval = nanosleep(pTVal, pTVal);
if (rval == 0)
{
return;
}
else
{
if (errno == EINTR)
continue;
else
return;
}
}
}
/**
Sleep for an interval in ms.
\param sleepTime_ms sleep time in ms
\return
- none
*/
static void dxs_MSecSleep(time_t sleepTime_ms)
{
struct timespec tv;
tv.tv_sec = sleepTime_ms/1000;
tv.tv_nsec = (long) ((sleepTime_ms - (tv.tv_sec * 1000)) * 1000 * 1000);
dxs_nanosleep(&tv);
return;
}
/**
Elapsed time in ms after given timestamp.
\param refTime_ms reference time in ms
\return
- elapsed time in ms as time_t value
*/
static time_t dxs_ElapsedTimeMSecGet(
time_t refTime_ms)
{
struct timeval tv = {0};
time_t nTime_ms = 0;
gettimeofday(&tv, NULL);
nTime_ms = (time_t)(tv.tv_sec*1000 + (tv.tv_usec) / 1000);
if ( (refTime_ms == 0) || (refTime_ms > nTime_ms) )
{
return nTime_ms;
}
return (nTime_ms - refTime_ms);
}
/**
Create new list_entry element and assign a callback function to it.
\param context pointer to tapi timer context structure
\param handler pointer to callback function
\param arg1 private data for the callback, can be pointer or int
\return
- pointer to newly allocated list_entry
- NULL in case of error, return value of DXS_TM_event_entry_create()
*/
static struct list_entry *DXS_TM_timeout_event_create(struct DXS_TM_Context *context,
TIMER_ENTRY handler,
unsigned long arg1)
{
struct timeout timeout;
timeout.handler = handler;
timeout.arg1 = arg1;
timeout.bPeriodical = DXS_TM_FALSE;
return DXS_TM_event_entry_create(context, &timeout);
}
/**
Create new list_entry element, allocate memory and copy timeout data.
\param context pointer to tapi timer context structure
\param timeout pointer to timeout stucture
\return
- pointer to newly allocated list_entry
- NULL in case of error
*/
static struct list_entry *DXS_TM_event_entry_create(struct DXS_TM_Context *context,
const struct timeout *timeout)
{
struct list_entry *new_entry;
struct timeout_list_entry *new_timeout_entry;
if (sem_wait(&context->timeout_list.lock))
return NULL;
/* allocate memory for list entry with payload */
new_entry = malloc(sizeof(struct list_entry) + context->timeout_list.payload_size);
if (!new_entry) {
sem_post(&context->timeout_list.lock);
return NULL;
}
new_timeout_entry = list_entry_data(new_entry);
new_timeout_entry->timeout = *timeout;
sem_post(&context->timeout_list.lock);
return new_entry;
}
/**
Initialize DXS_TM_Context structure and its list and start timeout control
thread.
\param context pointer to tapi timer context structure
\return
- DXS_TM_SUCCESS or DXS_TM_ERROR in case of errors
*/
static int32_t DXS_TM_timeout_init(struct DXS_TM_Context *context)
{
int32_t error;
error = DXS_TM_list_init(&context->timeout_list, sizeof(struct timeout_list_entry));
if (error)
{
fprintf( stderr,
("DXS_API: DXS_TM_list_init() failed for timer Initialization\n"));
return error;
}
error = (DXS_TM_SUCCESS == pthread_create(&context->timeout_thread_ctrl,
NULL,
DXS_TM_timeout_thread_main,
(void *)context)) ? DXS_TM_SUCCESS : DXS_TM_ERROR;
if (error)
{
fprintf( stderr,
("DXS_API: Thread init failed for timer Initialization\n"));
DXS_TM_list_delete(&context->timeout_list);
return error;
}
return DXS_TM_SUCCESS;
}
/**
Release memory and other resources used.
\param list pointer to list
*/
static void DXS_TM_list_delete(struct list *list)
{
struct list_entry *entry, *tmp_entry;
foreach_list_entry_safe_ll(&list->first_element, entry, tmp_entry)
{
free(entry);
}
/* if list is empty fileds next and prev should point first_element */
list->first_element.next = &list->first_element;
list->first_element.prev = &list->first_element;
(void)sem_destroy(&list->lock);
}
/*
* exit from timer thread
*/
void terminate_timer_thread(int sig)
{
timer_thr_exit = 1;
}
/** timeout events handling thread
\param[in] arg Thread arguments
\return 0
*/
static void *DXS_TM_timeout_thread_main(void *arg)
{
struct DXS_TM_Context *context = (struct DXS_TM_Context *)arg;
struct timeout timer = {0};
struct list_entry *timer_to_execute_entry = NULL;
time_t wait_time;
struct sigaction act = {0};
int32_t ret;
act.sa_handler = terminate_timer_thread;
sigaction(SIGTERM, &act, NULL);
/* while thread is running */
while (!timer_thr_exit)
{
/* wait for message in FIFO */
while (!timer_thr_exit)
{
wait_time = 0;
ret = sem_wait(&context->timeout_list.lock);
if (ret)
break;
timer_to_execute_entry = DXS_TM_next_active_event_get(context, &timer,
&wait_time);
sem_post(&context->timeout_list.lock);
if (timer_to_execute_entry == NULL)
{
/* if time to the next timeout is longer than the poll time,
then wait only TIMEOUT_THREAD_POLL_TIME ms */
if ((wait_time == 0) || (wait_time > TIMEOUT_THREAD_POLL_TIME))
{
wait_time = TIMEOUT_THREAD_POLL_TIME;
}
/** \todo replace sleep with select, for this some sync mechanism
with add/start entry is needed, for example select could wait
for a pipe fd being readable, each function for add/start entry
would write to this pipe, this could replace the polling,
if above is implemented then wait_time could be set to exact
time to next timeout or wait forever untill and event/entry
is added */
dxs_MSecSleep((time_t) wait_time);
}
else
{
break;
}
}
#if 0
/* check if we are shutting down */
if (thr_params->bShutDown == DXS_TM_TRUE
|| thr_params->bRunning == DXS_TM_FALSE)
{
break;
}
#endif
ret = sem_wait(&context->timeout_list.lock);
if (ret)
{
break;
}
else
{
struct timeout_list_entry *periodical_timeout_entry;
time_t time_in;
periodical_timeout_entry = list_entry_data(timer_to_execute_entry);
DXS_TM_lockless_event_stop(context, timer_to_execute_entry);
if (periodical_timeout_entry && timer.bPeriodical)
{
/* for periodical events get time to next timeout and add new timeout */
time_in = periodical_timeout_entry->time_in;
(void) DXS_TM_lockless_event_entry_start(context, timer_to_execute_entry,
time_in);
}
}
sem_post(&context->timeout_list.lock);
if (timer.handler != NULL)
{
(void) timer.handler((Timer_ID) timer_to_execute_entry, (void *) timer.arg1);
}
else
{
fprintf( stderr,
("DXS_TM_timeout_thread_main - ERROR: found event without timer handler"));
}
}
return 0;
}
/**
Lockless version of DXS_TM_timeout_event_remove.
\param context pointer to tapi timer context structure
\param entry_to_stop pointer to tapi entry structure
\return
- DXS_TM_SUCCESS
*/
static void DXS_TM_lockless_event_stop(struct DXS_TM_Context *context,
struct list_entry *entry_to_stop)
{
struct list_entry *current_entry;
if (is_list_empty(&context->timeout_list))
{
return;
}
foreach_list_entry(&context->timeout_list, current_entry)
{
if (current_entry == entry_to_stop)
{
DXS_TM_list_entry_remove(&context->timeout_list, current_entry);
return;
}
}
return;
}
/**
Remove entry from the list.
\param list pointer to list structure
\param entry pointer to entry structure that will be removed
*/
static void DXS_TM_list_entry_remove(struct list *list,
struct list_entry *entry)
{
entry->next->prev = entry->prev;
entry->prev->next = entry->next;
entry->prev = NULL;
entry->next = NULL;
if (list->first_element.next == entry)
{
list->first_element.next = &list->first_element;
}
}
/** Get next timeouted event
\param[in] context timer context pointer
\param[out] timeout Returns timeout descriptor
\param[out] time_to_next_entry time till the next timeout expires,
set only if no entry was found
\return first entry on the list for which timeout expired or NULL
*/
static struct list_entry *DXS_TM_next_active_event_get(struct DXS_TM_Context *context,
struct timeout *timeout,
time_t * time_to_next_entry)
{
struct timeout_list_entry *first_entry;
time_t currentTime;
if (is_list_empty(&context->timeout_list)) {
return NULL;
}
/* get the first entry from the list,
should be the one for which timeout expires first */
first_entry = list_entry_data(context->timeout_list.first_element.next);
if (!first_entry)
{
return NULL;
}
currentTime = (time_t)dxs_ElapsedTimeMSecGet(0);
if (first_entry->timeout_time <= currentTime)
{
/* timeout occured for this entry */
*timeout = first_entry->timeout;
return context->timeout_list.first_element.next;
}
else
{
/* get time to next timeout */
*time_to_next_entry = first_entry->timeout_time - currentTime;
}
return NULL;
}
/** Stop handling of given entry
\param[in] context timer context pointer
\param[in] timer_entry timer entry to stop
\return always DXS_TM_SUCCESS as DXS_TM_lockless_event_stop does
*/
static void DXS_TM_timeout_event_stop(struct DXS_TM_Context *context,
struct list_entry *timer_entry)
{
struct timeout_list_entry *timeout_entry_to_start;
sem_wait(&context->timeout_list.lock);
timeout_entry_to_start = list_entry_data(timer_entry);
timeout_entry_to_start->timeout.bPeriodical = DXS_TM_FALSE;
DXS_TM_lockless_event_stop(context, timer_entry);
sem_post(&context->timeout_list.lock);
return;
}
/** Remove given entry from the list
\param[in] context timer context pointer
\param[in] timer_entry timer entry to remove
\return always DXS_TM_SUCCESS as DXS_TM_lockless_event_remove does
*/
static int32_t DXS_TM_timeout_event_remove(struct DXS_TM_Context *context,
struct list_entry *timer_entry)
{
int32_t error;
sem_wait(&context->timeout_list.lock);
error = DXS_TM_lockless_event_remove(context, timer_entry);
sem_post(&context->timeout_list.lock);
return error;
}
/** Lockless version of DXS_TM_timeout_event_remove
\param[in] context timer context pointer
\param[in] entry_to_remove entry to remove
\return always DXS_TM_SUCCESS
*/
static int32_t DXS_TM_lockless_event_remove(struct DXS_TM_Context *context,
struct list_entry *entry_to_remove)
{
struct list_entry *current_entry;
if (is_list_empty(&context->timeout_list))
{
return DXS_TM_SUCCESS;
}
foreach_list_entry(&context->timeout_list, current_entry)
{
if (current_entry == entry_to_remove)
{
DXS_TM_list_entry_free(&context->timeout_list, current_entry);
return DXS_TM_SUCCESS;
}
}
return DXS_TM_SUCCESS;
}
/** Release memory allocated for given entry
\param[in] list pointeer to the list - unused
\param[in] entry entry to remove and free memory
*/
static void DXS_TM_list_entry_free(struct list *list,
struct list_entry *entry)
{
if ((NULL != entry->next) && (NULL != entry->prev))
{
entry->next->prev = entry->prev;
entry->prev->next = entry->next;
}
free(entry);
}
/** Release memory allocated for given entry
\param[in] list pointeer to the list
\param[in] payload_size size of memory in bytes needed to hold the entry data
\return value sem_init() call
*/
static int32_t DXS_TM_list_init(struct list *list, size_t payload_size)
{
/* for first element next and prev point to first element if list is empty */
list->first_element.next = &list->first_element;
list->first_element.prev = &list->first_element;
list->payload_size = payload_size;
/* get the lock */
return sem_init(&list->lock, 0, 1);
}
/** Start handling of given timeout entry
\param[in] context timer context pointer
\param[in] timer_entry timer entry to start
\param[in] timeout_time timoeout time for new timer entry
\param[in] bPeriodical if DXS_TM_TRUE, then timeout for event will be
checked periodicaly to execute the handler
\return always DXS_TM_SUCCESS as DXS_TM_event_entry_start does
*/
static int32_t DXS_TM_timeout_event_start(struct DXS_TM_Context *context,
struct list_entry *timer_entry,
time_t timeout_time, DXS_TM_BOOL_t bPeriodical)
{
/** \todo add a check if timers are initialized */
return DXS_TM_event_entry_start(context, timer_entry, timeout_time, bPeriodical);
}
/** Start handling of given timeout entry
\param[in] context timer context pointer
\param[in] timer_entry timer entry to start
\param[in] timeout_time timoeout time for new timer entry
\param[in] bPeriodical if DXS_TM_TRUE, then timeout for event will be
checked periodicaly to execute the handler
\return always DXS_TM_SUCCESS
*/
static int32_t DXS_TM_event_entry_start(struct DXS_TM_Context *context,
struct list_entry *timer_entry,
time_t timeout_time,
DXS_TM_BOOL_t bPeriodical)
{
struct timeout_list_entry *timeout_entry_to_start;
int32_t ret;
sem_wait(&context->timeout_list.lock);
timeout_entry_to_start = list_entry_data(timer_entry);
timeout_entry_to_start->timeout.bPeriodical = bPeriodical;
ret = DXS_TM_lockless_event_entry_start(context, timer_entry, timeout_time);
sem_post(&context->timeout_list.lock);
return ret;
}
/** Add timeout event
\param[in] context timer context pointer
\param[in] timer_entry timer entry to start
\param[in] timeout_time timoeout time for new timer entry (in ms)
\return always DXS_TM_SUCCESS
*/
static int32_t DXS_TM_lockless_event_entry_start(struct DXS_TM_Context *context,
struct list_entry *timer_entry,
time_t timeout_time)
{
struct list_entry *current_entry;
struct timeout_list_entry *timeout_entry, *timeout_entry_to_start;
DXS_TM_BOOL_t added = DXS_TM_FALSE;
if (timer_entry == NULL)
return DXS_TM_ERROR;
timeout_entry_to_start = list_entry_data(timer_entry);
if (timeout_entry_to_start == NULL)
return DXS_TM_ERROR;
/* get timeout time to compare with current time read
with dxs_ElapsedTimeMSecGet(0) */
timeout_entry_to_start->timeout_time = dxs_ElapsedTimeMSecGet(0) + timeout_time;
timeout_entry_to_start->time_in = timeout_time;
foreach_list_entry(&context->timeout_list, current_entry)
{
timeout_entry = list_entry_data(current_entry);
if (timeout_entry->timeout_time >
timeout_entry_to_start->timeout_time)
{
DXS_TM_list_entry_add_before(&context->timeout_list, current_entry,
timer_entry);
added = DXS_TM_TRUE;
break;
}
}
if (!added)
{
DXS_TM_list_entry_add_tail(&context->timeout_list, timer_entry);
}
return DXS_TM_SUCCESS;
}
/** Add add new entry before entry that is already on the list
\param[in] list the list - unused
\param[in] entry entry from the list
\param[in] new_entry entry to add
*/
static void DXS_TM_list_entry_add_before(struct list *list,
struct list_entry *entry,
struct list_entry *new_entry)
{
entry->prev->next = new_entry;
new_entry->prev = entry->prev;
new_entry->next = entry;
entry->prev = new_entry;
}
/** Add add new entry at the end of the list
\param[in] list the list
\param[in] entry entry to add
*/
static void DXS_TM_list_entry_add_tail(struct list *list,
struct list_entry *new_entry)
{
DXS_TM_list_entry_add_after(list, list->first_element.prev, new_entry);
}
/** Add add new entry after entry that is already on the list
\param[in] list the list - unused
\param[in] entry entry from the list
\param[in] new_entry entry to add
*/
static void DXS_TM_list_entry_add_after(struct list *list,
struct list_entry *entry,
struct list_entry *new_entry)
{
entry->next->prev = new_entry;
new_entry->next = entry->next;
entry->next = new_entry;
new_entry->prev = entry;
}
/* ========================================================================== */
/* Public functions */
/* ========================================================================== */
/**
Create a timer.
\param pTimerEntry Function pointer to the call back function.
\param nArgument Pointer to DXS channel structure.
\return
Timer_ID Pointer to internal timer structure.
\remarks
Initialize a task queue which will be scheduled once a timer interrupt occurs
to execute the appropriate operation in a process context, process in which
semaphores ... are allowed.
Please notice that this task has to run under the keventd process, in which
it can be executed thousands of times within a single timer tick.
*/
Timer_ID DXS_TimerCreate(TIMER_ENTRY pTimerEntry, void *nArgument)
{
if (DXS_TM_TRUE != G_timers.bTimersInialized)
{
/* timeout thread needs to be started only once */
G_timers.bTimersInialized = DXS_TM_TRUE;
DXS_TM_timeout_init(&G_timers);
}
return (Timer_ID) DXS_TM_timeout_event_create(&G_timers, pTimerEntry,
(unsigned long)nArgument);
}
/**
Sets a timer to the specified time and starts it. It can be choose if the
timer starts periodically.
\param Timer_ID Pointer to internal timer structure.
\param nTime Time in ms.
\param bPeriodically Starts the timer periodically or not.
\param bRestart Restart the timer or normal start.
\return
Returns an error code: DXS_TM_TRUE / DXS_TM_FALSE
*/
DXS_TM_BOOL_t DXS_TimerSet(Timer_ID Timer, uint32_t nTime, DXS_TM_BOOL_t bPeriodically,
DXS_TM_BOOL_t bRestart)
{
/** \todo add a check if timers are initialized */
if (bRestart == DXS_TM_TRUE)
{
DXS_TM_timeout_event_stop(&G_timers, (struct list_entry *) Timer);
}
if (DXS_TM_timeout_event_start(&G_timers,
(struct list_entry *) Timer,
(time_t) nTime,
bPeriodically) != 0)
{
fprintf ( stderr, ("DXS_TimerSet: failed to start timer\n"));
return DXS_TM_FALSE;
}
return DXS_TM_TRUE;
}
/**
Stop a timer.
\param Timer_ID Pointer to internal timer structure.
\return
Returns an error code: DXS_TM_TRUE / DXS_TM_FALSE
*/
DXS_TM_BOOL_t DXS_TimerStop(Timer_ID Timer)
{
if(Timer)
DXS_TM_timeout_event_stop(&G_timers, (struct list_entry *) Timer);
return DXS_TM_TRUE;
}
/**
Delete a timer.
\param Timer_ID Pointer to internal timer structure.
\return
Returns an error code: DXS_TM_TRUE / DXS_TM_FALSE
*/
DXS_TM_BOOL_t DXS_TimerDestroy(Timer_ID Timer)
{
if (Timer == NULL)
return DXS_TM_FALSE;
DXS_TimerStop(Timer);
/* free memory */
(void) DXS_TM_timeout_event_remove(&G_timers, (struct list_entry *)Timer);
return DXS_TM_TRUE;
}