mirror of
https://gitee.com/Lamdonn/varch.git
synced 2025-12-07 01:06:41 +08:00
TODO: 1. Add feature to bind CoTask and CoTimer to the specified CoScheduler 2. Compatibility with coroutine API highly intensive IO, sockets, etc 3. Improve functional safety
1235 lines
39 KiB
C
1235 lines
39 KiB
C
/*********************************************************************************************************
|
|
* ------------------------------------------------------------------------------------------------------
|
|
* file description
|
|
* ------------------------------------------------------------------------------------------------------
|
|
* \file coroutine.c
|
|
* \unit coroutine
|
|
* \brief This is a C language coroutine library
|
|
* \author Lamdonn
|
|
* \version v0.1.0
|
|
* \license GPL-2.0
|
|
* \copyright Copyright (C) 2025 Lamdonn.
|
|
********************************************************************************************************/
|
|
#include "coroutine.h"
|
|
|
|
/**
|
|
* \brief: Coroutine scheduler state
|
|
* \note: Coroutine scheduler state is used to indicate the current state of the coroutine scheduler.
|
|
* The coroutine scheduler state is updated periodically with the given tick interval.
|
|
*/
|
|
#define COSCHEDULER_STATE_INTI (0) /**< Coroutine scheduler state initialize */
|
|
#define COSCHEDULER_STATE_EXIT (1) /**< Coroutine scheduler state exit */
|
|
#define COSCHEDULER_STATE_START (2) /**< Coroutine scheduler state start */
|
|
#define COSCHEDULER_STATE_RUNNING (3) /**< Coroutine scheduler state running */
|
|
#define COSCHEDULER_STATE_SCHEDULE (4) /**< Coroutine scheduler state schedule */
|
|
|
|
/**
|
|
* \brief: Coroutine task state
|
|
* \note: Coroutine task state is used to indicate the current state of the coroutine task.
|
|
* The coroutine task state is updated periodically with the given tick interval.
|
|
*/
|
|
|
|
#define COTASK_STATE_INIT (0) /**< Coroutine task state initialize */
|
|
#define COTASK_STATE_READY (1) /**< Coroutine task state ready */
|
|
#define COTASK_STATE_RUNNING (2) /**< Coroutine task state running */
|
|
#define COTASK_STATE_SUSPEND (3) /**< Coroutine task state suspend */
|
|
#define COTASK_STATE_DELETED (4) /**< Coroutine task state deleted */
|
|
|
|
/**
|
|
* \brief: Coroutine task flag
|
|
* \note: Coroutine task flag is used to indicate the current flag of the coroutine task.
|
|
* The coroutine task flag is updated periodically with the given tick interval.
|
|
*/
|
|
|
|
#define COTASK_FLAG_ALLOCED (0x01) /**< Coroutine task flag alloced */
|
|
#define COTASK_FLAG_DYNC_TCB (0x02) /**< Coroutine task flag dynamic tcb */
|
|
#define COTASK_FLAG_DYNC_STACK (0x04) /**< Coroutine task flag dynamic stack */
|
|
|
|
/**
|
|
* \brief: Coroutine task stack mark
|
|
* \note: Coroutine task stack mark is used to mark the end of the coroutine task stack.
|
|
* The coroutine task stack mark is used to check the stack overflow.
|
|
* During stack counting, it is determined whether the stack address is used without this MARK defined by the stack value.
|
|
*/
|
|
#define COTASK_STACK_MARK (0xDEADBEEF)
|
|
|
|
/**
|
|
* \brief: Coroutine scheduler list
|
|
* \note: Coroutine scheduler list is used to store the coroutine scheduler.
|
|
* The scheduler will be added to this list when 'CoScheduler_InitP' is called
|
|
* The coroutine scheduler list is used to manage the coroutine scheduler.
|
|
* The coroutine scheduler list is a circular linked list.
|
|
* The coroutine scheduler list is initialized as NULL.
|
|
*/
|
|
static CoScheduler *sCoSchedulerList = NULL;
|
|
|
|
/**
|
|
* \brief: Coroutine scheduler lock
|
|
* \note: Coroutine scheduler lock is used to lock the coroutine scheduler.
|
|
* The coroutine scheduler lock is used to prevent the coroutine scheduler from being accessed by multiple threads.
|
|
*/
|
|
static CoLock_t sCoSchedulerLock = NULL;
|
|
|
|
/**
|
|
* \brief: Coroutine scheduler unlock
|
|
* \note: Coroutine scheduler unlock is used to unlock the coroutine scheduler.
|
|
* The coroutine scheduler unlock is used to release the lock of the coroutine scheduler.
|
|
*/
|
|
static CoUnlock_t sCoSchedulerUnlock = NULL;
|
|
|
|
/**
|
|
* \brief: Coroutine scheduler malloc
|
|
* \note: Coroutine scheduler malloc is used to malloc the coroutine scheduler.
|
|
* The coroutine scheduler malloc is used to malloc the memory for the coroutine scheduler.
|
|
*/
|
|
static CoMalloc_t sCoSchedulerMalloc = NULL;
|
|
|
|
/**
|
|
* \brief: Coroutine scheduler free
|
|
* \note: Coroutine scheduler free is used to free the coroutine scheduler.
|
|
* The coroutine scheduler free is used to free the memory for the coroutine scheduler.
|
|
*/
|
|
static CoFree_t sCoSchedulerFree = NULL;
|
|
|
|
/**
|
|
* \brief: Coroutine scheduler lock
|
|
* \note: Coroutine scheduler lock is used to lock the coroutine scheduler.
|
|
* Only for a single scheduler.
|
|
*/
|
|
static void CoScheduler_Lock(void) { }
|
|
|
|
/**
|
|
* \brief: Coroutine scheduler unlock
|
|
* \note: Coroutine scheduler unlock is used to unlock the coroutine scheduler.
|
|
* Only for a single scheduler.
|
|
*/
|
|
static void CoScheduler_Unlock(void) { }
|
|
|
|
static void Yeild(CoScheduler *pScheduler)
|
|
{
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
/* Calculate the loading time */
|
|
pScheduler->cTick = pScheduler->tick();
|
|
/* Update the loading time */
|
|
if (pScheduler->pTick == 0)
|
|
{
|
|
pScheduler->pTick = pScheduler->cTick;
|
|
}
|
|
/* Update the running time */
|
|
if (pScheduler->cTick > pScheduler->pTick)
|
|
{
|
|
/* Add the running time */
|
|
pScheduler->rTick += (pScheduler->cTick - pScheduler->pTick);
|
|
}
|
|
#endif
|
|
/* Yield to the next coroutine task */
|
|
longjmp(pScheduler->env, COSCHEDULER_STATE_SCHEDULE);
|
|
}
|
|
|
|
static CoScheduler *GetCurCoScheduler(void)
|
|
{
|
|
void *sp = NULL;
|
|
CoScheduler *pScheduler = sCoSchedulerList;
|
|
sCoSchedulerLock();
|
|
/* Find the current coroutine scheduler */
|
|
while (pScheduler->next != sCoSchedulerList)
|
|
{
|
|
/* Check sp wether in the current scheduler stack */
|
|
COROUTINE_GET_STACK(sp);
|
|
if (pScheduler->CoTaskCurrent->stackBase <= sp && sp < (void *)((char *)pScheduler->CoTaskCurrent->stackBase + pScheduler->CoTaskCurrent->stackSize))
|
|
{
|
|
break;
|
|
}
|
|
/* Move to the next scheduler */
|
|
pScheduler = pScheduler->next;
|
|
}
|
|
sCoSchedulerUnlock();
|
|
return pScheduler;
|
|
}
|
|
|
|
static CoScheduler *DistributeCoScheduler(void)
|
|
{
|
|
CoScheduler *pScheduler = NULL;
|
|
CoScheduler *minCoScheduler = NULL;
|
|
sCoSchedulerLock();
|
|
pScheduler = sCoSchedulerList;
|
|
/* Find the scheduler with the minimum number of coroutine tasks */
|
|
minCoScheduler = sCoSchedulerList;
|
|
do {
|
|
/* Check the number of coroutine tasks */
|
|
if (pScheduler->CoTaskListSize < minCoScheduler->CoTaskListSize)
|
|
{
|
|
minCoScheduler = pScheduler;
|
|
}
|
|
/* Move to the next scheduler */
|
|
pScheduler = pScheduler->next;
|
|
} while (pScheduler != sCoSchedulerList);
|
|
sCoSchedulerUnlock();
|
|
return minCoScheduler;
|
|
}
|
|
|
|
static CoTask_t CreateCoTask(CoScheduler *pScheduler)
|
|
{
|
|
#if (COROUTINE_STATIC_TASK_MAX_NUMBER > 0)
|
|
/* Find the first unallocated coroutine task */
|
|
for (int i = 0; i < COROUTINE_STATIC_TASK_MAX_NUMBER; i++)
|
|
{
|
|
/* Check the coroutine task is allocated */
|
|
if (!(pScheduler->sCoTasks[i].flag & COTASK_FLAG_ALLOCED))
|
|
{
|
|
/* Clear the flag */
|
|
pScheduler->sCoTasks[i].flag = 0;
|
|
/* Set the flag as allocated */
|
|
pScheduler->sCoTasks[i].flag |= COTASK_FLAG_ALLOCED;
|
|
return &pScheduler->sCoTasks[i];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Allocate the coroutine task dynamically */
|
|
if (sCoSchedulerMalloc)
|
|
{
|
|
CoTask_t pCoroutine = (CoTask_t)sCoSchedulerMalloc(sizeof(struct CoTask));
|
|
if (pCoroutine)
|
|
{
|
|
/* Set the flag as allocated */
|
|
pCoroutine->flag = COTASK_FLAG_ALLOCED | COTASK_FLAG_DYNC_TCB;
|
|
return pCoroutine;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void DeleteCoTask(CoScheduler *pScheduler, CoTask_t CoTaskCurrent)
|
|
{
|
|
#if (COROUTINE_STATIC_TASK_MAX_NUMBER > 0)
|
|
/* Check the coroutine task is statically allocated */
|
|
if (&pScheduler->sCoTasks[0] <= CoTaskCurrent && CoTaskCurrent <= &pScheduler->sCoTasks[COROUTINE_STATIC_TASK_MAX_NUMBER - 1])
|
|
{
|
|
/* Clear the flag */
|
|
CoTaskCurrent->flag &= ~COTASK_FLAG_ALLOCED;
|
|
return;
|
|
}
|
|
else
|
|
#endif
|
|
/* Check the coroutine task is dynamically allocated */
|
|
{
|
|
/* Check the stack is dynamically allocated */
|
|
if (CoTaskCurrent->flag & COTASK_FLAG_DYNC_STACK)
|
|
{
|
|
/* Free the stack */
|
|
sCoSchedulerFree(CoTaskCurrent->stackBase);
|
|
}
|
|
/* Check the TCB is dynamically allocated */
|
|
if (CoTaskCurrent->flag & COTASK_FLAG_DYNC_TCB)
|
|
{
|
|
/* Free the TCB */
|
|
sCoSchedulerFree(CoTaskCurrent);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void InsertCoTaskList(CoScheduler *pScheduler, CoTask_t NewCoTask)
|
|
{
|
|
/* Check the coroutine task list is empty */
|
|
if (pScheduler->CoTaskList == NULL)
|
|
{
|
|
/* Set the coroutine task as the head and tail */
|
|
pScheduler->CoTaskList = NewCoTask;
|
|
NewCoTask->next = NewCoTask;
|
|
NewCoTask->prev = NewCoTask;
|
|
}
|
|
/* Insert the coroutine task to the tail of the list */
|
|
else
|
|
{
|
|
/* Get the head and tail of the list */
|
|
CoTask_t Head = pScheduler->CoTaskList;
|
|
CoTask_t Tail = Head->prev;
|
|
|
|
/* Insert the coroutine task to the tail */
|
|
Tail->next = NewCoTask;
|
|
NewCoTask->prev = Tail;
|
|
Head->prev = NewCoTask;
|
|
NewCoTask->next = Head;
|
|
}
|
|
|
|
pScheduler->CoTaskListSize++;
|
|
}
|
|
|
|
static void EraseCoTaskList(CoScheduler *pScheduler, CoTask_t CoTaskCurrent)
|
|
{
|
|
/* Check the coroutine task is the head of the list */
|
|
if (CoTaskCurrent == pScheduler->CoTaskList)
|
|
{
|
|
/* Set the next coroutine task as the head */
|
|
pScheduler->CoTaskList = CoTaskCurrent->next;
|
|
}
|
|
|
|
/* Erase the coroutine task from the list */
|
|
CoTaskCurrent->prev->next = CoTaskCurrent->next;
|
|
CoTaskCurrent->next->prev = CoTaskCurrent->prev;
|
|
|
|
/* Decrease the number of coroutine tasks */
|
|
pScheduler->CoTaskListSize--;
|
|
|
|
/* Check the coroutine task list is empty */
|
|
if (pScheduler->CoTaskListSize == 0)
|
|
{
|
|
pScheduler->CoTaskList = NULL;
|
|
}
|
|
}
|
|
|
|
static void CoTaskRun(CoScheduler *pScheduler, uint32_t mark)
|
|
{
|
|
/* Set the coroutine task state as running */
|
|
pScheduler->CoTaskCurrent->state = COTASK_STATE_RUNNING;
|
|
|
|
/* Run the coroutine task */
|
|
pScheduler->CoTaskCurrent->entry(pScheduler->CoTaskCurrent->arg);
|
|
|
|
/* Block the coroutine task if the task stack be brokend */
|
|
while (mark != COTASK_STACK_MARK);
|
|
|
|
/* Set the coroutine task state as deleted */
|
|
pScheduler->CoTaskCurrent->state = COTASK_STATE_DELETED;
|
|
|
|
/* Check the coroutine task list is empty */
|
|
if (pScheduler->CoTaskListSize > 0)
|
|
{
|
|
/* Yield to the next coroutine task */
|
|
Yeild(pScheduler);
|
|
}
|
|
/* Exit the coroutine scheduler */
|
|
else
|
|
{
|
|
/* Exit the coroutine scheduler */
|
|
longjmp(pScheduler->env, COSCHEDULER_STATE_EXIT);
|
|
}
|
|
}
|
|
|
|
#if (COROUTINE_ENABLE_STACK_CALCULATE > 0)
|
|
static void CoTask_StackInit(uint32_t *pStack, size_t u32Count)
|
|
{
|
|
/* Initialize the stack with the mark */
|
|
for (size_t i = 0; i < u32Count; i++)
|
|
{
|
|
pStack[i] = COTASK_STACK_MARK;
|
|
}
|
|
}
|
|
|
|
static uint32_t CoTask_StackUsed(uint32_t *pStack, size_t u32Count)
|
|
{
|
|
uint32_t indexLeft = 0;
|
|
uint32_t indexRight = u32Count;
|
|
uint32_t indexCenter = u32Count / 2;
|
|
|
|
/* Binary search the mark in the stack */
|
|
while (indexLeft < indexRight)
|
|
{
|
|
/* Check the mark is in the left half */
|
|
if (pStack[indexCenter] == COTASK_STACK_MARK)
|
|
{
|
|
indexLeft = indexCenter + 1;
|
|
}
|
|
/* Check the mark is in the right half */
|
|
else
|
|
{
|
|
indexRight = indexCenter;
|
|
}
|
|
|
|
/* Update the center index */
|
|
indexCenter = (indexLeft + indexRight) / 2;
|
|
}
|
|
|
|
/* Calculate the stack used size */
|
|
return (u32Count - indexLeft) * sizeof(uint32_t);
|
|
}
|
|
#endif
|
|
|
|
CoTask_t CoTask_CreateP(CoTaskCreatePara *pPara)
|
|
{
|
|
CoScheduler *pScheduler = NULL;
|
|
CoTask_t NewCoTask = NULL;
|
|
void *stackBase = NULL;
|
|
size_t stackSize = 0;
|
|
uint32_t flag = 0;
|
|
|
|
/* Distribute the scheduler that the task need to bind */
|
|
pScheduler = DistributeCoScheduler();
|
|
|
|
/* Check the scheduler is valid */
|
|
if (pScheduler->tick == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Check the create parameter is valid */
|
|
if (pPara == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Check the entry function is valid */
|
|
if (pPara->entry == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
stackBase = pPara->pStack;
|
|
stackSize = pPara->stackSize;
|
|
/* If stackBase is empty, an automatic allocation of stackBase is attempted */
|
|
if (stackBase == NULL)
|
|
{
|
|
/* Check the malloc function is valid */
|
|
if (sCoSchedulerMalloc)
|
|
{
|
|
/* Check the stack size is valid, or use the defualt stack size */
|
|
if (stackSize == 0) stackSize = COROUTINE_STACK_DEFAULT_SIZE;
|
|
/* Allocate the stack memory */
|
|
stackBase = sCoSchedulerMalloc(stackSize);
|
|
if (stackBase == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
/* Set the flag as dynamic stack */
|
|
flag |= COTASK_FLAG_DYNC_STACK;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Check the stack size is valid */
|
|
if (stackSize == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Create the coroutine task */
|
|
NewCoTask = CreateCoTask(pScheduler);
|
|
if (NewCoTask == NULL)
|
|
{
|
|
/* Free the dynamic stack memory if it is allocated */
|
|
if (flag & COTASK_FLAG_DYNC_STACK)
|
|
{
|
|
sCoSchedulerFree(stackBase);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Set the coroutine task parameters */
|
|
NewCoTask->entry = pPara->entry;
|
|
NewCoTask->arg = pPara->arg;
|
|
NewCoTask->stackBase = stackBase;
|
|
NewCoTask->stackSize = stackSize;
|
|
NewCoTask->next = NULL;
|
|
NewCoTask->prev = NULL;
|
|
NewCoTask->state = COTASK_STATE_INIT;
|
|
NewCoTask->pEvent = NULL;
|
|
NewCoTask->nextRunTick = 0;
|
|
NewCoTask->flag |= flag;
|
|
NewCoTask->pScheduler = pScheduler;
|
|
|
|
/* Insert the coroutine task into the scheduler */
|
|
InsertCoTaskList(pScheduler, NewCoTask);
|
|
|
|
#if (COROUTINE_ENABLE_STACK_CALCULATE > 0)
|
|
/* Initialize the stack */
|
|
NewCoTask->stackMaxUsed = 0;
|
|
CoTask_StackInit((uint32_t *)stackBase, stackSize / sizeof(uint32_t));
|
|
#endif
|
|
|
|
return NewCoTask;
|
|
}
|
|
|
|
int CoTask_Delete(CoTask_t CoTask)
|
|
{
|
|
/* Check the coroutine task is valid */
|
|
if (CoTask == NULL)
|
|
{
|
|
return COROUTINE_E_INVALID_PARAMETER;
|
|
}
|
|
|
|
/* Check the coroutine task is running */
|
|
CoScheduler *pScheduler = CoTask->pScheduler;
|
|
/* Set the coroutine task state as deleted */
|
|
CoTask->state = COTASK_STATE_DELETED;
|
|
/* Return control to the scheduler so that it can clean up the task */
|
|
if (COSCHEDULER_STATE_RUNNING != setjmp(pScheduler->CoTaskCurrent->env))
|
|
{
|
|
Yeild(pScheduler);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
CoTask_t CoTask_Self(void)
|
|
{
|
|
/* Get the current coroutine scheduler */
|
|
CoScheduler *pScheduler = GetCurCoScheduler();
|
|
/* Return the current coroutine task */
|
|
return pScheduler->CoTaskCurrent;
|
|
}
|
|
|
|
#if (COROUTINE_ENABLE_STACK_CALCULATE > 0)
|
|
size_t CoTask_StackMaxUsed(CoTask_t CoTask)
|
|
{
|
|
/* Check the coroutine task is valid */
|
|
if (CoTask == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Check the coroutine task is initialized or deleted */
|
|
if (CoTask->state == COTASK_STATE_INIT || CoTask->state == COTASK_STATE_DELETED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Calculate the stack max used */
|
|
CoTask->stackMaxUsed = CoTask_StackUsed((uint32_t *)CoTask->stackBase, CoTask->stackSize / sizeof(uint32_t));
|
|
|
|
return CoTask->stackMaxUsed;
|
|
}
|
|
#endif
|
|
|
|
#if (COROUTINE_ENABLE_STACK_CALCULATE > 0)
|
|
size_t CoTask_StackCurUsed(CoTask_t CoTask)
|
|
{
|
|
char *sp = NULL;
|
|
/* Get the current stack pointer */
|
|
COROUTINE_GET_STACK(sp);
|
|
/* Calculate the current stack used */
|
|
return (size_t)((char *)CoTask->stackBase + CoTask->stackSize - sp);
|
|
}
|
|
#endif
|
|
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
uint16_t CoScheduler_CurLoad(CoScheduler *pScheduler)
|
|
{
|
|
/* Check the coroutine scheduler is valid */
|
|
if (pScheduler == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Return the current loading */
|
|
return pScheduler->curLoad;
|
|
}
|
|
#endif
|
|
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
uint16_t CoScheduler_MaxLoad(CoScheduler *pScheduler)
|
|
{
|
|
/* Check the coroutine scheduler is valid */
|
|
if (pScheduler == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Return the maximum loading */
|
|
return pScheduler->maxLoad;
|
|
}
|
|
#endif
|
|
|
|
void CoEvent_Init(CoEvent *pEvent)
|
|
{
|
|
/* Check the coroutine event is valid */
|
|
if (pEvent == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Initialize the event flag */
|
|
pEvent->flag = 0;
|
|
}
|
|
|
|
uint32_t CoTask_WaitP(CoTaskWaitPara *pPara)
|
|
{
|
|
CoScheduler *pScheduler = NULL;
|
|
uint32_t evs = 0;
|
|
CoTaskWaitPara para = {.pEvent=NULL,.ms=0,.tick=0};
|
|
uint64_t tick = 0;
|
|
|
|
/* Check the wait parameter */
|
|
if (pPara == NULL)
|
|
{
|
|
/* Use the default wait parameter, it will waiting nothing */
|
|
pPara = ¶
|
|
}
|
|
|
|
/* Get the current coroutine scheduler */
|
|
pScheduler = GetCurCoScheduler();
|
|
|
|
/* Check the wait parameter */
|
|
/* Default wait time is 0, means no wait */
|
|
if (pPara->ms != 0 || pPara->tick != 0)
|
|
{
|
|
/* Check the wait time ms */
|
|
if (pPara->ms != 0)
|
|
{
|
|
tick = pPara->ms * 1000000 / pScheduler->tickInterval;
|
|
}
|
|
/* Check the wait time tick */
|
|
if (pPara->tick != 0)
|
|
{
|
|
/* Select the smaller one */
|
|
if (tick == 0 || tick > pPara->tick)
|
|
{
|
|
tick = pPara->tick;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set the next run tick */
|
|
if (tick == 0 && pPara->pEvent != NULL)
|
|
{
|
|
pScheduler->CoTaskCurrent->nextRunTick = 0;
|
|
}
|
|
else
|
|
{
|
|
pScheduler->CoTaskCurrent->nextRunTick = pScheduler->tick() + tick;
|
|
}
|
|
|
|
/* Set the event */
|
|
pScheduler->CoTaskCurrent->pEvent = pPara->pEvent;
|
|
|
|
/* Set the coroutine task state as suspend */
|
|
pScheduler->CoTaskCurrent->state = COTASK_STATE_SUSPEND;
|
|
|
|
/* Return control to the scheduler so that it can schedule other tasks */
|
|
if (COSCHEDULER_STATE_RUNNING != setjmp(pScheduler->CoTaskCurrent->env))
|
|
{
|
|
Yeild(pScheduler);
|
|
}
|
|
|
|
if (pPara->pEvent != NULL)
|
|
{
|
|
/* Get the event flag */
|
|
evs = pPara->pEvent->flag;
|
|
/* Clear the event flag */
|
|
pPara->pEvent->flag = 0;
|
|
}
|
|
|
|
/* Return the event flag */
|
|
return evs;
|
|
}
|
|
|
|
void CoEvent_Notify(CoEvent *pEvent, uint32_t evs)
|
|
{
|
|
/* Check the coroutine event is valid */
|
|
if (pEvent == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/* Check the event flag */
|
|
if (evs == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
sCoSchedulerLock();
|
|
/* Set the event flag */
|
|
pEvent->flag |= evs;
|
|
sCoSchedulerUnlock();
|
|
}
|
|
|
|
static CoTimer_t CreateCoTimer(CoScheduler *pScheduler)
|
|
{
|
|
#if (COROUTINE_STATIC_TIMER_MAX_NUMBER > 0)
|
|
/* Search the free coroutine timer */
|
|
for (int i = 0; i < COROUTINE_STATIC_TIMER_MAX_NUMBER; i++)
|
|
{
|
|
/* Check the coroutine timer is free */
|
|
if (!(pScheduler->sCoTimers[i].flag & COTASK_FLAG_ALLOCED))
|
|
{
|
|
/* Allocate the coroutine timer */
|
|
pScheduler->sCoTimers[i].flag |= COTASK_FLAG_ALLOCED;
|
|
return &pScheduler->sCoTimers[i];
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Check the dynamic coroutine timer malloc function */
|
|
if (sCoSchedulerMalloc)
|
|
{
|
|
/* Allocate the coroutine timer */
|
|
CoTimer_t pCoroutineTimer = (CoTimer_t)sCoSchedulerMalloc(sizeof(struct CoTimer));
|
|
if (pCoroutineTimer)
|
|
{
|
|
/* Initialize the coroutine timer */
|
|
pCoroutineTimer->flag = COTASK_FLAG_ALLOCED | COTASK_FLAG_DYNC_TCB;
|
|
return pCoroutineTimer;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void DeleteCoTimer(CoScheduler *pScheduler, CoTimer_t CoTimerCurrent)
|
|
{
|
|
#if (COROUTINE_STATIC_TIMER_MAX_NUMBER > 0)
|
|
/* Check the coroutine timer is static */
|
|
if (&pScheduler->sCoTimers[0] <= CoTimerCurrent && CoTimerCurrent <= &pScheduler->sCoTimers[COROUTINE_STATIC_TIMER_MAX_NUMBER - 1])
|
|
{
|
|
/* Free the coroutine timer */
|
|
CoTimerCurrent->flag &= ~COTASK_FLAG_ALLOCED;
|
|
return;
|
|
}
|
|
else
|
|
#endif
|
|
/* Check the coroutine timer is dynamic */
|
|
{
|
|
/* Check the coroutine timer is dynamic */
|
|
if (CoTimerCurrent->flag & COTASK_FLAG_DYNC_TCB)
|
|
{
|
|
/* Free the coroutine timer */
|
|
sCoSchedulerFree(CoTimerCurrent);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void InsertCoTimerList(CoScheduler *pScheduler, CoTimer_t NewCoTimer)
|
|
{
|
|
/* Check the coroutine timer list is empty */
|
|
if (pScheduler->CoTimerList == NULL)
|
|
{
|
|
pScheduler->CoTimerList = NewCoTimer;
|
|
|
|
/* Initialize the coroutine timer list */
|
|
NewCoTimer->next = NewCoTimer;
|
|
NewCoTimer->prev = NewCoTimer;
|
|
}
|
|
else
|
|
{
|
|
/* Insert the coroutine timer to the end of the list */
|
|
CoTimer_t Head = pScheduler->CoTimerList;
|
|
CoTimer_t Tail = Head->prev;
|
|
|
|
/* Insert the coroutine timer to the end of the list */
|
|
Tail->next = NewCoTimer;
|
|
NewCoTimer->prev = Tail;
|
|
Head->prev = NewCoTimer;
|
|
NewCoTimer->next = Head;
|
|
}
|
|
|
|
/* Update the coroutine timer list size */
|
|
pScheduler->CoTimerListSize++;
|
|
}
|
|
|
|
static void EraseCoTimerList(CoScheduler *pScheduler, CoTimer_t CoTimerCurrent)
|
|
{
|
|
/* Check the coroutine timer is the head of the list */
|
|
if (CoTimerCurrent == pScheduler->CoTimerList)
|
|
{
|
|
/* Update the coroutine timer list head */
|
|
pScheduler->CoTimerList = CoTimerCurrent->next;
|
|
}
|
|
|
|
/* Erase the coroutine timer from the list */
|
|
CoTimerCurrent->prev->next = CoTimerCurrent->next;
|
|
CoTimerCurrent->next->prev = CoTimerCurrent->prev;
|
|
|
|
/* Update the coroutine timer list size */
|
|
pScheduler->CoTimerListSize--;
|
|
|
|
/* Check the coroutine timer list is empty */
|
|
if (pScheduler->CoTimerListSize == 0)
|
|
{
|
|
pScheduler->CoTimerList = NULL;
|
|
}
|
|
}
|
|
|
|
CoTimer_t CoTimer_CreateTick(CoTimerEntry_t entry, uint64_t tick)
|
|
{
|
|
CoScheduler *pScheduler = NULL;
|
|
CoTimer_t NewTimer = NULL;
|
|
|
|
/* Distribute the coroutine scheduler */
|
|
pScheduler = DistributeCoScheduler();
|
|
|
|
/* Check the coroutine scheduler is valid */
|
|
if (pScheduler->tick == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Check the coroutine timer entry is valid */
|
|
if (entry == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Check the coroutine timer tick is valid */
|
|
if (tick == (uint64_t)0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Create the coroutine timer */
|
|
NewTimer = CreateCoTimer(pScheduler);
|
|
if (NewTimer == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Initialize the coroutine timer */
|
|
NewTimer->entry = entry;
|
|
NewTimer->periodTick = tick;
|
|
NewTimer->nextRunTick = pScheduler->tick() + tick;
|
|
NewTimer->next = NULL;
|
|
NewTimer->prev = NULL;
|
|
NewTimer->pScheduler = pScheduler;
|
|
|
|
/* Insert the coroutine timer to the list */
|
|
InsertCoTimerList(pScheduler, NewTimer);
|
|
|
|
return NewTimer;
|
|
}
|
|
|
|
CoTimer_t CoTimer_CreateMs(CoTimerEntry_t entry, uint32_t ms)
|
|
{
|
|
CoScheduler *pScheduler = NULL;
|
|
CoTimer_t NewTimer = NULL;
|
|
|
|
/* Distribute the coroutine scheduler */
|
|
pScheduler = DistributeCoScheduler();
|
|
|
|
/* Check the coroutine scheduler is valid */
|
|
if (pScheduler->tick == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Check the coroutine timer entry is valid */
|
|
if (entry == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Check the coroutine timer ms is valid */
|
|
if (ms == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Create the coroutine timer */
|
|
NewTimer = CreateCoTimer(pScheduler);
|
|
if (NewTimer == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Calculate the coroutine timer tick */
|
|
uint64_t tick = ms * 1000000 / pScheduler->tickInterval;
|
|
|
|
/* Initialize the coroutine timer */
|
|
NewTimer->entry = entry;
|
|
NewTimer->periodTick = tick;
|
|
NewTimer->nextRunTick = pScheduler->tick() + tick;
|
|
NewTimer->next = NULL;
|
|
NewTimer->prev = NULL;
|
|
NewTimer->pScheduler = pScheduler;
|
|
|
|
/* Insert the coroutine timer to the list */
|
|
InsertCoTimerList(pScheduler, NewTimer);
|
|
|
|
return NewTimer;
|
|
}
|
|
|
|
void CoTimer_Delete(CoTimer_t Timer)
|
|
{
|
|
CoScheduler *pScheduler = NULL;
|
|
|
|
/* Check the coroutine timer is valid */
|
|
if (Timer == NULL)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pScheduler = Timer->pScheduler;
|
|
|
|
/* Erase the coroutine timer from the list */
|
|
EraseCoTimerList(pScheduler, Timer);
|
|
/* Free the coroutine timer */
|
|
DeleteCoTimer(pScheduler, Timer);
|
|
}
|
|
|
|
CoTimer_t CoTimer_Self(void)
|
|
{
|
|
CoTimer_t Self = NULL;
|
|
|
|
if (sCoSchedulerList != NULL)
|
|
{
|
|
/* Check the coroutine scheduler list wether is single scheduler */
|
|
if (sCoSchedulerList->next == sCoSchedulerList)
|
|
{
|
|
Self = sCoSchedulerList->CoTimerCurrent;
|
|
}
|
|
/* Check the coroutine scheduler list wether is multi scheduler */
|
|
else
|
|
{
|
|
CoScheduler *pScheduler = NULL;
|
|
CoScheduler *minCoScheduler = NULL;
|
|
void *sp = NULL;
|
|
size_t minSize = 0;
|
|
|
|
/* Get the current stack pointer */
|
|
COROUTINE_GET_STACK(sp);
|
|
|
|
sCoSchedulerLock();
|
|
pScheduler = sCoSchedulerList;
|
|
minCoScheduler = sCoSchedulerList;
|
|
|
|
/* The closest sp to the top of the stack is used to determine which scheduler is running */
|
|
do {
|
|
/* Check the coroutine scheduler stack top is above the current stack pointer */
|
|
if (pScheduler->stackTop - sp > 0)
|
|
{
|
|
/* Check the coroutine scheduler stack top is above the current stack pointer */
|
|
if (minSize == 0)
|
|
{
|
|
minSize = pScheduler->stackTop - sp;
|
|
minCoScheduler = pScheduler;
|
|
}
|
|
/* Check the coroutine scheduler stack top is above the current stack pointer */
|
|
else
|
|
{
|
|
size_t size = pScheduler->stackTop - sp;
|
|
if (pScheduler->stackTop - sp < minSize)
|
|
{
|
|
minSize = size;
|
|
minCoScheduler = pScheduler;
|
|
}
|
|
}
|
|
}
|
|
pScheduler = pScheduler->next;
|
|
} while (pScheduler != sCoSchedulerList);
|
|
sCoSchedulerUnlock();
|
|
|
|
/* Get the coroutine timer current of the closest scheduler */
|
|
Self = minCoScheduler->CoTimerCurrent;
|
|
}
|
|
}
|
|
|
|
return Self;
|
|
}
|
|
|
|
int CoScheduler_InitP(CoScheduler *pScheduler, CoSchedulerInitPara *pPara)
|
|
{
|
|
/* Check the coroutine scheduler is valid */
|
|
if (pScheduler == NULL)
|
|
{
|
|
return COROUTINE_E_INVALID_PARAMETER;
|
|
}
|
|
/* Check the coroutine scheduler init parameter is valid */
|
|
if (pPara == NULL)
|
|
{
|
|
return COROUTINE_E_INVALID_PARAMETER;
|
|
}
|
|
/* Check the coroutine scheduler tick function is valid */
|
|
if (pPara->tick == NULL)
|
|
{
|
|
return COROUTINE_E_INVALID_TICK;
|
|
}
|
|
/* Check the coroutine scheduler tick interval is valid */
|
|
if (pPara->tickInterval == 0)
|
|
{
|
|
return COROUTINE_E_INVALID_TICK_INTERVAL;
|
|
}
|
|
/* Check the coroutine scheduler lock function is valid */
|
|
if (sCoSchedulerLock && sCoSchedulerUnlock)
|
|
{
|
|
/* Ensure that different schedulers use the same lock */
|
|
if (pPara->lock != sCoSchedulerLock || pPara->unlock != sCoSchedulerUnlock)
|
|
{
|
|
return COROUTINE_E_INVALID_LOCK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Specifies the passed lock */
|
|
if (pPara->lock && pPara->unlock)
|
|
{
|
|
sCoSchedulerLock = pPara->lock;
|
|
sCoSchedulerUnlock = pPara->unlock;
|
|
}
|
|
/* Use the default lock */
|
|
else
|
|
{
|
|
sCoSchedulerLock = CoScheduler_Lock;
|
|
sCoSchedulerUnlock = CoScheduler_Unlock;
|
|
}
|
|
}
|
|
|
|
/* Check the coroutine scheduler malloc function is valid */
|
|
if (sCoSchedulerMalloc && sCoSchedulerFree)
|
|
{
|
|
/* Ensure that different schedulers use the same malloc */
|
|
if (pPara->malloc != sCoSchedulerMalloc || pPara->free != sCoSchedulerFree)
|
|
{
|
|
return COROUTINE_E_INVALID_MHOOK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sCoSchedulerLock();
|
|
/* Specifies the passed malloc */
|
|
if (pPara->malloc && pPara->free)
|
|
{
|
|
sCoSchedulerMalloc = pPara->malloc;
|
|
sCoSchedulerFree = pPara->free;
|
|
}
|
|
/* Not use */
|
|
else
|
|
{
|
|
sCoSchedulerMalloc = NULL;
|
|
sCoSchedulerFree = NULL;
|
|
}
|
|
sCoSchedulerUnlock();
|
|
}
|
|
|
|
/* Initialize the coroutine scheduler */
|
|
pScheduler->tick = pPara->tick;
|
|
pScheduler->tickInterval = pPara->tickInterval;
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
/* Initialize the coroutine scheduler loading calculate */
|
|
pScheduler->uTick = COROUTINE_LOADING_CALCULATE_PERIOD * 1000000 / pScheduler->tickInterval;
|
|
pScheduler->pTick = 0;
|
|
pScheduler->sTick = 0;
|
|
pScheduler->rTick = 0;
|
|
pScheduler->curLoad = 0;
|
|
pScheduler->maxLoad = 0;
|
|
#endif
|
|
pScheduler->CoTaskList = NULL;
|
|
pScheduler->CoTaskCurrent = NULL;
|
|
pScheduler->CoTaskListSize = 0;
|
|
pScheduler->CoTimerList = NULL;
|
|
pScheduler->CoTimerCurrent = NULL;
|
|
pScheduler->CoTimerListSize = 0;
|
|
pScheduler->stackTop = NULL;
|
|
pScheduler->CoTaskRun = CoTaskRun;
|
|
|
|
sCoSchedulerLock();
|
|
/* Add the coroutine scheduler to the scheduler list */
|
|
if (sCoSchedulerList == NULL)
|
|
{
|
|
sCoSchedulerList = pScheduler;
|
|
pScheduler->next = pScheduler;
|
|
}
|
|
/* Add the coroutine scheduler to the scheduler list */
|
|
else
|
|
{
|
|
pScheduler->next = sCoSchedulerList->next;
|
|
sCoSchedulerList->next = pScheduler;
|
|
}
|
|
sCoSchedulerUnlock();
|
|
|
|
return COROUTINE_E_OK;
|
|
}
|
|
|
|
int CoScheduler_Start(CoScheduler *pScheduler)
|
|
{
|
|
int ret = setjmp(pScheduler->env);
|
|
|
|
/* Initialize the coroutine scheduler stack pointer */
|
|
pScheduler->stackTop = &ret;
|
|
|
|
if (COSCHEDULER_STATE_INTI == ret)
|
|
{
|
|
/* Jump directly to the scheduling state */
|
|
longjmp(pScheduler->env, COSCHEDULER_STATE_SCHEDULE);
|
|
}
|
|
else if (COSCHEDULER_STATE_START == ret)
|
|
{
|
|
/* Calculate the coroutine scheduler stack pointer */
|
|
void *stackTop = (void *)((char *)pScheduler->CoTaskCurrent->stackBase + pScheduler->CoTaskCurrent->stackSize);
|
|
/* Set the coroutine scheduler stack pointer */
|
|
COROUTINE_SET_STACK(stackTop);
|
|
/* Run the coroutine scheduler */
|
|
pScheduler->CoTaskRun(pScheduler, COTASK_STACK_MARK);
|
|
}
|
|
else
|
|
{
|
|
/* Infinite loop to run the coroutine scheduler */
|
|
while (1)
|
|
{
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
/* Calculate the coroutine scheduler tick */
|
|
if (pScheduler->sTick == 0)
|
|
{
|
|
pScheduler->sTick = pScheduler->tick();
|
|
}
|
|
else
|
|
{
|
|
pScheduler->cTick = pScheduler->tick();
|
|
/* Calculate the time difference between task cut-in and cut-out */
|
|
uint64_t diffTick = pScheduler->cTick - pScheduler->sTick;
|
|
/* Check the time difference is greater than or equal to the tick interval */
|
|
if (diffTick >= pScheduler->uTick)
|
|
{
|
|
/* Calculate the loading percentage */
|
|
uint16_t tload = pScheduler->rTick * 10000 / diffTick;
|
|
|
|
/* Add the loading percentage to the loading queue */
|
|
pScheduler->loadQueueBase[pScheduler->loadQueueTail] = tload;
|
|
pScheduler->loadQueueTail = (pScheduler->loadQueueTail + 1) % COROUTINE_LOADING_CALCULATE_QSIZE;
|
|
|
|
/* Update the loading queue size */
|
|
/* If the loading queue size is less than the queue size, increment the size */
|
|
if (pScheduler->loadQueueSize < COROUTINE_LOADING_CALCULATE_QSIZE)
|
|
{
|
|
pScheduler->loadQueueSize++;
|
|
}
|
|
/* If the loading queue size is equal to the queue size, calculate the average loading percentage */
|
|
else
|
|
{
|
|
uint32_t totalLoad = 0;
|
|
/* Calculate the total loading percentage */
|
|
for (int i = 0; i < COROUTINE_LOADING_CALCULATE_QSIZE; i++)
|
|
{
|
|
totalLoad += pScheduler->loadQueueBase[i];
|
|
}
|
|
|
|
/* Calculate the average loading percentage */
|
|
pScheduler->curLoad = (uint16_t)(totalLoad / COROUTINE_LOADING_CALCULATE_QSIZE);
|
|
|
|
/* Update the maximum loading percentage */
|
|
if (pScheduler->curLoad > pScheduler->maxLoad)
|
|
{
|
|
pScheduler->maxLoad = pScheduler->curLoad;
|
|
}
|
|
}
|
|
|
|
/* Update the scheduler tick */
|
|
/* Set the scheduler tick to the current tick */
|
|
pScheduler->sTick = pScheduler->cTick;
|
|
/* Reset the running tick */
|
|
pScheduler->rTick = 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Check the coroutine scheduler has coroutine */
|
|
if (pScheduler->CoTaskList != NULL)
|
|
{
|
|
/* Check the current coroutine is NULL */
|
|
if (pScheduler->CoTaskCurrent == NULL)
|
|
{
|
|
/* Set the current coroutine to the first coroutine in the list */
|
|
pScheduler->CoTaskCurrent = pScheduler->CoTaskList;
|
|
}
|
|
|
|
/* Get the next coroutine */
|
|
CoTask_t tCoroutine = pScheduler->CoTaskCurrent->next;
|
|
|
|
/* Loop through the coroutine list */
|
|
do {
|
|
/* Check the coroutine is initialized */
|
|
if (tCoroutine->state == COTASK_STATE_INIT)
|
|
{
|
|
/* Set the current coroutine to the initialized coroutine */
|
|
pScheduler->CoTaskCurrent = tCoroutine;
|
|
/* Jump to the coroutine */
|
|
longjmp(pScheduler->env, COSCHEDULER_STATE_START);
|
|
}
|
|
/* Check the coroutine is ready */
|
|
else if (tCoroutine->state == COTASK_STATE_READY)
|
|
{
|
|
/* Set the current coroutine to the ready coroutine */
|
|
pScheduler->CoTaskCurrent = tCoroutine;
|
|
pScheduler->CoTaskCurrent->state = COTASK_STATE_RUNNING;
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
/* Record the task cut time */
|
|
pScheduler->pTick = pScheduler->tick();
|
|
#endif
|
|
/* Jump to the coroutine */
|
|
longjmp(pScheduler->CoTaskCurrent->env, COSCHEDULER_STATE_RUNNING);
|
|
}
|
|
/* Check the coroutine is running */
|
|
else if (tCoroutine->state == COTASK_STATE_RUNNING)
|
|
{
|
|
tCoroutine->state = COTASK_STATE_INIT;
|
|
continue;
|
|
}
|
|
/* Check the coroutine is suspend */
|
|
else if (tCoroutine->state == COTASK_STATE_SUSPEND)
|
|
{
|
|
/* Check the next run time is reached */
|
|
if (((tCoroutine->nextRunTick != 0 && pScheduler->tick() > tCoroutine->nextRunTick) ||
|
|
/* Check the event is set */
|
|
(tCoroutine->pEvent != NULL && tCoroutine->pEvent->flag != 0)))
|
|
{
|
|
/* Set the coroutine to ready */
|
|
tCoroutine->state = COTASK_STATE_READY;
|
|
continue;
|
|
}
|
|
}
|
|
/* Check the coroutine is deleted */
|
|
else if (tCoroutine->state == COTASK_STATE_DELETED)
|
|
{
|
|
/* Remove the coroutine from the list */
|
|
EraseCoTaskList(pScheduler, tCoroutine);
|
|
/* Delete the coroutine */
|
|
DeleteCoTask(pScheduler, tCoroutine);
|
|
}
|
|
/* Check the coroutine is unknown */
|
|
else
|
|
{
|
|
/* Set the coroutine to initialized */
|
|
tCoroutine->state = COTASK_STATE_INIT;
|
|
continue;
|
|
}
|
|
|
|
/* Check the next coroutine */
|
|
tCoroutine = tCoroutine->next;
|
|
} while (tCoroutine != pScheduler->CoTaskCurrent->next);
|
|
}
|
|
|
|
/* Check the timer scheduler has timer */
|
|
if (pScheduler->CoTimerList != NULL)
|
|
{
|
|
/* Get the first timer */
|
|
CoTimer_t tTimer = pScheduler->CoTimerList;
|
|
|
|
/* Loop through the timer list */
|
|
do {
|
|
/* Check the timer is expired */
|
|
if (pScheduler->tick() >= tTimer->nextRunTick)
|
|
{
|
|
/* Set the current timer to the expired timer */
|
|
pScheduler->CoTimerCurrent = tTimer;
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
/* Record the timer cut-in time */
|
|
uint64_t pTick = pScheduler->tick();
|
|
#endif
|
|
tTimer->entry();
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
/* Record the timer cut-out time */
|
|
uint64_t cTick = pScheduler->tick();
|
|
#endif
|
|
#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0)
|
|
/* Record the timer loading time */
|
|
pScheduler->rTick += cTick - pTick;
|
|
#endif
|
|
/* Set the timer to next run time */
|
|
pScheduler->CoTimerCurrent = NULL;
|
|
tTimer->nextRunTick = pScheduler->tick() + tTimer->periodTick;
|
|
}
|
|
|
|
/* Check the next timer */
|
|
tTimer = tTimer->next;
|
|
} while (tTimer != pScheduler->CoTimerList);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|