Update coroutine

1. Compatibility with coroutine API highly intensive IO, sockets, etc
2. Fix running fail on arm64
3. Add low power configuration hook
TODO:
1. Add a scheduling ready task queue mechanism
2. Add CoTask communication channel
This commit is contained in:
Lamdonn 2026-10-10 23:37:10 +08:00 committed by Lamdonn
parent af9120c9f8
commit c0a7f18093
9 changed files with 3095 additions and 379 deletions

View File

@ -68,6 +68,7 @@ ifeq ($(OS),Windows_NT)
UNAME := Windows
else
UNAME := $(shell uname -s)
CFLAG += -D_GNU_SOURCE
endif
##################################################################################

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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

View File

@ -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 <rtthread.h>
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 <os.h>
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 <windows.h>
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 <pthread.h>
#include <time.h>
#include <errno.h>
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;
}

View File

@ -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 <stdint.h>
#include <stddef.h>
#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 */

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
/* =======================================================================================================
* 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 <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
typedef int socklen_t;
typedef WSAPOLLFD PollFd_t;
#elif defined(CO_IO_PLATFORM_POSIX)
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
typedef struct pollfd PollFd_t;
#elif defined(CO_IO_PLATFORM_LWIP)
#include "lwip/sockets.h"
#include "lwip/netdb.h"
#include <errno.h>
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
}

View File

@ -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 <stdint.h>
#include <stddef.h>
#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 */

View File

@ -1,17 +1,30 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#if defined(_WIN32)
#include <windows.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#if defined(TEST_TARGET_coroutine)
#include <varch/command.h>
#include <varch/unitt.h>
#include <varch/coroutine.h>
#include <varch/coroutine_hook.h>
#include <varch/coroutine_io.h>
#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)
{
CoTask_Wait(.ms=1000);
printf("tick-running...\n");
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)
{
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 <execute> Specifies the function to execute, the default is the <base> test\n"
" <base> Test base function\n"
" -e <execute> Specifies the test mode to execute:\n"
" <manual> Test manual point-to-point stepping (libco style)\n"
" <shared> Test shared stack (copy-on-suspend)\n"
" <single> Test single scheduler automatic mode\n"
" <multi> Test multi-scheduler multi-threaded mode\n"
" <hook> Test low-power hook comparison (CPU load benchmark)\n"
" <io> Test high-throughput asynchronous socket I/O\n"
" <base> Test base default functions (manual & shared)\n"
" <ut> Unit test\n"
" ...\n"
" -h Print help\n"
" -v Print version\n"
" -u [<period>] 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
{