From 16ec74398cc405cefefecffabbf7970132b38218 Mon Sep 17 00:00:00 2001 From: srpatcha Date: Fri, 28 Aug 2026 23:35:15 -0700 Subject: [PATCH 1/4] =?UTF-8?q?fix:=20repair=20master=20=E2=80=94=20four?= =?UTF-8?q?=20merges=20landed=20code=20that=20does=20not=20compile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `master` does not configure, and once it does it does not build, and once it builds one suite fails. Each break is the same failure: a PR verified green on its own branch, squash-merged onto a base that had moved, and nothing re-verified the result. 1. tests/CMakeLists.txt registered test_crypto_aes and test_crypto_sha512 twice (#59 landing beside the block that already had them), so CMake refused to configure at all: add_test given test NAME "test_crypto_aes" which already exists 2. eos_task_set_current_internal was defined twice in task.c and declared twice in kernel_internal.h, with two different behaviours — one clears g_current and marks the task RUNNING, the other silently returns. Kept the first, which is what the header documents ("Handles >= EOS_MAX_TASKS clear it") and what the mutex tests rely on; folded the second declaration's comment into the surviving one, since it records why the function exists. 3. sync.c would not compile: g_blocked_on, pi_propagate and task_valid were used and defined nowhere, alongside a reference to a struct member original_prio that mtx_t does not have. #67 and #55 implement incompatible designs for the same feature. #67 recomputes effective priority from effective = min(base_priority, priority of every waiter on every mutex the task holds) on every change; #55 saves and restores a value around each lock. Merging #55 after #67 deleted #67's implementation while leaving its call sites, so neither design was left intact. Restored #67's. It is the stronger of the two — save/restore is wrong when a task holds two mutexes and releases one, and wrong again when a task is boosted while already holding a mutex — and it subsumes what #55 fixes: recomputing on timeout is one of the cases it recomputes on, and it already rejects a full waiter table rather than blocking a task that was never enqueued. 4. #74's tests were merged without its parser change. Six assertions in test_config failed because a "- name:" item arriving while section was SEC_PKG_DEPS fell into the dependency branch, so the package after a deps list was swallowed as a dependency of the previous one. Restored the SEC_PKG_DEPS arm of the outer condition, nested the dependency handling as the else of the name check, and added the deps transition under SEC_PKG_BUILD. before cmake: configure fails after build clean, ctest 28/28 Also re-checked the package-overflow guard that arrived via #61/#65, since it landed in a different shape than #78 proposed: AddressSanitizer is clean on a 129-package config that previously overflowed, and the 129th entry is refused with a message rather than silently dropped. Co-Authored-By: Claude Opus 5 (1M context) --- core/src/config.c | 18 +- kernel/include/eos/kernel_internal.h | 11 +- kernel/src/sync.c | 100 ++- kernel/src/task.c | 1156 +++++++++++++------------- tests/CMakeLists.txt | 10 - 5 files changed, 661 insertions(+), 634 deletions(-) diff --git a/core/src/config.c b/core/src/config.c index d4a91c7c..79b4f29e 100644 --- a/core/src/config.c +++ b/core/src/config.c @@ -124,7 +124,8 @@ EosResult eos_config_load(EosConfig *cfg, const char *path) { } else if (section == SEC_PACKAGES || section == SEC_PKG_ENTRY || section == SEC_PKG_BUILD || - section == SEC_PKG_OPTIONS) { + section == SEC_PKG_OPTIONS || + section == SEC_PKG_DEPS) { if (parse_kv(item, key, sizeof(key), val, sizeof(val)) == 0 && strcmp(key, "name") == 0) { pkg_idx = cfg->package_count; @@ -146,13 +147,13 @@ EosResult eos_config_load(EosConfig *cfg, const char *path) { EOS_MAX_PACKAGES, val); section = SEC_PACKAGES; } - } - } else if (section == SEC_PKG_DEPS && - pkg_idx >= 0 && pkg_idx < EOS_MAX_PACKAGES) { - if (cfg->packages[pkg_idx].dep_count < EOS_MAX_DEPS) { - strncpy(cfg->packages[pkg_idx].deps[cfg->packages[pkg_idx].dep_count], - item, EOS_MAX_NAME - 1); - cfg->packages[pkg_idx].dep_count++; + } else if (section == SEC_PKG_DEPS && + pkg_idx >= 0 && pkg_idx < EOS_MAX_PACKAGES) { + if (cfg->packages[pkg_idx].dep_count < EOS_MAX_DEPS) { + strncpy(cfg->packages[pkg_idx].deps[cfg->packages[pkg_idx].dep_count], + item, EOS_MAX_NAME - 1); + cfg->packages[pkg_idx].dep_count++; + } } } else if (section == SEC_SYSTEM_RTOS || section == SEC_SYSTEM_RTOS_ENTRY) { /* "- provider: freertos" starts a new RTOS entry */ @@ -279,6 +280,7 @@ EosResult eos_config_load(EosConfig *cfg, const char *path) { cfg->packages[pkg_idx].build_type = eos_build_type_from_str(val); } if (indent <= 2 && strcmp(key, "options") == 0) { section = SEC_PKG_OPTIONS; continue; } + if (indent <= 2 && strcmp(key, "deps") == 0) { section = SEC_PKG_DEPS; continue; } if (indent <= 2 && strcmp(key, "version") == 0) { section = SEC_PKG_ENTRY; strncpy(cfg->packages[pkg_idx].version, val, EOS_MAX_NAME - 1); diff --git a/kernel/include/eos/kernel_internal.h b/kernel/include/eos/kernel_internal.h index ce7b7362..674380fe 100644 --- a/kernel/include/eos/kernel_internal.h +++ b/kernel/include/eos/kernel_internal.h @@ -48,6 +48,9 @@ void eos_task_set_priority_internal(eos_task_handle_t h, uint8_t prio); /** * @brief Make @p h the running task. Handles >= EOS_MAX_TASKS clear it. + * + * Used by host tests to exercise multi-task mutex/IPC paths without a + * running context switcher. Not part of the public kernel API. */ void eos_task_set_current_internal(eos_task_handle_t h); @@ -65,14 +68,6 @@ void eos_task_block_with_timeout(eos_task_handle_t h, uint32_t timeout_ms); */ void eos_task_unblock(eos_task_handle_t h); -/** - * @brief Set the scheduler's notion of the current task. - * - * Used by host tests to exercise multi-task mutex/IPC paths without a - * running context switcher. Not part of the public kernel API. - */ -void eos_task_set_current_internal(eos_task_handle_t h); - #ifdef __cplusplus } #endif diff --git a/kernel/src/sync.c b/kernel/src/sync.c index 2d9db1f1..14dd98c2 100644 --- a/kernel/src/sync.c +++ b/kernel/src/sync.c @@ -45,31 +45,78 @@ typedef struct { static mtx_t g_mtx[EOS_MAX_MUTEXES]; static sem_t g_sem[EOS_MAX_SEMAPHORES]; -static void mtx_remove_waiter(mtx_t *m, uint8_t task) +/* ============================================================ + * Priority inheritance + * + * A task holding mutexes runs at + * + * effective = min( base_priority, + * priority of every task waiting on every mutex it owns ) + * + * with lower meaning more urgent. Recomputing from that invariant on every + * change, instead of saving and restoring a value around each lock, keeps the + * nested cases correct and lets a boost propagate along the blocking chain. + * ============================================================ */ + +/* Mutex each task is blocked on, as handle + 1 so that 0 means "none". */ +static uint8_t g_blocked_on[EOS_MAX_TASKS]; + +static inline int task_valid(uint8_t t) { - for (int i = 0; i < m->waiter_count; i++) { - if (m->waiters[i] == task) { - for (int j = i; j < m->waiter_count - 1; j++) - m->waiters[j] = m->waiters[j + 1]; - m->waiter_count--; - break; + return t < EOS_MAX_TASKS; +} + +/** @return non-zero if the effective priority changed. */ +static int pi_recompute(uint8_t task) +{ + if (!task_valid(task)) return 0; + + uint8_t eff = g_tasks[task].base_priority; + + for (int i = 0; i < EOS_MAX_MUTEXES; i++) { + const mtx_t *m = &g_mtx[i]; + if (!m->in_use || !m->locked || m->owner != task) continue; + for (int w = 0; w < m->waiter_count; w++) { + uint8_t waiter = m->waiters[w]; + if (!task_valid(waiter)) continue; + if (g_tasks[waiter].priority < eff) + eff = g_tasks[waiter].priority; } } + + if (g_tasks[task].priority == eff) return 0; + g_tasks[task].priority = eff; + return 1; } -/* Recompute inherited priority from remaining waiters, or restore original. */ -static void mtx_recompute_owner_priority(mtx_t *m) +static void pi_propagate(uint8_t task) { - if (m->owner == 0xFF) - return; + for (int depth = 0; depth < PI_MAX_CHAIN_DEPTH; depth++) { + if (!task_valid(task)) return; + if (!pi_recompute(task) && depth > 0) return; - uint8_t prio = m->original_prio; + uint8_t enc = g_blocked_on[task]; + if (enc == 0) return; + + uint8_t m = (uint8_t)(enc - 1u); + if (m >= EOS_MAX_MUTEXES) return; + if (!g_mtx[m].in_use || !g_mtx[m].locked) return; + if (g_mtx[m].owner == task) return; + + task = g_mtx[m].owner; + } +} + +static int mtx_remove_waiter(mtx_t *m, uint8_t task) +{ for (int i = 0; i < m->waiter_count; i++) { - uint8_t wp = eos_task_get_priority_internal(m->waiters[i]); - if (wp < prio) - prio = wp; + if (m->waiters[i] != task) continue; + for (int j = i; j < m->waiter_count - 1; j++) + m->waiters[j] = m->waiters[j + 1]; + m->waiter_count--; + return 1; } - eos_task_set_priority_internal(m->owner, prio); + return 0; } int eos_mutex_create(eos_mutex_handle_t *out) @@ -123,21 +170,17 @@ int eos_mutex_lock(eos_mutex_handle_t h, uint32_t timeout_ms) return EOS_KERN_TIMEOUT; } - /* Waiter table full: fail without boosting, or the owner would stay - * elevated even though this caller is never queued. */ + /* Refuse rather than block: a task that is not enqueued is never granted + * the mutex, so with EOS_WAIT_FOREVER it would sleep forever. */ if (g_mtx[h].waiter_count >= MTX_MAX_WAITERS) { eos_port_exit_critical(crit); return EOS_KERN_NO_MEMORY; } - /* Priority inheritance: boost owner to caller's priority if higher */ - uint8_t caller_prio = eos_task_get_priority_internal(caller); - uint8_t owner_prio = eos_task_get_priority_internal(g_mtx[h].owner); - if (caller_prio < owner_prio) { - eos_task_set_priority_internal(g_mtx[h].owner, caller_prio); - } - g_mtx[h].waiters[g_mtx[h].waiter_count++] = caller; + if (task_valid(caller)) + g_blocked_on[caller] = (uint8_t)(h + 1u); + pi_propagate(g_mtx[h].owner); /* Block the calling task */ eos_task_block_with_timeout(caller, timeout_ms); @@ -154,10 +197,11 @@ int eos_mutex_lock(eos_mutex_handle_t h, uint32_t timeout_ms) return EOS_KERN_OK; } - /* Timeout — drop from the wait queue and drop the inheritance boost - * if this waiter was the reason the owner was running elevated. */ + /* Timeout — leave the queue and drop the boost we were causing. */ + uint8_t owner = g_mtx[h].owner; mtx_remove_waiter(&g_mtx[h], caller); - mtx_recompute_owner_priority(&g_mtx[h]); + pi_propagate(owner); + eos_port_exit_critical(crit); return EOS_KERN_TIMEOUT; } diff --git a/kernel/src/task.c b/kernel/src/task.c index 9ff04800..10e3af04 100644 --- a/kernel/src/task.c +++ b/kernel/src/task.c @@ -1,580 +1,576 @@ -// SPDX-License-Identifier: MIT -// Copyright (c) 2026 EoS Project -// ISO/IEC 25000 | ISO/IEC/IEEE 15288:2023 - -/** - * @file task.c - * @brief EoS kernel task scheduler with real context switching - * - * Priority-based preemptive scheduler with SysTick-driven tick, - * PendSV context switching, software timers, and stack canary detection. - */ - -#include "eos/kernel.h" -#include "eos/kernel_internal.h" -#include "eos/arch.h" -#include "eos/mem.h" -#include - -/* ============================================================ - * Overflow-Safe Tick Comparison - * ============================================================ */ - -/** - * @brief Check whether a tick deadline has expired (overflow-safe). - * - * Uses unsigned subtraction so that the comparison works correctly - * even when the 32-bit tick counter wraps past UINT32_MAX. This is - * the standard technique used by FreeRTOS, Zephyr, and the Linux - * kernel (time_after macro). Valid for deadline distances up to - * INT32_MAX ticks (~24.8 days at 1 kHz). - * - * @param current The current tick value. - * @param deadline The target tick at which the event should fire. - * @return true if the deadline has passed. - */ -static inline bool tick_expired(uint32_t current, uint32_t deadline) -{ - return (int32_t)(current - deadline) >= 0; -} - -/* Stack canary value — placed at bottom of each task's stack */ -#define EOS_STACK_CANARY 0xDEADBEEFU - -/* Default idle task stack size */ -#define IDLE_STACK_SIZE 256 - -/* ============================================================ - * Internal State - * ============================================================ */ - -eos_task_t g_tasks[EOS_MAX_TASKS]; -static int g_task_count = 0; -static int g_current = -1; -static int g_running = 0; - -/* Tick counter — updated by SysTick_Handler */ -volatile uint32_t g_tick = 0; - -/* Reschedule flag */ -volatile uint8_t g_needs_reschedule = 0; - -/* Pointers used by the PendSV assembly handler */ -uint32_t **g_current_sp = NULL; -uint32_t **g_next_sp = NULL; - -/* Static heap for task stacks (used when eos_malloc is not available) */ -#ifndef EOS_TASK_STACK_POOL_SIZE -#define EOS_TASK_STACK_POOL_SIZE (EOS_MAX_TASKS * 1024) -#endif -static uint32_t g_stack_pool[EOS_TASK_STACK_POOL_SIZE / sizeof(uint32_t)]; -static uint32_t g_stack_pool_offset = 0; - -/* Idle task stack */ -static uint32_t g_idle_stack[IDLE_STACK_SIZE / sizeof(uint32_t)]; - -/* ============================================================ - * Software Timers - * ============================================================ */ - -typedef struct { - uint8_t in_use; - uint8_t active; - uint8_t auto_reload; - const char *name; - uint32_t period_ticks; - uint32_t next_tick; - eos_swtimer_callback_t callback; - void *ctx; -} swtimer_t; - -static swtimer_t g_timers[EOS_MAX_TIMERS]; - -/* ============================================================ - * Stack Allocation - * ============================================================ */ - -static uint32_t *alloc_task_stack(uint32_t size_bytes) -{ - uint32_t words = size_bytes / sizeof(uint32_t); - if (g_stack_pool_offset + words > EOS_TASK_STACK_POOL_SIZE / sizeof(uint32_t)) { - return NULL; /* Pool exhausted */ - } - uint32_t *base = &g_stack_pool[g_stack_pool_offset]; - g_stack_pool_offset += words; - - /* Place canary at the bottom of the stack */ - base[0] = EOS_STACK_CANARY; - - return base; -} - -/* ============================================================ - * Scheduler - * ============================================================ */ - -/** - * @brief Find the highest-priority ready task. - */ -static int find_next_task(void) -{ - int best = -1; - uint8_t best_prio = 255; - int start = (g_current + 1) % EOS_MAX_TASKS; - - for (int i = 0; i < EOS_MAX_TASKS; i++) { - int idx = (start + i) % EOS_MAX_TASKS; - eos_task_t *t = &g_tasks[idx]; - if (t->state != EOS_TASK_READY) continue; - if (t->priority < best_prio) { - best_prio = t->priority; - best = idx; - } - } - return best; -} - -/** - * @brief Called by PendSV to select the next task. - * - * Updates g_current_sp and g_next_sp for the assembly handler. - */ -void eos_schedule(void) -{ - int next = find_next_task(); - if (next < 0) return; /* No ready tasks — stay on current */ - - /* Mark current task as ready (if it was running) */ - if (g_current >= 0 && g_tasks[g_current].state == EOS_TASK_RUNNING) { - g_tasks[g_current].state = EOS_TASK_READY; - } - - /* Check stack canary on current task */ - if (g_current >= 0 && g_tasks[g_current].stack_base) { - if (g_tasks[g_current].stack_base[0] != EOS_STACK_CANARY) { - /* Stack overflow detected! */ - /* In production: trigger fault handler or reboot */ - g_tasks[g_current].state = EOS_TASK_DELETED; - } - } - - /* Switch to next task */ - g_current_sp = &g_tasks[g_current >= 0 ? g_current : next].stack_ptr; - g_current = next; - g_tasks[next].state = EOS_TASK_RUNNING; - g_tasks[next].run_count++; - g_next_sp = &g_tasks[next].stack_ptr; -} - -/** - * @brief Wake blocked tasks whose delay has expired. - * Called from SysTick_Handler. - */ -void eos_task_wake_check(uint32_t current_tick) -{ - for (int i = 0; i < EOS_MAX_TASKS; i++) { - eos_task_t *t = &g_tasks[i]; - if (t->state == EOS_TASK_BLOCKED && t->wake_tick > 0 && - tick_expired(current_tick, t->wake_tick)) { - t->state = EOS_TASK_READY; - t->wake_armed = 0; - t->wake_tick = 0; - } - } -} - -/* ============================================================ - * Idle Task - * ============================================================ */ - -static void idle_task_func(void *arg) -{ - (void)arg; - while (1) { - /* WFI — wait for interrupt, saves power */ -#if defined(__ARM_ARCH) - __asm volatile ("wfi"); -#elif defined(__riscv) - __asm volatile ("wfi"); -#elif (defined(__x86_64__) || defined(_M_X64)) && !defined(_WIN32) && \ - !defined(__STDC_HOSTED__) - __asm volatile ("hlt"); -#else - /* Hosted host/simulation: just spin */ - volatile int dummy = 0; - (void)dummy; -#endif - } -} - -/* ============================================================ - * Kernel API Implementation - * ============================================================ */ - -int eos_kernel_init(void) -{ - memset(g_tasks, 0, sizeof(g_tasks)); - memset(g_timers, 0, sizeof(g_timers)); - g_task_count = 0; - g_current = -1; - g_running = 0; - g_tick = 0; - g_stack_pool_offset = 0; - g_current_sp = NULL; - g_next_sp = NULL; - - /* Create idle task at lowest priority */ - eos_task_t *idle = &g_tasks[0]; - idle->id = 0; - idle->name = "idle"; - idle->entry = idle_task_func; - idle->arg = NULL; - idle->priority = 255; /* Lowest priority */ - idle->base_priority = 255; - idle->stack_size = IDLE_STACK_SIZE; - idle->stack_base = g_idle_stack; - idle->stack_base[0] = EOS_STACK_CANARY; - idle->stack_ptr = eos_port_init_stack( - g_idle_stack + IDLE_STACK_SIZE / sizeof(uint32_t), - idle_task_func, NULL - ); - idle->state = EOS_TASK_READY; - g_task_count = 1; - - return EOS_KERN_OK; -} - -void eos_kernel_start(void) -{ - g_running = 1; - - /* Find highest-priority task to start */ - int first = find_next_task(); - if (first < 0) first = 0; /* Fall back to idle */ - - g_current = first; - g_tasks[first].state = EOS_TASK_RUNNING; - g_tasks[first].run_count++; - g_next_sp = &g_tasks[first].stack_ptr; - g_current_sp = g_next_sp; - - /* Start the first task — this never returns */ - eos_port_start_first_task(); - - /* Should never reach here */ - while (1) {} -} - -bool eos_kernel_is_running(void) -{ - return g_running != 0; -} - -void eos_kernel_tick(void) -{ - g_tick++; -} - -int eos_task_create(const char *name, eos_task_func_t entry, void *arg, - uint8_t priority, uint32_t stack_size) -{ - if (!entry) return EOS_KERN_INVALID; - if (stack_size == 0) stack_size = 1024; - - /* Find free slot (skip slot 0 = idle) */ - int slot = -1; - for (int i = 1; i < EOS_MAX_TASKS; i++) { - if (g_tasks[i].entry == NULL || - g_tasks[i].state == EOS_TASK_DELETED) { - slot = i; - break; - } - } - if (slot < 0) return EOS_KERN_NO_MEMORY; - - /* Allocate stack */ - uint32_t *stack_base = alloc_task_stack(stack_size); - if (!stack_base) return EOS_KERN_NO_MEMORY; - - uint32_t *stack_top = stack_base + stack_size / sizeof(uint32_t); - - memset(&g_tasks[slot], 0, sizeof(eos_task_t)); - g_tasks[slot].id = (uint8_t)slot; - g_tasks[slot].name = name; - g_tasks[slot].entry = entry; - g_tasks[slot].arg = arg; - g_tasks[slot].priority = priority; - g_tasks[slot].base_priority = priority; - g_tasks[slot].stack_size = stack_size; - g_tasks[slot].stack_base = stack_base; - g_tasks[slot].stack_ptr = eos_port_init_stack(stack_top, entry, arg); - g_tasks[slot].state = EOS_TASK_READY; - g_task_count++; - - /* If kernel is running and new task has higher priority, yield */ - if (g_running && g_current >= 0 && - priority < g_tasks[g_current].priority) { - eos_port_yield(); - } - - return slot; -} - -int eos_task_delete(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS) return EOS_KERN_INVALID; - if (g_tasks[h].state == EOS_TASK_DELETED || g_tasks[h].entry == NULL) - return EOS_KERN_INVALID; - g_tasks[h].state = EOS_TASK_DELETED; - g_tasks[h].entry = NULL; - g_task_count--; - - if (h == (eos_task_handle_t)g_current) { - eos_port_yield(); - } - return EOS_KERN_OK; -} - -int eos_task_suspend(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS || g_tasks[h].entry == NULL) return EOS_KERN_INVALID; - g_tasks[h].state = EOS_TASK_SUSPENDED; - if (h == (eos_task_handle_t)g_current) { - eos_port_yield(); - } - return EOS_KERN_OK; -} - -int eos_task_resume(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS || g_tasks[h].entry == NULL) return EOS_KERN_INVALID; - if (g_tasks[h].state == EOS_TASK_SUSPENDED) { - g_tasks[h].state = EOS_TASK_READY; - } - return EOS_KERN_OK; -} - -void eos_task_yield(void) -{ - if (g_current >= 0) { - g_tasks[g_current].state = EOS_TASK_READY; - } - eos_port_yield(); -} - -void eos_task_delay_ms(uint32_t ms) -{ - if (g_current >= 0) { - uint32_t crit = eos_port_enter_critical(); - g_tasks[g_current].wake_tick = g_tick + ms; - g_tasks[g_current].wake_armed = 1; - g_tasks[g_current].state = EOS_TASK_BLOCKED; - eos_port_exit_critical(crit); - eos_port_yield(); - } -} - -eos_task_handle_t eos_task_get_current(void) -{ - return (g_current >= 0) ? (eos_task_handle_t)g_current : 0xFF; -} - -void eos_task_set_current_internal(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS) { - g_current = -1; - return; - } - g_current = (int)h; - g_tasks[h].state = EOS_TASK_RUNNING; -} - -eos_task_state_t eos_task_get_state(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS) return EOS_TASK_DELETED; - return g_tasks[h].state; -} - -const char *eos_task_get_name(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS || g_tasks[h].state == EOS_TASK_DELETED || !g_tasks[h].name) { - return "invalid"; - } - return g_tasks[h].name; -} - -/* ============================================================ - * Software Timer Implementation - * ============================================================ */ - -int eos_swtimer_create(eos_swtimer_handle_t *out, const char *name, - uint32_t period_ms, bool auto_reload, - eos_swtimer_callback_t callback, void *ctx) -{ - if (!out || !callback || period_ms == 0) return EOS_KERN_INVALID; - - for (int i = 0; i < EOS_MAX_TIMERS; i++) { - if (!g_timers[i].in_use) { - g_timers[i].in_use = 1; - g_timers[i].active = 0; - g_timers[i].auto_reload = auto_reload ? 1 : 0; - g_timers[i].name = name; - g_timers[i].period_ticks = period_ms; /* 1 tick = 1 ms */ - g_timers[i].next_tick = 0; - g_timers[i].callback = callback; - g_timers[i].ctx = ctx; - *out = (uint8_t)i; - return EOS_KERN_OK; - } - } - return EOS_KERN_NO_MEMORY; -} - -int eos_swtimer_start(eos_swtimer_handle_t h) -{ - if (h >= EOS_MAX_TIMERS || !g_timers[h].in_use) return EOS_KERN_INVALID; - g_timers[h].next_tick = g_tick + g_timers[h].period_ticks; - g_timers[h].active = 1; - return EOS_KERN_OK; -} - -int eos_swtimer_stop(eos_swtimer_handle_t h) -{ - if (h >= EOS_MAX_TIMERS || !g_timers[h].in_use) return EOS_KERN_INVALID; - g_timers[h].active = 0; - return EOS_KERN_OK; -} - -int eos_swtimer_reset(eos_swtimer_handle_t h) -{ - if (h >= EOS_MAX_TIMERS || !g_timers[h].in_use) return EOS_KERN_INVALID; - g_timers[h].next_tick = g_tick + g_timers[h].period_ticks; - return EOS_KERN_OK; -} - -int eos_swtimer_delete(eos_swtimer_handle_t h) -{ - if (h >= EOS_MAX_TIMERS || !g_timers[h].in_use) return EOS_KERN_INVALID; - g_timers[h].in_use = 0; - g_timers[h].active = 0; - return EOS_KERN_OK; -} - -/** - * @brief Process software timers — called from SysTick_Handler. - */ -void eos_swtimer_tick(uint32_t current_tick) -{ - for (int i = 0; i < EOS_MAX_TIMERS; i++) { - swtimer_t *t = &g_timers[i]; - if (!t->in_use || !t->active) continue; - if (tick_expired(current_tick, t->next_tick)) { - t->callback((eos_swtimer_handle_t)i, t->ctx); - if (t->auto_reload) { - t->next_tick = current_tick + t->period_ticks; - } else { - t->active = 0; - } - } - } -} - -/* ============================================================ - * Internal Accessor Functions (used by sync.c and ipc.c) - * ============================================================ */ - -uint32_t eos_tick_get(void) -{ - return g_tick; -} - -uint8_t eos_task_get_priority_internal(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS) return 255; - return g_tasks[h].priority; -} - -void eos_task_set_priority_internal(eos_task_handle_t h, uint8_t prio) -{ - if (h >= EOS_MAX_TASKS) return; - g_tasks[h].priority = prio; -} - -void eos_task_block_with_timeout(eos_task_handle_t h, uint32_t timeout_ms) -{ - if (h >= EOS_MAX_TASKS) return; - g_tasks[h].state = EOS_TASK_BLOCKED; - if (timeout_ms == EOS_WAIT_FOREVER) { - g_tasks[h].wake_tick = 0; - g_tasks[h].wake_armed = 0; /* Never auto-wake */ - } else { - g_tasks[h].wake_tick = g_tick + timeout_ms; - g_tasks[h].wake_armed = 1; - } -} - -void eos_task_unblock(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS) return; - g_tasks[h].state = EOS_TASK_READY; - g_tasks[h].wake_tick = 0; - g_tasks[h].wake_armed = 0; -} - -/* ============================================================ - * Runtime Statistics API - * ============================================================ */ - -int eos_task_get_stats(eos_task_handle_t h, eos_task_stats_t *out) -{ - if (h >= EOS_MAX_TASKS || !out) return EOS_KERN_INVALID; - if (g_tasks[h].entry == NULL && h != 0) return EOS_KERN_INVALID; - - out->id = g_tasks[h].id; - out->name = g_tasks[h].name; - out->priority = g_tasks[h].priority; - out->state = g_tasks[h].state; - out->run_count = g_tasks[h].run_count; - out->stack_size = g_tasks[h].stack_size; - - /* Estimate stack usage: scan from base upward for canary/zero words */ - if (g_tasks[h].stack_base && g_tasks[h].stack_size > 0) { - uint32_t total_words = g_tasks[h].stack_size / sizeof(uint32_t); - uint32_t unused_words = 0; - /* Skip word 0 (canary), count zero-filled words from bottom up */ - for (uint32_t w = 1; w < total_words; w++) { - if (g_tasks[h].stack_base[w] == 0) { - unused_words++; - } else { - break; - } - } - uint32_t used_bytes = (total_words - 1 - unused_words) * sizeof(uint32_t); - out->stack_used = used_bytes; - } else { - out->stack_used = 0; - } - - return EOS_KERN_OK; -} - -int eos_task_get_all_stats(eos_task_stats_t *out, int max_entries, int *count) -{ - if (!out || max_entries <= 0 || !count) return EOS_KERN_INVALID; - - int n = 0; - for (int i = 0; i < EOS_MAX_TASKS && n < max_entries; i++) { - if (g_tasks[i].entry != NULL || i == 0) { - eos_task_get_stats((eos_task_handle_t)i, &out[n]); - n++; - } - } - *count = n; - return EOS_KERN_OK; -} - -void eos_task_set_current_internal(eos_task_handle_t h) -{ - if (h >= EOS_MAX_TASKS) return; - g_current = (int)h; -} +// SPDX-License-Identifier: MIT +// Copyright (c) 2026 EoS Project +// ISO/IEC 25000 | ISO/IEC/IEEE 15288:2023 + +/** + * @file task.c + * @brief EoS kernel task scheduler with real context switching + * + * Priority-based preemptive scheduler with SysTick-driven tick, + * PendSV context switching, software timers, and stack canary detection. + */ + +#include "eos/kernel.h" +#include "eos/kernel_internal.h" +#include "eos/arch.h" +#include "eos/mem.h" +#include + +/* ============================================================ + * Overflow-Safe Tick Comparison + * ============================================================ */ + +/** + * @brief Check whether a tick deadline has expired (overflow-safe). + * + * Uses unsigned subtraction so that the comparison works correctly + * even when the 32-bit tick counter wraps past UINT32_MAX. This is + * the standard technique used by FreeRTOS, Zephyr, and the Linux + * kernel (time_after macro). Valid for deadline distances up to + * INT32_MAX ticks (~24.8 days at 1 kHz). + * + * @param current The current tick value. + * @param deadline The target tick at which the event should fire. + * @return true if the deadline has passed. + */ +static inline bool tick_expired(uint32_t current, uint32_t deadline) +{ + return (int32_t)(current - deadline) >= 0; +} + +/* Stack canary value — placed at bottom of each task's stack */ +#define EOS_STACK_CANARY 0xDEADBEEFU + +/* Default idle task stack size */ +#define IDLE_STACK_SIZE 256 + +/* ============================================================ + * Internal State + * ============================================================ */ + +eos_task_t g_tasks[EOS_MAX_TASKS]; +static int g_task_count = 0; +static int g_current = -1; +static int g_running = 0; + +/* Tick counter — updated by SysTick_Handler */ +volatile uint32_t g_tick = 0; + +/* Reschedule flag */ +volatile uint8_t g_needs_reschedule = 0; + +/* Pointers used by the PendSV assembly handler */ +uint32_t **g_current_sp = NULL; +uint32_t **g_next_sp = NULL; + +/* Static heap for task stacks (used when eos_malloc is not available) */ +#ifndef EOS_TASK_STACK_POOL_SIZE +#define EOS_TASK_STACK_POOL_SIZE (EOS_MAX_TASKS * 1024) +#endif +static uint32_t g_stack_pool[EOS_TASK_STACK_POOL_SIZE / sizeof(uint32_t)]; +static uint32_t g_stack_pool_offset = 0; + +/* Idle task stack */ +static uint32_t g_idle_stack[IDLE_STACK_SIZE / sizeof(uint32_t)]; + +/* ============================================================ + * Software Timers + * ============================================================ */ + +typedef struct { + uint8_t in_use; + uint8_t active; + uint8_t auto_reload; + const char *name; + uint32_t period_ticks; + uint32_t next_tick; + eos_swtimer_callback_t callback; + void *ctx; +} swtimer_t; + +static swtimer_t g_timers[EOS_MAX_TIMERS]; + +/* ============================================================ + * Stack Allocation + * ============================================================ */ + +static uint32_t *alloc_task_stack(uint32_t size_bytes) +{ + uint32_t words = size_bytes / sizeof(uint32_t); + if (g_stack_pool_offset + words > EOS_TASK_STACK_POOL_SIZE / sizeof(uint32_t)) { + return NULL; /* Pool exhausted */ + } + uint32_t *base = &g_stack_pool[g_stack_pool_offset]; + g_stack_pool_offset += words; + + /* Place canary at the bottom of the stack */ + base[0] = EOS_STACK_CANARY; + + return base; +} + +/* ============================================================ + * Scheduler + * ============================================================ */ + +/** + * @brief Find the highest-priority ready task. + */ +static int find_next_task(void) +{ + int best = -1; + uint8_t best_prio = 255; + int start = (g_current + 1) % EOS_MAX_TASKS; + + for (int i = 0; i < EOS_MAX_TASKS; i++) { + int idx = (start + i) % EOS_MAX_TASKS; + eos_task_t *t = &g_tasks[idx]; + if (t->state != EOS_TASK_READY) continue; + if (t->priority < best_prio) { + best_prio = t->priority; + best = idx; + } + } + return best; +} + +/** + * @brief Called by PendSV to select the next task. + * + * Updates g_current_sp and g_next_sp for the assembly handler. + */ +void eos_schedule(void) +{ + int next = find_next_task(); + if (next < 0) return; /* No ready tasks — stay on current */ + + /* Mark current task as ready (if it was running) */ + if (g_current >= 0 && g_tasks[g_current].state == EOS_TASK_RUNNING) { + g_tasks[g_current].state = EOS_TASK_READY; + } + + /* Check stack canary on current task */ + if (g_current >= 0 && g_tasks[g_current].stack_base) { + if (g_tasks[g_current].stack_base[0] != EOS_STACK_CANARY) { + /* Stack overflow detected! */ + /* In production: trigger fault handler or reboot */ + g_tasks[g_current].state = EOS_TASK_DELETED; + } + } + + /* Switch to next task */ + g_current_sp = &g_tasks[g_current >= 0 ? g_current : next].stack_ptr; + g_current = next; + g_tasks[next].state = EOS_TASK_RUNNING; + g_tasks[next].run_count++; + g_next_sp = &g_tasks[next].stack_ptr; +} + +/** + * @brief Wake blocked tasks whose delay has expired. + * Called from SysTick_Handler. + */ +void eos_task_wake_check(uint32_t current_tick) +{ + for (int i = 0; i < EOS_MAX_TASKS; i++) { + eos_task_t *t = &g_tasks[i]; + if (t->state == EOS_TASK_BLOCKED && t->wake_tick > 0 && + tick_expired(current_tick, t->wake_tick)) { + t->state = EOS_TASK_READY; + t->wake_armed = 0; + t->wake_tick = 0; + } + } +} + +/* ============================================================ + * Idle Task + * ============================================================ */ + +static void idle_task_func(void *arg) +{ + (void)arg; + while (1) { + /* WFI — wait for interrupt, saves power */ +#if defined(__ARM_ARCH) + __asm volatile ("wfi"); +#elif defined(__riscv) + __asm volatile ("wfi"); +#elif (defined(__x86_64__) || defined(_M_X64)) && !defined(_WIN32) && \ + !defined(__STDC_HOSTED__) + __asm volatile ("hlt"); +#else + /* Hosted host/simulation: just spin */ + volatile int dummy = 0; + (void)dummy; +#endif + } +} + +/* ============================================================ + * Kernel API Implementation + * ============================================================ */ + +int eos_kernel_init(void) +{ + memset(g_tasks, 0, sizeof(g_tasks)); + memset(g_timers, 0, sizeof(g_timers)); + g_task_count = 0; + g_current = -1; + g_running = 0; + g_tick = 0; + g_stack_pool_offset = 0; + g_current_sp = NULL; + g_next_sp = NULL; + + /* Create idle task at lowest priority */ + eos_task_t *idle = &g_tasks[0]; + idle->id = 0; + idle->name = "idle"; + idle->entry = idle_task_func; + idle->arg = NULL; + idle->priority = 255; /* Lowest priority */ + idle->base_priority = 255; + idle->stack_size = IDLE_STACK_SIZE; + idle->stack_base = g_idle_stack; + idle->stack_base[0] = EOS_STACK_CANARY; + idle->stack_ptr = eos_port_init_stack( + g_idle_stack + IDLE_STACK_SIZE / sizeof(uint32_t), + idle_task_func, NULL + ); + idle->state = EOS_TASK_READY; + g_task_count = 1; + + return EOS_KERN_OK; +} + +void eos_kernel_start(void) +{ + g_running = 1; + + /* Find highest-priority task to start */ + int first = find_next_task(); + if (first < 0) first = 0; /* Fall back to idle */ + + g_current = first; + g_tasks[first].state = EOS_TASK_RUNNING; + g_tasks[first].run_count++; + g_next_sp = &g_tasks[first].stack_ptr; + g_current_sp = g_next_sp; + + /* Start the first task — this never returns */ + eos_port_start_first_task(); + + /* Should never reach here */ + while (1) {} +} + +bool eos_kernel_is_running(void) +{ + return g_running != 0; +} + +void eos_kernel_tick(void) +{ + g_tick++; +} + +int eos_task_create(const char *name, eos_task_func_t entry, void *arg, + uint8_t priority, uint32_t stack_size) +{ + if (!entry) return EOS_KERN_INVALID; + if (stack_size == 0) stack_size = 1024; + + /* Find free slot (skip slot 0 = idle) */ + int slot = -1; + for (int i = 1; i < EOS_MAX_TASKS; i++) { + if (g_tasks[i].entry == NULL || + g_tasks[i].state == EOS_TASK_DELETED) { + slot = i; + break; + } + } + if (slot < 0) return EOS_KERN_NO_MEMORY; + + /* Allocate stack */ + uint32_t *stack_base = alloc_task_stack(stack_size); + if (!stack_base) return EOS_KERN_NO_MEMORY; + + uint32_t *stack_top = stack_base + stack_size / sizeof(uint32_t); + + memset(&g_tasks[slot], 0, sizeof(eos_task_t)); + g_tasks[slot].id = (uint8_t)slot; + g_tasks[slot].name = name; + g_tasks[slot].entry = entry; + g_tasks[slot].arg = arg; + g_tasks[slot].priority = priority; + g_tasks[slot].base_priority = priority; + g_tasks[slot].stack_size = stack_size; + g_tasks[slot].stack_base = stack_base; + g_tasks[slot].stack_ptr = eos_port_init_stack(stack_top, entry, arg); + g_tasks[slot].state = EOS_TASK_READY; + g_task_count++; + + /* If kernel is running and new task has higher priority, yield */ + if (g_running && g_current >= 0 && + priority < g_tasks[g_current].priority) { + eos_port_yield(); + } + + return slot; +} + +int eos_task_delete(eos_task_handle_t h) +{ + if (h >= EOS_MAX_TASKS) return EOS_KERN_INVALID; + if (g_tasks[h].state == EOS_TASK_DELETED || g_tasks[h].entry == NULL) + return EOS_KERN_INVALID; + g_tasks[h].state = EOS_TASK_DELETED; + g_tasks[h].entry = NULL; + g_task_count--; + + if (h == (eos_task_handle_t)g_current) { + eos_port_yield(); + } + return EOS_KERN_OK; +} + +int eos_task_suspend(eos_task_handle_t h) +{ + if (h >= EOS_MAX_TASKS || g_tasks[h].entry == NULL) return EOS_KERN_INVALID; + g_tasks[h].state = EOS_TASK_SUSPENDED; + if (h == (eos_task_handle_t)g_current) { + eos_port_yield(); + } + return EOS_KERN_OK; +} + +int eos_task_resume(eos_task_handle_t h) +{ + if (h >= EOS_MAX_TASKS || g_tasks[h].entry == NULL) return EOS_KERN_INVALID; + if (g_tasks[h].state == EOS_TASK_SUSPENDED) { + g_tasks[h].state = EOS_TASK_READY; + } + return EOS_KERN_OK; +} + +void eos_task_yield(void) +{ + if (g_current >= 0) { + g_tasks[g_current].state = EOS_TASK_READY; + } + eos_port_yield(); +} + +void eos_task_delay_ms(uint32_t ms) +{ + if (g_current >= 0) { + uint32_t crit = eos_port_enter_critical(); + g_tasks[g_current].wake_tick = g_tick + ms; + g_tasks[g_current].wake_armed = 1; + g_tasks[g_current].state = EOS_TASK_BLOCKED; + eos_port_exit_critical(crit); + eos_port_yield(); + } +} + +eos_task_handle_t eos_task_get_current(void) +{ + return (g_current >= 0) ? (eos_task_handle_t)g_current : 0xFF; +} + +void eos_task_set_current_internal(eos_task_handle_t h) +{ + if (h >= EOS_MAX_TASKS) { + g_current = -1; + return; + } + g_current = (int)h; + g_tasks[h].state = EOS_TASK_RUNNING; +} + +eos_task_state_t eos_task_get_state(eos_task_handle_t h) +{ + if (h >= EOS_MAX_TASKS) return EOS_TASK_DELETED; + return g_tasks[h].state; +} + +const char *eos_task_get_name(eos_task_handle_t h) +{ + if (h >= EOS_MAX_TASKS || g_tasks[h].state == EOS_TASK_DELETED || !g_tasks[h].name) { + return "invalid"; + } + return g_tasks[h].name; +} + +/* ============================================================ + * Software Timer Implementation + * ============================================================ */ + +int eos_swtimer_create(eos_swtimer_handle_t *out, const char *name, + uint32_t period_ms, bool auto_reload, + eos_swtimer_callback_t callback, void *ctx) +{ + if (!out || !callback || period_ms == 0) return EOS_KERN_INVALID; + + for (int i = 0; i < EOS_MAX_TIMERS; i++) { + if (!g_timers[i].in_use) { + g_timers[i].in_use = 1; + g_timers[i].active = 0; + g_timers[i].auto_reload = auto_reload ? 1 : 0; + g_timers[i].name = name; + g_timers[i].period_ticks = period_ms; /* 1 tick = 1 ms */ + g_timers[i].next_tick = 0; + g_timers[i].callback = callback; + g_timers[i].ctx = ctx; + *out = (uint8_t)i; + return EOS_KERN_OK; + } + } + return EOS_KERN_NO_MEMORY; +} + +int eos_swtimer_start(eos_swtimer_handle_t h) +{ + if (h >= EOS_MAX_TIMERS || !g_timers[h].in_use) return EOS_KERN_INVALID; + g_timers[h].next_tick = g_tick + g_timers[h].period_ticks; + g_timers[h].active = 1; + return EOS_KERN_OK; +} + +int eos_swtimer_stop(eos_swtimer_handle_t h) +{ + if (h >= EOS_MAX_TIMERS || !g_timers[h].in_use) return EOS_KERN_INVALID; + g_timers[h].active = 0; + return EOS_KERN_OK; +} + +int eos_swtimer_reset(eos_swtimer_handle_t h) +{ + if (h >= EOS_MAX_TIMERS || !g_timers[h].in_use) return EOS_KERN_INVALID; + g_timers[h].next_tick = g_tick + g_timers[h].period_ticks; + return EOS_KERN_OK; +} + +int eos_swtimer_delete(eos_swtimer_handle_t h) +{ + if (h >= EOS_MAX_TIMERS || !g_timers[h].in_use) return EOS_KERN_INVALID; + g_timers[h].in_use = 0; + g_timers[h].active = 0; + return EOS_KERN_OK; +} + +/** + * @brief Process software timers — called from SysTick_Handler. + */ +void eos_swtimer_tick(uint32_t current_tick) +{ + for (int i = 0; i < EOS_MAX_TIMERS; i++) { + swtimer_t *t = &g_timers[i]; + if (!t->in_use || !t->active) continue; + if (tick_expired(current_tick, t->next_tick)) { + t->callback((eos_swtimer_handle_t)i, t->ctx); + if (t->auto_reload) { + t->next_tick = current_tick + t->period_ticks; + } else { + t->active = 0; + } + } + } +} + +/* ============================================================ + * Internal Accessor Functions (used by sync.c and ipc.c) + * ============================================================ */ + +uint32_t eos_tick_get(void) +{ + return g_tick; +} + +uint8_t eos_task_get_priority_internal(eos_task_handle_t h) +{ + if (h >= EOS_MAX_TASKS) return 255; + return g_tasks[h].priority; +} + +void eos_task_set_priority_internal(eos_task_handle_t h, uint8_t prio) +{ + if (h >= EOS_MAX_TASKS) return; + g_tasks[h].priority = prio; +} + +void eos_task_block_with_timeout(eos_task_handle_t h, uint32_t timeout_ms) +{ + if (h >= EOS_MAX_TASKS) return; + g_tasks[h].state = EOS_TASK_BLOCKED; + if (timeout_ms == EOS_WAIT_FOREVER) { + g_tasks[h].wake_tick = 0; + g_tasks[h].wake_armed = 0; /* Never auto-wake */ + } else { + g_tasks[h].wake_tick = g_tick + timeout_ms; + g_tasks[h].wake_armed = 1; + } +} + +void eos_task_unblock(eos_task_handle_t h) +{ + if (h >= EOS_MAX_TASKS) return; + g_tasks[h].state = EOS_TASK_READY; + g_tasks[h].wake_tick = 0; + g_tasks[h].wake_armed = 0; +} + +/* ============================================================ + * Runtime Statistics API + * ============================================================ */ + +int eos_task_get_stats(eos_task_handle_t h, eos_task_stats_t *out) +{ + if (h >= EOS_MAX_TASKS || !out) return EOS_KERN_INVALID; + if (g_tasks[h].entry == NULL && h != 0) return EOS_KERN_INVALID; + + out->id = g_tasks[h].id; + out->name = g_tasks[h].name; + out->priority = g_tasks[h].priority; + out->state = g_tasks[h].state; + out->run_count = g_tasks[h].run_count; + out->stack_size = g_tasks[h].stack_size; + + /* Estimate stack usage: scan from base upward for canary/zero words */ + if (g_tasks[h].stack_base && g_tasks[h].stack_size > 0) { + uint32_t total_words = g_tasks[h].stack_size / sizeof(uint32_t); + uint32_t unused_words = 0; + /* Skip word 0 (canary), count zero-filled words from bottom up */ + for (uint32_t w = 1; w < total_words; w++) { + if (g_tasks[h].stack_base[w] == 0) { + unused_words++; + } else { + break; + } + } + uint32_t used_bytes = (total_words - 1 - unused_words) * sizeof(uint32_t); + out->stack_used = used_bytes; + } else { + out->stack_used = 0; + } + + return EOS_KERN_OK; +} + +int eos_task_get_all_stats(eos_task_stats_t *out, int max_entries, int *count) +{ + if (!out || max_entries <= 0 || !count) return EOS_KERN_INVALID; + + int n = 0; + for (int i = 0; i < EOS_MAX_TASKS && n < max_entries; i++) { + if (g_tasks[i].entry != NULL || i == 0) { + eos_task_get_stats((eos_task_handle_t)i, &out[n]); + n++; + } + } + *count = n; + return EOS_KERN_OK; +} + + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 79e04b23..48d13e1f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -73,16 +73,6 @@ add_executable(test_crypto_sha512 test_crypto_sha512.c) target_link_libraries(test_crypto_sha512 PRIVATE eos_crypto) add_test(NAME test_crypto_sha512 COMMAND test_crypto_sha512) -# --- test_crypto_aes: AES-128/192/256 ECB + CBC, NIST FIPS-197 / SP 800-38A --- -add_executable(test_crypto_aes test_crypto_aes.c) -target_link_libraries(test_crypto_aes PRIVATE eos_crypto) -add_test(NAME test_crypto_aes COMMAND test_crypto_aes) - -# --- test_crypto_sha512: SHA-512 against NIST vectors --- -add_executable(test_crypto_sha512 test_crypto_sha512.c) -target_link_libraries(test_crypto_sha512 PRIVATE eos_crypto) -add_test(NAME test_crypto_sha512 COMMAND test_crypto_sha512) - # --- test_security: Keystore, ACL --- add_executable(test_security test_security.c) target_link_libraries(test_security PRIVATE eos_security eos_crypto) From 890bc0da1b35f59f57475f4deb18d44fda5ade7e Mon Sep 17 00:00:00 2001 From: srpatcha Date: Sat, 29 Aug 2026 04:35:28 -0700 Subject: [PATCH 2/4] fix(core): make eos cross-compile for Cortex-M4 again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit eos does not build for the target it exists to build for. From a clean checkout of master, with the toolchain the CI job installs: $ cmake -B barm -DCMAKE_TOOLCHAIN_FILE=toolchains/arm-cortex-m4.cmake $ cmake --build barm core/src/log.c:113:9: error: implicit declaration of function 'clock_gettime' core/src/log.c:113:23: error: 'CLOCK_MONOTONIC' undeclared get_timestamp_ms() had two branches, _WIN32 and "else, assume POSIX". Bare- metal newlib is neither: arm-none-eabi declares neither clock_gettime nor CLOCK_MONOTONIC, so the POSIX branch cannot compile for any Cortex-M target. The macro is the precise test — where it is absent, so is the function. There is a timebase on a target, eos_get_tick_ms() in the HAL, but core/ sits below hal/ and must not depend upward, so log entries carry a zero timestamp on a freestanding build until a port supplies one. Ordering within the ring buffer is unaffected; only absolute time is missing. Also fixed the format specifier one line down. uint32_t is `unsigned int` on x86-64 and `unsigned long` on 32-bit ARM, so the literal %u was wrong on exactly the target this build is for. It is PRIu32 now, which is right on both. Why CI did not catch it. The ARM job points CMAKE_TOOLCHAIN_FILE at cmake/arm-cortex-m4.cmake; the file is at toolchains/. That fails loudly -- "Could not find toolchain file" -- so the job has been red rather than silently passing, but it dies at configure and never reaches the compile step where this bug lives. The path is not new breakage: #75 fixed it, #79 and #51 carried the fix, and #59 reverted it. That is the same stale-base merge pattern as the other three repairs on this branch. before ARM: configure fails on a missing toolchain file; with the real path, 2 compile errors in core/src/log.c after ARM: 70/70 targets built, artifacts report `architecture: armv7e-m`; host: 28/28 ctest, unchanged Kernel and services together measure 15.8 KB flash and 26 KB RAM on Cortex-M4, which is the first real footprint number this repo has produced. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/ci.yml | 2 +- core/src/log.c | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 896b868e..284371b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,7 +90,7 @@ jobs: run: | cmake -B build/arm -G Ninja \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \ - -DCMAKE_TOOLCHAIN_FILE=cmake/arm-cortex-m4.cmake \ + -DCMAKE_TOOLCHAIN_FILE=toolchains/arm-cortex-m4.cmake \ -DEOS_BUILD_TESTS=OFF - name: Build (ARM) diff --git a/core/src/log.c b/core/src/log.c index 2c573899..23943216 100644 --- a/core/src/log.c +++ b/core/src/log.c @@ -5,6 +5,7 @@ #include "eos/log.h" #include #include +#include #include #include @@ -106,13 +107,23 @@ static const char *color_reset(void) { } static uint32_t get_timestamp_ms(void) { -#ifdef _WIN32 +#if defined(_WIN32) return (uint32_t)GetTickCount(); -#else +#elif defined(CLOCK_MONOTONIC) struct timespec ts; if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000); return 0; +#else + /* Bare-metal newlib (arm-none-eabi and friends) declares neither + * clock_gettime nor CLOCK_MONOTONIC, so the POSIX branch does not + * compile for the Cortex-M targets this project exists to build for. + * + * There is a timebase on a target -- eos_get_tick_ms() in the HAL -- but + * core/ sits below hal/ and must not depend upward, so log entries carry + * a zero timestamp until a port supplies one. Ordering within the ring + * buffer is preserved regardless; only the absolute time is missing. */ + return 0; #endif } @@ -140,7 +151,7 @@ void eos_log_ring_dump(void) { for (int i = 0; i < g_ring_count; i++) { const EosLogEntry *e = eos_log_ring_get(i); if (e) { - fprintf(stderr, "[%u %5s] %s%s%s\n", + fprintf(stderr, "[%" PRIu32 " %5s] %s%s%s\n", e->timestamp_ms, level_str(e->level), e->module ? e->module : "", e->module ? ": " : "", e->message); From 936863b03f7a4a18a0a16a0804baca1100fe60e8 Mon Sep 17 00:00:00 2001 From: srpatcha Date: Sun, 30 Aug 2026 13:11:07 -0700 Subject: [PATCH 3/4] docs(tests): describe what each crypto test covers Deduplicating test_crypto_aes and test_crypto_sha512 left the grouped comment that had been sitting above the block: # --- test_crypto_aes / _ecc / _rsa / _sha512: were sitting in tests/ unused --- That records how the files got here, which stops being interesting the moment they are wired up. #93 fixes the same two duplicates and keeps the per-test descriptions instead: # --- test_crypto_aes: AES-128/192/256 ECB + CBC, NIST FIPS-197 / SP 800-38A --- # --- test_crypto_ecc: ECC point arithmetic and ECDSA --- # --- test_crypto_rsa: RSA modular exponentiation and verify --- # --- test_crypto_sha512: SHA-512 against NIST vectors --- Naming the standard each vector set comes from is worth more to the next reader than a note about the file's history. Adopting that here so the better wording survives the conflict between the two PRs. Comment-only; no target, source or link line changes. 28/28 tests passed, 0 build errors Co-authored-by: prakhar7017 Co-Authored-By: Claude Opus 5 (1M context) --- tests/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 48d13e1f..3f17a15e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,19 +56,22 @@ add_executable(test_crypto test_crypto.c) target_link_libraries(test_crypto PRIVATE eos_crypto) add_test(NAME test_crypto COMMAND test_crypto) -# --- test_crypto_aes / _ecc / _rsa / _sha512: were sitting in tests/ unused --- +# --- test_crypto_aes: AES-128/192/256 ECB + CBC, NIST FIPS-197 / SP 800-38A --- add_executable(test_crypto_aes test_crypto_aes.c) target_link_libraries(test_crypto_aes PRIVATE eos_crypto) add_test(NAME test_crypto_aes COMMAND test_crypto_aes) +# --- test_crypto_ecc: ECC point arithmetic and ECDSA --- add_executable(test_crypto_ecc test_crypto_ecc.c) target_link_libraries(test_crypto_ecc PRIVATE eos_crypto) add_test(NAME test_crypto_ecc COMMAND test_crypto_ecc) +# --- test_crypto_rsa: RSA modular exponentiation and verify --- add_executable(test_crypto_rsa test_crypto_rsa.c) target_link_libraries(test_crypto_rsa PRIVATE eos_crypto) add_test(NAME test_crypto_rsa COMMAND test_crypto_rsa) +# --- test_crypto_sha512: SHA-512 against NIST vectors --- add_executable(test_crypto_sha512 test_crypto_sha512.c) target_link_libraries(test_crypto_sha512 PRIVATE eos_crypto) add_test(NAME test_crypto_sha512 COMMAND test_crypto_sha512) From 2bae1eaf03de16023920dfc477e5f4d2b3a817a6 Mon Sep 17 00:00:00 2001 From: srpatcha Date: Sun, 30 Aug 2026 13:12:20 -0700 Subject: [PATCH 4/4] docs(sync): say why each priority-inheritance branch acts as it does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #94 repairs the same spliced priority-inheritance code as this PR, and converged on the same design independently — g_blocked_on, task_valid, pi_recompute, pi_propagate, recomputing from the invariant rather than saving and restoring a value around each lock. The two implementations of sync.c differ by nothing except these two comments, which explain the consequence rather than only the action: "...it would sleep forever." -> "...it would sleep forever. Refusing also avoids boosting the owner on behalf of a caller that never waits." "Timeout — leave the queue and drop the boost we were causing." -> "...so a waiter that gave up no longer keeps the owner running elevated." Both name the priority-inheritance effect of the branch, which is the part a reader is likely to get wrong when editing it later. Comment-only; sync.c is otherwise byte-identical to #94's. 28/28 tests passed, 0 build errors Co-authored-by: prakhar7017 Co-Authored-By: Claude Opus 5 (1M context) --- kernel/src/sync.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/src/sync.c b/kernel/src/sync.c index 14dd98c2..4d1868ca 100644 --- a/kernel/src/sync.c +++ b/kernel/src/sync.c @@ -171,7 +171,8 @@ int eos_mutex_lock(eos_mutex_handle_t h, uint32_t timeout_ms) } /* Refuse rather than block: a task that is not enqueued is never granted - * the mutex, so with EOS_WAIT_FOREVER it would sleep forever. */ + * the mutex, so with EOS_WAIT_FOREVER it would sleep forever. Refusing + * also avoids boosting the owner on behalf of a caller that never waits. */ if (g_mtx[h].waiter_count >= MTX_MAX_WAITERS) { eos_port_exit_critical(crit); return EOS_KERN_NO_MEMORY; @@ -197,7 +198,8 @@ int eos_mutex_lock(eos_mutex_handle_t h, uint32_t timeout_ms) return EOS_KERN_OK; } - /* Timeout — leave the queue and drop the boost we were causing. */ + /* Timeout — leave the queue and drop the boost we were causing, so a + * waiter that gave up no longer keeps the owner running elevated. */ uint8_t owner = g_mtx[h].owner; mtx_remove_waiter(&g_mtx[h], caller); pi_propagate(owner);