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
6 changes: 3 additions & 3 deletions Documentation/hal-calling-convention.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions app/cond.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions app/coop.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion app/cpubench.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
4 changes: 2 additions & 2 deletions app/echo.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 3 additions & 3 deletions app/hello.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
10 changes: 5 additions & 5 deletions app/mqueues.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
8 changes: 4 additions & 4 deletions app/mutex.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
8 changes: 4 additions & 4 deletions app/pipes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) */
Expand Down
6 changes: 3 additions & 3 deletions app/pipes_small.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) */
Expand Down
4 changes: 2 additions & 2 deletions app/pipes_struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down
6 changes: 3 additions & 3 deletions app/prodcons.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions app/progress.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 7 additions & 6 deletions app/rtsched.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
6 changes: 3 additions & 3 deletions app/suspend.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion app/test64.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
2 changes: 1 addition & 1 deletion app/timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 4 additions & 4 deletions app/timer_kill.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 3 additions & 4 deletions app/umode.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions arch/riscv/build.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 9 additions & 13 deletions include/sys/task.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion kernel/logger.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
25 changes: 24 additions & 1 deletion kernel/main.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
#include <hal.h>
#include <lib/libc.h>
#include <sys/logger.h>
#include <sys/syscall.h>
#include <sys/task.h>

#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
Expand Down Expand Up @@ -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);
Copy link

@cubic-dev-ai cubic-dev-ai bot Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Spawning the wrapper in U-mode makes kcb->task_current non-NULL before app_main creates any tasks, so the later ERR_NO_TASKS check can no longer detect when app_main failed to spawn tasks. This can leave the system running only the wrapper’s yield loop instead of panicking as intended. Consider tracking task_count before/after app_main (or requiring at least one additional task) in the U-mode path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kernel/main.c, line 67:

<comment>Spawning the wrapper in U-mode makes `kcb->task_current` non-NULL before app_main creates any tasks, so the later ERR_NO_TASKS check can no longer detect when app_main failed to spawn tasks. This can leave the system running only the wrapper’s yield loop instead of panicking as intended. Consider tracking task_count before/after app_main (or requiring at least one additional task) in the U-mode path.</comment>

<file context>
@@ -42,8 +58,15 @@ int32_t main(void)
+#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
+
</file context>
Fix with Cubic

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In U-mode, app_main() runs as a user task itself via wrapper. The wrapper is the application. If app_main() doesn't create additional tasks, the system has one valid task running.

This differs from M-mode semantics by design:

  • M-mode: app_main() is a bootstrapper that creates tasks and returns
  • U-mode: app_main() is the application entry point running in user space

The ERR_NO_TASKS check ensures there's at least one schedulable task, which is satisfied by the wrapper.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback! I've saved this as a new learning to improve future reviews.

#endif

printf("Scheduler mode: %s\n",
kcb->preemptive ? "Preemptive" : "Cooperative");

Expand Down
3 changes: 2 additions & 1 deletion kernel/syscall.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading