varch/source/08_coroutine/coroutine.c
Lamdonn 49025692ca Add the initial version coroutine module
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
2025-11-11 22:42:25 +08:00

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 = &para;
}
/* 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;
}