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
25 changes: 22 additions & 3 deletions Documentation/hal-calling-convention.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,30 @@ 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, mode) */
/* a0 = entry, a1 = stack_size, a2 = mode, return value in a0 */
int32_t result = mo_task_spawn(task_function, 2048, TASK_MODE_M);
/* Application API - the only interface apps should use: */
int32_t result = mo_task_spawn(task_function, 2048);
```

The `mo_task_spawn()` macro routes automatically based on build configuration:
- `CONFIG_PRIVILEGED`: Direct kernel call → M-mode task
- Otherwise: Syscall (`sys_task_spawn()`) → U-mode task

**Internal Architecture** (kernel developers only):

The implementation uses two internal functions declared in `include/private/task.h`:
- `mo_task_spawn_kernel()`: Creates M-mode tasks (used by logger.c)
- `mo_task_spawn_user()`: Creates U-mode tasks (used by main.c, syscall.c)

These are not exposed to applications. The public header only exposes the
`mo_task_spawn()` macro, which provides the correct behavior transparently.

**Security Model**:
- `mo_task_spawn_kernel()`: Protected by defense-in-depth:
- Runtime check rejects calls from syscall context (returns -1)
- Hardware protection: CRITICAL_ENTER traps if called from U-mode
- `mo_task_spawn_user()`: No privilege restrictions (creates restricted tasks)
- `mo_task_spawn()`: Safe for applications - routes to appropriate implementation

### System Call Interface

Linmo provides system calls through the RISC-V trap mechanism for privilege
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, 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);
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);

/* 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, 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(task0, DEFAULT_STACK_SIZE);
mo_task_spawn(task1, DEFAULT_STACK_SIZE);
mo_task_spawn(task2, DEFAULT_STACK_SIZE);

/* 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, TASK_MODE_M);
mo_task_spawn(idle, DEFAULT_STACK_SIZE);
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, TASK_MODE_M);
mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M);
mo_task_spawn(task0, DEFAULT_STACK_SIZE);
mo_task_spawn(task1, DEFAULT_STACK_SIZE);

/* 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, 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(task0, DEFAULT_STACK_SIZE);
mo_task_spawn(task1, DEFAULT_STACK_SIZE);
mo_task_spawn(task2, DEFAULT_STACK_SIZE);

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, 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);
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);

/* 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, 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);
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);

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, 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(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);

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, 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(task0, DEFAULT_STACK_SIZE);
mo_task_spawn(task1, DEFAULT_STACK_SIZE);
mo_task_spawn(task2, DEFAULT_STACK_SIZE);

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, TASK_MODE_M);
mo_task_spawn(task1, DEFAULT_STACK_SIZE, TASK_MODE_M);
mo_task_spawn(task0, DEFAULT_STACK_SIZE);
mo_task_spawn(task1, DEFAULT_STACK_SIZE);

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, TASK_MODE_M);
mo_task_spawn(consumer, DEFAULT_STACK_SIZE, TASK_MODE_M);
mo_task_spawn(consumer, DEFAULT_STACK_SIZE, TASK_MODE_M);
mo_task_spawn(producer, DEFAULT_STACK_SIZE);
mo_task_spawn(consumer, DEFAULT_STACK_SIZE);
mo_task_spawn(consumer, DEFAULT_STACK_SIZE);

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, TASK_MODE_M);
mo_task_spawn(task, DEFAULT_STACK_SIZE);
// add logger task
mo_task_spawn(logger, DEFAULT_STACK_SIZE, TASK_MODE_M);
mo_task_spawn(logger, DEFAULT_STACK_SIZE);

/* preemptive mode */
return 1;
Expand Down
13 changes: 6 additions & 7 deletions app/rtsched.c
Original file line number Diff line number Diff line change
Expand Up @@ -452,18 +452,17 @@ 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, 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 */
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 */
/* Non-RT task 4 - displays stats */
(void) mo_task_spawn(task4, DEFAULT_STACK_SIZE, TASK_MODE_M);
(void) mo_task_spawn(task4, DEFAULT_STACK_SIZE);

/* 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, TASK_MODE_M);
(void) mo_task_spawn(idle_task, DEFAULT_STACK_SIZE);

/* 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, 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(task0, DEFAULT_STACK_SIZE);
mo_task_spawn(task1, DEFAULT_STACK_SIZE);
mo_task_spawn(task2, DEFAULT_STACK_SIZE);

/* 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, TASK_MODE_M);
mo_task_spawn(task, DEFAULT_STACK_SIZE);

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, TASK_MODE_M);
mo_task_spawn(idle_task, DEFAULT_STACK_SIZE);

/* 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, 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);
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);

/* preemptive mode */
return 1;
Expand Down
9 changes: 5 additions & 4 deletions app/umode.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ void umode_validation_task(void)
umode_printf("[umode] FAIL: sys_uptime() failed (ret=%d)\n", uptime);
}

/* Note: Skipping sys_tadd for now, as kernel user pointer checks might
* block function pointers in the .text segment, avoiding distraction.
/* Note: Skipping sys_task_spawn for now, as kernel user pointer checks
* might block function pointers in the .text segment, avoiding distraction.
*/

/* --- Phase 2: Security Check (Privileged Access) --- */
Expand Down Expand Up @@ -65,9 +65,10 @@ int32_t app_main(void)
umode_printf("[Kernel] Spawning U-mode validation task...\n");

/* app_main now runs in U-mode by default.
* Use sys_tadd syscall to create additional U-mode tasks.
* mo_task_spawn routes to sys_task_spawn syscall for U-mode apps,
* ensuring consistent API usage across the codebase.
*/
sys_tadd(umode_validation_task, DEFAULT_STACK_SIZE);
mo_task_spawn(umode_validation_task, DEFAULT_STACK_SIZE);

/* Return 1 to enable preemptive scheduler */
return 1;
Expand Down
5 changes: 5 additions & 0 deletions include/private/error.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#pragma once

/* Kernel-only marker: defined when compiling kernel code.
* Used by private headers to enforce kernel-only usage at compile time.
*/
#define __LINMO_KERNEL 1

/* Centralizes all error codes used throughout the kernel. Error codes use
* automatic enumeration starting from -16383 to avoid conflicts with POSIX
* errno values. Each subsystem has its own logical grouping for easier
Expand Down
44 changes: 44 additions & 0 deletions include/private/task.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#pragma once

/* Kernel-internal task management APIs.
*
* These functions are for kernel use only (logger, main, syscall handlers).
* Applications should use the mo_task_spawn() macro from <sys/task.h>.
*
* Note: This header must be included after <sys/task.h> or other headers
* that define int32_t and uint16_t (project uses arch-specific types).
*/

/* Include error.h to get __LINMO_KERNEL marker for compile-time guard */
#include "private/error.h"

/* Compile-time guard: reject non-kernel builds */
#ifndef __LINMO_KERNEL
#error \
"private/task.h is for kernel use only. Applications must use mo_task_spawn() from <sys/task.h>"
#endif

/* Creates and starts a new task in kernel (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, -1 on privilege violation,
* or panics on memory allocation failure.
*
* Security: Protected by defense-in-depth:
* - Runtime check rejects calls from syscall context (returns -1)
* - Hardware protection: read_csr(mstatus) traps immediately if called
* from U-mode (illegal instruction exception)
*/
int32_t mo_task_spawn_kernel(void *task_entry, uint16_t stack_size);

/* Creates and starts a new task in user 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.
*
* Used by kernel bootstrap (main.c) and syscall handlers.
* U-mode tasks run with restricted privileges and must use syscalls.
*/
int32_t mo_task_spawn_user(void *task_entry, uint16_t stack_size);
2 changes: 1 addition & 1 deletion include/sys/syscall.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
/* 21-31 reserved for future POSIX extensions */ \
\
/* Linmo-specific system calls (32+) */ \
_(tadd, 32, int, (void *task, int stack_sz)) \
_(task_spawn, 32, int, (void *task, int stack_sz)) \
_(tcancel, 33, int, (int id)) \
_(tyield, 34, int, (void) ) \
_(tdelay, 35, int, (int ticks)) \
Expand Down
20 changes: 16 additions & 4 deletions include/sys/task.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ typedef struct tcb {
uint16_t delay; /* Ticks remaining for task in TASK_BLOCKED state */
uint16_t id; /* Unique task ID, assigned by kernel upon creation */
uint8_t state; /* Current lifecycle state (e.g., TASK_READY) */
uint8_t flags; /* Task flags for future extensions (reserved) */
task_mode_t mode; /* Privilege mode: TASK_MODE_M or TASK_MODE_U */

/* Real-time Scheduling Support */
void *rt_prio; /* Opaque pointer for custom real-time scheduler hook */
Expand Down Expand Up @@ -193,14 +193,26 @@ void _yield(void);

/* Task Lifecycle Management */

/* Creates and starts a new task.
/* Application task creation macro.
* @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.
*
* Routes to the correct implementation based on build configuration:
* - CONFIG_PRIVILEGED: Direct kernel call (M-mode task creation)
* - Otherwise: Syscall wrapper (U-mode task creation, hardware enforced)
*/
int32_t mo_task_spawn(void *task_entry, uint16_t stack_size, task_mode_t mode);
#ifdef CONFIG_PRIVILEGED
/* Include private header for kernel-internal function (not exposed to apps) */
#include "private/task.h"
#define mo_task_spawn(task_entry, stack_size) \
mo_task_spawn_kernel((task_entry), (stack_size))
#else
int sys_task_spawn(void *task, int stack_size);
#define mo_task_spawn(task_entry, stack_size) \
sys_task_spawn((task_entry), (stack_size))
#endif

/* Cancels and removes a task from the system. A task cannot cancel itself.
* @id : The ID of the task to cancel
Expand Down
Loading