/*
*********************************************************************************************************
*                                              uC/OS-III
*                                        The Real-Time Kernel
*
*                    Copyright 2009-2021 Silicon Laboratories Inc. www.silabs.com
*
*                                 SPDX-License-Identifier: APACHE-2.0
*
*               This software is subject to an open source license and is distributed by
*                Silicon Laboratories Inc. pursuant to the terms of the Apache License,
*                    Version 2.0 available at www.apache.org/licenses/LICENSE-2.0.
*
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                           TIMER MANAGEMENT
*
* File    : os_tmr.c
* Version : V3.08.01
*********************************************************************************************************
*/

#define  MICRIUM_SOURCE
#include "os.h"

#ifdef VSC_INCLUDE_SOURCE_FILE_NAMES
const  CPU_CHAR  *os_tmr__c = "$Id: $";
#endif


#if (OS_CFG_TMR_EN > 0u)
/*
************************************************************************************************************************
*                                               LOCAL FUNCTION PROTOTYPES
************************************************************************************************************************
*/

static  void  OS_TmrLock      (void);
static  void  OS_TmrUnlock    (void);

static  void  OS_TmrCondCreate(void);
static  void  OS_TmrCondSignal(void);
static  void  OS_TmrCondWait  (OS_TICK  timeout);


/*
************************************************************************************************************************
*                                                   CREATE A TIMER
*
* Description: This function is called by your application code to create a timer.
*
* Arguments  : p_tmr           Is a pointer to a timer control block
*
*              p_name          Is a pointer to an ASCII string that is used to name the timer.  Names are useful for
*                              debugging.
*
*              dly             Initial delay.
*                              If the timer is configured for ONE-SHOT mode, this is the timeout used
*                              If the timer is configured for PERIODIC mode, this is the first timeout to wait for
*                              before the timer starts entering periodic mode
*
*              period          The 'period' being repeated for the timer.
*                              If you specified 'OS_OPT_TMR_PERIODIC' as an option, when the timer expires, it will
*                              automatically restart with the same period.
*
*              opt             Specifies either:
*
*                                  OS_OPT_TMR_ONE_SHOT       The timer counts down only once
*                                  OS_OPT_TMR_PERIODIC       The timer counts down and then reloads itself
*
*              p_callback      Is a pointer to a callback function that will be called when the timer expires.  The
*                              callback function must be declared as follows:
*
*                                  void  MyCallback (OS_TMR *p_tmr, void *p_arg);
*
*              p_callback_arg  Is an argument (a pointer) that is passed to the callback function when it is called.
*
*              p_err           Is a pointer to an error code.  '*p_err' will contain one of the following:
*
*                                 OS_ERR_NONE                    The call succeeded
*                                 OS_ERR_ILLEGAL_CREATE_RUN_TIME If you are trying to create the timer after you called
*                                                                  OSSafetyCriticalStart()
*                                 OS_ERR_OBJ_PTR_NULL            Is 'p_tmr' is a NULL pointer
*                                 OS_ERR_OPT_INVALID             You specified an invalid option
*                                 OS_ERR_TMR_INVALID_CALLBACK    You specified an invalid callback for a periodic timer
*                                 OS_ERR_TMR_INVALID_DLY         You specified an invalid delay
*                                 OS_ERR_TMR_INVALID_PERIOD      You specified an invalid period
*                                 OS_ERR_TMR_ISR                 If the call was made from an ISR
*                                 OS_ERR_OBJ_CREATED             If the timer was already created
*
* Returns    : none
*
* Note(s)    : 1) This function only creates the timer.  In other words, the timer is not started when created.  To
*                 start the timer, call OSTmrStart().
************************************************************************************************************************
*/

void  OSTmrCreate (OS_TMR               *p_tmr,
                   CPU_CHAR             *p_name,
                   OS_TICK               dly,
                   OS_TICK               period,
                   OS_OPT                opt,
                   OS_TMR_CALLBACK_PTR   p_callback,
                   void                 *p_callback_arg,
                   OS_ERR               *p_err)
{
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE) {
       *p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;
        return;
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* See if trying to call from an ISR                    */
       *p_err = OS_ERR_TMR_ISR;
        return;
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_tmr == (OS_TMR *)0) {                                 /* Validate 'p_tmr'                                     */
       *p_err = OS_ERR_OBJ_PTR_NULL;
        return;
    }

    switch (opt) {
        case OS_OPT_TMR_PERIODIC:
             if (period == 0u) {
                *p_err = OS_ERR_TMR_INVALID_PERIOD;
                 return;
             }

             if (p_callback == (OS_TMR_CALLBACK_PTR)0) {        /* No point in a periodic timer without a callback      */
                *p_err = OS_ERR_TMR_INVALID_CALLBACK;
                 return;
             }
             break;

        case OS_OPT_TMR_ONE_SHOT:
             if (dly == 0u) {
                *p_err = OS_ERR_TMR_INVALID_DLY;
                 return;
             }
             break;

        default:
            *p_err = OS_ERR_OPT_INVALID;
             return;
    }
#endif

    if (OSRunning == OS_STATE_OS_RUNNING) {                     /* Only lock when the kernel is running                 */
        OS_TmrLock();
    }

#if (OS_OBJ_TYPE_REQ > 0u)
#if (OS_CFG_OBJ_CREATED_CHK_EN > 0u)
    if (p_tmr->Type == OS_OBJ_TYPE_TMR) {
        if (OSRunning == OS_STATE_OS_RUNNING) {
            OS_TmrUnlock();
        }
        *p_err = OS_ERR_OBJ_CREATED;
        return;
    }
#endif
    p_tmr->Type           = OS_OBJ_TYPE_TMR;
#endif

    p_tmr->State          = OS_TMR_STATE_STOPPED;               /* Initialize the timer fields                          */
#if (OS_CFG_DBG_EN > 0u)
    p_tmr->NamePtr        = p_name;
#else
    (void)p_name;
#endif
    p_tmr->Dly            =  dly    * OSTmrToTicksMult;         /* Convert to Timer Start Delay to ticks                */
    p_tmr->Remain         =  0u;
    p_tmr->Period         =  period * OSTmrToTicksMult;         /* Convert to Timer Period      to ticks                */
    p_tmr->Opt            =  opt;
    p_tmr->CallbackPtr    =  p_callback;
    p_tmr->CallbackPtrArg =  p_callback_arg;
    p_tmr->NextPtr        = (OS_TMR *)0;
    p_tmr->PrevPtr        = (OS_TMR *)0;

#if (OS_CFG_DBG_EN > 0u)
    OS_TmrDbgListAdd(p_tmr);
#endif
#if (OS_CFG_DBG_EN > 0u)
    OSTmrQty++;                                                 /* Keep track of the number of timers created           */
#endif

    if (OSRunning == OS_STATE_OS_RUNNING) {
        OS_TmrUnlock();
    }

   *p_err = OS_ERR_NONE;
}


/*
************************************************************************************************************************
*                                                   DELETE A TIMER
*
* Description: This function is called by your application code to delete a timer.
*
* Arguments  : p_tmr          Is a pointer to the timer to stop and delete.
*
*              p_err          Is a pointer to an error code.  '*p_err' will contain one of the following:
*
*                                 OS_ERR_NONE                    The call succeeded
*                                 OS_ERR_ILLEGAL_DEL_RUN_TIME    If you are trying to delete the timer after you called
*                                                                  OSStart()
*                                 OS_ERR_OBJ_TYPE                If 'p_tmr' is not pointing to a timer
*                                 OS_ERR_OS_NOT_RUNNING          If uC/OS-III is not running yet
*                                 OS_ERR_TMR_INACTIVE            If the timer was not created
*                                 OS_ERR_TMR_INVALID             If 'p_tmr' is a NULL pointer
*                                 OS_ERR_TMR_INVALID_STATE       The timer is in an invalid state
*                                 OS_ERR_TMR_ISR                 If the function was called from an ISR
*
* Returns    : OS_TRUE   if the timer was deleted
*              OS_FALSE  if not or upon an error
*
* Note(s)    : none
************************************************************************************************************************
*/

#if (OS_CFG_TMR_DEL_EN > 0u)
CPU_BOOLEAN  OSTmrDel (OS_TMR  *p_tmr,
                       OS_ERR  *p_err)
{
    CPU_BOOLEAN  success;
    OS_TICK      time;
    CPU_SR_ALLOC();


#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (OS_FALSE);
    }
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508
    if (OSSafetyCriticalStartFlag == OS_TRUE) {
       *p_err = OS_ERR_ILLEGAL_DEL_RUN_TIME;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* See if trying to call from an ISR                    */
       *p_err  = OS_ERR_TMR_ISR;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* Is the kernel running?                               */
       *p_err = OS_ERR_OS_NOT_RUNNING;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_tmr == (OS_TMR *)0) {
       *p_err = OS_ERR_TMR_INVALID;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_tmr->Type != OS_OBJ_TYPE_TMR) {                       /* Make sure timer was created                          */
       *p_err = OS_ERR_OBJ_TYPE;
        return (OS_FALSE);
    }
#endif

    OS_TmrLock();

    CPU_CRITICAL_ENTER();
    if (OSTCBCurPtr == &OSTmrTaskTCB) {                         /* Callbacks operate on the Tmr Task's tick base.       */
        time = OSTmrTaskTickBase;
    } else {
#if (OS_CFG_DYN_TICK_EN > 0u)
        time = OSTickCtr + OS_DynTickGet();
#else
        time = OSTickCtr;
#endif
    }
    CPU_CRITICAL_EXIT();

#if (OS_CFG_DBG_EN > 0u)
    OS_TmrDbgListRemove(p_tmr);
#endif

    switch (p_tmr->State) {
        case OS_TMR_STATE_RUNNING:
        case OS_TMR_STATE_TIMEOUT:
             OS_TmrUnlink(p_tmr, time);                         /* Remove from the list                                 */
             OS_TmrClr(p_tmr);
#if (OS_CFG_DBG_EN > 0u)
             OSTmrQty--;                                        /* One less timer                                       */
#endif
            *p_err   = OS_ERR_NONE;
             success = OS_TRUE;
             break;

        case OS_TMR_STATE_STOPPED:                              /* Timer has not started or ...                         */
        case OS_TMR_STATE_COMPLETED:                            /* ... timer has completed the ONE-SHOT time            */
             OS_TmrClr(p_tmr);                                  /* Clear timer fields                                   */
#if (OS_CFG_DBG_EN > 0u)
             OSTmrQty--;                                        /* One less timer                                       */
#endif
            *p_err   = OS_ERR_NONE;
             success = OS_TRUE;
             break;

        case OS_TMR_STATE_UNUSED:                               /* Already deleted                                      */
            *p_err   = OS_ERR_TMR_INACTIVE;
             success = OS_FALSE;
             break;

        default:
            *p_err   = OS_ERR_TMR_INVALID_STATE;
             success = OS_FALSE;
             break;
    }

    OS_TmrUnlock();

    return (success);
}
#endif


/*
************************************************************************************************************************
*                                    GET HOW MUCH TIME IS LEFT BEFORE A TIMER EXPIRES
*
* Description: This function is called to get the number of timer increments before a timer times out.
*
* Arguments  : p_tmr    Is a pointer to the timer to obtain the remaining time from.
*
*              p_err    Is a pointer to an error code.  '*p_err' will contain one of the following:
*
*                           OS_ERR_NONE               The call succeeded
*                           OS_ERR_OBJ_TYPE           If 'p_tmr' is not pointing to a timer
*                           OS_ERR_OS_NOT_RUNNING     If uC/OS-III is not running yet
*                           OS_ERR_TMR_INACTIVE       If 'p_tmr' points to a timer that is not active
*                           OS_ERR_TMR_INVALID        If 'p_tmr' is a NULL pointer
*                           OS_ERR_TMR_INVALID_STATE  The timer is in an invalid state
*                           OS_ERR_TMR_ISR            If the call was made from an ISR
*
* Returns    : The time remaining for the timer to expire.  The time represents 'timer' increments (typically 1/10 sec).
*
* Note(s)    : none
************************************************************************************************************************
*/

OS_TICK  OSTmrRemainGet (OS_TMR  *p_tmr,
                         OS_ERR  *p_err)
{
    OS_TMR   *p_tmr1;
    OS_TICK   remain;


#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (0u);
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* See if trying to call from an ISR                    */
       *p_err = OS_ERR_TMR_ISR;
        return (0u);
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* Is the kernel running?                               */
       *p_err = OS_ERR_OS_NOT_RUNNING;
        return (0u);
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_tmr == (OS_TMR *)0) {
       *p_err = OS_ERR_TMR_INVALID;
        return (0u);
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_tmr->Type != OS_OBJ_TYPE_TMR) {                       /* Make sure timer was created                          */
       *p_err = OS_ERR_OBJ_TYPE;
        return (0u);
    }
#endif

    OS_TmrLock();

    switch (p_tmr->State) {
        case OS_TMR_STATE_RUNNING:
             p_tmr1 = OSTmrListPtr;
             remain = 0u;
             while (p_tmr1 != (OS_TMR *)0) {                    /* Add up all the deltas up until the current timer     */
                 remain += p_tmr1->Remain;
                 if (p_tmr1 == p_tmr) {
                     break;
                 }
                 p_tmr1 = p_tmr1->NextPtr;
             }
             remain /= OSTmrToTicksMult;
            *p_err   = OS_ERR_NONE;
             break;

        case OS_TMR_STATE_STOPPED:                              /* It's assumed that the timer has not started yet      */
             if (p_tmr->Opt == OS_OPT_TMR_PERIODIC) {
                 if (p_tmr->Dly == 0u) {
                     remain = p_tmr->Period / OSTmrToTicksMult;
                 } else {
                     remain = p_tmr->Dly / OSTmrToTicksMult;
                 }
             } else {
                 remain = p_tmr->Dly / OSTmrToTicksMult;
             }
            *p_err = OS_ERR_NONE;
             break;

        case OS_TMR_STATE_TIMEOUT:                              /* Within a callback, timers are in the TIMEOUT state   */
        case OS_TMR_STATE_COMPLETED:                            /* Only ONE-SHOT timers can be in the COMPLETED state   */
            *p_err  = OS_ERR_NONE;
             remain = 0u;
             break;

        case OS_TMR_STATE_UNUSED:
            *p_err  = OS_ERR_TMR_INACTIVE;
             remain = 0u;
             break;

        default:
            *p_err = OS_ERR_TMR_INVALID_STATE;
             remain = 0u;
             break;
    }

    OS_TmrUnlock();

    return (remain);
}


/*
************************************************************************************************************************
*                                                    SET A TIMER
*
* Description: This function is called by your application code to set a timer.
*
* Arguments  : p_tmr           Is a pointer to a timer control block
*
*              dly             Initial delay.
*                              If the timer is configured for ONE-SHOT mode, this is the timeout used
*                              If the timer is configured for PERIODIC mode, this is the first timeout to wait for
*                              before the timer starts entering periodic mode
*
*              period          The 'period' being repeated for the timer.
*                              If you specified 'OS_OPT_TMR_PERIODIC' as an option, when the timer expires, it will
*                              automatically restart with the same period.
*
*              p_callback      Is a pointer to a callback function that will be called when the timer expires.  The
*                              callback function must be declared as follows:
*
*                                  void  MyCallback (OS_TMR *p_tmr, void *p_arg);
*
*              p_callback_arg  Is an argument (a pointer) that is passed to the callback function when it is called.
*
*              p_err           Is a pointer to an error code.  '*p_err' will contain one of the following:
*
*                                 OS_ERR_NONE                    The timer was configured as expected
*                                 OS_ERR_OBJ_TYPE                If the object type is invalid
*                                 OS_ERR_OS_NOT_RUNNING          If uC/OS-III is not running yet
*                                 OS_ERR_TMR_INVALID             If 'p_tmr' is a NULL pointer or invalid option
*                                 OS_ERR_TMR_INVALID_CALLBACK    you specified an invalid callback for a periodic timer
*                                 OS_ERR_TMR_INVALID_DLY         You specified an invalid delay
*                                 OS_ERR_TMR_INVALID_PERIOD      You specified an invalid period
*                                 OS_ERR_TMR_ISR                 If the call was made from an ISR
*
* Returns    : none
*
* Note(s)    : 1) This function can be called on a running timer. The change to the delay and period will only
*                 take effect after the current period or delay has passed. Change to the callback will take
*                 effect immediately.
************************************************************************************************************************
*/

void  OSTmrSet (OS_TMR               *p_tmr,
                OS_TICK               dly,
                OS_TICK               period,
                OS_TMR_CALLBACK_PTR   p_callback,
                void                 *p_callback_arg,
                OS_ERR               *p_err)
{
#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return;
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* See if trying to call from an ISR                    */
       *p_err = OS_ERR_TMR_ISR;
        return;
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* Is the kernel running?                               */
       *p_err = OS_ERR_OS_NOT_RUNNING;
        return;
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_tmr == (OS_TMR *)0) {                                 /* Validate 'p_tmr'                                     */
       *p_err = OS_ERR_TMR_INVALID;
        return;
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_tmr->Type != OS_OBJ_TYPE_TMR) {                       /* Make sure timer was created                          */
       *p_err = OS_ERR_OBJ_TYPE;
        return;
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    switch (p_tmr->Opt) {
        case OS_OPT_TMR_PERIODIC:
             if (period == 0u) {
                *p_err = OS_ERR_TMR_INVALID_PERIOD;
                 return;
             }

             if (p_callback == (OS_TMR_CALLBACK_PTR)0) {        /* No point in a periodic timer without a callback      */
                *p_err = OS_ERR_TMR_INVALID_CALLBACK;
                 return;
             }
             break;

        case OS_OPT_TMR_ONE_SHOT:
             if (dly == 0u) {
                *p_err = OS_ERR_TMR_INVALID_DLY;
                 return;
             }
             break;

        default:
            *p_err = OS_ERR_TMR_INVALID;
             return;
    }
#endif

    OS_TmrLock();

    p_tmr->Dly            = dly    * OSTmrToTicksMult;             /* Convert Timer Delay  to ticks                     */
    p_tmr->Period         = period * OSTmrToTicksMult;             /* Convert Timer Period to ticks                     */
    p_tmr->CallbackPtr    = p_callback;
    p_tmr->CallbackPtrArg = p_callback_arg;

   *p_err                 = OS_ERR_NONE;

    OS_TmrUnlock();
}


/*
************************************************************************************************************************
*                                                   START A TIMER
*
* Description: This function is called by your application code to start a timer.
*
* Arguments  : p_tmr    Is a pointer to an OS_TMR
*
*              p_err    Is a pointer to an error code.  '*p_err' will contain one of the following:
*
*                           OS_ERR_NONE                The timer was started
*                           OS_ERR_OBJ_TYPE            If 'p_tmr' is not pointing to a timer
*                           OS_ERR_OS_NOT_RUNNING      If uC/OS-III is not running yet
*                           OS_ERR_TMR_INACTIVE        If the timer was not created
*                           OS_ERR_TMR_INVALID         If 'p_tmr' is a NULL pointer
*                           OS_ERR_TMR_INVALID_STATE   The timer is in an invalid state
*                           OS_ERR_TMR_ISR             If the call was made from an ISR
*
* Returns    : OS_TRUE   is the timer was started
*              OS_FALSE  if not or upon an error
*
* Note(s)    : 1) When starting/restarting a timer, regardless if it is in PERIODIC or ONE-SHOT mode, the timer is
*                 linked to the timer list with the OS_OPT_LINK_DLY option. This option sets the initial expiration
*                 time for the timer. For timers in PERIODIC mode, subsequent expiration times are handled by
*                 the OS_TmrTask().
************************************************************************************************************************
*/

CPU_BOOLEAN  OSTmrStart (OS_TMR  *p_tmr,
                         OS_ERR  *p_err)
{
    CPU_BOOLEAN  success;
    OS_TICK      time;
    CPU_SR_ALLOC();


#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* See if trying to call from an ISR                    */
       *p_err = OS_ERR_TMR_ISR;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* Is the kernel running?                               */
       *p_err = OS_ERR_OS_NOT_RUNNING;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_tmr == (OS_TMR *)0) {
       *p_err = OS_ERR_TMR_INVALID;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_tmr->Type != OS_OBJ_TYPE_TMR) {                       /* Make sure timer was created                          */
       *p_err = OS_ERR_OBJ_TYPE;
        return (OS_FALSE);
    }
#endif

    OS_TmrLock();

    CPU_CRITICAL_ENTER();
    if (OSTCBCurPtr == &OSTmrTaskTCB) {                         /* Callbacks operate on the Tmr Task's tick base.       */
        time = OSTmrTaskTickBase;
    } else {
#if (OS_CFG_DYN_TICK_EN > 0u)
        time = OSTickCtr + OS_DynTickGet();
#else
        time = OSTickCtr;
#endif
    }
    CPU_CRITICAL_EXIT();


    switch (p_tmr->State) {
        case OS_TMR_STATE_RUNNING:                              /* Restart the timer                                    */
        case OS_TMR_STATE_TIMEOUT:
             p_tmr->State = OS_TMR_STATE_RUNNING;
             OS_TmrUnlink(p_tmr, time);                         /* Remove from current position in List                 */
             if (p_tmr->Dly == 0u) {
                 p_tmr->Remain = p_tmr->Period;
             } else {
                 p_tmr->Remain = p_tmr->Dly;
             }
             OS_TmrLink(p_tmr, time);                           /* Add timer to List                                    */
            *p_err   = OS_ERR_NONE;
             success = OS_TRUE;
             break;

        case OS_TMR_STATE_STOPPED:                              /* Start the timer                                      */
        case OS_TMR_STATE_COMPLETED:
             p_tmr->State = OS_TMR_STATE_RUNNING;
             if (p_tmr->Dly == 0u) {
                 p_tmr->Remain = p_tmr->Period;
             } else {
                 p_tmr->Remain = p_tmr->Dly;
             }
             OS_TmrLink(p_tmr, time);                           /* Add timer to List                                    */
            *p_err   = OS_ERR_NONE;
             success = OS_TRUE;
             break;

        case OS_TMR_STATE_UNUSED:                               /* Timer not created                                    */
            *p_err   = OS_ERR_TMR_INACTIVE;
             success = OS_FALSE;
             break;

        default:
            *p_err   = OS_ERR_TMR_INVALID_STATE;
             success = OS_FALSE;
             break;
    }

    OS_TmrUnlock();

    return (success);
}


/*
************************************************************************************************************************
*                                           FIND OUT WHAT STATE A TIMER IS IN
*
* Description: This function is called to determine what state the timer is in:
*
*                  OS_TMR_STATE_UNUSED     the timer has not been created
*                  OS_TMR_STATE_STOPPED    the timer has been created but has not been started or has been stopped
*                  OS_TMR_STATE_COMPLETED  the timer is in ONE-SHOT mode and has completed it's timeout
*                  OS_TMR_SATE_RUNNING     the timer is currently running
*
* Arguments  : p_tmr    Is a pointer to the desired timer
*
*              p_err    Is a pointer to an error code.  '*p_err' will contain one of the following:
*
*                           OS_ERR_NONE               The return value reflects the state of the timer
*                           OS_ERR_OBJ_TYPE           If 'p_tmr' is not pointing to a timer
*                           OS_ERR_OS_NOT_RUNNING     If uC/OS-III is not running yet
*                           OS_ERR_TMR_INVALID        If 'p_tmr' is a NULL pointer
*                           OS_ERR_TMR_INVALID_STATE  If the timer is not in a valid state
*                           OS_ERR_TMR_ISR            If the call was made from an ISR
*
* Returns    : The current state of the timer (see description).
*
* Note(s)    : none
************************************************************************************************************************
*/

OS_STATE  OSTmrStateGet (OS_TMR  *p_tmr,
                         OS_ERR  *p_err)
{
    OS_STATE  state;



#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (OS_TMR_STATE_UNUSED);
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* See if trying to call from an ISR                    */
       *p_err = OS_ERR_TMR_ISR;
        return (OS_TMR_STATE_UNUSED);
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* Is the kernel running?                               */
       *p_err = OS_ERR_OS_NOT_RUNNING;
        return (OS_TMR_STATE_UNUSED);
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_tmr == (OS_TMR *)0) {
       *p_err = OS_ERR_TMR_INVALID;
        return (OS_TMR_STATE_UNUSED);
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_tmr->Type != OS_OBJ_TYPE_TMR) {                       /* Make sure timer was created                          */
       *p_err = OS_ERR_OBJ_TYPE;
        return (OS_TMR_STATE_UNUSED);
    }
#endif

    OS_TmrLock();

    state = p_tmr->State;
    switch (state) {
        case OS_TMR_STATE_UNUSED:
        case OS_TMR_STATE_STOPPED:
        case OS_TMR_STATE_COMPLETED:
        case OS_TMR_STATE_RUNNING:
        case OS_TMR_STATE_TIMEOUT:
            *p_err = OS_ERR_NONE;
             break;

        default:
            *p_err = OS_ERR_TMR_INVALID_STATE;
             break;
    }

    OS_TmrUnlock();

    return (state);
}


/*
************************************************************************************************************************
*                                                    STOP A TIMER
*
* Description: This function is called by your application code to stop a timer.
*
* Arguments  : p_tmr          Is a pointer to the timer to stop.
*
*              opt            Allows you to specify an option to this functions which can be:
*
*                               OS_OPT_TMR_NONE            Do nothing special but stop the timer
*                               OS_OPT_TMR_CALLBACK        Execute the callback function, pass it the callback argument
*                                                          specified when the timer was created.
*                               OS_OPT_TMR_CALLBACK_ARG    Execute the callback function, pass it the callback argument
*                                                          specified in THIS function call
*
*              callback_arg   Is a pointer to a 'new' callback argument that can be passed to the callback function
*                               instead of the timer's callback argument.  In other words, use 'callback_arg' passed in
*                               THIS function INSTEAD of p_tmr->OSTmrCallbackArg
*
*              p_err          Is a pointer to an error code.  '*p_err' will contain one of the following:
*
*                               OS_ERR_NONE                The timer has stopped
*                               OS_ERR_OBJ_TYPE            If 'p_tmr' is not pointing to a timer
*                               OS_ERR_OPT_INVALID         If you specified an invalid option for 'opt'
*                               OS_ERR_OS_NOT_RUNNING      If uC/OS-III is not running yet
*                               OS_ERR_TMR_INACTIVE        If the timer was not created
*                               OS_ERR_TMR_INVALID         If 'p_tmr' is a NULL pointer
*                               OS_ERR_TMR_INVALID_STATE   The timer is in an invalid state
*                               OS_ERR_TMR_ISR             If the function was called from an ISR
*                               OS_ERR_TMR_NO_CALLBACK     If the timer does not have a callback function defined
*                               OS_ERR_TMR_STOPPED         If the timer was already stopped
*
* Returns    : OS_TRUE   If we stopped the timer (if the timer is already stopped, we also return OS_TRUE)
*              OS_FALSE  If not
*
* Note(s)    : none
************************************************************************************************************************
*/

CPU_BOOLEAN  OSTmrStop (OS_TMR  *p_tmr,
                        OS_OPT   opt,
                        void    *p_callback_arg,
                        OS_ERR  *p_err)
{
    OS_TMR_CALLBACK_PTR  p_fnct;
    CPU_BOOLEAN          success;
    OS_TICK              time;
    CPU_SR_ALLOC();


#ifdef OS_SAFETY_CRITICAL
    if (p_err == (OS_ERR *)0) {
        OS_SAFETY_CRITICAL_EXCEPTION();
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u)
    if (OSIntNestingCtr > 0u) {                                 /* See if trying to call from an ISR                    */
       *p_err = OS_ERR_TMR_ISR;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_INVALID_OS_CALLS_CHK_EN > 0u)
    if (OSRunning != OS_STATE_OS_RUNNING) {                     /* Is the kernel running?                               */
       *p_err = OS_ERR_OS_NOT_RUNNING;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_ARG_CHK_EN > 0u)
    if (p_tmr == (OS_TMR *)0) {
       *p_err = OS_ERR_TMR_INVALID;
        return (OS_FALSE);
    }
#endif

#if (OS_CFG_OBJ_TYPE_CHK_EN > 0u)
    if (p_tmr->Type != OS_OBJ_TYPE_TMR) {                       /* Make sure timer was created                          */
       *p_err = OS_ERR_OBJ_TYPE;
        return (OS_FALSE);
    }
#endif

    OS_TmrLock();

    CPU_CRITICAL_ENTER();
    if (OSTCBCurPtr == &OSTmrTaskTCB) {                         /* Callbacks operate on the Tmr Task's tick base.       */
        time = OSTmrTaskTickBase;
    } else {
#if (OS_CFG_DYN_TICK_EN > 0u)
        time = OSTickCtr + OS_DynTickGet();
#else
        time = OSTickCtr;
#endif
    }
    CPU_CRITICAL_EXIT();

    switch (p_tmr->State) {
        case OS_TMR_STATE_RUNNING:
        case OS_TMR_STATE_TIMEOUT:
             p_tmr->State = OS_TMR_STATE_STOPPED;               /* Ensure that any callbacks see the stop state         */
             switch (opt) {
                 case OS_OPT_TMR_CALLBACK:
                      OS_TmrUnlink(p_tmr, time);                /* Remove from timer list                               */
                      p_fnct = p_tmr->CallbackPtr;              /* Execute callback function ...                        */
                      if (p_fnct != (OS_TMR_CALLBACK_PTR)0) {   /* ... if available                                     */
                        (*p_fnct)(p_tmr, p_tmr->CallbackPtrArg);/* Use callback arg when timer was created              */
                      } else {
                         *p_err = OS_ERR_TMR_NO_CALLBACK;
                      }
                      break;

                 case OS_OPT_TMR_CALLBACK_ARG:
                      OS_TmrUnlink(p_tmr, time);                /* Remove from timer list                               */
                      p_fnct = p_tmr->CallbackPtr;              /* Execute callback function if available ...           */
                      if (p_fnct != (OS_TMR_CALLBACK_PTR)0) {
                        (*p_fnct)(p_tmr, p_callback_arg);       /* .. using the 'callback_arg' provided in call         */
                      } else {
                         *p_err = OS_ERR_TMR_NO_CALLBACK;
                      }
                      break;

                 case OS_OPT_TMR_NONE:
                      OS_TmrUnlink(p_tmr, time);                /* Remove from timer list                               */
                      break;

                 default:
                      OS_TmrUnlock();
                     *p_err = OS_ERR_OPT_INVALID;
                      return (OS_FALSE);
             }
            *p_err        = OS_ERR_NONE;
             success      = OS_TRUE;
             break;

        case OS_TMR_STATE_COMPLETED:                            /* Timer has already completed the ONE-SHOT or          */
        case OS_TMR_STATE_STOPPED:                              /* ... timer has not started yet.                       */
             p_tmr->State = OS_TMR_STATE_STOPPED;
            *p_err        = OS_ERR_TMR_STOPPED;
             success      = OS_TRUE;
             break;

        case OS_TMR_STATE_UNUSED:                               /* Timer was not created                                */
            *p_err        = OS_ERR_TMR_INACTIVE;
             success      = OS_FALSE;
             break;

        default:
            *p_err        = OS_ERR_TMR_INVALID_STATE;
             success      = OS_FALSE;
             break;
    }

    OS_TmrUnlock();

    return (success);
}


/*
************************************************************************************************************************
*                                                 CLEAR TIMER FIELDS
*
* Description: This function is called to clear all timer fields.
*
* Argument(s): p_tmr    Is a pointer to the timer to clear
*              -----
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

void  OS_TmrClr (OS_TMR  *p_tmr)
{
    p_tmr->State          = OS_TMR_STATE_UNUSED;                /* Clear timer fields                                   */
#if (OS_OBJ_TYPE_REQ > 0u)
    p_tmr->Type           = OS_OBJ_TYPE_NONE;
#endif
#if (OS_CFG_DBG_EN > 0u)
    p_tmr->NamePtr        = (CPU_CHAR *)((void *)"?TMR");
#endif
    p_tmr->Dly            =                      0u;
    p_tmr->Remain         =                      0u;
    p_tmr->Period         =                      0u;
    p_tmr->Opt            =                      0u;
    p_tmr->CallbackPtr    = (OS_TMR_CALLBACK_PTR)0;
    p_tmr->CallbackPtrArg = (void              *)0;
    p_tmr->NextPtr        = (OS_TMR            *)0;
    p_tmr->PrevPtr        = (OS_TMR            *)0;
}


/*
************************************************************************************************************************
*                                         ADD/REMOVE TIMER TO/FROM DEBUG TABLE
*
* Description: These functions are called by uC/OS-III to add or remove a timer to/from a timer debug table.
*
* Arguments  : p_tmr     is a pointer to the timer to add/remove
*
* Returns    : none
*
* Note(s)    : These functions are INTERNAL to uC/OS-III and your application should not call it.
************************************************************************************************************************
*/


#if (OS_CFG_DBG_EN > 0u)
void  OS_TmrDbgListAdd (OS_TMR  *p_tmr)
{
    p_tmr->DbgPrevPtr               = (OS_TMR *)0;
    if (OSTmrDbgListPtr == (OS_TMR *)0) {
        p_tmr->DbgNextPtr           = (OS_TMR *)0;
    } else {
        p_tmr->DbgNextPtr           =  OSTmrDbgListPtr;
        OSTmrDbgListPtr->DbgPrevPtr =  p_tmr;
    }
    OSTmrDbgListPtr                 =  p_tmr;
}



void  OS_TmrDbgListRemove (OS_TMR  *p_tmr)
{
    OS_TMR  *p_tmr_next;
    OS_TMR  *p_tmr_prev;


    p_tmr_prev = p_tmr->DbgPrevPtr;
    p_tmr_next = p_tmr->DbgNextPtr;

    if (p_tmr_prev == (OS_TMR *)0) {
        OSTmrDbgListPtr = p_tmr_next;
        if (p_tmr_next != (OS_TMR *)0) {
            p_tmr_next->DbgPrevPtr = (OS_TMR *)0;
        }
        p_tmr->DbgNextPtr = (OS_TMR *)0;

    } else if (p_tmr_next == (OS_TMR *)0) {
        p_tmr_prev->DbgNextPtr = (OS_TMR *)0;
        p_tmr->DbgPrevPtr      = (OS_TMR *)0;

    } else {
        p_tmr_prev->DbgNextPtr =  p_tmr_next;
        p_tmr_next->DbgPrevPtr =  p_tmr_prev;
        p_tmr->DbgNextPtr      = (OS_TMR *)0;
        p_tmr->DbgPrevPtr      = (OS_TMR *)0;
    }
}
#endif


/*
************************************************************************************************************************
*                                             INITIALIZE THE TIMER MANAGER
*
* Description: This function is called by OSInit() to initialize the timer manager module.
*
* Argument(s): p_err    is a pointer to a variable that will contain an error code returned by this function.
*
*                           OS_ERR_NONE
*                           OS_ERR_TMR_STK_INVALID       if you didn't specify a stack for the timer task
*                           OS_ERR_TMR_STK_SIZE_INVALID  if you didn't allocate enough space for the timer stack
*                           OS_ERR_PRIO_INVALID          if you specified the same priority as the idle task
*                           OS_ERR_xxx                   any error code returned by OSTaskCreate()
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

void  OS_TmrInit (OS_ERR  *p_err)
{
#if (OS_CFG_DBG_EN > 0u)
    OSTmrQty             =           0u;                        /* Keep track of the number of timers created           */
    OSTmrDbgListPtr      = (OS_TMR *)0;
#endif

    OSTmrListPtr         = (OS_TMR *)0;                         /* Create an empty timer list                           */
#if (OS_CFG_DBG_EN > 0u)
    OSTmrListEntries     =           0u;
#endif
                                                                /* Calculate Timer to Ticks multiplier                  */
    OSTmrToTicksMult = OSCfg_TickRate_Hz / OSCfg_TmrTaskRate_Hz;

#if (OS_CFG_TS_EN > 0u)
    OSTmrTaskTime        =           0u;
    OSTmrTaskTimeMax     =           0u;
#endif

    OSMutexCreate(&OSTmrMutex,                                  /* Use a mutex to protect the timers                    */
#if  (OS_CFG_DBG_EN == 0u)
                  (CPU_CHAR *)0,
#else
                  (CPU_CHAR *)"OS Tmr Mutex",
#endif
                  p_err);
    if (*p_err != OS_ERR_NONE) {
        return;
    }

    OS_TmrCondCreate();
                                                                /* -------------- CREATE THE TIMER TASK --------------- */
    if (OSCfg_TmrTaskStkBasePtr == (CPU_STK *)0) {
       *p_err = OS_ERR_TMR_STK_INVALID;
        return;
    }

    if (OSCfg_TmrTaskStkSize < OSCfg_StkSizeMin) {
       *p_err = OS_ERR_TMR_STK_SIZE_INVALID;
        return;
    }

    if (OSCfg_TmrTaskPrio >= (OS_CFG_PRIO_MAX - 1u)) {
       *p_err = OS_ERR_TMR_PRIO_INVALID;
        return;
    }

    OSTaskCreate(&OSTmrTaskTCB,
#if  (OS_CFG_DBG_EN == 0u)
                 (CPU_CHAR *)0,
#else
                 (CPU_CHAR *)"Timer",
#endif
                  OS_TmrTask,
                 (void     *)0,
                  OSCfg_TmrTaskPrio,
                  OSCfg_TmrTaskStkBasePtr,
                  OSCfg_TmrTaskStkLimit,
                  OSCfg_TmrTaskStkSize,
                  0u,
                  0u,
                 (void     *)0,
                 (OS_OPT_TASK_STK_CHK | (OS_OPT)(OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS)),
                  p_err);
}


/*
************************************************************************************************************************
*                                         ADD A TIMER TO THE TIMER LIST
*
* Description: This function is called to add a timer to the timer list.
*
* Arguments  : p_tmr          Is a pointer to the timer to add.
*
*              time           Is the system time when this timer was linked.
*              -----
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

void OS_TmrLink (OS_TMR   *p_tmr,
                 OS_TICK   time)
{
    OS_TMR   *p_tmr1;
    OS_TMR   *p_tmr2;
    OS_TICK   remain;
    OS_TICK   delta;


    if (OSTmrListPtr == (OS_TMR *)0) {                          /* Is the list empty?                                   */
        p_tmr->NextPtr    = (OS_TMR *)0;                        /* Yes, this is the first entry                         */
        p_tmr->PrevPtr    = (OS_TMR *)0;
        OSTmrListPtr      = p_tmr;
#if (OS_CFG_DBG_EN > 0u)
        OSTmrListEntries  = 1u;
#endif
        OSTmrTaskTickBase = time;
        OS_TmrCondSignal();

        return;
    }

#if (OS_CFG_DBG_EN > 0u)
    OSTmrListEntries++;
#endif

    delta = (time + p_tmr->Remain) - OSTmrTaskTickBase;

    p_tmr2 = OSTmrListPtr;                                      /* No,  Insert somewhere in the list in delta order     */
    remain = p_tmr2->Remain;

    if ((delta           <     remain) &&
        (p_tmr2->PrevPtr == (OS_TMR *)0)) {                     /* Are we the new head of the list?                     */
        p_tmr2->Remain    =  remain - delta;
        p_tmr->PrevPtr    = (OS_TMR *)0;
        p_tmr->NextPtr    =  p_tmr2;
        p_tmr2->PrevPtr   =  p_tmr;
        OSTmrListPtr      =  p_tmr;

        OSTmrTaskTickBase = time;
        OS_TmrCondSignal();

        return;
    }

                                                                /* No                                                   */
    delta  -= remain;                                           /* Make delta relative to the current head.             */
    p_tmr1  = p_tmr2;
    p_tmr2  = p_tmr1->NextPtr;


    while ((p_tmr2 !=        (OS_TMR *)0) &&                    /* Find the appropriate position in the delta list.     */
           (delta  >= p_tmr2->Remain)) {
        delta  -= p_tmr2->Remain;                               /* Update our delta as we traverse the list.            */
        p_tmr1  = p_tmr2;
        p_tmr2  = p_tmr2->NextPtr;
    }


    if (p_tmr2 != (OS_TMR *)0) {                                /* Our entry is not the last element in the list.       */
        p_tmr1           = p_tmr2->PrevPtr;
        p_tmr->Remain    = delta;                               /* Store remaining time                                 */
        p_tmr->PrevPtr   = p_tmr1;
        p_tmr->NextPtr   = p_tmr2;
        p_tmr2->Remain  -= delta;                               /* Reduce time of next entry in the list                */
        p_tmr2->PrevPtr  = p_tmr;
        p_tmr1->NextPtr  = p_tmr;

    } else {                                                    /* Our entry belongs at the end of the list.            */
        p_tmr->Remain    = delta;
        p_tmr->PrevPtr   = p_tmr1;
        p_tmr->NextPtr   = (OS_TMR *)0;
        p_tmr1->NextPtr  = p_tmr;
    }
}


/*
************************************************************************************************************************
*                                         REMOVE A TIMER FROM THE TIMER LIST
*
* Description: This function is called to remove the timer from the timer list.
*
* Arguments  : p_tmr          Is a pointer to the timer to remove.
*
*              time           Is the system time when this timer was unlinked.
*              -----
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

void  OS_TmrUnlink (OS_TMR   *p_tmr,
                    OS_TICK   time)
{
    OS_TMR   *p_tmr1;
    OS_TMR   *p_tmr2;
    OS_TICK   elapsed;


    p_tmr1                          = p_tmr->PrevPtr;
    p_tmr2                          = p_tmr->NextPtr;
    if (p_tmr1 == (OS_TMR *)0) {
        if (p_tmr2 == (OS_TMR *)0) {                            /* Remove the ONLY entry in the list?                   */
            OSTmrListPtr            = (OS_TMR *)0;
#if (OS_CFG_DBG_EN > 0u)
            OSTmrListEntries        = 0u;
#endif
            p_tmr->Remain           = 0u;

            OSTmrTaskTickBase       = time;
            OS_TmrCondSignal();
        } else {
#if (OS_CFG_DBG_EN > 0u)
            OSTmrListEntries--;
#endif
            elapsed                 = time - OSTmrTaskTickBase;
            p_tmr2->PrevPtr         = (OS_TMR *)0;
            p_tmr2->Remain         += p_tmr->Remain;            /* Add back the ticks to the delta                      */
            OSTmrListPtr            = p_tmr2;

            while ((elapsed >           0u) &&
                   (p_tmr2  != (OS_TMR *)0)) {

                if (elapsed > p_tmr2->Remain) {
                    elapsed        -= p_tmr2->Remain;
                    p_tmr2->Remain  = 0u;
                } else {
                    p_tmr2->Remain -= elapsed;
                    elapsed         = 0u;
                }


                p_tmr1              = p_tmr2;
                p_tmr2              = p_tmr1->NextPtr;
            }

            if ((OSTmrListPtr->Remain != p_tmr->Remain) ||      /* Reload if new head has a different delay         ... */
                (OSTmrListPtr->Remain ==            0u)) {      /* ... or has already timed out.                        */
                OSTmrTaskTickBase   = time;
                OS_TmrCondSignal();
            }

            p_tmr->NextPtr          = (OS_TMR *)0;
            p_tmr->Remain           =           0u;
        }
    } else {
#if (OS_CFG_DBG_EN > 0u)
        OSTmrListEntries--;
#endif
        p_tmr1->NextPtr             = p_tmr2;
        if (p_tmr2 != (OS_TMR *)0) {
            p_tmr2->PrevPtr         = p_tmr1;
            p_tmr2->Remain         += p_tmr->Remain;            /* Add back the ticks to the delta list                 */
        }
        p_tmr->PrevPtr              = (OS_TMR *)0;
        p_tmr->NextPtr              = (OS_TMR *)0;
        p_tmr->Remain               =           0u;
    }
}


/*
************************************************************************************************************************
*                                                 TIMER MANAGEMENT TASK
*
* Description: This task is created by OS_TmrInit().
*
* Arguments  : none
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
*
*              2) The timer list is processed in two stages.
*                   a) Subtract the expired time from the delta list, which leaves expired timers at the head.
*                   b) Process each of the expired timers by invoking its callback (if any) and removing it.
*                 This method allows timer callbacks to Link/Unlink timers while maintaining the correct delta values.
*
*              3) Timer callbacks are allowed to make calls to the Timer APIs.
************************************************************************************************************************
*/

void  OS_TmrTask (void  *p_arg)
{
    OS_TMR_CALLBACK_PTR   p_fnct;
    OS_TMR               *p_tmr;
    OS_TICK               timeout;
    OS_TICK               elapsed;
    OS_TICK               time;
#if (OS_CFG_TS_EN > 0u)
    CPU_TS                ts_start;
#endif
    CPU_SR_ALLOC();


    (void)p_arg;                                                /* Not using 'p_arg', prevent compiler warning          */

    OS_TmrLock();

    for (;;) {
        if (OSTmrListPtr == (OS_TMR *)0) {
            timeout                = 0u;
        } else {
            timeout                = OSTmrListPtr->Remain;
        }

        OS_TmrCondWait(timeout);                                /* Suspend the timer task until it needs to process ... */
                                                                /* ... the timer list again. Also release the mutex ... */
                                                                /* ... so that application tasks can add/remove timers. */

        if (OSTmrListPtr == (OS_TMR *)0) {                      /* Suppresses static analyzer warnings.                 */
            continue;
        }

#if (OS_CFG_TS_EN > 0u)
        ts_start = OS_TS_GET();
#endif

        CPU_CRITICAL_ENTER();
#if (OS_CFG_DYN_TICK_EN > 0u)
        time                       = OSTickCtr + OS_DynTickGet();
#else
        time                       = OSTickCtr;
#endif
        CPU_CRITICAL_EXIT();
        elapsed                    = time - OSTmrTaskTickBase;
        OSTmrTaskTickBase          = time;

                                                                /* Update the delta values.                             */
        p_tmr = OSTmrListPtr;
        while ((elapsed !=          0u) &&
               (p_tmr   != (OS_TMR *)0)) {

            if (elapsed > p_tmr->Remain) {
                elapsed           -= p_tmr->Remain;
                p_tmr->Remain      = 0u;
            } else {
                p_tmr->Remain     -= elapsed;
                elapsed            = 0u;
            }

            p_tmr                  = p_tmr->NextPtr;
        }

                                                                /* Process timers that have expired.                    */
        p_tmr                      = OSTmrListPtr;

        while ((p_tmr         != (OS_TMR *)0) &&
               (p_tmr->Remain ==          0u)) {
            p_tmr->State           = OS_TMR_STATE_TIMEOUT;
                                                                /* Execute callback function if available               */
            p_fnct                 = p_tmr->CallbackPtr;
            if (p_fnct != (OS_TMR_CALLBACK_PTR)0u) {
                (*p_fnct)(p_tmr, p_tmr->CallbackPtrArg);
            }

            if (p_tmr->State == OS_TMR_STATE_TIMEOUT) {
                OS_TmrUnlink(p_tmr, OSTmrTaskTickBase);

                if (p_tmr->Opt == OS_OPT_TMR_PERIODIC) {
                    p_tmr->State   = OS_TMR_STATE_RUNNING;
                    p_tmr->Remain  = p_tmr->Period;
                    OS_TmrLink(p_tmr, OSTmrTaskTickBase);
                } else {
                    p_tmr->PrevPtr = (OS_TMR *)0;
                    p_tmr->NextPtr = (OS_TMR *)0;
                    p_tmr->Remain  = 0u;
                    p_tmr->State   = OS_TMR_STATE_COMPLETED;
                }
            }

            p_tmr                  = OSTmrListPtr;
        }

#if (OS_CFG_TS_EN > 0u)
        OSTmrTaskTime = OS_TS_GET() - ts_start;                 /* Measure execution time of timer task                 */
        if (OSTmrTaskTimeMax < OSTmrTaskTime) {
            OSTmrTaskTimeMax       = OSTmrTaskTime;
        }
#endif
    }
}


/*
************************************************************************************************************************
*                                          TIMER MANAGEMENT LOCKING MECHANISM
*
* Description: These functions are used to handle timer critical sections.  The method uses a mutex
*              to protect access to the global timer list.
*
* Arguments  : none
*
* Returns    : none
*
* Note(s)    : 1) These functions are INTERNAL to uC/OS-III and your application MUST NOT call them.
************************************************************************************************************************
*/

static  void  OS_TmrLock (void)
{
    OS_ERR  err;


    OSMutexPend(&OSTmrMutex, 0u, OS_OPT_PEND_BLOCKING, (CPU_TS *)0, &err);
}


static  void  OS_TmrUnlock (void)
{
    OS_ERR  err;


    OSMutexPost(&OSTmrMutex, OS_OPT_POST_NONE, &err);
}


/*
************************************************************************************************************************
*                                         CREATE TIMER TASK CONDITION VARIABLE
*
* Description: Initializes a condition variable for INTERNAL use ONLY.
*
* Arguments  : none
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

static  void  OS_TmrCondCreate (void)
{
    CPU_SR_ALLOC();


    CPU_CRITICAL_ENTER();
#if (OS_OBJ_TYPE_REQ > 0u)
    OSTmrCond.Type  = OS_OBJ_TYPE_COND;                         /* Mark the data structure as a condition variable.     */
#endif
    OSTmrCond.Mutex = &OSTmrMutex;                              /* Bind the timer mutex to the condition variable.      */
    OS_PendListInit(&OSTmrCond.PendList);                       /* Initialize the waiting list                          */
    CPU_CRITICAL_EXIT();
}


/*
************************************************************************************************************************
*                                         WAIT ON TIMER TASK CONDITION VARIABLE
*
* Description: Allows the timer task to release the global mutex and pend atomically. This ensures that
*              timers are only added/removed after the timer task has processed the current list and pended
*              for the next timeout. The timer task will always acquire the mutex before returning from this function.
*
* Arguments  : timeout                   The number of ticks before the timer task will wake up.
*                                        A value of zero signifies an indefinite pend.
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

static  void  OS_TmrCondWait (OS_TICK  timeout)
{
    OS_TCB        *p_tcb;
    OS_PEND_LIST  *p_pend_list;
    CPU_TS         ts;
    CPU_SR_ALLOC();


    CPU_CRITICAL_ENTER();
#if (OS_CFG_TS_EN > 0u)
    ts             = OS_TS_GET();                               /* Get timestamp                                        */
    OSTmrMutex.TS  = ts;
#else
    ts             = 0u;
#endif
                                                                /* Release mutex to other tasks.                        */
    OS_MutexGrpRemove(&OSTmrTaskTCB, &OSTmrMutex);
    p_pend_list                    = &OSTmrMutex.PendList;

    if (OSTmrTaskTCB.Prio != OSTmrTaskTCB.BasePrio) {           /* Restore our original prio.                           */
        OS_TRACE_MUTEX_TASK_PRIO_DISINHERIT(&OSTmrTaskTCB, OSTmrTaskTCB.Prio);
        OSTmrTaskTCB.Prio          = OSTmrTaskTCB.BasePrio;
        OSPrioCur                  = OSTmrTaskTCB.BasePrio;
    }

    if (p_pend_list->HeadPtr == (OS_TCB *)0) {                  /* Any task waiting on mutex?                           */
        OSTmrMutex.OwnerTCBPtr     = (OS_TCB *)0;               /* No                                                   */
        OSTmrMutex.OwnerNestingCtr =           0u;
    } else {
        p_tcb                      = p_pend_list->HeadPtr;      /* Yes, give mutex to new owner                         */
        OSTmrMutex.OwnerTCBPtr     = p_tcb;
        OSTmrMutex.OwnerNestingCtr =           1u;
        OS_MutexGrpAdd(p_tcb, &OSTmrMutex);
                                                                /* Post to mutex                                        */
        OS_Post((OS_PEND_OBJ *)((void *)&OSTmrMutex),
                                         p_tcb,
                                (void *) 0,
                                         0u,
                                         ts);
    }

    OS_Pend((OS_PEND_OBJ *)((void *)&OSTmrCond),                /* Pend on the condition variable.                      */
                                   &OSTmrTaskTCB,
                                    OS_TASK_PEND_ON_COND,
                                    timeout);
    CPU_CRITICAL_EXIT();

    OSSched();

    CPU_CRITICAL_ENTER();                                       /* Either we timed out, or were signaled.               */

    if (OSTmrMutex.OwnerTCBPtr == (OS_TCB *)0) {                /* Can we grab the mutex?                               */
        OS_MutexGrpAdd(&OSTmrTaskTCB, &OSTmrMutex);             /* Yes, no-one else pending.                            */
        OSTmrMutex.OwnerTCBPtr     = &OSTmrTaskTCB;
        OSTmrMutex.OwnerNestingCtr = 1u;
        CPU_CRITICAL_EXIT();
    } else {
        p_tcb = OSTmrMutex.OwnerTCBPtr;                         /* No, we need to wait for it.                          */
        if (p_tcb->Prio > OSTmrTaskTCB.Prio) {                  /* See if mutex owner has a lower priority than TmrTask.*/
            OS_TaskChangePrio(p_tcb, OSTmrTaskTCB.Prio);
        }

        OS_Pend((OS_PEND_OBJ *)((void *)&OSTmrMutex),           /* Block TmrTask until it gets the Mutex.               */
                                        &OSTmrTaskTCB,
                                         OS_TASK_PEND_ON_MUTEX,
                                         0u);
        CPU_CRITICAL_EXIT();

        OSSched();
    }
}


/*
************************************************************************************************************************
*                                       SIGNAL THE TIMER TASK CONDITION VARIABLE
*
* Description: Used to signal the timer task when a timer is added/removed which requires the task to reload
*              its timeout. We ensure that this function is always called with the timer mutex locked.
*
* Arguments  : none.
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

static  void  OS_TmrCondSignal (void)
{
    OS_PEND_LIST  *p_pend_list;
    CPU_TS         ts;
    CPU_SR_ALLOC();


    CPU_CRITICAL_ENTER();
#if (OS_CFG_TS_EN > 0u)
    ts             = OS_TS_GET();                               /* Get timestamp                                        */
    OSTmrMutex.TS  = ts;
#else
    ts             = 0u;
#endif

    p_pend_list    = &OSTmrCond.PendList;

    if (p_pend_list->HeadPtr == (OS_TCB *)0) {                  /* Timer task waiting on cond?                          */
        CPU_CRITICAL_EXIT();
        return;                                                 /* No, nothing to signal.                               */
    } else {
                                                                /* Yes, signal the timer task.                          */
        OS_Post((OS_PEND_OBJ *)((void *)&OSTmrCond),
                                        &OSTmrTaskTCB,
                                (void *) 0,
                                         0u,
                                         ts);
    }

    CPU_CRITICAL_EXIT();
}
#endif
