/*
*********************************************************************************************************
*                                              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.
*
*********************************************************************************************************
*/

/*
*********************************************************************************************************
*                                            TICK MANAGEMENT
*
* File    : os_tick.c
* Version : V3.08.01
*********************************************************************************************************
*/

#define  MICRIUM_SOURCE
#include "os.h"

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

#if (OS_CFG_TICK_EN > 0u)
/*
************************************************************************************************************************
*                                                 FUNCTION PROTOTYPES
************************************************************************************************************************
*/

static  void  OS_TickListUpdate (OS_TICK  ticks);


/*
************************************************************************************************************************
*                                                      TICK INIT
*
* Description: This function initializes the variables related to the tick handler.
*              The function is internal to uC/OS-III.
*
* Arguments  : p_err          is a pointer to a variable that will contain an error code returned by this function.
*              -----
*                                 OS_ERR_NONE           the tick variables were initialized successfully
*
* Returns    : none
*
* Note(s)    : This function is INTERNAL to uC/OS-III and your application should not call it.
************************************************************************************************************************
*/

void  OS_TickInit (OS_ERR  *p_err)
{
    *p_err                = OS_ERR_NONE;

    OSTickCtr             = 0u;                               /* Clear the tick counter                               */

#if (OS_CFG_DYN_TICK_EN > 0u)
    OSTickCtrStep         = 0u;
#endif

    OSTickList.TCB_Ptr    = (OS_TCB *)0;

#if (OS_CFG_DBG_EN > 0u)
    OSTickList.NbrEntries = 0u;
    OSTickList.NbrUpdated = 0u;
#endif
}

/*
************************************************************************************************************************
*                                                      TICK UPDATE
*
* Description: This function updates the list of task either delayed pending with timeout.
*              The function is internal to uC/OS-III.
*
* Arguments  : ticks          the number of ticks which have elapsed
*              -----
*
* Returns    : none
*
* Note(s)    : This function is INTERNAL to uC/OS-III and your application should not call it.
************************************************************************************************************************
*/

void  OS_TickUpdate (OS_TICK  ticks)
{
#if (OS_CFG_TS_EN > 0u)
    CPU_TS  ts_start;
#endif
    CPU_SR_ALLOC();


    CPU_CRITICAL_ENTER();

    OSTickCtr += ticks;                                         /* Keep track of the number of ticks                    */

    OS_TRACE_TICK_INCREMENT(OSTickCtr);

#if (OS_CFG_TS_EN > 0u)
    ts_start   = OS_TS_GET();
    OS_TickListUpdate(ticks);
    OSTickTime = OS_TS_GET() - ts_start;
    if (OSTickTimeMax < OSTickTime) {
        OSTickTimeMax = OSTickTime;
    }
#else
    OS_TickListUpdate(ticks);
#endif

#if (OS_CFG_DYN_TICK_EN > 0u)
    if (OSTickList.TCB_Ptr != (OS_TCB *)0) {
        OSTickCtrStep = OSTickList.TCB_Ptr->TickRemain;
    } else {
        OSTickCtrStep = 0u;
    }

    OS_DynTickSet(OSTickCtrStep);
#endif
    CPU_CRITICAL_EXIT();
}

/*
************************************************************************************************************************
*                                                      INSERT
*
* Description: This task is internal to uC/OS-III and allows the insertion of a task in a tick list.
*
* Arguments  : p_tcb       is a pointer to the TCB to insert in the list
*
*              elapsed     is the number of elapsed ticks since the last tick interrupt
*
*              tick_base   is value of OSTickCtr from which time is offset
*
*              time        is the amount of time remaining (in ticks) for the task to become ready
*
* Returns    : OS_TRUE     if time is valid for the given tick base
*
*              OS_FALSE    if time is invalid (i.e. zero delay)
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application should not call it.
*
*              2) This function supports both Periodic Tick Mode (PTM) and Dynamic Tick Mode (DTM).
*
*              3) PTM should always call this function with elapsed == 0u.
************************************************************************************************************************
*/

CPU_BOOLEAN  OS_TickListInsert (OS_TCB   *p_tcb,
                                OS_TICK   elapsed,
                                OS_TICK   tick_base,
                                OS_TICK   time)
{
    OS_TCB        *p_tcb1;
    OS_TCB        *p_tcb2;
    OS_TICK_LIST  *p_list;
    OS_TICK        delta;
    OS_TICK        remain;


    delta = (time + tick_base) - (OSTickCtr + elapsed);         /* How many ticks until our delay expires?              */

    if (delta == 0u) {
        p_tcb->TickRemain = 0u;
        return (OS_FALSE);
    }

    OS_TRACE_TASK_DLY(delta);

    p_list = &OSTickList;
    if (p_list->TCB_Ptr == (OS_TCB *)0) {                       /* Is the list empty?                                   */
        p_tcb->TickRemain   = delta;                            /* Yes, Store time in TCB                               */
        p_tcb->TickNextPtr  = (OS_TCB *)0;
        p_tcb->TickPrevPtr  = (OS_TCB *)0;
        p_list->TCB_Ptr     = p_tcb;                            /* Point to TCB of task to place in the list            */
#if (OS_CFG_DYN_TICK_EN > 0u)
        if (elapsed != 0u) {
            OSTickCtr      += elapsed;                          /* Update OSTickCtr before we set a new tick step.      */
            OS_TRACE_TICK_INCREMENT(OSTickCtr);
        }

        OSTickCtrStep       = delta;
        OS_DynTickSet(OSTickCtrStep);
#endif
#if (OS_CFG_DBG_EN > 0u)
        p_list->NbrEntries  = 1u;                               /* List contains 1 entry                                */
#endif
        return (OS_TRUE);
    }


#if (OS_CFG_DBG_EN > 0u)
    p_list->NbrEntries++;                                       /* Update debug counter to reflect the new entry.       */
#endif

    p_tcb2 = p_list->TCB_Ptr;
    remain = p_tcb2->TickRemain - elapsed;                      /* How many ticks until the head's delay expires?       */

    if ((delta               <   remain) &&                     /* If our entry is the new head of the tick list    ... */
        (p_tcb2->TickPrevPtr == (OS_TCB *)0)) {
        p_tcb->TickRemain    =  delta;                          /* ... the delta is equivalent to the full delay    ... */
        p_tcb2->TickRemain   =  remain - delta;                 /* ... the previous head's delta is now relative to it. */

        p_tcb->TickPrevPtr   = (OS_TCB *)0;
        p_tcb->TickNextPtr   =  p_tcb2;
        p_tcb2->TickPrevPtr  =  p_tcb;
        p_list->TCB_Ptr      =  p_tcb;
#if (OS_CFG_DYN_TICK_EN > 0u)
        if (elapsed != 0u) {
            OSTickCtr       +=  elapsed;                        /* Update OSTickCtr before we set a new tick step.      */
            OS_TRACE_TICK_INCREMENT(OSTickCtr);
        }
                                                                /* In DTM, a new list head must update the tick     ... */
        OSTickCtrStep        =  delta;                          /* ... timer to interrupt at the new delay value.       */
        OS_DynTickSet(OSTickCtrStep);
#endif

        return (OS_TRUE);
    }

                                                                /* Our entry comes after the current list head.         */
    delta  -= remain;                                           /* Make delta relative to the head.                     */
    p_tcb1  = p_tcb2;
    p_tcb2  = p_tcb1->TickNextPtr;

    while ((p_tcb2 !=        (OS_TCB *)0) &&                    /* Find the appropriate position in the delta list.     */
           (delta  >= p_tcb2->TickRemain)) {
        delta  -= p_tcb2->TickRemain;
        p_tcb1  = p_tcb2;
        p_tcb2  = p_tcb2->TickNextPtr;
    }

    if (p_tcb2 != (OS_TCB *)0) {                                /* Our entry is not the last element in the list.       */
        p_tcb1               = p_tcb2->TickPrevPtr;
        p_tcb->TickRemain    = delta;                           /* Store remaining time                                 */
        p_tcb->TickPrevPtr   = p_tcb1;
        p_tcb->TickNextPtr   = p_tcb2;
        p_tcb2->TickRemain  -= delta;                           /* Reduce time of next entry in the list                */
        p_tcb2->TickPrevPtr  = p_tcb;
        p_tcb1->TickNextPtr  = p_tcb;

    } else {                                                    /* Our entry belongs at the end of the list.            */
        p_tcb->TickRemain    = delta;
        p_tcb->TickPrevPtr   = p_tcb1;
        p_tcb->TickNextPtr   = (OS_TCB *)0;
        p_tcb1->TickNextPtr  = p_tcb;
    }

    return (OS_TRUE);
}

/*
************************************************************************************************************************
*                                            ADD DELAYED TASK TO TICK LIST
*
* Description: This function is called to place a task in a list of task waiting for time to expire
*
* Arguments  : p_tcb          is a pointer to the OS_TCB of the task to add to the tick list
*              -----
*
*              time           represents either the 'match' value of OSTickCtr or a relative time from the current
*                             system time as specified by the 'opt' argument..
*
*                             relative when 'opt' is set to OS_OPT_TIME_DLY
*                             relative when 'opt' is set to OS_OPT_TIME_TIMEOUT
*                             match    when 'opt' is set to OS_OPT_TIME_MATCH
*                             periodic when 'opt' is set to OS_OPT_TIME_PERIODIC
*
*              opt            is an option specifying how to calculate time.  The valid values are:
*              ---
*                                 OS_OPT_TIME_DLY
*                                 OS_OPT_TIME_TIMEOUT
*                                 OS_OPT_TIME_PERIODIC
*                                 OS_OPT_TIME_MATCH
*
*              p_err          is a pointer to a variable that will contain an error code returned by this function.
*              -----
*                                 OS_ERR_NONE           the call was successful and the time delay was scheduled.
*                                 OS_ERR_TIME_ZERO_DLY  if the effective delay is zero
*
* Returns    : None
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
*
*              2) This function is assumed to be called with interrupts disabled.
************************************************************************************************************************
*/

void  OS_TickListInsertDly (OS_TCB   *p_tcb,
                            OS_TICK   time,
                            OS_OPT    opt,
                            OS_ERR   *p_err)
{
    OS_TICK      elapsed;
    OS_TICK      tick_base;
    OS_TICK      base_offset;
    CPU_BOOLEAN  valid_dly;


#if (OS_CFG_DYN_TICK_EN > 0u)
    elapsed  = OS_DynTickGet();
#else
    elapsed  = 0u;
#endif

    if (opt == OS_OPT_TIME_MATCH) {                             /* MATCH to absolute tick ctr value mode                */
        tick_base = 0u;                                         /* tick_base + time == time                             */

    } else if (opt == OS_OPT_TIME_PERIODIC) {                   /* PERIODIC mode.                                       */
        if (time == 0u) {
           *p_err = OS_ERR_TIME_ZERO_DLY;                       /* Infinite frequency is invalid.                       */
            return;
        }

        tick_base = p_tcb->TickCtrPrev;

#if (OS_CFG_DYN_TICK_EN > 0u)                                   /* How far is our tick-base from the system time?       */
        base_offset = OSTickCtr + elapsed - tick_base;
#else
        base_offset = OSTickCtr - tick_base;
#endif

        if (base_offset >= time) {                              /* If our task missed the last period, move         ... */
            tick_base += time * (base_offset / time);           /* ... tick_base up to the next one.                    */
            if ((base_offset % time) != 0u) {
                tick_base += time;                              /* Account for rounding errors with integer division    */
            }

            p_tcb->TickCtrPrev = tick_base;                     /* Adjust the periodic tick base                        */
        }

        p_tcb->TickCtrPrev += time;                             /* Update for the next time we perform a periodic dly.  */

    } else {                                                    /* RELATIVE time delay mode                             */
#if (OS_CFG_DYN_TICK_EN > 0u)                                   /* Our base is always the current system time.          */
        tick_base = OSTickCtr + elapsed;
#else
        tick_base = OSTickCtr;
#endif
    }

    valid_dly = OS_TickListInsert(p_tcb, elapsed, tick_base, time);

    if (valid_dly == OS_TRUE) {
        p_tcb->TaskState = OS_TASK_STATE_DLY;
       *p_err            = OS_ERR_NONE;
    } else {
       *p_err = OS_ERR_TIME_ZERO_DLY;
    }
}

/*
************************************************************************************************************************
*                                         REMOVE A TASK FROM THE TICK LIST
*
* Description: This function is called to remove a task from the tick list
*
* Arguments  : p_tcb          Is a pointer to the OS_TCB to remove.
*              -----
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
*
*              2) This function is assumed to be called with interrupts disabled.
************************************************************************************************************************
*/

void  OS_TickListRemove (OS_TCB  *p_tcb)
{
    OS_TCB        *p_tcb1;
    OS_TCB        *p_tcb2;
    OS_TICK_LIST  *p_list;
#if (OS_CFG_DYN_TICK_EN > 0u)
    OS_TICK        elapsed;
#endif

#if (OS_CFG_DYN_TICK_EN > 0u)
    elapsed = OS_DynTickGet();
#endif

    p_tcb1 = p_tcb->TickPrevPtr;
    p_tcb2 = p_tcb->TickNextPtr;
    p_list = &OSTickList;
    if (p_tcb1 == (OS_TCB *)0) {
        if (p_tcb2 == (OS_TCB *)0) {                            /* Remove the ONLY entry in the list?                   */
            p_list->TCB_Ptr      = (OS_TCB *)0;
#if (OS_CFG_DBG_EN > 0u)
            p_list->NbrEntries   =           0u;
#endif
            p_tcb->TickRemain    =           0u;
#if (OS_CFG_DYN_TICK_EN > 0u)
            if (elapsed != 0u) {
                OSTickCtr       += elapsed;                     /* Keep track of time.                                  */
                OS_TRACE_TICK_INCREMENT(OSTickCtr);
            }
            OSTickCtrStep        =           0u;
            OS_DynTickSet(OSTickCtrStep);
#endif
        } else {
            p_tcb2->TickPrevPtr  = (OS_TCB *)0;
            p_tcb2->TickRemain  += p_tcb->TickRemain;           /* Add back the ticks to the delta                      */
            p_list->TCB_Ptr      = p_tcb2;
#if (OS_CFG_DBG_EN > 0u)
            p_list->NbrEntries--;
#endif

#if (OS_CFG_DYN_TICK_EN > 0u)
            if (p_tcb2->TickRemain != p_tcb->TickRemain) {      /* Only set a new tick if tcb2 had a longer delay.      */
                if (elapsed != 0u) {
                    OSTickCtr          += elapsed;              /* Keep track of time.                                  */
                    OS_TRACE_TICK_INCREMENT(OSTickCtr);
                    p_tcb2->TickRemain -= elapsed;              /* We must account for any time which has passed.       */
                }
                OSTickCtrStep           = p_tcb2->TickRemain;
                OS_DynTickSet(OSTickCtrStep);
            }
#endif
            p_tcb->TickNextPtr          = (OS_TCB *)0;
            p_tcb->TickRemain           =           0u;
        }
    } else {
        p_tcb1->TickNextPtr = p_tcb2;
        if (p_tcb2 != (OS_TCB *)0) {
            p_tcb2->TickPrevPtr  = p_tcb1;
            p_tcb2->TickRemain  += p_tcb->TickRemain;            /* Add back the ticks to the delta list                 */
        }
        p_tcb->TickPrevPtr       = (OS_TCB *)0;
#if (OS_CFG_DBG_EN > 0u)
        p_list->NbrEntries--;
#endif
        p_tcb->TickNextPtr       = (OS_TCB *)0;
        p_tcb->TickRemain        =           0u;
    }
}

/*
************************************************************************************************************************
*                                 UPDATE THE LIST OF TASKS DELAYED OR PENDING WITH TIMEOUT
*
* Description: This function updates the delta list which contains tasks that are delayed or pending with a timeout.
*
* Arguments  : ticks          the number of ticks which have elapsed.
*
* Returns    : none
*
* Note(s)    : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

static  void  OS_TickListUpdate (OS_TICK  ticks)
{
    OS_TCB        *p_tcb;
    OS_TICK_LIST  *p_list;
#if (OS_CFG_DBG_EN > 0u)
    OS_OBJ_QTY     nbr_updated;
#endif
#if (OS_CFG_MUTEX_EN > 0u)
    OS_TCB        *p_tcb_owner;
    OS_PRIO        prio_new;
#endif



#if (OS_CFG_DBG_EN > 0u)
    nbr_updated = 0u;
#endif
    p_list      = &OSTickList;
    p_tcb       = p_list->TCB_Ptr;
    if (p_tcb != (OS_TCB *)0) {
        if (p_tcb->TickRemain <= ticks) {
            ticks              = ticks - p_tcb->TickRemain;
            p_tcb->TickRemain  = 0u;
        } else {
            p_tcb->TickRemain -= ticks;
        }

        while (p_tcb->TickRemain == 0u) {
#if (OS_CFG_DBG_EN > 0u)
            nbr_updated++;
#endif

            switch (p_tcb->TaskState) {
                case OS_TASK_STATE_DLY:
                     p_tcb->TaskState = OS_TASK_STATE_RDY;
                     OS_RdyListInsert(p_tcb);                            /* Insert the task in the ready list                    */
                     break;

                case OS_TASK_STATE_DLY_SUSPENDED:
                     p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
                     break;

                default:
#if (OS_CFG_MUTEX_EN > 0u)
                     p_tcb_owner = (OS_TCB *)0;
                     if (p_tcb->PendOn == OS_TASK_PEND_ON_MUTEX) {
                         p_tcb_owner = (OS_TCB *)((OS_MUTEX *)((void *)p_tcb->PendObjPtr))->OwnerTCBPtr;
                     }
#endif

#if (OS_MSG_EN > 0u)
                     p_tcb->MsgPtr  = (void *)0;
                     p_tcb->MsgSize = 0u;
#endif
#if (OS_CFG_TS_EN > 0u)
                     p_tcb->TS      = OS_TS_GET();
#endif
                     OS_PendListRemove(p_tcb);                           /* Remove task from pend list                           */

                     switch (p_tcb->TaskState) {
                         case OS_TASK_STATE_PEND_TIMEOUT:
                              OS_RdyListInsert(p_tcb);                   /* Insert the task in the ready list                    */
                              p_tcb->TaskState  = OS_TASK_STATE_RDY;
                              break;

                         case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
                              p_tcb->TaskState  = OS_TASK_STATE_SUSPENDED;
                              break;

                         default:
                              break;
                     }
                     p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT;         /* Indicate pend timed out                              */
                     p_tcb->PendOn     = OS_TASK_PEND_ON_NOTHING;        /* Indicate no longer pending                           */

#if (OS_CFG_MUTEX_EN > 0u)
                     if (p_tcb_owner != (OS_TCB *)0) {
                         if ((p_tcb_owner->Prio != p_tcb_owner->BasePrio) &&
                             (p_tcb_owner->Prio == p_tcb->Prio)) {       /* Has the owner inherited a priority?                  */
                             prio_new = OS_MutexGrpPrioFindHighest(p_tcb_owner);
                             prio_new = (prio_new > p_tcb_owner->BasePrio) ? p_tcb_owner->BasePrio : prio_new;
                             if (prio_new != p_tcb_owner->Prio) {
                                 OS_TaskChangePrio(p_tcb_owner, prio_new);
                                 OS_TRACE_MUTEX_TASK_PRIO_DISINHERIT(p_tcb_owner, p_tcb_owner->Prio);
                             }
                         }
                     }
#endif
                     break;
            }

            p_list->TCB_Ptr = p_tcb->TickNextPtr;
            p_tcb           = p_list->TCB_Ptr;                           /* Get 'p_tcb' again for loop                           */
            if (p_tcb == (OS_TCB *)0) {
#if (OS_CFG_DBG_EN > 0u)
                p_list->NbrEntries = 0u;
#endif
                break;
            } else {
#if (OS_CFG_DBG_EN > 0u)
                p_list->NbrEntries--;
#endif
                p_tcb->TickPrevPtr = (OS_TCB *)0;
                if (p_tcb->TickRemain <= ticks) {
                    ticks              = ticks - p_tcb->TickRemain;
                    p_tcb->TickRemain  = 0u;
                } else {
                    p_tcb->TickRemain -= ticks;
                }
            }
        }
    }
#if (OS_CFG_DBG_EN > 0u)
    p_list->NbrUpdated = nbr_updated;
#endif
}

#endif                                                                   /* #if OS_CFG_TICK_EN                                   */
