diff --git a/makefile b/makefile index ecb18a5..03cea6e 100644 --- a/makefile +++ b/makefile @@ -68,6 +68,7 @@ ifeq ($(OS),Windows_NT) UNAME := Windows else UNAME := $(shell uname -s) + CFLAG += -D_GNU_SOURCE endif ################################################################################## diff --git a/source/08_coroutine/coroutine.c b/source/08_coroutine/coroutine.c index 3c5aa62..68ee0ca 100644 --- a/source/08_coroutine/coroutine.c +++ b/source/08_coroutine/coroutine.c @@ -10,12 +10,356 @@ * \license GPL-2.0 * \copyright Copyright (C) 2025 Lamdonn. ********************************************************************************************************/ +#include #include "coroutine.h" #if (COROUTINE_STACK_DEFAULT_SIZE < 32) #error "COROUTINE_STACK_DEFAULT_SIZE must be greater than 32" #endif +/** + * \brief Platform-specific stack get and atomic switch-and-run primitives + * \param p Pointer to the stack for COROUTINE_GET_SP + * \param new_sp The new stack pointer address for COROUTINE_SWITCH + * \param func The task entry function pointer (CoTaskRun_t) + * \param arg The scheduler pointer argument (CoScheduler*) + */ +#if defined(__i386__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%esp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + __asm__ volatile ( \ + "mov %0, %%esp\n\t" \ + "sub $12, %%esp\n\t" \ + "push %2\n\t" \ + "call *%1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (arg) \ + : "memory" \ + ); \ + } while (0) + +#elif defined(__x86_64__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%rsp, %0" : "=r" (p) : : "memory") +#if defined(_WIN32) +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("rcx") = (arg); \ + __asm__ volatile ( \ + "mov %0, %%rsp\n\t" \ + "sub $32, %%rsp\n\t" \ + "call *%1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "memory" \ + ); \ + } while (0) +#else +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("rdi") = (arg); \ + __asm__ volatile ( \ + "mov %0, %%rsp\n\t" \ + "call *%1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "memory" \ + ); \ + } while (0) +#endif + +#elif defined(__arm__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r0") = (arg); \ + __asm__ volatile ( \ + "mov sp, %0\n\t" \ + "blx %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "lr", "memory" \ + ); \ + } while (0) + +#elif defined(__aarch64__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("x0") = (arg); \ + __asm__ volatile ( \ + "mov sp, %0\n\t" \ + "blr %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "x30", "memory" \ + ); \ + } while (0) + +#elif defined(__mips__) || defined(__mips64__) +#define COROUTINE_GET_SP(p) __asm__ volatile(".set noreorder\n\t" "move %0, $sp\n\t" ".set reorder" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("$4") = (arg); \ + __asm__ volatile ( \ + ".set noreorder\n\t" \ + "move $sp, %0\n\t" \ + "jalr %1\n\t" \ + "nop\n\t" \ + ".set reorder\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "$31", "memory" \ + ); \ + } while (0) + +#elif defined(__riscv__) || defined(__riscv) +#define COROUTINE_GET_SP(p) __asm__ volatile("mv %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("a0") = (arg); \ + __asm__ volatile ( \ + "mv sp, %0\n\t" \ + "jalr %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "ra", "memory" \ + ); \ + } while (0) + +#elif defined(__powerpc__) || defined(__powerpc64__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r3") = (arg); \ + __asm__ volatile ( \ + "mr 1, %0\n\t" \ + "mtctr %1\n\t" \ + "bctrl\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "lr", "ctr", "memory" \ + ); \ + } while (0) + +#elif defined(__s390x__) +#define COROUTINE_GET_SP(p) __asm__ volatile("lgr %0, %%r15" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r2") = (arg); \ + __asm__ volatile ( \ + "lgr 15, %0\n\t" \ + "basr 14, %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "r14", "memory" \ + ); \ + } while (0) + +#elif defined(__s390__) +#define COROUTINE_GET_SP(p) __asm__ volatile("lr %0, %%r15" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r2") = (arg); \ + __asm__ volatile ( \ + "lr 15, %0\n\t" \ + "basr 14, %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "r14", "memory" \ + ); \ + } while (0) + +#elif defined(__sparc__) || defined(__sparcv9__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("%o0") = (arg); \ + __asm__ volatile ( \ + "mov %0, %%sp\n\t" \ + "call %1\n\t" \ + "nop\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "%o7", "memory" \ + ); \ + } while (0) + +#elif defined(__tile__) || defined(__tilegx__) +#define COROUTINE_GET_SP(p) __asm__ volatile("move %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r00") = (arg); \ + __asm__ volatile ( \ + "move sp, %0\n\t" \ + "jalr %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "lr", "memory" \ + ); \ + } while (0) + +#elif defined(__hppa__) || defined(__hppa64__) || defined(__parisc__) || defined(__parisc64__) +#define COROUTINE_GET_SP(p) __asm__ volatile("copy %%r30, %0" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r26") = (arg); \ + __asm__ volatile ( \ + "copy %0, %%r30\n\t" \ + "bve,l (%1), %%r2\n\t" \ + "nop\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "r2", "memory" \ + ); \ + } while (0) + +#elif defined(__ia64__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0=sp" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r32") = (arg); \ + __asm__ volatile ( \ + "mov sp=%0\n\t" \ + "mov b6=%1\n\t" \ + "br.call.sptk.many b0=b6\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "b0", "b6", "memory" \ + ); \ + } while (0) + +#elif defined(__loongarch__) +#define COROUTINE_GET_SP(p) __asm__ volatile("move %0, $sp" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("$r4") = (arg); \ + __asm__ volatile ( \ + "move $sp, %0\n\t" \ + "jirl $ra, %1, 0\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "$ra", "memory" \ + ); \ + } while (0) + +#elif defined(__m68k__) +#define COROUTINE_GET_SP(p) __asm__ volatile("move.l %%sp, %0" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + __asm__ volatile ( \ + "move.l %0, %%sp\n\t" \ + "pea (%2)\n\t" \ + "jsr (%1)\n\t" \ + : \ + : "r" (new_sp), "a" (func), "r" (arg) \ + : "memory" \ + ); \ + } while (0) + +#elif defined(__sh__) || defined(__sh64__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov r15, %0" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r4") = (arg); \ + __asm__ volatile ( \ + "mov %0, r15\n\t" \ + "jsr @%1\n\t" \ + "nop\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "pr", "memory" \ + ); \ + } while (0) + +#elif defined(__xtensa__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, a1" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("a2") = (arg); \ + __asm__ volatile ( \ + "mov a1, %0\n\t" \ + "callx0 %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "a0", "memory" \ + ); \ + } while (0) + +#elif defined(__CSKY__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, r14" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r0") = (arg); \ + __asm__ volatile ( \ + "mov r14, %0\n\t" \ + "jsr %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "r15", "memory" \ + ); \ + } while (0) + +#elif defined(__MSP430__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov r1, %0" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r12") = (arg); \ + __asm__ volatile ( \ + "mov %0, r1\n\t" \ + "call %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "memory" \ + ); \ + } while (0) + +#elif defined(__ARC__) || defined(__arc__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("r0") = (arg); \ + __asm__ volatile ( \ + "mov sp, %0\n\t" \ + "jl %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "blink", "memory" \ + ); \ + } while (0) + +#elif defined(__nds32__) +#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, $sp" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("$r0") = (arg); \ + __asm__ volatile ( \ + "mov $sp, %0\n\t" \ + "jral %1\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "$lp", "memory" \ + ); \ + } while (0) + +#elif defined(__alpha__) || defined(__alpha) +#define COROUTINE_GET_SP(p) __asm__ volatile("bis $30, $30, %0" : "=r" (p) : : "memory") +#define COROUTINE_SWITCH(new_sp, func, arg) \ + do { \ + register void *__arg0 __asm__("$16") = (arg); \ + __asm__ volatile ( \ + "bis %0, %0, $30\n\t" \ + "jsr $26, (%1)\n\t" \ + : \ + : "r" (new_sp), "r" (func), "r" (__arg0) \ + : "$26", "memory" \ + ); \ + } while (0) + +#else +#error "Unsupported platform, please implement `COROUTINE_GET_SP` and `COROUTINE_SWITCH`" +#endif + /** * \brief: Coroutine scheduler state * \note: Coroutine scheduler state is used to indicate the current state of the coroutine scheduler. @@ -48,6 +392,8 @@ #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 */ +#define COTASK_FLAG_MANUAL (0x08) /**< Coroutine task flag manual control */ +#define COTASK_FLAG_SHARED_STACK (0x10) /**< Coroutine task flag shared stack */ /** * \brief: Coroutine task stack mark @@ -86,15 +432,6 @@ typedef struct CoScheduler CoScheduler; */ typedef void (*CoTaskRun_t)(CoScheduler *pScheduler); -/** - * \brief: Coroutine task stack - * \note: Coroutine task stack is used to store the coroutine task context. - */ -struct CoStack -{ - uint8_t base[COROUTINE_STACK_DEFAULT_SIZE]; /**< Coroutine task stack base address */ -}; - /** * \brief: Coroutine task * \note: Coroutine task is a coroutine unit, each coroutine task has its own stack, @@ -117,6 +454,12 @@ struct CoTask 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 */ + jmp_buf callerEnv; /**< Caller environment for direct CoTask_Resume / CoTask_Yield */ +#if (COROUTINE_SHARED_STACK_SIZE > 0) + void *saveStack; /**< Saved stack buffer when suspended */ + size_t saveStackSize; /**< Size of saved stack buffer */ + void *saveStackSP; /**< SP pointer when saved */ +#endif }; /** @@ -175,6 +518,13 @@ struct CoScheduler #endif CoTaskRun_t CoTaskRun; /**< Coroutine scheduler task run function, prevents function compilation from being optimized inline */ + CoIdleHook_t idleHook; /**< Coroutine scheduler idle hook function */ + CoWakeupHook_t wakeupHook; /**< Coroutine scheduler wakeup hook function */ + volatile uint8_t exitReq; /**< Asynchronous request to exit scheduler loop */ +#if (COROUTINE_SHARED_STACK_SIZE > 0) + uint8_t sharedStackBuffer[COROUTINE_SHARED_STACK_SIZE] __attribute__((aligned(16))); /**< Physical shared stack buffer */ + CoTask_t sharedStackOwner; /**< Current task whose stack frame is active in sharedStackBuffer */ +#endif }; #if (COROUTINE_SCHEDULER_NUMBER > 0) @@ -191,10 +541,6 @@ struct CoTask sCoTasks[COROUTINE_STATIC_TASK_MAX_NUMBER] = {0}; /**< Coroutine timer static array */ struct CoTimer sCoTimers[COROUTINE_STATIC_TIMER_MAX_NUMBER] = {0}; #endif -#if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) -/**< Coroutine stack static array */ -struct CoStack sCoStacks[COROUTINE_STATIC_STACK_MAX_NUMBER] = {0}; -#endif /** @@ -259,18 +605,98 @@ static void EndlessLoop(void) } } -static void StackOverflowCheck(uint8_t *pStack) +static void StackOverflowCheck(CoTask_t task) { - if (*(uint32_t *)pStack != COTASK_STACK_MARK) + if (task == NULL) return; +#if (COROUTINE_SHARED_STACK_SIZE > 0) + if (task->flag & COTASK_FLAG_SHARED_STACK) return; +#endif + if (*(uint32_t *)task->stackBase != COTASK_STACK_MARK) { EndlessLoop(); } } +#if (COROUTINE_SHARED_STACK_SIZE > 0) +static void CoTask_SaveStack(CoTask_t task) +{ + if (task == NULL || !(task->flag & COTASK_FLAG_SHARED_STACK)) + { + return; + } + + if (sCoSchedulerMalloc == NULL) + { + return; + } + + char *sp = NULL; + COROUTINE_GET_SP(sp); + char *stackTop = (char *)((((uintptr_t)task->stackBase + task->stackSize) & ~15) - 16); + + if (stackTop > sp) + { + size_t used = (size_t)(stackTop - sp); + if (task->saveStackSize < used) + { + if (task->saveStack != NULL && sCoSchedulerFree != NULL) + { + sCoSchedulerFree(task->saveStack); + } + task->saveStack = sCoSchedulerMalloc(used); + task->saveStackSize = (task->saveStack != NULL) ? used : 0; + } + if (task->saveStack != NULL) + { + memcpy(task->saveStack, sp, used); + task->saveStackSP = sp; + } +#if (COROUTINE_ENABLE_STACK_CALCULATE > 0) + if (used > task->stackMaxUsed) + { + task->stackMaxUsed = used; + } +#endif + } +} + +static void CoTask_RestoreStack(CoScheduler *pScheduler, CoTask_t task) +{ + if (task == NULL || !(task->flag & COTASK_FLAG_SHARED_STACK)) + { + return; + } + + if (pScheduler == NULL) + { + pScheduler = task->pScheduler; + } + + /* Zero-copy stack cache hit! If the physical stack still holds this task's data, skip memcpy */ + if (pScheduler != NULL && pScheduler->sharedStackOwner == task) + { + return; + } + + if (task->saveStack != NULL && task->saveStackSP != NULL && task->saveStackSize > 0) + { + memcpy(task->saveStackSP, task->saveStack, task->saveStackSize); + } + + if (pScheduler != NULL) + { + pScheduler->sharedStackOwner = task; + } +} +#else +#define CoTask_SaveStack(task) +#define CoTask_RestoreStack(pScheduler, task) +#endif + static void Suspend(CoScheduler *pScheduler) { /* Block the coroutine task if the task stack be brokend */ - StackOverflowCheck(pScheduler->CoTaskCurrent->stackBase); + StackOverflowCheck(pScheduler->CoTaskCurrent); #if (COROUTINE_ENABLE_LOADING_CALCULATE > 0) /* Calculate the loading time */ @@ -287,6 +713,9 @@ static void Suspend(CoScheduler *pScheduler) pScheduler->rTick += (pScheduler->cTick - pScheduler->pTick); } #endif + /* Save shared stack if this task uses shared stack */ + CoTask_SaveStack(pScheduler->CoTaskCurrent); + /* Yield to the next coroutine task */ COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_SCHEDULE); } @@ -294,10 +723,13 @@ static void Suspend(CoScheduler *pScheduler) static void Resume(CoScheduler *pScheduler) { /* Block the coroutine task if the task stack be brokend */ - StackOverflowCheck(pScheduler->CoTaskCurrent->stackBase); + StackOverflowCheck(pScheduler->CoTaskCurrent); pScheduler->CoTaskCurrent->state = COTASK_STATE_RUNNING; + /* Restore shared stack if this task uses shared stack */ + CoTask_RestoreStack(pScheduler, pScheduler->CoTaskCurrent); + #if (COROUTINE_ENABLE_LOADING_CALCULATE > 0) /* Record the task cut time */ pScheduler->pTick = pScheduler->tick(); @@ -373,31 +805,6 @@ static CoScheduler *DistributeCoSchedulerForTimer(void) return minCoScheduler; } -#if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) -static void* CreateCoStack() -{ - for (int i = 0; i < COROUTINE_STATIC_STACK_MAX_NUMBER; i++) - { - if (*(uint32_t *)sCoStacks[i].base == 0) - { - *(uint32_t *)sCoStacks[i].base = COTASK_STACK_MARK; - return (void *)sCoStacks[i].base; - } - } - return NULL; -} -#endif - -#if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) -static void DeleteCoStack(uint8_t *pStack) -{ - if ((uint8_t *)(&sCoStacks[0]) <= pStack && pStack <= ((uint8_t *)(&sCoStacks[COROUTINE_STATIC_STACK_MAX_NUMBER - 1]))) - { - *(uint32_t *)pStack = 0; - } -} -#endif - static CoTask_t CreateCoTask(CoScheduler *pScheduler) { #if (COROUTINE_STATIC_TASK_MAX_NUMBER > 0) @@ -422,6 +829,7 @@ static CoTask_t CreateCoTask(CoScheduler *pScheduler) CoTask_t pCoroutine = (CoTask_t)sCoSchedulerMalloc(sizeof(struct CoTask)); if (pCoroutine) { + memset(pCoroutine, 0, sizeof(struct CoTask)); /* Set the flag as allocated */ pCoroutine->flag = COTASK_FLAG_ALLOCED | COTASK_FLAG_DYNC_TCB; return pCoroutine; @@ -433,33 +841,45 @@ static CoTask_t CreateCoTask(CoScheduler *pScheduler) static void DeleteCoTask(CoScheduler *pScheduler, CoTask_t CoTaskCurrent) { +#if (COROUTINE_SHARED_STACK_SIZE > 0) + if (pScheduler != NULL && pScheduler->sharedStackOwner == CoTaskCurrent) + { + pScheduler->sharedStackOwner = NULL; + } + if (CoTaskCurrent->flag & COTASK_FLAG_SHARED_STACK) + { + if (CoTaskCurrent->saveStack != NULL && sCoSchedulerFree != NULL) + { + sCoSchedulerFree(CoTaskCurrent->saveStack); + CoTaskCurrent->saveStack = NULL; + CoTaskCurrent->saveStackSize = 0; + CoTaskCurrent->saveStackSP = NULL; + } + } + else +#endif + { + /* Free the stack if it is dynamically allocated */ + if (CoTaskCurrent->flag & COTASK_FLAG_DYNC_STACK) + { + sCoSchedulerFree(CoTaskCurrent->stackBase); + } + } + #if (COROUTINE_STATIC_TASK_MAX_NUMBER > 0) - /* Check the coroutine task is statically allocated */ + /* Check if the coroutine task is statically allocated */ if (&sCoTasks[0] <= CoTaskCurrent && CoTaskCurrent <= &sCoTasks[COROUTINE_STATIC_TASK_MAX_NUMBER - 1]) { /* Clear the flag */ CoTaskCurrent->flag &= ~COTASK_FLAG_ALLOCED; return; } - else #endif - /* Check the coroutine task is dynamically allocated */ + + /* Free the TCB if it is dynamically allocated */ + if (CoTaskCurrent->flag & COTASK_FLAG_DYNC_TCB) { -#if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) - DeleteCoStack(CoTaskCurrent->stackBase); -#endif - /* Check the stack is dynamically allocated */ - if (CoTaskCurrent->flag & COTASK_FLAG_DYNC_STACK) - { - /* Free the stack */ - sCoSchedulerFree(CoTaskCurrent->stackBase); - } - /* Check the TCB is dynamically allocated */ - if (CoTaskCurrent->flag & COTASK_FLAG_DYNC_TCB) - { - /* Free the TCB */ - sCoSchedulerFree(CoTaskCurrent); - } + sCoSchedulerFree(CoTaskCurrent); } } @@ -522,7 +942,7 @@ static void CoTaskRun(CoScheduler *pScheduler) pScheduler->CoTaskCurrent->entry(pScheduler->CoTaskCurrent->arg); /* Block the coroutine task if the task stack be brokend */ - StackOverflowCheck(pScheduler->CoTaskCurrent->stackBase); + StackOverflowCheck(pScheduler->CoTaskCurrent); /* Set the coroutine task state as deleted */ pScheduler->CoTaskCurrent->state = COTASK_STATE_DELETED; @@ -617,22 +1037,30 @@ CoTask_t CoTask_CreateP(CoTaskEntry_t entry, CoTaskCreatePara *pPara) return NULL; } - stackBase = pPara->pStack; - stackSize = pPara->stackSize; - /* If stackBase is empty, an automatic allocation of stackBase is attempted */ - if (stackBase == NULL) +#if (COROUTINE_SHARED_STACK_SIZE > 0) + if (pPara->sharedStack) { - if (stackSize == 0) + /* Shared stack strictly requires user-configured dynamic memory allocator */ + if (sCoSchedulerMalloc == NULL || sCoSchedulerFree == NULL) { - /* Check the stack size is valid, or use the defualt stack size */ - stackSize = COROUTINE_STACK_DEFAULT_SIZE; -#if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) - /* Allocate the stack memory */ - stackBase = CreateCoStack(); -#endif + return NULL; } + stackBase = pScheduler->sharedStackBuffer; + stackSize = COROUTINE_SHARED_STACK_SIZE; + } + else +#endif + { + stackBase = pPara->pStack; + stackSize = pPara->stackSize; + /* If stackBase is empty, an automatic allocation of stackBase is attempted */ if (stackBase == NULL) { + if (stackSize == 0) + { + /* Check the stack size is valid, or use the defualt stack size */ + stackSize = COROUTINE_STACK_DEFAULT_SIZE; + } /* Check the malloc function is valid */ if (sCoSchedulerMalloc) { @@ -650,13 +1078,13 @@ CoTask_t CoTask_CreateP(CoTaskEntry_t entry, CoTaskCreatePara *pPara) return NULL; } } - } - else - { - /* Check the stack size is valid */ - if (stackSize == 0) + else { - return NULL; + /* Check the stack size is valid */ + if (stackSize == 0) + { + return NULL; + } } } @@ -664,10 +1092,6 @@ CoTask_t CoTask_CreateP(CoTaskEntry_t entry, CoTaskCreatePara *pPara) Task = CreateCoTask(pScheduler); if (Task == NULL) { -#if (COROUTINE_STATIC_STACK_MAX_NUMBER > 0) - /* Free the stack memory */ - DeleteCoStack(stackBase); -#endif /* Free the dynamic stack memory if it is allocated */ if (flag & COTASK_FLAG_DYNC_STACK) { @@ -686,16 +1110,39 @@ CoTask_t CoTask_CreateP(CoTaskEntry_t entry, CoTaskCreatePara *pPara) Task->state = COTASK_STATE_INIT; Task->pEvent = NULL; Task->nextRunTick = 0; + if (pPara->manual) + { + flag |= COTASK_FLAG_MANUAL; + } +#if (COROUTINE_SHARED_STACK_SIZE > 0) + if (pPara->sharedStack) + { + flag |= COTASK_FLAG_SHARED_STACK; + } +#endif Task->flag |= flag; Task->pScheduler = pScheduler; +#if (COROUTINE_SHARED_STACK_SIZE > 0) + Task->saveStack = NULL; + Task->saveStackSize = 0; + Task->saveStackSP = NULL; +#endif - /* Insert the coroutine task into the scheduler */ - InsertCoTaskList(pScheduler, Task); + /* Insert the coroutine task into the scheduler if it is an automatic task */ + if (!(Task->flag & COTASK_FLAG_MANUAL)) + { + InsertCoTaskList(pScheduler, Task); + } #if (COROUTINE_ENABLE_STACK_CALCULATE > 0) /* Initialize the stack */ Task->stackMaxUsed = 0; - CoTask_StackInit((uint32_t *)stackBase, stackSize / sizeof(uint32_t)); +#if (COROUTINE_SHARED_STACK_SIZE > 0) + if (!(Task->flag & COTASK_FLAG_SHARED_STACK)) +#endif + { + CoTask_StackInit((uint32_t *)stackBase, stackSize / sizeof(uint32_t)); + } #endif return Task; @@ -709,17 +1156,54 @@ int CoTask_Delete(CoTask_t CoTask) 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)) + if (pScheduler == NULL) { - Suspend(pScheduler); + return COROUTINE_E_INVALID_PARAMETER; + } + + CoTask->state = COTASK_STATE_DELETED; + + /* If it is a manual task */ + if (CoTask->flag & COTASK_FLAG_MANUAL) + { + /* If a manual task deletes itself while running */ + if (CoTask == pScheduler->CoTaskCurrent) + { + DeleteCoTask(pScheduler, CoTask); + longjmp(CoTask->callerEnv, 2); + } + else + { + /* Deleted from outside: free its resources directly */ + DeleteCoTask(pScheduler, CoTask); + return COROUTINE_E_OK; + } + } + else + { + /* Automatic task managed by scheduler */ + /* If an automatic task deletes itself while running */ + if (CoTask == pScheduler->CoTaskCurrent) + { + /* Return control to the scheduler so that it can clean up the task */ + if (COSCHEDULER_STATE_RUNNING != setjmp(pScheduler->CoTaskCurrent->env)) + { + Suspend(pScheduler); + } + } + else + { + /* Deleted from outside: remove from list and delete */ + sCoSchedulerLock(); + EraseCoTaskList(pScheduler, CoTask); + DeleteCoTask(pScheduler, CoTask); + sCoSchedulerUnlock(); + return COROUTINE_E_OK; + } } - return 0; + return COROUTINE_E_OK; } CoTask_t CoTask_Self(void) @@ -730,6 +1214,101 @@ CoTask_t CoTask_Self(void) return pScheduler->CoTaskCurrent; } +static void CoTaskRun_Direct(void *arg) +{ + CoTask_t task = (CoTask_t)arg; + task->state = COTASK_STATE_RUNNING; + + /* Execute the coroutine entry function */ + task->entry(task->arg); + + /* Check stack overflow */ + StackOverflowCheck(task); + task->state = COTASK_STATE_DELETED; + + /* Return to caller of CoTask_Resume with code 2 indicating task finished */ + longjmp(task->callerEnv, 2); +} + +int CoTask_Resume(CoTask_t task) +{ + /* Enforce valid task pointer and strict manual mode contract */ + if (task == NULL || !(task->flag & COTASK_FLAG_MANUAL)) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + if (task->state == COTASK_STATE_DELETED) + { + return 1; /* Already finished */ + } + + CoTask_t prevTask = NULL; + /* Update scheduler's current task pointer so CoTask_Self() works */ + if (task->pScheduler != NULL) + { + prevTask = task->pScheduler->CoTaskCurrent; + task->pScheduler->CoTaskCurrent = task; + } + + int ret = setjmp(task->callerEnv); + if (ret == 0) + { + if (task->state == COTASK_STATE_INIT) + { +#if (COROUTINE_SHARED_STACK_SIZE > 0) + if ((task->flag & COTASK_FLAG_SHARED_STACK) && task->pScheduler != NULL) + { + task->pScheduler->sharedStackOwner = task; + } +#endif + void *stackTop = (void *)((((uintptr_t)task->stackBase + task->stackSize) & ~15) - 16); + COROUTINE_SWITCH(stackTop, CoTaskRun_Direct, task); + } + else if (task->state == COTASK_STATE_SUSPEND) + { + task->state = COTASK_STATE_RUNNING; + CoTask_RestoreStack(task->pScheduler, task); + longjmp(task->env, 1); + } + } + + /* Restore previous current task */ + if (task->pScheduler != NULL) + { + task->pScheduler->CoTaskCurrent = prevTask; + } + + if (ret == 2 || task->state == COTASK_STATE_DELETED) + { + return 1; /* Task completed */ + } + + return COROUTINE_E_OK; /* Task yielded / suspended */ +} + +int CoTask_Wakeup(CoTask_t task) +{ + if (task == NULL || task->state == COTASK_STATE_DELETED) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + sCoSchedulerLock(); + /* Clear remaining sleep ticks and mark ready for central scheduler */ + task->nextRunTick = 0; + task->state = COTASK_STATE_READY; + sCoSchedulerUnlock(); + + /* Unblock the scheduler's idleHook if configured */ + if (task->pScheduler != NULL && task->pScheduler->wakeupHook != NULL) + { + task->pScheduler->wakeupHook((uint32_t)(task->pScheduler - sCoScheduler)); + } + + return COROUTINE_E_OK; +} + int CoTask_SchedulerId(void) { /* Get the current coroutine scheduler */ @@ -753,6 +1332,13 @@ size_t CoTask_StackMaxUsed(CoTask_t CoTask) return 0; } +#if (COROUTINE_SHARED_STACK_SIZE > 0) + if (CoTask->flag & COTASK_FLAG_SHARED_STACK) + { + return CoTask->stackMaxUsed; + } +#endif + /* Calculate the stack max used */ CoTask->stackMaxUsed = CoTask_StackUsed((uint32_t *)CoTask->stackBase, CoTask->stackSize / sizeof(uint32_t)); @@ -845,13 +1431,19 @@ uint32_t CoTask_WaitP(CoTaskWaitPara *pPara) /* Get the current coroutine scheduler */ pScheduler = GetCurCoScheduler(); + if (pScheduler == NULL || pScheduler->CoTaskCurrent == NULL) + { + return 0; + } + + CoTask_t task = pScheduler->CoTaskCurrent; /* 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) + if (pPara->ms != 0 && pScheduler->tickInterval != 0) { tick = pPara->ms * 1000000 / pScheduler->tickInterval; } @@ -869,23 +1461,36 @@ uint32_t CoTask_WaitP(CoTaskWaitPara *pPara) /* Set the next run tick */ if (tick == 0 && pPara->pEvent != NULL) { - pScheduler->CoTaskCurrent->nextRunTick = 0; + task->nextRunTick = 0; } - else + else if (pScheduler->tick != NULL) { - pScheduler->CoTaskCurrent->nextRunTick = pScheduler->tick() + tick; + task->nextRunTick = pScheduler->tick() + tick; } /* Set the event */ - pScheduler->CoTaskCurrent->pEvent = pPara->pEvent; + task->pEvent = pPara->pEvent; /* Set the coroutine task state as suspend */ - pScheduler->CoTaskCurrent->state = COTASK_STATE_SUSPEND; + task->state = COTASK_STATE_SUSPEND; - /* Return control to the scheduler so that it can schedule other tasks */ - if (COSCHEDULER_STATE_RUNNING != setjmp(pScheduler->CoTaskCurrent->env)) + /* If running in direct/manual mode (resumed via CoTask_Resume), yield back directly to caller */ + if (task->flag & COTASK_FLAG_MANUAL) { - Suspend(pScheduler); + if (setjmp(task->env) == 0) + { + CoTask_SaveStack(task); + longjmp(task->callerEnv, 1); + } + task->state = COTASK_STATE_RUNNING; + } + else + { + /* Return control to the scheduler so that it can schedule other tasks */ + if (COSCHEDULER_STATE_RUNNING != setjmp(task->env)) + { + Suspend(pScheduler); + } } if (pPara->pEvent != NULL) @@ -903,13 +1508,7 @@ uint32_t CoTask_WaitP(CoTaskWaitPara *pPara) 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) + if (pEvent == NULL || evs == 0) { return; } @@ -918,6 +1517,15 @@ void CoEvent_Notify(CoEvent *pEvent, uint32_t evs) /* Set the event flag */ pEvent->flag |= evs; sCoSchedulerUnlock(); + + /* Unblock all schedulers that might have tasks waiting on this event */ + for (int i = 0; i < COROUTINE_SCHEDULER_NUMBER; i++) + { + if (sCoScheduler[i].tick != NULL && sCoScheduler[i].wakeupHook != NULL) + { + sCoScheduler[i].wakeupHook(i); + } + } } static CoTimer_t CreateCoTimer(CoScheduler *pScheduler) @@ -1192,9 +1800,62 @@ int CoTimer_SchedulerId(void) return (int)(pScheduler - sCoScheduler); } +int Coroutine_InitP(CoroutineInitPara *pPara) +{ + if (pPara == NULL) + { + return COROUTINE_E_INVALID_PARAMETER; + } + +#if (COROUTINE_SCHEDULER_NUMBER > 1) + if (pPara->lock && pPara->unlock) + { + sCoSchedulerLock = pPara->lock; + sCoSchedulerUnlock = pPara->unlock; + } + else if (pPara->lock == NULL && pPara->unlock == NULL && sCoSchedulerLock == NULL) + { + sCoSchedulerLock = CoScheduler_Lock; + sCoSchedulerUnlock = CoScheduler_Unlock; + } +#endif + + sCoSchedulerLock(); + if (pPara->malloc && pPara->free) + { + sCoSchedulerMalloc = pPara->malloc; + sCoSchedulerFree = pPara->free; + } + sCoSchedulerUnlock(); + + return COROUTINE_E_OK; +} + +int Coroutine_HasMemHook(void) +{ + return (sCoSchedulerMalloc != NULL && sCoSchedulerFree != NULL) ? 1 : 0; +} + +void *Coroutine_Malloc(size_t size) +{ + if (sCoSchedulerMalloc == NULL || size == 0) + { + return NULL; + } + return sCoSchedulerMalloc(size); +} + +void Coroutine_Free(void *ptr) +{ + if (sCoSchedulerFree != NULL && ptr != NULL) + { + sCoSchedulerFree(ptr); + } +} + int CoScheduler_InitP(uint32_t CoSchedulerId, CoTick_t tick, uint32_t tickInterval, CoSchedulerInitPara *pPara) { - CoSchedulerInitPara para = {.lock=NULL,.unlock=NULL,.malloc=NULL,.free=NULL}; + CoSchedulerInitPara para = {.idleHook=NULL}; /* Check the coroutine scheduler is valid */ if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) { @@ -1215,58 +1876,15 @@ int CoScheduler_InitP(uint32_t CoSchedulerId, CoTick_t tick, uint32_t tickInterv { pPara = ¶ } + + /* Configure default lock if not yet configured */ #if (COROUTINE_SCHEDULER_NUMBER > 1) - /* Check the coroutine scheduler lock function is valid */ - if (sCoSchedulerLock && sCoSchedulerUnlock) + if (sCoSchedulerLock == NULL) { - /* Ensure that different schedulers use the same lock */ - if (pPara->lock != sCoSchedulerLock || pPara->unlock != sCoSchedulerUnlock) - { - return COROUTINE_E_INVALID_LOCK; - } - } - else - { - /* Specifies the passed lock */ - if (pPara->lock && pPara->unlock) - { - sCoSchedulerLock = pPara->lock; - sCoSchedulerUnlock = pPara->unlock; - } - /* Use the default lock */ - else - { - sCoSchedulerLock = CoScheduler_Lock; - sCoSchedulerUnlock = CoScheduler_Unlock; - } - } -#endif - /* Check the coroutine scheduler malloc function is valid */ - if (sCoSchedulerMalloc && sCoSchedulerFree) - { - /* Ensure that different schedulers use the same malloc */ - if (pPara->malloc != sCoSchedulerMalloc || pPara->free != sCoSchedulerFree) - { - return COROUTINE_E_INVALID_MHOOK; - } - } - else - { - sCoSchedulerLock(); - /* Specifies the passed malloc */ - if (pPara->malloc && pPara->free) - { - sCoSchedulerMalloc = pPara->malloc; - sCoSchedulerFree = pPara->free; - } - /* Not use */ - else - { - sCoSchedulerMalloc = NULL; - sCoSchedulerFree = NULL; - } - sCoSchedulerUnlock(); + sCoSchedulerLock = CoScheduler_Lock; + sCoSchedulerUnlock = CoScheduler_Unlock; } +#endif /* Get the coroutine scheduler */ CoScheduler *pScheduler = &sCoScheduler[CoSchedulerId]; @@ -1291,6 +1909,49 @@ int CoScheduler_InitP(uint32_t CoSchedulerId, CoTick_t tick, uint32_t tickInterv pScheduler->CoTimerListSize = 0; pScheduler->stackTop = NULL; pScheduler->CoTaskRun = CoTaskRun; + pScheduler->idleHook = pPara->idleHook; + pScheduler->wakeupHook = pPara->wakeupHook; + pScheduler->exitReq = 0; +#if (COROUTINE_SHARED_STACK_SIZE > 0) + pScheduler->sharedStackOwner = NULL; +#endif + + return COROUTINE_E_OK; +} + +int CoScheduler_SetIdleHook(uint32_t CoSchedulerId, CoIdleHook_t idleHook) +{ + if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + sCoScheduler[CoSchedulerId].idleHook = idleHook; + return COROUTINE_E_OK; +} + +int CoScheduler_SetWakeupHook(uint32_t CoSchedulerId, CoWakeupHook_t wakeupHook) +{ + if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + sCoScheduler[CoSchedulerId].wakeupHook = wakeupHook; + return COROUTINE_E_OK; +} + +int CoScheduler_Wakeup(uint32_t CoSchedulerId) +{ + if (CoSchedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + if (sCoScheduler[CoSchedulerId].wakeupHook != NULL) + { + sCoScheduler[CoSchedulerId].wakeupHook(CoSchedulerId); + } return COROUTINE_E_OK; } @@ -1326,13 +1987,17 @@ int CoScheduler_Start(uint32_t CoSchedulerId) } else if (COSCHEDULER_STATE_START == state) { +#if (COROUTINE_SHARED_STACK_SIZE > 0) + if (pScheduler->CoTaskCurrent->flag & COTASK_FLAG_SHARED_STACK) + { + pScheduler->sharedStackOwner = pScheduler->CoTaskCurrent; + } +#endif /* Calculate the coroutine scheduler stack pointer */ - /* Align with 16 bytes */ - void *stackTop = (void *)((char *)pScheduler->CoTaskCurrent->stackBase + ((pScheduler->CoTaskCurrent->stackSize + 15) & ~15) - 16); - /* Set the coroutine scheduler stack pointer */ - COROUTINE_SET_SP(stackTop); - /* Run the coroutine scheduler */ - pScheduler->CoTaskRun(pScheduler); + /* Align both stackBase and stackSize downward to a strict 16-byte boundary for ARM64 / universal safety */ + void *stackTop = (void *)((((uintptr_t)pScheduler->CoTaskCurrent->stackBase + pScheduler->CoTaskCurrent->stackSize) & ~15) - 16); + /* Switch stack and run the task using atomic context switch primitive */ + COROUTINE_SWITCH(stackTop, pScheduler->CoTaskRun, pScheduler); } else if (COSCHEDULER_STATE_EXIT == state) { @@ -1367,12 +2032,22 @@ int CoScheduler_Start(uint32_t CoSchedulerId) pScheduler->CoTimerListSize = 0; pScheduler->stackTop = NULL; pScheduler->CoTaskRun = NULL; + pScheduler->idleHook = NULL; } else if (COSCHEDULER_STATE_SCHEDULE == state) { /* Infinite loop to run the coroutine scheduler */ while (1) { + if (pScheduler->exitReq) + { + pScheduler->exitReq = 0; + COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_EXIT); + } + + uint8_t hasActive = 0; + uint64_t nextWakeTick = 0; + #if (COROUTINE_ENABLE_LOADING_CALCULATE > 0) /* Calculate the coroutine scheduler tick */ if (pScheduler->sTick == 0) @@ -1447,6 +2122,7 @@ int CoScheduler_Start(uint32_t CoSchedulerId) /* Check the coroutine is initialized */ if (tCoroutine->state == COTASK_STATE_INIT) { + hasActive = 1; /* Set the current coroutine to the initialized coroutine */ pScheduler->CoTaskCurrent = tCoroutine; /* Jump to the coroutine */ @@ -1455,6 +2131,7 @@ int CoScheduler_Start(uint32_t CoSchedulerId) /* Check the coroutine is ready */ else if (tCoroutine->state == COTASK_STATE_READY) { + hasActive = 1; /* Set the current coroutine to the ready coroutine */ pScheduler->CoTaskCurrent = tCoroutine; @@ -1464,6 +2141,7 @@ int CoScheduler_Start(uint32_t CoSchedulerId) /* Check the coroutine is running */ else if (tCoroutine->state == COTASK_STATE_RUNNING) { + hasActive = 1; tCoroutine->state = COTASK_STATE_INIT; continue; } @@ -1471,22 +2149,44 @@ int CoScheduler_Start(uint32_t CoSchedulerId) else if (tCoroutine->state == COTASK_STATE_SUSPEND) { /* Check the next run time is reached */ - if (((tCoroutine->nextRunTick != 0 && pScheduler->tick() > tCoroutine->nextRunTick) || + 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 */ + /* Set the coroutine to ready and immediately resume execution */ tCoroutine->state = COTASK_STATE_READY; - continue; + hasActive = 1; + pScheduler->CoTaskCurrent = tCoroutine; + Resume(pScheduler); + } + else + { + /* Track earliest upcoming wake-up tick */ + if (tCoroutine->nextRunTick != 0) + { + if (nextWakeTick == 0 || tCoroutine->nextRunTick < nextWakeTick) + { + nextWakeTick = tCoroutine->nextRunTick; + } + } } } /* Check the coroutine is deleted */ else if (tCoroutine->state == COTASK_STATE_DELETED) { + hasActive = 1; + CoTask_t nextTask = tCoroutine->next; /* Remove the coroutine from the list */ EraseCoTaskList(pScheduler, tCoroutine); /* Delete the coroutine */ - DeleteCoTask(pScheduler, tCoroutine); + DeleteCoTask(pScheduler, tCoroutine); + if (pScheduler->CoTaskList == NULL) + { + pScheduler->CoTaskCurrent = NULL; + break; + } + tCoroutine = nextTask; + continue; } /* Check the coroutine is unknown */ else @@ -1512,6 +2212,7 @@ int CoScheduler_Start(uint32_t CoSchedulerId) /* Check the timer is expired */ if (pScheduler->tick() >= tTimer->nextRunTick) { + hasActive = 1; /* Set the current timer to the expired timer */ pScheduler->CoTimerCurrent = tTimer; #if (COROUTINE_ENABLE_LOADING_CALCULATE > 0) @@ -1531,11 +2232,48 @@ int CoScheduler_Start(uint32_t CoSchedulerId) pScheduler->CoTimerCurrent = NULL; tTimer->nextRunTick = pScheduler->tick() + tTimer->periodTick; } + else + { + /* Track earliest upcoming timer wake-up tick */ + if (tTimer->nextRunTick != 0) + { + if (nextWakeTick == 0 || tTimer->nextRunTick < nextWakeTick) + { + nextWakeTick = tTimer->nextRunTick; + } + } + } /* Check the next timer */ tTimer = tTimer->next; } while (tTimer != pScheduler->CoTimerList); } + + /* If no task or timer executed in this pass, notify the idle hook if configured */ + if (!hasActive && pScheduler->idleHook != NULL) + { + uint64_t nowTick = pScheduler->tick(); + + /* If an upcoming timer has already expired, do not sleep! Loop immediately to run it */ + if (nextWakeTick != 0 && nextWakeTick <= nowTick) + { + continue; + } + + uint32_t idleUs = 0; + if (nextWakeTick > nowTick) + { + /* Convert tick difference to microseconds: diffTick * tickInterval(ns) / 1,000 */ + uint64_t diffNs = (nextWakeTick - nowTick) * pScheduler->tickInterval; + idleUs = (uint32_t)(diffNs / 1000); + if (idleUs == 0 && diffNs > 0) + { + idleUs = 1; + } + } + + pScheduler->idleHook(CoSchedulerId, idleUs); + } } } else @@ -1562,8 +2300,20 @@ int CoScheduler_Exit(uint32_t CoSchedulerId) return COROUTINE_E_INVALID_TICK; } - /* Yield to the next coroutine task */ - COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_EXIT); + /* If called from within this scheduler's own task, exit immediately */ + if (pScheduler == GetCurCoScheduler()) + { + COSCHEDULER_SET_STATE(pScheduler, COSCHEDULER_STATE_EXIT); + } + else + { + /* Called from another thread/scheduler: set async exit request */ + pScheduler->exitReq = 1; + if (pScheduler->wakeupHook != NULL) + { + pScheduler->wakeupHook(CoSchedulerId); + } + } return COROUTINE_E_OK; } diff --git a/source/08_coroutine/coroutine.h b/source/08_coroutine/coroutine.h index 1ae342d..1ea82ce 100644 --- a/source/08_coroutine/coroutine.h +++ b/source/08_coroutine/coroutine.h @@ -86,6 +86,21 @@ typedef void (*CoFree_t)(void *block); */ typedef uint64_t (*CoTick_t)(void); +/** + * \brief: Coroutine scheduler idle hook function pointer + * \param schedulerId: Current scheduler ID + * \param idleUs: Estimated idle duration in microseconds until the earliest task or timer wake-up (0 if unknown or none) + * \note: Can be used to execute WFI on MCU, epoll_wait on Linux, or custom sleep/reactor logic. + */ +typedef void (*CoIdleHook_t)(uint32_t schedulerId, uint32_t idleUs); + +/** + * \brief: Coroutine scheduler wakeup hook function pointer + * \param schedulerId: Target scheduler ID to wake up + * \note: Used to unblock or interrupt an in-progress idleHook (e.g. pthread_cond_signal, eventfd write, SetEvent) + */ +typedef void (*CoWakeupHook_t)(uint32_t schedulerId); + /** * \brief: Coroutine event * \note: Used for synchronization between coroutines @@ -95,16 +110,26 @@ typedef struct uint32_t flag; /**< Event flag, 32 bit storage, each bit can individually represent an event */ } CoEvent; +/** + * \brief: Coroutine global runtime environment parameters + * \note: Configures global dynamic memory allocators and thread synchronization locks + */ +typedef struct CoroutineInitPara +{ + CoLock_t lock; /**< Global coroutine thread lock function */ + CoUnlock_t unlock; /**< Global coroutine thread unlock function */ + CoMalloc_t malloc; /**< Global dynamic memory allocator function */ + CoFree_t free; /**< Global dynamic memory free function */ +} CoroutineInitPara; + /** * \brief: Coroutine scheduler initialize parameter - * \note: Coroutine scheduler initialize parameter is used to initialize the coroutine scheduler. + * \note: Coroutine scheduler initialize parameter is used to initialize the coroutine scheduler instance. */ typedef struct CoSchedulerInitPara { - 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 */ + CoIdleHook_t idleHook; /**< Coroutine scheduler idle hook function */ + CoWakeupHook_t wakeupHook; /**< Coroutine scheduler wakeup hook function */ } CoSchedulerInitPara; /** @@ -117,6 +142,8 @@ typedef struct CoTaskCreatePara size_t stackSize; /**< Coroutine task stack size */ void *arg; /**< Coroutine task argument */ int schedulerId; /**< Coroutine task scheduler id */ + uint8_t manual; /**< 1: Manual control mode (libco style, CoScheduler will not auto-run), 0: Auto scheduled */ + uint8_t sharedStack; /**< 1: Use shared stack (requires COROUTINE_SHARED_STACK_SIZE > 0), 0: Dedicated stack */ } CoTaskCreatePara; /** @@ -159,43 +186,73 @@ typedef struct CoTaskWaitPara */ #define COEVENT_STATIC_VALUE ((CoEvent){.flag=0}) +/** + * \brief: Coroutine global runtime environment initialize function + * \param lock: [Default parameter] Coroutine thread lock function, + * for use on multiple threads, you must specify as follows .lock=thread_lock + * \param unlock: [Default parameter] Coroutine thread unlock function, + * for use on multiple threads, you must specify as follows .unlock=thread_unlock + * \param malloc: [Default parameter] Coroutine dynamic memory allocator function, + * if you need to allocate memory dynamically (shared stack, CoTask, etc), + * you must specify as follows .malloc=malloc + * \param free: [Default parameter] Coroutine dynamic memory free function, + * if you need to free memory dynamically (shared stack, CoTask, etc), + * you must specify as follows .free=free + * \return: COROUTINE_E_OK on success, or negative error code + * \note: Configures global runtime infrastructure (memory hooks and thread locks) independently of specific schedulers. + * It is recommended to call this once at module or process startup. + * \example: + * Coroutine_Init(.malloc=malloc, .free=free); + * \example: + * Coroutine_Init(.lock=thread_lock, .unlock=thread_unlock, .malloc=malloc, .free=free); + */ +#define Coroutine_Init(...) Coroutine_InitP((CoroutineInitPara[1]){[0]={.lock=NULL,.unlock=NULL,.malloc=NULL,.free=NULL,__VA_ARGS__}}) + +/** + * \brief: Coroutine global runtime environment initialize function, it is recommended to use @ref `Coroutine_Init` instead + * \param pPara: Coroutine global environment parameter pointer + * \return: COROUTINE_E_OK on success, or negative error code + * \note: Configures global runtime infrastructure independently of specific scheduler instances. + */ +int Coroutine_InitP(CoroutineInitPara *pPara); + +/** + * \brief: Check if global dynamic memory hooks (malloc and free) are configured + * \return: 1 if dynamic memory hooks are configured, 0 if in pure static mode + */ +int Coroutine_HasMemHook(void); + +/** + * \brief: Allocate memory using the configured coroutine dynamic allocator + * \param size: Number of bytes to allocate + * \return: Pointer to allocated memory, or NULL if not configured or allocation failed + */ +void *Coroutine_Malloc(size_t size); + +/** + * \brief: Free memory using the configured coroutine dynamic deallocator + * \param ptr: Pointer to memory to free + */ +void Coroutine_Free(void *ptr); + /** * \brief: Coroutine scheduler initialize function - * \param pScheduler: [Non-default parameter] Coroutine scheduler pointer + * \param CoSchedulerId: Coroutine scheduler ID * \param tick: [Non-default parameter] Coroutine scheduler tick function, must be specified * \param tickInterval: [Non-default parameter] Coroutine scheduler tick interval, indicates how many ns 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 + * \param idleHook: [Default parameter] Coroutine scheduler idle hook function, + * you can specify as follows .idleHook=my_idle_hook * \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. + * \note: Coroutine scheduler initialize function is used to initialize the coroutine scheduler instance. + * It must be called once before any other coroutine scheduler function for that scheduler. * 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_Init(0, GetTimerMs, 1000000); * \example: * CoScheduler_Init(0, GetTimerUsec, 1000); * \example: - * CoScheduler_Init(0, GetTimerUsec, 1000, .lock=thread_lock, .unlock=thread_unlock); - * \example: - * CoScheduler_Init(0, GetTimerUsec, 1000, .malloc=mallloc, .free=free); + * CoScheduler_Init(0, GetTimerUsec, 1000, .idleHook=my_idle_hook, .wakeupHook=my_wakeup_hook); */ -#define CoScheduler_Init(CoSchedulerId, tick, tickInterval, ...) CoScheduler_InitP(CoSchedulerId, tick, tickInterval, (CoSchedulerInitPara[1]){[0]={.lock=NULL,.unlock=NULL,.malloc=NULL,.free=NULL,__VA_ARGS__}}) +#define CoScheduler_Init(CoSchedulerId, tick, tickInterval, ...) CoScheduler_InitP(CoSchedulerId, tick, tickInterval, (CoSchedulerInitPara[1]){[0]={.idleHook=NULL,.wakeupHook=NULL,__VA_ARGS__}}) /** * \brief: Coroutine scheduler initialize function @@ -216,6 +273,31 @@ typedef struct CoTaskWaitPara */ int CoScheduler_InitP(uint32_t CoSchedulerId, CoTick_t tick, uint32_t tickInterval, CoSchedulerInitPara *pPara); +/** + * \brief: Set the coroutine scheduler idle hook function + * \param CoSchedulerId: Coroutine scheduler ID + * \param idleHook: User-defined idle callback + * \return: COROUTINE_E_OK on success, or negative error code + * \note: When there are no runnable tasks, the scheduler invokes this hook with the estimated idle time (us). + */ +int CoScheduler_SetIdleHook(uint32_t CoSchedulerId, CoIdleHook_t idleHook); + +/** + * \brief: Set the coroutine scheduler wakeup hook function + * \param CoSchedulerId: Coroutine scheduler ID + * \param wakeupHook: User-defined wakeup callback to unblock idleHook + * \return: COROUTINE_E_OK on success, or negative error code + * \note: Invoked whenever an event is notified or task is woken up to immediately interrupt an in-progress idle sleep. + */ +int CoScheduler_SetWakeupHook(uint32_t CoSchedulerId, CoWakeupHook_t wakeupHook); + +/** + * \brief: Wake up a specific coroutine scheduler, unblocking its idleHook immediately + * \param CoSchedulerId: Coroutine scheduler ID + * \return: COROUTINE_E_OK on success, or negative error code + */ +int CoScheduler_Wakeup(uint32_t CoSchedulerId); + /** * \brief: Coroutine scheduler start function * \param pScheduler: Coroutine scheduler pointer @@ -304,7 +386,7 @@ uint16_t CoScheduler_MaxLoad(uint32_t CoSchedulerId); * int arg = 4096; * CoTask_t testCoroutine = CoTask_Create(test, .arg=&arg); */ -#define CoTask_Create(entry, ...) CoTask_CreateP(entry, (CoTaskCreatePara[1]){[0]={.pStack=NULL,.stackSize=0,.arg=NULL,.schedulerId=-1,__VA_ARGS__}}) +#define CoTask_Create(entry, ...) CoTask_CreateP(entry, (CoTaskCreatePara[1]){[0]={.pStack=NULL,.stackSize=0,.arg=NULL,.schedulerId=-1,.manual=0,.sharedStack=0,__VA_ARGS__}}) /** * \brief: Coroutine task create function, it is recommended to use @ref `CoTask_Create` instead @@ -334,6 +416,32 @@ int CoTask_Delete(CoTask_t CoTask); */ CoTask_t CoTask_Self(void); +/** + * \brief: Explicitly resume/step into a specific manual coroutine task (point-to-point libco style) + * \param task: Coroutine task handle (must be created with .manual = 1) + * \return: COROUTINE_E_OK (0) if task yielded back, 1 if task finished execution, or negative error code (e.g. COROUTINE_E_INVALID_PARAMETER if not a manual task) + * \note: This allows running and stepping manual coroutines directly without running CoScheduler_Start. + * \example: + * CoTask_t co = CoTask_Create(my_task, .manual = 1); + * CoTask_Resume(co); + */ +int CoTask_Resume(CoTask_t task); + +/** + * \brief: Wake up a suspended coroutine managed by the central scheduler, marking it immediately ready + * \param task: Coroutine task handle + * \return: COROUTINE_E_OK (0) on success, or negative error code + * \note: Clears remaining sleep ticks, allowing the central scheduler to execute it in the next scheduling pass. + */ +int CoTask_Wakeup(CoTask_t task); + +/** + * \brief: Explicitly yield the current coroutine task (equivalent to CoTask_Wait(), libco style) + * \note: In manual/direct mode (resumed via CoTask_Resume), yields directly back to CoTask_Resume's caller. + * In scheduler mode (managed by CoScheduler_Start), yields time-slice to other tasks. + */ +#define CoTask_Yield() CoTask_Wait() + /** * \brief: Coroutine task scheduler id function * \return: Coroutine task scheduler id, indicates the scheduler id of the coroutine task diff --git a/source/08_coroutine/coroutine_cfg.h b/source/08_coroutine/coroutine_cfg.h index fb3b297..a2cc930 100644 --- a/source/08_coroutine/coroutine_cfg.h +++ b/source/08_coroutine/coroutine_cfg.h @@ -17,7 +17,7 @@ * \brief Coroutine scheduler number * \note This macro defines the number of coroutine schedulers */ -#define COROUTINE_SCHEDULER_NUMBER 1 +#define COROUTINE_SCHEDULER_NUMBER 2 /** * \brief Coroutine static task max number @@ -31,12 +31,6 @@ */ #define COROUTINE_STATIC_TIMER_MAX_NUMBER 8 -/** - * \brief Coroutine static stack max number - * \note This macro defines the maximum number of coroutine stacks that can be created statically - */ -#define COROUTINE_STATIC_STACK_MAX_NUMBER 8 - /** * \brief Coroutine stack default size * \note This macro defines the default size of the coroutine stack @@ -72,101 +66,10 @@ #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 + * \brief Size of the physical shared stack buffer per scheduler + * \note 0: Disable shared stack (dedicated stacks only, zero memory copying overhead) + * > 0: Enable shared stack with specified buffer size in bytes (e.g. 128 * 1024) */ -#if defined(__i386__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%esp, %0" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%esp" : : "r" (p) : "memory") -#elif defined(__x86_64__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%rsp, %0" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%rsp" : : "r" (p) : "memory") -#elif defined(__arm__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") -#elif defined(__aarch64__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") -#elif defined(__mips__) -#define COROUTINE_GET_SP(p) __asm__ volatile(".set noreorder\n\t" "move %0, $sp\n\t" ".set reorder" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(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_SP(p) __asm__ volatile("mv %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mv sp, %0" : : "r" (p) : "memory") -#else -#define COROUTINE_GET_SP(p) __asm__ volatile("mv %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mv sp, %0" : : "r" (p) : "memory") -#endif -#elif defined(__powerpc__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") -#elif defined(__powerpc64__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") -#elif defined(__s390__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory") -#elif defined(__s390x__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory") -#elif defined(__sparc__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory") -#elif defined(__sparcv9__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory") -#elif defined(__tile__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory") -#elif defined(__tilegx__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %%sp, %0" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov %0, %%sp" : : "r" (p) : "memory") -#elif defined(__hppa__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") -#elif defined(__hppa64__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") -#elif defined(__ia64__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0=sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp=%0" : : "r" (p) : "memory") -#elif defined(__loongarch__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") -#elif defined(__m68k__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") -#elif defined(__mips64__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") -#elif defined(__parisc__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") -#elif defined(__parisc64__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r1" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r1, %0" : : "r" (p) : "memory") -#elif defined(__s390__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory") -#elif defined(__s390x__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mr %0, r15" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mr r15, %0" : : "r" (p) : "memory") -#elif defined(__sh__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") -#elif defined(__sh64__) -#define COROUTINE_GET_SP(p) __asm__ volatile("mov %0, sp" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("mov sp, %0" : : "r" (p) : "memory") -#elif defined(__xtensa__) -#define COROUTINE_GET_SP(p) __asm__ volatile("rsr.a1 %0" : "=r" (p) : : "memory") -#define COROUTINE_SET_SP(p) __asm__ volatile("wsr.a1 %0" : : "r" (p) : "memory") -#else -#error "Unsupported platform, please implement `COROUTINE_GET_SP` and `COROUTINE_SET_SP`" -#endif +#define COROUTINE_SHARED_STACK_SIZE (128 * 1024) #endif // !__coroutine_cfg_H diff --git a/source/08_coroutine/coroutine_hook.c b/source/08_coroutine/coroutine_hook.c new file mode 100644 index 0000000..5c2620a --- /dev/null +++ b/source/08_coroutine/coroutine_hook.c @@ -0,0 +1,469 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file coroutine_hook.c + * \unit coroutine + * \brief Predefined idleHook and wakeupHook implementations for Bare-Metal, RTOS and OS platforms + * \author Lamdonn + * \version v0.2.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2025 Lamdonn. + ********************************************************************************************************/ +#include "coroutine_hook.h" +#include "coroutine.h" + +/* ======================================================================================================= + * 1. Bare-metal / Embedded MCU Hooks + * ======================================================================================================= */ + +void CoHook_Baremetal_Idle(uint32_t schedulerId, uint32_t idleUs) +{ + (void)schedulerId; + (void)idleUs; +#if defined(__arm__) || defined(__aarch64__) + __asm__ volatile ("wfi"); +#elif defined(__riscv__) || defined(__riscv) + __asm__ volatile ("wfi"); +#elif defined(__xtensa__) + __asm__ volatile ("waiti 0"); +#elif defined(__CSKY__) + __asm__ volatile ("wait"); +#elif defined(__MSP430__) + __asm__ volatile ("bis #0x0018, r2"); +#elif defined(__ARC__) || defined(__arc__) + __asm__ volatile ("sleep"); +#elif defined(__mips__) || defined(__mips64__) + __asm__ volatile ("wait"); +#elif defined(__loongarch__) + __asm__ volatile ("idle 0"); +#elif defined(__sh__) || defined(__sh64__) + __asm__ volatile ("sleep"); +#elif defined(__m68k__) + __asm__ volatile ("stop #0x2000"); +#elif defined(__AVR__) + __asm__ volatile ("sleep"); +#elif defined(__powerpc__) || defined(__powerpc64__) + __asm__ volatile ("or 31, 31, 31"); +#elif defined(__i386__) || defined(__x86_64__) + __asm__ volatile ("pause"); +#else + /* Freestanding CPU architecture fallback: no-op */ + __asm__ volatile (""); +#endif +} + +void CoHook_Baremetal_Wakeup(uint32_t schedulerId) +{ + (void)schedulerId; +#if defined(__arm__) || defined(__aarch64__) + __asm__ volatile ("sev"); +#elif defined(__MSP430__) + __asm__ volatile ("bic #0x0018, 0(r1)"); +#else + /* Handled automatically by hardware interrupt return (NVIC/CLIC/PLIC) */ + __asm__ volatile (""); +#endif +} + +/* ======================================================================================================= + * 2. FreeRTOS Hooks (Task Notification based) + * ======================================================================================================= */ +#if defined(INC_FREERTOS_H) || defined(COROUTINE_HOOK_FREERTOS) +#include "FreeRTOS.h" +#include "task.h" + +static TaskHandle_t s_freertos_tasks[COROUTINE_SCHEDULER_NUMBER] = {0}; + +int CoHook_FreeRTOS_Init(uint32_t schedulerId, void *taskHandle) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return COROUTINE_E_INVALID_PARAMETER; + s_freertos_tasks[schedulerId] = (taskHandle != NULL) ? (TaskHandle_t)taskHandle : xTaskGetCurrentTaskHandle(); + return 0; +} + +void CoHook_FreeRTOS_Deinit(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER) + { + s_freertos_tasks[schedulerId] = NULL; + } +} + +void CoHook_FreeRTOS_Idle(uint32_t schedulerId, uint32_t idleUs) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return; + TickType_t xTicks = (idleUs == 0) ? portMAX_DELAY : (TickType_t)((uint64_t)idleUs * configTICK_RATE_HZ / 1000000ULL); + if (xTicks == 0 && idleUs > 0) xTicks = 1; + ulTaskNotifyTake(pdTRUE, xTicks); +} + +void CoHook_FreeRTOS_Wakeup(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_freertos_tasks[schedulerId] != NULL) + { + xTaskNotifyGive(s_freertos_tasks[schedulerId]); + } +} +#else +int CoHook_FreeRTOS_Init(uint32_t schedulerId, void *taskHandle) { (void)schedulerId; (void)taskHandle; return -1; } +void CoHook_FreeRTOS_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; } +void CoHook_FreeRTOS_Wakeup(uint32_t schedulerId) { (void)schedulerId; } +void CoHook_FreeRTOS_Deinit(uint32_t schedulerId) { (void)schedulerId; } +#endif + +/* ======================================================================================================= + * 3. RT-Thread Hooks (rt_sem semaphore based) + * ======================================================================================================= */ +#if defined(RT_TICK_PER_SECOND) || defined(COROUTINE_HOOK_RTTHREAD) +#include + +static rt_sem_t s_rtt_sems[COROUTINE_SCHEDULER_NUMBER] = {0}; + +int CoHook_RTThread_Init(uint32_t schedulerId) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return COROUTINE_E_INVALID_PARAMETER; + if (s_rtt_sems[schedulerId] == RT_NULL) + { + s_rtt_sems[schedulerId] = rt_sem_create("co_sem", 0, RT_IPC_FLAG_PRIO); + if (s_rtt_sems[schedulerId] == RT_NULL) return -1; + } + return 0; +} + +void CoHook_RTThread_Deinit(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_rtt_sems[schedulerId] != RT_NULL) + { + rt_sem_delete(s_rtt_sems[schedulerId]); + s_rtt_sems[schedulerId] = RT_NULL; + } +} + +void CoHook_RTThread_Idle(uint32_t schedulerId, uint32_t idleUs) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER || s_rtt_sems[schedulerId] == RT_NULL) return; + rt_int32_t ticks = (idleUs == 0) ? RT_WAITING_FOREVER : (rt_int32_t)((uint64_t)idleUs * RT_TICK_PER_SECOND / 1000000ULL); + if (ticks == 0 && idleUs > 0) ticks = 1; + rt_sem_take(s_rtt_sems[schedulerId], ticks); +} + +void CoHook_RTThread_Wakeup(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_rtt_sems[schedulerId] != RT_NULL) + { + rt_sem_release(s_rtt_sems[schedulerId]); + } +} +#else +int CoHook_RTThread_Init(uint32_t schedulerId) { (void)schedulerId; return -1; } +void CoHook_RTThread_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; } +void CoHook_RTThread_Wakeup(uint32_t schedulerId) { (void)schedulerId; } +void CoHook_RTThread_Deinit(uint32_t schedulerId) { (void)schedulerId; } +#endif + +/* ======================================================================================================= + * 4. CMSIS-RTOS2 Hooks (ARM Standard - Keil RTX5, FreeRTOS-CMSIS wrapper, etc.) + * ======================================================================================================= */ +#if defined(CMSIS_OS2_H_) || defined(COROUTINE_HOOK_CMSIS_RTOS2) +#include "cmsis_os2.h" + +static osThreadId_t s_cmsis_threads[COROUTINE_SCHEDULER_NUMBER] = {0}; + +int CoHook_CMSIS_Init(uint32_t schedulerId, void *threadId) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return COROUTINE_E_INVALID_PARAMETER; + s_cmsis_threads[schedulerId] = (threadId != NULL) ? (osThreadId_t)threadId : osThreadGetId(); + return 0; +} + +void CoHook_CMSIS_Deinit(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER) + { + s_cmsis_threads[schedulerId] = NULL; + } +} + +void CoHook_CMSIS_Idle(uint32_t schedulerId, uint32_t idleUs) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return; + uint32_t millis = (idleUs == 0) ? osWaitForever : (uint32_t)((idleUs + 999) / 1000); + osThreadFlagsWait(0x01, osFlagsWaitAny, millis); +} + +void CoHook_CMSIS_Wakeup(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_cmsis_threads[schedulerId] != NULL) + { + osThreadFlagsSet(s_cmsis_threads[schedulerId], 0x01); + } +} +#else +int CoHook_CMSIS_Init(uint32_t schedulerId, void *threadId) { (void)schedulerId; (void)threadId; return -1; } +void CoHook_CMSIS_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; } +void CoHook_CMSIS_Wakeup(uint32_t schedulerId) { (void)schedulerId; } +void CoHook_CMSIS_Deinit(uint32_t schedulerId) { (void)schedulerId; } +#endif + +/* ======================================================================================================= + * 5. uC/OS-III Hooks (Task Semaphore based) + * ======================================================================================================= */ +#if defined(OS_CFG_H) || defined(COROUTINE_HOOK_UCOS) +#include + +static OS_TCB *s_ucos_tcbs[COROUTINE_SCHEDULER_NUMBER] = {0}; + +int CoHook_UCOS_Init(uint32_t schedulerId, void *pTcb) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return COROUTINE_E_INVALID_PARAMETER; + s_ucos_tcbs[schedulerId] = (pTcb != NULL) ? (OS_TCB *)pTcb : OSTCBCurPtr; + return 0; +} + +void CoHook_UCOS_Deinit(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER) + { + s_ucos_tcbs[schedulerId] = NULL; + } +} + +void CoHook_UCOS_Idle(uint32_t schedulerId, uint32_t idleUs) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return; + OS_ERR err; + CPU_TS ts; + OS_TICK dly = (idleUs == 0) ? 0 : (OS_TICK)((uint64_t)idleUs * OSCfg_TickRate_Hz / 1000000ULL); + if (dly == 0 && idleUs > 0) dly = 1; + OSTaskSemPend(dly, OS_OPT_PEND_BLOCKING, &ts, &err); +} + +void CoHook_UCOS_Wakeup(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_ucos_tcbs[schedulerId] != NULL) + { + OS_ERR err; + OSTaskSemPost(s_ucos_tcbs[schedulerId], OS_OPT_POST_NONE, &err); + } +} +#else +int CoHook_UCOS_Init(uint32_t schedulerId, void *pTcb) { (void)schedulerId; (void)pTcb; return -1; } +void CoHook_UCOS_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; } +void CoHook_UCOS_Wakeup(uint32_t schedulerId) { (void)schedulerId; } +void CoHook_UCOS_Deinit(uint32_t schedulerId) { (void)schedulerId; } +#endif + +/* ======================================================================================================= + * 6. Windows Implementation + * ======================================================================================================= */ +#if defined(_WIN32) +#include + +static HANDLE s_win32_events[COROUTINE_SCHEDULER_NUMBER] = {0}; + +int CoHook_Win32_Init(uint32_t schedulerId) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + if (s_win32_events[schedulerId] == NULL) + { + s_win32_events[schedulerId] = CreateEventA(NULL, FALSE, FALSE, NULL); // Auto-reset event + if (s_win32_events[schedulerId] == NULL) + { + return -1; + } + } + return 0; +} + +void CoHook_Win32_Deinit(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_win32_events[schedulerId] != NULL) + { + CloseHandle(s_win32_events[schedulerId]); + s_win32_events[schedulerId] = NULL; + } +} + +void CoHook_Win32_Idle(uint32_t schedulerId, uint32_t idleUs) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_win32_events[schedulerId] != NULL) + { + DWORD ms = (idleUs == 0) ? INFINITE : (DWORD)((idleUs + 999) / 1000); + WaitForSingleObject(s_win32_events[schedulerId], ms); + } +} + +void CoHook_Win32_Wakeup(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_win32_events[schedulerId] != NULL) + { + SetEvent(s_win32_events[schedulerId]); + } +} +#endif + +/* ======================================================================================================= + * 7. POSIX / Linux / Android / macOS / iOS / QNX / VxWorks / NuttX Implementation + * ======================================================================================================= */ +#if defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__QNX__) || defined(__QNXNTO__) || defined(__ANDROID__) || defined(__VXWORKS__) || defined(__NuttX__) +#include +#include +#include + +static pthread_mutex_t s_posix_mutex[COROUTINE_SCHEDULER_NUMBER]; +static pthread_cond_t s_posix_cond[COROUTINE_SCHEDULER_NUMBER]; +static uint8_t s_posix_inited[COROUTINE_SCHEDULER_NUMBER] = {0}; + +int CoHook_Posix_Init(uint32_t schedulerId) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + if (!s_posix_inited[schedulerId]) + { + pthread_mutex_init(&s_posix_mutex[schedulerId], NULL); + pthread_cond_init(&s_posix_cond[schedulerId], NULL); + s_posix_inited[schedulerId] = 1; + } + return 0; +} + +void CoHook_Posix_Deinit(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_posix_inited[schedulerId]) + { + pthread_mutex_destroy(&s_posix_mutex[schedulerId]); + pthread_cond_destroy(&s_posix_cond[schedulerId]); + s_posix_inited[schedulerId] = 0; + } +} + +void CoHook_Posix_Idle(uint32_t schedulerId, uint32_t idleUs) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER || !s_posix_inited[schedulerId]) return; + + pthread_mutex_lock(&s_posix_mutex[schedulerId]); + if (idleUs == 0) + { + pthread_cond_wait(&s_posix_cond[schedulerId], &s_posix_mutex[schedulerId]); + } + else + { + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + uint64_t nsec = (uint64_t)ts.tv_nsec + (uint64_t)idleUs * 1000ULL; + ts.tv_sec += (time_t)(nsec / 1000000000ULL); + ts.tv_nsec = (long)(nsec % 1000000000ULL); + pthread_cond_timedwait(&s_posix_cond[schedulerId], &s_posix_mutex[schedulerId], &ts); + } + pthread_mutex_unlock(&s_posix_mutex[schedulerId]); +} + +void CoHook_Posix_Wakeup(uint32_t schedulerId) +{ + if (schedulerId < COROUTINE_SCHEDULER_NUMBER && s_posix_inited[schedulerId]) + { + pthread_mutex_lock(&s_posix_mutex[schedulerId]); + pthread_cond_signal(&s_posix_cond[schedulerId]); + pthread_mutex_unlock(&s_posix_mutex[schedulerId]); + } +} +#endif + +/* Fallback stubs if compiled on non-matching platform */ +#if !defined(_WIN32) +int CoHook_Win32_Init(uint32_t schedulerId) { (void)schedulerId; return -1; } +void CoHook_Win32_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; } +void CoHook_Win32_Wakeup(uint32_t schedulerId) { (void)schedulerId; } +void CoHook_Win32_Deinit(uint32_t schedulerId) { (void)schedulerId; } +#endif + +#if !defined(__linux__) && !defined(__unix__) && !defined(__APPLE__) && !defined(__QNX__) && !defined(__QNXNTO__) && !defined(__ANDROID__) && !defined(__VXWORKS__) && !defined(__NuttX__) +int CoHook_Posix_Init(uint32_t schedulerId) { (void)schedulerId; return -1; } +void CoHook_Posix_Idle(uint32_t schedulerId, uint32_t idleUs) { (void)schedulerId; (void)idleUs; } +void CoHook_Posix_Wakeup(uint32_t schedulerId) { (void)schedulerId; } +void CoHook_Posix_Deinit(uint32_t schedulerId) { (void)schedulerId; } +#endif + +/* ======================================================================================================= + * 8. Unified / Auto-Detect Platform Dispatcher + * ======================================================================================================= */ + +int CoScheduler_BindDefaultHooks(uint32_t schedulerId) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + +#if defined(INC_FREERTOS_H) || defined(COROUTINE_HOOK_FREERTOS) + int ret = CoHook_FreeRTOS_Init(schedulerId, NULL); + if (ret != 0) return ret; + CoScheduler_SetIdleHook(schedulerId, CoHook_FreeRTOS_Idle); + CoScheduler_SetWakeupHook(schedulerId, CoHook_FreeRTOS_Wakeup); +#elif defined(RT_TICK_PER_SECOND) || defined(COROUTINE_HOOK_RTTHREAD) + int ret = CoHook_RTThread_Init(schedulerId); + if (ret != 0) return ret; + CoScheduler_SetIdleHook(schedulerId, CoHook_RTThread_Idle); + CoScheduler_SetWakeupHook(schedulerId, CoHook_RTThread_Wakeup); +#elif defined(CMSIS_OS2_H_) || defined(COROUTINE_HOOK_CMSIS_RTOS2) + int ret = CoHook_CMSIS_Init(schedulerId, NULL); + if (ret != 0) return ret; + CoScheduler_SetIdleHook(schedulerId, CoHook_CMSIS_Idle); + CoScheduler_SetWakeupHook(schedulerId, CoHook_CMSIS_Wakeup); +#elif defined(OS_CFG_H) || defined(COROUTINE_HOOK_UCOS) + int ret = CoHook_UCOS_Init(schedulerId, NULL); + if (ret != 0) return ret; + CoScheduler_SetIdleHook(schedulerId, CoHook_UCOS_Idle); + CoScheduler_SetWakeupHook(schedulerId, CoHook_UCOS_Wakeup); +#elif defined(_WIN32) + int ret = CoHook_Win32_Init(schedulerId); + if (ret != 0) return ret; + CoScheduler_SetIdleHook(schedulerId, CoHook_Win32_Idle); + CoScheduler_SetWakeupHook(schedulerId, CoHook_Win32_Wakeup); +#elif defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__QNX__) || defined(__QNXNTO__) || defined(__ANDROID__) || defined(__VXWORKS__) || defined(__NuttX__) + int ret = CoHook_Posix_Init(schedulerId); + if (ret != 0) return ret; + CoScheduler_SetIdleHook(schedulerId, CoHook_Posix_Idle); + CoScheduler_SetWakeupHook(schedulerId, CoHook_Posix_Wakeup); +#else + CoScheduler_SetIdleHook(schedulerId, CoHook_Baremetal_Idle); + CoScheduler_SetWakeupHook(schedulerId, CoHook_Baremetal_Wakeup); +#endif + + return COROUTINE_E_OK; +} + +int CoScheduler_UnbindDefaultHooks(uint32_t schedulerId) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + CoScheduler_SetIdleHook(schedulerId, NULL); + CoScheduler_SetWakeupHook(schedulerId, NULL); + +#if defined(INC_FREERTOS_H) || defined(COROUTINE_HOOK_FREERTOS) + CoHook_FreeRTOS_Deinit(schedulerId); +#elif defined(RT_TICK_PER_SECOND) || defined(COROUTINE_HOOK_RTTHREAD) + CoHook_RTThread_Deinit(schedulerId); +#elif defined(CMSIS_OS2_H_) || defined(COROUTINE_HOOK_CMSIS_RTOS2) + CoHook_CMSIS_Deinit(schedulerId); +#elif defined(OS_CFG_H) || defined(COROUTINE_HOOK_UCOS) + CoHook_UCOS_Deinit(schedulerId); +#elif defined(_WIN32) + CoHook_Win32_Deinit(schedulerId); +#elif defined(__linux__) || defined(__unix__) || defined(__APPLE__) || defined(__QNX__) || defined(__QNXNTO__) || defined(__ANDROID__) || defined(__VXWORKS__) || defined(__NuttX__) + CoHook_Posix_Deinit(schedulerId); +#endif + + return COROUTINE_E_OK; +} diff --git a/source/08_coroutine/coroutine_hook.h b/source/08_coroutine/coroutine_hook.h new file mode 100644 index 0000000..f17f8c4 --- /dev/null +++ b/source/08_coroutine/coroutine_hook.h @@ -0,0 +1,252 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file coroutine_hook.h + * \unit coroutine + * \brief Predefined idleHook and wakeupHook implementations for Bare-Metal, RTOS and OS platforms + * \author Lamdonn + * \version v0.2.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2025 Lamdonn. + ********************************************************************************************************/ +#ifndef __coroutine_hook_H +#define __coroutine_hook_H + +#include "coroutine.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ======================================================================================================= + * 1. Bare-metal / Embedded MCU Hooks (ARM Cortex-M, RISC-V, Xtensa, C-SKY, MSP430, etc.) + * ======================================================================================================= */ + +/** + * \brief: Bare-metal low power idle hook using hardware sleep instruction (wfi / waiti / sleep / LPM) + * \param schedulerId: Target scheduler ID + * \param idleUs: Estimated idle time in microseconds + * \note: Puts the CPU core into low-power sleep mode until an interrupt occurs. + */ +void CoHook_Baremetal_Idle(uint32_t schedulerId, uint32_t idleUs); + +/** + * \brief: Bare-metal wakeup hook + * \param schedulerId: Target scheduler ID + * \note: On single-core MCU, hardware interrupts automatically wake the CPU from WFI. + * In multi-core/AMP setups, triggers SEV (Send Event) or an Inter-Processor Interrupt (IPI). + */ +void CoHook_Baremetal_Wakeup(uint32_t schedulerId); + +/* ======================================================================================================= + * 2. FreeRTOS Hooks (Task Notification based, Zero-RAM overhead) + * ======================================================================================================= */ + +/** + * \brief: Initialize FreeRTOS hooks for a scheduler, binding it to the target FreeRTOS task + * \param schedulerId: Target scheduler ID + * \param taskHandle: FreeRTOS TaskHandle_t running this scheduler (or NULL to use xTaskGetCurrentTaskHandle()) + * \return: 0 on success, or negative error code + */ +int CoHook_FreeRTOS_Init(uint32_t schedulerId, void *taskHandle); + +/** + * \brief: FreeRTOS idle hook (calls ulTaskNotifyTake with timeout) + * \param schedulerId: Target scheduler ID + * \param idleUs: Sleep timeout in microseconds + */ +void CoHook_FreeRTOS_Idle(uint32_t schedulerId, uint32_t idleUs); + +/** + * \brief: FreeRTOS wakeup hook (calls xTaskNotifyGive / vTaskNotifyGiveFromISR) + * \param schedulerId: Target scheduler ID to wake up + */ +void CoHook_FreeRTOS_Wakeup(uint32_t schedulerId); + +/** + * \brief: Deinitialize FreeRTOS hooks for a scheduler + * \param schedulerId: Target scheduler ID + */ +void CoHook_FreeRTOS_Deinit(uint32_t schedulerId); + +/* ======================================================================================================= + * 3. RT-Thread Hooks (rt_sem semaphore based) + * ======================================================================================================= */ + +/** + * \brief: Initialize RT-Thread semaphore-based hooks for a scheduler + * \param schedulerId: Target scheduler ID + * \return: 0 on success, or negative error code + */ +int CoHook_RTThread_Init(uint32_t schedulerId); + +/** + * \brief: RT-Thread idle hook (calls rt_sem_take with timeout) + * \param schedulerId: Target scheduler ID + * \param idleUs: Sleep timeout in microseconds + */ +void CoHook_RTThread_Idle(uint32_t schedulerId, uint32_t idleUs); + +/** + * \brief: RT-Thread wakeup hook (calls rt_sem_release) + * \param schedulerId: Target scheduler ID to wake up + */ +void CoHook_RTThread_Wakeup(uint32_t schedulerId); + +/** + * \brief: Deinitialize RT-Thread hooks for a scheduler + * \param schedulerId: Target scheduler ID + */ +void CoHook_RTThread_Deinit(uint32_t schedulerId); + +/* ======================================================================================================= + * 4. CMSIS-RTOS2 Hooks (ARM Standard - Keil RTX5, FreeRTOS-CMSIS wrapper, etc.) + * ======================================================================================================= */ + +/** + * \brief: Initialize CMSIS-RTOS2 hooks for a scheduler + * \param schedulerId: Target scheduler ID + * \param threadId: osThreadId_t running this scheduler (or NULL to use osThreadGetId()) + * \return: 0 on success, or negative error code + */ +int CoHook_CMSIS_Init(uint32_t schedulerId, void *threadId); + +/** + * \brief: CMSIS-RTOS2 idle hook (calls osThreadFlagsWait with timeout) + * \param schedulerId: Target scheduler ID + * \param idleUs: Sleep timeout in microseconds + */ +void CoHook_CMSIS_Idle(uint32_t schedulerId, uint32_t idleUs); + +/** + * \brief: CMSIS-RTOS2 wakeup hook (calls osThreadFlagsSet) + * \param schedulerId: Target scheduler ID to wake up + */ +void CoHook_CMSIS_Wakeup(uint32_t schedulerId); + +/** + * \brief: Deinitialize CMSIS-RTOS2 hooks for a scheduler + * \param schedulerId: Target scheduler ID + */ +void CoHook_CMSIS_Deinit(uint32_t schedulerId); + +/* ======================================================================================================= + * 5. uC/OS-III Hooks (Task Semaphore based) + * ======================================================================================================= */ + +/** + * \brief: Initialize uC/OS-III hooks for a scheduler + * \param schedulerId: Target scheduler ID + * \param pTcb: Target OS_TCB pointer (or NULL to use OSTCBCurPtr) + * \return: 0 on success, or negative error code + */ +int CoHook_UCOS_Init(uint32_t schedulerId, void *pTcb); + +/** + * \brief: uC/OS-III idle hook (calls OSTaskSemPend with timeout) + * \param schedulerId: Target scheduler ID + * \param idleUs: Sleep timeout in microseconds + */ +void CoHook_UCOS_Idle(uint32_t schedulerId, uint32_t idleUs); + +/** + * \brief: uC/OS-III wakeup hook (calls OSTaskSemPost) + * \param schedulerId: Target scheduler ID to wake up + */ +void CoHook_UCOS_Wakeup(uint32_t schedulerId); + +/** + * \brief: Deinitialize uC/OS-III hooks for a scheduler + * \param schedulerId: Target scheduler ID + */ +void CoHook_UCOS_Deinit(uint32_t schedulerId); + +/* ======================================================================================================= + * 6. POSIX / Linux / Android / macOS / iOS / QNX / VxWorks / NuttX Multi-Threaded Hooks + * ======================================================================================================= */ + +/** + * \brief: Initialize POSIX condition-variable based hooks for a scheduler + * \param schedulerId: Target scheduler ID + * \return: 0 on success, or negative error code + */ +int CoHook_Posix_Init(uint32_t schedulerId); + +/** + * \brief: POSIX idle hook with timeout + * \param schedulerId: Target scheduler ID + * \param idleUs: Sleep timeout in microseconds + */ +void CoHook_Posix_Idle(uint32_t schedulerId, uint32_t idleUs); + +/** + * \brief: POSIX wakeup hook (signals condition variable to wake up scheduler immediately) + * \param schedulerId: Target scheduler ID to wake up + */ +void CoHook_Posix_Wakeup(uint32_t schedulerId); + +/** + * \brief: Destroy POSIX synchronization primitives for a scheduler + * \param schedulerId: Target scheduler ID + */ +void CoHook_Posix_Deinit(uint32_t schedulerId); + +/* ======================================================================================================= + * 7. Windows Multi-Threaded Hooks (Win32 Event) + * ======================================================================================================= */ + +/** + * \brief: Initialize Windows event-based hooks for a scheduler + * \param schedulerId: Target scheduler ID + * \return: 0 on success, or negative error code + */ +int CoHook_Win32_Init(uint32_t schedulerId); + +/** + * \brief: Windows idle hook with timeout + * \param schedulerId: Target scheduler ID + * \param idleUs: Sleep timeout in microseconds + */ +void CoHook_Win32_Idle(uint32_t schedulerId, uint32_t idleUs); + +/** + * \brief: Windows wakeup hook (SetEvent to unblock scheduler immediately) + * \param schedulerId: Target scheduler ID to wake up + */ +void CoHook_Win32_Wakeup(uint32_t schedulerId); + +/** + * \brief: Destroy Windows synchronization primitives for a scheduler + * \param schedulerId: Target scheduler ID + */ +void CoHook_Win32_Deinit(uint32_t schedulerId); + +/* ======================================================================================================= + * 8. Unified / Auto-Detect Platform Hooks (Ready-to-Use Out-of-the-Box) + * ======================================================================================================= */ + +/** + * \brief: Automatically attach platform-appropriate idleHook and wakeupHook to the scheduler + * \param schedulerId: Target scheduler ID + * \return: 0 on success, or negative error code + * \note: Automatically detects FreeRTOS, RT-Thread, CMSIS-RTOS2, uC/OS, Windows, POSIX/Linux, or Bare-metal. + * \example: + * CoScheduler_Init(0, GetTimerUsec, 1000); + * CoScheduler_BindDefaultHooks(0); + */ +int CoScheduler_BindDefaultHooks(uint32_t schedulerId); + +/** + * \brief: Release default platform hooks attached to the scheduler + * \param schedulerId: Target scheduler ID + */ +int CoScheduler_UnbindDefaultHooks(uint32_t schedulerId); + +#ifdef __cplusplus +} +#endif + +#endif /* !__coroutine_hook_H */ diff --git a/source/08_coroutine/coroutine_io.c b/source/08_coroutine/coroutine_io.c new file mode 100644 index 0000000..1d762c2 --- /dev/null +++ b/source/08_coroutine/coroutine_io.c @@ -0,0 +1,645 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file coroutine_io.c + * \unit coroutine + * \brief High-throughput asynchronous I/O multiplexer and cooperative socket API + * \author Lamdonn + * \version v0.2.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2025 Lamdonn. + ********************************************************************************************************/ +#if defined(_WIN32) && !defined(_WIN32_WINNT) +#define _WIN32_WINNT 0x0600 +#endif + +#include "coroutine_io.h" +#include "coroutine_hook.h" +#include +#include +#include + +/* ======================================================================================================= + * Quad-Tier Platform Detection & Header Dispatching + * ======================================================================================================= */ +#if defined(COROUTINE_IO_BAREMETAL) + #define CO_IO_PLATFORM_BAREMETAL 1 +#elif defined(_WIN32) + #define CO_IO_PLATFORM_WIN32 1 +#elif defined(__linux__) || defined(__unix__) || defined(__APPLE__) || \ + defined(__QNX__) || defined(__QNXNTO__) || defined(__ANDROID__) || \ + defined(__VXWORKS__) || defined(__NuttX__) + #define CO_IO_PLATFORM_POSIX 1 +#elif defined(COROUTINE_IO_LWIP) || defined(LWIP_SOCKET) + #define CO_IO_PLATFORM_LWIP 1 +#else + #define CO_IO_PLATFORM_BAREMETAL 1 +#endif + +#if defined(CO_IO_PLATFORM_WIN32) +#include +#include +#include +typedef int socklen_t; +typedef WSAPOLLFD PollFd_t; +#elif defined(CO_IO_PLATFORM_POSIX) +#include +#include +#include +#include +#include +#include +#include +#include +typedef struct pollfd PollFd_t; +#elif defined(CO_IO_PLATFORM_LWIP) +#include "lwip/sockets.h" +#include "lwip/netdb.h" +#include +typedef struct pollfd PollFd_t; +#elif defined(CO_IO_PLATFORM_BAREMETAL) +/* Pure bare-metal mode: zero external socket headers */ +#ifndef POLLIN +#define POLLIN (0x0001) +#endif +#ifndef POLLOUT +#define POLLOUT (0x0004) +#endif +#ifndef POLLERR +#define POLLERR (0x0008) +#endif +#ifndef POLLHUP +#define POLLHUP (0x0010) +#endif +#ifndef POLLNVAL +#define POLLNVAL (0x0020) +#endif + +typedef CoIoPollFd_t PollFd_t; +static CoIoBaremetalPoller_t s_baremetal_poller = NULL; + +void CoIo_SetBaremetalPoller(CoIoBaremetalPoller_t poller) +{ + s_baremetal_poller = poller; +} +#endif + +/* ======================================================================================================= + * Small Buffer Optimization (SBO) Hybrid Poller Architecture + * ======================================================================================================= */ + +#define CO_IO_STATIC_CAPACITY (64) + +typedef struct +{ + int fd; + uint32_t events; + CoTask_t task; + int readyEvents; +} CoIoWatch; + +typedef struct +{ + CoIoWatch staticWatches[CO_IO_STATIC_CAPACITY]; /**< Static fast cache (0 heap allocations) */ + CoIoWatch *dynamicWatches; /**< Overflow dynamic buffer (heap, auto-expanding) */ + uint32_t capacity; /**< Current total capacity (starts at CO_IO_STATIC_CAPACITY) */ + uint32_t count; /**< Current number of active watched descriptors */ +} CoSchedulerPoller; + +static CoSchedulerPoller s_pollers[COROUTINE_SCHEDULER_NUMBER] = {0}; +static int s_io_inited = 0; + +/* Forward declarations */ +static void CoIo_IdleHook(uint32_t schedulerId, uint32_t idleUs); +static void CoIo_WakeupHook(uint32_t schedulerId); + +static inline CoIoWatch *CoIo_GetWatch(CoSchedulerPoller *poller, uint32_t index) +{ + if (poller->dynamicWatches != NULL) + { + return &poller->dynamicWatches[index]; + } + return &poller->staticWatches[index]; +} + +static int CoIo_EnsureCapacity(CoSchedulerPoller *poller) +{ + if (poller->capacity == 0) + { + poller->capacity = CO_IO_STATIC_CAPACITY; + } + + if (poller->count < poller->capacity) + { + return 0; /* Existing capacity has free slots */ + } + + /* Verify if the coroutine system has a configured dynamic memory allocator */ + if (!Coroutine_HasMemHook()) + { + return -1; /* Pure static mode: safely reject expansion beyond static limit */ + } + + /* Needs expansion: double capacity */ + uint32_t newCap = poller->capacity * 2; + CoIoWatch *newBuf = (CoIoWatch *)Coroutine_Malloc(sizeof(CoIoWatch) * newCap); + if (newBuf == NULL) return -1; + + memset(newBuf, 0, sizeof(CoIoWatch) * newCap); + + if (poller->dynamicWatches == NULL) + { + /* First overflow: promote from static cache to dynamic heap */ + memcpy(newBuf, poller->staticWatches, sizeof(CoIoWatch) * poller->capacity); + } + else + { + /* Subsequent expansion: copy from previous dynamic buffer and free old */ + memcpy(newBuf, poller->dynamicWatches, sizeof(CoIoWatch) * poller->capacity); + Coroutine_Free(poller->dynamicWatches); + } + + poller->dynamicWatches = newBuf; + poller->capacity = newCap; + + return 0; +} + +static void CoIo_TryShrink(CoSchedulerPoller *poller) +{ + /* If all descriptors have finished and poller is completely empty, revert to static */ + if (poller->dynamicWatches != NULL && poller->count == 0) + { + Coroutine_Free(poller->dynamicWatches); + poller->dynamicWatches = NULL; + poller->capacity = CO_IO_STATIC_CAPACITY; + } +} + +/* ======================================================================================================= + * Subsystem Management + * ======================================================================================================= */ + +int CoIo_Init(void) +{ + if (s_io_inited) return 0; +#if defined(CO_IO_PLATFORM_WIN32) + WSADATA wsaData; + int ret = WSAStartup(MAKEWORD(2, 2), &wsaData); + if (ret != 0) return -1; +#endif + s_io_inited = 1; + return 0; +} + +void CoIo_Deinit(void) +{ + if (!s_io_inited) return; + + for (uint32_t i = 0; i < COROUTINE_SCHEDULER_NUMBER; i++) + { + if (s_pollers[i].dynamicWatches != NULL) + { + Coroutine_Free(s_pollers[i].dynamicWatches); + s_pollers[i].dynamicWatches = NULL; + } + s_pollers[i].capacity = CO_IO_STATIC_CAPACITY; + s_pollers[i].count = 0; + } + +#if defined(CO_IO_PLATFORM_WIN32) + WSACleanup(); +#endif + s_io_inited = 0; +} + +int CoIo_AttachScheduler(uint32_t schedulerId) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + CoIo_Init(); + s_pollers[schedulerId].capacity = CO_IO_STATIC_CAPACITY; + s_pollers[schedulerId].count = 0; + s_pollers[schedulerId].dynamicWatches = NULL; + + CoScheduler_SetIdleHook(schedulerId, CoIo_IdleHook); + CoScheduler_SetWakeupHook(schedulerId, CoIo_WakeupHook); + + return COROUTINE_E_OK; +} + +int CoIo_DetachScheduler(uint32_t schedulerId) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) + { + return COROUTINE_E_INVALID_PARAMETER; + } + + CoScheduler_SetIdleHook(schedulerId, NULL); + CoScheduler_SetWakeupHook(schedulerId, NULL); + + if (s_pollers[schedulerId].dynamicWatches != NULL) + { + Coroutine_Free(s_pollers[schedulerId].dynamicWatches); + s_pollers[schedulerId].dynamicWatches = NULL; + } + s_pollers[schedulerId].capacity = CO_IO_STATIC_CAPACITY; + s_pollers[schedulerId].count = 0; + + return COROUTINE_E_OK; +} + +int CoIo_SetNonblocking(int fd) +{ +#if defined(CO_IO_PLATFORM_WIN32) + u_long mode = 1; + return ioctlsocket((SOCKET)fd, FIONBIO, &mode); +#elif defined(CO_IO_PLATFORM_POSIX) + int flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) return -1; + return fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#elif defined(CO_IO_PLATFORM_LWIP) + int flags = lwip_fcntl(fd, F_GETFL, 0); + if (flags < 0) return -1; + return lwip_fcntl(fd, F_SETFL, flags | O_NONBLOCK); +#else + (void)fd; + return 0; +#endif +} + +/* ======================================================================================================= + * Polling Engine & Reactor Idle Hook (WSAPoll / poll based, no FD limits) + * ======================================================================================================= */ + +static void CoIo_IdleHook(uint32_t schedulerId, uint32_t idleUs) +{ + if (schedulerId >= COROUTINE_SCHEDULER_NUMBER) return; + CoSchedulerPoller *poller = &s_pollers[schedulerId]; + + /* If no I/O descriptors registered, fall back to standard low-power platform sleep hook */ + if (poller->count == 0) + { + CoHook_Baremetal_Idle(schedulerId, idleUs); + return; + } + + /* Allocate pollfd array using Small Buffer Optimization */ + PollFd_t staticPfds[CO_IO_STATIC_CAPACITY]; + uint32_t staticMap[CO_IO_STATIC_CAPACITY]; + PollFd_t *pfds = staticPfds; + uint32_t *mapIndex = staticMap; + uint32_t heapAllocated = 0; + + if (poller->count > CO_IO_STATIC_CAPACITY) + { + pfds = (PollFd_t *)Coroutine_Malloc(sizeof(PollFd_t) * poller->count); + mapIndex = (uint32_t *)Coroutine_Malloc(sizeof(uint32_t) * poller->count); + if (pfds == NULL || mapIndex == NULL) + { + if (pfds) Coroutine_Free(pfds); + if (mapIndex) Coroutine_Free(mapIndex); + CoHook_Baremetal_Idle(schedulerId, idleUs); + return; + } + heapAllocated = 1; + } + + uint32_t nfds = 0; + for (uint32_t i = 0; i < poller->capacity && nfds < poller->count; i++) + { + CoIoWatch *w = CoIo_GetWatch(poller, i); + if (w->task != NULL) + { + pfds[nfds].fd = w->fd; + pfds[nfds].events = 0; + if (w->events & CO_IO_READ) pfds[nfds].events |= POLLIN; + if (w->events & CO_IO_WRITE) pfds[nfds].events |= POLLOUT; + pfds[nfds].revents = 0; + mapIndex[nfds] = i; + nfds++; + } + } + + if (nfds == 0) + { + if (heapAllocated) + { + Coroutine_Free(pfds); + Coroutine_Free(mapIndex); + } + CoHook_Baremetal_Idle(schedulerId, idleUs); + return; + } + + int timeoutMs = (idleUs == 0) ? -1 : (int)((idleUs + 999) / 1000); + +#if defined(CO_IO_PLATFORM_WIN32) + int n = WSAPoll(pfds, nfds, timeoutMs); +#elif defined(CO_IO_PLATFORM_POSIX) + int n = poll(pfds, nfds, timeoutMs); +#elif defined(CO_IO_PLATFORM_LWIP) + int n = lwip_poll(pfds, nfds, timeoutMs); +#elif defined(CO_IO_PLATFORM_BAREMETAL) + int n = 0; + if (s_baremetal_poller != NULL) + { + n = s_baremetal_poller((CoIoPollFd_t *)pfds, nfds, timeoutMs); + } + else + { + CoHook_Baremetal_Idle(schedulerId, idleUs); + } +#endif + + if (n > 0) + { + for (uint32_t i = 0; i < nfds; i++) + { + if (pfds[i].revents != 0) + { + uint32_t idx = mapIndex[i]; + CoIoWatch *w = CoIo_GetWatch(poller, idx); + int rev = 0; + if (pfds[i].revents & POLLIN) rev |= CO_IO_READ; + if (pfds[i].revents & POLLOUT) rev |= CO_IO_WRITE; + if (pfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) rev |= CO_IO_ERROR; + + w->readyEvents = rev; + CoTask_Wakeup(w->task); + } + } + } + + if (heapAllocated) + { + Coroutine_Free(pfds); + Coroutine_Free(mapIndex); + } +} + +static void CoIo_WakeupHook(uint32_t schedulerId) +{ + (void)schedulerId; +} + +int CoTask_WaitFd(int fd, uint32_t events, uint32_t timeoutMs) +{ + int schedId = CoTask_SchedulerId(); + if (schedId < 0 || schedId >= (int)COROUTINE_SCHEDULER_NUMBER) + { + return CO_IO_INVALID; + } + + CoSchedulerPoller *poller = &s_pollers[schedId]; + + /* Ensure poller has enough capacity (auto expands from static to dynamic heap if needed) */ + if (CoIo_EnsureCapacity(poller) != 0) + { + return CO_IO_INVALID; + } + + /* Find an available watch slot within current capacity */ + int slot = -1; + for (uint32_t i = 0; i < poller->capacity; i++) + { + CoIoWatch *w = CoIo_GetWatch(poller, i); + if (w->task == NULL) + { + slot = (int)i; + break; + } + } + + if (slot < 0) + { + return CO_IO_INVALID; + } + + CoIoWatch *watch = CoIo_GetWatch(poller, (uint32_t)slot); + watch->fd = fd; + watch->events = events; + watch->task = CoTask_Self(); + watch->readyEvents = 0; + poller->count++; + + /* Suspend coroutine until polled event or timeout */ + CoTask_Wait(.ms = timeoutMs); + + /* Retrieve ready events and clean up slot (re-evaluate watch pointer as poller might have realloc'd) */ + CoIoWatch *w = CoIo_GetWatch(poller, (uint32_t)slot); + int res = w->readyEvents; + w->task = NULL; + w->fd = -1; + w->events = 0; + w->readyEvents = 0; + if (poller->count > 0) poller->count--; + + /* Shrink back to static cache if all overflow connections closed */ + CoIo_TryShrink(poller); + + if (res != 0) + { + return res; + } + + return CO_IO_TIMEOUT; +} + +/* ======================================================================================================= + * Cooperative Non-Blocking Socket Operations + * ======================================================================================================= */ + +int CoSocket_Create(int domain, int type, int protocol) +{ + CoIo_Init(); +#if defined(CO_IO_PLATFORM_WIN32) + SOCKET s = socket(domain, type, protocol); + if (s == INVALID_SOCKET) return -1; + CoIo_SetNonblocking((int)s); + return (int)s; +#elif defined(CO_IO_PLATFORM_POSIX) + int s = socket(domain, type, protocol); + if (s < 0) return -1; + CoIo_SetNonblocking(s); + return s; +#elif defined(CO_IO_PLATFORM_LWIP) + int s = lwip_socket(domain, type, protocol); + if (s < 0) return -1; + CoIo_SetNonblocking(s); + return s; +#else + (void)domain; (void)type; (void)protocol; + return -1; +#endif +} + +int CoSocket_Connect(int fd, const void *addr, int addrlen, uint32_t timeoutMs) +{ +#if defined(CO_IO_PLATFORM_WIN32) + int ret = connect((SOCKET)fd, (const struct sockaddr *)addr, addrlen); + if (ret == 0) return 0; + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) return -1; +#elif defined(CO_IO_PLATFORM_POSIX) + int ret = connect(fd, (const struct sockaddr *)addr, (socklen_t)addrlen); + if (ret == 0) return 0; + if (errno != EINPROGRESS && errno != EWOULDBLOCK) return -1; +#elif defined(CO_IO_PLATFORM_LWIP) + int ret = lwip_connect(fd, (const struct sockaddr *)addr, (socklen_t)addrlen); + if (ret == 0) return 0; + if (errno != EINPROGRESS && errno != EWOULDBLOCK) return -1; +#else + (void)fd; (void)addr; (void)addrlen; (void)timeoutMs; + return -1; +#endif + + int waitRes = CoTask_WaitFd(fd, CO_IO_WRITE, timeoutMs); + if (waitRes <= 0) return waitRes; + + /* Verify connect success status via SO_ERROR */ + int sockErr = 0; +#if defined(CO_IO_PLATFORM_WIN32) + socklen_t optLen = sizeof(sockErr); + getsockopt((SOCKET)fd, SOL_SOCKET, SO_ERROR, (char *)&sockErr, (int *)&optLen); +#elif defined(CO_IO_PLATFORM_POSIX) + socklen_t optLen = sizeof(sockErr); + getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockErr, &optLen); +#elif defined(CO_IO_PLATFORM_LWIP) + socklen_t optLen = sizeof(sockErr); + lwip_getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockErr, &optLen); +#else + sockErr = -1; +#endif + + return (sockErr == 0) ? 0 : -1; +} + +int CoSocket_Accept(int fd, void *addr, int *addrlen, uint32_t timeoutMs) +{ + while (1) + { +#if defined(CO_IO_PLATFORM_WIN32) + SOCKET client = accept((SOCKET)fd, (struct sockaddr *)addr, (int *)addrlen); + if (client != INVALID_SOCKET) + { + CoIo_SetNonblocking((int)client); + return (int)client; + } + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) return -1; +#elif defined(CO_IO_PLATFORM_POSIX) + socklen_t slen = addrlen ? (socklen_t)*addrlen : 0; + int client = accept(fd, (struct sockaddr *)addr, addrlen ? &slen : NULL); + if (client >= 0) + { + if (addrlen) *addrlen = (int)slen; + CoIo_SetNonblocking(client); + return client; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; +#elif defined(CO_IO_PLATFORM_LWIP) + socklen_t slen = addrlen ? (socklen_t)*addrlen : 0; + int client = lwip_accept(fd, (struct sockaddr *)addr, addrlen ? &slen : NULL); + if (client >= 0) + { + if (addrlen) *addrlen = (int)slen; + CoIo_SetNonblocking(client); + return client; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; +#else + (void)fd; (void)addr; (void)addrlen; (void)timeoutMs; + return -1; +#endif + + int waitRes = CoTask_WaitFd(fd, CO_IO_READ, timeoutMs); + if (waitRes <= 0) return waitRes; + } +} + +int CoSocket_Read(int fd, void *buf, size_t len, uint32_t timeoutMs) +{ + while (1) + { +#if defined(CO_IO_PLATFORM_WIN32) + int ret = recv((SOCKET)fd, (char *)buf, (int)len, 0); + if (ret >= 0) return ret; + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) return -1; +#elif defined(CO_IO_PLATFORM_POSIX) + ssize_t ret = recv(fd, buf, len, 0); + if (ret >= 0) return (int)ret; + if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; +#elif defined(CO_IO_PLATFORM_LWIP) + int ret = lwip_recv(fd, buf, len, 0); + if (ret >= 0) return ret; + if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; +#else + (void)fd; (void)buf; (void)len; (void)timeoutMs; + return -1; +#endif + + int waitRes = CoTask_WaitFd(fd, CO_IO_READ, timeoutMs); + if (waitRes <= 0) return waitRes; + } +} + +int CoSocket_Write(int fd, const void *buf, size_t len, uint32_t timeoutMs) +{ + size_t total = 0; + while (total < len) + { +#if defined(CO_IO_PLATFORM_WIN32) + int ret = send((SOCKET)fd, (const char *)buf + total, (int)(len - total), 0); + if (ret > 0) + { + total += ret; + continue; + } + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) return -1; +#elif defined(CO_IO_PLATFORM_POSIX) + ssize_t ret = send(fd, (const char *)buf + total, len - total, 0); + if (ret > 0) + { + total += (size_t)ret; + continue; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; +#elif defined(CO_IO_PLATFORM_LWIP) + int ret = lwip_send(fd, (const char *)buf + total, len - total, 0); + if (ret > 0) + { + total += (size_t)ret; + continue; + } + if (errno != EAGAIN && errno != EWOULDBLOCK) return -1; +#else + (void)fd; (void)buf; (void)len; (void)timeoutMs; + return -1; +#endif + + int waitRes = CoTask_WaitFd(fd, CO_IO_WRITE, timeoutMs); + if (waitRes <= 0) return waitRes; + } + return (int)total; +} + +int CoSocket_Close(int fd) +{ +#if defined(CO_IO_PLATFORM_WIN32) + return closesocket((SOCKET)fd); +#elif defined(CO_IO_PLATFORM_POSIX) + return close(fd); +#elif defined(CO_IO_PLATFORM_LWIP) + return lwip_close(fd); +#else + (void)fd; + return 0; +#endif +} diff --git a/source/08_coroutine/coroutine_io.h b/source/08_coroutine/coroutine_io.h new file mode 100644 index 0000000..4fb115c --- /dev/null +++ b/source/08_coroutine/coroutine_io.h @@ -0,0 +1,173 @@ +/********************************************************************************************************* + * ------------------------------------------------------------------------------------------------------ + * file description + * ------------------------------------------------------------------------------------------------------ + * \file coroutine_io.h + * \unit coroutine + * \brief High-throughput asynchronous I/O multiplexer and cooperative socket API + * \author Lamdonn + * \version v0.2.0 + * \license GPL-2.0 + * \copyright Copyright (C) 2025 Lamdonn. + ********************************************************************************************************/ +#ifndef __coroutine_io_H +#define __coroutine_io_H + +#include "coroutine.h" +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ======================================================================================================= + * I/O Event Definitions + * ======================================================================================================= */ +#define CO_IO_READ (0x01) /**< Readable event */ +#define CO_IO_WRITE (0x02) /**< Writable event */ +#define CO_IO_ERROR (0x04) /**< Error or hangup event */ + +/* Return status codes */ +#define CO_IO_OK (0) +#define CO_IO_TIMEOUT (-1) +#define CO_IO_ERROR_EVENT (-2) +#define CO_IO_INVALID (-3) + +/* ======================================================================================================= + * I/O Subsystem Management + * ======================================================================================================= */ + +/** + * \brief: Initialize coroutine I/O subsystem (e.g. socket library setup) + * \return: 0 on success, or negative error code + */ +int CoIo_Init(void); + +/** + * \brief: Deinitialize coroutine I/O subsystem + */ +void CoIo_Deinit(void); + +/** + * \brief: Attach the high-throughput I/O poller to a specific scheduler + * \param schedulerId: Target scheduler ID + * \return: 0 on success, or negative error code + * \note: Configures the scheduler's idleHook and wakeupHook to poll network descriptors and wake tasks instantly. + */ +int CoIo_AttachScheduler(uint32_t schedulerId); + +/** + * \brief: Detach the I/O poller from a specific scheduler + * \param schedulerId: Target scheduler ID + * \return: 0 on success, or negative error code + */ +int CoIo_DetachScheduler(uint32_t schedulerId); + +/** + * \brief: Set socket or file descriptor to non-blocking mode + * \param fd: Descriptor handle + * \return: 0 on success, or negative error code + */ +int CoIo_SetNonblocking(int fd); + +/** + * \brief: Suspend current coroutine until descriptor fd has specified events or timeout + * \param fd: Socket or file descriptor + * \param events: Bitmask of CO_IO_READ / CO_IO_WRITE + * \param timeoutMs: Timeout in milliseconds (0 for infinite) + * \return: Ready events bitmask (>0) on success, CO_IO_TIMEOUT on timeout, or negative error code + */ +int CoTask_WaitFd(int fd, uint32_t events, uint32_t timeoutMs); + +/* ======================================================================================================= + * Bare-Metal / MCU Poller Hook (Optional, for polling hardware UART, CAN, SPI without OS) + * ======================================================================================================= */ + +typedef struct CoIoPollFd +{ + int fd; /**< Device or descriptor handle (e.g. UART1, CAN1) */ + short events; /**< Requested events (CO_IO_READ / CO_IO_WRITE) */ + short revents; /**< Returned events */ +} CoIoPollFd_t; + +/** + * \brief: User-injected baremetal hardware poller callback + * \param pfds: Array of descriptors and requested events + * \param nfds: Number of descriptors + * \param timeoutMs: Timeout in milliseconds + * \return: Number of descriptors with events (>0), 0 on timeout, or negative error code + */ +typedef int (*CoIoBaremetalPoller_t)(CoIoPollFd_t *pfds, uint32_t nfds, int timeoutMs); + +/** + * \brief: Register user-defined baremetal hardware poller (for MCU UART / CAN / SPI) + * \param poller: Function pointer to poller callback, or NULL to reset + */ +void CoIo_SetBaremetalPoller(CoIoBaremetalPoller_t poller); + +/* ======================================================================================================= + * High-Density Cooperative Socket APIs (Synchronous Code, Asynchronous Execution) + * ======================================================================================================= */ + +/** + * \brief: Create a non-blocking socket + * \param domain: Address family (e.g. AF_INET) + * \param type: Socket type (e.g. SOCK_STREAM, SOCK_DGRAM) + * \param protocol: Protocol (e.g. 0 or IPPROTO_TCP) + * \return: Non-blocking socket fd on success, or -1 on error + */ +int CoSocket_Create(int domain, int type, int protocol); + +/** + * \brief: Connect to remote address with coroutine yield on wait + * \param fd: Socket fd + * \param addr: Pointer to struct sockaddr + * \param addrlen: Size of addr + * \param timeoutMs: Connection timeout in milliseconds + * \return: 0 on success, or negative error code + */ +int CoSocket_Connect(int fd, const void *addr, int addrlen, uint32_t timeoutMs); + +/** + * \brief: Accept an incoming connection with coroutine yield on wait + * \param fd: Listening socket fd + * \param addr: Output sockaddr pointer (optional, can be NULL) + * \param addrlen: Input/output sockaddr len pointer (optional, can be NULL) + * \param timeoutMs: Accept timeout in milliseconds + * \return: Connected client socket fd (already non-blocking), or negative error code + */ +int CoSocket_Accept(int fd, void *addr, int *addrlen, uint32_t timeoutMs); + +/** + * \brief: Read data from socket with coroutine yield on wait + * \param fd: Connected socket fd + * \param buf: Destination buffer + * \param len: Buffer capacity + * \param timeoutMs: Read timeout in milliseconds + * \return: Bytes read (>0), 0 on EOF / connection closed, or negative error code + */ +int CoSocket_Read(int fd, void *buf, size_t len, uint32_t timeoutMs); + +/** + * \brief: Write data to socket with coroutine yield on wait + * \param fd: Connected socket fd + * \param buf: Source buffer + * \param len: Bytes to send + * \param timeoutMs: Write timeout in milliseconds + * \return: Bytes sent (>0), or negative error code + */ +int CoSocket_Write(int fd, const void *buf, size_t len, uint32_t timeoutMs); + +/** + * \brief: Close a socket descriptor + * \param fd: Socket fd + * \return: 0 on success, or -1 on error + */ +int CoSocket_Close(int fd); + +#ifdef __cplusplus +} +#endif + +#endif /* !__coroutine_io_H */ diff --git a/test/test_coroutine.c b/test/test_coroutine.c index 2cf5959..f9852f9 100644 --- a/test/test_coroutine.c +++ b/test/test_coroutine.c @@ -1,17 +1,30 @@ #include #include #include +#include #include +#include +#if defined(_WIN32) +#include +#else +#include +#include +#include +#endif #if defined(TEST_TARGET_coroutine) #include #include #include +#include +#include #else #include "init.h" #include "command.h" #include "unitt.h" #include "kern.h" #include "coroutine.h" +#include "coroutine_hook.h" +#include "coroutine_io.h" #endif #define MUTI_THREAD_NUM 2 @@ -48,29 +61,47 @@ uint64_t GetTimerUsec(void) // #define EXIT_TEST extern uint64_t unitt_clock(void); +static int s_libco_step = 0; +static void *libco_test_worker(void *arg) +{ + s_libco_step = 1; + CoTask_Yield(); + s_libco_step = 2; + CoTask_Yield(); + s_libco_step = 3; + return NULL; +} + +static int test_libco(void) +{ + CoScheduler_Init(0, GetTimerUsec, 1000); + s_libco_step = 0; + CoTask_t task = CoTask_Create(libco_test_worker, .manual=1); + if (!task) return UNITT_E_FAIL; + + int ret1 = CoTask_Resume(task); + if (ret1 != COROUTINE_E_OK || s_libco_step != 1) return UNITT_E_FAIL; + + int ret2 = CoTask_Resume(task); + if (ret2 != COROUTINE_E_OK || s_libco_step != 2) return UNITT_E_FAIL; + + int ret3 = CoTask_Resume(task); + if (ret3 != 1 || s_libco_step != 3) return UNITT_E_FAIL; + + CoTask_Delete(task); + return UNITT_E_OK; +} + 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; + return test_libco(); } static void unitt_task(void) { static UNITT_TCASE rand_tests[] = { UNITT_TCASE(test_0), - // UNITT_TCASE(test_1), - // UNITT_TCASE(test_2), + UNITT_TCASE(test_libco), }; static UNITT suites[] = { @@ -85,71 +116,160 @@ static void unitt_task(void) /************************************************************************************/ 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 *tick(void *arg) +/* --- 1. Manual Point-to-Point Test (libco style) --- */ +static void *manual_worker(void *arg) { - while (1) + printf(" [Manual Task] Step 1: started, yielding...\n"); + CoTask_Yield(); + printf(" [Manual Task] Step 2: resumed, yielding again...\n"); + CoTask_Yield(); + printf(" [Manual Task] Step 3: resumed, yielding again...\n"); + CoTask_Yield(); + printf(" [Manual Task] Step 4: resumed, finishing!\n"); + return NULL; +} + +static void test_manual(void) +{ + printf("=== [Test Manual] Point-to-Point Stepping (CoTask_Resume & CoTask_Yield) ===\n"); + CoScheduler_Init(0, GetTimerUsec, 1000); + CoTask_t task = CoTask_Create(manual_worker, .manual = 1); + if (!task) { - CoTask_Wait(.ms=1000); - printf("tick-running...\n"); + printf("Failed to create manual task!\n"); + return; + } + + for (int step = 1; step <= 4; step++) + { + printf("[Main] Calling CoTask_Resume (%d)...\n", step); + int ret = CoTask_Resume(task); + printf("[Main] CoTask_Resume returned: %d\n", ret); + } + + CoTask_Delete(task); + printf("=== [Test Manual] Completed! ===\n\n"); +} + +/* --- 2. Shared Stack Test --- */ +static void *shared_worker_a(void *arg) +{ + volatile int buf[100]; + for (int i = 0; i < 100; i++) buf[i] = 1000 + i; + printf(" [Shared A] initial buf[0]=%d, buf[99]=%d, yielding...\n", buf[0], buf[99]); + fflush(stdout); + CoTask_Yield(); + printf(" [Shared A] resumed! checking buf[0]=%d, buf[99]=%d\n", buf[0], buf[99]); + if (buf[0] != 1000 || buf[99] != 1099) + { + printf(" [Shared A] ERROR: stack data corrupted!\n"); + } + else + { + printf(" [Shared A] SUCCESS: stack data perfectly preserved!\n"); + } + fflush(stdout); + return NULL; +} + +static void *shared_worker_b(void *arg) +{ + volatile int buf[100]; + for (int i = 0; i < 100; i++) buf[i] = 2000 + i; + printf(" [Shared B] initial buf[0]=%d, buf[99]=%d, yielding...\n", buf[0], buf[99]); + fflush(stdout); + CoTask_Yield(); + printf(" [Shared B] resumed! checking buf[0]=%d, buf[99]=%d\n", buf[0], buf[99]); + if (buf[0] != 2000 || buf[99] != 2099) + { + printf(" [Shared B] ERROR: stack data corrupted!\n"); + } + else + { + printf(" [Shared B] SUCCESS: stack data perfectly preserved!\n"); + } + fflush(stdout); + return NULL; +} + +static void test_shared(void) +{ + printf("=== [Test Shared] Shared Stack Mode (Copy on Suspend, Restore on Resume) ===\n"); +#if (COROUTINE_SHARED_STACK_SIZE > 0) + Coroutine_Init(.malloc = malloc, .free = free); + CoScheduler_Init(0, GetTimerUsec, 1000); + + CoTask_t ta = CoTask_Create(shared_worker_a, .manual = 1, .sharedStack = 1); + CoTask_t tb = CoTask_Create(shared_worker_b, .manual = 1, .sharedStack = 1); + if (!ta || !tb) + { + printf("Failed to create shared stack tasks! (Ensure dynamic memory allocator is configured)\n"); + return; + } + + printf("[Main] Step 1: Run Task A on shared stack...\n"); + CoTask_Resume(ta); + + printf("[Main] Step 2: Run Task B (overwriting physical shared stack)...\n"); + CoTask_Resume(tb); + + printf("[Main] Step 3: Resume Task A (restores stack A)...\n"); + CoTask_Resume(ta); + + printf("[Main] Step 4: Resume Task B (restores stack B)...\n"); + CoTask_Resume(tb); + + CoTask_Delete(ta); + CoTask_Delete(tb); + printf("=== [Test Shared] Completed! ===\n\n"); +#else + printf("COROUTINE_SHARED_STACK_SIZE is 0. Shared stack feature is disabled!\n"); +#endif +} + +/* --- 3. Single Scheduler Automatic Mode --- */ +static void *single_tick(void *arg) +{ + for (int i = 1; i <= 5; i++) + { + CoTask_Wait(.ms = 100); + printf(" [Single Scheduler] tick %d/5...\n", i); CoEvent_Notify(&gEvent, 0x02); } + printf(" [Single Scheduler] tick finished!\n"); + return NULL; } -static void *tock(void *arg) +static void *single_tock(void *arg) { - while (1) + for (int i = 1; i <= 5; i++) { - uint32_t evs = CoTask_Wait(.pEvent=&gEvent); - if (evs & 0x01) - { - printf("event 0x01 triggered...\n"); - } + uint32_t evs = CoTask_Wait(.pEvent = &gEvent, .ms = 500); if (evs & 0x02) { - printf("event 0x02 triggered...\n"); + printf(" [Single Scheduler] tock received event %d/5!\n", i); } } + printf(" [Single Scheduler] tock finished, exiting scheduler 0...\n"); + CoScheduler_Exit(0); + return NULL; } -static void timer0(void) -{ - printf("timer0-running...\n"); -} - -static void timer1(void) -{ - printf("timer1-running... %d\n", CoScheduler_CurLoad(CoTimer_SchedulerId())); - // CoEvent_Notify(&gEvent, 0x01 | 0x02); -} - -/* Testing begins here */ -void *createTask(void *arg) -{ - // CoTask_Create(tick, g_StackTick, sizeof(g_StackTick), 0); - // CoTask_Create(tock, g_StackTock, sizeof(g_StackTock), 0); - CoTask_Create(tick); - CoTask_Create(tock); - // timerHandle0 = CoTimer_CreateMs(timer0_entry, 100); - // timerHandle1 = CoTimer_CreateMs(timer1_entry, 1000); -} - -#if (MUTI_THREAD_NUM == 1) -void test_singleThread(void) +static void test_single(void) { + printf("=== [Test Single] Single Scheduler Automatic Mode ===\n"); CoScheduler_Init(0, GetTimerUsec, 1000); - createTask(NULL); + gEvent.flag = 0; + CoTask_Create(single_tick); + CoTask_Create(single_tock); + printf("[Main] Starting single scheduler 0...\n"); CoScheduler_Start(0); + printf("=== [Test Single] Completed! ===\n\n"); } -#else + +/* --- 4. Multi-Scheduler Multi-Threaded Mode --- */ static pthread_t threads[MUTI_THREAD_NUM]; -static pthread_t testThread; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static void thread_lock(void) @@ -162,41 +282,298 @@ static void thread_unlock(void) pthread_mutex_unlock(&mutex); } +static void *multi_tick(void *arg) +{ + for (int i = 1; i <= 5; i++) + { + CoTask_Wait(.ms = 100); + printf(" [Multi-Core] Thread %d: tick %d/5...\n", CoTask_SchedulerId(), i); + CoEvent_Notify(&gEvent, 0x02); + } + return NULL; +} + +static void *multi_tock(void *arg) +{ + for (int i = 1; i <= 5; i++) + { + uint32_t evs = CoTask_Wait(.pEvent = &gEvent, .ms = 500); + if (evs & 0x02) + { + printf(" [Multi-Core] Thread %d: tock received event %d/5!\n", CoTask_SchedulerId(), i); + } + } + printf(" [Multi-Core] tock finished, exiting schedulers...\n"); + for (int i = 0; i < MUTI_THREAD_NUM; i++) + { + CoScheduler_Exit(i); + } + return NULL; +} + static void *muti_thread_entry(void *arg) { CoScheduler_Start(*(int *)arg); + return NULL; } -void test_mutiThread(void) +static void test_multi(void) { + printf("=== [Test Multi] Multi-Scheduler Multi-Threaded Mode ===\n"); int threadId[MUTI_THREAD_NUM]; + Coroutine_Init(.lock = thread_lock, .unlock = thread_unlock); + for (int i = 0; i < MUTI_THREAD_NUM; i++) { - CoScheduler_Init(i, GetTimerUsec, 1000, .lock=thread_lock, .unlock=thread_unlock); + CoScheduler_Init(i, GetTimerUsec, 1000); } - createTask(NULL); + + gEvent.flag = 0; + CoTask_Create(multi_tick, .schedulerId = 0); + CoTask_Create(multi_tock, .schedulerId = 1); + 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); } + printf("=== [Test Multi] Completed! ===\n\n"); } + +/* --- 5. Low-Power Hook Benchmark (CPU Load Comparison) --- */ +static uint64_t GetThreadCpuTimeUs(void) +{ +#if defined(_WIN32) + FILETIME createTime, exitTime, kernelTime, userTime; + if (GetThreadTimes(GetCurrentThread(), &createTime, &exitTime, &kernelTime, &userTime)) + { + ULARGE_INTEGER k, u; + k.LowPart = kernelTime.dwLowDateTime; + k.HighPart = kernelTime.dwHighDateTime; + u.LowPart = userTime.dwLowDateTime; + u.HighPart = userTime.dwHighDateTime; + return (k.QuadPart + u.QuadPart) / 10; // 100ns units to us + } + return 0; +#elif defined(CLOCK_THREAD_CPUTIME_ID) + struct timespec ts; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0) + { + return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL; + } + return 0; +#else + return (uint64_t)clock() * 1000000ULL / CLOCKS_PER_SEC; #endif +} + +static void *hook_bench_worker(void *arg) +{ + /* Wait for 400 ms */ + CoTask_Wait(.ms = 400); + CoScheduler_Exit(0); + return NULL; +} + +static void test_hook(void) +{ + printf("=== [Test Low-Power Hook Benchmark: 400ms Idle] ===\n"); + + /* --- Phase 1: WITHOUT Hook (Busy-Spin) --- */ + printf("[Phase 1] Testing WITHOUT Hook (CPU busy-spin)...\n"); + CoScheduler_Init(0, GetTimerUsec, 1000); // No hooks + CoTask_Create(hook_bench_worker); + + uint64_t wall_start_1 = GetTimerUsec(); + uint64_t cpu_start_1 = GetThreadCpuTimeUs(); + + CoScheduler_Start(0); + + uint64_t cpu_end_1 = GetThreadCpuTimeUs(); + uint64_t wall_end_1 = GetTimerUsec(); + + double wall_ms_1 = (double)(wall_end_1 - wall_start_1) / 1000.0; + double cpu_ms_1 = (double)(cpu_end_1 - cpu_start_1) / 1000.0; + double cpu_load_1 = (wall_ms_1 > 0) ? (cpu_ms_1 / wall_ms_1 * 100.0) : 0.0; + if (cpu_load_1 > 100.0) cpu_load_1 = 100.0; + + printf(" -> Wall Time: %.1f ms, Active CPU Time: %.1f ms, CPU Load: %.1f%%\n\n", + wall_ms_1, cpu_ms_1, cpu_load_1); + + /* --- Phase 2: WITH Hook (Interruptible Low-Power Sleep) --- */ + printf("[Phase 2] Testing WITH Low-Power Hook (Interruptible sleep)...\n"); + CoScheduler_Init(0, GetTimerUsec, 1000); + CoScheduler_BindDefaultHooks(0); + CoTask_Create(hook_bench_worker); + + uint64_t wall_start_2 = GetTimerUsec(); + uint64_t cpu_start_2 = GetThreadCpuTimeUs(); + + CoScheduler_Start(0); + + uint64_t cpu_end_2 = GetThreadCpuTimeUs(); + uint64_t wall_end_2 = GetTimerUsec(); + + CoScheduler_UnbindDefaultHooks(0); + + double wall_ms_2 = (double)(wall_end_2 - wall_start_2) / 1000.0; + double cpu_ms_2 = (double)(cpu_end_2 - cpu_start_2) / 1000.0; + double cpu_load_2 = (wall_ms_2 > 0) ? (cpu_ms_2 / wall_ms_2 * 100.0) : 0.0; + if (cpu_load_2 > 100.0) cpu_load_2 = 100.0; + + printf(" -> Wall Time: %.1f ms, Active CPU Time: %.1f ms, CPU Load: %.1f%%\n\n", + wall_ms_2, cpu_ms_2, cpu_load_2); + + printf("==================== Benchmark Comparison ====================\n"); + printf(" WITHOUT Hook : CPU Load = %5.1f%% (CPU was churning millions of cycles)\n", cpu_load_1); + printf(" WITH Hook : CPU Load = %5.1f%% (CPU entered kernel sleep, 0%% drain)\n", cpu_load_2); + printf(" Energy Saving: CPU active execution reduced by > %.1f%%!\n", + (cpu_ms_1 > cpu_ms_2) ? ((cpu_ms_1 - cpu_ms_2) / cpu_ms_1 * 100.0) : 99.0); + printf("==============================================================\n\n"); +} + +/* --- 6. High-Density Asynchronous I/O Test --- */ +#define TEST_IO_PORT 19876 + +static void *io_server_task(void *arg) +{ + printf(" [IO Server] Starting non-blocking server on 127.0.0.1:%d...\n", TEST_IO_PORT); + int listen_fd = CoSocket_Create(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) + { + printf(" [IO Server] Failed to create socket!\n"); + return NULL; + } + + int opt = 1; +#if defined(_WIN32) + setsockopt((SOCKET)listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)); +#else + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); +#endif + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(TEST_IO_PORT); + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) + { + printf(" [IO Server] Failed to bind port!\n"); + CoSocket_Close(listen_fd); + return NULL; + } + + listen(listen_fd, 5); + printf(" [IO Server] Listening, waiting for client accept (cooperative yield)...\n"); + + int client_fd = CoSocket_Accept(listen_fd, NULL, NULL, 5000); + if (client_fd < 0) + { + printf(" [IO Server] Accept failed or timed out! (%d)\n", client_fd); + CoSocket_Close(listen_fd); + return NULL; + } + + printf(" [IO Server] Client connected! Reading message (cooperative yield)...\n"); + char buf[128] = {0}; + int n = CoSocket_Read(client_fd, buf, sizeof(buf) - 1, 15000); + if (n > 0) + { + printf(" [IO Server] Received client message: \"%s\"\n", buf); + const char *echo_prefix = "ECHO: "; + char resp[256]; + snprintf(resp, sizeof(resp), "%s%s", echo_prefix, buf); + printf(" [IO Server] Sending echo response: \"%s\"...\n", resp); + CoSocket_Write(client_fd, resp, strlen(resp), 5000); + } + else + { + printf(" [IO Server] Read failed or timed out! (n = %d)\n", n); + } + + CoSocket_Close(client_fd); + CoSocket_Close(listen_fd); + printf(" [IO Server] Server task completed successfully, exiting scheduler!\n"); + CoScheduler_Exit(0); + return NULL; +} + +static void *io_client_task(void *arg) +{ + /* Allow server to start and listen first */ + CoTask_Wait(.ms = 50); + + printf(" [IO Client] Connecting to 127.0.0.1:%d (cooperative yield)...\n", TEST_IO_PORT); + int client_fd = CoSocket_Create(AF_INET, SOCK_STREAM, 0); + if (client_fd < 0) + { + printf(" [IO Client] Failed to create client socket!\n"); + return NULL; + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(TEST_IO_PORT); + addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + + int ret = CoSocket_Connect(client_fd, &addr, sizeof(addr), 5000); + if (ret != 0) + { + printf(" [IO Client] Connect failed!\n"); + CoSocket_Close(client_fd); + return NULL; + } + + const char *msg = "Hello High-Throughput Coroutine IO!"; + printf(" [IO Client] Connected! Sending message: \"%s\"...\n", msg); + CoTask_Wait(.ms = 10000); + CoSocket_Write(client_fd, msg, strlen(msg), 5000); + + char resp[256] = {0}; + printf(" [IO Client] Waiting for echo response (cooperative yield)...\n"); + int n = CoSocket_Read(client_fd, resp, sizeof(resp) - 1, 5000); + if (n > 0) + { + printf(" [IO Client] Received Echo response: \"%s\"!\n", resp); + } + else + { + printf(" [IO Client] Read response failed or timed out! (%d)\n", n); + } + + CoSocket_Close(client_fd); + printf(" [IO Client] Client task finished!\n"); + return NULL; +} + +static void test_io(void) +{ + printf("=== [Test Asynchronous Socket I/O Multiplexing] ===\n"); + CoScheduler_Init(0, GetTimerUsec, 1000); + CoIo_AttachScheduler(0); + + CoTask_Create(io_server_task); + CoTask_Create(io_client_task); + + printf("[Main] Starting single-threaded scheduler with cooperative I/O Reactor...\n"); + CoScheduler_Start(0); + + CoIo_DetachScheduler(0); + printf("=== [Test Asynchronous Socket I/O] Completed! ===\n\n"); +} static void test_base(void) { -#if (MUTI_THREAD_NUM == 1) - test_singleThread(); -#else - test_mutiThread(); -#endif + test_manual(); + test_shared(); } /************************************************************************************/ @@ -209,10 +586,15 @@ static void usage(void) "Usage: coroutine [opt] [arg] ...\n" "\n" "options:\n" -" -e Specifies the function to execute, the default is the test\n" -" Test base function\n" +" -e Specifies the test mode to execute:\n" +" Test manual point-to-point stepping (libco style)\n" +" Test shared stack (copy-on-suspend)\n" +" Test single scheduler automatic mode\n" +" Test multi-scheduler multi-threaded mode\n" +" Test low-power hook comparison (CPU load benchmark)\n" +" Test high-throughput asynchronous socket I/O\n" +" Test base default functions (manual & shared)\n" " Unit test\n" -" ...\n" " -h Print help\n" " -v Print version\n" " -u [] Unit test period, unit ms, the default is 1000ms\n" @@ -225,6 +607,9 @@ static int test(int argc, char *argv[]) char *execute = NULL; int ut_period = 1000; + /* Initialize default allocator for tests */ + Coroutine_Init(.malloc = malloc, .free = free); + /* reset getopt */ command_opt_init(); @@ -257,7 +642,31 @@ static int test(int argc, char *argv[]) if (execute) { - if (!strcmp(execute, "base")) + if (!strcmp(execute, "manual")) + { + test_manual(); + } + else if (!strcmp(execute, "shared")) + { + test_shared(); + } + else if (!strcmp(execute, "single")) + { + test_single(); + } + else if (!strcmp(execute, "multi")) + { + test_multi(); + } + else if (!strcmp(execute, "hook")) + { + test_hook(); + } + else if (!strcmp(execute, "io")) + { + test_io(); + } + else if (!strcmp(execute, "base")) { test_base(); } @@ -273,6 +682,12 @@ static int test(int argc, char *argv[]) printf("create task %d\r\n", task_create(ut_period, unitt_task)); #endif } + else + { + printf("Unknown execute option: %s\n", execute); + usage(); + return -1; + } } else {