/********************************************************************************************************* * ------------------------------------------------------------------------------------------------------ * file description * ------------------------------------------------------------------------------------------------------ * \file coroutine.c * \unit coroutine * \brief This is a C language coroutine library * \author Lamdonn * \version v0.2.0 * \license GPL-2.0 * \copyright Copyright (C) 2025 Lamdonn. ********************************************************************************************************/ #include "coroutine.h" #if (COROUTINE_STACK_DEFAULT_SIZE < 32) #error "COROUTINE_STACK_DEFAULT_SIZE must be greater than 32" #endif /** * \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 (0xAAAAAAAA) /** * \brief: Coroutine scheduler state set * \param pScheduler: Coroutine scheduler * \param state: Coroutine scheduler state * \note: This function is used to set the coroutine scheduler state. */ #define COSCHEDULER_SET_STATE(pScheduler, state) longjmp((pScheduler)->env, (state)) /** * \brief: Coroutine scheduler state get * \param pScheduler: Coroutine scheduler * \return: Coroutine scheduler state * \note: This function is used to get the coroutine scheduler state. */ #define COSCHEDULER_GET_STATE(pScheduler) setjmp((pScheduler)->env) /** * \brief: Coroutine scheduler */ typedef struct CoScheduler CoScheduler; /** * \brief: Coroutine task run function * \param pScheduler: Coroutine scheduler * \note: This function must be called in the coroutine context, no matter * whether the coroutine is running or not. */ typedef void (*CoTaskRun_t)(CoScheduler *pScheduler); /** * \brief: Coroutine task stack * \note: Coroutine task stack is used to store the coroutine task context. */ struct CoStack { uint8_t base[COROUTINE_STACK_DEFAULT_SIZE]; /**< Coroutine task stack base address */ }; /** * \brief: Coroutine task * \note: Coroutine task is a coroutine unit, each coroutine task has its own stack, * and can be scheduled by the coroutine scheduler. */ struct CoTask { CoTask_t next; /**< Double-link list, next coroutine task */ CoTask_t prev; /**< Double-link list, previous coroutine task */ CoScheduler *pScheduler; /**< Coroutine scheduler, point to the scheduler that created this coroutine task */ uint8_t state; /**< Coroutine task state */ void *stackBase; /**< Coroutine task stack base address */ size_t stackSize; /**< Coroutine task stack size */ #if (COROUTINE_ENABLE_STACK_CALCULATE > 0) size_t stackMaxUsed; /**< Coroutine task stack max used size */ #endif uint64_t nextRunTick; /**< When the coroutine task is suspended, coroutine task next run tick */ uint32_t flag; /**< Coroutine task flag */ CoTaskEntry_t entry; /**< Coroutine task entry function */ void *arg; /**< Coroutine task argument address */ CoEvent *pEvent; /**< Coroutine task event, point to the event that the coroutine task is waiting for */ jmp_buf env; /**< Coroutine task environment, used for context switch */ }; /** * \brief: Coroutine timer * \note: Coroutine timer is a coroutine unit, each coroutine timer has its own period, * and can be scheduled by the coroutine scheduler. */ struct CoTimer { CoTimer_t next; /**< Double-link list, next coroutine timer */ CoTimer_t prev; /**< Double-link list, previous coroutine timer */ CoScheduler *pScheduler; /**< Coroutine scheduler, point to the scheduler that created this coroutine timer */ uint64_t periodTick; /**< Coroutine timer period tick */ uint64_t nextRunTick; /**< Coroutine timer next run tick */ uint32_t flag; /**< Coroutine timer flag */ CoTimerEntry_t entry; /**< Coroutine timer entry function */ }; /** * \brief: Coroutine scheduler * \note: Coroutine scheduler is a coroutine unit, each coroutine scheduler has its own coroutine task list and coroutine timer list, * and can be scheduled by the coroutine scheduler. */ struct CoScheduler { CoTask_t CoTaskCurrent; /**< Current coroutine task */ CoTask_t CoTaskList; /**< Coroutine task list head */ size_t CoTaskListSize; /**< Coroutine task list size */ CoTimer_t CoTimerCurrent; /**< Current coroutine timer */ CoTimer_t CoTimerList; /**< Coroutine timer list head */ size_t CoTimerListSize; /**< Coroutine timer list size */ void *stackTop; /**< Coroutine scheduler stack top address */ jmp_buf env; /**< Coroutine scheduler environment, used for context switch */ CoTick_t tick; /**< Coroutine scheduler tick function */ uint32_t tickInterval; /**< Coroutine scheduler tick interval, indicates how many us each tick is */ #if (COROUTINE_ENABLE_LOADING_CALCULATE > 0) /** The results of each measurement period are added to this queue, * so the load measured at the last `COROUTINE_LOADING_CALCULATE_QSIZE` time point is kept * and averaged as the real-time load */ uint16_t loadQueueBase[COROUTINE_LOADING_CALCULATE_QSIZE]; /**< Coroutine scheduler loading queue base */ uint16_t loadQueueSize; /**< Coroutine scheduler loading queue size */ uint16_t loadQueueTail; /**< Coroutine scheduler loading queue tail */ uint16_t curLoad; /**< Coroutine scheduler current load */ uint16_t maxLoad; /**< Coroutine scheduler max load */ uint64_t uTick; /**< Coroutine scheduler measures and calculates the load once every `uTick` */ uint64_t sTick; /**< Coroutine scheduler tick that start measures */ uint64_t pTick; /**< Coroutine scheduler tick that previous start measurement task run */ uint64_t cTick; /**< Coroutine scheduler tick that current tick */ uint64_t rTick; /**< Coroutine scheduler tick that task running */ #endif CoTaskRun_t CoTaskRun; /**< Coroutine scheduler task run function, prevents function compilation from being optimized inline */ }; #if (COROUTINE_SCHEDULER_NUMBER > 0) static CoScheduler sCoScheduler[COROUTINE_SCHEDULER_NUMBER] = {0}; #else #error "COROUTINE_SCHEDULER_NUMBER must be greater than 0" #endif #if (COROUTINE_STATIC_TASK_MAX_NUMBER > 0) /**< Coroutine task static array */ struct CoTask sCoTasks[COROUTINE_STATIC_TASK_MAX_NUMBER] = {0}; #endif #if (COROUTINE_STATIC_TIMER_MAX_NUMBER > 0) /**< Coroutine timer static array */ struct CoTimer sCoTimers[COROUTINE_STATIC_TIMER_MAX_NUMBER] = {0}; #endif #if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) /**< Coroutine stack static array */ struct CoStack sCoStacks[COROUTINE_STATIC_STACK_MAX_NUMBER] = {0}; #endif /** * \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. */ #if (COROUTINE_SCHEDULER_NUMBER > 1) static CoLock_t sCoSchedulerLock = NULL; #else #define sCoSchedulerLock() #endif /** * \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. */ #if (COROUTINE_SCHEDULER_NUMBER > 1) static CoUnlock_t sCoSchedulerUnlock = NULL; #else #define sCoSchedulerUnlock() #endif /** * \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. */ #if (COROUTINE_SCHEDULER_NUMBER > 1) static void CoScheduler_Lock(void) { } #endif /** * \brief: Coroutine scheduler unlock * \note: Coroutine scheduler unlock is used to unlock the coroutine scheduler. * Only for a single scheduler. */ #if (COROUTINE_SCHEDULER_NUMBER > 1) static void CoScheduler_Unlock(void) { } #endif static void EndlessLoop(void) { while (1) { /* Do nothing */ } } static void StackOverflowCheck(uint8_t *pStack) { if (*(uint32_t *)pStack != COTASK_STACK_MARK) { EndlessLoop(); } } static void Suspend(CoScheduler *pScheduler) { /* Block the coroutine task if the task stack be brokend */ StackOverflowCheck(pScheduler->CoTaskCurrent->stackBase); #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 */ COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_SCHEDULE); } static void Resume(CoScheduler *pScheduler) { /* Block the coroutine task if the task stack be brokend */ StackOverflowCheck(pScheduler->CoTaskCurrent->stackBase); 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); } static CoScheduler *GetCurCoScheduler(void) { void *sp = NULL; CoScheduler *pScheduler = NULL; sCoSchedulerLock(); for (int i = 0; i < COROUTINE_SCHEDULER_NUMBER; i++) { pScheduler = &sCoScheduler[i]; if (pScheduler->tick == NULL) continue; /* Check sp wether in the current scheduler stack */ COROUTINE_GET_SP(sp); if (pScheduler->CoTaskCurrent && pScheduler->CoTaskCurrent->stackBase <= sp && sp < (void *)((char *)pScheduler->CoTaskCurrent->stackBase + pScheduler->CoTaskCurrent->stackSize)) { break; } } sCoSchedulerUnlock(); return pScheduler; } static CoScheduler *DistributeCoScheduler(void) { CoScheduler *pScheduler = NULL; CoScheduler *minCoScheduler = NULL; sCoSchedulerLock(); for (int i = 0; i < COROUTINE_SCHEDULER_NUMBER; i++) { pScheduler = &sCoScheduler[i]; if (pScheduler->tick == NULL) continue; if (minCoScheduler == NULL) { minCoScheduler = pScheduler; } else if (pScheduler->CoTaskListSize < minCoScheduler->CoTaskListSize) { minCoScheduler = pScheduler; } } sCoSchedulerUnlock(); return minCoScheduler; } static CoScheduler *DistributeCoSchedulerForTimer(void) { CoScheduler *pScheduler = NULL; CoScheduler *minCoScheduler = NULL; sCoSchedulerLock(); for (int i = 0; i < COROUTINE_SCHEDULER_NUMBER; i++) { pScheduler = &sCoScheduler[i]; if (pScheduler->tick == NULL) continue; if (minCoScheduler == NULL) { minCoScheduler = pScheduler; } else if (pScheduler->CoTimerListSize < minCoScheduler->CoTimerListSize) { minCoScheduler = pScheduler; } } sCoSchedulerUnlock(); return minCoScheduler; } #if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) static void* CreateCoStack() { for (int i = 0; i < COROUTINE_STATIC_STACK_MAX_NUMBER; i++) { if (*(uint32_t *)sCoStacks[i].base == 0) { *(uint32_t *)sCoStacks[i].base = COTASK_STACK_MARK; return (void *)sCoStacks[i].base; } } return NULL; } #endif #if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) static void DeleteCoStack(uint8_t *pStack) { if ((uint8_t *)(&sCoStacks[0]) <= pStack && pStack <= ((uint8_t *)(&sCoStacks[COROUTINE_STATIC_STACK_MAX_NUMBER - 1]))) { *(uint32_t *)pStack = 0; } } #endif 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 (!(sCoTasks[i].flag & COTASK_FLAG_ALLOCED)) { /* Clear the flag */ sCoTasks[i].flag = 0; /* Set the flag as allocated */ sCoTasks[i].flag |= COTASK_FLAG_ALLOCED; return &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 (&sCoTasks[0] <= CoTaskCurrent && CoTaskCurrent <= &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 */ { #if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) DeleteCoStack(CoTaskCurrent->stackBase); #endif /* 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 Task) { /* Check the coroutine task list is empty */ if (pScheduler->CoTaskList == NULL) { /* Set the coroutine task as the head and tail */ pScheduler->CoTaskList = Task; Task->next = Task; Task->prev = Task; } /* 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 = Task; Task->prev = Tail; Head->prev = Task; Task->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) { /* 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 */ StackOverflowCheck(pScheduler->CoTaskCurrent->stackBase); /* Set the coroutine task state as deleted */ pScheduler->CoTaskCurrent->state = COTASK_STATE_DELETED; /* Return control to the scheduler so that it can delete the coroutine task */ Suspend(pScheduler); } #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; } } #endif #if (COROUTINE_ENABLE_STACK_CALCULATE > 0) 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(CoTaskEntry_t entry, CoTaskCreatePara *pPara) { CoScheduler *pScheduler = NULL; CoTask_t Task = NULL; void *stackBase = NULL; size_t stackSize = 0; uint32_t flag = 0; CoTaskCreatePara para = {.pStack=NULL,.stackSize=0,.arg=NULL,.schedulerId=-1}; /* Check the entry function is valid */ if (entry == NULL) { return NULL; } /* Check the create parameter is valid */ if (pPara == NULL) { pPara = ¶ } /* Auto distribute the scheduler if the scheduler id is negative */ if (pPara->schedulerId < 0) { pScheduler = DistributeCoScheduler(); } /* Manual distribute the scheduler if the scheduler id is valid */ else { /* Check the scheduler id is valid */ if (pPara->schedulerId >= COROUTINE_SCHEDULER_NUMBER) { return NULL; } /* Get the scheduler by id */ pScheduler = &sCoScheduler[pPara->schedulerId]; } /* Check the scheduler is valid */ if (pScheduler->tick == NULL) { return NULL; } stackBase = pPara->pStack; stackSize = pPara->stackSize; /* If stackBase is empty, an automatic allocation of stackBase is attempted */ if (stackBase == NULL) { if (stackSize == 0) { /* Check the stack size is valid, or use the defualt stack size */ stackSize = COROUTINE_STACK_DEFAULT_SIZE; #if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) /* Allocate the stack memory */ stackBase = CreateCoStack(); #endif } if (stackBase == NULL) { /* Check the malloc function is valid */ if (sCoSchedulerMalloc) { /* 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 */ Task = CreateCoTask(pScheduler); if (Task == NULL) { #if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) /* Free the stack memory */ DeleteCoStack(stackBase); #endif /* Free the dynamic stack memory if it is allocated */ if (flag & COTASK_FLAG_DYNC_STACK) { sCoSchedulerFree(stackBase); } return NULL; } /* Set the coroutine task parameters */ Task->entry = entry; Task->arg = pPara->arg; Task->stackBase = stackBase; Task->stackSize = stackSize; Task->next = NULL; Task->prev = NULL; Task->state = COTASK_STATE_INIT; Task->pEvent = NULL; Task->nextRunTick = 0; Task->flag |= flag; Task->pScheduler = pScheduler; /* Insert the coroutine task into the scheduler */ InsertCoTaskList(pScheduler, Task); #if (COROUTINE_ENABLE_STACK_CALCULATE > 0) /* Initialize the stack */ Task->stackMaxUsed = 0; CoTask_StackInit((uint32_t *)stackBase, stackSize / sizeof(uint32_t)); #endif return Task; } 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)) { Suspend(pScheduler); } return 0; } CoTask_t CoTask_Self(void) { /* Get the current coroutine scheduler */ CoScheduler *pScheduler = GetCurCoScheduler(); /* Return the current coroutine task */ return pScheduler->CoTaskCurrent; } int CoTask_SchedulerId(void) { /* Get the current coroutine scheduler */ CoScheduler *pScheduler = GetCurCoScheduler(); /* Return the current coroutine scheduler id */ return (int)(pScheduler - sCoScheduler); } #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_SP(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(uint32_t CoSchedulerId) { CoScheduler *pScheduler = NULL; /* Check the coroutine scheduler is valid */ if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) { return 0; } /* Get the coroutine scheduler */ pScheduler = &sCoScheduler[CoSchedulerId]; if (pScheduler->tick == NULL) { return 0; } /* Return the current loading */ return pScheduler->curLoad; } #endif #if (COROUTINE_ENABLE_LOADING_CALCULATE > 0) uint16_t CoScheduler_MaxLoad(uint32_t CoSchedulerId) { CoScheduler *pScheduler = NULL; /* Check the coroutine scheduler is valid */ if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) { return 0; } /* Get the coroutine scheduler */ pScheduler = &sCoScheduler[CoSchedulerId]; if (pScheduler->tick == 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)) { Suspend(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 (!(sCoTimers[i].flag & COTASK_FLAG_ALLOCED)) { /* Allocate the coroutine timer */ sCoTimers[i].flag |= COTASK_FLAG_ALLOCED; return &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 (&sCoTimers[0] <= CoTimerCurrent && CoTimerCurrent <= &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_CreateP(CoTimerEntry_t entry, CoTimerCreatePara *pPara) { CoScheduler *pScheduler = NULL; CoTimer_t Timer = NULL; uint64_t tick = 0; /* Check the coroutine timer entry is valid */ if (entry == NULL) { return NULL; } /* Check the coroutine timer parameter is valid */ if (pPara == NULL) { return NULL; } /* Check the coroutine timer period is valid */ if (pPara->ms == 0 && pPara->tick == 0) { return NULL; } /* Auto distribute the scheduler if the scheduler id is negative */ if (pPara->schedulerId < 0) { pScheduler = DistributeCoSchedulerForTimer(); } /* Manual distribute the scheduler if the scheduler id is valid */ else { /* Check the scheduler id is valid */ if (pPara->schedulerId >= COROUTINE_SCHEDULER_NUMBER) { return NULL; } /* Get the scheduler by id */ pScheduler = &sCoScheduler[pPara->schedulerId]; } /* Check the scheduler is valid */ if (pScheduler->tick == NULL) { return NULL; } /* 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; } } /* Create the coroutine timer */ Timer = CreateCoTimer(pScheduler); if (Timer == NULL) { return NULL; } /* Initialize the coroutine timer */ Timer->entry = entry; Timer->periodTick = tick; Timer->nextRunTick = pScheduler->tick() + tick; Timer->next = NULL; Timer->prev = NULL; Timer->pScheduler = pScheduler; /* Insert the coroutine timer to the list */ InsertCoTimerList(pScheduler, Timer); return Timer; } 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; CoScheduler *pScheduler = NULL; CoScheduler *minCoScheduler = NULL; void *sp = NULL; size_t minSize = 0; sCoSchedulerLock(); for (int i = 0; i < COROUTINE_SCHEDULER_NUMBER; i++) { pScheduler = &sCoScheduler[i]; if (pScheduler->tick == NULL) continue; /* Get the current stack pointer */ COROUTINE_GET_SP(sp); /* 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; } } } /* Get the coroutine timer current of the closest scheduler */ Self = minCoScheduler->CoTimerCurrent; } sCoSchedulerUnlock(); return Self; } int CoTimer_SchedulerId(void) { CoTimer_t Timer = NULL; CoScheduler *pScheduler = NULL; /* Get the current coroutine timer */ Timer = CoTimer_Self(); if (Timer == NULL) { return -1; } /* Get the current coroutine scheduler */ pScheduler = Timer->pScheduler; /* Return the current coroutine scheduler id */ return (int)(pScheduler - sCoScheduler); } int CoScheduler_InitP(uint32_t CoSchedulerId, CoTick_t tick, uint32_t tickInterval, CoSchedulerInitPara *pPara) { CoSchedulerInitPara para = {.lock=NULL,.unlock=NULL,.malloc=NULL,.free=NULL}; /* Check the coroutine scheduler is valid */ if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) { return COROUTINE_E_INVALID_PARAMETER; } /* Check the coroutine scheduler tick function is valid */ if (tick == NULL) { return COROUTINE_E_INVALID_TICK; } /* Check the coroutine scheduler tick interval is valid */ if (tickInterval == 0) { return COROUTINE_E_INVALID_TICK_INTERVAL; } /* Check the coroutine scheduler init parameter is valid */ if (pPara == NULL) { pPara = ¶ } #if (COROUTINE_SCHEDULER_NUMBER > 1) /* 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; } } #endif /* 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(); } /* Get the coroutine scheduler */ CoScheduler *pScheduler = &sCoScheduler[CoSchedulerId]; /* Initialize the coroutine scheduler */ pScheduler->tick = tick; pScheduler->tickInterval = 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; return COROUTINE_E_OK; } int CoScheduler_Start(uint32_t CoSchedulerId) { int state = COSCHEDULER_STATE_INTI; CoScheduler *pScheduler = NULL; if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) { return COROUTINE_E_INVALID_PARAMETER; } pScheduler = &sCoScheduler[CoSchedulerId]; state = COSCHEDULER_GET_STATE(pScheduler); /* Initialize the coroutine scheduler stack pointer */ pScheduler->stackTop = &state; if (COSCHEDULER_STATE_INTI == state) { if (pScheduler->tick != NULL) { /* Jump directly to the scheduling state */ COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_SCHEDULE); } else { /* Jump directly to the exit state */ COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_EXIT); } } else if (COSCHEDULER_STATE_START == state) { /* Calculate the coroutine scheduler stack pointer */ /* Align with 16 bytes */ void *stackTop = (void *)((char *)pScheduler->CoTaskCurrent->stackBase + ((pScheduler->CoTaskCurrent->stackSize + 15) & ~15) - 16); /* Set the coroutine scheduler stack pointer */ COROUTINE_SET_SP(stackTop); /* Run the coroutine scheduler */ pScheduler->CoTaskRun(pScheduler); } else if (COSCHEDULER_STATE_EXIT == state) { /* Delete all coroutines in the list */ while (pScheduler->CoTaskList) { CoTask_t Task = pScheduler->CoTaskList; /* Remove the coroutine from the list */ EraseCoTaskList(pScheduler, Task); /* Delete the coroutine */ DeleteCoTask(pScheduler, Task); } /* Delete all coroutine timers in the list */ while (pScheduler->CoTimerList) { CoTimer_t Timer = pScheduler->CoTimerList; /* Erase the coroutine timer from the list */ EraseCoTimerList(pScheduler, Timer); /* Free the coroutine timer */ DeleteCoTimer(pScheduler, Timer); } /* De-initialize the coroutine scheduler */ pScheduler->tick = NULL; pScheduler->tickInterval = 0; pScheduler->CoTaskList = NULL; pScheduler->CoTaskCurrent = NULL; pScheduler->CoTaskListSize = 0; pScheduler->CoTimerList = NULL; pScheduler->CoTimerCurrent = NULL; pScheduler->CoTimerListSize = 0; pScheduler->stackTop = NULL; pScheduler->CoTaskRun = NULL; } else if (COSCHEDULER_STATE_SCHEDULE == state) { /* 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 */ COSCHEDULER_SET_STATE(pScheduler, 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; /* Resume the coroutine */ Resume(pScheduler); } /* 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); } } } else { COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_EXIT); } return 0; } int CoScheduler_Exit(uint32_t CoSchedulerId) { CoScheduler *pScheduler = NULL; /* Check the coroutine scheduler is valid */ if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) { return COROUTINE_E_INVALID_PARAMETER; } /* Get the coroutine scheduler */ pScheduler = &sCoScheduler[CoSchedulerId]; if (pScheduler->tick == NULL) { return COROUTINE_E_INVALID_TICK; } /* Yield to the next coroutine task */ COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_EXIT); return COROUTINE_E_OK; } int CoScheduler_TaskCount(uint32_t CoSchedulerId) { CoScheduler *pScheduler = NULL; /* Check the coroutine scheduler is valid */ if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) { return COROUTINE_E_INVALID_PARAMETER; } /* Get the coroutine scheduler */ pScheduler = &sCoScheduler[CoSchedulerId]; if (pScheduler->tick == NULL) { return COROUTINE_E_INVALID_TICK; } return (int)pScheduler->CoTaskListSize; } int CoScheduler_TimerCount(uint32_t CoSchedulerId) { CoScheduler *pScheduler = NULL; /* Check the coroutine scheduler is valid */ if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) { return COROUTINE_E_INVALID_PARAMETER; } /* Get the coroutine scheduler */ pScheduler = &sCoScheduler[CoSchedulerId]; if (pScheduler->tick == NULL) { return COROUTINE_E_INVALID_TICK; } return (int)pScheduler->CoTimerListSize; }