diff --git a/Documentation/hal-calling-convention.md b/Documentation/hal-calling-convention.md index 6df5017f..00d68a52 100644 --- a/Documentation/hal-calling-convention.md +++ b/Documentation/hal-calling-convention.md @@ -141,9 +141,9 @@ This "red zone" is reserved at the top of every task stack to guarantee ISR safe Standard RISC-V calling convention applies: ```c -/* Example: mo_task_spawn(entry, stack_size) */ -/* a0 = entry, a1 = stack_size, return value in a0 */ -int32_t result = mo_task_spawn(task_function, 2048); +/* Example: mo_task_spawn(entry, stack_size, mode) */ +/* a0 = entry, a1 = stack_size, a2 = mode, return value in a0 */ +int32_t result = mo_task_spawn(task_function, 2048, TASK_MODE_M); ``` ### System Call Interface diff --git a/Makefile b/Makefile index 4bb9103c..a4bf6da1 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,17 @@ BUILD_APP_DIR := $(BUILD_DIR)/app BUILD_KERNEL_DIR := $(BUILD_DIR)/kernel BUILD_LIB_DIR := $(BUILD_DIR)/lib +# Apps requiring M-mode (kernel API tests) +# All other apps run in U-mode by default (secure) +MMODE_APPS := cond coop cpubench echo hello mqueues mutex \ + pipes pipes_small pipes_struct prodcons progress \ + rtsched semaphore suspend test64 test_libc timer timer_kill + +# Auto-detect: if building an M-mode app, enable CONFIG_PRIVILEGED +ifneq ($(filter $(MAKECMDGOALS),$(MMODE_APPS)),) +CONFIG_PRIVILEGED := 1 +endif + include mk/common.mk # architecture-specific settings include arch/$(ARCH)/build.mk @@ -97,7 +108,8 @@ $(BUILD_DIR)/code.txt: $(IMAGE_BASE).bin # Utility targets rebuild: - $(Q)find '$(BUILD_APP_DIR)' -type f -name '*.o' -delete 2>/dev/null || true + $(Q)find '$(BUILD_APP_DIR)' '$(BUILD_KERNEL_DIR)' -type f -name '*.o' -delete 2>/dev/null || true + $(Q)rm -f '$(BUILD_DIR)/liblinmo.a' 2>/dev/null || true $(Q)mkdir -p $(BUILD_APP_DIR) $(BUILD_KERNEL_DIR) $(BUILD_LIB_DIR) clean: diff --git a/app/cond.c b/app/cond.c index ace81ef0..7b63f997 100644 --- a/app/cond.c +++ b/app/cond.c @@ -110,10 +110,10 @@ int32_t app_main(void) mo_mutex_init(&m); mo_cond_init(&cv); - mo_task_spawn(producer, DEFAULT_STACK_SIZE); - mo_task_spawn(consumer, DEFAULT_STACK_SIZE); - mo_task_spawn(mutex_tester, DEFAULT_STACK_SIZE); - mo_task_spawn(idle_task, DEFAULT_STACK_SIZE); + mo_task_spawn(producer, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(consumer, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(mutex_tester, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(idle_task, DEFAULT_STACK_SIZE, TASK_MODE_M); /* preemptive mode */ return 1; diff --git a/app/coop.c b/app/coop.c index 7921d82c..c1f8b8ce 100644 --- a/app/coop.c +++ b/app/coop.c @@ -34,9 +34,9 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE); - mo_task_spawn(task1, DEFAULT_STACK_SIZE); - mo_task_spawn(task2, DEFAULT_STACK_SIZE); + mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); /* cooperative mode */ return 0; diff --git a/app/cpubench.c b/app/cpubench.c index 6330d7e5..83342261 100644 --- a/app/cpubench.c +++ b/app/cpubench.c @@ -62,6 +62,6 @@ int32_t app_main(void) printf("Result: a=%d, b=%d, c=%d\n", a, b, c); printf("Elapsed time: %lu.%03lus\n", elapsed / 1000, elapsed % 1000); - mo_task_spawn(idle, DEFAULT_STACK_SIZE); + mo_task_spawn(idle, DEFAULT_STACK_SIZE, TASK_MODE_M); return 1; } diff --git a/app/echo.c b/app/echo.c index e5e9db9a..9d1aa50e 100644 --- a/app/echo.c +++ b/app/echo.c @@ -55,8 +55,8 @@ int32_t app_main(void) { pipe = mo_pipe_create(PIPE_CAP); - mo_task_spawn(task0, DEFAULT_STACK_SIZE); - mo_task_spawn(task1, DEFAULT_STACK_SIZE); + mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); /* preemptive mode */ return 1; diff --git a/app/hello.c b/app/hello.c index d6e785be..7739b9d3 100644 --- a/app/hello.c +++ b/app/hello.c @@ -37,9 +37,9 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE); - mo_task_spawn(task1, DEFAULT_STACK_SIZE); - mo_task_spawn(task2, DEFAULT_STACK_SIZE); + mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); mo_task_priority(2, TASK_PRIO_LOW); diff --git a/app/mqueues.c b/app/mqueues.c index 5816a1ce..5dea26e4 100644 --- a/app/mqueues.c +++ b/app/mqueues.c @@ -162,11 +162,11 @@ int32_t app_main(void) /* Spawn all the tasks. The idle task (task 0) is typically spawned first * to ensure it's available when other tasks yield or block. */ - mo_task_spawn(idle, DEFAULT_STACK_SIZE); - mo_task_spawn(task1, DEFAULT_STACK_SIZE); - mo_task_spawn(task2, DEFAULT_STACK_SIZE); - mo_task_spawn(task3, DEFAULT_STACK_SIZE); - mo_task_spawn(task4, DEFAULT_STACK_SIZE); + mo_task_spawn(idle, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task3, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task4, DEFAULT_STACK_SIZE, TASK_MODE_M); /* Create the message queues with a capacity of 8 items each. */ mq1 = mo_mq_create(8); /* Queue for signaling task1. */ diff --git a/app/mutex.c b/app/mutex.c index c332687d..6550027e 100644 --- a/app/mutex.c +++ b/app/mutex.c @@ -191,10 +191,10 @@ int32_t app_main(void) printf("Binary semaphore created successfully\n"); /* Create tasks */ - int32_t task_a_id = mo_task_spawn(task_a, 1024); - int32_t task_b_id = mo_task_spawn(task_b, 1024); - int32_t monitor_id = mo_task_spawn(monitor_task, 1024); - int32_t idle_id = mo_task_spawn(idle_task, 512); + int32_t task_a_id = mo_task_spawn(task_a, 1024, TASK_MODE_M); + int32_t task_b_id = mo_task_spawn(task_b, 1024, TASK_MODE_M); + int32_t monitor_id = mo_task_spawn(monitor_task, 1024, TASK_MODE_M); + int32_t idle_id = mo_task_spawn(idle_task, 512, TASK_MODE_M); if (task_a_id < 0 || task_b_id < 0 || monitor_id < 0 || idle_id < 0) { printf("FATAL: Failed to create tasks\n"); diff --git a/app/pipes.c b/app/pipes.c index 242bda9b..3bd35eb7 100644 --- a/app/pipes.c +++ b/app/pipes.c @@ -57,10 +57,10 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE); - mo_task_spawn(task1, DEFAULT_STACK_SIZE); - mo_task_spawn(task2, DEFAULT_STACK_SIZE); - mo_task_spawn(task3, DEFAULT_STACK_SIZE); + mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task3, DEFAULT_STACK_SIZE, TASK_MODE_M); pipe1 = mo_pipe_create( 128); /* pipe buffer, 128 bytes (allocated on the heap) */ diff --git a/app/pipes_small.c b/app/pipes_small.c index a8cdea70..512829e5 100644 --- a/app/pipes_small.c +++ b/app/pipes_small.c @@ -42,9 +42,9 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE); - mo_task_spawn(task1, DEFAULT_STACK_SIZE); - mo_task_spawn(task2, DEFAULT_STACK_SIZE); + mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); pipe1 = mo_pipe_create( 64); /* pipe buffer, 64 bytes (allocated from the heap) */ diff --git a/app/pipes_struct.c b/app/pipes_struct.c index 645ae91f..5d04ee15 100644 --- a/app/pipes_struct.c +++ b/app/pipes_struct.c @@ -44,8 +44,8 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE); - mo_task_spawn(task1, DEFAULT_STACK_SIZE); + mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); pipe1 = mo_pipe_create(64); /* pipe buffer, 64 bytes */ diff --git a/app/prodcons.c b/app/prodcons.c index f590b95a..3a8bd946 100644 --- a/app/prodcons.c +++ b/app/prodcons.c @@ -38,9 +38,9 @@ static void consumer(void) int32_t app_main(void) { - mo_task_spawn(producer, DEFAULT_STACK_SIZE); - mo_task_spawn(consumer, DEFAULT_STACK_SIZE); - mo_task_spawn(consumer, DEFAULT_STACK_SIZE); + mo_task_spawn(producer, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(consumer, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(consumer, DEFAULT_STACK_SIZE, TASK_MODE_M); empty = mo_sem_create(3, N); full = mo_sem_create(3, 0); diff --git a/app/progress.c b/app/progress.c index 57ea1775..9565eeff 100644 --- a/app/progress.c +++ b/app/progress.c @@ -36,9 +36,9 @@ int32_t app_main(void) int32_t i; for (i = 0; i < N_TASKS; i++) - mo_task_spawn(task, DEFAULT_STACK_SIZE); + mo_task_spawn(task, DEFAULT_STACK_SIZE, TASK_MODE_M); // add logger task - mo_task_spawn(logger, DEFAULT_STACK_SIZE); + mo_task_spawn(logger, DEFAULT_STACK_SIZE, TASK_MODE_M); /* preemptive mode */ return 1; diff --git a/app/rtsched.c b/app/rtsched.c index 56402814..25c8c7f1 100644 --- a/app/rtsched.c +++ b/app/rtsched.c @@ -452,17 +452,18 @@ int32_t app_main(void) /* test_start_time will be initialized by first task that runs */ /* Spawn all 5 RT/background tasks first */ - int32_t tid0 = mo_task_spawn(task0, DEFAULT_STACK_SIZE); - int32_t tid1 = mo_task_spawn(task1, DEFAULT_STACK_SIZE); - int32_t tid2 = mo_task_spawn(task2, DEFAULT_STACK_SIZE); - (void) mo_task_spawn(task3, DEFAULT_STACK_SIZE); /* Non-RT task 3 */ + int32_t tid0 = mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); + int32_t tid1 = mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + int32_t tid2 = mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); + (void) mo_task_spawn(task3, DEFAULT_STACK_SIZE, + TASK_MODE_M); /* Non-RT task 3 */ /* Non-RT task 4 - displays stats */ - (void) mo_task_spawn(task4, DEFAULT_STACK_SIZE); + (void) mo_task_spawn(task4, DEFAULT_STACK_SIZE, TASK_MODE_M); /* Spawn IDLE task LAST so it's at end of round-robin list. * This ensures other ready tasks get scheduled before IDLE. */ - (void) mo_task_spawn(idle_task, DEFAULT_STACK_SIZE); + (void) mo_task_spawn(idle_task, DEFAULT_STACK_SIZE, TASK_MODE_M); /* Configure EDF priorities for RT tasks 0-2 with deadlines relative to * current time */ diff --git a/app/suspend.c b/app/suspend.c index 653ef68f..6153bcd4 100644 --- a/app/suspend.c +++ b/app/suspend.c @@ -54,9 +54,9 @@ void task0(void) int32_t app_main(void) { - mo_task_spawn(task0, DEFAULT_STACK_SIZE); - mo_task_spawn(task1, DEFAULT_STACK_SIZE); - mo_task_spawn(task2, DEFAULT_STACK_SIZE); + mo_task_spawn(task0, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(task2, DEFAULT_STACK_SIZE, TASK_MODE_M); /* preemptive mode */ return 1; diff --git a/app/test64.c b/app/test64.c index 6ed98aa8..9c6d4b3b 100644 --- a/app/test64.c +++ b/app/test64.c @@ -86,7 +86,7 @@ void task(void) int32_t app_main() { - mo_task_spawn(task, DEFAULT_STACK_SIZE); + mo_task_spawn(task, DEFAULT_STACK_SIZE, TASK_MODE_M); return 1; } diff --git a/app/timer.c b/app/timer.c index 19f53466..9db0e520 100644 --- a/app/timer.c +++ b/app/timer.c @@ -63,7 +63,7 @@ int32_t app_main(void) mo_timer_start(0x6002, TIMER_AUTORELOAD); /* Spawn a single idle task to keep the kernel running. */ - mo_task_spawn(idle_task, DEFAULT_STACK_SIZE); + mo_task_spawn(idle_task, DEFAULT_STACK_SIZE, TASK_MODE_M); /* preemptive mode */ return 1; diff --git a/app/timer_kill.c b/app/timer_kill.c index 3ab3c837..c1f9c82e 100644 --- a/app/timer_kill.c +++ b/app/timer_kill.c @@ -38,10 +38,10 @@ void idle(void) int32_t app_main(void) { - mo_task_spawn(timer1, DEFAULT_STACK_SIZE); - mo_task_spawn(timer2, DEFAULT_STACK_SIZE); - mo_task_spawn(timer3, DEFAULT_STACK_SIZE); - mo_task_spawn(idle, DEFAULT_STACK_SIZE); + mo_task_spawn(timer1, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(timer2, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(timer3, DEFAULT_STACK_SIZE, TASK_MODE_M); + mo_task_spawn(idle, DEFAULT_STACK_SIZE, TASK_MODE_M); /* preemptive mode */ return 1; diff --git a/app/umode.c b/app/umode.c index 518e111d..7b7c54d4 100644 --- a/app/umode.c +++ b/app/umode.c @@ -64,11 +64,10 @@ int32_t app_main(void) { umode_printf("[Kernel] Spawning U-mode validation task...\n"); - /* app_main is called from kernel context during bootstrap. - * Use mo_task_spawn_user to create the validation task in user mode. - * This ensures privilege isolation is properly tested. + /* app_main now runs in U-mode by default. + * Use sys_tadd syscall to create additional U-mode tasks. */ - mo_task_spawn_user(umode_validation_task, DEFAULT_STACK_SIZE); + sys_tadd(umode_validation_task, DEFAULT_STACK_SIZE); /* Return 1 to enable preemptive scheduler */ return 1; diff --git a/arch/riscv/build.mk b/arch/riscv/build.mk index 243a6ea2..a2cd1e7b 100644 --- a/arch/riscv/build.mk +++ b/arch/riscv/build.mk @@ -17,6 +17,11 @@ DEFINES := -DF_CPU=$(F_CLK) \ -DF_TIMER=$(F_TICK) \ -include config.h +# Privileged mode: app_main() runs in M-mode instead of default U-mode +ifdef CONFIG_PRIVILEGED +DEFINES += -DCONFIG_PRIVILEGED=1 +endif + CROSS_COMPILE ?= riscv-none-elf- # Detect LLVM/Clang toolchain diff --git a/include/sys/task.h b/include/sys/task.h index ccf5f4fa..4e030e69 100644 --- a/include/sys/task.h +++ b/include/sys/task.h @@ -44,6 +44,12 @@ enum task_states { TASK_SUSPENDED /* Task paused/excluded from scheduling until resumed */ }; +/* Task Privilege Mode */ +typedef enum { + TASK_MODE_M, /* Machine mode - full hardware access */ + TASK_MODE_U /* User mode - restricted, uses syscalls */ +} task_mode_t; + /* Priority Level Constants for Priority-Aware Time Slicing */ #define TASK_PRIORITY_LEVELS 8 /* Number of priority levels (0-7) */ #define TASK_HIGHEST_PRIORITY 0 /* Highest priority level */ @@ -187,24 +193,14 @@ void _yield(void); /* Task Lifecycle Management */ -/* Creates and starts a new task in machine mode. - * @task_entry : Pointer to the task's entry function (void func(void)) - * @stack_size : The desired stack size in bytes (minimum is enforced) - * - * Returns the new task's ID on success. Panics on memory allocation failure. - */ -int32_t mo_task_spawn(void *task_entry, uint16_t stack_size); - -/* Creates and starts a new task in user mode. - * User mode tasks run with reduced privileges and must use syscalls to access - * kernel services. This provides memory protection and privilege separation. - * +/* Creates and starts a new task. * @task_entry : Pointer to the task's entry function (void func(void)) * @stack_size : The desired stack size in bytes (minimum is enforced) + * @mode : Privilege mode (TASK_MODE_M or TASK_MODE_U) * * Returns the new task's ID on success. Panics on memory allocation failure. */ -int32_t mo_task_spawn_user(void *task_entry, uint16_t stack_size); +int32_t mo_task_spawn(void *task_entry, uint16_t stack_size, task_mode_t mode); /* Cancels and removes a task from the system. A task cannot cancel itself. * @id : The ID of the task to cancel diff --git a/kernel/logger.c b/kernel/logger.c index f2fe2733..cdb7a79b 100644 --- a/kernel/logger.c +++ b/kernel/logger.c @@ -82,7 +82,7 @@ int32_t mo_logger_init(void) return ERR_FAIL; /* 1024B stack: space for log_entry_t (130B) + ISR frame (128B) + calls */ - logger.task_id = mo_task_spawn(logger_task, 1024); + logger.task_id = mo_task_spawn(logger_task, 1024, TASK_MODE_M); if (logger.task_id < 0) { mo_mutex_destroy(&logger.lock); return ERR_FAIL; diff --git a/kernel/main.c b/kernel/main.c index 0015dca7..2b983187 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -1,10 +1,26 @@ #include #include #include +#include #include #include "private/error.h" +#ifndef CONFIG_PRIVILEGED +/* Wrapper to run app_main() in U-mode. + * + * In U-mode, the return value cannot control preemptive mode (kcb is + * inaccessible), so it's ignored. The infinite loop keeps the task alive + * after app_main() returns. + */ +static void app_main_wrapper(void) +{ + app_main(); + for (;;) + sys_tyield(); +} +#endif + /* C-level entry point for the kernel. * * This function is called from the boot code ('_entry'). It is responsible for @@ -42,8 +58,15 @@ int32_t main(void) printf("Logger initialized\n"); } - /* Call the application's main entry point to create initial tasks. */ +#ifdef CONFIG_PRIVILEGED + /* M-mode: app_main() controls preemptive mode via return value */ kcb->preemptive = (bool) app_main(); +#else + /* U-mode: preemptive mode fixed, app_main() runs as user task */ + kcb->preemptive = true; + mo_task_spawn(app_main_wrapper, DEFAULT_STACK_SIZE, TASK_MODE_U); +#endif + printf("Scheduler mode: %s\n", kcb->preemptive ? "Preemptive" : "Cooperative"); diff --git a/kernel/syscall.c b/kernel/syscall.c index 7be66632..af41f4c2 100644 --- a/kernel/syscall.c +++ b/kernel/syscall.c @@ -247,7 +247,8 @@ static int _tadd(void *task, int stack_size) if (unlikely(!task || stack_size <= 0)) return -EINVAL; - return mo_task_spawn(task, stack_size); + /* Syscall always creates U-mode tasks for security */ + return mo_task_spawn(task, stack_size, TASK_MODE_U); } int sys_tadd(void *task, int stack_size) diff --git a/kernel/task.c b/kernel/task.c index c9973e19..7c4fc4e8 100644 --- a/kernel/task.c +++ b/kernel/task.c @@ -712,10 +712,9 @@ static bool init_task_stack(tcb_t *tcb, size_t stack_size) /* Task Management API */ -/* Internal task spawning implementation with privilege mode control */ -static int32_t task_spawn_impl(void *task_entry, - uint16_t stack_size_req, - int user_mode) +int32_t mo_task_spawn(void *task_entry, + uint16_t stack_size_req, + task_mode_t mode) { if (!task_entry) panic(ERR_TCB_ALLOC); @@ -779,6 +778,7 @@ static int32_t task_spawn_impl(void *task_entry, CRITICAL_LEAVE(); /* Initialize execution context outside critical section. */ + int user_mode = (mode == TASK_MODE_U); hal_context_init(&tcb->context, (size_t) tcb->stack, new_stack_size, (size_t) task_entry, user_mode); @@ -799,16 +799,6 @@ static int32_t task_spawn_impl(void *task_entry, return tcb->id; } -int32_t mo_task_spawn(void *task_entry, uint16_t stack_size_req) -{ - return task_spawn_impl(task_entry, stack_size_req, false); -} - -int32_t mo_task_spawn_user(void *task_entry, uint16_t stack_size_req) -{ - return task_spawn_impl(task_entry, stack_size_req, true); -} - int32_t mo_task_cancel(uint16_t id) { if (id == 0 || id == mo_task_id())