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