Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ci/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ get_timeout() {

case "${OS_TYPE}" in
Darwin)
echo "600"
echo "900"
;;
Linux)
echo "90"
Expand Down
4 changes: 2 additions & 2 deletions .ci/test-netdev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export SCRIPT_DIR
source "${SCRIPT_DIR}/common.sh"

# Override timeout for netdev tests
# Network tests need different timeout: 30s for Linux, 600s for macOS
# Network tests need different timeout: 30s for Linux, 900s for macOS
case "${OS_TYPE}" in
Darwin)
TIMEOUT=600
TIMEOUT=900
;;
Linux)
TIMEOUT=30
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ jobs:
- name: automated test
run: .ci/autorun.sh
shell: bash
timeout-minutes: 15
timeout-minutes: 20
- name: netdev test
# NOTE: vmnet requires sudo privileges which may not be available in GitHub Actions
# This test is conditional and will be skipped if sudo is not available
Expand Down
63 changes: 35 additions & 28 deletions coro.c
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ typedef struct {
/* Internal coroutine structure */

typedef struct {
uint32_t id; /* Coroutine identifier */
void (*func)(void *); /* Entry point function (user-provided) */
void *user_data; /* User data (hart pointer) */
coro_state_t state; /* Current state */
Expand All @@ -213,7 +214,8 @@ typedef struct {

static struct {
coro_t **coroutines; /* Array of coroutine pointers */
uint32_t n_hart; /* Number of harts */
uint32_t total_slots; /* Total number of coroutine slots */
uint32_t hart_slots; /* Number of slots reserved for harts */
uint32_t current_hart; /* Currently executing hart ID */
bool initialized; /* True if subsystem initialized */
coro_t *running; /* Currently running coroutine */
Expand Down Expand Up @@ -399,25 +401,27 @@ static void coro_entry_wrapper(void *arg)

/* Public API implementation */

bool coro_init(uint32_t n_hart)
bool coro_init(uint32_t total_slots, uint32_t hart_slots)
{
if (coro_state.initialized) {
fprintf(stderr, "coro_init: already initialized\n");
return false;
}

if (n_hart == 0 || n_hart > 32) {
fprintf(stderr, "coro_init: invalid n_hart=%u\n", n_hart);
if (total_slots == 0 || total_slots > 256 || hart_slots > total_slots) {
fprintf(stderr, "coro_init: invalid slots (total=%u, hart=%u)\n",
total_slots, hart_slots);
return false;
}

coro_state.coroutines = calloc(n_hart, sizeof(coro_t *));
coro_state.coroutines = calloc(total_slots, sizeof(coro_t *));
if (!coro_state.coroutines) {
fprintf(stderr, "coro_init: failed to allocate coroutines array\n");
return false;
}

coro_state.n_hart = n_hart;
coro_state.total_slots = total_slots;
coro_state.hart_slots = hart_slots;
coro_state.current_hart = CORO_HART_ID_IDLE;
coro_state.initialized = true;
coro_state.running = NULL;
Expand All @@ -430,7 +434,7 @@ void coro_cleanup(void)
if (!coro_state.initialized)
return;

for (uint32_t i = 0; i < coro_state.n_hart; i++) {
for (uint32_t i = 0; i < coro_state.total_slots; i++) {
if (coro_state.coroutines[i]) {
coro_t *co = coro_state.coroutines[i];
if (co->context) {
Expand All @@ -446,22 +450,23 @@ void coro_cleanup(void)

free(coro_state.coroutines);
coro_state.coroutines = NULL;
coro_state.n_hart = 0;
coro_state.total_slots = 0;
coro_state.hart_slots = 0;
coro_state.current_hart = CORO_HART_ID_IDLE; /* Reset to idle state */
coro_state.initialized = false;
coro_state.running = NULL;
tls_running_coro = NULL; /* Reset TLS as well */
}

bool coro_create_hart(uint32_t hart_id, void (*func)(void *), void *hart)
bool coro_create_hart(uint32_t slot_id, void (*func)(void *), void *arg)
{
if (!coro_state.initialized) {
fprintf(stderr, "coro_create_hart: not initialized\n");
return false;
}

if (hart_id >= coro_state.n_hart) {
fprintf(stderr, "coro_create_hart: invalid hart_id=%u\n", hart_id);
if (slot_id >= coro_state.total_slots) {
fprintf(stderr, "coro_create_hart: invalid slot_id=%u\n", slot_id);
return false;
}

Expand All @@ -470,9 +475,9 @@ bool coro_create_hart(uint32_t hart_id, void (*func)(void *), void *hart)
return false;
}

if (coro_state.coroutines[hart_id]) {
fprintf(stderr, "coro_create_hart: hart %u already has coroutine\n",
hart_id);
if (coro_state.coroutines[slot_id]) {
fprintf(stderr, "coro_create_hart: slot %u already has coroutine\n",
slot_id);
return false;
}

Expand All @@ -484,8 +489,9 @@ bool coro_create_hart(uint32_t hart_id, void (*func)(void *), void *hart)
}

/* Store user function and data */
co->id = slot_id;
co->func = func;
co->user_data = hart;
co->user_data = arg;
co->state = CORO_STATE_SUSPENDED;

/* Allocate context */
Expand Down Expand Up @@ -529,34 +535,35 @@ bool coro_create_hart(uint32_t hart_id, void (*func)(void *), void *hart)
}
#endif

coro_state.coroutines[hart_id] = co;
coro_state.coroutines[slot_id] = co;
return true;
}

void coro_resume_hart(uint32_t hart_id)
void coro_resume_hart(uint32_t slot_id)
{
if (!coro_state.initialized || hart_id >= coro_state.n_hart) {
fprintf(stderr, "coro_resume_hart: invalid hart_id=%u\n", hart_id);
if (!coro_state.initialized || slot_id >= coro_state.total_slots) {
fprintf(stderr, "coro_resume_hart: invalid slot_id=%u\n", slot_id);
return;
}

coro_t *co = coro_state.coroutines[hart_id];
coro_t *co = coro_state.coroutines[slot_id];
if (!co || !co->context) {
fprintf(stderr, "coro_resume_hart: hart %u has no coroutine\n",
hart_id);
fprintf(stderr, "coro_resume_hart: slot %u has no coroutine\n",
slot_id);
return;
}

if (co->state != CORO_STATE_SUSPENDED) {
fprintf(stderr, "coro_resume_hart: hart %u not suspended (state=%d)\n",
hart_id, co->state);
/* This may happen if a coroutine is waiting on I/O and the main loop
* tries to resume it. It is not a fatal error.
*/
return;
}

/* Check for stack overflow before resuming */
coro_check_stack(co);

coro_state.current_hart = hart_id;
coro_state.current_hart = slot_id;
co->state = CORO_STATE_RUNNING;
jump_into(co);

Expand Down Expand Up @@ -586,12 +593,12 @@ void coro_yield(void)
jump_out(co);
}

bool coro_is_suspended(uint32_t hart_id)
bool coro_is_suspended(uint32_t slot_id)
{
if (!coro_state.initialized || hart_id >= coro_state.n_hart)
if (!coro_state.initialized || slot_id >= coro_state.hart_slots)
return false;

coro_t *co = coro_state.coroutines[hart_id];
coro_t *co = coro_state.coroutines[slot_id];
if (!co || !co->context)
return false;

Expand Down
45 changes: 20 additions & 25 deletions coro.h
Original file line number Diff line number Diff line change
@@ -1,44 +1,39 @@
/* Lightweight coroutine for multi-hart execution */

#pragma once

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

/* Forward declaration */
typedef struct __hart_internal hart_t;

/* Initialize coroutine subsystem for a VM with n_hart cores */
bool coro_init(uint32_t n_hart);
/* Sentinel value when no coroutine is executing */
#define CORO_INVALID_ID UINT32_MAX

/* Initialize coroutine subsystem for SMP hart scheduling.
* total_slots: total coroutine slots (usually n_hart)
* hart_slots: number of actual CPU harts
*/
bool coro_init(uint32_t total_slots, uint32_t hart_slots);

/* Cleanup coroutine subsystem */
void coro_cleanup(void);

/* Create coroutine for a specific hart.
* @hart_id: Hart identifier (0 to n_hart-1)
* @func: Entry point function for the coroutine
* @hart: User data (hart_t pointer) passed to the entry function
*
* Returns: true on success, false on failure
/* Create coroutine for a hart.
* slot_id: Hart identifier
* func: Entry point (hart execution loop)
* arg: User data (hart_t pointer)
*/
bool coro_create_hart(uint32_t hart_id, void (*func)(void *), void *hart);
bool coro_create_hart(uint32_t slot_id, void (*func)(void *), void *arg);

/* Resume execution of a specific hart's coroutine
* The coroutine will execute until it yields or terminates.
*/
void coro_resume_hart(uint32_t hart_id);
/* Resume execution of a hart coroutine */
void coro_resume_hart(uint32_t slot_id);

/* Yield from current hart (called from WFI)
* Suspends the current coroutine and returns control to the scheduler.
*/
/* Yield from current hart (called from WFI) */
void coro_yield(void);

/* Check if a hart's coroutine is suspended (waiting in WFI)
* Returns: true if suspended, false otherwise
*/
bool coro_is_suspended(uint32_t hart_id);
/* Check if hart is suspended (in WFI) */
bool coro_is_suspended(uint32_t slot_id);

/* Get the currently running hart ID
* Returns: Hart ID of the currently executing coroutine, or UINT32_MAX if idle
*/
/* Get currently executing hart ID */
uint32_t coro_current_hart_id(void);
Loading
Loading