Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c0777f4
feat: add sleep primitive and update main to test it
LuernOutOfOrder Feb 5, 2026
5e7a747
refactor: move sleep from primitive to task
LuernOutOfOrder Feb 5, 2026
cdeb511
feat: update the task structure to add a TaskBlockControl field, upda…
LuernOutOfOrder Feb 5, 2026
532b9cc
feat: add a new task primitive task_block_until and move some logic f…
LuernOutOfOrder Feb 5, 2026
e57c6c1
fix: trap_entry override hart id with trap frame
LuernOutOfOrder Feb 5, 2026
1082b92
feat: add a new task primitive task_awake_blocked and use it in timer…
LuernOutOfOrder Feb 5, 2026
28c894d
chore: format files
LuernOutOfOrder Feb 5, 2026
79da14f
feat: add the delay task primitive, improve the task_awake primitive …
LuernOutOfOrder Feb 6, 2026
33a83ea
chore: remove unused import
LuernOutOfOrder Feb 6, 2026
bddcad7
refactor: move the task sleep primitive function from sleep module to…
LuernOutOfOrder Feb 6, 2026
5e84006
docs(kernel): add a basic primitives documentation
LuernOutOfOrder Feb 6, 2026
0dc1c11
docs(kernel): add table of content to the primitives docs
LuernOutOfOrder Feb 6, 2026
cdb84ae
feat(tests): add beginning of task primitives test
LuernOutOfOrder Feb 6, 2026
5f2b81b
feat(linkers): update test mode linker ROM length to compile
LuernOutOfOrder Feb 9, 2026
ca2ebdb
feat(tests): add sleep test
LuernOutOfOrder Feb 9, 2026
1e45b48
chore: format file
LuernOutOfOrder Feb 9, 2026
7699c43
feat: remove init_trap_frame from a test, and comment the yield test …
LuernOutOfOrder Feb 9, 2026
48f3382
feat(tests): improve sleep test
LuernOutOfOrder Feb 9, 2026
f841d2a
feat(docs): update the primitives documentation and add data_structur…
LuernOutOfOrder Feb 9, 2026
50ac62e
feat: add file info header to primitives task file and update task sl…
LuernOutOfOrder Feb 9, 2026
3cb7357
refactor: remove debug implementation to RingBuffer type
LuernOutOfOrder Feb 9, 2026
cd23a58
chore: bump kernel version to 0.4.3
LuernOutOfOrder Feb 9, 2026
7961edb
chore: format and remove warning
LuernOutOfOrder Feb 9, 2026
231299a
refactor: remove no_mangle use on test_task_primitives_delay
LuernOutOfOrder Feb 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[package]
name = "lrnrtos"
version = "0.4.2"
version = "0.4.3"
edition = "2024"

[[bin]]
name= "lrnrtos"
name = "lrnrtos"
test = false

[dependencies]
arrayvec = {version = "0.7.6", default-features = false}
arrayvec = { version = "0.7.6", default-features = false }

[features]
default = ["logs", "kprint"]
Expand Down
39 changes: 39 additions & 0 deletions Documentation/kernel/data_structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Kernel data structure

<!--toc:start-->
- [Kernel data structure](#kernel-data-structure)
- [Description](#description)
- [RingBuffer](#ringbuffer)
- [Invariants](#invariants)
- [AlignedStack16](#alignedstack16)
- [Invariants](#invariants-1)
<!--toc:end-->

## Description

The kernel as multiple data structure implementation inside the codebase, they are useful to store and manipulate data inside the kernel.
These are all the data structure implemented inside the kernel.

### RingBuffer

A simple Ring buffer, used as an FIFO. If the RingBuffer is full and you try to push anyways, it will be abort.
It's like a close RingBuffer, you can't push if it's full and you don't pop before.

#### Invariants

- Length is always in [0, capacity].
- The length is len - 1; there's always an empty slot in the array.
- Head and tail always remain within the backing array bounds.
- Push is only valid when the buffer is not full; violating this is a logic error (abort).
- Pop is only valid when the buffer is not empty; violating this is a logic error (abort).

### AlignedStack16

This is just a structure wrapping a buffer of bytes, but the structure use the `#[repr(align(16))]`.
This type is used when you need a stack on a buffer, and the `sp` must be aligned on `16 bytes`.

#### Invariants

- The backing storage is always 16-byte aligned.
- Any stack pointer derived from this type must remain 16-byte aligned at all call boundaries.
- This type provides a memory-layout guarantee only; it does not validate stack usage correctness.
78 changes: 78 additions & 0 deletions Documentation/kernel/primitives.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Kernel primitives types and functions

<!--toc:start-->
- [Kernel primitives types and functions](#kernel-primitives-types-and-functions)
- [Description](#description)
- [Primitive type](#primitive-type)
- [Description](#description-1)
- [Task primitive](#task-primitive)
- [Description](#description-2)
- [yield](#yield)
- [sleep](#sleep)
- [task_awake_blocked](#taskawakeblocked)
- [Invariants](#invariants)
<!--toc:end-->

## Description

This document describes the kernel primitives.

In the context of this kernel, a *primitive* is defined as a low-level construct that
**directly affects kernel state or execution context**.
If using a type or function can change global kernel behavior, scheduling state,
or execution flow, it is considered a primitive.

Two categories of primitives are documented here:

- **Primitive types**: low-level types whose correct usage is required to preserve
kernel invariants. These types are not mere data structures; they encode execution,
synchronization, or memory-layout guarantees that the kernel relies on.
Examples include synchronization objects such as mutexes or types enforcing
strict alignment or execution constraints.

- **Task primitives**: execution control operations that may only be used from
task context. These primitives modify the scheduling or blocking state of the
current task and therefore have observable effects on global kernel execution.

Pure data structures that do not alter kernel state or execution context are
documented separately and are not considered primitives, even if they are used
internally by primitive implementations.
You can find data structure type here: `Documentation/kernel/data_structure.md`.

### Primitive type

#### Description

There are currently no primitive type implemented in the kernel.

### Task primitive

#### Description

To handle task correctly, the kernel need some primitives but only used in a task context.
These type can't and must not be used anywhere else than inside a task.

#### yield

Used in cooperative scheduling, when a task use `yield`, it will save it's context, and call a re-schedule.
It is used when you want a task to let another task take the control.

#### sleep

Put the task using the `sleep` primitive function to sleep for the given time.
It blocked the task until the kernel `GLOBAL_TICK` is equal or superior to the current tick + the given tick.
You can consider the given tick as `1ms`.

#### task_awake_blocked

Awake the oldest blocked task if it can.
This primitive is called from a timer interrupt, and only from a timer interrupt.
The timer interrupt will give the primitive `task_awake_blocked` the current `GLOBAL_TICK`, after updating it from the current interrupt.
The primitive will get the `oldest blocked task`, from the `BLOCKED_QUEUE`, then it'll check the reason why this task is blocked, and awake it if possible.

#### Invariants

- Task primitives must only be called from task context.
- The scheduler must be initialized before any task primitive is used.
- Time-based primitives rely on a functional timer subsystem.
- Principal data structure such as `RUN_QUEUE` and `BLOCKED_QUEUE` must be initialized before any task primitive is used.
22 changes: 22 additions & 0 deletions Documentation/kernel/timing_helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Kernel timing helpers

<!--toc:start-->
- [Kernel timing helpers](#kernel-timing-helpers)
- [Description](#description)
- [delay](#delay)
- [Invariants](#invariants)
<!--toc:end-->

## Description

The kernel sometimes need to use some helpers to handle or manage timing. Here's a list of some helpers to help with that.

### delay

Block the CPU for the given time, in `ms`.
This is not really recommended to use, it will not put the CPU to sleep, just waiting for the next timer interrupt.
If you need a task to wait or something else, prefer the use of `yield`.

### Invariants

- The scheduler must be initialized before any timing helpers is used.
2 changes: 1 addition & 1 deletion linkers/linker_test_mode.ld
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ENTRY(kstart)
MEMORY {
RAM (rwx) : ORIGIN = 0x80200000, LENGTH = 128K
/* Not real ROM or flash from qemu virt machine, just use another RAM reg for now */
ROM (rx) : ORIGIN = 0x80000000, LENGTH = 200K
ROM (rx) : ORIGIN = 0x80000000, LENGTH = 210K
}

SECTIONS {
Expand Down
2 changes: 2 additions & 0 deletions src/arch/riscv32/asm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ global_asm! {
include_str!("set_kernel_sp.S"),
// Yield function for context switch and scheduling
include_str!("yield.S"),
// Sleep function for task and scheduling
include_str!("sleep.S"),
// Scheduler context switch
include_str!("sched_context.S"),
// All task context offsets
Expand Down
18 changes: 18 additions & 0 deletions src/arch/riscv32/asm/sleep.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.global sleep
.type sleep, @function
sleep:
# Move tick to another reg, a0 is used in save_context
mv t5, a0
# Get current task ptr
la t0, TASK_HANDLER # Address in RAM
lw t1, 0(t0) # Get the value behind the ref
mv a0, t1
# Save current ra
mv a1, ra
# Save current sp
mv a2, sp
# Call the save context function
call save_context
# Once save_context return, call the task_set_wake_tick fn
mv a0, t5
call task_set_wake_tick
4 changes: 2 additions & 2 deletions src/arch/riscv32/asm/trap_entry.S
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ trap_entry:
csrr a2, mcause
csrr a3, mhartid
csrr a4, mstatus
# Move trap frame structure into a3 function argument register
mv a3, t6
# Move trap frame structure into a5 function argument register
mv a5, t6
# Call trap_handler rust function
call trap_handler
# Load all register back to previous state
Expand Down
2 changes: 0 additions & 2 deletions src/arch/riscv32/task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ pub mod task_context;

// Asm function for task context switch
unsafe extern "C" {
// Yield function for cooperative scheduling
pub fn r#yield();
// Restore the task context
pub fn restore_context(context: usize);
// Save the current task context
Expand Down
8 changes: 7 additions & 1 deletion src/arch/riscv32/traps/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ Tests files:

use crate::{
config::TICK_DURATION,
ktime::{set_ktime_ms, tick::increment_tick},
ktime::{
set_ktime_ms,
tick::{get_tick, increment_tick},
},
task::primitives::task_awake_blocked,
};

use super::trap_frame::TrapFrame;
Expand Down Expand Up @@ -90,5 +94,7 @@ fn timer_interrupt(hart: usize) {
if hart == 0 {
increment_tick();
}
let tick = get_tick();
task_awake_blocked(tick);
set_ktime_ms(TICK_DURATION);
}
2 changes: 1 addition & 1 deletion src/info.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// The kernel exposes build-time version information at runtime.
// This information is immutable and reflects the exact binary currently running.
// It is intended for debugging, logging, and diagnostic purposes.
pub static KERNEL_VERSION: &str = "0.4.2";
pub static KERNEL_VERSION: &str = "0.4.3";
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ use primitives::ring_buff::RingBuffer;

// Static buffer to use as a ready queue for task.
pub static mut BUFFER: RingBuffer<u16, 3> = RingBuffer::init();
// Queue containing all blocked task.
pub static mut BLOCKED_QUEUE: RingBuffer<u16, 3> = RingBuffer::init();

#[unsafe(no_mangle)]
unsafe extern "C" fn main() -> ! {
Expand Down
3 changes: 1 addition & 2 deletions src/primitives/ring_buff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ References:

use crate::{log, logs::LogLevel};

#[derive(Debug)]
pub struct RingBuffer<T, const N: usize> {
buff: [Option<T>; N],
// Oldest element in the buffer
Expand All @@ -34,7 +33,7 @@ pub struct RingBuffer<T, const N: usize> {
count: usize,
}

impl<T: Copy + core::fmt::Debug, const N: usize> RingBuffer<T, N> {
impl<T: Copy, const N: usize> RingBuffer<T, N> {
pub const fn init() -> Self {
RingBuffer {
buff: [None; N],
Expand Down
16 changes: 9 additions & 7 deletions src/scheduler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ use crate::{
pub fn dispatch() {
// Current running task
let mut current_task = unsafe { *TASK_HANDLER };
current_task.state = TaskState::Ready;
let pid = task_pid(&current_task);
task_list_update_task_by_pid(pid, current_task);
#[allow(static_mut_refs)]
unsafe {
BUFFER.push(pid)
};
if current_task.state != TaskState::Blocked {
current_task.state = TaskState::Ready;
let pid = task_pid(&current_task);
task_list_update_task_by_pid(pid, current_task);
#[allow(static_mut_refs)]
unsafe {
BUFFER.push(pid)
};
}
// Update and load next task
#[allow(static_mut_refs)]
let get_next_task = unsafe { BUFFER.pop() };
Expand Down
13 changes: 12 additions & 1 deletion src/task/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use list::task_list_add_task;
use crate::{arch::task::task_context::TaskContext, log, logs::LogLevel, mem::mem_task_alloc};

pub mod list;
pub mod primitives;

// Mutable static to keep track of the current task
// Only relevant on a monocore CPU.
Expand All @@ -36,21 +37,30 @@ pub static mut TASK_HANDLER: *mut Task = core::ptr::null_mut();
#[repr(u8)]
// Allow unused for now because this issue doesn't need to handle all task state
#[allow(unused)]
#[derive(Copy, Clone)]
#[derive(Copy, Clone, PartialEq)]
pub enum TaskState {
New,
Running,
Ready,
Waiting,
Blocked,
Terminated,
}

#[derive(Copy, Clone)]
pub enum TaskBlockControl {
AwakeTick(usize),
None,
}

#[derive(Copy, Clone)]
#[repr(C)]
pub struct Task {
// Arch dependant context, don't handle this field in task, only use struct method when
// interacting with it.
pub context: TaskContext,
// Task block control, define the reason the task is blocked.
pub block_control: TaskBlockControl,
// Fn ptr to task entry point, this must never return.
pub func: fn() -> !,
pid: u16,
Expand Down Expand Up @@ -89,6 +99,7 @@ impl Task {
mem_reg.expect("Error: failed to get the task memory region"),
func,
),
block_control: TaskBlockControl::None,
func,
pid: 0,
name: buf,
Expand Down
Loading