diff --git a/README.en.md b/README.en.md index f5f8dd5..a8c7f20 100644 --- a/README.en.md +++ b/README.en.md @@ -77,6 +77,7 @@ It has the characteristics of **simplicity, universality, and efficiency**, with | intl | 01.01.00 | [link](/doc/intl.en.md) | [path](./source/07_math) | Large integer arithmetic module | floatl | 01.01.01 | [link](/doc/floatl.en.md) | [path](./source/07_math) | Large floating-point arithmetic module | flmath | 01.00.00 | [link](/doc/flmath.en.md) | [path](./source/07_math) | Large floating-point arithmetic math module +| coroutine | 00.01.00 | [link](/doc/coroutine.en.md) | [path](./source/08_coroutine) | Coroutine module ## Usage diff --git a/README.md b/README.md index 964daa6..3b1a113 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,7 @@ varch(we-architecture,意为我们的框架库)是嵌入式C语言常用 | intl | 01.01.00 | [link](/doc/intl.md) | [path](./source/07_math) | 大型整数运算模块 | floatl | 01.01.01 | [link](/doc/floatl.md) | [path](./source/07_math) | 大型浮点数运算模块 | flmath | 01.00.00 | [link](/doc/flmath.md) | [path](./source/07_math) | 大型浮点数数学运算模块 +| coroutine | 00.01.00 | [link](/doc/coroutine.md) | [path](./source/08_coroutine) | 协程模块 ## 使用说明 diff --git a/doc/coroutine.en.md b/doc/coroutine.en.md new file mode 100644 index 0000000..e69de29 diff --git a/doc/coroutine.md b/doc/coroutine.md new file mode 100644 index 0000000..e69de29 diff --git a/makefile b/makefile index f2fa3a0..ecb18a5 100644 --- a/makefile +++ b/makefile @@ -21,6 +21,7 @@ ALGORITHM_PATH = $(WORKSPACE)/04_algorithm PARSER_PATH = $(WORKSPACE)/05_parser PERFORMANCE_PATH = $(WORKSPACE)/06_performance MATH_PATH = $(WORKSPACE)/07_math +COROUTINE_PATH = $(WORKSPACE)/08_coroutine ################################################################################## ### sources, libaries and head path @@ -33,6 +34,7 @@ INCLUDE += $(ALGORITHM_PATH) INCLUDE += $(PARSER_PATH) INCLUDE += $(PERFORMANCE_PATH) INCLUDE += $(MATH_PATH) +INCLUDE += $(COROUTINE_PATH) LIBSRCS += $(APPLICATION_PATH)/init.c LIBSRCS += $(wildcard $(APPLICATION_PATH)/console/*.c) @@ -43,6 +45,7 @@ LIBSRCS += $(wildcard $(ALGORITHM_PATH)/*.c) LIBSRCS += $(wildcard $(PARSER_PATH)/*.c) LIBSRCS += $(wildcard $(PERFORMANCE_PATH)/*.c) LIBSRCS += $(wildcard $(MATH_PATH)/*.c) +LIBSRCS += $(wildcard $(COROUTINE_PATH)/*.c) LIBLIST += m LIBLIST += pthread diff --git a/source/08_coroutine/coroutine.c b/source/08_coroutine/coroutine.c new file mode 100644 index 0000000..d28b1dd --- /dev/null +++ b/source/08_coroutine/coroutine.c @@ -0,0 +1,1234 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * 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; +} + diff --git a/source/08_coroutine/coroutine.h b/source/08_coroutine/coroutine.h new file mode 100644 index 0000000..c4a8da2 --- /dev/null +++ b/source/08_coroutine/coroutine.h @@ -0,0 +1,588 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file coroutine.h + * \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. + ********************************************************************************************************/ +#ifndef __coroutine_H +#define __coroutine_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Version infomation */ +#define COROUTINE_V_MAJOR 0 +#define COROUTINE_V_MINOR 1 +#define COROUTINE_V_PATCH 0 + +#include "coroutine_cfg.h" +#include +#include +#include +#include +#include +#include + +/** + * \brief: Coroutine scheduler + */ +typedef struct CoScheduler CoScheduler; + +/** + * \brief: Coroutine task + */ +typedef struct CoTask* CoTask_t; + +/** + * \brief: Coroutine timer + */ +typedef struct CoTimer* CoTimer_t; + +/** + * \brief: Coroutine task entry function + * \param arg: Task argument address + * \return: Task return value address + */ +typedef void *(*CoTaskEntry_t)(void *arg); + +/** + * \brief: Coroutine timer entry function + */ +typedef void (*CoTimerEntry_t)(void); + +/** + * \brief: Coroutine task run function + * \param pScheduler: Coroutine scheduler + * \param mark: Coroutine mark + * \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, uint32_t mark); + +/** + * \brief: Coroutine lock function + * \note: Use coroutines for multi-thread, protect the security of multi-thread shared resources, lock + */ +typedef void (*CoLock_t)(void); + +/** + * \brief: Coroutine unlock function + * \note: Use coroutines for multi-thread, protect the security of multi-thread shared resources, unlock + */ +typedef void (*CoUnlock_t)(void); + +/** + * \brief: Coroutine malloc function + * \param size: Allocate memory size + * \return: Allocate memory address + * \note: Use coroutines for multi-thread, protect the security of multi-thread shared resources, malloc + */ +typedef void *(*CoMalloc_t)(size_t size); + +/** + * \brief: Coroutine free function + * \param block: Free memory address + * \note: Use coroutines for multi-thread, protect the security of multi-thread shared resources, free + */ +typedef void (*CoFree_t)(void *block); + +/** + * \brief: Coroutine tick function + * \return: Current tick value + * \note: The time base on which the coroutine schedule depends, such as system tick. + */ +typedef uint64_t (*CoTick_t)(void); + +/** + * \brief: Coroutine event + * \note: Used for synchronization between coroutines + */ +typedef struct +{ + uint32_t flag; /**< Event flag, 32 bit storage, each bit can individually represent an event */ +} CoEvent; + +/** + * \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 +{ + CoScheduler *next; /**< Single-link list, next coroutine scheduler */ + + 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 */ + +#if (COROUTINE_STATIC_TASK_MAX_NUMBER > 0) + struct CoTask sCoTasks[COROUTINE_STATIC_TASK_MAX_NUMBER]; /**< Coroutine task static array */ +#endif +#if (COROUTINE_STATIC_TIMER_MAX_NUMBER > 0) + struct CoTimer sCoTimers[COROUTINE_STATIC_TIMER_MAX_NUMBER]; /**< Coroutine timer static array */ +#endif + + 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 */ +}; + +/** + * \brief: Coroutine scheduler initialize parameter + * \note: Coroutine scheduler initialize parameter is used to initialize the coroutine scheduler. + */ +typedef struct CoSchedulerInitPara +{ + CoTick_t tick; /**< Coroutine scheduler tick function */ + uint32_t tickInterval; /**< Coroutine scheduler tick interval, indicates how many ns each tick is */ + CoLock_t lock; /**< Coroutine scheduler lock function */ + CoUnlock_t unlock; /**< Coroutine scheduler unlock function */ + CoMalloc_t malloc; /**< Coroutine scheduler malloc function */ + CoFree_t free; /**< Coroutine scheduler free function */ +} CoSchedulerInitPara; + +/** + * \brief: Coroutine task create parameter + * \note: Coroutine task create parameter is used to create a coroutine task. + */ +typedef struct CoTaskCreatePara +{ + CoTaskEntry_t entry; /**< Coroutine task entry function */ + void *pStack; /**< Coroutine task stack address */ + size_t stackSize; /**< Coroutine task stack size */ + void *arg; /**< Coroutine task argument */ +} CoTaskCreatePara; + +typedef struct CoTaskWaitPara +{ + CoEvent *pEvent; + uint64_t ms; + uint64_t tick; +} CoTaskWaitPara; + +/** + * \brief: Coroutine scheduler default initialize parameter + * \note: Coroutine scheduler default initialize parameter is used to initialize the coroutine scheduler. + */ +#define COSCHEDULER_INIT_PARA(...) (CoSchedulerInitPara[1]){[0]={.tick=NULL,.tickInterval=0,.lock=NULL,.unlock=NULL,.malloc=NULL,.free=NULL},[0]={__VA_ARGS__}} + +/** + * \brief: Coroutine task default create parameter + * \note: Coroutine task default create parameter is used to create a coroutine task. + */ +#define COTASK_CREATE_PARA(...) (CoTaskCreatePara[1]){[0]={.entry=NULL,.pStack=NULL,.stackSize=0,.arg=NULL},[0]={__VA_ARGS__}} + +/** + * \brief: Coroutine wait default wait parameter + * \note: Coroutine wait default wait parameter is used to blocking task waiting for event or timeout. + */ +#define COTASK_WAIT_PARA(...) (CoTaskWaitPara[1]){[0]={.pEvent=NULL,.ms=0,.tick=0},[0]={__VA_ARGS__}} + +/** + * \brief: Coroutine task create parameter default value + * \note: Coroutine task create parameter default value is used to create a coroutine task. + */ +#define COROUTINE_E_OK (0) /**< Coroutine task create parameter default value, indicates success */ +#define COROUTINE_E_INVALID_PARAMETER (-1) /**< Coroutine task create parameter default value, indicates invalid parameter */ +#define COROUTINE_E_INVALID_TICK (-2) /**< Coroutine task create parameter default value, indicates invalid tick function */ +#define COROUTINE_E_INVALID_TICK_INTERVAL (-3) /**< Coroutine task create parameter default value, indicates invalid tick interval */ +#define COROUTINE_E_INVALID_LOCK (-4) /**< Coroutine task create parameter default value, indicates invalid lock function */ +#define COROUTINE_E_INVALID_MHOOK (-5) /**< Coroutine task create parameter default value, indicates invalid memory hook function */ +#define COROUTINE_E_TCB_MEM_STACK_FAIL (-6) /**< Coroutine task create parameter default value, indicates memory stack fail */ + +/** + * \brief: Coroutine event default value + * \note: Coroutine event default value is used to initialize the coroutine event. + */ +#define COEVENT_STATIC_VALUE ((CoEvent){.flag=0}) + +/** + * \brief: Coroutine scheduler initialize function + * \param pScheduler: [Non-default parameter] Coroutine scheduler pointer + * \param tick: [Default parameter] Coroutine scheduler tick function, must be specified + * \param tickInterval: [Default parameter] Coroutine scheduler tick interval, indicates how many us each tick is, must be specified + * \param lock: [Default parameter] Coroutine scheduler lock function, + * for use on multiple threads, you must specify as follows .lock=thread_lock + * \param unlock: [Default parameter] Coroutine scheduler unlock function, + * for use on multiple threads, you must specify as follows .unlock=thread_unlock + * \param malloc: [Default parameter] Coroutine scheduler malloc function, + * if you need to allocate memory dynamically (CoTask, CoTimer, stack, etc), + * you must specify as follows .malloc=mallloc + * \param free: [Default parameter] Coroutine scheduler free function, + * if you need to free memory dynamically (CoTask, CoTimer, stack, etc), + * you must specify as follows .free=free + * \return: Coroutine task create parameter default value, indicates success + * \note: Coroutine scheduler initialize function is used to initialize the coroutine scheduler. + * It must be called once before any other coroutine scheduler function. + * Only one initialization is allowed within a single thread + * It will initialize the coroutine scheduler with the given parameter. + * The coroutine scheduler will use the given tick function to measure the load of each task. + * The tick function must be called periodically with the given tick interval. + * The coroutine scheduler will use the given lock function to protect the shared resources. + * The coroutine scheduler will use the given malloc function to allocate memory for the tasks. + * The coroutine scheduler will use the given free function to free memory of the tasks. + * \warning: The coroutine scheduler will not check the validity of the given parameter. + * You must ensure that the given parameter is valid. + * \example: + * CoScheduler scheduler; + * CoScheduler_Init(&scheduler, GetTimerMs, 1000000); + * \example: + * CoScheduler scheduler; + * CoScheduler_Init(&scheduler, GetTimerUsec, 1000); + * \example: + * CoScheduler scheduler; + * CoScheduler_Init(&scheduler, GetTimerUsec, 1000, .lock=thread_lock, .unlock=thread_unlock); + * \example: + * CoScheduler scheduler; + * CoScheduler_Init(&scheduler, GetTimerUsec, 1000, .malloc=mallloc, .free=free); + */ +#define CoScheduler_Init(pScheduler, ...) CoScheduler_InitP(pScheduler, COSCHEDULER_INIT_PARA(__VA_ARGS__)) + +/** + * \brief: Coroutine scheduler initialize function + * \param pScheduler: Coroutine scheduler pointer + * \param pPara: Coroutine scheduler initialize parameter pointer + * \return: Coroutine task create parameter default value, indicates success + * \note: Coroutine scheduler initialize function is used to initialize the coroutine scheduler. + * It must be called once before any other coroutine scheduler function. + * Only one initialization is allowed within a single thread + * It will initialize the coroutine scheduler with the given parameter. + * The coroutine scheduler will use the given tick function to measure the load of each task. + * The tick function must be called periodically with the given tick interval. + * The coroutine scheduler will use the given lock function to protect the shared resources. + * The coroutine scheduler will use the given malloc function to allocate memory for the tasks. + * The coroutine scheduler will use the given free function to free memory of the tasks. + */ +int CoScheduler_InitP(CoScheduler *pScheduler, CoSchedulerInitPara *pPara); + +/** + * \brief: Coroutine scheduler start function + * \param pScheduler: Coroutine scheduler pointer + * \return: Coroutine task create parameter default value, indicates success + * \note: Coroutine scheduler start function is used to start the coroutine scheduler. + * It must be called once after the coroutine scheduler is initialized. + * It will start the coroutine scheduler and run the tasks. + * \example: + * CoScheduler scheduler; + * CoScheduler_Init(&scheduler, GetTimerUsec, 1000); + * testCoroutine = CoTask_Create(test, g_StackTest, sizeof(g_StackTest), 0); + * CoScheduler_Start(&scheduler); + */ +int CoScheduler_Start(CoScheduler *pScheduler); + +/** + * \brief: Coroutine scheduler current load function + * \param pScheduler: Coroutine scheduler pointer + * \return: Coroutine scheduler current load, indicates the load of the coroutine scheduler + * \note: Coroutine scheduler current load function is used to get the current load of the coroutine scheduler. + * The load is the number of ticks that the coroutine scheduler has run. + * The load is updated periodically with the given tick interval. + */ +#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0) +uint16_t CoScheduler_CurLoad(CoScheduler *pScheduler); +#endif + +/** + * \brief: Coroutine scheduler maximum load function + * \param pScheduler: Coroutine scheduler pointer + * \return: Coroutine scheduler maximum load, indicates the maximum load of the coroutine scheduler + * \note: Coroutine scheduler maximum load function is used to get the maximum load of the coroutine scheduler. + * The load is the number of ticks that the coroutine scheduler has run. + * The load is updated periodically with the given tick interval. + */ +#if (COROUTINE_ENABLE_LOADING_CALCULATE > 0) +uint16_t CoScheduler_MaxLoad(CoScheduler *pScheduler); +#endif + +/** + * \brief: Coroutine task create function + * \param entry: [Default parameter] Coroutine task entry function, indicates the entry function of the coroutine task, must be specified + * \param stack: [Default parameter] Coroutine task stack, indicates the stack of the coroutine task. + * If not specified, it is allocated via the scheduler's malloc function. + * \param stackSize: [Default parameter] Coroutine task stack size, indicates the size of the coroutine task stack + * If not specified, it is the default stack size `COROUTINE_STACK_DEFAULT_SIZE`. + * \param arg: [Default parameter] Coroutine task argument, indicates the argument of the coroutine task + * \return: Coroutine task handle, indicates the created coroutine task + * \note: Coroutine task create function is used to create a coroutine task. + * It will create a coroutine task with the given parameter. + * The coroutine task will run in the coroutine scheduler. + * \example: + * CoTask_t testCoroutine = CoTask_Create(test); + * \example: + * CoTask_t testCoroutine = CoTask_Create(test, g_StackTest, sizeof(g_StackTest)); + * \example: + * CoTask_t testCoroutine = CoTask_Create(test, .stackSize=4096); + * \example: + * int arg = 4096; + * CoTask_t testCoroutine = CoTask_Create(test, .arg=&arg); + */ +#define CoTask_Create(...) CoTask_CreateP(COTASK_CREATE_PARA(__VA_ARGS__)) + +/** + * \brief: Coroutine task create function, it is recommended to use @ref `CoTask_Create` instead + * \param pPara: Coroutine task create parameter pointer + * \return: Coroutine task handle, indicates the created coroutine task + * \note: Coroutine task create function is used to create a coroutine task. + * It will create a coroutine task with the given parameter. + * The coroutine task will run in the coroutine scheduler. + */ +CoTask_t CoTask_CreateP(CoTaskCreatePara *pPara); + +/** + * \brief: Coroutine task delete function + * \param CoTask: Coroutine task handle, indicates the coroutine task to be deleted + * \return: Coroutine task delete parameter default value, indicates success + * \note: Coroutine task delete function is used to delete a coroutine task. + * It will delete the given coroutine task. + * The coroutine task must be in the deleted state. + */ +int CoTask_Delete(CoTask_t CoTask); + +/** + * \brief: Coroutine task self function + * \return: Coroutine task handle, indicates the current coroutine task + * \note: Coroutine task self function is used to get the current coroutine task. + * It will return the handle of the current coroutine task. + */ +CoTask_t CoTask_Self(void); + +/** + * \brief: Coroutine task stack max used function + * \param CoTask: Coroutine task handle, indicates the coroutine task to get the stack max used + * \return: Coroutine task stack max used, indicates the maximum stack used of the coroutine task + * \note: Coroutine task stack max used function is used to get the maximum stack used of the coroutine task. + * The stack used is the maximum stack used of the coroutine task. + * The stack used is updated periodically with the given tick interval. + */ +#if (COROUTINE_ENABLE_STACK_CALCULATE > 0) +size_t CoTask_StackMaxUsed(CoTask_t CoTask); +#endif + +/** + * \brief: Coroutine task stack current used function + * \param CoTask: Coroutine task handle, indicates the coroutine task to get the stack current used + * \return: Coroutine task stack current used, indicates the current stack used of the coroutine task + * \note: Coroutine task stack current used function is used to get the current stack used of the coroutine task. + * The stack used is the current stack used of the coroutine task. + * The stack used is updated periodically with the given tick interval. + */ +#if (COROUTINE_ENABLE_STACK_CALCULATE > 0) +size_t CoTask_StackCurUsed(CoTask_t CoTask); +#endif + +/** + * \brief: Coroutine task wait function, blocking task waiting for event or timeout. + * \param pEvent: [Default parameter] Coroutine event pointer, indicates the event to wait + * \param ms: [Default parameter] Coroutine task wait ms, indicates the ms to wait + * \param tick: [Default parameter] Coroutine task wait tick, indicates the tick to wait + * \return: Coroutine event value, indicates the event value that triggered the coroutine task + * \note: Coroutine task wait event function is used to wait for the given event. + * @ref `CoEvent_Init` must first be called to initialize the event. + * @ref `CoEvent_Notify` notifies the occurrence of the event + * It will block the current coroutine task. + * The event value is a bit mask, each bit represents an event. + * If multiple events are triggered, the corresponding bits will be set. + * \note: Coroutine task wait ms function is used to wait for the given ms. + * It will block the current coroutine task. + * \note: Coroutine task wait tick function is used to wait for the given tick. + * It will block the current coroutine task. + * \example: + * void *test(void *arg) + * { + * while (1) + * { + * // Give up access to the scheduler without waiting, similar to yeild + * CoTask_Wait(); + * + * // Wait for 1000 ms + * CoTask_Wait(.ms=1000); + * + * // Wait for 1000000 tick + * CoTask_Wait(.tick=1000000); + * + * // Wait for 1000 ms or event, if no any events occur within 1000 ms, and will timeout blocking + * CoTask_Wait(.pEvent=&g_Event, .ms=1000); + * + * // Always wait for the event, and judge every events + * uint32_t evs = CoTask_Wait(.pEvent=&g_Event); + * if (evs & 0x01) + * { + * printf("event 0x01 triggered\n"); + * } + * if (evs & 0x02) + * { + * printf("event 0x02 triggered\n"); + * } + * } + * } + */ +#define CoTask_Wait(...) CoTask_WaitP(COTASK_WAIT_PARA(__VA_ARGS__)) + +/** + * \brief: Coroutine task wait function, it is recommended to use @ref `CoTask_Wait` instead + * \param pPara: Coroutine task wait parameter pointer + * \return: Coroutine event value, indicates the event value that triggered the coroutine task + * \note: Coroutine task wait function is used to wait for the given event or timeout. + * It will block the current coroutine task. + * The event value is a bit mask, each bit represents an event. + * If multiple events are triggered, the corresponding bits will be set. + */ +uint32_t CoTask_WaitP(CoTaskWaitPara *pPara); + +/** + * \brief: Coroutine task wait functions, the version of the macro definition used is often used + * \param m: Coroutine task wait ms, indicates the ms to wait + * \param t: Coroutine task wait tick, indicates the tick to wait + * \param e: Coroutine event pointer, indicates the event to wait + * \return: Coroutine event value, indicates the event value that triggered the coroutine task + */ +#define CoTask_WaitMs(m) CoTask_Wait(.ms=m) +#define CoTask_WaitTick(t) CoTask_Wait(.tick=t) +#define CoTask_WaitEvent(e) CoTask_Wait(.pEvent=e) +#define CoTask_WaitEventMs(e, m) CoTask_Wait(.pEvent=e, .ms=m) +#define CoTask_WaitEventTick(e, t) CoTask_Wait(.pEvent=e, .tick=t) + +/** + * \brief: Coroutine event initialize function + * \param pEvent: Coroutine event pointer, indicates the event to initialize + * \note: Coroutine event initialize function is used to initialize the given event. + * It must be called before @ref `CoTask_WaitEvent`. + * \example: + * CoEvent_Init(&g_Event); + */ +void CoEvent_Init(CoEvent *pEvent); + +/** + * \brief: Coroutine event notify function + * \param pEvent: Coroutine event pointer, indicates the event to notify + * \param evs: Coroutine event value, indicates the event value to notify + * \note: Coroutine event notify function is used to notify the occurrence of the event. + * It will set the corresponding bits in the event value. + * \example: + * CoEvent_Notify(&g_Event, 0x01); // notify event 0x01 + * \example: + * CoEvent_Notify(&g_Event, 0x01 | 0x02); // notify event 0x01 and 0x02 + */ +void CoEvent_Notify(CoEvent *pEvent, uint32_t evs); + +/** + * \brief: Coroutine timer create tick function + * \param entry: Coroutine timer entry, indicates the timer entry function + * \param tick: Coroutine timer tick, indicates the tick interval + * \return: Coroutine timer handle, indicates the created timer + * \note: Coroutine timer create tick function is used to create a timer with the given tick interval. + * The timer will call the given entry function periodically with the given tick interval. + * \example: + * CoTimer_t timer = CoTimer_CreateTick(timer_entry, 100); // create a timer with 100 tick interval + */ +CoTimer_t CoTimer_CreateTick(CoTimerEntry_t entry, uint64_t tick); + +/** + * \brief: Coroutine timer create ms function + * \param entry: Coroutine timer entry, indicates the timer entry function + * \param ms: Coroutine timer ms, indicates the ms interval + * \return: Coroutine timer handle, indicates the created timer + * \note: Coroutine timer create ms function is used to create a timer with the given ms interval. + * The timer will call the given entry function periodically with the given ms interval. + * \example: + * CoTimer_t timer = CoTimer_CreateMs(timer_entry, 100); // create a timer with 100ms interval + */ +CoTimer_t CoTimer_CreateMs(CoTimerEntry_t entry, uint32_t ms); + +/** + * \brief: Coroutine timer delete function + * \param Timer: Coroutine timer handle, indicates the timer to delete + * \note: Coroutine timer delete function is used to delete the given timer. + * It must be called after @ref `CoTimer_CreateTick` or @ref `CoTimer_CreateMs`. + * \example: + * CoTimer_Delete(timer); // delete the timer + */ +void CoTimer_Delete(CoTimer_t Timer); + +/** + * \brief: Coroutine timer self function + * \return: Coroutine timer handle, indicates the current timer + * \note: Coroutine timer self function is used to get the handle of the current timer. + * \example: + * CoTimer_t timer = CoTimer_Self(); // get the current timer + */ +CoTimer_t CoTimer_Self(void); + +#ifdef __cplusplus +} +#endif + +#endif // !__coroutine_H diff --git a/source/08_coroutine/coroutine_cfg.h b/source/08_coroutine/coroutine_cfg.h new file mode 100644 index 0000000..5cfc765 --- /dev/null +++ b/source/08_coroutine/coroutine_cfg.h @@ -0,0 +1,160 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file coroutine_cfg.h + * \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. + ********************************************************************************************************/ +#ifndef __coroutine_cfg_H +#define __coroutine_cfg_H + +/** + * \brief Coroutine static task max number + * \note This macro defines the maximum number of coroutine tasks that can be created statically + */ +#define COROUTINE_STATIC_TASK_MAX_NUMBER 32 + +/** + * \brief Coroutine static timer max number + * \note This macro defines the maximum number of coroutine timers that can be created statically + */ +#define COROUTINE_STATIC_TIMER_MAX_NUMBER 32 + +/** + * \brief Coroutine stack default size + * \note This macro defines the default size of the coroutine stack + * \note If the coroutine stack size is not specified, this value will be used + */ +#define COROUTINE_STACK_DEFAULT_SIZE 4096 // 10240 // + +/** + * \brief Coroutine enable stack calculate + * \note This macro defines whether to enable the stack calculate feature + * \note If enabled, the coroutine stack using will be calculated automatically + */ +#define COROUTINE_ENABLE_STACK_CALCULATE 1 + +/** + * \brief Coroutine enable loading calculate + * \note This macro defines whether to enable the loading calculate feature + * \note If enabled, the coroutine loading will be calculated automatically + */ +#define COROUTINE_ENABLE_LOADING_CALCULATE 1 + +/** + * \brief Coroutine loading queue size + * \note This macro defines the size of the coroutine loading queue + */ +#define COROUTINE_LOADING_CALCULATE_QSIZE 10 + +/** + * \brief Coroutine loading calculate period + * \note This macro defines the period of the coroutine loading calculate + * \note The unit is ms + */ +#define COROUTINE_LOADING_CALCULATE_PERIOD 100 + +/** + * \brief Stack get and set Pointers depending on the platform definition + * \param p Pointer to the stack + * \return None + * \note This macro is used to get and set the stack pointer depending on the platform definition + * \note For x86_32, the stack pointer is esp; for x86_64, the stack pointer is rsp; for arm, the stack pointer is sp + * \note Common platforms have been implemented, not implemented platforms need to be implemented by users + */ +#if defined(__i386__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %%esp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov %0, %%esp" : : "r" (p) : "memory") +#elif defined(__x86_64__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %%rsp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov %0, %%rsp" : : "r" (p) : "memory") +#elif defined(__arm__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") +#elif defined(__aarch64__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") +#elif defined(__mips__) +#define COROUTINE_GET_STACK(p) __asm__ volatile(".set noreorder\n\t" "move %0, $sp\n\t" ".set reorder" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile(".set noreorder\n\t" "move $sp, %0\n\t" ".set reorder" : : "r" (p) : "memory") +#elif defined(__riscv__) +#if __riscv_xlen == 32 +#define COROUTINE_GET_STACK(p) __asm__ volatile("mv %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mv sp, %0" : : "r" (p) : "memory") +#else +#define COROUTINE_GET_STACK(p) __asm__ volatile("mv %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mv sp, %0" : : "r" (p) : "memory") +#endif +#elif defined(__powerpc__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") +#elif defined(__powerpc64__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") +#elif defined(__s390__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory") +#elif defined(__s390x__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory") +#elif defined(__sparc__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory") +#elif defined(__sparcv9__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory") +#elif defined(__tile__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory") +#elif defined(__tilegx__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory") +#elif defined(__hppa__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") +#elif defined(__hppa64__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") +#elif defined(__ia64__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %0=sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov sp=%0" : : "r" (p) : "memory") +#elif defined(__loongarch__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") +#elif defined(__m68k__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") +#elif defined(__mips64__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") +#elif defined(__parisc__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") +#elif defined(__parisc64__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") +#elif defined(__s390__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory") +#elif defined(__s390x__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory") +#elif defined(__sh__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") +#elif defined(__sh64__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") +#elif defined(__xtensa__) +#define COROUTINE_GET_STACK(p) __asm__ volatile("rsr.a1 %0" : "=r" (p) : : "memory") +#define COROUTINE_SET_STACK(p) __asm__ volatile("wsr.a1 %0" : : "r" (p) : "memory") +#else +#error "Unsupported platform, please implement `COROUTINE_GET_STACK` and `COROUTINE_SET_STACK`" +#endif + +#endif // !__coroutine_cfg_H diff --git a/test/test.mk b/test/test.mk index 3743dc0..f0e51c4 100644 --- a/test/test.mk +++ b/test/test.mk @@ -47,3 +47,4 @@ TEST_LIST += slup # TEST_LIST += cpul TEST_LIST += date TEST_LIST += unitt +TEST_LIST += coroutine diff --git a/test/test_coroutine.c b/test/test_coroutine.c new file mode 100644 index 0000000..84369c8 --- /dev/null +++ b/test/test_coroutine.c @@ -0,0 +1,321 @@ +#include +#include +#include +#include +#if defined(TEST_TARGET_coroutine) +#include +#include +#include +#else +#include "init.h" +#include "command.h" +#include "unitt.h" +#include "kern.h" +#include "coroutine.h" +#endif + +#ifdef _WIN32 +#include +uint64_t GetTimerUsec(void) +{ + LARGE_INTEGER li; + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&li); + return (uint64_t)li.QuadPart * 1000000 / frequency.QuadPart; +} +#else +#include +uint64_t GetTimerUsec(void) +{ + struct timeval mstime; + uint64_t us = 0; + gettimeofday(&mstime, NULL); + us = mstime.tv_sec * 1000000 + mstime.tv_usec; + return us; +} +#endif + + +#define MUTI_THREAD_NUM 2 +static pthread_t threads[MUTI_THREAD_NUM]; +static pthread_t testThread; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static CoScheduler gMutiScheduler[MUTI_THREAD_NUM]; + +static CoScheduler gSingleScheduler; +static CoEvent gEvent = COEVENT_STATIC_VALUE; +static CoTask_t gTickCoroutine = NULL; +static CoTask_t gTockCoroutine = NULL; +static uint8_t gStackTick[1024 * 10]; +static uint8_t gStackTock[1024 * 10]; +static CoTimer_t gTimer0Handle = NULL; +static CoTimer_t gTimer1Handle = NULL; + +static void thread_lock(void) +{ + pthread_mutex_lock(&mutex); +} + +static void thread_unlock(void) +{ + pthread_mutex_unlock(&mutex); +} + +static void *tick(void *arg) +{ + while (1) + { + CoTask_Wait(.ms=1000); + printf("tick-running...\n"); + CoEvent_Notify(&gEvent, 0x02); + } +} + +static void *tock(void *arg) +{ + while (1) + { + uint32_t evs = CoTask_Wait(.pEvent=&gEvent); + if (evs & 0x01) + { + printf("event 0x01 triggered...\n"); + } + if (evs & 0x02) + { + printf("event 0x02 triggered...\n"); + } + } +} + +static void timer0(void) +{ + printf("timer0-running...\n"); +} + +static void timer1(void) +{ + printf("timer1-running... %d\n", CoScheduler_CurLoad(CoTimer_Self()->pScheduler)); + // CoEvent_Notify(&gEvent, 0x01 | 0x02); +} + +/************************************************************************************/ +/************************************* Unit Test ************************************/ +/************************************************************************************/ + +// #define EXIT_TEST +extern uint64_t unitt_clock(void); + +static int test_0(void) +{ + for (int i = 0; i < 100; i++) + { + if (0) + { + + #if defined (EXIT_TEST) + exit(0); + #endif + return UNITT_E_FAIL; + } + } + + return UNITT_E_OK; +} + +static void unitt_task(void) +{ + static UNITT_TCASE rand_tests[] = { + UNITT_TCASE(test_0), + // UNITT_TCASE(test_1), + // UNITT_TCASE(test_2), + }; + + static UNITT suites[] = { + { "coroutine suite", rand_tests, sizeof(rand_tests) / sizeof(rand_tests[0]) , unitt_clock }, + }; + + UNITT_EXE(suites); +} + +/************************************************************************************/ +/************************************* Base Test ************************************/ +/************************************************************************************/ + +static void test_base(void) +{ + +} + +void test_singleThread(void) +{ + CoScheduler_Init(&gSingleScheduler, GetTimerUsec, 1000); + gTickCoroutine = CoTask_Create(tick, gStackTick, sizeof(gStackTick)); + gTockCoroutine = CoTask_Create(tock, gStackTock, sizeof(gStackTock)); + gTimer0Handle = CoTimer_CreateMs(timer0, 1000); + gTimer1Handle = CoTimer_CreateMs(timer1, 1000); + CoScheduler_Start(&gSingleScheduler); +} + +static void *muti_thread_entry(void *arg) +{ + int threadId = *(int *)arg; + CoScheduler_Init(&gMutiScheduler[threadId], GetTimerUsec, 1000, thread_lock, thread_unlock); + if (threadId == 0) + { + gTickCoroutine = CoTask_Create(tick, gStackTick, sizeof(gStackTick)); + } + else + { + gTockCoroutine = CoTask_Create(tock, gStackTock, sizeof(gStackTock)); + } + CoScheduler_Start(&gMutiScheduler[threadId]); +} + +void test_mutiThread(void) +{ + int threadId[MUTI_THREAD_NUM]; + for (int i = 0; i < MUTI_THREAD_NUM; i++) + { + threadId[i] = i; + pthread_create(&threads[i], NULL, muti_thread_entry, &threadId[i]); + + /* You can bind thread to specific core */ + /* So that each CoScheduler can run on different core */ + } + for (int i = 0; i < MUTI_THREAD_NUM; i++) + { + pthread_join(threads[i], NULL); + } +} + + +/************************************************************************************/ +/************************************* Command ************************************/ +/************************************************************************************/ + +static void usage(void) +{ + printf( +"Usage: coroutine [opt] [arg] ...\n" +"\n" +"options:\n" +" -e Specifies the function to execute, the default is the test\n" +" Test base function\n" +" Unit test\n" +" Test single thread\n" +" Test muti thread\n" +" ...\n" +" -h Print help\n" +" -v Print version\n" +" -u [] Unit test period, unit ms, the default is 1000ms\n" +"\n" + ); +} + +static int test(int argc, char *argv[]) +{ + char *execute = NULL; + int ut_period = 1000; + + /* reset getopt */ + command_opt_init(); + + while (1) + { + int opt = command_getopt(argc, argv, "e:hvu::"); + if (opt == -1) break; + + switch (opt) + { + // Add others opt here + case 'u' : + if (command_optarg) ut_period = atoi(command_optarg); + break; + case 'e' : + execute = command_optarg; + break; + case 'v' : + printf("coroutine version %d.%d.%d\r\n", COROUTINE_V_MAJOR, COROUTINE_V_MINOR, COROUTINE_V_PATCH); + return 0; + case '?': + printf("Unknown option `%c`\r\n", command_optopt); + return -1; + case 'h' : + default: + usage(); + return 0; + } + } + + if (execute) + { + if (!strcmp(execute, "base")) + { + test_base(); + } + else if (!strcmp(execute, "ut")) + { + #if defined(TEST_TARGET_coroutine) + while (1) + { + unitt_task(); + usleep(1000 * ut_period); + } + #else + printf("create task %d\r\n", task_create(ut_period, unitt_task)); + #endif + } + else if (!strcmp(execute, "single")) + { + test_singleThread(); + } + else if (!strcmp(execute, "muti")) + { + test_mutiThread(); + } + } + else + { + test_base(); + } + +#if 0 + if (1 == command_optind) // no opt + { + + } + + for (int index = command_optind; index < argc; index++) + { + if (!strcmp(argv[index], "base")) + { + test_base(); + } + } + + if (1 == argc) + { + test_base(); + } +#endif + + return 0; +} + +/************************************************************************************/ +/************************************ Test entry ************************************/ +/************************************************************************************/ + +#if defined(TEST_TARGET_coroutine) +int main(int argc, char *argv[]) +{ + return test(argc, argv); +} +#else +void test_coroutine(void) +{ + command_export("coroutine", test); +} +init_export_app(test_coroutine); +#endif